This commit is contained in:
@@ -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<Set<string>>(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<string, ShotWithDetails[]>();
|
||||
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({
|
||||
<div>
|
||||
{shots.length === 0 ? (
|
||||
<EmptyState icon={Film} label="No shots yet" />
|
||||
) : projectType === "EPISODIC" ? (
|
||||
<div className="space-y-4">
|
||||
{episodeGroups.map(([episode, episodeShots]) => {
|
||||
const collapsed = collapsedEpisodes.has(episode);
|
||||
return (
|
||||
<div key={episode}>
|
||||
<button
|
||||
onClick={() => toggleEpisode(episode)}
|
||||
className="flex items-center gap-2 w-full mb-3 group text-left"
|
||||
>
|
||||
{collapsed
|
||||
? <ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
: <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />}
|
||||
<span className="font-semibold text-sm">
|
||||
Episode {episode}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground font-normal">
|
||||
{episodeShots.length} shot{episodeShots.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
<div className="flex-1 h-px bg-border ml-1" />
|
||||
</button>
|
||||
{!collapsed && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{episodeShots.map((shot) => (
|
||||
<ShotCard key={shot.id} shot={shot} projectId={projectId} canManage={canManage} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{shots.map((shot) => (
|
||||
|
||||
Reference in New Issue
Block a user