diff --git a/worklenz-frontend/src/components/task-list-common/priorityDropdown/priority-dropdown.tsx b/worklenz-frontend/src/components/task-list-common/priorityDropdown/priority-dropdown.tsx index b16b250b..05e8c82f 100644 --- a/worklenz-frontend/src/components/task-list-common/priorityDropdown/priority-dropdown.tsx +++ b/worklenz-frontend/src/components/task-list-common/priorityDropdown/priority-dropdown.tsx @@ -33,11 +33,59 @@ const PriorityDropdown = ({ task, teamId }: PriorityDropdownProps) => { ); }; - useEffect(() => { - const foundPriority = priorityList.find(priority => priority.id === task.priority); - setSelectedPriority(foundPriority); + // Helper function to get display name for raw priority values + const getPriorityDisplayName = (priority: string | undefined) => { + if (!priority) return 'Medium'; + + // Handle raw priority values from backend + const priorityDisplayMap: Record = { + 'critical': 'Critical', + 'high': 'High', + 'medium': 'Medium', + 'low': 'Low', + }; + + return priorityDisplayMap[priority.toLowerCase()] || priority; + }; + + // Helper function to get priority color for raw priority values + const getPriorityColor = (priority: string | undefined) => { + if (!priority) return themeMode === 'dark' ? '#434343' : '#f0f0f0'; + + // Default colors for raw priority values + const priorityColorMap: Record = { + 'critical': { light: '#ff4d4f', dark: '#ff7875' }, + 'high': { light: '#fa8c16', dark: '#ffa940' }, + 'medium': { light: '#1890ff', dark: '#40a9ff' }, + 'low': { light: '#52c41a', dark: '#73d13d' }, + }; + + const colorPair = priorityColorMap[priority.toLowerCase()]; + return colorPair ? (themeMode === 'dark' ? colorPair.dark : colorPair.light) : (themeMode === 'dark' ? '#434343' : '#f0f0f0'); + }; + + // Find matching priority from the list, or use raw value + const currentPriority = useMemo(() => { + if (!task.priority) return null; + + // First try to find by ID + const priorityById = priorityList.find(priority => priority.id === task.priority); + if (priorityById) return priorityById; + + // Then try to find by name (case insensitive) + const priorityByName = priorityList.find(priority => + priority.name.toLowerCase() === task.priority?.toLowerCase() + ); + if (priorityByName) return priorityByName; + + // Return null if no match found (will use fallback rendering) + return null; }, [task.priority, priorityList]); + useEffect(() => { + setSelectedPriority(currentPriority || undefined); + }, [currentPriority]); + const options = useMemo( () => priorityList.map(priority => ({ @@ -74,36 +122,51 @@ const PriorityDropdown = ({ task, teamId }: PriorityDropdownProps) => { [priorityList, themeMode] ); + // If we have a valid priority from the list, render the dropdown + if (currentPriority && priorityList.length > 0) { + return ( + { - const priority = priorityList.find(priority => priority.id === value.value); - return priority ? ( - - {priority.name} - - ) : ( - '' - ); - }} - options={options} - /> - )} - +
+ {getPriorityDisplayName(task.priority)} +
); }; diff --git a/worklenz-frontend/src/components/task-list-common/status-dropdown/status-dropdown.tsx b/worklenz-frontend/src/components/task-list-common/status-dropdown/status-dropdown.tsx index d431df9c..eaefbb4a 100644 --- a/worklenz-frontend/src/components/task-list-common/status-dropdown/status-dropdown.tsx +++ b/worklenz-frontend/src/components/task-list-common/status-dropdown/status-dropdown.tsx @@ -36,6 +36,59 @@ const StatusDropdown = ({ task, teamId }: StatusDropdownProps) => { return getCurrentGroup().value === GROUP_BY_STATUS_VALUE; }; + // Helper function to get display name for raw status values + const getStatusDisplayName = (status: string | undefined) => { + if (!status) return 'To Do'; + + // Handle raw status values from backend + const statusDisplayMap: Record = { + 'to_do': 'To Do', + 'todo': 'To Do', + 'doing': 'Doing', + 'in_progress': 'In Progress', + 'done': 'Done', + 'completed': 'Completed', + }; + + return statusDisplayMap[status.toLowerCase()] || status; + }; + + // Helper function to get status color for raw status values + const getStatusColor = (status: string | undefined) => { + if (!status) return themeMode === 'dark' ? '#434343' : '#f0f0f0'; + + // Default colors for raw status values + const statusColorMap: Record = { + 'to_do': { light: '#f0f0f0', dark: '#434343' }, + 'todo': { light: '#f0f0f0', dark: '#434343' }, + 'doing': { light: '#1890ff', dark: '#177ddc' }, + 'in_progress': { light: '#1890ff', dark: '#177ddc' }, + 'done': { light: '#52c41a', dark: '#389e0d' }, + 'completed': { light: '#52c41a', dark: '#389e0d' }, + }; + + const colorPair = statusColorMap[status.toLowerCase()]; + return colorPair ? (themeMode === 'dark' ? colorPair.dark : colorPair.light) : (themeMode === 'dark' ? '#434343' : '#f0f0f0'); + }; + + // Find matching status from the list, or use raw value + const currentStatus = useMemo(() => { + if (!task.status) return null; + + // First try to find by ID + const statusById = statusList.find(status => status.id === task.status); + if (statusById) return statusById; + + // Then try to find by name (case insensitive) + const statusByName = statusList.find(status => + status.name.toLowerCase() === task.status?.toLowerCase() + ); + if (statusByName) return statusByName; + + // Return null if no match found (will use fallback rendering) + return null; + }, [task.status, statusList]); + const options = useMemo( () => statusList.map(status => ({ @@ -46,31 +99,49 @@ const StatusDropdown = ({ task, teamId }: StatusDropdownProps) => { [statusList, themeMode] ); + // If we have a valid status from the list, render the dropdown + if (currentStatus && statusList.length > 0) { + return ( + { - return status ? {status.label} : ''; - }} - options={options} - optionRender={(option) => ( - - {option.label} - - )} - /> - )} - +
+ {getStatusDisplayName(task.status)} +
); }; diff --git a/worklenz-frontend/src/components/task-management/task-row.tsx b/worklenz-frontend/src/components/task-management/task-row.tsx index 85163043..8244a3c0 100644 --- a/worklenz-frontend/src/components/task-management/task-row.tsx +++ b/worklenz-frontend/src/components/task-management/task-row.tsx @@ -481,7 +481,10 @@ const TaskRow: React.FC = React.memo(({ return (
- {task.status || 'Todo'} + {task.status === 'todo' ? 'To Do' : + task.status === 'doing' ? 'Doing' : + task.status === 'done' ? 'Done' : + task.status || 'To Do'}
); @@ -499,7 +502,11 @@ const TaskRow: React.FC = React.memo(({ return (
- {task.priority || 'Medium'} + {task.priority === 'critical' ? 'Critical' : + task.priority === 'high' ? 'High' : + task.priority === 'medium' ? 'Medium' : + task.priority === 'low' ? 'Low' : + task.priority || 'Medium'}
); @@ -513,6 +520,81 @@ const TaskRow: React.FC = React.memo(({ ); + case 'members': + return ( +
+
+ {task.assignee_names && task.assignee_names.length > 0 ? ( +
+ {task.assignee_names.slice(0, 3).map((member, index) => ( +
+ {member.name ? member.name.charAt(0).toUpperCase() : '?'} +
+ ))} + {task.assignee_names.length > 3 && ( +
+ +{task.assignee_names.length - 3} +
+ )} +
+ ) : ( +
+ +
+ )} +
+
+ ); + + case 'labels': + return ( +
+
+ {task.labels && task.labels.length > 0 ? ( + task.labels.slice(0, 3).map((label, index) => ( +
+ {label.name || 'Label'} +
+ )) + ) : ( +
+ No labels +
+ )} + {task.labels && task.labels.length > 3 && ( +
+ +{task.labels.length - 3} +
+ )} +
+
+ ); + default: // For non-essential columns, show placeholder during initial load return ( diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx index 8690d895..48ef8a29 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx @@ -159,16 +159,86 @@ const CustomCell = React.memo(({ renderColumnContent: any; updateTaskCustomColumnValue: (taskId: string, columnKey: string, value: string) => void; }) => { - if (column.custom_column && column.key && column.pinned) { - return renderCustomColumnContent( - column.custom_column_obj || {}, - column.custom_column_obj?.fieldType, - task, - column.key, - updateTaskCustomColumnValue - ); + try { + if (column.custom_column && column.key && column.pinned) { + return renderCustomColumnContent( + column.custom_column_obj || {}, + column.custom_column_obj?.fieldType, + task, + column.key, + updateTaskCustomColumnValue + ); + } + + const result = renderColumnContent(column.key || '', task, isSubtask); + + // If renderColumnContent returns null or undefined, provide a fallback + if (result === null || result === undefined) { + // Handle specific column types with fallbacks + switch (column.key) { + case 'STATUS': + return ( +
+ {task.status_name || task.status || 'To Do'} +
+ ); + case 'PRIORITY': + return ( +
+ {task.priority_name || task.priority || 'Medium'} +
+ ); + case 'ASSIGNEES': + return ( +
+ {task.assignees?.length ? `${task.assignees.length} assignee(s)` : 'No assignees'} +
+ ); + case 'LABELS': + return ( +
+ {task.labels?.length ? `${task.labels.length} label(s)` : 'No labels'} +
+ ); + default: + return
-
; + } + } + + return result; + } catch (error) { + console.error('Error rendering task cell:', error, { column: column.key, task: task.id }); + + // Fallback rendering for errors + switch (column.key) { + case 'STATUS': + return ( +
+ {task.status_name || task.status || 'Error'} +
+ ); + case 'PRIORITY': + return ( +
+ {task.priority_name || task.priority || 'Error'} +
+ ); + case 'ASSIGNEES': + return ( +
+ Error loading assignees +
+ ); + case 'LABELS': + return ( +
+ Error loading labels +
+ ); + default: + return
Error
; + } } - return renderColumnContent(column.key || '', task, isSubtask); }); // First, let's extract the custom column cell to a completely separate component