- P1-4.2: 新增班级详情页 courses/[classId],展示教师/学校/教室信息与课表 - P2-2.5: 今日课表卡片高亮当前/下一节课(useMemo 实时计算) - P2-3.9: 作业作答进度网格支持点击跳转题目(scrollIntoView) - P2-3.10: 作业复习视图显示正确答案(选择/判断/文本题) - P2-4.4: 课程列表支持按班级名/教师/学校搜索 - P2-5.2: 成绩页新增趋势折线图组件 GradeTrendCard - P2-9.2/9.3: 诊断报告新增历史记录卡片与弱点练习入口 - P2-10.2: 选课列表支持搜索与选课模式筛选 - P2-11.3: 修复教材阅读页全屏溢出 - P3-1.5: 面包屑保留首个角色段作为根上下文 - P3-7.3: 课表项支持点击跳转至班级详情页(ScheduleList href)
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
|
import { EmptyState } from "@/shared/components/ui/empty-state"
|
|
import { CalendarX } from "lucide-react"
|
|
import { ScheduleList } from "@/shared/components/schedule/schedule-list"
|
|
import { cn } from "@/shared/lib/utils"
|
|
|
|
import type { StudentScheduleItem } from "@/modules/classes/types"
|
|
|
|
const WEEKDAYS: Array<{ key: 1 | 2 | 3 | 4 | 5 | 6 | 7; label: string }> = [
|
|
{ key: 1, label: "Mon" },
|
|
{ key: 2, label: "Tue" },
|
|
{ key: 3, label: "Wed" },
|
|
{ key: 4, label: "Thu" },
|
|
{ key: 5, label: "Fri" },
|
|
{ key: 6, label: "Sat" },
|
|
{ key: 7, label: "Sun" },
|
|
]
|
|
|
|
const getTodayWeekday = (): 1 | 2 | 3 | 4 | 5 | 6 | 7 => {
|
|
// getDay() returns 0 (Sun) - 6 (Sat); convert to 1 (Mon) - 7 (Sun)
|
|
const WEEKDAY_MAP = [7, 1, 2, 3, 4, 5, 6] as const
|
|
const day = new Date().getDay()
|
|
if (day < 0 || day > 6) {
|
|
throw new Error(`Invalid day from getDay(): ${day}`)
|
|
}
|
|
return WEEKDAY_MAP[day]
|
|
}
|
|
|
|
export function StudentScheduleView({ items }: { items: StudentScheduleItem[] }) {
|
|
if (items.length === 0) {
|
|
return (
|
|
<EmptyState
|
|
icon={CalendarX}
|
|
title="No schedule"
|
|
description="No timetable entries found for your enrolled classes."
|
|
className="h-80"
|
|
/>
|
|
)
|
|
}
|
|
|
|
const todayKey = getTodayWeekday()
|
|
|
|
const itemsByDay = new Map<number, StudentScheduleItem[]>()
|
|
for (const item of items) {
|
|
const list = itemsByDay.get(item.weekday) ?? []
|
|
list.push(item)
|
|
itemsByDay.set(item.weekday, list)
|
|
}
|
|
for (const list of itemsByDay.values()) {
|
|
list.sort((a, b) => a.startTime.localeCompare(b.startTime))
|
|
}
|
|
|
|
return (
|
|
<div className="grid gap-4 lg:grid-cols-2">
|
|
{WEEKDAYS.map((d) => {
|
|
const dayItems = itemsByDay.get(d.key) ?? []
|
|
const isToday = d.key === todayKey
|
|
return (
|
|
<Card
|
|
key={d.key}
|
|
className={cn(
|
|
isToday && "border-primary ring-1 ring-primary/30 shadow-sm"
|
|
)}
|
|
>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
|
<span>{d.label}</span>
|
|
{isToday && (
|
|
<span className="rounded-full bg-primary px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary-foreground">
|
|
Today
|
|
</span>
|
|
)}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{dayItems.length === 0 ? (
|
|
<div className="text-sm text-muted-foreground">No classes.</div>
|
|
) : (
|
|
<ScheduleList
|
|
items={dayItems.map((item) => ({
|
|
...item,
|
|
href: `/student/learning/courses/${encodeURIComponent(item.classId)}`,
|
|
}))}
|
|
variant="card"
|
|
spacingClassName="space-y-3"
|
|
/>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|