- Add parent-attention-banner for highlighting items needing attention - Add parent-export-button for data export capability - Add child-grade-detail component for detailed grade viewing - Update existing child-card, child-detail-header, child-grade-summary, child-schedule-card - Update parent-children-data-page and data-access
119 lines
3.9 KiB
TypeScript
119 lines
3.9 KiB
TypeScript
import { CalendarDays, CalendarX } from "lucide-react"
|
|
|
|
import { ScheduleList } from "@/shared/components/schedule/schedule-list"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
|
import { EmptyState } from "@/shared/components/ui/empty-state"
|
|
import { cn } from "@/shared/lib/utils"
|
|
import type { ChildScheduleItem, ChildWeeklyScheduleItem } from "@/modules/parent/types"
|
|
|
|
const WEEKDAY_LABELS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] as const
|
|
|
|
const toWeekdayIndex = (w: 1 | 2 | 3 | 4 | 5 | 6 | 7): 0 | 1 | 2 | 3 | 4 | 5 | 6 =>
|
|
(w - 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
|
|
const isToday = (w: 1 | 2 | 3 | 4 | 5 | 6 | 7): boolean => {
|
|
const today = new Date().getDay()
|
|
const normalizedToday = today === 0 ? 7 : today
|
|
return w === normalizedToday
|
|
}
|
|
|
|
export function ChildScheduleCard({
|
|
items,
|
|
childName,
|
|
weeklyItems,
|
|
}: {
|
|
items: ChildScheduleItem[]
|
|
childName: string
|
|
/** 完整周课表(可选)。提供时切换为周课表视图。 */
|
|
weeklyItems?: ChildWeeklyScheduleItem[]
|
|
}) {
|
|
const hasSchedule = items.length > 0
|
|
const hasWeekly = weeklyItems && weeklyItems.length > 0
|
|
|
|
if (hasWeekly) {
|
|
const grouped: Record<number, ChildWeeklyScheduleItem[]> = {}
|
|
for (const item of weeklyItems!) {
|
|
const key = item.weekday
|
|
if (!grouped[key]) grouped[key] = []
|
|
grouped[key].push(item)
|
|
}
|
|
const weekdays = Object.keys(grouped).map(Number).sort((a, b) => a - b) as Array<1 | 2 | 3 | 4 | 5 | 6 | 7>
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<CalendarDays className="h-4 w-4 text-muted-foreground" aria-hidden />
|
|
{childName}'s Weekly Schedule
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{weekdays.map((w) => {
|
|
const dayItems = grouped[w]
|
|
const today = isToday(w)
|
|
const label = WEEKDAY_LABELS[toWeekdayIndex(w)]
|
|
return (
|
|
<div
|
|
key={w}
|
|
className={cn(
|
|
"rounded-md border p-3",
|
|
today && "border-primary/40 bg-primary/5",
|
|
)}
|
|
>
|
|
<div className="mb-2 flex items-center gap-2">
|
|
<span className="text-sm font-medium">{label}</span>
|
|
{today ? (
|
|
<span className="rounded-full bg-primary/15 px-2 py-0.5 text-[10px] font-medium text-primary">
|
|
Today
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
<ScheduleList
|
|
items={dayItems.map((d) => ({
|
|
id: d.id,
|
|
classId: d.classId,
|
|
className: d.className,
|
|
course: d.course,
|
|
startTime: d.startTime,
|
|
endTime: d.endTime,
|
|
location: d.location ?? null,
|
|
}))}
|
|
variant="separator"
|
|
spacingClassName="space-y-2"
|
|
/>
|
|
</div>
|
|
)
|
|
})}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<CalendarDays className="h-4 w-4 text-muted-foreground" aria-hidden />
|
|
{childName}'s Today Schedule
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{!hasSchedule ? (
|
|
<EmptyState
|
|
icon={CalendarX}
|
|
title="No classes today"
|
|
description="The timetable is clear for today."
|
|
className="border-none h-48"
|
|
/>
|
|
) : (
|
|
<ScheduleList
|
|
items={items}
|
|
variant="separator"
|
|
spacingClassName="space-y-3"
|
|
/>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|