layout: 320 px ohne Ueberstand, Schubladen in voller Breite, Doppelklick-Sperre greift sofort
Befunde aus der Sichtpruefung (Attrappe des Backends, 320/768/1024/1440 px):
- Zaehlwerk: Das "H" ragte bei 320 px 30 px ueber sein Feld. Ziffern skalieren jetzt
mit der Feldbreite (Container-Einheiten), am PC bleibt es bei 38 x 56 px.
- Flugplan und Update-Verlauf: Der Text bekam schmal nur ~50 px und lief in den Zustand.
Schmales Feld: Zeit und Zustand oben, Text darunter (nach Feldbreite, nicht Bildschirm).
- Update-Zeilen am Handy: Name und Version stiessen aneinander ("BetriebssystemUbuntu"),
die Version steht jetzt darunter; scheitert die Pruefung, heisst sie "unbekannt" statt "-".
- Schubladen (Modell-Suche, Dienste, Einstellungen, Protokoll) waren am Handy nur 75 %
und am PC hoechstens 384 px breit - die shadcn-Grundklassen schlugen die eigenen.
- Doppelklick: Die Sperre beim Installieren haengt jetzt an einem Ref und greift sofort
(vorher starteten zwei Klicks im selben Takt zwei Downloads); die Ja-Knoepfe von
"Alles aktualisieren" und "Auftrag abbrechen" ignorieren den zweiten Klick.
- Balken eines abgebrochenen Downloads grau statt cyan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
634ad27302
commit
a091ccb3b8
@@ -8,18 +8,21 @@ const STATUS: Record<FlugplanEintrag["status"], { text: string; farbe: string }>
|
|||||||
geplant: { text: "Geplant", farbe: "text-text-3" },
|
geplant: { text: "Geplant", farbe: "text-text-3" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Die Liste richtet sich nach der Breite ihres Feldes, nicht des Bildschirms (@container): Schmal —
|
||||||
|
// am Handy und in der rechten Spalte bei 1024 px — stehen Zeit und Zustand oben, der Text darunter
|
||||||
|
// über die volle Breite. Vorher bekam der Text dort ~50 px und lief in den Zustand hinein.
|
||||||
function Zeile({ e }: { e: FlugplanEintrag }) {
|
function Zeile({ e }: { e: FlugplanEintrag }) {
|
||||||
const s = STATUS[e.status] ?? STATUS.geplant
|
const s = STATUS[e.status] ?? STATUS.geplant
|
||||||
// Updates sind das Wichtigste im Plan — sie stehen in Cyan.
|
// Updates sind das Wichtigste im Plan — sie stehen in Cyan.
|
||||||
const farbe = e.status === "geplant" && /update/i.test(e.titel) ? "text-cyan" : s.farbe
|
const farbe = e.status === "geplant" && /update/i.test(e.titel) ? "text-cyan" : s.farbe
|
||||||
return (
|
return (
|
||||||
<li className="grid grid-cols-[112px_minmax(0,1fr)_auto] items-center gap-3 border-t border-linie py-2.5">
|
<li className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-1 border-t border-linie py-2.5 @sm:grid-cols-[112px_minmax(0,1fr)_auto]">
|
||||||
<span className="ziffern text-sm text-text-3">{flugplanZeit(e.zeit)}</span>
|
<span className="ziffern col-start-1 row-start-1 text-sm text-text-3">{flugplanZeit(e.zeit)}</span>
|
||||||
<span className="min-w-0 text-base">
|
<span className="col-span-2 row-start-2 min-w-0 text-base break-words @sm:col-span-1 @sm:col-start-2 @sm:row-start-1">
|
||||||
{e.titel}
|
{e.titel}
|
||||||
{e.text && <span className="text-text-3">, {e.text}</span>}
|
{e.text && <span className="text-text-3">, {e.text}</span>}
|
||||||
</span>
|
</span>
|
||||||
<span className={cn("schild text-[13px]", farbe)}>{s.text}</span>
|
<span className={cn("schild col-start-2 row-start-1 justify-self-end text-[13px] @sm:col-start-3", farbe)}>{s.text}</span>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -27,5 +30,5 @@ function Zeile({ e }: { e: FlugplanEintrag }) {
|
|||||||
export function Flugplan({ gelaufen, geplant }: { gelaufen: FlugplanEintrag[]; geplant: FlugplanEintrag[] }) {
|
export function Flugplan({ gelaufen, geplant }: { gelaufen: FlugplanEintrag[]; geplant: FlugplanEintrag[] }) {
|
||||||
const alle = [...gelaufen, ...geplant.slice(0, 5)]
|
const alle = [...gelaufen, ...geplant.slice(0, 5)]
|
||||||
if (alle.length === 0) return <p className="text-[15px] text-text-2">Heute ist noch nichts gelaufen, und nichts ist geplant.</p>
|
if (alle.length === 0) return <p className="text-[15px] text-text-2">Heute ist noch nichts gelaufen, und nichts ist geplant.</p>
|
||||||
return <ol className="m-0 flex list-none flex-col p-0">{alle.map((e) => <Zeile key={`${e.titel}-${e.zeit}`} e={e} />)}</ol>
|
return <ol className="@container m-0 flex list-none flex-col p-0">{alle.map((e) => <Zeile key={`${e.titel}-${e.zeit}`} e={e} />)}</ol>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { cn } from "cn"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { laeuftNoch } from "@/lib/anzeige"
|
import { laeuftNoch } from "@/lib/anzeige"
|
||||||
import { abbrechbar, fortschrittText, jobDownload, jobTitel, laufText, letzteMeldung } from "@/lib/wartung"
|
import { abbrechbar, fortschrittText, jobDownload, jobTitel, laufText, letzteMeldung } from "@/lib/wartung"
|
||||||
@@ -15,6 +16,10 @@ const ZUSTAND: Record<JobZustand, { text: string; art: ChipArt }> = {
|
|||||||
canceled: { text: "Abgebrochen", art: "grau" },
|
canceled: { text: "Abgebrochen", art: "grau" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BALKEN: Record<JobZustand, string> = {
|
||||||
|
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())
|
const zeitVon = (sekunden: number) => flugplanZeit(new Date(sekunden * 1000).toISOString())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +58,7 @@ export function JobKarte({ job, onAbbrechen, abbrechenGesperrt }: {
|
|||||||
className="h-2 overflow-hidden rounded-full bg-linie"
|
className="h-2 overflow-hidden rounded-full bg-linie"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={job.state === "failed" ? "h-full bg-rot" : "h-full bg-cyan"}
|
className={cn("h-full", BALKEN[job.state] ?? "bg-cyan")}
|
||||||
style={{ width: `${Math.max(2, Math.min(100, job.progress))}%` }}
|
style={{ width: `${Math.max(2, Math.min(100, job.progress))}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,6 +110,7 @@ export function JobKarte({ job, onAbbrechen, abbrechenGesperrt }: {
|
|||||||
neinKnopf="Weiterlaufen lassen"
|
neinKnopf="Weiterlaufen lassen"
|
||||||
onNein={() => setFrage(false)}
|
onNein={() => setFrage(false)}
|
||||||
onJa={() => {
|
onJa={() => {
|
||||||
|
if (!frage) return // zweiter Klick eines Doppelklicks
|
||||||
setFrage(false)
|
setFrage(false)
|
||||||
onAbbrechen?.(job)
|
onAbbrechen?.(job)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function Protokollfenster({ offen, titel, text, onSchliessen }: {
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
||||||
<SheetContent side="right" className="w-full border-linie bg-panel sm:max-w-2xl">
|
<SheetContent side="right" className="border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="schild text-lg">Protokoll</SheetTitle>
|
<SheetTitle className="schild text-lg">Protokoll</SheetTitle>
|
||||||
<SheetDescription className="text-text-2">{titel}</SheetDescription>
|
<SheetDescription className="text-text-2">{titel}</SheetDescription>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { laufzeit } from "@/lib/zeit"
|
import { laufzeit } from "@/lib/zeit"
|
||||||
|
|
||||||
|
// Alle Maße hängen an der Schriftgröße der Zeile (em), und die wächst mit der Breite des Feldes
|
||||||
|
// (cqw): So passt „TT T SS H“ auch in die schmale Hälfte eines 320-px-Handys, und am PC bleibt
|
||||||
|
// es bei den alten 38 × 56 px je Ziffer.
|
||||||
function Ziffer({ z }: { z: string }) {
|
function Ziffer({ z }: { z: string }) {
|
||||||
return (
|
return (
|
||||||
<span className="ziffern inline-flex h-14 w-[38px] items-center justify-center rounded-md border border-[#2a3036] bg-[#0b0d0f] text-[32px] font-medium">
|
<span className="ziffern inline-flex h-[1.75em] w-[1.1875em] shrink-0 items-center justify-center rounded-md border border-[#2a3036] bg-[#0b0d0f] font-medium">
|
||||||
{z}
|
{z}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -15,14 +18,19 @@ export function Zaehlwerk({ sekunden }: { sekunden: number | null | undefined })
|
|||||||
const std = String(lz?.[1] ?? 0).padStart(2, "0")
|
const std = String(lz?.[1] ?? 0).padStart(2, "0")
|
||||||
const text = lz ? `Laufzeit ohne Neustart: ${lz[0]} Tage, ${lz[1]} Stunden` : "Laufzeit unbekannt"
|
const text = lz ? `Laufzeit ohne Neustart: ${lz[0]} Tage, ${lz[1]} Stunden` : "Laufzeit unbekannt"
|
||||||
return (
|
return (
|
||||||
<figure className="m-0 flex min-w-0 flex-col items-center justify-end gap-1">
|
<figure className="@container m-0 flex min-w-0 flex-col items-center justify-end gap-1">
|
||||||
<div aria-label={text} role="img" className="flex h-[150px] max-w-full items-center gap-1.5 pb-5">
|
{/* Höhe wie die Rundinstrumente daneben (200 × 150, also ¾ der Breite). */}
|
||||||
|
<div
|
||||||
|
aria-label={text}
|
||||||
|
role="img"
|
||||||
|
className="flex h-[min(75cqw,150px)] items-center gap-[0.1875em] pb-[min(10cqw,20px)] text-[length:clamp(14px,14.5cqw,32px)]"
|
||||||
|
>
|
||||||
<Ziffer z={lz ? tage[0] : "–"} />
|
<Ziffer z={lz ? tage[0] : "–"} />
|
||||||
<Ziffer z={lz ? tage[1] : "–"} />
|
<Ziffer z={lz ? tage[1] : "–"} />
|
||||||
<span className="schild mr-2 ml-0.5 text-lg font-bold text-text-3">T</span>
|
<span className="schild mr-[0.444em] ml-[0.111em] text-[0.5625em] font-bold text-text-3">T</span>
|
||||||
<Ziffer z={lz ? std[0] : "–"} />
|
<Ziffer z={lz ? std[0] : "–"} />
|
||||||
<Ziffer z={lz ? std[1] : "–"} />
|
<Ziffer z={lz ? std[1] : "–"} />
|
||||||
<span className="schild ml-0.5 text-lg font-bold text-text-3">H</span>
|
<span className="schild ml-[0.111em] text-[0.5625em] font-bold text-text-3">H</span>
|
||||||
</div>
|
</div>
|
||||||
<figcaption className="schild text-[15px] text-text-3">Ohne Neustart</figcaption>
|
<figcaption className="schild text-[15px] text-text-3">Ohne Neustart</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function Dienste({ offen, onSchliessen }: { offen: boolean; onSchliessen:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
||||||
<SheetContent side="right" className="w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-2xl">
|
<SheetContent side="right" className="gap-3 overflow-y-auto border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="schild text-lg">Dienste und Protokolle</SheetTitle>
|
<SheetTitle className="schild text-lg">Dienste und Protokolle</SheetTitle>
|
||||||
<SheetDescription className="text-text-2">Welcher Dienst läuft, was er zuletzt geschrieben hat.</SheetDescription>
|
<SheetDescription className="text-text-2">Welcher Dienst läuft, was er zuletzt geschrieben hat.</SheetDescription>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function Einstellungen({ offen, onSchliessen }: { offen: boolean; onSchli
|
|||||||
const s = stand.data
|
const s = stand.data
|
||||||
return (
|
return (
|
||||||
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
||||||
<SheetContent side="right" className="w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-lg">
|
<SheetContent side="right" className="gap-3 overflow-y-auto border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-lg">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="schild text-lg">Einstellungen</SheetTitle>
|
<SheetTitle className="schild text-lg">Einstellungen</SheetTitle>
|
||||||
<SheetDescription className="text-text-2">Was die Box sich merkt.</SheetDescription>
|
<SheetDescription className="text-text-2">Was die Box sich merkt.</SheetDescription>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react"
|
import { useRef, useState } from "react"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Bestaetigen } from "@/components/cockpit/Bestaetigen"
|
import { Bestaetigen } from "@/components/cockpit/Bestaetigen"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
@@ -30,6 +30,7 @@ export function ModellSuche({ offen, onSchliessen, rollenJetzt }: {
|
|||||||
const [repo, setRepo] = useState<string | null>(null)
|
const [repo, setRepo] = useState<string | null>(null)
|
||||||
const [rolle, setRolle] = useState("")
|
const [rolle, setRolle] = useState("")
|
||||||
const [auswahl, setAuswahl] = useState<{ quant: string; bytes?: number } | null>(null)
|
const [auswahl, setAuswahl] = useState<{ quant: string; bytes?: number } | null>(null)
|
||||||
|
const unterwegs = useRef(false)
|
||||||
|
|
||||||
const treffer = useQuery({
|
const treffer = useQuery({
|
||||||
queryKey: ["hf-suche", begriff],
|
queryKey: ["hf-suche", begriff],
|
||||||
@@ -47,8 +48,10 @@ export function ModellSuche({ offen, onSchliessen, rollenJetzt }: {
|
|||||||
const ersatz = rollenName && repo ? rollenErsatzText(repo, rollenName, rollenJetzt[rolle]) : null
|
const ersatz = rollenName && repo ? rollenErsatzText(repo, rollenName, rollenJetzt[rolle]) : null
|
||||||
|
|
||||||
function starten() {
|
function starten() {
|
||||||
// Die Sperre gegen den Doppelklick: Solange ein Start unterwegs ist, geht kein zweiter los.
|
// Die Sperre gegen den Doppelklick: Solange ein Start unterwegs ist, geht kein zweiter los. Der
|
||||||
if (!repo || !auswahl || installieren.isPending) return
|
// Ref greift sofort — der Zustand von useMutation erst nach dem nächsten Rendern.
|
||||||
|
if (!repo || !auswahl || unterwegs.current) return
|
||||||
|
unterwegs.current = true
|
||||||
installieren.mutate(
|
installieren.mutate(
|
||||||
{ repo, quant: auswahl.quant, rolle: rolle || null },
|
{ repo, quant: auswahl.quant, rolle: rolle || null },
|
||||||
{
|
{
|
||||||
@@ -57,13 +60,16 @@ export function ModellSuche({ offen, onSchliessen, rollenJetzt }: {
|
|||||||
if (e.ok !== false) onSchliessen()
|
if (e.ok !== false) onSchliessen()
|
||||||
},
|
},
|
||||||
onError: () => setAuswahl(null),
|
onError: () => setAuswahl(null),
|
||||||
|
onSettled: () => {
|
||||||
|
unterwegs.current = false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
<Sheet open={offen} onOpenChange={(o) => !o && onSchliessen()}>
|
||||||
<SheetContent side="right" className="w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-xl">
|
<SheetContent side="right" className="gap-3 overflow-y-auto border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="schild text-lg">Modelle selbst suchen</SheetTitle>
|
<SheetTitle className="schild text-lg">Modelle selbst suchen</SheetTitle>
|
||||||
<SheetDescription className="text-text-2">Auf Hugging Face suchen, Quantisierung wählen, installieren.</SheetDescription>
|
<SheetDescription className="text-text-2">Auf Hugging Face suchen, Quantisierung wählen, installieren.</SheetDescription>
|
||||||
|
|||||||
@@ -130,7 +130,9 @@ function BausteinZeile({ id, name, neuLautStart, fest, gesperrt, onStart, onFrei
|
|||||||
const neu = hatUpdate ? v?.neu ?? neuLautStart ?? null : null
|
const neu = hatUpdate ? v?.neu ?? neuLautStart ?? null : null
|
||||||
const summary = zusammenfassung(d?.summary)
|
const summary = zusammenfassung(d?.summary)
|
||||||
|
|
||||||
const laeuftZelle = laedt ? <Skelett /> : <>{v?.laeuft ?? "–"}</>
|
// Scheitert die Prüfung, kennt das Backend oft nicht einmal die laufende Version (Hermes: git fetch).
|
||||||
|
const laeuftText = v && v.laeuft !== "–" ? v.laeuft : fehler ? "unbekannt" : "–"
|
||||||
|
const laeuftZelle = laedt ? <Skelett /> : <>{laeuftText}</>
|
||||||
const neuZelle = laedt ? (
|
const neuZelle = laedt ? (
|
||||||
<Skelett breit="w-20" />
|
<Skelett breit="w-20" />
|
||||||
) : neu ? (
|
) : neu ? (
|
||||||
@@ -165,22 +167,22 @@ function BausteinZeile({ id, name, neuLautStart, fest, gesperrt, onStart, onFrei
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Am PC eine Tabellenzeile, am Handy ein Block: Name und Versionen oben, darunter was sich
|
// Am PC eine Tabellenzeile, am Handy ein Block: Name, darunter die Versionen, darunter was sich
|
||||||
// ändert, darunter die Knöpfe — ohne seitliches Scrollen.
|
// ändert, darunter die Knöpfe — ohne seitliches Scrollen und ohne dass Name und Version aneinanderstoßen.
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="row"
|
role="row"
|
||||||
aria-busy={laedt || undefined}
|
aria-busy={laedt || undefined}
|
||||||
className="grid grid-cols-[minmax(0,1fr)_auto] items-baseline gap-x-4 gap-y-2 border-t border-linie py-3.5 md:grid-cols-[170px_100px_170px_minmax(0,1fr)_auto] md:items-start"
|
className="grid grid-cols-1 gap-x-4 gap-y-2 border-t border-linie py-3.5 md:grid-cols-[170px_100px_170px_minmax(0,1fr)_auto] md:items-start"
|
||||||
>
|
>
|
||||||
<span role="rowheader" className="text-[15px] font-semibold">{name}</span>
|
<span role="rowheader" className="text-[15px] font-semibold">{name}</span>
|
||||||
<span role="cell" className="ziffern text-right text-sm text-text-2 md:hidden">
|
<span role="cell" className="ziffern -mt-1 text-sm text-text-2 md:hidden">
|
||||||
{laeuftZelle}
|
{laeuftZelle}
|
||||||
{!laedt && (neu || fehler) && <> {neu ? "→" : "·"} {neuZelle}</>}
|
{!laedt && (neu || fehler) && <> {neu ? "→" : "·"} {neuZelle}</>}
|
||||||
</span>
|
</span>
|
||||||
<span role="cell" className="ziffern hidden text-sm text-text-2 md:block">{laeuftZelle}</span>
|
<span role="cell" className="ziffern hidden text-sm text-text-2 md:block">{laeuftZelle}</span>
|
||||||
<span role="cell" className="ziffern hidden text-sm md:block">{neuZelle}</span>
|
<span role="cell" className="ziffern hidden text-sm md:block">{neuZelle}</span>
|
||||||
<div role="cell" className="col-span-2 text-sm leading-relaxed text-text-2 md:col-span-1">
|
<div role="cell" className="text-sm leading-relaxed text-text-2">
|
||||||
{fest && (
|
{fest && (
|
||||||
<span className="mb-1.5 block text-bernstein">
|
<span className="mb-1.5 block text-bernstein">
|
||||||
Festgehalten seit {fest.seit} auf {fest.version}: {fest.grund}. Der Sonntags-Lauf überspringt {name}.
|
Festgehalten seit {fest.seit} auf {fest.version}: {fest.grund}. Der Sonntags-Lauf überspringt {name}.
|
||||||
@@ -189,7 +191,7 @@ function BausteinZeile({ id, name, neuLautStart, fest, gesperrt, onStart, onFrei
|
|||||||
{was}
|
{was}
|
||||||
</div>
|
</div>
|
||||||
{(fest || hatUpdate) && (
|
{(fest || hatUpdate) && (
|
||||||
<span role="cell" className="col-span-2 flex flex-wrap gap-2 md:col-span-1 md:flex-col md:items-end">
|
<span role="cell" className="flex flex-wrap gap-2 md:flex-col md:items-end">
|
||||||
{fest && (
|
{fest && (
|
||||||
<Button variant="outline" size="sm" onClick={onFreigeben}>
|
<Button variant="outline" size="sm" onClick={onFreigeben}>
|
||||||
Freigeben
|
Freigeben
|
||||||
@@ -217,18 +219,18 @@ const PUNKT: Record<UpdateLauf["stufe"], string> = {
|
|||||||
function LaufEintrag({ lauf }: { lauf: UpdateLauf }) {
|
function LaufEintrag({ lauf }: { lauf: UpdateLauf }) {
|
||||||
return (
|
return (
|
||||||
<li className="flex flex-col gap-2 border-t border-linie py-3">
|
<li className="flex flex-col gap-2 border-t border-linie py-3">
|
||||||
{/* Handy: Zeit und Ergebnis oben, der Anlass darunter über die volle Breite. */}
|
{/* Schmales Feld: Zeit und Ergebnis oben, der Anlass darunter über die volle Breite. */}
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-1 md:grid-cols-[112px_minmax(0,1fr)_auto]">
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-1 @sm:grid-cols-[112px_minmax(0,1fr)_auto]">
|
||||||
<span className="ziffern col-start-1 row-start-1 text-sm text-text-3">{flugplanZeit(lauf.start)}</span>
|
<span className="ziffern col-start-1 row-start-1 text-sm text-text-3">{flugplanZeit(lauf.start)}</span>
|
||||||
<span className="col-span-2 row-start-2 min-w-0 text-[15px] break-words md:col-span-1 md:col-start-2 md:row-start-1">
|
<span className="col-span-2 row-start-2 min-w-0 text-[15px] break-words @sm:col-span-1 @sm:col-start-2 @sm:row-start-1">
|
||||||
{lauf.anlass}
|
{lauf.anlass}
|
||||||
</span>
|
</span>
|
||||||
<span className="col-start-2 row-start-1 justify-self-end md:col-start-3">
|
<span className="col-start-2 row-start-1 justify-self-end @sm:col-start-3">
|
||||||
<Chip art={LAUF_ART[lauf.stufe]}>{lauf.titel}</Chip>
|
<Chip art={LAUF_ART[lauf.stufe]}>{lauf.titel}</Chip>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{lauf.stufe !== "grau" && lauf.zeilen.length > 0 && (
|
{lauf.stufe !== "grau" && lauf.zeilen.length > 0 && (
|
||||||
<ul className="m-0 flex list-none flex-col gap-1 p-0 md:pl-[124px]">
|
<ul className="m-0 flex list-none flex-col gap-1 p-0 @sm:pl-[124px]">
|
||||||
{lauf.zeilen.map((z) => (
|
{lauf.zeilen.map((z) => (
|
||||||
<li key={`${z.baustein}-${z.text}`} className="flex items-baseline gap-2 text-sm leading-snug text-text-2">
|
<li key={`${z.baustein}-${z.text}`} className="flex items-baseline gap-2 text-sm leading-snug text-text-2">
|
||||||
<span aria-hidden className={cn("size-1.5 shrink-0 -translate-y-px rounded-full", PUNKT[z.stufe])} />
|
<span aria-hidden className={cn("size-1.5 shrink-0 -translate-y-px rounded-full", PUNKT[z.stufe])} />
|
||||||
@@ -322,7 +324,7 @@ export function UpdatesSeite() {
|
|||||||
verlaufInhalt = <p className="text-[15px] text-text-2">Noch keine Update-Läufe aufgezeichnet.</p>
|
verlaufInhalt = <p className="text-[15px] text-text-2">Noch keine Update-Läufe aufgezeichnet.</p>
|
||||||
} else {
|
} else {
|
||||||
verlaufInhalt = (
|
verlaufInhalt = (
|
||||||
<ol className="m-0 flex list-none flex-col p-0">
|
<ol className="@container m-0 flex list-none flex-col p-0">
|
||||||
{verlauf.data.laeufe.map((l) => <LaufEintrag key={l.start} lauf={l} />)}
|
{verlauf.data.laeufe.map((l) => <LaufEintrag key={l.start} lauf={l} />)}
|
||||||
</ol>
|
</ol>
|
||||||
)
|
)
|
||||||
@@ -416,7 +418,8 @@ export function UpdatesSeite() {
|
|||||||
gesperrt={gesperrt}
|
gesperrt={gesperrt}
|
||||||
onNein={() => setFrage(null)}
|
onNein={() => setFrage(null)}
|
||||||
onJa={() => {
|
onJa={() => {
|
||||||
updateStarten.mutate("alle")
|
// Die Abfrage auf `frage` fängt den zweiten Klick eines Doppelklicks: Dann ist sie schon null.
|
||||||
|
if (frage?.art === "alle") updateStarten.mutate("alle")
|
||||||
setFrage(null)
|
setFrage(null)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user