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
126 lines
5.6 KiB
TypeScript
126 lines
5.6 KiB
TypeScript
import Link from "next/link"
|
||
import { getTranslations } from "next-intl/server"
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||
import { Badge } from "@/shared/components/ui/badge"
|
||
import { CalendarDays, CalendarX, MapPin } from "lucide-react"
|
||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||
import { cn } from "@/shared/lib/utils"
|
||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||
import { getScheduleStatus } from "@/modules/dashboard/lib/dashboard-utils"
|
||
import type { TeacherTodayScheduleItem } from "@/modules/dashboard/types"
|
||
|
||
export async function TeacherSchedule({ items }: { items: TeacherTodayScheduleItem[] }) {
|
||
const t = await getTranslations("dashboard")
|
||
const hasSchedule = items.length > 0
|
||
|
||
return (
|
||
<Card>
|
||
<CardHeader className="pb-3">
|
||
<CardTitle className="flex items-center gap-2 text-base">
|
||
<CalendarDays className="h-4 w-4 text-muted-foreground" />
|
||
{t("sections.todaySchedule")}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-0">
|
||
{!hasSchedule ? (
|
||
<EmptyState
|
||
icon={CalendarX}
|
||
title={t("empty.noClassesToday")}
|
||
description={t("empty.noClassesTodayDesc")}
|
||
action={{ label: t("quickActions.viewSchedule"), href: "/teacher/classes/schedule" }}
|
||
className="border-none h-[200px]"
|
||
/>
|
||
) : (
|
||
<ScrollArea className="h-[240px] px-6 py-2">
|
||
<div className="relative space-y-0 ml-1">
|
||
<div className="absolute left-[11px] -top-2 -bottom-2 w-px bg-border/50" />
|
||
|
||
<div className="absolute left-[11px] -top-3 h-3 w-px bg-gradient-to-t from-border/50 to-transparent" />
|
||
|
||
{items.map((item, index) => {
|
||
const status = getScheduleStatus(item.startTime, item.endTime, new Date())
|
||
const isLive = status === "live"
|
||
const isPast = status === "past"
|
||
const isLast = index === items.length - 1
|
||
|
||
return (
|
||
<div key={item.id} className="relative pl-8 py-2 first:pt-0 last:pb-0 group">
|
||
<div className={cn(
|
||
"absolute left-[7px] top-[14px] h-2.5 w-2.5 rounded-full border-2 ring-4 ring-background transition-colors z-10",
|
||
isLive ? "bg-primary border-primary" :
|
||
isPast ? "bg-muted border-muted-foreground/30" :
|
||
"bg-background border-primary"
|
||
)} />
|
||
|
||
<Link
|
||
href={`/teacher/classes/my/${encodeURIComponent(item.classId)}`}
|
||
className={cn(
|
||
"block rounded-md border p-2.5 transition-all hover:bg-muted/50",
|
||
isLive ? "bg-primary/5 border-primary/50 shadow-sm" :
|
||
isPast ? "opacity-60 grayscale bg-muted/20 border-transparent" :
|
||
"bg-card"
|
||
)}
|
||
>
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="space-y-0.5 min-w-0">
|
||
<div className="flex items-center gap-2">
|
||
<span className={cn(
|
||
"font-medium text-sm truncate",
|
||
isLive ? "text-primary" : "text-foreground"
|
||
)}>
|
||
{item.course}
|
||
</span>
|
||
{isLive && (
|
||
<Badge variant="default" className="h-4 px-1 text-[9px] animate-pulse">
|
||
{t("badge.live")}
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground truncate">
|
||
<span>{item.className}</span>
|
||
{item.location && (
|
||
<>
|
||
<span>·</span>
|
||
<span className="flex items-center">
|
||
<MapPin className="mr-0.5 h-2.5 w-2.5" />
|
||
{item.location}
|
||
</span>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className={cn(
|
||
"text-right text-xs font-medium tabular-nums whitespace-nowrap",
|
||
isLive ? "text-primary" : "text-muted-foreground"
|
||
)}>
|
||
{item.startTime}
|
||
<span className="text-[10px] opacity-70 ml-0.5">– {item.endTime}</span>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
|
||
{!isLast && (
|
||
<div className="absolute left-[11px] top-[24px] bottom-[-8px] w-px bg-border" />
|
||
)}
|
||
</div>
|
||
)
|
||
})}
|
||
|
||
{items.length > 3 ? (
|
||
<div className="text-[10px] text-center text-muted-foreground pt-2 pb-1 opacity-50">
|
||
{t("schedule.scrollForMore")}
|
||
</div>
|
||
) : (
|
||
<div className="text-[10px] text-center text-muted-foreground pt-4 pb-1 opacity-50 italic">
|
||
{t("schedule.noMoreClasses")}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</ScrollArea>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|