Documentation Index
Fetch the complete documentation index at: https://mintlify.com/raceliciouss/YUSEN-LIMO-WAREHOUSE/llms.txt
Use this file to discover all available pages before exploring further.
Raw shipment records in warehouseShipments represent individual transaction events — one record per inbound receipt, one per outbound release. Nothing is pre-computed at write-time. Every inventory row, every dashboard statistic, and every activity entry is produced by reading the flat array and running a series of in-memory transformations at render-time. The central function driving all of this is aggregateShipmentsByReference() in assets/app.js, which two derived accessors — getInventoryData() and getActivityData() — both call on top of getStoredShipments().
aggregateShipmentsByReference()
aggregateShipmentsByReference(shipments) accepts the raw shipment array returned by getStoredShipments() and returns one aggregate object per unique cargo reference. A reference is identified by getShipmentAggregateKey(), which resolves in priority order:
- HAWB — if
shipment.hawb is a non-empty string (after trimming and upper-casing), the key is hawb:<HAWB>.
- MAWB — if
hawb is absent but shipment.mawb is present, the key is mawb:<MAWB>.
- Timestamp fallback — if neither is set, the key is
manual:<savedAt>, meaning the record is treated as a standalone entry and never merged.
The function iterates every record and, for each key, maintains a running aggregate object in a Map. The merge rules are:
- Latest record wins for metadata — descriptive fields (
client, destination, location, status, transactionType, trucker, driver, etc.) are overwritten only when the incoming record’s savedAt is equal to or greater than the aggregate’s current savedAt. This means the most recently saved record’s field values win.
- Inbound records accumulate
qtyInValue — getShipmentEntryType() inspects the entryType field (falling back to presence of qtyOut, releaseQty, releaseDate, releasePlate, or releasedBy if entryType is absent). For each inbound record, parseQuantityNumber(shipment.qtyIn || shipment.quantity) is added to the aggregate’s qtyInValue counter.
- Outbound records accumulate
qtyOutValue and outboundDetails — for each outbound record, parseQuantityNumber(shipment.qtyOut || shipment.releaseQty || shipment.quantity) is added to qtyOutValue, and a detail object { date, time, qty, plate, driver, remarks, savedAt } is pushed to outboundDetails.
After all records are processed, outboundDetails for each aggregate is sorted chronologically by parsing detail.date + "T" + (detail.time || "00:00:00") into a timestamp. The latest entry in the sorted array is then promoted to the top-level release summary fields (releaseDate, releaseTime, releasePlate, releaseDriver, remarks).
Finally, remainingValue is computed:
remainingValue = qtyInValue - qtyOutValue
remainingValue is the authoritative remaining-cargo figure used throughout the UI. It is exposed on inventory rows as the formatted string remainingQuantity (via normalizeShipmentForInventory()).
Partial Outbound Handling
A single HAWB can have cargo released across multiple separate transactions. Each outbound form submission produces a new record in warehouseShipments with entryType: "outbound" and its own qtyOut value. aggregateShipmentsByReference() handles this by:
- Summing all outbound quantities — every outbound record for the same reference adds to
qtyOutValue independently, so three partial releases of 10 boxes each produce a qtyOutValue of 30.
- Preserving the full outbound history — each release event is appended to
outboundDetails in the order it is encountered, then the entire array is sorted chronologically.
- Surfacing the most recent release — after sorting, the function finds the latest detail that has a
date value and copies its fields to the aggregate’s top-level releaseDate, releaseTime, releasePlate, releaseDriver, and remarks. This means the inventory and activity views always show the most recent release event in the summary row, while the full outboundDetails array is available in the drawer.
Dashboard Statistics
The dashboard calls aggregateShipmentsByReference(getStoredShipments()) and derives three headline numbers from the result:
| Statistic | Element ID | Calculation |
|---|
| Total Shipments | dashboardTotalShipments | shipmentRows.length — count of unique aggregated references |
| Cargo in Warehouse | dashboardCargoInWarehouse | shipmentRows.reduce((sum, s) => sum + (s.remainingValue || 0), 0) — sum of remainingValue across all aggregates |
| Outgoing Today | dashboardOutgoingToday | Count of aggregates where itemHasTodayOutbound(shipment, today) returns true — i.e. any outbound detail whose date matches today’s ISO date string |
Each headline card is also a filter button. Clicking Total Shipments resets the dashboard activity list to all. Clicking Cargo in Warehouse filters to aggregates with remainingValue > 0. Clicking Outgoing Today filters to aggregates with an outbound event dated today. The active card is tracked in the local variable dashboardActivityFilter.
Search Algorithm
Global search is initiated from the topbar input present on every authenticated page. The matching logic compares the lowercased query string against three fields on each aggregated shipment:
[item.hawb || '', item.mawb || '', item.client || ''].some(
(field) => String(field).toLowerCase().includes(query)
)
Matches are grouped into two suggestion categories — Inventory and Activity Report — and displayed in a dropdown (capped at 10 results per group). Selecting a suggestion:
- Writes a JSON object to
sessionStorage under the key warehouseLiveSearchSelection:
{ "targetPage": "inventory", "value": "HAWB-20240601-001" }
- Navigates to
inventory.html (or dashboard.html for Activity Report suggestions).
On the destination page, the code reads warehouseLiveSearchSelection from sessionStorage, pre-fills the search input with the stored value, triggers a filtered render, and then removes the key with sessionStorage.removeItem('warehouseLiveSearchSelection') so the selection does not persist beyond the initial load.
Filters
Inventory Filters
The inventory filter panel exposes three controls that are applied together before rendering the table:
| Filter | Source | Behavior |
|---|
| Location | inventoryLocation select | Exact match against item.location |
| Transaction type | inventoryTransaction select | Exact match against item.transactionType |
| Search query | Text input | Lowercase substring match against HAWB, MAWB, or client |
Clicking Clear Filters resets all three controls and re-renders the table unfiltered.
Activity Filters
The activity filter panel adds date-range controls to the same set:
| Filter | Behavior |
|---|
| Date from / Date to | Filters to aggregates whose dateIn or dateOut falls within the range |
| Transaction type | Exact match against transactionType |
| Location | Exact match against location |
| Search query | Lowercase substring match against HAWB, MAWB, or client |
Dashboard Activity List Filters
As described in Dashboard Statistics, clicking a dashboard card applies a filter to the activity list rendered below the headline cards:
all — all aggregates
remaining — aggregates where remainingValue > 0
outgoingToday — aggregates with an outbound event dated today
Sorting
Inventory Table
The inventory table exposes a single sort control (inventory-sort select element). The sort is applied to the remainingQuantity string after parsing it back to a number:
| Option | Behavior |
|---|
| Default | Sorted by savedAt descending (most recent first) |
asc | Ascending by remaining quantity (lowest first) |
desc | Descending by remaining quantity (highest first) |
The sort is re-applied on every filter change via the refreshInventory callback, which is also bound to the change event on the sort select.
User Management Table
The user table supports multi-column sorting. The sortable column indices map to keys as follows:
| Column index | Sort key |
|---|
| 0 | employeeId |
| 1 | name |
| 2 | userId (username) |
| 3 | role |
| 4 | status |
| 5 | lastLogin |
Sort state is tracked in userManagementSortState ({ key, direction }). Clicking the same column header again toggles direction between asc and desc. All comparisons are performed after calling .toLowerCase() on the field value, making sorting case-insensitive.