"use client"; import { useRef } from "react"; import { useRouter } from "next/navigation"; import { Lock } from "lucide-react"; interface PlanPosition { id: string; anfrage_id: string; maschine_name: string; maschine_kategorie: string; mietbeginn: string; mietende: string; gesamt_tage: number; tagessatz: number | null; anfrage_status: string; firma: string; } /** Minimaler Sperrung-Typ fuer den Gantt. maschine_name wird benoetigt, * weil der Gantt Zeilen anhand des Namens gruppiert. */ export interface SperrungGantt { sperr_id: string; maschine_name: string; von: string; bis: string; grund: string; notiz?: string; } interface Props { planDaten: PlanPosition[]; heuteISO: string; startISO?: string; // Optional: Startdatum (default: heuteISO) tageGesamt?: number; // Optional: Sichtbare Tage (default: 95) sperrungen?: SperrungGantt[]; } const STATUS_FARBE: Record = { bestaetigt: "bg-green-500 border-green-600", offen: "bg-amber-400 border-amber-500", abgeschlossen: "bg-slate-400 border-slate-500", abgelehnt: "bg-red-300 border-red-400", }; const STATUS_LABEL: Record = { bestaetigt: "Bestätigt", offen: "Offen", abgeschlossen: "Abgeschlossen", abgelehnt: "Abgelehnt", }; // Sperrungen werden in einem neutralen Dunkelgrau mit diagonaler Schraffur // dargestellt, damit sie sich visuell klar von Anfrage-Balken abheben. const SPERRUNG_STYLE = "bg-slate-700 border-slate-800 text-white " + "bg-[repeating-linear-gradient(45deg,rgba(255,255,255,0.08)_0_6px,transparent_6px_12px)]"; function addDays(iso: string, days: number): string { const d = new Date(iso + "T00:00:00"); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); } function daysBetween(a: string, b: string): number { const da = new Date(a + "T00:00:00"); const db = new Date(b + "T00:00:00"); return Math.round((db.getTime() - da.getTime()) / 86400000); } function formatDate(iso: string): string { return new Date(iso + "T00:00:00").toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", }); } function fmt(n: number) { return n.toLocaleString("de-DE", { minimumFractionDigits: 0, maximumFractionDigits: 0 }); } const TAGE_GESAMT = 95; // ca. 3 Monate + etwas Puffer const TAG_BREITE = 28; // px pro Tag export function GanttKalender({ planDaten, heuteISO, startISO, tageGesamt, sperrungen = [], }: Props) { const scrollRef = useRef(null); const router = useRouter(); // Defaults setzen const kalenderStart = startISO ?? heuteISO; const TAGE_GESAMT = tageGesamt ?? 95; const kalenderEnde = addDays(kalenderStart, TAGE_GESAMT - 1); // Nur relevante Status anzeigen (nicht abgelehnt) const sichtbar = planDaten.filter((p) => p.anfrage_status !== "abgelehnt"); // Sperrungen auf den sichtbaren Zeitraum eingrenzen const sperrungenSichtbar = sperrungen.filter( (s) => s.bis >= kalenderStart && s.von <= kalenderEnde ); // Eindeutige Maschinen: zusammenfuehren aus Anfragen + Sperrungen, damit // Sperrungen fuer reine "Leer-Maschinen" ebenfalls sichtbar sind. const maschinenSet = new Set(); for (const p of sichtbar) maschinenSet.add(p.maschine_name); for (const s of sperrungenSichtbar) maschinenSet.add(s.maschine_name); const maschinenOhneDouble = Array.from(maschinenSet).sort(); // Kalender-Tage generieren (ab kalenderStart statt immer ab heute) const tage: string[] = []; for (let i = 0; i < TAGE_GESAMT; i++) { tage.push(addDays(kalenderStart, i)); } // Monats-Separatoren const monate: { label: string; start: number; breite: number }[] = []; let currentMonat = ""; let startIdx = 0; tage.forEach((d, i) => { const m = d.slice(0, 7); if (m !== currentMonat) { if (currentMonat) { monate.push({ label: new Date(currentMonat + "-01").toLocaleDateString("de-DE", { month: "long", year: "numeric", }), start: startIdx * TAG_BREITE, breite: (i - startIdx) * TAG_BREITE, }); } currentMonat = m; startIdx = i; } }); if (currentMonat) { monate.push({ label: new Date(currentMonat + "-01").toLocaleDateString("de-DE", { month: "long", year: "numeric", }), start: startIdx * TAG_BREITE, breite: (tage.length - startIdx) * TAG_BREITE, }); } const gesamtBreite = TAGE_GESAMT * TAG_BREITE; if (maschinenOhneDouble.length === 0) { return (
Keine geplanten Vermietungen oder Sperrungen im gewählten Zeitraum.
); } return (
{/* Legende */}
{Object.entries(STATUS_LABEL) .filter(([k]) => k !== "abgelehnt") .map(([k, v]) => (
{v}
))}
Gesperrt
{/* Scrollbarer Gantt */}
{/* Monats-Header */}
Maschine
{monate.map((m) => (
{m.label}
))}
{/* Tages-Header */}
{tage.map((d, i) => { const wochentag = new Date(d + "T00:00:00").getDay(); const istHeute = d === heuteISO; const istSo = wochentag === 0; const istMo = wochentag === 1; const tag = d.slice(8); return (
{tag}
); })}
{/* Maschinen-Zeilen */} {maschinenOhneDouble.map((maschine, rowIdx) => { const positionen = sichtbar.filter((p) => p.maschine_name === maschine); const rowBg = rowIdx % 2 === 0 ? "bg-white" : "bg-slate-50/60"; return (
{/* Maschinenname */}
{maschine}
{/* Balkenfläche */}
{/* Heute-Linie */}
{/* Wochenenden */} {tage.map((d, i) => { const wt = new Date(d + "T00:00:00").getDay(); if (wt !== 0) return null; return (
); })} {/* Sperrungs-Balken (unter den Anfragen gerendert, damit Anfragen bei einer unerwarteten Ueberlappung oben liegen. Durch die Collision-Pruefung in /api/admin/sperrungen sollte das in der Praxis aber nicht vorkommen.) */} {sperrungenSichtbar .filter((s) => s.maschine_name === maschine) .map((s) => { const startOff = daysBetween(kalenderStart, s.von); const endOff = daysBetween(kalenderStart, s.bis); const left = Math.max(0, startOff) * TAG_BREITE; const right = Math.min(TAGE_GESAMT, endOff + 1) * TAG_BREITE; const width = right - left; if (width <= 0) return null; return (
{s.grund} {/* Tooltip bei Hover */}

Sperrung

{s.grund}

{formatDate(s.von)} – {formatDate(s.bis)}

{s.notiz && (

{s.notiz}

)}
); })} {/* Vermietungs-Balken */} {positionen.map((p) => { const startOff = daysBetween(kalenderStart, p.mietbeginn); const endOff = daysBetween(kalenderStart, p.mietende); const left = Math.max(0, startOff) * TAG_BREITE; const right = Math.min(TAGE_GESAMT, endOff + 1) * TAG_BREITE; const width = right - left; if (width <= 0) return null; const umsatz = p.tagessatz != null ? p.tagessatz * p.gesamt_tage : null; return (
router.push(`/admin/anfragen/${p.anfrage_id}`)} > {p.firma} {/* Tooltip bei Hover */}

{p.firma}

{STATUS_LABEL[p.anfrage_status]}

{formatDate(p.mietbeginn)} – {formatDate(p.mietende)}

{p.gesamt_tage} Miettage

{umsatz != null && (

{fmt(umsatz)} € netto

)}

Klicken zum Öffnen →

); })}
); })}
); }