feat: export widget data as CSV (#28) #38

Merged
pitfriedrich merged 1 commits from ai/issue-28-widget-csv-export into main 2026-07-28 19:49:21 +00:00
Collaborator

Closes #28

Was

Die ApexCharts-Toolbar bleibt (wie in DialectTheme festgelegt) ausgeblendet — damit gab es bisher gar keinen Export. Der Einstieg ist jetzt, wie im Issue vorgeschlagen, das Per-Widget-Aktionsmenü aus #27: neuer Eintrag „Als CSV exportieren", serverseitig erzeugt aus denselben Daten, die das Widget rendert.

Änderungen

  • export/CsvExport (neu): die Exporttabelle plus CSV-Dialekt — ; als Trenner (Dezimalkomma macht , unbrauchbar), CRLF, UTF-8-BOM für Excel, Zahlen im Locale der UI, RFC-4180-Quoting. Dazu die Dateinamen-Slugs (Umlaute/ß werden aufgelöst).
  • GridStackItem.setActionDownload(Action, DownloadHandler): macht aus der Caption eines Menüeintrags einen Anchor über einen DownloadHandler — der Eintrag gibt eine Datei heraus, statt nur ein ActionEvent zu melden. Neuer Action.EXPORT.
  • WidgetRegistry: bind(...) hält jetzt die Query, nicht nur deren Ergebnis, so dass Refresh und der neue Export-Hook sie gegen den aktuellen Filter neu ausführen.
  • DashboardView: baut den Download beim Klick. Der Request läuft ohne Session-Lock und ohne current UI (siehe StreamRequestHandler), deshalb wird der Lock für den Lesevorgang genommen und das Locale explizit durchgereicht.
  • Copy in allen drei Bundles (gridstack.export, export.category), Bedienungs-Hinweis ergänzt; styles.css streckt den Link über die ganze Menüzeile.

Akzeptanzkriterien

  • CSV enthält die Werte des Widgets inkl. aktivem globalem Filter (#24) — Callback liest erst beim Klick.
  • Sprechender Dateiname: Widget-Titel + Zeitraum, z.B. umsatz-entwicklung-halbjahr.csv.
  • Chart-Styling unverändert, ApexCharts-Toolbar bleibt aus.
  • Export-Copy auf Deutsch, Keys in allen drei Bundles.

Der Bild-Export (dataURI()) ist bewusst nicht dabei — das Issue nennt ihn optionalen Follow-up.

Tests

./mvnw test67/67 grün. Neu:

  • export/CsvExportTest — CSV-Dialekt, Quoting, BOM, Slugging.
  • views/DashboardViewTest — Export-Eintrag ist ein Download-Link (nicht bei KPI-Kacheln), Exportdaten entsprechen dem Service, folgen dem Filter, Pie-Chart exportiert die Regionen.
  • e2e/WidgetActionMenuPlaywrightTest — echter Browser-Download: Dateiname revenue-trend-half-year.csv, BOM, Header- und Datenzeilen.

🤖 Generated with Claude Code

https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69

Closes #28 ## Was Die ApexCharts-Toolbar bleibt (wie in `DialectTheme` festgelegt) ausgeblendet — damit gab es bisher gar keinen Export. Der Einstieg ist jetzt, wie im Issue vorgeschlagen, das Per-Widget-Aktionsmenü aus #27: neuer Eintrag **„Als CSV exportieren"**, serverseitig erzeugt aus denselben Daten, die das Widget rendert. ## Änderungen - **`export/CsvExport`** (neu): die Exporttabelle plus CSV-Dialekt — `;` als Trenner (Dezimalkomma macht `,` unbrauchbar), CRLF, UTF-8-BOM für Excel, Zahlen im Locale der UI, RFC-4180-Quoting. Dazu die Dateinamen-Slugs (Umlaute/`ß` werden aufgelöst). - **`GridStackItem.setActionDownload(Action, DownloadHandler)`**: macht aus der Caption eines Menüeintrags einen Anchor über einen `DownloadHandler` — der Eintrag gibt eine Datei heraus, statt nur ein `ActionEvent` zu melden. Neuer `Action.EXPORT`. - **`WidgetRegistry`**: `bind(...)` hält jetzt die Query, nicht nur deren Ergebnis, so dass `Refresh` und der neue `Export`-Hook sie gegen den *aktuellen* Filter neu ausführen. - **`DashboardView`**: baut den Download beim Klick. Der Request läuft ohne Session-Lock und ohne current `UI` (siehe `StreamRequestHandler`), deshalb wird der Lock für den Lesevorgang genommen und das Locale explizit durchgereicht. - Copy in allen drei Bundles (`gridstack.export`, `export.category`), Bedienungs-Hinweis ergänzt; `styles.css` streckt den Link über die ganze Menüzeile. ## Akzeptanzkriterien - [x] CSV enthält die Werte des Widgets inkl. aktivem globalem Filter (#24) — Callback liest erst beim Klick. - [x] Sprechender Dateiname: Widget-Titel + Zeitraum, z.B. `umsatz-entwicklung-halbjahr.csv`. - [x] Chart-Styling unverändert, ApexCharts-Toolbar bleibt aus. - [x] Export-Copy auf Deutsch, Keys in allen drei Bundles. Der Bild-Export (`dataURI()`) ist bewusst nicht dabei — das Issue nennt ihn optionalen Follow-up. ## Tests `./mvnw test` → **67/67 grün**. Neu: - `export/CsvExportTest` — CSV-Dialekt, Quoting, BOM, Slugging. - `views/DashboardViewTest` — Export-Eintrag ist ein Download-Link (nicht bei KPI-Kacheln), Exportdaten entsprechen dem Service, folgen dem Filter, Pie-Chart exportiert die Regionen. - `e2e/WidgetActionMenuPlaywrightTest` — echter Browser-Download: Dateiname `revenue-trend-half-year.csv`, BOM, Header- und Datenzeilen. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
claude-bot added 1 commit 2026-07-28 19:46:06 +00:00
fix: export widget data as CSV from the action menu (#28)
CI / build-and-test (pull_request) Successful in 2m26s
cfa644c0e1
The ApexCharts toolbar stays hidden (DialectTheme), so the dashboard had no
export at all. Adds an "Als CSV exportieren" entry to the per-widget action
menu, served server-side from the same data the widget renders.

- CsvExport: the export table plus its CSV dialect (';' separator, CRLF,
  UTF-8 BOM, locale-formatted numbers) and file-name slugging.
- GridStackItem.setActionDownload: turns a menu entry's caption into an
  anchor over a DownloadHandler, so an entry can hand out a file.
- WidgetRegistry: keeps each widget's query, not only its result, so the
  Export hook re-runs it — an export always matches the current filter.
- DashboardView: builds the download on click; file name is widget title +
  period (revenue-trend-half-year.csv).

Chart image export is left as the follow-up the issue calls optional.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
pitfriedrich merged commit 71892ea7ad into main 2026-07-28 19:49:21 +00:00
pitfriedrich deleted branch ai/issue-28-widget-csv-export 2026-07-28 19:49:21 +00:00
Sign in to join this conversation.