client: fix reset-error handling, undersized qty buttons, chart resize,

dedupe j()/errText(), stale-fetch guard, empty bar-picker state

Six small, mostly-unrelated items from the #47 review round (#57):

1. 'Statistik zurücksetzen' now checks res.ok and shows the server's
   error instead of silently re-rendering the un-reset data on a 401/500
   with no indication anything went wrong.
2. DayChart now tracks its container width via ResizeObserver and calls
   uPlot's setSize() on change — previously read once at plot-creation
   time, so a resize/orientation-change (more visible on the phone/
   tablet path into /stats) left the chart the wrong width.
3. .qty-btn (cart +/- buttons) grown from 28x28px to 44x44px, the
   conventional minimum touch target — was the one conspicuously small
   interactive control in the money path, at a fast-moving bar counter.
4. Tablet's bar-config fetch (App.tsx) now guards against an
   out-of-order resolution if barId changes again while a previous
   api.config() call is still in flight. Currently unreachable (nothing
   triggers a second barId change mid-fetch today) but closes the gap
   before the next 'add a cancel button to the loading screen' change
   could make it live.
5. j()/errText() were duplicated byte-for-byte between Admin.tsx and
   Stats.tsx (Admin.tsx's own comment noted this exact duplication had
   already caused drift once before) — centralized in api.ts, both files
   now import instead of redeclaring.
6. Tablet's bar picker now shows an explicit empty-state message when
   zero bars exist (fresh install, or every bar deleted), previously
   indistinguishable from 'still loading'.

Closes #57. Verified: tsc --noEmit and vite build both clean.
This commit is contained in:
iris 2026-07-31 11:17:08 +02:00 • committed by mara
commit 7a58a4a1bb

View file

@ -1,24 +1,6 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import type { Bar, Drink } from '@wutzcalc/shared';
import { formatCents } from '../api';
async function j<T = any>(res: Response): Promise<T> {
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return res.json();
}
// Best-effort error message from a failed response. Prefers RFC 7807
// (application/problem+json) `detail`/`title` — what the admin routes send
// — falls back to the older ad hoc `{ error }` shape (still used by a few
// not-yet-converted endpoints), then plain status text.
async function errText(res: Response): Promise<string> {
try {
const body = await res.json();
return body.detail ?? body.title ?? body.error ?? res.statusText;
} catch {
return res.statusText;
}
}
import { errText, formatCents, j } from '../api';
// `Drink`/`Bar` come from @wutzcalc/shared — the tablet code already did
// this, but Admin.tsx used to re-declare its own near-identical copies,