"use client"; import { cloneElement, isValidElement, useState } from "react"; import { IconArchive, IconCheck, IconCopy, IconLoader, IconPalette, IconPencil, IconPin, IconPinFilled, IconTrash, } from "@tabler/icons-react"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from "@kandev/ui/context-menu"; import { TaskMoveContextMenuItems, type TaskMoveWorkflow, } from "@/components/task/task-move-context-menu"; import { useTaskWorkflowMove } from "@/hooks/use-task-workflow-move"; import { useSetTaskColor, useTaskColor } from "@/hooks/use-task-color"; import { TASK_COLORS, TASK_COLOR_BAR_CLASS, TASK_COLOR_LABEL, type TaskColor, } from "@/lib/task-colors"; import { cn } from "@/lib/utils"; import type { TaskSwitcherItem } from "./task-switcher"; export type StepDef = { id: string; title: string; color?: string; events?: { on_enter?: Array<{ type: string; config?: Record }> }; }; type ContextMenuProps = { task: TaskSwitcherItem; workflows?: TaskMoveWorkflow[]; stepsByWorkflowId?: Record; steps?: StepDef[]; children: React.ReactElement<{ menuOpen?: boolean }>; onRenameTask?: (taskId: string, currentTitle: string) => void; onArchiveTask?: (taskId: string) => void; onDeleteTask?: (taskId: string) => void; onMoveToStep?: (taskId: string, workflowId: string, targetStepId: string) => void; onTogglePin?: (taskId: string) => void; isPinned?: boolean; isDeleting?: boolean; }; export function TaskItemWithContextMenu({ task, workflows, stepsByWorkflowId, steps, children, onRenameTask, onArchiveTask, onDeleteTask, onMoveToStep, onTogglePin, isPinned, isDeleting, }: ContextMenuProps) { const [contextOpen, setContextOpen] = useState(false); const [menuKey, setMenuKey] = useState(0); const moveTasks = useTaskWorkflowMove(); const menuOpen = contextOpen || isDeleting === true; const closeMenu = () => { setContextOpen(false); setMenuKey((k) => k + 1); }; return (
{cloneWithMenuOpen(children, menuOpen)}
{onTogglePin && ( onTogglePin(task.id)}> {isPinned ? ( ) : ( )} {isPinned ? "Unpin" : "Pin"} )} {onRenameTask && ( onRenameTask(task.id, task.title)}> Rename )} Duplicate {onArchiveTask && ( onArchiveTask(task.id)}> Archive )} {task.workflowId && ( { closeMenu(); onMoveToStep(task.id, task.workflowId!, stepId); } : undefined } onSendToWorkflow={(workflowId, stepId) => { closeMenu(); void moveTasks([task.id], workflowId, stepId).catch(() => { // useTaskWorkflowMove already shows the failure toast. }); }} /> )} {onDeleteTask && ( <> onDeleteTask(task.id)} > {isDeleting ? ( ) : ( )} Delete )}
); } function cloneWithMenuOpen( children: React.ReactElement<{ menuOpen?: boolean }>, menuOpen: boolean, ): React.ReactNode { if (isValidElement(children)) return cloneElement(children, { menuOpen }); return children; } function TaskColorMenu({ taskId, disabled }: { taskId: string; disabled?: boolean }) { const currentColor = useTaskColor(taskId); const setColor = useSetTaskColor(); return ( Color {currentColor && ( )} {TASK_COLORS.map((color) => ( setColor(taskId, color)} /> ))} setColor(taskId, null)}> None ); } function TaskColorMenuItem({ color, selected, onSelect, }: { color: TaskColor; selected: boolean; onSelect: () => void; }) { return ( {TASK_COLOR_LABEL[color]} {selected && } ); }