mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 15:18:11 +00:00
612 lines
22 KiB
TypeScript
612 lines
22 KiB
TypeScript
import type { SidebarItem } from "@/components/layout/types";
|
|
import type { RuleEngineResourceSlug } from "@/types/rule-engine";
|
|
|
|
export type RuleEngineNavCategory = "configuration" | "rules";
|
|
|
|
export type ColumnFormat =
|
|
| "text"
|
|
| "code"
|
|
| "boolean"
|
|
| "activeBadge"
|
|
| "rateStatus"
|
|
| "date"
|
|
| "number"
|
|
| "currency"
|
|
| "entityLabel"
|
|
| "rateLabel";
|
|
|
|
export type FormFieldType = "text" | "number" | "boolean" | "date" | "email" | "select" | "multiselect" | "textarea";
|
|
|
|
export interface ResourceColumn {
|
|
id: string;
|
|
header: string;
|
|
accessorKey: string;
|
|
format?: ColumnFormat;
|
|
}
|
|
|
|
/** Radix Select cannot use empty string as an item value; use this for optional "none" choices. */
|
|
export const RULE_ENGINE_SELECT_NONE = "__none__";
|
|
|
|
export interface FormFieldDef {
|
|
name: string;
|
|
label: string;
|
|
type: FormFieldType;
|
|
required?: boolean;
|
|
optional?: boolean;
|
|
options?: { label: string; value: string }[];
|
|
placeholder?: string;
|
|
description?: string;
|
|
disabled?: boolean;
|
|
/** Trailing unit label shown inside the input (e.g. "USD" on a rate value). */
|
|
suffix?: string;
|
|
/** Hide this field when another field currently equals one of these values. */
|
|
hideWhen?: { field: string; equals: string[] };
|
|
/**
|
|
* Show this field ONLY when another field currently equals one of these
|
|
* values (inverse of hideWhen). When both are set, the field must satisfy
|
|
* showWhen and not match hideWhen.
|
|
*/
|
|
showWhen?: { field: string; equals: string[] };
|
|
/**
|
|
* Select options computed from other fields' current values. When set, the
|
|
* form resolves the option list at render time from the live form state
|
|
* instead of the static `options` list. Used for the rate unit selector,
|
|
* whose valid choices depend on `appliesTo` + `trigger`. (Named distinctly
|
|
* from the fleet config's string-based `dynamicOptions` to avoid a clash.)
|
|
*/
|
|
optionsFromValues?: (values: Record<string, unknown>) => { label: string; value: string }[];
|
|
}
|
|
|
|
export interface RuleEngineOrderConfig {
|
|
field: "displayOrder" | "stepOrder";
|
|
scopeField?: "requiresDirectorApproval";
|
|
label: string;
|
|
}
|
|
|
|
export interface RuleEngineResourceConfig {
|
|
slug: RuleEngineResourceSlug;
|
|
label: string;
|
|
subtitle: string;
|
|
category: RuleEngineNavCategory;
|
|
searchPlaceholder: string;
|
|
columns: ResourceColumn[];
|
|
formFields: FormFieldDef[];
|
|
supportsSearch?: boolean;
|
|
orderConfig?: RuleEngineOrderConfig;
|
|
/** Primary line on card view (inferred from columns when omitted). */
|
|
cardTitleKey?: string;
|
|
/** Secondary line under title on card view (inferred when omitted). */
|
|
cardSubtitleKey?: string;
|
|
/** Code badge on card header (inferred from code column when omitted). */
|
|
cardCodeKey?: string;
|
|
}
|
|
|
|
export const RULE_ENGINE_CATEGORY_BASE_PATH: Record<RuleEngineNavCategory, string> = {
|
|
configuration: "/dashboard/configuration",
|
|
rules: "/dashboard/rules",
|
|
};
|
|
|
|
const TRADE_DIRECTIONS = [
|
|
{ label: "Import", value: "IMPORT" },
|
|
{ label: "Export", value: "EXPORT" },
|
|
{ label: "Both", value: "BOTH" },
|
|
];
|
|
|
|
const APPROVAL_ROLES = [
|
|
{ label: "Line staff", value: "LINE_STAFF" },
|
|
{ label: "Director", value: "DIRECTOR" },
|
|
{ label: "CEO", value: "CEO" },
|
|
];
|
|
|
|
/**
|
|
* Friendly, admin-facing rate categories. Choosing one drives which fields the
|
|
* Rate form shows (see the `rates` resource below). Base-freight categories
|
|
* carry a trade direction + container/bulk scope; OTHER is for surcharges.
|
|
*/
|
|
const RATE_APPLIES_TO = [
|
|
{ label: "Bulk (base freight)", value: "BULK" },
|
|
{ label: "Container (base freight)", value: "CONTAINER" },
|
|
{ label: "Intercity (base freight)", value: "INTERCITY" },
|
|
{ label: "First mile", value: "FIRST_MILE" },
|
|
{ label: "Last mile", value: "LAST_MILE" },
|
|
{ label: "Other (surcharge)", value: "OTHER" },
|
|
];
|
|
|
|
/** Surcharge triggers — only relevant when Applies to = Other. */
|
|
const RATE_TRIGGERS = [
|
|
{ label: "Hazardous cargo", value: "HAZARDOUS" },
|
|
{ label: "Overweight (per excess ton)", value: "OVERWEIGHT" },
|
|
{ label: "Reefer cargo", value: "REEFER" },
|
|
{ label: "Shipping line mapped", value: "SHIPPING_LINE" },
|
|
{ label: "Consolidation", value: "CONSOLIDATION" },
|
|
{ label: "Cancellation", value: "CANCELLATION" },
|
|
{ label: "Demurrage", value: "DEMURRAGE" },
|
|
{ label: "Shipping line extra fee (PIL)", value: "PIL_EXTRA_FEE" },
|
|
];
|
|
|
|
const unitOption = (value: string) => ({ label: value.replace(/_/g, " "), value });
|
|
|
|
/**
|
|
* Valid weighting units for a rate shape — mirrors the API's
|
|
* `allowedRateUnits`. The unit is driven by the *type* being billed: containers
|
|
* bill per container, bulk per ton, overweight always per excess ton, etc. Kept
|
|
* in sync with apps/edr-freight-api/.../entities/rate-unit.util.ts.
|
|
*/
|
|
const allowedRateUnits = (appliesTo: string, trigger: string): string[] => {
|
|
if (appliesTo === "OTHER") {
|
|
switch (trigger) {
|
|
case "OVERWEIGHT":
|
|
return ["PER_TON"];
|
|
case "REEFER":
|
|
case "HAZARDOUS":
|
|
case "DEMURRAGE":
|
|
return ["PER_CONTAINER", "PER_TON"];
|
|
case "CANCELLATION":
|
|
return ["FLAT", "PER_INVOICE"];
|
|
case "CONSOLIDATION":
|
|
case "SHIPPING_LINE":
|
|
case "PIL_EXTRA_FEE":
|
|
return ["PER_CONTAINER", "FLAT"];
|
|
default:
|
|
return ["FLAT", "PER_TON", "PER_CONTAINER"];
|
|
}
|
|
}
|
|
switch (appliesTo) {
|
|
case "CONTAINER":
|
|
return ["PER_CONTAINER", "PER_WAGON"];
|
|
case "BULK":
|
|
return ["PER_TON", "PER_WAGON"];
|
|
case "INTERCITY":
|
|
return ["PER_CONTAINER", "PER_TON", "PER_WAGON", "PER_KM"];
|
|
case "FIRST_MILE":
|
|
case "LAST_MILE":
|
|
return ["PER_CONTAINER", "PER_TON", "PER_KM", "FLAT"];
|
|
default:
|
|
return ["FLAT"];
|
|
}
|
|
};
|
|
|
|
const rateUnitOptions = (values: Record<string, unknown>) => {
|
|
const appliesTo = String(values.appliesTo ?? "");
|
|
const trigger = appliesTo === "OTHER" ? String(values.trigger ?? "") : "ALWAYS";
|
|
if (!appliesTo) return [];
|
|
return allowedRateUnits(appliesTo, trigger).map(unitOption);
|
|
};
|
|
|
|
const CURRENCIES = [
|
|
{ label: "USD", value: "USD" },
|
|
{ label: "ETB", value: "ETB" },
|
|
];
|
|
|
|
const PRIORITY_CONFIG_TYPES = [
|
|
{ label: "Wagon count", value: "WAGON" },
|
|
{ label: "Payment currency", value: "CURRENCY" },
|
|
];
|
|
|
|
const codeColumn = (key: string, header = "Code"): ResourceColumn => ({
|
|
id: key,
|
|
header,
|
|
accessorKey: key,
|
|
format: "code",
|
|
});
|
|
|
|
const activeColumn: ResourceColumn = {
|
|
id: "isActive",
|
|
header: "Status",
|
|
accessorKey: "isActive",
|
|
format: "activeBadge",
|
|
};
|
|
|
|
export const RULE_ENGINE_RESOURCES: RuleEngineResourceConfig[] = [
|
|
{
|
|
slug: "cargo-types",
|
|
label: "Cargo Types",
|
|
category: "configuration",
|
|
subtitle: "Manage freight cargo classification and approval rules",
|
|
searchPlaceholder: "Search cargo types by name or code...",
|
|
supportsSearch: true,
|
|
orderConfig: { field: "displayOrder", label: "Display order" },
|
|
columns: [
|
|
codeColumn("code"),
|
|
{ id: "cargoTypeName", header: "Name", accessorKey: "cargoTypeName" },
|
|
{
|
|
id: "requiresDirectorApproval",
|
|
header: "Director approval",
|
|
accessorKey: "requiresDirectorApproval",
|
|
format: "boolean",
|
|
},
|
|
{ id: "displayOrder", header: "Order", accessorKey: "displayOrder", format: "number" },
|
|
activeColumn,
|
|
],
|
|
formFields: [
|
|
{ name: "cargoTypeName", label: "Cargo type name", type: "text", required: true },
|
|
{
|
|
name: "parentGroupId",
|
|
label: "Parent group",
|
|
type: "select",
|
|
optional: true,
|
|
placeholder: "Select parent cargo type (optional)",
|
|
},
|
|
{ name: "requiresDirectorApproval", label: "Requires director approval", type: "boolean" },
|
|
{ name: "isActive", label: "Active", type: "boolean" },
|
|
],
|
|
},
|
|
{
|
|
slug: "container-types",
|
|
label: "Container Types",
|
|
category: "configuration",
|
|
subtitle: "Configure container sizes",
|
|
searchPlaceholder: "Search container types...",
|
|
orderConfig: { field: "displayOrder", label: "Display order" },
|
|
columns: [
|
|
codeColumn("code"),
|
|
{ id: "label", header: "Label", accessorKey: "label" },
|
|
{ id: "displayOrder", header: "#", accessorKey: "displayOrder", format: "number" },
|
|
{ id: "sizeFt", header: "Size (ft)", accessorKey: "sizeFt", format: "number" },
|
|
activeColumn,
|
|
],
|
|
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" },
|
|
],
|
|
},
|
|
{
|
|
slug: "wagon-types",
|
|
label: "Wagon Types",
|
|
category: "configuration",
|
|
subtitle: "Configure wagon classes used for capacity and train planning",
|
|
searchPlaceholder: "Search wagon types by name or code...",
|
|
cardTitleKey: "name",
|
|
columns: [
|
|
codeColumn("code"),
|
|
{ id: "name", header: "Name", accessorKey: "name" },
|
|
{ id: "capacityTons", header: "Capacity (t)", accessorKey: "capacityTons", format: "number" },
|
|
{ id: "lengthMeters", header: "Length (m)", accessorKey: "lengthMeters", format: "number" },
|
|
{ id: "maxWagonsPerTrain", header: "Max / train", accessorKey: "maxWagonsPerTrain", format: "number" },
|
|
{
|
|
id: "supportedLoadTypes",
|
|
header: "Load types",
|
|
accessorKey: "supportedLoadTypes",
|
|
},
|
|
activeColumn,
|
|
],
|
|
formFields: [
|
|
{ name: "name", label: "Name", type: "text", required: true },
|
|
{ name: "capacityTons", label: "Capacity (tons)", type: "number", required: true },
|
|
{ name: "lengthMeters", label: "Length (meters)", type: "number", required: true },
|
|
{
|
|
name: "maxWagonsPerTrain",
|
|
label: "Max wagons per train",
|
|
type: "number",
|
|
optional: true,
|
|
},
|
|
{
|
|
name: "supportedLoadTypes",
|
|
label: "Supported load types",
|
|
type: "textarea",
|
|
optional: true,
|
|
placeholder: "CONTAINER, BULK",
|
|
},
|
|
{ name: "isActive", label: "Active", type: "boolean" },
|
|
],
|
|
},
|
|
{
|
|
slug: "priority-configs",
|
|
label: "Priority Rules",
|
|
category: "rules",
|
|
subtitle: "Wagon-count and payment-currency scoring rules",
|
|
searchPlaceholder: "Search priority rules...",
|
|
orderConfig: { field: "displayOrder", label: "Display order" },
|
|
columns: [
|
|
{ id: "type", header: "Type", accessorKey: "type" },
|
|
{ id: "currency", header: "Currency", accessorKey: "currency" },
|
|
{ id: "minWagonCount", header: "Min wagons", accessorKey: "minWagonCount", format: "number" },
|
|
{ id: "maxWagonCount", header: "Max wagons", accessorKey: "maxWagonCount", format: "number" },
|
|
{ id: "scorePoints", header: "Points", accessorKey: "scorePoints", format: "number" },
|
|
activeColumn,
|
|
],
|
|
formFields: [
|
|
{
|
|
name: "type",
|
|
label: "Type",
|
|
type: "select",
|
|
required: true,
|
|
options: PRIORITY_CONFIG_TYPES,
|
|
placeholder: "Wagon count or payment currency",
|
|
},
|
|
{
|
|
name: "currency",
|
|
label: "Currency",
|
|
type: "select",
|
|
optional: true,
|
|
options: [{ label: "None", value: RULE_ENGINE_SELECT_NONE }, ...CURRENCIES],
|
|
placeholder: "Select a currency",
|
|
hideWhen: { field: "type", equals: ["WAGON"] },
|
|
},
|
|
{ name: "minWagonCount", label: "Min wagon count", type: "number", required: true },
|
|
{ name: "maxWagonCount", label: "Max wagon count", type: "number", required: true },
|
|
{ name: "scorePoints", label: "Score points", type: "number", required: true },
|
|
{ name: "isActive", label: "Active", type: "boolean" },
|
|
],
|
|
},
|
|
{
|
|
slug: "service-types",
|
|
label: "Service Types",
|
|
category: "configuration",
|
|
subtitle: "Freight service offerings and booking options",
|
|
searchPlaceholder: "Search service types...",
|
|
supportsSearch: true,
|
|
orderConfig: { field: "displayOrder", label: "Display order" },
|
|
columns: [
|
|
codeColumn("code"),
|
|
{ id: "serviceName", header: "Service name", accessorKey: "serviceName" },
|
|
{ id: "displayOrder", header: "#", accessorKey: "displayOrder", format: "number" },
|
|
{ id: "priorityBonusPoints", header: "Bonus pts", accessorKey: "priorityBonusPoints", format: "number" },
|
|
activeColumn,
|
|
],
|
|
formFields: [
|
|
{ name: "serviceName", label: "Service name", type: "text", required: true },
|
|
{ name: "description", label: "Description", type: "textarea" },
|
|
{ name: "canBeBookedAlone", label: "Can be booked alone", type: "boolean" },
|
|
{ name: "includesFirstMile", label: "Includes first mile", type: "boolean" },
|
|
{ name: "includesLastMile", label: "Includes last mile", type: "boolean" },
|
|
{ name: "includesCustoms", label: "Includes customs", type: "boolean" },
|
|
{ name: "priorityBonusPoints", label: "Priority bonus points", type: "number" },
|
|
{ name: "isActive", label: "Active", type: "boolean" },
|
|
],
|
|
},
|
|
{
|
|
slug: "weight-limit-rules",
|
|
label: "Weight Limit Rules",
|
|
category: "rules",
|
|
subtitle: "VGM limits by container and trade direction",
|
|
searchPlaceholder: "Search weight limit rules...",
|
|
cardTitleKey: "containerType",
|
|
cardSubtitleKey: "tradeDirection",
|
|
columns: [
|
|
{
|
|
id: "containerType",
|
|
header: "Container",
|
|
accessorKey: "containerType",
|
|
format: "entityLabel",
|
|
},
|
|
{ id: "tradeDirection", header: "Direction", accessorKey: "tradeDirection" },
|
|
{ id: "maxVgmTons", header: "Max VGM (t)", accessorKey: "maxVgmTons", format: "number" },
|
|
{
|
|
id: "maxCapacityTons",
|
|
header: "Max capacity (t)",
|
|
accessorKey: "maxCapacityTons",
|
|
format: "number",
|
|
},
|
|
],
|
|
formFields: [
|
|
{
|
|
name: "containerTypeId",
|
|
label: "Container type",
|
|
type: "select",
|
|
required: true,
|
|
placeholder: "Select container type",
|
|
},
|
|
{
|
|
name: "tradeDirection",
|
|
label: "Trade direction",
|
|
type: "select",
|
|
required: true,
|
|
options: TRADE_DIRECTIONS,
|
|
},
|
|
{ name: "maxVgmTons", label: "Max VGM (tons)", type: "number", required: true },
|
|
{
|
|
name: "maxCapacityTons",
|
|
label: "Max capacity (tons)",
|
|
type: "number",
|
|
optional: true,
|
|
description:
|
|
"Hard ceiling — a booking whose line weight exceeds this cannot be created at all. Leave empty for no ceiling (overweight surcharge only).",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
slug: "yards",
|
|
label: "Yards",
|
|
category: "configuration",
|
|
subtitle: "Terminal and yard locations",
|
|
searchPlaceholder: "Search yards...",
|
|
orderConfig: { field: "displayOrder", label: "Display order" },
|
|
columns: [
|
|
codeColumn("code"),
|
|
{ id: "label", header: "Label", accessorKey: "label" },
|
|
{ id: "country", header: "Country", accessorKey: "country" },
|
|
{ id: "displayOrder", header: "Order", accessorKey: "displayOrder", format: "number" },
|
|
activeColumn,
|
|
],
|
|
formFields: [
|
|
{ name: "label", label: "Label", type: "text", required: true },
|
|
{ name: "country", label: "Country", type: "text", required: true },
|
|
{ name: "isActive", label: "Active", type: "boolean" },
|
|
],
|
|
},
|
|
{
|
|
slug: "shipping-lines",
|
|
label: "Shipping Lines",
|
|
category: "configuration",
|
|
subtitle: "Shipping line codes and pricing mappings",
|
|
searchPlaceholder: "Search shipping lines...",
|
|
columns: [
|
|
codeColumn("code"),
|
|
{ id: "label", header: "Label", accessorKey: "label" },
|
|
{ id: "mappedToCode", header: "Mapped to", accessorKey: "mappedToCode" },
|
|
{
|
|
id: "showExtraFeeNotice",
|
|
header: "Extra fee notice",
|
|
accessorKey: "showExtraFeeNotice",
|
|
format: "boolean",
|
|
},
|
|
activeColumn,
|
|
],
|
|
formFields: [
|
|
{ name: "code", label: "Code", type: "text", required: true },
|
|
{ name: "label", label: "Label", type: "text", required: true },
|
|
{ name: "mappedToCode", label: "Mapped to code", type: "text" },
|
|
{ name: "showExtraFeeNotice", label: "Show extra fee notice", type: "boolean" },
|
|
{ name: "isActive", label: "Active", type: "boolean" },
|
|
],
|
|
},
|
|
{
|
|
slug: "rates",
|
|
label: "Rates",
|
|
category: "rules",
|
|
cardTitleKey: "appliesTo",
|
|
cardSubtitleKey: "currency",
|
|
subtitle: "Freight rates and approval workflow",
|
|
searchPlaceholder: "Search rates by type or status...",
|
|
columns: [
|
|
{ id: "appliesTo", header: "Applies to", accessorKey: "appliesTo", format: "code" },
|
|
{ id: "trigger", header: "Trigger", accessorKey: "trigger" },
|
|
{ id: "rateValue", header: "Value", accessorKey: "rateValue", format: "currency" },
|
|
{ id: "rateUnit", header: "Unit", accessorKey: "rateUnit" },
|
|
{ id: "status", header: "Status", accessorKey: "status", format: "rateStatus" },
|
|
],
|
|
formFields: [
|
|
{
|
|
name: "appliesTo",
|
|
label: "Applies to",
|
|
type: "select",
|
|
required: true,
|
|
options: RATE_APPLIES_TO,
|
|
description:
|
|
"Pick what this rate is for. Bulk/Container/Intercity are base freight; Other is an auto-applied surcharge.",
|
|
},
|
|
// ── Surcharge trigger — only when Applies to = Other ──────────────────
|
|
{
|
|
name: "trigger",
|
|
label: "Surcharge trigger",
|
|
type: "select",
|
|
required: true,
|
|
options: RATE_TRIGGERS,
|
|
placeholder: "What makes this surcharge apply?",
|
|
showWhen: { field: "appliesTo", equals: ["OTHER"] },
|
|
},
|
|
// ── Trade direction — Bulk & Container only (intercity is domestic) ───
|
|
{
|
|
name: "tradeDirection",
|
|
label: "Trade direction",
|
|
type: "select",
|
|
required: true,
|
|
options: TRADE_DIRECTIONS.filter((d) => d.value !== "BOTH"),
|
|
showWhen: { field: "appliesTo", equals: ["BULK", "CONTAINER"] },
|
|
},
|
|
// ── Container type — Container & Intercity ────────────────────────────
|
|
{
|
|
name: "containerTypeId",
|
|
label: "Container type",
|
|
type: "select",
|
|
optional: true,
|
|
placeholder: "Select container type (optional)",
|
|
showWhen: { field: "appliesTo", equals: ["CONTAINER", "INTERCITY"] },
|
|
},
|
|
// ── Bulk cargo (leaf commodity) — Bulk & Intercity ───────────────────
|
|
{
|
|
name: "cargoTypeId",
|
|
label: "Bulk cargo type",
|
|
type: "select",
|
|
optional: true,
|
|
placeholder: "Select bulk commodity (optional)",
|
|
showWhen: { field: "appliesTo", equals: ["BULK", "INTERCITY"] },
|
|
},
|
|
{ name: "rateValue", label: "Rate value", type: "number", required: true, suffix: "USD" },
|
|
// Unit choices are driven by the rate shape (appliesTo + trigger). Overweight
|
|
// is always per excess ton, so the unit field is hidden for it — the API
|
|
// forces PER_TON regardless.
|
|
{
|
|
name: "rateUnit",
|
|
label: "Rate unit",
|
|
type: "select",
|
|
required: true,
|
|
optionsFromValues: rateUnitOptions,
|
|
description: "Weighting basis — options depend on what the rate applies to.",
|
|
hideWhen: { field: "trigger", equals: ["OVERWEIGHT"] },
|
|
},
|
|
],
|
|
},
|
|
{
|
|
slug: "approval-rules",
|
|
label: "Approval Rules",
|
|
category: "rules",
|
|
cardTitleKey: "actionLabel",
|
|
cardSubtitleKey: "requiredRole",
|
|
subtitle: "Multi-step booking approval chain",
|
|
searchPlaceholder: "Search approval rules...",
|
|
orderConfig: {
|
|
field: "stepOrder",
|
|
scopeField: "requiresDirectorApproval",
|
|
label: "Step order",
|
|
},
|
|
columns: [
|
|
{
|
|
id: "requiresDirectorApproval",
|
|
header: "Director chain",
|
|
accessorKey: "requiresDirectorApproval",
|
|
format: "boolean",
|
|
},
|
|
{ id: "stepOrder", header: "Step", accessorKey: "stepOrder", format: "number" },
|
|
{ id: "requiredRole", header: "Role", accessorKey: "requiredRole" },
|
|
{ id: "actionLabel", header: "Action", accessorKey: "actionLabel" },
|
|
{ id: "blocksRole", header: "Blocks", accessorKey: "blocksRole" },
|
|
],
|
|
formFields: [
|
|
{ name: "requiresDirectorApproval", label: "Requires director approval chain", type: "boolean" },
|
|
{
|
|
name: "requiredRole",
|
|
label: "Required role",
|
|
type: "select",
|
|
required: true,
|
|
options: APPROVAL_ROLES,
|
|
},
|
|
{ name: "actionLabel", label: "Action label", type: "text", required: true },
|
|
{
|
|
name: "blocksRole",
|
|
label: "Blocks role",
|
|
type: "select",
|
|
optional: true,
|
|
options: [{ label: "None", value: RULE_ENGINE_SELECT_NONE }, ...APPROVAL_ROLES],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
export const RULE_ENGINE_RESOURCE_MAP = Object.fromEntries(
|
|
RULE_ENGINE_RESOURCES.map((r) => [r.slug, r]),
|
|
) as Record<RuleEngineResourceSlug, RuleEngineResourceConfig>;
|
|
|
|
export const getRuleEngineResource = (slug: string): RuleEngineResourceConfig | undefined =>
|
|
RULE_ENGINE_RESOURCE_MAP[slug as RuleEngineResourceSlug];
|
|
|
|
export const ruleEngineResourcePath = (slug: RuleEngineResourceSlug): string => {
|
|
const resource = RULE_ENGINE_RESOURCE_MAP[slug];
|
|
return `${RULE_ENGINE_CATEGORY_BASE_PATH[resource.category]}/${slug}`;
|
|
};
|
|
|
|
export const getCategorySidebarChildren = (
|
|
category: RuleEngineNavCategory,
|
|
): SidebarItem[] =>
|
|
RULE_ENGINE_RESOURCES.filter((r) => r.category === category).map((r) => ({
|
|
label: r.label,
|
|
href: ruleEngineResourcePath(r.slug),
|
|
}));
|
|
|
|
export const DEFAULT_CONFIGURATION_SLUG: RuleEngineResourceSlug = "cargo-types";
|
|
export const DEFAULT_RULES_SLUG: RuleEngineResourceSlug = "priority-configs";
|
|
|
|
/** @deprecated Use DEFAULT_CONFIGURATION_SLUG */
|
|
export const DEFAULT_RULE_ENGINE_SLUG = DEFAULT_CONFIGURATION_SLUG;
|