"use client"; import { useState, useCallback } from "react"; import { ArrowLeft, Plus, Trash2, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select } from "@/components/ui/select"; import { Card, CardContent } from "@/components/ui/card"; import { createInvoice, updateInvoiceStatus, fetchInvoices, type InvoiceRow, } from "@/lib/supabase/invoicing-actions"; import { centsToDecimal, decimalToCents } from "@/lib/supabase/invoicing-types"; interface LineItem { description: string; quantity: string; unit_price_cents: number; tax_rate: string; } const CURRENCIES = ["AUD", "USD", "EUR", "GBP", "NZD", "CAD"]; function todayISO(): string { return new Date().toISOString().split("T")[0]; } function dueDateISO(): string { const d = new Date(); d.setDate(d.getDate() + 30); return d.toISOString().split("T")[0]; } function computeLineTotal(item: LineItem): number { const qty = parseFloat(item.quantity) || 0; return Math.round(qty * item.unit_price_cents); } export default function InvoicesNewView({ onBack, onCreated, }: { onBack: () => void; onCreated: (invoiceId: string) => void; }) { const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [clientName, setClientName] = useState(""); const [clientEmail, setClientEmail] = useState(""); const [clientAddress, setClientAddress] = useState(""); const [issueDate, setIssueDate] = useState(todayISO); const [dueDate, setDueDate] = useState(dueDateISO); const [currency, setCurrency] = useState("AUD"); const [notes, setNotes] = useState(""); const [lineItems, setLineItems] = useState([ { description: "", quantity: "1", unit_price_cents: 0, tax_rate: "10" }, ]); const updateLineItem = useCallback( (index: number, field: keyof LineItem, value: string | number) => { setLineItems((prev) => { const next = [...prev]; next[index] = { ...next[index], [field]: value }; return next; }); }, [] ); const addLineItem = useCallback(() => { setLineItems((prev) => [ ...prev, { description: "", quantity: "1", unit_price_cents: 0, tax_rate: "10" }, ]); }, []); const removeLineItem = useCallback((index: number) => { setLineItems((prev) => prev.filter((_, i) => i !== index)); }, []); const subtotalCents = lineItems.reduce((sum, item) => sum + computeLineTotal(item), 0); const taxTotalCents = lineItems.reduce((sum, item) => { const lineTotal = computeLineTotal(item); const rate = parseFloat(item.tax_rate) || 0; return sum + Math.round((lineTotal * rate) / 100); }, 0); const totalCents = subtotalCents + taxTotalCents; async function handleSubmit(action: "draft" | "sent") { setError(null); if (!clientName.trim()) { setError("Client name is required"); return; } if (!dueDate) { setError("Due date is required"); return; } if (lineItems.some((item) => !item.description.trim())) { setError("All line items must have a description"); return; } if (lineItems.some((item) => item.unit_price_cents <= 0)) { setError("All line items must have a unit price greater than 0"); return; } setSaving(true); try { const result = await createInvoice({ client_name: clientName.trim(), client_email: clientEmail.trim() || undefined, client_address: clientAddress.trim() || undefined, issue_date: issueDate, due_date: dueDate, currency, notes: notes.trim() || undefined, line_items: lineItems.map((item, i) => ({ description: item.description.trim(), quantity: item.quantity, unit_price_cents: item.unit_price_cents, tax_rate: item.tax_rate, sort_order: i, })), }); if (!result.success || !result.invoiceId) { setError(result.error || "Failed to create invoice"); return; } if (action === "sent") { await updateInvoiceStatus(result.invoiceId, "sent"); } onCreated(result.invoiceId); } catch (e: any) { setError(e.message || "An unexpected error occurred"); } finally { setSaving(false); } } return (
{/* Header */}

New Invoice

Create a new invoice for your client

{/* Error Banner */} {error && (
{error}
)}
{/* Main Form */}
{/* Client Details */}

Client Details

setClientName(e.target.value)} placeholder="e.g. Acme Corp" className="mt-1" />
setClientEmail(e.target.value)} placeholder="billing@acme.com" className="mt-1" />