214 lines
7.9 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|