|
|
"use client";
|
|
|
|
|
|
import { CountdownTimer } from "@/components/quote/countdown-timer";
|
|
|
import { ErrorBanner } from "@/components/ui/error-banner";
|
|
|
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
|
|
import { WarningBanner } from "@/components/ui/warning-banner";
|
|
|
import type { QuoteDetail } from "@/lib/frontend/types";
|
|
|
import { PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE } from "@/modules/priority1/constants";
|
|
|
import { Priority1FtlScheduleCalendar } from "@/components/priority1/priority1-ftl-schedule-calendar";
|
|
|
import type { Priority1ShipmentSummary } from "@/components/priority1/priority1-ftl-schedule-calendar";
|
|
|
import { Priority1LtlQuoteOptions } from "@/components/priority1/priority1-ltl-quote-options";
|
|
|
|
|
|
type Props = {
|
|
|
quote: QuoteDetail | null;
|
|
|
loading: boolean;
|
|
|
error: string | null;
|
|
|
shipmentContext?: Priority1ShipmentSummary;
|
|
|
onRequestNewQuote?: () => void;
|
|
|
};
|
|
|
|
|
|
export function Priority1QuoteResultPanel({
|
|
|
quote,
|
|
|
loading,
|
|
|
error,
|
|
|
shipmentContext,
|
|
|
onRequestNewQuote,
|
|
|
}: Props) {
|
|
|
if (loading) {
|
|
|
return (
|
|
|
<div className="flex min-h-[320px] flex-col items-center justify-center gap-4 rounded-lg border border-neutral-200 bg-white p-8">
|
|
|
<LoadingSpinner />
|
|
|
<p className="text-sm text-neutral-600">正在获取 Priority1 报价,请稍候…</p>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (error) {
|
|
|
return (
|
|
|
<ErrorBanner>
|
|
|
{error.trim() || "暂时无法获取报价,请稍后重试。"}
|
|
|
</ErrorBanner>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (!quote) {
|
|
|
return (
|
|
|
<div className="flex min-h-[320px] items-center justify-center rounded-lg border border-dashed border-neutral-300 bg-neutral-50 p-8 text-sm text-neutral-500">
|
|
|
填写左侧表单并提交,即可查看 Priority1 官网风格的报价结果。
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (quote.status === "processing") {
|
|
|
return (
|
|
|
<div className="flex min-h-[320px] flex-col items-center justify-center gap-4 rounded-lg border border-neutral-200 bg-white p-8">
|
|
|
<LoadingSpinner />
|
|
|
<p className="text-sm text-neutral-600">报价处理中…</p>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (quote.status === "failed") {
|
|
|
const message =
|
|
|
quote.error_message?.trim() ||
|
|
|
"报价失败,请稍后重试。";
|
|
|
return (
|
|
|
<ErrorBanner
|
|
|
action={
|
|
|
onRequestNewQuote ? (
|
|
|
<button
|
|
|
type="button"
|
|
|
onClick={onRequestNewQuote}
|
|
|
className="text-sm font-medium text-error underline"
|
|
|
>
|
|
|
重新询价
|
|
|
</button>
|
|
|
) : undefined
|
|
|
}
|
|
|
>
|
|
|
{message}
|
|
|
</ErrorBanner>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (quote.error_code === PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE) {
|
|
|
return (
|
|
|
<WarningBanner
|
|
|
action={
|
|
|
onRequestNewQuote ? (
|
|
|
<button
|
|
|
type="button"
|
|
|
onClick={onRequestNewQuote}
|
|
|
className="text-sm font-medium text-amber-900 underline"
|
|
|
>
|
|
|
重新询价
|
|
|
</button>
|
|
|
) : undefined
|
|
|
}
|
|
|
>
|
|
|
<p className="font-medium">需人工跟进</p>
|
|
|
<p className="mt-1">
|
|
|
{quote.error_message ??
|
|
|
"Priority1 未返回可展示的即时报价,已记录任务供人工处理。"}
|
|
|
</p>
|
|
|
</WarningBanner>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (quote.status === "expired") {
|
|
|
return (
|
|
|
<WarningBanner
|
|
|
action={
|
|
|
onRequestNewQuote ? (
|
|
|
<button
|
|
|
type="button"
|
|
|
onClick={onRequestNewQuote}
|
|
|
className="text-sm font-medium text-amber-900 underline"
|
|
|
>
|
|
|
重新询价
|
|
|
</button>
|
|
|
) : undefined
|
|
|
}
|
|
|
>
|
|
|
<p className="font-medium">报价已过期</p>
|
|
|
<p className="mt-1">该报价已超过有效期,请重新提交询价。</p>
|
|
|
</WarningBanner>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const p1 = quote.priority1;
|
|
|
if (!p1 || p1.lines.length === 0) {
|
|
|
const legacyHint =
|
|
|
quote.source_type === "stale"
|
|
|
? "本次使用了历史缓存降级,数据格式可能不兼容,请重新询价。"
|
|
|
: quote.quotes && quote.quotes.length > 0
|
|
|
? "服务端返回了旧版报价格式,请重新部署 worker 与 API 后重试。"
|
|
|
: "RPA 未抓取到报价金额,请核对重量/等级是否合理后重试。";
|
|
|
return (
|
|
|
<WarningBanner
|
|
|
action={
|
|
|
onRequestNewQuote ? (
|
|
|
<button
|
|
|
type="button"
|
|
|
onClick={onRequestNewQuote}
|
|
|
className="text-sm font-medium text-amber-900 underline"
|
|
|
>
|
|
|
重新询价
|
|
|
</button>
|
|
|
) : undefined
|
|
|
}
|
|
|
>
|
|
|
<p className="font-medium">暂无报价</p>
|
|
|
<p className="mt-1">{legacyHint}</p>
|
|
|
</WarningBanner>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const validUntil = quote.valid_until;
|
|
|
|
|
|
return (
|
|
|
<div className="overflow-hidden rounded-lg border border-neutral-800">
|
|
|
{validUntil ? (
|
|
|
<div className="flex items-center justify-between border-b border-neutral-200 bg-neutral-50 px-4 py-2 text-xs text-neutral-600">
|
|
|
<span>报价有效期</span>
|
|
|
<CountdownTimer validUntil={validUntil} />
|
|
|
</div>
|
|
|
) : null}
|
|
|
|
|
|
{p1.display_mode === "ftl_calendar" && shipmentContext ? (
|
|
|
<Priority1FtlScheduleCalendar
|
|
|
lines={p1.lines}
|
|
|
shipment={shipmentContext}
|
|
|
onRequestNewQuote={onRequestNewQuote}
|
|
|
/>
|
|
|
) : (
|
|
|
<Priority1LtlQuoteOptions lines={p1.lines} />
|
|
|
)}
|
|
|
</div>
|
|
|
);
|
|
|
}
|