Skip to content

feat(analytics): analytics page, and dashboard money from the API - #8

Merged
PhilippTheServer merged 1 commit into
mainfrom
pl/analytics
Aug 26, 2026
Merged

PhilippTheServer merged 1 commit into
mainfrom
pl/analytics

Conversation

@PhilippTheServer

Copy link
Copy Markdown
Contributor

Closes #7 · consumes OpenTaberna/fastapi#44

The dashboard summed revenue and AOV client-side from the last 100 orders — its own comment
said so. Those figures now come from /v1/admin/analytics/*, computed in SQL over the whole
order history, and a new Analytics page adds the trend, product and funnel views that
did not exist.

What's here

Analytics page — net revenue over time, orders and units, the order funnel with drop-off
per step, per-SKU performance with return rate, and active-but-unsold stock. Period selector
of 7 / 30 / 90 days / 12 months, switching to weekly buckets past 120 days because daily
buckets over a year are unreadable.

Dashboard — money tiles read the summary endpoint over 30 days. Work queues still read
the orders list, which is correct for them: "what needs doing now" is the newest orders.

Decisions worth reviewing

No Angular chart wrapper. Chart.js is used directly behind a thin standalone component.
The app is zoneless and on Angular 22, and a wrapper's peer range gates every future Angular
upgrade — ng2-charts@10 would also pull in @angular/cdk, which this app does not
otherwise use. Only the controllers actually drawn are registered, so the rest of Chart.js
tree-shakes away.

Chart.js is not in the initial bundle. It sits in a lazy chunk (187 kB raw / 57 kB
transfer) loaded only by /analytics. Initial total is unchanged at 345.94 kB / 93.41 kB.

While wiring this I caught a real problem: the dashboard imported rangeEndingToday from
analytics.page.ts, which drags the analytics page — and through it Chart.js — into the
dependency graph of a screen that draws no charts. The helpers moved to core/date-range.
grep -rln "from 'chart.js'" now returns only the two chart components and the analytics
page.

Currencies are never summed. The API returns a list keyed by currency; the page renders
one block each and charts the highest-earning one, saying so when there is more than one.

No baseline renders as "no prior data". Change from zero is undefined — not 0%, and not
an arrow.

Verification

$ npx ng test --watch=false
 Test Files  9 passed (9)
      Tests  53 passed (53)

$ npx prettier --check "src/app/**/*.{ts,html}"
All matched files use Prettier code style!

$ npx ng build
Application bundle generation complete.
Initial total | 345.94 kB | 93.41 kB

The dashboard specs catch the actual regression. Restoring the client-side sum — with an
orders stub carrying a 999999 order that would dominate any such sum:

 × reports net revenue from the API, not from the orders list
 × shows the currency that earned the most, never a sum across them
 × degrades to zero and flags a partial view when analytics fails
AssertionError: expected 999999 to be 40000 // Object.is equality

The analytics page spec is a render test with teeth: it compiles the template, runs every
computed and builds real Chart.js configurations, so a mistyped field fails in CI rather
than on an operator's screen.

nav.model.spec.ts was updated from six navigation entries to seven — an intentional change
to an existing assertion, not a loosened one.

Verified against the live dev stack: all four endpoints answer, and 403 without a token.

The dashboard summed revenue and average order value client-side from the last
100 orders. Its own comment admitted it. The headline numbers therefore stopped
being the shop's numbers at order 101, and there was no trend, no product view
and no sight of where orders stop converting.

Adds an Analytics page — revenue over time, orders and units, the order funnel,
per-SKU performance and active-but-unsold stock — reading
/v1/admin/analytics/*, which computes everything in SQL over the whole order
history.

The dashboard's money tiles now come from the same summary endpoint over a
30-day window. Its work queues still read the orders list, which is the right
source for them: "what needs doing now" is exactly the newest orders.

Money is shown per currency and never summed across them. The API returns a
list keyed by currency because a cross-currency total is meaningless; adding
them up here would put the bug straight back.

Charts use Chart.js directly behind a thin standalone wrapper rather than an
Angular wrapper library. The app is zoneless and tracks the newest Angular, and
a wrapper's peer range would gate every future Angular upgrade — ng2-charts
would also pull in @angular/cdk, which this app does not otherwise need. Only
the controllers actually drawn are registered, so the rest of Chart.js is
tree-shaken, and the whole library sits in a lazy chunk: nothing but /analytics
pays for it.

isoDate and rangeEndingToday live in core/date-range rather than beside the
page, because the dashboard needs them and importing from a page component
would drag Chart.js into a screen that draws no charts.

Percentage change from a zero baseline renders as "no prior data", not 0% and
not an arrow. It is undefined, and a direction would assert what the data
cannot support.

Closes #7

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YY1ekLLeFLkAU2kvdQ8Ey4
@PhilippTheServer
PhilippTheServer merged commit 4c4f32c into main Aug 26, 2026
1 check passed
@PhilippTheServer
PhilippTheServer deleted the pl/analytics branch August 26, 2026 13:35
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 figures are computed from the last 100 orders, and there is no trend or product view

1 participant