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

246 lines
12 KiB
JavaScript

import React from "react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { MapPin, TrendingUp, AlertTriangle, CheckCircle2, Clock, Users } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, AreaChart, Area } from 'recharts';
import PageHeader from "../components/common/PageHeader";
const coverageData = [
{ hub: 'San Jose', coverage: 97, incidents: 0, satisfaction: 4.9 },
{ hub: 'San Francisco', coverage: 92, incidents: 1, satisfaction: 4.7 },
{ hub: 'Oakland', coverage: 89, incidents: 2, satisfaction: 4.5 },
{ hub: 'Sacramento', coverage: 95, incidents: 1, satisfaction: 4.8 },
];
const demandForecast = [
{ week: 'Week 1', predicted: 120, actual: 118 },
{ week: 'Week 2', predicted: 135, actual: 140 },
{ week: 'Week 3', predicted: 145, actual: 142 },
{ week: 'Week 4', predicted: 160, actual: null },
{ week: 'Week 5', predicted: 155, actual: null },
];
const incidents = [
{ id: 1, type: 'Late Arrival', hub: 'San Francisco', staff: 'John Doe', severity: 'low', time: '2 hours ago' },
{ id: 2, type: 'No Show', hub: 'Oakland', staff: 'Jane Smith', severity: 'high', time: '5 hours ago' },
];
const feedbackData = [
{ client: 'Tech Corp', rating: 5, comment: 'Excellent service, very professional staff', date: '2 days ago' },
{ client: 'Event Solutions', rating: 4, comment: 'Good overall, minor scheduling issues', date: '3 days ago' },
{ client: 'Premier Events', rating: 5, comment: 'Outstanding performance, will book again', date: '1 week ago' },
];
export default function OperatorDashboard() {
const navigate = useNavigate();
return (
<div className="p-4 md:p-8 bg-slate-50 min-h-screen">
<div className="max-w-7xl mx-auto">
<PageHeader
title="Operator & Sector Dashboard"
subtitle="Live coverage, demand forecasting, and incident tracking"
showUnpublished={true}
backTo={createPageUrl("Dashboard")}
backButtonLabel="Back to Dashboard"
/>
{/* Key Metrics */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card className="border-slate-200 shadow-lg">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-2">
<Users className="w-8 h-8 text-[#0A39DF]" />
<Badge className="bg-green-100 text-green-700">+5%</Badge>
</div>
<p className="text-sm text-slate-500">Coverage Rate</p>
<p className="text-3xl font-bold text-[#1C323E]">94%</p>
</CardContent>
</Card>
<Card className="border-slate-200 shadow-lg">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-2">
<AlertTriangle className="w-8 h-8 text-yellow-600" />
<Badge className="bg-green-100 text-green-700">Low</Badge>
</div>
<p className="text-sm text-slate-500">Active Incidents</p>
<p className="text-3xl font-bold text-[#1C323E]">2</p>
</CardContent>
</Card>
<Card className="border-slate-200 shadow-lg">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-2">
<CheckCircle2 className="w-8 h-8 text-green-600" />
<Badge className="bg-blue-100 text-blue-700">4.8/5.0</Badge>
</div>
<p className="text-sm text-slate-500">Client Satisfaction</p>
<p className="text-3xl font-bold text-[#1C323E]">4.8</p>
</CardContent>
</Card>
<Card className="border-slate-200 shadow-lg">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-2">
<TrendingUp className="w-8 h-8 text-emerald-600" />
<Badge className="bg-emerald-100 text-emerald-700">91%</Badge>
</div>
<p className="text-sm text-slate-500">Forecast Accuracy</p>
<p className="text-3xl font-bold text-[#1C323E]">91%</p>
</CardContent>
</Card>
</div>
{/* Shift Coverage Heat Map */}
<Card className="mb-8 border-slate-200 shadow-lg">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
<CardTitle className="text-[#1C323E]">Live Shift Coverage Map</CardTitle>
</CardHeader>
<CardContent className="p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{coverageData.map((hub, index) => (
<div key={index} className="p-6 rounded-xl border-2 border-slate-200 hover:border-[#0A39DF] hover:shadow-lg transition-all">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
hub.coverage >= 95 ? 'bg-green-100' :
hub.coverage >= 90 ? 'bg-blue-100' :
'bg-yellow-100'
}`}>
<MapPin className={`w-6 h-6 ${
hub.coverage >= 95 ? 'text-green-600' :
hub.coverage >= 90 ? 'text-blue-600' :
'text-yellow-600'
}`} />
</div>
<div>
<h4 className="font-bold text-[#1C323E]">{hub.hub}</h4>
<p className="text-sm text-slate-500">Coverage: {hub.coverage}%</p>
</div>
</div>
<Badge className={`${
hub.coverage >= 95 ? 'bg-green-100 text-green-700' :
hub.coverage >= 90 ? 'bg-blue-100 text-blue-700' :
'bg-yellow-100 text-yellow-700'
} text-lg px-3 py-1`}>
{hub.coverage}%
</Badge>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-slate-500">Incidents</p>
<p className="font-bold text-red-600">{hub.incidents}</p>
</div>
<div>
<p className="text-slate-500">Satisfaction</p>
<p className="font-bold text-emerald-600">{hub.satisfaction}/5.0</p>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
{/* Predictive Demand Forecast */}
<Card className="border-slate-200 shadow-lg">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
<CardTitle className="text-[#1C323E]">Predictive Demand Forecast</CardTitle>
</CardHeader>
<CardContent className="p-6">
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={demandForecast}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="week" />
<YAxis />
<Tooltip />
<Legend />
<Area type="monotone" dataKey="predicted" stackId="1" stroke="#0A39DF" fill="#0A39DF" fillOpacity={0.3} name="Predicted" />
<Area type="monotone" dataKey="actual" stackId="2" stroke="#10b981" fill="#10b981" fillOpacity={0.6} name="Actual" />
</AreaChart>
</ResponsiveContainer>
<p className="text-sm text-slate-500 mt-4">
Forecast accuracy: <span className="font-bold text-[#0A39DF]">91%</span> based on historical data
</p>
</CardContent>
</Card>
{/* Incident Feed */}
<Card className="border-slate-200 shadow-lg">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
<CardTitle className="text-[#1C323E]">Incident Feed</CardTitle>
</CardHeader>
<CardContent className="p-6">
<div className="space-y-4">
{incidents.map((incident) => (
<div key={incident.id} className="p-4 rounded-lg border-2 border-slate-200 hover:border-red-300 transition-all">
<div className="flex items-start justify-between mb-2">
<div className="flex items-center gap-2">
<AlertTriangle className={`w-5 h-5 ${
incident.severity === 'high' ? 'text-red-600' :
incident.severity === 'medium' ? 'text-yellow-600' :
'text-blue-600'
}`} />
<h4 className="font-semibold text-[#1C323E]">{incident.type}</h4>
</div>
<Badge className={`${
incident.severity === 'high' ? 'bg-red-100 text-red-700' :
incident.severity === 'medium' ? 'bg-yellow-100 text-yellow-700' :
'bg-blue-100 text-blue-700'
}`}>
{incident.severity.toUpperCase()}
</Badge>
</div>
<div className="space-y-1 text-sm">
<p><span className="text-slate-500">Hub:</span> <span className="font-medium">{incident.hub}</span></p>
<p><span className="text-slate-500">Staff:</span> <span className="font-medium">{incident.staff}</span></p>
<p className="text-slate-400 flex items-center gap-1">
<Clock className="w-3 h-3" />
{incident.time}
</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* Client Feedback */}
<Card className="border-slate-200 shadow-lg">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
<CardTitle className="text-[#1C323E]">Recent Client Feedback</CardTitle>
</CardHeader>
<CardContent className="p-6">
<div className="space-y-4">
{feedbackData.map((feedback, index) => (
<div key={index} className="p-4 rounded-lg border border-slate-200 hover:border-[#0A39DF] hover:shadow-md transition-all">
<div className="flex items-start justify-between mb-2">
<div>
<h4 className="font-semibold text-[#1C323E]">{feedback.client}</h4>
<div className="flex items-center gap-1 mt-1">
{[...Array(5)].map((_, i) => (
<span key={i} className={i < feedback.rating ? "text-yellow-400" : "text-slate-300"}></span>
))}
</div>
</div>
<span className="text-xs text-slate-400">{feedback.date}</span>
</div>
<p className="text-sm text-slate-600">{feedback.comment}</p>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
);
}