import React, { useState } from "react"; 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 { Plus, Search, Filter, Download, LayoutGrid, List, Eye, Edit, Trash2, MoreHorizontal, Users, UserPlus, RefreshCw, Copy, Calendar as CalendarIcon, ArrowUpDown, Check, Bell, Send, FileText, Zap } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { format, isToday, addDays } from "date-fns"; import { useToast } from "@/components/ui/use-toast"; import { Link, useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import EventAssignmentModal from "@/components/events/EventAssignmentModal"; const getStatusColor = (order) => { // Check for Rapid Request first if (order.is_rapid_request) { return "bg-red-500 text-white"; } if (!order.shifts_data || order.shifts_data.length === 0) { return "bg-orange-500 text-white"; } let totalNeeded = 0; let totalAssigned = 0; order.shifts_data.forEach(shift => { shift.roles.forEach(role => { const needed = parseInt(role.count) || 0; const assigned = role.assignments?.length || 0; totalNeeded += needed; totalAssigned += assigned; }); }); if (totalNeeded === 0) return "bg-orange-500 text-white"; if (totalAssigned === 0) return "bg-orange-500 text-white"; if (totalAssigned < totalNeeded) return "bg-blue-500 text-white"; if (totalAssigned >= totalNeeded) return "bg-green-500 text-white"; return "bg-slate-500 text-white"; }; const getStatusText = (order) => { if (order.is_rapid_request) return "Rapid Request"; if (!order.shifts_data || order.shifts_data.length === 0) return "Pending"; let totalNeeded = 0; let totalAssigned = 0; order.shifts_data.forEach(shift => { shift.roles.forEach(role => { totalNeeded += parseInt(role.count) || 0; totalAssigned += role.assignments?.length || 0; }); }); if (totalAssigned === 0) return "Pending"; if (totalAssigned < totalNeeded) return "Partially Filled"; if (totalAssigned >= totalNeeded && totalNeeded > 0) return "Fully Staffed"; return "Pending"; }; const convertTo12Hour = (time24) => { if (!time24) return ''; const [hours, minutes] = time24.split(':'); const hour = parseInt(hours); const ampm = hour >= 12 ? 'PM' : 'AM'; const hour12 = hour % 12 || 12; return `${hour12}:${minutes} ${ampm}`; }; export default function VendorOrders() { const [searchQuery, setSearchQuery] = useState(""); const [filters, setFilters] = useState({ status: "all", hub: "all" }); const [viewMode, setViewMode] = useState("list"); const [sortBy, setSortBy] = useState("date"); const [sortOrder, setSortOrder] = useState("desc"); const [selectedDate, setSelectedDate] = useState(null); const [notifyingOrders, setNotifyingOrders] = useState(new Set()); const [assignmentModal, setAssignmentModal] = useState({ open: false, order: null }); const queryClient = useQueryClient(); const { toast } = useToast(); const navigate = useNavigate(); const { data: user } = useQuery({ queryKey: ['current-user-vendor-orders'], queryFn: () => base44.auth.me(), }); const { data: allOrders = [], isLoading } = useQuery({ queryKey: ['orders'], queryFn: () => base44.entities.Order.list('-created_date'), }); const { data: invoices = [] } = useQuery({ queryKey: ['invoices'], queryFn: () => base44.entities.Invoice.list(), }); // Filter orders for this vendor only const orders = allOrders.filter(order => order.vendor_id === user?.id || order.vendor_name === user?.company_name || order.created_by === user?.email ); const getInvoiceForOrder = (orderId) => { return invoices.find(inv => inv.order_id === orderId); }; const sendNotificationToStaff = async (order, assignment) => { const employees = await base44.entities.Employee.filter({ id: assignment.employee_id }); if (employees.length === 0) return { success: false, method: 'none', reason: 'Employee not found' }; const employee = employees[0]; const formattedDate = assignment.shift_date ? format(new Date(assignment.shift_date), 'EEEE, MMMM d, yyyy') : 'TBD'; const startTime = assignment.shift_start ? convertTo12Hour(assignment.shift_start) : 'TBD'; const endTime = assignment.shift_end ? convertTo12Hour(assignment.shift_end) : 'TBD'; const emailBody = ` Hi ${assignment.employee_name}, Great news! You've been assigned to a new shift. EVENT DETAILS: ━━━━━━━━━━━━━━━━━━━━ 📅 Event: ${order.event_name} 🏢 Client: ${order.client_business} 📍 Location: ${assignment.location || 'TBD'} 🏠 Hub: ${assignment.hub_location || order.hub_location} SHIFT DETAILS: ━━━━━━━━━━━━━━━━━━━━ 📆 Date: ${formattedDate} 🕐 Time: ${startTime} - ${endTime} 👔 Position: ${assignment.position} ${order.notes ? `\nADDITIONAL NOTES:\n${order.notes}\n` : ''} Please confirm your availability as soon as possible. If you have any questions, please contact your manager. Thank you, Legendary Event Staffing Team `.trim(); const smsBody = `🎉 Legendary Event Staffing\n\nYou're assigned to ${order.event_name}!\n📆 ${formattedDate}\n🕐 ${startTime}-${endTime}\n👔 ${assignment.position}\n📍 ${assignment.location || order.hub_location}\n\nPlease confirm ASAP. Check email for full details.`; let emailSent = false; let smsSent = false; if (employee.phone) { try { const cleanPhone = employee.phone.replace(/\D/g, ''); await base44.integrations.Core.InvokeLLM({ prompt: `Send an SMS text message to phone number ${cleanPhone} with the following content:\n\n${smsBody}\n\nUse a reliable SMS service to send this message. Return success status.`, add_context_from_internet: false, }); smsSent = true; console.log(`✅ SMS sent to ${employee.phone}`); } catch (error) { console.error(`❌ Failed to send SMS to ${employee.phone}:`, error); } } if (employee.email) { try { await base44.integrations.Core.SendEmail({ from_name: 'Legendary Event Staffing', to: employee.email, subject: `🎉 You've been assigned to ${order.event_name}`, body: emailBody }); emailSent = true; console.log(`✅ Email sent to ${employee.email}`); } catch (error) { console.error(`❌ Failed to send email to ${employee.email}:`, error); } } if (emailSent || smsSent) { const methods = []; if (smsSent) methods.push('SMS'); if (emailSent) methods.push('Email'); return { success: true, method: methods.join(' & '), employee: employee.full_name }; } return { success: false, method: 'none', reason: 'No contact info or failed to send' }; }; const handleNotifyStaff = async (order) => { if (notifyingOrders.has(order.id)) return; setNotifyingOrders(prev => new Set(prev).add(order.id)); const allAssignments = []; if (order.shifts_data) { order.shifts_data.forEach((shift, shiftIdx) => { shift.roles.forEach(role => { if (role.assignments && role.assignments.length > 0) { role.assignments.forEach(assignment => { allAssignments.push({ ...assignment, shift_date: order.event_date, shift_start: role.start_time, shift_end: role.end_time, position: role.service, location: shift.address || order.event_address, hub_location: order.hub_location, }); }); } }); }); } if (allAssignments.length === 0) { toast({ title: "No staff assigned", description: "Please assign staff before sending notifications.", variant: "destructive", }); setNotifyingOrders(prev => { const next = new Set(prev); next.delete(order.id); return next; }); return; } toast({ title: "Sending notifications...", description: `Notifying ${allAssignments.length} staff member${allAssignments.length > 1 ? 's' : ''} for "${order.event_name}"...`, }); let successCount = 0; const results = []; for (const assignment of allAssignments) { const result = await sendNotificationToStaff(order, assignment); if (result.success) { successCount++; results.push(`✅ ${result.employee} (${result.method})`); } else { results.push(`❌ ${assignment.employee_name} (${result.reason})`); } } toast({ title: `Notifications sent!`, description: `Successfully notified ${successCount} of ${allAssignments.length} staff members for "${order.event_name}".`, }); setNotifyingOrders(prev => { const next = new Set(prev); next.delete(order.id); return next; }); }; const handleSort = (field) => { if (sortBy === field) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { setSortBy(field); setSortOrder('asc'); } }; const handleOpenAssignment = (order) => { setAssignmentModal({ open: true, order }); }; const handleCloseAssignment = () => { setAssignmentModal({ open: false, order: null }); }; const filteredOrders = orders.filter(order => { const matchesSearch = order.event_name?.toLowerCase().includes(searchQuery.toLowerCase()) || order.client_business?.toLowerCase().includes(searchQuery.toLowerCase()) || order.manager?.toLowerCase().includes(searchQuery.toLowerCase()); // Updated status filtering to include "Rapid Request" and "Partially Filled" let matchesStatus = filters.status === 'all'; if (!matchesStatus) { const statusText = getStatusText(order); if (filters.status === 'Rapid Request') { matchesStatus = order.is_rapid_request; } else { matchesStatus = statusText === filters.status; } } const matchesHub = filters.hub === 'all' || order.hub_location === filters.hub; const matchesDate = !selectedDate || order.event_date === format(selectedDate, 'yyyy-MM-dd'); return matchesSearch && matchesStatus && matchesHub && matchesDate; }).sort((a, b) => { let compareA, compareB; switch(sortBy) { case 'business': compareA = a.client_business || ''; compareB = b.client_business || ''; break; case 'status': compareA = getStatusText(a); compareB = getStatusText(b); break; case 'date': compareA = a.event_date || ''; compareB = b.event_date || ''; break; default: return 0; } if (compareA < compareB) return sortOrder === 'asc' ? -1 : 1; if (compareA > compareB) return sortOrder === 'asc' ? 1 : -1; return 0; }); const hubs = [...new Set(orders.map(o => o.hub_location).filter(Boolean))]; const today = format(new Date(), 'yyyy-MM-dd'); const todaysOrders = orders.filter(o => o.event_date === today); const totalStaffToday = todaysOrders.reduce((sum, order) => { if (!order.shifts_data) return sum; return sum + order.shifts_data.reduce((shiftSum, shift) => { return shiftSum + shift.roles.reduce((roleSum, role) => { return roleSum + (role.assignments?.length || 0); }, 0); }, 0); }, 0); const rapidRequestToday = todaysOrders.filter(o => o.is_rapid_request).length; const pendingToday = todaysOrders.filter(o => getStatusText(o) === 'Pending').length; const partiallyFilledToday = todaysOrders.filter(o => getStatusText(o) === 'Partially Filled').length; const fullyStaffedToday = todaysOrders.filter(o => getStatusText(o) === 'Fully Staffed').length; return (
Manage and track all event orders
Total Staff Today
{totalStaffToday}
Rapid Request
{rapidRequestToday}
Pending
{pendingToday}
Partially Filled
{partiallyFilledToday}
Fully Staffed
{fullyStaffedToday}
| Hub | Event Name | Requested | Assigned | Invoice | Actions | |||
|---|---|---|---|---|---|---|---|---|
|
{order.client_business || 'N/A'}
|
|
{order.event_name}
|
|
{order.event_date ? format(new Date(order.event_date), 'MM/dd/yy') : 'N/A'} | {totalNeeded} |
|
{invoice ? ( e.stopPropagation()}> ) : ( — )} |
e.stopPropagation()}>
|
Showing {filteredOrders.length} orders
{order.client_business}
No orders found
Try adjusting your filters