129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { TaskCard } from "./TaskCard";
|
|
import { NewTaskDialog } from "./NewTaskDialog";
|
|
import { Plus, ListTodo } from "lucide-react";
|
|
import { TaskType } from "@prisma/client";
|
|
|
|
const SHOT_QUICK_TYPES: TaskType[] = ["TRACK", "ROTO", "KEY", "COMP", "FX"];
|
|
const ASSET_QUICK_TYPES: TaskType[] = ["MODEL", "TEXTURE", "RIG", "LOOKDEV"];
|
|
|
|
interface Artist {
|
|
id: string;
|
|
name: string | null;
|
|
email: string;
|
|
}
|
|
|
|
interface Task {
|
|
id: string;
|
|
title: string;
|
|
type: TaskType;
|
|
status: any;
|
|
priority: string;
|
|
dueDate: Date | null;
|
|
assignedArtist?: { id: string; name: string | null; email: string; image: string | null } | null;
|
|
_count?: { versions: number };
|
|
versions?: { id: string; versionNumber: number; approvalStatus: string; createdAt: Date }[];
|
|
}
|
|
|
|
interface TaskListProps {
|
|
tasks: Task[];
|
|
projectId: string;
|
|
shotId?: string;
|
|
assetId?: string;
|
|
artists: Artist[];
|
|
canManage?: boolean;
|
|
onTaskCreated?: () => void;
|
|
}
|
|
|
|
export function TaskList({ tasks, projectId, shotId, assetId, artists, canManage = false, onTaskCreated }: TaskListProps) {
|
|
const [showNew, setShowNew] = useState(false);
|
|
const [prefillType, setPrefillType] = useState<TaskType | undefined>();
|
|
|
|
const quickTypes = shotId ? SHOT_QUICK_TYPES : assetId ? ASSET_QUICK_TYPES : [];
|
|
|
|
const openQuick = (type: TaskType) => {
|
|
setPrefillType(type);
|
|
setShowNew(true);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-medium text-zinc-300">Tasks</h3>
|
|
{canManage && (
|
|
<Button size="sm" variant="ghost" className="h-7 gap-1.5 text-xs" onClick={() => { setPrefillType(undefined); setShowNew(true); }}>
|
|
<Plus className="h-3.5 w-3.5" />
|
|
Add Task
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Quick-add templates */}
|
|
{canManage && quickTypes.length > 0 && (
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{quickTypes.map((t) => {
|
|
const label = {
|
|
TRACK: "Track",
|
|
ROTO: "Roto",
|
|
KEY: "Key",
|
|
COMP: "Comp",
|
|
FX: "FX",
|
|
ANIMATION: "Animation",
|
|
GENERAL: "General",
|
|
LIGHTING: "Lighting",
|
|
RENDER: "Render",
|
|
MODEL: "Model",
|
|
TEXTURE: "Texture",
|
|
RIG: "Rig",
|
|
LOOKDEV: "Lookdev"
|
|
}[t] ?? t;
|
|
const alreadyExists = tasks.some((task) => task.type === t);
|
|
return (
|
|
<button
|
|
key={t}
|
|
type="button"
|
|
disabled={alreadyExists}
|
|
onClick={() => openQuick(t)}
|
|
className={`px-2 py-0.5 rounded text-[11px] font-medium border transition-colors ${
|
|
alreadyExists
|
|
? "opacity-30 cursor-not-allowed bg-zinc-900 text-zinc-500 border-zinc-800"
|
|
: "bg-zinc-800 text-zinc-400 border-zinc-700 hover:border-amber-500/50 hover:text-amber-400"
|
|
}`}
|
|
>
|
|
+ {label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{tasks.length === 0 ? (
|
|
<div className="text-center py-6 border border-dashed border-border rounded-lg">
|
|
<ListTodo className="h-6 w-6 mx-auto mb-2 text-zinc-600" />
|
|
<p className="text-xs text-muted-foreground">No tasks yet</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-1">
|
|
{tasks.map((task) => (
|
|
<TaskCard key={task.id} task={task} projectId={projectId} canManage={canManage} />
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<NewTaskDialog
|
|
projectId={projectId}
|
|
shotId={shotId}
|
|
assetId={assetId}
|
|
artists={artists}
|
|
open={showNew}
|
|
prefillType={prefillType}
|
|
onClose={() => setShowNew(false)}
|
|
onSuccess={onTaskCreated}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|