feat(dashboard,diagnostic,elective): add widgets, layout, parent dashboard, role-config, services, elective components

dashboard:

- Add comparison-badge, dashboard-notification-widget, dashboard-responsive-layout, dashboard-time-range-filter

- Add parent-dashboard components directory

- Add config, hooks, and services directories

diagnostic:

- Add role-config and services directory

elective:

- Add elective-course-detail, elective-stats-cards, parent-selection-view components

- Add data-access-settings and data-access-stats
This commit is contained in:
SpecialX
2026-07-03 10:25:46 +08:00
parent dfffb61e94
commit 138b6f1b00
58 changed files with 3313 additions and 695 deletions

View File

@@ -59,7 +59,7 @@ async function StudentDashboardBody({
<StudentDashboardHeader studentName={student.name} />
</header>
<DashboardSection variant="stats">
<DashboardSection variant="stats" ariaLabel={t("sections.quickStats")}>
<StudentStatsGrid
enrolledClassCount={dashboardProps.enrolledClassCount}
dueSoonCount={dashboardProps.dueSoonCount}
@@ -74,15 +74,15 @@ async function StudentDashboardBody({
aria-label={t("sections.upcomingAssignments")}
className="lg:col-span-2 space-y-6"
>
<DashboardSection variant="list">
<DashboardSection variant="list" ariaLabel={t("sections.upcomingAssignments")}>
<StudentUpcomingAssignmentsCard upcomingAssignments={dashboardProps.upcomingAssignments} />
</DashboardSection>
<DashboardSection variant="card">
<DashboardSection variant="card" ariaLabel={t("sections.grades")}>
<StudentGradesCard grades={dashboardProps.grades} />
</DashboardSection>
</section>
<aside aria-label={t("sections.todaySchedule")} className="space-y-6">
<DashboardSection variant="card">
<DashboardSection variant="card" ariaLabel={t("sections.todaySchedule")}>
<StudentTodayScheduleCard items={dashboardProps.todayScheduleItems} />
</DashboardSection>
</aside>

View File

@@ -12,13 +12,9 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui
import { EmptyState } from "@/shared/components/ui/empty-state"
import { useCurrentTime } from "@/shared/hooks"
import { cn } from "@/shared/lib/utils"
import { timeToMinutes } from "@/modules/dashboard/lib/dashboard-utils"
import type { StudentTodayScheduleItem } from "@/modules/dashboard/types"
const timeToMinutes = (t: string): number => {
const [h, m] = t.split(":").map(Number)
return (h ?? 0) * 60 + (m ?? 0)
}
export function StudentTodayScheduleCard({ items }: { items: StudentTodayScheduleItem[] }) {
const t = useTranslations("dashboard")
const hasSchedule = items.length > 0

View File

@@ -9,40 +9,18 @@ import { EmptyState } from "@/shared/components/ui/empty-state"
import { StatusBadge } from "@/shared/components/ui/status-badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/shared/components/ui/table"
import { formatDate, cn } from "@/shared/lib/utils"
import { getActionLabelKey, getActionVariant, getDueUrgency } from "@/modules/dashboard/lib/dashboard-utils"
import type { StudentHomeworkAssignmentListItem } from "@/modules/homework/types"
import {
STUDENT_HOMEWORK_PROGRESS_VARIANT,
STUDENT_HOMEWORK_PROGRESS_LABEL,
} from "@/modules/homework/types"
const getActionLabelKey = (status: string): "action.review" | "action.view" | "action.continue" | "action.start" => {
if (status === "graded") return "action.review"
if (status === "submitted") return "action.view"
if (status === "in_progress") return "action.continue"
return "action.start"
}
const getActionVariant = (status: string): "default" | "secondary" | "outline" => {
if (status === "graded" || status === "submitted") return "outline"
return "default"
}
const getDueUrgency = (dueAt: string | null): "overdue" | "urgent" | "warning" | "normal" | null => {
if (!dueAt) return null
const now = new Date()
const due = new Date(dueAt)
const diffHours = (due.getTime() - now.getTime()) / (1000 * 60 * 60)
if (diffHours < 0) return "overdue"
if (diffHours < 48) return "urgent"
if (diffHours < 120) return "warning"
return "normal"
}
export async function StudentUpcomingAssignmentsCard({ upcomingAssignments }: { upcomingAssignments: StudentHomeworkAssignmentListItem[] }) {
const t = await getTranslations("dashboard")
const locale = await getLocale()
const hasAssignments = upcomingAssignments.length > 0
const now = new Date()
return (
<Card>
@@ -78,7 +56,7 @@ export async function StudentUpcomingAssignmentsCard({ upcomingAssignments }: {
</TableHeader>
<TableBody>
{upcomingAssignments.map((a) => {
const urgency = getDueUrgency(a.dueAt)
const urgency = getDueUrgency(a.dueAt, now)
const isGraded = a.progressStatus === "graded"
return (