import React, { useState, useEffect } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery } from "@tanstack/react-query"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Plus, Save, FileText, Building2, Calendar, Zap, Shield, Search, Minus, Trash2, MapPin, X, RefreshCw, Users, CheckCircle2 } from "lucide-react"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Calendar as CalendarComponent } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { format } from "date-fns"; import { Checkbox } from "@/components/ui/checkbox"; import { Badge } from "@/components/ui/badge"; import { Textarea } from "@/components/ui/textarea"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command"; const UNIFORM_TYPES = ["Type 1", "Type 2", "Type 3", "All Black", "Business Casual", "Chef Whites"]; export default function EventForm({ event, onSubmit, isSubmitting, currentUser }) { const [formData, setFormData] = useState(event || { event_name: "", order_type: "one_time", // rapid, one_time, recurring, permanent recurrence_type: "single", // This field is largely superseded by recurring_frequency in the new UI, but kept for compatibility. recurrence_start_date: "", recurrence_end_date: "", scatter_dates: [], recurring_days: [], // Added for weekly recurring recurring_frequency: "weekly", // weekly, monthly, custom - default for recurring events business_id: "", business_name: "", hub: "", po_reference: "", status: "Draft", date: "", include_backup: false, backup_staff_count: 0, shifts: [{ shift_name: "Shift 1", location_address: "", same_as_billing: true, roles: [{ role: "", department: "", count: 1, start_time: "09:00", end_time: "17:00", hours: 8, uniform: "Type 1", break_minutes: 30, rate_per_hour: 0, total_value: 0 }] }], notes: "", total: 0 }); const [roleSearchOpen, setRoleSearchOpen] = useState({}); const { data: user } = useQuery({ queryKey: ['current-user-form'], queryFn: () => base44.auth.me(), enabled: !currentUser, }); const currentUserData = currentUser || user; const userRole = currentUserData?.user_role || currentUserData?.role || "admin"; const isVendor = userRole === "vendor"; const { data: businesses = [] } = useQuery({ queryKey: ['businesses'], queryFn: () => base44.entities.Business.list(), initialData: [], }); const { data: allRates = [] } = useQuery({ queryKey: ['vendor-rates-all'], queryFn: () => base44.entities.VendorRate.list(), initialData: [], }); const { data: vendorSettings = [] } = useQuery({ queryKey: ['vendor-settings'], queryFn: () => base44.entities.VendorDefaultSettings.list(), initialData: [], }); // Get unique roles for dropdown const availableRoles = [...new Set(allRates.map(r => r.role_name))].sort(); // Get hubs for current vendor const vendorHubs = isVendor ? [...new Set(businesses.filter(b => b.business_name === currentUserData?.company_name).map(b => b.hub_building))].filter(Boolean) : []; // Auto-fill client details if current user is a client useEffect(() => { if (currentUserData && userRole === "client" && !event) { const clientBusiness = businesses.find(b => b.email === currentUserData.email || b.contact_name === currentUserData.full_name ); if (clientBusiness) { setFormData(prev => ({ ...prev, business_id: clientBusiness.id, business_name: clientBusiness.business_name, hub: clientBusiness.hub_building || prev.hub, shifts: prev.shifts.map(shift => ({ ...shift, location_address: clientBusiness.address || shift.location_address })) })); } } }, [currentUserData, businesses, event, userRole]); useEffect(() => { if (event) { setFormData(event); } }, [event]); const handleChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); }; const handleBusinessChange = (businessId) => { const selectedBusiness = businesses.find(b => b.id === businessId); if (selectedBusiness) { setFormData(prev => ({ ...prev, business_id: businessId, business_name: selectedBusiness.business_name || "", shifts: prev.shifts.map(shift => ({ ...shift, location_address: shift.same_as_billing ? selectedBusiness.address || shift.location_address : shift.location_address })) })); } }; const calculateHours = (startTime, endTime, breakMinutes = 0) => { if (!startTime || !endTime) return 0; const [startHour, startMin] = startTime.split(':').map(Number); const [endHour, endMin] = endTime.split(':').map(Number); const startMinutes = startHour * 60 + startMin; const endMinutes = endHour * 60 + endMin; let totalMinutes = endMinutes - startMinutes; if (totalMinutes < 0) totalMinutes += 24 * 60; totalMinutes -= (breakMinutes || 0); return Math.max(0, totalMinutes / 60); }; const getRateForRole = (roleName) => { const rate = allRates.find(r => r.role_name === roleName && r.is_active); return rate ? parseFloat(rate.client_rate || 0) : 0; }; const handleRoleChange = (shiftIndex, roleIndex, field, value) => { setFormData(prev => { const newShifts = [...prev.shifts]; const role = newShifts[shiftIndex].roles[roleIndex]; role[field] = value; // Auto-populate rate when role is selected if (field === 'role') { const rate = getRateForRole(value); role.rate_per_hour = rate; } // Recalculate hours when time changes if (field === 'start_time' || field === 'end_time' || field === 'break_minutes') { role.hours = calculateHours(role.start_time, role.end_time, role.break_minutes); } // Recalculate total role.total_value = (role.rate_per_hour || 0) * (role.hours || 0) * (role.count || 1); return { ...prev, shifts: newShifts }; }); // Recalculate grand total updateGrandTotal(); }; const updateGrandTotal = () => { setTimeout(() => { setFormData(prev => { const total = prev.shifts.reduce((sum, shift) => { const shiftTotal = shift.roles.reduce((roleSum, role) => roleSum + (role.total_value || 0), 0); return sum + shiftTotal; }, 0); return { ...prev, total }; }); }, 0); }; const handleAddRole = (shiftIndex) => { setFormData(prev => { const newShifts = [...prev.shifts]; newShifts[shiftIndex].roles.push({ role: "", department: "", count: 1, start_time: "09:00", end_time: "17:00", hours: 8, uniform: "Type 1", break_minutes: 30, rate_per_hour: 0, total_value: 0 }); return { ...prev, shifts: newShifts }; }); }; const handleRemoveRole = (shiftIndex, roleIndex) => { setFormData(prev => { const newShifts = [...prev.shifts]; newShifts[shiftIndex].roles.splice(roleIndex, 1); return { ...prev, shifts: newShifts }; }); updateGrandTotal(); }; const handleAddShift = () => { setFormData(prev => ({ ...prev, shifts: [...prev.shifts, { shift_name: `Shift ${prev.shifts.length + 1}`, location_address: "", same_as_billing: true, roles: [{ role: "", department: "", count: 1, start_time: "09:00", end_time: "17:00", hours: 8, uniform: "Type 1", break_minutes: 30, rate_per_hour: 0, total_value: 0 }] }] })); }; const handleOrderTypeChange = (type) => { setFormData(prev => { const newState = { ...prev, order_type: type, date: "", // Always clear the single 'date' field on order type change, it will be set by rapid/one_time/permanent if needed. recurrence_start_date: "", // Clear these when order type changes recurrence_end_date: "", scatter_dates: [], // Clear these when order type changes recurring_days: [], // Clear these when order type changes recurring_frequency: "weekly" // Default for recurring }; // Set recurrence_type for backend compatibility if it's still used there, but new UI uses recurring_frequency if (type === "recurring") { newState.recurrence_type = "date_range"; // Default to 'date_range' to maintain previous behavior if possible } else { newState.recurrence_type = "single"; // For one_time, rapid, permanent } if (type === "rapid" && !prev.date) { // Only set if not already set, or if changing from another type newState.date = format(new Date(), 'yyyy-MM-dd'); // Default rapid to today if no date } else if (type === "one_time" || type === "permanent") { newState.date = prev.date; // Preserve date if user had one for one_time/permanent } // If type is recurring, date is already cleared. Other recurring fields default/cleared. return newState; }); }; const handleScatterDateSelect = (dates) => { setFormData(prev => ({ ...prev, scatter_dates: dates?.map(d => format(d, 'yyyy-MM-dd')).sort() || [] })); }; const handleDayToggle = (day) => { setFormData(prev => { const days = prev.recurring_days || []; const exists = days.includes(day); return { ...prev, recurring_days: exists ? days.filter(d => d !== day) : [...days, day].sort((a,b) => a-b) // Ensure days are sorted }; }); }; const handleSubmit = (e, isDraft = false) => { e.preventDefault(); let status; if (isDraft) { status = "DRAFT"; } else { switch (formData.order_type) { case "rapid": status = "ACTIVE"; // Rapid requests are active immediately upon submission break; case "one_time": case "recurring": case "permanent": default: // In case of an unexpected order_type, default to Pending status = "PENDING"; // These types typically need approval/processing break; } } const dataToSubmit = { ...formData, status: status }; onSubmit(dataToSubmit); }; return (
handleSubmit(e, false)} className="space-y-6"> {/* Order Type Selection - 4 Options */}

