diff --git a/web/src/app/consumption/page.tsx b/web/src/app/consumption/page.tsx index 922f94b..5e7294f 100644 --- a/web/src/app/consumption/page.tsx +++ b/web/src/app/consumption/page.tsx @@ -1,3 +1,4 @@ +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"; @@ -6,8 +7,13 @@ import { Row, Shell, Tile } from "@/components/shell"; export const dynamic = "force-dynamic"; -export default async function ConsumptionPage() { +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) { @@ -26,8 +32,74 @@ export default async function ConsumptionPage() { // 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 ( - + + {/* Date filter */} +
+
+ +
+ + + {hasFilter && ( + Temizle + )} +
+
+
+ + {/* Selected day detail — shown above the period tiles when a filter is active */} + {hasFilter && ( +
+

{isoDate(selectedDate!)}

+ {validFilter && selected ? ( + <> +
+ + + + 0 ? num(selected.day.km / selected.day.hours) : "—"} + unit={selected.day.hours > 0 ? "km/h" : undefined} + /> +
+
+ + + +
+ + ) : ( +

Bu tarih için tüketim kaydı yok.

+ )} +
+ )} +
@@ -62,35 +134,42 @@ export default async function ConsumptionPage() {

Gün gün

    - {table.map((day) => ( -
  • -
    - -
    -
    {isoDate(day.date)}
    -
    - {num(day.day.km)} km · {efficiency(day.day.kwh, day.day.km)} + {table.map((day) => { + const isSelected = hasFilter && day.date === selectedDate; + return ( +
  • +
    + +
    +
    + + {isoDate(day.date)} + +
    +
    + {num(day.day.km)} km · {efficiency(day.day.kwh, day.day.km)} +
    +
    +
    + {precise(day.day.kwh)} + kWh +
    +
    +
    +
    + + 0 ? `${num(day.day.km / day.day.hours)} km/h` : "—"} + /> + +
    -
    - {precise(day.day.kwh)} - kWh -
    -
  • -
    -
    - - 0 ? `${num(day.day.km / day.day.hours)} km/h` : "—"} - /> - - -
    -
    -
    -
  • - ))} + + + ); + })}
diff --git a/web/src/app/globals.css b/web/src/app/globals.css index ef58ada..d10dbbc 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -362,3 +362,69 @@ a { padding: 5px 12px; cursor: pointer; } + +/* ---------- Consumption date filter ---------- */ + +.filter-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.filter-label { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--muted); +} + +.filter-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.date-input { + width: auto; + min-width: 0; + flex: 1 1 150px; + margin-top: 0; + color-scheme: dark; +} + +.filter-button { + width: auto; + margin-top: 0; + padding: 12px 18px; + flex: 0 0 auto; +} + +.filter-clear { + font: inherit; + font-size: 13px; + color: var(--muted); + text-decoration: none; + border: 1px solid var(--outline); + border-radius: 12px; + padding: 11px 14px; + flex: 0 0 auto; +} + +.filter-clear:active { + border-color: var(--accent); +} + +/* Highlighted selected day in the day-by-day list */ +.session.selected { + border-color: var(--accent); +} + +.day-link { + color: var(--text); + text-decoration: none; +} + +.day-link:active { + color: var(--accent); +}