import { useEffect, useState } from "react" import { cn } from "cn" import { Button } from "@/components/ui/button" import { laeuftNoch } from "@/lib/anzeige" import { abbrechbar, fortschrittText, jobDownload, jobTitel, laufText, letzteMeldung } from "@/lib/wartung" import type { Job, JobZustand } from "@/lib/typen" import { flugplanZeit } from "@/lib/zeit" import { Bestaetigen } from "./Bestaetigen" import { Chip, type ChipArt } from "./Chip" const ZUSTAND: Record = { queued: { text: "Wartet", art: "cyan" }, running: { text: "Läuft", art: "cyan" }, done: { text: "Fertig", art: "gruen" }, failed: { text: "Fehler", art: "rot" }, canceled: { text: "Abgebrochen", art: "grau" }, } const BALKEN: Record = { queued: "bg-cyan", running: "bg-cyan", done: "bg-gruen", failed: "bg-rot", canceled: "bg-text-3", } const zeitVon = (sekunden: number) => flugplanZeit(new Date(sekunden * 1000).toISOString()) /** * Die Uhrzeit in Sekunden, alle 30 s neu, solange `aktiv`. Ohne sie bliebe „Läuft seit 5 min“ stehen, * wenn ein Auftrag eine Weile nichts schreibt: Die Karte zeichnet sonst nur neu, wenn sich der Auftrag ändert. */ function useJetzt(aktiv: boolean) { const [jetzt, setJetzt] = useState(() => Date.now() / 1000) useEffect(() => { if (!aktiv) return const t = window.setInterval(() => setJetzt(Date.now() / 1000), 30_000) return () => window.clearInterval(t) }, [aktiv]) return jetzt } /** * Ein Hintergrund-Auftrag als Karte: Titel und Zustand, bei Downloads der Fortschritt, bei Fehlern * die letzte Meldung, auf Wunsch das Protokoll. Abbrechen nur, wo es wirklich stoppt (`abbrechbar`). * `ebene` ist die Überschriftenebene (unter einem Panel-Titel h3, unter einer Zwischenüberschrift h4). */ export function JobKarte({ job, onAbbrechen, abbrechenGesperrt, ebene = 4 }: { job: Job onAbbrechen?: (job: Job) => void abbrechenGesperrt?: boolean ebene?: 3 | 4 }) { const [frage, setFrage] = useState(false) const [protokoll, setProtokoll] = useState(false) const zustand = ZUSTAND[job.state] ?? { text: job.state, art: "grau" as const } const titel = jobTitel(job) const fortschritt = fortschrittText(job) const meldung = letzteMeldung(job) const laeuft = laeuftNoch(job) const jetzt = useJetzt(laeuft) const darfAbbrechen = !!onAbbrechen && abbrechbar(job) const protokollZeilen = (job.log ?? []).slice(-12) const Titel = ebene === 3 ? "h3" : "h4" return (
{titel} {zustand.text}
{job.progress != null && (
)}

{laeuft ? fortschritt ?? laufText(job, jetzt) : `${zustand.text} · ${zeitVon(job.finished_at ?? job.started_at)}`}

{/* Bei Fehlern steht der Grund meist in der letzten Zeile; ohne Fortschrittsbalken zeigt sie, woran der Auftrag gerade ist. */} {meldung && (job.state === "failed" || (laeuft && job.progress == null)) && (

{job.state === "failed" ? `Letzte Meldung: ${meldung}` : meldung}

)} {(darfAbbrechen || protokollZeilen.length > 1) && (
{darfAbbrechen && ( )} {protokollZeilen.length > 1 && ( )}
)} {protokoll && (
          {protokollZeilen.join("\n")}
        
)} {darfAbbrechen && ( setFrage(false)} onJa={() => { if (!frage) return // zweiter Klick eines Doppelklicks setFrage(false) onAbbrechen?.(job) }} /> )}
) }