Select Order Type

{/* Rapid */} {/* One-Time */} {/* Recurring */} {/* Permanent */}
{/* Recurring Options - Android Style */} {formData.order_type === 'recurring' && (
{/* Frequency Selection */}
{/* Weekly - Android Alarm Style Day Selection */} {formData.recurring_frequency === 'weekly' && (
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((day, index) => { const isSelected = (formData.recurring_days || []).includes(index); return ( ); })}
{formData.recurring_days && formData.recurring_days.length > 0 && (
{formData.recurring_days.length} day{formData.recurring_days.length > 1 ? 's' : ''} selected
)}
)} {/* Monthly - Date Range */} {formData.recurring_frequency === 'monthly' && (
handleChange('recurrence_start_date', e.target.value)} className="border-slate-300 bg-white h-12" />
handleChange('recurrence_end_date', e.target.value)} className="border-slate-300 bg-white h-12" />
)} {/* Custom - Multiple Specific Dates */} {formData.recurring_frequency === 'custom' && (
new Date(d)) || []} onSelect={handleScatterDateSelect} initialFocus className="rounded-lg border-0" /> {formData.scatter_dates && formData.scatter_dates.length > 0 && (
{formData.scatter_dates.map(date => ( {format(new Date(date), 'MMM d, yyyy')} ))}
)}
)} {/* Summary Section */} {((formData.recurring_frequency === 'weekly' && formData.recurring_days?.length > 0) || (formData.recurring_frequency === 'monthly' && formData.recurrence_start_date && formData.recurrence_end_date) || (formData.recurring_frequency === 'custom' && formData.scatter_dates?.length > 0)) && (
Recurrence Summary
{formData.recurring_frequency === 'weekly' && formData.recurring_days?.length > 0 && (

• Repeats every {['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] .filter((_, i) => formData.recurring_days.includes(i)) .join(', ')}

)} {formData.recurring_frequency === 'monthly' && formData.recurrence_start_date && formData.recurrence_end_date && (

• Repeats monthly from {format(new Date(formData.recurrence_start_date), 'MMM d, yyyy')} to {format(new Date(formData.recurrence_end_date), 'MMM d, yyyy')}

)} {formData.recurring_frequency === 'custom' && formData.scatter_dates?.length > 0 && (

{formData.scatter_dates.length} specific date{formData.scatter_dates.length > 1 ? 's' : ''} selected

)}
)}
)} {/* Event Details */}

