238 lines
9.2 KiB
JavaScript
238 lines
9.2 KiB
JavaScript
import React from "react";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Download, Zap, Clock, TrendingUp, CheckCircle } from "lucide-react";
|
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { useToast } from "@/components/ui/use-toast";
|
|
|
|
const COLORS = ['#10b981', '#3b82f6', '#f59e0b', '#ef4444'];
|
|
|
|
export default function OperationalEfficiencyReport({ events, staff }) {
|
|
const { toast } = useToast();
|
|
|
|
// Automation impact metrics
|
|
const totalEvents = events.length;
|
|
const autoAssignedEvents = events.filter(e =>
|
|
e.assigned_staff && e.assigned_staff.length > 0
|
|
).length;
|
|
const automationRate = totalEvents > 0 ? ((autoAssignedEvents / totalEvents) * 100).toFixed(1) : 0;
|
|
|
|
// Fill rate by status
|
|
const statusBreakdown = events.reduce((acc, event) => {
|
|
const status = event.status || 'Draft';
|
|
acc[status] = (acc[status] || 0) + 1;
|
|
return acc;
|
|
}, {});
|
|
|
|
const statusData = Object.entries(statusBreakdown).map(([name, value]) => ({
|
|
name,
|
|
value,
|
|
}));
|
|
|
|
// Time to fill metrics
|
|
const avgTimeToFill = 2.3; // Mock - would calculate from event creation to full assignment
|
|
const avgResponseTime = 1.5; // Mock - hours to respond to requests
|
|
|
|
// Efficiency over time
|
|
const efficiencyTrend = [
|
|
{ month: 'Jan', automation: 75, fillRate: 88, responseTime: 2.1 },
|
|
{ month: 'Feb', automation: 78, fillRate: 90, responseTime: 1.9 },
|
|
{ month: 'Mar', automation: 82, fillRate: 92, responseTime: 1.7 },
|
|
{ month: 'Apr', automation: 85, fillRate: 94, responseTime: 1.5 },
|
|
];
|
|
|
|
const handleExport = () => {
|
|
const csv = [
|
|
['Operational Efficiency Report'],
|
|
['Generated', new Date().toISOString()],
|
|
[''],
|
|
['Summary Metrics'],
|
|
['Total Events', totalEvents],
|
|
['Auto-Assigned Events', autoAssignedEvents],
|
|
['Automation Rate', `${automationRate}%`],
|
|
['Avg Time to Fill (hours)', avgTimeToFill],
|
|
['Avg Response Time (hours)', avgResponseTime],
|
|
[''],
|
|
['Status Breakdown'],
|
|
['Status', 'Count'],
|
|
...Object.entries(statusBreakdown).map(([status, count]) => [status, count]),
|
|
[''],
|
|
['Efficiency Trend'],
|
|
['Month', 'Automation %', 'Fill Rate %', 'Response Time (hrs)'],
|
|
...efficiencyTrend.map(t => [t.month, t.automation, t.fillRate, t.responseTime]),
|
|
].map(row => row.join(',')).join('\n');
|
|
|
|
const blob = new Blob([csv], { type: 'text/csv' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `operational-efficiency-${new Date().toISOString().split('T')[0]}.csv`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
|
|
toast({ title: "✅ Report Exported", description: "Efficiency report downloaded as CSV" });
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h2 className="text-xl font-bold text-slate-900">Operational Efficiency & Automation Impact</h2>
|
|
<p className="text-sm text-slate-500">Track process improvements and automation effectiveness</p>
|
|
</div>
|
|
<Button onClick={handleExport} variant="outline">
|
|
<Download className="w-4 h-4 mr-2" />
|
|
Export CSV
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Summary Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Automation Rate</p>
|
|
<p className="text-2xl font-bold text-slate-900">{automationRate}%</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
|
|
<Zap className="w-6 h-6 text-purple-600" />
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Avg Time to Fill</p>
|
|
<p className="text-2xl font-bold text-slate-900">{avgTimeToFill}h</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
|
<Clock className="w-6 h-6 text-blue-600" />
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Response Time</p>
|
|
<p className="text-2xl font-bold text-slate-900">{avgResponseTime}h</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
|
|
<TrendingUp className="w-6 h-6 text-green-600" />
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Completed</p>
|
|
<p className="text-2xl font-bold text-slate-900">{events.filter(e => e.status === 'Completed').length}</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-emerald-100 rounded-full flex items-center justify-center">
|
|
<CheckCircle className="w-6 h-6 text-emerald-600" />
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Efficiency Trend */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Efficiency Metrics Over Time</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<BarChart data={efficiencyTrend}>
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|
<XAxis dataKey="month" />
|
|
<YAxis />
|
|
<Tooltip />
|
|
<Legend />
|
|
<Bar dataKey="automation" fill="#a855f7" name="Automation %" />
|
|
<Bar dataKey="fillRate" fill="#3b82f6" name="Fill Rate %" />
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Status Breakdown */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Event Status Distribution</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<PieChart>
|
|
<Pie
|
|
data={statusData}
|
|
cx="50%"
|
|
cy="50%"
|
|
labelLine={false}
|
|
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
|
|
outerRadius={80}
|
|
fill="#8884d8"
|
|
dataKey="value"
|
|
>
|
|
{statusData.map((entry, index) => (
|
|
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
|
))}
|
|
</Pie>
|
|
<Tooltip />
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Key Performance Indicators</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex items-center justify-between p-3 bg-purple-50 rounded-lg">
|
|
<div>
|
|
<p className="text-sm font-medium text-slate-700">Manual Work Reduction</p>
|
|
<p className="text-2xl font-bold text-purple-700">85%</p>
|
|
</div>
|
|
<Badge className="bg-purple-600">Excellent</Badge>
|
|
</div>
|
|
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
|
<div>
|
|
<p className="text-sm font-medium text-slate-700">First-Time Fill Rate</p>
|
|
<p className="text-2xl font-bold text-blue-700">92%</p>
|
|
</div>
|
|
<Badge className="bg-blue-600">Good</Badge>
|
|
</div>
|
|
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
|
<div>
|
|
<p className="text-sm font-medium text-slate-700">Staff Utilization</p>
|
|
<p className="text-2xl font-bold text-green-700">88%</p>
|
|
</div>
|
|
<Badge className="bg-green-600">Optimal</Badge>
|
|
</div>
|
|
<div className="flex items-center justify-between p-3 bg-amber-50 rounded-lg">
|
|
<div>
|
|
<p className="text-sm font-medium text-slate-700">Conflict Detection</p>
|
|
<p className="text-2xl font-bold text-amber-700">97%</p>
|
|
</div>
|
|
<Badge className="bg-amber-600">High</Badge>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |