From 5d1b411c71fb3f06dc00960301523df70d1dc98e Mon Sep 17 00:00:00 2001 From: KappaKoder Date: Thu, 5 Feb 2026 10:38:06 -0600 Subject: [PATCH 1/4] Importing Old Task Portal Features Importing task portal features from old version into newer version. To ensure that the commit history is clean and packages consistent with current workflow. --- .../ui/task-components/AssigneesCombobox.tsx | 118 +++++ .../ui/task-components/ColumnContainer.tsx | 499 ++++++++++++++++++ .../components/ui/task-components/Filter.tsx | 435 +++++++++++++++ .../ui/task-components/KanbanBoard.tsx | 499 ++++++++++++++++++ .../ui/task-components/LearnMore.tsx | 47 ++ .../ui/task-components/MarkdownEditor.tsx | 102 ++++ .../ui/task-components/MyTaskContent.tsx | 356 +++++++++++++ .../ui/task-components/MyTaskDropdown.tsx | 54 ++ .../ui/task-components/MyTaskTable.tsx | 298 +++++++++++ .../ui/task-components/TagsCombobox.tsx | 106 ++++ .../ui/task-components/TaskCard.tsx | 144 +++++ .../ui/task-components/TaskSheet.tsx | 170 ++++++ frontend/src/lib/dummyData/dummyTasks.ts | 167 ++++++ frontend/src/pages/Kanban.tsx | 220 ++++++++ 14 files changed, 3215 insertions(+) create mode 100644 frontend/src/components/ui/task-components/AssigneesCombobox.tsx create mode 100644 frontend/src/components/ui/task-components/ColumnContainer.tsx create mode 100644 frontend/src/components/ui/task-components/Filter.tsx create mode 100644 frontend/src/components/ui/task-components/KanbanBoard.tsx create mode 100644 frontend/src/components/ui/task-components/LearnMore.tsx create mode 100644 frontend/src/components/ui/task-components/MarkdownEditor.tsx create mode 100644 frontend/src/components/ui/task-components/MyTaskContent.tsx create mode 100644 frontend/src/components/ui/task-components/MyTaskDropdown.tsx create mode 100644 frontend/src/components/ui/task-components/MyTaskTable.tsx create mode 100644 frontend/src/components/ui/task-components/TagsCombobox.tsx create mode 100644 frontend/src/components/ui/task-components/TaskCard.tsx create mode 100644 frontend/src/components/ui/task-components/TaskSheet.tsx create mode 100644 frontend/src/lib/dummyData/dummyTasks.ts create mode 100644 frontend/src/pages/Kanban.tsx diff --git a/frontend/src/components/ui/task-components/AssigneesCombobox.tsx b/frontend/src/components/ui/task-components/AssigneesCombobox.tsx new file mode 100644 index 0000000..4ad7cd6 --- /dev/null +++ b/frontend/src/components/ui/task-components/AssigneesCombobox.tsx @@ -0,0 +1,118 @@ +// Allows users to select team members as assignees for a task + +import { Check, ChevronsUpDown, X } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Badge } from "@/components/ui/badge"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { useState } from "react"; +import { teamMembers } from "@/lib/dummyData/dummyTasks"; + +interface Props { + selectedAssignees: string[]; + onAssigneesChange: (assignees: string[]) => void; +} + +export function AssigneesCombobox({ selectedAssignees, onAssigneesChange }: Props) { + const [open, setOpen] = useState(false); + + const toggleAssignee = (assigneeValue: string) => { + if (selectedAssignees.includes(assigneeValue)) { + onAssigneesChange(selectedAssignees.filter((assignee) => assignee !== assigneeValue)); + } else { + onAssigneesChange([...selectedAssignees, assigneeValue]); + } + }; + + const removeAssignee = (assigneeValue: string) => { + onAssigneesChange(selectedAssignees.filter((assignee) => assignee !== assigneeValue)); + }; + + return ( + // container for both the flex-wrap and assignee popover +
+ + + + + + + + No team members found. + + {teamMembers.map((member) => ( + toggleAssignee(member.value)} + className="cursor-pointer" + > + +
+ + + {member.initials} + + + {member.label} +
+
+ ))} +
+
+
+
+ {/* displays the different assignee badges for selection display */} + {selectedAssignees.length > 0 && ( +
+ {selectedAssignees.map((assigneeValue) => { + const member = teamMembers.find((member) => member.value === assigneeValue); + if (!member) return null; + return ( + + + + {member.initials} + + + {member.label} + + + ); + })} +
+ )} +
+ ); +} diff --git a/frontend/src/components/ui/task-components/ColumnContainer.tsx b/frontend/src/components/ui/task-components/ColumnContainer.tsx new file mode 100644 index 0000000..e8417ae --- /dev/null +++ b/frontend/src/components/ui/task-components/ColumnContainer.tsx @@ -0,0 +1,499 @@ +// Component for an entire Kanban column +// Renders Column Header and Title +// Renders the tasks inside the columns +// Handles task creation and editing inside each column +// Dropdown for moving columns left and right +// Allows for deleting and editing column creation + +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { useMemo, useState, useEffect, useCallback } from "react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Plus, + Ellipsis, + Trash, + Circle, + CalendarIcon, + Pencil, + PaperclipIcon, + MoveLeftIcon, + MoveRightIcon, +} from "lucide-react"; +import { SortableContext } from "@dnd-kit/sortable"; +import type { Column, Id, Task } from "./KanbanBoard"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { TaskCard } from "./TaskCard"; +import { TagsCombobox } from "./TagsCombobox"; +import { AssigneesCombobox } from "./AssigneesCombobox"; +import { Calendar } from "@/components/ui/calendar"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { DescriptionEditor } from "./MarkdownEditor"; +import { useDroppable } from "@dnd-kit/core"; + +const strokeColors = [ + "stroke-[#25292E]", + "stroke-[#1D76DD]", + "stroke-[#1A7F37]", + "stroke-[#9A6700]", + "stroke-[#BC4C00]", + "stroke-[#DA4E57]", + "stroke-[#BF3989]", + "stroke-[#8250DF]", +]; + +interface Props { + column: Column; + deleteColumn: (id: Id) => void; + createTask: ( + columnId: Id, + title: string, + description: string, + startDate?: Date, + endDate?: Date, + priority?: "High" | "Medium" | "Low", + tags?: string[], + assignees?: string[], + attachments?: File[] + ) => void; + deleteTask: (id: Id) => void; + tasks: Task[]; + updateTask: ( + taskId: Id, + title: string, + description: string, + startDate?: Date, + endDate?: Date, + priority?: "High" | "Medium" | "Low", + tags?: string[], + assignees?: string[], + attachments?: File[] + ) => void; + editingTask: Task | null; + setEditingTask: (task: Task | null) => void; + setEditingColumn: (column: Column | null) => void; + moveColumnLeft: (id: Id) => void; + moveColumnRight: (id: Id) => void; + columnIndex: number; + totalColumns: number; +} + +export function ColumnContainer({ + column, + deleteColumn, + createTask, + deleteTask, + tasks, + updateTask, + editingTask, + setEditingTask, + setEditingColumn, + moveColumnLeft, + moveColumnRight, + columnIndex, + totalColumns, +}: Props) { + const [taskTitle, setTaskTitle] = useState(""); + const [taskDescription, setTaskDescription] = useState(""); + const [startOpen, setStartOpen] = useState(false); + const [endOpen, setEndOpen] = useState(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [isOpen, setIsOpen] = useState(false); + const [priority, setPriority] = useState<"High" | "Medium" | "Low">("Medium"); + const [tags, setTags] = useState([]); + const [assignees, setAssignees] = useState([]); + const [attachments, setAttachments] = useState([]); + + const tasksIds = useMemo(() => tasks.map((t) => t.id), [tasks]); + + // Reset all form fields + const resetForm = useCallback(() => { + setTaskTitle(""); + setTaskDescription(""); + setStartDate(undefined); + setEndDate(undefined); + setPriority("Medium"); + setTags([]); + setAssignees([]); + setAttachments([]); + }, []); + + // Populates dialog with data when editing. Adjust when adding features to dialog. + useEffect(() => { + if (!editingTask || editingTask.columnId !== column.id) return; + setTaskTitle(editingTask.content); + setTaskDescription(editingTask.taskDescription); + setStartDate(editingTask.startDate); + setEndDate(editingTask.endDate); + setPriority(editingTask.priority ?? "Medium"); + setTags(editingTask.tags ?? []); + setAssignees(editingTask.assignees ?? []); + setAttachments(editingTask.attachments ?? []); + setIsOpen(true); + }, [editingTask, column.id]); + + // When clicking out of dialog without creation makes sure to clear inputs + useEffect(() => { + if (!isOpen) { + setEditingTask(null); + resetForm(); + } + }, [isOpen, resetForm, setEditingTask]); + + const { setNodeRef } = useDroppable({ + id: column.id, + data: { type: "Column", column }, + }); + + // Handler for creating and updating tasks. + const handleSubmit = useCallback(() => { + if (editingTask) { + updateTask( + editingTask.id, + taskTitle, + taskDescription, + startDate, + endDate, + priority, + tags, + assignees, + attachments + ); + setEditingTask(null); + } else { + createTask( + column.id, + taskTitle, + taskDescription, + startDate, + endDate, + priority, + tags, + assignees, + attachments + ); + } + resetForm(); + setIsOpen(false); + }, [ + editingTask, + updateTask, + createTask, + column.id, + taskTitle, + taskDescription, + startDate, + endDate, + priority, + tags, + assignees, + attachments, + resetForm, + setEditingTask, + ]); + + return ( + + {/* Column Header with drag listeners attached */} +
+
+
+ +
+ {/* Column Title and Description Display */} +
+

