'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Loader2, CheckCircle2, AlertTriangle, Undo2 } from 'lucide-react'; import BottomSheet from '@/components/BottomSheet'; import { RUN_EVENT, collectMatched, matchInvoice, saveReviewNote, undoMatched, type InvoiceMatchResult, type MatchedLine, type ReviewLine, } from '@/lib/invoiceCheck'; interface InvoiceMatchHostProps { /** Lines just marked collected: drop them from the driver's list. */ onCollected: (ids: string[]) => void; /** Lines put back: reload the driver's list. */ onRestored: () => void; /** The sheet closed: the page asks about anything still to collect. */ onClosed?: () => void; } // Beta helper, mounted once on the driver page. Renders nothing until a PO's // "Check invoice" runs. It marks only exact matches collected (same product, // invoice quantity = ordered quantity); everything uncertain is listed for the // driver and left alone. Every auto-collected line can be undone. export default function InvoiceMatchHost({ onCollected, onRestored, onClosed }: InvoiceMatchHostProps) { const [open, setOpen] = useState(false); const [busy, setBusy] = useState(false); const [poNumber, setPoNumber] = useState(''); const [result, setResult] = useState(null); const [collected, setCollected] = useState([]); const [failedIds, setFailedIds] = useState([]); const [error, setError] = useState(''); const [noteFor, setNoteFor] = useState(null); const [noteText, setNoteText] = useState(''); const [noteBusy, setNoteBusy] = useState(false); const [noteError, setNoteError] = useState(''); const [savedNotes, setSavedNotes] = useState([]); const running = useRef(false); const run = useCallback( async (po: string) => { if (running.current) return; running.current = true; setPoNumber(po); setOpen(true); setBusy(true); setError(''); setResult(null); setCollected([]); setFailedIds([]); setNoteFor(null); setSavedNotes([]); try { const r = await matchInvoice(po); setResult(r); const done: MatchedLine[] = []; const failed: string[] = []; for (const line of r.matched) { try { await collectMatched(line); done.push(line); } catch (e) { console.warn('[invoice-match] collect failed for', line.id, e); failed.push(line.id); } } setCollected(done); setFailedIds(failed); if (done.length > 0) onCollected(done.map((l) => l.id)); } catch (e) { setError(e instanceof Error ? e.message : 'Check failed'); } finally { setBusy(false); running.current = false; } }, [onCollected], ); useEffect(() => { const handler = (e: Event) => { const po = (e as CustomEvent<{ poNumber?: string }>).detail?.poNumber; if (po) void run(po); }; window.addEventListener(RUN_EVENT, handler); return () => window.removeEventListener(RUN_EVENT, handler); }, [run]); const undo = async (lines: MatchedLine[]) => { const kept: MatchedLine[] = []; for (const line of lines) { try { await undoMatched(line); } catch (e) { console.warn('[invoice-match] undo failed for', line.id, e); kept.push(line); } } setCollected((prev) => prev.filter((l) => kept.some((k) => k.id === l.id) || !lines.some((x) => x.id === l.id))); onRestored(); }; const saveNote = async (line: ReviewLine) => { if (!noteText.trim()) { setNoteError('Write the reason first.'); return; } setNoteBusy(true); setNoteError(''); try { await saveReviewNote(line, noteText); setSavedNotes((prev) => [...prev, line.id]); setNoteFor(null); setNoteText(''); } catch (e) { console.warn('[invoice-match] note failed for', line.id, e); setNoteError('Could not save the note. Try again.'); } finally { setNoteBusy(false); } }; if (!open) return null; const close = () => { setOpen(false); onClosed?.(); }; return ( {busy && (

)} {error && (

Couldn’t check: {error}. Nothing was changed — carry on as normal.

)} {result && !busy && ( <> {collected.length > 0 && (

Marked collected ({collected.length})

    {collected.map((l) => (
  • ))}
)} {failedIds.length > 0 && (

{failedIds.length} matching item{failedIds.length === 1 ? '' : 's'} couldn’t be marked collected — collect {failedIds.length === 1 ? 'it' : 'them'} as normal.

)} {result.review.length > 0 && (

Check these yourself ({result.review.length})

    {result.review.map((l) => (