feat: add dashboard with vehicle fleet overview (refs #2)

Implements responsive dashboard showing:
- Summary cards (vehicle count, upcoming maintenance, recent fuel logs)
- Vehicles needing attention with priority highlighting
- Quick action buttons for navigation
- Loading skeletons and empty states
- Mobile-first responsive layout (320px to 1920px+)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-01 22:35:48 -06:00
parent 0b16b8307f
commit bcb39b9cda
8 changed files with 644 additions and 13 deletions

View File

@@ -0,0 +1,131 @@
/**
* @ai-summary List of vehicles needing attention (overdue maintenance)
*/
import React from 'react';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { VehicleNeedingAttention } from '../types';
interface VehicleAttentionProps {
vehicles: VehicleNeedingAttention[];
onVehicleClick?: (vehicleId: string) => void;
}
export const VehicleAttention: React.FC<VehicleAttentionProps> = ({ vehicles, onVehicleClick }) => {
if (vehicles.length === 0) {
return (
<GlassCard padding="md">
<div className="text-center py-8">
<div className="text-4xl mb-3"></div>
<h3 className="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-2">
All Caught Up!
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400">
No vehicles need immediate attention
</p>
</div>
</GlassCard>
);
}
const priorityConfig = {
high: {
color: 'text-red-600',
bgColor: 'bg-red-50',
borderColor: 'border-red-200',
icon: '🚨',
},
medium: {
color: 'text-orange-600',
bgColor: 'bg-orange-50',
borderColor: 'border-orange-200',
icon: '⚠️',
},
low: {
color: 'text-yellow-600',
bgColor: 'bg-yellow-50',
borderColor: 'border-yellow-200',
icon: '⏰',
},
};
return (
<GlassCard padding="md">
<div className="mb-4">
<h3 className="text-lg font-semibold text-slate-800 dark:text-slate-200">
Needs Attention
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400">
Vehicles with overdue maintenance
</p>
</div>
<div className="space-y-3">
{vehicles.map((vehicle) => {
const config = priorityConfig[vehicle.priority];
return (
<div
key={vehicle.id}
className={`p-4 rounded-2xl border ${config.borderColor} ${config.bgColor} dark:bg-opacity-10 ${
onVehicleClick ? 'cursor-pointer hover:shadow-md transition-shadow' : ''
}`}
onClick={() => onVehicleClick?.(vehicle.id)}
role={onVehicleClick ? 'button' : undefined}
tabIndex={onVehicleClick ? 0 : undefined}
onKeyDown={(e) => {
if (onVehicleClick && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
onVehicleClick(vehicle.id);
}
}}
>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 text-2xl">
{config.icon}
</div>
<div className="flex-1 min-w-0">
<h4 className={`font-semibold ${config.color} dark:opacity-90 text-base mb-1`}>
{vehicle.nickname || `${vehicle.year} ${vehicle.make} ${vehicle.model}`}
</h4>
<p className="text-sm text-slate-600 dark:text-slate-400">
{vehicle.reason}
</p>
<div className="flex items-center gap-2 mt-2">
<span className={`text-xs font-medium px-2 py-1 rounded-full ${config.bgColor} ${config.color} dark:bg-opacity-20`}>
{vehicle.priority.toUpperCase()} PRIORITY
</span>
</div>
</div>
</div>
</div>
);
})}
</div>
</GlassCard>
);
};
export const VehicleAttentionSkeleton: React.FC = () => {
return (
<GlassCard padding="md">
<div className="mb-4">
<div className="h-6 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-32 mb-2" />
<div className="h-4 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-48" />
</div>
<div className="space-y-3">
{[1, 2].map((i) => (
<div key={i} className="p-4 rounded-2xl bg-slate-50 dark:bg-slate-800">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-6 h-6 bg-slate-100 dark:bg-slate-700 rounded animate-pulse" />
<div className="flex-1 space-y-2">
<div className="h-5 bg-slate-100 dark:bg-slate-700 rounded animate-pulse w-3/4" />
<div className="h-4 bg-slate-100 dark:bg-slate-700 rounded animate-pulse w-1/2" />
<div className="h-6 bg-slate-100 dark:bg-slate-700 rounded animate-pulse w-24 mt-2" />
</div>
</div>
</div>
))}
</div>
</GlassCard>
);
};