Files
NextEdu/src/modules/announcements/components/announcement-card.tsx
SpecialX fde711ce46 feat(announcements,messaging): 公告与消息模块审计重构 — i18n + Error Boundary + a11y
- 新增审计报告 docs/architecture/audit/announcements-messages-audit-report.md
- 新增中英双语 i18n 字典 announcements.json / messages.json(11/13 个命名空间)
- 重构所有 announcements 和 messaging 组件接入 next-intl(useTranslations)
- 所有页面 page.tsx 使用 generateMetadata + getTranslations 替代硬编码 metadata
- 新增 7 个 error.tsx 错误边界(4 公告 + 3 消息),统一 EmptyState + i18n + 重试
- a11y 改进:announcement-card / message-list / notification-dropdown 添加 aria-label
- 同步架构图 004 和 005:i18n.messages 清单 + 已知问题修复记录
2026-06-22 16:02:07 +08:00

65 lines
2.0 KiB
TypeScript

"use client"
import Link from "next/link"
import { useTranslations } from "next-intl"
import { Badge } from "@/shared/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { formatDate } from "@/shared/lib/utils"
import type { Announcement } from "../types"
export function AnnouncementCard({
announcement,
href,
}: {
announcement: Announcement
href?: string
}) {
const t = useTranslations("announcements")
const statusVariant: Record<Announcement["status"], "default" | "secondary" | "outline"> = {
draft: "secondary",
published: "default",
archived: "outline",
}
const card = (
<Card className="h-full transition-colors hover:bg-accent/50">
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0">
<CardTitle className="line-clamp-2 text-base">{announcement.title}</CardTitle>
<Badge variant={statusVariant[announcement.status]} className="shrink-0">
{t(`status.${announcement.status}`)}
</Badge>
</CardHeader>
<CardContent className="space-y-2">
<p className="line-clamp-3 text-sm text-muted-foreground whitespace-pre-wrap">
{announcement.content}
</p>
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
<Badge variant="outline" className="capitalize">
{t(`type.${announcement.type}`)}
</Badge>
<span>
{announcement.publishedAt
? t("meta.publishedAt", { date: formatDate(announcement.publishedAt) })
: t("meta.updatedAt", { date: formatDate(announcement.updatedAt) })}
</span>
{announcement.authorName ? (
<span className="ml-auto">{t("meta.author", { name: announcement.authorName })}</span>
) : null}
</div>
</CardContent>
</Card>
)
if (href) {
return (
<Link href={href} className="block h-full" aria-label={announcement.title}>
{card}
</Link>
)
}
return card
}