"use client" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { fetchAsBase64 } from "@/lib/utils" import { SettingsMap } from "@/models/settings" import { Currency, User } from "@/prisma/client" import { FileDown, Loader2, Save, TextSelect, X } from "lucide-react" import { useRouter } from "next/navigation" import { startTransition, useMemo, useReducer, useState } from "react" import { addNewTemplateAction, deleteTemplateAction, generateInvoicePDF, saveInvoiceAsTransactionAction, } from "../actions" import defaultTemplates, { InvoiceTemplate } from "../default-templates" import { InvoiceAppData } from "../page" import { InvoiceFormData, InvoicePage } from "./invoice-page" function invoiceFormReducer(state: InvoiceFormData, action: any): InvoiceFormData { switch (action.type) { case "SET_FORM": return action.payload case "UPDATE_FIELD": return { ...state, [action.field]: action.value } case "ADD_ITEM": return { ...state, items: [ ...state.items, { name: "", subtitle: "", showSubtitle: false, quantity: 1, unitPrice: 0, subtotal: 0 }, ], } case "UPDATE_ITEM": { const items = [...state.items] items[action.index] = { ...items[action.index], [action.field]: action.value } if (action.field === "quantity" || action.field === "unitPrice") { items[action.index].subtotal = Number(items[action.index].quantity) * Number(items[action.index].unitPrice) } return { ...state, items } } case "REMOVE_ITEM": return { ...state, items: state.items.filter((_, i) => i !== action.index) } case "ADD_TAX": return { ...state, additionalTaxes: [...state.additionalTaxes, { name: "", rate: 0, amount: 0 }] } case "UPDATE_TAX": { const taxes = [...state.additionalTaxes] taxes[action.index] = { ...taxes[action.index], [action.field]: action.value } if (action.field === "rate") { const subtotal = state.items.reduce((sum, item) => sum + item.subtotal, 0) taxes[action.index].amount = (subtotal * Number(action.value)) / 100 } return { ...state, additionalTaxes: taxes } } case "REMOVE_TAX": return { ...state, additionalTaxes: state.additionalTaxes.filter((_, i) => i !== action.index) } case "ADD_FEE": return { ...state, additionalFees: [...state.additionalFees, { name: "", amount: 0 }] } case "UPDATE_FEE": { const fees = [...state.additionalFees] fees[action.index] = { ...fees[action.index], [action.field]: action.value } return { ...state, additionalFees: fees } } case "REMOVE_FEE": return { ...state, additionalFees: state.additionalFees.filter((_, i) => i !== action.index) } default: return state } } export function InvoiceGenerator({ user, settings, currencies, appData, }: { user: User settings: SettingsMap currencies: Currency[] appData: InvoiceAppData | null }) { const templates: InvoiceTemplate[] = useMemo( () => [...defaultTemplates(user, settings), ...(appData?.templates || [])], [appData] ) const [selectedTemplate, setSelectedTemplate] = useState(templates[0].name) const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false) const [newTemplateName, setNewTemplateName] = useState("") const [formData, dispatch] = useReducer(invoiceFormReducer, templates[0].formData) const [isPdfLoading, setIsPdfLoading] = useState(false) const [isSavingTransaction, setIsSavingTransaction] = useState(false) const router = useRouter() // Function to handle template selection const handleTemplateSelect = (templateName: string) => { const template = templates.find((t) => t.name === templateName) if (template) { setSelectedTemplate(templateName) dispatch({ type: "SET_FORM", payload: template.formData }) } } const handleGeneratePDF = async (e: React.FormEvent) => { e.preventDefault() setIsPdfLoading(true) try { if (formData.businessLogo) { formData.businessLogo = await fetchAsBase64(formData.businessLogo) } const pdfBuffer = await generateInvoicePDF(formData) // Create a blob from the buffer const blob = new Blob([pdfBuffer], { type: "application/pdf" }) // Create a URL for the blob const url = URL.createObjectURL(blob) // Create a temporary link element const link = document.createElement("a") link.href = url link.download = `invoice-${formData.invoiceNumber}.pdf` // Append the link to the document, click it, and remove it document.body.appendChild(link) link.click() document.body.removeChild(link) // Clean up the URL URL.revokeObjectURL(url) } catch (error) { console.error("Error generating PDF:", error) alert("Failed to generate PDF. Please try again.") } finally { setIsPdfLoading(false) } } const handleSaveTemplate = async () => { if (!newTemplateName.trim()) { alert("Please enter a template name") return } if (templates.some((t) => t.name === newTemplateName)) { alert("A template with this name already exists") return } try { const result = await addNewTemplateAction(user, { id: `tmpl_${Math.random().toString(36).substring(2, 15)}`, name: newTemplateName, formData: formData, }) if (result.success) { setIsTemplateDialogOpen(false) setNewTemplateName("") router.refresh() } else { alert("Failed to save template. Please try again.") } } catch (error) { console.error("Error saving template:", error) alert("Failed to save template. Please try again.") } } const handleDeleteTemplate = async (templateId: string | undefined, e: React.MouseEvent) => { e.stopPropagation() if (!templateId) return // Don't allow deleting default templates try { const result = await deleteTemplateAction(user, templateId) if (result.success) { router.refresh() } } catch (error) { console.error("Error deleting template:", error) alert("Failed to delete template. Please try again.") } } // Accept optional event, prevent default only if present const handleSaveAsTransaction = async (e?: React.FormEvent) => { if (e) e.preventDefault() setIsSavingTransaction(true) try { if (formData.businessLogo) { formData.businessLogo = await fetchAsBase64(formData.businessLogo) } const result = await saveInvoiceAsTransactionAction(formData) if (result.success && result.data?.id) { console.log("SUCCESS! REDIRECTING TO TRANSACTION", result.data?.id) startTransition(() => { router.push(`/transactions/${result.data?.id}`) }) } else { alert(result.error || "Failed to save as transaction") } } catch (error) { console.error("Error saving as transaction:", error) alert("Failed to save as transaction. Please try again.") } finally { setIsSavingTransaction(false) } } return (
{/* Templates Section */}
{templates.map((template) => (
{template.id && ( )}
))}
{/* Generate PDF Button */}
{/* New Template Dialog */} Save as Template
setNewTemplateName(e.target.value)} placeholder="Enter template name" className="w-full px-3 py-2 border rounded-md" />
) }