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/quote/address-disambiguation-moda...

195 lines
5.8 KiB

"use client";
import { useEffect, useState } from "react";
import type { MothershipAddressCandidate } from "@/lib/frontend/types";
import { PrimaryButton } from "@/components/ui/primary-button";
export interface AddressDisambiguationModalProps {
open: boolean;
pickupCandidates: MothershipAddressCandidate[];
deliveryCandidates: MothershipAddressCandidate[];
onConfirm: (pickup: MothershipAddressCandidate, delivery: MothershipAddressCandidate) => void;
onCancel: () => void;
}
function isCandidateSelectable(item: MothershipAddressCandidate): boolean {
return item.selectable !== false;
}
function pickDefaultId(
candidates: MothershipAddressCandidate[],
): string | null {
if (candidates.length === 0) {
return null;
}
const selectable = candidates.filter(isCandidateSelectable);
if (selectable.length === 1) {
return selectable[0]!.option_id;
}
return null;
}
function findSelectableById(
candidates: MothershipAddressCandidate[],
id: string | null,
): MothershipAddressCandidate | null {
if (!id) {
return null;
}
const hit = candidates.find((c) => c.option_id === id);
if (!hit || !isCandidateSelectable(hit)) {
return null;
}
return hit;
}
function CandidateGroup({
title,
candidates,
selectedId,
onSelect,
}: {
title: string;
candidates: MothershipAddressCandidate[];
selectedId: string | null;
onSelect: (id: string) => void;
}) {
if (candidates.length === 0) {
return null;
}
const multi = candidates.length > 1;
return (
<fieldset className="space-y-2">
<legend className="text-sm font-medium text-text-primary">{title}</legend>
<p className="text-xs text-text-secondary">
{multi
? "MotherShip 提供多个精确坐标,请选择与实际位置一致的一项"
: "请确认以下 MotherShip 解析结果是否与实际位置一致"}
</p>
<ul className="space-y-2">
{candidates.map((item) => {
const disabled = !isCandidateSelectable(item);
return (
<li key={item.option_id}>
<label
className={`flex items-start gap-3 rounded-md border border-border p-3 ${
disabled
? "cursor-not-allowed bg-surface-muted opacity-70"
: "cursor-pointer hover:bg-surface-muted"
}`}
>
<input
type="radio"
name={title}
className="mt-1"
disabled={disabled}
checked={!disabled && selectedId === item.option_id}
onChange={() => onSelect(item.option_id)}
/>
<span className="text-sm text-text-primary">
<span className="block">{item.display_label}</span>
{item.unavailable_reason ? (
<span className="mt-1 block text-xs text-error">
{item.unavailable_reason}
</span>
) : null}
</span>
</label>
</li>
);
})}
</ul>
</fieldset>
);
}
export function AddressDisambiguationModal({
open,
pickupCandidates,
deliveryCandidates,
onConfirm,
onCancel,
}: AddressDisambiguationModalProps) {
const [pickupId, setPickupId] = useState<string | null>(null);
const [deliveryId, setDeliveryId] = useState<string | null>(null);
useEffect(() => {
if (!open) {
return;
}
setPickupId(pickDefaultId(pickupCandidates));
setDeliveryId(pickDefaultId(deliveryCandidates));
}, [open, pickupCandidates, deliveryCandidates]);
if (!open) {
return null;
}
const pickup = findSelectableById(pickupCandidates, pickupId);
const delivery = findSelectableById(deliveryCandidates, deliveryId);
const canConfirm = pickup != null && delivery != null;
const handleConfirm = () => {
if (!pickup || !delivery) {
return;
}
onConfirm(pickup, delivery);
};
const hasMultiple =
pickupCandidates.length > 1 || deliveryCandidates.length > 1;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
role="dialog"
aria-modal="true"
aria-labelledby="ms-address-title"
>
<div className="max-h-[90vh] w-full max-w-lg overflow-y-auto rounded-lg border border-border bg-surface p-6 shadow-card">
<h2
id="ms-address-title"
className="text-lg font-semibold text-text-primary"
>
MotherShip
</h2>
<p className="mt-2 text-sm text-text-secondary">
{hasMultiple
? "系统检测到 MotherShip 对您的地址有多个精确坐标。错误选择会导致报价不准,请逐项确认后再获取报价。"
: "请确认 MotherShip 解析的提货/派送地址是否准确,确认无误后再获取报价。"}
</p>
<div className="mt-6 space-y-6">
<CandidateGroup
title="提货地址"
candidates={pickupCandidates}
selectedId={pickupId}
onSelect={setPickupId}
/>
<CandidateGroup
title="派送地址"
candidates={deliveryCandidates}
selectedId={deliveryId}
onSelect={setDeliveryId}
/>
</div>
<div className="mt-6 flex flex-wrap gap-3">
<PrimaryButton disabled={!canConfirm} onClick={handleConfirm}>
</PrimaryButton>
<button
type="button"
className="rounded-md border border-border px-4 py-2 text-sm text-text-secondary hover:bg-surface-muted"
onClick={onCancel}
>
</button>
</div>
</div>
</div>
);
}