@tuturuuu/ui 0.2.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/package.json +79 -67
  3. package/src/components/ui/__tests__/avatar.test.tsx +8 -5
  4. package/src/components/ui/calendar-app/components/calendar-connections-compact.tsx +414 -0
  5. package/src/components/ui/calendar-app/components/calendar-connections-manager.tsx +5 -1
  6. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +529 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +26 -1429
  8. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +711 -0
  9. package/src/components/ui/chart.test.tsx +29 -0
  10. package/src/components/ui/chart.tsx +12 -3
  11. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +396 -2
  12. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +36 -8
  13. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +14 -0
  14. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +5 -0
  15. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +21 -7
  16. package/src/components/ui/chat/chat-agent-details-utils.test.ts +73 -0
  17. package/src/components/ui/chat/chat-agent-details-utils.tsx +100 -26
  18. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +517 -0
  19. package/src/components/ui/chat/chat-workspace.tsx +31 -1
  20. package/src/components/ui/chat/hooks-messages.test.tsx +45 -1
  21. package/src/components/ui/chat/hooks-messages.ts +1 -1
  22. package/src/components/ui/chat/hooks-realtime.ts +13 -16
  23. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +24 -1
  24. package/src/components/ui/custom/__tests__/tuturuuu-logo.test.ts +12 -3
  25. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +39 -0
  26. package/src/components/ui/custom/common-footer.tsx +16 -1
  27. package/src/components/ui/custom/production-indicator.tsx +1 -1
  28. package/src/components/ui/custom/settings/sidebar-settings.tsx +1 -1
  29. package/src/components/ui/custom/settings/task-settings.tsx +18 -0
  30. package/src/components/ui/custom/settings-dialog-shell.tsx +38 -23
  31. package/src/components/ui/custom/sidebar-context-compile-graph.test.ts +60 -0
  32. package/src/components/ui/custom/sidebar-context.tsx +61 -61
  33. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +123 -0
  34. package/src/components/ui/custom/tuturuuu-logo-urls.ts +6 -0
  35. package/src/components/ui/custom/tuturuuu-logo.tsx +25 -7
  36. package/src/components/ui/custom/workspace-select-helpers.ts +20 -0
  37. package/src/components/ui/custom/workspace-select.tsx +33 -12
  38. package/src/components/ui/finance/invoices/components/invoice-checkout-summary.tsx +7 -1
  39. package/src/components/ui/finance/invoices/components/invoice-payment-settings.tsx +3 -0
  40. package/src/components/ui/finance/invoices/components/invoice-products-permission-warning.tsx +58 -0
  41. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +12 -20
  42. package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +10 -9
  43. package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +10 -5
  44. package/src/components/ui/finance/invoices/hooks.ts +75 -20
  45. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +137 -0
  46. package/src/components/ui/finance/invoices/new-invoice-page.tsx +86 -37
  47. package/src/components/ui/finance/invoices/product-selection.test.tsx +8 -26
  48. package/src/components/ui/finance/invoices/product-selection.tsx +2 -10
  49. package/src/components/ui/finance/invoices/standard-invoice.tsx +88 -26
  50. package/src/components/ui/finance/invoices/subscription-invoice.tsx +154 -46
  51. package/src/components/ui/finance/invoices/utils.test.ts +50 -0
  52. package/src/components/ui/finance/invoices/utils.ts +75 -17
  53. package/src/components/ui/finance/shared/finance-display-amount.tsx +3 -1
  54. package/src/components/ui/finance/shared/finance-permission-warning-dialog.test.tsx +34 -0
  55. package/src/components/ui/finance/shared/finance-permission-warning-dialog.tsx +157 -0
  56. package/src/components/ui/finance/transactions/form-basic-tab.tsx +8 -0
  57. package/src/components/ui/finance/transactions/form-more-tab.tsx +8 -0
  58. package/src/components/ui/finance/transactions/form-types.ts +2 -0
  59. package/src/components/ui/finance/transactions/form.test.tsx +43 -0
  60. package/src/components/ui/finance/transactions/form.tsx +60 -0
  61. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +27 -0
  62. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +13 -1
  63. package/src/components/ui/finance/transactions/transactions-infinite-page.tsx +4 -0
  64. package/src/components/ui/finance/transactions/transactions-page.tsx +23 -1
  65. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  66. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +5 -0
  67. package/src/components/ui/legacy/calendar/calendar-content.tsx +9 -1
  68. package/src/components/ui/legacy/calendar/event-modal.tsx +146 -2
  69. package/src/components/ui/legacy/calendar/event-preview-popover.tsx +200 -0
  70. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +76 -0
  71. package/src/components/ui/legacy/calendar/smart-calendar.tsx +13 -1
  72. package/src/components/ui/legacy/meet/page.test.ts +180 -0
  73. package/src/components/ui/legacy/meet/page.tsx +87 -39
  74. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +79 -25
  75. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-external-workspaces.test.tsx +392 -0
  76. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.test.tsx +57 -0
  77. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.tsx +106 -0
  78. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +106 -161
  79. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-assignees.ts +96 -150
  80. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-labels.ts +63 -79
  81. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-projects.ts +64 -83
  82. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +115 -155
  83. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-utils.ts +319 -2
  84. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +8 -1
  85. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +63 -37
  86. package/src/components/ui/tu-do/boards/boardId/kanban/kanban-column-collapse.ts +16 -0
  87. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +46 -0
  88. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +5 -3
  89. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +19 -7
  90. package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +181 -2
  91. package/src/components/ui/tu-do/boards/boardId/menus/index.ts +1 -0
  92. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +463 -0
  93. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-utils.ts +109 -0
  94. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +4 -0
  95. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardCheckbox.tsx +6 -3
  96. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardDates.tsx +26 -9
  97. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-checkbox-style.ts +39 -0
  98. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.test.ts +43 -0
  99. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +33 -0
  100. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.test.ts +31 -0
  101. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.ts +9 -0
  102. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.test.tsx +124 -0
  103. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.tsx +88 -0
  104. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +151 -76
  105. package/src/components/ui/tu-do/boards/boardId/task-card/task-scheduling-badge.tsx +174 -0
  106. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +34 -13
  107. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +54 -1
  108. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +158 -0
  109. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +5 -2
  110. package/src/components/ui/tu-do/shared/board-client.tsx +12 -2
  111. package/src/components/ui/tu-do/shared/board-views.tsx +195 -328
  112. package/src/components/ui/tu-do/shared/list-view.tsx +18 -8
  113. package/src/components/ui/tu-do/shared/task-due-date-visibility.test.ts +72 -0
  114. package/src/components/ui/tu-do/shared/task-due-date-visibility.ts +38 -0
  115. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +6 -3
  116. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +2 -2
  117. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +33 -0
  118. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +74 -3
  119. package/src/hooks/__tests__/use-task-actions.test.tsx +118 -0
  120. package/src/hooks/__tests__/use-user-config.test.tsx +65 -0
  121. package/src/hooks/__tests__/use-workspace-presence.test.tsx +1 -1
  122. package/src/hooks/use-calendar-sync.tsx +22 -277
  123. package/src/hooks/use-calendar.tsx +95 -525
  124. package/src/hooks/use-semantic-task-search.ts +10 -33
  125. package/src/hooks/use-task-actions.ts +43 -117
  126. package/src/hooks/use-user-config.ts +1 -1
  127. package/src/hooks/use-workspace-config.ts +6 -2
  128. package/src/hooks/use-workspace-presence.ts +1 -1
  129. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-bar.tsx +0 -94
