feat: rewire DashboardScreen with vehicle roster layout (refs #200)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-15 10:53:35 -06:00
parent 767df9e9f2
commit 654a7f0fc3
8 changed files with 73 additions and 639 deletions

View File

@@ -1,47 +1,72 @@
/**
* @ai-summary Main dashboard screen component showing fleet overview
* @ai-summary Main dashboard screen showing vehicle fleet roster with health indicators
*/
import React, { useState } from 'react';
import { Box, Dialog, DialogTitle, DialogContent, IconButton, useMediaQuery, useTheme } from '@mui/material';
import { Box, Dialog, DialogTitle, DialogContent, IconButton, Skeleton, Typography, useMediaQuery, useTheme } from '@mui/material';
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded';
import DirectionsCarRoundedIcon from '@mui/icons-material/DirectionsCarRounded';
import CloseIcon from '@mui/icons-material/Close';
import { SummaryCards, SummaryCardsSkeleton } from './SummaryCards';
import { VehicleAttention, VehicleAttentionSkeleton } from './VehicleAttention';
import { QuickActions, QuickActionsSkeleton } from './QuickActions';
import { RecentActivity, RecentActivitySkeleton } from './RecentActivity';
import { useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity } from '../hooks/useDashboardData';
import { VehicleRosterCard } from './VehicleRosterCard';
import { ActionBar } from './ActionBar';
import { useVehicleRoster } from '../hooks/useDashboardData';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { Button } from '../../../shared-minimal/components/Button';
import { PendingAssociationBanner } from '../../email-ingestion/components/PendingAssociationBanner';
import { PendingAssociationList } from '../../email-ingestion/components/PendingAssociationList';
import { MobileScreen } from '../../../core/store';
import { Vehicle } from '../../vehicles/types/vehicles.types';
interface DashboardScreenProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- matches navigation store type signature
onNavigate?: (screen: MobileScreen, metadata?: Record<string, any>) => void;
onVehicleClick?: (vehicle: Vehicle) => void;
onViewMaintenance?: () => void;
onAddVehicle?: () => void;
}
const RosterSkeleton: React.FC = () => (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{[0, 1, 2, 3].map(i => (
<GlassCard key={i}>
<div className="flex items-center gap-3 mb-3">
<Skeleton variant="circular" width={48} height={48} />
<div className="flex-1">
<Skeleton variant="text" width="60%" />
</div>
<Skeleton variant="circular" width={12} height={12} />
</div>
<div className="mb-3 space-y-1">
<Skeleton variant="text" width="80%" />
<Skeleton variant="text" width="80%" />
</div>
<Skeleton variant="text" width="30%" />
</GlassCard>
))}
</div>
);
export const DashboardScreen: React.FC<DashboardScreenProps> = ({
onNavigate,
onVehicleClick,
onViewMaintenance,
onAddVehicle
onAddVehicle,
}) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down('sm'));
const [showPendingReceipts, setShowPendingReceipts] = useState(false);
const { data: summary, isLoading: summaryLoading, error: summaryError } = useDashboardSummary();
const { data: vehiclesNeedingAttention, isLoading: attentionLoading, error: attentionError } = useVehiclesNeedingAttention();
const { data: recentActivity } = useRecentActivity();
const { data: roster, vehicles, isLoading, error } = useVehicleRoster();
const handleAddVehicle = onAddVehicle ?? (() => onNavigate?.('Vehicles'));
const handleLogFuel = () => onNavigate?.('Log Fuel');
const handleVehicleClick = (vehicleId: string) => {
const vehicle = vehicles?.find(v => v.id === vehicleId);
if (vehicle && onVehicleClick) {
onVehicleClick(vehicle);
}
};
// Error state
if (summaryError || attentionError) {
if (error) {
return (
<div className="space-y-4">
<GlassCard>
@@ -69,19 +94,21 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
}
// Loading state
if (summaryLoading || attentionLoading || !summary || !vehiclesNeedingAttention) {
if (isLoading || !roster) {
return (
<div className="space-y-6">
<SummaryCardsSkeleton />
<VehicleAttentionSkeleton />
<RecentActivitySkeleton />
<QuickActionsSkeleton />
<div className="flex items-center justify-between">
<Typography variant="h4" sx={{ fontWeight: 700, color: 'text.primary' }}>
Your Fleet
</Typography>
</div>
<RosterSkeleton />
</div>
);
}
// Empty state - no vehicles
if (summary.totalVehicles === 0) {
if (roster.length === 0) {
return (
<div className="space-y-6">
<GlassCard>
@@ -98,7 +125,7 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
<Button
variant="primary"
size="lg"
onClick={onAddVehicle ?? (() => onNavigate?.('Vehicles'))}
onClick={handleAddVehicle}
>
Add Your First Vehicle
</Button>
@@ -114,32 +141,24 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
{/* Pending Receipts Banner */}
<PendingAssociationBanner onViewPending={() => setShowPendingReceipts(true)} />
{/* Summary Cards */}
<SummaryCards summary={summary} onNavigate={onNavigate} />
{/* Heading + Action Bar */}
<div className="flex items-center justify-between">
<Typography variant="h4" sx={{ fontWeight: 700, color: 'text.primary' }}>
Your Fleet
</Typography>
<ActionBar onAddVehicle={handleAddVehicle} onLogFuel={handleLogFuel} />
</div>
{/* Vehicles Needing Attention */}
{vehiclesNeedingAttention && vehiclesNeedingAttention.length > 0 && (
<VehicleAttention
vehicles={vehiclesNeedingAttention}
onVehicleClick={(vehicleId) => {
const vehicle = vehiclesNeedingAttention.find(v => v.id === vehicleId);
if (vehicle && onVehicleClick) {
onVehicleClick(vehicle);
}
}}
/>
)}
{/* Recent Activity */}
{recentActivity && <RecentActivity items={recentActivity} />}
{/* Quick Actions */}
<QuickActions
onAddVehicle={onAddVehicle ?? (() => onNavigate?.('Vehicles'))}
onLogFuel={() => onNavigate?.('Log Fuel')}
onViewMaintenance={onViewMaintenance ?? (() => onNavigate?.('Vehicles'))}
onViewVehicles={() => onNavigate?.('Vehicles')}
/>
{/* Vehicle Roster Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{roster.map(rosterData => (
<VehicleRosterCard
key={rosterData.vehicle.id}
data={rosterData}
onClick={handleVehicleClick}
/>
))}
</div>
{/* Pending Receipts Dialog */}
<Dialog

View File

@@ -1,167 +0,0 @@
/**
* @ai-summary Quick action buttons for common tasks
*/
import React from 'react';
import { Box, SvgIconProps } from '@mui/material';
import DirectionsCarRoundedIcon from '@mui/icons-material/DirectionsCarRounded';
import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRounded';
import BuildRoundedIcon from '@mui/icons-material/BuildRounded';
import FormatListBulletedRoundedIcon from '@mui/icons-material/FormatListBulletedRounded';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
interface QuickAction {
id: string;
title: string;
description: string;
icon: React.ComponentType<SvgIconProps>;
onClick: () => void;
}
interface QuickActionsProps {
onAddVehicle: () => void;
onLogFuel: () => void;
onViewMaintenance: () => void;
onViewVehicles: () => void;
}
export const QuickActions: React.FC<QuickActionsProps> = ({
onAddVehicle,
onLogFuel,
onViewMaintenance,
onViewVehicles,
}) => {
const actions: QuickAction[] = [
{
id: 'add-vehicle',
title: 'Add Vehicle',
description: 'Register a new vehicle',
icon: DirectionsCarRoundedIcon,
onClick: onAddVehicle,
},
{
id: 'log-fuel',
title: 'Log Fuel',
description: 'Record a fuel purchase',
icon: LocalGasStationRoundedIcon,
onClick: onLogFuel,
},
{
id: 'view-maintenance',
title: 'Maintenance',
description: 'View maintenance records',
icon: BuildRoundedIcon,
onClick: onViewMaintenance,
},
{
id: 'view-vehicles',
title: 'My Vehicles',
description: 'View all vehicles',
icon: FormatListBulletedRoundedIcon,
onClick: onViewVehicles,
},
];
return (
<GlassCard padding="md">
<div className="mb-4">
<h3 className="text-lg font-semibold text-slate-800 dark:text-avus">
Quick Actions
</h3>
<p className="text-sm text-slate-500 dark:text-titanio">
Common tasks and navigation
</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{actions.map((action) => {
const IconComponent = action.icon;
return (
<Box
key={action.id}
component="button"
onClick={action.onClick}
sx={{
p: 2,
borderRadius: 1.5,
bgcolor: 'action.hover',
border: '1px solid transparent',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
textAlign: 'left',
minHeight: { xs: 100, sm: 120 },
transition: 'all 0.2s',
cursor: 'pointer',
'&:hover': {
bgcolor: 'action.selected',
borderColor: 'divider',
},
'&:focus': {
outline: 'none',
borderColor: 'primary.main',
},
}}
>
<Box
sx={{
color: 'primary.main',
mb: 1.5,
}}
>
<IconComponent sx={{ fontSize: 28 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Box
component="span"
sx={{
display: 'block',
fontWeight: 600,
fontSize: '0.875rem',
color: 'text.primary',
mb: 0.5,
}}
>
{action.title}
</Box>
<Box
component="span"
sx={{
display: { xs: 'none', sm: 'block' },
fontSize: '0.75rem',
color: 'text.secondary',
}}
>
{action.description}
</Box>
</Box>
</Box>
);
})}
</div>
</GlassCard>
);
};
export const QuickActionsSkeleton: 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="grid grid-cols-2 sm:grid-cols-4 gap-3">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className="p-4 rounded-xl bg-slate-50 dark:bg-slate-800 min-h-[100px] sm:min-h-[120px]"
>
<div className="w-7 h-7 bg-slate-100 dark:bg-slate-700 rounded animate-pulse mb-3" />
<div className="h-4 bg-slate-100 dark:bg-slate-700 rounded animate-pulse w-20 mb-2" />
<div className="h-3 bg-slate-100 dark:bg-slate-700 rounded animate-pulse w-full hidden sm:block" />
</div>
))}
</div>
</GlassCard>
);
};

View File

@@ -1,118 +0,0 @@
/**
* @ai-summary Recent activity feed showing latest fuel logs and maintenance events
*/
import React from 'react';
import { Box } from '@mui/material';
import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRounded';
import BuildRoundedIcon from '@mui/icons-material/BuildRounded';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { RecentActivityItem } from '../types';
interface RecentActivityProps {
items: RecentActivityItem[];
}
const formatRelativeTime = (timestamp: string): string => {
const now = new Date();
const date = new Date(timestamp);
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 0) {
// Future date (upcoming maintenance)
const absDays = Math.abs(diffDays);
if (absDays === 0) return 'Today';
if (absDays === 1) return 'Tomorrow';
return `In ${absDays} days`;
}
if (diffDays === 0) {
if (diffHours === 0) return diffMins <= 1 ? 'Just now' : `${diffMins}m ago`;
return `${diffHours}h ago`;
}
if (diffDays === 1) return 'Yesterday';
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
};
export const RecentActivity: React.FC<RecentActivityProps> = ({ items }) => {
if (items.length === 0) {
return (
<GlassCard padding="md">
<h3 className="text-base font-semibold text-slate-800 dark:text-avus mb-3">
Recent Activity
</h3>
<p className="text-sm text-slate-400 dark:text-canna text-center py-4">
No recent activity. Start by logging fuel or scheduling maintenance.
</p>
</GlassCard>
);
}
return (
<GlassCard padding="md">
<h3 className="text-base font-semibold text-slate-800 dark:text-avus mb-3">
Recent Activity
</h3>
<div className="space-y-1">
{items.map((item, index) => (
<div
key={`${item.type}-${item.timestamp}-${index}`}
className="flex items-start gap-3 py-2"
>
<Box
sx={{
flexShrink: 0,
width: 32,
height: 32,
borderRadius: 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'action.hover',
}}
>
{item.type === 'fuel' ? (
<LocalGasStationRoundedIcon sx={{ fontSize: 18, color: 'primary.main' }} />
) : (
<BuildRoundedIcon sx={{ fontSize: 18, color: 'primary.main' }} />
)}
</Box>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-slate-800 dark:text-avus truncate">
{item.vehicleName}
</p>
<p className="text-xs text-slate-500 dark:text-titanio truncate">
{item.description}
</p>
</div>
<span className="text-xs text-slate-400 dark:text-canna whitespace-nowrap flex-shrink-0">
{formatRelativeTime(item.timestamp)}
</span>
</div>
))}
</div>
</GlassCard>
);
};
export const RecentActivitySkeleton: React.FC = () => {
return (
<GlassCard padding="md">
<div className="h-5 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-32 mb-3" />
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-start gap-3">
<div className="flex-shrink-0 w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 animate-pulse" />
<div className="flex-1 space-y-1.5">
<div className="h-4 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-24" />
<div className="h-3 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-40" />
</div>
</div>
))}
</div>
</GlassCard>
);
};

