It said library and meant collection. Its dropdown was always empty, because the only page that renders this preview passes no collections — so it read "Choose collection…" with nothing under it. And the "New library…" input called setCollections, which does not exist in that component: typing a name and pressing Enter created the collection, added the question to it, and then threw a ReferenceError. Twelve lines, none of which worked as written. Removed rather than repaired. This preview is a moderator's editing surface; putting a question aside is a learner's act and already lives in the session and reading flows, where the collections are actually loaded. While there, the same word elsewhere: the Collections page called its own contents "the question libraries you keep", which is the mislabel the user spotted, on the page named after the other word. A library here is the article library or an image library; a collection is what a learner puts aside. Found by the ped-ai session; the ReferenceError is mine to have missed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
123 lines
6.7 KiB
JavaScript
123 lines
6.7 KiB
JavaScript
import { Suspense } from 'react'
|
|
import lazyPage from '../utils/lazyPage'
|
|
import { Link } from 'react-router-dom'
|
|
import RichText from './RichText'
|
|
import QuestionReadingLinks from './QuestionReadingLinks'
|
|
import { uploadUrl } from '../utils/uploads'
|
|
|
|
const TeachChat = lazyPage(() => import('./TeachChat'))
|
|
|
|
/**
|
|
* One question, shown whole, without leaving the page you found it on.
|
|
*
|
|
* It is a preview, not practice: the correct option, the per-option reasoning,
|
|
* the explanation and the key points are all shown at once. Making someone
|
|
* answer first is the right shape for a quiz and the wrong one for a list of
|
|
* questions being inspected.
|
|
*
|
|
* Favourites and personal folders are optional and off by default — those
|
|
* belong to studying a question, not to looking through the bank.
|
|
*/
|
|
/* No "Add to library" here any more. It was three faults in one twelve-line
|
|
block: it said library and meant collection; its dropdown was always empty,
|
|
because the only page that renders this preview passes no collections; and
|
|
the "New library…" input called setCollections, which does not exist in this
|
|
component — so typing a name and pressing Enter created the collection,
|
|
added the question, and then threw a ReferenceError. Putting a question
|
|
aside belongs to the reading and session flows, where the collections are
|
|
actually loaded; this is a moderator's editing preview. */
|
|
export default function QuestionPreview({ question, onClose, isFavorited, onToggleFavorite }) {
|
|
return (
|
|
<>
|
|
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }}
|
|
onClick={e => e.target === e.currentTarget && onClose()}>
|
|
<div style={{ background: 'var(--card-bg)', borderRadius: 'var(--card-radius)', padding: 24, maxWidth: 600, width: '100%', maxHeight: '90vh', overflowY: 'auto', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
|
<span style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase' }}>
|
|
{question.quiz_title}{question.question_category_name ? ` · ${question.question_category_name}` : ''}
|
|
</span>
|
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
{onToggleFavorite && (
|
|
<button onClick={() => onToggleFavorite(question.id)}
|
|
title={isFavorited ? 'Remove from favorites' : 'Add to favorites'}
|
|
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: '1.4rem', padding: 2, lineHeight: 1 }}>
|
|
{isFavorited ? '⭐' : '☆'}
|
|
</button>
|
|
)}
|
|
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: '1.2rem' }}>✕</button>
|
|
</div>
|
|
</div>
|
|
{/* Markdown, not injected HTML: a stem is educator prose, and the
|
|
same renderer the quiz player uses keeps a lab table a table. */}
|
|
<div style={{ fontWeight: 600, fontSize: '0.95rem', marginBottom: 16 }}>
|
|
<RichText value={question.question_text} />
|
|
</div>
|
|
{question.image_path && (
|
|
<div style={{ margin: '0 0 14px' }}>
|
|
<img src={`/uploads/${question.image_path}`} alt="Question illustration"
|
|
style={{ maxWidth: '100%', maxHeight: 280, borderRadius: 8, border: '1px solid var(--border)' }}
|
|
onError={e => e.target.style.display = 'none'} />
|
|
</div>
|
|
)}
|
|
{question.options && (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }}>
|
|
{question.options.map((opt, i) => {
|
|
const isCorrectOpt = opt === question.correct_answer
|
|
return (
|
|
<div key={i}
|
|
className={`option ${isCorrectOpt ? 'correct' : ''}`}>
|
|
<span className="option-letter">{String.fromCharCode(65 + i)}</span>
|
|
<span style={{ flex: 1 }}>{opt}</span>
|
|
{isCorrectOpt && <span style={{ marginLeft: 'auto', fontSize: '0.8rem', fontWeight: 700, color: 'var(--correct-fg)' }}>✓ Correct</span>}
|
|
{question.option_explanations?.[opt] && (
|
|
<span style={{ flexBasis: '100%', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
|
|
<RichText value={question.option_explanations[opt]} />
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
{(question.explanation || question.explanation_image_path) && (
|
|
<div className="explanation">
|
|
<strong>Explanation:</strong>
|
|
{question.explanation && <div style={{ marginTop: 8 }}><RichText value={question.explanation} /></div>}
|
|
{question.explanation_image_path && (
|
|
<div style={{ marginTop: 12 }}>
|
|
<img src={`/uploads/${question.explanation_image_path}`} alt="Explanation illustration"
|
|
style={{ maxWidth: '100%', maxHeight: 280, borderRadius: 8, border: '1px solid var(--border)' }}
|
|
onError={e => e.currentTarget.style.display = 'none'} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
{(question.key_points || []).length > 0 && (
|
|
<div className="explanation" style={{ marginTop: 12 }}>
|
|
<strong>Key points</strong>
|
|
<ul style={{ margin: '6px 0 0', paddingLeft: 18, fontSize: '0.85rem' }}>
|
|
{question.key_points.map((point, i) => (
|
|
<li key={i}>
|
|
{point.text}
|
|
{point.article_id && (
|
|
<Link to={`/articles/${point.article_id}${point.article_section_id ? `?section=${point.article_section_id}` : ''}`}
|
|
style={{ marginLeft: 8, color: 'var(--primary)', textDecoration: 'none', fontWeight: 600, fontSize: '0.8rem' }}>📖 Read more</Link>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
<QuestionReadingLinks questionId={question.id} />
|
|
<div style={{ marginTop: 14, display: 'flex', gap: 8 }}>
|
|
<button className="btn btn-secondary btn-sm" onClick={onClose}>Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* AI Tutor — z-index above the modal */}
|
|
<Suspense fallback={null}>
|
|
<TeachChat question={question} elevated />
|
|
</Suspense>
|
|
</>
|
|
)
|
|
}
|