Files
Krow-workspace/src/pages/Dashboard.jsx

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>
);
}