feat(board): Mehrfachauswahl fuer Status, Tags und Loeschen

Kaestchen auf jeder Karte, darunter eine Leiste am unteren Rand. Fest
verankert, weil sie bei einer langen Liste oben laengst aus dem Blick
gescrollt waere, waehrend man noch auswaehlt.

Ein eigener Endpunkt statt vieler einzelner PATCHes: eine Transaktion statt
zwanzig Rundgaengen, und der Verlauf bekommt je Projekt genau einen Eintrag
statt eines Schwalls.

Was der Filter ausblendet, faellt aus der Auswahl - sonst traefe eine
Massenaenderung Projekte, die man gar nicht mehr vor Augen hat. Fremde IDs
sammelt der Server wegen der Besitzerbedingung erst gar nicht ein und meldet
sie als uebergangen.

Das Kaestchen ist gedaempft, aber immer sichtbar. Erst beim Ueberfahren
einzublenden waere auf einem Touchgeraet nicht erreichbar - und wer es nie
sieht, weiss nicht, dass es die Auswahl gibt.

Ansicht, Sortierung und Archivschalter ueberdauern nun das Neuladen. Wer sein
Brett von Hand anordnet, will es beim naechsten Aufruf auch sehen. Der
Suchbegriff bleibt aussen vor: eine Suche von gestern wiederherzustellen
waere eher verwirrend.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 14:30:17 +02:00
co-authored by Claude Opus 5
parent 1400bca047
commit 49e253f70b
4 changed files with 485 additions and 5 deletions
+30 -2
View File
@@ -29,7 +29,17 @@ const PROVIDER_LABEL: Record<string, string> = {
* Karte im Board. Die gesamte Flaeche ist ein Link (per "stretched link"),
* interaktive Elemente liegen darueber und bekommen pointer-events zurueck.
*/
export function ProjectCard({ project, index = 0 }: { project: ProjectListItem; index?: number }) {
export function ProjectCard({
project,
index = 0,
selected = false,
onToggleSelect,
}: {
project: ProjectListItem;
index?: number;
selected?: boolean;
onToggleSelect?: (id: string, selected: boolean) => void;
}) {
const accent = accentOf(project.accent);
const priority = PRIORITY_META[project.priority] ?? PRIORITY_META[2];
// Auf der Karte ist nur fuer eines Platz: das fuehrende. Wie viele es
@@ -60,7 +70,10 @@ export function ProjectCard({ project, index = 0 }: { project: ProjectListItem;
return (
<article
className="glass card-glow fade-up group relative overflow-hidden p-5"
className={cn(
"glass card-glow fade-up group relative overflow-hidden p-5",
selected && "ring-1 ring-violet-400/60",
)}
style={
{
"--accent-from": accent.from,
@@ -81,6 +94,21 @@ export function ProjectCard({ project, index = 0 }: { project: ProjectListItem;
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
{/* Gedaempft, aber immer da. Nur beim Ueberfahren einzublenden
waere auf einem Touchgeraet gar nicht erreichbar – und wer
es nie sieht, weiss nicht, dass es die Auswahl gibt. */}
{onToggleSelect ? (
<input
type="checkbox"
checked={selected}
onChange={(event) => onToggleSelect(project.id, event.target.checked)}
aria-label={`${project.name} auswaehlen`}
className={cn(
"pointer-events-auto h-3.5 w-3.5 shrink-0 accent-violet-500 transition-opacity",
!selected && "opacity-35 group-hover:opacity-100 focus-visible:opacity-100",
)}
/>
) : null}
<h3 className="truncate text-base font-semibold text-white transition-colors group-hover:text-violet-200">
{project.name}
</h3>