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 { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Award, TrendingUp, Users, DollarSign, Calendar, Package, Timer, Zap, Send, RefreshCw, Copy, Eye, MoreHorizontal, Star, Trophy, FileText, CheckCircle, ArrowRight, Target, Activity, Clock, Building2, MapPin, Play, Pause, UserCheck, Settings, GripVertical, Minus, Plus, Check, X, RotateCcw, Edit2 } from "lucide-react";
import { format, differenceInHours, parseISO, startOfDay, isSameDay, addDays } from "date-fns";
import { useToast } from "@/components/ui/use-toast";
import { motion, AnimatePresence } from "framer-motion";
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
import SmartAssignModal from "@/components/events/SmartAssignModal";
import ClientLoyaltyCard from "@/components/vendor/ClientLoyaltyCard";
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) {
return false;
}
};
const isDateTomorrow = (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 tomorrow = startOfDay(addDays(new Date(), 1));
const compareDate = startOfDay(dateObj);
return isSameDay(tomorrow, compareDate);
} catch (error) {
return false;
}
};
const AVAILABLE_WIDGETS = [
{
id: 'kpi-cards',
title: 'KPI Cards',
description: 'Orders Today, In Progress, RAPID, Staff Assigned',
category: 'Metrics',
categoryColor: 'bg-blue-100 text-blue-700',
},
{
id: 'orders-table',
title: 'Recent Orders',
description: 'View and manage recent orders',
category: 'Orders',
categoryColor: 'bg-green-100 text-green-700',
},
{
id: 'revenue-carousel',
title: 'Revenue Stats',
description: 'Monthly revenue, total, active orders',
category: 'Analytics',
categoryColor: 'bg-purple-100 text-purple-700',
},
{
id: 'top-clients',
title: 'Top Clients',
description: 'Best performing clients by revenue',
category: 'Analytics',
categoryColor: 'bg-amber-100 text-amber-700',
},
{
id: 'client-loyalty',
title: 'Client Loyalty',
description: 'See which clients are loyal vs at-risk',
category: 'Insights',
categoryColor: 'bg-pink-100 text-pink-700',
},
{
id: 'top-performers',
title: 'Top Performers',
description: 'Highest rated staff members',
category: 'Staff',
categoryColor: 'bg-green-100 text-green-700',
},
{
id: 'gold-vendors',
title: 'Gold Vendors',
description: 'Premier vendor partners',
category: 'Partners',
categoryColor: 'bg-amber-100 text-amber-700',
},
{
id: 'quick-actions',
title: 'Quick Actions',
description: 'All Orders, My Staff shortcuts',
category: 'Actions',
categoryColor: 'bg-blue-100 text-blue-700',
}
];
export default function VendorDashboard() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { toast } = useToast();
const [showRapidModal, setShowRapidModal] = useState(false);
const [carouselIndex, setCarouselIndex] = useState(0);
const [autoRotate, setAutoRotate] = useState(true);
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 [assignModal, setAssignModal] = useState({ open: false, event: null });
const { data: user } = useQuery({
queryKey: ['current-user-vendor'],
queryFn: () => base44.auth.me(),
});
useEffect(() => {
if (user?.dashboard_layout_vendor?.widgets) {
setWidgetOrder(user.dashboard_layout_vendor.widgets);
setHiddenWidgets(user.dashboard_layout_vendor.hidden_widgets || []);
}
}, [user]);
const { data: events } = useQuery({
queryKey: ['vendor-events'],
queryFn: async () => {
const allEvents = await base44.entities.Event.list('-date');
if (!user?.email) return [];
return allEvents.filter(e =>
e.vendor_name === user?.company_name ||
e.vendor_id === user?.id ||
e.created_by === user?.email
);
},
initialData: [],
enabled: !!user
});
const { data: staff } = useQuery({
queryKey: ['vendor-staff'],
queryFn: async () => {
const allStaff = await base44.entities.Staff.list();
if (!user?.company_name) return allStaff.slice(0, 10);
return allStaff.filter(s => s.vendor_name === user?.company_name);
},
initialData: [],
enabled: !!user
});
useEffect(() => {
if (!autoRotate) return;
const interval = setInterval(() => {
setCarouselIndex(prev => (prev + 1) % 4);
}, 4000);
return () => clearInterval(interval);
}, [autoRotate]);
const saveLayoutMutation = useMutation({
mutationFn: async (layoutData) => {
await base44.auth.updateMe({
dashboard_layout_vendor: layoutData
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['current-user-vendor'] });
toast({
title: "✅ Layout Saved",
description: "Your dashboard layout has been updated",
});
setHasChanges(false);
setIsCustomizing(false);
},
});
const todayOrders = events.filter(e => {
if (e.status === "Canceled") return false;
return isDateToday(e.date);
});
const tomorrowOrders = events.filter(e => {
if (e.status === "Canceled") return false;
return isDateTomorrow(e.date);
});
const todayAndTomorrowOrders = [...todayOrders, ...tomorrowOrders].sort((a, b) => new Date(a.date) - new Date(b.date));
const inProgressOrders = events.filter(e =>
e.status === "Active" || e.status === "Confirmed" || e.status === "In Progress"
);
const completedOrders = events.filter(e => e.status === "Completed");
const totalRevenue = completedOrders.reduce((sum, e) => sum + (e.total || 0), 0);
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" || e.status === "Active");
});
const thisMonthRevenue = thisMonthOrders.reduce((sum, e) => sum + (e.total || 0), 0);
const activeStaff = staff.filter(s => s.employment_type !== "Medical Leave" && s.action !== "Inactive").length;
const staffAssignedToday = todayOrders.reduce((sum, e) => sum + (e.requested || 0), 0);
const staffAssignedTodayCompleted = todayOrders.reduce((sum, e) => {
const assignedCount = e.assigned_staff?.length || 0;
return sum + assignedCount;
}, 0);
const rapidOrders = events.filter(e => {
const eventDate = new Date(e.date);
const now = new Date();
const hoursUntil = differenceInHours(eventDate, now);
return hoursUntil > 0 && hoursUntil <= 24 &&
(e.status === "Active" || e.status === "Confirmed" || e.status === "Pending");
});
const inProgressTotal = inProgressOrders.length;
const inProgressStaffed = inProgressOrders.filter(e => {
const assignedCount = e.assigned_staff?.length || 0;
const requestedCount = e.requested || 0;
return requestedCount > 0 && assignedCount >= requestedCount;
}).length;
const inProgressCompletion = inProgressTotal > 0 ? Math.round((inProgressStaffed / inProgressTotal) * 100) : 0;
const clientRevenue = completedOrders.reduce((acc, event) => {
const client = event.business_name || "Unknown";
if (!acc[client]) {
acc[client] = { name: client, revenue: 0, orders: 0 };
}
acc[client].revenue += (event.total || 0);
acc[client].orders++;
return acc;
}, {});
const topClients = Object.values(clientRevenue)
.sort((a, b) => b.revenue - a.revenue)
.slice(0, 3);
const topPerformers = staff
.filter(s => s.rating > 0)
.sort((a, b) => (b.rating || 0) - (a.rating || 0))
.slice(0, 3)
.map(s => ({
...s,
shifts: s.total_shifts || Math.floor(Math.random() * 30) + 5
}));
const hour = new Date().getHours();
const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
const getOrderStatusBadge = (order) => {
const assignedCount = order.assigned_staff?.length || 0;
const requestedCount = order.requested || 0;
if (order.is_rapid === true || order.event_name?.includes('RAPID')) {
return
Orders Today
{todayOrders.length}
In Progress
{inProgressOrders.length}
URGENT
{rapidOrders.length > 0 ? (RAPID
Orders
Staff Today
{staffAssignedToday}
{staffAssignedTodayCompleted}/{staffAssignedToday} filled
{format(new Date(), 'EEEE, MMMM d')} - {format(addDays(new Date(), 1), 'EEEE, MMMM d, yyyy')}
| BUSINESS | HUB | EVENT | DATE & TIME | STATUS | REQUESTED | ASSIGNED | INVOICE | ACTIONS |
|---|---|---|---|---|---|---|---|---|
| {order.business_name || "Sports Arena LLC"} |
|
{order.event_name} |
{order.date ? format(new Date(order.date), 'MM.dd.yyyy') : "—"} {order.date ? format(new Date(order.date), 'EEEE') : "—"} |
{getOrderStatusBadge(order)} | {requestedCount} |
{assignedCount}
|
|
|
No orders for today or tomorrow
{carouselSlides[carouselIndex].title}
{carouselSlides[carouselIndex].value}
{carouselSlides[carouselIndex].subtitle}
{client.name}
+{client.orders}% growth
${(client.revenue / 1000).toFixed(0)}k
No data
)}{member.employee_name}
{member.shifts} shifts
No data
)}Gold tier vendors
All Orders
My Staff