feat(parent): add attention banner, export button, and grade detail

- 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
This commit is contained in:
SpecialX
2026-06-23 17:37:49 +08:00
parent 95145cd03b
commit 1abf58c0b6
9 changed files with 632 additions and 31 deletions

View File

@@ -3,22 +3,97 @@ 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 type { ChildScheduleItem } from "@/modules/parent/types"
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}&apos;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" />
<CalendarDays className="h-4 w-4 text-muted-foreground" aria-hidden />
{childName}&apos;s Today Schedule
</CardTitle>
</CardHeader>