mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 13:05:44 +00:00
update joins in repository services to use entity classes and enhance booking form with hazardous/reefer toggles
This commit is contained in:
@@ -21,6 +21,8 @@ interface AuthEmployeePosition {
|
||||
isDelegate?: boolean;
|
||||
parentPositionId?: string | null;
|
||||
permissions?: AuthPermission[];
|
||||
/** Some IAM payloads nest the position record instead of flattening its key. */
|
||||
position?: { id?: string; key?: string; name?: LocaleText };
|
||||
}
|
||||
|
||||
interface AuthEmployeeRecord {
|
||||
|
||||
@@ -174,6 +174,23 @@ export const useContainerTypeOptions = (
|
||||
buildContainerTypeSelectOptions(result.data ?? [], includeNone),
|
||||
});
|
||||
|
||||
/**
|
||||
* Active wagon-type options for the cargo-type / container-type "Wagon type"
|
||||
* picker. The FK the selection sets drives train-scheduling wagon resolution.
|
||||
*/
|
||||
export const useWagonTypeOptions = (enabled = true) =>
|
||||
useQuery({
|
||||
...api.wagonTypes.list.queryOptions(),
|
||||
enabled,
|
||||
select: (rows: { id: string; code: string; name: string; isActive?: boolean }[]) =>
|
||||
rows
|
||||
.filter((wt) => wt.isActive !== false)
|
||||
.map((wt) => ({
|
||||
label: wt.name ? `${wt.name} (${wt.code})` : wt.code,
|
||||
value: wt.id,
|
||||
})),
|
||||
});
|
||||
|
||||
const LIVE_RATE_PAGE_SIZE = 500;
|
||||
|
||||
export const useLiveRateOptions = (enabled = true) =>
|
||||
|
||||
@@ -73,15 +73,23 @@ export function getPermissionKeys(user: AuthUser | null | undefined): string[] {
|
||||
return [...keys];
|
||||
}
|
||||
|
||||
/** Position keys held by the user (e.g. "ethiopian_gl", "djibouti_gl"). */
|
||||
/**
|
||||
* Position keys held by the user (e.g. "ethiopian_gl", "djibouti_gl").
|
||||
* Tolerates IAM payload shape variants: the key flat on the employee position,
|
||||
* nested under `position.key`, or the GL modeled as a role instead.
|
||||
*/
|
||||
export function getPositionKeys(user: AuthUser | null | undefined): string[] {
|
||||
if (!user) return [];
|
||||
const keys = new Set<string>();
|
||||
for (const emp of user.employee ?? []) {
|
||||
for (const pos of emp.positions ?? []) {
|
||||
if (pos.key) keys.add(pos.key);
|
||||
if (pos.position?.key) keys.add(pos.position.key);
|
||||
}
|
||||
}
|
||||
for (const role of user.roles ?? []) {
|
||||
if (role.key) keys.add(role.key);
|
||||
}
|
||||
return [...keys];
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
import {
|
||||
useRuleEngineList,
|
||||
useRuleEngineMutations,
|
||||
useWagonTypeOptions,
|
||||
} from "@/hooks/rule-engine/useRuleEngine";
|
||||
import type { RuleEngineRecord } from "@/types/rule-engine";
|
||||
|
||||
@@ -54,6 +55,8 @@ interface CargoNode extends RuleEngineRecord {
|
||||
requiresDirectorApproval?: boolean;
|
||||
/** How this cargo is measured (PER_TON / PER_ITEM); null for groups/unset. */
|
||||
unitOfMeasure?: string | null;
|
||||
/** Wagon type FK used to carry this bulk cargo during scheduling; null if unset. */
|
||||
wagonTypeId?: string | null;
|
||||
isActive?: boolean;
|
||||
displayOrder?: number;
|
||||
}
|
||||
@@ -79,6 +82,18 @@ const FORM_FIELDS: FormFieldDef[] = [
|
||||
{ label: "Per item (break-bulk)", value: "PER_ITEM" },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Wagon type that carries this (bulk) commodity — drives train-scheduling
|
||||
// wagon resolution. Optional: leave "None" for grouping categories and
|
||||
// container/legacy cargo; set it on scheduled bulk commodities.
|
||||
// Options injected at render from useWagonTypeOptions.
|
||||
name: "wagonTypeId",
|
||||
label: "Wagon type",
|
||||
type: "select",
|
||||
optional: true,
|
||||
placeholder: "Select wagon type (bulk cargo)",
|
||||
options: [{ label: "None", value: RULE_ENGINE_SELECT_NONE }],
|
||||
},
|
||||
{ name: "requiresDirectorApproval", label: "Requires director approval", type: "boolean" },
|
||||
{ name: "isActive", label: "Active", type: "boolean" },
|
||||
];
|
||||
@@ -105,6 +120,24 @@ const CargoTypesPage = () => {
|
||||
|
||||
const { create, update, remove } = useRuleEngineMutations(CARGO_SLUG);
|
||||
|
||||
// Wagon-type options for the "Wagon type" picker (bulk cargo → wagon FK).
|
||||
const { data: wagonTypeOptions } = useWagonTypeOptions(canManage);
|
||||
const formFields = useMemo<FormFieldDef[]>(
|
||||
() =>
|
||||
FORM_FIELDS.map((field) =>
|
||||
field.name === "wagonTypeId"
|
||||
? {
|
||||
...field,
|
||||
options: [
|
||||
{ label: "None", value: RULE_ENGINE_SELECT_NONE },
|
||||
...(wagonTypeOptions ?? []),
|
||||
],
|
||||
}
|
||||
: field,
|
||||
),
|
||||
[wagonTypeOptions],
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [formMode, setFormMode] = useState<FormMode | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<CargoNode | null>(null);
|
||||
@@ -350,7 +383,7 @@ const CargoTypesPage = () => {
|
||||
? "Create a top-level cargo category."
|
||||
: "Create a cargo type inside this category. It's attached here automatically."
|
||||
}
|
||||
fields={FORM_FIELDS}
|
||||
fields={formFields}
|
||||
initialRecord={formMode?.kind === "edit" ? formMode.record : null}
|
||||
isSubmitting={create.isPending || update.isPending}
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
useCargoTypeParentOptions,
|
||||
useContainerTypeOptions,
|
||||
useLiveRateOptions,
|
||||
useWagonTypeOptions,
|
||||
useRateWorkflow,
|
||||
useRuleEngineList,
|
||||
useRuleEngineMutations,
|
||||
@@ -151,6 +152,9 @@ const RuleEngineResourcePage = () => {
|
||||
const usesLiveRateField = Boolean(
|
||||
config?.formFields.some((f) => f.name === "rateId"),
|
||||
);
|
||||
const usesWagonTypeField = Boolean(
|
||||
config?.formFields.some((f) => f.name === "wagonTypeId"),
|
||||
);
|
||||
|
||||
const { data: cargoParentOptions, isLoading: cargoParentOptionsLoading } =
|
||||
useCargoTypeParentOptions(editingId, config?.slug === "cargo-types");
|
||||
@@ -160,6 +164,8 @@ const RuleEngineResourcePage = () => {
|
||||
useContainerTypeOptions(config?.slug === "rates", usesContainerTypeField);
|
||||
const { data: liveRateOptions, isLoading: liveRateOptionsLoading } =
|
||||
useLiveRateOptions(usesLiveRateField);
|
||||
const { data: wagonTypeOptions, isLoading: wagonTypeOptionsLoading } =
|
||||
useWagonTypeOptions(usesWagonTypeField);
|
||||
|
||||
const formFields = useMemo(() => {
|
||||
if (!config) return [];
|
||||
@@ -193,9 +199,16 @@ const RuleEngineResourcePage = () => {
|
||||
options: liveRateOptions ?? [],
|
||||
};
|
||||
}
|
||||
if (field.name === "wagonTypeId") {
|
||||
return {
|
||||
...field,
|
||||
type: "select" as const,
|
||||
options: wagonTypeOptions ?? [],
|
||||
};
|
||||
}
|
||||
return field;
|
||||
});
|
||||
}, [config, cargoParentOptions, cargoLeafOptions, containerTypeOptions, liveRateOptions]);
|
||||
}, [config, cargoParentOptions, cargoLeafOptions, containerTypeOptions, liveRateOptions, wagonTypeOptions]);
|
||||
|
||||
const rows = data?.data ?? [];
|
||||
const meta = data?.meta;
|
||||
@@ -502,7 +515,8 @@ const RuleEngineResourcePage = () => {
|
||||
(config.slug === "cargo-types" && cargoParentOptionsLoading) ||
|
||||
(usesContainerTypeField && containerTypeOptionsLoading) ||
|
||||
(usesCargoTypeField && cargoLeafOptionsLoading) ||
|
||||
(usesLiveRateField && liveRateOptionsLoading)
|
||||
(usesLiveRateField && liveRateOptionsLoading) ||
|
||||
(usesWagonTypeField && wagonTypeOptionsLoading)
|
||||
}
|
||||
positionOptions={!editing ? createPositionOptions : undefined}
|
||||
positionLoading={createPositionLoading}
|
||||
|
||||
@@ -248,6 +248,14 @@ export const RULE_ENGINE_RESOURCES: RuleEngineResourceConfig[] = [
|
||||
formFields: [
|
||||
{ name: "label", label: "Label", type: "text", required: true },
|
||||
{ name: "sizeFt", label: "Size (ft)", type: "number", required: true },
|
||||
// Options injected at render from useWagonTypeOptions (RuleEngineResourcePage).
|
||||
{
|
||||
name: "wagonTypeId",
|
||||
label: "Wagon type",
|
||||
type: "select",
|
||||
required: true,
|
||||
description: "Wagon type used to carry this container during train scheduling.",
|
||||
},
|
||||
{ name: "isOpenTop", label: "Open top", type: "boolean" },
|
||||
{ name: "isActive", label: "Active", type: "boolean" },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user