"use client"; import React, { useState } from 'react'; // Types export interface InvoiceItem { id: string; description: string; quantity: number; unitPrice: number; total: number; tax?: number; } export interface InvoiceData { id: string; invoiceNumber: string; status: 'draft' | 'pending' | 'paid' | 'overdue' | 'cancelled'; issueDate: Date; dueDate: Date; paidDate?: Date; from: { name: string; address?: string; email?: string; phone?: string; logo?: string; taxId?: string; }; to: { name: string; address?: string; email?: string; phone?: string; taxId?: string; }; items: InvoiceItem[]; subtotal: number; tax?: number; taxRate?: number; discount?: number; discountType?: 'fixed' | 'percentage'; total: number; currency?: string; notes?: string; terms?: string; paymentMethod?: string; } // Invoice Template export interface InvoiceTemplateProps { /** Invoice data */ invoice: InvoiceData; /** Print handler */ onPrint?: () => void; /** Download handler */ onDownload?: () => void; /** Send handler */ onSend?: () => void; /** Edit handler */ onEdit?: () => void; /** Additional CSS classes */ className?: string; } export const InvoiceTemplate: React.FC = ({ invoice, onPrint, onDownload, onSend, onEdit, className = '', }) => { const currency = invoice.currency || '$'; const formatCurrency = (amount: number) => { return `${currency}${amount.toFixed(2)}`; }; const formatDate = (date: Date) => { return date.toLocaleDateString([], { year: 'numeric', month: 'long', day: 'numeric', }); }; const getStatusColor = (status: InvoiceData['status']) => { switch (status) { case 'paid': return '#10b981'; case 'pending': return '#f59e0b'; case 'overdue': return '#ef4444'; case 'draft': return '#6b7280'; case 'cancelled': return '#9ca3af'; default: return '#6b7280'; } }; return (
{onEdit && ( )} {onPrint && ( )} {onDownload && ( )} {onSend && ( )}
{invoice.from.logo ? ( {invoice.from.name} ) : (

{invoice.from.name}

)}

INVOICE

#{invoice.invoiceNumber}
{invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}

From

{invoice.from.name}

{invoice.from.address &&

{invoice.from.address}

} {invoice.from.email &&

{invoice.from.email}

} {invoice.from.phone &&

{invoice.from.phone}

} {invoice.from.taxId &&

Tax ID: {invoice.from.taxId}

}

Bill To

{invoice.to.name}

