diff --git a/worklenz-backend/src/controllers/tasks-controller-v2.ts b/worklenz-backend/src/controllers/tasks-controller-v2.ts index daa29ee5b..8385bcac3 100644 --- a/worklenz-backend/src/controllers/tasks-controller-v2.ts +++ b/worklenz-backend/src/controllers/tasks-controller-v2.ts @@ -1280,6 +1280,7 @@ export default class TasksControllerV2 extends TasksControllerBase { end: l.end, names: l.names, })) || [], + all_labels: task.all_labels || [], dueDate: task.end_date || task.END_DATE, startDate: task.start_date, timeTracking: { diff --git a/worklenz-frontend/src/App.tsx b/worklenz-frontend/src/App.tsx index 92beecd6a..778d8d102 100644 --- a/worklenz-frontend/src/App.tsx +++ b/worklenz-frontend/src/App.tsx @@ -203,7 +203,7 @@ const App: React.FC = memo(() => { return ( }> - + = ({ task, isDarkMode = fals const { t } = useTranslation('task-list-table'); const filteredLabels = useMemo(() => { - return ( - (labels as ITaskLabel[])?.filter(label => - label.name?.toLowerCase().includes(searchQuery.toLowerCase()) - ) || [] - ); - }, [labels, searchQuery]); + const filtered = (labels as ITaskLabel[])?.filter(label => + label.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ) || []; + + // Sort to show selected labels first using shared utility + return sortLabelsBySelection(filtered, task?.labels || []); + }, [labels, searchQuery, task?.labels]); // Update dropdown position const updateDropdownPosition = useCallback(() => { @@ -149,7 +151,8 @@ const LabelsSelector: React.FC = ({ task, isDarkMode = fals }; const checkLabelSelected = (labelId: string) => { - return task?.all_labels?.some(existingLabel => existingLabel.id === labelId) || false; + // Use task.labels (currently selected labels) instead of all_labels + return isLabelSelected(labelId, task?.labels); }; const handleKeyDown = (e: React.KeyboardEvent) => { @@ -261,19 +264,24 @@ const LabelsSelector: React.FC = ({ task, isDarkMode = fals >
{t('noLabelsFound')}
{searchQuery.trim() && ( - + <> + +
+ {t('labelsSelectorInputTip')} +
+ )} )} diff --git a/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-labels/task-drawer-labels.tsx b/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-labels/task-drawer-labels.tsx index 2b70d41fc..914abd787 100644 --- a/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-labels/task-drawer-labels.tsx +++ b/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-labels/task-drawer-labels.tsx @@ -31,6 +31,7 @@ import { setBoardLabels, updateBoardTaskLabel } from '@/features/board/board-sli import { updateEnhancedKanbanTaskLabels } from '@/features/enhanced-kanban/enhanced-kanban.slice'; import { ILabelsChangeResponse } from '@/types/tasks/taskList.types'; import { ITaskLabelFilter } from '@/types/tasks/taskLabel.types'; +import { sortLabelsBySelection, isLabelSelected } from '@/utils/labelUtils'; interface TaskDrawerLabelsProps { task: ITaskViewModel; @@ -98,8 +99,13 @@ const TaskDrawerLabels = ({ task, t }: TaskDrawerLabelsProps) => { // used useMemo hook for re render the list when searching const filteredLabelData = useMemo(() => { - return labelList.filter(label => label.name?.toLowerCase().includes(searchQuery.toLowerCase())); - }, [labelList, searchQuery]); + const filtered = labelList.filter(label => + label.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Sort to show selected labels first using shared utility + return sortLabelsBySelection(filtered, task?.labels || []); + }, [labelList, searchQuery, task?.labels]); const labelDropdownContent = ( { > existingLabel.id === label.id) - : false - } + checked={isLabelSelected(label.id || '', task?.labels)} onChange={e => e.preventDefault()} > @@ -186,6 +188,11 @@ const TaskDrawerLabels = ({ task, t }: TaskDrawerLabelsProps) => { { + e.preventDefault(); + handleLabelChange(label); + }} style={{ display: 'flex', alignItems: 'center', diff --git a/worklenz-frontend/src/components/task-list-v2/components/TaskRowColumns.tsx b/worklenz-frontend/src/components/task-list-v2/components/TaskRowColumns.tsx index 3681140e5..84d3432b9 100644 --- a/worklenz-frontend/src/components/task-list-v2/components/TaskRowColumns.tsx +++ b/worklenz-frontend/src/components/task-list-v2/components/TaskRowColumns.tsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; + import React, { memo } from 'react'; import { CheckCircleOutlined, HolderOutlined } from '@/shared/antd-imports'; import { Checkbox } from '@/shared/antd-imports'; import { Task } from '@/types/task-management.types'; diff --git a/worklenz-frontend/src/components/taskListCommon/labelsSelector/LabelsSelector.tsx b/worklenz-frontend/src/components/taskListCommon/labelsSelector/LabelsSelector.tsx index 5a711ca85..026aff04c 100644 --- a/worklenz-frontend/src/components/taskListCommon/labelsSelector/LabelsSelector.tsx +++ b/worklenz-frontend/src/components/taskListCommon/labelsSelector/LabelsSelector.tsx @@ -20,6 +20,7 @@ import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; import { useAuthService } from '@/hooks/useAuth'; import { SocketEvents } from '@/shared/socket-events'; import { useSocket } from '@/socket/socketContext'; +import { sortLabelsBySelection, isLabelSelected } from '@/utils/labelUtils'; interface LabelsSelectorProps { task: IProjectTask; @@ -67,8 +68,13 @@ const LabelsSelector = ({ task }: LabelsSelectorProps) => { // used useMemo hook for re render the list when searching const filteredLabelData = useMemo(() => { - return labelList.filter(label => label.name?.toLowerCase().includes(searchQuery.toLowerCase())); - }, [labelList, searchQuery]); + const filtered = labelList.filter(label => + label.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Sort to show selected labels first using shared utility + return sortLabelsBySelection(filtered, task?.labels || []); + }, [labelList, searchQuery, task?.labels]); const labelDropdownContent = ( @@ -112,11 +118,7 @@ const LabelsSelector = ({ task }: LabelsSelectorProps) => { > existingLabel.id === label.id) - : false - } + checked={isLabelSelected(label.id || '', task?.labels)} onChange={() => handleLabelChange(label)} > diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index 72a1f71a5..462fd5915 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -180,9 +180,8 @@ export const useTaskSocketHandlers = () => { await Promise.all([ dispatch(updateTaskLabel(labels)), dispatch(setTaskLabels(labels)), - // Remove unnecessary refetches - real-time updates handle this - // dispatch(fetchLabels()), - // projectId && dispatch(fetchLabelsByProject(projectId)), + // Fetch labels when a new label is created to update the global labels list + labels.is_new && dispatch(fetchLabels()), ]); // Update enhanced kanban slice diff --git a/worklenz-frontend/src/pages/settings/labels/LabelsSettings.tsx b/worklenz-frontend/src/pages/settings/labels/LabelsSettings.tsx index 659a7d63e..6c73951bb 100644 --- a/worklenz-frontend/src/pages/settings/labels/LabelsSettings.tsx +++ b/worklenz-frontend/src/pages/settings/labels/LabelsSettings.tsx @@ -129,7 +129,12 @@ const LabelsSettings = () => { onConfirm={() => deleteLabel(record.id!)} > -