Files
mg4/web/src/app/consumption/page.tsx
T
nilsandClaude Opus 5.5 538d03361d Translate the dashboard to English; euro instead of lira
Dashboard: every label, empty state, chart legend and the login page
are English; numbers and dates use en-GB (24-hour clock, day-month
dates), money is EUR, percentages read 80% instead of %80, durations
"2 h 05 min". html lang is en. Server version 1.1.2; manifests pin it.

Car app: the charging tab's price suffix, validation message and total
cost use € instead of ₺.

Checked by rendering all three pages from the 1.1.2 image against
InfluxDB 2.7.12 in podman.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
2026-10-17 14:39:59 +02:00

179 lines
7.2 KiB
TypeScript

import Link from "next/link";
import { requireSession } from "@/lib/auth";
import { loadOverview, sumDays } from "@/lib/data";
import { duration, efficiency, isoDate, num, precise } from "@/lib/format";
import { DailyEnergyChart, EfficiencyChart } from "@/components/charts";
import { Row, Shell, Tile } from "@/components/shell";
export const dynamic = "force-dynamic";
export default async function ConsumptionPage({
searchParams,
}: {
searchParams: Promise<{ date?: string }>;
}) {
await requireSession();
const { date: selectedDate } = await searchParams;
const { installationId, days } = await loadOverview(90, 1);
if (!installationId || days.length === 0) {
return (
<Shell active="/consumption" title="Consumption">
<div className="card">
<p className="empty">No daily consumption records yet. The car uploads them as it drives.</p>
</div>
</Shell>
);
}
const chartDays = days.map((day) => ({ date: day.date, kwh: day.day.kwh, km: day.day.km }));
const total = sumDays(days);
// Lifetime is derived: the sum of every day's totals, same as the backend computes.
const lifetime = { km: total.km, kwh: total.kwh, hours: total.hours, socDrop: 0 };
// Newest first for reading; the charts above keep chronological order.
const table = [...days].reverse();
// Find the selected day's record (if any). table is newest-first; a Map would be overkill
// for ≤90 entries.
const selected = selectedDate ? table.find((d) => d.date === selectedDate) : null;
const hasFilter = !!selectedDate;
const validFilter = hasFilter && !!selected;
// Date input bounds: oldest record to today.
const minDate = days[0].date; // days is chronological (oldest first after loadOverview reverse)
const maxDate = new Date().toISOString().slice(0, 10);
return (
<Shell
active="/consumption"
title="Consumption"
subtitle={hasFilter ? (validFilter ? isoDate(selectedDate!) : "No record for the selected day") : `${days.length} day${days.length === 1 ? "" : "s"} recorded`}
>
{/* Date filter */}
<section className="card">
<form className="filter-form" method="get" action="/consumption">
<label className="filter-label" htmlFor="date-picker">
Filter by date
</label>
<div className="filter-row">
<input
id="date-picker"
type="date"
name="date"
defaultValue={selectedDate ?? ""}
min={minDate}
max={maxDate}
className="field date-input"
/>
<button className="button filter-button" type="submit">Filter</button>
{hasFilter && (
<Link className="filter-clear" href="/consumption">Clear</Link>
)}
</div>
</form>
</section>
{/* Selected day detail — shown above the period tiles when a filter is active */}
{hasFilter && (
<section className="card">
<h2 className="card-title">{isoDate(selectedDate!)}</h2>
{validFilter && selected ? (
<>
<div className="tiles">
<Tile label="Distance" value={num(selected.day.km)} unit="km" tone="teal" />
<Tile label="Energy" value={precise(selected.day.kwh)} unit="kWh" tone="gold" />
<Tile label="Average" value={efficiency(selected.day.kwh, selected.day.km)} tone="teal" />
<Tile
label="Average speed"
value={selected.day.hours > 0 ? num(selected.day.km / selected.day.hours) : "—"}
unit={selected.day.hours > 0 ? "km/h" : undefined}
/>
</div>
<div className="rows">
<Row label="Driving time" value={duration(selected.day.hours * 3600)} />
<Row label="SoC used" value={`${num(selected.day.socDrop)}%`} />
<Row label="Lifetime at end of day" value={selected.lifetime ? `${num(selected.lifetime.km)} km` : "—"} />
</div>
</>
) : (
<p className="empty">No consumption recorded for this date.</p>
)}
</section>
)}
<div className="tiles">
<Tile label="Total distance" value={num(total.km)} unit="km" tone="teal" />
<Tile label="Total energy" value={precise(total.kwh)} unit="kWh" tone="gold" />
<Tile label="Average" value={efficiency(total.kwh, total.km)} tone="teal" />
<Tile
label="Average speed"
value={total.hours > 0 ? num(total.km / total.hours) : "—"}
unit={total.hours > 0 ? "km/h" : undefined}
/>
</div>
<section className="card">
<h2 className="card-title">Daily energy</h2>
<DailyEnergyChart days={chartDays} />
</section>
<section className="card">
<h2 className="card-title">Efficiency trend</h2>
<EfficiencyChart days={chartDays} />
</section>
<section className="card">
<h2 className="card-title">Lifetime</h2>
<div className="rows">
<Row label="Distance" value={`${num(lifetime.km)} km`} />
<Row label="Energy" value={`${precise(lifetime.kwh)} kWh`} />
<Row label="Average" value={efficiency(lifetime.kwh, lifetime.km)} />
<Row label="Driving time" value={duration(lifetime.hours * 3600)} />
</div>
</section>
<section className="card">
<h2 className="card-title">Day by day</h2>
<ul className="list">
{table.map((day) => {
const isSelected = hasFilter && day.date === selectedDate;
return (
<li key={day.date}>
<details className={`session${isSelected ? " selected" : ""}`} open={isSelected}>
<summary>
<div>
<div className="session-when">
<Link href={`/consumption?date=${day.date}`} className="day-link">
{isoDate(day.date)}
</Link>
</div>
<div className="session-meta">
{num(day.day.km)} km · {efficiency(day.day.kwh, day.day.km)}
</div>
</div>
<div className="session-energy">
{precise(day.day.kwh)}
<span className="session-cost"> kWh</span>
</div>
</summary>
<div className="session-body">
<div className="rows">
<Row label="Driving time" value={duration(day.day.hours * 3600)} />
<Row
label="Average speed"
value={day.day.hours > 0 ? `${num(day.day.km / day.day.hours)} km/h` : "—"}
/>
<Row label="SoC used" value={`${num(day.day.socDrop)}%`} />
<Row label="Lifetime at end of day" value={day.lifetime ? `${num(day.lifetime.km)} km` : "—"} />
</div>
</div>
</details>
</li>
);
})}
</ul>
</section>
</Shell>
);
}