import React, { useState, useMemo, useEffect } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Link, useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Calendar, Plus, Clock, DollarSign, MessageSquare, RefreshCw, ArrowRight, Users, Package, AlertTriangle, Zap, CheckCircle, AlertCircle, Store, BarChart3, Sparkles, Edit2, X, MapPin, Grid, List, Eye, ChevronRight, FileText, Send, Copy, Award, Crown, Settings, GripVertical, Minus, Check, RotateCcw, TrendingUp } from "lucide-react"; import { format, parseISO, isToday, startOfDay, isSameDay } from "date-fns"; import { useToast } from "@/components/ui/use-toast"; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'; import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; const COLORS = ['#0A39DF', '#6366f1', '#8b5cf6', '#a855f7', '#c026d3', '#d946ef']; const convertTo12Hour = (time24) => { if (!time24 || time24 === "—") return time24; try { const parts = time24.split(':'); if (!parts || parts.length < 2) return time24; const hours = parseInt(parts[0], 10); const minutes = parseInt(parts[1], 10); if (isNaN(hours) || isNaN(minutes)) return time24; const period = hours >= 12 ? 'PM' : 'AM'; const hours12 = hours % 12 || 12; const minutesStr = minutes.toString().padStart(2, '0'); return `${hours12}:${minutesStr} ${period}`; } catch (error) { return time24; } }; const isDateToday = (dateValue) => { if (!dateValue) return false; try { let dateObj; if (typeof dateValue === 'string') { dateObj = parseISO(dateValue); if (isNaN(dateObj.getTime())) { dateObj = new Date(dateValue); } } else if (dateValue instanceof Date) { dateObj = dateValue; } else { return false; } if (isNaN(dateObj.getTime())) { return false; } const today = startOfDay(new Date()); const compareDate = startOfDay(dateObj); return isSameDay(today, compareDate); } catch (error) { console.error('Error checking if date is today:', error, dateValue); return false; } }; const AVAILABLE_WIDGETS = [ { id: 'order-now', title: 'Order Now', description: 'Create new standard order', category: 'Quick Actions', categoryColor: 'bg-blue-100 text-blue-700', }, { id: 'rapid-order', title: 'RAPID Order', description: 'Emergency urgent orders', category: 'Quick Actions', categoryColor: 'bg-red-100 text-red-700', }, { id: 'today-count', title: "Today's Count", description: 'Orders scheduled for today', category: 'Quick Stats', categoryColor: 'bg-blue-100 text-blue-700', }, { id: 'in-progress', title: 'In Progress', description: 'Active orders count', category: 'Quick Stats', categoryColor: 'bg-green-100 text-green-700', }, { id: 'needs-attention', title: 'Needs Attention', description: 'Incomplete orders', category: 'Quick Stats', categoryColor: 'bg-amber-100 text-amber-700', }, { id: 'todays-orders', title: "Today's Orders Table", description: 'Full table of today\'s orders', category: 'Orders', categoryColor: 'bg-green-100 text-green-700', }, { id: 'labor-summary', title: 'Labor Summary', description: 'Detailed breakdown of labor costs', category: 'Analytics', categoryColor: 'bg-purple-100 text-purple-700', }, { id: 'sales-analytics', title: 'Sales Analytics', description: 'Visual spending breakdown', category: 'Analytics', categoryColor: 'bg-purple-100 text-purple-700', }, { id: 'vendor-marketplace', title: 'Find Vendors', description: 'Browse vendor marketplace', category: 'Actions', categoryColor: 'bg-blue-100 text-blue-700', } ]; export default function ClientDashboard() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { toast } = useToast(); const [cancelDialog, setCancelDialog] = useState({ open: false, order: null }); const [showAllToday, setShowAllToday] = useState(false); const [widgetOrder, setWidgetOrder] = useState(AVAILABLE_WIDGETS.map(w => w.id)); const [hiddenWidgets, setHiddenWidgets] = useState([]); const [isCustomizing, setIsCustomizing] = useState(false); const [hasChanges, setHasChanges] = useState(false); const { data: user } = useQuery({ queryKey: ['current-user'], queryFn: () => base44.auth.me(), }); useEffect(() => { if (user?.dashboard_layout_client?.widgets) { setWidgetOrder(user.dashboard_layout_client.widgets); setHiddenWidgets(user.dashboard_layout_client.hidden_widgets || []); } }, [user]); const { data: events } = useQuery({ queryKey: ['client-events'], queryFn: async () => { const allEvents = await base44.entities.Event.list('-date'); const clientEvents = allEvents.filter(e => e.client_email === user?.email || e.business_name === user?.company_name || e.created_by === user?.email ); if (clientEvents.length === 0) { return allEvents.filter(e => e.status === "Completed"); } return clientEvents; }, initialData: [], enabled: !!user }); const { data: invoices = [] } = useQuery({ queryKey: ['client-invoices-reminder'], queryFn: () => base44.entities.Invoice.list(), initialData: [], enabled: !!user }); const saveLayoutMutation = useMutation({ mutationFn: async (layoutData) => { await base44.auth.updateMe({ dashboard_layout_client: layoutData }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['current-user'] }); toast({ title: "✅ Layout Saved", description: "Your dashboard layout has been updated", }); setHasChanges(false); setIsCustomizing(false); }, }); const cancelOrderMutation = useMutation({ mutationFn: (orderId) => base44.entities.Event.update(orderId, { status: "Canceled" }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['client-events'] }); toast({ title: "✅ Order Canceled", description: "Your order has been canceled successfully", }); setCancelDialog({ open: false, order: null }); }, }); const todayOrders = useMemo(() => { return events.filter(e => { if (e.status === "Canceled") return false; return isDateToday(e.date); }); }, [events]); const inProgressOrders = useMemo(() => { return events.filter(e => e.status === "Active" || e.status === "Confirmed" || e.status === "In Progress" ); }, [events]); const needsAttention = useMemo(() => { return events.filter(e => { const assignedCount = e.assigned_staff?.length || 0; const requestedCount = e.requested || 0; const isFuture = new Date(e.date) > new Date(); return isFuture && requestedCount > 0 && assignedCount < requestedCount; }); }, [events]); const rapidOrders = useMemo(() => { return events.filter(e => { const eventDate = new Date(e.date); const now = new Date(); const hoursUntil = (eventDate - now) / (1000 * 60 * 60); return hoursUntil > 0 && hoursUntil <= 24 && (e.status === "Active" || e.status === "Confirmed" || e.status === "Pending"); }); }, [events]); const currentMonth = new Date().getMonth(); const currentYear = new Date().getFullYear(); const thisMonthOrders = events.filter(e => { const eventDate = new Date(e.date); return eventDate.getMonth() === currentMonth && eventDate.getFullYear() === currentYear && e.status === "Completed"; }); const laborByPosition = useMemo(() => { const summary = {}; thisMonthOrders.forEach(order => { if (order.shifts_data) { order.shifts_data.forEach(shift => { shift.roles?.forEach(role => { const position = role.service || 'Other'; const count = parseInt(role.count) || 0; const startTime = role.start_time || '00:00'; const endTime = role.end_time || '00:00'; const [startHour, startMin] = startTime.split(':').map(Number); const [endHour, endMin] = endTime.split(':').map(Number); const hours = (endHour * 60 + endMin - startHour * 60 - startMin) / 60; const totalHours = hours * count; const rate = role.bill_rate || role.pay_rate || 25; const cost = totalHours * rate; if (!summary[position]) { summary[position] = { position, headcount: 0, regHours: 0, otHours: 0, dtHours: 0, regAmt: 0, otAmt: 0, dtAmt: 0, totalPay: 0 }; } summary[position].headcount += count; summary[position].regHours += totalHours; summary[position].regAmt += cost; summary[position].totalPay += cost; }); }); } }); return Object.values(summary).sort((a, b) => b.totalPay - a.totalPay); }, [thisMonthOrders]); const totalLaborCost = laborByPosition.reduce((sum, p) => sum + p.totalPay, 0); const totalHours = laborByPosition.reduce((sum, p) => sum + p.regHours + p.otHours + p.dtHours, 0); const hour = new Date().getHours(); const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening"; const pieChartData = laborByPosition.slice(0, 5).map(item => ({ name: item.position, value: item.totalPay })); const pendingInvoices = useMemo(() => { return invoices.filter(inv => (inv.business_name === user?.company_name || inv.created_by === user?.email) && (inv.status === 'Open' || inv.status === 'Overdue' || inv.status === 'Pending') ); }, [invoices, user]); const completedOrders = useMemo(() => { return events.filter(e => e.status === 'Completed'); }, [events]); const avgFulfillmentRate = useMemo(() => { if (completedOrders.length === 0) return 0; return (completedOrders.reduce((sum, e) => { const assigned = e.assigned_staff?.length || 0; const requested = e.requested || 1; return sum + (assigned / requested); }, 0) / completedOrders.length * 100); }, [completedOrders]); const handleCancelOrder = (order) => { setCancelDialog({ open: true, order }); }; const confirmCancel = () => { if (cancelDialog.order) { cancelOrderMutation.mutate(cancelDialog.order.id); } }; const handleDragEnd = (result) => { if (!result.destination) return; const items = Array.from(widgetOrder); const [reorderedItem] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, reorderedItem); setWidgetOrder(items); setHasChanges(true); }; const handleRemoveWidget = (widgetId) => { setHiddenWidgets([...hiddenWidgets, widgetId]); setHasChanges(true); }; const handleAddWidget = (widgetId) => { setHiddenWidgets(hiddenWidgets.filter(id => id !== widgetId)); setHasChanges(true); }; const handleSaveLayout = () => { saveLayoutMutation.mutate({ widgets: widgetOrder, hidden_widgets: hiddenWidgets, layout_version: "2.0" }); }; const handleCancelCustomize = () => { if (user?.dashboard_layout_client) { setWidgetOrder(user.dashboard_layout_client.widgets || AVAILABLE_WIDGETS.map(w => w.id)); setHiddenWidgets(user.dashboard_layout_client.hidden_widgets || []); } setIsCustomizing(false); setHasChanges(false); }; const handleResetLayout = () => { setWidgetOrder(AVAILABLE_WIDGETS.map(w => w.id)); setHiddenWidgets([]); setHasChanges(true); }; const getOrderStatusBadge = (order) => { const assignedCount = order.assigned_staff?.length || 0; const requestedCount = order.requested || 0; if (order.is_rapid === true) { return URGENT; } if (order.status === "Canceled") { return Canceled; } if (assignedCount >= requestedCount && requestedCount > 0) { return Fully Staffed; } if (order.status === "Active" || order.status === "Confirmed") { return Active; } if (order.status === "Pending") { return Pending; } if (order.status === "Completed") { return Completed; } return {order.status || "Draft"}; }; const getShiftTimes = (order) => { if (order.shifts && order.shifts.length > 0) { const shift = order.shifts[0]; if (shift.roles && shift.roles.length > 0) { const role = shift.roles[0]; const startTime = convertTo12Hour(role.start_time) || "—"; const endTime = convertTo12Hour(role.end_time) || "—"; return { startTime, endTime }; } } return { startTime: "—", endTime: "—" }; }; const renderOrderNow = () => ( !isCustomizing && navigate(createPageUrl("CreateEvent"))} >

