import React, { useState, useMemo } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Search, Eye, Edit, Copy, UserCheck, Zap, Clock, Users, RefreshCw, Calendar as CalendarIcon, AlertTriangle, List, LayoutGrid, CheckCircle, FileText, X, MapPin } from "lucide-react"; import { useToast } from "@/components/ui/use-toast"; import { format, parseISO, isValid } from "date-fns"; import { Alert, AlertDescription } from "@/components/ui/alert"; import SmartAssignModal from "@/components/events/SmartAssignModal"; import { autoFillShifts } from "@/components/scheduling/SmartAssignmentEngine"; import { detectAllConflicts, ConflictAlert } from "@/components/scheduling/ConflictDetection"; const safeParseDate = (dateString) => { if (!dateString) return null; try { // If date is in format YYYY-MM-DD, parse it without timezone conversion if (typeof dateString === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateString)) { const [year, month, day] = dateString.split('-').map(Number); const date = new Date(year, month - 1, day); return isValid(date) ? date : null; } const date = typeof dateString === 'string' ? parseISO(dateString) : new Date(dateString); return isValid(date) ? date : null; } catch { return null; } }; const safeFormatDate = (dateString, formatStr) => { const date = safeParseDate(dateString); if (!date) return "-"; try { return format(date, formatStr); } catch { return "-"; } }; const convertTo12Hour = (time24) => { if (!time24) return "-"; try { const [hours, minutes] = time24.split(':'); const hour = parseInt(hours); const ampm = hour >= 12 ? 'PM' : 'AM'; const hour12 = hour % 12 || 12; return `${hour12}:${minutes} ${ampm}`; } catch { return time24; } }; const getStatusBadge = (event, hasConflicts) => { if (event.is_rapid) { return (
RAPID {hasConflicts && ( )}
); } const statusConfig = { 'Draft': { bg: 'bg-slate-500', icon: FileText }, 'Pending': { bg: 'bg-amber-500', icon: Clock }, 'Partial Staffed': { bg: 'bg-orange-500', icon: AlertTriangle }, 'Fully Staffed': { bg: 'bg-emerald-500', icon: CheckCircle }, 'Active': { bg: 'bg-blue-500', icon: Users }, 'Completed': { bg: 'bg-slate-400', icon: CheckCircle }, 'Canceled': { bg: 'bg-red-500', icon: X }, }; const config = statusConfig[event.status] || { bg: 'bg-slate-400', icon: Clock }; const Icon = config.icon; return (
{event.status} {hasConflicts && ( )}
); }; export default function VendorOrders() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { toast } = useToast(); const [searchTerm, setSearchTerm] = useState(""); const [activeTab, setActiveTab] = useState("all"); const [viewMode, setViewMode] = useState("table"); const [showConflicts, setShowConflicts] = useState(true); const [assignModal, setAssignModal] = useState({ open: false, event: null }); const [assignmentOptions] = useState({ prioritizeSkill: true, prioritizeReliability: true, prioritizeVendor: true, prioritizeFatigue: true, prioritizeCompliance: true, prioritizeProximity: true, prioritizeCost: false, }); const { data: user } = useQuery({ queryKey: ['current-user-vendor-orders'], queryFn: () => base44.auth.me(), }); const { data: allEvents = [] } = useQuery({ queryKey: ['all-events-vendor'], queryFn: () => base44.entities.Event.list('-date'), }); const { data: allStaff = [] } = useQuery({ queryKey: ['staff-for-auto-assign'], queryFn: () => base44.entities.Staff.list(), }); const { data: vendorRates = [] } = useQuery({ queryKey: ['vendor-rates-auto-assign'], queryFn: () => base44.entities.VendorRate.list(), initialData: [], }); const updateEventMutation = useMutation({ mutationFn: ({ id, data }) => base44.entities.Event.update(id, data), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['all-events-vendor'] }), }); const autoAssignMutation = useMutation({ mutationFn: async (event) => { const assignments = await autoFillShifts(event, allStaff, vendorEvents, vendorRates, assignmentOptions); if (assignments.length === 0) throw new Error("No suitable staff found"); const updatedAssignedStaff = [...(event.assigned_staff || []), ...assignments]; const updatedShifts = (event.shifts || []).map(shift => { const updatedRoles = (shift.roles || []).map(role => { const roleAssignments = assignments.filter(a => a.role === role.role); return { ...role, assigned: (role.assigned || 0) + roleAssignments.length }; }); return { ...shift, roles: updatedRoles }; }); const totalRequested = updatedShifts.reduce((accShift, shift) => { return accShift + (shift.roles?.reduce((accRole, role) => accRole + (role.count || 0), 0) || 0); }, 0); const totalAssigned = updatedAssignedStaff.length; let newStatus = event.status; if (totalAssigned >= totalRequested && totalRequested > 0) { newStatus = 'Fully Staffed'; } else if (totalAssigned > 0 && totalAssigned < totalRequested) { newStatus = 'Partial Staffed'; } else if (totalAssigned === 0) { newStatus = 'Pending'; } await base44.entities.Event.update(event.id, { assigned_staff: updatedAssignedStaff, shifts: updatedShifts, requested: (event.requested || 0) + assignments.length, status: newStatus, }); return assignments.length; }, onSuccess: (count) => { queryClient.invalidateQueries({ queryKey: ['all-events-vendor'] }); toast({ title: "✅ Auto-Assigned", description: `Assigned ${count} staff automatically` }); }, onError: (error) => { toast({ title: "⚠️ Auto-Assign Failed", description: error.message, variant: "destructive" }); }, }); const vendorEvents = useMemo(() => { return allEvents.filter(e => e.vendor_name === user?.company_name || e.vendor_id === user?.id || e.created_by === user?.email ); }, [allEvents, user]); const eventsWithConflicts = useMemo(() => { return vendorEvents.map(event => { const conflicts = detectAllConflicts(event, vendorEvents); return { ...event, detected_conflicts: conflicts }; }); }, [vendorEvents]); const totalConflicts = eventsWithConflicts.reduce((sum, e) => sum + (e.detected_conflicts?.length || 0), 0); const filteredEvents = useMemo(() => { let filtered = eventsWithConflicts; if (activeTab === "upcoming") filtered = filtered.filter(e => { const eventDate = safeParseDate(e.date); return eventDate && eventDate > new Date(); }); else if (activeTab === "active") filtered = filtered.filter(e => e.status === "Active"); else if (activeTab === "past") filtered = filtered.filter(e => e.status === "Completed"); else if (activeTab === "conflicts") filtered = filtered.filter(e => e.detected_conflicts && e.detected_conflicts.length > 0); if (searchTerm) { const lower = searchTerm.toLowerCase(); filtered = filtered.filter(e => e.event_name?.toLowerCase().includes(lower) || e.business_name?.toLowerCase().includes(lower) || e.hub?.toLowerCase().includes(lower) ); } return filtered; }, [eventsWithConflicts, searchTerm, activeTab]); const getAssignmentStatus = (event) => { const totalRequested = event.shifts?.reduce((accShift, shift) => { return accShift + (shift.roles?.reduce((accRole, role) => accRole + (role.count || 0), 0) || 0); }, 0) || 0; const assigned = event.assigned_staff?.length || 0; const fillPercent = totalRequested > 0 ? Math.round((assigned / totalRequested) * 100) : 0; if (assigned === 0) return { color: 'bg-slate-100 text-slate-600', text: '0', percent: '0%', status: 'empty' }; if (totalRequested > 0 && assigned >= totalRequested) return { color: 'bg-emerald-500 text-white', text: assigned, percent: '100%', status: 'full' }; if (totalRequested > 0 && assigned < totalRequested) return { color: 'bg-orange-500 text-white', text: assigned, percent: `${fillPercent}%`, status: 'partial' }; return { color: 'bg-slate-500 text-white', text: assigned, percent: '0%', status: 'partial' }; }; const getTabCount = (tab) => { if (tab === "all") return vendorEvents.length; if (tab === "conflicts") return eventsWithConflicts.filter(e => e.detected_conflicts && e.detected_conflicts.length > 0).length; if (tab === "upcoming") return vendorEvents.filter(e => { const eventDate = safeParseDate(e.date); return eventDate && eventDate > new Date(); }).length; if (tab === "active") return vendorEvents.filter(e => e.status === "Active").length; if (tab === "past") return vendorEvents.filter(e => e.status === "Completed").length; return 0; }; // The original handleAutoAssignEvent is removed as the button now opens the modal directly. // const handleAutoAssignEvent = (event) => autoAssignMutation.mutate(event); const getEventTimes = (event) => { const firstShift = event.shifts?.[0]; const rolesInFirstShift = firstShift?.roles || []; let startTime = null; let endTime = null; if (rolesInFirstShift.length > 0) { startTime = rolesInFirstShift[0].start_time || null; endTime = rolesInFirstShift[0].end_time || null; } return { startTime: startTime ? convertTo12Hour(startTime) : "-", endTime: endTime ? convertTo12Hour(endTime) : "-" }; }; return (

