Sessions: export csv by month (#5883)

* make vehicle of session editable

* filter sessions by month and adjust the csv export

* Revert "filter sessions by month and adjust the csv export"

This reverts commit c5d2202d181312357396369019872217ccd6d4b1.

* make code and ui changes for editable session vehicle

* updated generated files

* add error handling for json unmarshal

* filter sessions by month and adjust the csv export

* delete filter and add button to monthly section

* Merge master

* revert unwanted merge conflict changes

* revert unwanted merge conflict changes

* fix code after merge

* optimize code

* Update http_session_handler.go

* align button and text centered

---------

Co-authored-by: Krümel <christopher.teipen@andsafe.de>
Co-authored-by: andig <cpuidle@gmx.de>
This commit is contained in:
CuBy 2023-01-31 15:54:31 +01:00 • committed by GitHub
parent 03e08fe6f3
commit b7b523eedc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 97 deletions

View file

@ -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);
}

View file

@ -12,10 +12,10 @@
<div class="row">
<main class="col-12">
<div class="mb-4">
<div class="d-flex justify-content-start mb-4">
<a
class="btn btn-outline-secondary text-nowrap my-2"
:href="`./api/sessions?format=csv&amp;lang=${$i18n.locale}`"
:href="csvHrefLink()"
download="sessions.csv"
>
{{ $t("sessions.downloadCsv") }}
@ -23,97 +23,96 @@
</div>
<div v-for="group in sessionsByMonthAndLoadpoint" :key="group.month">
<div class="mx-2">
<div class="d-flex align-items-baseline my-5">
<h2 class="me-4 mb-0">
{{ formatGroupHeadline(group.month) }}
</h2>
<div class="d-flex align-items-center my-5">
<h2 class="me-4 mb-0">
{{ formatGroupHeadline(group.month) }}
</h2>
<a
class="btn btn-xs btn-outline-secondary text-nowrap"
:href="csvHrefLink(group.month)"
download="sessions.csv"
>
CSV
</a>
</div>
<div v-for="loadpoint in group.loadpoints" :key="loadpoint.name">
<div class="d-flex align-items-baseline mb-3">
<h3 class="me-4 mb-0">
{{ loadpoint.name }}
</h3>
<div class="large">{{ fmtKWh(loadpoint.total) }}</div>
</div>
<div v-for="loadpoint in group.loadpoints" :key="loadpoint.name">
<div class="d-flex align-items-baseline mb-3">
<h3 class="me-4 mb-0">
{{ loadpoint.name }}
</h3>
<div class="large">{{ fmtKWh(loadpoint.total) }}</div>
</div>
<ul class="breakdown text-gray d-sm-flex flex-sm-wrap ps-0 mb-2">
<li
v-for="(vehicle, id) in groupedKWh(
'vehicle',
loadpoint.sessions
)"
:key="id"
class="breakdown-item"
>
{{ vehicle.name }}: {{ fmtKWh(vehicle.energy) }}
</li>
</ul>
<div class="table-responsive my-3">
<table class="table">
<thead>
<tr>
<th scope="col">{{ $t("sessions.vehicle") }}</th>
<th scope="col" class="text-end ps-sm-4 pe-md-5">
{{ $t("sessions.energy") }}
</th>
<th scope="col" class="ps-3 ps-md-4 ps-md-5">
{{ $t("sessions.date") }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(session, id) in loadpoint.sessions"
:key="id"
role="button"
@click="showDetails(session.id)"
<ul class="breakdown text-gray d-sm-flex flex-sm-wrap ps-0 mb-2">
<li
v-for="(vehicle, id) in groupedKWh('vehicle', loadpoint.sessions)"
:key="id"
class="breakdown-item"
>
{{ vehicle.name }}: {{ fmtKWh(vehicle.energy) }}
</li>
</ul>
<div class="table-responsive my-3">
<table class="table">
<thead>
<tr>
<th scope="col">{{ $t("sessions.vehicle") }}</th>
<th scope="col" class="text-end ps-sm-4 pe-md-5">
{{ $t("sessions.energy") }}
</th>
<th scope="col" class="ps-3 ps-md-4 ps-md-5">
{{ $t("sessions.date") }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(session, id) in loadpoint.sessions"
:key="id"
role="button"
@click="showDetails(session.id)"
>
<td class="align-middle">
{{ session.vehicle }}
</td>
<td
class="text-nowrap text-end ps-sm-4 pe-md-5 align-middle"
>
<td class="align-middle">
{{ session.vehicle }}
</td>
<td
class="text-nowrap text-end ps-sm-4 pe-md-5 align-middle"
>
{{ fmtKWh(session.chargedEnergy * 1e3) }}
</td>
<td class="text-nowrap ps-3 ps-md-4 ps-md-5">
<span class="d-block d-sm-none">
{{
fmtFullDateTime(
new Date(session.created),
true
)
}}
<br />
{{
fmtFullDateTime(
new Date(session.finished),
true
)
}}
</span>
<span class="d-none d-sm-block">
{{
fmtFullDateTime(
new Date(session.created),
false
)
}}
<br />
{{
fmtFullDateTime(
new Date(session.finished),
false
)
}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
{{ fmtKWh(session.chargedEnergy * 1e3) }}
</td>
<td class="text-nowrap ps-3 ps-md-4 ps-md-5">
<span class="d-block d-sm-none">
{{
fmtFullDateTime(new Date(session.created), true)
}}
<br />
{{
fmtFullDateTime(
new Date(session.finished),
true
)
}}
</span>
<span class="d-none d-sm-block">
{{
fmtFullDateTime(
new Date(session.created),
false
)
}}
<br />
{{
fmtFullDateTime(
new Date(session.finished),
false
)
}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
@ -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;
},
},
};
</script>

View file

@ -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
}