Create New

Order Now

); const renderRapidOrder = () => ( !isCustomizing && navigate(createPageUrl("RapidOrder"))} >

ORDER TYPE

{rapidOrders.length > 0 ? ( {rapidOrders.length} Urgent ) : ( Urgent )}

RAPID

Click to order

); const renderTodayCount = () => (

Today's Orders

{todayOrders.length}

Active
); const renderInProgress = () => (

In Progress

{inProgressOrders.length}

Active
); const renderNeedsAttention = () => (

Needs Attention

{needsAttention.length}

); const renderTodaysOrders = () => { return (
Today's Orders

{format(new Date(), 'EEEE, MMMM d, yyyy')}

{todayOrders.length} Active {user?.preferred_vendor_name && !isCustomizing && ( )}
{todayOrders.length > 0 ? ( <>
{(showAllToday ? todayOrders : todayOrders.slice(0, 4)).map((order, index) => { const assignedCount = order.assigned_staff?.length || 0; const requestedCount = order.requested || 0; const { startTime, endTime } = getShiftTimes(order); const displayOrders = showAllToday ? todayOrders : todayOrders.slice(0, 4); const isLastRow = index === displayOrders.length - 1; return ( ); })}
BUSINESS HUB EVENT NAME STATUS DATE TIME REQUESTED ASSIGNED INVOICE ACTIONS
{order.business_name || "Primary Location"} {order.hub || order.event_location || "Main Hub"} {order.event_name} {getOrderStatusBadge(order)} {order.date ? format(new Date(order.date), 'MM/dd/yy') : "—"}
{startTime} {endTime}
{requestedCount} {assignedCount}
{todayOrders.length > 4 && (
)} ) : (
{/* Main Empty State */}

No orders scheduled for today

Stay ahead of your operations — create a new order or schedule one for today to keep your workforce running smoothly

{/* Action Buttons */}
{/* Smart Insights Section */}

Smart Insights

{/* Invoice Reminder */} {pendingInvoices.length > 0 && (
navigate(createPageUrl("Invoices"))} >

Action Required

{pendingInvoices.length} invoice{pendingInvoices.length !== 1 ? 's' : ''} requiring attention

${pendingInvoices.reduce((sum, inv) => sum + (inv.amount || 0), 0).toLocaleString()}
View Invoices
)} {/* Order Health Report */}

Order Health Report

Your fulfillment performance this month

Fulfillment Rate {Math.round(avgFulfillmentRate)}%
Completed Orders {completedOrders.length}
)} ); }; const renderLaborSummary = () => (
Labor Summary

Detailed breakdown for November 2025

Total Amount

${totalLaborCost.toFixed(2)}

Total Hours

{totalHours.toFixed(2)} hours

{laborByPosition.length > 0 ? ( laborByPosition.map((item) => ( )) ) : ( )}
Job Title Reg Hrs OT Hrs DT Hrs Reg Amt OT Amt DT Amt Total Pay
{item.position} {item.regHours.toFixed(1)} {item.otHours.toFixed(1)} {item.dtHours.toFixed(1)} ${item.regAmt.toFixed(0)} ${item.otAmt.toFixed(0)} ${item.dtAmt.toFixed(0)} ${item.totalPay.toFixed(0)}
No labor data for this month
); const renderSalesAnalytics = () => (
Sales analytics
{pieChartData.length > 0 ? ( <> {pieChartData.map((entry, index) => ( ))} `$${Math.round(value).toLocaleString()}`} />
{pieChartData.map((item, idx) => (
{item.name}
${Math.round(item.value).toLocaleString()}
))}
) : (
No sales data available
)} ); const renderVendorMarketplace = () => (

Find Vendors

Browse marketplace

); const renderWidget = (widgetId) => { switch (widgetId) { case 'order-now': return renderOrderNow(); case 'rapid-order': return renderRapidOrder(); case 'today-count': return renderTodayCount(); case 'in-progress': return renderInProgress(); case 'needs-attention': return renderNeedsAttention(); case 'todays-orders': return renderTodaysOrders(); case 'labor-summary': return renderLaborSummary(); case 'sales-analytics': return renderSalesAnalytics(); case 'vendor-marketplace': return renderVendorMarketplace(); default: return null; } }; const visibleWidgetIds = widgetOrder.filter(id => !hiddenWidgets.includes(id)); const availableToAdd = AVAILABLE_WIDGETS.filter(w => hiddenWidgets.includes(w.id)); const quickActionWidgets = ['order-now', 'rapid-order', 'today-count', 'in-progress', 'needs-attention']; const visibleQuickActions = visibleWidgetIds.filter(id => quickActionWidgets.includes(id)); const visibleOtherWidgets = visibleWidgetIds.filter(id => !quickActionWidgets.includes(id)); return (

{greeting} here's what matters today

{isCustomizing && hasChanges && ( Unsaved Changes )} {isCustomizing ? ( <> ) : ( )}
{(provided) => (
{visibleQuickActions.map((widgetId, index) => ( {(provided, snapshot) => (
{isCustomizing && ( )}
{renderWidget(widgetId)}
)}
))} {provided.placeholder}
)}
{(provided) => (
{visibleOtherWidgets.map((widgetId, index) => ( {(provided, snapshot) => (
{isCustomizing && ( )}
{renderWidget(widgetId)}
)}
))} {provided.placeholder}
)}
{isCustomizing && availableToAdd.length > 0 && (

Add Widgets

{availableToAdd.length} available
{availableToAdd.map((widget) => ( ))}
)}
setCancelDialog({ open, order: null })}> Cancel Order? Are you sure you want to cancel this order? This action cannot be undone. {cancelDialog.order && (

{cancelDialog.order.event_name}

{cancelDialog.order.date ? format(new Date(cancelDialog.order.date), "MMMM d, yyyy") : "—"}
{cancelDialog.order.hub || cancelDialog.order.event_location}
)}
); }