Spaces:
Sleeping
Sleeping
Download src/components/CodeCheckDialog.tsx from brestok/supplier-segmentation: direct link, hf CLI and curl.
- Browser
- Download file 7.5 kB
-
https://huggingface.co/spaces/brestok/supplier-segmentation/resolve/main/src/components/CodeCheckDialog.tsx
- Command line
-
hf download hf://spaces/brestok/supplier-segmentation/src/components/CodeCheckDialog.tsx
-
curl -L -o CodeCheckDialog.tsx https://huggingface.co/spaces/brestok/supplier-segmentation/resolve/main/src/components/CodeCheckDialog.tsx
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" | |
| > | |
| × | |
| </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> | |
| ); | |
| } |