From f2b1262e3d1656fdaad8c7f7f0e35de200b58d2e Mon Sep 17 00:00:00 2001 From: shancds Date: Fri, 11 Jul 2025 13:45:13 +0530 Subject: [PATCH 1/2] feat(enhanced-kanban): enhance section creation with category selection and input handling - Added state management for section creation, including input focus and category selection. - Implemented dropdown for category selection with visual feedback and improved accessibility. - Refactored section creation logic to handle user input and category assignment more effectively. - Enhanced user experience by managing input focus and handling outside clicks to close dropdowns. --- .../EnhancedKanbanCreateSection.tsx | 241 ++++++++++++++---- 1 file changed, 188 insertions(+), 53 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx index 97f70bd0..1c4d7087 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSection.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useState, useRef, useEffect, useMemo } from 'react'; import { Button, Flex } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { nanoid } from '@reduxjs/toolkit'; +import { DownOutlined } from '@ant-design/icons'; import { useAppSelector } from '@/hooks/useAppSelector'; import { themeWiseColor } from '@/utils/themeWiseColor'; @@ -33,6 +34,52 @@ const EnhancedKanbanCreateSection: React.FC = () => { const isOwnerorAdmin = useAuthService().isOwnerOrAdmin(); const isProjectManager = useIsProjectManager(); + const [isAdding, setIsAdding] = useState(false); + const [sectionName, setSectionName] = useState(''); + const [selectedCategoryId, setSelectedCategoryId] = useState(''); + const [showCategoryDropdown, setShowCategoryDropdown] = useState(false); + const inputRef = useRef(null); + const dropdownRef = useRef(null); + const categoryDropdownRef = useRef(null); + + // Find selected category object + const selectedCategory = statusCategories?.find(cat => cat.id === selectedCategoryId); + + // Compute header background color + const headerBackgroundColor = React.useMemo(() => { + if (!selectedCategory) return themeWiseColor('#f5f5f5', '#1e1e1e', themeMode); + return selectedCategory.color_code || (themeMode === 'dark' ? '#1e1e1e' : '#f5f5f5'); + }, [themeMode, selectedCategory]); + + // Focus input when adding + useEffect(() => { + if (isAdding && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [isAdding]); + + // Close on outside click (for both input and category dropdown) + useEffect(() => { + if (!isAdding && !showCategoryDropdown) return; + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + inputRef.current && + !inputRef.current.contains(event.target as Node) && + (!categoryDropdownRef.current || !categoryDropdownRef.current.contains(event.target as Node)) + ) { + setIsAdding(false); + setSectionName(''); + setSelectedCategoryId(''); + setShowCategoryDropdown(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isAdding, showCategoryDropdown]); + // Don't show for priority grouping or if user doesn't have permissions if (groupBy === IGroupBy.PRIORITY || (!isOwnerorAdmin && !isProjectManager)) { return null; @@ -59,49 +106,38 @@ const EnhancedKanbanCreateSection: React.FC = () => { }; const handleAddSection = async () => { - const sectionId = nanoid(); - const baseNameSection = 'Untitled section'; - const sectionName = getUniqueSectionName(baseNameSection); + setIsAdding(true); + setSectionName(''); + // Default to first category if available + if (statusCategories && statusCategories.length > 0 && typeof statusCategories[0].id === 'string') { + setSelectedCategoryId(statusCategories[0].id); + } else { + setSelectedCategoryId(''); + } + }; - if (groupBy === IGroupBy.STATUS && projectId) { - // Find the "To do" category - const todoCategory = statusCategories.find( - category => - category.name?.toLowerCase() === 'to do' || category.name?.toLowerCase() === 'todo' - ); - - if (todoCategory && todoCategory.id) { - // Create a new status - const body = { - name: sectionName, - project_id: projectId, - category_id: todoCategory.id, - }; - - try { - // Create the status - const response = await dispatch( - createStatus({ body, currentProjectId: projectId }) - ).unwrap(); - - if (response.done && response.body) { - // Refresh the board to show the new section - dispatch(fetchEnhancedKanbanGroups(projectId)); - // Refresh statuses - dispatch(fetchStatuses(projectId)); - } - } catch (error) { - logger.error('Failed to create status:', error); + const handleCreateSection = async () => { + if (!sectionName.trim() || !projectId) return; + const name = getUniqueSectionName(sectionName.trim()); + if (groupBy === IGroupBy.STATUS && selectedCategoryId) { + const body = { + name, + project_id: projectId, + category_id: selectedCategoryId, + }; + try { + const response = await dispatch( + createStatus({ body, currentProjectId: projectId }) + ).unwrap(); + if (response.done && response.body) { + dispatch(fetchEnhancedKanbanGroups(projectId)); + dispatch(fetchStatuses(projectId)); } + } catch (error) { + logger.error('Failed to create status:', error); } } - - if (groupBy === IGroupBy.PHASE && projectId) { - const body = { - name: sectionName, - project_id: projectId, - }; - + if (groupBy === IGroupBy.PHASE) { try { const response = await phasesApiService.addPhaseOption(projectId); if (response.done && response.body) { @@ -111,6 +147,19 @@ const EnhancedKanbanCreateSection: React.FC = () => { logger.error('Failed to create phase:', error); } } + setIsAdding(false); + setSectionName(''); + setSelectedCategoryId(''); + }; + + const handleInputKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleCreateSection(); + } else if (e.key === 'Escape') { + setIsAdding(false); + setSectionName(''); + setSelectedCategoryId(''); + } }; return ( @@ -136,19 +185,105 @@ const EnhancedKanbanCreateSection: React.FC = () => { ), }} > - + {isAdding ? ( +
+ {/* Header-like area */} +
+ {/* Borderless input */} + setSectionName(e.target.value)} + onKeyDown={handleInputKeyDown} + className={`bg-transparent border-none outline-none text-sm font-semibold capitalize min-w-[120px] flex-1 ${themeMode === 'dark' ? 'text-gray-800 placeholder-gray-800' : 'text-gray-800 placeholder-gray-600'}`} + placeholder={t('untitledSection')} + style={{ marginBottom: 0 }} + /> + {/* Category selector dropdown */} + {groupBy === IGroupBy.STATUS && statusCategories && statusCategories.length > 0 && ( +
+ + {showCategoryDropdown && ( +
+
+ {statusCategories.filter(cat => typeof cat.id === 'string').map(cat => ( + + ))} +
+
+ )} +
+ )} +
+
+ + +
+
+ ) : ( + + )} ); From ea37b550788d875e823de348ac40315dc33896b9 Mon Sep 17 00:00:00 2001 From: shancds Date: Fri, 11 Jul 2025 14:06:11 +0530 Subject: [PATCH 2/2] fix(kanban-group): update section name handling in status update - Removed unused section name generation and replaced it with trimmed name input for better consistency. - Ensured that the updated name is set correctly after a successful status update, improving data integrity. --- .../EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx index 387efad1..705c467a 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx @@ -108,9 +108,9 @@ const KanbanGroup: React.FC = memo(({ const updateStatus = async (category = group.category_id ?? null) => { if (!category || !projectId || !group.id) return; - const sectionName = getUniqueSectionName(name); + // const sectionName = getUniqueSectionName(name); const body: ITaskStatusUpdateModel = { - name: sectionName, + name: name.trim(), project_id: projectId, category_id: category, }; @@ -118,7 +118,7 @@ const KanbanGroup: React.FC = memo(({ if (res.done) { dispatch(fetchEnhancedKanbanGroups(projectId)); dispatch(fetchStatuses(projectId)); - setName(sectionName); + setName(name.trim()); } else { setName(editName); logger.error('Error updating status', res.message);