Merge pull request #1063 from Tria-plc/freight_feature/usermanagement

Freight feature/usermanagement
This commit is contained in:
marshal
2026-08-01 13:04:33 +03:00
committed by GitHub
30 changed files with 602 additions and 211 deletions

View File

@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import { format } from "date-fns";
import { Input } from "@/shared/common/ui/input";
import {
Select,
@@ -219,7 +218,7 @@ const buildQuery = (): CollectionQueryDTO => {
<TableCell>{log.message}</TableCell>
<TableCell className="text-muted-foreground">
{log.timestamp && !isNaN(new Date(log.timestamp).getTime())
? format(new Date(log.timestamp), "yyyy-MM-dd HH:mm:ss")
? new Date(log.timestamp).toLocaleString("sv-SE")
: "N/A"}
</TableCell>
</TableRow>

View File

@@ -58,10 +58,15 @@ interface CargoNode extends RuleEngineRecord {
unitOfMeasure?: string | null;
/** Wagon types that can carry this bulk cargo during scheduling; empty if unset. */
wagonTypes?: { id: string; code?: string; name?: string }[];
/** PER_ITEM only: whole items that physically fit one wagon, keyed by wagon-type id. */
itemsPerWagonMap?: Record<string, number> | null;
isActive?: boolean;
displayOrder?: number;
}
/** Form-value prefix for the per-wagon-type items-fit inputs (PER_ITEM cargo). */
const ITEMS_FIT_PREFIX = "itemsFit__";
const str = (v: unknown): string => (v == null ? "" : String(v));
const orderOf = (n: CargoNode): number => Number(n.displayOrder ?? 0);
@@ -131,15 +136,33 @@ const CargoTypesPage = () => {
// Wagon-type options for the "Wagon types" picker (bulk cargo → allowed list).
const { data: wagonTypeOptions } = useWagonTypeOptions(canCreate || canUpdate);
const formFields = useMemo<FormFieldDef[]>(
() =>
FORM_FIELDS.map((field) =>
field.name === "wagonTypeIds"
? { ...field, options: wagonTypeOptions ?? [] }
: field,
),
[wagonTypeOptions],
);
const formFields = useMemo<FormFieldDef[]>(() => {
const base = FORM_FIELDS.map((field) =>
field.name === "wagonTypeIds"
? { ...field, options: wagonTypeOptions ?? [] }
: field,
);
// PER_ITEM cargo: one "items per wagon" input per SELECTED wagon type — how
// many whole items physically fit that wagon (floor space binds before
// tonnage). Shown only while the wagon type is picked; the API requires a
// fit for every selected type on PER_ITEM cargo.
const fitFields: FormFieldDef[] = (wagonTypeOptions ?? []).map((opt) => ({
name: `${ITEMS_FIT_PREFIX}${opt.value}`,
label: `Items per ${opt.label} wagon`,
type: "number",
required: true,
placeholder: "e.g. 4",
showIf: (values) =>
values.unitOfMeasure === "PER_ITEM" &&
Array.isArray(values.wagonTypeIds) &&
(values.wagonTypeIds as string[]).includes(opt.value),
getInitialValue: (record) =>
(record as CargoNode).itemsPerWagonMap?.[opt.value],
}));
const wagonTypesAt = base.findIndex((field) => field.name === "wagonTypeIds");
base.splice(wagonTypesAt + 1, 0, ...fitFields);
return base;
}, [wagonTypeOptions]);
const [search, setSearch] = useState("");
const [formMode, setFormMode] = useState<FormMode | null>(null);
@@ -203,7 +226,18 @@ const CargoTypesPage = () => {
const countAtRoot = (childrenOf.get("__root__") ?? []).length;
const handleSubmit = (values: Record<string, unknown>) => {
const payload: Record<string, unknown> = { ...values };
// Fold the per-wagon-type fit inputs into the API's map shape. Null when
// none are visible (not PER_ITEM) so an update clears stale fits.
const payload: Record<string, unknown> = {};
const itemsPerWagonMap: Record<string, number> = {};
for (const [key, value] of Object.entries(values)) {
if (key.startsWith(ITEMS_FIT_PREFIX)) {
itemsPerWagonMap[key.slice(ITEMS_FIT_PREFIX.length)] = Number(value);
} else {
payload[key] = value;
}
}
payload.itemsPerWagonMap = Object.keys(itemsPerWagonMap).length ? itemsPerWagonMap : null;
// Add always attaches to the page we're on; edit keeps the node's parent.
if (formMode?.kind === "create" && current) {
payload.parentGroupId = current.id;