import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { ArrowLeft, Plus, Trash2, Clock } from "lucide-react"; import { createPageUrl } from "@/utils"; import { useToast } from "@/components/ui/use-toast"; import { format, addDays } from "date-fns"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; export default function InvoiceEditor() { const navigate = useNavigate(); const { toast } = useToast(); const queryClient = useQueryClient(); const urlParams = new URLSearchParams(window.location.search); const invoiceId = urlParams.get('id'); const isEdit = !!invoiceId; const { data: user } = useQuery({ queryKey: ['current-user-invoice-editor'], queryFn: () => base44.auth.me(), }); const { data: invoices = [] } = useQuery({ queryKey: ['invoices'], queryFn: () => base44.entities.Invoice.list(), enabled: isEdit, }); const { data: events = [] } = useQuery({ queryKey: ['events-for-invoice'], queryFn: () => base44.entities.Event.list(), }); const existingInvoice = invoices.find(inv => inv.id === invoiceId); const [formData, setFormData] = useState({ invoice_number: existingInvoice?.invoice_number || `INV-G00G${Math.floor(Math.random() * 100000)}`, event_id: existingInvoice?.event_id || "", event_name: existingInvoice?.event_name || "", invoice_date: existingInvoice?.issue_date || format(new Date(), 'yyyy-MM-dd'), due_date: existingInvoice?.due_date || format(addDays(new Date(), 30), 'yyyy-MM-dd'), payment_terms: existingInvoice?.payment_terms || "30", hub: existingInvoice?.hub || "", manager: existingInvoice?.manager_name || "", vendor_id: existingInvoice?.vendor_id || "", department: existingInvoice?.department || "", po_reference: existingInvoice?.po_reference || "", from_company: existingInvoice?.from_company || { name: "Legendary Event Staffing", address: "848 E Gish Rd Ste 1, San Jose, CA 95112", phone: "(408) 936-0180", email: "order@legendaryeventstaff.com" }, to_company: existingInvoice?.to_company || { name: "Thinkloops", phone: "4086702861", email: "mohsin@thikloops.com", address: "Dublin St, San Francisco, CA 94112, USA", manager_name: "Manager Name", hub_name: "Hub Name", vendor_id: "Vendor #" }, staff_entries: existingInvoice?.roles?.[0]?.staff_entries || [], charges: existingInvoice?.charges || [], other_charges: existingInvoice?.other_charges || 0, notes: existingInvoice?.notes || "", }); const [timePickerOpen, setTimePickerOpen] = useState(null); const [selectedTime, setSelectedTime] = useState({ hours: "09", minutes: "00", period: "AM" }); const saveMutation = useMutation({ mutationFn: async (data) => { // Calculate totals const staffTotal = data.staff_entries.reduce((sum, entry) => sum + (entry.total || 0), 0); const chargesTotal = data.charges.reduce((sum, charge) => sum + ((charge.qty * charge.rate) || 0), 0); const subtotal = staffTotal + chargesTotal; const total = subtotal + (parseFloat(data.other_charges) || 0); const roles = data.staff_entries.length > 0 ? [{ role_name: "Mixed", staff_entries: data.staff_entries, role_subtotal: staffTotal }] : []; const invoiceData = { invoice_number: data.invoice_number, event_id: data.event_id, event_name: data.event_name, event_date: data.invoice_date, po_reference: data.po_reference, from_company: data.from_company, to_company: data.to_company, business_name: data.to_company.name, manager_name: data.manager, vendor_name: data.from_company.name, vendor_id: data.vendor_id, hub: data.hub, department: data.department, cost_center: data.po_reference, roles: roles, charges: data.charges, subtotal: subtotal, other_charges: parseFloat(data.other_charges) || 0, amount: total, status: existingInvoice?.status || "Draft", issue_date: data.invoice_date, due_date: data.due_date, payment_terms: data.payment_terms, is_auto_generated: false, notes: data.notes, }; if (isEdit) { return base44.entities.Invoice.update(invoiceId, invoiceData); } else { return base44.entities.Invoice.create(invoiceData); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['invoices'] }); toast({ title: isEdit ? "✅ Invoice Updated" : "✅ Invoice Created", description: isEdit ? "Invoice has been updated successfully" : "Invoice has been created successfully", }); navigate(createPageUrl('Invoices')); }, }); const handleAddStaffEntry = () => { setFormData({ ...formData, staff_entries: [ ...formData.staff_entries, { name: "Mohsin", date: format(new Date(), 'MM/dd/yyyy'), position: "Bartender", check_in: "hh:mm", lunch: 0, check_out: "", worked_hours: 0, regular_hours: 0, ot_hours: 0, dt_hours: 0, rate: 52.68, regular_value: 0, ot_value: 0, dt_value: 0, total: 0 } ] }); }; const handleAddCharge = () => { setFormData({ ...formData, charges: [ ...formData.charges, { name: "Gas Compensation", qty: 7.30, rate: 0, price: 0 } ] }); }; const handleStaffChange = (index, field, value) => { const newEntries = [...formData.staff_entries]; newEntries[index] = { ...newEntries[index], [field]: value }; // Recalculate totals if time-related fields change if (['worked_hours', 'regular_hours', 'ot_hours', 'dt_hours', 'rate'].includes(field)) { const entry = newEntries[index]; entry.regular_value = (entry.regular_hours || 0) * (entry.rate || 0); entry.ot_value = (entry.ot_hours || 0) * (entry.rate || 0) * 1.5; entry.dt_value = (entry.dt_hours || 0) * (entry.rate || 0) * 2; entry.total = entry.regular_value + entry.ot_value + entry.dt_value; } setFormData({ ...formData, staff_entries: newEntries }); }; const handleChargeChange = (index, field, value) => { const newCharges = [...formData.charges]; newCharges[index] = { ...newCharges[index], [field]: value }; if (['qty', 'rate'].includes(field)) { newCharges[index].price = (newCharges[index].qty || 0) * (newCharges[index].rate || 0); } setFormData({ ...formData, charges: newCharges }); }; const handleRemoveStaff = (index) => { setFormData({ ...formData, staff_entries: formData.staff_entries.filter((_, i) => i !== index) }); }; const handleRemoveCharge = (index) => { setFormData({ ...formData, charges: formData.charges.filter((_, i) => i !== index) }); }; const handleTimeSelect = (entryIndex, field) => { const timeString = `${selectedTime.hours}:${selectedTime.minutes} ${selectedTime.period}`; handleStaffChange(entryIndex, field, timeString); setTimePickerOpen(null); }; const calculateTotals = () => { const staffTotal = formData.staff_entries.reduce((sum, entry) => sum + (entry.total || 0), 0); const chargesTotal = formData.charges.reduce((sum, charge) => sum + (charge.price || 0), 0); const subtotal = staffTotal + chargesTotal; const otherCharges = parseFloat(formData.other_charges) || 0; const grandTotal = subtotal + otherCharges; return { subtotal, otherCharges, grandTotal }; }; const totals = calculateTotals(); return (

{isEdit ? 'Edit Invoice' : 'Create New Invoice'}

Complete all invoice details below

{existingInvoice?.status || "Draft"}
{/* Invoice Details Header */}
📄

Invoice Details

Event: {formData.event_name || "Internal Support"}

Invoice Number
{formData.invoice_number}
setFormData({ ...formData, invoice_date: e.target.value })} className="mt-1 border-blue-200 focus:border-blue-500" />
setFormData({ ...formData, due_date: e.target.value })} className="mt-1 border-blue-200 focus:border-blue-500" />
setFormData({ ...formData, hub: e.target.value })} placeholder="Hub" className="mt-1" />
setFormData({ ...formData, manager: e.target.value })} placeholder="Manager Name" className="mt-1" />
setFormData({ ...formData, vendor_id: e.target.value })} placeholder="Vendor #" className="mt-1" />
setFormData({ ...formData, payment_terms: "30", due_date: format(addDays(new Date(formData.invoice_date), 30), 'yyyy-MM-dd') })} > 30 days setFormData({ ...formData, payment_terms: "45", due_date: format(addDays(new Date(formData.invoice_date), 45), 'yyyy-MM-dd') })} > 45 days setFormData({ ...formData, payment_terms: "60", due_date: format(addDays(new Date(formData.invoice_date), 60), 'yyyy-MM-dd') })} > 60 days
Department: setFormData({ ...formData, department: e.target.value })} placeholder="INV-G00G20242" className="h-8 w-48" />
PO#: setFormData({ ...formData, po_reference: e.target.value })} placeholder="INV-G00G20242" className="h-8 w-48" />
{/* From and To */}

