import React, { useState, useMemo } 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 { Calendar, Plus, Clock, DollarSign, MessageSquare, RefreshCw, ArrowRight, Users, TrendingUp, TrendingDown, BarChart3, Sparkles, Zap, CheckCircle, AlertCircle, Coffee, ChevronRight, User } from "lucide-react"; import { format, parseISO, startOfMonth, endOfMonth, isToday, isTomorrow, addDays, differenceInDays } from "date-fns"; import { useToast } from "@/components/ui/use-toast"; import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from 'recharts'; const COLORS = ['#0A39DF', '#6366f1', '#8b5cf6', '#a855f7', '#c026d3', '#d946ef']; export default function ClientDashboard() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { toast } = useToast(); const [reorderingId, setReorderingId] = useState(null); const { data: user } = useQuery({ queryKey: ['current-user'], queryFn: () => base44.auth.me(), }); 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 createEventMutation = useMutation({ mutationFn: (eventData) => base44.entities.Event.create(eventData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['client-events'] }); toast({ title: "✅ Order Created", description: "Your reorder has been created successfully", }); setReorderingId(null); }, onError: () => { toast({ title: "❌ Failed to Create Order", description: "Please try again", variant: "destructive", }); setReorderingId(null); }, }); // Today's orders const todayOrders = useMemo(() => { return events.filter(e => { const eventDate = new Date(e.date); return isToday(eventDate); }); }, [events]); // Upcoming orders (next 7 days) const upcomingOrders = useMemo(() => { return events .filter(e => { const eventDate = new Date(e.date); const today = new Date(); const daysUntil = differenceInDays(eventDate, today); return daysUntil > 0 && daysUntil <= 7 && e.status !== "Canceled"; }) .sort((a, b) => new Date(a.date) - new Date(b.date)) .slice(0, 5); }, [events]); // Completed orders for analytics const completedOrders = events.filter(e => e.status === "Completed"); // Current month data 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"; }); // Calculate labor summary by position 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'; // Calculate hours 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, hours: 0, cost: 0 }; } summary[position].headcount += count; summary[position].hours += totalHours; summary[position].cost += cost; }); }); } else if (order.requested) { const position = 'Staff'; const count = order.requested; const hours = 8 * count; const cost = hours * 25; if (!summary[position]) { summary[position] = { position, headcount: 0, hours: 0, cost: 0 }; } summary[position].headcount += count; summary[position].hours += hours; summary[position].cost += cost; } }); return Object.values(summary).sort((a, b) => b.cost - a.cost); }, [thisMonthOrders]); // Cost breakdown const totalLaborCost = laborByPosition.reduce((sum, p) => sum + p.cost, 0); const totalHours = laborByPosition.reduce((sum, p) => sum + p.hours, 0); const totalHeadcount = laborByPosition.reduce((sum, p) => sum + p.headcount, 0); const avgCostPerHour = totalHours > 0 ? totalLaborCost / totalHours : 0; // Last month comparison const lastMonth = new Date(); lastMonth.setMonth(lastMonth.getMonth() - 1); const lastMonthOrders = events.filter(e => { const eventDate = new Date(e.date); return eventDate.getMonth() === lastMonth.getMonth() && eventDate.getFullYear() === lastMonth.getFullYear() && e.status === "Completed"; }); const lastMonthCost = lastMonthOrders.reduce((sum, e) => sum + (e.total || 0), 0); const costChange = lastMonthCost > 0 ? ((totalLaborCost - lastMonthCost) / lastMonthCost) * 100 : 0; // Frequent orders for quick reorder const pastOrders = events.filter(e => e.status === "Completed"); const orderFrequency = pastOrders.reduce((acc, event) => { const key = event.event_name; if (!acc[key]) { acc[key] = { event, count: 0, lastOrdered: event.date, totalCost: 0 }; } acc[key].count++; acc[key].totalCost += (event.total || 0); if (new Date(event.date) > new Date(acc[key].lastOrdered)) { acc[key].lastOrdered = event.date; acc[key].event = event; } return acc; }, {}); const favoriteOrders = Object.values(orderFrequency) .sort((a, b) => b.count - a.count) .slice(0, 4); const handleQuickReorder = (event) => { setReorderingId(event.id); const reorderData = { event_name: event.event_name, business_id: event.business_id, business_name: event.business_name, hub: event.hub, status: "Draft", requested: event.requested, shifts: event.shifts, notes: `Reorder of: ${event.event_name}`, }; createEventMutation.mutate(reorderData); }; const handleRapidOrder = () => { navigate(createPageUrl("CreateEvent") + "?rapid=true"); }; const hour = new Date().getHours(); const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening"; // Prepare data for pie chart const pieChartData = laborByPosition.slice(0, 5).map(item => ({ name: item.position, value: item.cost })); return (
{/* Hero Header */}

