'use client' import { useState, useEffect } from 'react' import Link from 'next/link' import { createClient } from '@/lib/supabase/client' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { PageHeader } from '@/components/ui/page-header' import { useToast } from '@/components/ui/use-toast' import { formatCurrency, formatDate } from '@/lib/utils' import { Plus, Search, Receipt, FileText, Send, CheckCircle, Clock, XCircle, ReceiptText, AlertTriangle, FileQuestion, Truck } from 'lucide-react' import { EmptyInvoices } from '@/components/ui/empty-state' import type { Invoice, InvoiceStatus } from '@/types' const statusConfig: Record = { draft: { label: 'Utkast', variant: 'secondary', icon: FileText, borderColor: 'border-l-muted-foreground/30' }, sent: { label: 'Skickad', variant: 'default', icon: Send, borderColor: 'border-l-warning' }, paid: { label: 'Betald', variant: 'success', icon: CheckCircle, borderColor: 'border-l-success' }, overdue: { label: 'Förfallen', variant: 'destructive', icon: Clock, borderColor: 'border-l-destructive' }, cancelled: { label: 'Makulerad', variant: 'secondary', icon: XCircle, borderColor: 'border-l-muted-foreground/30' }, credited: { label: 'Krediterad', variant: 'secondary', icon: XCircle, borderColor: 'border-l-muted-foreground/30' }, } function getRelativeTimeLabel(dueDateStr: string, status: InvoiceStatus): { text: string; color: string } | null { if (status === 'paid' || status === 'cancelled' || status === 'credited' || status === 'draft') return null const today = new Date() today.setHours(0, 0, 0, 0) const dueDate = new Date(dueDateStr) dueDate.setHours(0, 0, 0, 0) const diffDays = Math.round((dueDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)) if (diffDays < 0) { return { text: `${Math.abs(diffDays)} dagar försenad`, color: 'text-destructive' } } else if (diffDays === 0) { return { text: 'Förfaller idag', color: 'text-warning-foreground' } } else if (diffDays <= 3) { return { text: `${diffDays} dagar kvar`, color: 'text-warning-foreground' } } else if (diffDays <= 7) { return { text: `${diffDays} dagar kvar`, color: 'text-muted-foreground' } } return null } export default function InvoicesPage() { const [invoices, setInvoices] = useState([]) const [isLoading, setIsLoading] = useState(true) const [searchTerm, setSearchTerm] = useState('') const [activeTab, setActiveTab] = useState('all') const { toast } = useToast() const supabase = createClient() useEffect(() => { fetchInvoices() }, []) async function fetchInvoices() { setIsLoading(true) const { data, error } = await supabase .from('invoices') .select('*, customer:customers(name)') .order('invoice_date', { ascending: false }) if (error) { toast({ title: 'Fel', description: 'Kunde inte hämta fakturor', variant: 'destructive', }) } else { setInvoices(data || []) } setIsLoading(false) } const filteredInvoices = invoices.filter((invoice) => { const matchesSearch = invoice.invoice_number.toLowerCase().includes(searchTerm.toLowerCase()) || (invoice.customer as { name: string })?.name?.toLowerCase().includes(searchTerm.toLowerCase()) const isCreditNote = !!invoice.credited_invoice_id const docType = (invoice as Invoice & { document_type?: string }).document_type || 'invoice' const matchesTab = activeTab === 'all' || (activeTab === 'unpaid' && ['sent', 'overdue'].includes(invoice.status) && !isCreditNote && docType === 'invoice') || (activeTab === 'credit' && isCreditNote) || (activeTab === 'proforma' && docType === 'proforma') || (activeTab === 'delivery_note' && docType === 'delivery_note') || (activeTab !== 'proforma' && activeTab !== 'delivery_note' && invoice.status === activeTab) return matchesSearch && matchesTab }) const stats = { unpaid: invoices.filter((i) => ['sent', 'overdue'].includes(i.status)).length, unpaidAmount: invoices .filter((i) => ['sent', 'overdue'].includes(i.status)) .reduce((sum, i) => sum + Number(i.total_sek || i.total), 0), overdue: invoices.filter((i) => i.status === 'overdue').length, } return (
} /> {/* Stats */}

Totalt antal

{invoices.length}

Obetalda

{stats.unpaid}

{stats.overdue > 0 && ( {stats.overdue} förfallna )}

Att få in

{formatCurrency(stats.unpaidAmount)}

{/* Search and tabs */}
setSearchTerm(e.target.value)} className="pl-10" />
Alla Obetalda Betalda Utkast Proforma Följesedel Kredit
{/* Invoice list */} {isLoading ? (
{[1, 2, 3].map((i) => (
))}
) : filteredInvoices.length === 0 ? ( {searchTerm ? (

Inga träffar

Inga fakturor matchar "{searchTerm}"

) : invoices.length === 0 ? ( ) : (

Inga fakturor i denna kategori

Prova att byta flik för att se fler fakturor

)}
) : (
{filteredInvoices.map((invoice) => { const status = statusConfig[invoice.status] const isCreditNote = !!invoice.credited_invoice_id const docType = (invoice as Invoice & { document_type?: string }).document_type || 'invoice' const isProforma = docType === 'proforma' const isDeliveryNote = docType === 'delivery_note' const StatusIcon = isCreditNote ? ReceiptText : isProforma ? FileQuestion : isDeliveryNote ? Truck : status.icon const relativeTime = invoice.due_date ? getRelativeTimeLabel(invoice.due_date, invoice.status) : null const borderClass = isCreditNote ? 'border-l-4 border-l-destructive/50' : isProforma ? 'border-l-4 border-l-blue-400' : isDeliveryNote ? 'border-l-4 border-l-emerald-400' : `border-l-4 ${status.borderColor}` return (

{invoice.invoice_number}

{isCreditNote && ( Kredit )} {isProforma && ( Proforma )} {isDeliveryNote && ( Följesedel )} {status.label}

{(invoice.customer as { name: string })?.name} · {formatDate(invoice.invoice_date)}

{relativeTime && ( {relativeTime.text} )}

{formatCurrency(Number(invoice.total), invoice.currency)}

{invoice.currency !== 'SEK' && invoice.total_sek && (

{formatCurrency(Number(invoice.total_sek))}

)}
) })}
)}
) }