View File

@@ -1,134 +0,0 @@
/**
* @ai-summary Summary cards showing key dashboard metrics
*/
import React from 'react';
import { Box } from '@mui/material';
import DirectionsCarRoundedIcon from '@mui/icons-material/DirectionsCarRounded';
import BuildRoundedIcon from '@mui/icons-material/BuildRounded';
import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRounded';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { DashboardSummary } from '../types';
import { MobileScreen } from '../../../core/store';
interface SummaryCardsProps {
summary: DashboardSummary;
onNavigate?: (screen: MobileScreen) => void;
}
export const SummaryCards: React.FC<SummaryCardsProps> = ({ summary, onNavigate }) => {
const cards = [
{
title: 'Total Vehicles',
value: summary.totalVehicles,
icon: DirectionsCarRoundedIcon,
color: 'primary.main',
ctaText: 'Add a vehicle',
ctaScreen: 'Vehicles' as MobileScreen,
},
{
title: 'Upcoming Maintenance',
value: summary.upcomingMaintenanceCount,
subtitle: 'Next 30 days',
icon: BuildRoundedIcon,
color: 'primary.main',
ctaText: 'Schedule maintenance',
ctaScreen: 'Maintenance' as MobileScreen,
},
{
title: 'Recent Fuel Logs',
value: summary.recentFuelLogsCount,
subtitle: 'Last 7 days',
icon: LocalGasStationRoundedIcon,
color: 'primary.main',
ctaText: 'Log your first fill-up',
ctaScreen: 'Log Fuel' as MobileScreen,
},
];
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{cards.map((card) => {
const IconComponent = card.icon;
return (
<GlassCard key={card.title} padding="md">
<div className="flex items-start gap-3">
<Box
sx={{
flexShrink: 0,
width: 48,
height: 48,
borderRadius: 3,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'action.hover',
}}
>
<IconComponent sx={{ fontSize: 24, color: card.color }} />
</Box>
<div className="flex-1 min-w-0">
<p className="text-sm text-slate-500 dark:text-titanio font-medium mb-1">
{card.title}
</p>
<Box
component="p"
sx={{
fontSize: '1.875rem',
fontWeight: 700,
color: 'text.primary',
lineHeight: 1.2,
}}
>
{card.value}
</Box>
{card.value === 0 && card.ctaText ? (
<Box
component="button"
onClick={() => onNavigate?.(card.ctaScreen)}
sx={{
background: 'none',
border: 'none',
padding: 0,
cursor: 'pointer',
color: 'primary.main',
fontSize: '0.75rem',
fontWeight: 500,
mt: 0.5,
'&:hover': { textDecoration: 'underline' },
}}
>
{card.ctaText}
</Box>
) : card.subtitle ? (
<p className="text-xs text-slate-400 dark:text-canna mt-1">
{card.subtitle}
</p>
) : null}
</div>
</div>
</GlassCard>
);
})}
</div>
);
};
export const SummaryCardsSkeleton: React.FC = () => {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{[1, 2, 3].map((i) => (
<GlassCard key={i} padding="md">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-slate-100 dark:bg-slate-800 animate-pulse" />
<div className="flex-1 min-w-0 space-y-2">
<div className="h-4 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-24" />
<div className="h-8 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-16" />
<div className="h-3 bg-slate-100 dark:bg-slate-800 rounded animate-pulse w-20" />
</div>
</div>
</GlassCard>
))}
</div>
);
};

