"use client"; import { useDraggable } from "@dnd-kit/core"; import { format, isAfter, parseISO, } from "date-fns"; import { useRouter } from "next/navigation"; import { cn } from "@/lib/utils"; import { CalendarDays, Clock, ExternalLink, CalendarOff } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { TaskStatus, TaskType } from "@prisma/client"; import { ScheduleTask, ActiveDragData, } from "@/app/(dashboard)/schedule/SchedulePageClient"; import { TASK_TYPE_LABELS } from "@/components/tasks/TaskCard"; const STATUS_STYLES: Record< TaskStatus, { bg: string; border: string; text: string; dot: string } > = { TODO: { bg: "bg-zinc-800/80", border: "border-zinc-600/60", text: "text-zinc-300", dot: "bg-zinc-500", }, IN_PROGRESS: { bg: "bg-blue-900/50", border: "border-blue-600/60", text: "text-blue-200", dot: "bg-blue-400", }, INTERNAL_REVIEW: { bg: "bg-purple-900/50", border: "border-purple-600/60", text: "text-purple-200", dot: "bg-purple-400", }, CLIENT_REVIEW: { bg: "bg-amber-900/50", border: "border-amber-600/60", text: "text-amber-200", dot: "bg-amber-400", }, CHANGES: { bg: "bg-orange-900/50", border: "border-orange-600/60", text: "text-orange-200", dot: "bg-orange-400", }, DONE: { bg: "bg-emerald-900/50", border: "border-emerald-600/60", text: "text-emerald-200", dot: "bg-emerald-400", }, }; function toDate(val: string | null | undefined): Date | null { if (!val) return null; try { return parseISO(val); } catch { return new Date(val); } } interface ScheduleTaskBlockProps { task: ScheduleTask; dayIndex: number; duration: number; artistIndex: number; viewStart: Date; days: Date[]; canEdit: boolean; isDragging: boolean; laneTop: number; taskHeight: number; onResizeMouseDown: ( taskId: string, currentEndDate: string ) => (e: React.MouseEvent) => void; onUnschedule: (taskId: string) => void; dayWidth: number; } export function ScheduleTaskBlock({ task, dayIndex, duration, canEdit, isDragging, laneTop, taskHeight, onResizeMouseDown, onUnschedule, dayWidth, }: ScheduleTaskBlockProps) { const router = useRouter(); const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: task.id, disabled: !canEdit, data: { type: "scheduled", taskId: task.id, duration, } as ActiveDragData, }); const taskWidth = Math.max(20, (task.estimatedHours ?? 8) / 8 * dayWidth - 4); const style = { position: "absolute" as const, left: Math.max(0, dayIndex) * dayWidth + 2, width: taskWidth, top: laneTop, height: taskHeight, transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined, zIndex: isDragging ? 10 : 1, }; const statusStyle = STATUS_STYLES[task.status]; const contextCode = task.shot?.shotCode ?? task.asset?.assetCode; const contextName = task.shot?.shotCode ?? task.asset?.name ?? task.title; const thumbnail = task.shot?.thumbnailUrl ?? null; const isOverdue = task.dueDate && task.scheduledEndDate && isAfter( toDate(task.scheduledEndDate)!, toDate(task.dueDate)! ); const dueDate = toDate(task.dueDate); const tooNarrow = (task.estimatedHours ?? 8) < 3; // < 3h = too narrow for labels const tooShort = taskHeight < 16; return (
{/* Status dot */}
{/* Thumbnail */} {thumbnail && !tooNarrow && ( // eslint-disable-next-line @next/next/no-img-element )} {/* Content */}
{!tooShort && ( {contextName} )} {!tooNarrow && !tooShort && ( {TASK_TYPE_LABELS[task.type as TaskType]} )} {!tooNarrow && !tooShort && task.estimatedHours && ( {task.estimatedHours}h )}
{/* Overdue indicator */} {isOverdue && (
)} {/* Resize handle */} {canEdit && task.scheduledEndDate && (
e.stopPropagation()} onMouseDown={onResizeMouseDown(task.id, task.scheduledEndDate)} onClick={(e) => e.stopPropagation()} >
)}
{contextCode && ( {contextCode} )} {task.title}
{TASK_TYPE_LABELS[task.type as TaskType]} ·{" "} {task.project.code}
{task.scheduledStartDate && task.scheduledEndDate && (
{format(toDate(task.scheduledStartDate)!, "MMM d")} →{" "} {format(toDate(task.scheduledEndDate)!, "MMM d")}
)} {task.estimatedHours && (
{task.estimatedHours}h estimated
)} {dueDate && (
Due {format(dueDate, "MMM d")} {isOverdue && " — Late!"}
)}
router.push(`/tasks/${task.id}`)} > View Task {canEdit && ( <> onUnschedule(task.id)} > Unschedule )} ); }