diff --git a/app/client/[token]/page.tsx b/app/client/[token]/page.tsx index ee92f77..191eb33 100644 --- a/app/client/[token]/page.tsx +++ b/app/client/[token]/page.tsx @@ -125,6 +125,10 @@ const TASK_TYPE_LABELS: Record = { GENERAL: 'Task', }; +const SCROLL_KEY = 'client-portal-scroll'; +const QUEUE_KEY = 'client-portal-queue'; +const STATE_KEY = 'client-portal-state'; + function getLatestVersion(task: ClientTask): ClientVersion | undefined { return task.versions[0]; } @@ -174,6 +178,27 @@ export default function ClientPortalPage({ }); }, [params]); + useEffect(() => { + if (!loading) { + const savedState = sessionStorage.getItem(STATE_KEY); + if (savedState) { + sessionStorage.removeItem(STATE_KEY); + try { + const { statusFilter: sf, sortMode: sm } = JSON.parse(savedState); + if (sf !== undefined) setStatusFilter(sf); + if (sm !== undefined) setSortMode(sm); + } catch { /* ignore */ } + } + const saved = sessionStorage.getItem(SCROLL_KEY); + if (saved) { + sessionStorage.removeItem(SCROLL_KEY); + requestAnimationFrame(() => { + window.scrollTo(0, parseInt(saved, 10)); + }); + } + } + }, [loading]); + const loadProject = (t: string) => { setLoading(true); setError(null); @@ -298,6 +323,40 @@ export default function ClientPortalPage({ return bDate.localeCompare(aDate); }); + const getReviewQueue = (): Array<{ versionId: string; label: string }> => { + if (sortMode === 'recent') { + return filteredFlatItems + .map((item) => { + const ver = getLatestVersion(item.task); + return ver ? { versionId: ver.id, label: item.label } : null; + }) + .filter((x): x is { versionId: string; label: string } => x !== null); + } + const queue: Array<{ versionId: string; label: string }> = []; + Object.entries(shotsByEpisode).forEach(([, epShots]) => { + epShots + .map((shot) => ({ ...shot, tasks: shot.tasks.filter(taskMatchesFilter) })) + .filter((shot) => shot.tasks.length > 0) + .forEach((shot) => { + shot.tasks.forEach((task) => { + const ver = getLatestVersion(task); + if (ver) queue.push({ versionId: ver.id, label: shot.shotCode }); + }); + }); + }); + filteredAssetTasks.forEach((task) => { + const ver = getLatestVersion(task); + if (ver) queue.push({ versionId: ver.id, label: task.asset?.assetCode ?? task.title }); + }); + return queue; + }; + + const handleReviewClick = () => { + sessionStorage.setItem(SCROLL_KEY, String(window.scrollY)); + sessionStorage.setItem(QUEUE_KEY, JSON.stringify(getReviewQueue())); + sessionStorage.setItem(STATE_KEY, JSON.stringify({ statusFilter, sortMode })); + }; + return (
@@ -498,6 +557,7 @@ export default function ClientPortalPage({

@@ -766,13 +822,8 @@ export default function ClientPortalPage({ - + onClick={handleReviewClick} + className={cn( className="h-4 w-4 text-zinc-500 shrink-0" />

{task.asset?.assetCode ?? TASK_TYPE_LABELS[task.type]} diff --git a/app/client/[token]/review/[versionId]/page.tsx b/app/client/[token]/review/[versionId]/page.tsx index 0b6bbb6..b2a6f74 100644 --- a/app/client/[token]/review/[versionId]/page.tsx +++ b/app/client/[token]/review/[versionId]/page.tsx @@ -26,6 +26,7 @@ import { MessageSquare, Send, Clock, + ChevronRight, } from "lucide-react"; import { useReviewStore } from "@/hooks/use-review-player"; import { ReviewPasswordGate } from "@/components/clients/ReviewPasswordGate"; @@ -71,6 +72,8 @@ const APPROVAL_STATUS_STYLES: Record = { NEEDS_CHANGES: "bg-orange-500/10 text-orange-400 border-orange-500/20", }; +const QUEUE_KEY = 'client-portal-queue'; + export default function ClientReviewPage({ params, }: { @@ -99,6 +102,7 @@ export default function ClientReviewPage({ const [approvalNotes, setApprovalNotes] = useState(""); const [submittingApproval, setSubmittingApproval] = useState(false); const [currentApprovalStatus, setCurrentApprovalStatus] = useState("PENDING_REVIEW"); + const [nextReview, setNextReview] = useState<{ versionId: string; label: string } | null>(null); const playerRef = useRef(null); const { toast } = useToast(); @@ -111,6 +115,19 @@ export default function ClientReviewPage({ }); }, [params]); + useEffect(() => { + if (!versionId) return; + try { + const queueJson = sessionStorage.getItem(QUEUE_KEY); + if (!queueJson) { setNextReview(null); return; } + const queue: Array<{ versionId: string; label: string }> = JSON.parse(queueJson); + const idx = queue.findIndex((item) => item.versionId === versionId); + setNextReview(idx >= 0 && idx < queue.length - 1 ? queue[idx + 1] : null); + } catch { + setNextReview(null); + } + }, [versionId]); + const loadVersion = (t: string, vId: string) => { setLoading(true); setError(null); @@ -320,6 +337,19 @@ export default function ClientReviewPage({ Approve + {nextReview && ( + <> +

+ + {nextReview.label} + Next + + + + )}