From de59bbc0aa9254e7e408e55c254b622a0cff9ee2 Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Wed, 20 May 2026 22:20:20 +0200 Subject: [PATCH] added episodic grouping on project pages --- .../projects/[id]/ProjectTabsClient.tsx | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/app/(dashboard)/projects/[id]/ProjectTabsClient.tsx b/app/(dashboard)/projects/[id]/ProjectTabsClient.tsx index a6414fa..cede4f3 100644 --- a/app/(dashboard)/projects/[id]/ProjectTabsClient.tsx +++ b/app/(dashboard)/projects/[id]/ProjectTabsClient.tsx @@ -11,7 +11,7 @@ import { TaskCard } from "@/components/tasks/TaskCard"; import { NewTaskDialog } from "@/components/tasks/NewTaskDialog"; import { KanbanBoard } from "@/components/tasks/KanbanBoard"; import { cn } from "@/lib/utils"; -import { Film, Package, ListTodo, LayoutDashboard, Plus, Settings, FileUp } from "lucide-react"; +import { Film, Package, ListTodo, LayoutDashboard, Plus, Settings, FileUp, ChevronDown, ChevronRight } from "lucide-react"; import type { ShotWithDetails } from "@/types"; import { ProjectSettingsTab } from "@/components/projects/ProjectSettingsTab"; @@ -80,6 +80,34 @@ export function ProjectTabsClient({ const [showImportShots, setShowImportShots] = useState(false); const [showNewAsset, setShowNewAsset] = useState(false); const [showNewTask, setShowNewTask] = useState(false); + const [collapsedEpisodes, setCollapsedEpisodes] = useState>(new Set()); + + const toggleEpisode = (ep: string) => + setCollapsedEpisodes((prev) => { + const next = new Set(prev); + next.has(ep) ? next.delete(ep) : next.add(ep); + return next; + }); + + // For episodic projects: group and sort shots by episode → scene → shotNumber + const episodeGroups: [string, ShotWithDetails[]][] = projectType === "EPISODIC" + ? (() => { + const sorted = [...shots].sort((a, b) => { + const ea = a.episode ?? ""; + const eb = b.episode ?? ""; + if (ea !== eb) return ea.localeCompare(eb, undefined, { numeric: true }); + if (a.scene !== b.scene) return a.scene.localeCompare(b.scene, undefined, { numeric: true }); + return a.shotNumber - b.shotNumber; + }); + const map = new Map(); + for (const shot of sorted) { + const key = shot.episode ?? "(No Episode)"; + if (!map.has(key)) map.set(key, []); + map.get(key)!.push(shot); + } + return Array.from(map.entries()); + })() + : []; const tabs: { id: Tab; label: string; icon: React.ElementType; count: number; managerOnly?: boolean }[] = [ { id: "shots", label: "Shots", icon: Film, count: shots.length }, @@ -151,6 +179,38 @@ export function ProjectTabsClient({
{shots.length === 0 ? ( + ) : projectType === "EPISODIC" ? ( +
+ {episodeGroups.map(([episode, episodeShots]) => { + const collapsed = collapsedEpisodes.has(episode); + return ( +
+ + {!collapsed && ( +
+ {episodeShots.map((shot) => ( + + ))} +
+ )} +
+ ); + })} +
) : (
{shots.map((shot) => (