View File

@@ -1,162 +0,0 @@
/**
* @ai-summary List of vehicles needing attention (overdue maintenance)
*/
import React from 'react';
import { Box, SvgIconProps } from '@mui/material';
import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';
import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded';
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded';
import ScheduleRoundedIcon from '@mui/icons-material/ScheduleRounded';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
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">
<Box sx={{ color: 'success.main', mb: 1.5 }}>
<CheckCircleRoundedIcon sx={{ fontSize: 48 }} />
</Box>
<h3 className="text-lg font-semibold text-slate-800 dark:text-avus mb-2">
All Caught Up!
</h3>
<p className="text-sm text-slate-500 dark:text-titanio">
No vehicles need immediate attention
</p>
</div>
</GlassCard>
);
}
const priorityConfig: Record<string, { color: string; icon: React.ComponentType<SvgIconProps> }> = {
high: {
color: 'error.main',
icon: ErrorRoundedIcon,
},
medium: {
color: 'warning.main',
icon: WarningAmberRoundedIcon,
},
low: {
color: 'info.main',
icon: ScheduleRoundedIcon,
},
};
return (
<GlassCard padding="md">
<div className="mb-4">
<h3 className="text-lg font-semibold text-slate-800 dark:text-avus">
Needs Attention
</h3>
<p className="text-sm text-slate-500 dark:text-titanio">
Vehicles with overdue maintenance
</p>
</div>
<div className="space-y-3">
{vehicles.map((vehicle) => {
const config = priorityConfig[vehicle.priority];
const IconComponent = config.icon;
return (
<Box
key={vehicle.id}
onClick={() => onVehicleClick?.(vehicle.id)}
role={onVehicleClick ? 'button' : undefined}
tabIndex={onVehicleClick ? 0 : undefined}
onKeyDown={(e: React.KeyboardEvent) => {
if (onVehicleClick && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
onVehicleClick(vehicle.id);
}
}}
sx={{
p: 2,
borderRadius: 3,
bgcolor: 'action.hover',
border: '1px solid',
borderColor: 'divider',
cursor: onVehicleClick ? 'pointer' : 'default',
transition: 'all 0.2s',
'&:hover': onVehicleClick ? {
bgcolor: 'action.selected',
} : {},
}}
>
<div className="flex items-start gap-3">
<Box sx={{ flexShrink: 0, color: config.color }}>
<IconComponent sx={{ fontSize: 24 }} />
</Box>
<div className="flex-1 min-w-0">
<Box
component="h4"
sx={{
fontWeight: 600,
color: 'text.primary',
fontSize: '1rem',
mb: 0.5,
}}
>
{getVehicleLabel(vehicle)}
</Box>
<p className="text-sm text-slate-600 dark:text-titanio">
{vehicle.reason}
</p>
<Box
component="span"
sx={{
display: 'inline-block',
mt: 1,
px: 1.5,
py: 0.5,
borderRadius: 2,
fontSize: '0.75rem',
fontWeight: 500,
bgcolor: 'action.selected',
color: config.color,
}}
>
{vehicle.priority.toUpperCase()} PRIORITY
</Box>
</div>
</div>
</Box>
);
})}
</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-xl 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>
);
};

