From 9d3efe7f15c30ff90e0236447aa69d58a5de7d38 Mon Sep 17 00:00:00 2001 From: natib21 Date: Sat, 4 Jul 2026 03:47:36 +0000 Subject: [PATCH] fix --- .../components/ContainerAllocationTable.tsx | 11 +- .../gl-actions/TransportDocumentCard.tsx | 2 +- .../backoffice/src/constants/URLS.ts | 1 - .../src/pages/bookings/BookingDetailPage.tsx | 10 +- .../pages/contracts/GlClearanceDetailPage.tsx | 2 +- .../src/pages/fleet/DriverDetailPage.tsx | 2 +- .../src/pages/fleet/FinancialReportsPage.tsx | 62 +++-- .../src/pages/fleet/FleetDashboard.tsx | 45 ++-- .../src/pages/fleet/FleetResourcePage.tsx | 2 +- .../src/pages/fleet/FuelPurchasePage.tsx | 54 ++-- .../src/pages/fleet/FuelStatsPage.tsx | 33 ++- .../src/pages/fleet/MaintenancePage.tsx | 235 +++++++++++------- .../backoffice/src/pages/fleet/RoutesPage.tsx | 1 - .../src/pages/fleet/TrackingPage.tsx | 51 ++-- .../src/pages/fleet/VehicleDetailPage.tsx | 2 +- .../src/pages/fleet/config/resources.ts | 2 +- .../src/pages/ruleEngine/CargoTypesPage.tsx | 1 - .../src/services/bookings.service.ts | 5 - .../src/services/vehicles.service.ts | 3 + 19 files changed, 304 insertions(+), 220 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/components/ContainerAllocationTable.tsx b/apps/edr-freight-web/backoffice/src/components/ContainerAllocationTable.tsx index 8971d4ccb..67407486f 100644 --- a/apps/edr-freight-web/backoffice/src/components/ContainerAllocationTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/ContainerAllocationTable.tsx @@ -33,7 +33,6 @@ export interface ContainerAllocationTableProps { * Displays containers with type/qty, vehicle dropdown per row, and save action. */ export function ContainerAllocationTable({ - bookingId, containers, onSave, }: ContainerAllocationTableProps) { @@ -43,7 +42,10 @@ export function ContainerAllocationTable({ const { data: vehicles = [], isLoading: vehiclesLoading } = useQuery({ queryKey: ["vehicles", "free"], - queryFn: () => vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }), + queryFn: async () => { + const res = await vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }); + return res.data ?? []; + }, }); const vehicleOptions = useMemo( @@ -85,13 +87,12 @@ export function ContainerAllocationTable({ }); const allocatedCount = Object.values(allocations).filter(Boolean).length; - const allAllocated = allocatedCount === containers.length; if (vehiclesLoading) { return ( - + - + ); } diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/gl-actions/TransportDocumentCard.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/gl-actions/TransportDocumentCard.tsx index 52bcfef98..1ef5d1139 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/gl-actions/TransportDocumentCard.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/gl-actions/TransportDocumentCard.tsx @@ -32,7 +32,7 @@ export function TransportDocumentCard({ bookingId }: { bookingId: string }) { if (!file) return; setLoading(true); try { - await contractsService.uploadTransportDocument(bookingId, file); + await contractsService.uploadTransportDocument(bookingId, { transportDocument: file }); toast.success("Transport document uploaded"); } catch (e) { toast.error(e instanceof Error ? e.message : "Upload failed"); diff --git a/apps/edr-freight-web/backoffice/src/constants/URLS.ts b/apps/edr-freight-web/backoffice/src/constants/URLS.ts index d0f09f508..e1e379bf0 100644 --- a/apps/edr-freight-web/backoffice/src/constants/URLS.ts +++ b/apps/edr-freight-web/backoffice/src/constants/URLS.ts @@ -282,7 +282,6 @@ export const URL_CONSTANTS = { `/train-scheduling/schedules/${id}/run-allocation`, DOC_REVIEW_COMPLETE: (id: string) => `/train-scheduling/schedules/${id}/doc-review-complete`, - RUN_ALLOCATION: (id: string) => `/train-scheduling/schedules/${id}/run-allocation`, ASSIGN_UNASSIGNED_BOOKING: (id: string) => `/train-scheduling/schedules/${id}/assign-unassigned-booking`, BOOKING_WINDOW: (id: string) => diff --git a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx index d49e24f95..7408b9aca 100644 --- a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx @@ -18,8 +18,8 @@ import { type BookingDetailView, } from "@/components/bookings/detail"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; -import ContainerAllocationTable from "@/components/ContainerAllocationTable"; -import { api } from "@/services/api"; +import { ContainerAllocationTable } from "@/components/ContainerAllocationTable"; +import { api } from "@/auth/http"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; const BookingDetailPage = () => { @@ -160,9 +160,9 @@ const BookingDetailPage = () => { type: c.containerType?.label ?? "Unknown", qty: c.quantity, }))} - onSave={(allocations) => - allocateMutation.mutateAsync({ allocations }) - } + onSave={async (allocations) => { + await allocateMutation.mutateAsync({ allocations }); + }} /> - + {data.kind === "booking" ? ( diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/DriverDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/DriverDetailPage.tsx index dfdf822e4..d5e8655be 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/DriverDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/DriverDetailPage.tsx @@ -70,7 +70,7 @@ const DriverDetailPage = () => { driver?.licenseExpiryDate && new Date(driver.licenseExpiryDate) < new Date(); return ( - + navigate("/dashboard/drivers")} aria-label="Back"> diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FinancialReportsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FinancialReportsPage.tsx index 73a7456af..a62c4cbc5 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FinancialReportsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FinancialReportsPage.tsx @@ -1,26 +1,10 @@ import { useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { Card, Button, Stack, Group, Grid, Select, Text, ThemeIcon, RingProgress, Container } from '@mantine/core'; +import { Card, Stack, Group, Grid, Select, Text, RingProgress, Container, Title } from '@mantine/core'; +import Breadcrumbs from '@/components/ui/Breadcrumbs'; import { QUERY_KEYS } from '@/constants/QUERY_KEYS'; -import { api } from '@/services/api'; +import { api } from '@/auth/http'; import { vehiclesService } from '@/services/vehicles.service'; -import { freightBrand } from '@/theme/freight-brand'; - -interface FuelStats { - vehicleId: string; - totalPurchases: number; - totalFuel: number; - totalCost: number; - averageCostPerLiter: number; -} - -interface MaintenanceStats { - vehicleId: string; - totalCost: number; - numberOfMaintenanceItems: number; - averageCostPerMaintenance: number; - costByType: Record; -} interface CombinedReport { vehicleId: string; @@ -31,6 +15,9 @@ interface CombinedReport { maintenancePercentage: number; } +const etb = (n: number) => + 'ETB ' + Number(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + export function FinancialReportsPage() { const [selectedVehicle, setSelectedVehicle] = useState(null); const [months, setMonths] = useState('12'); @@ -45,13 +32,21 @@ export function FinancialReportsPage() { const { data: fuelStats } = useQuery({ queryKey: QUERY_KEYS.FUEL.stats(selectedVehicle || ''), - queryFn: () => selectedVehicle ? api.get(`/fuel/stats/${selectedVehicle}?months=${months}`) : Promise.resolve(null), + queryFn: async () => { + if (!selectedVehicle) return Promise.resolve(null); + const res = await api.get(`/fuel/stats/${selectedVehicle}?months=${months}`); + return res.data; + }, enabled: !!selectedVehicle, }); const { data: maintenanceStats } = useQuery({ queryKey: QUERY_KEYS.MAINTENANCE.stats(selectedVehicle || ''), - queryFn: () => selectedVehicle ? api.get(`/maintenance/stats/${selectedVehicle}`) : Promise.resolve(null), + queryFn: async () => { + if (!selectedVehicle) return Promise.resolve(null); + const res = await api.get(`/maintenance/stats/${selectedVehicle}`); + return res.data; + }, enabled: !!selectedVehicle, }); @@ -60,11 +55,11 @@ export function FinancialReportsPage() { [vehicles] ); - const report = useMemo(() => { - if (!fuelStats || !maintenanceStats) return null; + const report = useMemo(() => { + if (!fuelStats && !maintenanceStats) return null; - const fuelCost = Number(fuelStats.totalCost) || 0; - const maintenanceCost = Number(maintenanceStats.totalCost) || 0; + const fuelCost = Number(fuelStats?.totalCost ?? 0) || 0; + const maintenanceCost = Number(maintenanceStats?.totalCost ?? 0) || 0; const total = fuelCost + maintenanceCost; return { @@ -93,6 +88,9 @@ export function FinancialReportsPage() { return ( + + Financial Reports + Fleet Financial Analysis @@ -126,13 +124,13 @@ export function FinancialReportsPage() { <> - + - + - + @@ -141,7 +139,7 @@ export function FinancialReportsPage() { Monthly Avg - ${(report.totalOperatingCost / parseInt(months)).toFixed(2)} + {etb(report.totalOperatingCost / parseInt(months))} @@ -166,7 +164,7 @@ export function FinancialReportsPage() { + {report.fuelPercentage}% } @@ -184,7 +182,7 @@ export function FinancialReportsPage() { + {report.maintenancePercentage}% } @@ -228,7 +226,7 @@ export function FinancialReportsPage() { Avg Maintenance Cost - ${maintenanceStats?.averageCostPerMaintenance?.toFixed(2) || '0.00'} + {etb(maintenanceStats?.averageCostPerMaintenance ?? 0)} diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FleetDashboard.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FleetDashboard.tsx index a311a16eb..32341505a 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FleetDashboard.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FleetDashboard.tsx @@ -1,7 +1,8 @@ import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { Card, Stack, Group, Grid, Text, ThemeIcon, Progress, Badge, Table, RingProgress, Container, Title, Box, Tabs, Button } from '@mantine/core'; -import { Truck, Fuel, Wrench, TrendingUp, AlertCircle, Users, User, MapPin, Calendar, BarChart3 } from 'lucide-react'; +import { Card, Stack, Group, Grid, Text, ThemeIcon, Progress, Badge, Table, RingProgress, Container, Title, Box, Tabs } from '@mantine/core'; +import { Truck, Fuel, Wrench, AlertCircle, Users, User, MapPin } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; import Breadcrumbs from '@/components/ui/Breadcrumbs'; import { QUERY_KEYS } from '@/constants/QUERY_KEYS'; import { api } from '@/auth/http'; @@ -39,7 +40,20 @@ interface FleetMetrics { assignedDrivers: number; } -const StatCard = ({ icon: Icon, label, value, color = 'edr-green', change }: any) => ( +/** ETB money, whole-birr for dashboard headlines. */ +const etb = (n: number) => `ETB ${(Number(n) || 0).toLocaleString('en-US', { maximumFractionDigits: 0 })}`; +/** Safe percentage — 0 when the denominator is 0 (empty fleet). */ +const pct = (n: number, d: number) => (d > 0 ? (n / d) * 100 : 0); + +interface StatCardProps { + icon: LucideIcon; + label: string; + value: string | number; + color?: string; + change?: number; +} + +const StatCard = ({ icon: Icon, label, value, color = 'edr-green', change }: StatCardProps) => ( @@ -111,8 +125,8 @@ export function FleetDashboard() { const totalDrivers = (drivers as Driver[]).length; const assignedDrivers = (drivers as Driver[]).filter(d => d.assignedVehicle).length; - const fuelTotal = fuelStats?.totalCost || 0; - const maintenanceTotal = maintenanceStats?.totalCost || 0; + const fuelTotal = Number(fuelStats?.totalCost) || 0; + const maintenanceTotal = Number(maintenanceStats?.totalCost) || 0; return { totalVehicles, @@ -152,10 +166,10 @@ export function FleetDashboard() { - + - + @@ -173,7 +187,7 @@ export function FleetDashboard() { Active Vehicles {metrics.activeVehicles} / {metrics.totalVehicles} - +
@@ -189,7 +203,7 @@ export function FleetDashboard() { Idle / Under Maintenance {metrics.totalVehicles - metrics.activeVehicles} - +
@@ -212,7 +226,7 @@ export function FleetDashboard() { label={
- ${operatingCost.toFixed(0)} + {etb(operatingCost)} Total Cost @@ -325,13 +339,12 @@ export function FleetDashboard() { {d.licenseNumber || 'N/A'} - + {d.phone && ( - - - {d.phone} - - + + + {d.phone} + )} {d.email && {d.email}} diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx index ff7caa9d2..f7bee5b41 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx @@ -357,7 +357,7 @@ const FleetResourcePage = () => { const itemLabel = config.label.toLowerCase(); return ( - + diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx index b3c168df3..6ca9f8870 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { - Box, Button, Card, Container, Group, + Loader, Modal, NumberInput, Select, @@ -17,13 +17,12 @@ import { Badge, Grid, } from "@mantine/core"; -import { Plus, Trash2 } from "lucide-react"; +import { Plus } from "lucide-react"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; import { useToast } from "@/hooks/use-toast"; import { api } from "@/auth/http"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; import { vehiclesService, type Vehicle as VehicleType } from "@/services/vehicles.service"; -import { freightBrand } from "@/theme/freight-brand"; interface FuelPurchase { id: string; @@ -67,7 +66,7 @@ export default function FuelPurchasePage() { }); // Fetch fuel purchases - const { data: purchasesData = [] } = useQuery({ + const { data: purchasesData = [], isLoading: isLoadingPurchases } = useQuery({ queryKey: ["fuel-purchases"], queryFn: async () => { const res = await api.get("/fuel/purchases"); @@ -104,8 +103,8 @@ export default function FuelPurchasePage() { onError: (error: any) => { toast({ title: "Error recording purchase", - message: error?.response?.data?.message || "Failed to record fuel purchase", - color: "red", + description: error?.response?.data?.message || "Failed to record fuel purchase", + variant: "destructive", }); }, }); @@ -118,6 +117,17 @@ export default function FuelPurchasePage() { const totalCost = formData.liters * formData.costPerLiter; + // Aggregate stats (guarded against divide-by-zero when there are no purchases) + const totalLiters = (purchasesData as FuelPurchase[]).reduce( + (sum, p) => sum + Number(p.liters), + 0 + ); + const totalPurchaseCost = (purchasesData as FuelPurchase[]).reduce( + (sum, p) => sum + Number(p.totalCost), + 0 + ); + const avgPricePerLiter = totalLiters > 0 ? totalPurchaseCost / totalLiters : 0; + return ( @@ -147,10 +157,7 @@ export default function FuelPurchasePage() { Total Liters - {purchasesData - .reduce((sum: number, p: FuelPurchase) => sum + Number(p.liters), 0) - .toFixed(2)}{" "} - L + {totalLiters.toFixed(2)} L @@ -160,9 +167,7 @@ export default function FuelPurchasePage() { Total Cost - ETB {purchasesData - .reduce((sum: number, p: FuelPurchase) => sum + Number(p.totalCost), 0) - .toLocaleString("en-US", { maximumFractionDigits: 2 })} + ETB {totalPurchaseCost.toLocaleString("en-US", { maximumFractionDigits: 2 })} @@ -172,11 +177,7 @@ export default function FuelPurchasePage() { Avg Price/L - ETB{" "} - {( - purchasesData.reduce((sum: number, p: FuelPurchase) => sum + Number(p.totalCost), 0) / - purchasesData.reduce((sum: number, p: FuelPurchase) => sum + Number(p.liters), 0) || 0 - ).toFixed(2)} + ETB {avgPricePerLiter.toFixed(2)} @@ -197,6 +198,23 @@ export default function FuelPurchasePage() { + {isLoadingPurchases ? ( + + + + + + + + ) : purchasesData.length === 0 ? ( + + + + No fuel purchases recorded yet. + + + + ) : null} {(purchasesData as FuelPurchase[])?.map((purchase) => ( {(purchase as any).vehicle?.registrationNumber || (purchase as any).vehicle?.plateNumber || purchase.vehicleId} diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FuelStatsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FuelStatsPage.tsx index 04c872966..904347cf0 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FuelStatsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FuelStatsPage.tsx @@ -1,20 +1,11 @@ import { useQuery } from "@tanstack/react-query"; -import { Box, Card, Container, Grid, Group, Select, Stack, Table, Text, Title, Badge } from "@mantine/core"; +import { Card, Container, Grid, Group, Loader, Select, Stack, Text, Title } from "@mantine/core"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; import { api } from "@/auth/http"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; import { vehiclesService, type Vehicle as VehicleType } from "@/services/vehicles.service"; import { useState } from "react"; -interface FuelStats { - vehicleId: string; - totalPurchases: number; - totalLiters: number; - totalCost: number; - averagePricePerLiter: number; - dateRange: { startDate: string; endDate: string }; -} - export default function FuelStatsPage() { const [selectedVehicleId, setSelectedVehicleId] = useState(""); const [monthsBack, setMonthsBack] = useState("12"); @@ -29,7 +20,7 @@ export default function FuelStatsPage() { }); // Fetch fuel stats - const { data: statsData } = useQuery({ + const { data: statsData, isFetching: isStatsFetching } = useQuery({ queryKey: ["fuel-stats", selectedVehicleId, monthsBack], queryFn: async () => { if (!selectedVehicleId) return null; @@ -102,7 +93,7 @@ export default function FuelStatsPage() { Total Purchases - {statsData.totalPurchases} + {Number(statsData.totalPurchases) || 0} @@ -112,7 +103,7 @@ export default function FuelStatsPage() { Total Fuel - {statsData.totalLiters.toFixed(2)} L + {(Number(statsData.totalLiters) || 0).toFixed(2)} L @@ -122,7 +113,7 @@ export default function FuelStatsPage() { Total Cost - ETB {statsData.totalCost.toLocaleString("en-US", { maximumFractionDigits: 2 })} + ETB {(Number(statsData.totalCost) || 0).toLocaleString("en-US", { maximumFractionDigits: 2 })} @@ -132,7 +123,7 @@ export default function FuelStatsPage() { Avg Price/L - ETB {statsData.averagePricePerLiter.toFixed(2)} + ETB {(Number(statsData.averagePricePerLiter) || 0).toFixed(2)} @@ -172,15 +163,15 @@ export default function FuelStatsPage() { {selectedVehicle?.plateNumber} consumed{" "} - {statsData.totalLiters.toFixed(2)} liters + {(Number(statsData.totalLiters) || 0).toFixed(2)} liters {" "} over the last {monthsBack} months, costing{" "} - ETB {statsData.totalCost.toLocaleString("en-US", { maximumFractionDigits: 2 })} + ETB {(Number(statsData.totalCost) || 0).toLocaleString("en-US", { maximumFractionDigits: 2 })} . Average fuel price was{" "} - ETB {statsData.averagePricePerLiter.toFixed(2)} per liter + ETB {(Number(statsData.averagePricePerLiter) || 0).toFixed(2)} per liter . @@ -188,6 +179,12 @@ export default function FuelStatsPage() { + ) : selectedVehicleId && isStatsFetching ? ( + + + + + ) : ( diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/MaintenancePage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/MaintenancePage.tsx index b2bd155d3..3aa2506b5 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/MaintenancePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/MaintenancePage.tsx @@ -1,12 +1,26 @@ -import { useState, useMemo } from 'react'; +import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Card, Button, Modal, Stack, Group, Grid, Select, TextInput, NumberInput, Table, Badge, Text, Container } from '@mantine/core'; -import { DateInput } from '@mantine/dates'; +import { + Card, + Button, + Modal, + Stack, + Group, + Select, + TextInput, + NumberInput, + Table, + Badge, + Text, + Title, + Container, +} from '@mantine/core'; import { Plus } from 'lucide-react'; +import Breadcrumbs from '@/components/ui/Breadcrumbs'; +import { useToast } from '@/hooks/use-toast'; import { QUERY_KEYS } from '@/constants/QUERY_KEYS'; -import { api } from '@/services/api'; -import { vehiclesService } from '@/services/vehicles.service'; -import { freightBrand } from '@/theme/freight-brand'; +import { api } from '@/auth/http'; +import { vehiclesService, type Vehicle as VehicleType } from '@/services/vehicles.service'; interface MaintenanceSchedule { id: string; @@ -21,21 +35,23 @@ interface MaintenanceSchedule { serviceProvider?: string; } +const emptyForm = { + maintenanceType: 'PREVENTIVE', + description: '', + scheduledDate: new Date().toISOString().split('T')[0], + estimatedCost: 0, + serviceProvider: '', + notes: '', +}; + export function MaintenancePage() { + const { toast } = useToast(); + const queryClient = useQueryClient(); const [selectedVehicle, setSelectedVehicle] = useState(null); const [openScheduleModal, setOpenScheduleModal] = useState(false); - const [formData, setFormData] = useState({ - maintenanceType: 'PREVENTIVE', - description: '', - scheduledDate: new Date(), - estimatedCost: 0, - serviceProvider: '', - notes: '', - }); + const [formData, setFormData] = useState(emptyForm); - const queryClient = useQueryClient(); - - const { data: vehicles } = useQuery({ + const { data: vehiclesData } = useQuery({ queryKey: QUERY_KEYS.VEHICLES.list(), queryFn: async () => { const res = await vehiclesService.getAll({ limit: 1000 }); @@ -45,36 +61,49 @@ export function MaintenancePage() { const { data: upcoming, isLoading } = useQuery({ queryKey: QUERY_KEYS.MAINTENANCE.upcoming(selectedVehicle || ''), - queryFn: () => selectedVehicle ? api.get(`/maintenance/upcoming/${selectedVehicle}`) : Promise.resolve([]), + queryFn: async () => { + if (!selectedVehicle) return []; + const res = await api.get(`/maintenance/upcoming/${selectedVehicle}`); + return res.data || []; + }, enabled: !!selectedVehicle, }); + const upcomingList: MaintenanceSchedule[] = Array.isArray(upcoming) ? upcoming : []; + const scheduleMutation = useMutation({ mutationFn: async () => { if (!selectedVehicle) return; - return api.post('/maintenance/schedules', { + const res = await api.post('/maintenance/schedules', { vehicleId: selectedVehicle, ...formData, }); + return res.data; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: QUERY_KEYS.MAINTENANCE.upcoming(selectedVehicle || '') }); + toast({ title: 'Maintenance scheduled' }); + queryClient.invalidateQueries({ + queryKey: QUERY_KEYS.MAINTENANCE.upcoming(selectedVehicle || ''), + }); setOpenScheduleModal(false); - setFormData({ - maintenanceType: 'PREVENTIVE', - description: '', - scheduledDate: new Date(), - estimatedCost: 0, - serviceProvider: '', - notes: '', + setFormData(emptyForm); + }, + onError: (err: any) => { + toast({ + title: 'Error', + description: err?.response?.data?.message ?? 'Failed', + variant: 'destructive', }); }, }); - const vehicleOptions = useMemo( - () => vehicles?.map(v => ({ label: v.registrationNumber || v.id, value: v.id })) || [], - [vehicles] - ); + const vehicleOptions = + vehiclesData?.map((v: VehicleType) => ({ + value: v.id, + label: v.plateNumber + ? `${v.plateNumber} - ${v.manufacturer} ${v.model}` + : v.registrationNumber || v.id, + })) || []; const statusColor = (status: string) => { const colors: Record = { @@ -88,66 +117,85 @@ export function MaintenancePage() { return ( + + + + Maintenance + + + - - - - Schedule Maintenance - - - - +