Skip to content

Commit 9748792

Browse files
authored
Merge pull request #349 from Worklenz/development
Development
2 parents 90de033 + b061896 commit 9748792

10 files changed

Lines changed: 98 additions & 41 deletions

File tree

worklenz-backend/src/controllers/tasks-controller-v2.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,7 @@ export default class TasksControllerV2 extends TasksControllerBase {
12801280
end: l.end,
12811281
names: l.names,
12821282
})) || [],
1283+
all_labels: task.all_labels || [],
12831284
dueDate: task.end_date || task.END_DATE,
12841285
startDate: task.start_date,
12851286
timeTracking: {

worklenz-frontend/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ const App: React.FC = memo(() => {
203203
return (
204204
<Suspense fallback={<SuspenseFallback />}>
205205
<ThemeWrapper>
206-
<UpdateNotificationProvider>
206+
<UpdateNotificationProvider enableAutoCheck={false}>
207207
<ModuleErrorBoundary>
208208
<RouterProvider
209209
router={router}

worklenz-frontend/src/components/LabelsSelector.tsx

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useSocket } from '@/socket/socketContext';
1010
import { SocketEvents } from '@/shared/socket-events';
1111
import { useAuthService } from '@/hooks/useAuth';
1212
import { Button, Checkbox, Tag } from '@/components';
13+
import { sortLabelsBySelection, isLabelSelected } from '@/utils/labelUtils';
1314

1415
interface LabelsSelectorProps {
1516
task: IProjectTask;
@@ -30,12 +31,13 @@ const LabelsSelector: React.FC<LabelsSelectorProps> = ({ task, isDarkMode = fals
3031
const { t } = useTranslation('task-list-table');
3132

3233
const filteredLabels = useMemo(() => {
33-
return (
34-
(labels as ITaskLabel[])?.filter(label =>
35-
label.name?.toLowerCase().includes(searchQuery.toLowerCase())
36-
) || []
37-
);
38-
}, [labels, searchQuery]);
34+
const filtered = (labels as ITaskLabel[])?.filter(label =>
35+
label.name?.toLowerCase().includes(searchQuery.toLowerCase())
36+
) || [];
37+
38+
// Sort to show selected labels first using shared utility
39+
return sortLabelsBySelection(filtered, task?.labels || []);
40+
}, [labels, searchQuery, task?.labels]);
3941

4042
// Update dropdown position
4143
const updateDropdownPosition = useCallback(() => {
@@ -149,7 +151,8 @@ const LabelsSelector: React.FC<LabelsSelectorProps> = ({ task, isDarkMode = fals
149151
};
150152

151153
const checkLabelSelected = (labelId: string) => {
152-
return task?.all_labels?.some(existingLabel => existingLabel.id === labelId) || false;
154+
// Use task.labels (currently selected labels) instead of all_labels
155+
return isLabelSelected(labelId, task?.labels);
153156
};
154157

155158
const handleKeyDown = (e: React.KeyboardEvent) => {
@@ -261,19 +264,24 @@ const LabelsSelector: React.FC<LabelsSelectorProps> = ({ task, isDarkMode = fals
261264
>
262265
<div className="text-xs">{t('noLabelsFound')}</div>
263266
{searchQuery.trim() && (
264-
<button
265-
onClick={handleCreateLabel}
266-
className={`
267-
mt-2 px-3 py-1 text-xs rounded border transition-colors
268-
${
269-
isDarkMode
270-
? 'border-gray-600 text-gray-300 hover:bg-gray-700'
271-
: 'border-gray-300 text-gray-600 hover:bg-gray-50'
272-
}
273-
`}
274-
>
275-
{t('createLabelButton', { name: searchQuery.trim() })}
276-
</button>
267+
<>
268+
<button
269+
onClick={handleCreateLabel}
270+
className={`
271+
mt-2 px-3 py-1 text-xs rounded border transition-colors
272+
${
273+
isDarkMode
274+
? 'border-gray-600 text-gray-300 hover:bg-gray-700'
275+
: 'border-gray-300 text-gray-600 hover:bg-gray-50'
276+
}
277+
`}
278+
>
279+
{t('createLabelButton', { name: searchQuery.trim() })}
280+
</button>
281+
<div className={`mt-2 text-xs ${isDarkMode ? 'text-gray-500' : 'text-gray-400'}`}>
282+
{t('labelsSelectorInputTip')}
283+
</div>
284+
</>
277285
)}
278286
</div>
279287
)}

worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-labels/task-drawer-labels.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { setBoardLabels, updateBoardTaskLabel } from '@/features/board/board-sli
3131
import { updateEnhancedKanbanTaskLabels } from '@/features/enhanced-kanban/enhanced-kanban.slice';
3232
import { ILabelsChangeResponse } from '@/types/tasks/taskList.types';
3333
import { ITaskLabelFilter } from '@/types/tasks/taskLabel.types';
34+
import { sortLabelsBySelection, isLabelSelected } from '@/utils/labelUtils';
3435

3536
interface TaskDrawerLabelsProps {
3637
task: ITaskViewModel;
@@ -98,8 +99,13 @@ const TaskDrawerLabels = ({ task, t }: TaskDrawerLabelsProps) => {
9899

99100
// used useMemo hook for re render the list when searching
100101
const filteredLabelData = useMemo(() => {
101-
return labelList.filter(label => label.name?.toLowerCase().includes(searchQuery.toLowerCase()));
102-
}, [labelList, searchQuery]);
102+
const filtered = labelList.filter(label =>
103+
label.name?.toLowerCase().includes(searchQuery.toLowerCase())
104+
);
105+
106+
// Sort to show selected labels first using shared utility
107+
return sortLabelsBySelection(filtered, task?.labels || []);
108+
}, [labelList, searchQuery, task?.labels]);
103109

104110
const labelDropdownContent = (
105111
<Card
@@ -143,11 +149,7 @@ const TaskDrawerLabels = ({ task, t }: TaskDrawerLabelsProps) => {
143149
>
144150
<Checkbox
145151
id={label.id}
146-
checked={
147-
task?.labels
148-
? task?.labels.some(existingLabel => existingLabel.id === label.id)
149-
: false
150-
}
152+
checked={isLabelSelected(label.id || '', task?.labels)}
151153
onChange={e => e.preventDefault()}
152154
>
153155
<Flex gap={8}>
@@ -186,6 +188,11 @@ const TaskDrawerLabels = ({ task, t }: TaskDrawerLabelsProps) => {
186188
<Tag
187189
key={label.id}
188190
color={label.color_code + ALPHA_CHANNEL}
191+
closable
192+
onClose={(e) => {
193+
e.preventDefault();
194+
handleLabelChange(label);
195+
}}
189196
style={{
190197
display: 'flex',
191198
alignItems: 'center',

worklenz-frontend/src/components/task-list-v2/components/TaskRowColumns.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { memo } from 'react';
1+
import React, { memo } from 'react';
22
import { CheckCircleOutlined, HolderOutlined } from '@/shared/antd-imports';
33
import { Checkbox } from '@/shared/antd-imports';
44
import { Task } from '@/types/task-management.types';

worklenz-frontend/src/components/taskListCommon/labelsSelector/LabelsSelector.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
2020
import { useAuthService } from '@/hooks/useAuth';
2121
import { SocketEvents } from '@/shared/socket-events';
2222
import { useSocket } from '@/socket/socketContext';
23+
import { sortLabelsBySelection, isLabelSelected } from '@/utils/labelUtils';
2324

2425
interface LabelsSelectorProps {
2526
task: IProjectTask;
@@ -67,8 +68,13 @@ const LabelsSelector = ({ task }: LabelsSelectorProps) => {
6768

6869
// used useMemo hook for re render the list when searching
6970
const filteredLabelData = useMemo(() => {
70-
return labelList.filter(label => label.name?.toLowerCase().includes(searchQuery.toLowerCase()));
71-
}, [labelList, searchQuery]);
71+
const filtered = labelList.filter(label =>
72+
label.name?.toLowerCase().includes(searchQuery.toLowerCase())
73+
);
74+
75+
// Sort to show selected labels first using shared utility
76+
return sortLabelsBySelection(filtered, task?.labels || []);
77+
}, [labelList, searchQuery, task?.labels]);
7278

7379
const labelDropdownContent = (
7480
<Card className="custom-card" styles={{ body: { padding: 8, overflow: 'hidden' } }}>
@@ -112,11 +118,7 @@ const LabelsSelector = ({ task }: LabelsSelectorProps) => {
112118
>
113119
<Checkbox
114120
id={label.id}
115-
checked={
116-
task?.all_labels
117-
? task?.all_labels.some(existingLabel => existingLabel.id === label.id)
118-
: false
119-
}
121+
checked={isLabelSelected(label.id || '', task?.labels)}
120122
onChange={() => handleLabelChange(label)}
121123
>
122124
<Flex gap={8}>

worklenz-frontend/src/hooks/useTaskSocketHandlers.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,8 @@ export const useTaskSocketHandlers = () => {
180180
await Promise.all([
181181
dispatch(updateTaskLabel(labels)),
182182
dispatch(setTaskLabels(labels)),
183-
// Remove unnecessary refetches - real-time updates handle this
184-
// dispatch(fetchLabels()),
185-
// projectId && dispatch(fetchLabelsByProject(projectId)),
183+
// Fetch labels when a new label is created to update the global labels list
184+
labels.is_new && dispatch(fetchLabels()),
186185
]);
187186

188187
// Update enhanced kanban slice

worklenz-frontend/src/pages/settings/labels/LabelsSettings.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,12 @@ const LabelsSettings = () => {
129129
onConfirm={() => deleteLabel(record.id!)}
130130
>
131131
<Tooltip title={t('deleteTooltip', 'Delete')}>
132-
<Button shape="default" icon={<DeleteOutlined />} size="small" />
132+
<Button
133+
shape="default"
134+
icon={<DeleteOutlined />}
135+
size="small"
136+
onClick={(e) => e.stopPropagation()}
137+
/>
133138
</Tooltip>
134139
</Popconfirm>
135140
</Flex>

worklenz-frontend/src/types/tasks/task.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export interface ITask {
3232
project_id: string;
3333
team_id: string;
3434
task_key: string;
35-
labels: string[];
35+
labels: ITaskLabel[];
3636
assignees: string[];
3737
names: string[];
3838
sub_tasks_count: number;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ITaskLabel } from '@/types/tasks/taskLabel.types';
2+
3+
/**
4+
* Sorts labels to show selected labels first
5+
* @param labels - All available labels
6+
* @param selectedLabels - Currently selected labels
7+
* @returns Sorted array with selected labels first
8+
*/
9+
export const sortLabelsBySelection = (
10+
labels: ITaskLabel[],
11+
selectedLabels: ITaskLabel[]
12+
): ITaskLabel[] => {
13+
return [...labels].sort((a, b) => {
14+
const aSelected = selectedLabels.some(label => label.id === a.id);
15+
const bSelected = selectedLabels.some(label => label.id === b.id);
16+
17+
if (aSelected && !bSelected) return -1;
18+
if (!aSelected && bSelected) return 1;
19+
return 0;
20+
});
21+
};
22+
23+
/**
24+
* Checks if a label is selected
25+
* @param labelId - ID of the label to check
26+
* @param selectedLabels - Currently selected labels
27+
* @returns true if label is selected
28+
*/
29+
export const isLabelSelected = (
30+
labelId: string,
31+
selectedLabels?: ITaskLabel[]
32+
): boolean => {
33+
if (!selectedLabels || selectedLabels.length === 0) return false;
34+
return selectedLabels.some(label => label.id === labelId);
35+
};

0 commit comments

Comments
 (0)