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 (
Complete all invoice details below
Event: {formData.event_name || "Internal Support"}
{formData.staff_entries.length} entries
| # | 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" /> |
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" /> |
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"} |
{formData.charges.length} charges
| # | 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"} |