mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
Merge pull request #1096 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
This commit is contained in:
@@ -2,10 +2,6 @@ import { useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import {
|
||||
useAllExternalUsers,
|
||||
userTypeEnum,
|
||||
} from "@/super-admin/hooks/useExternalUsers";
|
||||
import {
|
||||
ALL_TRADE_DIRECTIONS,
|
||||
TRADE_DIRECTION_LABELS,
|
||||
@@ -39,9 +35,9 @@ export default function TradeAccessPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { data: usersResponse, isLoading: usersLoading } = useAllExternalUsers({
|
||||
userType: userTypeEnum.employee,
|
||||
take: 3000,
|
||||
const { data: usersResponse, isLoading: usersLoading } = useQuery({
|
||||
queryKey: ["staff-users", "employees"],
|
||||
queryFn: userTradeAccessService.employees,
|
||||
});
|
||||
|
||||
const { data: configs, isLoading: configsLoading } = useQuery({
|
||||
|
||||
@@ -60,12 +60,16 @@ interface CargoNode extends RuleEngineRecord {
|
||||
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;
|
||||
/** PER_TON only: max tons of this cargo per wagon, keyed by wagon-type id. */
|
||||
tonsPerWagonMap?: 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__";
|
||||
/** Form-value prefix for the per-wagon-type tonnage-cap inputs (PER_TON cargo). */
|
||||
const TONS_CAP_PREFIX = "tonsCap__";
|
||||
|
||||
const str = (v: unknown): string => (v == null ? "" : String(v));
|
||||
const orderOf = (n: CargoNode): number => Number(n.displayOrder ?? 0);
|
||||
@@ -159,8 +163,26 @@ const CargoTypesPage = () => {
|
||||
getInitialValue: (record) =>
|
||||
(record as CargoNode).itemsPerWagonMap?.[opt.value],
|
||||
}));
|
||||
// PER_TON cargo: an OPTIONAL "max tons per wagon" per selected wagon type —
|
||||
// how much of this commodity actually rides one wagon, which can be less
|
||||
// than its rating (sugar 50T on a 70T wagon, so 200T takes 4 wagons not 3).
|
||||
// Left blank the wagon's full rated capacity applies, so existing cargo is
|
||||
// unaffected; the API rejects a value above the rating.
|
||||
const tonsCapFields: FormFieldDef[] = (wagonTypeOptions ?? []).map((opt) => ({
|
||||
name: `${TONS_CAP_PREFIX}${opt.value}`,
|
||||
label: `Max tons per ${opt.label} wagon`,
|
||||
type: "number",
|
||||
optional: true,
|
||||
placeholder: "Blank = full wagon capacity",
|
||||
showIf: (values) =>
|
||||
values.unitOfMeasure === "PER_TON" &&
|
||||
Array.isArray(values.wagonTypeIds) &&
|
||||
(values.wagonTypeIds as string[]).includes(opt.value),
|
||||
getInitialValue: (record) =>
|
||||
(record as CargoNode).tonsPerWagonMap?.[opt.value],
|
||||
}));
|
||||
const wagonTypesAt = base.findIndex((field) => field.name === "wagonTypeIds");
|
||||
base.splice(wagonTypesAt + 1, 0, ...fitFields);
|
||||
base.splice(wagonTypesAt + 1, 0, ...fitFields, ...tonsCapFields);
|
||||
return base;
|
||||
}, [wagonTypeOptions]);
|
||||
|
||||
@@ -230,14 +252,22 @@ const CargoTypesPage = () => {
|
||||
// none are visible (not PER_ITEM) so an update clears stale fits.
|
||||
const payload: Record<string, unknown> = {};
|
||||
const itemsPerWagonMap: Record<string, number> = {};
|
||||
const tonsPerWagonMap: 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 if (key.startsWith(TONS_CAP_PREFIX)) {
|
||||
// Blank means "no cap" (use the full rated capacity), so an empty input
|
||||
// must stay OUT of the map — sending 0 would be a zero-ton wagon.
|
||||
if (value !== "" && value !== null && value !== undefined) {
|
||||
tonsPerWagonMap[key.slice(TONS_CAP_PREFIX.length)] = Number(value);
|
||||
}
|
||||
} else {
|
||||
payload[key] = value;
|
||||
}
|
||||
}
|
||||
payload.itemsPerWagonMap = Object.keys(itemsPerWagonMap).length ? itemsPerWagonMap : null;
|
||||
payload.tonsPerWagonMap = Object.keys(tonsPerWagonMap).length ? tonsPerWagonMap : 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;
|
||||
|
||||
@@ -25,7 +25,27 @@ export interface MyTradeAccess {
|
||||
directions: TradeDirection[];
|
||||
}
|
||||
|
||||
export interface StaffUser {
|
||||
id: string;
|
||||
/** Localized jsonb on iam.users — not a plain string. */
|
||||
name: { en?: string; am?: string } | null;
|
||||
username: string;
|
||||
email: string | null;
|
||||
}
|
||||
|
||||
export const userTradeAccessService = {
|
||||
/**
|
||||
* Employees to assign scopes to. Served by the freight API rather than IAM's
|
||||
* `/users/filter`, which 400s on its own pagination params (its @Query() DTO
|
||||
* omits skip/take/orderBy while the global whitelist pipe rejects them).
|
||||
*/
|
||||
employees: async (): Promise<{ items: StaffUser[] }> =>
|
||||
(
|
||||
await client.get("/staff/users", {
|
||||
params: { userType: "employee", pageSize: 100 },
|
||||
})
|
||||
).data,
|
||||
|
||||
/** All configured per-user scopes (admin only). */
|
||||
list: async (): Promise<UserTradeAccessRow[]> =>
|
||||
(await client.get("/user-trade-access")).data,
|
||||
|
||||
Reference in New Issue
Block a user