From 6dcbcfee252f3c034eb803f51c10c186a715cc54 Mon Sep 17 00:00:00 2001 From: Stan Runge Date: Sun, 25 Oct 2026 20:48:00 +0000 Subject: [PATCH] groceries stats: fix array/json params for the app client (live store filter 500), CPI axis, header spacing --- src/lib/server/groceries/stats.ts | 10 ++++++---- src/routes/trackers/groceries/stats/+page.svelte | 11 ++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lib/server/groceries/stats.ts b/src/lib/server/groceries/stats.ts index f972a3c..0cc4214 100644 --- a/src/lib/server/groceries/stats.ts +++ b/src/lib/server/groceries/stats.ts @@ -41,6 +41,8 @@ type Tx = TransactionSql; const ALL = 'all'; // Leaves under food tops that are not edible (the taxonomy flags them food via their top). const NON_FOOD_LEAVES = ['baby-kind--luiers', 'baby-kind--verzorging', 'baby-kind--accessoires']; +// Passed as an array literal: the app's postgres client doesn't serialise JS arrays here. +const NON_FOOD_ARR = `{${NON_FOOD_LEAVES.join(',')}}`; // Own (private-label) brands per store, as a regex on lower(brand). Superunie labels are shared // by its members (Dirk, Hoogvliet, Vomar, PLUS). `unbranded`: items without a brand are the @@ -327,10 +329,10 @@ async function prepare(tx: Tx, scope: StatsScope, stores: number[] | null) { left join groceries.groups g on g.id = i.group_id left join groceries.categories c on c.id = i.category_id left join groceries.categories tc on tc.id = coalesce(c.parent_id, c.id) - left join json_to_recordset(${tx.json(OWN_BRANDS as never)}::json) + left join json_to_recordset(${JSON.stringify(OWN_BRANDS)}::text::json) as ob(slug text, rx text, unbranded boolean) on ob.slug = s.slug where i.available and i.price_cents > 0 and i.unit_price_cents > 0 and i.unit is not null - and (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_LEAVES}) + and (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_ARR}::text[]) and i.name !~* 'e-?gift|cadeaukaart|giftcard|gift card')) and (${storeArr}::int[] is null or i.store_id = any(${storeArr}::int[])) and i.unit_price_cents >= case i.unit when 'piece' then 1 else 10 end @@ -717,7 +719,7 @@ async function history(tx: Tx, scope: StatsScope, stores: number[] | null) { join groceries.stores s on s.id = i.store_id and s.archived_at is null left join groceries.categories c on c.id = i.category_id left join groceries.categories tc on tc.id = coalesce(c.parent_id, c.id) - where (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_LEAVES}) + where (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_ARR}::text[]) and i.name !~* 'e-?gift|cadeaukaart|giftcard|gift card')) and (${storeArr}::int[] is null or i.store_id = any(${storeArr}::int[]))`; await tx`create unique index on it (id)`; @@ -904,7 +906,7 @@ async function history(tx: Tx, scope: StatsScope, stores: number[] | null) { async function basket(tx: Tx) { const rows = await tx` with st as ( - select * from json_to_recordset(${tx.json(STAPLES as never)}::json) + select * from json_to_recordset(${JSON.stringify(STAPLES)}::text::json) as x(key text, rx text, nrx text, units text, qty float8, cats text, min float8, max float8, "pieceKg" float8) ), diff --git a/src/routes/trackers/groceries/stats/+page.svelte b/src/routes/trackers/groceries/stats/+page.svelte index 98ed833..54b1960 100644 --- a/src/routes/trackers/groceries/stats/+page.svelte +++ b/src/routes/trackers/groceries/stats/+page.svelte @@ -394,7 +394,12 @@ tooltip: { trigger: 'axis', valueFormatter: (v: number) => (v == null ? '–' : v.toFixed(2)) }, legend: legend(t), xAxis: { type: 'category', data: (s?.days ?? []).map(fmtDay), axisLabel: { color: t.muted } }, - yAxis: axisVal(t), + yAxis: { + ...axisVal(t), + scale: true, + minInterval: 0.1, + axisLabel: { color: t.muted, formatter: (v: number) => v.toFixed(1) } + }, series: ids.map((k) => ({ name: sname(Number(k)), type: 'line', @@ -588,8 +593,8 @@