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

Freight feature/usermanagement
This commit is contained in:
marshal
2026-07-07 12:05:49 +03:00
committed by GitHub
10 changed files with 224 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { Check, ShieldCheck } from "lucide-react";
import { Check, ShieldCheck, X } from "lucide-react";
import {
Stack,
Group,
@@ -8,6 +8,7 @@ import {
Button,
Box,
Modal,
Textarea,
} from "@mantine/core";
import type { Freight } from "@edr/types";
@@ -30,6 +31,10 @@ export function ContractApprovalStepsCard({
const [confirmOpen, setConfirmOpen] = useState(false);
const [pendingStep, setPendingStep] =
useState<Freight.IContractApprovalStep | null>(null);
const [rejectOpen, setRejectOpen] = useState(false);
const [rejectStepRow, setRejectStepRow] =
useState<Freight.IContractApprovalStep | null>(null);
const [rejectReason, setRejectReason] = useState("");
const steps = useMemo(
() =>
@@ -60,6 +65,28 @@ export function ContractApprovalStepsCard({
);
};
const openReject = (step: Freight.IContractApprovalStep) => {
setRejectStepRow(step);
setRejectReason("");
setRejectOpen(true);
};
const closeReject = () => {
setRejectOpen(false);
setRejectStepRow(null);
setRejectReason("");
};
const trimmedReason = rejectReason.trim();
const runReject = () => {
if (!rejectStepRow || !trimmedReason) return;
mutations.rejectStep.mutate(
{ stepId: rejectStepRow.id, reason: trimmedReason },
{ onSuccess: () => closeReject() },
);
};
const subtitle =
summary.detail ||
(nextPending
@@ -106,8 +133,12 @@ export function ContractApprovalStepsCard({
key={step.id}
step={step}
isNext={nextPending?.id === step.id}
isPending={mutations.approveStep.isPending}
isPending={
mutations.approveStep.isPending ||
mutations.rejectStep.isPending
}
onApprove={() => openApprove(step)}
onReject={() => openReject(step)}
/>
))}
</Stack>
@@ -149,6 +180,54 @@ export function ContractApprovalStepsCard({
</Group>
</Stack>
</Modal>
<Modal
opened={rejectOpen}
onClose={closeReject}
title="Reject this step?"
radius="md"
centered
>
<Stack gap="md">
<Text size="sm" c="dimmed">
Rejecting the{" "}
<Text span fw={600} c="dark">
{rejectStepRow?.requiredRole}
</Text>{" "}
step rejects contract{" "}
<Text span fw={600} c="dark">
{contract.reference}
</Text>{" "}
outright. The customer must create a new contract this cannot be
undone.
</Text>
<Textarea
label="Reason for rejection"
description="Shared with the customer and the approval chain."
placeholder="Explain why this contract is rejected…"
minRows={3}
autosize
withAsterisk
value={rejectReason}
onChange={(e) => setRejectReason(e.currentTarget.value)}
/>
<Group justify="flex-end" gap="sm">
<Button variant="default" radius="md" onClick={closeReject}>
Cancel
</Button>
<Button
color="red"
radius="md"
leftSection={<X size={16} />}
loading={mutations.rejectStep.isPending}
disabled={!trimmedReason}
onClick={runReject}
>
Reject contract
</Button>
</Group>
</Stack>
</Modal>
</>
);
}
@@ -158,11 +237,13 @@ function StepRow({
isNext,
isPending,
onApprove,
onReject,
}: {
step: Freight.IContractApprovalStep;
isNext: boolean;
isPending: boolean;
onApprove: () => void;
onReject: () => void;
}) {
const statusColor =
step.status === "APPROVED"
@@ -222,15 +303,27 @@ function StepRow({
</Group>
<Group gap="xs" wrap="nowrap" style={{ flexShrink: 0 }}>
{isNext && step.status === "PENDING" && (
<Button
size="compact-sm"
color="edr-green"
leftSection={<Check size={14} />}
disabled={isPending}
onClick={onApprove}
>
Approve
</Button>
<>
<Button
size="compact-sm"
color="edr-green"
leftSection={<Check size={14} />}
disabled={isPending}
onClick={onApprove}
>
Approve
</Button>
<Button
size="compact-sm"
variant="light"
color="red"
leftSection={<X size={14} />}
disabled={isPending}
onClick={onReject}
>
Reject
</Button>
</>
)}
<Badge
variant="light"