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 { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Award, TrendingUp, Users, DollarSign, CheckCircle2, Clock, UserCheck, Mail, Edit, Bot, ArrowRight, Rocket, Star, Brain, Check, Sparkles, Zap, Calendar, Package, MessageSquare, AlertTriangle, MapPin, BarChart3, RefreshCw, Download, X, Lightbulb, TrendingDown, Timer } from "lucide-react";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts';
import { format, startOfMonth, endOfMonth, subMonths, differenceInHours, parseISO } from "date-fns";
import { useToast } from "@/components/ui/use-toast";
const mockWorkers = [
{ id: 1, name: "Maria G.", skill: "Server", rating: 4.9, distance: 3.2, reliability: 0.99, pro: 2 },
{ id: 2, name: "Andre P.", skill: "Culinary", rating: 4.8, distance: 5.5, reliability: 0.96, pro: 3 },
{ id: 3, name: "Jae L.", skill: "Dishwasher", rating: 4.6, distance: 1.1, reliability: 0.93, pro: 1 },
{ id: 4, name: "Rita C.", skill: "Server", rating: 4.7, distance: 4.0, reliability: 0.95, pro: 2 },
{ id: 5, name: "Luis M.", skill: "Culinary", rating: 4.5, distance: 7.4, reliability: 0.9, pro: 1 },
];
const proColors = {
1: "bg-slate-200 text-slate-800",
2: "bg-blue-100 text-blue-900",
3: "bg-amber-100 text-amber-900",
};
const formatPct = (v) => `${Math.round(v * 100)}%`;
function AINudge({ text, cta, onCta, type = "success", onDismiss }) {
const [dismissed, setDismissed] = useState(false);
const handleDismiss = () => {
setDismissed(true);
if (onDismiss) onDismiss();
};
if (dismissed) return null;
const typeConfig = {
success: {
icon: Sparkles,
gradient: "from-emerald-500 via-teal-500 to-cyan-500",
bgGradient: "from-emerald-50/80 via-teal-50/80 to-cyan-50/80",
iconBg: "from-emerald-400 to-teal-500",
textColor: "text-emerald-900",
subTextColor: "text-emerald-700",
buttonColor: "from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700",
borderColor: "border-emerald-200/60",
glowColor: "shadow-emerald-200/50"
},
insight: {
icon: Lightbulb,
gradient: "from-amber-500 via-orange-500 to-yellow-500",
bgGradient: "from-amber-50/80 via-orange-50/80 to-yellow-50/80",
iconBg: "from-amber-400 to-orange-500",
textColor: "text-amber-900",
subTextColor: "text-amber-700",
buttonColor: "from-amber-600 to-orange-600 hover:from-amber-700 hover:to-orange-700",
borderColor: "border-amber-200/60",
glowColor: "shadow-amber-200/50"
},
achievement: {
icon: Award,
gradient: "from-purple-500 via-pink-500 to-rose-500",
bgGradient: "from-purple-50/80 via-pink-50/80 to-rose-50/80",
iconBg: "from-purple-400 to-pink-500",
textColor: "text-purple-900",
subTextColor: "text-purple-700",
buttonColor: "from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700",
borderColor: "border-purple-200/60",
glowColor: "shadow-purple-200/50"
}
};
const config = typeConfig[type];
const IconComponent = config.icon;
return (
);
}
export default function VendorDashboard() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { toast } = useToast();
const { data: user } = useQuery({
queryKey: ['current-user-vendor'],
queryFn: () => base44.auth.me(),
});
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
});
// Calculations
const todayOrders = events.filter(e => {
const eventDate = new Date(e.date);
const today = new Date();
return eventDate.toDateString() === today.toDateString();
});
const activeOrders = 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 avgRating = staff.reduce((sum, s) => sum + (s.rating || 0), 0) / (staff.length || 1);
const reliabilityScore = staff.reduce((sum, s) => sum + (s.reliability_score || 0), 0) / (staff.length || 1);
// Rapid Orders (urgent orders within 24 hours)
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");
})
.sort((a, b) => new Date(a.date) - new Date(b.date))
.slice(0, 3);
// 6-month revenue and payroll trend
const last6Months = Array.from({ length: 6 }, (_, i) => {
const date = subMonths(new Date(), 5 - i);
return {
month: format(date, 'MMM'),
fullDate: date
};
});
const salesPayrollTrend = last6Months.map(({ month, fullDate }) => {
const monthStart = startOfMonth(fullDate);
const monthEnd = endOfMonth(fullDate);
const monthOrders = events.filter(e => {
const eventDate = new Date(e.date);
return eventDate >= monthStart && eventDate <= monthEnd && e.status === "Completed";
});
const sales = monthOrders.reduce((sum, e) => sum + (e.total || 0), 0);
const payroll = sales * 0.68; // Assume 68% payroll ratio
return { month, sales, payroll };
});
// Top clients with fill rates
const clientRevenue = completedOrders.reduce((acc, event) => {
const client = event.business_name || "Unknown";
if (!acc[client]) {
acc[client] = { name: client, revenue: 0, orders: 0, requested: 0, assigned: 0 };
}
acc[client].revenue += (event.total || 0);
acc[client].orders++;
acc[client].requested += (event.requested || 0);
acc[client].assigned += (event.assigned_staff?.length || 0);
return acc;
}, {});
const topClients = Object.values(clientRevenue)
.map(c => ({
...c,
fillRate: c.requested > 0 ? (c.assigned / c.requested) * 100 : 0
}))
.sort((a, b) => b.revenue - a.revenue)
.slice(0, 4);
// Top performers with shift counts
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
}));
// Staff assigned today
const staffAssignedToday = todayOrders.reduce((sum, e) => sum + (e.requested || 0), 0);
// Avg speed to fill (mock calculation)
const avgSpeedToFill = "1h 12m";
const speedChange = "-14m";
const hour = new Date().getHours();
const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
return (
{/* Header */}
{greeting}, {user?.full_name?.split(' ')[0] || 'Partner'}
Here's your performance overview
{/* Top KPI Metrics - NEW DESIGN */}
{/* Sales */}
${Math.round(thisMonthRevenue / 1000)},000
+5% vs last month
{/* Payroll */}
${Math.round(thisMonthRevenue * 0.68 / 1000)},000
68% of sales
{/* Active Workforce */}
{activeStaff}
+3 this week
{/* Avg Speed to Fill */}
{avgSpeedToFill}
{speedChange} vs last week
{/* AI Insights */}
{/* Main Content Grid */}
{/* Left Column (2 cols) */}
{/* RAPID ORDERS - NEW SECTION */}
Rapid Orders
Urgent orders within 24 hours
{rapidOrders.length > 0 && (
{rapidOrders.length} Urgent
)}
{rapidOrders.length > 0 ? (
{rapidOrders.map((order) => {
const eventDate = order.date ? parseISO(order.date) : new Date();
const hoursUntil = differenceInHours(eventDate, new Date());
const assignedCount = order.assigned_staff?.length || 0;
const requestedCount = order.requested || 0;
const fillPercentage = requestedCount > 0 ? Math.round((assignedCount / requestedCount) * 100) : 0;
return (
{order.business_name || "Client"} – {order.event_name}
{hoursUntil}h left
{assignedCount}/{requestedCount} filled
{format(eventDate, "h:mm a")}
);
})}
) : (
No urgent orders
Orders within 24h will appear here
)}
{/* Sales vs Payroll Chart */}
Sales vs Payroll
`${(value / 1000).toFixed(0)}k`}
/>
[`$${Math.round(value).toLocaleString()}`, '']}
/>
{/* Client Analysis */}
Client Analysis
{topClients.length > 0 ? (
{topClients.map((client) => (
${(client.revenue / 1000).toFixed(0)}k
{Math.round(client.fillRate)}%
))}
) : (
)}
{/* Right Column (1 col) */}
{/* Total Revenue Card - NEW DESIGN */}
Total Revenue
${Math.round(totalRevenue / 1000)}k
All time earnings
{/* Quick Actions - NEW DESIGN */}
All Orders
View & manage
My Orders
Manage staff
{/* Today's Metrics */}
{todayOrders.length}
Orders Today
{activeOrders.length}
In Progress
{/* Staff Assigned Today */}
{staffAssignedToday}
Staff Assigned
{/* Top Clients - NEW DESIGN */}
Top Clients
{topClients.slice(0, 3).length > 0 ? (
{topClients.slice(0, 3).map((client) => (
{client.name}
+{client.orders}%
${(client.revenue / 1000).toFixed(0)}k
))}
) : (
)}
{/* Top Performers - NEW DESIGN */}
Top Performers
{topPerformers.length > 0 ? (
{topPerformers.map((member) => (
{member.employee_name} - {member.position || "Staff"}
{member.shifts} shifts
{(member.rating || 0).toFixed(1)}
))}
) : (
)}
{/* Gold Vendors - NEW SECTION */}
Gold Vendors
);
}