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:
parent
03e08fe6f3
commit
b7b523eedc
3 changed files with 140 additions and 97 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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&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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue