|
|
"use client";
|
|
|
|
|
|
import { P1_RESULT_UI } from "@/lib/priority1/ui-labels";
|
|
|
import type { Priority1DisplayLine } from "@/modules/priority1/quote-storage";
|
|
|
|
|
|
type Props = {
|
|
|
lines: Priority1DisplayLine[];
|
|
|
};
|
|
|
|
|
|
function carrierInitials(carrier: string): string {
|
|
|
const parts = carrier.trim().split(/\s+/).filter(Boolean);
|
|
|
if (parts.length >= 2) {
|
|
|
return (parts[0]![0]! + parts[1]![0]!).toUpperCase();
|
|
|
}
|
|
|
return carrier.slice(0, 2).toUpperCase();
|
|
|
}
|
|
|
|
|
|
function formatUsd(n: number): string {
|
|
|
return new Intl.NumberFormat("en-US", {
|
|
|
style: "currency",
|
|
|
currency: "USD",
|
|
|
minimumFractionDigits: 2,
|
|
|
}).format(n);
|
|
|
}
|
|
|
|
|
|
function formatDisplayDate(raw?: string | null): string {
|
|
|
if (!raw) return P1_RESULT_UI.dateUnknown;
|
|
|
const trimmed = raw.trim();
|
|
|
const dash = trimmed.match(/^(\d{2})-(\d{2})-(\d{4})$/);
|
|
|
if (dash) return `${dash[1]}-${dash[2]}-${dash[3]}`;
|
|
|
const slash = trimmed.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
|
|
if (slash) return `${slash[1]}-${slash[2]}-${slash[3]}`;
|
|
|
const iso = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
|
if (iso) return `${iso[2]}-${iso[3]}-${iso[1]}`;
|
|
|
return trimmed;
|
|
|
}
|
|
|
|
|
|
function transitLabel(days?: number): string {
|
|
|
if (days == null || days <= 0) return P1_RESULT_UI.transitUnknown;
|
|
|
return P1_RESULT_UI.transit(days);
|
|
|
}
|
|
|
|
|
|
function serviceLabel(level?: string): string {
|
|
|
if (!level?.trim()) return P1_RESULT_UI.standardLtl;
|
|
|
if (/standard\s*ltl/i.test(level)) return P1_RESULT_UI.standardLtl;
|
|
|
return level;
|
|
|
}
|
|
|
|
|
|
function QuoteCard({ line }: { line: Priority1DisplayLine }) {
|
|
|
const displayUsd = line.totalUsd > 0 ? line.totalUsd : line.final_total_usd;
|
|
|
|
|
|
return (
|
|
|
<article className="flex overflow-hidden rounded border border-neutral-700 bg-neutral-900">
|
|
|
<div className="flex w-[34%] min-w-[140px] flex-col items-center justify-center gap-3 bg-neutral-800 px-4 py-6">
|
|
|
<div className="flex h-16 w-16 items-center justify-center rounded bg-white text-lg font-bold text-neutral-900">
|
|
|
{carrierInitials(line.carrier)}
|
|
|
</div>
|
|
|
<p className="text-3xl font-bold tracking-tight text-[#f5d000]">
|
|
|
{formatUsd(displayUsd)}
|
|
|
</p>
|
|
|
</div>
|
|
|
<div className="flex flex-1 flex-col gap-3 p-5">
|
|
|
<div className="flex items-start justify-between gap-3">
|
|
|
<div className="flex items-center gap-2">
|
|
|
<h3 className="text-sm font-bold tracking-wide text-white">
|
|
|
{serviceLabel(line.serviceLevel)}
|
|
|
</h3>
|
|
|
<span
|
|
|
className="inline-flex h-4 w-4 items-center justify-center rounded-full border border-neutral-500 text-[10px] text-neutral-300"
|
|
|
title="服务说明"
|
|
|
aria-hidden
|
|
|
>
|
|
|
i
|
|
|
</span>
|
|
|
</div>
|
|
|
<span className="rounded bg-neutral-800 px-2 py-0.5 text-[10px] font-semibold tracking-wider text-neutral-300">
|
|
|
{P1_RESULT_UI.quoteBadge(line.rank)}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div className="flex flex-wrap items-baseline justify-between gap-2">
|
|
|
<p className="text-base text-white">{line.carrier}</p>
|
|
|
<p className="text-sm text-neutral-300">{transitLabel(line.transitDays)}</p>
|
|
|
</div>
|
|
|
<div className="space-y-1 text-sm text-neutral-300">
|
|
|
<p>
|
|
|
{P1_RESULT_UI.expiration}:{formatDisplayDate(line.expirationDate)}
|
|
|
</p>
|
|
|
<p>
|
|
|
{P1_RESULT_UI.delivery}:{formatDisplayDate(line.deliveryDate)}
|
|
|
</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</article>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export function Priority1LtlQuoteOptions({ lines }: Props) {
|
|
|
const sorted = [...lines].sort((a, b) => a.rank - b.rank);
|
|
|
|
|
|
return (
|
|
|
<div className="space-y-5 bg-black p-6 text-white">
|
|
|
<h2 className="text-2xl font-bold tracking-wide">{P1_RESULT_UI.title}</h2>
|
|
|
<div className="rounded border border-neutral-800 bg-neutral-900/80 p-4 text-sm leading-relaxed text-neutral-300">
|
|
|
{P1_RESULT_UI.intro}
|
|
|
</div>
|
|
|
<div className="space-y-4">
|
|
|
{sorted.map((line) => (
|
|
|
<QuoteCard key={`${line.rank}-${line.carrier}-${line.totalUsd}`} line={line} />
|
|
|
))}
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|