Merge pull request #868 from Tria-plc/testfixes

Fleet management cards
This commit is contained in:
Hagernesh Tadesse
2026-07-21 12:27:03 +03:00
committed by GitHub
3 changed files with 67 additions and 26 deletions

View File

@@ -1,6 +1,7 @@
import { Card, Skeleton, Text } from "@mantine/core";
import type { LucideIcon } from "lucide-react";
import type { ReactNode } from "react";
import type { ElementType, ReactNode } from "react";
import { Link } from "react-router-dom";
import { cn } from "@/lib/utils";
@@ -22,6 +23,11 @@ export interface KpiItem {
* (green up, red down, muted zero). E.g. today's count minus yesterday's.
*/
delta?: number;
/**
* Optional route the cell links to — its detail view. When set the cell
* becomes clickable (pointer, hover tint); when absent it stays static.
*/
href?: string;
}
export interface KpiStripProps {
@@ -47,13 +53,22 @@ export function KpiStrip({ items, loading = false }: KpiStripProps) {
{cells.map((item, index) => {
const Icon = item.icon;
const color = item.color ?? "edr-green";
// A cell with an href becomes a link to its detail; without one it
// stays a plain div. Same layout classes either way.
const Cell: ElementType = item.href ? Link : "div";
const linkProps = item.href
? { to: item.href, "aria-label": `${item.label} — view detail` }
: {};
return (
<div
<Cell
key={item.label}
{...(linkProps as Record<string, unknown>)}
className={cn(
"flex flex-1 items-center gap-3 px-5 py-4",
index > 0 &&
"border-t border-edr-border sm:border-l sm:border-t-0",
item.href &&
"cursor-pointer no-underline transition-colors hover:bg-gray-50 focus-visible:bg-gray-50",
)}
>
{Icon ? (
@@ -102,7 +117,7 @@ export function KpiStrip({ items, loading = false }: KpiStripProps) {
{item.hint ? ` · ${item.hint}` : ""}
</Text>
</div>
</div>
</Cell>
);
})}
</div>

View File

@@ -23,18 +23,23 @@ export function WarehouseOpsKpiStrip() {
delta:
data != null ? data.receivedToday - data.receivedYesterday : undefined,
hint: "vs yesterday",
// The received cargo itself, on the inventory board.
href: "/dashboard/warehouse-inventory?status=RECEIVED",
},
{
label: "Pending inspection",
value: data?.pendingInspection ?? 0,
icon: ClipboardCheck,
color: "yellow",
// Received cargo still awaiting inspection lives in the RECEIVED bucket.
href: "/dashboard/warehouse-inventory?status=RECEIVED",
},
{
label: "Trucks on-site",
value: data?.trucksOnSite ?? 0,
icon: Truck,
color: "blue",
href: "/dashboard/trucks-on-site",
},
{
label: "Items aging (>7d)",
@@ -42,6 +47,8 @@ export function WarehouseOpsKpiStrip() {
icon: AlertTriangle,
color: (data?.itemsAging ?? 0) > 0 ? "red" : "edr-green",
hint: "In warehouse over 7 days",
// No aging filter on the board; the inventory list is the landing.
href: "/dashboard/warehouse-inventory",
},
]}
/>

View File

@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query';
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 { Link } from 'react-router-dom';
import Breadcrumbs from '@/components/ui/Breadcrumbs';
import { QUERY_KEYS } from '@/constants/QUERY_KEYS';
import { api } from '@/auth/http';
@@ -51,28 +52,46 @@ interface StatCardProps {
value: string | number;
color?: string;
change?: number;
/** Detail route the card opens. When set the card is a link; otherwise static. */
href?: string;
}
const StatCard = ({ icon: Icon, label, value, color = 'edr-green', change }: StatCardProps) => (
<Card withBorder p="lg" style={{ borderTop: `3px solid ${freightBrand.primary}` }}>
<Group justify="space-between" mb="sm">
<ThemeIcon size="xl" radius="md" color={color} variant="light">
<Icon size={28} />
</ThemeIcon>
</Group>
<Stack gap="xs">
<Text size="xs" c="dimmed" fw={500} tt="uppercase">
{label}
</Text>
<Group justify="space-between">
<Text fw={700} size="xl" c="edr-ink">
{value}
</Text>
{change && <Badge color={change > 0 ? 'edr-green' : 'edr-red'} size="lg">{change > 0 ? '+' : ''}{change}%</Badge>}
const StatCard = ({ icon: Icon, label, value, color = 'edr-green', change, href }: StatCardProps) => {
const card = (
<Card withBorder p="lg" style={{ borderTop: `3px solid ${freightBrand.primary}`, height: '100%' }}>
<Group justify="space-between" mb="sm">
<ThemeIcon size="xl" radius="md" color={color} variant="light">
<Icon size={28} />
</ThemeIcon>
</Group>
</Stack>
</Card>
);
<Stack gap="xs">
<Text size="xs" c="dimmed" fw={500} tt="uppercase">
{label}
</Text>
<Group justify="space-between">
<Text fw={700} size="xl" c="edr-ink">
{value}
</Text>
{change && <Badge color={change > 0 ? 'edr-green' : 'edr-red'} size="lg">{change > 0 ? '+' : ''}{change}%</Badge>}
</Group>
</Stack>
</Card>
);
// Wrap in a link to the detail view rather than morphing the Card itself —
// keeps Mantine's Card typing clean. Static when no href.
return href ? (
<Link
to={href}
aria-label={`${label} — view detail`}
className="block h-full cursor-pointer no-underline transition-opacity hover:opacity-90"
>
{card}
</Link>
) : (
card
);
};
export function FleetDashboard() {
const { data: vehicles = [] } = useQuery({
@@ -160,16 +179,16 @@ export function FleetDashboard() {
{/* Primary Metrics */}
<Grid mb="xl">
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard icon={Truck} label="Total Vehicles" value={metrics.totalVehicles} color="edr-green" />
<StatCard icon={Truck} label="Total Vehicles" value={metrics.totalVehicles} color="edr-green" href="/dashboard/vehicles" />
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard icon={Users} label="Total Drivers" value={metrics.totalDrivers} color="edr-blue" />
<StatCard icon={Users} label="Total Drivers" value={metrics.totalDrivers} color="edr-blue" href="/dashboard/drivers" />
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard icon={Fuel} label="Fuel Spend" value={etb(metrics.totalFuelSpend)} color="edr-accent" />
<StatCard icon={Fuel} label="Fuel Spend" value={etb(metrics.totalFuelSpend)} color="edr-accent" href="/dashboard/fuel-purchases" />
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
<StatCard icon={Wrench} label="Maintenance" value={etb(metrics.totalMaintenanceSpend)} color="edr-red" />
<StatCard icon={Wrench} label="Maintenance" value={etb(metrics.totalMaintenanceSpend)} color="edr-red" href="/dashboard/maintenance" />
</Grid.Col>
</Grid>