View File

@@ -144,8 +144,9 @@ export const useDashboardData = () => {
enabled: isAuthenticated && !authLoading,
staleTime: 2 * 60 * 1000,
gcTime: 5 * 60 * 1000,
retry: (failureCount, error: any) => {
if (error?.response?.status === 401 && failureCount < 3) {
retry: (failureCount, error: unknown) => {
const status = (error as { response?: { status?: number } })?.response?.status;
if (status === 401 && failureCount < 3) {
return true;
}
return false;

View File

@@ -4,9 +4,7 @@
export { DashboardScreen } from './components/DashboardScreen';
export { DashboardPage } from './pages/DashboardPage';
export { SummaryCards, SummaryCardsSkeleton } from './components/SummaryCards';
export { VehicleAttention, VehicleAttentionSkeleton } from './components/VehicleAttention';
export { QuickActions, QuickActionsSkeleton } from './components/QuickActions';
export { RecentActivity, RecentActivitySkeleton } from './components/RecentActivity';
export { useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity } from './hooks/useDashboardData';
export type { DashboardSummary, VehicleNeedingAttention, RecentActivityItem, DashboardData } from './types';
export { VehicleRosterCard } from './components/VehicleRosterCard';
export { ActionBar } from './components/ActionBar';
export { useVehicleRoster } from './hooks/useDashboardData';
export type { VehicleHealth, AttentionItem, VehicleRosterData } from './types';

View File

@@ -4,7 +4,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Typography } from '@mui/material';
import { Box } from '@mui/material';
import { DashboardScreen } from '../components/DashboardScreen';
import { MobileScreen } from '../../../core/store';
import { Vehicle } from '../../vehicles/types/vehicles.types';
@@ -49,9 +49,6 @@ export const DashboardPage: React.FC = () => {
return (
<Box sx={{ py: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, color: 'text.primary', mb: 4 }}>
Dashboard
</Typography>
<DashboardScreen
onNavigate={handleNavigate}
onVehicleClick={handleVehicleClick}