mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 19:58:11 +00:00
feat(bookings): Implement data-driven service type selection and configuration
This commit is contained in:
@@ -106,16 +106,10 @@ export default function NewBookingPage() {
|
|||||||
: Number(data.cargoWeight || 0);
|
: Number(data.cargoWeight || 0);
|
||||||
|
|
||||||
// ── Reference data lookups ──────────────────────────────────────────
|
// ── Reference data lookups ──────────────────────────────────────────
|
||||||
const services = referenceData?.service ?? [];
|
|
||||||
const shippingLines = referenceData?.shipping_line ?? [];
|
const shippingLines = referenceData?.shipping_line ?? [];
|
||||||
const cargoTree = referenceData?.cargo_type ?? [];
|
const cargoTree = referenceData?.cargo_type ?? [];
|
||||||
const containerGroups = referenceData?.containers ?? [];
|
const containerGroups = referenceData?.containers ?? [];
|
||||||
|
|
||||||
const findServiceTypeId = (): string => {
|
|
||||||
const code = data.serviceType === "rail" ? "RAIL" : "RAIL_AND_FORWARDING";
|
|
||||||
return services.find((s) => s.code === code)?.id ?? services[0]?.id ?? "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const findShippingLineId = (name: string): string | undefined =>
|
const findShippingLineId = (name: string): string | undefined =>
|
||||||
shippingLines.find((l) => l.name === name)?.id;
|
shippingLines.find((l) => l.name === name)?.id;
|
||||||
|
|
||||||
@@ -127,32 +121,32 @@ export default function NewBookingPage() {
|
|||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedChild =
|
const bulkChild = cargoTree
|
||||||
data.cargoType !== "container" && data.bulkCommoditytype
|
.flatMap((g) => g.children ?? [])
|
||||||
? cargoTree
|
.find((c) => c.id === data.bulkCommoditytype);
|
||||||
.find((g) => g.code.toLowerCase() === data.freightType)
|
|
||||||
?.children?.find((c) => c.name === data.bulkCommoditytype)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const cargoTypeId = selectedChild?.id;
|
const cargoTypeId =
|
||||||
|
data.cargoType === "bulk" ? data.bulkCommoditytype : undefined;
|
||||||
|
|
||||||
const cargoFreeText =
|
const cargoFreeText = bulkChild?.show_free_text_box
|
||||||
data.cargoType === "container"
|
? data.cargoFreeText
|
||||||
? undefined
|
: undefined;
|
||||||
: selectedChild?.show_free_text_box
|
|
||||||
? data.cargoFreeText
|
const serviceType = referenceData?.service.find(
|
||||||
: undefined;
|
(s) => s.id === data.serviceTypeId,
|
||||||
|
)!;
|
||||||
|
|
||||||
// ── Build API payload ───────────────────────────────────────────────
|
// ── Build API payload ───────────────────────────────────────────────
|
||||||
const apiPayload: CreateBookingPayload = {
|
const apiPayload: CreateBookingPayload = {
|
||||||
scheduledDate: new Date().toISOString(),
|
scheduledDate: new Date().toISOString(),
|
||||||
contractType:
|
contractType:
|
||||||
data.contractType.toUpperCase() as CreateBookingPayload["contractType"],
|
data.contractType.toUpperCase() as CreateBookingPayload["contractType"],
|
||||||
serviceTypeId: findServiceTypeId(),
|
serviceTypeId: data.serviceTypeId,
|
||||||
equipmentReturn:
|
equipmentReturn:
|
||||||
data.equipmentReturn === "with_return"
|
data.equipmentReturn === "with_return"
|
||||||
? "WITH_RETURN"
|
? "WITH_RETURN"
|
||||||
: "WITHOUT_RETURN",
|
: "WITHOUT_RETURN",
|
||||||
|
paymentCurrency: "USD",
|
||||||
originYardId: data.originYard,
|
originYardId: data.originYard,
|
||||||
destinationYardId: data.destinationYard,
|
destinationYardId: data.destinationYard,
|
||||||
tradeDirection: direction!,
|
tradeDirection: direction!,
|
||||||
@@ -180,10 +174,10 @@ export default function NewBookingPage() {
|
|||||||
...(data.contractType === "renewal" && data.previousContractRef
|
...(data.contractType === "renewal" && data.previousContractRef
|
||||||
? { pnrCode: data.previousContractRef }
|
? { pnrCode: data.previousContractRef }
|
||||||
: {}),
|
: {}),
|
||||||
...(data.serviceType === "rail_forwarding" && data.firstMile.enabled
|
...(serviceType.includesFirstMile && data.firstMile.enabled
|
||||||
? { firstMilePickupAddress: data.firstMile.pickUpAddress }
|
? { firstMilePickupAddress: data.firstMile.pickUpAddress }
|
||||||
: {}),
|
: {}),
|
||||||
...(data.serviceType === "rail_forwarding" && data.lastMile.enabled
|
...(serviceType.includesLastMile && data.lastMile.enabled
|
||||||
? { lastMileDeliveryAddress: data.lastMile.deliveryAddress }
|
? { lastMileDeliveryAddress: data.lastMile.deliveryAddress }
|
||||||
: {}),
|
: {}),
|
||||||
...(data.shippingLine
|
...(data.shippingLine
|
||||||
@@ -265,7 +259,9 @@ export default function NewBookingPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{step === 1 && <Step1ContractType form={form} />}
|
{step === 1 && <Step1ContractType form={form} />}
|
||||||
{step === 2 && <Step2ServiceType form={form} />}
|
{step === 2 && (
|
||||||
|
<Step2ServiceType referenceData={referenceData} form={form} />
|
||||||
|
)}
|
||||||
{step === 3 && (
|
{step === 3 && (
|
||||||
<Step4Route
|
<Step4Route
|
||||||
form={form}
|
form={form}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const bookingFormSchema = z
|
|||||||
.object({
|
.object({
|
||||||
contractType: z.enum(["new", "renewal"], "Select a contract type."),
|
contractType: z.enum(["new", "renewal"], "Select a contract type."),
|
||||||
previousContractRef: z.string(),
|
previousContractRef: z.string(),
|
||||||
serviceType: z.enum(["rail", "rail_forwarding"], "Select a service type."),
|
serviceTypeId: z.string("Select a service type."),
|
||||||
firstMile: z
|
firstMile: z
|
||||||
.object({
|
.object({
|
||||||
enabled: z.boolean().default(false),
|
enabled: z.boolean().default(false),
|
||||||
@@ -247,7 +247,7 @@ export const initialBookingFormValues: DeepPartial<BookingFormValues> = {
|
|||||||
export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
|
export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
|
||||||
1: ["contractType", "previousContractRef"],
|
1: ["contractType", "previousContractRef"],
|
||||||
2: [
|
2: [
|
||||||
"serviceType",
|
"serviceTypeId",
|
||||||
"firstMile",
|
"firstMile",
|
||||||
"lastMile",
|
"lastMile",
|
||||||
"equipmentReturn",
|
"equipmentReturn",
|
||||||
|
|||||||
@@ -1,18 +1,52 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { Controller, type UseFormReturn } from "react-hook-form";
|
import { Controller, type UseFormReturn } from "react-hook-form";
|
||||||
import { FileText, Package, Train, Truck } from "lucide-react";
|
import { FileText, Train, Truck } from "lucide-react";
|
||||||
import { Badge, Switch, TextInput } from "@mantine/core";
|
import { Switch, TextInput } from "@mantine/core";
|
||||||
import { BookingFormInputValues, type BookingFormValues } from "./schema";
|
import { BookingFormInputValues, type BookingFormValues } from "./schema";
|
||||||
import { OptionCard, OptionFieldError, StepHeader } from "./shared";
|
import { OptionCard, OptionFieldError, StepHeader } from "./shared";
|
||||||
|
|
||||||
type BookingForm = UseFormReturn<BookingFormInputValues, any, BookingFormValues>;
|
import type { Freight } from "@edr/types";
|
||||||
|
|
||||||
export function Step2ServiceType({ form }: { form: BookingForm }) {
|
type BookingForm = UseFormReturn<
|
||||||
const serviceType = form.watch("serviceType");
|
BookingFormInputValues,
|
||||||
|
any,
|
||||||
|
BookingFormValues
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function Step2ServiceType({
|
||||||
|
form,
|
||||||
|
referenceData,
|
||||||
|
}: {
|
||||||
|
form: BookingForm;
|
||||||
|
|
||||||
|
referenceData?: Freight.BookingReferenceData;
|
||||||
|
}) {
|
||||||
|
const serviceTypeId = form.watch("serviceTypeId");
|
||||||
|
const serviceType = referenceData?.service.find(
|
||||||
|
(s) => s.id === serviceTypeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { includesCustoms, includesFirstMile, includesLastMile } =
|
||||||
|
serviceType ?? {};
|
||||||
const firstMileEnabled = form.watch("firstMile.enabled");
|
const firstMileEnabled = form.watch("firstMile.enabled");
|
||||||
const lastMileEnabled = form.watch("lastMile.enabled");
|
const lastMileEnabled = form.watch("lastMile.enabled");
|
||||||
|
|
||||||
const prevServiceType = useRef(serviceType);
|
const prevServiceType = useRef(serviceType);
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValue(
|
||||||
|
"firstMile",
|
||||||
|
{ enabled: false, pickUpAddress: "" },
|
||||||
|
{ shouldValidate: true },
|
||||||
|
);
|
||||||
|
}, [includesFirstMile]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValue(
|
||||||
|
"lastMile",
|
||||||
|
{ enabled: false, deliveryAddress: "" },
|
||||||
|
{ shouldValidate: true },
|
||||||
|
);
|
||||||
|
}, [includesLastMile]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prev = prevServiceType.current;
|
const prev = prevServiceType.current;
|
||||||
@@ -20,35 +54,12 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
|
|||||||
|
|
||||||
if (!prev || prev === serviceType) return;
|
if (!prev || prev === serviceType) return;
|
||||||
|
|
||||||
if (serviceType === "rail") {
|
if (!includesCustoms)
|
||||||
form.setValue(
|
|
||||||
"firstMile",
|
|
||||||
{ enabled: false, pickUpAddress: "" },
|
|
||||||
{ shouldDirty: true, shouldValidate: true },
|
|
||||||
);
|
|
||||||
form.setValue(
|
|
||||||
"lastMile",
|
|
||||||
{ enabled: false, deliveryAddress: "" },
|
|
||||||
{ shouldDirty: true, shouldValidate: true },
|
|
||||||
);
|
|
||||||
form.setValue("equipmentReturn", "with_return", { shouldDirty: true });
|
|
||||||
form.setValue("customsClearingEnabled", false, { shouldDirty: true });
|
form.setValue("customsClearingEnabled", false, { shouldDirty: true });
|
||||||
} else if (serviceType === "rail_forwarding") {
|
}, [serviceTypeId, form]);
|
||||||
form.setValue(
|
|
||||||
"firstMile",
|
|
||||||
{ enabled: false, pickUpAddress: "" },
|
|
||||||
{ shouldDirty: false, shouldValidate: false },
|
|
||||||
);
|
|
||||||
form.setValue(
|
|
||||||
"lastMile",
|
|
||||||
{ enabled: false, deliveryAddress: "" },
|
|
||||||
{ shouldDirty: false, shouldValidate: false },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [serviceType, form]);
|
|
||||||
|
|
||||||
const showServiceSections = serviceType === "rail_forwarding";
|
|
||||||
|
|
||||||
|
const showServiceSections =
|
||||||
|
includesCustoms || includesFirstMile || includesLastMile;
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<StepHeader
|
<StepHeader
|
||||||
@@ -57,44 +68,29 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="serviceType"
|
name="serviceTypeId"
|
||||||
control={form.control}
|
control={form.control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<div>
|
<div>
|
||||||
<div className="grid gap-3 md:grid-cols-2">
|
<div className="grid gap-3 md:grid-cols-2">
|
||||||
<OptionCard
|
{referenceData?.service
|
||||||
selected={serviceType === "rail"}
|
.filter((s) => s.canBeBookedAlone)
|
||||||
onClick={() => field.onChange("rail")}
|
.map((s) => {
|
||||||
>
|
return (
|
||||||
<div className="mb-2 flex h-9 w-9 items-center justify-center rounded-lg bg-indigo-100">
|
<OptionCard
|
||||||
<Train className="h-4 w-4 text-indigo-600" />
|
selected={field.value === s.id}
|
||||||
</div>
|
onClick={() => field.onChange(s.id)}
|
||||||
<p className="font-semibold">Rail Transport Only</p>
|
>
|
||||||
<p className="mt-0.5 text-xs text-gray-500">
|
<div className="mb-2 flex h-9 w-9 items-center justify-center rounded-lg bg-indigo-100">
|
||||||
Rail transport along the EDR corridor, with optional
|
<Train className="h-4 w-4 text-indigo-600" />
|
||||||
first/last mile trucking.
|
</div>
|
||||||
</p>
|
<p className="font-semibold">{s.serviceName}</p>
|
||||||
<Badge color="indigo" variant="light" mt="xs" size="sm">
|
<p className="mt-0.5 text-xs text-gray-500">
|
||||||
Option A
|
{s.description}
|
||||||
</Badge>
|
</p>
|
||||||
</OptionCard>
|
</OptionCard>
|
||||||
|
);
|
||||||
<OptionCard
|
})}
|
||||||
selected={serviceType === "rail_forwarding"}
|
|
||||||
onClick={() => field.onChange("rail_forwarding")}
|
|
||||||
>
|
|
||||||
<div className="mb-2 flex h-9 w-9 items-center justify-center rounded-lg bg-emerald-100">
|
|
||||||
<Package className="h-4 w-4 text-emerald-600" />
|
|
||||||
</div>
|
|
||||||
<p className="font-semibold">Logistics</p>
|
|
||||||
<p className="mt-0.5 text-xs text-gray-500">
|
|
||||||
Rail transport plus documentation, customs liaison, and a
|
|
||||||
dedicated coordinator.
|
|
||||||
</p>
|
|
||||||
<Badge color="edr-green" variant="light" mt="xs" size="sm">
|
|
||||||
Option B
|
|
||||||
</Badge>
|
|
||||||
</OptionCard>
|
|
||||||
</div>
|
</div>
|
||||||
<OptionFieldError error={fieldState.error} />
|
<OptionFieldError error={fieldState.error} />
|
||||||
</div>
|
</div>
|
||||||
@@ -104,112 +100,120 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
|
|||||||
{showServiceSections && (
|
{showServiceSections && (
|
||||||
<div className="divide-y divide-gray-200 rounded-xl border border-gray-200">
|
<div className="divide-y divide-gray-200 rounded-xl border border-gray-200">
|
||||||
{/* First Mile */}
|
{/* First Mile */}
|
||||||
<div className="p-4">
|
{includesFirstMile && (
|
||||||
<Controller
|
<div className="p-4">
|
||||||
name="firstMile.enabled"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<div className="flex items-start justify-between gap-4">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium">First Mile — Pick-up</p>
|
|
||||||
<p className="mt-0.5 text-xs text-gray-500">
|
|
||||||
Truck pick-up from your premises (Door to Port) to the
|
|
||||||
origin rail yard.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.currentTarget.checked;
|
|
||||||
field.onChange(value);
|
|
||||||
if (!value) {
|
|
||||||
form.setValue("firstMile.pickUpAddress", "", {
|
|
||||||
shouldDirty: true,
|
|
||||||
shouldValidate: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="edr-green"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{firstMileEnabled && (
|
|
||||||
<Controller
|
<Controller
|
||||||
name="firstMile.pickUpAddress"
|
name="firstMile.enabled"
|
||||||
control={form.control}
|
control={form.control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field }) => (
|
||||||
<TextInput
|
<div className="flex items-start justify-between gap-4">
|
||||||
{...field}
|
<div className="flex items-start gap-3">
|
||||||
mt="sm"
|
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
|
||||||
placeholder="Pick-up address *"
|
<div>
|
||||||
error={fieldState.error?.message}
|
<p className="text-sm font-medium">
|
||||||
radius="md"
|
First Mile — Pick-up
|
||||||
/>
|
</p>
|
||||||
|
<p className="mt-0.5 text-xs text-gray-500">
|
||||||
|
Truck pick-up from your premises (Door to Port) to the
|
||||||
|
origin rail yard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.currentTarget.checked;
|
||||||
|
field.onChange(value);
|
||||||
|
if (!value) {
|
||||||
|
form.setValue("firstMile.pickUpAddress", "", {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldValidate: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color="edr-green"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
{firstMileEnabled && (
|
||||||
</div>
|
<Controller
|
||||||
|
name="firstMile.pickUpAddress"
|
||||||
|
control={form.control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextInput
|
||||||
|
{...field}
|
||||||
|
mt="sm"
|
||||||
|
placeholder="Pick-up address *"
|
||||||
|
error={fieldState.error?.message}
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Last Mile */}
|
{/* Last Mile */}
|
||||||
<div className="p-4">
|
{includesLastMile && (
|
||||||
<Controller
|
<div className="p-4">
|
||||||
name="lastMile.enabled"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<div className="flex items-start justify-between gap-4">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium">Last Mile — Delivery</p>
|
|
||||||
<p className="mt-0.5 text-xs text-gray-500">
|
|
||||||
Truck delivery from the destination rail yard to the
|
|
||||||
final address (Port to Door).
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.currentTarget.checked;
|
|
||||||
field.onChange(value);
|
|
||||||
if (!value) {
|
|
||||||
form.setValue("lastMile.deliveryAddress", "", {
|
|
||||||
shouldDirty: true,
|
|
||||||
shouldValidate: true,
|
|
||||||
});
|
|
||||||
form.setValue("equipmentReturn", "with_return", {
|
|
||||||
shouldDirty: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="edr-green"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{lastMileEnabled && (
|
|
||||||
<Controller
|
<Controller
|
||||||
name="lastMile.deliveryAddress"
|
name="lastMile.enabled"
|
||||||
control={form.control}
|
control={form.control}
|
||||||
render={({ field, fieldState }) => (
|
render={({ field }) => (
|
||||||
<TextInput
|
<div className="flex items-start justify-between gap-4">
|
||||||
{...field}
|
<div className="flex items-start gap-3">
|
||||||
mt="sm"
|
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
|
||||||
placeholder="Delivery address *"
|
<div>
|
||||||
error={fieldState.error?.message}
|
<p className="text-sm font-medium">
|
||||||
radius="md"
|
Last Mile — Delivery
|
||||||
/>
|
</p>
|
||||||
|
<p className="mt-0.5 text-xs text-gray-500">
|
||||||
|
Truck delivery from the destination rail yard to the
|
||||||
|
final address (Port to Door).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.currentTarget.checked;
|
||||||
|
field.onChange(value);
|
||||||
|
if (!value) {
|
||||||
|
form.setValue("lastMile.deliveryAddress", "", {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldValidate: true,
|
||||||
|
});
|
||||||
|
form.setValue("equipmentReturn", "with_return", {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color="edr-green"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
{lastMileEnabled && (
|
||||||
</div>
|
<Controller
|
||||||
|
name="lastMile.deliveryAddress"
|
||||||
|
control={form.control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextInput
|
||||||
|
{...field}
|
||||||
|
mt="sm"
|
||||||
|
placeholder="Delivery address *"
|
||||||
|
error={fieldState.error?.message}
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Equipment Return */}
|
{/* Equipment Return */}
|
||||||
{lastMileEnabled && (
|
{includesLastMile && lastMileEnabled && (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Controller
|
<Controller
|
||||||
name="equipmentReturn"
|
name="equipmentReturn"
|
||||||
@@ -228,7 +232,9 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
|
|||||||
checked={field.value === "with_return"}
|
checked={field.value === "with_return"}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
field.onChange(
|
field.onChange(
|
||||||
e.currentTarget.checked ? "with_return" : "without_return",
|
e.currentTarget.checked
|
||||||
|
? "with_return"
|
||||||
|
: "without_return",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
color="edr-green"
|
color="edr-green"
|
||||||
@@ -240,31 +246,35 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Customs Clearing */}
|
{/* Customs Clearing */}
|
||||||
<div className="p-4">
|
{includesCustoms && (
|
||||||
<Controller
|
<div className="p-4">
|
||||||
name="customsClearingEnabled"
|
<Controller
|
||||||
control={form.control}
|
name="customsClearingEnabled"
|
||||||
render={({ field }) => (
|
control={form.control}
|
||||||
<div className="flex items-start justify-between gap-4">
|
render={({ field }) => (
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<FileText className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
|
<div className="flex items-start gap-3">
|
||||||
<div>
|
<FileText className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
|
||||||
<p className="text-sm font-medium">Customs Clearing Service</p>
|
<div>
|
||||||
<p className="mt-0.5 text-xs text-gray-500">
|
<p className="text-sm font-medium">
|
||||||
EDR handles customs documentation and clearance on your
|
Customs Clearing Service
|
||||||
behalf.
|
</p>
|
||||||
</p>
|
<p className="mt-0.5 text-xs text-gray-500">
|
||||||
|
EDR handles customs documentation and clearance on
|
||||||
|
your behalf.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onChange={(e) => field.onChange(e.currentTarget.checked)}
|
||||||
|
color="edr-green"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
)}
|
||||||
checked={field.value}
|
/>
|
||||||
onChange={(e) => field.onChange(e.currentTarget.checked)}
|
</div>
|
||||||
color="edr-green"
|
)}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ export * from "./dropdown_settings";
|
|||||||
export * from "./overview";
|
export * from "./overview";
|
||||||
|
|
||||||
export enum TradeDirection {
|
export enum TradeDirection {
|
||||||
IMPORT = 'IMPORT',
|
IMPORT = "IMPORT",
|
||||||
EXPORT = 'EXPORT',
|
EXPORT = "EXPORT",
|
||||||
BOTH = 'BOTH',
|
BOTH = "BOTH",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PriorityType {
|
export enum PriorityType {
|
||||||
USD_PAYER = 'USD_PAYER',
|
USD_PAYER = "USD_PAYER",
|
||||||
RAIL_AND_FORWARDING = 'RAIL_AND_FORWARDING',
|
RAIL_AND_FORWARDING = "RAIL_AND_FORWARDING",
|
||||||
GOVERNMENT_ACCOUNT = 'GOVERNMENT_ACCOUNT',
|
GOVERNMENT_ACCOUNT = "GOVERNMENT_ACCOUNT",
|
||||||
HIGH_VOLUME_SHIPMENT = 'HIGH_VOLUME_SHIPMENT',
|
HIGH_VOLUME_SHIPMENT = "HIGH_VOLUME_SHIPMENT",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bonus applied to government bookings so they outrank commercial priority. */
|
/** Bonus applied to government bookings so they outrank commercial priority. */
|
||||||
@@ -26,19 +26,19 @@ export interface GovernmentBookingFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ExceededAction {
|
export enum ExceededAction {
|
||||||
WARNING_ONLY = 'WARNING_ONLY',
|
WARNING_ONLY = "WARNING_ONLY",
|
||||||
HARD_BLOCK = 'HARD_BLOCK',
|
HARD_BLOCK = "HARD_BLOCK",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CalculationMethod {
|
export enum CalculationMethod {
|
||||||
PER_TON = 'PER_TON',
|
PER_TON = "PER_TON",
|
||||||
FLAT_FEE = 'FLAT_FEE',
|
FLAT_FEE = "FLAT_FEE",
|
||||||
PERCENTAGE = 'PERCENTAGE',
|
PERCENTAGE = "PERCENTAGE",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FreightType {
|
export enum FreightType {
|
||||||
Container = 'CONTAINER',
|
Container = "CONTAINER",
|
||||||
Bulk = 'BULK',
|
Bulk = "BULK",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BookingStatus {
|
export enum BookingStatus {
|
||||||
@@ -390,6 +390,18 @@ export interface BookingReferenceService {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
serviceName: string;
|
||||||
|
description?: string | null | undefined;
|
||||||
|
canBeBookedAlone: boolean;
|
||||||
|
includesFirstMile: boolean;
|
||||||
|
includesLastMile: boolean;
|
||||||
|
includesCustoms: boolean;
|
||||||
|
priorityBonusPoints: number;
|
||||||
|
isActive: boolean;
|
||||||
|
displayOrder: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt?: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookingReferenceShippingLine {
|
export interface BookingReferenceShippingLine {
|
||||||
@@ -462,31 +474,34 @@ export interface CreateBookingContainerDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateBookingDto {
|
export interface CreateBookingDto {
|
||||||
reference?: string;
|
freightShapeValidation?: boolean | undefined;
|
||||||
companyId?: string;
|
reference?: string | undefined;
|
||||||
trainId?: string;
|
isGovernment?: boolean | undefined;
|
||||||
trainScheduleId?: string;
|
governmentInstitution?: string | undefined;
|
||||||
|
companyId?: string | undefined;
|
||||||
|
trainId?: string | undefined;
|
||||||
|
trainScheduleId?: string | undefined;
|
||||||
scheduledDate: string;
|
scheduledDate: string;
|
||||||
contractType: "NEW" | "RENEWAL";
|
contractType: string;
|
||||||
previousContractId?: string;
|
previousContractId?: string | undefined;
|
||||||
serviceTypeId: string;
|
serviceTypeId: string;
|
||||||
firstMilePickupAddress?: string;
|
firstMilePickupAddress?: string | undefined;
|
||||||
lastMileDeliveryAddress?: string;
|
lastMileDeliveryAddress?: string | undefined;
|
||||||
equipmentReturn: "WITH_RETURN" | "WITHOUT_RETURN" | "NA";
|
equipmentReturn: string;
|
||||||
originYardId: string;
|
originYardId: string;
|
||||||
destinationYardId: string;
|
destinationYardId: string;
|
||||||
tradeDirection: "IMPORT" | "EXPORT" | "DOMESTIC";
|
tradeDirection: string;
|
||||||
freightType: FreightType;
|
freightType: string;
|
||||||
cargoTypeId?: string;
|
cargoTypeId?: string | undefined;
|
||||||
cargoFreeText?: string;
|
cargoFreeText?: string | undefined;
|
||||||
shippingLineId?: string;
|
shippingLineId?: string | undefined;
|
||||||
cargoTotalWeightVgm: number;
|
cargoTotalWeightVgm: number;
|
||||||
isHazardous?: boolean;
|
isHazardous?: boolean | undefined;
|
||||||
paymentCurrency: "ETB" | "USD";
|
paymentCurrency: string;
|
||||||
pnrCode?: string;
|
pnrCode?: string | undefined;
|
||||||
startDate?: string;
|
startDate?: string | undefined;
|
||||||
endDate?: string;
|
endDate?: string | undefined;
|
||||||
financialTerms?: string;
|
financialTerms?: string | undefined;
|
||||||
containers?: CreateBookingContainerDto[];
|
containers?: CreateBookingContainerDto[];
|
||||||
allowConsolidation?: boolean;
|
allowConsolidation?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user