Initial commit
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { db } from "@/lib/db";
|
||||
import { auth } from "@/auth";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ProjectTabsClient } from "./ProjectTabsClient";
|
||||
import {
|
||||
Film,
|
||||
Layers,
|
||||
CheckCircle2,
|
||||
ListTodo,
|
||||
} from "lucide-react";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const project = await db.project.findUnique({ where: { id }, select: { name: true } });
|
||||
return { title: project?.name ?? "Project" };
|
||||
}
|
||||
|
||||
async function getProject(id: string) {
|
||||
return db.project.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
client: true,
|
||||
producer: { select: { id: true, name: true, image: true, email: true } },
|
||||
supervisor: { select: { id: true, name: true, image: true, email: true } },
|
||||
shots: {
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
artist: { select: { id: true, name: true, image: true, email: true } },
|
||||
versions: {
|
||||
take: 1,
|
||||
orderBy: { versionNumber: "desc" },
|
||||
include: {
|
||||
comments: { select: { id: true, isResolved: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assets: {
|
||||
orderBy: { assetCode: "asc" },
|
||||
include: {
|
||||
lead: { select: { id: true, name: true, email: true, image: true } },
|
||||
_count: { select: { tasks: true } },
|
||||
tasks: {
|
||||
orderBy: { sortOrder: "asc" },
|
||||
include: {
|
||||
assignedArtist: { select: { id: true, name: true, email: true, image: true } },
|
||||
_count: { select: { versions: true } },
|
||||
versions: {
|
||||
take: 1,
|
||||
orderBy: { versionNumber: "desc" },
|
||||
select: { id: true, versionNumber: true, approvalStatus: true, createdAt: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: {
|
||||
orderBy: [{ status: "asc" }, { sortOrder: "asc" }],
|
||||
include: {
|
||||
shot: { select: { id: true, shotCode: true } },
|
||||
asset: { select: { id: true, assetCode: true, name: true } },
|
||||
assignedArtist: { select: { id: true, name: true, email: true, image: true } },
|
||||
_count: { select: { versions: true } },
|
||||
versions: {
|
||||
take: 1,
|
||||
orderBy: { versionNumber: "desc" },
|
||||
select: { id: true, versionNumber: true, approvalStatus: true, createdAt: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function getProjectArtists() {
|
||||
return db.user.findMany({
|
||||
where: { isActive: true },
|
||||
select: { id: true, name: true, email: true },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
}
|
||||
|
||||
async function getClients() {
|
||||
return db.client.findMany({
|
||||
where: { isActive: true },
|
||||
select: { id: true, company: true },
|
||||
orderBy: { company: "asc" },
|
||||
});
|
||||
}
|
||||
|
||||
async function getTeamMembers() {
|
||||
return db.user.findMany({
|
||||
where: { isActive: true, role: { in: ["ADMIN", "PRODUCER", "SUPERVISOR"] } },
|
||||
select: { id: true, name: true, email: true, role: true },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
}
|
||||
|
||||
export default async function ProjectPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const session = await auth();
|
||||
const [project, artists, clients, teamMembers] = await Promise.all([
|
||||
getProject(id),
|
||||
getProjectArtists(),
|
||||
getClients(),
|
||||
getTeamMembers(),
|
||||
]);
|
||||
if (!project) notFound();
|
||||
|
||||
const canManage = session?.user && ["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role);
|
||||
|
||||
const totalShots = project.shots.length;
|
||||
const approvedShots = project.shots.filter((s) => s.status === "COMPLETE").length;
|
||||
const totalTasks = project.tasks.length;
|
||||
const doneTasks = project.tasks.filter((t) => t.status === "DONE").length;
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-6 max-w-[1600px] mx-auto">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center gap-2 text-sm text-zinc-500">
|
||||
<Link href="/projects" className="hover:text-white transition-colors">
|
||||
Projects
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-white">{project.name}</span>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<span className="font-mono text-sm text-muted-foreground">{project.code}</span>
|
||||
{project.client && (
|
||||
<span className="text-sm text-muted-foreground">• {project.client.company}</span>
|
||||
)}
|
||||
<Badge
|
||||
className={
|
||||
project.status === "ACTIVE"
|
||||
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
|
||||
: "bg-zinc-500/10 text-zinc-400"
|
||||
}
|
||||
>
|
||||
{project.status.replace("_", " ")}
|
||||
</Badge>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-white">{project.name}</h1>
|
||||
{project.description && (
|
||||
<p className="text-zinc-400 mt-1">{project.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats row */}
|
||||
<div className="grid grid-cols-4 gap-3 max-w-md">
|
||||
<div className="flex flex-col items-center rounded-xl border border-zinc-800 bg-zinc-900 py-3">
|
||||
<Layers className="h-4 w-4 text-zinc-400 mb-1" />
|
||||
<span className="text-xl font-bold text-white">{totalShots}</span>
|
||||
<span className="text-xs text-zinc-400">shots</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center rounded-xl border border-zinc-800 bg-zinc-900 py-3">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-400 mb-1" />
|
||||
<span className="text-xl font-bold text-white">{approvedShots}</span>
|
||||
<span className="text-xs text-zinc-400">approved</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center rounded-xl border border-zinc-800 bg-zinc-900 py-3">
|
||||
<ListTodo className="h-4 w-4 text-amber-400 mb-1" />
|
||||
<span className="text-xl font-bold text-white">{totalTasks}</span>
|
||||
<span className="text-xs text-zinc-400">tasks</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center rounded-xl border border-zinc-800 bg-zinc-900 py-3">
|
||||
<Film className="h-4 w-4 text-blue-400 mb-1" />
|
||||
<span className="text-xl font-bold text-white">{doneTasks}</span>
|
||||
<span className="text-xs text-zinc-400">done</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<ProjectTabsClient
|
||||
projectId={id}
|
||||
projectType={project.projectType}
|
||||
projectSettings={{
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
code: project.code,
|
||||
showId: project.showId,
|
||||
projectType: project.projectType,
|
||||
description: project.description,
|
||||
status: project.status,
|
||||
clientId: project.clientId,
|
||||
producerId: project.producerId,
|
||||
supervisorId: project.supervisorId,
|
||||
dueDate: project.dueDate,
|
||||
startDate: project.startDate,
|
||||
slackWebhook: project.slackWebhook,
|
||||
slackChannel: project.slackChannel,
|
||||
}}
|
||||
clients={clients}
|
||||
teamMembers={teamMembers}
|
||||
shots={project.shots as any}
|
||||
assets={project.assets as any}
|
||||
tasks={project.tasks as any}
|
||||
artists={artists}
|
||||
canManage={!!canManage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user