mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 13:40:57 +00:00
- WarehouseLoading entity + Batch3 migration (wagon loading records) - wagon-aware load() with validation; dispatch moved to PATCH - read-only SchedulingReadFacade (schedule/wagon/departure) — never writes scheduling - new endpoints: GET /warehouse-loadings, loadable-wagons, booking schedule - Loading Queue / Loaded Inventory / Dispatch Queue pages + routes + sidebar - FreightVisual illustrations (page heroes + empty states) - booking detail: loaded/dispatched/wagon + read-only train schedule Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
185 lines
5.5 KiB
TypeScript
185 lines
5.5 KiB
TypeScript
import type { CSSProperties, ReactElement } from 'react';
|
|
|
|
export type FreightVisualVariant =
|
|
| 'train'
|
|
| 'warehouse'
|
|
| 'container'
|
|
| 'wagon'
|
|
| 'cargo'
|
|
| 'route'
|
|
| 'empty';
|
|
|
|
interface FreightVisualProps {
|
|
variant: FreightVisualVariant;
|
|
/** Pixel size of the (square) artwork. Defaults to 64. */
|
|
size?: number;
|
|
style?: CSSProperties;
|
|
className?: string;
|
|
title?: string;
|
|
}
|
|
|
|
/**
|
|
* Lightweight railway/freight illustrations — minimal, enterprise-logistics style.
|
|
* Inline SVG (no network cost) using EDR brand colors: green, yellow, dark text,
|
|
* light gray. Purposely low-contrast so it never overpowers tables/forms.
|
|
*
|
|
* Use only in page headers, empty states, and KPI cards.
|
|
*/
|
|
const EDR = {
|
|
green: '#2F9E44',
|
|
greenSoft: '#D3F9D8',
|
|
yellow: '#F59F00',
|
|
yellowSoft: '#FFF3BF',
|
|
dark: '#343A40',
|
|
gray: '#ADB5BD',
|
|
graySoft: '#E9ECEF',
|
|
};
|
|
|
|
function Train() {
|
|
return (
|
|
<>
|
|
{/* track */}
|
|
<rect x="2" y="52" width="60" height="3" rx="1.5" fill={EDR.graySoft} />
|
|
{/* locomotive body */}
|
|
<rect x="6" y="20" width="26" height="26" rx="3" fill={EDR.green} />
|
|
<rect x="10" y="24" width="8" height="8" rx="1.5" fill={EDR.greenSoft} />
|
|
<rect x="22" y="24" width="6" height="8" rx="1.5" fill={EDR.greenSoft} />
|
|
{/* cab roof */}
|
|
<rect x="9" y="15" width="14" height="6" rx="2" fill={EDR.dark} />
|
|
{/* wagon */}
|
|
<rect x="36" y="26" width="22" height="20" rx="2.5" fill={EDR.yellow} />
|
|
<rect x="40" y="30" width="14" height="6" rx="1" fill={EDR.yellowSoft} />
|
|
{/* wheels */}
|
|
{[12, 24, 42, 52].map((cx) => (
|
|
<circle key={cx} cx={cx} cy={48} r={3.2} fill={EDR.dark} />
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Warehouse() {
|
|
return (
|
|
<>
|
|
{/* ground */}
|
|
<rect x="4" y="50" width="56" height="3" rx="1.5" fill={EDR.graySoft} />
|
|
{/* roof */}
|
|
<path d="M10 24 L32 12 L54 24 Z" fill={EDR.green} />
|
|
{/* body */}
|
|
<rect x="14" y="24" width="36" height="26" rx="1.5" fill={EDR.greenSoft} />
|
|
{/* shutter door */}
|
|
<rect x="26" y="32" width="12" height="18" rx="1" fill={EDR.dark} />
|
|
<rect x="27.5" y="35" width="9" height="2" fill={EDR.gray} />
|
|
<rect x="27.5" y="39" width="9" height="2" fill={EDR.gray} />
|
|
<rect x="27.5" y="43" width="9" height="2" fill={EDR.gray} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Container() {
|
|
return (
|
|
<>
|
|
{/* stacked containers */}
|
|
<rect x="8" y="34" width="22" height="16" rx="1.5" fill={EDR.green} />
|
|
<rect x="34" y="34" width="22" height="16" rx="1.5" fill={EDR.yellow} />
|
|
<rect x="20" y="16" width="24" height="16" rx="1.5" fill={EDR.dark} />
|
|
{/* corrugation lines */}
|
|
{[12, 16, 20, 24].map((x) => (
|
|
<rect key={`a${x}`} x={x} y="37" width="1.5" height="10" fill={EDR.greenSoft} />
|
|
))}
|
|
{[38, 42, 46, 50].map((x) => (
|
|
<rect key={`b${x}`} x={x} y="37" width="1.5" height="10" fill={EDR.yellowSoft} />
|
|
))}
|
|
{[25, 29, 33, 37].map((x) => (
|
|
<rect key={`c${x}`} x={x} y="19" width="1.5" height="10" fill={EDR.gray} />
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Wagon() {
|
|
return (
|
|
<>
|
|
<rect x="4" y="50" width="56" height="3" rx="1.5" fill={EDR.graySoft} />
|
|
{/* flatbed wagon */}
|
|
<rect x="8" y="38" width="48" height="8" rx="1.5" fill={EDR.dark} />
|
|
{/* cargo on wagon */}
|
|
<rect x="14" y="22" width="16" height="16" rx="1.5" fill={EDR.green} />
|
|
<rect x="34" y="26" width="16" height="12" rx="1.5" fill={EDR.yellow} />
|
|
{/* wheels */}
|
|
{[16, 26, 40, 50].map((cx) => (
|
|
<circle key={cx} cx={cx} cy={48} r={3.2} fill={EDR.dark} />
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Cargo() {
|
|
return (
|
|
<>
|
|
{/* cargo boxes */}
|
|
<rect x="12" y="30" width="20" height="20" rx="2" fill={EDR.yellow} />
|
|
<rect x="34" y="34" width="18" height="16" rx="2" fill={EDR.green} />
|
|
{/* tape */}
|
|
<rect x="21" y="30" width="2" height="20" fill={EDR.yellowSoft} />
|
|
<rect x="12" y="38" width="20" height="2" fill={EDR.yellowSoft} />
|
|
<rect x="42" y="34" width="2" height="16" fill={EDR.greenSoft} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Route() {
|
|
return (
|
|
<>
|
|
{/* track line with stations */}
|
|
<rect x="6" y="31" width="52" height="2" rx="1" fill={EDR.gray} />
|
|
{[10, 22, 34, 46, 58].map((x) => (
|
|
<rect key={x} x={x - 0.5} y="28" width="1.5" height="8" fill={EDR.graySoft} />
|
|
))}
|
|
<circle cx="10" cy="32" r="5" fill={EDR.green} />
|
|
<circle cx="54" cy="32" r="5" fill={EDR.yellow} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Empty() {
|
|
return (
|
|
<>
|
|
{/* empty open box */}
|
|
<path d="M14 28 L32 22 L50 28 L50 30 L32 24 L14 30 Z" fill={EDR.gray} />
|
|
<path d="M14 30 L32 36 L32 50 L14 44 Z" fill={EDR.graySoft} />
|
|
<path d="M50 30 L32 36 L32 50 L50 44 Z" fill={EDR.graySoft} />
|
|
<path d="M14 30 L32 24 L50 30 L32 36 Z" fill="#F8F9FA" />
|
|
<circle cx="32" cy="14" r="2.5" fill={EDR.yellow} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
const VARIANTS: Record<FreightVisualVariant, () => ReactElement> = {
|
|
train: Train,
|
|
warehouse: Warehouse,
|
|
container: Container,
|
|
wagon: Wagon,
|
|
cargo: Cargo,
|
|
route: Route,
|
|
empty: Empty,
|
|
};
|
|
|
|
export function FreightVisual({ variant, size = 64, style, className, title }: FreightVisualProps) {
|
|
const Art = VARIANTS[variant];
|
|
return (
|
|
<svg
|
|
width={size}
|
|
height={size}
|
|
viewBox="0 0 64 64"
|
|
fill="none"
|
|
role="img"
|
|
aria-label={title ?? `${variant} illustration`}
|
|
className={className}
|
|
style={style}
|
|
>
|
|
{title ? <title>{title}</title> : null}
|
|
<Art />
|
|
</svg>
|
|
);
|
|
}
|