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

feat: implement MANUAL_ONLY status for bookings and update related logic
This commit is contained in:
marshal
2026-08-26 01:02:18 +03:00
committed by GitHub
8 changed files with 257 additions and 36 deletions

View File

@@ -35,6 +35,8 @@ export function SchedulingStatusBadge({ status }: { status?: string | null }) {
ELIGIBLE: "blue",
SCHEDULED: "indigo",
DISPATCHED: "edr-green",
WAITING_FOR_WAGON: "yellow",
MANUAL_ONLY: "orange",
};
return (
<Badge variant="light" color={colors[status] ?? "gray"} size="sm">

View File

@@ -8,6 +8,7 @@ import {
Card,
Group,
Modal,
Radio,
SegmentedControl,
Select,
SimpleGrid,
@@ -23,10 +24,13 @@ import {
Container,
Eye,
FileText,
Flag,
Globe,
Lock,
Pencil,
Plus,
Trash2,
type LucideIcon,
} from "lucide-react";
import { useAuth } from "@/auth/useAuth";
@@ -109,6 +113,39 @@ function directionOf(template: ContractTemplate): string {
: template.code.split("_")[0];
}
/** The three customs options offered in the create-template modal. */
const CUSTOMS_OPTIONS: Array<{
value: string;
label: string;
description: string;
color: string;
icon: LucideIcon;
}> = [
{
value: "true",
label: "With customs clearing",
description:
"The Service Provider clears customs in both Djibouti and Ethiopia on the client's behalf.",
color: "teal",
icon: Globe,
},
{
value: "ethiopian",
label: "Ethiopian customs only",
description:
"The Service Provider clears the Ethiopian side only — Djibouti clearing stays with the client. Used for service types marked “Ethiopian customs only”.",
color: "indigo",
icon: Flag,
},
{
value: "false",
label: "Without customs clearing",
description: "Transport only — the client handles its own declarations.",
color: "gray",
icon: FileText,
},
];
function formatUpdated(value: string): string {
return new Date(value).toLocaleDateString("en-GB", {
day: "numeric",
@@ -281,8 +318,25 @@ function CreateTemplateModal({
};
return (
<Modal opened={opened} onClose={close} title="New bulk contract template" centered>
<Stack gap="md">
<Modal
opened={opened}
onClose={close}
size={640}
radius="lg"
padding="xl"
centered
title={
<div>
<Text fw={600} size="lg">
New bulk contract template
</Text>
<Text size="xs" c="dimmed" mt={2}>
One template per trade direction, customs option and commodity
</Text>
</div>
}
>
<Stack gap="lg">
<div>
<Text size="sm" fw={500} mb={6}>
Trade direction
@@ -299,36 +353,6 @@ function CreateTemplateModal({
/>
</div>
{intercity ? (
<Text size="xs" c="dimmed">
Intercity contracts are domestic and cross no border, so they have
no customs clearing variant one template per cargo type.
</Text>
) : (
<div>
<Text size="sm" fw={500} mb={6}>
Customs clearing
</Text>
<SegmentedControl
fullWidth
value={withCustoms}
onChange={setWithCustoms}
data={[
{ value: "true", label: "With customs clearing" },
{ value: "ethiopian", label: "Ethiopian customs only" },
{ value: "false", label: "Without customs clearing" },
]}
/>
{withCustoms === "ethiopian" && (
<Text size="xs" c="dimmed" mt={6}>
Used for service types marked Ethiopian customs only: the
Service Provider clears the Ethiopian side, Djibouti clearing
stays with the client.
</Text>
)}
</div>
)}
<Select
label="Bulk cargo type"
description="Only cargo types with “has contract template” enabled are listed"
@@ -340,6 +364,62 @@ function CreateTemplateModal({
nothingFoundMessage="No cargo type allows contract templates yet — enable the flag on the cargo type first"
/>
{intercity ? (
<Text size="xs" c="dimmed">
Intercity contracts are domestic and cross no border, so they have
no customs clearing variant one template per cargo type.
</Text>
) : (
<Radio.Group
value={withCustoms}
onChange={setWithCustoms}
label="Customs clearing"
styles={{ label: { fontWeight: 500, marginBottom: 6 } }}
>
<Stack gap="xs">
{CUSTOMS_OPTIONS.map((option) => {
const checked = withCustoms === option.value;
return (
<Radio.Card
key={option.value}
value={option.value}
radius="md"
p="sm"
style={{
borderColor: checked
? `var(--mantine-color-${option.color}-6)`
: undefined,
backgroundColor: checked
? `var(--mantine-color-${option.color}-light)`
: undefined,
}}
>
<Group wrap="nowrap" align="flex-start" gap="sm">
<Radio.Indicator mt={2} color={option.color} />
<ThemeIcon
size={34}
radius="md"
variant="light"
color={option.color}
>
<option.icon size={17} strokeWidth={1.75} />
</ThemeIcon>
<div>
<Text size="sm" fw={500}>
{option.label}
</Text>
<Text size="xs" c="dimmed" lh={1.45}>
{option.description}
</Text>
</div>
</Group>
</Radio.Card>
);
})}
</Stack>
</Radio.Group>
)}
<Group justify="flex-end" gap="sm">
<Button variant="default" onClick={close}>
Cancel

View File

@@ -8,7 +8,9 @@ export type SchedulingStatus =
| "ELIGIBLE"
| "SCHEDULED"
| "DISPATCHED"
| "WAITING_FOR_WAGON";
| "WAITING_FOR_WAGON"
/** Removed from a train by staff — auto-allocation skips it; manual assign only. */
| "MANUAL_ONLY";
export type TrainScheduleStatus =
| "DRAFT"