Reports frontend — spec-driven generate form, sliceable viewer, dimension bars #16

Merged
aetos merged 15 commits from cached-reporting into main 2026-08-21 14:16:36 +00:00
Owner

MR: Reports frontend — spec-driven generate form, sliceable viewer, dimension bars

Branch: cached-reportingTarget: main

Summary

Reports UI rebuilt to treat the backend OpenAPI spec as the source of truth: the
generate-report form renders generically from ReportQuery, and the viewer displays
API-computed metrics verbatim instead of recomputing them client-side.

Changes

react-openapi (library)

  • MultiEnumField renderer for array-of-enum fields; dispatch branch in
    FormFieldRenderer
  • useFkFieldOptions hook + FKFieldConfig.value (FK options keyed by a non-PK
    field — accounts resolve by name)
  • FieldConfig.defaultValue seeding in extractFields; enum lift for
    items.enum arrays; sanitize-payload bypasses FK resolution when fk.value is set

Reports page

  • GenerateReportPanel: rewritten (~275 → ~110 lines) — fields extracted from the
    served ReportQuery schema (labels, order, defaults, multi-enum, account picker);
    server-side validation only
  • ReportViewer:
    • Period groups built from cube buckets keyed by canonical period_id; buckets
      merged per period (disjoint slices), txn ids deduped
    • Cards/bars/metric strips read metrics verbatim (outflows/inflows/sum/count/
      avg/min/max/cadence/frequency); Net = one subtraction at render
    • Dimension-switchable bar strip (Period / Payees / Tags): segmented pill control
      with sliding indicator; pills disabled when the report has ≤1 distinct value;
      top-10 bars in a fixed view paged by chevrons (hidden scrollbar)
    • Active dim requests the full distribution via payee=* / tags=* when its own
      filter is unselected; explicit selections always slice normally
  • TransactionList: shared component with optional groups prop (API periods +
    metrics) used by the viewer; Expense page keeps the client-side fallback path;
    group strip shows Sum · Avg · Min · Max · Cadence (or Frequency /day when
    cadence < 1)

Fixes

  • Strict DD-MM-YYYY parsing (parseOccurredAt) in period merges and date ordering —
    new Date() mis-read dates as MM-DD-YYYY and corrupted merged cadence
    (Aug Zomato: 12.89 days displayed vs 0.76 actual)
  • Bar widths magnitude-based so all-outflow slices render at proper width
# MR: Reports frontend — spec-driven generate form, sliceable viewer, dimension bars **Branch:** `cached-reporting` → **Target:** `main` ## Summary Reports UI rebuilt to treat the backend OpenAPI spec as the source of truth: the generate-report form renders generically from `ReportQuery`, and the viewer displays API-computed metrics verbatim instead of recomputing them client-side. ## Changes ### react-openapi (library) - `MultiEnumField` renderer for array-of-enum fields; dispatch branch in `FormFieldRenderer` - `useFkFieldOptions` hook + `FKFieldConfig.value` (FK options keyed by a non-PK field — accounts resolve by `name`) - `FieldConfig.defaultValue` seeding in `extractFields`; enum lift for `items.enum` arrays; sanitize-payload bypasses FK resolution when `fk.value` is set ### Reports page - **GenerateReportPanel**: rewritten (~275 → ~110 lines) — fields extracted from the served `ReportQuery` schema (labels, order, defaults, multi-enum, account picker); server-side validation only - **ReportViewer**: - Period groups built from cube buckets keyed by canonical `period_id`; buckets merged per period (disjoint slices), txn ids deduped - Cards/bars/metric strips read `metrics` verbatim (outflows/inflows/sum/count/ avg/min/max/cadence/frequency); Net = one subtraction at render - Dimension-switchable bar strip (Period / Payees / Tags): segmented pill control with sliding indicator; pills disabled when the report has ≤1 distinct value; top-10 bars in a fixed view paged by chevrons (hidden scrollbar) - Active dim requests the full distribution via `payee=*` / `tags=*` when its own filter is unselected; explicit selections always slice normally - **TransactionList**: shared component with optional `groups` prop (API periods + metrics) used by the viewer; Expense page keeps the client-side fallback path; group strip shows Sum · Avg · Min · Max · Cadence (or Frequency /day when cadence < 1) ### Fixes - Strict DD-MM-YYYY parsing (`parseOccurredAt`) in period merges and date ordering — `new Date()` mis-read dates as MM-DD-YYYY and corrupted merged cadence (Aug Zomato: 12.89 days displayed vs 0.76 actual) - Bar widths magnitude-based so all-outflow slices render at proper width
aetos added 15 commits 2026-08-21 14:13:19 +00:00
Add a /reports page built on react-openapi's runtime configs:
- inline generate panel (GroupSpec enum, prefetched payee picker,
  strict DD-MM-YYYY range, 400 surfacing)