{invoice.to.address &&

{invoice.to.address}

} {invoice.to.email &&

{invoice.to.email}

} {invoice.to.phone &&

{invoice.to.phone}

} {invoice.to.taxId &&

Tax ID: {invoice.to.taxId}

}
Issue Date {formatDate(invoice.issueDate)}
Due Date {formatDate(invoice.dueDate)}
{invoice.paidDate && (
Paid Date {formatDate(invoice.paidDate)}
)}
{invoice.items.some((item) => item.tax !== undefined) && } {invoice.items.map((item) => ( {invoice.items.some((i) => i.tax !== undefined) && ( )} ))}
Description Qty Unit PriceTaxTotal
{item.description} {item.quantity} {formatCurrency(item.unitPrice)}{item.tax !== undefined ? `${item.tax}%` : '-'}{formatCurrency(item.total)}
{invoice.notes && (

Notes

{invoice.notes}

)} {invoice.terms && (

Terms & Conditions

{invoice.terms}

)} {invoice.paymentMethod && (

Payment Method

{invoice.paymentMethod}

)}
Subtotal {formatCurrency(invoice.subtotal)}
{invoice.discount !== undefined && invoice.discount > 0 && (
Discount {invoice.discountType === 'percentage' && ` (${invoice.discount}%)`} -{formatCurrency( invoice.discountType === 'percentage' ? invoice.subtotal * (invoice.discount / 100) : invoice.discount )}
)} {invoice.tax !== undefined && (
Tax{invoice.taxRate && ` (${invoice.taxRate}%)`} {formatCurrency(invoice.tax)}
)}
Total {formatCurrency(invoice.total)}
); }; // Invoice List export interface InvoiceListProps { /** Invoices to display */ invoices: InvoiceData[]; /** Invoice click handler */ onInvoiceClick?: (invoice: InvoiceData) => void; /** Selection mode */ selectable?: boolean; /** Selected IDs */ selectedIds?: string[]; /** Selection change handler */ onSelectionChange?: (ids: string[]) => void; /** Sort field */ sortBy?: 'date' | 'number' | 'client' | 'amount' | 'status'; /** Sort direction */ sortDirection?: 'asc' | 'desc'; /** Sort change handler */ onSortChange?: (field: string, direction: 'asc' | 'desc') => void; /** Loading state */ loading?: boolean; /** Additional CSS classes */ className?: string; } export const InvoiceList: React.FC = ({ invoices, onInvoiceClick, selectable = false, selectedIds = [], onSelectionChange, sortBy = 'date', sortDirection = 'desc', onSortChange, loading = false, className = '', }) => { const currency = '$'; const formatCurrency = (amount: number) => { return `${currency}${amount.toFixed(2)}`; }; const formatDate = (date: Date) => { return date.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' }); }; const getStatusClass = (status: InvoiceData['status']) => { return `ll-invoice-list-status-${status}`; }; const handleSelectAll = () => { if (selectedIds.length === invoices.length) { onSelectionChange?.([]); } else { onSelectionChange?.(invoices.map((i) => i.id)); } }; const handleSelect = (id: string) => { if (selectedIds.includes(id)) { onSelectionChange?.(selectedIds.filter((sid) => sid !== id)); } else { onSelectionChange?.([...selectedIds, id]); } }; const handleSort = (field: string) => { const newDirection = sortBy === field && sortDirection === 'asc' ? 'desc' : 'asc'; onSortChange?.(field, newDirection); }; const SortIcon = ({ field }: { field: string }) => { if (sortBy !== field) return null; return ( ); }; if (loading) { return (
); } return (
{selectable && ( )} {invoices.map((invoice) => ( onInvoiceClick?.(invoice)} > {selectable && ( )} ))}
0} onChange={handleSelectAll} /> handleSort('number')} className="ll-invoice-list-sortable"> Invoice handleSort('client')} className="ll-invoice-list-sortable"> Client handleSort('date')} className="ll-invoice-list-sortable"> Date Due Date handleSort('amount')} className="ll-invoice-list-sortable"> Amount handleSort('status')} className="ll-invoice-list-sortable"> Status
e.stopPropagation()}> handleSelect(invoice.id)} /> #{invoice.invoiceNumber} {invoice.to.name} {formatDate(invoice.issueDate)} {formatDate(invoice.dueDate)} {formatCurrency(invoice.total)} {invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
{invoices.length === 0 && (

No invoices found

)}
); }; // Invoice Grid export interface InvoiceGridProps { /** Invoices to display */ invoices: InvoiceData[]; /** Invoice click handler */ onInvoiceClick?: (invoice: InvoiceData) => void; /** Loading state */ loading?: boolean; /** Additional CSS classes */ className?: string; } export const InvoiceGrid: React.FC = ({ invoices, onInvoiceClick, loading = false, className = '', }) => { const currency = '$'; const formatCurrency = (amount: number) => { return `${currency}${amount.toFixed(2)}`; }; const formatDate = (date: Date) => { return date.toLocaleDateString([], { month: 'short', day: 'numeric' }); }; const getStatusColor = (status: InvoiceData['status']) => { switch (status) { case 'paid': return '#10b981'; case 'pending': return '#f59e0b'; case 'overdue': return '#ef4444'; case 'draft': return '#6b7280'; case 'cancelled': return '#9ca3af'; default: return '#6b7280'; } }; if (loading) { return (
); } return (
{invoices.map((invoice) => (
onInvoiceClick?.(invoice)} >
#{invoice.invoiceNumber} {invoice.status}

{invoice.to.name}

{invoice.to.email &&

{invoice.to.email}

}
Issue Date {formatDate(invoice.issueDate)}
Due Date {formatDate(invoice.dueDate)}
Total {formatCurrency(invoice.total)}
))} {invoices.length === 0 && (

No invoices found

)}
); }; // Invoice Form export interface InvoiceFormProps { /** Initial invoice data */ initialData?: Partial; /** Save handler */ onSave?: (invoice: InvoiceData) => void; /** Cancel handler */ onCancel?: () => void; /** Clients for autocomplete */ clients?: { id: string; name: string; email?: string; address?: string }[]; /** Additional CSS classes */ className?: string; } export const InvoiceForm: React.FC = ({ initialData, onSave, onCancel, clients = [], className = '', }) => { const [invoice, setInvoice] = useState>({ invoiceNumber: '', status: 'draft', issueDate: new Date(), dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), from: { name: '' }, to: { name: '' }, items: [], subtotal: 0, total: 0, currency: '$', ...initialData, }); const handleChange = (field: string, value: unknown) => { setInvoice((prev) => ({ ...prev, [field]: value })); }; const handleFromChange = (field: string, value: string) => { setInvoice((prev) => ({ ...prev, from: { ...prev.from!, [field]: value }, })); }; const handleToChange = (field: string, value: string) => { setInvoice((prev) => ({ ...prev, to: { ...prev.to!, [field]: value }, })); }; const addItem = () => { const newItem: InvoiceItem = { id: Date.now().toString(), description: '', quantity: 1, unitPrice: 0, total: 0, }; setInvoice((prev) => ({ ...prev, items: [...(prev.items || []), newItem], })); }; const updateItem = (id: string, field: keyof InvoiceItem, value: unknown) => { setInvoice((prev) => { const items = prev.items?.map((item) => { if (item.id !== id) return item; const updated = { ...item, [field]: value }; if (field === 'quantity' || field === 'unitPrice') { updated.total = updated.quantity * updated.unitPrice; } return updated; }) || []; const subtotal = items.reduce((sum, item) => sum + item.total, 0); const tax = prev.taxRate ? subtotal * (prev.taxRate / 100) : prev.tax || 0; let discount = 0; if (prev.discount) { discount = prev.discountType === 'percentage' ? subtotal * (prev.discount / 100) : prev.discount; } const total = subtotal + tax - discount; return { ...prev, items, subtotal, tax, total }; }); }; const removeItem = (id: string) => { setInvoice((prev) => { const items = prev.items?.filter((item) => item.id !== id) || []; const subtotal = items.reduce((sum, item) => sum + item.total, 0); const tax = prev.taxRate ? subtotal * (prev.taxRate / 100) : prev.tax || 0; let discount = 0; if (prev.discount) { discount = prev.discountType === 'percentage' ? subtotal * (prev.discount / 100) : prev.discount; } const total = subtotal + tax - discount; return { ...prev, items, subtotal, tax, total }; }); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (invoice.invoiceNumber && invoice.from?.name && invoice.to?.name) { onSave?.(invoice as InvoiceData); } }; return (

{initialData?.id ? 'Edit Invoice' : 'New Invoice'}

Invoice Details

handleChange('invoiceNumber', e.target.value)} required />
handleChange('issueDate', new Date(e.target.value))} required />
handleChange('dueDate', new Date(e.target.value))} required />

From

handleFromChange('name', e.target.value)} required />