Skip to content

feat: commercial dashboard, sortable tables, multi-filters, user management - #4

Merged
PhilippTheServer merged 1 commit into
mainfrom
pl/dashboard-tables-users
Aug 19, 2026
Merged

PhilippTheServer merged 1 commit into
mainfrom
pl/dashboard-tables-users

Conversation

@PhilippTheServer

Copy link
Copy Markdown
Contributor

Closes #3.

Dashboard

Greeting gone. In its place, the operational picture: revenue taken, value awaiting
payment
, outstanding shipments and their value, average order, then catalogue and
stock, an order-mix breakdown, and the value of cancelled and refunded orders.
Restocking stays — it was the part already earning its place.

Every figure is derived from the orders list, because the API has no stats endpoint. That
carries a limit worth knowing, so the page states it rather than implying all-time totals:
"Money, fulfilment and stock across the most recent 100 orders."

Clickable rows

Products opens the editor, orders the detail, users the role change.

Implemented as a directive that adds the button role, a tab stop and Enter/Space handling —
a row that answers only to a mouse is invisible to keyboard and screen-reader users. Clicks
originating on a nested control are ignored, so Delete never also opens the editor.

Sorting

Clicking the active column flips direction; clicking another moves to it ascending. In one
helper, so no table invents its own rule, and aria-sort is set so the ordering is
announced rather than being visual-only.

Missing values sort last in both directions. Absent is not "smallest", and burying real
data under blanks on one direction is not what anyone wants.

Multi-filters

A shared bar combining free-text search with any number of multi-select facets, applied
together. Multi-select because the real questions are compound — "paid or ready to ship",
"low or out of stock" — and one-at-a-time filtering makes the operator look twice and hold
the result in their head. Selections appear as removable chips, so what is filtered is never
hidden inside a collapsed control.

Orders filtering moved client-side: the API's status parameter takes a single value and
cannot express those questions.

Users

Lists Keycloak accounts with their role; promote, demote, delete.

It calls Keycloak's admin API directly rather than proxying through FastAPI. Keycloak
owns accounts and roles, so going straight there means Keycloak enforces permission — the
admin's token carries manage-users and view-realm through the composite admin role,
and a customer's token gets 403 without our code being involved. Re-implementing that check
in the API would put a privilege-escalation bug one mistake away. The interceptor's
allowlist now names that host explicitly rather than widening to "anything".

Two guards prevent the damaging mistake:

  • Self-actions refused — you cannot delete your own account or change your own role.
  • Last administrator protected — demoting the final admin is blocked, because a realm
    with no administrator is only recoverable from the Keycloak console.

Both are shown as disabled buttons with the reason in the tooltip, rather than a dead
control.

Verification

Driven against the live stack — real Keycloak, real API:

/dashboard -> "Overview"          (greeting gone)
/users     -> "Users"

sort toggled: "House Red" -> "Salted Pretzels"
aria-sort on active column: descending

orders: 16 rows -> 4 with 2 statuses selected, 2 chips
row click opened: "Edit product"

testuser before: customer
after promote  : administrator
after demote   : customer
rows before delete: 3  ->  after: 2
feedback: "throwaway was deleted."

console errors: none
failed calls: none
Test Files  6 passed (6)      Tests  30 passed (30)
All matched files use Prettier code style!
Initial total 342.75 kB

New tests cover the sort helper (including the missing-value rule and non-mutation) and the
two user-management guards.

Not included

Users are listed with max=200 and roles resolved per account, since Keycloak has no
endpoint returning both. Fine at this size; a realm with thousands of accounts should move
to a paged view resolving roles lazily. Noted in the service.

…gement

Dashboard

Drops the greeting and replaces it with the operational picture: revenue
taken, value awaiting payment, outstanding shipments and their value,
average order, then catalogue and stock, an order-mix breakdown and the
value of cancelled and refunded orders. Restocking stays, since it was
the part already earning its place.

Every figure is derived from the orders list because the API exposes no
stats endpoint. That has a limit worth knowing, so the page says it: the
figures cover the most recent 100 orders rather than implying all time.

Tables

Rows are now activatable - products opens the editor, orders opens the
detail, users opens the role change. Done as a directive that adds the
button role, a tab stop and Enter/Space handling, because a row that only
answers to a mouse is invisible to keyboard and screen-reader users.
Clicks that start on a nested button are ignored, so Delete never also
opens the editor.

Column headings sort. Clicking the active column flips direction and
clicking another moves to it ascending, which is what people expect;
having it in one helper stops each table inventing its own rule. Missing
values sort last in both directions - absent is not "smallest", and
burying real data under blanks on one direction is not useful.

Filtering

A shared filter bar combining free-text search with any number of
multi-select facets, all applied together. Multi-select because the real
questions are compound - "paid or ready to ship", "low or out of stock" -
and one-at-a-time filtering makes the operator look twice and hold the
result in their head. Selections show as removable chips so what is being
filtered is never hidden in a collapsed control.

Orders filtering moved client-side: the API's status parameter accepts
only one value, which cannot express the questions above.

Users

New section listing Keycloak accounts with their role, able to promote a
customer to admin, demote an admin, and delete an account.

It calls Keycloak's admin API directly rather than proxying through
FastAPI. Keycloak owns accounts and roles, so going straight there means
Keycloak enforces permission - the admin's token carries manage-users and
view-realm through the composite admin role, and a customer's token gets
403. Re-implementing that check in the API would put a
privilege-escalation bug one mistake away. The interceptor's allowlist
now covers that host explicitly rather than sending the token anywhere.

Two guards stop the worst mistake: self-actions are refused, and demoting
the last remaining administrator is blocked, because a realm with no
admin is only recoverable from the Keycloak console.

Verified against the live stack: promote, demote and delete all round-trip
through real Keycloak with no failed calls; sorting sets aria-sort;
selecting two order statuses narrows 16 rows to 4 with two chips shown;
clicking a product row opens the editor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@PhilippTheServer
PhilippTheServer merged commit 1ca0872 into main Aug 19, 2026
1 check passed
@PhilippTheServer
PhilippTheServer deleted the pl/dashboard-tables-users branch August 19, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard needs real signal; tables need sorting, row editing and multi-filters; add user management

1 participant