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"))}
>
);
const renderRapidOrder = () => (
!isCustomizing && navigate(createPageUrl("RapidOrder"))}
>
ORDER TYPE
{rapidOrders.length > 0 ? (
{rapidOrders.length} Urgent
) : (
Urgent
)}
);
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 ? (
<>
| BUSINESS |
HUB |
EVENT NAME |
STATUS |
DATE |
TIME |
REQUESTED |
ASSIGNED |
INVOICE |
ACTIONS |
{(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 (
|
{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 */}
{/* 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()}
)}
{/* 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
| Job Title |
Reg Hrs |
OT Hrs |
DT Hrs |
Reg Amt |
OT Amt |
DT Amt |
Total Pay |
{laborByPosition.length > 0 ? (
laborByPosition.map((item) => (
| {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 = () => (
{pieChartData.length > 0 ? (
<>
{pieChartData.map((entry, index) => (
|
))}
`$${Math.round(value).toLocaleString()}`} />
{pieChartData.map((item, idx) => (
${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) => (
))}
)}
);
}