import React, { useState, useRef, useEffect, useMemo } from 'react'; import { Button, Flex } from '@/shared/antd-imports'; import { PlusOutlined } from '@/shared/antd-imports'; import { useTranslation } from 'react-i18next'; import { nanoid } from '@reduxjs/toolkit'; import { DownOutlined } from '@/shared/antd-imports'; import { useAppSelector } from '@/hooks/useAppSelector'; import { themeWiseColor } from '@/utils/themeWiseColor'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { IGroupBy, fetchEnhancedKanbanGroups, } from '@/features/enhanced-kanban/enhanced-kanban.slice'; import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; import { createStatus, fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; import { ALPHA_CHANNEL } from '@/shared/constants'; import logger from '@/utils/errorLogger'; import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; import { useAuthService } from '@/hooks/useAuth'; import useIsProjectManager from '@/hooks/useIsProjectManager'; const EnhancedKanbanCreateSection: React.FC = () => { const { t } = useTranslation('kanban-board'); const themeMode = useAppSelector(state => state.themeReducer.mode); const { projectId } = useAppSelector(state => state.projectReducer); const groupBy = useAppSelector(state => state.enhancedKanbanReducer.groupBy); const { statusCategories, status: existingStatuses } = useAppSelector( state => state.taskStatusReducer ); const dispatch = useAppDispatch(); 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; } const getUniqueSectionName = (baseName: string): string => { // Check if the base name already exists const existingNames = existingStatuses.map(status => status.name?.toLowerCase()); if (!existingNames.includes(baseName.toLowerCase())) { return baseName; } // If the base name exists, add a number suffix let counter = 1; let newName = `${baseName.trim()} (${counter})`; while (existingNames.includes(newName.toLowerCase())) { counter++; newName = `${baseName.trim()} (${counter})`; } return newName; }; const handleAddSection = async () => { 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(''); } }; 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) { try { const response = await phasesApiService.addPhaseOption(projectId, name); if (response.done && response.body) { dispatch(fetchEnhancedKanbanGroups(projectId)); } } catch (error) { 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 (
{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 => ( ))}
)}
)}
) : ( )}
); }; export default React.memo(EnhancedKanbanCreateSection);