213 lines
6.8 KiB
TypeScript
213 lines
6.8 KiB
TypeScript
import { auth } from "@/auth";
|
|
import { db } from "@/lib/db";
|
|
import { StatsCards } from "@/components/dashboard/StatsCards";
|
|
import { ShotQueue } from "@/components/dashboard/ShotQueue";
|
|
import { RecentActivity } from "@/components/dashboard/RecentActivity";
|
|
import { TaskWidgets } from "@/components/dashboard/TaskWidgets";
|
|
import { ScheduleWidgets } from "@/components/dashboard/ScheduleWidgets";
|
|
import type { DashboardStats } from "@/types";
|
|
|
|
export const metadata = { title: "Dashboard" };
|
|
|
|
async function getDashboardData(userId: string, role: string) {
|
|
const isArtist = role === "ARTIST";
|
|
const isClient = role === "CLIENT";
|
|
const now = new Date();
|
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const todayEnd = new Date(todayStart.getTime() + 86400000);
|
|
|
|
const [awaitingReview, needsRevisions, approved, activeProjects,
|
|
tasksDueToday, tasksInReview, tasksOverdue, myTasksCount] =
|
|
await Promise.all([
|
|
db.version.count({ where: { approvalStatus: "PENDING_REVIEW" } }),
|
|
db.version.count({ where: { approvalStatus: "NEEDS_CHANGES" } }),
|
|
db.version.count({ where: { approvalStatus: "APPROVED" } }),
|
|
db.project.count({ where: { status: "ACTIVE" } }),
|
|
db.task.count({
|
|
where: {
|
|
dueDate: { gte: todayStart, lt: todayEnd },
|
|
status: { not: "DONE" },
|
|
...(isArtist ? { assignedArtistId: userId } : {}),
|
|
},
|
|
}),
|
|
db.task.count({
|
|
where: {
|
|
status: { in: ["INTERNAL_REVIEW", "CLIENT_REVIEW"] },
|
|
...(isArtist ? { assignedArtistId: userId } : {}),
|
|
},
|
|
}),
|
|
db.task.count({
|
|
where: {
|
|
dueDate: { lt: todayStart },
|
|
status: { not: "DONE" },
|
|
...(isArtist ? { assignedArtistId: userId } : {}),
|
|
},
|
|
}),
|
|
db.task.count({
|
|
where: { assignedArtistId: userId, status: { not: "DONE" } },
|
|
}),
|
|
]);
|
|
|
|
const shotsWhere = isArtist ? { artistId: userId } : {};
|
|
const shots = await db.shot.findMany({
|
|
where: { ...shotsWhere, status: { not: "COMPLETE" } },
|
|
take: 15,
|
|
orderBy: { updatedAt: "desc" },
|
|
include: {
|
|
artist: { select: { id: true, name: true, image: true, email: true } },
|
|
versions: {
|
|
take: 1,
|
|
orderBy: { versionNumber: "desc" },
|
|
include: { comments: { select: { id: true, isResolved: true } } },
|
|
},
|
|
},
|
|
});
|
|
|
|
// My tasks (for widget)
|
|
const myTasks = await db.task.findMany({
|
|
where: {
|
|
assignedArtistId: userId,
|
|
status: { not: "DONE" },
|
|
},
|
|
take: 8,
|
|
orderBy: [{ dueDate: "asc" }, { priority: "desc" }],
|
|
include: {
|
|
shot: { select: { shotCode: true } },
|
|
asset: { select: { assetCode: true } },
|
|
project: { select: { id: true, name: true, code: true } },
|
|
},
|
|
});
|
|
|
|
// Tasks in review (for supervisors/producers)
|
|
const reviewTasks = isArtist || isClient ? [] : await db.task.findMany({
|
|
where: { status: { in: ["INTERNAL_REVIEW", "CLIENT_REVIEW"] } },
|
|
take: 8,
|
|
orderBy: { updatedAt: "desc" },
|
|
include: {
|
|
shot: { select: { shotCode: true } },
|
|
asset: { select: { assetCode: true } },
|
|
project: { select: { id: true, name: true, code: true } },
|
|
assignedArtist: { select: { id: true, name: true, image: true, email: true } },
|
|
},
|
|
});
|
|
|
|
const activity = await db.notification.findMany({
|
|
where: isClient ? { userId } : {},
|
|
take: 20,
|
|
orderBy: { createdAt: "desc" },
|
|
include: { user: { select: { name: true, image: true } } },
|
|
});
|
|
|
|
// Schedule data (for producers/supervisors/admins)
|
|
const scheduledTasks =
|
|
!isArtist && !isClient
|
|
? await db.task.findMany({
|
|
where: { scheduledStartDate: { not: null }, status: { not: "DONE" } },
|
|
include: {
|
|
shot: { select: { shotCode: true } },
|
|
asset: { select: { assetCode: true } },
|
|
project: { select: { id: true, name: true, code: true } },
|
|
assignedArtist: {
|
|
select: { id: true, name: true, email: true, image: true },
|
|
},
|
|
},
|
|
orderBy: { scheduledStartDate: "asc" },
|
|
})
|
|
: [];
|
|
|
|
const scheduleArtists =
|
|
!isArtist && !isClient
|
|
? await db.user.findMany({
|
|
where: { isActive: true, role: { not: "CLIENT" } },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
image: true,
|
|
role: true,
|
|
},
|
|
orderBy: { name: "asc" },
|
|
})
|
|
: [];
|
|
|
|
const stats: DashboardStats = {
|
|
awaitingReview,
|
|
needsRevisions,
|
|
approved,
|
|
overdue: tasksOverdue,
|
|
activeProjects,
|
|
tasksDueToday,
|
|
tasksInReview,
|
|
tasksOverdue,
|
|
myTasksCount,
|
|
};
|
|
|
|
return { stats, shots, activity, myTasks, reviewTasks, scheduledTasks, scheduleArtists };
|
|
}
|
|
|
|
export default async function DashboardPage() {
|
|
const session = await auth();
|
|
if (!session?.user) return null;
|
|
|
|
const { stats, shots, activity, myTasks, reviewTasks, scheduledTasks, scheduleArtists } =
|
|
await getDashboardData(session.user.id, session.user.role);
|
|
|
|
const isClient = session.user.role === "CLIENT";
|
|
const canSeeSchedule = ["ADMIN", "PRODUCER", "SUPERVISOR"].includes(
|
|
session.user.role
|
|
);
|
|
|
|
return (
|
|
<div className="p-8 space-y-6 max-w-[1600px] mx-auto">
|
|
<div className="mb-4">
|
|
<h1 className="text-3xl font-bold text-white">Dashboard</h1>
|
|
<p className="text-zinc-400 mt-1">
|
|
Welcome back, {session.user.name ?? session.user.email}
|
|
</p>
|
|
</div>
|
|
|
|
<StatsCards stats={stats} />
|
|
|
|
{!isClient && (
|
|
<TaskWidgets
|
|
myTasks={myTasks as any}
|
|
reviewTasks={reviewTasks as any}
|
|
role={session.user.role}
|
|
/>
|
|
)}
|
|
|
|
{canSeeSchedule && (
|
|
<div className="space-y-3">
|
|
<h2 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
Schedule Overview
|
|
</h2>
|
|
<ScheduleWidgets
|
|
scheduledTasks={scheduledTasks as any}
|
|
artists={scheduleArtists}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
|
<div className="xl:col-span-2 space-y-3">
|
|
<h2 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
Shot Queue
|
|
</h2>
|
|
<div className="rounded-lg border border-border bg-card p-1">
|
|
<ShotQueue shots={shots as any} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<h2 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
Recent Activity
|
|
</h2>
|
|
<div className="rounded-lg border border-border bg-card h-[400px] overflow-hidden">
|
|
<RecentActivity activities={activity as any} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|