200 lines
7.2 KiB
JavaScript
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>
|
|
);
|
|
} |