@@ -165,6 +165,48 @@ export async function shareVersionWithClient(versionId: string) {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteVersion(versionId: string) {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session?.user) throw new Error("Unauthorized");
|
||||||
|
if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) {
|
||||||
|
throw new Error("Insufficient permissions");
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = await db.version.findUnique({
|
||||||
|
where: { id: versionId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isLatest: true,
|
||||||
|
taskId: true,
|
||||||
|
task: { select: { projectId: true, shotId: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!version) throw new Error("Version not found");
|
||||||
|
|
||||||
|
// If this was the latest version, promote the next most recent one
|
||||||
|
if (version.isLatest && version.taskId) {
|
||||||
|
const next = await db.version.findFirst({
|
||||||
|
where: { taskId: version.taskId, id: { not: versionId } },
|
||||||
|
orderBy: { versionNumber: "desc" },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (next) {
|
||||||
|
await db.version.update({ where: { id: next.id }, data: { isLatest: true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.version.delete({ where: { id: versionId } });
|
||||||
|
|
||||||
|
const projectId = version.task?.projectId;
|
||||||
|
const shotId = version.task?.shotId;
|
||||||
|
if (shotId) await recalcShotStatus(shotId).catch(() => {});
|
||||||
|
if (projectId) revalidatePath(`/projects/${projectId}`);
|
||||||
|
if (version.taskId) revalidatePath(`/tasks/${version.taskId}`);
|
||||||
|
if (shotId && projectId) revalidatePath(`/projects/${projectId}/shots/${shotId}`);
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
export async function getVersionById(versionId: string) {
|
export async function getVersionById(versionId: string) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user) throw new Error("Unauthorized");
|
if (!session?.user) throw new Error("Unauthorized");
|
||||||
|
|||||||
@@ -41,9 +41,11 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
User,
|
User,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { TASK_STATUS_CONFIG, TASK_TYPE_LABELS } from "@/components/tasks/TaskCard";
|
import { TASK_STATUS_CONFIG, TASK_TYPE_LABELS } from "@/components/tasks/TaskCard";
|
||||||
import { VersionUpload } from "@/components/versions/VersionUpload";
|
import { VersionUpload } from "@/components/versions/VersionUpload";
|
||||||
|
import { deleteVersion } from "@/actions/versions";
|
||||||
import type { CommentWithReplies } from "@/types";
|
import type { CommentWithReplies } from "@/types";
|
||||||
import { CommentPanel } from "@/components/comments/CommentPanel";
|
import { CommentPanel } from "@/components/comments/CommentPanel";
|
||||||
|
|
||||||
@@ -155,6 +157,17 @@ export function TaskDetailClient({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteVersion = async (versionId: string, label: string) => {
|
||||||
|
if (!confirm(`Delete ${label}? This will permanently remove the file and all comments.`)) return;
|
||||||
|
try {
|
||||||
|
await deleteVersion(versionId);
|
||||||
|
toast({ title: "Version deleted" });
|
||||||
|
router.refresh();
|
||||||
|
} catch (e) {
|
||||||
|
toast({ title: "Delete failed", description: e instanceof Error ? e.message : undefined, variant: "destructive" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
<div className="max-w-5xl mx-auto p-6 space-y-6">
|
<div className="max-w-5xl mx-auto p-6 space-y-6">
|
||||||
@@ -298,6 +311,21 @@ export function TaskDetailClient({
|
|||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
{canManage && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-muted-foreground hover:text-red-500"
|
||||||
|
onClick={() =>
|
||||||
|
handleDeleteVersion(
|
||||||
|
v.id,
|
||||||
|
`v${String(v.versionNumber).padStart(3, "0")}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { VersionWithDetails } from "@/types";
|
import type { VersionWithDetails } from "@/types";
|
||||||
import { submitApproval } from "@/actions/approvals";
|
import { submitApproval } from "@/actions/approvals";
|
||||||
|
import { deleteVersion } from "@/actions/versions";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
|
||||||
interface VersionListProps {
|
interface VersionListProps {
|
||||||
@@ -43,6 +45,7 @@ interface VersionListProps {
|
|||||||
currentVersionId?: string;
|
currentVersionId?: string;
|
||||||
onVersionSelect?: (versionId: string) => void;
|
onVersionSelect?: (versionId: string) => void;
|
||||||
canApprove?: boolean;
|
canApprove?: boolean;
|
||||||
|
canManage?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const APPROVAL_STATUS_STYLES: Record<string, string> = {
|
const APPROVAL_STATUS_STYLES: Record<string, string> = {
|
||||||
@@ -75,6 +78,7 @@ export function VersionList({
|
|||||||
currentVersionId,
|
currentVersionId,
|
||||||
onVersionSelect,
|
onVersionSelect,
|
||||||
canApprove = false,
|
canApprove = false,
|
||||||
|
canManage = false,
|
||||||
}: VersionListProps) {
|
}: VersionListProps) {
|
||||||
const [expanded, setExpanded] = useState<string | null>(currentVersionId ?? null);
|
const [expanded, setExpanded] = useState<string | null>(currentVersionId ?? null);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -109,6 +113,17 @@ export function VersionList({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (versionId: string, label: string) => {
|
||||||
|
if (!confirm(`Delete ${label}? This will permanently remove the file and all comments.`)) return;
|
||||||
|
try {
|
||||||
|
await deleteVersion(versionId);
|
||||||
|
toast({ title: "Version deleted" });
|
||||||
|
router.refresh();
|
||||||
|
} catch (e) {
|
||||||
|
toast({ title: "Delete failed", description: e instanceof Error ? e.message : undefined, variant: "destructive" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{versions.map((version) => {
|
{versions.map((version) => {
|
||||||
@@ -220,6 +235,23 @@ export function VersionList({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{canManage && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-red-500 focus:text-red-500"
|
||||||
|
onClick={() =>
|
||||||
|
handleDelete(
|
||||||
|
version.id,
|
||||||
|
`v${String(version.versionNumber).padStart(3, "0")}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5 mr-2" />
|
||||||
|
Delete version
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user