import { useState, useCallback } from "react"; import { useForm, useFieldArray } from "react-hook-form"; 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, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { calcItemAmounts, calcItemAmountsKleinunternehmer, calcInvoiceTotals, formatCurrency, TAX_RATES } from "@/lib/tax"; import { Plus, Trash2 } from "lucide-react"; interface Customer { id: string; name: string; } interface ItemFormData { position: number; description: string; quantity: string; unit: string; unitPrice: string; taxRate: string; netAmount: number; taxAmount: number; grossAmount: number; } interface InvoiceFormValues { customerId: string; issueDate: string; deliveryDate: string; dueDate: string; notes: string; items: ItemFormData[]; } interface InvoiceFormProps { customers: Customer[]; companyId: string; onSubmit: (data: Record) => Promise; defaultValues?: Partial; defaultKleinunternehmer?: boolean; } const defaultItem = (): ItemFormData => ({ position: 1, description: "", quantity: "1", unit: "Stück", unitPrice: "0.00", taxRate: "19", netAmount: 0, taxAmount: 0, grossAmount: 0, }); export function InvoiceForm({ customers, companyId, onSubmit, defaultKleinunternehmer = false }: InvoiceFormProps) { const today = new Date().toISOString().split("T")[0]; const dueDefault = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split("T")[0]; const [kleinunternehmer, setKleinunternehmer] = useState(defaultKleinunternehmer); const { register, handleSubmit, watch, setValue, control, formState: { errors, isSubmitting } } = useForm({ defaultValues: { customerId: "", issueDate: today, deliveryDate: today, dueDate: dueDefault, notes: "", items: [defaultItem()], }, }); const { fields, append, remove } = useFieldArray({ control, name: "items" }); const watchedItems = watch("items"); const recalcItem = useCallback((index: number) => { const item = watchedItems[index]; if (!item) return; const qty = parseFloat(item.quantity) || 0; const price = parseFloat(item.unitPrice) || 0; if (kleinunternehmer) { const { netAmount, taxAmount, grossAmount } = calcItemAmountsKleinunternehmer(qty, price); setValue(`items.${index}.netAmount`, netAmount); setValue(`items.${index}.taxAmount`, taxAmount); setValue(`items.${index}.grossAmount`, grossAmount); } else { const rate = parseFloat(item.taxRate) || 0; const { netAmount, taxAmount, grossAmount } = calcItemAmounts(qty, price, rate); setValue(`items.${index}.netAmount`, netAmount); setValue(`items.${index}.taxAmount`, taxAmount); setValue(`items.${index}.grossAmount`, grossAmount); } }, [watchedItems, setValue, kleinunternehmer]); const totals = calcInvoiceTotals( watchedItems.map((item) => ({ netAmount: item.netAmount ?? 0, taxAmount: item.taxAmount ?? 0, grossAmount: item.grossAmount ?? 0, })) ); async function handleFormSubmit(data: InvoiceFormValues) { const items = data.items.map((item, i) => { const qty = parseFloat(item.quantity) || 0; const price = parseFloat(item.unitPrice) || 0; if (kleinunternehmer) { const { netAmount, taxAmount, grossAmount } = calcItemAmountsKleinunternehmer(qty, price); return { position: i + 1, description: item.description, quantity: qty, unit: item.unit || undefined, unitPrice: price, taxRate: 0, netAmount, taxAmount, grossAmount, }; } const rate = parseFloat(item.taxRate) || 0; const { netAmount, taxAmount, grossAmount } = calcItemAmounts(qty, price, rate); return { position: i + 1, description: item.description, quantity: qty, unit: item.unit || undefined, unitPrice: price, taxRate: rate, netAmount, taxAmount, grossAmount, }; }); const totals = calcInvoiceTotals(items); await onSubmit({ companyId, customerId: data.customerId, issueDate: data.issueDate, deliveryDate: data.deliveryDate || undefined, dueDate: data.dueDate, notes: data.notes || undefined, kleinunternehmer, items, ...totals, }); } return (
{errors.customerId &&

Pflichtfeld

}

Rechnungspositionen

Beschreibung
Menge
Einh.
{kleinunternehmer ? "Einzelpreis (brutto)" : "Einzelpreis"}
{!kleinunternehmer &&
MwSt.
}
Gesamt (brutto)
{fields.map((field, index) => (
recalcItem(index)} />
recalcItem(index)} />
{!kleinunternehmer && (
)}
{formatCurrency(watchedItems[index]?.grossAmount ?? 0)}
{fields.length > 1 && ( )}
))}
{kleinunternehmer ? ( <>
Gesamtbetrag {formatCurrency(totals.grossTotal)}

Dieser Rechnungsbetrag enthält nach §19 Abs. 1 UStG keine USt.

) : ( <>
Netto {formatCurrency(totals.netTotal)}
MwSt. {formatCurrency(totals.taxTotal)}
Gesamt (brutto) {formatCurrency(totals.grossTotal)}
)}