refactor(modules): update existing module implementations across attendance, audit, auth, classes, course-plans, exams, files, homework, layout, proctoring, questions, scheduling, textbooks, users

- Update attendance components and data-access for record management

- Update audit log views, filters, and data-access

- Update auth login and register forms

- Update classes actions, components, and data-access (admin, schedule, stats)

- Update course-plans actions, form, list, progress, and schema

- Update exams actions, AI pipeline, preview components, and hooks

- Update files components (icon, list, preview, upload) and data-access

- Update homework assignment form, review view, auto-save hook, and stats-service

- Update layout sidebar, header, and navigation config

- Update proctoring actions, anti-cheat monitor, and data-access

- Update questions actions, components (dialog, actions, columns, filters), and data-access

- Update scheduling actions, auto-scheduler, components, and schema

- Update textbooks constants and text-selection hook

- Update users class-registration, import-dialog, data-access, and user-service
This commit is contained in:
SpecialX
2026-06-23 17:38:56 +08:00
parent 1a9377222c
commit 4f0ef217a0
56 changed files with 1251 additions and 850 deletions

View File

@@ -43,14 +43,19 @@ export function AttendanceRecordList({ records }: { records: AttendanceListItem[
const handleDelete = async () => {
if (!deleteId) return
setIsDeleting(true)
const result = await deleteAttendanceAction(deleteId)
setIsDeleting(false)
if (result.success) {
toast.success(result.message || t("sheet.deleted"))
setDeleteId(null)
router.refresh()
} else {
toast.error(result.message || t("errors.unexpected"))
try {
const result = await deleteAttendanceAction(deleteId)
if (result.success) {
toast.success(result.message || t("sheet.deleted"))
setDeleteId(null)
router.refresh()
} else {
toast.error(result.message || t("errors.unexpected"))
}
} catch {
toast.error(t("errors.unexpected"))
} finally {
setIsDeleting(false)
}
}

View File

@@ -72,12 +72,16 @@ export function AttendanceRulesForm({
formData.set("earlyLeaveThresholdMinutes", earlyLeaveThreshold)
formData.set("enableAutoMark", enableAutoMark ? "true" : "false")
const result = await saveAttendanceRulesAction(null, formData)
if (result.success) {
toast.success(result.message || t("rules.saved"))
router.refresh()
} else {
toast.error(result.message || t("errors.unexpected"))
try {
const result = await saveAttendanceRulesAction(null, formData)
if (result.success) {
toast.success(result.message || t("rules.saved"))
router.refresh()
} else {
toast.error(result.message || t("errors.unexpected"))
}
} catch {
toast.error(t("errors.unexpected"))
}
}

View File

@@ -210,14 +210,19 @@ export function AttendanceSheet({
setIsSubmitting(true)
formData.set("recordsJson", JSON.stringify(records))
const result = await batchRecordAttendanceAction(null, formData)
setIsSubmitting(false)
if (result.success) {
toast.success(result.message || t("sheet.saved"))
router.push("/teacher/attendance")
router.refresh()
} else {
toast.error(result.message || t("errors.unexpected"))
try {
const result = await batchRecordAttendanceAction(null, formData)
if (result.success) {
toast.success(result.message || t("sheet.saved"))
router.push("/teacher/attendance")
router.refresh()
} else {
toast.error(result.message || t("errors.unexpected"))
}
} catch {
toast.error(t("errors.unexpected"))
} finally {
setIsSubmitting(false)
}
}

View File

@@ -2,6 +2,7 @@ import { Users, CheckCircle2, XCircle, Clock, LogOut, FileText } from "lucide-re
import { useTranslations } from "next-intl"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { cn } from "@/shared/lib/utils"
interface AttendanceStatsCardsProps {
stats: {
@@ -68,8 +69,8 @@ export function AttendanceStatsCards({ stats }: AttendanceStatsCardsProps) {
<Card key={card.title} className="shadow-none">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{card.title}</CardTitle>
<div className={`flex h-8 w-8 items-center justify-center rounded-md ${card.bgColor}`}>
<card.icon className={`h-4 w-4 ${card.color}`} />
<div className={cn("flex h-8 w-8 items-center justify-center rounded-md", card.bgColor)}>
<card.icon className={cn("h-4 w-4", card.color)} />
</div>
</CardHeader>
<CardContent>

View File

@@ -11,6 +11,7 @@ import {
users,
} from "@/shared/db/schema"
import { getClassActiveStudentsWithInfo } from "@/modules/classes/data-access"
import { safeParseDate } from "@/shared/lib/action-utils"
import type { DataScope } from "@/shared/types/permissions"
import type {
@@ -96,9 +97,9 @@ export async function getAttendanceRecords(
}
if (params.classId) conditions.push(eq(attendanceRecords.classId, params.classId))
if (params.studentId) conditions.push(eq(attendanceRecords.studentId, params.studentId))
if (params.date) conditions.push(eq(attendanceRecords.date, new Date(params.date)))
if (params.startDate) conditions.push(gte(attendanceRecords.date, new Date(params.startDate)))
if (params.endDate) conditions.push(lte(attendanceRecords.date, new Date(params.endDate)))
if (params.date) conditions.push(eq(attendanceRecords.date, safeParseDate(params.date, "日期")))
if (params.startDate) conditions.push(gte(attendanceRecords.date, safeParseDate(params.startDate, "开始日期")))
if (params.endDate) conditions.push(lte(attendanceRecords.date, safeParseDate(params.endDate, "结束日期")))
if (params.status) conditions.push(eq(attendanceRecords.status, params.status))
const where = conditions.length > 0 ? and(...conditions) : undefined
@@ -163,7 +164,7 @@ export async function createAttendanceRecord(
studentId: data.studentId,
classId: data.classId,
scheduleId: data.scheduleId ?? null,
date: new Date(data.date),
date: safeParseDate(data.date, "日期"),
status: data.status,
remark: data.remark ?? null,
recordedBy,
@@ -181,7 +182,7 @@ export async function batchCreateAttendanceRecords(
studentId: r.studentId,
classId: r.classId,
scheduleId: r.scheduleId ?? null,
date: new Date(r.date),
date: safeParseDate(r.date, "日期"),
status: r.status,
remark: r.remark ?? null,
recordedBy,
@@ -304,7 +305,7 @@ export async function getAttendanceStats(params: {
conditions.push(eq(attendanceRecords.studentId, params.currentUserId))
}
if (params.classId) conditions.push(eq(attendanceRecords.classId, params.classId))
if (params.date) conditions.push(eq(attendanceRecords.date, new Date(params.date)))
if (params.date) conditions.push(eq(attendanceRecords.date, safeParseDate(params.date, "日期")))
const where = conditions.length > 0 ? and(...conditions) : undefined