"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)}
>
标记已处理
)}
)}
);
}