supplier-segmentation / src /components /CodeCheckDialog.tsx
brestok's picture
added feature
002fdfb
Raw History Blame Contribute Delete
7.5 kB
import React, { useRef, useState } from 'react';
import { checkLine } from '../api/client';
import { LineCheckResponse, TradeDirection } from '../api/types';
import { ErrorText, PrimaryButton, SecondaryButton, TextInput, Field, Select } from './controls';
export function CodeCheckDialog({
open,
onClose,
}: {
open: boolean;
onClose: () => void;
}) {
const [code, setCode] = useState('');
const [direction, setDirection] = useState<TradeDirection>(TradeDirection.IMPORT);
const [originCountry, setOriginCountry] = useState('');
const [destinationCountry, setDestinationCountry] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<LineCheckResponse | null>(null);
const dialogRef = useRef<HTMLDialogElement>(null);
// Sync the dialog open/close with the prop
React.useEffect(() => {
const el = dialogRef.current;
if (!el) return;
if (open && !el.open) el.showModal();
if (!open && el.open) el.close();
}, [open]);
const reset = () => {
setCode('');
setDirection(TradeDirection.IMPORT);
setOriginCountry('');
setDestinationCountry('');
setError(null);
setResult(null);
};
const handleClose = () => {
reset();
onClose();
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!code.trim()) return;
setLoading(true);
setError(null);
setResult(null);
try {
const res = await checkLine({
code: code.trim(),
direction,
originCountry: originCountry.trim() || null,
destinationCountry: destinationCountry.trim() || null,
});
setResult(res);
} catch (err: any) {
setError(err?.message ?? 'Request failed');
} finally {
setLoading(false);
}
};
if (!open) return null;
return (
<dialog
ref={dialogRef}
onClose={handleClose}
className="w-full max-w-[520px] rounded-xl border border-line-header bg-white p-0 shadow-lg backdrop:bg-black/30"
>
<div className="flex items-center justify-between border-b border-line-header px-6 py-4">
<h2 className="text-sm font-semibold">Quick code check</h2>
<button
type="button"
onClick={handleClose}
className="text-lg text-slate-400 hover:text-slate-600"
aria-label="Close"
>
&times;
</button>
</div>
<form onSubmit={handleSubmit} className="px-6 py-5">
<Field label="Commodity code" htmlFor="cc-code">
<TextInput
id="cc-code"
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="e.g. 6203 42 31"
autoFocus
/>
</Field>
<Field label="Direction" htmlFor="cc-direction">
<Select
id="cc-direction"
value={direction}
onChange={(e) => setDirection(Number(e.target.value) as TradeDirection)}
>
<option value={TradeDirection.IMPORT}>Import</option>
<option value={TradeDirection.EXPORT}>Export</option>
<option value={TradeDirection.BOTH}>Both</option>
<option value={TradeDirection.WAREHOUSE}>Warehouse</option>
</Select>
</Field>
<div className="flex gap-4">
<Field label="Origin country" htmlFor="cc-origin" className="flex-1">
<TextInput
id="cc-origin"
value={originCountry}
onChange={(e) => setOriginCountry(e.target.value)}
placeholder="e.g. CN"
/>
</Field>
<Field label="Destination country" htmlFor="cc-dest" className="flex-1">
<TextInput
id="cc-dest"
value={destinationCountry}
onChange={(e) => setDestinationCountry(e.target.value)}
placeholder="e.g. NL"
/>
</Field>
</div>
<div className="flex gap-3">
<PrimaryButton type="submit" disabled={loading || !code.trim()}>
{loading ? 'Checking...' : 'Check'}
</PrimaryButton>
<SecondaryButton onClick={handleClose}>Close</SecondaryButton>
</div>
{error ? <ErrorText>{error}</ErrorText> : null}
</form>
{result ? <CodeCheckResult result={result} /> : null}
</dialog>
);
}
const STATUS_LABELS: Record<string, string> = {
'1': 'OK',
'2': 'Ambiguous',
'3': 'Invalid',
'4': 'Expiring',
};
const CONFIDENCE_LABELS: Record<string, string> = {
'1': 'Red',
'2': 'Amber',
'3': 'Green',
};
const CONFIDENCE_COLORS: Record<string, string> = {
'1': 'text-confidence-red',
'2': 'text-confidence-amber',
'3': 'text-green-600',
};
function CodeCheckResult({ result }: { result: LineCheckResponse }) {
return (
<div className="max-h-[45vh] overflow-y-auto border-t border-line-header px-6 py-5 text-xs">
<div className="mb-3 grid grid-cols-[auto_1fr] gap-x-4 gap-y-2">
<span className="text-slate-result">Input code</span>
<span className="font-semibold">{result.inputCode}</span>
<span className="text-slate-result">Resolved code</span>
<span className="font-semibold">{result.formattedCode ?? result.resolvedCode ?? '-'}</span>
<span className="text-slate-result">Description</span>
<span>{result.codeDescription ?? '-'}</span>
<span className="text-slate-result">Status</span>
<span className="font-semibold">{STATUS_LABELS[String(result.status)] ?? result.status}</span>
<span className="text-slate-result">Code confidence</span>
<span className={`font-semibold ${CONFIDENCE_COLORS[String(result.codeConfidence)] ?? ''}`}>
{CONFIDENCE_LABELS[String(result.codeConfidence)] ?? result.codeConfidence}
{result.codeConfidenceReason ? ` — ${result.codeConfidenceReason}` : ''}
</span>
<span className="text-slate-result">Document confidence</span>
<span className={`font-semibold ${CONFIDENCE_COLORS[String(result.documentConfidence)] ?? ''}`}>
{CONFIDENCE_LABELS[String(result.documentConfidence)] ?? result.documentConfidence}
{result.documentConfidenceReason ? ` — ${result.documentConfidenceReason}` : ''}
</span>
<span className="text-slate-result">Checked on</span>
<span>{result.checkedOn}</span>
</div>
{result.warnings.length > 0 ? (
<div className="mt-3">
<span className="font-semibold">Warnings ({result.warnings.length})</span>
<ul className="mt-1 list-inside list-disc space-y-1">
{result.warnings.map((w, i) => (
<li key={i} className={w.severity >= 2 ? 'text-confidence-amber' : ''}>
{w.message}
</li>
))}
</ul>
</div>
) : null}
{result.documentsRequired.length > 0 ? (
<div className="mt-3">
<span className="font-semibold">Required documents ({result.documentsRequired.length})</span>
<ul className="mt-1 list-inside list-disc space-y-1">
{result.documentsRequired.map((d, i) => (
<li key={i}>
<span className="font-mono">{d.code}</span>
{d.description ? ` — ${d.description}` : ''}
</li>
))}
</ul>
</div>
) : null}
</div>
);
}