import Link from "next/link" import { getTranslations } from "next-intl/server" import { EmptyState } from "@/shared/components/ui/empty-state" import { Button } from "@/shared/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { StatusBadge } from "@/shared/components/ui/status-badge" import { formatDate, cn } from "@/shared/lib/utils" import { getStudentHomeworkAssignments } from "@/modules/homework/data-access" import { getCurrentStudentUser } from "@/modules/users/data-access" import { AssignmentFilters } from "@/modules/homework/components/assignment-filters" import { Inbox, UserX, TriangleAlert } from "lucide-react" import type { StudentHomeworkAssignmentListItem, StudentHomeworkProgressStatus, } from "@/modules/homework/types" import { STUDENT_HOMEWORK_PROGRESS_VARIANT, } from "@/modules/homework/types" export const dynamic = "force-dynamic" type SearchParams = { [key: string]: string | string[] | undefined } const getParam = (params: SearchParams, key: string) => { const v = params[key] return Array.isArray(v) ? v[0] : v } const getActionLabel = (status: StudentHomeworkProgressStatus, t: (key: string) => string): string => { switch (status) { case "graded": return t("homework.review.title") case "submitted": return t("common.view") case "in_progress": return t("common.continue") default: return t("homework.take.startAssignment") } } const getActionVariant = ( status: StudentHomeworkProgressStatus ): "default" | "secondary" | "outline" => { return status === "graded" || status === "submitted" ? "outline" : "default" } const isAnswered = (status: StudentHomeworkProgressStatus): boolean => status === "submitted" || status === "graded" const matchesStatusFilter = ( status: StudentHomeworkProgressStatus, filter: string ): boolean => { if (filter === "all") return true if (filter === "pending") return status === "not_started" || status === "in_progress" if (filter === "submitted") return status === "submitted" if (filter === "graded") return status === "graded" return true } // Stable color mapping for subjects (hash-based, deterministic) const SUBJECT_DOT_COLORS = [ "bg-blue-500", "bg-emerald-500", "bg-amber-500", "bg-violet-500", "bg-rose-500", "bg-cyan-500", "bg-orange-500", "bg-pink-500", "bg-indigo-500", "bg-teal-500", ] const getSubjectColor = (subject: string): string => { let hash = 0 for (let i = 0; i < subject.length; i++) { hash = (hash * 31 + subject.charCodeAt(i)) | 0 } const idx = Math.abs(hash) % SUBJECT_DOT_COLORS.length return SUBJECT_DOT_COLORS[idx] } type TranslationFn = (key: string) => string function AssignmentCard({ assignment: a, t, statusLabelMap, }: { assignment: StudentHomeworkAssignmentListItem t: TranslationFn statusLabelMap: Record }) { const now = new Date() const isOverdue = a.dueAt ? new Date(a.dueAt) < now : false const showOverdueBadge = isOverdue && !isAnswered(a.progressStatus) return (
{a.title}
{t("homework.list.columns.dueAt")} {a.dueAt ? formatDate(a.dueAt) : "-"} {t("homework.take.attempts")} {a.attemptsUsed}/{a.maxAttempts} {showOverdueBadge && ( <> {t("homework.take.overdue")} )}
{t("homework.grade.score")}
{a.latestScore ?? "-"}
) } export default async function StudentAssignmentsPage({ searchParams, }: { searchParams: Promise }) { const t = await getTranslations("examHomework") const student = await getCurrentStudentUser() const statusLabelMap: Record = { not_started: t("homework.status.not_started"), in_progress: t("homework.status.in_progress"), submitted: t("homework.status.submitted"), graded: t("homework.status.graded"), } if (!student) { return (

{t("homework.list.title")}

{t("homework.list.description")}

) } const [sp, assignments] = await Promise.all([ searchParams, getStudentHomeworkAssignments(student.id), ]) const q = (getParam(sp, "q") || "").toLowerCase().trim() const statusFilter = getParam(sp, "status") || "all" // 应用筛选 const filtered = assignments.filter((a) => { if (q && !a.title.toLowerCase().includes(q)) return false if (!matchesStatusFilter(a.progressStatus, statusFilter)) return false return true }) const hasAssignments = assignments.length > 0 const hasFiltered = filtered.length > 0 const assignmentsBySubject = filtered.reduce((acc, assignment) => { const subject = assignment.subjectName?.trim() || t("common.other") const existing = acc.get(subject) if (existing) { existing.push(assignment) } else { acc.set(subject, [assignment]) } return acc }, new Map()) const subjectEntries = Array.from(assignmentsBySubject.entries()).sort((a, b) => a[0].localeCompare(b[0]) ) return (

{t("homework.list.title")}

{t("homework.list.description")}

{hasAssignments && } {!hasAssignments ? ( ) : !hasFiltered ? ( ) : (
{subjectEntries.map(([subject, items]) => { // 单次遍历分桶,避免重复 filter(PERF-05) const answered: StudentHomeworkAssignmentListItem[] = [] const unanswered: StudentHomeworkAssignmentListItem[] = [] for (const a of items) { if (isAnswered(a.progressStatus)) { answered.push(a) } else { unanswered.push(a) } } return (
{unanswered.length > 0 && (
{t("homework.status.not_started")}
{unanswered.map((a) => ( ))}
)} {answered.length > 0 && (
{t("common.completed")}
{answered.map((a) => ( ))}
)}
) })}
)}
) }