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 (
Your staffing operations at a glance
{format(new Date(), 'EEEE, MMMM d, yyyy')}
This month breakdown
| Position | Headcount | Hours | Total Cost | Avg/Hour |
|---|---|---|---|---|
|
{item.position}
|
|
{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 |
This Month
${Math.round(totalLaborCost / 1000)}k
Avg Cost per Hour
${Math.round(avgCostPerHour)}
Across {totalHours.toFixed(0)} hours
Total Staff Hired
{totalHeadcount}
This month
One tap to reorder
{event.hub || 'No location'}
No previous orders
Your favorites will appear here
Next 7 days
No upcoming orders
Find Vendors
Browse marketplace
Messages
Chat with vendors