pdf-quiz-generator/frontend/src/pages/TrashPage.jsx
Daniel 45add79db3 Fix quiz delete (500 error), add trash bin, quiz resume, hide/publish, remove JS dialogs
Critical bug fix:
- DELETE /quizzes/{id} was returning 500 due to {\"quiz_id\": None} update using
  the DB column name instead of Python attribute name (source_quiz_id).
  Fixed with {QuestionModel.source_quiz_id: None, synchronize_session=False}

Extraction fix:
- Two-phase extraction was incorrectly triggering for PREP 2012/2014 format
  documents that have 'Preferred Response:' in their explanation text.
  Fix: check for inline 'Correct Answer:' first — if found, always use standard
  extraction regardless of 'Preferred Response:' appearing elsewhere.

Quiz trash bin:
- DELETE /quizzes/{id} now soft-deletes (sets deleted_at)
- GET /quizzes/trash — list deleted quizzes (moderator)
- PATCH /quizzes/{id}/restore — restore from trash
- DELETE /quizzes/{id}/permanent — permanent delete (must be in trash first)
- TrashPage.jsx — accessible via Settings → Admin → Trash

Hide/publish quizzes:
- is_published column on quizzes (1=visible, 0=hidden)
- PATCH /quizzes/{id}/publish?published=false — hide from regular users
- Moderators see all quizzes; regular users only see published
- 👁/🙈 toggle button per quiz card (moderators only)

Quiz progress resume (cross-browser via Redis):
- POST /attempts/progress — save {answers, current_idx, mode} to Redis (7 days)
- GET /attempts/progress?quiz_id=N — retrieve saved progress
- DELETE /attempts/progress/{quiz_id} — clear on submit
- QuizPage auto-saves to Redis every 1.5s (debounced) while in progress
- ModeSelectScreen loads saved progress from server, shows Resume button
- Works across browsers, devices, and after logout

Delete attempt:
- DELETE /attempts/{id} — user can delete own attempt + clears reminders for that quiz

ConfirmButton component:
- Replaces all window.confirm() / window.prompt() across the app
- Double-click pattern: first click shows [Confirm] [Cancel] inline
- Applied to: QuizzesPage, DocumentDetailPage, QuizEditPage, QuestionBankPage

Category delete (QuestionBankPage):
- window.prompt() replaced with inline modal dialog with select dropdown
- User chooses where to move questions before deletion

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 04:51:41 +02:00

61 lines
2.2 KiB
JavaScript

import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import api from '../api/client'
import ConfirmButton from '../components/ConfirmButton'
export default function TrashPage() {
const [trashed, setTrashed] = useState([])
const [loading, setLoading] = useState(true)
const navigate = useNavigate()
useEffect(() => {
api.get('/quizzes/trash')
.then(res => setTrashed(res.data))
.catch(console.error)
.finally(() => setLoading(false))
}, [])
const restore = async (id) => {
await api.patch(`/quizzes/${id}/restore`)
setTrashed(prev => prev.filter(q => q.id !== id))
}
const permanentDelete = async (id) => {
await api.delete(`/quizzes/${id}/permanent`)
setTrashed(prev => prev.filter(q => q.id !== id))
}
if (loading) return <div className="loading"><div className="spinner" /></div>
return (
<div style={{ maxWidth: 700, margin: '0 auto' }}>
<div className="card" style={{ marginBottom: 16 }}>
<h2>Trash</h2>
<p style={{ color: 'var(--text-muted)', fontSize: '0.875rem', marginTop: 4 }}>
Deleted quizzes restore or permanently delete.
</p>
</div>
{trashed.length === 0 ? (
<div className="card"><div className="empty-state">Trash is empty</div></div>
) : trashed.map(quiz => (
<div className="card" key={quiz.id} style={{ marginBottom: 10, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
<div>
<div style={{ fontWeight: 600 }}>{quiz.title}</div>
<div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
{quiz.questions_count} questions · deleted {new Date(quiz.deleted_at).toLocaleDateString()}
</div>
</div>
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
<button className="btn btn-primary btn-sm" onClick={() => restore(quiz.id)}>Restore</button>
<ConfirmButton
label="Delete forever"
confirmLabel="Yes, permanently delete"
onConfirm={() => permanentDelete(quiz.id)}
/>
</div>
</div>
))}
</div>
)
}