+ {column.title} +

+

+ {column.description} +

+
+
+ {/* Dropdown Button for Editing, Deleting, and Moving Columns*/} +
+ + + + + + Column + + {columnIndex > 0 && ( + moveColumnLeft(column.id)} + className="cursor-pointer" + > + Move Left + + )} + {columnIndex < totalColumns - 1 && ( + moveColumnRight(column.id)} + className="cursor-pointer" + > + Move Right + + )} + + setEditingColumn(column)} className="cursor-pointer"> + + Edit + + { + deleteColumn(column.id); + }} + className="cursor-pointer" + > + + Delete + + + +
+
+ {/* Task List through sortable */} +
+
+ + {tasks.map((task) => ( + + ))} + +
+
+ {/* Task Creation Dialog */} + + + + + + + {editingTask ? "Edit task" : "Create new task"} +
+ +
+
+
+
+ + setTaskTitle(e.target.value)} + /> +
+
+ + +
+
+ +
+ { + if (e.target.files) { + setAttachments([...attachments, ...Array.from(e.target.files)]); + } + }} + /> + +
+ {attachments.length > 0 ? ( +
+ {attachments.map((file, index) => ( +

+ + {file.name} +

+ ))} +
+ ) : ( +

No attachments uploaded

+ )} +
+ +
+
+ + + + + + + { + setStartDate(date); + setStartOpen(false); + }} + /> + + +
+
+ + + + + + + { + setEndDate(date); + setEndOpen(false); + }} + /> + + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + + + + + + + +
+
+
+ ); +} diff --git a/frontend/src/components/ui/task-components/Filter.tsx b/frontend/src/components/ui/task-components/Filter.tsx new file mode 100644 index 0000000..3464d84 --- /dev/null +++ b/frontend/src/components/ui/task-components/Filter.tsx @@ -0,0 +1,435 @@ +// Filter Command Bar for the KanbanBoard +// Allows for multiselection filtering + +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; +import { Check, Tag, User, ListFilter, Calendar, Hash, X } from "lucide-react"; +import type { Column, Task } from "./KanbanBoard"; +import { useState, useMemo } from "react"; +import { Button } from "../button"; +import { cn } from "@/lib/utils"; +import { Badge } from "@/components/ui/badge"; + +interface Props { + columns: Column[]; + tasks: Task[]; + filters: { + statuses: string[]; + assignees: string[]; + priorities: string[]; + labels: string[]; + startDates: string[]; + endDates: string[]; + }; + updateFilters: (key: keyof Props["filters"], value: string[]) => void; +} + +export function FilterCommandBar({ columns, tasks, filters, updateFilters }: Props) { + const [isOpen, setIsOpen] = useState(false); + const [inputValue, setInputValue] = useState(""); + + const toggleFilter = (filterKey: keyof Props["filters"], filterValue: string) => { + const currentFilterList = filters[filterKey]; + if (currentFilterList.includes(filterValue)) { + updateFilters( + filterKey, + currentFilterList.filter((value) => value !== filterValue) + ); + } else { + updateFilters(filterKey, [...currentFilterList, filterValue]); + } + }; + + const clearAllFilters = () => { + updateFilters("statuses", []); + updateFilters("assignees", []); + updateFilters("priorities", []); + updateFilters("labels", []); + updateFilters("startDates", []); + updateFilters("endDates", []); + setInputValue(""); + }; + + const uniqueAssignees = Array.from(new Set(tasks.flatMap((task) => task.assignees || []))); + const uniqueLabels = Array.from(new Set(tasks.flatMap((task) => task.tags || []))); + + const uniqueStartDates = Array.from( + new Set( + tasks + .filter((task) => task.startDate) + .map((task) => + new Date(task.startDate!).toLocaleDateString("en-US", { + month: "2-digit", + day: "2-digit", + year: "numeric", + }) + ) + ) + ); + + const uniqueEndDates = Array.from( + new Set( + tasks + .filter((task) => task.endDate) + .map((task) => + new Date(task.endDate!).toLocaleDateString("en-US", { + month: "2-digit", + day: "2-digit", + year: "numeric", + }) + ) + ) + ); + + const getFilterDisplayName = (filterKey: keyof Props["filters"], value: string) => { + switch (filterKey) { + case "statuses": + return columns.find((col) => String(col.id) === value)?.title || value; + case "assignees": + case "labels": + case "startDates": + case "endDates": + return value; + case "priorities": + return value; + default: + return value; + } + }; + + const activeFilterTokens = [ + ...filters.statuses.map((value) => ({ + key: "statuses" as const, + value, + display: `status:${getFilterDisplayName("statuses", value)}`, + })), + ...filters.assignees.map((value) => ({ + key: "assignees" as const, + value, + display: `assignee:${value}`, + })), + ...filters.priorities.map((value) => ({ + key: "priorities" as const, + value, + display: `priority:${value.toLowerCase()}`, + })), + ...filters.labels.map((value) => ({ + key: "labels" as const, + value, + display: `label:${value}`, + })), + ...filters.startDates.map((value) => ({ + key: "startDates" as const, + value, + display: `start:${value}`, + })), + ...filters.endDates.map((value) => ({ + key: "endDates" as const, + value, + display: `due:${value}`, + })), + ]; + + const parsedInput = useMemo(() => { + const trimmedInput = inputValue.trim().toLowerCase(); + + if (!trimmedInput) { + return { type: "all", query: "", showGroupSuggestions: true }; + } + + if (trimmedInput.includes(":")) { + const [prefix, query] = trimmedInput.split(":"); + return { + type: prefix, + query: query || "", + showGroupSuggestions: !query, + }; + } + return { type: "search", query: trimmedInput, showGroupSuggestions: true }; + }, [inputValue]); + + const filterPrefixes = [ + { value: "status:", label: "Status", icon: Tag, description: "Filter by task status" }, + { value: "assignee:", label: "Assignee", icon: User, description: "Filter by assignee" }, + { value: "priority:", label: "Priority", icon: Tag, description: "Filter by priority level" }, + { value: "label:", label: "Label", icon: Hash, description: "Filter by label" }, + { value: "start:", label: "Start Date", icon: Calendar, description: "Filter by start date" }, + { value: "due:", label: "Due Date", icon: Calendar, description: "Filter by due date" }, + ]; + + const filteredGroupSuggestions = useMemo(() => { + const { query } = parsedInput; + if (!query) return filterPrefixes; + return filterPrefixes.filter( + (prefix) => + prefix.value.toLowerCase().includes(query) || prefix.label.toLowerCase().includes(query) + ); + }, [parsedInput]); + + const filteredSuggestions = useMemo(() => { + const { type, query } = parsedInput; + + const suggestions = { + statuses: columns.filter((col) => + type === "all" || type === "status" || type === "search" + ? col.title.toLowerCase().includes(query) + : false + ), + assignees: uniqueAssignees.filter((assignee) => + type === "all" || type === "assignee" || type === "search" + ? assignee.toLowerCase().includes(query) + : false + ), + priorities: (["High", "Medium", "Low"] as const).filter((priority) => + type === "all" || type === "priority" || type === "search" + ? priority.toLowerCase().includes(query) + : false + ), + labels: uniqueLabels.filter((label) => + type === "all" || type === "label" || type === "search" + ? label.toLowerCase().includes(query) + : false + ), + startDates: uniqueStartDates.filter((date) => + type === "all" || type === "start" || type === "search" + ? date.toLowerCase().includes(query) + : false + ), + endDates: uniqueEndDates.filter((date) => + type === "all" || type === "due" || type === "search" + ? date.toLowerCase().includes(query) + : false + ), + }; + return suggestions; + }, [parsedInput, columns, uniqueAssignees, uniqueLabels, uniqueStartDates, uniqueEndDates]); + + const handleSelectSuggestion = (filterKey: keyof Props["filters"], value: string) => { + toggleFilter(filterKey, value); + setInputValue(""); + }; + + const handleSelectPrefix = (prefix: string) => { + setInputValue(prefix); + }; + + return ( +
+
+ + +
+ + {activeFilterTokens.length > 0 ? ( +
+ {activeFilterTokens.map(({ key, value, display }) => ( + e.stopPropagation()} + > + {display} + + + ))} +
+ ) : ( + Filter tasks… + )} +
+
+ + + + + No results found. + {parsedInput.showGroupSuggestions && filteredGroupSuggestions.length > 0 && ( + + {filteredGroupSuggestions.map((prefix) => ( + handleSelectPrefix(prefix.value)} + className="flex items-center gap-2" + > + +
+ {prefix.label} + + {prefix.description} + +
+ + {prefix.value} + +
+ ))} +
+ )} + {filteredSuggestions.statuses.length > 0 && parsedInput.type === "status" && ( + + {filteredSuggestions.statuses.map((column) => ( + handleSelectSuggestion("statuses", String(column.id))} + className="flex items-center gap-2" + > + {filters.statuses.includes(String(column.id)) ? ( + + ) : ( + + )} +
+ {column.title} + + status:{column.title.toLowerCase()} + + + ))} + + )} + {filteredSuggestions.assignees.length > 0 && parsedInput.type === "assignee" && ( + + {filteredSuggestions.assignees.map((assigneeName) => ( + handleSelectSuggestion("assignees", assigneeName)} + className="flex items-center gap-2" + > + {filters.assignees.includes(assigneeName) ? ( + + ) : ( + + )} + + {assigneeName} + + assignee:{assigneeName} + + + ))} + + )} + {filteredSuggestions.priorities.length > 0 && parsedInput.type === "priority" && ( + + {filteredSuggestions.priorities.map((priorityLevel) => ( + handleSelectSuggestion("priorities", priorityLevel)} + className="flex items-center gap-2" + > + {filters.priorities.includes(priorityLevel) ? ( + + ) : ( + + )} + + {priorityLevel} + + priority:{priorityLevel.toLowerCase()} + + + ))} + + )} + {filteredSuggestions.labels.length > 0 && parsedInput.type === "label" && ( + + {filteredSuggestions.labels.map((labelName) => ( + handleSelectSuggestion("labels", labelName)} + className="flex items-center gap-2" + > + {filters.labels.includes(labelName) ? ( + + ) : ( + + )} + + {labelName} + + label:{labelName} + + + ))} + + )} + {filteredSuggestions.startDates.length > 0 && parsedInput.type === "start" && ( + + {filteredSuggestions.startDates.map((startDate) => ( + handleSelectSuggestion("startDates", startDate)} + className="flex items-center gap-2" + > + {filters.startDates.includes(startDate) ? ( + + ) : ( + + )} + + {startDate} + + start:{startDate} + + + ))} + + )} + {filteredSuggestions.endDates.length > 0 && parsedInput.type === "due" && ( + + {filteredSuggestions.endDates.map((dueDate) => ( + handleSelectSuggestion("endDates", dueDate)} + className="flex items-center gap-2" + > + {filters.endDates.includes(dueDate) ? ( + + ) : ( + + )} + + {dueDate} + due:{dueDate} + + ))} + + )} + + + + + {activeFilterTokens.length > 0 && ( + + )} +
+
+ ); +} diff --git a/frontend/src/components/ui/task-components/KanbanBoard.tsx b/frontend/src/components/ui/task-components/KanbanBoard.tsx new file mode 100644 index 0000000..358498b --- /dev/null +++ b/frontend/src/components/ui/task-components/KanbanBoard.tsx @@ -0,0 +1,499 @@ +// Board that contains all of the Tasks and Columns and their implementations +// Contains the dialog for column creation +// Contains functions for Dragging, Creation of Task and Columns, Moving of Columns, Updating and Editing Tasks and Columns + +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { Textarea } from "@/components/ui/textarea"; +import { Plus, Circle } from "lucide-react"; +import { useState, useMemo, useEffect } from "react"; +import { ColumnContainer } from "./ColumnContainer"; +import { + DndContext, + DragOverlay, + useSensors, + useSensor, + type DragEndEvent, + type DragStartEvent, + PointerSensor, + type DragOverEvent, +} from "@dnd-kit/core"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { SortableContext, arrayMove } from "@dnd-kit/sortable"; +import { TaskCard } from "./TaskCard"; +import { dummyColumns, dummyTasks } from "@/lib/dummyData/dummyTasks"; +import { Popover, PopoverContent, PopoverTrigger } from "../shadcn-components/popover"; + +const buttonColors = [ + "bg-gray-600", + "bg-blue-400", + "bg-green-500", + "bg-yellow-500", + "bg-orange-500", + "bg-red-500", + "bg-pink-500", + "bg-purple-500", +]; + +const strokeColors = [ + "stroke-gray-900", + "stroke-blue-900", + "stroke-green-900", + "stroke-yellow-900", + "stroke-orange-900", + "stroke-red-900", + "stroke-pink-900", + "stroke-purple-900", +]; + +export type Id = string | number; + +export type Column = { + id: Id; + color: string; + colorIndex: number; + title: string; + description: string; +}; + +export type Task = { + id: Id; + columnId: Id; + content: string; + taskDescription: string; + startDate?: Date; + endDate?: Date; + priority?: "High" | "Medium" | "Low"; + tags?: string[]; + assignees?: string[]; + attachments?: File[]; +}; + +interface KanbanBoardProps { + columns: Column[]; + setColumns: React.Dispatch>; + tasks: Task[]; + setTasks: React.Dispatch>; +} + +export function KanbanBoard({ columns, setColumns, tasks, setTasks }: KanbanBoardProps) { + const [userTitle, setUserTitle] = useState(""); + const [userDescription, setUserDescription] = useState(""); + const [isSelected, setIsSelected] = useState(0); + const [columnColor, setColumnColor] = useState(buttonColors[0]); + const columnsId = useMemo(() => columns.map((col) => col.id), [columns]); + const [activeTask, setActiveTask] = useState(null); + const [editingTask, setEditingTask] = useState(null); + const [editingColumn, setEditingColumn] = useState(null); + const [columnDialogOpen, setColumnDialogOpen] = useState(false); + + const resetToDefaultData = () => { + setColumns(dummyColumns); + setTasks(dummyTasks); + }; + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 3, + }, + }) + ); + + useEffect(() => { + if (editingColumn) { + setUserTitle(editingColumn.title); + setUserDescription(editingColumn.description); + setColumnColor(editingColumn.color); + setIsSelected(editingColumn.colorIndex); + setColumnDialogOpen(true); + } + }, [editingColumn]); + useEffect(() => { + if (!columnDialogOpen) { + setUserTitle(""); + setUserDescription(""); + setIsSelected(0); + setColumnColor(buttonColors[0]); + setColumnDialogOpen(false); + setEditingColumn(null); + } + }, [columnDialogOpen]); + + return ( + +
+ + +
+ {columns.map((col, index) => ( + task.columnId === col.id)} + moveColumnLeft={moveColumnLeft} + moveColumnRight={moveColumnRight} + columnIndex={index} + totalColumns={columns.length} + /> + ))} +
+
+
+ + {columns.length === 0 ? ( + // Initial button + + ) : ( + // After first column + + )} + + +
+ + + {editingColumn ? "Edit column" : "Create new column"} +
+ +
+
+
+
+ + setUserTitle(e.target.value)} /> +
+
+ + + + + + +
+ {buttonColors.map((color, i) => ( + + ))} +
+
+
+
+ {buttonColors.map((color, i) => ( + + ))} +
+
+
+ +