Final MVP valid

This commit is contained in:
Marcelo
2026-01-21 01:45:57 +00:00
parent c183dda383
commit 511d80b629
29 changed files with 4827 additions and 381 deletions

View File

@@ -0,0 +1,110 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import DowntimeParetoCard from "@/components/analytics/DowntimeParetoCard";
import { usePathname } from "next/navigation";
import { DOWNTIME_RANGES, coerceDowntimeRange, type DowntimeRange } from "@/lib/analytics/downtimeRange";
type MachineLite = {
id: string;
name: string;
siteName?: string | null; // optional for later
};
export default function DowntimeParetoReportClient() {
const sp = useSearchParams();
const router = useRouter();
const pathname = usePathname();
const [range, setRange] = useState<DowntimeRange>(coerceDowntimeRange(sp.get("range")));
const [machineId, setMachineId] = useState<string>(sp.get("machineId") || "");
const [machines, setMachines] = useState<MachineLite[]>([]);
const [loadingMachines, setLoadingMachines] = useState(true);
// Keep URL in sync (so deep-links work)
useEffect(() => {
const qs = new URLSearchParams();
if (range) qs.set("range", range);
if (machineId) qs.set("machineId", machineId);
const next = `${pathname}?${qs.toString()}`;
const current = `${pathname}?${sp.toString()}`;
// avoid needless replace loops
if (next !== current) router.replace(next);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [range, machineId, pathname]);
useEffect(() => {
let cancelled = false;
async function loadMachines() {
setLoadingMachines(true);
try {
// Use whatever endpoint you already have for listing machines:
// If you dont have one, easiest is GET /api/machines returning [{id,name}]
const res = await fetch("/api/machines", { credentials: "include" });
const json = await res.json();
if (!cancelled && res.ok) setMachines(json.machines ?? json ?? []);
} finally {
if (!cancelled) setLoadingMachines(false);
}
}
loadMachines();
return () => {
cancelled = true;
};
}, []);
const machineOptions = useMemo(() => {
return [{ id: "", name: "All machines" }, ...machines];
}, [machines]);
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="text-lg font-semibold text-white">Downtime Pareto</div>
<div className="text-sm text-zinc-400">Org-wide report with drilldown</div>
</div>
<div className="flex flex-wrap gap-2">
<select
className="rounded-xl border border-white/10 bg-black/40 px-3 py-2 text-sm text-white"
value={range}
onChange={(e) => setRange(e.target.value as DowntimeRange)}
>
<option className="bg-black text-white" value="24h">Last 24h</option>
<option className="bg-black text-white" value="7d">Last 7d</option>
<option className="bg-black text-white" value="30d">Last 30d</option>
</select>
<select
className="min-w-[240px] rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-white"
value={machineId}
onChange={(e) => setMachineId(e.target.value)}
disabled={loadingMachines}
>
{machineOptions.map((m) => (
<option className="bg-black text-white" key={m.id || "all"} value={m.id}>
{m.name}
</option>
))}
</select>
</div>
</div>
<DowntimeParetoCard
range={range}
machineId={machineId || undefined}
showOpenFullReport={false}
/>
</div>
);
}