Skip to content

feat(ui): persist list table sort, columns and filters in localStorage #146

Description

@pskillen

Problem

Each entity list page (Channels, Zones, Talk groups, Contacts, RX group lists) has a DataTable with filters, sort, and optional columns. Switching between pages — or returning from bulk operations, detail views, or edit flows — resets some of that UI state, which makes repeated list work tedious.

What persists today:

State Channels Other entity lists
Column visibility localStorage via useDataTableColumnVisibility / loadChannelVisibleColumns Not applicable (fixed columns)
Name search URL ?q= via useChannelListQuery URL ?q= via useListNameQuery
Band/mode/duplex/distance filters + sort mode URL search params via useChannelListQuery
Column header sort React state in channels/list.tsx (columnSortOverride) — lost on navigation React state / default only — lost on navigation

URL params only survive while the route keeps them. Navigating to /channels/abc and back to /channels drops ?q=, filters, and sort mode. Column header sort is never persisted at all.

Intended outcome

Persist the most recent list UI state per entity in browser localStorage, restored when the operator returns to that list (even without URL params).

State to persist (per list)

Key Channels Zones / TG / Contacts / RGL
Name search (q)
Column header sort (key + direction)
Band / mode / duplex filters
Sort mode (name | distance)
Distance filter enabled + max km
Visible optional columns ✓ (already) — (future if column picker added)

Behaviour

  • Restore on mount: when the list route loads with no relevant URL params, hydrate from localStorage defaults for that list.
  • URL wins on explicit navigation: if the operator lands with query params (shared link, bookmark), URL values take precedence over stored prefs for that visit.
  • Write-through: every change to filter/sort/column state updates localStorage (debounce text search if needed).
  • Per-project scope: keys should include active project id (or a stable project slug) so switching codeplugs does not leak list prefs across projects.
  • Register new keys in storageKeyRegistry.ts for the debug localStorage viewer.

Implementation sketch

  • Introduce a small hook, e.g. usePersistedListQuery(storageKey, defaults) or extend existing query hooks with a localStorage backing layer.
  • Channels: extend useChannelListQuery + column sort state in channels/list.tsx.
  • Other lists: extend useListNameQuery and wire column sort from each list route's DataTable.
  • Keep URL params as an optional shareable overlay — do not remove them.

Affected

  • src/hooks/useChannelListQuery.ts, useListNameQuery.ts (or new shared hook)
  • src/routes/channels/list.tsx, zones/list.tsx, ContactsList.tsx, TalkGroupsList.tsx, RxGroupListsList.tsx
  • src/lib/debug/storageKeyRegistry.ts
  • docs/features/ui/README.md — document persistence contract

Related

Out of scope

  • Persisting operator geolocation (session-only per #70).
  • Cross-browser sync (cloud storage is a separate initiative).
  • Persisting row selection for bulk actions (future groundwork only).

Manual verify

  1. Channels list: set name search, band filter, distance sort, column header sort, optional columns → navigate to a channel detail → back → all settings restored.
  2. Reload page on /channels with no query string → stored prefs applied.
  3. Open /channels?q=GB7 directly → URL q wins; changing search updates storage.
  4. Switch active project → each project retains its own list prefs.
  5. Zones (and one other entity list): name filter + column sort survive navigation.
  6. Debug localStorage viewer lists the new keys.

Workflow note

Branch from origin/main, atomic conventional commits (shared hook → channels → other lists → registry/docs), PR linking Closes #.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquality-of-lifefeature to improve the QOL of the person making a codeplug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions