'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Loader2, Send, CreditCard } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { InvoicePdfPreview } from './invoice-pdf-preview'; import { apiFetch } from '@/lib/api/client'; import { recordPaymentSchema, type RecordPaymentInput } from '@/lib/validators/invoices'; const STATUS_COLORS: Record = { draft: 'bg-gray-100 text-gray-700 border-gray-200', sent: 'bg-blue-100 text-blue-700 border-blue-200', paid: 'bg-green-100 text-green-700 border-green-200', overdue: 'bg-red-100 text-red-700 border-red-200', cancelled: 'bg-gray-100 text-gray-500 border-gray-200', }; interface InvoiceDetailProps { invoiceId: string; } export function InvoiceDetail({ invoiceId }: InvoiceDetailProps) { const queryClient = useQueryClient(); const [tab, setTab] = useState('overview'); const { data, isLoading, error } = useQuery<{ data: Record }>({ queryKey: ['invoices', invoiceId], queryFn: () => apiFetch(`/api/v1/invoices/${invoiceId}`), }); const sendMutation = useMutation({ mutationFn: () => apiFetch(`/api/v1/invoices/${invoiceId}/send`, { method: 'POST' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['invoices', invoiceId] }); queryClient.invalidateQueries({ queryKey: ['invoices'] }); }, }); const paymentForm = useForm({ resolver: zodResolver(recordPaymentSchema), defaultValues: { paymentDate: new Date().toISOString().split('T')[0] }, }); const paymentMutation = useMutation({ mutationFn: (values: RecordPaymentInput) => apiFetch(`/api/v1/invoices/${invoiceId}/payment`, { method: 'PATCH', body: JSON.stringify(values), }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['invoices', invoiceId] }); queryClient.invalidateQueries({ queryKey: ['invoices'] }); }, }); if (isLoading) { return (
); } if (error || !data?.data) { return (
Failed to load invoice details.
); } const invoice = data.data; const statusColor = STATUS_COLORS[invoice.status] ?? STATUS_COLORS.draft; return (
{/* Header */}

{invoice.invoiceNumber}

{invoice.status}

{invoice.clientName}

{invoice.status === 'draft' && ( )}
Overview Linked Expenses PDF Preview Payment {/* Overview */}
Total

{Number(invoice.total).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, })}{' '} {invoice.currency}

Due Date

{invoice.dueDate}

Payment Terms

{invoice.paymentTerms}

{/* Line items */} {invoice.lineItems && invoice.lineItems.length > 0 && ( Line Items
Description Qty Unit Price Total
{(invoice.lineItems as Record[]).map((li) => (
{li.description} {li.quantity} {Number(li.unitPrice).toFixed(2)} {Number(li.total).toFixed(2)}
))}
{/* Totals */}
Subtotal {Number(invoice.subtotal).toFixed(2)} {invoice.currency}
{Number(invoice.discountAmount) > 0 && (
Discount ({invoice.discountPct}%) -{Number(invoice.discountAmount).toFixed(2)} {invoice.currency}
)} {Number(invoice.feeAmount) > 0 && (
Fee ({invoice.feePct}%) +{Number(invoice.feeAmount).toFixed(2)} {invoice.currency}
)}
Total {Number(invoice.total).toFixed(2)} {invoice.currency}
)} {invoice.notes && ( Notes

{invoice.notes}

)}
{/* Linked Expenses */} {invoice.linkedExpenses && invoice.linkedExpenses.length > 0 ? (
{(invoice.linkedExpenses as Record[]).map((exp) => (

{exp.establishmentName ?? 'Unnamed Expense'}

{exp.category ?? '—'} · {exp.expenseDate}

{Number(exp.amount).toFixed(2)} {exp.currency}
))}
) : (

No expenses linked to this invoice.

)}
{/* PDF Preview */} {/* Payment */} {invoice.status === 'paid' ? (
Paid
Payment Date

{invoice.paymentDate ?? '—'}

Method

{invoice.paymentMethod ?? '—'}

Reference

{invoice.paymentReference ?? '—'}

) : ( Record Payment
paymentMutation.mutate(values), )} className="space-y-4" >
{paymentForm.formState.errors.paymentDate && (

{paymentForm.formState.errors.paymentDate.message}

)}
)}
); }