Files
edr-platform/apps/edr-freight-web/backoffice/src/pages/fleet/MaintenancePage.tsx
natib21 69d1d3073f style: standardize dashboard padding
All fleet pages now use consistent layout:
- Container size: xl
- Vertical padding: xl

Pages updated:
- FleetDashboard: size="xl" py="xl" (unchanged)
- FuelPurchasePage: lg → xl
- FuelStatsPage: lg → xl
- MaintenancePage: Added Container wrapper (xl, xl)
- FinancialReportsPage: Added Container wrapper (xl, xl)

Uniform spacing across all fleet management dashboards.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 13:59:20 +00:00

207 lines
6.8 KiB
TypeScript

import { useState, useMemo } 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 { Plus } from 'lucide-react';
import { QUERY_KEYS } from '@/constants/QUERY_KEYS';
import { api } from '@/services/api';
import { vehiclesService } from '@/services/vehicles.service';
import { freightBrand } from '@/theme/freight-brand';
interface MaintenanceSchedule {
id: string;
vehicleId: string;
maintenanceType: string;
description: string;
scheduledDate: string;
completedDate?: string;
status: string;
estimatedCost?: number;
actualCost?: number;
serviceProvider?: string;
}
export function MaintenancePage() {
const [selectedVehicle, setSelectedVehicle] = useState<string | null>(null);
const [openScheduleModal, setOpenScheduleModal] = useState(false);
const [formData, setFormData] = useState({
maintenanceType: 'PREVENTIVE',
description: '',
scheduledDate: new Date(),
estimatedCost: 0,
serviceProvider: '',
notes: '',
});
const queryClient = useQueryClient();
const { data: vehicles } = useQuery({
queryKey: QUERY_KEYS.VEHICLES.list(),
queryFn: async () => {
const res = await vehiclesService.getAll({ limit: 1000 });
return res.data || [];
},
});
const { data: upcoming, isLoading } = useQuery({
queryKey: QUERY_KEYS.MAINTENANCE.upcoming(selectedVehicle || ''),
queryFn: () => selectedVehicle ? api.get(`/maintenance/upcoming/${selectedVehicle}`) : Promise.resolve([]),
enabled: !!selectedVehicle,
});
const scheduleMutation = useMutation({
mutationFn: async () => {
if (!selectedVehicle) return;
return api.post('/maintenance/schedules', {
vehicleId: selectedVehicle,
...formData,
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.MAINTENANCE.upcoming(selectedVehicle || '') });
setOpenScheduleModal(false);
setFormData({
maintenanceType: 'PREVENTIVE',
description: '',
scheduledDate: new Date(),
estimatedCost: 0,
serviceProvider: '',
notes: '',
});
},
});
const vehicleOptions = useMemo(
() => vehicles?.map(v => ({ label: v.registrationNumber || v.id, value: v.id })) || [],
[vehicles]
);
const statusColor = (status: string) => {
const colors: Record<string, string> = {
SCHEDULED: 'edr-blue',
IN_PROGRESS: 'edr-amber-soft',
COMPLETED: 'edr-green',
OVERDUE: 'edr-red',
};
return colors[status] || 'edr-slate';
};
return (
<Container size="xl" py="xl">
<Stack gap="md">
<Card>
<Card.Section p="md" withBorder>
<Group justify="space-between">
<Text fw={500}>Schedule Maintenance</Text>
<Button onClick={() => setOpenScheduleModal(true)} color="edr-green" leftSection={<Plus size={16} />}>
New Schedule
</Button>
</Group>
</Card.Section>
<Card.Section p="md">
<Select
label="Select Vehicle"
placeholder="Pick a vehicle"
data={vehicleOptions}
value={selectedVehicle}
onChange={setSelectedVehicle}
/>
</Card.Section>
</Card>
{selectedVehicle && (
<Card>
<Card.Section p="md" withBorder>
<Text fw={500}>Upcoming Maintenance</Text>
</Card.Section>
<Card.Section p="md">
{isLoading ? (
<Text>Loading...</Text>
) : (upcoming || []).length > 0 ? (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Type</Table.Th>
<Table.Th>Description</Table.Th>
<Table.Th>Scheduled</Table.Th>
<Table.Th>Est. Cost</Table.Th>
<Table.Th>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{(upcoming as MaintenanceSchedule[]).map(m => (
<Table.Tr key={m.id}>
<Table.Td>{m.maintenanceType}</Table.Td>
<Table.Td>{m.description}</Table.Td>
<Table.Td>{new Date(m.scheduledDate).toLocaleDateString()}</Table.Td>
<Table.Td>${m.estimatedCost?.toFixed(2) || '—'}</Table.Td>
<Table.Td>
<Badge color={statusColor(m.status)}>{m.status}</Badge>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
) : (
<Text c="dimmed">No upcoming maintenance</Text>
)}
</Card.Section>
</Card>
)}
<Modal
opened={openScheduleModal}
onClose={() => setOpenScheduleModal(false)}
title="Schedule Maintenance"
size="md"
>
<Stack gap="md">
<Select
label="Type"
data={['PREVENTIVE', 'CORRECTIVE', 'INSPECTION', 'REPAIR']}
value={formData.maintenanceType}
onChange={v => setFormData({ ...formData, maintenanceType: v || 'PREVENTIVE' })}
/>
<TextInput
label="Description"
placeholder="What needs to be done?"
value={formData.description}
onChange={e => setFormData({ ...formData, description: e.currentTarget.value })}
/>
<DateInput
label="Scheduled Date"
value={formData.scheduledDate}
onChange={d => setFormData({ ...formData, scheduledDate: d || new Date() })}
/>
<NumberInput
label="Estimated Cost"
value={formData.estimatedCost}
onChange={v => setFormData({ ...formData, estimatedCost: Number(v) })}
/>
<TextInput
label="Service Provider"
placeholder="e.g., John's Auto Repair"
value={formData.serviceProvider}
onChange={e => setFormData({ ...formData, serviceProvider: e.currentTarget.value })}
/>
<TextInput
label="Notes"
placeholder="Additional notes"
value={formData.notes}
onChange={e => setFormData({ ...formData, notes: e.currentTarget.value })}
/>
<Group justify="flex-end">
<Button variant="light" onClick={() => setOpenScheduleModal(false)}>
Cancel
</Button>
<Button onClick={() => scheduleMutation.mutate()} loading={scheduleMutation.isPending}>
Schedule
</Button>
</Group>
</Stack>
</Modal>
</Stack>
</Container>
);
}