feat(task-management): enhance task grouping and localization support

- Implemented unmapped task grouping for better organization of tasks without valid phases.
- Updated task distribution logic to handle unmapped tasks and added a corresponding group in the response.
- Enhanced localization by adding translations for "noTasksInGroup" in multiple languages.
- Improved task list components to support custom columns and better task management features.
- Refactored task management slice to include loading states for columns and custom columns.
This commit is contained in:
chamikaJ
2025-07-04 20:41:03 +05:30
parent 9e29031703
commit f30fde553d
23 changed files with 1560 additions and 380 deletions

View File

@@ -5,9 +5,11 @@ import { useAppDispatch } from '@/hooks/useAppDispatch';
import { toggleDrawer } from '../../../features/projects/status/StatusSlice';
import { colors } from '@/styles/colors';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '@/hooks/useAppSelector';
const CreateStatusButton = () => {
const { t } = useTranslation('task-list-filters');
const themeMode = useAppSelector(state => state.themeReducer.mode);
const dispatch = useAppDispatch();
@@ -19,9 +21,7 @@ const CreateStatusButton = () => {
onClick={() => dispatch(toggleDrawer())}
icon={
<SettingOutlined
style={{
color: colors.skyBlue,
}}
style={{ color: themeMode === 'dark' ? colors.white : colors.midBlue }}
/>
}
/>

View File

@@ -79,7 +79,7 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
return (
<div
ref={setNodeRef}
className={`inline-flex w-max items-center px-4 py-2 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out border-b border-gray-200 dark:border-gray-700 rounded-t-md ${
className={`inline-flex w-max items-center px-4 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out border-b border-gray-200 dark:border-gray-700 rounded-t-md ${
isOver ? 'ring-2 ring-blue-400 ring-opacity-50' : ''
}`}
style={{
@@ -87,7 +87,10 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
color: headerTextColor,
position: 'sticky',
top: 0,
zIndex: 20 // Higher than sticky columns (zIndex: 1) and column headers (zIndex: 2)
zIndex: 25, // Higher than task rows but lower than column headers (z-30)
height: '36px',
minHeight: '36px',
maxHeight: '36px'
}}
onClick={onToggle}
>
@@ -95,18 +98,22 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
<div style={{ width: '32px' }} className="flex items-center justify-center">
{/* Chevron button */}
<button
className="p-1 rounded-md hover:bg-opacity-20 transition-colors"
style={{ backgroundColor: headerBackgroundColor, color: headerTextColor, borderColor: headerTextColor, border: '1px solid' }}
className="p-1 rounded-md hover:shadow-lg hover:scale-105 transition-all duration-300 ease-out"
style={{ backgroundColor: 'transparent', color: headerTextColor }}
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
>
{isCollapsed ? (
<ChevronRightIcon className="h-4 w-4" style={{ color: headerTextColor }} />
) : (
<ChevronDownIcon className="h-4 w-4" style={{ color: headerTextColor }} />
)}
<div
className="transition-transform duration-300 ease-out"
style={{
transform: isCollapsed ? 'rotate(0deg)' : 'rotate(90deg)',
transformOrigin: 'center'
}}
>
<ChevronRightIcon className="h-3.5 w-3.5" style={{ color: headerTextColor }} />
</div>
</button>
</div>
@@ -124,12 +131,12 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
</div>
{/* Group indicator and name */}
<div className="ml-2 flex items-center gap-3 flex-1">
<div className="ml-1 flex items-center gap-2 flex-1">
{/* Color indicator (removed as full header is colored) */}
{/* Group name and count */}
<div className="flex items-center flex-1">
<span className="text-sm font-medium">
<span className="text-sm font-semibold">
{group.name} ({group.count})
</span>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import React, { memo, useMemo, useCallback, useState } from 'react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { CheckCircleOutlined, HolderOutlined, CloseOutlined, DownOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons';
import { CheckCircleOutlined, HolderOutlined, CloseOutlined, DownOutlined, RightOutlined, DoubleRightOutlined, ArrowsAltOutlined } from '@ant-design/icons';
import { Checkbox, DatePicker } from 'antd';
import { dayjs, taskManagementAntdConfig } from '@/shared/antd-imports';
import { Task } from '@/types/task-management.types';
@@ -35,6 +35,7 @@ interface TaskRowProps {
isSticky?: boolean;
}>;
isSubtask?: boolean;
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
}
interface TaskLabelsCellProps {
@@ -91,7 +92,7 @@ const formatDate = (dateString: string): string => {
}
};
const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumns, isSubtask = false }) => {
const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumns, isSubtask = false, updateTaskCustomColumnValue }) => {
const dispatch = useAppDispatch();
const task = useAppSelector(state => selectTaskById(state, taskId));
const isSelected = useAppSelector(state => selectIsTaskSelected(state, taskId));
@@ -277,7 +278,7 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
case 'taskKey':
return (
<div className="flex items-center" style={baseStyle}>
<span className="text-sm font-medium text-gray-900 dark:text-white whitespace-nowrap">
<span className="text-sm font-medium text-gray-900 dark:text-white whitespace-nowrap badge badge-primary">
{task.task_key || 'N/A'}
</span>
</div>
@@ -294,21 +295,21 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
{!isSubtask && (
<button
onClick={handleToggleExpansion}
className={`flex h-4 w-4 items-center justify-center rounded-sm text-xs mr-2 hover:border hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors ${
className={`flex h-4 w-4 items-center justify-center rounded-sm text-xs mr-2 hover:border hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 hover:scale-110 transition-all duration-300 ease-out ${
task.sub_tasks_count && task.sub_tasks_count > 0
? 'opacity-100'
: 'opacity-0 group-hover:opacity-100'
}`}
>
{task.sub_tasks_count && task.sub_tasks_count > 0 ? (
task.show_sub_tasks ? (
<DownOutlined className="text-gray-600 dark:text-gray-400" />
) : (
<RightOutlined className="text-gray-600 dark:text-gray-400" />
)
) : (
<div
className="transition-transform duration-300 ease-out"
style={{
transform: task.show_sub_tasks ? 'rotate(90deg)' : 'rotate(0deg)',
transformOrigin: 'center'
}}
>
<RightOutlined className="text-gray-600 dark:text-gray-400" />
)}
</div>
</button>
)}
@@ -333,13 +334,14 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
</div>
<button
className="opacity-0 group-hover:opacity-100 transition-opacity duration-200 ml-2 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 border-none bg-transparent cursor-pointer"
className="opacity-0 group-hover:opacity-100 transition-all duration-200 ml-2 mr-2 px-3 py-1.5 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 cursor-pointer rounded-md shadow-sm hover:shadow-md flex items-center gap-1"
onClick={(e) => {
e.stopPropagation();
dispatch(setSelectedTaskId(task.id));
dispatch(setShowTaskDrawer(true));
}}
>
<ArrowsAltOutlined />
{t('openButton')}
</button>
</div>
@@ -642,9 +644,11 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
isDragging ? 'shadow-lg border border-blue-300' : ''
}`}
>
{visibleColumns.map((column, index) =>
renderColumn(column.id, column.width, column.isSticky, index)
)}
{visibleColumns.map((column, index) => (
<React.Fragment key={column.id}>
{renderColumn(column.id, column.width, column.isSticky, index)}
</React.Fragment>
))}
</div>
);
});

View File

@@ -19,6 +19,7 @@ interface TaskRowWithSubtasksProps {
width: string;
isSticky?: boolean;
}>;
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
}
interface AddSubtaskRowProps {
@@ -140,7 +141,8 @@ AddSubtaskRow.displayName = 'AddSubtaskRow';
const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
taskId,
projectId,
visibleColumns
visibleColumns,
updateTaskCustomColumnValue
}) => {
const task = useAppSelector(state => selectTaskById(state, taskId));
const isLoadingSubtasks = useAppSelector(state => selectSubtaskLoading(state, taskId));
@@ -162,6 +164,7 @@ const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
taskId={taskId}
projectId={projectId}
visibleColumns={visibleColumns}
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
/>
{/* Subtasks and add subtask row when expanded */}
@@ -182,6 +185,7 @@ const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
projectId={projectId}
visibleColumns={visibleColumns}
isSubtask={true}
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
/>
</div>
))}

View File

@@ -443,11 +443,11 @@ const FilterDropdown: React.FC<{
${
selectedCount > 0
? isDarkMode
? 'bg-blue-600 text-white border-blue-500'
? 'bg-gray-600 text-white border-gray-500'
: 'bg-blue-50 text-blue-800 border-blue-300 font-semibold'
: `${themeClasses.buttonBg} ${themeClasses.buttonBorder} ${themeClasses.buttonText}`
}
hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2
${isDarkMode ? 'focus:ring-offset-gray-900' : 'focus:ring-offset-white'}
`}
aria-expanded={isOpen}
@@ -456,7 +456,7 @@ const FilterDropdown: React.FC<{
<IconComponent className="w-3.5 h-3.5" />
<span>{section.label}</span>
{selectedCount > 0 && (
<span className="inline-flex items-center justify-center w-4 h-4 text-xs font-bold text-white bg-blue-500 rounded-full">
<span className="inline-flex items-center justify-center w-4 h-4 text-xs font-bold text-white bg-gray-500 rounded-full">
{selectedCount}
</span>
)}
@@ -518,7 +518,7 @@ const FilterDropdown: React.FC<{
${
isSelected
? isDarkMode
? 'bg-blue-600 text-white'
? 'bg-gray-600 text-white'
: 'bg-blue-50 text-blue-800 font-semibold'
: `${themeClasses.optionText} ${themeClasses.optionHover}`
}
@@ -530,7 +530,7 @@ const FilterDropdown: React.FC<{
flex items-center justify-center w-3.5 h-3.5 border rounded
${
isSelected
? 'bg-blue-500 border-blue-500 text-white'
? 'bg-gray-600 border-gray-800 text-white'
: 'border-gray-300 dark:border-gray-600'
}
`}
@@ -730,7 +730,7 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
${
visibleCount > 0
? isDarkMode
? 'bg-blue-600 text-white border-blue-500'
? 'bg-gray-600 text-white border-gray-500'
: 'bg-blue-50 text-blue-800 border-blue-300 font-semibold'
: `${themeClasses.buttonBg} ${themeClasses.buttonBorder} ${themeClasses.buttonText}`
}
@@ -743,7 +743,9 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
<EyeOutlined className="w-3.5 h-3.5" />
<span>Fields</span>
{visibleCount > 0 && (
<span className="inline-flex items-center justify-center w-4 h-4 text-xs font-bold text-white bg-blue-500 rounded-full">
<span
className={`inline-flex items-center justify-center w-4 h-4 text-xs font-bold ${isDarkMode ? 'text-white bg-gray-500' : 'text-gray-800 bg-gray-300'} rounded-full`}
>
{visibleCount}
</span>
)}
@@ -778,8 +780,8 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
${
isSelected
? isDarkMode
? 'bg-blue-600 text-white'
: 'bg-blue-50 text-blue-800 font-semibold'
? 'text-white font-semibold'
: 'text-gray-800 font-semibold'
: `${themeClasses.optionText} ${themeClasses.optionHover}`
}
`}
@@ -790,7 +792,7 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
flex items-center justify-center w-3.5 h-3.5 border rounded
${
isSelected
? 'bg-blue-500 border-blue-500 text-white'
? 'bg-gray-600 border-gray-600 text-white'
: 'border-gray-300 dark:border-gray-600'
}
`}