Key Shots
Deploy / deploy (push) Successful in 3m0s

This commit is contained in:
twotalesanimation
2026-07-09 13:43:16 +02:00
parent 637b141c87
commit 4624a4f67d
6 changed files with 138 additions and 10 deletions
@@ -22,7 +22,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import { updateShotNotes, bulkUpdateDueDates } from "@/actions/shots";
import { updateShotNotes, bulkUpdateDueDates, toggleKeyShot } from "@/actions/shots";
import { useToast } from "@/components/ui/use-toast";
import {
Film,
@@ -37,6 +37,7 @@ import {
X,
ShieldCheck,
Eye,
Star,
} from "lucide-react";
import { format } from "date-fns";
@@ -52,6 +53,7 @@ type ShotRow = {
thumbnailUrl: string | null;
notes: string | null;
description: string | null;
isKeyShot: boolean;
artist: { id: string; name: string | null; image: string | null; email: string } | null;
};
@@ -261,6 +263,49 @@ function NotesCell({
);
}
// ── KeyShotToggle ─────────────────────────────────────────────────────────────
function KeyShotToggle({ shot }: { shot: ShotRow }) {
const [optimistic, setOptimistic] = useState(shot.isKeyShot);
const [isPending, startTransition] = useTransition();
const { toast } = useToast();
const handle = () => {
const next = !optimistic;
setOptimistic(next);
startTransition(async () => {
try {
await toggleKeyShot(shot.id, next);
} catch {
setOptimistic(!next);
toast({ title: "Failed to update key shot", variant: "destructive" });
}
});
};
return (
<button
type="button"
role="switch"
aria-checked={optimistic}
aria-label={optimistic ? "Remove key shot" : "Mark as key shot"}
disabled={isPending}
onClick={handle}
className={cn(
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-400 disabled:opacity-50",
optimistic ? "bg-amber-500" : "bg-zinc-600 hover:bg-zinc-500"
)}
>
<span
className={cn(
"pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform",
optimistic ? "translate-x-4" : "translate-x-0"
)}
/>
</button>
);
}
function ShotTable({
shots,
canManage,
@@ -297,6 +342,12 @@ function ShotTable({
<th className="text-left py-2 px-3 text-xs font-medium text-zinc-500 w-32">Status</th>
<th className="text-left py-2 px-3 text-xs font-medium text-zinc-500 w-32">Due Date</th>
<th className="text-left py-2 px-3 text-xs font-medium text-zinc-500">Notes</th>
<th className="py-2 px-3 text-xs font-medium text-zinc-500 w-20 text-center">
<span className="flex items-center gap-1 justify-center">
<Star className="h-3 w-3" />
Key
</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800/60">
@@ -308,7 +359,13 @@ function ShotTable({
const isSelected = selectedIds.has(shot.id);
return (
<tr key={shot.id} className={cn("hover:bg-zinc-800/30 transition-colors", isSelected && "bg-blue-500/5 hover:bg-blue-500/10")}>
<tr key={shot.id} className={cn(
"transition-colors",
shot.isKeyShot
? "bg-amber-500/5 hover:bg-amber-500/10 border-l-2 border-l-amber-500"
: "hover:bg-zinc-800/30",
isSelected && "bg-blue-500/5 hover:bg-blue-500/10"
)}>
{/* Checkbox */}
{canManage && (
<td className="py-2 px-3">
@@ -340,13 +397,18 @@ function ShotTable({
{/* Shot name */}
<td className="py-2 px-3">
<Link
href={`/projects/${projectId}/shots/${shot.id}`}
className="font-mono text-sm text-white hover:text-blue-400 transition-colors"
onClick={() => sessionStorage.setItem(SCROLL_KEY, String(window.scrollY))}
>
{shot.shotCode}
</Link>
<div className="flex items-center gap-1.5">
{shot.isKeyShot && (
<Star className="h-3 w-3 text-amber-400 fill-amber-400 shrink-0" />
)}
<Link
href={`/projects/${projectId}/shots/${shot.id}`}
className="font-mono text-sm text-white hover:text-blue-400 transition-colors"
onClick={() => sessionStorage.setItem(SCROLL_KEY, String(window.scrollY))}
>
{shot.shotCode}
</Link>
</div>
{shot.description && (
<p className="text-xs text-zinc-500 mt-0.5 truncate max-w-[200px]">
{shot.description}
@@ -383,6 +445,11 @@ function ShotTable({
<td className="py-2 px-3 min-w-[220px]">
<NotesCell shot={shot} canManage={canManage} />
</td>
{/* Key shot toggle */}
<td className="py-2 px-3 text-center">
<KeyShotToggle shot={shot} />
</td>
</tr>
);
})}
+1
View File
@@ -39,6 +39,7 @@ async function getShotsForProject(projectId: string) {
thumbnailUrl: true,
notes: true,
description: true,
isKeyShot: true,
artist: { select: { id: true, name: true, image: true, email: true } },
},
});