CSV Group updates
Deploy / deploy (push) Successful in 2m43s

This commit is contained in:
twotalesanimation
2026-08-06 09:21:48 +02:00
parent 98120f3ca8
commit 852081b4d6
@@ -490,6 +490,8 @@ export function ShotStatusClient({
}, []); }, []);
const [expandedEpisodes, setExpandedEpisodes] = useState<Set<string>>(new Set()); const [expandedEpisodes, setExpandedEpisodes] = useState<Set<string>>(new Set());
const [expandedScenes, setExpandedScenes] = useState<Set<string>>(new Set());
const [groupByScene, setGroupByScene] = useState(false);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
// Load saved episode expand state when project changes // Load saved episode expand state when project changes
@@ -501,6 +503,12 @@ export function ShotStatusClient({
} catch { } catch {
setExpandedEpisodes(new Set()); setExpandedEpisodes(new Set());
} }
try {
const savedGroup = localStorage.getItem(`shotStatus:${selectedProjectId}:groupByScene`);
setGroupByScene(savedGroup === "1");
} catch {
setGroupByScene(false);
}
}, [selectedProjectId]); }, [selectedProjectId]);
const [dueDateDialogOpen, setDueDateDialogOpen] = useState(false); const [dueDateDialogOpen, setDueDateDialogOpen] = useState(false);
@@ -516,6 +524,21 @@ export function ShotStatusClient({
return next; return next;
}); });
const toggleScene = (sc: string) =>
setExpandedScenes((prev) => {
const next = new Set(prev);
next.has(sc) ? next.delete(sc) : next.add(sc);
return next;
});
const handleGroupByScene = (checked: boolean) => {
setGroupByScene(checked);
if (checked) setExpandedScenes(new Set(shots.map((s) => s.scene)));
if (selectedProjectId) {
try { localStorage.setItem(`shotStatus:${selectedProjectId}:groupByScene`, checked ? "1" : "0"); } catch {}
}
};
const handleProjectChange = (id: string) => { const handleProjectChange = (id: string) => {
localStorage.setItem("shotStatus:lastProjectId", id); localStorage.setItem("shotStatus:lastProjectId", id);
setSelectedIds(new Set()); setSelectedIds(new Set());
@@ -567,6 +590,18 @@ export function ShotStatusClient({
})() })()
: []; : [];
const sceneGroups: [string, ShotRow[]][] = !isEpisodic && groupByScene
? (() => {
const map = new Map<string, ShotRow[]>();
for (const shot of shots) {
const key = shot.scene || "(No Scene)";
if (!map.has(key)) map.set(key, []);
map.get(key)!.push(shot);
}
return Array.from(map.entries());
})()
: [];
const episodeDueDateMap = new Map( const episodeDueDateMap = new Map(
episodeDueDates.map((e) => [e.episode, new Date(e.dueDate)]) episodeDueDates.map((e) => [e.episode, new Date(e.dueDate)])
); );
@@ -605,6 +640,17 @@ export function ShotStatusClient({
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{selectedProject && !isEpisodic && (
<label className="flex items-center gap-2 cursor-pointer select-none">
<input
type="checkbox"
checked={groupByScene}
onChange={(e) => handleGroupByScene(e.target.checked)}
className="cursor-pointer accent-blue-500 w-3.5 h-3.5"
/>
<span className="text-sm text-zinc-400">Group by scene</span>
</label>
)}
</div> </div>
{/* No project selected */} {/* No project selected */}
@@ -727,6 +773,55 @@ export function ShotStatusClient({
); );
})} })}
</div> </div>
) : groupByScene ? (
<div className="divide-y divide-zinc-800">
{sceneGroups.map(([scene, sceneShots]) => {
const collapsed = !expandedScenes.has(scene);
const sceneIds = sceneShots.map((s) => s.id);
const allScSelected = sceneIds.every((id) => selectedIds.has(id));
const someScSelected = sceneIds.some((id) => selectedIds.has(id));
return (
<div key={scene}>
<div className="flex items-center gap-2 px-4 py-3 bg-zinc-800/60 hover:bg-zinc-800/90 transition-colors">
{canManage && (
<IndeterminateCheckbox
checked={allScSelected}
indeterminate={someScSelected && !allScSelected}
onChange={(e) => toggleMany(sceneIds, e.target.checked)}
className="shrink-0"
/>
)}
<button
onClick={() => toggleScene(scene)}
className="flex items-center gap-2 flex-1 text-left"
>
{collapsed ? (
<ChevronRight className="h-4 w-4 text-zinc-400 shrink-0" />
) : (
<ChevronDown className="h-4 w-4 text-zinc-400 shrink-0" />
)}
<span className="font-semibold text-sm text-white">Scene {scene}</span>
<span className="text-xs text-zinc-500 font-normal">
{sceneShots.length} shot{sceneShots.length !== 1 ? "s" : ""}
</span>
</button>
</div>
{!collapsed && (
<div className="overflow-x-auto">
<ShotTable
shots={sceneShots}
canManage={canManage}
projectId={selectedProjectId!}
selectedIds={selectedIds}
onToggle={toggleShot}
onToggleAll={toggleMany}
/>
</div>
)}
</div>
);
})}
</div>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<ShotTable <ShotTable