F
From (Vendor):

setFormData({ ...formData, from_company: { ...formData.from_company, name: e.target.value } })} className="font-semibold mb-2" /> setFormData({ ...formData, from_company: { ...formData.from_company, address: e.target.value } })} className="text-sm" /> setFormData({ ...formData, from_company: { ...formData.from_company, phone: e.target.value } })} className="text-sm" /> setFormData({ ...formData, from_company: { ...formData.from_company, email: e.target.value } })} className="text-sm" />

T
To (Client):

Company: setFormData({ ...formData, to_company: { ...formData.to_company, name: e.target.value } })} className="flex-1" />
Phone: setFormData({ ...formData, to_company: { ...formData.to_company, phone: e.target.value } })} className="flex-1" />
Manager Name: setFormData({ ...formData, to_company: { ...formData.to_company, manager_name: e.target.value } })} className="flex-1" />
Email: setFormData({ ...formData, to_company: { ...formData.to_company, email: e.target.value } })} className="flex-1" />
Hub Name: setFormData({ ...formData, to_company: { ...formData.to_company, hub_name: e.target.value } })} className="flex-1" />
Address: setFormData({ ...formData, to_company: { ...formData.to_company, address: e.target.value } })} className="flex-1" />
Vendor #: setFormData({ ...formData, to_company: { ...formData.to_company, vendor_id: e.target.value } })} className="flex-1" />
{/* Staff Table */}
👥

