feat(task-management): centralize Ant Design imports and enhance task components

- Introduced a new `antd-imports.ts` file to centralize Ant Design component imports, optimizing tree-shaking and improving maintainability.
- Updated various task management components (e.g., TaskRow, TaskGroup, VirtualizedTaskList) to utilize centralized imports, ensuring consistent styling and configuration.
- Enhanced task filtering and display features by adding new fields (e.g., start date, due date, estimation) to task components for improved usability.
- Refactored date handling in task components to utilize memoization for performance optimization.
- Improved overall styling and responsiveness of task management components, particularly in dark mode.
This commit is contained in:
chamikaJ
2025-06-25 12:31:39 +05:30
parent cf5919a3a0
commit 680e84d19b
12 changed files with 850 additions and 229 deletions

View File

@@ -22,7 +22,7 @@
"@tanstack/react-table": "^8.20.6",
"@tanstack/react-virtual": "^3.11.2",
"@tinymce/tinymce-react": "^5.1.1",
"antd": "^5.24.9",
"antd": "^5.26.2",
"axios": "^1.9.0",
"chart.js": "^4.4.7",
"chartjs-plugin-datalabels": "^2.2.0",
@@ -1924,9 +1924,9 @@
}
},
"node_modules/@rc-component/trigger": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz",
"integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==",
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.7.tgz",
"integrity": "sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2",
@@ -2864,9 +2864,9 @@
}
},
"node_modules/antd": {
"version": "5.24.9",
"resolved": "https://registry.npmjs.org/antd/-/antd-5.24.9.tgz",
"integrity": "sha512-liB+Y/JwD5/KSKbK1Z1EVAbWcoWYvWJ1s97AbbT+mOdigpJQuWwH7kG8IXNEljI7onvj0DdD43TXhSRLUu9AMA==",
"version": "5.26.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-5.26.2.tgz",
"integrity": "sha512-C8dBgwSzXfUS5ousUN+mfcaGFhEOd9wuyhvmw0lQnU9gukpRoFe1B0UKzvr6Z50QgapIl+s03nYlQJUghKqVjQ==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^7.2.1",
@@ -2880,7 +2880,7 @@
"@rc-component/mutate-observer": "^1.1.0",
"@rc-component/qrcode": "~1.0.0",
"@rc-component/tour": "~1.15.1",
"@rc-component/trigger": "^2.2.6",
"@rc-component/trigger": "^2.2.7",
"classnames": "^2.5.1",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.11",
@@ -2888,7 +2888,7 @@
"rc-checkbox": "~3.5.0",
"rc-collapse": "~3.9.0",
"rc-dialog": "~9.6.0",
"rc-drawer": "~7.2.0",
"rc-drawer": "~7.3.0",
"rc-dropdown": "~4.2.1",
"rc-field-form": "~2.7.0",
"rc-image": "~7.12.0",
@@ -2908,13 +2908,13 @@
"rc-slider": "~11.1.8",
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
"rc-table": "~7.50.5",
"rc-table": "~7.51.1",
"rc-tabs": "~15.6.1",
"rc-textarea": "~1.10.0",
"rc-tooltip": "~6.4.0",
"rc-tree": "~5.13.1",
"rc-tree-select": "~5.27.0",
"rc-upload": "~4.9.0",
"rc-upload": "~4.9.2",
"rc-util": "^5.44.4",
"scroll-into-view-if-needed": "^3.1.0",
"throttle-debounce": "^5.0.2"
@@ -5529,9 +5529,9 @@
}
},
"node_modules/rc-drawer": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz",
"integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==",
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz",
"integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
@@ -5942,9 +5942,9 @@
}
},
"node_modules/rc-table": {
"version": "7.50.5",
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.5.tgz",
"integrity": "sha512-FDZu8aolhSYd3v9KOc3lZOVAU77wmRRu44R0Wfb8Oj1dXRUsloFaXMSl6f7yuWZUxArJTli7k8TEOX2mvhDl4A==",
"version": "7.51.1",
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz",
"integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.10.1",
@@ -6055,9 +6055,9 @@
}
},
"node_modules/rc-upload": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.0.tgz",
"integrity": "sha512-pAzlPnyiFn1GCtEybEG2m9nXNzQyWXqWV2xFYCmDxjN9HzyjS5Pz2F+pbNdYw8mMJsixLEKLG0wVy9vOGxJMJA==",
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.2.tgz",
"integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",

View File

@@ -26,7 +26,7 @@
"@tanstack/react-table": "^8.20.6",
"@tanstack/react-virtual": "^3.11.2",
"@tinymce/tinymce-react": "^5.1.1",
"antd": "^5.24.9",
"antd": "^5.26.2",
"axios": "^1.9.0",
"chart.js": "^4.4.7",
"chartjs-plugin-datalabels": "^2.2.0",

View File

