Files
Krow-workspace/frontend-web/src/pages/InvoiceEditor.jsx
2025-11-21 09:13:05 -05:00

869 lines
38 KiB
JavaScript

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 (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-slate-50 p-6">
<div className="max-w-7xl mx-auto">
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-4">
<Button variant="outline" onClick={() => navigate(createPageUrl('Invoices'))} className="bg-white">
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Invoices
</Button>
<div>
<h1 className="text-2xl font-bold text-slate-900">{isEdit ? 'Edit Invoice' : 'Create New Invoice'}</h1>
<p className="text-sm text-slate-600">Complete all invoice details below</p>
</div>
</div>
<Badge className="bg-blue-100 text-blue-700 text-sm px-3 py-1">
{existingInvoice?.status || "Draft"}
</Badge>
</div>
<Card className="p-8 bg-white shadow-lg border-blue-100">
{/* Invoice Details Header */}
<div className="flex items-start justify-between mb-6 pb-6 border-b border-blue-100">
<div className="flex-1">
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-lg">📄</span>
</div>
<div>
<h2 className="text-xl font-bold text-slate-900">Invoice Details</h2>
<p className="text-sm text-slate-500">Event: {formData.event_name || "Internal Support"}</p>
</div>
</div>
<div className="bg-gradient-to-r from-blue-50 to-blue-100 p-4 rounded-lg mb-4">
<div className="text-xs text-blue-600 font-semibold mb-1">Invoice Number</div>
<div className="font-bold text-2xl text-blue-900">{formData.invoice_number}</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<Label className="text-xs font-semibold text-slate-700">Invoice Date</Label>
<Input
type="date"
value={formData.invoice_date}
onChange={(e) => setFormData({ ...formData, invoice_date: e.target.value })}
className="mt-1 border-blue-200 focus:border-blue-500"
/>
</div>
<div>
<Label className="text-xs font-semibold text-slate-700">Due Date</Label>
<Input
type="date"
value={formData.due_date}
onChange={(e) => setFormData({ ...formData, due_date: e.target.value })}
className="mt-1 border-blue-200 focus:border-blue-500"
/>
</div>
</div>
<div className="mb-4">
<Label className="text-xs">Hub</Label>
<Input
value={formData.hub}
onChange={(e) => setFormData({ ...formData, hub: e.target.value })}
placeholder="Hub"
className="mt-1"
/>
</div>
<div className="mb-4">
<Label className="text-xs">Manager</Label>
<Input
value={formData.manager}
onChange={(e) => setFormData({ ...formData, manager: e.target.value })}
placeholder="Manager Name"
className="mt-1"
/>
</div>
<div>
<Label className="text-xs">Vendor #</Label>
<Input
value={formData.vendor_id}
onChange={(e) => setFormData({ ...formData, vendor_id: e.target.value })}
placeholder="Vendor #"
className="mt-1"
/>
</div>
</div>
<div className="flex-1 text-right">
<div className="mb-4">
<Label className="text-xs font-semibold text-slate-700 block mb-2">Payment Terms</Label>
<div className="flex gap-2 justify-end">
<Badge
className={`cursor-pointer transition-all ${formData.payment_terms === "30" ? "bg-blue-600 text-white" : "bg-white border-2 border-slate-200 text-slate-700 hover:border-blue-300"}`}
onClick={() => setFormData({ ...formData, payment_terms: "30", due_date: format(addDays(new Date(formData.invoice_date), 30), 'yyyy-MM-dd') })}
>
30 days
</Badge>
<Badge
className={`cursor-pointer transition-all ${formData.payment_terms === "45" ? "bg-blue-600 text-white" : "bg-white border-2 border-slate-200 text-slate-700 hover:border-blue-300"}`}
onClick={() => setFormData({ ...formData, payment_terms: "45", due_date: format(addDays(new Date(formData.invoice_date), 45), 'yyyy-MM-dd') })}
>
45 days
</Badge>
<Badge
className={`cursor-pointer transition-all ${formData.payment_terms === "60" ? "bg-blue-600 text-white" : "bg-white border-2 border-slate-200 text-slate-700 hover:border-blue-300"}`}
onClick={() => setFormData({ ...formData, payment_terms: "60", due_date: format(addDays(new Date(formData.invoice_date), 60), 'yyyy-MM-dd') })}
>
60 days
</Badge>
</div>
</div>
<div className="mt-4 space-y-2">
<div className="flex items-center gap-2">
<span className="text-sm text-slate-500">Department:</span>
<Input
value={formData.department}
onChange={(e) => setFormData({ ...formData, department: e.target.value })}
placeholder="INV-G00G20242"
className="h-8 w-48"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-slate-500">PO#:</span>
<Input
value={formData.po_reference}
onChange={(e) => setFormData({ ...formData, po_reference: e.target.value })}
placeholder="INV-G00G20242"
className="h-8 w-48"
/>
</div>
</div>
</div>
</div>
{/* From and To */}
<div className="grid grid-cols-2 gap-6 mb-6">
<div className="bg-gradient-to-br from-blue-50 to-blue-100 p-5 rounded-xl border border-blue-200">
<h3 className="font-bold mb-4 flex items-center gap-2 text-blue-900">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white text-sm font-bold shadow-md">F</div>
From (Vendor):
</h3>
<div className="space-y-2 text-sm">
<Input
value={formData.from_company.name}
onChange={(e) => setFormData({
...formData,
from_company: { ...formData.from_company, name: e.target.value }
})}
className="font-semibold mb-2"
/>
<Input
value={formData.from_company.address}
onChange={(e) => setFormData({
...formData,
from_company: { ...formData.from_company, address: e.target.value }
})}
className="text-sm"
/>
<Input
value={formData.from_company.phone}
onChange={(e) => setFormData({
...formData,
from_company: { ...formData.from_company, phone: e.target.value }
})}
className="text-sm"
/>
<Input
value={formData.from_company.email}
onChange={(e) => setFormData({
...formData,
from_company: { ...formData.from_company, email: e.target.value }
})}
className="text-sm"
/>
</div>
</div>
<div className="bg-gradient-to-br from-slate-50 to-slate-100 p-5 rounded-xl border border-slate-200">
<h3 className="font-bold mb-4 flex items-center gap-2 text-slate-900">
<div className="w-8 h-8 bg-slate-600 rounded-lg flex items-center justify-center text-white text-sm font-bold shadow-md">T</div>
To (Client):
</h3>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Company:</span>
<Input
value={formData.to_company.name}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, name: e.target.value }
})}
className="flex-1"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Phone:</span>
<Input
value={formData.to_company.phone}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, phone: e.target.value }
})}
className="flex-1"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Manager Name:</span>
<Input
value={formData.to_company.manager_name}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, manager_name: e.target.value }
})}
className="flex-1"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Email:</span>
<Input
value={formData.to_company.email}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, email: e.target.value }
})}
className="flex-1"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Hub Name:</span>
<Input
value={formData.to_company.hub_name}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, hub_name: e.target.value }
})}
className="flex-1"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Address:</span>
<Input
value={formData.to_company.address}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, address: e.target.value }
})}
className="flex-1"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-500 w-32">Vendor #:</span>
<Input
value={formData.to_company.vendor_id}
onChange={(e) => setFormData({
...formData,
to_company: { ...formData.to_company, vendor_id: e.target.value }
})}
className="flex-1"
/>
</div>
</div>
</div>
</div>
{/* Staff Table */}
<div className="mb-6">
<div className="flex items-center justify-between mb-4 p-4 bg-gradient-to-r from-blue-50 to-blue-100 rounded-lg">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-lg">👥</span>
</div>
<div>
<h3 className="font-bold text-blue-900">Staff Entries</h3>
<p className="text-xs text-blue-700">{formData.staff_entries.length} entries</p>
</div>
</div>
<Button size="sm" onClick={handleAddStaffEntry} className="bg-blue-600 hover:bg-blue-700 text-white shadow-md">
<Plus className="w-4 h-4 mr-1" />
Add Staff Entry
</Button>
</div>
<div className="overflow-x-auto border rounded-lg">
<table className="w-full text-sm">
<thead className="bg-slate-50">
<tr>
<th className="p-2 text-left">#</th>
<th className="p-2 text-left">Name</th>
<th className="p-2 text-left">ClockIn</th>
<th className="p-2 text-left">Lunch</th>
<th className="p-2 text-left">Checkout</th>
<th className="p-2 text-left">Worked H</th>
<th className="p-2 text-left">Reg H</th>
<th className="p-2 text-left">OT Hours</th>
<th className="p-2 text-left">DT Hours</th>
<th className="p-2 text-left">Rate</th>
<th className="p-2 text-left">Reg Value</th>
<th className="p-2 text-left">OT Value</th>
<th className="p-2 text-left">DT Value</th>
<th className="p-2 text-left">Total</th>
<th className="p-2">Action</th>
</tr>
</thead>
<tbody>
{formData.staff_entries.map((entry, idx) => (
<tr key={idx} className="border-t hover:bg-slate-50">
<td className="p-2">{idx + 1}</td>
<td className="p-2">
<Input
value={entry.name}
onChange={(e) => handleStaffChange(idx, 'name', e.target.value)}
className="h-8 w-24"
/>
</td>
<td className="p-2">
<Popover open={timePickerOpen === `checkin-${idx}`} onOpenChange={(open) => setTimePickerOpen(open ? `checkin-${idx}` : null)}>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 w-24 justify-start font-normal">
<Clock className="w-3 h-3 mr-1" />
{entry.check_in}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-3">
<div className="space-y-2">
<div className="flex gap-2">
<Input
type="number"
min="01"
max="12"
value={selectedTime.hours}
onChange={(e) => setSelectedTime({ ...selectedTime, hours: e.target.value.padStart(2, '0') })}
className="w-16"
placeholder="HH"
/>
<span className="text-2xl">:</span>
<Input
type="number"
min="00"
max="59"
value={selectedTime.minutes}
onChange={(e) => setSelectedTime({ ...selectedTime, minutes: e.target.value.padStart(2, '0') })}
className="w-16"
placeholder="MM"
/>
<Select value={selectedTime.period} onValueChange={(val) => setSelectedTime({ ...selectedTime, period: val })}>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AM">AM</SelectItem>
<SelectItem value="PM">PM</SelectItem>
</SelectContent>
</Select>
</div>
<Button size="sm" onClick={() => handleTimeSelect(idx, 'check_in')} className="w-full">
Set Time
</Button>
</div>
</PopoverContent>
</Popover>
</td>
<td className="p-2">
<Input
type="number"
value={entry.lunch}
onChange={(e) => handleStaffChange(idx, 'lunch', parseFloat(e.target.value))}
className="h-8 w-16"
/>
</td>
<td className="p-2">
<Popover open={timePickerOpen === `checkout-${idx}`} onOpenChange={(open) => setTimePickerOpen(open ? `checkout-${idx}` : null)}>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 w-24 justify-start font-normal">
<Clock className="w-3 h-3 mr-1" />
{entry.check_out || "hh:mm"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-3">
<div className="space-y-2">
<div className="flex gap-2">
<Input
type="number"
min="01"
max="12"
value={selectedTime.hours}
onChange={(e) => setSelectedTime({ ...selectedTime, hours: e.target.value.padStart(2, '0') })}
className="w-16"
placeholder="HH"
/>
<span className="text-2xl">:</span>
<Input
type="number"
min="00"
max="59"
value={selectedTime.minutes}
onChange={(e) => setSelectedTime({ ...selectedTime, minutes: e.target.value.padStart(2, '0') })}
className="w-16"
placeholder="MM"
/>
<Select value={selectedTime.period} onValueChange={(val) => setSelectedTime({ ...selectedTime, period: val })}>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AM">AM</SelectItem>
<SelectItem value="PM">PM</SelectItem>
</SelectContent>
</Select>
</div>
<Button size="sm" onClick={() => handleTimeSelect(idx, 'check_out')} className="w-full">
Set Time
</Button>
</div>
</PopoverContent>
</Popover>
</td>
<td className="p-2">
<Input
type="number"
step="0.1"
value={entry.worked_hours}
onChange={(e) => handleStaffChange(idx, 'worked_hours', parseFloat(e.target.value))}
className="h-8 w-16"
/>
</td>
<td className="p-2">
<Input
type="number"
step="0.1"
value={entry.regular_hours}
onChange={(e) => handleStaffChange(idx, 'regular_hours', parseFloat(e.target.value))}
className="h-8 w-16"
/>
</td>
<td className="p-2">
<Input
type="number"
step="0.1"
value={entry.ot_hours}
onChange={(e) => handleStaffChange(idx, 'ot_hours', parseFloat(e.target.value))}
className="h-8 w-16"
/>
</td>
<td className="p-2">
<Input
type="number"
step="0.1"
value={entry.dt_hours}
onChange={(e) => handleStaffChange(idx, 'dt_hours', parseFloat(e.target.value))}
className="h-8 w-16"
/>
</td>
<td className="p-2">
<Input
type="number"
step="0.01"
value={entry.rate}
onChange={(e) => handleStaffChange(idx, 'rate', parseFloat(e.target.value))}
className="h-8 w-20"
/>
</td>
<td className="p-2 text-right">${entry.regular_value?.toFixed(2) || "0.00"}</td>
<td className="p-2 text-right">${entry.ot_value?.toFixed(2) || "0.00"}</td>
<td className="p-2 text-right">${entry.dt_value?.toFixed(2) || "0.00"}</td>
<td className="p-2 text-right font-semibold">${entry.total?.toFixed(2) || "0.00"}</td>
<td className="p-2 text-center">
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveStaff(idx)}
className="h-8 w-8 p-0 text-red-600 hover:bg-red-50"
>
<Trash2 className="w-4 h-4" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Charges */}
<div className="mb-6">
<div className="flex items-center justify-between mb-4 p-4 bg-gradient-to-r from-green-50 to-emerald-100 rounded-lg">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-emerald-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-lg">💰</span>
</div>
<div>
<h3 className="font-bold text-emerald-900">Additional Charges</h3>
<p className="text-xs text-emerald-700">{formData.charges.length} charges</p>
</div>
</div>
<Button size="sm" onClick={handleAddCharge} className="bg-emerald-600 hover:bg-emerald-700 text-white shadow-md">
<Plus className="w-4 h-4 mr-1" />
Add Charge
</Button>
</div>
<div className="overflow-x-auto border rounded-lg">
<table className="w-full text-sm">
<thead className="bg-slate-50">
<tr>
<th className="p-2 text-left">#</th>
<th className="p-2 text-left">Name</th>
<th className="p-2 text-left">QTY</th>
<th className="p-2 text-left">Rate</th>
<th className="p-2 text-left">Price</th>
<th className="p-2">Actions</th>
</tr>
</thead>
<tbody>
{formData.charges.map((charge, idx) => (
<tr key={idx} className="border-t hover:bg-slate-50">
<td className="p-2">{idx + 1}</td>
<td className="p-2">
<Input
value={charge.name}
onChange={(e) => handleChargeChange(idx, 'name', e.target.value)}
className="h-8"
/>
</td>
<td className="p-2">
<Input
type="number"
step="0.01"
value={charge.qty}
onChange={(e) => handleChargeChange(idx, 'qty', parseFloat(e.target.value))}
className="h-8 w-20"
/>
</td>
<td className="p-2">
<Input
type="number"
step="0.01"
value={charge.rate}
onChange={(e) => handleChargeChange(idx, 'rate', parseFloat(e.target.value))}
className="h-8 w-20"
/>
</td>
<td className="p-2">${charge.price?.toFixed(2) || "0.00"}</td>
<td className="p-2 text-center">
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveCharge(idx)}
className="h-8 w-8 p-0 text-red-600 hover:bg-red-50"
>
<Trash2 className="w-4 h-4" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Totals */}
<div className="flex justify-end mb-6">
<div className="w-96 bg-gradient-to-br from-blue-50 to-blue-100 p-6 rounded-xl border-2 border-blue-200 shadow-lg">
<div className="space-y-4">
<div className="flex justify-between text-sm">
<span className="text-slate-600">Sub total:</span>
<span className="font-semibold text-slate-900">${totals.subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-slate-600">Other charges:</span>
<Input
type="number"
step="0.01"
value={formData.other_charges}
onChange={(e) => setFormData({ ...formData, other_charges: e.target.value })}
className="h-9 w-32 text-right border-blue-300 focus:border-blue-500 bg-white"
/>
</div>
<div className="flex justify-between text-xl font-bold pt-4 border-t-2 border-blue-300">
<span className="text-blue-900">Grand total:</span>
<span className="text-blue-900">${totals.grandTotal.toFixed(2)}</span>
</div>
</div>
</div>
</div>
{/* Notes */}
<div className="mb-6">
<Label className="mb-2 block">Notes</Label>
<Textarea
value={formData.notes}
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
placeholder="Enter your notes here..."
rows={3}
/>
</div>
{/* Actions */}
<div className="flex justify-between items-center pt-6 border-t-2 border-blue-100">
<Button variant="outline" onClick={() => navigate(createPageUrl('Invoices'))} className="border-slate-300">
Cancel
</Button>
<div className="flex gap-3">
<Button
variant="outline"
onClick={() => saveMutation.mutate({ ...formData, status: "Draft" })}
disabled={saveMutation.isPending}
className="border-blue-300 text-blue-700 hover:bg-blue-50"
>
Save as Draft
</Button>
<Button
onClick={() => saveMutation.mutate(formData)}
disabled={saveMutation.isPending}
className="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-semibold px-8 shadow-lg"
>
{saveMutation.isPending ? "Saving..." : isEdit ? "Update Invoice" : "Create Invoice"}
</Button>
</div>
</div>
</Card>
</div>
</div>
);
}