{greeting}, {user?.full_name?.split(' ')[0] || 'there'}

Your staffing operations at a glance

{/* Today's Orders - Prominent Section */} {todayOrders.length > 0 && (
Today's Orders

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

{todayOrders.length} Active
{todayOrders.map((order) => { const assignedCount = order.assigned_staff?.length || 0; const requestedCount = order.requested || 0; const fillPercent = requestedCount > 0 ? Math.round((assignedCount / requestedCount) * 100) : 0; return (

{order.event_name}

{fillPercent >= 100 ? ( ) : ( )}
{order.shifts?.[0]?.roles?.[0]?.start_time || 'Time TBD'}
{assignedCount}/{requestedCount} Staff
Staffing Progress {fillPercent}%
= 100 ? 'bg-green-500' : fillPercent >= 50 ? 'bg-blue-500' : 'bg-orange-500' }`} style={{ width: `${fillPercent}%` }} />
); })}
)} {/* Main Dashboard Grid */}
{/* Left Column - Labor & Cost Analytics */}
{/* Labor Summary */}
Labor Summary

This month breakdown

{thisMonthOrders.length} Orders
{laborByPosition.length > 0 ? ( laborByPosition.map((item, idx) => ( )) ) : ( )} {laborByPosition.length > 0 && ( )}
Position Headcount Hours Total Cost Avg/Hour
{item.position}
{item.headcount} {Math.round(item.hours)}h ${Math.round(item.cost).toLocaleString()} ${Math.round(item.cost / item.hours)}/hr
No labor data for this month
TOTAL {totalHeadcount} {Math.round(totalHours)}h ${Math.round(totalLaborCost).toLocaleString()} ${Math.round(avgCostPerHour)}/hr
{/* Cost Analysis */}
{/* Pie Chart */} Cost Distribution {pieChartData.length > 0 ? ( <> {pieChartData.map((entry, index) => ( ))} `$${Math.round(value).toLocaleString()}`} />
{pieChartData.map((item, idx) => (
{item.name}
${Math.round(item.value).toLocaleString()}
))}
) : (
No cost data available
)} {/* Cost Summary Cards */}

This Month

${Math.round(totalLaborCost / 1000)}k

{costChange >= 0 ? ( ) : ( )} = 0 ? 'text-red-600' : 'text-green-600' }`}> {Math.abs(costChange).toFixed(1)}% vs last month

Avg Cost per Hour

${Math.round(avgCostPerHour)}

Across {totalHours.toFixed(0)} hours

Total Staff Hired

{totalHeadcount}

This month

{/* Right Column - Quick Actions */}
{/* Quick Reorder - DoorDash Style */}
Reorder Favorites

One tap to reorder

{favoriteOrders.length > 0 ? ( favoriteOrders.map((item) => { const { event, count } = item; const isReordering = reorderingId === event.id; return (
Ordered {count}x

{event.event_name}

{event.hub || 'No location'}

Last: {format(parseISO(event.date), "MMM d")}
); }) ) : (

No previous orders

Your favorites will appear here

)}
{/* Upcoming Orders */}
Coming Up

Next 7 days

{upcomingOrders.length > 0 ? ( upcomingOrders.map((order) => { const eventDate = new Date(order.date); const daysUntil = differenceInDays(eventDate, new Date()); const isUrgent = daysUntil <= 2; return (

{order.event_name}

{isUrgent && ( {daysUntil === 0 ? 'Today' : daysUntil === 1 ? 'Tomorrow' : `${daysUntil}d`} )}
{format(eventDate, "MMM d, h:mm a")}
); }) ) : (

No upcoming orders

)}
{/* Quick Actions */}

Find Vendors

Browse marketplace

Messages

Chat with vendors

); }