feat: implement shipping line bookings management

- Add ShippingLineBookingsPage for listing and managing shipping line bookings.
- Create ShippingLineDocumentsModal for document uploads related to bookings.
- Introduce ShippingLineInitiateModal for initiating new shipping line bookings.
- Implement booking document state management with booking-doc-state utility.
- Add shipping line bookings service for API interactions.
- Update index to export new components and services.
- Enhance types for freight to include shipping line credits.
This commit is contained in:
marshalyordanos
2026-08-13 15:54:40 +03:00
parent 9aae132dd4
commit 9fff469ffa
50 changed files with 4485 additions and 77 deletions

View File

@@ -43,6 +43,7 @@ import {
useCargoTypeParentOptions,
useContainerTypeOptions,
useLiveRateOptions,
useShippingLineCompanyOptions,
useWagonTypeOptions,
useYardOptions,
type YardOption,
@@ -93,6 +94,20 @@ const yardOptionsForLegEnd = (
values: Record<string, unknown>,
end: "origin" | "destination",
): { label: string; value: string }[] => {
// A shipping-line rate names its shape in its own fields and is always
// import; map it onto the appliesTo/direction pair the rest of this function
// reads so the country narrowing is shared rather than duplicated.
if (values.isShippingLineRate === true) {
if (!values.shippingLineCompanyId) return [];
const isBase = values.shippingLineRateKind === "BASE";
values = {
...values,
appliesTo: isBase
? String(values.shippingLineCargoKind ?? "")
: "OTHER",
tradeDirection: "IMPORT",
};
}
const appliesTo = String(values.appliesTo ?? "");
let country: string | undefined;
if (appliesTo === "INTERCITY") {
@@ -280,6 +295,13 @@ const RuleEngineResourcePage = () => {
useContainerTypeOptions(false, usesContainerTypeField);
const { data: liveRateOptions, isLoading: liveRateOptionsLoading } =
useLiveRateOptions(usesLiveRateField);
const usesShippingLineField = Boolean(
config?.formFields.some((f) => f.name === "shippingLineCompanyId"),
);
const {
data: shippingLineOptions,
isLoading: shippingLineOptionsLoading,
} = useShippingLineCompanyOptions(usesShippingLineField);
const { data: wagonTypeOptions, isLoading: wagonTypeOptionsLoading } =
useWagonTypeOptions(usesWagonTypeField);
const usesYardField = Boolean(
@@ -390,6 +412,13 @@ const RuleEngineResourcePage = () => {
),
};
}
if (field.name === "shippingLineCompanyId") {
return {
...field,
type: "select" as const,
options: shippingLineOptions ?? [],
};
}
if (field.name === "rateId") {
return {
...field,
@@ -612,7 +641,39 @@ const RuleEngineResourcePage = () => {
const handleFormSubmit = (values: Record<string, unknown>) => {
let payload = values;
if (config.slug === "rates") {
if (config.slug === "rates" && values.isShippingLineRate === true) {
// A shipping-line rate asks its shape as "base freight vs surcharge" +
// "container vs bulk"; the API takes the same appliesTo/trigger pair as a
// customer rate, so translate here and drop the form-only fields. Always
// import (the only direction a line ships) and always USD.
const {
isShippingLineRate: _toggle,
shippingLineRateKind,
shippingLineCargoKind,
...rest
} = values;
void _toggle;
const isBase = shippingLineRateKind === "BASE";
payload = {
...rest,
appliesTo: isBase ? String(shippingLineCargoKind ?? "CONTAINER") : "OTHER",
trigger: isBase ? "ALWAYS" : values.trigger,
tradeDirection: "IMPORT",
currency: "USD",
};
if (editing?.id && editing.status === "LIVE") {
rateChangeWorkflow.submit.mutate(
{ rateId: String(editing.id), update: payload },
{
onSuccess: () => {
setFormOpen(false);
setEditing(null);
},
},
);
return;
}
} else if (config.slug === "rates") {
// Base-freight categories have no surcharge trigger field — the engine
// treats them as ALWAYS. Surcharges (Applies to = Other) keep their
// chosen trigger.
@@ -934,6 +995,7 @@ const RuleEngineResourcePage = () => {
(usesLiveRateField && liveRateOptionsLoading) ||
(usesWagonTypeField && wagonTypeOptionsLoading) ||
(usesYardField && yardOptionsLoading) ||
(usesShippingLineField && shippingLineOptionsLoading) ||
(usesApprovalRoleField && approvalRoleOptionsLoading)
}
positionOptions={!editing ? createPositionOptions : undefined}