pipeline: equal-width columns + per-column clear/stop button
All checks were successful
Build and Push Docker Image / build (push) Successful in 39s

Extract a ColumnShell component so all four columns share the same flex-1
basis-0 width (no more 24/16/18/16 rem mix) and the same header layout
(title + count + optional action button on the right).

Per-column actions:
- Review:     'Skip all' → POST /api/review/skip-all (new endpoint, sets all
              pending non-noop plans to skipped in one update)
- Queued:     'Clear'    → POST /api/execute/clear (existing; cancels pending jobs)
- Processing: 'Stop'     → POST /api/execute/stop (new; SIGTERMs the running
              ffmpeg via a tracked Bun.spawn handle, runJob's catch path
              marks the job error and cleans up)
- Done:       'Clear'    → POST /api/execute/clear-completed (existing)

All destructive actions confirm before firing.
This commit is contained in:
2026-04-13 10:08:42 +02:00
parent ec28e43484
commit 4a378eb833
8 changed files with 184 additions and 77 deletions

View File

@@ -1,4 +1,5 @@
import { api } from "~/shared/lib/api";
import { ColumnShell } from "./ColumnShell";
import { PipelineCard } from "./PipelineCard";
import { SeriesCard } from "./SeriesCard";
@@ -11,6 +12,12 @@ interface ReviewColumnProps {
export function ReviewColumn({ items, total, jellyfinUrl, onMutate }: ReviewColumnProps) {
const truncated = total > items.length;
const skipAll = async () => {
if (!confirm(`Skip all ${total} pending items? They won't be processed unless you unskip them.`)) return;
await api.post("/api/review/skip-all");
onMutate();
};
// Group by series (movies are standalone)
const movies = items.filter((i: any) => i.type === "Movie");
const seriesMap = new Map<string, { name: string; key: string; jellyfinId: string | null; episodes: any[] }>();
@@ -45,11 +52,12 @@ export function ReviewColumn({ items, total, jellyfinUrl, onMutate }: ReviewColu
};
return (
<div className="flex flex-col w-96 min-w-96 min-h-0 bg-gray-50 rounded-lg">
<div className="px-3 py-2 border-b font-medium text-sm">
Review <span className="text-gray-400">({truncated ? `${items.length} of ${total}` : total})</span>
</div>
<div className="flex-1 overflow-y-auto p-2 space-y-2">
<ColumnShell
title="Review"
count={truncated ? `${items.length} of ${total}` : total}
action={total > 0 ? { label: "Skip all", onClick: skipAll } : undefined}
>
<div className="space-y-2">
{allItems.map((entry) => {
if (entry.type === "movie") {
return (
@@ -86,6 +94,6 @@ export function ReviewColumn({ items, total, jellyfinUrl, onMutate }: ReviewColu
</p>
)}
</div>
</div>
</ColumnShell>
);
}