From 3ce6600143c1dd028bff4c66a3af18247d0b7a7f Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 12 Jul 2026 11:58:02 +0200 Subject: [PATCH] feat: add xlsx export wherever csv export exists (#31690) --- AGENTS.md | 1 + api/api.go | 7 - assets/js/components/Config/HemsModal.vue | 62 +++---- .../js/components/Helper/DownloadButton.vue | 63 +++++++ assets/js/views/History.vue | 45 ++--- assets/js/views/Sessions.vue | 54 ++---- cmd/metrics_data.go | 24 ++- core/metrics/db_history.go | 52 +++--- core/metrics/db_history_test.go | 43 +++-- core/session/session.go | 13 +- go.mod | 6 + go.sum | 14 ++ hems/smartgrid/types.go | 12 +- i18n/de.json | 3 + i18n/en.json | 11 +- server/http_gridsessions_handler.go | 11 +- server/http_history_handler.go | 11 +- server/http_session_handler.go | 45 ++++- tests/hems.spec.ts | 47 ++++-- tests/sessions.spec.ts | 71 ++++++-- util/csv/writer.go | 156 ------------------ util/export/api.go | 22 +++ util/export/csv/csv.go | 56 +++++++ .../writer_test.go => export/csv/csv_test.go} | 59 ++----- util/export/writer.go | 121 ++++++++++++++ util/export/writer_test.go | 28 ++++ util/export/xlsx/xlsx.go | 131 +++++++++++++++ util/export/xlsx/xlsx_test.go | 112 +++++++++++++ util/locale/locale.go | 18 +- 29 files changed, 873 insertions(+), 425 deletions(-) create mode 100644 assets/js/components/Helper/DownloadButton.vue delete mode 100644 util/csv/writer.go create mode 100644 util/export/api.go create mode 100644 util/export/csv/csv.go rename util/{csv/writer_test.go => export/csv/csv_test.go} (57%) create mode 100644 util/export/writer.go create mode 100644 util/export/writer_test.go create mode 100644 util/export/xlsx/xlsx.go create mode 100644 util/export/xlsx/xlsx_test.go diff --git a/AGENTS.md b/AGENTS.md index 6a43b6572..2347c1edc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,6 +173,7 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w - Implement accessibility features (tabindex, aria-label, keyboard handlers) - Use descriptive names for variables, functions, and event handlers - Use early returns for readability +- Prefer named computed properties over inline template expressions, even for single use. Readability beats saving lines - Use configured Axios instance for HTTP communication ### State Management diff --git a/api/api.go b/api/api.go index b764073bc..0e726d747 100644 --- a/api/api.go +++ b/api/api.go @@ -1,8 +1,6 @@ package api import ( - "context" - "io" "net/url" "time" @@ -254,11 +252,6 @@ type TitleDescriber interface { GetTitle() string } -// CsvWriter converts to csv -type CsvWriter interface { - WriteCsv(context.Context, io.Writer) error -} - // CircuitMeasurements is the measurements a circuit or load must deliver type CircuitMeasurements interface { GetChargePower() float64 diff --git a/assets/js/components/Config/HemsModal.vue b/assets/js/components/Config/HemsModal.vue index 11a1e0252..18e1a6e42 100644 --- a/assets/js/components/Config/HemsModal.vue +++ b/assets/js/components/Config/HemsModal.vue @@ -38,28 +38,23 @@ {{ $t("config.general.docsLink") }}

- @@ -117,7 +109,7 @@ import { PERIODS } from "../components/Sessions/types"; import { GROUP_ORDER, groupColor, hasColorPicker } from "../components/History/groups"; import colors, { resolveColors, deviceColorMap, darken, batteryColor } from "../colors"; import LegendList from "../components/Sessions/LegendList.vue"; -import { handleDownloadClick } from "@/utils/native"; +import DownloadButton from "../components/Helper/DownloadButton.vue"; import formatter, { POWER_UNIT } from "../mixins/formatter"; import api from "../api"; import store from "../store"; @@ -138,6 +130,7 @@ export default defineComponent({ PeriodHeader, GroupChart, LegendList, + DownloadButton, }, mixins: [formatter], props: { @@ -316,16 +309,6 @@ export default defineComponent({ } return this.fmtWh(Math.abs(sum) * 1000, POWER_UNIT.AUTO); }, - csvLink(): string { - const params = new URLSearchParams({ - format: "csv", - lang: this.$i18n?.locale, - from: this.from.toISOString(), - to: this.to.toISOString(), - aggregate: this.aggregate, - }); - return `./api/history/energy?${params.toString()}`; - }, }, watch: { fetchKey() { @@ -359,7 +342,15 @@ export default defineComponent({ }, methods: { groupColor, - handleDownloadClick, + downloadHref(): string { + const params = new URLSearchParams({ + lang: this.$i18n?.locale, + from: this.from.toISOString(), + to: this.to.toISOString(), + aggregate: this.aggregate, + }); + return `./api/history/energy?${params.toString()}`; + }, legendsForGroup(group: string): Legend[] { const items = this.entityLegends(group); const focused = this.focusedEntity[group] ?? null; @@ -571,14 +562,6 @@ export default defineComponent({ opacity: 0.8; } } -.history-csv-link { - text-decoration: none; -} -.history-csv-link:hover, -.history-csv-link:focus { - color: var(--evcc-default-text); - text-decoration: underline; -} @media (max-width: 575.98px) { /* edge-to-edge on mobile: cancel the container's px-4 padding */ .history-tile { diff --git a/assets/js/views/Sessions.vue b/assets/js/views/Sessions.vue index 8f03afb01..94ca7c7c4 100644 --- a/assets/js/views/Sessions.vue +++ b/assets/js/views/Sessions.vue @@ -157,17 +157,8 @@ @show-session="showDetails" /> -
- - {{ csvLinkLabel }} - +
+