- Add leave-requests module for staff and student leave request management - Add invitation-codes module for class invitation code generation and redemption - Add standards module for curriculum standards management
118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
"use client"
|
||
|
||
import { useTranslations } from "next-intl"
|
||
import { CalendarDays, CheckCircle2, XCircle, Clock, Ban } from "lucide-react"
|
||
|
||
import { Badge } from "@/shared/components/ui/badge"
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||
import type { LeaveRequestListItem, LeaveStatus, LeaveType } from "../types"
|
||
|
||
type IconKey = "clock" | "check" | "x" | "ban"
|
||
|
||
const STATUS_STYLES: Record<LeaveStatus, { variant: "default" | "secondary" | "destructive" | "outline"; iconKey: IconKey }> = {
|
||
pending: { variant: "secondary", iconKey: "clock" },
|
||
approved: { variant: "default", iconKey: "check" },
|
||
rejected: { variant: "destructive", iconKey: "x" },
|
||
cancelled: { variant: "outline", iconKey: "ban" },
|
||
}
|
||
|
||
const STATUS_ICONS: Record<IconKey, typeof Clock> = {
|
||
clock: Clock,
|
||
check: CheckCircle2,
|
||
x: XCircle,
|
||
ban: Ban,
|
||
}
|
||
|
||
/**
|
||
* 请假申请列表(只读展示)。
|
||
*
|
||
* 适用于家长/学生查看自己的请假记录,教师查看本班请假(审批操作走 LeaveReviewDialog)。
|
||
*/
|
||
export function LeaveRequestList({
|
||
items,
|
||
emptyTitle,
|
||
emptyDescription,
|
||
}: {
|
||
items: LeaveRequestListItem[]
|
||
emptyTitle: string
|
||
emptyDescription: string
|
||
}) {
|
||
const t = useTranslations("leave")
|
||
|
||
if (items.length === 0) {
|
||
return (
|
||
<EmptyState
|
||
icon={CalendarDays}
|
||
title={emptyTitle}
|
||
description={emptyDescription}
|
||
className="border-none shadow-none"
|
||
/>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-3">
|
||
{items.map((item) => {
|
||
const meta = STATUS_STYLES[item.status]
|
||
const Icon = STATUS_ICONS[meta.iconKey]
|
||
return (
|
||
<Card key={item.id}>
|
||
<CardHeader className="pb-3">
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="space-y-1">
|
||
<CardTitle className="text-base flex items-center gap-2">
|
||
<Icon className="h-4 w-4 text-muted-foreground" aria-hidden />
|
||
<span>{item.studentName}</span>
|
||
<span className="text-muted-foreground font-normal">·</span>
|
||
<span className="text-muted-foreground font-normal">{item.className}</span>
|
||
</CardTitle>
|
||
<p className="text-sm text-muted-foreground">
|
||
{t("types." + item.leaveType)} · {item.startDate} ~ {item.endDate}
|
||
</p>
|
||
</div>
|
||
<Badge variant={meta.variant}>
|
||
{t("status." + item.status)}
|
||
</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="space-y-2">
|
||
<div className="text-sm">
|
||
<span className="text-muted-foreground">{t("list.reason")}:</span>
|
||
<span>{item.reason}</span>
|
||
</div>
|
||
{item.reviewerName && item.reviewedAt && (
|
||
<div className="text-sm text-muted-foreground">
|
||
{t("list.reviewedBy", { name: item.reviewerName, date: item.reviewedAt.slice(0, 10) })}
|
||
</div>
|
||
)}
|
||
{item.reviewComment && (
|
||
<div className="rounded-md bg-muted/50 p-2 text-sm">
|
||
<span className="text-muted-foreground">{t("list.reviewComment")}:</span>
|
||
<span>{item.reviewComment}</span>
|
||
</div>
|
||
)}
|
||
{item.attendanceSynced && (
|
||
<div className="text-xs text-emerald-600 dark:text-emerald-400">
|
||
{t("list.attendanceSynced")}
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
})}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** 请假类型中文标签(复用工具函数,避免重复定义)。 */
|
||
export function leaveTypeLabel(type: LeaveType): string {
|
||
const labels: Record<LeaveType, string> = {
|
||
sick: "病假",
|
||
personal: "事假",
|
||
family: "家庭事务",
|
||
other: "其他",
|
||
}
|
||
return labels[type] ?? type
|
||
}
|