This commit is contained in:
@@ -60,6 +60,7 @@ interface ProjectTabsClientProps {
|
||||
assets: any[];
|
||||
tasks: any[];
|
||||
artists: Artist[];
|
||||
shotGroups: { id: string; name: string }[];
|
||||
canManage: boolean;
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ export function ProjectTabsClient({
|
||||
assets,
|
||||
tasks,
|
||||
artists,
|
||||
shotGroups,
|
||||
canManage,
|
||||
}: ProjectTabsClientProps) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>("shots");
|
||||
@@ -109,6 +111,32 @@ export function ProjectTabsClient({
|
||||
})()
|
||||
: [];
|
||||
|
||||
// For standard projects: group by custom shot group when any exist
|
||||
const standardGroups: [string, ShotWithDetails[]][] = projectType === "STANDARD" && shots.some((s) => s.shotGroup)
|
||||
? (() => {
|
||||
const sorted = [...shots].sort((a, b) => {
|
||||
const ga = a.shotGroup?.name ?? "\uFFFF";
|
||||
const gb = b.shotGroup?.name ?? "\uFFFF";
|
||||
if (ga !== gb) return ga.localeCompare(gb, 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.shotGroup?.name ?? "(Ungrouped)";
|
||||
if (!map.has(key)) map.set(key, []);
|
||||
map.get(key)!.push(shot);
|
||||
}
|
||||
// Move ungrouped to the end
|
||||
const ungrouped = map.get("(Ungrouped)");
|
||||
if (ungrouped) {
|
||||
map.delete("(Ungrouped)");
|
||||
map.set("(Ungrouped)", ungrouped);
|
||||
}
|
||||
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 },
|
||||
{ id: "assets", label: "Assets", icon: Package, count: assets.length },
|
||||
@@ -211,6 +239,36 @@ export function ProjectTabsClient({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : standardGroups.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{standardGroups.map(([groupName, groupShots]) => {
|
||||
const collapsed = collapsedEpisodes.has(groupName);
|
||||
return (
|
||||
<div key={groupName}>
|
||||
<button
|
||||
onClick={() => toggleEpisode(groupName)}
|
||||
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">{groupName}</span>
|
||||
<span className="text-xs text-muted-foreground font-normal">
|
||||
{groupShots.length} shot{groupShots.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">
|
||||
{groupShots.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) => (
|
||||
@@ -267,6 +325,7 @@ export function ProjectTabsClient({
|
||||
<NewShotDialog
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
shotGroups={shotGroups}
|
||||
open={showNewShot}
|
||||
onClose={() => setShowNewShot(false)}
|
||||
onSuccess={() => setShowNewShot(false)}
|
||||
|
||||
@@ -28,6 +28,7 @@ async function getProject(id: string) {
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
artist: { select: { id: true, name: true, image: true, email: true } },
|
||||
shotGroup: { select: { id: true, name: true } },
|
||||
versions: {
|
||||
take: 1,
|
||||
orderBy: { versionNumber: "desc" },
|
||||
@@ -70,6 +71,10 @@ async function getProject(id: string) {
|
||||
},
|
||||
},
|
||||
},
|
||||
shotGroups: {
|
||||
orderBy: [{ sortOrder: "asc" }, { name: "asc" }],
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -202,6 +207,7 @@ export default async function ProjectPage({ params }: { params: Promise<{ id: st
|
||||
assets={project.assets as any}
|
||||
tasks={project.tasks as any}
|
||||
artists={artists}
|
||||
shotGroups={project.shotGroups}
|
||||
canManage={!!canManage}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user