From 746d38017f31eee5289f0ae35716aed4fd67e1aa Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 7 Jul 2025 03:39:39 +0530 Subject: [PATCH] feat(task-list): refine task list components and improve UI consistency - Updated SubtaskLoadingSkeleton and TaskRow components for better spacing and visual consistency. - Simplified TaskGroupHeader by removing unnecessary elements and enhancing the display of group names. - Adjusted TaskListV2 to improve column rendering and added state management for field visibility synchronization with the database. - Enhanced AddTaskRow and AddSubtaskRow components for improved user interaction and layout. - Updated placeholder texts in CustomColumnComponents for better clarity. --- .../task-list-v2/SubtaskLoadingSkeleton.tsx | 9 +- .../task-list-v2/TaskGroupHeader.tsx | 104 +----------- .../components/task-list-v2/TaskListV2.tsx | 153 +++++++++--------- .../src/components/task-list-v2/TaskRow.tsx | 2 +- .../task-list-v2/TaskRowWithSubtasks.tsx | 14 +- .../task-list-v2/components/AddTaskRow.tsx | 8 +- .../components/CustomColumnComponents.tsx | 6 +- .../task-management/improved-task-filters.tsx | 22 ++- .../task-management/taskListFields.slice.ts | 103 +++++++++++- 9 files changed, 226 insertions(+), 195 deletions(-) diff --git a/worklenz-frontend/src/components/task-list-v2/SubtaskLoadingSkeleton.tsx b/worklenz-frontend/src/components/task-list-v2/SubtaskLoadingSkeleton.tsx index ce437500..fddfda10 100644 --- a/worklenz-frontend/src/components/task-list-v2/SubtaskLoadingSkeleton.tsx +++ b/worklenz-frontend/src/components/task-list-v2/SubtaskLoadingSkeleton.tsx @@ -26,7 +26,6 @@ const SubtaskLoadingSkeleton: React.FC = ({ visible case 'title': return (
- {/* Subtask indentation - tighter spacing */}
@@ -35,7 +34,7 @@ const SubtaskLoadingSkeleton: React.FC = ({ visible case 'description': return (
-
+
); case 'status': @@ -66,7 +65,7 @@ const SubtaskLoadingSkeleton: React.FC = ({ visible case 'progress': return (
-
+
); case 'labels': @@ -91,7 +90,7 @@ const SubtaskLoadingSkeleton: React.FC = ({ visible case 'estimation': return (
-
+
); case 'startDate': @@ -132,7 +131,7 @@ const SubtaskLoadingSkeleton: React.FC = ({ visible return (
- {visibleColumns.map((column) => ( + {visibleColumns.map((column, index) => (
{renderColumn(column.id, column.width)}
diff --git a/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx b/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx index 8501ac7b..dcddc6d8 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx @@ -166,8 +166,6 @@ const TaskGroupHeader: React.FC = ({ group, isCollapsed, o setCategoryModalVisible(true); }, []); - - // Handle category change const handleCategoryChange = useCallback(async (categoryId: string, e?: React.MouseEvent) => { e?.stopPropagation(); @@ -292,107 +290,17 @@ const TaskGroupHeader: React.FC = ({ group, isCollapsed, o
{/* Group name and count */}
- {isEditingName && isOwnerOrAdmin ? ( - setEditingName(e.target.value)} - onKeyDown={handleNameKeyDown} - onBlur={handleNameBlur} - className="text-sm font-semibold px-2 py-1 rounded-md transition-all duration-200 focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" - style={{ - color: headerTextColor, - fontSize: '14px', - fontWeight: 600, - width: `${Math.max(editingName.length * 8 + 16, 80)}px`, - minWidth: '80px', - backgroundColor: 'rgba(255, 255, 255, 0.1)', - border: `1px solid ${headerTextColor}40`, - backdropFilter: 'blur(4px)' - }} - styles={{ - input: { - color: headerTextColor, - backgroundColor: 'transparent', - border: 'none', - outline: 'none', - boxShadow: 'none', - padding: '0' - } - }} - autoFocus - disabled={isRenaming} - placeholder={t('enterGroupName')} - /> - ) : ( - - {group.name} - - )} + + {group.name} + ({group.count})
- - {/* Three dots menu */} -
- - - -
- - - - {/* Change Category Modal */} - setCategoryModalVisible(false)} - footer={null} - width={400} - > -
-
- {statusCategories?.map((category) => ( -
category.id && handleCategoryChange(category.id, e)} - > - - - {category.name} - - {isChangingCategory && ( -
- -
- )} -
- ))} -
-
-
); }; diff --git a/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx b/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx index b597a6c1..b1b4c4a9 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useEffect } from 'react'; +import React, { useCallback, useMemo, useEffect, useState } from 'react'; import { GroupedVirtuoso } from 'react-virtuoso'; import { DndContext, @@ -156,7 +156,7 @@ const TaskListV2: React.FC = () => { const fieldType = column.custom_column_obj?.fieldType; let defaultWidth = 160; if (fieldType === 'selection') { - defaultWidth = 180; // Extra width for selection dropdowns + defaultWidth = 150; // Reduced width for selection dropdowns } else if (fieldType === 'people') { defaultWidth = 170; // Extra width for people with avatars } @@ -177,36 +177,6 @@ const TaskListV2: React.FC = () => { return [...baseVisibleColumns, ...visibleCustomColumns]; }, [fields, columns, customColumns, t]); - // Sync local field changes with backend column configuration (debounced) - useEffect(() => { - if (!urlProjectId || columns.length === 0 || fields.length === 0) return; - - const timeoutId = setTimeout(() => { - const changedFields = fields.filter(field => { - const backendColumn = columns.find(c => c.key === field.key); - if (backendColumn) { - return (backendColumn.pinned ?? false) !== field.visible; - } - return false; - }); - - changedFields.forEach(field => { - const backendColumn = columns.find(c => c.key === field.key); - if (backendColumn) { - dispatch(updateColumnVisibility({ - projectId: urlProjectId, - item: { - ...backendColumn, - pinned: field.visible - } - })); - } - }); - }, 500); - - return () => clearTimeout(timeoutId); - }, [fields, columns, urlProjectId, dispatch]); - // Effects useEffect(() => { if (urlProjectId) { @@ -215,6 +185,38 @@ const TaskListV2: React.FC = () => { } }, [dispatch, urlProjectId]); + // Initialize field visibility from database when columns are loaded (only once) + const [initializedFromDatabase, setInitializedFromDatabase] = useState(false); + useEffect(() => { + if (columns.length > 0 && fields.length > 0 && !initializedFromDatabase) { + // Update local fields to match database state only on initial load + import('@/features/task-management/taskListFields.slice').then(({ setFields }) => { + // Create updated fields based on database column state + const updatedFields = fields.map(field => { + const backendColumn = columns.find(c => c.key === field.key); + if (backendColumn) { + return { + ...field, + visible: backendColumn.pinned ?? field.visible + }; + } + return field; + }); + + // Only update if there are actual changes + const hasChanges = updatedFields.some((field, index) => + field.visible !== fields[index].visible + ); + + if (hasChanges) { + dispatch(setFields(updatedFields)); + } + + setInitializedFromDatabase(true); + }); + } + }, [columns, fields, dispatch, initializedFromDatabase]); + // Event handlers const handleTaskSelect = useCallback( (taskId: string, event: React.MouseEvent) => { @@ -360,7 +362,7 @@ const TaskListV2: React.FC = () => { {isGroupEmpty && !isGroupCollapsed && (
- {visibleColumns.map((column) => ( + {visibleColumns.map((column, index) => (
{ // Render column headers const renderColumnHeaders = useCallback(() => ( -
-
- {visibleColumns.map(column => { - const columnStyle: ColumnStyle = { - width: column.width, - flexShrink: 0, - ...(column.id === 'labels' && column.width === 'auto' - ? { - minWidth: '200px', - flexGrow: 1, - } - : {}), - ...((column as any).minWidth && { minWidth: (column as any).minWidth }), - ...((column as any).maxWidth && { maxWidth: (column as any).maxWidth }), - }; +
+
+ {visibleColumns.map((column, index) => { + const columnStyle: ColumnStyle = { + width: column.width, + flexShrink: 0, + ...(column.id === 'labels' && column.width === 'auto' + ? { + minWidth: '200px', + flexGrow: 1, + } + : {}), + ...((column as any).minWidth && { minWidth: (column as any).minWidth }), + ...((column as any).maxWidth && { maxWidth: (column as any).maxWidth }), + }; - return ( -
+ return ( +
{column.id === 'dragHandle' || column.id === 'checkbox' ? ( - - ) : (column as any).isCustom ? ( - - ) : ( - t(column.label || '') - )} -
- ); - })} -
- -
-
+ + ) : (column as any).isCustom ? ( + + ) : ( + t(column.label || '') + )}
+ ); + })} +
+ +
+
+
), [visibleColumns, t, handleCustomColumnSettings]); // Loading and error states @@ -470,13 +472,16 @@ const TaskListV2: React.FC = () => { >
{/* Task Filters */} -
+
+ {/* Spacing between filters and table */} +
+ {/* Table Container */}
= memo(({ taskId, projectId, visibleColumn case 'dragHandle': return (
diff --git a/worklenz-frontend/src/components/task-list-v2/TaskRowWithSubtasks.tsx b/worklenz-frontend/src/components/task-list-v2/TaskRowWithSubtasks.tsx index 01780383..9c0bb436 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskRowWithSubtasks.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskRowWithSubtasks.tsx @@ -109,7 +109,7 @@ const AddSubtaskRow: React.FC = memo(({ className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors h-full" > - {t('addSubTaskText')} + {t('addSubtaskText')} ) : ( = memo(({ className="w-full h-full border-none shadow-none bg-transparent" style={{ height: '100%', - minHeight: '42px', + minHeight: '32px', padding: '0', fontSize: '14px' }} @@ -137,10 +137,12 @@ const AddSubtaskRow: React.FC = memo(({ }, [isAdding, subtaskName, handleAddSubtask, handleCancel, t]); return ( -
- {visibleColumns.map((column) => - renderColumn(column.id, column.width) - )} +
+ {visibleColumns.map((column, index) => ( + + {renderColumn(column.id, column.width)} + + ))}
); }); diff --git a/worklenz-frontend/src/components/task-list-v2/components/AddTaskRow.tsx b/worklenz-frontend/src/components/task-list-v2/components/AddTaskRow.tsx index 367ed4f5..1e8411da 100644 --- a/worklenz-frontend/src/components/task-list-v2/components/AddTaskRow.tsx +++ b/worklenz-frontend/src/components/task-list-v2/components/AddTaskRow.tsx @@ -135,9 +135,11 @@ const AddTaskRow: React.FC = memo(({ return (
- {visibleColumns.map((column) => - renderColumn(column.id, column.width) - )} + {visibleColumns.map((column, index) => ( + + {renderColumn(column.id, column.width)} + + ))}
); }); diff --git a/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx b/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx index cbe895a9..25293e75 100644 --- a/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx +++ b/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx @@ -285,7 +285,7 @@ export const DateCustomColumnCell: React.FC<{ onOpenChange={setIsOpen} value={dateValue} onChange={handleDateChange} - placeholder={dateValue ? "" : "Click to set date"} + placeholder={dateValue ? "" : "Set date"} format="MMM DD, YYYY" suffixIcon={null} size="small" @@ -468,7 +468,7 @@ export const SelectionCustomColumnCell: React.FC<{ : 'border-gray-200 text-gray-600 bg-gray-50' } `}> - Select an option + Select option
{/* Options */} @@ -569,7 +569,7 @@ export const SelectionCustomColumnCell: React.FC<{ <>
- Select option + Select diff --git a/worklenz-frontend/src/components/task-management/improved-task-filters.tsx b/worklenz-frontend/src/components/task-management/improved-task-filters.tsx index 7ed94cdc..00b7108f 100644 --- a/worklenz-frontend/src/components/task-management/improved-task-filters.tsx +++ b/worklenz-frontend/src/components/task-management/improved-task-filters.tsx @@ -20,7 +20,8 @@ import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import useTabSearchParam from '@/hooks/useTabSearchParam'; import { useFilterDataLoader } from '@/hooks/useFilterDataLoader'; -import { toggleField } from '@/features/task-management/taskListFields.slice'; +import { toggleField, syncFieldWithDatabase } from '@/features/task-management/taskListFields.slice'; +import { selectColumns } from '@/features/task-management/task-management.slice'; // Import Redux actions import { @@ -698,8 +699,10 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({ isDarkMode, }) => { const { t } = useTranslation('task-list-filters'); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const fieldsRaw = useSelector((state: RootState) => state.taskManagementFields); + const columns = useSelector(selectColumns); + const projectId = useAppSelector(state => state.projectReducer.projectId); const fields = Array.isArray(fieldsRaw) ? fieldsRaw : []; const sortedFields = useMemo(() => [...fields].sort((a, b) => a.order - b.order), [fields]); @@ -792,7 +795,20 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({ return (