import { Button, Input, Modal } from "@heroui/react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import { AppModalShell } from "../../components/ui/AppModalShell"; import { FieldSelect } from "../../components/ui/FieldSelect"; import { useDebouncedValue } from "../../hooks/useDebouncedValue"; import { useToast } from "../../hooks/useToast"; import { useI18n } from "../../i18n"; import { bizCompareListTemplates, bizMonitorCreateTemplate, bizMonitorDeleteTemplate, bizMonitorListTemplates, bizMonitorUpdateTemplate, formatErr, } from "../../services/api"; type CompareTpl = { id: string; name: string }; type MonitorTpl = { id: string; name: string; compare_template_id: string; compare_template_name?: string; collect_metric_ids?: string[]; defaults?: Record; sheet_overrides?: unknown[]; note?: string; }; export function BizMonitorTemplatesPage() { const { t } = useI18n(); const { showOk, showError } = useToast(); const [items, setItems] = useState([]); const [compareTpls, setCompareTpls] = useState([]); const [busy, setBusy] = useState(false); const [listKw, setListKw] = useState(""); const debouncedKw = useDebouncedValue(listKw, 250); const [editOpen, setEditOpen] = useState(false); const [editId, setEditId] = useState(""); const [name, setName] = useState(""); const [compareId, setCompareId] = useState(""); const [note, setNote] = useState(""); const [collectText, setCollectText] = useState(""); const [defaultsText, setDefaultsText] = useState('{"dual_mode":"migrate_pair","out_of_expect":"strict"}'); const [overridesText, setOverridesText] = useState("[]"); const refresh = useCallback(async () => { const [mon, cmp] = await Promise.all([bizMonitorListTemplates(), bizCompareListTemplates()]); setItems((mon.items || []) as MonitorTpl[]); setCompareTpls( ((cmp.items || []) as Record[]).map((x) => ({ id: String(x.id || ""), name: String(x.name || x.id || ""), })), ); }, []); useEffect(() => { void (async () => { try { await refresh(); } catch (e) { showError(formatErr(e)); } })(); }, [refresh, showError]); const filtered = useMemo(() => { const kw = debouncedKw.trim().toLowerCase(); if (!kw) return items; return items.filter((row) => { const blob = `${row.name} ${row.compare_template_name || ""} ${row.note || ""} ${row.compare_template_id}`.toLowerCase(); return blob.includes(kw); }); }, [items, debouncedKw]); const openCreate = () => { setEditId(""); setName(""); setCompareId(compareTpls[0]?.id || ""); setNote(""); setCollectText(""); setDefaultsText('{"dual_mode":"migrate_pair","out_of_expect":"strict"}'); setOverridesText("[]"); setEditOpen(true); }; const openEdit = (row: MonitorTpl) => { setEditId(row.id); setName(row.name || ""); setCompareId(row.compare_template_id || ""); setNote(row.note || ""); setCollectText((row.collect_metric_ids || []).join(", ")); setDefaultsText(JSON.stringify(row.defaults || {}, null, 2)); setOverridesText(JSON.stringify(row.sheet_overrides || [], null, 2)); setEditOpen(true); }; const closeEdit = () => setEditOpen(false); const save = async () => { if (!name.trim()) { showError(t("bizMonitorTpl.needName")); return; } let defaults: Record = {}; let overrides: unknown[] = []; try { defaults = JSON.parse(defaultsText || "{}") as Record; if (!defaults || typeof defaults !== "object" || Array.isArray(defaults)) { throw new Error("defaults"); } } catch { showError(t("bizMonitorTpl.defaultsInvalid")); return; } try { overrides = JSON.parse(overridesText || "[]") as unknown[]; if (!Array.isArray(overrides)) throw new Error("overrides"); } catch { showError(t("bizMonitorTpl.overridesInvalid")); return; } const collect_metric_ids = collectText .split(/[,;\s]+/) .map((x) => x.trim()) .filter(Boolean); const body = { name: name.trim(), compare_template_id: compareId, collect_metric_ids, defaults, sheet_overrides: overrides, note: note.trim(), }; setBusy(true); try { if (editId) { await bizMonitorUpdateTemplate(editId, body); showOk(t("bizMonitorTpl.saved")); } else { await bizMonitorCreateTemplate(body); showOk(t("bizMonitorTpl.created")); } closeEdit(); await refresh(); } catch (e) { showError(formatErr(e)); } finally { setBusy(false); } }; const remove = async (id: string) => { if (!window.confirm(t("bizMonitorTpl.confirmDelete"))) return; setBusy(true); try { await bizMonitorDeleteTemplate(id); showOk(t("bizMonitorTpl.deleted")); await refresh(); } catch (e) { showError(formatErr(e)); } finally { setBusy(false); } }; return (

{t("bizMonitorTpl.title")}

{t("bizMonitorTpl.hint")}

{t("bizMonitorTpl.kpiTotal")}
{items.length}
setListKw(e.target.value)} />
{filtered.map((row) => ( ))} {!filtered.length ? ( ) : null}
{t("bizMonitorTpl.colName")} {t("bizMonitorTpl.colCompare")} {t("bizMonitorTpl.colCollect")} {t("bizMonitorTpl.colNote")} {t("bizMonitorTpl.colActions")}
{row.name}
{row.compare_template_name || row.compare_template_id || "—"} {row.compare_template_id ? (
{t("bizMonitorTpl.openCompare")}
) : null}
{(row.collect_metric_ids || []).length ? (row.collect_metric_ids || []).join(", ") : t("bizMonitorTpl.collectAll")} {row.note || "—"}
{t("bizMonitorTpl.empty")}
{editId ? t("bizMonitorTpl.edit") : t("bizMonitorTpl.create")}

{t("bizMonitorTpl.formHint")}

setCompareId(e.target.value)} fullWidth hint={t("bizMonitorTpl.compareHint")} > {compareTpls.map((c) => ( ))}