Files
edr-platform/apps/edr-freight-web/backoffice/src/components/warehouses/FreightVisual.tsx
Hagernesh 95c6a71438 feat(warehouse): batch 3 — loading, dispatch & train-departure visibility
- 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>
2026-06-12 12:44:40 +00:00

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>
);
}