feat(ux): helpful empty states with CTAs across list pages (PMS-354) #251

Merged
nrupard merged 1 commit from feat/pms-354-empty-state-cta into main 2026-06-17 16:26:57 +02:00
Owner

What

Makes empty states consistent across the SPA (PMS-354). The shared EmptyState component already existed (components/layout.rs: title / description / actions: Option<Element>) but was unused; list tables rendered the message-only TableEmpty.

Extended TableEmpty with optional title / description / actions: when title is set it renders the shared EmptyState (title + next-action body + optional CTA) inside the table's colspan cell. Back-compat: calls passing only message are unchanged.

Converted (primary entity lists, with CTA)

Tickets (New Ticket), Contracts (New Contract), Rate Cards (New Rate Card, gated on can_edit), Companies (New Company), Contacts (New Contact), Invoices (New Invoice), Payments (Record Payment), Tax Rates (New Tax Rate), Payment Gateways (Configure Gateway), Knowledge Base articles (New Article), Projects (New Project - rendered via EmptyState directly since the project list is a card grid, not a table).

No-CTA cases (AC4)

SLA policies / business-hours / holiday calendars (create is an inline header button, so title + "use New X above" body), Admin tenants, Team invitations, Audit log ("No activity yet" - entries are system-recorded).

Untouched (by design)

  • Filtered-to-nothing branches: title + "adjust your filters" body, no CTA.
  • Error rows ("Could not load …") and sub-resource tables (line items, hour balances, company-detail cards): keep their plain message.
  • Settings lookup sub-tables (work types, ticket statuses, …): message-only - config sub-tables with inline add-buttons, not primary entity lists.

AC mapping

  • AC1 (shared EmptyState exists + documented) - existed; now documented + wired through TableEmpty.
  • AC2 (Tickets matches Contracts: title + body + CTA) - done; both now have a "New X" CTA (Contracts previously only had a better string, no button).
  • AC3 (every list page uses the shared component) - done for all primary entity lists; carve-outs above.
  • AC4 (no-CTA case supported) - yes (actions: Option<Element>); used for audit log, admin, SLA, team.

Verification

wasm cargo check, cargo clippy --target wasm32-unknown-unknown --lib -- -D warnings, and cargo fmt --all --check all pass.

#PMS-354

## What Makes empty states consistent across the SPA (PMS-354). The shared `EmptyState` component already existed (`components/layout.rs`: `title` / `description` / `actions: Option<Element>`) but was unused; list tables rendered the message-only `TableEmpty`. Extended `TableEmpty` with optional `title` / `description` / `actions`: when `title` is set it renders the shared `EmptyState` (title + next-action body + optional CTA) inside the table's colspan cell. Back-compat: calls passing only `message` are unchanged. ## Converted (primary entity lists, with CTA) Tickets (New Ticket), Contracts (New Contract), Rate Cards (New Rate Card, gated on `can_edit`), Companies (New Company), Contacts (New Contact), Invoices (New Invoice), Payments (Record Payment), Tax Rates (New Tax Rate), Payment Gateways (Configure Gateway), Knowledge Base articles (New Article), Projects (New Project - rendered via `EmptyState` directly since the project list is a card grid, not a table). ## No-CTA cases (AC4) SLA policies / business-hours / holiday calendars (create is an inline header button, so title + "use New X above" body), Admin tenants, Team invitations, Audit log ("No activity yet" - entries are system-recorded). ## Untouched (by design) - Filtered-to-nothing branches: title + "adjust your filters" body, no CTA. - Error rows ("Could not load …") and sub-resource tables (line items, hour balances, company-detail cards): keep their plain message. - Settings lookup sub-tables (work types, ticket statuses, …): message-only - config sub-tables with inline add-buttons, not primary entity lists. ## AC mapping - AC1 (shared EmptyState exists + documented) - existed; now documented + wired through TableEmpty. - AC2 (Tickets matches Contracts: title + body + CTA) - done; both now have a "New X" CTA (Contracts previously only had a better string, no button). - AC3 (every list page uses the shared component) - done for all primary entity lists; carve-outs above. - AC4 (no-CTA case supported) - yes (`actions: Option<Element>`); used for audit log, admin, SLA, team. ## Verification wasm `cargo check`, `cargo clippy --target wasm32-unknown-unknown --lib -- -D warnings`, and `cargo fmt --all --check` all pass. #PMS-354
feat(ux): helpful empty states with CTAs across list pages (PMS-354)
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 2m9s
6a4b468ea8
External review found empty states inconsistent: Contracts guided the next action while Tickets and others showed a bare "No X yet" line with no CTA. A shared EmptyState component already existed (components/layout.rs) but was unused; list tables rendered the message-only TableEmpty.

Extend TableEmpty with optional title/description/actions: when title is set it renders the shared EmptyState (title + next-action body + optional CTA) inside the table's colspan cell, so every list's empty state follows the same pattern. Back-compat: calls passing only `message` are unchanged (sub-resource tables, error rows, filtered-to-nothing states keep their one-liner).

Converted the primary entity lists to title + next-action body + a "New X" CTA where a create affordance exists:
- Tickets (New Ticket), Contracts (New Contract), Rate Cards (New Rate Card, gated on can_edit), Companies (New Company), Contacts (New Contact), Invoices (New Invoice), Payments (Record Payment), Tax Rates (New Tax Rate), Payment Gateways (Configure Gateway), Knowledge Base articles (New Article), Projects (New Project, rendered via EmptyState directly since the project list is a card grid).
- No-CTA cases (AC4): SLA policies / business-hours / holiday calendars (create is an inline header button, so title + "use New X above" body), Admin tenants, Team invitations, Audit log ("No activity yet"; entries are system-recorded).

Filtered-to-nothing branches get a title + "adjust your filters" body without a CTA; error rows and sub-resource tables keep their plain message.

Out of scope: the settings lookup sub-tables (work types, ticket statuses, etc.) keep their message-only TableEmpty - they are config sub-tables with inline add-buttons, not primary entity lists.

#PMS-354
nrupard deleted branch feat/pms-354-empty-state-cta 2026-06-17 16:26:58 +02:00
Sign in to join this conversation.
No reviewers
No labels
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
psa-systems/mokosh-apps!251
No description provided.