- metadata report list with view/regenerate/delete
- viewer that slices the cached per-entity payload via
  metadata.group_options, aggregates metrics + txns, pivots
  period x payee, and renders month accordions
- route + nav entry; no hand-rolled schema duplication
Collapse the Expenses page into a single src/Expense.tsx file and rename
the Reports page to src/Reports/Report.tsx, extracting their shared
transaction-list UI, date/grouping helpers, field configs, and types
into an expense/report-agnostic src/common module. Update ReportViewer
and GenerateReportPanel to consume the shared components, rewire
main.jsx imports, and remove the old src/Expense/ folder and
src/Reports/Reports.tsx.
Add two-layer grouping to the shared transaction list: each month
accordion now renders one card per occurred_at date (most recent first)
with a date label and txn count header, nesting the usual transaction
rows inside. Add dateLabel + groupByDate helpers to src/common and use
them in TransactionList. Month accordions, scroll pill/menu, and month
totals are unchanged.
Make the outer accordion grouping in TransactionList granularity-aware
instead of hardcoded months. TransactionList accepts a granularity prop
(weekly/monthly/quarterly/yearly, default monthly); ReportViewer passes
the report's granularity so a weekly report buckets transactions by ISO
week, quarterly by quarter, yearly by year. Expense page keeps monthly
grouping. Add periodKey/periodLabel/groupByPeriod/toPeriodGranularity to
src/common/utils/transactions.ts and drop the dead groupByMonth/
GroupedMonth helpers.
Compute Sum/Count/Avg/Min/Max/First/Last per accordion group in the
report view (mirroring backend ReportMetrics.compute_metrics signed
semantics) via new computeTxnMetrics helper and a showMetrics prop on
TransactionList. Remove the now-redundant top-level metric strip from
ReportViewer and restore its Outflows/Inflows/Transactions stat cards.
Export FkMultiSelectField from react-openapi and use it directly in the
report viewer for the Period/Payee dimension filters instead of the
custom OptionMultiSelect component, gaining the admin panel's QoL
behavior (no close-on-click, selected-first with tick marks, chips).
Feed it fabricated FieldConfigs plus fkOptions derived from the report's
metadata.group_options; delete the standalone OptionMultiSelect.
Move the Sum/Count/Avg/Min/Max/First/Last metric row from inside the
expanded accordion details into the accordion summary as a second row,
so group stats are visible while the group is collapsed. Second row
renders compact label-value pairs instead of pills; summary content is
now a two-row column.
Replace the dated First/Last pills with a Cadence metric computed from
consecutive transaction dates (mirrors backend ReportMetrics.cadence_days)
and render the group stats as spaced StatCards (Sum/Avg/Min/Max/Cadence)
in the collapsed accordion header, matching the Outflows/Inflows layout.
Replace hand-written GenerateReportPanel inputs with a generic loop over
extractFields("ReportQuery") + FormFieldRenderer; submit via
useResource("reports").create with server-side validation only.

react-openapi additions (all additive):
- lift items.enum into FieldConfig.enumValues for array-of-enum props
- extract schema `default` into FieldConfig.defaultValue
- new MultiEnumField renderer dispatched for isArray && enumValues
- FKFieldConfig.value: bind option values to a target property instead
  of the primary key (account names); sanitize-payload skips resolveFk
  when set
- new useFkFieldOptions hook encapsulating FK option loading/prefetch
- export extractFields, useFkFieldOptions, MultiEnumField

Drop now-unused granularityOptions/groupDimOptions helpers.
Group strip is now Sum/Avg/Min/Max/Cadence-Frequency; count already
shown in the accordion header, per-month rate derivable from monthly
buckets.
Segmented pill control with sliding indicator; pills disabled when the
report has <=1 distinct value for that dim. Bars show top 10 by |sum|
in a fixed-height view paged by chevrons (hidden scrollbar); widths are
magnitude-based so all-outflow slices render correctly.
When the active bar dimension's own filter is unselected, the viewer sends payee=*/tags=* so bars show the whole distribution; explicit selections still slice normally.
new Date() mis-read DD-MM-YYYY as MM-DD-YYYY, so multi-bucket periods
(e.g. merged inflow+outflow slices) re-derived cadence from garbage
dates — Aug Zomato showed 12.89 days instead of 0.76. Use the strict
parseOccurredAt() in the cadence branch and dateVal() so first/last
date merges order correctly too.
aetos merged commit 2add23efe7 into main 2026-08-21 14:16:36 +00:00
aetos deleted branch cached-reporting 2026-08-21 14:16:37 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: apps/khata-ui#16
No description provided.