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:
138
src/modules/homework/components/homework-assignment-form.tsx
Normal file
138
src/modules/homework/components/homework-assignment-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user