Dashboard: Add dense shipment list view for operational scanning #27

Closed
opened 2026-06-27 15:41:14 +02:00 by bullitt · 2 comments
Owner

Problem

With 10+ active shipments, the card grid requires significant scrolling. Power users (checking daily status of many parcels) would benefit from a compact table/list view that shows ~20 shipments without scrolling. The existing filter bar already suggests power-user intent.

Implementation

File: frontend/src/pages/Dashboard.tsx

  1. Add a view mode state: const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
  2. Persist to localStorage: localStorage.setItem('trackbox_view_mode', viewMode)
  3. Add a toggle button to the header (next to the refresh button) using LayoutGrid and List lucide icons.
  4. In list mode, replace the grid with a compact table:
// List view — one row per shipment
<table className="w-full text-sm">
  <thead>
    <tr className="border-b border-border text-xs text-muted-foreground uppercase tracking-wide">
      <th className="text-left py-2 pr-4">Shipment</th>
      <th className="text-left py-2 pr-4">Carrier</th>
      <th className="text-left py-2 pr-4">Status</th>
      <th className="text-left py-2 pr-4">Updated</th>
      <th className="text-left py-2">Actions</th>
    </tr>
  </thead>
  <tbody>
    {filteredActive.map(s => (
      <tr key={s.id} className="border-b border-border hover:bg-accent/40 cursor-pointer" onClick={() => navigate(`/shipments/${s.id}`)}>
        <td className="py-2 pr-4 font-medium">{s.title || `#${s.id}`}</td>
        <td className="py-2 pr-4 text-muted-foreground flex items-center gap-1.5">
          <CarrierIcon carrier={s.carrier} size={14} />{s.carrier}
        </td>
        <td className="py-2 pr-4"><StateBadge state={s.current_state} /></td>
        <td className="py-2 pr-4 text-muted-foreground">{relativeTime(s.last_updated_at)}</td>
        <td className="py-2">
          {s.stalled && <span className="text-xs text-amber-600">Stalled</span>}
        </td>
      </tr>
    ))}
  </tbody>
</table>
  1. Apply same view mode to delivered/archived sections.

Acceptance criteria

  • Toggle between grid and list view persists across page reloads (localStorage)
  • List view shows all key info in a scannable single row per shipment
  • All existing filter/sort/archive/unarchive interactions work in list view

Brand identity alignment

The brand brief explicitly supports both comfortable card view and dense list/table view. The list mode should feel like an operational shipment table, not a generic SaaS grid.

Brand-compliant implementation notes

  • Use moderate row height, subtle dividers, neutral background, and semantic status badges.
  • Recommended columns: Shipment, Status, Carrier, Tracking ID, Last carrier update, Last sync/source, Actions.
  • Tracking numbers must use monospace and remain copy-friendly.
  • Carrier badges/icons should be neutral and secondary; status is the semantic color signal.
  • Problems should sort or group naturally above routine in-transit rows when filters allow it.

Additional acceptance criteria

  • Dense list view supports scanning roughly 20 shipments without visual overload.
  • Status is identifiable by text and icon/color, not color alone.
  • Table rows preserve keyboard/focus behavior and do not rely on hover-only actions.

Migrated from GitHub issue #6: https://github.com/bullitt186/trackbox/issues/6
Original author: @bullitt186
Original created: 2026-06-27T13:22:46Z
Original labels: ux

