Build and Push Lunchtime Images (Kaniko) / build-and-push (push) Successful in 1m44s
My Orders: year calendar and lunch profile first, chips and "How you compare" removed, open and paid payments merged into one table (open first, then newest). Activity is now a line chart with a cumulative switch and finer buckets (daily up to ~3 months, weekly up to ~13). Cards get subtle elevation and tighter spacing. Responsive: phone tab strip instead of the side menu, heatmap fits narrow screens, grouped column-chart labels are truncated, the manager weekday table scrolls instead of overflowing. Tooling: viewports.mjs (presets, overflow detector), shoot.mjs --sizes and --select, tests/responsive.ui.spec.ts, docs/ui-testing.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
5.2 KiB
5.2 KiB
Statistics
Statistics pages live under /stats/* (src/frontend/src/views/StatisticsView.tsx). Status: ✅ done, 🔲 TODO (needs backend changes, listed under each item).
Architecture
- Backend (
services/stats_service.py) returns compact fact rows, not aggregates, for the per-user pages. Aggregation (months, weekdays, hours) happens in the frontend (lib/stats.ts) so buckets use the viewer's timezone. - The site overview is aggregated in the backend (
GET /api/admin/stats) because it spans all users and needs files on disk. - Item names/prices come from the per-order config files (
data/configs/<admin_token>.json), resolved in the backend. - Estimated totals only exist for orders whose menu items have prices.
| Endpoint | Auth | Used by |
|---|---|---|
GET /api/stats/me |
any user | My Orders |
GET /api/stats/managed |
any user (own orders only) | Order Management |
GET /api/admin/stats |
site admin | Site Overview |
Timeframe and insights
- One page-wide timeframe filter (
range.tsx: 1W, 1M, 3M, 1Y, YTD, All, custom) drives every number and chart, except the all-time blocks labelled as such (lunch profile, calendar, payments, funnel). - Stat tiles show the change vs. the previous period of equal length (
Delta); "All" has no comparison. - The "Activity" panel (
TimeSeriesPanel.tsx) draws line charts of per-period totals (days up to ~3 months, weeks up to ~13 months, months beyond;periodBucketsinlib/stats.ts) with a "Cumulative" switch (off by default) for running totals. Site-wide daily counts come fromdailyin/api/admin/stats. - Personal insights (lunch personality, records, adventurousness, calendar and weekday x hour heatmaps) are computed in
lib/stats.tsfrom the fact rows.communityin/api/stats/me(participant count, average, percentile) is the only extra aggregate, fromuser_order_tokens. - Site "active users" now means users who created an order or submission in the window (was: last profile update). The funnel and weekly retention use the same data.
- Privacy model: My Orders only ever contains the viewer's own data plus anonymous aggregates (percentile, orders-per-participant histogram, organizer counts/shares).
/api/stats/mereturns no emails; organizers are numbered per response. Order Management is scoped to the viewer's own orders (their participants are visible, as in the order admin view). Only Site Overview (admin) sees site-wide data and names. - Not possible without new tracking: fill rate per order (no invitee list), display names (only emails exist; the local part is shown).
My Orders (orderees)
Layout: year calendar, lunch profile (all-time facts: orders, streak, average, busiest month, milestone, organizers, adventurousness, rank), activity, favourite items + payments, weekday x hour.
- ✅ Participation over time (orders joined per month) and current streak (consecutive weeks)
- ✅ Favourite items and categories, items repeated most (drill-down: all items, by category, or one category)
- ✅ Total and average spend per order (priced menus only)
- ✅ Usual order day / hour
- ✅ Most frequent organizers
- ✅ Payments: one table of open (accepted, unpaid) and paid orders, open first, then newest first; status filter
- 🔲 How early you submit before the deadline — orders have no deadline. Backend: add
group_orders.deadline(nullable ISO text) + create/update API + UI field; expose it in/api/stats/me.
Order Management
- ✅ Orders created over time; open vs. closed (closed = ordering done)
- ✅ Participants per order (average, trend); repeat vs. new participants
- ✅ Submission timeline: time from order creation to submission
- ✅ Popular items and categories across own orders (same category drill-down)
- ✅ Order volume: items and value per order
- ✅ Payment status: paid / accepted-unpaid / pending
- 🔲 Average time to payment — only the current
paidflag is stored. Backend: addsubmissions.paid_at(andaccepted_at), set on status updates, backfill NULL. - 🔲 Participants who are invited but rarely submit — there is no invite/participant list. Backend:
order_invitestable (order, email) or a per-owner contact list. - 🔲 Submission timeline relative to the deadline — needs the deadline (see above).
Site Overview (admins)
- ✅ Active contributors per week (users who created an order or submission), new registrations per week
- ✅ Users with recent activity (7 / 30 days, from last known activity — approximation)
- ✅ Unconfirmed accounts, pending email / user-id changes, open registration links
- ✅ Orders per week and share of orders with ≥1 submission
- ✅ Top order managers, most active participants
- ✅ Most ordered items site-wide (same category drill-down;
itemsin/api/admin/statsis capped at 300) - ✅ Storage usage per data folder and orphaned files
- 🔲 Real daily/weekly/monthly active users — only creation timestamps are recorded. Backend:
user_profiles.last_seen_atupdated (throttled) inget_existing_user_id, or anactivity_logtable. - 🔲 Email volume and failures —
email_servicedoes not log sends. Backend:email_logtable (kind, recipient hash, status, error, created_at) written byemail_service.