Event

handleChange('event_name', e.target.value)} placeholder="Event Name" required className="border-slate-200" />
{(formData.order_type === 'one_time' || formData.order_type === 'rapid' || formData.order_type === 'permanent') && (
handleChange('date', e.target.value)} required className="border-slate-200" />
)} {/* Hub Selection - Show if vendor has multiple locations */} {isVendor && vendorHubs.length > 1 ? (
) : (
handleChange('hub', e.target.value)} placeholder="Hub location" className="border-slate-200" />
)}
{/* PO Reference - Now Optional */}
handleChange('po_reference', e.target.value)} placeholder="PO reference" className="border-slate-200" />
{/* Client/Business Selection */} {isVendor && (
{formData.business_id && businesses.find(b => b.id === formData.business_id)?.rate_group && (

ℹ️ Rate Group: {businesses.find(b => b.id === formData.business_id)?.rate_group} (Auto-detected from location)

)}
)} {/* Zero Risk Backup Staff */}
{ handleChange('include_backup', checked); if (!checked) handleChange('backup_staff_count', 0); }} className="mt-1" />

Extra pool of staff in case of call-outs or no-shows

{formData.include_backup && (
handleChange('backup_staff_count', parseInt(e.target.value) || 1)} className="w-24 mt-1 border-green-300" />
)}
{/* Shifts Section */} {formData.shifts.map((shift, shiftIndex) => (
{shiftIndex + 1}

{shift.shift_name}

{shift.location_address && (

{shift.location_address}

)}
{/* Roles */}
{shift.roles.map((role, roleIndex) => (
{roleIndex + 1}
{/* Service/Role with Search */}
setRoleSearchOpen(prev => ({ ...prev, [`${shiftIndex}-${roleIndex}`]: open }))} > No role found. {availableRoles.map((roleName) => ( { handleRoleChange(shiftIndex, roleIndex, 'role', roleName); setRoleSearchOpen(prev => ({ ...prev, [`${shiftIndex}-${roleIndex}`]: false })); }} > {roleName} ))}
{/* Count */}
handleRoleChange(shiftIndex, roleIndex, 'count', parseInt(e.target.value) || 1)} className="w-16 text-center" />
{/* Start Time */}
handleRoleChange(shiftIndex, roleIndex, 'start_time', e.target.value)} className="border-slate-200" />
{/* End Time */}
handleRoleChange(shiftIndex, roleIndex, 'end_time', e.target.value)} className="border-slate-200" />
{shift.roles.length > 1 && ( )}
{/* Department */}
handleRoleChange(shiftIndex, roleIndex, 'department', e.target.value)} placeholder="Department" className="border-slate-200" />
{/* Hours (Auto-calculated) */}
{role.hours || 0}
{/* Break (min) */}
handleRoleChange(shiftIndex, roleIndex, 'break_minutes', parseInt(e.target.value) || 0)} className="border-slate-200" />
{/* Uniform */}
{/* Rate and Total */}
$
${(role.total_value || 0).toFixed(2)}
))} {/* Add Role Button */}
{/* Shift Total */}
Shift Total: ${shift.roles.reduce((sum, r) => sum + (r.total_value || 0), 0).toFixed(2)}
))} {/* Add Another Shift Button */} {/* Other Instructions */}