mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 22:58:17 +00:00
style: finalize the style of the dashboard
This commit is contained in:
@@ -1,25 +1,35 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
import type { ColumnDef } from "@edr/ui-common";
|
||||
import { Box, Button, Card, Group, Modal, Select, Stack, Text } from "@mantine/core";
|
||||
import {
|
||||
Archive,
|
||||
Circle,
|
||||
CircleCheck,
|
||||
CircleSlash,
|
||||
Layers,
|
||||
Link2,
|
||||
Plus,
|
||||
Wrench,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
|
||||
import { PageContainer } from "@/components/page";
|
||||
import FleetCardGrid from "@/components/fleet/FleetCardGrid";
|
||||
import FleetFormDialog from "@/components/fleet/FleetFormDialog";
|
||||
import FleetRecordActions from "@/components/fleet/FleetRecordActions";
|
||||
import FleetToolbar from "@/components/fleet/FleetToolbar";
|
||||
import { formatFleetCell, registerFleetOptionLabels } from "@/components/fleet/fleetFormat";
|
||||
import { useFleetViewMode } from "@/components/fleet/useFleetViewMode";
|
||||
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
|
||||
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
|
||||
import { useFleetList, useFleetMutations } from "@/hooks/fleet/useFleet";
|
||||
import { useCargoTypes } from "@/hooks/use-cargo-types";
|
||||
import { useContainerTypes } from "@/hooks/use-container-types";
|
||||
import { useWagonTypes } from "@/hooks/use-wagon-types";
|
||||
import { useFleetList, useFleetMutations } from "@/hooks/fleet/useFleet";
|
||||
import { useContainers } from "@/hooks/useContainers";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useWagonTypes } from "@/hooks/use-wagon-types";
|
||||
import { useContainers } from "@/hooks/useContainers";
|
||||
import { useRouteYards } from "@/hooks/useRoutes";
|
||||
import { useWagons } from "@/hooks/useWagons";
|
||||
import type { FleetListFilters } from "@/services/fleet/fleet.service";
|
||||
import {
|
||||
FLEET_SELECT_NONE,
|
||||
getFleetResource,
|
||||
@@ -27,11 +37,27 @@ import {
|
||||
type FleetFormFieldDef,
|
||||
type FleetResourceSlug,
|
||||
} from "@/pages/fleet/config/resources";
|
||||
import type { FleetRecord } from "@/services/fleet/fleet.service";
|
||||
import type { FleetListFilters, FleetRecord } from "@/services/fleet/fleet.service";
|
||||
import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common";
|
||||
|
||||
const DEFAULT_SLUG: FleetResourceSlug = "locomotives";
|
||||
|
||||
const FLEET_STATUS_META: Record<
|
||||
string,
|
||||
{ label: string; icon: LucideIcon; color: string }
|
||||
> = {
|
||||
AVAILABLE: { label: "Available", icon: CircleCheck, color: "edr-green" },
|
||||
ASSIGNED: { label: "Assigned", icon: Link2, color: "blue" },
|
||||
MAINTENANCE: { label: "Maintenance", icon: Wrench, color: "yellow" },
|
||||
OUT_OF_SERVICE: { label: "Out of service", icon: CircleSlash, color: "red" },
|
||||
RETIRED: { label: "Retired", icon: Archive, color: "gray" },
|
||||
};
|
||||
|
||||
const humanizeStatus = (status: string) => {
|
||||
const text = status.replace(/_/g, " ").toLowerCase();
|
||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||
};
|
||||
|
||||
const FleetResourcePage = () => {
|
||||
const location = useLocation();
|
||||
const slug = getFleetSlugFromPath(location.pathname) ?? DEFAULT_SLUG;
|
||||
@@ -250,6 +276,39 @@ const FleetResourcePage = () => {
|
||||
|
||||
const tableStatus = isLoading ? "loading" : isError ? "error" : "success";
|
||||
|
||||
const kpiItems = useMemo(() => {
|
||||
const items = [
|
||||
{
|
||||
label: `Total ${config?.label.toLowerCase() ?? ""}`,
|
||||
value: allRows.length,
|
||||
icon: Layers,
|
||||
color: "edr-green",
|
||||
},
|
||||
];
|
||||
if (hasStatusColumn) {
|
||||
const counts = new Map<string, number>();
|
||||
for (const row of allRows) {
|
||||
const status = String(
|
||||
(row as unknown as Record<string, unknown>).status ?? "",
|
||||
);
|
||||
if (status) counts.set(status, (counts.get(status) ?? 0) + 1);
|
||||
}
|
||||
const top = [...counts.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 4);
|
||||
for (const [status, count] of top) {
|
||||
const meta = FLEET_STATUS_META[status];
|
||||
items.push({
|
||||
label: meta?.label ?? humanizeStatus(status),
|
||||
value: count,
|
||||
icon: meta?.icon ?? Circle,
|
||||
color: meta?.color ?? "gray",
|
||||
});
|
||||
}
|
||||
}
|
||||
return items.slice(0, 5);
|
||||
}, [allRows, hasStatusColumn, config?.label]);
|
||||
|
||||
if (!config) {
|
||||
return <Navigate to="/dashboard/locomotives" replace />;
|
||||
}
|
||||
@@ -293,6 +352,24 @@ const FleetResourcePage = () => {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title={config.label}
|
||||
subtitle={config.subtitle}
|
||||
action={
|
||||
<Button
|
||||
leftSection={<Plus size={18} />}
|
||||
onClick={() => {
|
||||
setEditing(null);
|
||||
setFormOpen(true);
|
||||
}}
|
||||
>
|
||||
{config.addLabel}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<KpiStrip loading={isLoading} items={kpiItems} />
|
||||
|
||||
<Card radius="lg" padding={0} withBorder>
|
||||
<Stack gap={0}>
|
||||
<Box px="md" pt="md" pb="sm" w="100%">
|
||||
@@ -301,11 +378,6 @@ const FleetResourcePage = () => {
|
||||
onSearchChange={setSearch}
|
||||
searchPlaceholder={config.searchPlaceholder}
|
||||
showSearch={config.supportsSearch}
|
||||
addLabel={config.addLabel}
|
||||
onAdd={() => {
|
||||
setEditing(null);
|
||||
setFormOpen(true);
|
||||
}}
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
filters={
|
||||
@@ -316,7 +388,6 @@ const FleetResourcePage = () => {
|
||||
key={filter.key}
|
||||
size="sm"
|
||||
radius="lg"
|
||||
label={filter.label}
|
||||
value={filter.value}
|
||||
onChange={(v) => {
|
||||
if (!v) return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FormEvent, useMemo, useState } from "react";
|
||||
import { Edit, Eye, Trash2 } from "lucide-react";
|
||||
import { Ban, CircleCheck, Edit, Eye, Plus, Route as RouteIcon, Trash2 } from "lucide-react";
|
||||
import type { ColumnDef } from "@edr/ui-common";
|
||||
import {
|
||||
ActionIcon,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
|
||||
import { PageContainer } from "@/components/page";
|
||||
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
|
||||
import FleetToolbar from "@/components/fleet/FleetToolbar";
|
||||
import { useFleetViewMode } from "@/components/fleet/useFleetViewMode";
|
||||
import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter";
|
||||
@@ -102,6 +102,9 @@ export default function RoutesPage() {
|
||||
return filteredRoutes.slice(start, start + pagination.pageSize);
|
||||
}, [filteredRoutes, pagination.pageIndex, pagination.pageSize]);
|
||||
|
||||
const allRoutes = routesQuery.data ?? [];
|
||||
const activeCount = allRoutes.filter((route) => route.isActive).length;
|
||||
|
||||
const yardOptions = useMemo(
|
||||
() =>
|
||||
(yardsQuery.data ?? []).map((yard) => ({
|
||||
@@ -281,6 +284,30 @@ export default function RoutesPage() {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Routes"
|
||||
subtitle="Define rail corridors and their ordered yard stops used by train scheduling."
|
||||
action={
|
||||
<Button leftSection={<Plus size={18} />} onClick={openCreate}>
|
||||
Add route
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<KpiStrip
|
||||
loading={routesQuery.isLoading}
|
||||
items={[
|
||||
{ label: "Total routes", value: allRoutes.length, icon: RouteIcon },
|
||||
{ label: "Active", value: activeCount, icon: CircleCheck, color: "edr-green" },
|
||||
{
|
||||
label: "Inactive",
|
||||
value: allRoutes.length - activeCount,
|
||||
icon: Ban,
|
||||
color: "gray",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Card radius="lg" padding={0} withBorder>
|
||||
<Stack gap={0}>
|
||||
<Box px="md" pt="md" pb="sm" w="100%">
|
||||
@@ -288,8 +315,6 @@ export default function RoutesPage() {
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
searchPlaceholder="Search routes…"
|
||||
addLabel="Add Route"
|
||||
onAdd={openCreate}
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user