You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
4.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"use client";
import { Truck } from "@phosphor-icons/react";
import type { QuoteDetail, QuotePageStatus } from "@/lib/frontend/types";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { ErrorBanner } from "@/components/ui/error-banner";
import { WarningBanner } from "@/components/ui/warning-banner";
import { SecondaryButton } from "@/components/ui/primary-button";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { QuoteCard } from "@/components/quote/quote-card";
import { CountdownTimer } from "@/components/quote/countdown-timer";
interface QuoteResultPanelProps {
status: QuotePageStatus;
quote: QuoteDetail | null;
error: string | null;
onRetry: () => void;
onExpire: () => void;
onExpiredConfirm: () => void;
/** 轮询阶段提示(如 Priority1 填表较慢) */
processingHint?: string;
/** 空闲态副文案 */
idleHint?: string;
}
const STATUS_LABEL: Partial<Record<QuotePageStatus, string>> = {
resolving_address: "正在解析 MotherShip 地址联想…",
validating: "正在提交询价请求…",
processing: "正在查询报价,请稍候…",
};
function LoadingProgressCard({
title,
hint,
}: {
title: string;
hint?: string;
}) {
return (
<Card>
<p className="mb-3 text-sm font-medium text-text-secondary">{title}</p>
{hint ? (
<p className="mb-4 text-xs text-text-secondary">{hint}</p>
) : null}
<div className="mb-4 h-2 w-full overflow-hidden rounded-full bg-bg">
<div className="h-full w-full animate-pulse rounded-full bg-primary/50" />
</div>
<div className="space-y-3">
<Skeleton className="h-8 w-1/3" />
<Skeleton className="h-12 w-2/3" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
</div>
</Card>
);
}
export function QuoteResultPanel({
status,
quote,
error,
onRetry,
onExpire,
onExpiredConfirm,
processingHint,
idleHint,
}: QuoteResultPanelProps) {
if (status === "resolving_address") {
return (
<LoadingProgressCard
title={STATUS_LABEL.resolving_address!}
hint="若出现多个精确坐标,将弹出地址确认窗口,请逐项选择后再报价"
/>
);
}
if (status === "validating" || status === "processing") {
return (
<LoadingProgressCard
title={
STATUS_LABEL[status] ?? STATUS_LABEL.processing!
}
hint={
status === "processing"
? processingHint ??
"通常 10~30 秒;若超过 2 分钟仍在等待,请查看 RPA Worker 日志或重启 worker"
: undefined
}
/>
);
}
if (status === "error") {
return (
<ErrorBanner
action={
<SecondaryButton onClick={onRetry}></SecondaryButton>
}
>
{error ?? "暂时无法获取报价,请稍后重试"}
</ErrorBanner>
);
}
if (status === "expired") {
return (
<>
<Card className="relative opacity-60">
{quote && <QuoteCard quote={quote} />}
</Card>
<ConfirmDialog
open
title="报价已过期"
message="当前报价已超过有效期,请重新询价。"
confirmLabel="重新询价"
onConfirm={onExpiredConfirm}
onCancel={onExpiredConfirm}
/>
</>
);
}
if (status === "manual_followup") {
return (
<WarningBanner
action={
<SecondaryButton onClick={onRetry}></SecondaryButton>
}
>
{error ?? "表单已提交,站点判定需人工确认细节后报价"}
</WarningBanner>
);
}
if ((status === "success" || status === "fallback") && quote) {
return (
<div className="space-y-4">
{status === "fallback" && (
<WarningBanner>
使
</WarningBanner>
)}
<QuoteCard quote={quote} />
{quote.valid_until && (
<div className="flex justify-end">
<CountdownTimer
validUntil={quote.valid_until}
onExpire={onExpire}
/>
</div>
)}
</div>
);
}
return (
<Card className="flex min-h-[280px] flex-col items-center justify-center text-center">
<Truck size={48} weight="thin" className="text-text-disabled" />
<p className="mt-3 text-base font-medium text-text-primary">
</p>
<p className="mt-1 text-sm text-text-secondary">
{idleHint ?? "提交后将返回 MotherShip 全部可用运价档位"}
</p>
</Card>
);
}