'use client'; import { useState, useEffect } from 'react'; import { toast } from 'sonner'; import { CheckCircle2, Circle, Loader2, AlertTriangle, CalendarDays } from 'lucide-react'; import { api } from '@/api'; import { cn, fmt, fmtDate } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog'; const STATUS_META = { unmatched: { label: 'Unmatched', className: 'text-muted-foreground', icon: null }, matched_this_bill:{ label: 'Already linked', className: 'text-emerald-600 dark:text-emerald-400', icon: CheckCircle2 }, matched_other_bill:{ label: null, className: 'text-amber-600 dark:text-amber-400', icon: AlertTriangle }, payment_exists: { label: 'Payment exists', className: 'text-emerald-600 dark:text-emerald-400', icon: CheckCircle2 }, }; function StatusChip({ candidate }) { const meta = STATUS_META[candidate.status] ?? STATUS_META.unmatched; const Icon = meta.icon; const label = candidate.status === 'matched_other_bill' ? `Matched to ${candidate.matched_bill_name || 'another bill'}` : meta.label; if (!label) return null; return ( {Icon && } {label} ); } // ── Main dialog ─────────────────────────────────────────────────────────────── export default function BillHistoricalImportDialog({ billId, billName, open, onClose, onImported }) { const [step, setStep] = useState('choice'); // 'choice' | 'pick' const [candidates, setCandidates] = useState([]); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState(new Set()); const [importing, setImporting] = useState(false); // Load candidates whenever the dialog opens useEffect(() => { if (!open || !billId) return; setStep('choice'); setSelected(new Set()); setLoading(true); api.merchantRuleCandidates(billId) .then(data => { // Pre-select importable candidates (not already a payment for this bill) const importable = (data.candidates || []).filter(c => c.status !== 'payment_exists' && c.status !== 'matched_this_bill'); setCandidates(data.candidates || []); setSelected(new Set(importable.map(c => c.id))); }) .catch(() => setCandidates([])) .finally(() => setLoading(false)); }, [open, billId]); const importable = candidates.filter(c => c.status !== 'payment_exists' && c.status !== 'matched_this_bill'); const alreadyDone = candidates.filter(c => c.status === 'payment_exists' || c.status === 'matched_this_bill'); async function doImport(ids) { if (ids.length === 0) { onClose(); return; } setImporting(true); try { const result = await api.importHistoricalPayments(billId, ids); toast.success(`${result.imported} payment${result.imported === 1 ? '' : 's'} imported for ${billName}`); onImported?.(result); onClose(); } catch (err) { toast.error(err.message || 'Import failed'); } finally { setImporting(false); } } function toggleAll(checked) { setSelected(checked ? new Set(importable.map(c => c.id)) : new Set()); } function toggle(id) { setSelected(prev => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); } const allSelected = importable.length > 0 && importable.every(c => selected.has(c.id)); // ── Choice step ────────────────────────────────────────────────────────────── if (step === 'choice') { return ( { if (!v) onClose(); }}> Past payments found {loading ? 'Searching your bank history…' : importable.length === 0 ? `No past transactions found matching ${billName}.` : `Found ${importable.length} past transaction${importable.length === 1 ? '' : 's'} matching ${billName}. What would you like to do?`} {loading && (
)} {!loading && importable.length > 0 && (
{/* Populate all */} {/* Pick one by one */} {/* Skip */}
)} {!loading && importable.length === 0 && alreadyDone.length > 0 && (

All matching transactions are already linked or have payments. Nothing to import.

)} {importing && (
Importing…
)} {(!loading || importing) && ( )}
); } // ── Pick step ──────────────────────────────────────────────────────────────── return ( { if (!v) onClose(); }}> Choose transactions to import Select which past transactions to import as payments for {billName}. {/* Select all toggle */}
{selected.size} selected
{/* Transaction list */}
{importable.map(c => ( ))} {/* Already-done items (dimmed, informational) */} {alreadyDone.length > 0 && (

Already handled

{alreadyDone.map(c => (

{c.payee}

{fmt(c.amount)}
))}
)}
); }