## Problem With 10+ active shipments, the card grid requires significant scrolling. Power users (checking daily status of many parcels) would benefit from a compact table/list view that shows ~20 shipments without scrolling. The existing filter bar already suggests power-user intent. ## Implementation File: `frontend/src/pages/Dashboard.tsx` 1. Add a view mode state: `const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')` 2. Persist to localStorage: `localStorage.setItem('trackbox_view_mode', viewMode)` 3. Add a toggle button to the header (next to the refresh button) using `LayoutGrid` and `List` lucide icons. 4. In list mode, replace the grid with a compact table: ```tsx // List view — one row per shipment <table className="w-full text-sm"> <thead> <tr className="border-b border-border text-xs text-muted-foreground uppercase tracking-wide"> <th className="text-left py-2 pr-4">Shipment</th> <th className="text-left py-2 pr-4">Carrier</th> <th className="text-left py-2 pr-4">Status</th> <th className="text-left py-2 pr-4">Updated</th> <th className="text-left py-2">Actions</th> </tr> </thead> <tbody> {filteredActive.map(s => ( <tr key={s.id} className="border-b border-border hover:bg-accent/40 cursor-pointer" onClick={() => navigate(`/shipments/${s.id}`)}> <td className="py-2 pr-4 font-medium">{s.title || `#${s.id}`}</td> <td className="py-2 pr-4 text-muted-foreground flex items-center gap-1.5"> <CarrierIcon carrier={s.carrier} size={14} />{s.carrier} </td> <td className="py-2 pr-4"><StateBadge state={s.current_state} /></td> <td className="py-2 pr-4 text-muted-foreground">{relativeTime(s.last_updated_at)}</td> <td className="py-2"> {s.stalled && <span className="text-xs text-amber-600">Stalled</span>} </td> </tr> ))} </tbody> </table> ``` 5. Apply same view mode to delivered/archived sections. ## Acceptance criteria - Toggle between grid and list view persists across page reloads (localStorage) - List view shows all key info in a scannable single row per shipment - All existing filter/sort/archive/unarchive interactions work in list view ## Brand identity alignment The brand brief explicitly supports both comfortable card view and dense list/table view. The list mode should feel like an operational shipment table, not a generic SaaS grid. ## Brand-compliant implementation notes - Use moderate row height, subtle dividers, neutral background, and semantic status badges. - Recommended columns: Shipment, Status, Carrier, Tracking ID, Last carrier update, Last sync/source, Actions. - Tracking numbers must use monospace and remain copy-friendly. - Carrier badges/icons should be neutral and secondary; status is the semantic color signal. - Problems should sort or group naturally above routine in-transit rows when filters allow it. ## Additional acceptance criteria - Dense list view supports scanning roughly 20 shipments without visual overload. - Status is identifiable by text and icon/color, not color alone. - Table rows preserve keyboard/focus behavior and do not rely on hover-only actions. --- Migrated from GitHub issue #6: https://github.com/bullitt186/trackbox/issues/6 Original author: @bullitt186 Original created: 2026-06-27T13:22:46Z Original labels: ux
bullitt changed title from Dashboard: Add list/table view toggle for dense shipment browsing to Dashboard: Add dense shipment list view for operational scanning 2026-06-27 15:46:21 +02:00
Author
Owner

Implemented in commit bd6b0e7. Added a view mode toggle (LayoutGrid / List icons) in the dashboard header. The list view renders a compact table with columns: Shipment, Status, Carrier, Tracking, Updated, Actions. View preference is persisted in localStorage. All sections (active, delivered, archived) respect the view mode. Hover-reveal actions work in both modes.

Implemented in commit bd6b0e7. Added a view mode toggle (LayoutGrid / List icons) in the dashboard header. The list view renders a compact table with columns: Shipment, Status, Carrier, Tracking, Updated, Actions. View preference is persisted in localStorage. All sections (active, delivered, archived) respect the view mode. Hover-reveal actions work in both modes.
Author
Owner

Follow-up fix: Added select-all class to tracking numbers in list view for easier copy. Changed list row action buttons from opacity-0 (hover-only) to opacity-40 with focus-within:opacity-100 so keyboard users can reach them. Both were acceptance criteria gaps identified in review.

Follow-up fix: Added `select-all` class to tracking numbers in list view for easier copy. Changed list row action buttons from `opacity-0` (hover-only) to `opacity-40` with `focus-within:opacity-100` so keyboard users can reach them. Both were acceptance criteria gaps identified in review.
Sign in to join this conversation.
No labels
arch
harness
security
ux
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bullitt/trackbox#27
No description provided.