@@ -2,12 +2,17 @@
2
2
 
3
3
  import { type QueryClient, useMutation } from '@tanstack/react-query';
4
4
  import { bulkWorkspaceTasks } from '@tuturuuu/internal-api/tasks';
5
- import type { Task } from '@tuturuuu/types/primitives/Task';
6
5
  import { toast } from '@tuturuuu/ui/sonner';
7
6
  import type { BoardBroadcastFn } from '../../../../shared/board-broadcast-context';
8
7
  import type { BulkOperationI18n } from './bulk-operation-i18n';
9
8
  import type { WorkspaceProject } from './bulk-operation-types';
10
- import { getInternalApiOptions } from './bulk-operation-utils';
9
+ import {
10
+ getInternalApiOptions,
11
+ restoreBoardTaskCaches,
12
+ restoreFailedBoardTasks,
13
+ snapshotBoardTaskCaches,
14
+ updateBoardTaskCaches,
15
+ } from './bulk-operation-utils';
11
16
 
12
17
  export function useBulkAddProject(
13
18
  queryClient: QueryClient,
@@ -52,8 +57,9 @@ export function useBulkAddProject(
52
57
  },
53
58
  onMutate: async ({ projectId, taskIds }) => {
54
59
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
55
- const previousTasks = queryClient.getQueryData(['tasks', boardId]);
56
- const current = (previousTasks as Task[] | undefined) || [];
60
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
61
+ const cacheSnapshot = snapshotBoardTaskCaches(queryClient, boardId);
62
+ const current = cacheSnapshot.previousTasks || [];
57
63
  const projectMeta = workspaceProjects.find((p) => p.id === projectId);
58
64
 
59
65
  const missingTaskIds = taskIds.filter((id) => {
@@ -61,35 +67,30 @@ export function useBulkAddProject(
61
67
  return !task?.projects?.some((p) => p.id === projectId);
62
68
  });
63
69
 
64
- queryClient.setQueryData(
65
- ['tasks', boardId],
66
- (old: Task[] | undefined) => {
67
- if (!old) return old;
68
- return old.map((task) => {
69
- if (!missingTaskIds.includes(task.id)) return task;
70
- return {
71
- ...task,
72
- projects: [
73
- ...(task.projects || []),
74
- {
75
- id: projectId,
76
- name:
77
- projectMeta?.name ||
78
- i18n?.defaultProjectName() ||
79
- 'Project',
80
- status: projectMeta?.status ?? 'unknown',
81
- },
82
- ],
83
- };
84
- });
85
- }
86
- );
70
+ updateBoardTaskCaches(queryClient, boardId, (old) => {
71
+ if (!old) return old;
72
+ return old.map((task) => {
73
+ if (!missingTaskIds.includes(task.id)) return task;
74
+ return {
75
+ ...task,
76
+ projects: [
77
+ ...(task.projects || []),
78
+ {
79
+ id: projectId,
80
+ name:
81
+ projectMeta?.name || i18n?.defaultProjectName() || 'Project',
82
+ status: projectMeta?.status ?? 'unknown',
83
+ },
84
+ ],
85
+ };
86
+ });
87
+ });
87
88
 
88
- return { previousTasks, modifiedTaskIds: missingTaskIds };
89
+ return { ...cacheSnapshot, modifiedTaskIds: missingTaskIds };
89
90
  },
90
91
  onError: (error, _, context) => {
91
- if (context?.previousTasks) {
92
- queryClient.setQueryData(['tasks', boardId], context.previousTasks);
92
+ if (context) {
93
+ restoreBoardTaskCaches(queryClient, boardId, context);
93
94
  }
94
95
  console.error('Bulk add project failed', error);
95
96
  toast.error(
@@ -101,22 +102,13 @@ export function useBulkAddProject(
101
102
  data.failures.map((failure) => failure.taskId)
102
103
  );
103
104
 
104
- if (failedTaskIds.size > 0 && Array.isArray(context?.previousTasks)) {
105
- const previousTaskMap = new Map(
106
- (context.previousTasks as Task[]).map((task) => [task.id, task])
107
- );
108
-
109
- queryClient.setQueryData(
110
- ['tasks', boardId],
111
- (old: Task[] | undefined) => {
112
- if (!old) return old;
113
- return old.map((task) => {
114
- if (!failedTaskIds.has(task.id)) return task;
115
- return previousTaskMap.get(task.id) ?? task;
116
- });
117
- }
118
- );
119
- }
105
+ restoreFailedBoardTasks({
106
+ queryClient,
107
+ boardId,
108
+ previousTasks: context?.previousTasks,
109
+ previousFullTasks: context?.previousFullTasks,
110
+ failedTaskIds,
111
+ });
120
112
 
121
113
  const modifiedTaskIds = context?.modifiedTaskIds ?? data.taskIds;
122
114
  const succeededModifiedTaskIds = modifiedTaskIds.filter(
@@ -211,8 +203,9 @@ export function useBulkRemoveProject(
211
203
  },
212
204
  onMutate: async ({ projectId, taskIds }) => {
213
205
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
214
- const previousTasks = queryClient.getQueryData(['tasks', boardId]);
215
- const current = (previousTasks as Task[] | undefined) || [];
206
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
207
+ const cacheSnapshot = snapshotBoardTaskCaches(queryClient, boardId);
208
+ const current = cacheSnapshot.previousTasks || [];
216
209
  const modifiedTaskIds = taskIds.filter((id) => {
217
210
  const task = current.find((ct) => ct.id === id);
218
211
  if (!task) {
@@ -223,28 +216,25 @@ export function useBulkRemoveProject(
223
216
  });
224
217
  const taskIdSet = new Set(taskIds);
225
218
 
226
- queryClient.setQueryData(
227
- ['tasks', boardId],
228
- (old: Task[] | undefined) => {
229
- if (!old) return old;
230
- return old.map((task) =>
231
- taskIdSet.has(task.id)
232
- ? {
233
- ...task,
234
- projects: (task.projects || []).filter(
235
- (p) => p.id !== projectId
236
- ),
237
- }
238
- : task
239
- );
240
- }
241
- );
219
+ updateBoardTaskCaches(queryClient, boardId, (old) => {
220
+ if (!old) return old;
221
+ return old.map((task) =>
222
+ taskIdSet.has(task.id)
223
+ ? {
224
+ ...task,
225
+ projects: (task.projects || []).filter(
226
+ (p) => p.id !== projectId
227
+ ),
228
+ }
229
+ : task
230
+ );
231
+ });
242
232
 
243
- return { previousTasks, modifiedTaskIds };
233
+ return { ...cacheSnapshot, modifiedTaskIds };
244
234
  },
245
235
  onError: (error, _, context) => {
246
- if (context?.previousTasks) {
247
- queryClient.setQueryData(['tasks', boardId], context.previousTasks);
236
+ if (context) {
237
+ restoreBoardTaskCaches(queryClient, boardId, context);
248
238
  }
249
239
  console.error('Bulk remove project failed', error);
250
240
  toast.error(
@@ -257,22 +247,13 @@ export function useBulkRemoveProject(
257
247
  data.failures.map((failure) => failure.taskId)
258
248
  );
259
249
 
260
- if (failedTaskIds.size > 0 && Array.isArray(context?.previousTasks)) {
261
- const previousTaskMap = new Map(
262
- (context.previousTasks as Task[]).map((task) => [task.id, task])
263
- );
264
-
265
- queryClient.setQueryData(
266
- ['tasks', boardId],
267
- (old: Task[] | undefined) => {
268
- if (!old) return old;
269
- return old.map((task) => {
270
- if (!failedTaskIds.has(task.id)) return task;
271
- return previousTaskMap.get(task.id) ?? task;
272
- });
273
- }
274
- );
275
- }
250
+ restoreFailedBoardTasks({
251
+ queryClient,
252
+ boardId,
253
+ previousTasks: context?.previousTasks,
254
+ previousFullTasks: context?.previousFullTasks,
255
+ failedTaskIds,
256
+ });
276
257
 
277
258
  const modifiedTaskIds = context?.modifiedTaskIds ?? data.succeededTaskIds;
278
259
  const modifiedTaskIdSet = new Set(modifiedTaskIds);
@@ -1,14 +1,18 @@
1
1
  'use client';
2
2
 
3
3
  import { type QueryClient, useMutation } from '@tanstack/react-query';
4
- import { bulkWorkspaceTasks } from '@tuturuuu/internal-api/tasks';
5
4
  import type { Task } from '@tuturuuu/types/primitives/Task';
6
5
  import { toast } from '@tuturuuu/ui/sonner';
7
6
  import type { BoardBroadcastFn } from '../../../../shared/board-broadcast-context';
8
7
  import type { BulkOperationI18n } from './bulk-operation-i18n';
9
8
  import {
9
+ bulkWorkspaceTasksByEffectiveWorkspace,
10
10
  getInternalApiOptions,
11
11
  resolveDueDatePreset,
12
+ restoreBoardTaskCaches,
13
+ restoreFailedBoardTasks,
14
+ snapshotBoardTaskCaches,
15
+ updateBoardTaskCaches,
12
16
  } from './bulk-operation-utils';
13
17
 
14
18
  export function useBulkUpdatePriority(
@@ -28,17 +32,17 @@ export function useBulkUpdatePriority(
28
32
  }) => {
29
33
  const apiOptions = getInternalApiOptions();
30
34
 
31
- const result = await bulkWorkspaceTasks(
32
- wsId,
33
- {
34
- taskIds,
35
- operation: {
36
- type: 'update_fields',
37
- updates: { priority },
38
- },
35
+ const result = await bulkWorkspaceTasksByEffectiveWorkspace({
36
+ queryClient,
37
+ boardId,
38
+ defaultWorkspaceId: wsId,
39
+ taskIds,
40
+ operation: {
41
+ type: 'update_fields',
42
+ updates: { priority },
39
43
  },
40
- apiOptions
41
- );
44
+ options: apiOptions,
45
+ });
42
46
 
43
47
  if (result.successCount === 0) {
44
48
  throw new Error(
@@ -56,22 +60,20 @@ export function useBulkUpdatePriority(
56
60
  },
57
61
  onMutate: async ({ priority, taskIds }) => {
58
62
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
59
- const previousTasks = queryClient.getQueryData(['tasks', boardId]);
63
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
64
+ const cacheSnapshot = snapshotBoardTaskCaches(queryClient, boardId);
60
65
  const taskIdSet = new Set(taskIds);
61
66
 
62
- queryClient.setQueryData(
63
- ['tasks', boardId],
64
- (old: Task[] | undefined) => {
65
- if (!old) return old;
66
- return old.map((t) => (taskIdSet.has(t.id) ? { ...t, priority } : t));
67
- }
68
- );
67
+ updateBoardTaskCaches(queryClient, boardId, (old) => {
68
+ if (!old) return old;
69
+ return old.map((t) => (taskIdSet.has(t.id) ? { ...t, priority } : t));
70
+ });
69
71
 
70
- return { previousTasks };
72
+ return cacheSnapshot;
71
73
  },
72
74
  onError: (error, _, context) => {
73
- if (context?.previousTasks) {
74
- queryClient.setQueryData(['tasks', boardId], context.previousTasks);
75
+ if (context) {
76
+ restoreBoardTaskCaches(queryClient, boardId, context);
75
77
  }
76
78
  console.error('Bulk priority update failed', error);
77
79
  toast.error(
@@ -84,22 +86,13 @@ export function useBulkUpdatePriority(
84
86
  data.failures.map((failure) => failure.taskId)
85
87
  );
86
88
 
87
- if (failedTaskIds.size > 0 && Array.isArray(context?.previousTasks)) {
88
- const previousTaskMap = new Map(
89
- (context.previousTasks as Task[]).map((task) => [task.id, task])
90
- );
91
-
92
- queryClient.setQueryData(
93
- ['tasks', boardId],
94
- (old: Task[] | undefined) => {
95
- if (!old) return old;
96
- return old.map((task) => {
97
- if (!failedTaskIds.has(task.id)) return task;
98
- return previousTaskMap.get(task.id) ?? task;
99
- });
100
- }
101
- );
102
- }
89
+ restoreFailedBoardTasks({
90
+ queryClient,
91
+ boardId,
92
+ previousTasks: context?.previousTasks,
93
+ previousFullTasks: context?.previousFullTasks,
94
+ failedTaskIds,
95
+ });
103
96
 
104
97
  for (const tid of data.succeededTaskIds) {
105
98
  broadcast?.('task:upsert', {
@@ -147,17 +140,17 @@ export function useBulkUpdateEstimation(
147
140
  }) => {
148
141
  const apiOptions = getInternalApiOptions();
149
142
 
150
- const result = await bulkWorkspaceTasks(
151
- wsId,
152
- {
153
- taskIds,
154
- operation: {
155
- type: 'update_fields',
156
- updates: { estimation_points: points },
157
- },
143
+ const result = await bulkWorkspaceTasksByEffectiveWorkspace({
144
+ queryClient,
145
+ boardId,
146
+ defaultWorkspaceId: wsId,
147
+ taskIds,
148
+ operation: {
149
+ type: 'update_fields',
150
+ updates: { estimation_points: points },
158
151
  },
159
- apiOptions
160
- );
152
+ options: apiOptions,
153
+ });
161
154
 
162
155
  if (result.successCount === 0) {
163
156
  throw new Error(
@@ -175,24 +168,22 @@ export function useBulkUpdateEstimation(
175
168
  },
176
169
  onMutate: async ({ points, taskIds }) => {
177
170
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
178
- const previousTasks = queryClient.getQueryData(['tasks', boardId]);
171
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
172
+ const cacheSnapshot = snapshotBoardTaskCaches(queryClient, boardId);
179
173
  const taskIdSet = new Set(taskIds);
180
174
 
181
- queryClient.setQueryData(
182
- ['tasks', boardId],
183
- (old: Task[] | undefined) => {
184
- if (!old) return old;
185
- return old.map((t) =>
186
- taskIdSet.has(t.id) ? { ...t, estimation_points: points } : t
187
- );
188
- }
189
- );
175
+ updateBoardTaskCaches(queryClient, boardId, (old) => {
176
+ if (!old) return old;
177
+ return old.map((t) =>
178
+ taskIdSet.has(t.id) ? { ...t, estimation_points: points } : t
179
+ );
180
+ });
190
181
 
191
- return { previousTasks };
182
+ return cacheSnapshot;
192
183
  },
193
184
  onError: (error, _, context) => {
194
- if (context?.previousTasks) {
195
- queryClient.setQueryData(['tasks', boardId], context.previousTasks);
185
+ if (context) {
186
+ restoreBoardTaskCaches(queryClient, boardId, context);
196
187
  }
197
188
  console.error('Bulk estimation update failed', error);
198
189
  toast.error(
@@ -205,22 +196,13 @@ export function useBulkUpdateEstimation(
205
196
  data.failures.map((failure) => failure.taskId)
206
197
  );
207
198
 
208
- if (failedTaskIds.size > 0 && Array.isArray(context?.previousTasks)) {
209
- const previousTaskMap = new Map(
210
- (context.previousTasks as Task[]).map((task) => [task.id, task])
211
- );
212
-
213
- queryClient.setQueryData(
214
- ['tasks', boardId],
215
- (old: Task[] | undefined) => {
216
- if (!old) return old;
217
- return old.map((task) => {
218
- if (!failedTaskIds.has(task.id)) return task;
219
- return previousTaskMap.get(task.id) ?? task;
220
- });
221
- }
222
- );
223
- }
199
+ restoreFailedBoardTasks({
200
+ queryClient,
201
+ boardId,
202
+ previousTasks: context?.previousTasks,
203
+ previousFullTasks: context?.previousFullTasks,
204
+ failedTaskIds,
205
+ });
224
206
 
225
207
  for (const tid of data.succeededTaskIds) {
226
208
  broadcast?.('task:upsert', {
@@ -270,17 +252,17 @@ export function useBulkUpdateDueDate(
270
252
  const newDate = resolveDueDatePreset(preset, weekStartsOn);
271
253
  const apiOptions = getInternalApiOptions();
272
254
 
273
- const result = await bulkWorkspaceTasks(
274
- wsId,
275
- {
276
- taskIds,
277
- operation: {
278
- type: 'update_fields',
279
- updates: { end_date: newDate },
280
- },
255
+ const result = await bulkWorkspaceTasksByEffectiveWorkspace({
256
+ queryClient,
257
+ boardId,
258
+ defaultWorkspaceId: wsId,
259
+ taskIds,
260
+ operation: {
261
+ type: 'update_fields',
262
+ updates: { end_date: newDate },
281
263
  },
282
- apiOptions
283
- );
264
+ options: apiOptions,
265
+ });
284
266
 
285
267
  if (result.successCount === 0) {
286
268
  throw new Error(
@@ -298,25 +280,23 @@ export function useBulkUpdateDueDate(
298
280
  },
299
281
  onMutate: async ({ preset, taskIds }) => {
300
282
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
301
- const previousTasks = queryClient.getQueryData(['tasks', boardId]);
283
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
284
+ const cacheSnapshot = snapshotBoardTaskCaches(queryClient, boardId);
302
285
  const newDate = resolveDueDatePreset(preset, weekStartsOn);
303
286
  const taskIdSet = new Set(taskIds);
304
287
 
305
- queryClient.setQueryData(
306
- ['tasks', boardId],
307
- (old: Task[] | undefined) => {
308
- if (!old) return old;
309
- return old.map((t) =>
310
- taskIdSet.has(t.id) ? { ...t, end_date: newDate } : t
311
- );
312
- }
313
- );
288
+ updateBoardTaskCaches(queryClient, boardId, (old) => {
289
+ if (!old) return old;
290
+ return old.map((t) =>
291
+ taskIdSet.has(t.id) ? { ...t, end_date: newDate } : t
292
+ );
293
+ });
314
294
 
315
- return { previousTasks };
295
+ return cacheSnapshot;
316
296
  },
317
297
  onError: (error, _, context) => {
318
- if (context?.previousTasks) {
319
- queryClient.setQueryData(['tasks', boardId], context.previousTasks);
298
+ if (context) {
299
+ restoreBoardTaskCaches(queryClient, boardId, context);
320
300
  }
321
301
  console.error('Bulk due date update failed', error);
322
302
  toast.error(
@@ -329,22 +309,13 @@ export function useBulkUpdateDueDate(
329
309
  data.failures.map((failure) => failure.taskId)
330
310
  );
331
311
 
332
- if (failedTaskIds.size > 0 && Array.isArray(context?.previousTasks)) {
333
- const previousTaskMap = new Map(
334
- (context.previousTasks as Task[]).map((task) => [task.id, task])
335
- );
336
-
337
- queryClient.setQueryData(
338
- ['tasks', boardId],
339
- (old: Task[] | undefined) => {
340
- if (!old) return old;
341
- return old.map((task) => {
342
- if (!failedTaskIds.has(task.id)) return task;
343
- return previousTaskMap.get(task.id) ?? task;
344
- });
345
- }
346
- );
347
- }
312
+ restoreFailedBoardTasks({
313
+ queryClient,
314
+ boardId,
315
+ previousTasks: context?.previousTasks,
316
+ previousFullTasks: context?.previousFullTasks,
317
+ failedTaskIds,
318
+ });
348
319
 
349
320
  for (const tid of data.succeededTaskIds) {
350
321
  broadcast?.('task:upsert', {
@@ -393,17 +364,17 @@ export function useBulkUpdateCustomDueDate(
393
364
  const newDate = date ? date.toISOString() : null;
394
365
  const apiOptions = getInternalApiOptions();
395
366
 
396
- const result = await bulkWorkspaceTasks(
397
- wsId,
398
- {
399
- taskIds,
400
- operation: {
401
- type: 'update_fields',
402
- updates: { end_date: newDate },
403
- },
367
+ const result = await bulkWorkspaceTasksByEffectiveWorkspace({
368
+ queryClient,
369
+ boardId,
370
+ defaultWorkspaceId: wsId,
371
+ taskIds,
372
+ operation: {
373
+ type: 'update_fields',
374
+ updates: { end_date: newDate },
404
375
  },
405
- apiOptions
406
- );
376
+ options: apiOptions,
377
+ });
407
378
 
408
379
  if (result.successCount === 0) {
409
380
  throw new Error(
@@ -421,25 +392,23 @@ export function useBulkUpdateCustomDueDate(
421
392
  },
422
393
  onMutate: async ({ date, taskIds }) => {
423
394
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
424
- const previousTasks = queryClient.getQueryData(['tasks', boardId]);
395
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
396
+ const cacheSnapshot = snapshotBoardTaskCaches(queryClient, boardId);
425
397
  const newDate = date ? date.toISOString() : null;
426
398
  const taskIdSet = new Set(taskIds);
427
399
 
428
- queryClient.setQueryData(
429
- ['tasks', boardId],
430
- (old: Task[] | undefined) => {
431
- if (!old) return old;
432
- return old.map((t) =>
433
- taskIdSet.has(t.id) ? { ...t, end_date: newDate } : t
434
- );
435
- }
436
- );
400
+ updateBoardTaskCaches(queryClient, boardId, (old) => {
401
+ if (!old) return old;
402
+ return old.map((t) =>
403
+ taskIdSet.has(t.id) ? { ...t, end_date: newDate } : t
404
+ );
405
+ });
437
406
 
438
- return { previousTasks };
407
+ return cacheSnapshot;
439
408
  },
440
409
  onError: (error, _, context) => {
441
- if (context?.previousTasks) {
442
- queryClient.setQueryData(['tasks', boardId], context.previousTasks);
410
+ if (context) {
411
+ restoreBoardTaskCaches(queryClient, boardId, context);
443
412
  }
444
413
  console.error('Bulk custom due date update failed', error);
445
414
  toast.error(
@@ -452,22 +421,13 @@ export function useBulkUpdateCustomDueDate(
452
421
  data.failures.map((failure) => failure.taskId)
453
422
  );
454
423
 
455
- if (failedTaskIds.size > 0 && Array.isArray(context?.previousTasks)) {
456
- const previousTaskMap = new Map(
457
- (context.previousTasks as Task[]).map((task) => [task.id, task])
458
- );
459
-
460
- queryClient.setQueryData(
461
- ['tasks', boardId],
462
- (old: Task[] | undefined) => {
463
- if (!old) return old;
464
- return old.map((task) => {
465
- if (!failedTaskIds.has(task.id)) return task;
466
- return previousTaskMap.get(task.id) ?? task;
467
- });
468
- }
469
- );
470
- }
424
+ restoreFailedBoardTasks({
425
+ queryClient,
426
+ boardId,
427
+ previousTasks: context?.previousTasks,
428
+ previousFullTasks: context?.previousFullTasks,
429
+ failedTaskIds,
430
+ });
471
431
 
472
432
  for (const tid of data.succeededTaskIds) {
473
433
  broadcast?.('task:upsert', {