"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 { SelectField } from "@/components/ui/select-field"; import { Card } from "@/components/ui/card"; import { formatDateTime } from "@/lib/frontend/format"; import { adminGetQueryLogs } from "@/lib/frontend/api-client"; import { useAuth } from "@/hooks/use-auth"; import { buildQueryLogCargoCopy, buildQueryLogDeliveryCopy, buildQueryLogFullCopy, buildQueryLogPickupCopy, buildQueryLogQuotesCopy, formatQueryLogQuoteLine, preferredAdminAddress, } from "@/lib/frontend/query-log-copy"; import type { DataPageStatus, QuoteQueryLogQuotePreview, QuoteQueryLogRecord, QuoteQueryOutcome, } from "@/lib/frontend/types"; const PAGE_SIZE = 20; const OUTCOME_OPTIONS = [ { value: "", label: "全部结果" }, { value: "success", label: "成功" }, { value: "failed", label: "失败" }, { value: "stale", label: "降级缓存" }, { value: "processing", label: "进行中" }, ]; const OUTCOME_LABEL: Record = { success: "成功", failed: "失败", stale: "降级缓存", processing: "进行中", }; function outcomeBadgeClass(outcome: QuoteQueryOutcome): string { if (outcome === "success") return "bg-emerald-50 text-success"; if (outcome === "failed") return "bg-red-50 text-error"; if (outcome === "stale") return "bg-amber-50 text-warning"; return "bg-slate-100 text-text-secondary"; } function formatQuotedTotal(value: number | null | undefined): string { if (typeof value !== "number" || !Number.isFinite(value)) { return "—"; } return `USD ${value.toFixed(2)}`; } function quotesOf(row: QuoteQueryLogRecord): QuoteQueryLogQuotePreview[] { return Array.isArray(row.quotes) ? row.quotes : []; } function formatQuotedSummary(row: QuoteQueryLogRecord): { primary: string; secondary: string | null; } { const quotes = quotesOf(row); if (quotes.length > 1) { return { primary: `${quotes.length} 档`, secondary: `最低 ${formatQuotedTotal(row.quoted_total)}`, }; } if (quotes.length === 1) { return { primary: formatQuotedTotal(quotes[0]?.final_total ?? row.quoted_total), secondary: quotes[0]?.carrier?.trim() || null, }; } return { primary: formatQuotedTotal(row.quoted_total), secondary: null }; } function displayOrDash(value: string | null | undefined): string { const t = (value ?? "").trim(); return t || "—"; } async function copyText(value: string): Promise { const text = value.trim(); if (!text) return false; try { await navigator.clipboard.writeText(text); return true; } catch { return false; } } function CopyButton({ text, label = "复制", }: { text: string; label?: string; }) { const [copied, setCopied] = useState(false); const disabled = !text.trim(); return ( ); } function DetailField({ label, value, copyTextValue, copyLabel, mono, }: { label: string; value: string; copyTextValue?: string; copyLabel?: string; mono?: boolean; }) { return (
{label}
{copyTextValue ? ( ) : null}
{value}
); } export default function QueryLogsPage() { const { token } = useAuth(); const [status, setStatus] = useState("loading"); const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [outcomeFilter, setOutcomeFilter] = useState(""); const [detail, setDetail] = useState(null); const load = useCallback( async (p: number, outcome: string) => { if (!token) return; setStatus("loading"); const res = await adminGetQueryLogs( "", token, p, PAGE_SIZE, undefined, undefined, outcome || 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, outcomeFilter); }, [load, page, outcomeFilter]); const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return ( { setPage(1); setOutcomeFilter(e.target.value); }} /> } /> {status === "loading" && (
{Array.from({ length: 6 }).map((_, i) => ( ))}
)} {status === "error" && ( void load(page, outcomeFilter)}> 重试 } > 加载查价记录失败 )} {status === "empty" && (

暂无查价记录

)} {status === "success" && (
{rows.map((row) => { const pickup = preferredAdminAddress( row.pickup_selected, row.pickup_customer, ); const delivery = preferredAdminAddress( row.delivery_selected, row.delivery_customer, ); return ( ); })}
查询时间 客户 线路 结果 报价 操作
{formatDateTime(row.created_at)} {row.customer_id}

提 {pickup || "—"}

送 {delivery || "—"}

{OUTCOME_LABEL[row.outcome]} {(() => { const summary = formatQuotedSummary(row); return ( <>

{summary.primary}

{summary.secondary ? (

{summary.secondary}

) : null} ); })()}
共 {total} 条(系统最多保留 1000 条)
setPage((p) => p - 1)} > 上一页 {page} / {totalPages} = totalPages} onClick={() => setPage((p) => p + 1)} > 下一页
)} {detail && (
setDetail(null)} >
e.stopPropagation()} role="dialog" aria-modal aria-labelledby="query-log-detail-title" >

询价详情

可复制完整地址与货物参数

报价列表 {quotesOf(detail).length > 0 ? `(${quotesOf(detail).length} 档)` : ""}
{quotesOf(detail).length > 0 ? ( ) : null}
{quotesOf(detail).length === 0 ? (

{detail.quoted_total != null ? `${displayOrDash(detail.quoted_carrier)} · ${formatQuotedTotal(detail.quoted_total)}` : "—"}

) : ( quotesOf(detail).map((q, i) => (

{formatQueryLogQuoteLine(q)}

)) )}
setDetail(null)}> 关闭
)}
); }