From cfe145f94fed0b65cb383cff774721beaac0e232 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 6 Nov 2026 13:35:02 +0100 Subject: [PATCH] Stats: column charts for per-period activity, Payments card last on My Orders Co-Authored-By: Claude Sonnet 5.5 --- .../components/statistics/MyOrdersStats.tsx | 27 +++++++++---------- .../components/statistics/TimeSeriesPanel.tsx | 12 ++++++--- .../src/components/statistics/charts.tsx | 6 +++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/components/statistics/MyOrdersStats.tsx b/src/frontend/src/components/statistics/MyOrdersStats.tsx index ee1e0ad..375e156 100644 --- a/src/frontend/src/components/statistics/MyOrdersStats.tsx +++ b/src/frontend/src/components/statistics/MyOrdersStats.tsx @@ -25,7 +25,7 @@ import { FactGrid, Headline } from "./Highlights"; import ItemRanking from "./ItemRanking"; import { useRange } from "./range"; import TimeSeriesPanel from "./TimeSeriesPanel"; -import { CardGrid, ChartCard, PageStack } from "./StatCard"; +import { ChartCard, PageStack } from "./StatCard"; const load = () => apiService.stats.getMine(); @@ -144,23 +144,22 @@ export default function MyOrdersStats() { ]} /> - - s.items))} /> - s.total ?? 0)))} to pay` : "all settled"}> - Number(x.paid) - Number(y.paid) || y.submitted_at.localeCompare(x.submitted_at))} - columns={paymentColumns} - locale={{ emptyText: "No payments yet" }} - /> - - + s.items))} /> + + s.total ?? 0)))} to pay` : "all settled"}> +
Number(x.paid) - Number(y.paid) || y.submitted_at.localeCompare(x.submitted_at))} + columns={paymentColumns} + locale={{ emptyText: "No payments yet" }} + /> + ); diff --git a/src/frontend/src/components/statistics/TimeSeriesPanel.tsx b/src/frontend/src/components/statistics/TimeSeriesPanel.tsx index 7c63b4e..8470bb4 100644 --- a/src/frontend/src/components/statistics/TimeSeriesPanel.tsx +++ b/src/frontend/src/components/statistics/TimeSeriesPanel.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Switch, Typography } from "antd"; import { Bucket, periodBuckets, sumIn, TimedValue } from "../../lib/stats"; -import { LineChart } from "./charts"; +import { ColumnChart, LineChart } from "./charts"; import { useRange } from "./range"; import { CardGrid, ChartCard, Delta } from "./StatCard"; @@ -17,7 +17,7 @@ const accumulate = (buckets: Bucket[]): Bucket[] => { return buckets.map((b) => ({ ...b, value: (total += b.value) })); }; -/** Per-period values (days, weeks or months, depending on the timeframe) as line charts, optionally cumulative, each with its change vs. the previous period. */ +/** Per-period values (days, weeks or months, depending on the timeframe) as columns (one per day for ranges up to ~3 months) or, when cumulative, as lines, each with its change vs. the previous period. */ export default function TimeSeriesPanel({ title = "Activity", metrics }: { title?: string; metrics: TimeMetric[] }) { const [cumulative, setCumulative] = useState(false); const earliestMs = Math.min(Infinity, ...metrics.flatMap((m) => m.events.map((e) => e.date.getTime()))); @@ -38,7 +38,7 @@ export default function TimeSeriesPanel({ title = "Activity", metrics }: { title } > - + {metrics.map((metric) => { const format = metric.format ?? String; const total = sumIn(metric.events, range); @@ -52,7 +52,11 @@ export default function TimeSeriesPanel({ title = "Activity", metrics }: { title {format(total)} - + {cumulative ? ( + + ) : ( + + )} ); })} diff --git a/src/frontend/src/components/statistics/charts.tsx b/src/frontend/src/components/statistics/charts.tsx index f6b7009..64da491 100644 --- a/src/frontend/src/components/statistics/charts.tsx +++ b/src/frontend/src/components/statistics/charts.tsx @@ -19,6 +19,8 @@ export function ColumnChart({ }) { const { token } = theme.useToken(); const max = Math.max(1, ...data.map((d) => d.value + (d.extra ?? 0))); + // Dense series (a bar per day) need thinner gaps to leave the bars visible + const gap = data.length > 60 ? 1 : data.length > 30 ? 2 : 3; if (data.every((d) => d.value + (d.extra ?? 0) === 0)) return noData; return ( @@ -44,7 +46,7 @@ export function ColumnChart({
{/* Labels sit in their own row. With every n-th label shown, each one spans its n slots and is truncated there, so long names never collide. */} -
+
{data.map((d, i) => i % labelEvery === 0 ? (