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}
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}
/>
);
}