Release Order plus Storage Allocation Rule and fee

This commit is contained in:
hagiye
2026-06-24 16:21:45 +03:00
180 changed files with 11271 additions and 4928 deletions

View File

@@ -1,3 +1,3 @@
export const API_BASE_URL = 'https://edrfreightapi.triaplc.com';
// export const API_BASE_URL = 'https://edrfreightapi.triaplc.com';
// export const API_BASE_URL = 'http://localhost:3001';
export const API_BASE_URL = 'http://localhost:3001';

View File

@@ -47,7 +47,10 @@ export const vehiclesConfig: FleetResourceConfig = {
],
searchKeys: ["plateNumber", "registrationNumber", "manufacturer", "model", "vehicleType", "status"],
columns: [
{ id: "code", header: "Code", accessorKey: "code", format: "code", size: 110 },
{ id: "plateNumber", header: "Plate Number", accessorKey: "plateNumber", format: "code", size: 130 },
{ id: "powerPlateNo", header: "Power Plate No", accessorKey: "powerPlateNo", format: "code", size: 140 },
{ id: "trailerPlateNo", header: "Trailer Plate No", accessorKey: "trailerPlateNo", format: "code", size: 140 },
{ id: "registrationNumber", header: "Registration", accessorKey: "registrationNumber", format: "code", size: 140 },
{ id: "manufacturer", header: "Manufacturer", accessorKey: "manufacturer", format: "code", size: 140 },
{ id: "model", header: "Model", accessorKey: "model", format: "code", size: 120 },
@@ -59,7 +62,10 @@ export const vehiclesConfig: FleetResourceConfig = {
{ id: "status", header: "Status", accessorKey: "status", format: "statusBadge", size: 100 },
],
formFields: [
{ name: "code", label: "Code", type: "text" },
{ name: "plateNumber", label: "Plate Number", type: "text", required: true },
{ name: "powerPlateNo", label: "Power Plate No", type: "text" },
{ name: "trailerPlateNo", label: "Trailer Plate No", type: "text" },
{ name: "vehicleType", label: "Vehicle Type", type: "select", required: true, options: VEHICLE_TYPE_OPTIONS },
{ name: "manufacturer", label: "Manufacturer", type: "text", required: true },
{ name: "model", label: "Model", type: "text", required: true },
@@ -70,7 +76,10 @@ export const vehiclesConfig: FleetResourceConfig = {
{ name: "description", label: "Description", type: "textarea" },
],
emptyValues: {
code: "",
plateNumber: "",
powerPlateNo: "",
trailerPlateNo: "",
vehicleType: "TRUCK",
manufacturer: "",
model: "",

View File

@@ -1,6 +1,7 @@
import { type ReactNode, useMemo, useState } from "react";
import {
ArrowRight,
ChevronRight,
Eye,
MoreHorizontal,
Printer,
@@ -26,6 +27,7 @@ import {
Stack,
Text,
TextInput,
UnstyledButton,
} from "@mantine/core";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
@@ -73,7 +75,11 @@ const FILTER_OPTIONS: { value: StatusFilter; label: string }[] = [
const vehicleLabel = (record: FirstMileRecord) => {
if (!record.vehicle) return null;
const v = record.vehicle;
return `${v.manufacturer} ${v.model} (${v.plateNumber})`;
const parts = [`${v.manufacturer} ${v.model}`, v.plateNumber];
if (v.code) parts.unshift(v.code);
const plates = [v.powerPlateNo, v.trailerPlateNo].filter(Boolean).join(" / ");
if (plates) parts.push(plates);
return parts.join(" · ");
};
const isAssigned = (record: FirstMileRecord) => Boolean(record.vehicleId);
@@ -89,8 +95,9 @@ const cargoDesc = (r: FirstMileRecord) => {
};
const priceAmount = (r: FirstMileRecord) =>
r.booking?.totalAmount ?? r.advancedPayment;
// First-mile destination is the origin yard (pickup → origin yard)
const destinationYardName = (r: FirstMileRecord) =>
r.booking?.destinationYard?.name ?? "—";
r.booking?.originYard?.label ?? "—";
const contactPersonName = (r: FirstMileRecord) =>
r.booking?.company?.contactPersonName ?? "—";
const contactPhone = (r: FirstMileRecord) =>
@@ -100,7 +107,7 @@ const requestedDate = (r: FirstMileRecord) => {
return d ? new Date(d).toISOString().slice(0, 10) : "—";
};
const serviceTypeName = (r: FirstMileRecord) =>
r.booking?.serviceType?.name ?? "—";
r.booking?.serviceType?.label ?? "—";
const InfoRow = ({ label, value }: { label: string; value: string }) => (
<Stack gap={2}>
@@ -133,7 +140,7 @@ const BookingInfo = ({ record }: { record: FirstMileRecord }) => (
<InfoRow label="Customer" value={customerName(record)} />
<InfoRow label="Service type" value={serviceTypeName(record)} />
<InfoRow label="Pickup location" value={pickupLocation(record)} />
<InfoRow label="Destination yard" value={destinationYardName(record)} />
<InfoRow label="Destination (origin yard)" value={destinationYardName(record)} />
<InfoRow label="Cargo" value={cargoDesc(record)} />
<InfoRow label="Price" value={formatPrice(priceAmount(record))} />
<InfoRow label="Contact" value={contactPersonName(record)} />
@@ -343,10 +350,13 @@ const FirstMilePage = () => {
const vehicleOptions = useMemo(
() =>
(Array.isArray(vehiclesData) ? vehiclesData : []).map((v) => ({
value: v.id,
label: `${v.manufacturer} ${v.model} (${v.plateNumber})`,
})),
(Array.isArray(vehiclesData) ? vehiclesData : []).map((v) => {
const parts = [`${v.manufacturer} ${v.model}`, v.plateNumber];
if (v.code) parts.unshift(v.code);
const plates = [v.powerPlateNo, v.trailerPlateNo].filter(Boolean).join(" / ");
if (plates) parts.push(plates);
return { value: v.id, label: parts.join(" · ") };
}),
[vehiclesData],
);
@@ -585,6 +595,12 @@ const FirstMilePage = () => {
meta: { headerClassName, cellClassName },
cell: ({ row }) => pickupLocation(row.original),
},
{
id: "destination",
header: "Destination",
meta: { headerClassName, cellClassName },
cell: ({ row }) => destinationYardName(row.original),
},
{
id: "cargo",
header: "Cargo",
@@ -865,33 +881,54 @@ const FirstMilePage = () => {
<Text c="dimmed" size="sm" ta="center" py="md">No paid bookings found.</Text>
) : (
filteredPaidBookings.map((b) => (
<Card
<UnstyledButton
key={b.id}
withBorder
padding="sm"
radius="md"
style={{ cursor: "pointer" }}
w="100%"
onClick={() => {
setSelectedBooking(b);
setAcceptStep(2);
}}
style={{
borderRadius: "var(--mantine-radius-md)",
border: "1px solid var(--mantine-color-gray-3)",
padding: "10px 12px",
backgroundColor: "var(--mantine-color-white)",
transition: "background-color 120ms ease, border-color 120ms ease",
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLButtonElement).style.backgroundColor =
"var(--mantine-color-blue-0)";
(e.currentTarget as HTMLButtonElement).style.borderColor =
"var(--mantine-color-blue-4)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLButtonElement).style.backgroundColor =
"var(--mantine-color-white)";
(e.currentTarget as HTMLButtonElement).style.borderColor =
"var(--mantine-color-gray-3)";
}}
>
<Group justify="space-between" wrap="nowrap">
<Stack gap={2}>
<Text fw={600} size="sm">{b.reference}</Text>
<Text size="xs" c="dimmed">{b.company?.name ?? b.company?.companyName ?? "—"}</Text>
</Stack>
<Stack gap={2} align="flex-end">
<Text size="xs" c="dimmed">
{b.originYard?.name ?? "—"} {b.destinationYard?.name ?? "—"}
<Group justify="space-between" wrap="nowrap" gap="sm">
<Stack gap={3} style={{ flex: 1, minWidth: 0 }}>
<Text fw={700} size="sm" c="dark">{b.reference}</Text>
<Text size="xs" c="dimmed" truncate>
{b.company?.name ?? b.company?.companyName ?? "—"}
</Text>
</Stack>
<Stack gap={3} align="flex-end" style={{ flexShrink: 0 }}>
<Text size="xs" c="dimmed">
{b.originYard?.label ?? "—"} {b.destinationYard?.label ?? "—"}
</Text>
<Text size="sm" fw={600} c="blue">
{formatPrice(b.totalAmount)}
</Text>
<Text size="sm" fw={500}>{formatPrice(b.totalAmount)}</Text>
<Text size="xs" c="dimmed">
{b.scheduledDate ? b.scheduledDate.slice(0, 10) : "—"}
</Text>
</Stack>
<ChevronRight size={16} color="var(--mantine-color-gray-5)" />
</Group>
</Card>
</UnstyledButton>
))
)}
</Stack>
@@ -912,8 +949,8 @@ const FirstMilePage = () => {
<InfoRow label="Customer" value={selectedBooking.company?.name ?? selectedBooking.company?.companyName ?? "—"} />
<InfoRow label="Service type" value={selectedBooking.serviceType?.name ?? "—"} />
<InfoRow label="Pickup address" value={selectedBooking.firstMilePickupAddress ?? "—"} />
<InfoRow label="Origin yard" value={selectedBooking.originYard?.name ?? "—"} />
<InfoRow label="Destination yard" value={selectedBooking.destinationYard?.name ?? "—"} />
<InfoRow label="Destination (origin yard)" value={selectedBooking.originYard?.label ?? "—"} />
<InfoRow label="Train destination yard" value={selectedBooking.destinationYard?.label ?? "—"} />
<InfoRow label="Cargo type" value={selectedBooking.cargoType?.name ?? "—"} />
<InfoRow label="Weight (VGM)" value={`${selectedBooking.cargoTotalWeightVgm} t`} />
<InfoRow label="Total amount" value={formatPrice(selectedBooking.totalAmount)} />

View File

@@ -71,7 +71,11 @@ const FILTER_OPTIONS: { value: StatusFilter; label: string }[] = [
const vehicleLabel = (record: LastMileRecord) => {
if (!record.vehicle) return null;
const v = record.vehicle;
return `${v.manufacturer} ${v.model} (${v.plateNumber})`;
const parts = [`${v.manufacturer} ${v.model}`, v.plateNumber];
if (v.code) parts.unshift(v.code);
const plates = [v.powerPlateNo, v.trailerPlateNo].filter(Boolean).join(" / ");
if (plates) parts.push(plates);
return parts.join(" · ");
};
const isAssigned = (record: LastMileRecord) => Boolean(record.vehicleId);
@@ -313,10 +317,13 @@ const LastMilePage = () => {
const vehicleOptions = useMemo(
() =>
(Array.isArray(vehiclesData) ? vehiclesData : []).map((v) => ({
value: v.id,
label: `${v.manufacturer} ${v.model} (${v.plateNumber})`,
})),
(Array.isArray(vehiclesData) ? vehiclesData : []).map((v) => {
const parts = [`${v.manufacturer} ${v.model}`, v.plateNumber];
if (v.code) parts.unshift(v.code);
const plates = [v.powerPlateNo, v.trailerPlateNo].filter(Boolean).join(" / ");
if (plates) parts.push(plates);
return { value: v.id, label: parts.join(" · ") };
}),
[vehiclesData],
);

View File

@@ -18,10 +18,10 @@ export interface FirstMileBooking {
totalAmount: number;
scheduledDate?: string | null;
company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null;
serviceType?: { id: string; name?: string } | null;
originYard?: { id: string; name?: string } | null;
destinationYard?: { id: string; name?: string } | null;
cargoType?: { id: string; name?: string } | null;
serviceType?: { id: string; label?: string } | null;
originYard?: { id: string; label?: string } | null;
destinationYard?: { id: string; label?: string } | null;
cargoType?: { id: string; label?: string } | null;
}
export interface FirstMileVehicle {
@@ -29,6 +29,9 @@ export interface FirstMileVehicle {
plateNumber: string;
manufacturer: string;
model: string;
code?: string | null;
powerPlateNo?: string | null;
trailerPlateNo?: string | null;
}
export interface FirstMileRecord {

View File

@@ -29,6 +29,9 @@ export interface LastMileVehicle {
plateNumber: string;
manufacturer: string;
model: string;
code?: string | null;
powerPlateNo?: string | null;
trailerPlateNo?: string | null;
}
export interface LastMileRecord {

View File

@@ -26,6 +26,9 @@ export interface Vehicle {
capacity: number;
status: VehicleStatus;
description?: string | null;
code?: string | null;
powerPlateNo?: string | null;
trailerPlateNo?: string | null;
createdAt: string;
updatedAt: string;
}