feat: Initial commit of KROW Workforce Web client (Base44 export)
This commit is contained in:
261
src/pages/Dashboard.jsx
Normal file
261
src/pages/Dashboard.jsx
Normal file
@@ -0,0 +1,261 @@
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user