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 清单 + 已知问题修复记录
This commit is contained in:
@@ -1,32 +1,13 @@
|
||||
"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"
|
||||
|
||||
const STATUS_LABEL: Record<Announcement["status"], string> = {
|
||||
draft: "Draft",
|
||||
published: "Published",
|
||||
archived: "Archived",
|
||||
}
|
||||
|
||||
const STATUS_VARIANT: Record<
|
||||
Announcement["status"],
|
||||
"default" | "secondary" | "outline"
|
||||
> = {
|
||||
draft: "secondary",
|
||||
published: "default",
|
||||
archived: "outline",
|
||||
}
|
||||
|
||||
const TYPE_LABEL: Record<Announcement["type"], string> = {
|
||||
school: "School",
|
||||
grade: "Grade",
|
||||
class: "Class",
|
||||
}
|
||||
|
||||
export function AnnouncementCard({
|
||||
announcement,
|
||||
href,
|
||||
@@ -34,12 +15,20 @@ export function AnnouncementCard({
|
||||
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={STATUS_VARIANT[announcement.status]} className="shrink-0">
|
||||
{STATUS_LABEL[announcement.status]}
|
||||
<Badge variant={statusVariant[announcement.status]} className="shrink-0">
|
||||
{t(`status.${announcement.status}`)}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -48,15 +37,15 @@ export function AnnouncementCard({
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||
<Badge variant="outline" className="capitalize">
|
||||
{TYPE_LABEL[announcement.type]}
|
||||
{t(`type.${announcement.type}`)}
|
||||
</Badge>
|
||||
<span>
|
||||
{announcement.publishedAt
|
||||
? `Published ${formatDate(announcement.publishedAt)}`
|
||||
: `Updated ${formatDate(announcement.updatedAt)}`}
|
||||
? t("meta.publishedAt", { date: formatDate(announcement.publishedAt) })
|
||||
: t("meta.updatedAt", { date: formatDate(announcement.updatedAt) })}
|
||||
</span>
|
||||
{announcement.authorName ? (
|
||||
<span className="ml-auto">by {announcement.authorName}</span>
|
||||
<span className="ml-auto">{t("meta.author", { name: announcement.authorName })}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -65,7 +54,7 @@ export function AnnouncementCard({
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href} className="block h-full">
|
||||
<Link href={href} className="block h-full" aria-label={announcement.title}>
|
||||
{card}
|
||||
</Link>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user