Repository navigation
feat(dashboard): admin sales dashboard and native controls in GET forms - #24
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and best sellers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h axe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t JavaScript Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vaScript e2e test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…act it The logger censors any `code` key (TOTP codes, spec 0004), so dashboard.query_failed always logged "[redacted]". A unit test now runs the line through the real logger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 0015 done: scope status done, spec Accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…range The date inputs stay filled when scripts never load, and the dates won on the server, so the preset was ignored. The picker now marks a custom range page with shown=custom, and a valid period wins over the dates then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e copy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e queries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in full Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T forms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of the context Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0015: the range line under the picker, the relabelled orders date inputs, and the fourth query for the other currency count. 0016: the checkout select is the saved addresses one, and the category page search has its no JavaScript test too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
/adminis now a sales dashboard. It shows net revenue, orders, average order value, net units sold, a net revenue chart and the top 10 best sellers, for a period you pick. Every GET form (filters, the period picker, search) also moves to native browser controls, so it works before the app's JavaScript loads. A lint rule keeps the Base UI Select out of the codebase from now on.Why
Changes
Sales dashboard (
src/features/dashboard/,/admin)Welcomecomponent is gone./adminshows a heading, "Signed in as …", the period picker, four stat cards, the chart and the best sellers table. "Dashboard" is the first sidebar item. The page callsrequireAdmin()itself.period.ts): the presetstoday,7d,30d(the default),monthand12m, plus a customfrom/torange. Days are store days inSTORE_TIMEZONE. Atoafter today is clamped to today. A bad range falls back to 30 days and shows a notice. The previous window has the same number of days and ends the day before the chosen one starts.queries.ts): live$queryRawaggregates on every request, run in oneREPEATABLE READ, read only transaction. No cache, no new table, column or index. A refund counts in the period it succeeded in, so past periods never change. Orders in another currency are left out and named in a notice. Sums that are not safe integers throw instead of rounding.recharts3.8.0,src/components/ui/chart.tsx) with a bucket per day, week or month depending on the window length. A "Show as table" toggle shows the same buckets in a table. The SVG is hidden from screen readers.log.ts):dashboard.query_failedlogs the SQLSTATE aspgCode. The logger censors anycodekey, which is why it isn't calledcode(fix 9978f80). Reads slower than 1,000 ms logdashboard.slow.Orders list
/admin/ordersacceptsdates=created|paid. Withpaid,from/tofilterpaid_at, and unpaid orders are left out. The param flows throughadminOrdersHrefandhasFilters, and the filter form has a new "Date" select. The Orders card links to?status=all&dates=paid&from=…&to=….src/lib/dates.tsgainsstoreToday()and a sharedCalendarDaytype.Native controls in GET forms
NativeSelectfrom the shadcn registry (src/components/ui/native-select.tsx), with its states on/style-guide.id,nameand label.eslint.config.mjsbans@/components/ui/selecteverywhere exceptsrc/features/style-guide/**.Tests
period,change,paths,log,datesandlist-params. A lint test (tests/lint/no-base-ui-select.test.ts) proves the ban fires.dashboard.db.test.tschecks exact sums, refund timing, currency, buckets that add up, best sellers and failure logging.dashboard-year.db.test.tsseeds about 20,000 orders and 2,000 refunds, checks every figure exactly, and asserts the median of 3 warm runs stays within 1,500 ms (144 ms measured locally).admin-orders.db.test.tscoversdates=paid.admin/dashboard.spec.tswith axe, keyboard and phone width. A newblockAppScriptshelper (tests/e2e/no-js.ts) gives each GET form a test with the app's scripts blocked. Specs that clicked Base UI options now useselectOptionandtoHaveValue.How to test / verify
pnpm lint && pnpm typecheck && pnpm test, thenpnpm test:db(needspnpm db:start), thenpnpm test:e2e tests/e2e/admin/dashboard.spec.ts./admin. Try each preset and a custom range such as?from=2026-02-30&to=2026-03-05, which should show the notice and fall back to 30 days. Press "Show as table", then click "View these orders". The orders list should open with Date set to "Paid", and its rows should match the Orders card./admin/orders,/admin/discounts,/admin/productsand the period picker. Each should change the results.Risk & rollout
recharts3.8.0./admin. The seeded year test keeps this within a 1,500 ms CI budget, and slow reads logdashboard.slow.Notes for reviewers
blockAppScriptsblocks only/_next/static/chunks/*.js, not CSS. Without styles, fill images cover the controls./syncto add the native controls rule toAGENTS.md.🤖 Generated with Claude Code