Staff Entries

{formData.staff_entries.length} entries

{formData.staff_entries.map((entry, idx) => ( ))}
# Name ClockIn Lunch Checkout Worked H Reg H OT Hours DT Hours Rate Reg Value OT Value DT Value Total Action
{idx + 1} handleStaffChange(idx, 'name', e.target.value)} className="h-8 w-24" /> setTimePickerOpen(open ? `checkin-${idx}` : null)}>
setSelectedTime({ ...selectedTime, hours: e.target.value.padStart(2, '0') })} className="w-16" placeholder="HH" /> : setSelectedTime({ ...selectedTime, minutes: e.target.value.padStart(2, '0') })} className="w-16" placeholder="MM" />
handleStaffChange(idx, 'lunch', parseFloat(e.target.value))} className="h-8 w-16" /> setTimePickerOpen(open ? `checkout-${idx}` : null)}>
setSelectedTime({ ...selectedTime, hours: e.target.value.padStart(2, '0') })} className="w-16" placeholder="HH" /> : setSelectedTime({ ...selectedTime, minutes: e.target.value.padStart(2, '0') })} className="w-16" placeholder="MM" />
handleStaffChange(idx, 'worked_hours', parseFloat(e.target.value))} className="h-8 w-16" /> handleStaffChange(idx, 'regular_hours', parseFloat(e.target.value))} className="h-8 w-16" /> handleStaffChange(idx, 'ot_hours', parseFloat(e.target.value))} className="h-8 w-16" /> handleStaffChange(idx, 'dt_hours', parseFloat(e.target.value))} className="h-8 w-16" /> handleStaffChange(idx, 'rate', parseFloat(e.target.value))} className="h-8 w-20" /> ${entry.regular_value?.toFixed(2) || "0.00"} ${entry.ot_value?.toFixed(2) || "0.00"} ${entry.dt_value?.toFixed(2) || "0.00"} ${entry.total?.toFixed(2) || "0.00"}
{/* Charges */}
💰

Additional Charges

{formData.charges.length} charges

{formData.charges.map((charge, idx) => ( ))}
# Name QTY Rate Price Actions
{idx + 1} handleChargeChange(idx, 'name', e.target.value)} className="h-8" /> handleChargeChange(idx, 'qty', parseFloat(e.target.value))} className="h-8 w-20" /> handleChargeChange(idx, 'rate', parseFloat(e.target.value))} className="h-8 w-20" /> ${charge.price?.toFixed(2) || "0.00"}
{/* Totals */}
Sub total: ${totals.subtotal.toFixed(2)}
Other charges: setFormData({ ...formData, other_charges: e.target.value })} className="h-9 w-32 text-right border-blue-300 focus:border-blue-500 bg-white" />
Grand total: ${totals.grandTotal.toFixed(2)}
{/* Notes */}