Merge pull request #1438 from Tria-plc/freight/nati-2

Freight/nati 2
This commit is contained in:
Nathnael Wondisha
2026-08-28 14:09:18 +03:00
committed by GitHub
26 changed files with 733 additions and 27 deletions

View File

@@ -140,6 +140,25 @@ export default function BookingRequestsPage() {
[refData],
);
// Content options mirror the booking wizard's cargo picker: the group itself
// — which the server expands to every commodity beneath it — then each
// commodity, labelled by its full path so a generically-named leaf still
// reads unambiguously. A group with no descendants is emitted by the
// reference-data tree as its own single child; drop that duplicate.
const cargoTypeOptions = useMemo(
() =>
(refData?.cargo_type ?? []).flatMap((group) => [
{ value: group.id, label: group.name },
...(group.children ?? [])
.filter((child) => child.id !== group.id)
.map((child) => ({
value: child.id,
label: `${group.name}${child.name}`,
})),
]),
[refData],
);
// Deep links land here pre-filtered (?statuses=A,B&tradeDirection=IMPORT) —
// the header's document-review alarm opens exactly the undecided requests
// it is counting down for. No sync effect needed any more: controls.values
@@ -147,6 +166,15 @@ export default function BookingRequestsPage() {
// already mounted just works, and every filter — direction included —
// auto-pins its own pill the moment it has a value (FilterBar's `secondary`
// split), so a deep link can never land behind "More filters" unseen.
// Container types, flattened out of the reference data's size groups.
const containerTypeOptions = useMemo(
() =>
(refData?.containers ?? []).flatMap((group) =>
group.types.map((t) => ({ value: t.id, label: t.name || t.code })),
),
[refData],
);
const bookingFilterDefs: FilterDef[] = useMemo(
() => [
{
@@ -183,6 +211,46 @@ export default function BookingRequestsPage() {
multiple: false,
options: FREIGHT_TYPE_OPTIONS,
},
{
// Cargo group or commodity. The group row matches its whole subtree
// server-side, so "Bulk" returns every bulk commodity under it.
key: "cargoTypeId",
label: "Content",
type: "enum",
multiple: false,
options: cargoTypeOptions,
},
{
// Containers carry no customer-written description, so this is also how
// they are reached: it matches container types ("40FT") as well as the
// commodity name and the bulk cargo description.
key: "cargoText",
label: "Content contains",
type: "text",
secondary: true,
placeholder: "Commodity, description or container type",
},
{
key: "containerTypeId",
label: "Container type",
type: "enum",
multiple: false,
options: containerTypeOptions,
secondary: true,
},
{
// Counts boxes. Scoped to the container-type filter when one is set, so
// this one control answers "10 containers" and "10 forty-footers" both.
key: "containers",
label: "Containers",
type: "number",
secondary: true,
operators: ["is", "between"],
toParams: (v) =>
v.op === "between"
? { containersMin: v.v[0], containersMax: v.v[1] }
: { containersMin: v.v[0], containersMax: v.v[0] },
},
{
key: "serviceTypeId",
label: "Service",
@@ -244,7 +312,13 @@ export default function BookingRequestsPage() {
toParams: dateRangeParams("scheduledFrom", "scheduledTo"),
},
],
[filterOptions, yardOptions, serviceTypeOptions],
[
filterOptions,
yardOptions,
serviceTypeOptions,
cargoTypeOptions,
containerTypeOptions,
],
);
const controls = useFilters(bookingFilterDefs, {

View File

@@ -19,8 +19,10 @@ import { ExportButton } from "@/components/export/ExportButton";
import { useExchangeSettingsQuery } from "@/hooks/useExchangeSettings";
import { api } from "@/services/api";
import {
INVOICE_TYPE_OPTIONS,
PAYMENT_METHOD_OPTIONS,
invoicePaymentMethod,
invoiceTypeLabel,
paymentMethodLabel,
type Invoice,
type InvoiceListFilter,
@@ -55,6 +57,7 @@ const EIMS_STATUS_OPTIONS = [
const INVOICE_FILTER_DEFS: FilterDef[] = [
{ key: "statuses", label: "Status", type: "enum", options: STATUS_OPTIONS },
{ key: "sources", label: "Source", type: "enum", options: SOURCE_OPTIONS },
{ key: "types", label: "Type", type: "enum", options: INVOICE_TYPE_OPTIONS },
{
key: "currency",
label: "Currency",
@@ -261,6 +264,15 @@ export default function InvoicesPanel() {
size: 220,
cell: ({ row }) => <InvoiceSourceCell invoice={row.original} />,
},
{
id: "type",
header: "Type",
cell: ({ row }) => (
<Text size="sm" c="edr-text" truncate maw={180}>
{invoiceTypeLabel(row.original.type)}
</Text>
),
},
{
id: "status",
header: "Status",
@@ -389,7 +401,7 @@ export default function InvoicesPanel() {
</Box>
<Box style={{ overflowX: "auto" }} w="100%">
<Box miw={1040}>
<Box miw={1200}>
<DataTable
columns={columns}
data={rows}

View File

@@ -44,7 +44,9 @@ import { useManualPaymentSettingsQuery } from "@/hooks/useManualPaymentSettings"
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
import { api } from "@/services/api";
import {
INVOICE_TYPE_OPTIONS,
PAYMENT_METHOD_OPTIONS,
invoiceTypeLabel,
type InvoiceListFilter,
type OfflineUsdInvoice,
} from "@/types/invoice";
@@ -109,6 +111,13 @@ const MANUAL_PAYMENT_FILTER_DEFS: FilterDef[] = [
secondary: true,
options: SOURCE_OPTIONS,
},
{
key: "types",
label: "Type",
type: "enum",
secondary: true,
options: INVOICE_TYPE_OPTIONS,
},
{
key: "paymentMethods",
label: "Payment method",
@@ -424,6 +433,15 @@ export default function UsdPaymentsPanel({
);
},
},
{
id: "type",
header: "Type",
cell: ({ row }) => (
<Text size="sm" c="edr-text" truncate maw={180}>
{invoiceTypeLabel(row.original.type)}
</Text>
),
},
{
id: "status",
header: "Status",
@@ -523,7 +541,7 @@ export default function UsdPaymentsPanel({
</Box>
<Box style={{ overflowX: "auto" }} w="100%">
<Box miw={1160}>
<Box miw={1320}>
<DataTable
columns={columns}
data={rows}