Files
Moped-Tracking/src/app/(app)/motorcycles/[id]/maintenance/page.tsx
T

214 lines
7.9 KiB
TypeScript

import Link from "next/link";
import { notFound } from "next/navigation";
import { requireSessionUser } from "@/lib/auth";
import { db, initDatabase } from "@/lib/db";
import { formatMotorcycleName } from "@/lib/format";
import { motorcycleAccessFilter } from "@/lib/motorcycle-access";
export const runtime = "nodejs";
type MotorcycleMaintenanceRow = {
id: number;
brand: string;
model: string;
year: number | null;
tire_pressure_front_bar: number | null;
tire_pressure_rear_bar: number | null;
chain_tension_min_mm: number | null;
chain_tension_max_mm: number | null;
oil_type: string | null;
oil_capacity_liters: number | null;
oil_check_temp: "warm" | "cold" | null;
oil_check_run_minutes: number | null;
oil_check_wait_min_minutes: number | null;
oil_check_wait_max_minutes: number | null;
oil_check_method: "dipstick" | "sight_glass" | null;
};
type ChecklistItem = {
label: string;
ok: boolean;
};
function formatBar(value: number | null) {
if (value == null) return "—";
return `${new Intl.NumberFormat("de-DE", {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}).format(value)} bar`;
}
function oilTempLabel(value: MotorcycleMaintenanceRow["oil_check_temp"]) {
if (value === "warm") return "Warm";
if (value === "cold") return "Kalt";
return "—";
}
function oilMethodLabel(value: MotorcycleMaintenanceRow["oil_check_method"]) {
if (value === "dipstick") return "Peilstab";
if (value === "sight_glass") return "Schauglas";
return "—";
}
function oilInstruction(bike: MotorcycleMaintenanceRow) {
const tempText =
bike.oil_check_temp === "warm"
? "bei warmem Motor"
: bike.oil_check_temp === "cold"
? "bei kaltem Motor"
: null;
const methodText =
bike.oil_check_method === "dipstick"
? "mit Peilstab"
: bike.oil_check_method === "sight_glass"
? "über Schauglas"
: null;
if (!tempText && !methodText) return "Keine Anleitung hinterlegt.";
const runText =
bike.oil_check_temp === "warm" && bike.oil_check_run_minutes != null
? `Motor mindestens ${bike.oil_check_run_minutes} Min. laufen lassen, `
: "";
const waitText =
bike.oil_check_temp === "warm" &&
bike.oil_check_wait_min_minutes != null &&
bike.oil_check_wait_max_minutes != null
? `danach ${bike.oil_check_wait_min_minutes}-${bike.oil_check_wait_max_minutes} Min. warten, `
: "";
return `${runText}${waitText}Ölstand ${tempText ?? "im passenden Motorzustand"} messen ${methodText ?? "Methode offen"}.`.trim();
}
function maintenanceChecklist(bike: MotorcycleMaintenanceRow): ChecklistItem[] {
return [
{ label: "Reifendruck vorne/hinten hinterlegt", ok: bike.tire_pressure_front_bar != null && bike.tire_pressure_rear_bar != null },
{ label: "Kettenspannung von/bis hinterlegt", ok: bike.chain_tension_min_mm != null && bike.chain_tension_max_mm != null },
{ label: "Öltyp und Gesamtmenge hinterlegt", ok: bike.oil_type != null && bike.oil_capacity_liters != null },
{ label: "Ölprüfung (Temperatur + Methode) hinterlegt", ok: bike.oil_check_temp != null && bike.oil_check_method != null },
{
label: "Warm-Messung vollständig (Laufzeit + Wartefenster)",
ok:
bike.oil_check_temp !== "warm" ||
(bike.oil_check_run_minutes != null &&
bike.oil_check_wait_min_minutes != null &&
bike.oil_check_wait_max_minutes != null),
},
];
}
export default async function MotorcycleMaintenancePage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const user = await requireSessionUser();
initDatabase();
const { id } = await params;
const motorcycleId = Number(id);
if (!Number.isSafeInteger(motorcycleId) || motorcycleId < 1) notFound();
const visible = motorcycleAccessFilter(user);
const bike = db
.prepare(
`SELECT id, brand, model, year,
tire_pressure_front_bar, tire_pressure_rear_bar, chain_tension_min_mm, chain_tension_max_mm,
oil_type, oil_capacity_liters,
oil_check_temp, oil_check_run_minutes, oil_check_wait_min_minutes, oil_check_wait_max_minutes, oil_check_method
FROM motorcycles m
WHERE m.id = ? AND ${visible.clause}`,
)
.get(motorcycleId, ...visible.params) as MotorcycleMaintenanceRow | undefined;
if (!bike) notFound();
const checklist = maintenanceChecklist(bike);
const allComplete = checklist.every((item) => item.ok);
const missing = checklist.filter((item) => !item.ok);
return (
<>
<div className="breadcrumbs">
<Link href="/">Übersicht</Link>
<span>/</span>
<Link href={`/motorcycles/${bike.id}`}>
{formatMotorcycleName(bike.brand, bike.model)}
</Link>
<span>/</span>
Wartungsansicht
</div>
<div className="page-heading">
<div>
<p className="eyebrow">{bike.brand} · {bike.model}{bike.year ? ` · ${bike.year}` : ""}</p>
<h1>Wartungsansicht</h1>
</div>
<div className="heading-actions">
<Link className="button" href={`/motorcycles/${bike.id}`}>Zurück</Link>
<Link className="button button-primary" href={`/motorcycles/${bike.id}/maintenance/edit`}>Werte bearbeiten</Link>
</div>
</div>
<section className="section-block">
<div className="panel">
<h3 className="panel-title">Wartungsprofil-Status</h3>
<p className={`status-pill ${allComplete ? "status-ok" : "status-soon"}`}>
{allComplete ? "Vollständig" : `Unvollständig (${missing.length} offen)`}
</p>
{!allComplete && (
<div className="notes">
<strong>Fehlende Angaben:</strong>
<ul>
{missing.map((item) => (
<li key={item.label}>{item.label}</li>
))}
</ul>
</div>
)}
</div>
</section>
<section className="detail-grid">
<div className="panel">
<h3 className="panel-title">Reifen & Kette</h3>
<dl className="data-list">
<div><dt>Reifendruck vorne</dt><dd>{formatBar(bike.tire_pressure_front_bar)}</dd></div>
<div><dt>Reifendruck hinten</dt><dd>{formatBar(bike.tire_pressure_rear_bar)}</dd></div>
<div>
<dt>Kettenspannung</dt>
<dd>
{bike.chain_tension_min_mm != null && bike.chain_tension_max_mm != null
? `${bike.chain_tension_min_mm}-${bike.chain_tension_max_mm} mm`
: "—"}
</dd>
</div>
</dl>
</div>
<div className="panel">
<h3 className="panel-title">Ölstand-Kontrolle</h3>
<dl className="data-list">
<div><dt>Benötigtes Öl</dt><dd>{bike.oil_type ?? "—"}</dd></div>
<div>
<dt>Ölmenge gesamt</dt>
<dd>{bike.oil_capacity_liters != null ? `${new Intl.NumberFormat("de-DE", { minimumFractionDigits: 1, maximumFractionDigits: 1 }).format(bike.oil_capacity_liters)} l` : "—"}</dd>
</div>
<div><dt>Motorzustand</dt><dd>{oilTempLabel(bike.oil_check_temp)}</dd></div>
<div>
<dt>Laufzeit vor Messung</dt>
<dd>{bike.oil_check_temp === "warm" && bike.oil_check_run_minutes != null ? `${bike.oil_check_run_minutes} Min.` : "—"}</dd>
</div>
<div>
<dt>Wartezeitfenster</dt>
<dd>
{bike.oil_check_temp === "warm" &&
bike.oil_check_wait_min_minutes != null &&
bike.oil_check_wait_max_minutes != null
? `${bike.oil_check_wait_min_minutes}-${bike.oil_check_wait_max_minutes} Min.`
: "—"}
</dd>
</div>
<div><dt>Methode</dt><dd>{oilMethodLabel(bike.oil_check_method)}</dd></div>
</dl>
<p className="notes">{oilInstruction(bike)}</p>
</div>
</section>
</>
);
}