Files
Krow-workspace/frontend-web-free/src/components/events/EventForm.jsx
2025-12-18 09:50:54 -05:00

1103 lines
46 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (
<form onSubmit={(e) => handleSubmit(e, false)} className="space-y-6">
{/* Order Type Selection - 4 Options */}
<Card className="border-slate-200 shadow-sm bg-white">
<CardContent className="p-6">
<h3 className="text-lg font-bold text-[#1C323E] mb-4 text-center">Select Order Type</h3>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{/* Rapid */}
<button
type="button"
onClick={() => handleOrderTypeChange('rapid')}
className={`p-6 rounded-2xl border-3 transition-all duration-300 hover:scale-105 ${
formData.order_type === 'rapid'
? 'border-red-500 bg-gradient-to-br from-red-50 to-orange-50 shadow-lg'
: 'border-slate-200 bg-white hover:border-red-300'
}`}
>
<div className="text-center">
<Zap className={`w-10 h-10 mx-auto mb-3 ${formData.order_type === 'rapid' ? 'text-red-600' : 'text-slate-400'}`} />
<h4 className={`text-2xl font-bold mb-2 ${formData.order_type === 'rapid' ? 'text-red-600' : 'text-slate-700'}`}>
RAPID
</h4>
<p className="text-xs text-slate-600 font-medium">URGENT same-day Coverage</p>
</div>
</button>
{/* One-Time */}
<button
type="button"
onClick={() => handleOrderTypeChange('one_time')}
className={`p-6 rounded-2xl border-3 transition-all duration-300 hover:scale-105 ${
formData.order_type === 'one_time'
? 'border-[#0A39DF] bg-gradient-to-br from-blue-50 to-indigo-50 shadow-lg'
: 'border-slate-200 bg-white hover:border-[#0A39DF]/30'
}`}
>
<div className="text-center">
<Calendar className={`w-10 h-10 mx-auto mb-3 ${formData.order_type === 'one_time' ? 'text-[#0A39DF]' : 'text-slate-400'}`} />
<h4 className={`text-2xl font-bold mb-2 ${formData.order_type === 'one_time' ? 'text-[#0A39DF]' : 'text-slate-700'}`}>
One-Time
</h4>
<p className="text-xs text-slate-600 font-medium">Single Event or Shift Request</p>
</div>
</button>
{/* Recurring */}
<button
type="button"
onClick={() => handleOrderTypeChange('recurring')}
className={`p-6 rounded-2xl border-3 transition-all duration-300 hover:scale-105 ${
formData.order_type === 'recurring'
? 'border-purple-500 bg-gradient-to-br from-purple-50 to-pink-50 shadow-lg'
: 'border-slate-200 bg-white hover:border-purple-300'
}`}
>
<div className="text-center">
<RefreshCw className={`w-10 h-10 mx-auto mb-3 ${formData.order_type === 'recurring' ? 'text-purple-600' : 'text-slate-400'}`} />
<h4 className={`text-2xl font-bold mb-2 ${formData.order_type === 'recurring' ? 'text-purple-600' : 'text-slate-700'}`}>
Recurring
</h4>
<p className="text-xs text-slate-600 font-medium">Ongoing Weekly / Monthly Coverage</p>
</div>
</button>
{/* Permanent */}
<button
type="button"
onClick={() => handleOrderTypeChange('permanent')}
className={`p-6 rounded-2xl border-3 transition-all duration-300 hover:scale-105 ${
formData.order_type === 'permanent'
? 'border-green-500 bg-gradient-to-br from-green-50 to-emerald-50 shadow-lg'
: 'border-slate-200 bg-white hover:border-green-300'
}`}
>
<div className="text-center">
<Users className={`w-10 h-10 mx-auto mb-3 ${formData.order_type === 'permanent' ? 'text-green-600' : 'text-slate-400'}`} />
<h4 className={`text-2xl font-bold mb-2 ${formData.order_type === 'permanent' ? 'text-green-600' : 'text-slate-700'}`}>
Permanent
</h4>
<p className="text-xs text-slate-600 font-medium">Long-Term Staffing Placement</p>
</div>
</button>
</div>
</CardContent>
</Card>
{/* Recurring Options - Android Style */}
{formData.order_type === 'recurring' && (
<Card className="border-[#0A39DF]/20 shadow-md bg-white">
<CardContent className="p-6">
<div className="flex items-center gap-2 mb-5">
<Calendar className="w-5 h-5 text-[#0A39DF]" />
<Label className="text-base font-bold text-[#1C323E]">Recurrence Pattern</Label>
</div>
{/* Frequency Selection */}
<div className="space-y-4 mb-6">
<Label className="text-sm font-semibold text-slate-700">Repeat Frequency</Label>
<div className="grid grid-cols-3 gap-3">
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, recurring_frequency: 'weekly' }))}
className={`p-4 rounded-xl border-2 transition-all text-center ${
formData.recurring_frequency === 'weekly'
? 'border-[#0A39DF] bg-blue-50 text-[#0A39DF] font-semibold'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<RefreshCw className="w-5 h-5 mx-auto mb-2" />
<span className="text-sm">Weekly</span>
</button>
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, recurring_frequency: 'monthly' }))}
className={`p-4 rounded-xl border-2 transition-all text-center ${
formData.recurring_frequency === 'monthly'
? 'border-[#0A39DF] bg-blue-50 text-[#0A39DF] font-semibold'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<Calendar className="w-5 h-5 mx-auto mb-2" />
<span className="text-sm">Monthly</span>
</button>
<button
type="button"
onClick={() => setFormData(prev => ({ ...prev, recurring_frequency: 'custom' }))}
className={`p-4 rounded-xl border-2 transition-all text-center ${
formData.recurring_frequency === 'custom'
? 'border-[#0A39DF] bg-blue-50 text-[#0A39DF] font-semibold'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<Users className="w-5 h-5 mx-auto mb-2" />
<span className="text-sm">Custom</span>
</button>
</div>
</div>
{/* Weekly - Android Alarm Style Day Selection */}
{formData.recurring_frequency === 'weekly' && (
<div className="space-y-4 p-5 bg-gradient-to-br from-blue-50 to-white rounded-xl border border-blue-100">
<Label className="text-sm font-semibold text-slate-700">Repeat on Days</Label>
<div className="flex items-center justify-center gap-2">
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((day, index) => {
const isSelected = (formData.recurring_days || []).includes(index);
return (
<button
key={day}
type="button"
onClick={() => handleDayToggle(index)}
className={`w-12 h-12 rounded-full flex items-center justify-center transition-all text-xs font-medium ${
isSelected
? 'bg-[#0A39DF] text-white shadow-md scale-105 font-bold'
: 'bg-white border-2 border-slate-200 text-slate-600 hover:border-[#0A39DF]/50 hover:scale-105'
}`}
>
{day}
</button>
);
})}
</div>
{formData.recurring_days && formData.recurring_days.length > 0 && (
<div className="text-center text-sm text-slate-600 mt-3">
<span className="font-semibold text-[#0A39DF]">{formData.recurring_days.length}</span> day{formData.recurring_days.length > 1 ? 's' : ''} selected
</div>
)}
</div>
)}
{/* Monthly - Date Range */}
{formData.recurring_frequency === 'monthly' && (
<div className="space-y-4 p-5 bg-gradient-to-br from-purple-50 to-white rounded-xl border border-purple-100">
<Label className="text-sm font-semibold text-slate-700">Select Monthly Pattern</Label>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="recurrence_start_date" className="text-sm font-medium text-slate-700 mb-2 block">
Start Date
</Label>
<Input
id="recurrence_start_date"
type="date"
value={formData.recurrence_start_date || ""}
onChange={(e) => handleChange('recurrence_start_date', e.target.value)}
className="border-slate-300 bg-white h-12"
/>
</div>
<div>
<Label htmlFor="recurrence_end_date" className="text-sm font-medium text-slate-700 mb-2 block">
End Date
</Label>
<Input
id="recurrence_end_date"
type="date"
value={formData.recurrence_end_date || ""}
onChange={(e) => handleChange('recurrence_end_date', e.target.value)}
className="border-slate-300 bg-white h-12"
/>
</div>
</div>
</div>
)}
{/* Custom - Multiple Specific Dates */}
{formData.recurring_frequency === 'custom' && (
<div className="space-y-4 p-5 bg-gradient-to-br from-green-50 to-white rounded-xl border border-green-100">
<Label className="text-sm font-semibold text-slate-700">Select Specific Dates</Label>
<Popover>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
className="w-full justify-start text-left h-12 border-2 border-slate-300 hover:border-[#0A39DF] bg-white"
>
<Calendar className="w-5 h-5 mr-2 text-[#0A39DF]" />
<span className="font-medium">
{formData.scatter_dates && formData.scatter_dates.length > 0
? `${formData.scatter_dates.length} date${formData.scatter_dates.length > 1 ? 's' : ''} selected`
: "Click to select dates"}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<CalendarComponent
mode="multiple"
selected={formData.scatter_dates?.map(d => new Date(d)) || []}
onSelect={handleScatterDateSelect}
initialFocus
className="rounded-lg border-0"
/>
</PopoverContent>
</Popover>
{formData.scatter_dates && formData.scatter_dates.length > 0 && (
<div className="mt-4 flex flex-wrap gap-2">
{formData.scatter_dates.map(date => (
<Badge
key={date}
className="bg-[#0A39DF] text-white px-4 py-2 text-sm flex items-center gap-2 hover:bg-[#0A39DF]/90"
>
{format(new Date(date), 'MMM d, yyyy')}
<button
type="button"
onClick={() => {
setFormData(prev => ({
...prev,
scatter_dates: prev.scatter_dates.filter(d => d !== date)
}));
}}
className="hover:bg-white/20 rounded-full p-0.5 ml-1"
>
<X className="w-3.5 h-3.5" />
</button>
</Badge>
))}
</div>
)}
</div>
)}
{/* 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)) && (
<div className="mt-6 p-4 bg-gradient-to-r from-[#0A39DF]/10 to-purple-500/10 rounded-lg border border-[#0A39DF]/30">
<div className="flex items-center gap-2 mb-2">
<CheckCircle2 className="w-5 h-5 text-[#0A39DF]" />
<span className="font-semibold text-[#1C323E]">Recurrence Summary</span>
</div>
<div className="text-sm text-slate-700 space-y-1">
{formData.recurring_frequency === 'weekly' && formData.recurring_days?.length > 0 && (
<p> Repeats every <strong>
{['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
.filter((_, i) => formData.recurring_days.includes(i))
.join(', ')}
</strong></p>
)}
{formData.recurring_frequency === 'monthly' && formData.recurrence_start_date && formData.recurrence_end_date && (
<p> Repeats monthly from <strong>{format(new Date(formData.recurrence_start_date), 'MMM d, yyyy')}</strong> to <strong>{format(new Date(formData.recurrence_end_date), 'MMM d, yyyy')}</strong></p>
)}
{formData.recurring_frequency === 'custom' && formData.scatter_dates?.length > 0 && (
<p> <strong>{formData.scatter_dates.length}</strong> specific date{formData.scatter_dates.length > 1 ? 's' : ''} selected</p>
)}
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* Event Details */}
<Card className="border-slate-200">
<CardContent className="p-6">
<h3 className="text-lg font-bold text-slate-900 mb-6">Event</h3>
<div className="space-y-4">
<div>
<Label htmlFor="event_name" className="text-sm mb-2 block">Event Name *</Label>
<Input
id="event_name"
value={formData.event_name || ""}
onChange={(e) => handleChange('event_name', e.target.value)}
placeholder="Event Name"
required
className="border-slate-200"
/>
</div>
<div className="grid grid-cols-2 gap-4">
{(formData.order_type === 'one_time' || formData.order_type === 'rapid' || formData.order_type === 'permanent') && (
<div>
<Label htmlFor="date" className="text-sm mb-2 block">Date *</Label>
<Input
id="date"
type="date"
value={formData.date || ""}
onChange={(e) => handleChange('date', e.target.value)}
required
className="border-slate-200"
/>
</div>
)}
{/* Hub Selection - Show if vendor has multiple locations */}
{isVendor && vendorHubs.length > 1 ? (
<div>
<Label htmlFor="hub" className="text-sm mb-2 block flex items-center gap-2">
<MapPin className="w-4 h-4" />
Hub Location *
</Label>
<Select value={formData.hub || ""} onValueChange={(value) => handleChange('hub', value)}>
<SelectTrigger className="border-slate-200">
<SelectValue placeholder="Select hub" />
</SelectTrigger>
<SelectContent>
{vendorHubs.map(hub => (
<SelectItem key={hub} value={hub}>{hub}</SelectItem>
))}
</SelectContent>
</Select>
</div>
) : (
<div>
<Label htmlFor="hub" className="text-sm mb-2 block">Hub</Label>
<Input
id="hub"
value={formData.hub || ""}
onChange={(e) => handleChange('hub', e.target.value)}
placeholder="Hub location"
className="border-slate-200"
/>
</div>
)}
</div>
{/* PO Reference - Now Optional */}
<div>
<Label htmlFor="po_reference" className="text-sm mb-2 block">Purchase Order (Optional)</Label>
<Input
id="po_reference"
value={formData.po_reference || ""}
onChange={(e) => handleChange('po_reference', e.target.value)}
placeholder="PO reference"
className="border-slate-200"
/>
</div>
{/* Client/Business Selection */}
{isVendor && (
<div>
<Label htmlFor="business_id" className="text-sm mb-2 block">Client / Business</Label>
<Select value={formData.business_id || ""} onValueChange={handleBusinessChange}>
<SelectTrigger className="border-slate-200">
<SelectValue placeholder="Select a client">
{formData.business_name && (
<div className="flex items-center gap-2">
<Building2 className="w-4 h-4 text-slate-500" />
<span>{formData.business_name}</span>
</div>
)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{businesses.map((business) => (
<SelectItem key={business.id} value={business.id}>
<div className="flex flex-col">
<span className="font-medium">{business.business_name || "Unnamed Business"}</span>
{business.contact_name && (
<span className="text-xs text-slate-500">{business.contact_name}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{formData.business_id && businesses.find(b => b.id === formData.business_id)?.rate_group && (
<p className="text-xs text-blue-600 mt-2 flex items-center gap-1">
Rate Group: {businesses.find(b => b.id === formData.business_id)?.rate_group} (Auto-detected from location)
</p>
)}
</div>
)}
{/* Zero Risk Backup Staff */}
<div className="p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-lg border-2 border-green-200">
<div className="flex items-start gap-3">
<Checkbox
id="include_backup"
checked={formData.include_backup}
onCheckedChange={(checked) => {
handleChange('include_backup', checked);
if (!checked) handleChange('backup_staff_count', 0);
}}
className="mt-1"
/>
<div className="flex-1">
<Label htmlFor="include_backup" className="font-semibold text-green-900 cursor-pointer flex items-center gap-2">
<Shield className="w-5 h-5" />
Zero Risk - Include Backup Staff
</Label>
<p className="text-sm text-green-700 mt-1">
Extra pool of staff in case of call-outs or no-shows
</p>
{formData.include_backup && (
<div className="mt-3">
<Label className="text-sm text-green-800">Number of backup staff</Label>
<Input
type="number"
min="1"
max="5"
value={formData.backup_staff_count || 1}
onChange={(e) => handleChange('backup_staff_count', parseInt(e.target.value) || 1)}
className="w-24 mt-1 border-green-300"
/>
</div>
)}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Shifts Section */}
{formData.shifts.map((shift, shiftIndex) => (
<Card key={shiftIndex} className="border-slate-200">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-br from-purple-500 to-purple-700 rounded-xl flex items-center justify-center text-white font-bold text-lg shadow-md">
{shiftIndex + 1}
</div>
<div>
<h3 className="text-lg font-bold text-[#1C323E]">{shift.shift_name}</h3>
{shift.location_address && (
<p className="text-xs text-slate-500 flex items-center gap-1">
<MapPin className="w-3 h-3" />
{shift.location_address}
</p>
)}
</div>
</div >
<Button type="button" variant="ghost" size="sm">
Add contact
</Button>
</div>
{/* Roles */}
<div className="space-y-4">
{shift.roles.map((role, roleIndex) => (
<div key={roleIndex} className="bg-slate-50 rounded-lg p-4 border border-slate-200">
<div className="flex items-center gap-2 mb-4">
<div className="w-8 h-8 bg-[#1C323E] rounded-full flex items-center justify-center text-white font-bold text-sm">
{roleIndex + 1}
</div>
<div className="flex-1 grid grid-cols-4 gap-3">
{/* Service/Role with Search */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Service / Role</Label>
<Popover
open={roleSearchOpen[`${shiftIndex}-${roleIndex}`]}
onOpenChange={(open) => setRoleSearchOpen(prev => ({ ...prev, [`${shiftIndex}-${roleIndex}`]: open }))}
>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="w-full justify-between text-left font-normal"
>
{role.role || "Select service..."}
<Search className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0">
<Command>
<CommandInput placeholder="Search roles..." />
<CommandEmpty>No role found.</CommandEmpty>
<CommandGroup className="max-h-64 overflow-auto">
{availableRoles.map((roleName) => (
<CommandItem
key={roleName}
value={roleName}
onSelect={() => {
handleRoleChange(shiftIndex, roleIndex, 'role', roleName);
setRoleSearchOpen(prev => ({ ...prev, [`${shiftIndex}-${roleIndex}`]: false }));
}}
>
{roleName}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
{/* Count */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Count</Label>
<div className="flex items-center gap-1">
<Button
type="button"
variant="outline"
size="icon"
className="h-9 w-9"
onClick={() => handleRoleChange(shiftIndex, roleIndex, 'count', Math.max(1, (role.count || 1) - 1))}
>
<Minus className="w-4 h-4" />
</Button>
<Input
type="number"
min="1"
value={role.count || 1}
onChange={(e) => handleRoleChange(shiftIndex, roleIndex, 'count', parseInt(e.target.value) || 1)}
className="w-16 text-center"
/>
<Button
type="button"
variant="outline"
size="icon"
className="h-9 w-9"
onClick={() => handleRoleChange(shiftIndex, roleIndex, 'count', (role.count || 1) + 1)}
>
<Plus className="w-4 h-4" />
</Button>
</div>
</div>
{/* Start Time */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Start Time</Label>
<Input
type="time"
value={role.start_time || "09:00"}
onChange={(e) => handleRoleChange(shiftIndex, roleIndex, 'start_time', e.target.value)}
className="border-slate-200"
/>
</div>
{/* End Time */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">End Time</Label>
<Input
type="time"
value={role.end_time || "17:00"}
onChange={(e) => handleRoleChange(shiftIndex, roleIndex, 'end_time', e.target.value)}
className="border-slate-200"
/>
</div>
</div>
{shift.roles.length > 1 && (
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveRole(shiftIndex, roleIndex)}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="w-4 h-4" />
</Button>
)}
</div>
<div className="grid grid-cols-4 gap-3">
{/* Department */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Department</Label>
<Input
value={role.department || ""}
onChange={(e) => handleRoleChange(shiftIndex, roleIndex, 'department', e.target.value)}
placeholder="Department"
className="border-slate-200"
/>
</div>
{/* Hours (Auto-calculated) */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Hours</Label>
<div className="flex items-center justify-center h-9">
<Badge className="bg-[#0A39DF] text-white text-lg px-4 py-1 rounded-full">
{role.hours || 0}
</Badge>
</div>
</div>
{/* Break (min) */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Break (min)</Label>
<Input
type="number"
min="0"
value={role.break_minutes || 30}
onChange={(e) => handleRoleChange(shiftIndex, roleIndex, 'break_minutes', parseInt(e.target.value) || 0)}
className="border-slate-200"
/>
</div>
{/* Uniform */}
<div>
<Label className="text-xs text-slate-600 mb-1 block">Uniform</Label>
<Select
value={role.uniform || "Type 1"}
onValueChange={(value) => handleRoleChange(shiftIndex, roleIndex, 'uniform', value)}
>
<SelectTrigger className="border-slate-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
{UNIFORM_TYPES.map(type => (
<SelectItem key={type} value={type}>{type}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* Rate and Total */}
<div className="grid grid-cols-2 gap-3 mt-3">
<div>
<Label className="text-xs text-slate-600 mb-1 block flex items-center gap-1">
Rate/hr
{role.role && (
<span className="text-blue-600"> FoodBuy</span>
)}
</Label>
<div className="flex items-center gap-1">
<span className="text-slate-500">$</span>
<Input
type="number"
step="0.01"
value={role.rate_per_hour || 0}
readOnly
className="border-slate-200 bg-slate-50 font-semibold"
/>
</div>
</div>
<div>
<Label className="text-xs text-slate-600 mb-1 block">Total</Label>
<div className="text-2xl font-bold text-[#0A39DF]">
${(role.total_value || 0).toFixed(2)}
</div>
</div>
</div>
</div>
))}
{/* Add Role Button */}
<Button
type="button"
variant="outline"
className="w-full border-dashed border-2 border-slate-300 hover:border-[#0A39DF] hover:bg-blue-50"
onClick={() => handleAddRole(shiftIndex)}
>
<Plus className="w-4 h-4 mr-2" />
Add Role
</Button>
</div>
{/* Shift Total */}
<div className="mt-4 pt-4 border-t border-slate-200 flex items-center justify-between">
<span className="font-semibold text-slate-700">Shift Total:</span>
<span className="text-2xl font-bold text-[#0A39DF]">
${shift.roles.reduce((sum, r) => sum + (r.total_value || 0), 0).toFixed(2)}
</span>
</div>
</CardContent>
</Card>
))}
{/* Add Another Shift Button */}
<Button
type="button"
variant="outline"
className="w-full border-dashed border-2 border-slate-300 hover:border-purple-500 hover:bg-purple-50"
onClick={handleAddShift}
>
<Plus className="w-4 h-4 mr-2" />
Add Another Shift (Different Location)
</Button>
{/* Other Instructions */}
<Card className="border-slate-200">
<CardContent className="p-6">
<Label className="text-sm font-semibold mb-3 block">Other Instructions</Label>
<Textarea
value={formData.notes || ""}
onChange={(e) => handleChange('notes', e.target.value)}
placeholder="Add any additional instructions..."
rows={3}
maxLength={300}
className="border-slate-200"
/>
<p className="text-xs text-slate-500 text-right mt-1">
{(formData.notes || "").length} / 300
</p>
</CardContent>
</Card>
{/* Grand Total */}
<Card className="border-2 border-[#0A39DF] bg-gradient-to-br from-blue-50 to-white">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-slate-600">Grand Total</p>
<p className="text-xs text-slate-500">
{formData.shifts.reduce((sum, s) => sum + s.roles.reduce((roleSum, r) => roleSum + (r.count || 1), 0), 0)} roles across {formData.shifts.length} shift(s)
</p>
</div>
<div className="text-4xl font-bold text-[#0A39DF]">
${(formData.total || 0).toFixed(2)}
</div>
</div>
</CardContent>
</Card>
{/* Action Buttons */}
<div className="flex items-center justify-end gap-3">
<Button
type="button"
variant="outline"
className="border-slate-300"
onClick={() => window.history.back()}
>
Cancel
</Button>
<Button
type="button"
variant="outline"
onClick={(e) => handleSubmit(e, true)}
disabled={isSubmitting}
className="border-slate-300"
>
<FileText className="w-4 h-4 mr-2" />
Draft Event
</Button>
<Button
type="submit"
disabled={isSubmitting}
className="bg-gradient-to-r from-[#0A39DF] to-[#1C323E] hover:from-[#0829B0] hover:to-[#0F1D26] text-white px-8"
>
<Save className="w-4 h-4 mr-2" />
Create Event
</Button>
</div>
</form>
);
}