From d255e2626123cd5a657369d399a605d43b5b803e Mon Sep 17 00:00:00 2001 From: Shiva Gupta Date: Tue, 30 Dec 2025 22:46:40 +0530 Subject: [PATCH] feat(tags): implement tags dropdown in Add/Edit Task dialogs - Replace text input with Select dropdown for tag selection - Add "Create new tag" option in dropdown - Display selected tags as removable badges - Add uniqueTags, isCreatingNewTag, setIsCreatingNewTag props - Add data-testid to Priority, Tags, Recur SelectTriggers Tests: - Update Select component mocks for testing - Rewrite tag tests to use dropdown selection - Add tests for dropdown selection and tag removal Fixes: #210 --- .../HomeComponents/Tasks/AddTaskDialog.tsx | 102 +++++++--- .../HomeComponents/Tasks/TaskDialog.tsx | 185 +++++++++++------- .../components/HomeComponents/Tasks/Tasks.tsx | 14 +- .../Tasks/__tests__/AddTaskDialog.test.tsx | 100 +++++++--- .../Tasks/__tests__/TaskDialog.test.tsx | 26 +-- .../Tasks/__tests__/Tasks.test.tsx | 77 +++----- .../Tasks/__tests__/tasks-utils.test.ts | 33 ++++ .../HomeComponents/Tasks/tasks-utils.ts | 11 ++ frontend/src/components/utils/types.ts | 8 +- 9 files changed, 362 insertions(+), 194 deletions(-) diff --git a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx index 1b60ba10..9d299938 100644 --- a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx +++ b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx @@ -24,6 +24,7 @@ import { } from '@/components/ui/select'; import { AddTaskDialogProps } from '@/components/utils/types'; import { format } from 'date-fns'; +import { processTagInput } from './tasks-utils'; export const AddTaskdialog = ({ isOpen, @@ -36,6 +37,9 @@ export const AddTaskdialog = ({ isCreatingNewProject, setIsCreatingNewProject, uniqueProjects = [], + isCreatingNewTag, + setIsCreatingNewTag, + uniqueTags = [], allTasks = [], }: AddTaskDialogProps) => { const [annotationInput, setAnnotationInput] = useState(''); @@ -102,13 +106,6 @@ export const AddTaskdialog = ({ }); }; - const handleAddTag = () => { - if (tagInput && !newTask.tags.includes(tagInput, 0)) { - setNewTask({ ...newTask, tags: [...newTask.tags, tagInput] }); - setTagInput(''); - } - }; - const handleRemoveTag = (tagToRemove: string) => { setNewTask({ ...newTask, @@ -194,6 +191,7 @@ export const AddTaskdialog = ({
setTagInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleAddTag()} - required - className="col-span-6" - /> +
+ + {isCreatingNewTag && ( + setTagInput(e.target.value)} + onKeyDown={(e) => { + const newTag = processTagInput( + e.key, + tagInput, + newTask.tags + ); + if (newTag) { + setNewTask({ + ...newTask, + tags: [...newTask.tags, newTag], + }); + setTagInput(''); + setIsCreatingNewTag(false); + } + }} + /> + )}
-
{newTask.tags.length > 0 && (
diff --git a/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx index d2e5770e..d4b7fbf9 100644 --- a/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx +++ b/frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx @@ -33,7 +33,7 @@ import { XIcon, } from 'lucide-react'; import CopyToClipboard from 'react-copy-to-clipboard'; -import { formattedDate, handleCopy } from './tasks-utils'; +import { formattedDate, handleCopy, processTagInput } from './tasks-utils'; export const TaskDialog = ({ index, @@ -47,9 +47,12 @@ export const TaskDialog = ({ editState, onUpdateState, allTasks, + uniqueProjects, isCreatingNewProject, setIsCreatingNewProject, - uniqueProjects, + uniqueTags, + isCreatingNewTag, + setIsCreatingNewTag, onSaveDescription, onSaveTags, onSavePriority, @@ -781,6 +784,7 @@ export const TaskDialog = ({ {editState.isEditingPriority ? (
- + Tags: {editState.isEditingTags ? ( -
-
- { - // For allowing only alphanumeric characters - if (e.target.value.length > 1) { - /^[a-zA-Z0-9]*$/.test(e.target.value.trim()) - ? onUpdateState({ - editTagInput: e.target.value.trim(), - }) - : ''; - } else { - /^[a-zA-Z]*$/.test(e.target.value.trim()) - ? onUpdateState({ - editTagInput: e.target.value.trim(), - }) - : ''; - } - }} - placeholder="Add a tag (press enter to add)" - className="flex-grow mr-2" - onKeyDown={(e) => { - if ( - e.key === 'Enter' && - editState.editTagInput.trim() - ) { - onUpdateState({ - editedTags: [ - ...editState.editedTags, - editState.editTagInput.trim(), - ], - editTagInput: '', - }); +
+
+
-
- {editState.editedTags != null && - editState.editedTags.length > 0 && ( -
-
- {editState.editedTags.map((tag, index) => ( - - {tag} - - - ))} -
-
- )} -
+ {isCreatingNewTag && ( + + onUpdateState({ editTagInput: e.target.value }) + } + onKeyDown={(e) => { + const newTag = processTagInput( + e.key, + editState.editTagInput, + editState.editedTags + ); + if (newTag) { + onUpdateState({ + editedTags: [...editState.editedTags, newTag], + editTagInput: '', + }); + setIsCreatingNewTag(false); + } + }} + /> + )} + {editState.editedTags.length > 0 && ( +
+ {editState.editedTags.map((tag, index) => ( + + {tag} + + + ))} +
+ )}
) : (
@@ -1209,6 +1251,7 @@ export const TaskDialog = ({ {editState.isEditingRecur ? (
onValueChange?.(e.target.value)} > @@ -42,9 +46,9 @@ jest.mock('@/components/ui/select', () => { ); }, - SelectTrigger: ({ children, 'data-testid': dataTestId, ...props }: any) => ( -
{children}
- ), + SelectTrigger: ({ children, ...props }: any) => { + return
{children}
; + }, SelectValue: ({ placeholder }: any) => (