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.
chajia/components/priority1/priority1-ltl-quote-options...

129 lines
4.5 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 { P1_RESULT_UI } from "@/lib/priority1/ui-labels";
import {
priority1CustomerDisplayUsd,
priority1HasMarkup,
} from "@/lib/priority1/display-price";
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 = priority1CustomerDisplayUsd(line);
const showMarkup = priority1HasMarkup(line);
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>
{showMarkup ? (
<p className="text-xs text-neutral-400 line-through">
{formatUsd(line.totalUsd)}
</p>
) : null}
<p className="text-3xl font-bold tracking-tight text-[#f5d000]">
{formatUsd(displayUsd)}
</p>
{showMarkup ? (
<p className="text-center text-[10px] text-neutral-400">
{P1_RESULT_UI.includesMarkup(formatUsd(line.markup_amount))}
</p>
) : null}
</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>
);
}