'use client' import { useState, useEffect, useMemo } from 'react' import { createClient } from '@/lib/supabase/client' import { Input } from '@/components/ui/input' import { formatCurrency, formatDate, cn } from '@/lib/utils' import { Search, FileText, Loader2 } from 'lucide-react' import { useCompany } from '@/contexts/CompanyContext' import type { Invoice, Customer } from '@/types' import type { TransactionWithInvoice } from './transaction-types' type OpenInvoice = Invoice & { customer?: Customer } interface InvoicePickerProps { transaction: TransactionWithInvoice onSelect: (invoice: OpenInvoice) => void isProcessing: boolean } export default function InvoicePicker({ transaction, onSelect, isProcessing }: InvoicePickerProps) { const { company } = useCompany() const supabase = useMemo(() => createClient(), []) const [invoices, setInvoices] = useState([]) const [isLoading, setIsLoading] = useState(true) const [search, setSearch] = useState('') useEffect(() => { if (!company) return let cancelled = false async function load() { setIsLoading(true) // Filter out fully-settled invoices defensively — match-invoice should // flip status to 'paid' on full settlement, but a stale 'sent'/'overdue' // row with remaining_amount=0 would otherwise be selectable here and // could be matched a second time, double-booking the income. // Also exclude proformas (PF- series) — proforma is not a faktura per // ML 17 kap 24§, has no VAT obligation, and must never be matched // against a bank receipt or trigger a verifikation. const { data } = await supabase .from('invoices') .select('*, customer:customers(*)') .eq('company_id', company!.id) .eq('document_type', 'invoice') .in('status', ['sent', 'overdue', 'partially_paid']) .gt('remaining_amount', 0) .order('invoice_date', { ascending: false }) .limit(200) if (cancelled) return setInvoices((data as OpenInvoice[]) || []) setIsLoading(false) } load() return () => { cancelled = true } }, [company, supabase]) const sorted = useMemo(() => { const txAmount = Math.abs(transaction.amount) const filtered = !search ? invoices : invoices.filter((inv) => { const q = search.toLowerCase() return ( (inv.invoice_number ?? '').toLowerCase().includes(q) || (inv.customer?.name ?? '').toLowerCase().includes(q) ) }) return [...filtered].sort((a, b) => { const remainA = a.remaining_amount ?? a.total const remainB = b.remaining_amount ?? b.total const diffA = Math.abs(remainA - txAmount) const diffB = Math.abs(remainB - txAmount) if (diffA !== diffB) return diffA - diffB return b.invoice_date.localeCompare(a.invoice_date) }) }, [invoices, search, transaction.amount]) if (isLoading) { return (
Laddar fakturor...
) } if (invoices.length === 0) { return (

Inga öppna fakturor att matcha mot.

) } return (
setSearch(e.target.value)} className="pl-9" autoFocus />
{sorted.map((invoice) => { const txAmount = Math.abs(transaction.amount) const remaining = invoice.remaining_amount ?? invoice.total const sameCurrency = transaction.currency === invoice.currency const exact = sameCurrency && Math.abs(remaining - txAmount) < 0.01 const close = sameCurrency && !exact && txAmount > 0 && Math.abs(remaining - txAmount) / txAmount < 0.01 return ( ) })} {sorted.length === 0 && (

Ingen faktura matchar "{search}"

)}
) }