diff --git a/apps/edr-freight-api/src/modules/import-operations/entities/empty-container-return.entity.ts b/apps/edr-freight-api/src/modules/import-operations/entities/empty-container-return.entity.ts index 7d53825eb..230581dd4 100644 --- a/apps/edr-freight-api/src/modules/import-operations/entities/empty-container-return.entity.ts +++ b/apps/edr-freight-api/src/modules/import-operations/entities/empty-container-return.entity.ts @@ -84,3 +84,14 @@ export class EmptyContainerReturn extends BaseEntity { performedBy: string | null; }>; } + +/** + * A row of the returns list: the entity's own columns plus the booking + * reference and owning company joined in. Standalone returns leave + * `bookingId`/`bookingReference` null. + */ +export interface EmptyContainerReturnListItem + extends Omit { + bookingReference: string | null; + createdAt: Date; +} diff --git a/apps/edr-freight-api/src/modules/import-operations/import-operations.service.ts b/apps/edr-freight-api/src/modules/import-operations/import-operations.service.ts index 415438884..9a84a0551 100644 --- a/apps/edr-freight-api/src/modules/import-operations/import-operations.service.ts +++ b/apps/edr-freight-api/src/modules/import-operations/import-operations.service.ts @@ -27,6 +27,7 @@ import { import { assertWagonLoad } from './empty-container-wagon.util'; import { EmptyContainerReturn, + type EmptyContainerReturnListItem, type EmptyContainerReturnStatus, } from './entities/empty-container-return.entity'; import { @@ -163,8 +164,43 @@ export class ImportOperationsService { return this.getCustoms(bookingId); } - listEmptyReturns() { - return this.emptyReturns.find({ order: { createdAt: 'DESC' } as never }); + /** + * Every empty return, booking-linked and standalone alike, in one list. The + * booking reference and the owning company are joined in so the table can + * show which booking a box came back on without a second round trip — a + * standalone row simply has neither, and falls back to the typed + * `company_name`. + */ + listEmptyReturns(): Promise { + return this.emptyReturns.manager.query(` + SELECT + r.id, + r.container_number AS "containerNumber", + r.booking_id AS "bookingId", + b.reference AS "bookingReference", + r.customer_id AS "customerId", + COALESCE(r.company_name, c.name) AS "companyName", + r.return_date AS "returnDate", + r.facility, + r.yard, + r.zone, + r.condition, + r.handover_note AS "handoverNote", + r.status, + r.wagon_allocation_reference AS "wagonAllocationReference", + r.container_size AS "containerSize", + r.train_schedule_id AS "trainScheduleId", + r.wagon_sequence_no AS "wagonSequenceNo", + r.performed_by AS "performedBy", + r.returned_by AS "returnedBy", + r.status_history AS "statusHistory", + r.created_at AS "createdAt" + FROM freight.empty_container_returns r + LEFT JOIN freight.bookings b ON b.id = r.booking_id + LEFT JOIN freight.companies c ON c.id = b.company_id + WHERE r.deleted_at IS NULL + ORDER BY r.created_at DESC + `); } listEmptyReturnsForBooking(bookingId: string) { diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/BulkContainerReturnModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/BulkContainerReturnModal.tsx index 36b6d68e4..66a20aff4 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/BulkContainerReturnModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/BulkContainerReturnModal.tsx @@ -18,6 +18,7 @@ import { } from "@mantine/core"; import { Upload } from "lucide-react"; +import { localNowForInput } from "@/lib/format"; import { useToast } from "@/hooks/use-toast"; import { useWarehouseYards, useWarehouseZones } from "@/hooks/useWarehouses"; import { importOperationsService } from "@/services/importOperations.service"; @@ -61,7 +62,7 @@ export default function BulkContainerReturnModal({ const [warehouseId, setWarehouseId] = useState(null); const [yardId, setYardId] = useState(null); const [zoneId, setZoneId] = useState(null); - const [returnDate, setReturnDate] = useState(new Date().toISOString().split("T")[0]); + const [returnDate, setReturnDate] = useState(localNowForInput()); const { data: warehousesResponse } = useQuery({ queryKey: ["warehouses-list"], @@ -239,10 +240,10 @@ export default function BulkContainerReturnModal({ />
- Returned Date (default) + Returned Date & Time (default) setReturnDate(e.target.value)} style={{ padding: "8px", borderRadius: "4px", border: "1px solid #ced4da", width: "100%" }} diff --git a/apps/edr-freight-web/backoffice/src/lib/format.ts b/apps/edr-freight-web/backoffice/src/lib/format.ts index 4c5c6b639..1c8e66bb3 100644 --- a/apps/edr-freight-web/backoffice/src/lib/format.ts +++ b/apps/edr-freight-web/backoffice/src/lib/format.ts @@ -60,3 +60,10 @@ export function formatBytes(bytes: number): string { const value = bytes / Math.pow(1024, i); return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`; } + +/** `YYYY-MM-DDTHH:mm` for now, in local time — what `datetime-local` expects. */ +export function localNowForInput(): string { + const d = new Date(); + d.setMinutes(d.getMinutes() - d.getTimezoneOffset()); + return d.toISOString().slice(0, 16); +} diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx index dcae9e85a..6e8658ad2 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx @@ -19,6 +19,7 @@ import { Select, Checkbox, Autocomplete, + Input, } from "@mantine/core"; import { ChevronDown, ChevronRight, FileText, History, Upload } from "lucide-react"; import { DataTable, type ColumnDef } from "@edr/ui-common"; @@ -44,7 +45,7 @@ import type { EmptyContainerSize, } from "@/types/importOperations"; import type { TrainScheduleListItem } from "@/types/trainScheduling"; -import { formatDateTime } from "@/lib/format"; +import { formatDateTime, localNowForInput } from "@/lib/format"; type ReturnType = "all" | "edr" | "customer"; @@ -383,15 +384,16 @@ export default function ContainerReturnsPage() { ), }, - { - id: "bookingRef", - header: "Booking Ref", - cell: ({ row }) => (row.original.bookingId ? "Associated" : "—"), - }, { id: "company", header: "Company", - cell: ({ row }) => row.original.companyName || "—", + // One identity column: who the box belongs to, and the booking it came + // back on. A standalone return has no booking, so only the name shows. + cell: ({ row }) => { + const { companyName, bookingReference } = row.original; + if (!companyName) return bookingReference || "—"; + return bookingReference ? `${companyName} (${bookingReference})` : companyName; + }, }, { id: "returnedBy", @@ -407,9 +409,8 @@ export default function ContainerReturnsPage() { }, { id: "returnDate", - header: "Returned Date", - cell: ({ row }) => - row.original.returnDate ? new Date(row.original.returnDate).toLocaleDateString() : "—", + header: "Returned Date & Time", + cell: ({ row }) => formatDateTime(row.original.returnDate), }, { id: "facility", @@ -878,7 +879,7 @@ interface ContainerReturnModalProps { function ContainerReturnModal({ opened, onClose, group, onSubmit, loading }: ContainerReturnModalProps) { const [selectedContainers, setSelectedContainers] = useState([]); - const [returnDate, setReturnDate] = useState(new Date().toISOString().split("T")[0]); + const [returnDate, setReturnDate] = useState(localNowForInput()); const [warehouse, setWarehouse] = useState(null); const [condition, setCondition] = useState(""); const [handoverNote, setHandoverNote] = useState(""); @@ -970,13 +971,20 @@ function ContainerReturnModal({ opened, onClose, group, onSubmit, loading }: Con searchable /> - setReturnDate(e.target.value)} - style={{ padding: "8px", borderRadius: "4px", border: "1px solid #ccc" }} - required - /> + + setReturnDate(e.target.value)} + style={{ + padding: "8px", + borderRadius: "4px", + border: "1px solid #ced4da", + width: "100%", + }} + required + /> +