From beedb76afb01155a19cab8d63b67a67a6da1d7ef Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 10 Sep 2026 04:04:28 +0200 Subject: [PATCH] fix: bank stylesheet was never imported; drop quiz categories; rail and sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QuestionBankPage.css was not imported, so none of the filter-bar styling applied — which is why "⚙ Filters" and the question count ran together with no card behind them. Importing it fixes the whole bar, not just the spacing. Quiz/test categories removed. PREP lives in study plans now, so the Categories tab, the category selector in the session menu, and the handlers behind them were managing a concept that no longer exists. Sessions list shows the first six with "Show all" and a link to the full history on the analysis page, instead of printing every session inline. The analysis session rail now runs the full height of the viewport against the left edge rather than sitting in a boxed card, so a long list is one continuous column. Below 1100px it returns to a card above the content. Tests: 136 frontend green, build clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01365DYKu14YtsBKv2ycW6eG --- frontend/src/pages/AnalysisPage.css | 20 +++- frontend/src/pages/QuestionBankPage.jsx | 1 + frontend/src/pages/QuizzesPage.css | 3 + frontend/src/pages/QuizzesPage.jsx | 122 +++++++----------------- 4 files changed, 52 insertions(+), 94 deletions(-) diff --git a/frontend/src/pages/AnalysisPage.css b/frontend/src/pages/AnalysisPage.css index 44eb7b2..69cc4f9 100644 --- a/frontend/src/pages/AnalysisPage.css +++ b/frontend/src/pages/AnalysisPage.css @@ -1,12 +1,19 @@ /* Session rail beside the analysis, as in a Qbank analysis view. */ -.an-layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 24px; align-items: start; } -.an-layout.rail-closed { grid-template-columns: 52px minmax(0, 1fr); } -.an-rail { position: sticky; top: 16px; background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; } +/* The rail runs the full height of the viewport against the left edge, rather + than sitting in a card, so a long session list is one continuous column. */ +.an-layout { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 28px; align-items: start; } +.an-layout.rail-closed { grid-template-columns: 48px minmax(0, 1fr); } +.an-rail { + position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; + background: var(--card-bg); border-right: 1px solid var(--border); + margin-left: calc(50% - 50vw); padding-left: max(0px, calc(50vw - 50%)); + box-sizing: content-box; +} .an-rail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border); } .an-rail-head h2 { margin: 0; font-size: .92rem; font-weight: 650; white-space: nowrap; } .an-layout.rail-closed .an-rail-head h2 { display: none; } .an-rail-head button { background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 1.1rem; line-height: 1; padding: 2px 4px; } -.an-rail-list { list-style: none; margin: 0; padding: 0; max-height: 70vh; overflow-y: auto; } +.an-rail-list { list-style: none; margin: 0; padding: 0; flex: 1; overflow-y: auto; } .an-rail-list > li { border-bottom: 1px solid var(--border); } .an-rail-list > li:last-child { border-bottom: 0; } .an-rail-list a { display: flex; flex-direction: column; gap: 7px; padding: 12px 14px; text-decoration: none; color: var(--text); } @@ -17,7 +24,10 @@ @media (max-width: 1100px) { .an-layout, .an-layout.rail-closed { grid-template-columns: 1fr; } - .an-rail { position: static; } + .an-rail { + position: static; height: auto; margin-left: 0; padding-left: 0; + border-right: 0; border: 1px solid var(--border); border-radius: 12px; + } .an-rail-list { max-height: 260px; } } diff --git a/frontend/src/pages/QuestionBankPage.jsx b/frontend/src/pages/QuestionBankPage.jsx index 388c15a..d188cb4 100644 --- a/frontend/src/pages/QuestionBankPage.jsx +++ b/frontend/src/pages/QuestionBankPage.jsx @@ -6,6 +6,7 @@ import Dialog from '../components/Dialog' import CategoryTree from '../components/CategoryTree' import FacetPicker, { FacetRow } from '../components/FacetPicker' import './CustomQuizPage.css' // facet row + picker panel styles +import './QuestionBankPage.css' import { useDialog } from '../hooks/useDialog' const TeachChat = lazy(() => import('../components/TeachChat')) diff --git a/frontend/src/pages/QuizzesPage.css b/frontend/src/pages/QuizzesPage.css index 755ef23..5b1b295 100644 --- a/frontend/src/pages/QuizzesPage.css +++ b/frontend/src/pages/QuizzesPage.css @@ -261,3 +261,6 @@ .qz-grid { grid-template-columns: 1fr; } } + +.qz-seemore { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin: 6px 0 4px; } +@media (max-width: 640px) { .qz-seemore .btn { flex: 1; text-align: center; } } diff --git a/frontend/src/pages/QuizzesPage.jsx b/frontend/src/pages/QuizzesPage.jsx index 87b844c..70d42bb 100644 --- a/frontend/src/pages/QuizzesPage.jsx +++ b/frontend/src/pages/QuizzesPage.jsx @@ -202,7 +202,7 @@ function QuestionStudyModal({ question, query, onClose }) { } /* ── One management row: progress, primary action, and the action menu ── */ -function SessionRow({ row, categories, isModerator, onChanged, onDelete }) { +function SessionRow({ row, isModerator, onChanged, onDelete }) { const [menuOpen, setMenuOpen] = useState(false) const [renaming, setRenaming] = useState(false) const [draftTitle, setDraftTitle] = useState(row.title) @@ -254,16 +254,6 @@ function SessionRow({ row, categories, isModerator, onChanged, onDelete }) { finally { setBusy(false) } } - const assignCategory = async (value) => { - closeMenu(); setBusy(true); setError('') - const catId = value === '' ? null : Number(value) - try { - await api.patch(`/categories/quizzes/${row.quiz_id}`, null, { params: catId == null ? {} : { category_id: catId } }) - onChanged(row.quiz_id, { category_id: catId, category_name: categories.find(c => c.id === catId)?.name ?? null }) - } catch (err) { setError(err.response?.data?.detail || 'Could not move this test') } - finally { setBusy(false) } - } - return (
@@ -347,15 +337,6 @@ function SessionRow({ row, categories, isModerator, onChanged, onDelete }) { - } {canManage && <>
@@ -398,18 +379,18 @@ function QuizCard({ quiz, isModerator, onOpenSessions }) { export default function QuizzesPage() { const [rows, setRows] = useState([]) - const [categories, setCategories] = useState([]) const [loading, setLoading] = useState(true) const [tab, setTab] = useState('sessions') const [stateFilter, setStateFilter] = useState('all') + // The full history lives on the analysis page; this is a launcher. + const [showAllSessions, setShowAllSessions] = useState(false) + const SESSION_PREVIEW = 6 const [searchQuery, setSearchQuery] = useState('') const [searchMode, setSearchMode] = useState('all') const [searchResults, setSearchResults] = useState(null) const [searching, setSearching] = useState(false) const [expandedSearch, setExpandedSearch] = useState(false) const [studyQuestion, setStudyQuestion] = useState(null) - const [newCatName, setNewCatName] = useState('') - const [addingCat, setAddingCat] = useState(false) const { dialogProps, openAlert } = useDialog() const debounceRef = useRef(null) const { user } = useAuth() @@ -417,13 +398,8 @@ export default function QuizzesPage() { const isModerator = user?.role === 'admin' || user?.role === 'moderator' useEffect(() => { - Promise.all([ - api.get('/quizzes/sessions'), - api.get('/categories/'), - ]).then(([sRes, cRes]) => { - setRows(Array.isArray(sRes.data) ? sRes.data : []) - setCategories(Array.isArray(cRes.data) ? cRes.data : []) - }).catch(console.error).finally(() => setLoading(false)) + api.get('/quizzes/sessions') + .then(res => setRows(Array.isArray(res.data) ? res.data : [])).catch(console.error).finally(() => setLoading(false)) }, []) useEffect(() => { @@ -451,24 +427,6 @@ export default function QuizzesPage() { const patchRow = (quizId, patch) => setRows(prev => prev.map(r => (r.quiz_id === quizId ? { ...r, ...patch } : r))) - const addCategory = async () => { - if (!newCatName.trim()) return - try { - const res = await api.post('/categories/', { name: newCatName.trim() }) - setCategories(prev => [...prev, res.data]) - setNewCatName('') - setAddingCat(false) - } catch (err) { await openAlert(err.response?.data?.detail || 'Failed to create category', { title: 'Error' }) } - } - - const deleteCategory = async (catId) => { - try { - await api.delete(`/categories/${catId}`) - setCategories(prev => prev.filter(c => c.id !== catId)) - setRows(prev => prev.map(r => (r.category_id === catId ? { ...r, category_id: null, category_name: null } : r))) - } catch { } - } - const counts = useMemo(() => ({ all: rows.length, in_progress: rows.filter(r => r.state === 'in_progress').length, @@ -481,16 +439,21 @@ export default function QuizzesPage() { [rows, stateFilter], ) + const previewRows = useMemo( + () => (showAllSessions ? visibleRows : visibleRows.slice(0, SESSION_PREVIEW)), + [visibleRows, showAllSessions], + ) + const dayGroups = useMemo(() => { const groups = [] - for (const row of visibleRows) { + for (const row of previewRows) { const label = dayLabel(row.last_activity) const last = groups[groups.length - 1] if (last && last.label === label) last.rows.push(row) else groups.push({ label, rows: [row] }) } return groups - }, [visibleRows]) + }, [previewRows]) const libraryGroups = useMemo(() => { const byCat = new Map() @@ -619,11 +582,6 @@ export default function QuizzesPage() { - {isModerator && ( - - )}
{tab === 'sessions' && ( @@ -649,13 +607,32 @@ export default function QuizzesPage() {

{group.label}

{group.rows.map(row => ( - ))}
))} + {visibleRows.length > SESSION_PREVIEW && ( +
+ {showAllSessions ? ( + + ) : ( + <> + + + Full session history → + + + )} +
+ )} + ) @@ -677,39 +654,6 @@ export default function QuizzesPage() { )) )} - {tab === 'manage' && isModerator && ( -
-
-

Test categories

- -
- {addingCat && ( -
- setNewCatName(e.target.value)} - placeholder="Category name…" aria-label="New category name" - onKeyDown={e => e.key === 'Enter' && addCategory()} - style={{ flex: 1, minWidth: 170, padding: '7px 12px', border: '1px solid var(--border)', borderRadius: 8, fontSize: '0.875rem', background: 'var(--input-bg)', color: 'var(--text)' }} /> - - -
- )} - {categories.length === 0 ? ( -

No categories yet.

- ) : ( -
- {categories.map(cat => ( -
- {cat.name} - ({cat.quiz_count}) - deleteCategory(cat.id)} /> -
- ))} -
- )} -
- )} )}