diff --git a/app/(dashboard)/playlist/PlaylistClient.tsx b/app/(dashboard)/playlist/PlaylistClient.tsx index 96c70b1..1ce25f9 100644 --- a/app/(dashboard)/playlist/PlaylistClient.tsx +++ b/app/(dashboard)/playlist/PlaylistClient.tsx @@ -14,6 +14,9 @@ import { } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { internallyApproveShot, shareWithClient } from "@/actions/shots"; +import { useToast } from "@/components/ui/use-toast"; import { Film, Loader2, @@ -21,6 +24,8 @@ import { AlertCircle, Clock, ExternalLink, + ShieldCheck, + Send, } from "lucide-react"; interface Project { @@ -46,12 +51,15 @@ interface PlaylistShot { shotCode: string; episode: string | null; status: string; + shotApprovalStatus: string; + sharedWithClient: boolean; thumbnailUrl: string | null; latestVersion: PlaylistVersion; } interface PlaylistClientProps { projects: Project[]; + userRole: string; } const APPROVAL_COLORS: Record = { @@ -68,14 +76,18 @@ const APPROVAL_ICONS: Record = { NEEDS_CHANGES: AlertCircle, }; -export function PlaylistClient({ projects }: PlaylistClientProps) { +export function PlaylistClient({ projects, userRole }: PlaylistClientProps) { const playerRef = useRef(null); const reset = useReviewStore((s) => s.reset); + const { toast } = useToast(); const [projectId, setProjectId] = useState(null); const [shots, setShots] = useState([]); const [activeShot, setActiveShot] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [isActioning, setIsActioning] = useState(false); + + const canApprove = ["ADMIN", "PRODUCER", "SUPERVISOR"].includes(userRole); // Restore last project from localStorage on mount useEffect(() => { @@ -125,6 +137,46 @@ export function PlaylistClient({ projects }: PlaylistClientProps) { setActiveShot(shot); }, [reset]); + const refreshShots = useCallback(() => { + if (!projectId) return; + fetch(`/api/playlist?projectId=${projectId}`) + .then((r) => r.json()) + .then((data) => { + const list: PlaylistShot[] = data.shots ?? []; + setShots(list); + setActiveShot((prev) => prev ? (list.find((s) => s.id === prev.id) ?? prev) : null); + }) + .catch(() => {}); + }, [projectId]); + + const handleInternallyApprove = async () => { + if (!activeShot) return; + setIsActioning(true); + try { + await internallyApproveShot(activeShot.id); + toast({ title: "Shot internally approved", description: "Status: Ready for Client" }); + refreshShots(); + } catch (e) { + toast({ title: "Failed", description: (e as Error).message, variant: "destructive" }); + } finally { + setIsActioning(false); + } + }; + + const handleShareWithClient = async () => { + if (!activeShot) return; + setIsActioning(true); + try { + await shareWithClient(activeShot.id); + toast({ title: "Shared with client", description: "Status: Client Review" }); + refreshShots(); + } catch (e) { + toast({ title: "Failed", description: (e as Error).message, variant: "destructive" }); + } finally { + setIsActioning(false); + } + }; + // Group shots by episode for episodic projects const selectedProject = projects.find((p) => p.id === projectId); const isEpisodic = selectedProject?.projectType === "EPISODIC"; @@ -173,6 +225,33 @@ export function PlaylistClient({ projects }: PlaylistClientProps) { })()} {activeShot.latestVersion.approvalStatus.replace(/_/g, " ")} + + {canApprove && activeShot.shotApprovalStatus === "PENDING" && ( + + )} + + {canApprove && activeShot.shotApprovalStatus === "INTERNALLY_APPROVED" && !activeShot.sharedWithClient && ( + + )} + ; + return ; } diff --git a/app/api/playlist/route.ts b/app/api/playlist/route.ts index 9cebae0..6b66041 100644 --- a/app/api/playlist/route.ts +++ b/app/api/playlist/route.ts @@ -30,6 +30,8 @@ export async function GET(req: NextRequest) { shotCode: true, episode: true, status: true, + shotApprovalStatus: true, + sharedWithClient: true, thumbnailUrl: true, tasks: { select: { @@ -71,6 +73,8 @@ export async function GET(req: NextRequest) { shotCode: shot.shotCode, episode: shot.episode, status: shot.status, + shotApprovalStatus: shot.shotApprovalStatus, + sharedWithClient: shot.sharedWithClient, thumbnailUrl: shot.thumbnailUrl, latestVersion: { id: latest.id,