@@ -0,0 +1,237 @@
/**
* Centralized Ant Design imports for Task Management components
*
* This file provides:
* - Tree-shaking optimization by importing only used components
* - Type safety with proper TypeScript types
* - Performance optimization through selective imports
* - Consistent component versions across task management
* - Easy maintenance and updates
*/
// Core Components
export {
Button,
Input,
Select,
Typography,
Card,
Spin,
Empty,
Space,
Tooltip,
Badge,
Popconfirm,
message,
Checkbox,
Dropdown,
Menu
} from 'antd/es';
// Date & Time Components
export {
DatePicker,
TimePicker
} from 'antd/es';
// Form Components (if needed for task management)
export {
Form,
InputNumber
} from 'antd/es';
// Layout Components
export {
Row,
Col,
Divider,
Flex
} from 'antd/es';
// Icon Components (commonly used in task management)
export {
EditOutlined,
DeleteOutlined,
PlusOutlined,
MoreOutlined,
CheckOutlined,
CloseOutlined,
CalendarOutlined,
ClockCircleOutlined,
UserOutlined,
TeamOutlined,
TagOutlined,
FlagOutlined,
BarsOutlined,
TableOutlined,
AppstoreOutlined,
FilterOutlined,
SortAscendingOutlined,
SortDescendingOutlined,
SearchOutlined,
ReloadOutlined,
SettingOutlined,
EyeOutlined,
EyeInvisibleOutlined,
CopyOutlined,
ExportOutlined,
ImportOutlined,
DownOutlined,
RightOutlined,
LeftOutlined,
UpOutlined,
DragOutlined,
HolderOutlined,
MessageOutlined,
PaperClipOutlined,
GroupOutlined,
InboxOutlined,
TagsOutlined,
UsergroupAddOutlined,
UserAddOutlined,
RetweetOutlined
} from '@ant-design/icons';
// TypeScript Types
export type {
ButtonProps,
InputProps,
InputRef,
SelectProps,
TypographyProps,
CardProps,
SpinProps,
EmptyProps,
SpaceProps,
TooltipProps,
BadgeProps,
PopconfirmProps,
CheckboxProps,
CheckboxChangeEvent,
DropdownProps,
MenuProps,
DatePickerProps,
TimePickerProps,
FormProps,
FormInstance,
InputNumberProps,
RowProps,
ColProps,
DividerProps,
FlexProps
} from 'antd/es';
// Dayjs (used with DatePicker)
export { default as dayjs } from 'dayjs';
export type { Dayjs } from 'dayjs';
// Re-export commonly used Ant Design utilities
export {
ConfigProvider,
theme
} from 'antd';
// Custom hooks for task management (if any Ant Design specific hooks are needed)
export const useAntdBreakpoint = () => {
// You can add custom breakpoint logic here if needed
return {
xs: window.innerWidth < 576,
sm: window.innerWidth >= 576 && window.innerWidth < 768,
md: window.innerWidth >= 768 && window.innerWidth < 992,
lg: window.innerWidth >= 992 && window.innerWidth < 1200,
xl: window.innerWidth >= 1200 && window.innerWidth < 1600,
xxl: window.innerWidth >= 1600,
};
};
// Import message separately to avoid circular dependency
import { message as antdMessage } from 'antd';
// Performance optimized message utility
export const taskMessage = {
success: (content: string) => antdMessage.success(content),
error: (content: string) => antdMessage.error(content),
warning: (content: string) => antdMessage.warning(content),
info: (content: string) => antdMessage.info(content),
loading: (content: string) => antdMessage.loading(content),
};
// Commonly used Ant Design configurations for task management
export const taskManagementAntdConfig = {
// DatePicker default props for consistency
datePickerDefaults: {
format: 'MMM DD, YYYY',
placeholder: 'Set Date',
suffixIcon: null,
size: 'small' as const,
},
// Button default props for task actions
taskButtonDefaults: {
size: 'small' as const,
type: 'text' as const,
},
// Input default props for task editing
taskInputDefaults: {
size: 'small' as const,
variant: 'borderless' as const,
},
// Select default props for dropdowns
taskSelectDefaults: {
size: 'small' as const,
variant: 'borderless' as const,
showSearch: true,
optionFilterProp: 'label' as const,
},
// Tooltip default props
tooltipDefaults: {
placement: 'top' as const,
mouseEnterDelay: 0.5,
mouseLeaveDelay: 0.1,
},
// Dropdown default props
dropdownDefaults: {
trigger: ['click'] as const,
placement: 'bottomLeft' as const,
},
};
// Theme tokens specifically for task management
export const taskManagementTheme = {
light: {
colorBgContainer: '#ffffff',
colorBorder: '#e5e7eb',
colorText: '#374151',
colorTextSecondary: '#6b7280',
colorPrimary: '#3b82f6',
colorSuccess: '#10b981',
colorWarning: '#f59e0b',
colorError: '#ef4444',
colorBgHover: '#f9fafb',
colorBgSelected: '#eff6ff',
},
dark: {
colorBgContainer: '#1f2937',
colorBorder: '#374151',
colorText: '#f9fafb',
colorTextSecondary: '#d1d5db',
colorPrimary: '#60a5fa',
colorSuccess: '#34d399',
colorWarning: '#fbbf24',
colorError: '#f87171',
colorBgHover: '#374151',
colorBgSelected: '#1e40af',
},
};
// Export default configuration object
export default {
config: taskManagementAntdConfig,
theme: taskManagementTheme,
message: taskMessage,
useBreakpoint: useAntdBreakpoint,
};

View File

