From c4df6d5244c216caac0e07f10a8a325084babaf0 Mon Sep 17 00:00:00 2001 From: Ankit-69k Date: Fri, 5 Dec 2025 14:06:36 +0530 Subject: [PATCH] refactor add and edit task --- .../HomeComponents/Tasks/AddTaskDialog.tsx | 245 +++ .../HomeComponents/Tasks/EditTaskDialog.tsx | 1116 ++++++++++++ .../components/HomeComponents/Tasks/Tasks.tsx | 1526 ++--------------- 3 files changed, 1536 insertions(+), 1351 deletions(-) create mode 100644 frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx create mode 100644 frontend/src/components/HomeComponents/Tasks/EditTaskDialog.tsx diff --git a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx new file mode 100644 index 00000000..dc7c65ae --- /dev/null +++ b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx @@ -0,0 +1,245 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { DatePicker } from '@/components/ui/date-picker'; +import { format } from 'date-fns'; +import { Key } from '@/components/ui/key-button'; + +interface AddTaskDialogProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + newTask: { + description: string; + priority: string; + project: string; + due: string; + tags: string[]; + }; + setNewTask: React.Dispatch< + React.SetStateAction<{ + description: string; + priority: string; + project: string; + due: string; + tags: string[]; + }> + >; + tagInput: string; + setTagInput: (value: string) => void; + handleAddTag: () => void; + handleRemoveTag: (tag: string) => void; + handleAddTask: ( + email: string, + encryptionSecret: string, + UUID: string, + description: string, + project: string, + priority: string, + due: string, + tags: string[] + ) => void; + email: string; + encryptionSecret: string; + UUID: string; + showKeyBindings?: boolean; +} + +export const AddTaskDialog: React.FC = ({ + isOpen, + onOpenChange, + newTask, + setNewTask, + tagInput, + setTagInput, + handleAddTag, + handleRemoveTag, + handleAddTask, + email, + encryptionSecret, + UUID, + showKeyBindings = true, +}) => { + return ( + + + + + + + + + + Add a{' '} + + new task + + + + Fill in the details below to add a new task. + + +
+
+ + + setNewTask({ + ...newTask, + description: e.target.value, + }) + } + required + className="col-span-3" + /> +
+
+ +
+ +
+
+ +
+ + + setNewTask({ + ...newTask, + project: e.target.value, + }) + } + className="col-span-3" + /> +
+
+ +
+ { + setNewTask({ + ...newTask, + due: date ? format(date, 'yyyy-MM-dd') : '', + }); + }} + placeholder="Select a due date" + /> +
+
+
+ + setTagInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleAddTag()} // Allow adding tag on pressing Enter + required + className="col-span-3" + /> +
+ +
+ {newTask.tags.length > 0 && ( +
+
+
+ {newTask.tags.map((tag, index) => ( + + {tag} + + + ))} +
+
+ )} +
+
+ + + + +
+
+ ); +}; diff --git a/frontend/src/components/HomeComponents/Tasks/EditTaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/EditTaskDialog.tsx new file mode 100644 index 00000000..9b4c5686 --- /dev/null +++ b/frontend/src/components/HomeComponents/Tasks/EditTaskDialog.tsx @@ -0,0 +1,1116 @@ +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { DatePicker } from '@/components/ui/date-picker'; +import { format } from 'date-fns'; +import { Key } from '@/components/ui/key-button'; +import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table'; +import { + CheckIcon, + XIcon, + PencilIcon, + Trash2Icon, + Folder, + Tag, +} from 'lucide-react'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { CopyIcon } from '@radix-ui/react-icons'; +import { Task } from '../../utils/types'; +import { formattedDate } from './tasks-utils'; + +interface EditTaskDialogProps { + task: Task; + isDialogOpen: boolean; + onOpenChange: (open: boolean) => void; + tasks: Task[]; + + // Description editing + isEditing: boolean; + editedDescription: string; + setEditedDescription: (value: string) => void; + handleEditClick: (description: string) => void; + handleSaveClick: (task: Task) => void; + handleCancelClick: () => void; + + // Due date editing + isEditingDueDate: boolean; + editedDueDate: string; + setEditedDueDate: (value: string) => void; + setIsEditingDueDate: (value: boolean) => void; + handleDueDateSaveClick: (task: Task) => void; + + // Start date editing + isEditingStartDate: boolean; + editedStartDate: string; + setEditedStartDate: (value: string) => void; + setIsEditingStartDate: (value: boolean) => void; + handleStartDateSaveClick: (task: Task) => void; + + // End date editing + isEditingEndDate: boolean; + editedEndDate: string; + setEditedEndDate: (value: string) => void; + setIsEditingEndDate: (value: boolean) => void; + handleEndDateSaveClick: (task: Task) => void; + + // Wait date editing + isEditingWaitDate: boolean; + editedWaitDate: string; + setEditedWaitDate: (value: string) => void; + setIsEditingWaitDate: (value: boolean) => void; + handleWaitDateSaveClick: (task: Task) => void; + + // Entry date editing + isEditingEntryDate: boolean; + editedEntryDate: string; + setEditedEntryDate: (value: string) => void; + setIsEditingEntryDate: (value: boolean) => void; + handleEntryDateSaveClick: (task: Task) => void; + + // Depends editing + isEditingDepends: boolean; + editedDepends: string[]; + setIsEditingDepends: (value: boolean) => void; + handleDependsSaveClick: (task: Task) => void; + handleAddDependency: (uuid: string) => void; + handleRemoveDependency: (uuid: string) => void; + dependsDropdownOpen: boolean; + setDependsDropdownOpen: (value: boolean) => void; + dependsSearchTerm: string; + setDependsSearchTerm: (value: string) => void; + setIsDialogOpen: (value: boolean) => void; + setSelectedTask: (task: Task) => void; + + // Priority editing + isEditingPriority: boolean; + editedPriority: string; + setEditedPriority: (value: string) => void; + handleEditPriorityClick: (task: Task) => void; + handleSavePriority: (task: Task) => void; + handleCancelPriority: () => void; + + // Project editing + isEditingProject: boolean; + editedProject: string; + setEditedProject: (value: string) => void; + setIsEditingProject: (value: boolean) => void; + handleProjectSaveClick: (task: Task) => void; + + // Tags editing + isEditingTags: boolean; + editedTags: string[]; + editTagInput: string; + setEditTagInput: (value: string) => void; + handleEditTagsClick: (task: Task) => void; + handleSaveTags: (task: Task) => void; + handleCancelTags: () => void; + handleAddEditTag: () => void; + handleRemoveEditTag: (tag: string) => void; + + // Utility functions + isOverdue: (due?: string) => boolean; + handleCopy: (text: string) => void; + + // Actions + markTaskAsCompleted: ( + email: string, + encryptionSecret: string, + UUID: string, + uuid: string + ) => void; + markTaskAsDeleted: ( + email: string, + encryptionSecret: string, + UUID: string, + uuid: string + ) => void; + + // Props data + email: string; + encryptionSecret: string; + UUID: string; + + // Optional + showKeyBindings?: boolean; +} + +export const EditTaskDialog: React.FC = ({ + task, + isDialogOpen, + onOpenChange, + tasks, + isEditing, + editedDescription, + setEditedDescription, + handleEditClick, + handleSaveClick, + handleCancelClick, + isEditingDueDate, + editedDueDate, + setEditedDueDate, + setIsEditingDueDate, + handleDueDateSaveClick, + isEditingStartDate, + editedStartDate, + setEditedStartDate, + setIsEditingStartDate, + handleStartDateSaveClick, + isEditingEndDate, + editedEndDate, + setEditedEndDate, + setIsEditingEndDate, + handleEndDateSaveClick, + isEditingWaitDate, + editedWaitDate, + setEditedWaitDate, + setIsEditingWaitDate, + handleWaitDateSaveClick, + isEditingEntryDate, + editedEntryDate, + setEditedEntryDate, + setIsEditingEntryDate, + handleEntryDateSaveClick, + isEditingDepends, + editedDepends, + setIsEditingDepends, + handleDependsSaveClick, + handleAddDependency, + handleRemoveDependency, + dependsDropdownOpen, + setDependsDropdownOpen, + dependsSearchTerm, + setDependsSearchTerm, + setIsDialogOpen, + setSelectedTask, + isEditingPriority, + editedPriority, + setEditedPriority, + handleEditPriorityClick, + handleSavePriority, + handleCancelPriority, + isEditingProject, + editedProject, + setEditedProject, + setIsEditingProject, + handleProjectSaveClick, + isEditingTags, + editedTags, + editTagInput, + setEditTagInput, + handleEditTagsClick, + handleSaveTags, + handleCancelTags, + handleAddEditTag, + handleRemoveEditTag, + isOverdue, + handleCopy, + markTaskAsCompleted, + markTaskAsDeleted, + email, + encryptionSecret, + UUID, + showKeyBindings = true, +}) => { + return ( + + +
{/* Trigger is handled by TableRow click */}
+
+ + + + + + Task{' '} + + Details + + + + + {/* Scrollable content */} +
+ + + + + ID: + + {task.id} + {task.status === 'pending' && isOverdue(task.due) && ( + + Overdue + + )} + + + + Description: + + {isEditing ? ( + <> +
+ + setEditedDescription(e.target.value) + } + className="flex-grow mr-2" + /> + + +
+ + ) : ( + <> + {task.description} + + + )} +
+
+ + Due: + + {isEditingDueDate ? ( +
+ { + try { + const dateStr = editedDueDate.includes('T') + ? editedDueDate.split('T')[0] + : editedDueDate; + const parsed = new Date( + dateStr + 'T00:00:00' + ); + return isNaN(parsed.getTime()) + ? undefined + : parsed; + } catch { + return undefined; + } + })() + : undefined + } + onDateChange={(date) => + setEditedDueDate( + date ? format(date, 'yyyy-MM-dd') : '' + ) + } + placeholder="Select due date" + /> + + +
+ ) : ( + <> + {formattedDate(task.due)} + + + )} +
+
+ + Start: + + {isEditingStartDate ? ( +
+ { + try { + // Handle YYYY-MM-DD format + const dateStr = editedStartDate.includes( + 'T' + ) + ? editedStartDate.split('T')[0] + : editedStartDate; + const parsed = new Date( + dateStr + 'T00:00:00' + ); + return isNaN(parsed.getTime()) + ? undefined + : parsed; + } catch { + return undefined; + } + })() + : undefined + } + onDateChange={(date) => + setEditedStartDate( + date ? format(date, 'yyyy-MM-dd') : '' + ) + } + /> + + + + +
+ ) : ( + <> + {formattedDate(task.start)} + + + )} +
+
+ + End: + + {isEditingEndDate ? ( +
+ { + try { + const dateStr = editedEndDate.includes('T') + ? editedEndDate.split('T')[0] + : editedEndDate; + const parsed = new Date( + dateStr + 'T00:00:00' + ); + return isNaN(parsed.getTime()) + ? undefined + : parsed; + } catch { + return undefined; + } + })() + : undefined + } + onDateChange={(date) => + setEditedEndDate( + date ? format(date, 'yyyy-MM-dd') : '' + ) + } + placeholder="Select end date" + /> + + +
+ ) : ( +
+ {formattedDate(task.end)} + +
+ )} +
+
+ + Wait: + + {isEditingWaitDate ? ( +
+ + setEditedWaitDate( + date ? format(date, 'yyyy-MM-dd') : '' + ) + } + /> + + + + +
+ ) : ( + <> + {formattedDate(task.wait)} + + + )} +
+
+ + Depends: + + {!isEditingDepends ? ( +
+ {(task.depends || []).map((depUuid) => { + const depTask = tasks.find((t) => t.uuid === depUuid); + return ( + { + if (depTask) { + setIsDialogOpen(false); + setTimeout(() => { + setSelectedTask(depTask); + setIsDialogOpen(true); + }, 100); + } + }} + > + {depTask?.description || depUuid.substring(0, 8)} + + ); + })} + +
+ ) : ( +
+
+ {editedDepends.map((depUuid) => { + const depTask = tasks.find( + (t) => t.uuid === depUuid + ); + return ( + + + {depTask?.description || + depUuid.substring(0, 8)} + + + + ); + })} +
+
+
+ + {dependsDropdownOpen && ( +
+ + setDependsSearchTerm(e.target.value) + } + className="m-2 w-[calc(100%-1rem)]" + /> + {tasks + .filter( + (t) => + t.uuid !== task.uuid && + t.status === 'pending' && + !editedDepends.includes(t.uuid) && + t.description + .toLowerCase() + .includes( + dependsSearchTerm.toLowerCase() + ) + ) + .map((t) => ( +
{ + handleAddDependency(t.uuid); + setDependsSearchTerm(''); + }} + > + + + {t.description} + +
+ ))} +
+ )} +
+ + +
+
+ )} +
+
+ + Recur: + {task.recur} + + + RType: + {task.rtype} + + + Priority: + + {isEditingPriority ? ( +
+ + + +
+ ) : ( +
+ + {task.priority + ? task.priority === 'H' + ? 'High (H)' + : task.priority === 'M' + ? 'Medium (M)' + : task.priority === 'L' + ? 'Low (L)' + : task.priority + : 'None'} + + +
+ )} +
+
+ + Project: + + {isEditingProject ? ( + <> +
+ setEditedProject(e.target.value)} + className="flex-grow mr-2" + /> + + +
+ + ) : ( + <> + {task.project} + + + )} +
+
+ + Status: + {task.status} + + + Tags: + + {isEditingTags ? ( +
+
+ { + // For allowing only alphanumeric characters + if (e.target.value.length > 1) { + /^[a-zA-Z0-9]*$/.test(e.target.value.trim()) + ? setEditTagInput(e.target.value.trim()) + : ''; + } else { + /^[a-zA-Z]*$/.test(e.target.value.trim()) + ? setEditTagInput(e.target.value.trim()) + : ''; + } + }} + placeholder="Add a tag (press enter to add)" + className="flex-grow mr-2" + onKeyDown={(e) => + e.key === 'Enter' && handleAddEditTag() + } + /> + + +
+
+ {editedTags != null && editedTags.length > 0 && ( +
+
+ {editedTags.map((tag, index) => ( + + {tag} + + + ))} +
+
+ )} +
+
+ ) : ( +
+ {task.tags !== null && task.tags.length >= 1 ? ( + task.tags.map((tag, index) => ( + + + {tag} + + )) + ) : ( + No Tags + )} + +
+ )} +
+
+ + Entry: + + {isEditingEntryDate ? ( +
+ { + try { + // Handle YYYY-MM-DD format + const dateStr = editedEntryDate.includes( + 'T' + ) + ? editedEntryDate.split('T')[0] + : editedEntryDate; + const parsed = new Date( + dateStr + 'T00:00:00' + ); + return isNaN(parsed.getTime()) + ? undefined + : parsed; + } catch { + return undefined; + } + })() + : undefined + } + onDateChange={(date) => + setEditedEntryDate( + date ? format(date, 'yyyy-MM-dd') : '' + ) + } + /> + + + + +
+ ) : ( + <> + {formattedDate(task.entry)} + + + )} +
+
+ + Urgency: + {task.urgency} + + + UUID: + + {task.uuid} + handleCopy('Task UUID')} + > + + + + +
+
+
+
+ + {/* Non-scrollable footer */} + + {task.status == 'pending' ? ( + + + + + + + + + Are you{' '} + + sure? + + + + + + + + + + + + + ) : null} + + {task.status != 'deleted' ? ( + + + + + + + + + Are you{' '} + + sure? + + + + + + + + + + + + + ) : null} + + + + +
+
+ ); +}; diff --git a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx index 7229bfc2..55ec5fd3 100644 --- a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx +++ b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback, useRef } from 'react'; +import React, { useEffect, useState, useCallback, useRef } from 'react'; import { Task } from '../../utils/types'; import { ReportsView } from './ReportsView'; import Fuse from 'fuse.js'; @@ -74,6 +74,8 @@ import { DatePicker } from '@/components/ui/date-picker'; import { format } from 'date-fns'; import { Taskskeleton } from './TaskSkeleton'; import { Key } from '@/components/ui/key-button'; +import { AddTaskDialog } from './AddTaskDialog'; +import { EditTaskDialog } from './EditTaskDialog'; const db = new TasksDatabase(); export let syncTasksWithTwAndDb: () => any; @@ -999,197 +1001,20 @@ export const Tasks = ( icon={} />
- - - - - - - - - - Add a{' '} - - new task - - - - Fill in the details below to add a new task. - - -
-
- - - setNewTask({ - ...newTask, - description: e.target.value, - }) - } - required - className="col-span-3" - /> -
-
- -
- -
-
- -
- - - setNewTask({ - ...newTask, - project: e.target.value, - }) - } - className="col-span-3" - /> -
-
- -
- { - setNewTask({ - ...newTask, - due: date - ? format(date, 'yyyy-MM-dd') - : '', - }); - }} - placeholder="Select a due date" - /> -
-
-
- - setTagInput(e.target.value)} - onKeyDown={(e) => - e.key === 'Enter' && handleAddTag() - } // Allow adding tag on pressing Enter - required - className="col-span-3" - /> -
- -
- {newTask.tags.length > 0 && ( -
-
-
- {newTask.tags.map((tag, index) => ( - - {tag} - - - ))} -
-
- )} -
-
- - - - -
-
+ newTask={newTask} + setNewTask={setNewTask} + tagInput={tagInput} + setTagInput={setTagInput} + handleAddTag={handleAddTag} + handleRemoveTag={handleRemoveTag} + handleAddTask={handleAddTask} + email={props.email} + encryptionSecret={props.encryptionSecret} + UUID={props.UUID} + />
- - - - - - Are you{' '} - - sure? - - - - - - - - - - - - - ) : null} - - {task.status != 'deleted' ? ( - - - - - - - - - Are you{' '} - - sure? - - - - - - - - - - - - - ) : null} - - - - - - + ? 'default' + : 'secondary' + } + > + {task.status === 'pending' && + isOverdue(task.due) + ? 'O' + : task.status === 'completed' + ? 'C' + : task.status === 'deleted' + ? 'D' + : 'P'} + + + + {_selectedTask?.id === task.id && ( + + handleDialogOpenChange(_isDialogOpen, task) + } + tasks={tasks} + isEditing={isEditing} + editedDescription={editedDescription} + setEditedDescription={setEditedDescription} + handleEditClick={handleEditClick} + handleSaveClick={handleSaveClick} + handleCancelClick={handleCancelClick} + isEditingDueDate={isEditingDueDate} + editedDueDate={editedDueDate} + setEditedDueDate={setEditedDueDate} + setIsEditingDueDate={setIsEditingDueDate} + handleDueDateSaveClick={handleDueDateSaveClick} + isEditingStartDate={isEditingStartDate} + editedStartDate={editedStartDate} + setEditedStartDate={setEditedStartDate} + setIsEditingStartDate={setIsEditingStartDate} + handleStartDateSaveClick={ + handleStartDateSaveClick + } + isEditingEndDate={isEditingEndDate} + editedEndDate={editedEndDate} + setEditedEndDate={setEditedEndDate} + setIsEditingEndDate={setIsEditingEndDate} + handleEndDateSaveClick={handleEndDateSaveClick} + isEditingWaitDate={isEditingWaitDate} + editedWaitDate={editedWaitDate} + setEditedWaitDate={setEditedWaitDate} + setIsEditingWaitDate={setIsEditingWaitDate} + handleWaitDateSaveClick={ + handleWaitDateSaveClick + } + isEditingEntryDate={isEditingEntryDate} + editedEntryDate={editedEntryDate} + setEditedEntryDate={setEditedEntryDate} + setIsEditingEntryDate={setIsEditingEntryDate} + handleEntryDateSaveClick={ + handleEntryDateSaveClick + } + isEditingDepends={isEditingDepends} + editedDepends={editedDepends} + setIsEditingDepends={setIsEditingDepends} + handleDependsSaveClick={handleDependsSaveClick} + handleAddDependency={handleAddDependency} + handleRemoveDependency={handleRemoveDependency} + dependsDropdownOpen={dependsDropdownOpen} + setDependsDropdownOpen={setDependsDropdownOpen} + dependsSearchTerm={dependsSearchTerm} + setDependsSearchTerm={setDependsSearchTerm} + setIsDialogOpen={setIsDialogOpen} + setSelectedTask={setSelectedTask} + isEditingPriority={isEditingPriority} + editedPriority={editedPriority} + setEditedPriority={setEditedPriority} + handleEditPriorityClick={ + handleEditPriorityClick + } + handleSavePriority={handleSavePriority} + handleCancelPriority={handleCancelPriority} + isEditingProject={isEditingProject} + editedProject={editedProject} + setEditedProject={setEditedProject} + setIsEditingProject={setIsEditingProject} + handleProjectSaveClick={handleProjectSaveClick} + isEditingTags={isEditingTags} + editedTags={editedTags} + editTagInput={editTagInput} + setEditTagInput={setEditTagInput} + handleEditTagsClick={handleEditTagsClick} + handleSaveTags={handleSaveTags} + handleCancelTags={handleCancelTags} + handleAddEditTag={handleAddEditTag} + handleRemoveEditTag={handleRemoveEditTag} + isOverdue={isOverdue} + handleCopy={handleCopy} + markTaskAsCompleted={markTaskAsCompleted} + markTaskAsDeleted={markTaskAsDeleted} + email={props.email} + encryptionSecret={props.encryptionSecret} + UUID={props.UUID} + /> + )} + )) )}