diff --git a/assets/css/app.css b/assets/css/app.css index 4fb8591a3..5d830fb33 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -183,6 +183,14 @@ a:hover { border-color: var(--evcc-accent3) !important; } +.btn-xs { + --bs-btn-padding-y: 0; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.7rem; + --bs-btn-border-radius: 0.25rem; + --bs-btn-line-height: 2; +} + .text-evcc { color: var(--evcc-dark-green); } diff --git a/assets/js/views/ChargingSessions.vue b/assets/js/views/ChargingSessions.vue index 28926b1ad..86a0d5f50 100644 --- a/assets/js/views/ChargingSessions.vue +++ b/assets/js/views/ChargingSessions.vue @@ -12,10 +12,10 @@
-
+
-
-
-

- {{ formatGroupHeadline(group.month) }} -

+
+ +
+
+

+ {{ loadpoint.name }} +

+
{{ fmtKWh(loadpoint.total) }}
-
-
-

- {{ loadpoint.name }} -

-
{{ fmtKWh(loadpoint.total) }}
-
- -
    -
  • - {{ vehicle.name }}: {{ fmtKWh(vehicle.energy) }} -
  • -
-
- - - - - - - - - - +
  • + {{ vehicle.name }}: {{ fmtKWh(vehicle.energy) }} +
  • + +
    +
    {{ $t("sessions.vehicle") }} - {{ $t("sessions.energy") }} - - {{ $t("sessions.date") }} -
    + + + + + + + + + + + - - - - -
    {{ $t("sessions.vehicle") }} + {{ $t("sessions.energy") }} + + {{ $t("sessions.date") }} +
    + {{ session.vehicle }} + - - {{ session.vehicle }} - - {{ fmtKWh(session.chargedEnergy * 1e3) }} - - - {{ - fmtFullDateTime( - new Date(session.created), - true - ) - }} -
    - {{ - fmtFullDateTime( - new Date(session.finished), - true - ) - }} -
    - - {{ - fmtFullDateTime( - new Date(session.created), - false - ) - }} -
    - {{ - fmtFullDateTime( - new Date(session.finished), - false - ) - }} -
    -
    -
    + {{ fmtKWh(session.chargedEnergy * 1e3) }} + + + + {{ + fmtFullDateTime(new Date(session.created), true) + }} +
    + {{ + fmtFullDateTime( + new Date(session.finished), + true + ) + }} +
    + + {{ + fmtFullDateTime( + new Date(session.created), + false + ) + }} +
    + {{ + fmtFullDateTime( + new Date(session.finished), + false + ) + }} +
    + + + +
    @@ -131,7 +130,6 @@ import Modal from "bootstrap/js/dist/modal"; import TopNavigation from "../components/TopNavigation.vue"; import "@h2d2/shopicons/es/bold/arrowback"; -import "@h2d2/shopicons/es/regular/trash"; import formatter from "../mixins/formatter"; import api from "../api"; import store from "../store"; @@ -187,7 +185,7 @@ export default { groupByMonth(sessions) { return sessions.reduce((groups, session) => { const date = new Date(session.finished); - const month = `${date.getFullYear()}.${date.getMonth()}`; + const month = `${date.getFullYear()}.${date.getMonth() + 1}`; if (!groups[month]) groups[month] = []; groups[month].push(session); return groups; @@ -219,7 +217,7 @@ export default { formatGroupHeadline(group) { const date = new Date(); const [year, month] = group.split("."); - date.setMonth(month); + date.setMonth(month - 1); date.setFullYear(year); return this.fmtMonthYear(date); }, @@ -228,6 +226,18 @@ export default { const modal = Modal.getOrCreateInstance(document.getElementById("sessionDetailsModal")); modal.show(); }, + csvHrefLink(groupKey) { + var url = `./api/sessions?format=csv&lang=${this.$i18n.locale}`; + if (groupKey) { + const [year, month] = groupKey.split("."); + url += `&year=${year}`; + + if (month) { + url += `&month=${month}`; + } + } + return url; + }, }, }; diff --git a/server/http_session_handler.go b/server/http_session_handler.go index f8172db3b..b83fb3a7d 100644 --- a/server/http_session_handler.go +++ b/server/http_session_handler.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "errors" + "fmt" "net/http" + "strconv" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/db" @@ -14,9 +16,9 @@ import ( "golang.org/x/text/language" ) -func csvResult(ctx context.Context, w http.ResponseWriter, res any) { +func csvResult(ctx context.Context, w http.ResponseWriter, res any, filename string) { w.Header().Set("Content-Type", "text/csv") - w.Header().Set("Content-Disposition", `attachment; filename="sessions.csv"`) + w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`.csv"`) if ww, ok := res.(api.CsvWriter); ok { _ = ww.WriteCsv(ctx, w) @@ -33,8 +35,31 @@ func sessionHandler(w http.ResponseWriter, r *http.Request) { } var res db.Sessions + year := r.URL.Query().Get("year") + month := r.URL.Query().Get("month") - if txn := dbserver.Instance.Table("sessions").Where("charged_kwh>=0.05").Order("created desc").Find(&res); txn.Error != nil { + filename := "session" + + fmtYear := "%" + fmtMonth := "%" + + if year != "" { + fmtYear = year + filename += "-" + fmtYear + + if month != "" { + iMonth, err := strconv.Atoi(month) + if err != nil { + jsonError(w, http.StatusInternalServerError, err) + return + } + fmtMonth = fmt.Sprintf("%02d", iMonth) + filename += "." + fmtMonth + } + } + + whereQuery := "charged_kwh>=0.05 and strftime('%Y', created) like ? and strftime('%m', created) like ?" + if txn := dbserver.Instance.Where(whereQuery, fmtYear, fmtMonth).Order("created desc").Find(&res); txn.Error != nil { jsonError(w, http.StatusInternalServerError, txn.Error) return } @@ -50,7 +75,7 @@ func sessionHandler(w http.ResponseWriter, r *http.Request) { } ctx := context.WithValue(context.Background(), locale.Locale, lang) - csvResult(ctx, w, &res) + csvResult(ctx, w, &res, filename) return }