"use client"; import { FormEvent, useMemo, useState } from "react"; import { Bell, CheckCircle2, FileText, Search, Undo2 } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog } from "@/components/ui/dialog"; import { Input, Textarea } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Table } from "@/components/ui/table"; import type { ApiResult } from "@/modules/core/server/api"; import type { Notice, NoticeCenterData, NoticeInput, NoticeStatus } from "@/modules/notices/types"; import { NOTICE_STATUS_LABELS, NOTICE_STATUS_VALUES } from "@/modules/notices/types"; type NoticesWorkspaceClientProps = { canManage: boolean; initialData: NoticeCenterData; }; type DialogState = { mode: "create"; value: NoticeInput } | { mode: "edit"; id: string; value: NoticeInput }; const emptyNotice: NoticeInput = { title: "", content: "", audience: "全体员工", status: "draft", }; function formatDateTime(value: string | undefined): string { return value ? new Date(value).toLocaleString("zh-CN") : "-"; } function noticeToInput(notice: Notice): NoticeInput { return { title: notice.title, content: notice.content, audience: notice.audience, status: notice.status, }; } function badgeVariant(status: NoticeStatus): "secondary" | "success" | "warning" { if (status === "published") { return "success"; } return status === "draft" ? "warning" : "secondary"; } export function NoticesWorkspaceClient({ canManage, initialData }: NoticesWorkspaceClientProps): React.ReactElement { const [data, setData] = useState(initialData); const [query, setQuery] = useState(""); const [statusFilter, setStatusFilter] = useState<"all" | NoticeStatus>("all"); const [dialog, setDialog] = useState(); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const normalizedQuery = query.trim().toLowerCase(); const filteredNotices = useMemo( () => data.notices.filter((notice) => { const matchesQuery = !normalizedQuery || [notice.title, notice.content, notice.audience].join(" ").toLowerCase().includes(normalizedQuery); const matchesStatus = statusFilter === "all" || notice.status === statusFilter; return matchesQuery && matchesStatus; }), [data.notices, normalizedQuery, statusFilter], ); async function refreshData(): Promise { const response = await fetch("/api/notices"); const result = (await response.json()) as ApiResult<{ data: NoticeCenterData }>; if (result.success) { setData(result.data); } } async function submitDialog(event: FormEvent): Promise { event.preventDefault(); if (!dialog || !canManage) { setMessage("当前角色无权维护公告"); return; } setIsPending(true); const response = await fetch(dialog.mode === "create" ? "/api/notices" : `/api/notices/${dialog.id}`, { method: dialog.mode === "create" ? "POST" : "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(dialog.value), }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) { setDialog(undefined); await refreshData(); } } async function remove(id: string): Promise { if (!canManage) { setMessage("当前角色无权删除公告"); return; } setIsPending(true); const response = await fetch(`/api/notices/${id}`, { method: "DELETE" }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) { await refreshData(); } } async function markRead(id: string): Promise { setIsPending(true); const response = await fetch(`/api/notices/${id}`, { method: "POST" }); const result = (await response.json()) as ApiResult>; setIsPending(false); setMessage(result.reason); if (result.success) { await refreshData(); } } return (

公告通知

发布机构公告并跟踪阅读状态。

setDialog({ ...dialog, value: { ...dialog.value, title: event.target.value } })} required value={dialog.value.title} /> setDialog({ ...dialog, value: { ...dialog.value, audience: event.target.value } })} value={dialog.value.audience} />