@@ -1,11 +1,13 @@
import React, { useEffect, useRef, useState } from 'react';
import { Button, Typography, Dropdown, Menu, Popconfirm, message, Tooltip, Badge, CheckboxChangeEvent, InputRef } from 'antd';
import {
import {
Button,
Typography,
Dropdown,
Menu,
Popconfirm,
Tooltip,
Badge,
DeleteOutlined,
EditOutlined,
TagOutlined,
UserOutlined,
CheckOutlined,
CloseOutlined,
MoreOutlined,
RetweetOutlined,
@@ -13,11 +15,11 @@ import {
InboxOutlined,
TagsOutlined,
UsergroupAddOutlined,
} from '@ant-design/icons';
type CheckboxChangeEvent,
type InputRef
} from './antd-imports';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { IGroupBy, fetchTaskGroups } from '@/features/tasks/tasks.slice';
import { AppDispatch, RootState } from '@/app/store';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';

View File

@@ -16,7 +16,7 @@ import {
CheckOutlined,
SettingOutlined,
MoreOutlined,
} from '@ant-design/icons';
} from './antd-imports';
import { RootState } from '@/app/store';
import { AppDispatch } from '@/app/store';
import { useAppSelector } from '@/hooks/useAppSelector';
@@ -27,7 +27,13 @@ import { SocketEvents } from '@/shared/socket-events';
import { colors } from '@/styles/colors';
import SingleAvatar from '@components/common/single-avatar/single-avatar';
import { useFilterDataLoader } from '@/hooks/useFilterDataLoader';
import { Dropdown, Checkbox, Button, Space } from 'antd';
import {
Dropdown,
Checkbox,
Button,
Space,
taskManagementAntdConfig
} from './antd-imports';
import { toggleField, TaskListField } from '@/features/task-management/taskListFields.slice';
// Import Redux actions
@@ -40,50 +46,40 @@ import { ITaskPriority } from '@/types/tasks/taskPriority.types';
import { ITaskListColumn } from '@/types/tasks/taskList.types';
import { IGroupBy } from '@/features/tasks/tasks.slice';
// Memoized selectors to prevent unnecessary re-renders
const selectPriorities = createSelector(
[(state: any) => state.priorityReducer.priorities],
(priorities) => priorities || []
);
const selectTaskPriorities = createSelector(
[(state: any) => state.taskReducer.priorities],
(priorities) => priorities || []
);
const selectBoardPriorities = createSelector(
[(state: any) => state.boardReducer.priorities],
(priorities) => priorities || []
);
const selectTaskLabels = createSelector(
[(state: any) => state.taskReducer.labels],
(labels) => labels || []
);
const selectBoardLabels = createSelector(
[(state: any) => state.boardReducer.labels],
(labels) => labels || []
);
const selectTaskAssignees = createSelector(
[(state: any) => state.taskReducer.taskAssignees],
(assignees) => assignees || []
);
const selectBoardAssignees = createSelector(
[(state: any) => state.boardReducer.taskAssignees],
(assignees) => assignees || []
);
const selectProject = createSelector(
[(state: any) => state.projectReducer.project],
(project) => project
);
const selectSelectedPriorities = createSelector(
[(state: any) => state.taskManagement.selectedPriorities],
(selectedPriorities) => selectedPriorities || []
// Optimized selectors with proper transformation logic
const selectFilterData = createSelector(
[
(state: any) => state.priorityReducer.priorities,
(state: any) => state.taskReducer.priorities,
(state: any) => state.boardReducer.priorities,
(state: any) => state.taskReducer.labels,
(state: any) => state.boardReducer.labels,
(state: any) => state.taskReducer.taskAssignees,
(state: any) => state.boardReducer.taskAssignees,
(state: any) => state.projectReducer.project,
(state: any) => state.taskManagement.selectedPriorities,
],
(
priorities,
taskPriorities,
boardPriorities,
taskLabels,
boardLabels,
taskAssignees,
boardAssignees,
project,
selectedPriorities
) => ({
priorities: priorities || [],
taskPriorities: taskPriorities || [],
boardPriorities: boardPriorities || [],
taskLabels: taskLabels || [],
boardLabels: boardLabels || [],
taskAssignees: taskAssignees || [],
boardAssignees: boardAssignees || [],
project,
selectedPriorities: selectedPriorities || [],
})
);
// Types
@@ -118,50 +114,29 @@ const useFilterData = (): FilterSection[] => {
const [searchParams] = useSearchParams();
const { projectView } = useTabSearchParam();
// Use memoized selectors to prevent unnecessary re-renders
const priorities = useAppSelector(selectPriorities);
const taskPriorities = useAppSelector(selectTaskPriorities);
const boardPriorities = useAppSelector(selectBoardPriorities);
const taskLabels = useAppSelector(selectTaskLabels);
const boardLabels = useAppSelector(selectBoardLabels);
const taskAssignees = useAppSelector(selectTaskAssignees);
const boardAssignees = useAppSelector(selectBoardAssignees);
const taskGroupBy = useAppSelector(state => state.taskReducer.groupBy);
const boardGroupBy = useAppSelector(state => state.boardReducer.groupBy);
const project = useAppSelector(selectProject);
// Use optimized selector to get all filter data at once
const filterData = useAppSelector(selectFilterData);
const currentGrouping = useAppSelector(selectCurrentGrouping);
const selectedPriorities = useAppSelector(selectSelectedPriorities);
const tab = searchParams.get('tab');
const currentProjectView = tab === 'tasks-list' ? 'list' : 'kanban';
// Debug logging
console.log('Filter Data Debug:', {
priorities: priorities?.length,
taskAssignees: taskAssignees?.length,
boardAssignees: boardAssignees?.length,
labels: taskLabels?.length,
boardLabels: boardLabels?.length,
currentProjectView,
projectId: project?.id
});
return useMemo(() => {
const currentPriorities = currentProjectView === 'list' ? taskPriorities : boardPriorities;
const currentLabels = currentProjectView === 'list' ? taskLabels : boardLabels;
const currentAssignees = currentProjectView === 'list' ? taskAssignees : boardAssignees;
const currentPriorities = currentProjectView === 'list' ? filterData.taskPriorities : filterData.boardPriorities;
const currentLabels = currentProjectView === 'list' ? filterData.taskLabels : filterData.boardLabels;
const currentAssignees = currentProjectView === 'list' ? filterData.taskAssignees : filterData.boardAssignees;
const groupByValue = currentGrouping || 'status';
return [
{
id: 'priority',
label: 'Priority',
options: priorities.map((p: any) => ({
options: filterData.priorities.map((p: any) => ({
value: p.id,
label: p.name,
color: p.color_code,
})),
selectedValues: selectedPriorities,
selectedValues: filterData.selectedPriorities,
multiSelect: true,
searchable: false,
icon: FlagOutlined,
@@ -206,25 +181,15 @@ const useFilterData = (): FilterSection[] => {
options: [
{ id: 'status', label: t('statusText'), value: 'status' },
{ id: 'priority', label: t('priorityText'), value: 'priority' },
{ id: 'phase', label: project?.phase_label || t('phaseText'), value: 'phase' },
{ id: 'phase', label: filterData.project?.phase_label || t('phaseText'), value: 'phase' },
],
},
];
}, [
priorities,
taskPriorities,
boardPriorities,
taskLabels,
boardLabels,
taskAssignees,
boardAssignees,
taskGroupBy,
boardGroupBy,
project,
filterData,
currentProjectView,
t,
currentGrouping,
selectedPriorities
currentGrouping
]);
};

View File

@@ -1,9 +1,15 @@
import React, { useState, useMemo, useCallback } from 'react';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { useDroppable } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useSelector } from 'react-redux';
import { Button, Typography } from 'antd';
import { PlusOutlined, RightOutlined, DownOutlined } from '@ant-design/icons';
import {
Button,
Typography,
taskManagementAntdConfig,
PlusOutlined,
RightOutlined,
DownOutlined
} from './antd-imports';
import { TaskGroup as TaskGroupType, Task } from '@/types/task-management.types';
import { taskManagementSelectors } from '@/features/task-management/task-management.slice';
import { RootState } from '@/app/store';
@@ -72,6 +78,8 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
// Get field visibility from taskListFields slice
const taskListFields = useSelector((state: RootState) => state.taskManagementFields) as TaskListField[];
// Define all possible columns
const allFixedColumns = [
{ key: 'drag', label: '', width: 40, alwaysVisible: true },
@@ -81,12 +89,22 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
];
const allScrollableColumns = [
{ key: 'description', label: 'Description', width: 200, fieldKey: 'DESCRIPTION' },
{ key: 'progress', label: 'Progress', width: 90, fieldKey: 'PROGRESS' },
{ key: 'members', label: 'Members', width: 150, fieldKey: 'ASSIGNEES' },
{ key: 'labels', label: 'Labels', width: 200, fieldKey: 'LABELS' },
{ key: 'phase', label: 'Phase', width: 100, fieldKey: 'PHASE' },
{ key: 'status', label: 'Status', width: 100, fieldKey: 'STATUS' },
{ key: 'priority', label: 'Priority', width: 100, fieldKey: 'PRIORITY' },
{ key: 'timeTracking', label: 'Time Tracking', width: 120, fieldKey: 'TIME_TRACKING' },
{ key: 'estimation', label: 'Estimation', width: 100, fieldKey: 'ESTIMATION' },
{ key: 'startDate', label: 'Start Date', width: 120, fieldKey: 'START_DATE' },
{ key: 'dueDate', label: 'Due Date', width: 120, fieldKey: 'DUE_DATE' },
{ key: 'dueTime', label: 'Due Time', width: 100, fieldKey: 'DUE_TIME' },
{ key: 'completedDate', label: 'Completed Date', width: 130, fieldKey: 'COMPLETED_DATE' },
{ key: 'createdDate', label: 'Created Date', width: 120, fieldKey: 'CREATED_DATE' },
{ key: 'lastUpdated', label: 'Last Updated', width: 130, fieldKey: 'LAST_UPDATED' },
{ key: 'reporter', label: 'Reporter', width: 100, fieldKey: 'REPORTER' },
];
// Filter columns based on field visibility
@@ -150,6 +168,8 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
return { isAllSelected, isIndeterminate };
}, [groupTasks, selectedTaskIds]);
// Get group color based on grouping type - memoized
const groupColor = useMemo(() => {
if (group.color) return group.color;
@@ -218,8 +238,7 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
style={{ backgroundColor: groupColor }}
>
<Button
type="text"
size="small"
{...taskManagementAntdConfig.taskButtonDefaults}
icon={isCollapsed ? <RightOutlined /> : <DownOutlined />}
onClick={handleToggleCollapse}
className="task-group-header-button"
@@ -290,6 +309,7 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
<br />
<Button
type="link"
{...taskManagementAntdConfig.taskButtonDefaults}
icon={<PlusOutlined />}
onClick={handleAddTask}
className="mt-2"

View File

@@ -0,0 +1,283 @@
/**
* Example: Task Row Component using Centralized Ant Design Imports
*
* This file demonstrates how to migrate from direct antd imports to the centralized import system.
*
* BEFORE (Direct imports):
* import { Input, Typography, DatePicker } from 'antd';
* import type { InputRef } from 'antd';
*
* AFTER (Centralized imports):
* import { Input, Typography, DatePicker, type InputRef, dayjs, taskManagementAntdConfig } from './antd-imports';
*/
import React, { useState, useCallback, useMemo } from 'react';
import {
Input,
Typography,
DatePicker,
Button,
Select,
Tooltip,
Badge,
Space,
Checkbox,
UserOutlined,
CalendarOutlined,
ClockCircleOutlined,
EditOutlined,
MoreOutlined,
dayjs,
taskManagementAntdConfig,
taskMessage,
type InputRef,
type DatePickerProps,
type Dayjs
} from './antd-imports';
// Your existing task type import
import { Task } from '@/types/task-management.types';
interface TaskRowExampleProps {
task: Task;
projectId: string;
isDarkMode?: boolean;
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
}
const TaskRowExample: React.FC<TaskRowExampleProps> = ({
task,
projectId,
isDarkMode = false,
onTaskUpdate
}) => {
const [isEditing, setIsEditing] = useState(false);
const [editingField, setEditingField] = useState<string | null>(null);
// Use centralized config for consistent DatePicker props
const datePickerProps = useMemo(() => ({
...taskManagementAntdConfig.datePickerDefaults,
className: "w-full bg-transparent border-none shadow-none"
}), []);
// Use centralized config for consistent Button props
const buttonProps = useMemo(() => ({
...taskManagementAntdConfig.taskButtonDefaults,
icon: <EditOutlined />
}), []);
// Handle date changes with centralized message system
const handleDateChange = useCallback((date: Dayjs | null, field: 'startDate' | 'dueDate') => {
if (onTaskUpdate) {
onTaskUpdate(task.id, {
[field]: date?.toISOString() || null
});
taskMessage.success(`${field === 'startDate' ? 'Start' : 'Due'} date updated`);
}
}, [task.id, onTaskUpdate]);
// Handle task title edit
const handleTitleEdit = useCallback((newTitle: string) => {
if (onTaskUpdate && newTitle.trim() !== task.title) {
onTaskUpdate(task.id, { title: newTitle.trim() });
taskMessage.success('Task title updated');
}
setIsEditing(false);
}, [task.id, task.title, onTaskUpdate]);
// Memoized date values for performance
const startDateValue = useMemo(() =>
task.startDate ? dayjs(task.startDate) : undefined,
[task.startDate]
);
const dueDateValue = useMemo(() =>
task.dueDate ? dayjs(task.dueDate) : undefined,
[task.dueDate]
);
return (
<div className={`task-row-example ${isDarkMode ? 'dark' : 'light'}`}>
<div className="task-row-content">
{/* Task Selection Checkbox */}
<div className="task-cell">
<Checkbox
onChange={(e) => {
// Handle selection logic here
console.log('Task selected:', e.target.checked);
}}
/>
</div>
{/* Task Title */}
<div className="task-cell task-title">
{isEditing ? (
<Input
{...taskManagementAntdConfig.taskInputDefaults}
defaultValue={task.title}
autoFocus
onPressEnter={(e) => handleTitleEdit(e.currentTarget.value)}
onBlur={(e) => handleTitleEdit(e.currentTarget.value)}
/>
) : (
<Space>
<Typography.Text
className="task-title-text"
onClick={() => setIsEditing(true)}
>
{task.title}
</Typography.Text>
<Button
{...buttonProps}
onClick={() => setIsEditing(true)}
/>
</Space>
)}
</div>
{/* Task Progress */}
<div className="task-cell">
<Badge
count={`${task.progress || 0}%`}
color={task.progress === 100 ? 'green' : 'blue'}
/>
</div>
{/* Task Assignees */}
<div className="task-cell">
<Space>
<UserOutlined />
<Typography.Text>
{task.assignee_names?.join(', ') || 'Unassigned'}
</Typography.Text>
</Space>
</div>
{/* Start Date */}
<div className="task-cell">
<Tooltip
{...taskManagementAntdConfig.tooltipDefaults}
title="Start Date"
>
<DatePicker
{...datePickerProps}
value={startDateValue}
onChange={(date) => handleDateChange(date, 'startDate')}
placeholder="Start Date"
/>
</Tooltip>
</div>
{/* Due Date */}
<div className="task-cell">
<Tooltip
{...taskManagementAntdConfig.tooltipDefaults}
title="Due Date"
>
<DatePicker
{...datePickerProps}
value={dueDateValue}
onChange={(date) => handleDateChange(date, 'dueDate')}
placeholder="Due Date"
disabledDate={(current) =>
startDateValue ? current.isBefore(startDateValue, 'day') : false
}
/>
</Tooltip>
</div>
{/* Task Status */}
<div className="task-cell">
<Select
{...taskManagementAntdConfig.taskSelectDefaults}
value={task.status}
placeholder="Status"
onChange={(value) => {
if (onTaskUpdate) {
onTaskUpdate(task.id, { status: value });
taskMessage.success('Status updated');
}
}}
options={[
{ label: 'To Do', value: 'todo' },
{ label: 'In Progress', value: 'in_progress' },
{ label: 'Done', value: 'done' },
]}
/>
</div>
{/* Task Priority */}
<div className="task-cell">
<Select
{...taskManagementAntdConfig.taskSelectDefaults}
value={task.priority}
placeholder="Priority"
onChange={(value) => {
if (onTaskUpdate) {
onTaskUpdate(task.id, { priority: value });
taskMessage.success('Priority updated');
}
}}
options={[
{ label: 'Low', value: 'low' },
{ label: 'Medium', value: 'medium' },
{ label: 'High', value: 'high' },
{ label: 'Critical', value: 'critical' },
]}
/>
</div>
{/* Time Tracking */}
<div className="task-cell">
<Space>
<ClockCircleOutlined />
<Typography.Text>
{task.timeTracking?.logged ? `${task.timeTracking.logged}h` : '0h'}
</Typography.Text>
</Space>
</div>
{/* Actions */}
<div className="task-cell">
<Button
{...taskManagementAntdConfig.taskButtonDefaults}
icon={<MoreOutlined />}
onClick={() => {
// Handle more actions
console.log('More actions clicked');
}}
/>
</div>
</div>
</div>
);
};
export default TaskRowExample;
/**
* Migration Guide:
*
* 1. Replace direct antd imports with centralized imports:
* - Change: import { DatePicker } from 'antd';
* - To: import { DatePicker } from './antd-imports';
*
* 2. Use centralized configurations:
* - Apply taskManagementAntdConfig.datePickerDefaults to all DatePickers
* - Use taskMessage instead of direct message calls
* - Apply consistent styling with taskManagementTheme
*
* 3. Benefits:
* - Better tree-shaking (smaller bundle size)
* - Consistent component props across all task management components
* - Centralized theme management
* - Type safety with proper TypeScript types
* - Easy maintenance and updates
*
* 4. Performance optimizations included:
* - Memoized date values to prevent unnecessary dayjs parsing
* - Centralized configurations to prevent prop recreation
* - Optimized message utilities
*/

View File

@@ -2,17 +2,22 @@ import React, { useMemo, useCallback, useState, useRef, useEffect } from 'react'
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { useSelector } from 'react-redux';
import { Input, Typography } from 'antd';
import type { InputRef } from 'antd';
import {
import {
Input,
Typography,
DatePicker,
dayjs,
taskManagementAntdConfig,
HolderOutlined,
MessageOutlined,
PaperClipOutlined,
ClockCircleOutlined,
} from '@ant-design/icons';
UserOutlined,
type InputRef
} from './antd-imports';
import { Task } from '@/types/task-management.types';
import { RootState } from '@/app/store';
import { AssigneeSelector, Avatar, AvatarGroup, Button, Checkbox, CustomColordLabel, CustomNumberLabel, LabelsSelector, Progress, Tag, Tooltip } from '@/components';
import { AssigneeSelector, Avatar, AvatarGroup, Button, Checkbox, CustomColordLabel, CustomNumberLabel, LabelsSelector, Progress, Tooltip } from '@/components';
import { useSocket } from '@/socket/socketContext';
import { SocketEvents } from '@/shared/socket-events';
import TaskStatusDropdown from './task-status-dropdown';
@@ -147,19 +152,20 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
const containerClasses = useMemo(() => {
const baseClasses = 'border-b transition-all duration-300';
const themeClasses = isDarkMode
? 'border-gray-700 bg-gray-900 hover:bg-gray-800'
: 'border-gray-200 bg-white hover:bg-gray-50';
? 'border-gray-600 hover:bg-gray-800'
: 'border-gray-300 hover:bg-gray-50';
const backgroundClasses = isDarkMode ? 'bg-[#18181b]' : 'bg-white';
const selectedClasses = isSelected
? (isDarkMode ? 'bg-blue-900/20' : 'bg-blue-50')
: '';
const overlayClasses = isDragOverlay
? `rounded shadow-lg border-2 ${isDarkMode ? 'bg-gray-900 border-gray-600 shadow-2xl' : 'bg-white border-gray-300 shadow-2xl'}`
? `rounded shadow-lg border-2 ${isDarkMode ? 'border-gray-600 shadow-2xl' : 'border-gray-300 shadow-2xl'}`
: '';
return `${baseClasses} ${themeClasses} ${selectedClasses} ${overlayClasses}`;
return `${baseClasses} ${themeClasses} ${backgroundClasses} ${selectedClasses} ${overlayClasses}`;
}, [isDarkMode, isSelected, isDragOverlay]);
const fixedColumnsClasses = useMemo(() =>
`flex sticky left-0 z-10 border-r-2 shadow-sm ${isDarkMode ? 'bg-gray-900 border-gray-700' : 'bg-white border-gray-200'}`,
`flex sticky left-0 z-10 border-r-2 shadow-sm ${isDarkMode ? 'bg-gray-900 border-gray-600' : 'bg-white border-gray-300'}`,
[isDarkMode]
);
@@ -180,6 +186,23 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
const getStatusColor = useCallback((status: string) =>
STATUS_COLORS[status as keyof typeof STATUS_COLORS] || '#d9d9d9', []);
// Memoize date values for performance optimization
const startDateValue = useMemo(() =>
task.startDate ? dayjs(task.startDate) : undefined,
[task.startDate]
);
const dueDateValue = useMemo(() =>
task.dueDate ? dayjs(task.dueDate) : undefined,
[task.dueDate]
);
// Memoize DatePicker configuration
const datePickerProps = useMemo(() => ({
...taskManagementAntdConfig.datePickerDefaults,
className: "w-full bg-transparent border-none shadow-none"
}), []);
// Create adapter for LabelsSelector - memoized
const taskAdapter = useMemo(() => ({
id: task.id,
@@ -229,6 +252,35 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
}
}, [task.dueDate]);
// Memoize date formatting functions
const formatDate = useCallback((dateString?: string) => {
if (!dateString) return '';
return dayjs(dateString).format('MMM DD, YYYY');
}, []);
const formatDateTime = useCallback((dateString?: string) => {
if (!dateString) return '';
return dayjs(dateString).format('MMM DD, YYYY HH:mm');
}, []);
// Handle date changes
const handleDateChange = useCallback((date: dayjs.Dayjs | null, field: 'startDate' | 'dueDate') => {
if (!connected || !socket) return;
const eventType = field === 'startDate' ? SocketEvents.TASK_START_DATE_CHANGE : SocketEvents.TASK_END_DATE_CHANGE;
const dateField = field === 'startDate' ? 'start_date' : 'end_date';
socket.emit(
eventType.toString(),
JSON.stringify({
task_id: task.id,
[dateField]: date?.format('YYYY-MM-DD'),
parent_task: null,
time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,
})
);
}, [connected, socket, task.id]);
return (
<div
ref={setNodeRef}
@@ -238,18 +290,18 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
<div className="flex h-10 max-h-10 overflow-visible relative">
{/* Fixed Columns */}
<div
className="fixed-columns-row"
className="flex"
style={{
display: 'flex',
background: isDarkMode ? '#1a1a1a' : '#fff',
width: fixedColumns?.reduce((sum, col) => sum + col.width, 0) || 0,
}}
>
{fixedColumns?.map(col => {
{fixedColumns?.map((col, colIdx) => {
const isLastFixed = colIdx === fixedColumns.length - 1;
const borderClasses = `${isLastFixed ? '' : 'border-r'} border-b ${isDarkMode ? 'border-gray-600' : 'border-gray-300'}`;
switch (col.key) {
case 'drag':
return (
<div key={col.key} className="w-10 flex items-center justify-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`w-10 flex items-center justify-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<Button
variant="text"
size="small"
@@ -263,7 +315,7 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
case 'select':
return (
<div key={col.key} className="w-10 flex items-center justify-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`w-10 flex items-center justify-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<Checkbox
checked={isSelected}
onChange={handleSelectChange}
@@ -273,14 +325,16 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
case 'key':
return (
<div key={col.key} className="w-20 flex items-center px-2 border-r" style={{ width: col.width }}>
<Tag
backgroundColor={isDarkMode ? "#374151" : "#f0f0f0"}
color={isDarkMode ? "#d1d5db" : "#666"}
className="truncate whitespace-nowrap max-w-full"
<div key={col.key} className={`w-20 flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span
className={`px-2 py-1 text-xs font-medium rounded truncate whitespace-nowrap max-w-full ${
isDarkMode
? 'bg-gray-700 text-gray-300'
: 'bg-gray-100 text-gray-600'
}`}
>
{task.task_key}
</Tag>
</span>
</div>
);
case 'task':
@@ -291,7 +345,7 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
return (
<div
key={col.key}
className={`flex items-center px-2${editTaskName ? ' task-name-edit-active' : ''}`}
className={`flex items-center px-2 ${borderClasses}${editTaskName ? ' task-name-edit-active' : ''}`}
style={cellStyle}
>
<div className="flex-1 min-w-0 flex flex-col justify-center h-full overflow-hidden">
@@ -300,7 +354,7 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
{editTaskName ? (
<input
ref={inputRef}
className="task-name-input"
className="task-name-input w-full bg-transparent border-none outline-none text-sm"
value={taskName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTaskName(e.target.value)}
onBlur={handleTaskNameSave}
@@ -310,10 +364,6 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
}
}}
style={{
background: 'transparent',
border: 'none',
outline: 'none',
width: '100%',
color: isDarkMode ? '#ffffff' : '#262626'
}}
autoFocus
@@ -339,12 +389,29 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
})}
</div>
{/* Scrollable Columns */}
<div className="scrollable-columns-row overflow-visible" style={{ display: 'flex', minWidth: scrollableColumns?.reduce((sum, col) => sum + col.width, 0) || 0 }}>
{scrollableColumns?.map(col => {
<div className="overflow-visible" style={{ display: 'flex', minWidth: scrollableColumns?.reduce((sum, col) => sum + col.width, 0) || 0 }}>
{scrollableColumns?.map((col, colIdx) => {
const isLastScrollable = colIdx === scrollableColumns.length - 1;
const borderClasses = `${isLastScrollable ? '' : 'border-r'} border-b ${isDarkMode ? 'border-gray-600' : 'border-gray-300'}`;
switch (col.key) {
case 'description':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<Typography.Paragraph
ellipsis={{
expandable: false,
rows: 1,
tooltip: task.description,
}}
className={`w-full mb-0 text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}
>
{task.description || ''}
</Typography.Paragraph>
</div>
);
case 'progress':
return (
<div key={col.key} className="flex items-center justify-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`flex items-center justify-center px-2 ${borderClasses}`} style={{ width: col.width }}>
{task.progress !== undefined && task.progress >= 0 && (
<Progress
type="circle"
@@ -360,7 +427,7 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
case 'members':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<div className="flex items-center gap-2">
{avatarGroupMembers.length > 0 && (
<AvatarGroup
@@ -380,7 +447,7 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
case 'labels':
return (
<div key={col.key} className="max-w-[200px] flex items-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`max-w-[200px] flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<div className="flex items-center gap-1 flex-wrap h-full w-full overflow-visible relative">
{task.labels?.map((label, index) => (
label.end && label.names && label.name ? (
@@ -405,9 +472,17 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
</div>
</div>
);
case 'phase':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.phase || 'No Phase'}
</span>
</div>
);
case 'status':
return (
<div key={col.key} className="flex items-center px-2 border-r overflow-visible" style={{ width: col.width }}>
<div key={col.key} className={`flex items-center px-2 ${borderClasses} overflow-visible`} style={{ width: col.width }}>
<TaskStatusDropdown
task={task}
projectId={projectId}
@@ -417,13 +492,13 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
case 'priority':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<div className="flex items-center gap-2">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: getPriorityColor(task.priority) }}
/>
<span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.priority}
</span>
</div>
@@ -431,12 +506,12 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
case 'timeTracking':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<div className="flex items-center gap-2 h-full overflow-hidden">
{task.timeTracking?.logged && task.timeTracking.logged > 0 && (
<div className="flex items-center gap-1">
<ClockCircleOutlined className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`} />
<span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
<ClockCircleOutlined className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`} />
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{typeof task.timeTracking.logged === 'number'
? `${task.timeTracking.logged}h`
: task.timeTracking.logged
@@ -447,6 +522,79 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
</div>
</div>
);
case 'estimation':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.timeTracking?.estimated ? `${task.timeTracking.estimated}h` : '-'}
</span>
</div>
);
case 'startDate':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<DatePicker
{...datePickerProps}
value={startDateValue}
onChange={(date) => handleDateChange(date, 'startDate')}
placeholder="Start Date"
/>
</div>
);
case 'dueDate':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<DatePicker
{...datePickerProps}
value={dueDateValue}
onChange={(date) => handleDateChange(date, 'dueDate')}
placeholder="Due Date"
/>
</div>
);
case 'dueTime':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.dueDate ? dayjs(task.dueDate).format('HH:mm') : '-'}
</span>
</div>
);
case 'completedDate':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.completedAt ? formatDate(task.completedAt) : '-'}
</span>
</div>
);
case 'createdDate':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.createdAt ? formatDate(task.createdAt) : '-'}
</span>
</div>
);
case 'lastUpdated':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.updatedAt ? formatDateTime(task.updatedAt) : '-'}
</span>
</div>
);
case 'reporter':
return (
<div key={col.key} className={`flex items-center px-2 ${borderClasses}`} style={{ width: col.width }}>
<div className="flex items-center gap-2">
<UserOutlined className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`} />
<span className={`text-xs ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
{task.reporter || '-'}
</span>
</div>
</div>
);
default:
return null;
}
@@ -457,66 +605,33 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
);
}, (prevProps, nextProps) => {
// Simplified comparison for better performance
return (
const taskPropsEqual = (
prevProps.task.id === nextProps.task.id &&
prevProps.task.title === nextProps.task.title &&
prevProps.task.progress === nextProps.task.progress &&
prevProps.task.status === nextProps.task.status &&
prevProps.task.priority === nextProps.task.priority &&
prevProps.task.labels?.length === nextProps.task.labels?.length &&
prevProps.task.assignee_names?.length === nextProps.task.assignee_names?.length &&
prevProps.task.assignee_names?.length === nextProps.task.assignee_names?.length
);
const otherPropsEqual = (
prevProps.isSelected === nextProps.isSelected &&
prevProps.isDragOverlay === nextProps.isDragOverlay &&
prevProps.groupId === nextProps.groupId
);
// Check column props - these are critical for re-rendering when columns change
const columnPropsEqual = (
prevProps.fixedColumns?.length === nextProps.fixedColumns?.length &&
prevProps.scrollableColumns?.length === nextProps.scrollableColumns?.length &&
JSON.stringify(prevProps.fixedColumns?.map(c => c.key)) === JSON.stringify(nextProps.fixedColumns?.map(c => c.key)) &&
JSON.stringify(prevProps.scrollableColumns?.map(c => c.key)) === JSON.stringify(nextProps.scrollableColumns?.map(c => c.key))
);
return taskPropsEqual && otherPropsEqual && columnPropsEqual;
});
TaskRow.displayName = 'TaskRow';
// Add styles for better border visibility
const taskRowStyles = `
.task-row-container {
border-bottom: 1px solid #f0f0f0;
transition: border-color 0.3s ease;
}
.dark .task-row-container,
[data-theme="dark"] .task-row-container {
border-bottom-color: #374151;
}
.task-row-container:hover {
border-bottom-color: #e8e8e8;
}
.dark .task-row-container:hover,
[data-theme="dark"] .task-row-container:hover {
border-bottom-color: #4b5563;
}
.fixed-columns-row > div,
.scrollable-columns-row > div {
border-bottom: 1px solid #f0f0f0;
transition: border-color 0.3s ease;
}
.dark .fixed-columns-row > div,
.dark .scrollable-columns-row > div,
[data-theme="dark"] .fixed-columns-row > div,
[data-theme="dark"] .scrollable-columns-row > div {
border-bottom-color: #374151;
}
`;
// Inject styles
if (typeof document !== 'undefined') {
const styleId = 'task-row-styles';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = taskRowStyles;
document.head.appendChild(style);
}
}
export default TaskRow;
export default TaskRow;

View File

@@ -39,14 +39,7 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
// Get field visibility from taskListFields slice
const taskListFields = useSelector((state: RootState) => state.taskManagementFields) as TaskListField[];
// Debug logging
useEffect(() => {
console.log('VirtualizedTaskList Debug:', {
taskListFields,
fieldsLength: taskListFields?.length,
fieldsState: taskListFields?.map(f => ({ key: f.key, visible: f.visible }))
});
}, [taskListFields]);
// Get tasks for this group using memoization for performance
const groupTasks = useMemo(() => {
@@ -105,12 +98,22 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
];
const allScrollableColumns = [
{ key: 'description', label: 'Description', width: 200, fieldKey: 'DESCRIPTION' },
{ key: 'progress', label: 'Progress', width: 90, fieldKey: 'PROGRESS' },
{ key: 'members', label: 'Members', width: 150, fieldKey: 'ASSIGNEES' },
{ key: 'labels', label: 'Labels', width: 200, fieldKey: 'LABELS' },
{ key: 'phase', label: 'Phase', width: 100, fieldKey: 'PHASE' },
{ key: 'status', label: 'Status', width: 100, fieldKey: 'STATUS' },
{ key: 'priority', label: 'Priority', width: 100, fieldKey: 'PRIORITY' },
{ key: 'timeTracking', label: 'Time Tracking', width: 120, fieldKey: 'TIME_TRACKING' },
{ key: 'estimation', label: 'Estimation', width: 100, fieldKey: 'ESTIMATION' },
{ key: 'startDate', label: 'Start Date', width: 120, fieldKey: 'START_DATE' },
{ key: 'dueDate', label: 'Due Date', width: 120, fieldKey: 'DUE_DATE' },
{ key: 'dueTime', label: 'Due Time', width: 100, fieldKey: 'DUE_TIME' },
{ key: 'completedDate', label: 'Completed Date', width: 130, fieldKey: 'COMPLETED_DATE' },
{ key: 'createdDate', label: 'Created Date', width: 120, fieldKey: 'CREATED_DATE' },
{ key: 'lastUpdated', label: 'Last Updated', width: 130, fieldKey: 'LAST_UPDATED' },
{ key: 'reporter', label: 'Reporter', width: 100, fieldKey: 'REPORTER' },
];
// Filter columns based on field visibility
@@ -145,6 +148,8 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
const scrollableWidth = scrollableColumns.reduce((sum, col) => sum + col.width, 0);
const totalTableWidth = fixedWidth + scrollableWidth;
// Row renderer for virtualization (only task rows)
const Row = useCallback(({ index, style }: { index: number; style: React.CSSProperties }) => {
const task: Task | undefined = groupTasks[index];

View File

@@ -31,26 +31,17 @@ const LOCAL_STORAGE_KEY = 'worklenz.taskManagement.fields';
function loadFields(): TaskListField[] {
const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
console.log('Loading fields from localStorage:', stored);
// Temporarily force defaults to debug
console.log('FORCING DEFAULT FIELDS FOR DEBUGGING');
return DEFAULT_FIELDS;
/* Commented out for debugging
if (stored) {
try {
const parsed = JSON.parse(stored);
console.log('Parsed fields from localStorage:', parsed);
return parsed;
} catch (error) {
console.warn('Failed to parse stored fields, using defaults:', error);
}
}
console.log('Using default fields:', DEFAULT_FIELDS);
return DEFAULT_FIELDS;
*/
}
function saveFields(fields: TaskListField[]) {

View File

@@ -177,7 +177,7 @@ const ProjectView = () => {
onChange={handleTabChange}
items={tabMenuItems}
tabBarStyle={{ paddingInline: 0 }}
destroyOnHidden={true}
destroyInactiveTabPane
/>
{portalElements}

View File

@@ -12,7 +12,10 @@ export interface Task {
assignees: string[];
assignee_names?: InlineMember[];
labels: Label[];
dueDate?: string;
startDate?: string; // Start date for the task
dueDate?: string; // Due date for the task
completedAt?: string; // When the task was completed
reporter?: string; // Who reported/created the task
timeTracking: {
estimated?: number;
logged: number;