/* ===== Excel upload modal — column mapping + row validation ===== */ const ExcelUploadModal = ({ open, onClose, onConfirm }) => { const [step, setStep] = React.useState(2); // skip to mapping for demo if (!open) return null; return (
e.stopPropagation()}> {/* header */}
Import cargo manifest
shanghai_export_w19.xlsx · 3 sheets · 142 rows
{/* body */}
{step === 0 && setStep(1)} />} {step === 1 && setStep(2)} />} {step === 2 && setStep(3)} />} {step === 3 && { onConfirm(); }} />}
{/* footer */}
{step < 3 ? ( ) : ( )}
); }; const Stepper = ({ step, steps }) => (
{steps.map((s, i) => ( {i : null} {s} {i›} ))}
); const UploadDrop = ({ onNext }) => (
Drop your file here or browse
.xlsx, .xls, .csv up to 25 MB
); const SheetPicker = ({ onNext }) => (
Detected sheets
{['cargo_manifest','rules','category_matrix'].map((n, i) => ( ))}
); const ColumnMapper = ({ onNext }) => { const COLS = [ { their: 'SKU_CODE', ours: 'sku', conf: 0.99, sample: 'GD-1029' }, { their: 'Description', ours: 'desc', conf: 0.96, sample: 'Loose box · Tools' }, { their: 'Lenght (mm)', ours: 'L', conf: 0.92, sample: '380' }, { their: 'Width-mm', ours: 'W', conf: 0.94, sample: '240' }, { their: 'Hght', ours: 'H', conf: 0.78, sample: '220' }, { their: 'WGT (kg)', ours: 'weight', conf: 0.95, sample: '11' }, { their: 'QTY', ours: 'qty', conf: 0.99, sample: '6' }, { their: 'Cat.', ours: 'category', conf: 0.71, sample: 'general' }, { their: 'STK', ours: 'stackable', conf: 0.42, sample: 'Y' }, { their: 'STOP_NO', ours: 'stop', conf: 0.88, sample: '2' }, { their: 'orient', ours: 'orientation', conf: 0.86, sample: 'upright' }, ]; return (
Column mapping
We auto-matched 11 of 11 columns. Review and confirm.
{COLS.map(c => { const tone = c.conf >= 0.9 ? 'ok' : c.conf >= 0.7 ? 'warn' : 'bad'; return ( ); })}
Your column Sample Mojro field Confidence
{c.their} {c.sample}
{Math.round(c.conf*100)}%
); }; const RowValidator = ({ onConfirm }) => { const ROWS = [ { row: 1, status:'ok', sku:'GD-1029', qty: 6, L: 380, W: 240, H: 220, weight:11, err: null }, { row: 2, status:'ok', sku:'BX-LRG-01',qty:14, L: 600, W: 400, H: 400, weight:22, err: null }, { row: 3, status:'warn', sku:'PP-3M-DN50',qty:12, L: 3000,W: 60, H: 60, weight:18, err: 'Length 3000mm exceeds preferred carton; will fit container only.' }, { row: 4, status:'ok', sku:'BR-200L', qty: 4, L: 580, W: 580, H: 880, weight:196, err: null }, { row: 5, status:'bad', sku:'BX-?', qty: '?',L: '?', W: 400, H: 280, weight:14, err: 'Missing required: sku, qty, L. Skip row or fix.' }, { row: 6, status:'ok', sku:'PLT-EUR-A',qty: 8, L:1200, W: 800, H:1450, weight:720, err: null }, { row: 7, status:'warn', sku:'PLT-EUR-C',qty: 4, L:1200, W: 800, H:1300, weight:540, err: 'Category "Hazmat" requires segregation distance — applied automatically.' }, ]; const counts = ROWS.reduce((a, r) => (a[r.status]++, a), { ok:0, warn:0, bad:0 }); return (
Row validation
{ROWS.length} rows scanned in cargo_manifest
{counts.ok} valid {counts.warn} warnings {counts.bad} blocking
{ROWS.map(r => ( ))}
Row Status SKU Qty L×W×H (mm) Weight (kg) Notes
{r.row} {r.status==='ok' && OK} {r.status==='warn' && warn} {r.status==='bad' && error} {r.sku} {r.qty} {r.L}×{r.W}×{r.H} {r.weight} {r.err || }
Importing will skip 1 blocking row and apply 2 auto-corrections. You can fix and re-import the skipped row at any time.
); }; window.ExcelUploadModal = ExcelUploadModal;