262 lines
11 KiB
JavaScript
262 lines
11 KiB
JavaScript
|
|
import React, { useState } from "react";
|
|
import { base44 } from "@/api/base44Client";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { Link, useNavigate } from "react-router-dom";
|
|
import { createPageUrl } from "@/utils";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|
import { Users, Building2, UserPlus, TrendingUp, MapPin, Calendar, DollarSign, Award, Target, BarChart3, Shield, Leaf } from "lucide-react";
|
|
import StatsCard from "../components/staff/StatsCard";
|
|
import EcosystemWheel from "../components/dashboard/EcosystemWheel";
|
|
import QuickMetrics from "../components/dashboard/QuickMetrics";
|
|
import PageHeader from "../components/common/PageHeader";
|
|
|
|
export default function Dashboard() {
|
|
const navigate = useNavigate();
|
|
const [selectedLayer, setSelectedLayer] = useState(null);
|
|
|
|
const { data: staff, isLoading: loadingStaff } = useQuery({
|
|
queryKey: ['staff'],
|
|
queryFn: () => base44.entities.Staff.list('-created_date'),
|
|
initialData: [],
|
|
});
|
|
|
|
const { data: events, isLoading: loadingEvents } = useQuery({
|
|
queryKey: ['events'],
|
|
queryFn: () => base44.entities.Event.list('-date'),
|
|
initialData: [],
|
|
});
|
|
|
|
const recentStaff = staff.slice(0, 6);
|
|
const uniqueDepartments = [...new Set(staff.map(s => s.department).filter(Boolean))];
|
|
const uniqueLocations = [...new Set(staff.map(s => s.hub_location).filter(Boolean))];
|
|
|
|
// Calculate key metrics
|
|
const totalFillRate = 97;
|
|
const totalSpend = 2.3;
|
|
const overallScore = "A+";
|
|
const activeEvents = events.filter(e => e.status === "Active" || e.status === "Confirmed").length;
|
|
const completionRate = events.length > 0 ? Math.round((events.filter(e => e.status === "Completed").length / events.length) * 100) : 0;
|
|
|
|
const ecosystemLayers = [
|
|
{
|
|
name: "Buyer(Procurements)",
|
|
icon: DollarSign,
|
|
color: "from-[#0A39DF] to-[#1C323E]",
|
|
metrics: { fillRate: "97%", spend: "$2.3M", score: "A+" },
|
|
route: "ProcurementDashboard"
|
|
},
|
|
{
|
|
name: "Enterprises (Operator)",
|
|
icon: Target,
|
|
color: "from-emerald-500 to-emerald-700",
|
|
metrics: { coverage: "94%", incidents: "2", satisfaction: "4.8/5" },
|
|
route: "OperatorDashboard"
|
|
},
|
|
{
|
|
name: "Sectors (Execution)",
|
|
icon: Building2,
|
|
color: "from-purple-500 to-purple-700",
|
|
metrics: { active: activeEvents, revenue: "$1.8M", growth: "+12%" },
|
|
route: "OperatorDashboard"
|
|
},
|
|
{
|
|
name: "Partner",
|
|
icon: Users,
|
|
color: "from-pink-500 to-pink-700",
|
|
metrics: { total: "45", retention: "92%", nps: "8.5" },
|
|
route: "Business"
|
|
},
|
|
{
|
|
name: "Approved Vendor",
|
|
icon: Award,
|
|
color: "from-amber-500 to-amber-700",
|
|
metrics: { partners: "12", rating: "4.7/5", compliance: "98%" },
|
|
route: "VendorDashboard"
|
|
},
|
|
{
|
|
name: "Workforce",
|
|
icon: UserPlus,
|
|
color: "from-[#0A39DF] to-[#0A39DF]/80",
|
|
metrics: { total: staff.length, active: staff.filter(s => s.check_in).length, trained: "89%" },
|
|
route: "WorkforceDashboard"
|
|
}
|
|
];
|
|
|
|
return (
|
|
<div className="p-4 md:p-8 bg-gradient-to-br from-slate-50 to-slate-100 min-h-screen">
|
|
<div className="max-w-7xl mx-auto">
|
|
<PageHeader
|
|
title="Welcome to KROW"
|
|
subtitle="Your Complete Workforce Management Ecosystem"
|
|
actions={
|
|
<>
|
|
<Button variant="outline" className="border-slate-300 hover:bg-slate-100">
|
|
<BarChart3 className="w-4 h-4 mr-2" />
|
|
Reports
|
|
</Button>
|
|
<Link to={createPageUrl("Events")}>
|
|
<Button className="bg-gradient-to-r from-[#0A39DF] to-[#1C323E] hover:from-[#0A39DF]/90 hover:to-[#1C323E]/90 text-white shadow-lg">
|
|
<Calendar className="w-5 h-5 mr-2" />
|
|
View All Events
|
|
</Button>
|
|
</Link>
|
|
</>
|
|
}
|
|
/>
|
|
|
|
{/* Global Metrics */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<StatsCard
|
|
title="Fill Rate"
|
|
value={`${totalFillRate}%`}
|
|
icon={Target}
|
|
gradient="bg-gradient-to-br from-[#0A39DF] to-[#1C323E]"
|
|
change="+2.5% this month"
|
|
/>
|
|
<StatsCard
|
|
title="Total Spend"
|
|
value={`$${totalSpend}M`}
|
|
icon={DollarSign}
|
|
gradient="bg-gradient-to-br from-emerald-500 to-emerald-700"
|
|
change="+$180K this month"
|
|
/>
|
|
<StatsCard
|
|
title="Overall Score"
|
|
value={overallScore}
|
|
icon={Award}
|
|
gradient="bg-gradient-to-br from-amber-500 to-amber-600"
|
|
/>
|
|
<StatsCard
|
|
title="Active Events"
|
|
value={activeEvents}
|
|
icon={Calendar}
|
|
gradient="bg-gradient-to-br from-purple-500 to-purple-700"
|
|
change={`${completionRate}% completion rate`}
|
|
/>
|
|
</div>
|
|
|
|
{/* Ecosystem Puzzle */}
|
|
<Card className="mb-8 border-slate-200 shadow-xl overflow-hidden">
|
|
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
|
|
<CardTitle className="text-[#1C323E] flex items-center gap-2">
|
|
<Target className="w-6 h-6 text-[#0A39DF]" />
|
|
Ecosystem Connection Map
|
|
</CardTitle>
|
|
<p className="text-sm text-slate-500 mt-1">Interactive puzzle showing how each layer connects • Hover to see metrics • Click to explore</p>
|
|
</CardHeader>
|
|
<CardContent className="p-8">
|
|
<EcosystemWheel
|
|
layers={ecosystemLayers}
|
|
onLayerClick={(layer) => navigate(createPageUrl(layer.route))}
|
|
selectedLayer={selectedLayer}
|
|
onLayerHover={setSelectedLayer}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Quick Access Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
<QuickMetrics
|
|
title="Procurement & Vendor Intelligence"
|
|
description="Vendor efficiency, spend analysis, compliance tracking"
|
|
icon={Shield}
|
|
metrics={[
|
|
{ label: "Vendor Score", value: "A+", color: "text-green-600" },
|
|
{ label: "Compliance", value: "98%", color: "text-blue-600" },
|
|
{ label: "ESG Rating", value: "B+", color: "text-emerald-600" }
|
|
]}
|
|
route="ProcurementDashboard"
|
|
gradient="from-[#0A39DF]/10 to-[#1C323E]/10"
|
|
/>
|
|
|
|
<QuickMetrics
|
|
title="Operator & Sector Dashboard"
|
|
description="Live coverage, demand forecast, incident tracking"
|
|
icon={MapPin}
|
|
metrics={[
|
|
{ label: "Coverage", value: "94%", color: "text-green-600" },
|
|
{ label: "Incidents", value: "2", color: "text-yellow-600" },
|
|
{ label: "Forecast Accuracy", value: "91%", color: "text-blue-600" }
|
|
]}
|
|
route="OperatorDashboard"
|
|
gradient="from-emerald-500/10 to-emerald-700/10"
|
|
/>
|
|
|
|
<QuickMetrics
|
|
title="Vendor Dashboard"
|
|
description="Orders, invoices, workforce pulse, KROW score"
|
|
icon={Award}
|
|
metrics={[
|
|
{ label: "Fill Rate", value: "97%", color: "text-green-600" },
|
|
{ label: "Attendance", value: "95%", color: "text-blue-600" },
|
|
{ label: "Training", value: "92%", color: "text-purple-600" }
|
|
]}
|
|
route="VendorDashboard"
|
|
gradient="from-amber-500/10 to-amber-700/10"
|
|
/>
|
|
</div>
|
|
|
|
{/* Workforce Section */}
|
|
<Card className="border-slate-200 shadow-lg">
|
|
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle className="text-[#1C323E] flex items-center gap-2">
|
|
<Users className="w-6 h-6 text-[#0A39DF]" />
|
|
Workforce Overview
|
|
</CardTitle>
|
|
<p className="text-sm text-slate-500 mt-1">Recent additions and active workers</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Link to={createPageUrl("WorkforceDashboard")}>
|
|
<Button variant="outline" className="border-slate-300">
|
|
View Workforce App
|
|
</Button>
|
|
</Link>
|
|
<Link to={createPageUrl("StaffDirectory")}>
|
|
<Button className="bg-[#0A39DF] hover:bg-[#0A39DF]/90">
|
|
View All Staff
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="p-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{recentStaff.slice(0, 3).map((member) => (
|
|
<div key={member.id} className="p-4 rounded-lg border border-slate-200 hover:border-[#0A39DF] hover:shadow-md transition-all">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className="w-12 h-12 bg-gradient-to-br from-[#0A39DF] to-[#1C323E] rounded-xl flex items-center justify-center text-white font-bold">
|
|
{member.initial || member.employee_name?.charAt(0)}
|
|
</div>
|
|
<div>
|
|
<h4 className="font-semibold text-[#1C323E]">{member.employee_name}</h4>
|
|
<p className="text-sm text-slate-500">{member.position}</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-500">Rating:</span>
|
|
<span className="font-semibold">{member.rating || 0}/5 ⭐</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-500">Coverage:</span>
|
|
<span className="font-semibold text-green-600">{member.shift_coverage_percentage || 0}%</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-500">Cancellations:</span>
|
|
<span className="font-semibold text-red-600">{member.cancellation_count || 0}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|