Merge exams grading into homework

Redirect /teacher/exams/grading* to /teacher/homework/submissions; remove exam grading UI/actions/data-access; add homework student workflow and update design docs.
This commit is contained in:
SpecialX
2025-12-31 11:59:03 +08:00
parent f8e39f518d
commit 13e91e628d
36 changed files with 4491 additions and 452 deletions

View File

@@ -0,0 +1,138 @@
"use client"
import { useMemo, useState } from "react"
import { useFormStatus } from "react-dom"
import { toast } from "sonner"
import { useRouter } from "next/navigation"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Button } from "@/shared/components/ui/button"
import { Input } from "@/shared/components/ui/input"
import { Label } from "@/shared/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/shared/components/ui/select"
import { Textarea } from "@/shared/components/ui/textarea"
import { createHomeworkAssignmentAction } from "../actions"
type ExamOption = { id: string; title: string }
function SubmitButton() {
const { pending } = useFormStatus()
return (
<Button type="submit" disabled={pending}>
{pending ? "Creating..." : "Create Assignment"}
</Button>
)
}
export function HomeworkAssignmentForm({ exams }: { exams: ExamOption[] }) {
const router = useRouter()
const initialExamId = useMemo(() => exams[0]?.id ?? "", [exams])
const [examId, setExamId] = useState<string>(initialExamId)
const [allowLate, setAllowLate] = useState<boolean>(false)
const handleSubmit = async (formData: FormData) => {
if (!examId) {
toast.error("Please select an exam")
return
}
formData.set("sourceExamId", examId)
formData.set("allowLate", allowLate ? "true" : "false")
formData.set("publish", "true")
const result = await createHomeworkAssignmentAction(null, formData)
if (result.success) {
toast.success(result.message)
router.push("/teacher/homework/assignments")
} else {
toast.error(result.message || "Failed to create")
}
}
return (
<Card>
<CardHeader>
<CardTitle>Create Assignment</CardTitle>
</CardHeader>
<CardContent>
<form action={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="grid gap-2 md:col-span-2">
<Label>Source Exam</Label>
<Select value={examId} onValueChange={setExamId}>
<SelectTrigger>
<SelectValue placeholder="Select an exam" />
</SelectTrigger>
<SelectContent>
{exams.map((e) => (
<SelectItem key={e.id} value={e.id}>
{e.title}
</SelectItem>
))}
</SelectContent>
</Select>
<input type="hidden" name="sourceExamId" value={examId} />
</div>
<div className="grid gap-2 md:col-span-2">
<Label htmlFor="title">Assignment Title (optional)</Label>
<Input id="title" name="title" placeholder="Defaults to exam title" />
</div>
<div className="grid gap-2 md:col-span-2">
<Label htmlFor="description">Description (optional)</Label>
<Textarea id="description" name="description" className="min-h-[80px]" />
</div>
<div className="grid gap-2">
<Label htmlFor="availableAt">Available At (optional)</Label>
<Input id="availableAt" name="availableAt" type="datetime-local" />
</div>
<div className="grid gap-2">
<Label htmlFor="dueAt">Due At (optional)</Label>
<Input id="dueAt" name="dueAt" type="datetime-local" />
</div>
<div className="flex items-center gap-2 md:col-span-2">
<input
id="allowLate"
type="checkbox"
checked={allowLate}
onChange={(e) => setAllowLate(e.target.checked)}
/>
<Label htmlFor="allowLate">Allow late submissions</Label>
<input type="hidden" name="allowLate" value={allowLate ? "true" : "false"} />
</div>
<div className="grid gap-2">
<Label htmlFor="lateDueAt">Late Due At (optional)</Label>
<Input id="lateDueAt" name="lateDueAt" type="datetime-local" />
</div>
<div className="grid gap-2">
<Label htmlFor="maxAttempts">Max Attempts</Label>
<Input id="maxAttempts" name="maxAttempts" type="number" min={1} max={20} defaultValue={1} />
</div>
<div className="grid gap-2 md:col-span-2">
<Label htmlFor="targetStudentIdsText">Target student IDs (optional)</Label>
<Textarea
id="targetStudentIdsText"
name="targetStudentIdsText"
placeholder="Leave empty to assign to all students. You can paste IDs separated by comma or newline."
className="min-h-[90px]"
/>
</div>
</div>
<CardFooter className="justify-end">
<SubmitButton />
</CardFooter>
</form>
</CardContent>
</Card>
)
}