feat(dashboard): optimize teacher dashboard ui and layout

- Refactor layout: move Needs Grading to main column, Homework to sidebar
- Enhance TeacherStats: replace static counts with actionable metrics (Needs Grading, Active Assignments, Avg Score, Submission Rate)
- Update RecentSubmissions: table view with quick grade actions and late status
- Update TeacherSchedule: vertical timeline view with scroll hints
- Update TeacherHomeworkCard: compact list view
- Integrate Recharts: add TeacherGradeTrends chart and shared chart component
- Update documentation
This commit is contained in:
SpecialX
2026-01-12 11:38:27 +08:00
parent 8577280ab2
commit ade8d4346c
17 changed files with 1383 additions and 234 deletions

View File

@@ -29,8 +29,69 @@ import type {
StudentDashboardGradeProps,
StudentHomeworkScoreAnalytics,
StudentRanking,
TeacherGradeTrendItem,
} from "./types"
export const getTeacherGradeTrends = cache(async (teacherId: string, limit: number = 5): Promise<TeacherGradeTrendItem[]> => {
const recentAssignments = await db.query.homeworkAssignments.findMany({
where: and(
eq(homeworkAssignments.creatorId, teacherId),
or(eq(homeworkAssignments.status, "published"), eq(homeworkAssignments.status, "archived"))
),
orderBy: [desc(homeworkAssignments.createdAt)],
limit: limit,
})
if (recentAssignments.length === 0) return []
const assignmentIds = recentAssignments.map((a) => a.id)
const [maxScoreMap, targetCountRows, submissionStats] = await Promise.all([
getAssignmentMaxScoreById(assignmentIds),
db
.select({
assignmentId: homeworkAssignmentTargets.assignmentId,
count: count(homeworkAssignmentTargets.studentId),
})
.from(homeworkAssignmentTargets)
.where(inArray(homeworkAssignmentTargets.assignmentId, assignmentIds))
.groupBy(homeworkAssignmentTargets.assignmentId),
db
.select({
assignmentId: homeworkSubmissions.assignmentId,
avgScore: sql<number>`AVG(${homeworkSubmissions.score})`,
count: count(homeworkSubmissions.id),
})
.from(homeworkSubmissions)
.where(
and(
inArray(homeworkSubmissions.assignmentId, assignmentIds),
eq(homeworkSubmissions.status, "graded")
)
)
.groupBy(homeworkSubmissions.assignmentId),
])
const targetCountMap = new Map<string, number>()
for (const r of targetCountRows) targetCountMap.set(r.assignmentId, r.count)
const statsMap = new Map<string, { avg: number; count: number }>()
for (const r of submissionStats) statsMap.set(r.assignmentId, { avg: Number(r.avgScore), count: Number(r.count) })
return recentAssignments.map((a) => {
const stats = statsMap.get(a.id) ?? { avg: 0, count: 0 }
return {
id: a.id,
title: a.title,
averageScore: stats.avg,
maxScore: maxScoreMap.get(a.id) ?? 0,
submissionCount: stats.count,
totalStudents: targetCountMap.get(a.id) ?? 0,
createdAt: a.createdAt.toISOString(),
}
}).reverse() // Reverse to show trend from left (older) to right (newer)
})
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null
const toQuestionContent = (v: unknown): HomeworkQuestionContent | null => {