"use client"; import { useCallback, useEffect, useState } from "react"; import { AdminLayout } from "@/components/layout/admin-layout"; import { PageHeader } from "@/components/layout/page-header"; import { Skeleton } from "@/components/ui/skeleton"; import { ErrorBanner } from "@/components/ui/error-banner"; import { SecondaryButton } from "@/components/ui/primary-button"; import { PrimaryButton } from "@/components/ui/primary-button"; import { SelectField } from "@/components/ui/select-field"; import { Card } from "@/components/ui/card"; import { ALERT_TYPE_LABEL, ALERT_TYPE_TONE, } from "@/lib/frontend/constants"; import { formatDateTime } from "@/lib/frontend/format"; import { adminGetAlerts, adminResolveAlert, } from "@/lib/frontend/api-client"; import { useAuth } from "@/hooks/use-auth"; import type { AlertRecord, AlertType, DataPageStatus } from "@/lib/frontend/types"; const PAGE_SIZE = 10; const TYPE_OPTIONS = [ { value: "", label: "全部类型" }, ...( Object.keys(ALERT_TYPE_LABEL) as AlertType[] ).map((t) => ({ value: t, label: ALERT_TYPE_LABEL[t] })), ]; const STATUS_OPTIONS = [ { value: "", label: "全部状态" }, { value: "open", label: "待处理" }, { value: "resolved", label: "已处理" }, ]; function toneClass(tone: "warning" | "error" | "neutral"): string { if (tone === "warning") return "bg-amber-100 text-warning"; if (tone === "error") return "bg-red-100 text-error"; return "bg-slate-100 text-text-secondary"; } function DetailRow({ label, value, mono, }: { label: string; value: string; mono?: boolean; }) { return (
{label}
{value}
); } export default function AlertsPage() { const { token, user } = useAuth(); const [status, setStatus] = useState("loading"); const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [typeFilter, setTypeFilter] = useState(""); const [statusFilter, setStatusFilter] = useState("open"); const [detail, setDetail] = useState(null); const [resolving, setResolving] = useState(false); const load = useCallback( async (p: number, type: string, st: string) => { if (!token) return; setStatus("loading"); const res = await adminGetAlerts("", token, p, PAGE_SIZE, type || undefined, st || undefined); if (res.code !== 0) { setStatus("error"); return; } setRows(res.data.list); setTotal(res.data.total); setStatus(res.data.total === 0 ? "empty" : "success"); }, [token], ); useEffect(() => { void load(page, typeFilter, statusFilter); }, [load, page, typeFilter, statusFilter]); const handleResolve = async (alert: AlertRecord) => { if (!token || !user) return; setResolving(true); const res = await adminResolveAlert("", token, alert.id, user.user_id); setResolving(false); if (res.code !== 0) return; setRows((list) => list.map((a) => a.id === alert.id ? { ...a, status: "resolved", resolved_at: new Date().toISOString() } : a, ), ); setDetail(null); }; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return (
{ setPage(1); setTypeFilter(e.target.value); }} />
{ setPage(1); setStatusFilter(e.target.value); }} />
} /> {status === "loading" && (
{Array.from({ length: 5 }).map((_, i) => ( ))}
)} {status === "error" && ( void load(page, typeFilter, statusFilter)}> 重试 } > 加载预警失败 )} {status === "empty" && (

暂无符合条件的预警

)} {status === "success" && (
{rows.map((row) => ( setDetail(row)} > ))}
时间 类型 客户 报价单号 状态
{formatDateTime(row.created_at)} {ALERT_TYPE_LABEL[row.alert_type]} {row.customer_id ?? "—"} {row.quote_id ?? "—"} {row.status === "open" ? ( 待处理 ) : ( 已处理 )}
共 {total} 条
setPage((p) => p - 1)} > 上一页 {page} / {totalPages} = totalPages} onClick={() => setPage((p) => p + 1)} > 下一页
)} {detail && (
!resolving && setDetail(null)} >
e.stopPropagation()} >

预警详情

根本原因

{detail.root_cause ?? "暂无详细说明"}

地址与货物

setDetail(null)}> 关闭 {detail.status === "open" && ( void handleResolve(detail)} > 标记已处理 )}
)}
); }