Order Management

View, assign, and track all your orders

{showConflicts && totalConflicts > 0 && (
{totalConflicts} scheduling conflict{totalConflicts !== 1 ? 's' : ''} detected
)}

RAPID

{vendorEvents.filter(e => e.is_rapid).length}

REQUESTED

{vendorEvents.filter(e => e.status === 'Pending' || e.status === 'Draft').length}

PARTIAL

{vendorEvents.filter(e => { const status = getAssignmentStatus(e); return status.status === 'partial'; }).length}

FULLY STAFFED

{vendorEvents.filter(e => { const status = getAssignmentStatus(e); return status.status === 'full'; }).length}

setSearchTerm(e.target.value)} className="pl-10 border-slate-200 h-10" />
All ({getTabCount("all")}) Conflicts ({getTabCount("conflicts")}) Upcoming ({getTabCount("upcoming")}) Active ({getTabCount("active")}) Past ({getTabCount("past")})
BUSINESS HUB EVENT DATE & TIME STATUS REQUESTED ASSIGNED INVOICE ACTIONS {filteredEvents.length === 0 ? (

No orders found

) : ( filteredEvents.map((event) => { const assignmentStatus = getAssignmentStatus(event); const showAutoButton = assignmentStatus.status !== 'full' && event.status !== 'Canceled' && event.status !== 'Completed'; const hasConflicts = event.detected_conflicts && event.detected_conflicts.length > 0; const eventTimes = getEventTimes(event); const eventDate = safeParseDate(event.date); const dayOfWeek = eventDate ? format(eventDate, 'EEEE') : ''; const invoiceReady = event.status === "Completed"; return (

{event.business_name || "—"}

{event.hub || event.event_location || "Main Hub"}

{event.event_name}

{eventDate ? format(eventDate, 'MM.dd.yyyy') : '-'}

{dayOfWeek}

{eventTimes.startTime} - {eventTimes.endTime}
{getStatusBadge(event, hasConflicts)} {event.requested || 0}
{assignmentStatus.text} {assignmentStatus.percent}
{hasConflicts && activeTab === "conflicts" && ( )}
); }) )}
setAssignModal({ open: false, event: null })} event={assignModal.event} />
); }