refactor(modules): update classes, course-plans, diagnostic, questions, settings, student, layout

- Update classes data-access (invitations, main) for invitation management

- Update course-plans actions, data-access, and types

- Update diagnostic data-access for report queries

- Update questions data-access for question bank queries

- Update settings actions, ai-provider-settings-card, data-access, and types

- Update student course-filters, student-courses-view, student-schedule-filters, student-schedule-view

- Update layout app-sidebar, site-header, and navigation config
This commit is contained in:
SpecialX
2026-06-24 12:03:35 +08:00
parent c9e46f9f80
commit 8c2fe14c20
18 changed files with 712 additions and 189 deletions

View File

@@ -15,6 +15,7 @@ import {
import {
getCoursePlans,
getCoursePlanById,
getGradeCoursePlanProgress,
createCoursePlan,
updateCoursePlan,
deleteCoursePlan,
@@ -22,7 +23,7 @@ import {
updateCoursePlanItem,
deleteCoursePlanItem,
} from "./data-access"
import type { CoursePlanWithItems, GetCoursePlansParams, CoursePlanListItem } from "./types"
import type { CoursePlanWithItems, GetCoursePlansParams, CoursePlanListItem, GradeCoursePlanProgressResult } from "./types"
const revalidatePlanPaths = (id?: string) => {
revalidatePath("/admin/course-plans")
@@ -261,3 +262,23 @@ export async function toggleCoursePlanItemCompletedAction(
return handleActionError(e)
}
}
/**
* 年级仪表盘 - 维度4获取年级下所有班级的教学计划进度。
*/
export async function getGradeCoursePlanProgressAction(
gradeId: string
): Promise<ActionState<GradeCoursePlanProgressResult>> {
try {
await requirePermission(Permissions.COURSE_PLAN_READ)
if (!gradeId || gradeId.trim().length === 0) {
return { success: false, message: "Invalid grade id" }
}
const data = await getGradeCoursePlanProgress({ gradeId })
return { success: true, data }
} catch (e) {
return handleActionError(e)
}
}

View File

@@ -20,6 +20,8 @@ import type {
CoursePlanStatus,
CoursePlanWithItems,
GetCoursePlansParams,
GradeCoursePlanProgressItem,
GradeCoursePlanProgressResult,
ReorderCoursePlanItemInput,
} from "./types"
import type {
@@ -324,3 +326,100 @@ export const getSubjectOptions = cache(async (): Promise<{ id: string; name: str
return []
}
})
/**
* 年级仪表盘 - 维度4获取年级下所有班级的教学计划进度。
* 通过 getClassesByGradeId 获取年级下所有班级,再用 inArray 查询 course_plans
* 关联 course_plan_items 统计条目完成情况。
*/
export const getGradeCoursePlanProgress = cache(
async (params: { gradeId: string }): Promise<GradeCoursePlanProgressResult> => {
const { getClassesByGradeId } = await import("@/modules/classes/data-access")
const classRows = await getClassesByGradeId(params.gradeId)
if (classRows.length === 0) {
return {
gradeId: params.gradeId,
overall: { totalPlans: 0, totalHours: 0, completedHours: 0, progressRate: 0, activePlans: 0, completedPlans: 0 },
items: [],
}
}
const classIds = classRows.map((c) => c.id)
// 查询年级下所有教学计划(含班级/科目/教师名称)
const planRows = await buildPlanSelect()
.where(inArray(coursePlans.classId, classIds))
.orderBy(asc(classes.name), asc(subjects.name))
if (planRows.length === 0) {
return {
gradeId: params.gradeId,
overall: { totalPlans: 0, totalHours: 0, completedHours: 0, progressRate: 0, activePlans: 0, completedPlans: 0 },
items: [],
}
}
const planIds = planRows.map((p) => p.id)
// 查询所有计划的周次条目,统计完成情况
const itemRows = await db
.select({
planId: coursePlanItems.planId,
isCompleted: coursePlanItems.isCompleted,
})
.from(coursePlanItems)
.where(inArray(coursePlanItems.planId, planIds))
const itemStatsByPlan = new Map<string, { total: number; completed: number }>()
for (const it of itemRows) {
const entry = itemStatsByPlan.get(it.planId) ?? { total: 0, completed: 0 }
entry.total += 1
if (it.isCompleted) entry.completed += 1
itemStatsByPlan.set(it.planId, entry)
}
const items: GradeCoursePlanProgressItem[] = planRows.map((p) => {
const totalHours = Number(p.totalHours)
const completedHours = Number(p.completedHours)
const progressRate = totalHours > 0
? Math.round((completedHours / totalHours) * 1000) / 10
: 0
const itemStats = itemStatsByPlan.get(p.id) ?? { total: 0, completed: 0 }
return {
planId: p.id,
classId: p.classId,
className: p.className,
subjectId: p.subjectId,
subjectName: p.subjectName,
teacherName: p.teacherName,
semester: p.semester,
totalHours,
completedHours,
progressRate,
status: p.status,
itemCount: itemStats.total,
completedItemCount: itemStats.completed,
}
})
const totalHours = items.reduce((sum, i) => sum + i.totalHours, 0)
const completedHours = items.reduce((sum, i) => sum + i.completedHours, 0)
const progressRate = totalHours > 0
? Math.round((completedHours / totalHours) * 1000) / 10
: 0
return {
gradeId: params.gradeId,
overall: {
totalPlans: items.length,
totalHours,
completedHours,
progressRate,
activePlans: items.filter((i) => i.status === "active").length,
completedPlans: items.filter((i) => i.status === "completed").length,
},
items,
}
}
)

View File

@@ -58,3 +58,40 @@ export interface ReorderCoursePlanItemInput {
id: string
week: number
}
/**
* 年级仪表盘 - 维度4年级下各班级/科目的课本进度。
*/
export interface GradeCoursePlanProgressItem {
planId: string
classId: string
className: string | null
subjectId: string
subjectName: string | null
teacherName: string | null
semester: CoursePlanSemester
totalHours: number
completedHours: number
/** 进度百分比 0-100 */
progressRate: number
status: CoursePlanStatus
/** 周次条目总数 */
itemCount: number
/** 已完成条目数 */
completedItemCount: number
}
export interface GradeCoursePlanProgressResult {
gradeId: string
/** 年级整体进度汇总 */
overall: {
totalPlans: number
totalHours: number
completedHours: number
progressRate: number
activePlans: number
completedPlans: number
}
/** 按班级 + 科目拆分的进度列表 */
items: GradeCoursePlanProgressItem[]
}