This commit is contained in:
Marshal
2026-07-15 13:29:01 +00:00
parent c71a0043d6
commit 19c9da28ae
59 changed files with 3008 additions and 284 deletions

View File

@@ -18,6 +18,7 @@ import { Navigate, useLocation, useParams } from "react-router-dom";
import { PageContainer, PageHeader } from "@/components/page";
import ManageRuleEngineOrderDialog from "@/components/ruleEngine/ManageRuleEngineOrderDialog";
import PriorityRuleApprovalsSection from "@/pages/ruleEngine/PriorityRuleApprovalsSection";
import RuleEngineCardGrid from "@/components/ruleEngine/RuleEngineCardGrid";
import RuleEngineFormDialog from "@/components/ruleEngine/RuleEngineFormDialog";
import RuleEngineOrderControls from "@/components/ruleEngine/RuleEngineOrderControls";
@@ -34,6 +35,7 @@ import {
useContainerTypeOptions,
useLiveRateOptions,
useWagonTypeOptions,
usePriorityRuleWorkflow,
useRateWorkflow,
useRuleEngineList,
useRuleEngineMutations,
@@ -142,6 +144,13 @@ const RuleEngineResourcePage = () => {
chainOpen && config?.slug === "approval-rules",
);
// Priority rules never mutate directly: changes are filed for approval and a
// pending queue renders above the table.
const isPriorityRules = config?.slug === "priority-configs";
const priorityWorkflow = usePriorityRuleWorkflow(
Boolean(isPriorityRules && canView),
);
const editingId = editing?.id ? String(editing.id) : undefined;
const usesContainerTypeField = Boolean(
config?.formFields.some((f) => f.name === "containerTypeId"),
@@ -360,9 +369,37 @@ const RuleEngineResourcePage = () => {
currency: "USD",
trigger: isSurcharge ? values.trigger : "ALWAYS",
};
} else if (config.slug === "priority-configs") {
} else if (isPriorityRules) {
// Label is required by the backend but hidden in the UI for now.
payload = { ...values, label: String(Date.now()) };
// Approval workflow: file a change request instead of mutating directly.
// On update, keep the target's existing label rather than a fresh stamp.
if (editing?.id) {
priorityWorkflow.submit.mutate(
{
action: "UPDATE",
priorityConfigId: String(editing.id),
update: { ...values, label: String(editing.label ?? Date.now()) },
},
{
onSuccess: () => {
setFormOpen(false);
setEditing(null);
},
},
);
} else {
priorityWorkflow.submit.mutate(
{ action: "CREATE", create: payload },
{
onSuccess: () => {
setFormOpen(false);
setEditing(null);
},
},
);
}
return;
} else if (config.slug === "weight-limit-rules") {
// Empty max capacity means "no ceiling" — send null explicitly so an
// edit can clear a previously-set ceiling (omitting the key keeps it).
@@ -406,6 +443,15 @@ const RuleEngineResourcePage = () => {
}
/>
{isPriorityRules ? (
<PriorityRuleApprovalsSection
requests={priorityWorkflow.pending.data ?? []}
canDecide={canManage}
approve={priorityWorkflow.approve}
reject={priorityWorkflow.reject}
/>
) : null}
<Card p={0}>
<Stack gap={0}>
<Box px="md" pt="md" pb="sm" w="100%">
@@ -519,7 +565,9 @@ const RuleEngineResourcePage = () => {
}
fields={formFields}
initialRecord={editing}
isSubmitting={create.isPending || update.isPending}
isSubmitting={
create.isPending || update.isPending || priorityWorkflow.submit.isPending
}
selectOptionsLoading={
(config.slug === "cargo-types" && cargoParentOptionsLoading) ||
(usesContainerTypeField && containerTypeOptionsLoading) ||
@@ -557,8 +605,9 @@ const RuleEngineResourcePage = () => {
>
<Stack gap="md">
<Text size="sm">
This will soft-delete the selected {config.label.toLowerCase()}{" "}
record.
{isPriorityRules
? "This files a delete request for approval — the rule is removed once an approver confirms."
: `This will soft-delete the selected ${config.label.toLowerCase()} record.`}
</Text>
<Group justify="flex-end" gap="sm">
<Button variant="default" onClick={() => setDeleteTarget(null)}>
@@ -566,15 +615,25 @@ const RuleEngineResourcePage = () => {
</Button>
<Button
color="red"
loading={remove.isPending}
loading={remove.isPending || priorityWorkflow.submit.isPending}
onClick={() => {
if (!deleteTarget) return;
if (isPriorityRules) {
priorityWorkflow.submit.mutate(
{
action: "DELETE",
priorityConfigId: String(deleteTarget.id),
},
{ onSuccess: () => setDeleteTarget(null) },
);
return;
}
remove.mutate(deleteTarget.id, {
onSuccess: () => setDeleteTarget(null),
});
}}
>
Delete
{isPriorityRules ? "Request delete" : "Delete"}
</Button>
</Group>
</Stack>