Files
Krow-workspace/frontend-web-free/src/components/invoices/CreateInvoiceModal.jsx
2025-12-04 18:02:28 -05:00

200 lines
7.2 KiB
JavaScript

import React, { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
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 { useToast } from "@/components/ui/use-toast";
import { base44 } from "@/api/base44Client";
import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
import { format, addDays } from "date-fns";
import { Plus, Trash2, FileEdit } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
export default function CreateInvoiceModal({ open, onClose }) {
const { toast } = useToast();
const queryClient = useQueryClient();
const { data: events = [] } = useQuery({
queryKey: ['events-for-invoice'],
queryFn: () => base44.entities.Event.list(),
enabled: open,
});
const navigate = useNavigate();
const handleAdvancedEditor = () => {
onClose();
navigate(createPageUrl('InvoiceEditor'));
};
const createMutation = useMutation({
mutationFn: async (data) => {
const selectedEvent = events.find(e => e.id === data.event_id);
if (!selectedEvent) throw new Error("Event not found");
// Generate roles and staff entries from event
const roleGroups = {};
if (selectedEvent.assigned_staff && selectedEvent.shifts) {
selectedEvent.shifts.forEach(shift => {
shift.roles?.forEach(role => {
const assignedForRole = selectedEvent.assigned_staff.filter(
s => s.role === role.role
);
if (!roleGroups[role.role]) {
roleGroups[role.role] = {
role_name: role.role,
staff_entries: [],
role_subtotal: 0
};
}
assignedForRole.forEach(staff => {
const workedHours = role.hours || 8;
const baseRate = role.cost_per_hour || role.rate_per_hour || 0;
const regularHours = Math.min(workedHours, 8);
const otHours = Math.max(0, Math.min(workedHours - 8, 4));
const dtHours = Math.max(0, workedHours - 12);
const regularRate = baseRate;
const otRate = baseRate * 1.5;
const dtRate = baseRate * 2;
const regularValue = regularHours * regularRate;
const otValue = otHours * otRate;
const dtValue = dtHours * dtRate;
const total = regularValue + otValue + dtValue;
const entry = {
staff_name: staff.staff_name,
staff_id: staff.staff_id,
date: selectedEvent.date,
position: role.role,
check_in: role.start_time || "09:00 AM",
check_out: role.end_time || "05:00 PM",
worked_hours: workedHours,
regular_hours: regularHours,
ot_hours: otHours,
dt_hours: dtHours,
regular_rate: regularRate,
ot_rate: otRate,
dt_rate: dtRate,
regular_value: regularValue,
ot_value: otValue,
dt_value: dtValue,
rate: baseRate,
total: total
};
roleGroups[role.role].staff_entries.push(entry);
roleGroups[role.role].role_subtotal += total;
});
});
});
}
const roles = Object.values(roleGroups);
const subtotal = roles.reduce((sum, role) => sum + role.role_subtotal, 0);
const otherCharges = parseFloat(data.other_charges) || 0;
const total = subtotal + otherCharges;
const invoiceNumber = `INV-${Math.floor(Math.random() * 10000)}`;
const vendorInfo = {
name: selectedEvent.vendor_name || "Legendary",
address: "848 E Gish Rd Ste 1, San Jose, CA 95112",
email: "orders@legendaryeventstaff.com",
phone: "(408) 936-0180"
};
const clientInfo = {
name: selectedEvent.business_name || "Client Company",
address: selectedEvent.event_location || "Address",
email: selectedEvent.client_email || "",
manager: selectedEvent.client_name || selectedEvent.manager_name || "Manager",
phone: selectedEvent.client_phone || "",
vendor_id: "Vendor #"
};
return base44.entities.Invoice.create({
invoice_number: invoiceNumber,
event_id: selectedEvent.id,
event_name: selectedEvent.event_name,
event_date: selectedEvent.date,
po_reference: data.po_reference || selectedEvent.po_reference,
from_company: vendorInfo,
to_company: clientInfo,
business_name: selectedEvent.business_name,
manager_name: selectedEvent.client_name || selectedEvent.business_name,
vendor_name: selectedEvent.vendor_name,
vendor_id: selectedEvent.vendor_id,
hub: selectedEvent.hub,
cost_center: data.po_reference || selectedEvent.po_reference,
roles: roles,
subtotal: subtotal,
other_charges: otherCharges,
amount: total,
status: "Draft",
issue_date: format(new Date(), 'yyyy-MM-dd'),
due_date: format(addDays(new Date(), 30), 'yyyy-MM-dd'),
is_auto_generated: false,
notes: data.notes,
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['invoices'] });
toast({
title: "✅ Invoice Created",
description: "Invoice has been created successfully",
});
onClose();
setFormData({ event_id: "", po_reference: "", other_charges: 0, notes: "" });
},
});
const handleSubmit = (e) => {
e.preventDefault();
if (!formData.event_id) {
toast({
title: "Error",
description: "Please select an event",
variant: "destructive",
});
return;
}
createMutation.mutate(formData);
};
const completedEvents = events.filter(e => e.status === "Completed");
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Create New Invoice</DialogTitle>
</DialogHeader>
<div className="py-6 text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full mx-auto mb-4 flex items-center justify-center">
<FileEdit className="w-8 h-8 text-blue-600" />
</div>
<h3 className="text-lg font-semibold mb-2">Ready to create an invoice?</h3>
<p className="text-slate-600 mb-6">Use the advanced editor to create a detailed invoice with full control.</p>
<Button
onClick={handleAdvancedEditor}
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-semibold h-12"
>
<FileEdit className="w-5 h-5 mr-2" />
Open Invoice Editor
</Button>
</div>
</DialogContent>
</Dialog>
);
}