Files
edr-platform/apps/edr-freight-web/backoffice/src/pages/fleet/FinancialReportsPage.tsx
natib21 9d3efe7f15 fix
2026-07-04 03:47:36 +00:00

250 lines
8.6 KiB
TypeScript

import { useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
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 '@/auth/http';
import { vehiclesService } from '@/services/vehicles.service';
interface CombinedReport {
vehicleId: string;
fuelCost: number;
maintenanceCost: number;
totalOperatingCost: number;
fuelPercentage: number;
maintenancePercentage: number;
}
const etb = (n: number) =>
'ETB ' + Number(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
export function FinancialReportsPage() {
const [selectedVehicle, setSelectedVehicle] = useState<string | null>(null);
const [months, setMonths] = useState('12');
const { data: vehicles } = useQuery({
queryKey: QUERY_KEYS.VEHICLES.list(),
queryFn: async () => {
const res = await vehiclesService.getAll({ limit: 1000 });
return res.data || [];
},
});
const { data: fuelStats } = useQuery({
queryKey: QUERY_KEYS.FUEL.stats(selectedVehicle || ''),
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: async () => {
if (!selectedVehicle) return Promise.resolve(null);
const res = await api.get(`/maintenance/stats/${selectedVehicle}`);
return res.data;
},
enabled: !!selectedVehicle,
});
const vehicleOptions = useMemo(
() => vehicles?.map(v => ({ label: v.registrationNumber || v.id, value: v.id })) || [],
[vehicles]
);
const report = useMemo<CombinedReport | null>(() => {
if (!fuelStats && !maintenanceStats) return null;
const fuelCost = Number(fuelStats?.totalCost ?? 0) || 0;
const maintenanceCost = Number(maintenanceStats?.totalCost ?? 0) || 0;
const total = fuelCost + maintenanceCost;
return {
vehicleId: selectedVehicle!,
fuelCost,
maintenanceCost,
totalOperatingCost: total,
fuelPercentage: total > 0 ? Math.round((fuelCost / total) * 100) : 0,
maintenancePercentage: total > 0 ? Math.round((maintenanceCost / total) * 100) : 0,
};
}, [fuelStats, maintenanceStats, selectedVehicle]);
const StatCard = ({ label, value }: { label: string; value: string }) => (
<Card withBorder>
<Card.Section p="md">
<Text size="sm" c="dimmed">
{label}
</Text>
<Text fw={700} size="lg">
{value}
</Text>
</Card.Section>
</Card>
);
return (
<Container size="xl" py="xl" px="lg">
<Stack gap="md">
<Breadcrumbs items={[{ label: 'Fleet' }, { label: 'Financial Reports' }]} />
<Title order={1}>Financial Reports</Title>
<Card>
<Card.Section p="md" withBorder>
<Text fw={500}>Fleet Financial Analysis</Text>
</Card.Section>
<Card.Section p="md">
<Group>
<Select
label="Vehicle"
placeholder="Select a vehicle"
data={vehicleOptions}
value={selectedVehicle}
onChange={setSelectedVehicle}
style={{ flex: 1 }}
/>
<Select
label="Period"
data={[
{ label: 'Last 3 months', value: '3' },
{ label: 'Last 6 months', value: '6' },
{ label: 'Last 12 months', value: '12' },
]}
value={months}
onChange={v => setMonths(v || '12')}
style={{ flex: 1 }}
/>
</Group>
</Card.Section>
</Card>
{report && (
<>
<Grid>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard label="Total Operating Cost" value={etb(report.totalOperatingCost)} />
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard label="Fuel Cost" value={etb(report.fuelCost)} />
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard label="Maintenance Cost" value={etb(report.maintenanceCost)} />
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<Card withBorder>
<Card.Section p="md">
<Text size="sm" c="dimmed">
Monthly Avg
</Text>
<Text fw={700} size="lg">
{etb(report.totalOperatingCost / parseInt(months))}
</Text>
</Card.Section>
</Card>
</Grid.Col>
</Grid>
<Grid>
<Grid.Col span={{ base: 12, sm: 6 }}>
<Card>
<Card.Section p="md" withBorder>
<Text fw={500}>Cost Breakdown</Text>
</Card.Section>
<Card.Section p="md">
<Stack gap="lg">
<Group justify="space-between">
<Stack gap={0}>
<Text size="sm" c="dimmed">
Fuel
</Text>
<Text fw={500}>{report.fuelPercentage}%</Text>
</Stack>
<RingProgress
sections={[{ value: report.fuelPercentage, color: 'edr-accent' }]}
label={
<Text size="xs" ta="center">
{report.fuelPercentage}%
</Text>
}
size={100}
thickness={4}
/>
</Group>
<Group justify="space-between">
<Stack gap={0}>
<Text size="sm" c="dimmed">
Maintenance
</Text>
<Text fw={500}>{report.maintenancePercentage}%</Text>
</Stack>
<RingProgress
sections={[{ value: report.maintenancePercentage, color: 'edr-red' }]}
label={
<Text size="xs" ta="center">
{report.maintenancePercentage}%
</Text>
}
size={100}
thickness={4}
/>
</Group>
</Stack>
</Card.Section>
</Card>
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6 }}>
<Card>
<Card.Section p="md" withBorder>
<Text fw={500}>Operational Insights</Text>
</Card.Section>
<Card.Section p="md">
<Stack gap="sm">
<div>
<Text size="sm" c="dimmed">
Fuel Purchases
</Text>
<Text fw={500}>{fuelStats?.totalPurchases || 0} transactions</Text>
</div>
<div>
<Text size="sm" c="dimmed">
Fuel Efficiency
</Text>
<Text fw={500}>
{fuelStats?.fuelEfficiency?.toFixed(2) || 'N/A'} km/L
</Text>
</div>
<div>
<Text size="sm" c="dimmed">
Maintenance Items
</Text>
<Text fw={500}>{maintenanceStats?.numberOfMaintenanceItems || 0} records</Text>
</div>
<div>
<Text size="sm" c="dimmed">
Avg Maintenance Cost
</Text>
<Text fw={500}>{etb(maintenanceStats?.averageCostPerMaintenance ?? 0)}</Text>
</div>
</Stack>
</Card.Section>
</Card>
</Grid.Col>
</Grid>
</>
)}
{!selectedVehicle && (
<Card>
<Card.Section p="md">
<Text c="dimmed">Select a vehicle to view financial reports</Text>
</Card.Section>
</Card>
)}
</Stack>
</Container>
);
}