diff --git a/frontend/src/features/dashboard/components/DashboardScreen.tsx b/frontend/src/features/dashboard/components/DashboardScreen.tsx index a65ac9d..01b76aa 100644 --- a/frontend/src/features/dashboard/components/DashboardScreen.tsx +++ b/frontend/src/features/dashboard/components/DashboardScreen.tsx @@ -10,7 +10,8 @@ import CloseIcon from '@mui/icons-material/Close'; import { SummaryCards, SummaryCardsSkeleton } from './SummaryCards'; import { VehicleAttention, VehicleAttentionSkeleton } from './VehicleAttention'; import { QuickActions, QuickActionsSkeleton } from './QuickActions'; -import { useDashboardSummary, useVehiclesNeedingAttention } from '../hooks/useDashboardData'; +import { RecentActivity, RecentActivitySkeleton } from './RecentActivity'; +import { useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity } 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'; @@ -37,6 +38,7 @@ export const DashboardScreen: React.FC = ({ 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(); // Error state if (summaryError || attentionError) { @@ -72,6 +74,7 @@ export const DashboardScreen: React.FC = ({
+
); @@ -127,6 +130,9 @@ export const DashboardScreen: React.FC = ({ /> )} + {/* Recent Activity */} + {recentActivity && } + {/* Quick Actions */} onNavigate?.('Vehicles'))} diff --git a/frontend/src/features/dashboard/components/RecentActivity.tsx b/frontend/src/features/dashboard/components/RecentActivity.tsx new file mode 100644 index 0000000..51e6b9f --- /dev/null +++ b/frontend/src/features/dashboard/components/RecentActivity.tsx @@ -0,0 +1,118 @@ +/** + * @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 = ({ items }) => { + if (items.length === 0) { + return ( + +

+ Recent Activity +

+

+ No recent activity. Start by logging fuel or scheduling maintenance. +

+
+ ); + } + + return ( + +

+ Recent Activity +

+
+ {items.map((item, index) => ( +
+ + {item.type === 'fuel' ? ( + + ) : ( + + )} + +
+

+ {item.vehicleName} +

+

+ {item.description} +

+
+ + {formatRelativeTime(item.timestamp)} + +
+ ))} +
+
+ ); +}; + +export const RecentActivitySkeleton: React.FC = () => { + return ( + +
+
+ {[1, 2, 3].map((i) => ( +
+
+
+
+
+
+
+ ))} +
+ + ); +}; diff --git a/frontend/src/features/dashboard/hooks/useDashboardData.ts b/frontend/src/features/dashboard/hooks/useDashboardData.ts index d85011a..28dc63a 100644 --- a/frontend/src/features/dashboard/hooks/useDashboardData.ts +++ b/frontend/src/features/dashboard/hooks/useDashboardData.ts @@ -8,8 +8,9 @@ import { useAuth0 } from '@auth0/auth0-react'; import { vehiclesApi } from '../../vehicles/api/vehicles.api'; import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api'; import { maintenanceApi } from '../../maintenance/api/maintenance.api'; -import { DashboardSummary, VehicleNeedingAttention } from '../types'; +import { DashboardSummary, VehicleNeedingAttention, RecentActivityItem } from '../types'; import { MaintenanceSchedule } from '../../maintenance/types/maintenance.types'; +import { getVehicleLabel } from '@/core/utils/vehicleDisplay'; /** * Combined dashboard data structure @@ -17,6 +18,7 @@ import { MaintenanceSchedule } from '../../maintenance/types/maintenance.types'; interface DashboardData { summary: DashboardSummary; vehiclesNeedingAttention: VehicleNeedingAttention[]; + recentActivity: RecentActivityItem[]; } /** @@ -115,7 +117,30 @@ export const useDashboardData = () => { const priorityOrder = { high: 0, medium: 1, low: 2 }; vehiclesNeedingAttention.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]); - return { summary, vehiclesNeedingAttention }; + // Build recent activity feed + const vehicleMap = new Map(vehicles.map(v => [v.id, v])); + + const fuelActivity: RecentActivityItem[] = recentFuelLogs.map(log => ({ + type: 'fuel' as const, + vehicleId: log.vehicleId, + vehicleName: getVehicleLabel(vehicleMap.get(log.vehicleId)), + description: `Filled ${log.fuelUnits.toFixed(1)} gal at $${log.costPerUnit.toFixed(2)}/gal`, + timestamp: log.dateTime, + })); + + const maintenanceActivity: RecentActivityItem[] = upcomingMaintenance.map(schedule => ({ + type: 'maintenance' as const, + vehicleId: schedule.vehicleId, + vehicleName: getVehicleLabel(vehicleMap.get(schedule.vehicleId)), + description: `${schedule.category.replace(/_/g, ' ')} due${schedule.nextDueDate ? ` ${new Date(schedule.nextDueDate).toLocaleDateString()}` : ''}`, + timestamp: schedule.nextDueDate || now.toISOString(), + })); + + const recentActivity = [...fuelActivity, ...maintenanceActivity] + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + .slice(0, 7); + + return { summary, vehiclesNeedingAttention, recentActivity }; }, enabled: isAuthenticated && !authLoading, staleTime: 2 * 60 * 1000, // 2 minutes @@ -146,6 +171,21 @@ export const useDashboardSummary = () => { }; }; +/** + * Hook to fetch recent activity feed + * Derives from unified dashboard data query + */ +export const useRecentActivity = () => { + const { data, isLoading, error, refetch } = useDashboardData(); + + return { + data: data?.recentActivity, + isLoading, + error, + refetch, + }; +}; + /** * Hook to fetch vehicles needing attention (overdue maintenance) * Derives from unified dashboard data query diff --git a/frontend/src/features/dashboard/index.ts b/frontend/src/features/dashboard/index.ts index 09b2fde..cac9ef7 100644 --- a/frontend/src/features/dashboard/index.ts +++ b/frontend/src/features/dashboard/index.ts @@ -7,5 +7,6 @@ export { DashboardPage } from './pages/DashboardPage'; export { SummaryCards, SummaryCardsSkeleton } from './components/SummaryCards'; export { VehicleAttention, VehicleAttentionSkeleton } from './components/VehicleAttention'; export { QuickActions, QuickActionsSkeleton } from './components/QuickActions'; -export { useDashboardSummary, useVehiclesNeedingAttention } from './hooks/useDashboardData'; -export type { DashboardSummary, VehicleNeedingAttention, DashboardData } from './types'; +export { RecentActivity, RecentActivitySkeleton } from './components/RecentActivity'; +export { useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity } from './hooks/useDashboardData'; +export type { DashboardSummary, VehicleNeedingAttention, RecentActivityItem, DashboardData } from './types'; diff --git a/frontend/src/features/dashboard/types/index.ts b/frontend/src/features/dashboard/types/index.ts index 2b87066..0992627 100644 --- a/frontend/src/features/dashboard/types/index.ts +++ b/frontend/src/features/dashboard/types/index.ts @@ -15,7 +15,16 @@ export interface VehicleNeedingAttention extends Vehicle { priority: 'high' | 'medium' | 'low'; } +export interface RecentActivityItem { + type: 'fuel' | 'maintenance'; + vehicleId: string; + vehicleName: string; + description: string; + timestamp: string; +} + export interface DashboardData { summary: DashboardSummary; vehiclesNeedingAttention: VehicleNeedingAttention[]; + recentActivity: RecentActivityItem[]; }