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

@@ -1,9 +1,10 @@
"use server"
import { revalidatePath } from "next/cache"
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import type { ActionState } from "@/shared/types/action-state"
import { handleActionError } from "@/shared/lib/action-utils"
import {
CreateCoursePlanSchema,
@@ -23,12 +24,6 @@ import {
} from "./data-access"
import type { CoursePlanWithItems, GetCoursePlansParams, CoursePlanListItem } from "./types"
const handleError = (e: unknown): ActionState<never> => {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
if (e instanceof Error) return { success: false, message: e.message }
return { success: false, message: "Unexpected error" }
}
const revalidatePlanPaths = (id?: string) => {
revalidatePath("/admin/course-plans")
revalidatePath("/teacher/course-plans")
@@ -72,7 +67,7 @@ export async function createCoursePlanAction(
revalidatePlanPaths(id)
return { success: true, message: "Course plan created", data: id }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -115,7 +110,7 @@ export async function updateCoursePlanAction(
revalidatePlanPaths(id)
return { success: true, message: "Course plan updated", data: id }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -132,7 +127,7 @@ export async function deleteCoursePlanAction(
revalidatePlanPaths()
return { success: true, message: "Course plan deleted" }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -144,7 +139,7 @@ export async function getCoursePlansAction(
const data = await getCoursePlans(params)
return { success: true, data }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -157,7 +152,7 @@ export async function getCoursePlanAction(
if (!data) return { success: false, message: "Course plan not found" }
return { success: true, data }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -190,7 +185,7 @@ export async function createCoursePlanItemAction(
revalidatePlanPaths(parsed.data.planId)
return { success: true, message: "Week plan added", data: itemId }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -230,7 +225,7 @@ export async function updateCoursePlanItemAction(
revalidatePlanPaths()
return { success: true, message: "Week plan updated", data: id }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -243,7 +238,7 @@ export async function deleteCoursePlanItemAction(
revalidatePlanPaths()
return { success: true, message: "Week plan deleted" }
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}
@@ -263,6 +258,6 @@ export async function toggleCoursePlanItemCompletedAction(
message: completed ? "Marked as completed" : "Marked as incomplete",
}
} catch (e) {
return handleError(e)
return handleActionError(e)
}
}

View File

@@ -12,6 +12,7 @@ import {
subjects,
users,
} from "@/shared/db/schema"
import { safeParseDate } from "@/shared/lib/action-utils"
import type {
CoursePlan,
CoursePlanItem,
@@ -203,8 +204,8 @@ export async function createCoursePlan(
totalHours: data.totalHours,
completedHours: 0,
weeklyHours: data.weeklyHours,
startDate: data.startDate ? new Date(data.startDate) : null,
endDate: data.endDate ? new Date(data.endDate) : null,
startDate: data.startDate ? safeParseDate(data.startDate, "开始日期") : null,
endDate: data.endDate ? safeParseDate(data.endDate, "结束日期") : null,
syllabus: data.syllabus,
objectives: data.objectives,
status: data.status,
@@ -227,9 +228,9 @@ export async function updateCoursePlan(
if (data.completedHours !== undefined) update.completedHours = data.completedHours
if (data.weeklyHours !== undefined) update.weeklyHours = data.weeklyHours
if (data.startDate !== undefined)
update.startDate = data.startDate ? new Date(data.startDate) : null
update.startDate = data.startDate ? safeParseDate(data.startDate, "开始日期") : null
if (data.endDate !== undefined)
update.endDate = data.endDate ? new Date(data.endDate) : null
update.endDate = data.endDate ? safeParseDate(data.endDate, "结束日期") : null
if (data.syllabus !== undefined) update.syllabus = data.syllabus
if (data.objectives !== undefined) update.objectives = data.objectives
if (data.status !== undefined) update.status = data.status
@@ -273,7 +274,7 @@ export async function updateCoursePlanItem(
if (data.notes !== undefined) update.notes = data.notes
if (data.isCompleted !== undefined) update.isCompleted = data.isCompleted
if (data.completedAt !== undefined)
update.completedAt = data.completedAt ? new Date(data.completedAt) : null
update.completedAt = data.completedAt ? safeParseDate(data.completedAt, "完成日期") : null
if (Object.keys(update).length === 0) return

View File

@@ -1,5 +1,11 @@
import { z } from "zod"
const isValidDateString = (v: string | null | undefined): boolean => {
if (v === null || v === undefined || v === "") return true
const d = new Date(v)
return !Number.isNaN(d.getTime())
}
export const CreateCoursePlanSchema = z
.object({
classId: z.string().trim().min(1),
@@ -9,8 +15,18 @@ export const CreateCoursePlanSchema = z
semester: z.enum(["1", "2"]).optional(),
totalHours: z.coerce.number().int().min(0).optional(),
weeklyHours: z.coerce.number().int().min(0).optional(),
startDate: z.string().trim().optional().nullable(),
endDate: z.string().trim().optional().nullable(),
startDate: z
.string()
.trim()
.optional()
.nullable()
.refine(isValidDateString, "开始日期格式无效"),
endDate: z
.string()
.trim()
.optional()
.nullable()
.refine(isValidDateString, "结束日期格式无效"),
syllabus: z.string().trim().optional().nullable(),
objectives: z.string().trim().optional().nullable(),
status: z.enum(["planning", "active", "completed", "paused"]).optional(),
@@ -42,8 +58,18 @@ export const UpdateCoursePlanSchema = z
totalHours: z.coerce.number().int().min(0).optional(),
completedHours: z.coerce.number().int().min(0).optional(),
weeklyHours: z.coerce.number().int().min(0).optional(),
startDate: z.string().trim().optional().nullable(),
endDate: z.string().trim().optional().nullable(),
startDate: z
.string()
.trim()
.optional()
.nullable()
.refine(isValidDateString, "开始日期格式无效"),
endDate: z
.string()
.trim()
.optional()
.nullable()
.refine(isValidDateString, "结束日期格式无效"),
syllabus: z.string().trim().optional().nullable(),
objectives: z.string().trim().optional().nullable(),
status: z.enum(["planning", "active", "completed", "paused"]).optional(),
@@ -116,7 +142,12 @@ export const UpdateCoursePlanItemSchema = z
textbookChapter: z.string().trim().optional().nullable(),
notes: z.string().trim().optional().nullable(),
isCompleted: z.boolean().optional(),
completedAt: z.string().trim().optional().nullable(),
completedAt: z
.string()
.trim()
.optional()
.nullable()
.refine(isValidDateString, "完成日期格式无效"),
})
.transform((v) => ({
...v,