diff --git a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx index 77b57c56..ec6cdc66 100644 --- a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx +++ b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx @@ -514,6 +514,7 @@ export const AddTaskdialog = ({
(inputRefs.current.tags = element)} availableItems={uniqueTags} selectedItems={newTask.tags} onItemsChange={(tags: string[]) => diff --git a/frontend/src/components/HomeComponents/Tasks/MultiSelect.tsx b/frontend/src/components/HomeComponents/Tasks/MultiSelect.tsx index 34ed629a..1fd50815 100644 --- a/frontend/src/components/HomeComponents/Tasks/MultiSelect.tsx +++ b/frontend/src/components/HomeComponents/Tasks/MultiSelect.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -6,194 +6,204 @@ import { MultiSelectProps } from '@/components/utils/types'; import { ChevronDown, Plus, Check, X } from 'lucide-react'; import { getFilteredItems, shouldShowCreateOption } from './multi-select-utils'; -export const MultiSelect = ({ - availableItems, - selectedItems, - onItemsChange, - placeholder = 'Select or create items', - disabled = false, - className = '', - showActions = false, - onSave, - onCancel, -}: MultiSelectProps) => { - const [isOpen, setIsOpen] = useState(false); - const [searchTerm, setSearchTerm] = useState(''); - const dropdownRef = useRef(null); - const inputRef = useRef(null); +export const MultiSelect = React.forwardRef< + HTMLButtonElement, + MultiSelectProps +>( + ( + { + availableItems, + selectedItems, + onItemsChange, + placeholder = 'Select or create items', + disabled = false, + className = '', + showActions = false, + onSave, + onCancel, + }, + ref + ) => { + const [isOpen, setIsOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const dropdownRef = useRef(null); + const inputRef = useRef(null); - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - dropdownRef.current && - !dropdownRef.current.contains(event.target as Node) - ) { - setIsOpen(false); - setSearchTerm(''); - } - }; + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + setSearchTerm(''); + } + }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); + document.addEventListener('mousedown', handleClickOutside); + return () => + document.removeEventListener('mousedown', handleClickOutside); + }, []); - const filteredItems = getFilteredItems( - availableItems, - selectedItems, - searchTerm - ); + const filteredItems = getFilteredItems( + availableItems, + selectedItems, + searchTerm + ); - const handleItemSelect = (item: string) => { - if (!selectedItems.includes(item)) { - onItemsChange([...selectedItems, item]); - } - setSearchTerm(''); - }; + const handleItemSelect = (item: string) => { + if (!selectedItems.includes(item)) { + onItemsChange([...selectedItems, item]); + } + setSearchTerm(''); + }; - const handleItemRemove = (itemToRemove: string) => { - onItemsChange(selectedItems.filter((item) => item !== itemToRemove)); - }; + const handleItemRemove = (itemToRemove: string) => { + onItemsChange(selectedItems.filter((item) => item !== itemToRemove)); + }; - const handleNewItemCreate = () => { - const trimmedTerm = searchTerm.trim(); - if ( - trimmedTerm && - !selectedItems.includes(trimmedTerm) && - !availableItems.includes(trimmedTerm) - ) { - onItemsChange([...selectedItems, trimmedTerm]); - setSearchTerm(''); - } - }; + const handleNewItemCreate = () => { + const trimmedTerm = searchTerm.trim(); + if ( + trimmedTerm && + !selectedItems.includes(trimmedTerm) && + !availableItems.includes(trimmedTerm) + ) { + onItemsChange([...selectedItems, trimmedTerm]); + setSearchTerm(''); + } + }; - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault(); - if (searchTerm.trim()) { - if (filteredItems.length > 0) { - handleItemSelect(filteredItems[0]); - } else { - handleNewItemCreate(); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + if (searchTerm.trim()) { + if (filteredItems.length > 0) { + handleItemSelect(filteredItems[0]); + } else { + handleNewItemCreate(); + } } + } else if (e.key === 'Escape') { + setIsOpen(false); + setSearchTerm(''); } - } else if (e.key === 'Escape') { - setIsOpen(false); - setSearchTerm(''); - } - }; - - const showCreate = shouldShowCreateOption( - searchTerm, - availableItems, - selectedItems - ); + }; - return ( -
- + const showCreate = shouldShowCreateOption( + searchTerm, + availableItems, + selectedItems + ); - {selectedItems.length > 0 && ( -
- {selectedItems.map((item) => ( - - {item} - - - ))} - {showActions && onSave && onCancel && ( -
- - -
- )} -
- )} + return ( +
+ - {isOpen && ( -
-
- setSearchTerm(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="Search or create..." - className="h-8" - autoFocus - /> -
- -
- {filteredItems.map((item) => ( -
handleItemSelect(item)} - > - {item} -
+ {selectedItems.length > 0 && ( +
+ {selectedItems.map((item) => ( + + {item} + + ))} - - {showCreate && ( -
- - - Create "{searchTerm.trim()}" - + {showActions && onSave && onCancel && ( +
+ +
)} +
+ )} - {filteredItems.length === 0 && !showCreate && ( -
- No items found -
- )} + {isOpen && ( +
+
+ setSearchTerm(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Search or create..." + className="h-8" + autoFocus + /> +
+ +
+ {filteredItems.map((item) => ( +
handleItemSelect(item)} + > + {item} +
+ ))} + + {showCreate && ( +
+ + + Create "{searchTerm.trim()}" + +
+ )} + + {filteredItems.length === 0 && !showCreate && ( +
+ No items found +
+ )} +
-
- )} -
- ); -}; + )} +
+ ); + } +);