@tuturuuu/ui 0.17.0 → 0.19.0

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 (643) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/biome.json +1 -1
  3. package/package.json +111 -152
  4. package/src/components/ui/calendar-app/calendar-client-page.tsx +30 -3
  5. package/src/components/ui/calendar-app/calendar-page-shell.tsx +60 -29
  6. package/src/components/ui/calendar-app/components/calendar-connections-manager-helpers.ts +70 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +516 -578
  8. package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +1 -0
  9. package/src/components/ui/calendar-app/components/calendar-sync-settings-panel.tsx +155 -0
  10. package/src/components/ui/calendar-app/components/merge-provider-calendars.test.ts +46 -0
  11. package/src/components/ui/calendar-app/components/merge-provider-calendars.ts +81 -0
  12. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +22 -129
  13. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +17 -3
  14. package/src/components/ui/chat/chat-agent-details-utils.tsx +2 -0
  15. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +17 -6
  16. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +35 -0
  17. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +17 -0
  18. package/src/components/ui/custom/nav-link.test.tsx +1 -0
  19. package/src/components/ui/custom/nav-link.tsx +4 -1
  20. package/src/components/ui/custom/notification-popover-client.tsx +3 -3
  21. package/src/components/ui/custom/settings-dialog-shell.tsx +23 -14
  22. package/src/components/ui/custom/statistic-card.tsx +3 -5
  23. package/src/components/ui/custom/structure.test.tsx +4 -0
  24. package/src/components/ui/custom/user-filters.tsx +4 -7
  25. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +1 -1
  26. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +1 -1
  27. package/src/components/ui/custom/workspace-select.tsx +38 -53
  28. package/src/components/ui/finance/analytics/category-spending-chart.test.tsx +3 -3
  29. package/src/components/ui/finance/budgets/budget-card.test.tsx +3 -3
  30. package/src/components/ui/finance/debts/debt-loan-card.test.tsx +3 -3
  31. package/src/components/ui/finance/debts/debt-loan-detail-cards.test.tsx +3 -3
  32. package/src/components/ui/finance/invoices/charts/invoice-totals-chart.test.tsx +3 -3
  33. package/src/components/ui/finance/invoices/columns.test.tsx +3 -3
  34. package/src/components/ui/finance/invoices/components/invoice-customer-select-card.tsx +1 -1
  35. package/src/components/ui/finance/shared/numbers-visibility-toggle.test.tsx +1 -1
  36. package/src/components/ui/finance/tags/tag-manager.test.tsx +2 -2
  37. package/src/components/ui/finance/transactions/categories/columns.test.tsx +5 -5
  38. package/src/components/ui/finance/transactions/columns.test.tsx +2 -2
  39. package/src/components/ui/finance/transactions/confidential-field.test.tsx +2 -2
  40. package/src/components/ui/finance/transactions/transaction-edit-dialog.test.tsx +2 -2
  41. package/src/components/ui/finance/transactions/transaction-statistics.test.tsx +3 -3
  42. package/src/components/ui/finance/wallets/form.test.tsx +3 -3
  43. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +10 -0
  44. package/src/components/ui/legacy/calendar/calendar-cell.tsx +1 -1
  45. package/src/components/ui/storefront/accent-button.tsx +4 -1
  46. package/src/components/ui/storefront/browse-panel.tsx +6 -1
  47. package/src/components/ui/storefront/cart-summary-parts.tsx +18 -4
  48. package/src/components/ui/storefront/cart-summary.tsx +22 -6
  49. package/src/components/ui/storefront/hero-panel.tsx +13 -17
  50. package/src/components/ui/storefront/index.ts +2 -0
  51. package/src/components/ui/storefront/listing-card.tsx +6 -4
  52. package/src/components/ui/storefront/product-detail.tsx +51 -21
  53. package/src/components/ui/storefront/product-dialog.tsx +8 -1
  54. package/src/components/ui/storefront/storefront-surface.test.tsx +79 -3
  55. package/src/components/ui/storefront/storefront-surface.tsx +151 -100
  56. package/src/components/ui/storefront/types.ts +3 -0
  57. package/src/components/ui/storefront/utils.ts +5 -1
  58. package/src/components/ui/tasks/progress/activity-heatmap.tsx +256 -0
  59. package/src/components/ui/tasks/progress/breakdown-bars.tsx +79 -0
  60. package/src/components/ui/tasks/progress/cumulative-progress-chart.tsx +185 -0
  61. package/src/components/ui/tasks/progress/daily-bar-chart.tsx +97 -0
  62. package/src/components/ui/tasks/progress/habit-gauge.tsx +92 -0
  63. package/src/components/ui/tasks/progress/mini-sparkline.tsx +56 -0
  64. package/src/components/ui/tasks/progress/weekday-distribution-chart.tsx +72 -0
  65. package/src/components/ui/text-editor/__tests__/task-mention-checkbox.test.ts +23 -6
  66. package/src/components/ui/text-editor/__tests__/upload-placeholder.test.ts +1 -10
  67. package/src/components/ui/text-editor/color-controls.tsx +1 -1
  68. package/src/components/ui/text-editor/editor.tsx +7 -1
  69. package/src/components/ui/text-editor/extensions.ts +4 -1
  70. package/src/components/ui/text-editor/mention-extension.tsx +33 -121
  71. package/src/globals.css +20 -0
  72. package/src/globals.test.ts +75 -0
  73. package/src/hooks/__tests__/use-calendar-sync.test.tsx +103 -1
  74. package/src/hooks/use-calendar-sync.tsx +51 -14
  75. package/src/lib/task-personal-external.ts +1 -1
  76. package/src/lib/workspace-actions.ts +15 -4
  77. package/src/components/ui/calendar-app/components/actions-dropdown.tsx +0 -269
  78. package/src/components/ui/calendar-app/components/auto-schedule-comprehensive-dialog.tsx +0 -220
  79. package/src/components/ui/calendar-app/components/calendar-header-actions.tsx +0 -67
  80. package/src/components/ui/calendar-app/components/experimental-feature-dialog.tsx +0 -56
  81. package/src/components/ui/calendar-app/components/habits-panel.tsx +0 -335
  82. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +0 -143
  83. package/src/components/ui/calendar-app/components/priority-dropdown.tsx +0 -113
  84. package/src/components/ui/calendar-app/components/priority-view.tsx +0 -954
  85. package/src/components/ui/calendar-app/components/quick-task-dialog.tsx +0 -335
  86. package/src/components/ui/calendar-app/components/quick-task-timer.tsx +0 -112
  87. package/src/components/ui/calendar-app/components/scheduling-dialog.tsx +0 -664
  88. package/src/components/ui/calendar-app/components/sidebar/calendar-sidebar.tsx +0 -128
  89. package/src/components/ui/calendar-app/components/sidebar/index.tsx +0 -1
  90. package/src/components/ui/calendar-app/components/smart-schedule-button.tsx +0 -117
  91. package/src/components/ui/calendar-app/components/smart-schedule-preview-dialog.tsx +0 -845
  92. package/src/components/ui/calendar-app/components/smart-schedule-preview-panel.tsx +0 -941
  93. package/src/components/ui/calendar-app/components/task-fetcher.ts +0 -51
  94. package/src/components/ui/calendar-app/components/task-form.tsx +0 -132
  95. package/src/components/ui/calendar-app/components/task-list-form.tsx +0 -84
  96. package/src/components/ui/calendar-app/components/task-scheduler-panel.tsx +0 -494
  97. package/src/components/ui/calendar-app/components/tasks-sidebar-content.tsx +0 -232
  98. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +0 -31
  99. package/src/components/ui/calendar-app/components/time-tracker/components/active-session-card.tsx +0 -113
  100. package/src/components/ui/calendar-app/components/time-tracker/components/index.ts +0 -3
  101. package/src/components/ui/calendar-app/components/time-tracker/components/session-card.tsx +0 -153
  102. package/src/components/ui/calendar-app/components/time-tracker/components/stats-cards.tsx +0 -58
  103. package/src/components/ui/calendar-app/components/time-tracker/dialogs/create-task-dialog.tsx +0 -190
  104. package/src/components/ui/calendar-app/components/time-tracker/dialogs/delete-session-dialog.tsx +0 -52
  105. package/src/components/ui/calendar-app/components/time-tracker/dialogs/edit-session-dialog.tsx +0 -173
  106. package/src/components/ui/calendar-app/components/time-tracker/dialogs/index.ts +0 -3
  107. package/src/components/ui/calendar-app/components/time-tracker/hooks/index.ts +0 -2
  108. package/src/components/ui/calendar-app/components/time-tracker/hooks/use-sessions.ts +0 -191
  109. package/src/components/ui/calendar-app/components/time-tracker/hooks/use-time-tracker.ts +0 -408
  110. package/src/components/ui/calendar-app/components/time-tracker/index.tsx +0 -907
  111. package/src/components/ui/calendar-app/components/time-tracker/tabs/history-tab.tsx +0 -28
  112. package/src/components/ui/calendar-app/components/time-tracker/tabs/index.ts +0 -2
  113. package/src/components/ui/calendar-app/components/time-tracker/tabs/recent-sessions-tab.tsx +0 -173
  114. package/src/components/ui/calendar-app/components/time-tracker/time-tracker-trigger.tsx +0 -50
  115. package/src/components/ui/calendar-app/components/time-tracker/utils/category-colors.ts +0 -38
  116. package/src/components/ui/calendar-app/components/time-tracker/utils/format-time.ts +0 -26
  117. package/src/components/ui/calendar-app/components/time-tracker/utils/index.ts +0 -2
  118. package/src/components/ui/calendar-app/components/time-tracker.tsx +0 -2040
  119. package/src/components/ui/custom/settings/task-settings.tsx +0 -543
  120. package/src/components/ui/custom/settings/task-sound-settings.test.tsx +0 -146
  121. package/src/components/ui/text-editor/__tests__/task-mention-chip.test.tsx +0 -476
  122. package/src/components/ui/text-editor/task-mention-chip.tsx +0 -1397
  123. package/src/components/ui/text-editor/task-mention-resolution.ts +0 -235
  124. package/src/components/ui/text-editor/task-summary-popover.tsx +0 -360
  125. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +0 -286
  126. package/src/components/ui/tu-do/boards/__tests__/quick-create-board-dialog.test.tsx +0 -52
  127. package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +0 -142
  128. package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +0 -169
  129. package/src/components/ui/tu-do/boards/analytics/GanttChart.tsx +0 -419
  130. package/src/components/ui/tu-do/boards/analytics/GanttControls.tsx +0 -123
  131. package/src/components/ui/tu-do/boards/analytics/GanttHeader.tsx +0 -48
  132. package/src/components/ui/tu-do/boards/analytics/GanttTimeline.tsx +0 -355
  133. package/src/components/ui/tu-do/boards/analytics/StatusDistribution.tsx +0 -130
  134. package/src/components/ui/tu-do/boards/analytics/TaskCreationAnalytics.tsx +0 -285
  135. package/src/components/ui/tu-do/boards/analytics/TaskDetailCard.tsx +0 -270
  136. package/src/components/ui/tu-do/boards/analytics/TaskGroup.tsx +0 -282
  137. package/src/components/ui/tu-do/boards/analytics/TaskWorkflowAnalytics.tsx +0 -280
  138. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +0 -231
  139. package/src/components/ui/tu-do/boards/board-selector.tsx +0 -301
  140. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +0 -69
  141. package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +0 -351
  142. package/src/components/ui/tu-do/boards/boardId/__tests__/list-actions.test.tsx +0 -199
  143. package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +0 -185
  144. package/src/components/ui/tu-do/boards/boardId/board-column-initial-load.test.ts +0 -38
  145. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +0 -922
  146. package/src/components/ui/tu-do/boards/boardId/board-text-utils.test.ts +0 -24
  147. package/src/components/ui/tu-do/boards/boardId/board-text-utils.ts +0 -11
  148. package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +0 -543
  149. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-external-workspaces.test.tsx +0 -392
  150. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-move.test.tsx +0 -252
  151. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.test.tsx +0 -57
  152. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.tsx +0 -106
  153. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-menu.tsx +0 -689
  154. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-custom-date-dialog.tsx +0 -82
  155. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-delete-dialog.tsx +0 -58
  156. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +0 -418
  157. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +0 -662
  158. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-assignees.ts +0 -375
  159. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-labels.ts +0 -278
  160. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-projects.ts +0 -297
  161. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations.ts +0 -12
  162. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +0 -462
  163. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-i18n.ts +0 -524
  164. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +0 -34
  165. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-utils.ts +0 -377
  166. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +0 -348
  167. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-personal-external-move.ts +0 -132
  168. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +0 -90
  169. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-applied-sets.ts +0 -79
  170. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +0 -112
  171. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-filtered-resources.ts +0 -54
  172. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +0 -80
  173. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.test.ts +0 -118
  174. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.ts +0 -160
  175. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +0 -65
  176. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +0 -117
  177. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/kanban-sort-helpers.ts +0 -76
  178. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +0 -367
  179. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-geometry.test.ts +0 -67
  180. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-geometry.ts +0 -94
  181. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +0 -150
  182. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-pending.test.ts +0 -37
  183. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-pending.ts +0 -42
  184. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-preview.test.ts +0 -318
  185. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-preview.ts +0 -260
  186. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-types.ts +0 -39
  187. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +0 -47
  188. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +0 -859
  189. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +0 -1659
  190. package/src/components/ui/tu-do/boards/boardId/kanban/kanban-column-collapse.ts +0 -16
  191. package/src/components/ui/tu-do/boards/boardId/kanban/kanban-constants.ts +0 -43
  192. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +0 -380
  193. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +0 -204
  194. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +0 -61
  195. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +0 -54
  196. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +0 -251
  197. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +0 -27
  198. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +0 -58
  199. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +0 -238
  200. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +0 -143
  201. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +0 -65
  202. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +0 -234
  203. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-column-width.ts +0 -30
  204. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +0 -1077
  205. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +0 -447
  206. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +0 -703
  207. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-tasks.test.ts +0 -267
  208. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-tasks.ts +0 -112
  209. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +0 -113
  210. package/src/components/ui/tu-do/boards/boardId/kanban/selection/use-keyboard-shortcuts.ts +0 -137
  211. package/src/components/ui/tu-do/boards/boardId/kanban/selection/use-multi-select.ts +0 -119
  212. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +0 -631
  213. package/src/components/ui/tu-do/boards/boardId/list-actions.tsx +0 -522
  214. package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-estimation-menu.test.tsx +0 -261
  215. package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +0 -708
  216. package/src/components/ui/tu-do/boards/boardId/menus/index.ts +0 -12
  217. package/src/components/ui/tu-do/boards/boardId/menus/task-assignees-menu.tsx +0 -176
  218. package/src/components/ui/tu-do/boards/boardId/menus/task-blocking-menu.tsx +0 -304
  219. package/src/components/ui/tu-do/boards/boardId/menus/task-due-date-menu.tsx +0 -201
  220. package/src/components/ui/tu-do/boards/boardId/menus/task-estimation-menu.tsx +0 -103
  221. package/src/components/ui/tu-do/boards/boardId/menus/task-labels-menu.tsx +0 -134
  222. package/src/components/ui/tu-do/boards/boardId/menus/task-move-menu.tsx +0 -66
  223. package/src/components/ui/tu-do/boards/boardId/menus/task-parent-menu.tsx +0 -228
  224. package/src/components/ui/tu-do/boards/boardId/menus/task-picker-popover.tsx +0 -300
  225. package/src/components/ui/tu-do/boards/boardId/menus/task-priority-menu.tsx +0 -178
  226. package/src/components/ui/tu-do/boards/boardId/menus/task-projects-menu.tsx +0 -143
  227. package/src/components/ui/tu-do/boards/boardId/menus/task-related-menu.tsx +0 -242
  228. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +0 -465
  229. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-utils.ts +0 -109
  230. package/src/components/ui/tu-do/boards/boardId/status-grouped-board.tsx +0 -533
  231. package/src/components/ui/tu-do/boards/boardId/status-section.tsx +0 -274
  232. package/src/components/ui/tu-do/boards/boardId/task-actions.tsx +0 -675
  233. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +0 -209
  234. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +0 -109
  235. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardCheckbox.tsx +0 -50
  236. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardDates.tsx +0 -123
  237. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardHeader.tsx +0 -97
  238. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardMetadata.tsx +0 -152
  239. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +0 -118
  240. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-archive-visibility.test.ts +0 -44
  241. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-archive-visibility.ts +0 -17
  242. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-checkbox-style.ts +0 -39
  243. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.test.ts +0 -104
  244. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +0 -123
  245. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.test.ts +0 -31
  246. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.ts +0 -9
  247. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.test.tsx +0 -124
  248. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.tsx +0 -88
  249. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-label-options.ts +0 -24
  250. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +0 -166
  251. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +0 -148
  252. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.test.ts +0 -113
  253. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.ts +0 -50
  254. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-visibility.test.ts +0 -41
  255. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-visibility.ts +0 -14
  256. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +0 -2889
  257. package/src/components/ui/tu-do/boards/boardId/task-card/task-scheduling-badge.tsx +0 -174
  258. package/src/components/ui/tu-do/boards/boardId/task-dialogs/BoardEstimationConfigDialog.tsx +0 -499
  259. package/src/components/ui/tu-do/boards/boardId/task-dialogs/TaskCustomDateDialog.tsx +0 -109
  260. package/src/components/ui/tu-do/boards/boardId/task-dialogs/TaskDeleteDialog.tsx +0 -96
  261. package/src/components/ui/tu-do/boards/boardId/task-dialogs/TaskNewLabelDialog.tsx +0 -178
  262. package/src/components/ui/tu-do/boards/boardId/task-dialogs/TaskNewProjectDialog.tsx +0 -127
  263. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +0 -152
  264. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +0 -996
  265. package/src/components/ui/tu-do/boards/boardId/task-form.tsx +0 -498
  266. package/src/components/ui/tu-do/boards/boardId/task-list-drag-preview.test.ts +0 -101
  267. package/src/components/ui/tu-do/boards/boardId/task-list-form.tsx +0 -91
  268. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +0 -535
  269. package/src/components/ui/tu-do/boards/boardId/task-parent-badge-state.test.ts +0 -64
  270. package/src/components/ui/tu-do/boards/boardId/task-parent-badge-state.ts +0 -31
  271. package/src/components/ui/tu-do/boards/boardId/task.tsx +0 -2
  272. package/src/components/ui/tu-do/boards/boardId/timeline/task-edit-dialog.tsx +0 -109
  273. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +0 -103
  274. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +0 -381
  275. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +0 -339
  276. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +0 -356
  277. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +0 -169
  278. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +0 -517
  279. package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +0 -164
  280. package/src/components/ui/tu-do/boards/boardId/timeline-board.test.tsx +0 -217
  281. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +0 -834
  282. package/src/components/ui/tu-do/boards/boards-list-skeleton.tsx +0 -62
  283. package/src/components/ui/tu-do/boards/columns.tsx +0 -159
  284. package/src/components/ui/tu-do/boards/copy-board-dialog.tsx +0 -156
  285. package/src/components/ui/tu-do/boards/enhanced-boards-view.tsx +0 -347
  286. package/src/components/ui/tu-do/boards/form.tsx +0 -264
  287. package/src/components/ui/tu-do/boards/quick-create-board-dialog.tsx +0 -75
  288. package/src/components/ui/tu-do/boards/row-actions.tsx +0 -397
  289. package/src/components/ui/tu-do/boards/share-section.tsx +0 -100
  290. package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +0 -192
  291. package/src/components/ui/tu-do/boards/workspace-projects-page.tsx +0 -155
  292. package/src/components/ui/tu-do/cycles/task-cycles-client.tsx +0 -753
  293. package/src/components/ui/tu-do/cycles/task-cycles-page.tsx +0 -103
  294. package/src/components/ui/tu-do/drafts/draft-card.tsx +0 -204
  295. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +0 -195
  296. package/src/components/ui/tu-do/drafts/drafts-page.tsx +0 -122
  297. package/src/components/ui/tu-do/drafts/task-drafts-page.tsx +0 -90
  298. package/src/components/ui/tu-do/estimates/client.tsx +0 -292
  299. package/src/components/ui/tu-do/estimates/edit-estimation-dialog.tsx +0 -374
  300. package/src/components/ui/tu-do/estimates/task-estimates-page.tsx +0 -57
  301. package/src/components/ui/tu-do/estimates/use-task-estimates.ts +0 -324
  302. package/src/components/ui/tu-do/habits/client.tsx +0 -263
  303. package/src/components/ui/tu-do/habits/habit-card.tsx +0 -183
  304. package/src/components/ui/tu-do/habits/habit-form-dialog.tsx +0 -701
  305. package/src/components/ui/tu-do/habits/habits-page.tsx +0 -27
  306. package/src/components/ui/tu-do/hooks/__tests__/useDraftPersistence.test.ts +0 -494
  307. package/src/components/ui/tu-do/hooks/__tests__/useTaskDialog.test.tsx +0 -127
  308. package/src/components/ui/tu-do/hooks/__tests__/useTaskDialogState.test.ts +0 -217
  309. package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +0 -572
  310. package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +0 -144
  311. package/src/components/ui/tu-do/hooks/useDraftPersistence.ts +0 -199
  312. package/src/components/ui/tu-do/hooks/useTaskCardRelationships.ts +0 -340
  313. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +0 -122
  314. package/src/components/ui/tu-do/hooks/useTaskDialogState.ts +0 -145
  315. package/src/components/ui/tu-do/hooks/useTaskFormState.ts +0 -206
  316. package/src/components/ui/tu-do/hooks/useTaskKeyboardShortcuts.ts +0 -58
  317. package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +0 -425
  318. package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +0 -454
  319. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +0 -889
  320. package/src/components/ui/tu-do/initiatives/task-initiatives-page.tsx +0 -116
  321. package/src/components/ui/tu-do/labels/client.tsx +0 -201
  322. package/src/components/ui/tu-do/labels/components/delete-label-dialog.tsx +0 -57
  323. package/src/components/ui/tu-do/labels/components/label-card.tsx +0 -86
  324. package/src/components/ui/tu-do/labels/components/label-dialog.tsx +0 -259
  325. package/src/components/ui/tu-do/labels/components/label-list.tsx +0 -83
  326. package/src/components/ui/tu-do/labels/hooks/use-task-labels.ts +0 -170
  327. package/src/components/ui/tu-do/labels/task-labels-page.tsx +0 -53
  328. package/src/components/ui/tu-do/labels/types.ts +0 -6
  329. package/src/components/ui/tu-do/logs/columns.tsx +0 -755
  330. package/src/components/ui/tu-do/logs/logs-client.tsx +0 -1006
  331. package/src/components/ui/tu-do/logs/logs-timeline.tsx +0 -2483
  332. package/src/components/ui/tu-do/logs/task-logs-page.tsx +0 -59
  333. package/src/components/ui/tu-do/my-tasks/__tests__/my-tasks-content.test.tsx +0 -408
  334. package/src/components/ui/tu-do/my-tasks/__tests__/use-my-tasks-query.test.ts +0 -469
  335. package/src/components/ui/tu-do/my-tasks/__tests__/use-my-tasks-state.test.ts +0 -1017
  336. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +0 -584
  337. package/src/components/ui/tu-do/my-tasks/ai-credit-indicator.tsx +0 -143
  338. package/src/components/ui/tu-do/my-tasks/board-selector-dialog.tsx +0 -239
  339. package/src/components/ui/tu-do/my-tasks/command-bar.tsx +0 -1351
  340. package/src/components/ui/tu-do/my-tasks/label-project-filter.tsx +0 -194
  341. package/src/components/ui/tu-do/my-tasks/my-task-context-menu.tsx +0 -356
  342. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +0 -328
  343. package/src/components/ui/tu-do/my-tasks/my-tasks-data-loader.tsx +0 -17
  344. package/src/components/ui/tu-do/my-tasks/my-tasks-filters.tsx +0 -258
  345. package/src/components/ui/tu-do/my-tasks/my-tasks-header.tsx +0 -121
  346. package/src/components/ui/tu-do/my-tasks/my-tasks-page.tsx +0 -40
  347. package/src/components/ui/tu-do/my-tasks/personal-placement-dialog.tsx +0 -188
  348. package/src/components/ui/tu-do/my-tasks/task-filter.tsx +0 -235
  349. package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +0 -832
  350. package/src/components/ui/tu-do/my-tasks/task-list.tsx +0 -258
  351. package/src/components/ui/tu-do/my-tasks/task-preview-dialog.tsx +0 -1597
  352. package/src/components/ui/tu-do/my-tasks/task-section.tsx +0 -261
  353. package/src/components/ui/tu-do/my-tasks/use-my-tasks-query.ts +0 -149
  354. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +0 -1037
  355. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +0 -474
  356. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +0 -118
  357. package/src/components/ui/tu-do/notes/note-list.tsx +0 -799
  358. package/src/components/ui/tu-do/notes/notes-content.tsx +0 -178
  359. package/src/components/ui/tu-do/notes/notes-page.tsx +0 -27
  360. package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +0 -60
  361. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +0 -156
  362. package/src/components/ui/tu-do/progress/task-progress-page.tsx +0 -382
  363. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +0 -303
  364. package/src/components/ui/tu-do/projects/components/index.ts +0 -11
  365. package/src/components/ui/tu-do/projects/components/project-actions-menu.tsx +0 -66
  366. package/src/components/ui/tu-do/projects/components/project-badges.tsx +0 -84
  367. package/src/components/ui/tu-do/projects/components/project-filter-menu.tsx +0 -170
  368. package/src/components/ui/tu-do/projects/components/project-grid-card.tsx +0 -198
  369. package/src/components/ui/tu-do/projects/components/project-list-item.tsx +0 -195
  370. package/src/components/ui/tu-do/projects/components/project-metrics.tsx +0 -87
  371. package/src/components/ui/tu-do/projects/components/project-progress-meter.tsx +0 -33
  372. package/src/components/ui/tu-do/projects/components/project-sort-menu.tsx +0 -71
  373. package/src/components/ui/tu-do/projects/components/projects-empty-state.tsx +0 -38
  374. package/src/components/ui/tu-do/projects/components/projects-loading-state.tsx +0 -26
  375. package/src/components/ui/tu-do/projects/components/projects-toolbar.tsx +0 -179
  376. package/src/components/ui/tu-do/projects/dialogs/create-project-dialog.tsx +0 -117
  377. package/src/components/ui/tu-do/projects/dialogs/edit-project-dialog.tsx +0 -125
  378. package/src/components/ui/tu-do/projects/dialogs/index.ts +0 -3
  379. package/src/components/ui/tu-do/projects/dialogs/manage-tasks-dialog.tsx +0 -205
  380. package/src/components/ui/tu-do/projects/hooks/index.ts +0 -2
  381. package/src/components/ui/tu-do/projects/hooks/use-project-filters.ts +0 -248
  382. package/src/components/ui/tu-do/projects/hooks/use-task-projects.ts +0 -238
  383. package/src/components/ui/tu-do/projects/projectId/components/__tests__/tasks-tab-layout.test.tsx +0 -169
  384. package/src/components/ui/tu-do/projects/projectId/components/documents-tab.tsx +0 -90
  385. package/src/components/ui/tu-do/projects/projectId/components/index.ts +0 -9
  386. package/src/components/ui/tu-do/projects/projectId/components/overview/description-card.tsx +0 -77
  387. package/src/components/ui/tu-do/projects/projectId/components/overview/linked-documents-card.tsx +0 -81
  388. package/src/components/ui/tu-do/projects/projectId/components/overview/linked-tasks-card.tsx +0 -85
  389. package/src/components/ui/tu-do/projects/projectId/components/overview/updates-card.tsx +0 -85
  390. package/src/components/ui/tu-do/projects/projectId/components/overview-tab.tsx +0 -35
  391. package/src/components/ui/tu-do/projects/projectId/components/project-configuration.tsx +0 -229
  392. package/src/components/ui/tu-do/projects/projectId/components/project-header.tsx +0 -115
  393. package/src/components/ui/tu-do/projects/projectId/components/project-lead-selector.tsx +0 -55
  394. package/src/components/ui/tu-do/projects/projectId/components/project-overview-context.tsx +0 -82
  395. package/src/components/ui/tu-do/projects/projectId/components/project-sidebar.tsx +0 -246
  396. package/src/components/ui/tu-do/projects/projectId/components/tasks-tab.tsx +0 -266
  397. package/src/components/ui/tu-do/projects/projectId/components/update-card.tsx +0 -162
  398. package/src/components/ui/tu-do/projects/projectId/components/updates-tab.tsx +0 -129
  399. package/src/components/ui/tu-do/projects/projectId/dialogs/index.ts +0 -1
  400. package/src/components/ui/tu-do/projects/projectId/dialogs/link-task-dialog.tsx +0 -114
  401. package/src/components/ui/tu-do/projects/projectId/hooks/index.ts +0 -4
  402. package/src/components/ui/tu-do/projects/projectId/hooks/use-animation-variants.ts +0 -68
  403. package/src/components/ui/tu-do/projects/projectId/hooks/use-project-form.ts +0 -214
  404. package/src/components/ui/tu-do/projects/projectId/hooks/use-project-updates.ts +0 -209
  405. package/src/components/ui/tu-do/projects/projectId/hooks/use-task-linking.ts +0 -101
  406. package/src/components/ui/tu-do/projects/projectId/task-project-detail-page-client.tsx +0 -93
  407. package/src/components/ui/tu-do/projects/projectId/task-project-detail-page.tsx +0 -78
  408. package/src/components/ui/tu-do/projects/projectId/task-project-detail.tsx +0 -367
  409. package/src/components/ui/tu-do/projects/projectId/types.ts +0 -51
  410. package/src/components/ui/tu-do/projects/task-projects-client.tsx +0 -270
  411. package/src/components/ui/tu-do/projects/task-projects-page.tsx +0 -97
  412. package/src/components/ui/tu-do/projects/types.ts +0 -62
  413. package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +0 -616
  414. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +0 -923
  415. package/src/components/ui/tu-do/providers/workspace-presence-provider.tsx +0 -88
  416. package/src/components/ui/tu-do/shared/AccessibleButton.tsx +0 -79
  417. package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +0 -143
  418. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +0 -358
  419. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +0 -598
  420. package/src/components/ui/tu-do/shared/__tests__/board-query-cache.test.ts +0 -219
  421. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +0 -374
  422. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +0 -1288
  423. package/src/components/ui/tu-do/shared/__tests__/create-list-dialog.test.tsx +0 -118
  424. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +0 -99
  425. package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +0 -147
  426. package/src/components/ui/tu-do/shared/__tests__/task-detail-page.test.tsx +0 -170
  427. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +0 -945
  428. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +0 -101
  429. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +0 -645
  430. package/src/components/ui/tu-do/shared/assignee-select.tsx +0 -592
  431. package/src/components/ui/tu-do/shared/board-broadcast-context.tsx +0 -45
  432. package/src/components/ui/tu-do/shared/board-client.tsx +0 -283
  433. package/src/components/ui/tu-do/shared/board-config-storage.ts +0 -94
  434. package/src/components/ui/tu-do/shared/board-header.tsx +0 -803
  435. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +0 -1134
  436. package/src/components/ui/tu-do/shared/board-query-cache.ts +0 -157
  437. package/src/components/ui/tu-do/shared/board-switcher.tsx +0 -322
  438. package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +0 -578
  439. package/src/components/ui/tu-do/shared/board-views.tsx +0 -1238
  440. package/src/components/ui/tu-do/shared/clear-menu-item.tsx +0 -34
  441. package/src/components/ui/tu-do/shared/create-list-dialog.tsx +0 -401
  442. package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +0 -245
  443. package/src/components/ui/tu-do/shared/cursor-overlay.tsx +0 -78
  444. package/src/components/ui/tu-do/shared/custom-date-picker/custom-date-picker-dialog.tsx +0 -157
  445. package/src/components/ui/tu-do/shared/description-overflow-warning-dialog.tsx +0 -68
  446. package/src/components/ui/tu-do/shared/edit-list-dialog.tsx +0 -383
  447. package/src/components/ui/tu-do/shared/empty-state-card.tsx +0 -46
  448. package/src/components/ui/tu-do/shared/estimation-mapping.test.ts +0 -32
  449. package/src/components/ui/tu-do/shared/estimation-mapping.ts +0 -74
  450. package/src/components/ui/tu-do/shared/estimation-utils.ts +0 -54
  451. package/src/components/ui/tu-do/shared/fade-setting-initializer.tsx +0 -61
  452. package/src/components/ui/tu-do/shared/label-chip.tsx +0 -46
  453. package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +0 -203
  454. package/src/components/ui/tu-do/shared/list-view-sorting.test.ts +0 -50
  455. package/src/components/ui/tu-do/shared/list-view-sorting.ts +0 -108
  456. package/src/components/ui/tu-do/shared/list-view.tsx +0 -1248
  457. package/src/components/ui/tu-do/shared/mention-system/__tests__/mention-system.test.ts +0 -250
  458. package/src/components/ui/tu-do/shared/mention-system/mention-menu.tsx +0 -217
  459. package/src/components/ui/tu-do/shared/mention-system/types.ts +0 -141
  460. package/src/components/ui/tu-do/shared/mention-system/use-mention-suggestions.ts +0 -244
  461. package/src/components/ui/tu-do/shared/progressive-loader-context.tsx +0 -45
  462. package/src/components/ui/tu-do/shared/recent-sidebar-events.ts +0 -58
  463. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +0 -651
  464. package/src/components/ui/tu-do/shared/relationship-task-identifier.ts +0 -14
  465. package/src/components/ui/tu-do/shared/slash-commands/__tests__/slash-commands.test.ts +0 -315
  466. package/src/components/ui/tu-do/shared/slash-commands/definitions.ts +0 -167
  467. package/src/components/ui/tu-do/shared/slash-commands/slash-command-menu.tsx +0 -114
  468. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +0 -51
  469. package/src/components/ui/tu-do/shared/sync-warning-dialog.tsx +0 -133
  470. package/src/components/ui/tu-do/shared/task-board-errors.ts +0 -19
  471. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +0 -82
  472. package/src/components/ui/tu-do/shared/task-cache-patches.ts +0 -394
  473. package/src/components/ui/tu-do/shared/task-detail-page.tsx +0 -176
  474. package/src/components/ui/tu-do/shared/task-detail-server-page.tsx +0 -37
  475. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +0 -596
  476. package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +0 -53
  477. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +0 -30
  478. package/src/components/ui/tu-do/shared/task-dialog-wrapper.tsx +0 -28
  479. package/src/components/ui/tu-do/shared/task-due-date-visibility.test.ts +0 -72
  480. package/src/components/ui/tu-do/shared/task-due-date-visibility.ts +0 -38
  481. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +0 -325
  482. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +0 -268
  483. package/src/components/ui/tu-do/shared/task-edit-dialog/components/mobile-floating-save-button.tsx +0 -46
  484. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +0 -198
  485. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +0 -129
  486. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +0 -358
  487. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +0 -496
  488. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +0 -545
  489. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-picker-panel.tsx +0 -220
  490. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +0 -165
  491. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-trigger-styles.ts +0 -69
  492. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +0 -180
  493. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +0 -218
  494. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-suggestion-menus.tsx +0 -148
  495. package/src/components/ui/tu-do/shared/task-edit-dialog/constants.ts +0 -18
  496. package/src/components/ui/tu-do/shared/task-edit-dialog/context-menu-guard.test.ts +0 -37
  497. package/src/components/ui/tu-do/shared/task-edit-dialog/description-diff-viewer.tsx +0 -1665
  498. package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.test.ts +0 -97
  499. package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.ts +0 -210
  500. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +0 -716
  501. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-dialog-close.test.ts +0 -218
  502. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-dialog-keyboard-shortcuts.test.ts +0 -140
  503. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-mutations.test.tsx +0 -231
  504. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-overrides.test.ts +0 -412
  505. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-realtime-sync.test.tsx +0 -237
  506. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-yjs-sync.test.ts +0 -190
  507. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-update-shared-task.test.ts +0 -107
  508. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.test.ts +0 -171
  509. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +0 -296
  510. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-editor-commands.ts +0 -314
  511. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-suggestion-menus.ts +0 -503
  512. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-change-detection.ts +0 -175
  513. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +0 -401
  514. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-dependencies.ts +0 -773
  515. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-dialog-close.ts +0 -193
  516. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-dialog-keyboard-shortcuts.ts +0 -365
  517. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +0 -231
  518. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-state.ts +0 -307
  519. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +0 -609
  520. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-overrides.ts +0 -134
  521. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +0 -270
  522. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +0 -212
  523. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +0 -554
  524. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-revert.ts +0 -140
  525. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.test.ts +0 -289
  526. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +0 -1473
  527. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-snapshot.ts +0 -59
  528. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-yjs-sync.ts +0 -224
  529. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-update-shared-task.ts +0 -60
  530. package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +0 -368
  531. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/components/clickable-task-item.tsx +0 -90
  532. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/components/index.ts +0 -4
  533. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/components/subtask-action-buttons.tsx +0 -140
  534. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/components/tab-button.tsx +0 -76
  535. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/components/task-relationship-action-buttons.tsx +0 -178
  536. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +0 -140
  537. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/index.ts +0 -35
  538. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +0 -70
  539. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +0 -81
  540. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +0 -87
  541. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/task-search-popover.tsx +0 -229
  542. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +0 -194
  543. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +0 -91
  544. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +0 -365
  545. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +0 -802
  546. package/src/components/ui/tu-do/shared/task-edit-dialog/task-delete-dialog.tsx +0 -104
  547. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.test.tsx +0 -63
  548. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.tsx +0 -218
  549. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-restore-banner.tsx +0 -83
  550. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.test.tsx +0 -120
  551. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.tsx +0 -180
  552. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.test.tsx +0 -68
  553. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +0 -239
  554. package/src/components/ui/tu-do/shared/task-edit-dialog/task-instances-section.tsx +0 -173
  555. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +0 -303
  556. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +0 -2213
  557. package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +0 -315
  558. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +0 -167
  559. package/src/components/ui/tu-do/shared/task-edit-dialog/types/pending-relationship.test.ts +0 -52
  560. package/src/components/ui/tu-do/shared/task-edit-dialog/types/pending-relationship.ts +0 -134
  561. package/src/components/ui/tu-do/shared/task-edit-dialog/types.ts +0 -64
  562. package/src/components/ui/tu-do/shared/task-edit-dialog/user-display.test.ts +0 -58
  563. package/src/components/ui/tu-do/shared/task-edit-dialog/user-display.ts +0 -45
  564. package/src/components/ui/tu-do/shared/task-edit-dialog/utils/inline-task-target-list.test.ts +0 -98
  565. package/src/components/ui/tu-do/shared/task-edit-dialog/utils/inline-task-target-list.ts +0 -39
  566. package/src/components/ui/tu-do/shared/task-edit-dialog/utils.test.ts +0 -504
  567. package/src/components/ui/tu-do/shared/task-edit-dialog/utils.ts +0 -582
  568. package/src/components/ui/tu-do/shared/task-edit-dialog/yjs-prosemirror-compat.test.ts +0 -65
  569. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +0 -3196
  570. package/src/components/ui/tu-do/shared/task-estimation-display.tsx +0 -52
  571. package/src/components/ui/tu-do/shared/task-estimation-picker.tsx +0 -226
  572. package/src/components/ui/tu-do/shared/task-filter.types.ts +0 -65
  573. package/src/components/ui/tu-do/shared/task-label-selector.tsx +0 -217
  574. package/src/components/ui/tu-do/shared/task-labels-display.test.tsx +0 -51
  575. package/src/components/ui/tu-do/shared/task-labels-display.tsx +0 -119
  576. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +0 -65
  577. package/src/components/ui/tu-do/shared/task-projects-display.tsx +0 -80
  578. package/src/components/ui/tu-do/shared/task-quick-create-target-list.ts +0 -20
  579. package/src/components/ui/tu-do/shared/task-resource-search-field.tsx +0 -49
  580. package/src/components/ui/tu-do/shared/task-resource-search-filters.ts +0 -27
  581. package/src/components/ui/tu-do/shared/task-row-actions-menu.test.tsx +0 -138
  582. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +0 -402
  583. package/src/components/ui/tu-do/shared/task-share-dialog/components/share-link-settings.tsx +0 -86
  584. package/src/components/ui/tu-do/shared/task-share-dialog/components/shares-list.tsx +0 -85
  585. package/src/components/ui/tu-do/shared/task-share-dialog/hooks/use-task-sharing.ts +0 -296
  586. package/src/components/ui/tu-do/shared/task-share-dialog.tsx +0 -144
  587. package/src/components/ui/tu-do/shared/task-sound-effects.test.ts +0 -189
  588. package/src/components/ui/tu-do/shared/task-sound-effects.tsx +0 -468
  589. package/src/components/ui/tu-do/shared/task-url.ts +0 -56
  590. package/src/components/ui/tu-do/shared/text-diff-viewer.tsx +0 -55
  591. package/src/components/ui/tu-do/shared/types.ts +0 -14
  592. package/src/components/ui/tu-do/shared/unsaved-changes-warning-dialog.tsx +0 -71
  593. package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +0 -312
  594. package/src/components/ui/tu-do/shared/user-avatar.tsx +0 -46
  595. package/src/components/ui/tu-do/shared/user-presence-avatars.tsx +0 -415
  596. package/src/components/ui/tu-do/shared/utils/translate-task-list-display-name.ts +0 -28
  597. package/src/components/ui/tu-do/tasks-route-context.tsx +0 -50
  598. package/src/components/ui/tu-do/templates/client.tsx +0 -505
  599. package/src/components/ui/tu-do/templates/marketplace/client.tsx +0 -487
  600. package/src/components/ui/tu-do/templates/marketplace/task-marketplace-page.tsx +0 -169
  601. package/src/components/ui/tu-do/templates/save-as-template-dialog.tsx +0 -381
  602. package/src/components/ui/tu-do/templates/task-template-api.ts +0 -142
  603. package/src/components/ui/tu-do/templates/task-template-card.tsx +0 -118
  604. package/src/components/ui/tu-do/templates/task-template-client.test.tsx +0 -52
  605. package/src/components/ui/tu-do/templates/task-template-client.tsx +0 -258
  606. package/src/components/ui/tu-do/templates/task-template-dialogs.test.tsx +0 -167
  607. package/src/components/ui/tu-do/templates/task-template-dialogs.tsx +0 -376
  608. package/src/components/ui/tu-do/templates/task-templates-hub.test.tsx +0 -114
  609. package/src/components/ui/tu-do/templates/task-templates-hub.tsx +0 -53
  610. package/src/components/ui/tu-do/templates/task-templates-page.tsx +0 -157
  611. package/src/components/ui/tu-do/templates/templateId/client.tsx +0 -674
  612. package/src/components/ui/tu-do/templates/templateId/edit-template-dialog.tsx +0 -300
  613. package/src/components/ui/tu-do/templates/templateId/share-template-dialog.tsx +0 -261
  614. package/src/components/ui/tu-do/templates/templateId/task-template-detail-page-client.tsx +0 -127
  615. package/src/components/ui/tu-do/templates/templateId/task-template-detail-page.tsx +0 -89
  616. package/src/components/ui/tu-do/templates/templateId/use-template-dialog.tsx +0 -165
  617. package/src/components/ui/tu-do/templates/types.ts +0 -53
  618. package/src/components/ui/tu-do/utils/__tests__/label-colors.test.ts +0 -43
  619. package/src/components/ui/tu-do/utils/__tests__/taskColorUtils.test.ts +0 -267
  620. package/src/components/ui/tu-do/utils/__tests__/taskConstants.test.ts +0 -178
  621. package/src/components/ui/tu-do/utils/__tests__/taskDateUtils.test.ts +0 -226
  622. package/src/components/ui/tu-do/utils/__tests__/weekDateUtils.test.ts +0 -182
  623. package/src/components/ui/tu-do/utils/label-colors.test.ts +0 -15
  624. package/src/components/ui/tu-do/utils/label-colors.ts +0 -59
  625. package/src/components/ui/tu-do/utils/taskColorUtils.ts +0 -116
  626. package/src/components/ui/tu-do/utils/taskConstants.ts +0 -102
  627. package/src/components/ui/tu-do/utils/taskDateUtils.ts +0 -81
  628. package/src/components/ui/tu-do/utils/taskPriorityUtils.tsx +0 -80
  629. package/src/components/ui/tu-do/utils/weekDateUtils.ts +0 -64
  630. package/src/hooks/__tests__/use-task-actions.test.tsx +0 -2098
  631. package/src/hooks/__tests__/useBoardPresence.test.tsx +0 -191
  632. package/src/hooks/__tests__/useBoardRealtime.test.tsx +0 -1036
  633. package/src/hooks/__tests__/useCursorTracking.test.tsx +0 -212
  634. package/src/hooks/task-actions-personal-external.ts +0 -313
  635. package/src/hooks/use-task-actions.ts +0 -1910
  636. package/src/hooks/useBoardPresence.ts +0 -364
  637. package/src/hooks/useBoardRealtime.ts +0 -428
  638. package/src/hooks/useBoardRealtime.types.ts +0 -56
  639. package/src/hooks/useBoardRealtimeEventHandler.ts +0 -452
  640. package/src/hooks/useCursorTracking.ts +0 -402
  641. package/src/hooks/usePresence.ts +0 -272
  642. package/src/hooks/useTaskUserRealtime.ts +0 -340
  643. /package/src/{components/ui/tu-do/shared → lib}/task-open-events.ts +0 -0
@@ -1,1910 +0,0 @@
1
- import { useQueryClient } from '@tanstack/react-query';
2
- import {
3
- resolveTaskProjectWorkspaceId,
4
- updateWorkspaceTask,
5
- } from '@tuturuuu/internal-api/tasks';
6
- import type { TaskPriority } from '@tuturuuu/types/primitives/Priority';
7
- import type { Task } from '@tuturuuu/types/primitives/Task';
8
- import type { TaskBoardStatus } from '@tuturuuu/types/primitives/TaskBoard';
9
- import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
10
- import { toast } from '@tuturuuu/ui/sonner';
11
- import {
12
- isTaskBoardCompletedStatus,
13
- isTaskBoardResolvedStatus,
14
- isTaskBoardTerminalStatus,
15
- } from '@tuturuuu/utils/task-list-status';
16
- import { addDays } from 'date-fns';
17
- import { useCallback } from 'react';
18
- import { invalidateKanbanDeadlineTasks } from '../components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query';
19
- import { useBoardBroadcast } from '../components/ui/tu-do/shared/board-broadcast-context';
20
- import {
21
- dispatchTaskSoundCue,
22
- type TaskSoundCue,
23
- } from '../components/ui/tu-do/shared/task-sound-effects';
24
- import {
25
- isPersonalExternalTask,
26
- moveExternalTaskToPersonalList,
27
- } from './task-actions-personal-external';
28
-
29
- interface UseTaskActionsProps {
30
- task?: Task; // Made optional to handle loading states
31
- boardId: string;
32
- workspaceId?: string;
33
- targetCompletionList?: TaskList | null;
34
- targetClosedList?: TaskList | null;
35
- availableLists: TaskList[];
36
- onUpdate: () => void;
37
- setIsLoading: (loading: boolean) => void;
38
- setMenuOpen: (open: boolean) => void;
39
- setCustomDateDialogOpen?: (open: boolean) => void;
40
- setDeleteDialogOpen?: (open: boolean) => void;
41
- setEstimationSaving?: (saving: boolean) => void;
42
- selectedTasks?: Set<string>; // For bulk operations
43
- isMultiSelectMode?: boolean;
44
- onClearSelection?: () => void; // Callback to clear selection after bulk operations
45
- taskId?: string; // Optional task ID for syncing individual task cache
46
- // Bulk operation functions from useBulkOperations hook
47
- bulkUpdateCustomDueDate?: (date: Date | null) => Promise<void>;
48
- }
49
-
50
- function dispatchTaskActionSound(cue: TaskSoundCue, count = 1) {
51
- dispatchTaskSoundCue({
52
- cue,
53
- count,
54
- intensity: count > 1 ? 1.15 : 1,
55
- });
56
- }
57
-
58
- function getMoveSoundCue(targetList?: TaskList | null): TaskSoundCue {
59
- return isTaskBoardCompletedStatus(targetList?.status) ||
60
- isTaskBoardTerminalStatus(targetList?.status)
61
- ? 'complete'
62
- : 'move';
63
- }
64
-
65
- export function useTaskActions({
66
- task,
67
- boardId,
68
- workspaceId,
69
- targetCompletionList,
70
- targetClosedList,
71
- availableLists,
72
- onUpdate,
73
- setIsLoading,
74
- setMenuOpen,
75
- setCustomDateDialogOpen,
76
- setDeleteDialogOpen,
77
- setEstimationSaving,
78
- selectedTasks,
79
- isMultiSelectMode,
80
- taskId,
81
- bulkUpdateCustomDueDate,
82
- }: UseTaskActionsProps) {
83
- const queryClient = useQueryClient();
84
- const broadcast = useBoardBroadcast();
85
- const invalidateDeadlineTasks = useCallback(() => {
86
- void invalidateKanbanDeadlineTasks(queryClient, boardId);
87
- }, [boardId, queryClient]);
88
-
89
- const resolveWorkspaceIdForTask = useCallback(
90
- async (taskRecord?: Task) => {
91
- const sourceWorkspaceId = taskRecord?.source_workspace_id ?? undefined;
92
- const taskWorkspaceId =
93
- (taskRecord as Task & { ws_id?: string })?.ws_id ??
94
- (
95
- taskRecord as Task & {
96
- task_lists?: { workspace_boards?: { ws_id?: string } };
97
- }
98
- )?.task_lists?.workspace_boards?.ws_id;
99
- const resolvedWorkspaceId =
100
- sourceWorkspaceId ??
101
- taskWorkspaceId ??
102
- (boardId
103
- ? await resolveTaskProjectWorkspaceId({ boardId }).catch(() => null)
104
- : null) ??
105
- workspaceId;
106
-
107
- if (!resolvedWorkspaceId) {
108
- throw new Error('Workspace ID is required');
109
- }
110
-
111
- return resolvedWorkspaceId;
112
- },
113
- [boardId, workspaceId]
114
- );
115
-
116
- const getWorkspaceId = useCallback(async () => {
117
- return resolveWorkspaceIdForTask(task);
118
- }, [resolveWorkspaceIdForTask, task]);
119
-
120
- const getEffectiveTaskIds = useCallback(
121
- (taskRecord?: Task) => {
122
- if (!taskRecord) return [];
123
-
124
- if (
125
- isMultiSelectMode &&
126
- selectedTasks &&
127
- selectedTasks.size > 1 &&
128
- selectedTasks.has(taskRecord.id)
129
- ) {
130
- return Array.from(selectedTasks);
131
- }
132
-
133
- return [taskRecord.id];
134
- },
135
- [isMultiSelectMode, selectedTasks]
136
- );
137
-
138
- const getExternalMoveOptions = useCallback((targetList: TaskList) => {
139
- const options: {
140
- sourceStatus?: TaskBoardStatus;
141
- placementPosition: 'top' | 'end';
142
- } = {
143
- placementPosition: isTaskBoardResolvedStatus(targetList.status)
144
- ? 'top'
145
- : 'end',
146
- };
147
-
148
- if (isTaskBoardTerminalStatus(targetList.status)) {
149
- options.sourceStatus = targetList.status;
150
- }
151
-
152
- return options;
153
- }, []);
154
-
155
- const markLocallyMutatedTask = useCallback((taskRecord: Task): Task => {
156
- return {
157
- ...(taskRecord as Task & { _localMutationAt?: number }),
158
- _localMutationAt: Date.now(),
159
- } as Task;
160
- }, []);
161
-
162
- const mergeLocallyMutatedTask = useCallback(
163
- (taskId: string, taskPatch: Partial<Task>) => {
164
- queryClient.setQueryData<Task[]>(['tasks', boardId], (current) => {
165
- if (!current) {
166
- return current;
167
- }
168
-
169
- return current.map((item) =>
170
- item.id === taskId
171
- ? markLocallyMutatedTask({
172
- ...item,
173
- ...taskPatch,
174
- } as Task)
175
- : item
176
- );
177
- });
178
- },
179
- [boardId, markLocallyMutatedTask, queryClient]
180
- );
181
-
182
- const rollbackTaskIds = useCallback(
183
- (previousTasks: Task[] | undefined, failedTaskIds: string[]) => {
184
- if (!previousTasks || failedTaskIds.length === 0) {
185
- return;
186
- }
187
-
188
- const previousTaskMap = new Map(
189
- previousTasks.map((item) => [item.id, item])
190
- );
191
-
192
- queryClient.setQueryData<Task[]>(['tasks', boardId], (current) => {
193
- if (!current) {
194
- return current;
195
- }
196
-
197
- return current.map((item) =>
198
- failedTaskIds.includes(item.id)
199
- ? previousTaskMap.get(item.id) || item
200
- : item
201
- );
202
- });
203
- },
204
- [boardId, queryClient]
205
- );
206
-
207
- const restoreDeletedTaskIds = useCallback(
208
- (
209
- previousTasks: Task[] | undefined,
210
- previousDeletedTasks: Task[] | undefined,
211
- failedTaskIds: string[]
212
- ) => {
213
- if (!previousTasks || failedTaskIds.length === 0) {
214
- return;
215
- }
216
-
217
- const failedTaskIdSet = new Set(failedTaskIds);
218
- const failedTasks = previousTasks.filter((item) =>
219
- failedTaskIdSet.has(item.id)
220
- );
221
-
222
- queryClient.setQueryData<Task[]>(['tasks', boardId], (current) => {
223
- if (!current) {
224
- return failedTasks;
225
- }
226
-
227
- const currentIds = new Set(current.map((item) => item.id));
228
- const restoredTasks = failedTasks.filter(
229
- (item) => !currentIds.has(item.id)
230
- );
231
- return [...restoredTasks, ...current];
232
- });
233
-
234
- queryClient.setQueryData<Task[]>(
235
- ['deleted-tasks', boardId],
236
- (current) => {
237
- if (!current) {
238
- return previousDeletedTasks;
239
- }
240
-
241
- return current.filter((item) => !failedTaskIdSet.has(item.id));
242
- }
243
- );
244
- },
245
- [boardId, queryClient]
246
- );
247
-
248
- const handleArchiveToggle = useCallback(async () => {
249
- if (!task || !onUpdate) return;
250
- setIsLoading(true);
251
-
252
- const newClosedState = !task.closed_at;
253
-
254
- // Store previous state for rollback
255
- const previousTasks = queryClient.getQueryData<Task[]>(['tasks', boardId]);
256
-
257
- if (
258
- isPersonalExternalTask(task) &&
259
- newClosedState &&
260
- targetCompletionList &&
261
- targetCompletionList.id !== task.list_id
262
- ) {
263
- try {
264
- await moveExternalTaskToPersonalList({
265
- boardId,
266
- markLocallyMutatedTask,
267
- queryClient,
268
- task,
269
- targetList: targetCompletionList,
270
- sourceStatus:
271
- targetCompletionList.status === 'closed' ? 'closed' : 'done',
272
- placementPosition: 'top',
273
- });
274
-
275
- toast.success('Task completed', {
276
- description: `Task marked as ${targetCompletionList.status === 'done' ? 'done' : 'closed'} and moved to ${targetCompletionList.name}`,
277
- });
278
- dispatchTaskActionSound('complete');
279
- invalidateDeadlineTasks();
280
- } catch (error) {
281
- console.error('Failed to complete external task:', error);
282
- toast.error('Error', {
283
- description: 'Failed to complete task. Please try again.',
284
- });
285
- } finally {
286
- setIsLoading(false);
287
- }
288
- return;
289
- }
290
-
291
- if (
292
- newClosedState &&
293
- targetCompletionList &&
294
- targetCompletionList.id !== task.list_id
295
- ) {
296
- const now = new Date().toISOString();
297
-
298
- try {
299
- // Optimistic update: move task to completion list and set closed_at
300
- queryClient.setQueryData(
301
- ['tasks', boardId],
302
- (old: Task[] | undefined) => {
303
- if (!old) return old;
304
- return old.map((t) =>
305
- t.id === task.id
306
- ? markLocallyMutatedTask({
307
- ...t,
308
- list_id: targetCompletionList.id,
309
- completed: targetCompletionList.status === 'done',
310
- closed_at: now,
311
- completed_at:
312
- targetCompletionList.status === 'done' ? now : null,
313
- } as Task)
314
- : t
315
- );
316
- }
317
- );
318
-
319
- // moveTask handles setting archived status based on target list
320
- const workspaceId = await getWorkspaceId();
321
-
322
- const { task: movedTask } = await updateWorkspaceTask(
323
- workspaceId,
324
- task.id,
325
- {
326
- list_id: targetCompletionList.id,
327
- }
328
- );
329
- mergeLocallyMutatedTask(task.id, {
330
- ...movedTask,
331
- list_id: targetCompletionList.id,
332
- closed_at: movedTask?.closed_at ?? now,
333
- completed_at:
334
- movedTask?.completed_at ??
335
- (targetCompletionList.status === 'done' ? now : undefined),
336
- });
337
- broadcast?.('task:upsert', {
338
- task: {
339
- id: task.id,
340
- list_id: targetCompletionList.id,
341
- completed_at: movedTask?.completed_at,
342
- closed_at: movedTask?.closed_at,
343
- },
344
- });
345
- invalidateDeadlineTasks();
346
-
347
- toast.success('Task completed', {
348
- description: `Task marked as done and moved to ${targetCompletionList.name}`,
349
- });
350
- dispatchTaskActionSound('complete');
351
- } catch (error) {
352
- // Rollback on error
353
- if (previousTasks) {
354
- queryClient.setQueryData(['tasks', boardId], previousTasks);
355
- }
356
- console.error('Failed to complete task:', error);
357
- toast.error('Error', {
358
- description: 'Failed to complete task. Please try again.',
359
- });
360
- } finally {
361
- setIsLoading(false);
362
- }
363
- } else {
364
- // Optimistic update for simple toggle
365
- queryClient.setQueryData(
366
- ['tasks', boardId],
367
- (old: Task[] | undefined) => {
368
- if (!old) return old;
369
- return old.map((t) =>
370
- t.id === task.id
371
- ? markLocallyMutatedTask({
372
- ...t,
373
- closed_at: newClosedState ? new Date().toISOString() : null,
374
- } as Task)
375
- : t
376
- );
377
- }
378
- );
379
-
380
- try {
381
- const workspaceId = await getWorkspaceId();
382
-
383
- const { task: updatedTask } = await updateWorkspaceTask(
384
- workspaceId,
385
- task.id,
386
- {
387
- closed_at: newClosedState ? new Date().toISOString() : null,
388
- }
389
- );
390
- mergeLocallyMutatedTask(task.id, {
391
- ...updatedTask,
392
- closed_at: updatedTask.closed_at,
393
- completed_at: updatedTask.completed_at,
394
- });
395
-
396
- broadcast?.('task:upsert', {
397
- task: {
398
- id: task.id,
399
- closed_at: updatedTask.closed_at,
400
- completed_at: updatedTask.completed_at,
401
- },
402
- });
403
- dispatchTaskActionSound(newClosedState ? 'complete' : 'update');
404
- invalidateDeadlineTasks();
405
- } catch (error) {
406
- // Rollback on error
407
- if (previousTasks) {
408
- queryClient.setQueryData(['tasks', boardId], previousTasks);
409
- }
410
- console.error('Failed to toggle task status:', error);
411
- toast.error('Error', {
412
- description: 'Failed to update task. Please try again.',
413
- });
414
- } finally {
415
- setIsLoading(false);
416
- }
417
- }
418
- }, [
419
- task?.id,
420
- task?.closed_at,
421
- task?.list_id,
422
- targetCompletionList,
423
- onUpdate,
424
- setIsLoading,
425
- queryClient,
426
- boardId,
427
- task,
428
- broadcast,
429
- getWorkspaceId,
430
- markLocallyMutatedTask,
431
- mergeLocallyMutatedTask,
432
- invalidateDeadlineTasks,
433
- ]);
434
-
435
- const handleMoveToCompletion = useCallback(async () => {
436
- if (!task || !targetCompletionList || !onUpdate) return;
437
-
438
- setIsLoading(true);
439
-
440
- const tasksToMove = getEffectiveTaskIds(task);
441
- const shouldBulkMove = tasksToMove.length > 1;
442
-
443
- // Store previous state for rollback
444
- const previousTasks = queryClient.getQueryData<Task[]>(['tasks', boardId]);
445
-
446
- try {
447
- if (isPersonalExternalTask(task) && !shouldBulkMove) {
448
- await moveExternalTaskToPersonalList({
449
- boardId,
450
- markLocallyMutatedTask,
451
- queryClient,
452
- task,
453
- targetList: targetCompletionList,
454
- sourceStatus:
455
- targetCompletionList.status === 'closed' ? 'closed' : 'done',
456
- placementPosition: 'top',
457
- });
458
-
459
- toast.success('Task completed', {
460
- description: `Task marked as ${targetCompletionList.status === 'done' ? 'done' : 'closed'} and moved to ${targetCompletionList.name}`,
461
- });
462
- dispatchTaskActionSound('complete');
463
- invalidateDeadlineTasks();
464
- return;
465
- }
466
-
467
- // Optimistic update: move tasks to completion list and set closed_at/completed_at
468
- queryClient.setQueryData(
469
- ['tasks', boardId],
470
- (old: Task[] | undefined) => {
471
- if (!old) return old;
472
- const now = new Date().toISOString();
473
- return old.map((t) =>
474
- tasksToMove.includes(t.id)
475
- ? markLocallyMutatedTask({
476
- ...t,
477
- list_id: targetCompletionList.id,
478
- closed_at: now,
479
- completed_at:
480
- targetCompletionList.status === 'done'
481
- ? now
482
- : t.completed_at,
483
- } as Task)
484
- : t
485
- );
486
- }
487
- );
488
-
489
- // Move tasks one by one to ensure triggers fire for each task
490
- let successCount = 0;
491
- const workspaceId = await getWorkspaceId();
492
- const failedTaskIds: string[] = [];
493
- const previousTaskMap = new Map(
494
- previousTasks?.map((item) => [item.id, item]) ?? []
495
- );
496
-
497
- for (const taskId of tasksToMove) {
498
- try {
499
- const { task: movedTask } = await updateWorkspaceTask(
500
- workspaceId,
501
- taskId,
502
- {
503
- list_id: targetCompletionList.id,
504
- }
505
- );
506
- broadcast?.('task:upsert', {
507
- task: {
508
- id: taskId,
509
- list_id: targetCompletionList.id,
510
- completed_at: movedTask?.completed_at,
511
- closed_at: movedTask?.closed_at,
512
- },
513
- });
514
- successCount++;
515
- } catch (error) {
516
- failedTaskIds.push(taskId);
517
- rollbackTaskIds(previousTasks, [taskId]);
518
- const previousTask = previousTaskMap.get(taskId);
519
- if (previousTask) {
520
- broadcast?.('task:upsert', { task: previousTask });
521
- }
522
- console.error(`Failed to move task ${taskId}:`, error);
523
- }
524
- }
525
- if (successCount === 0) throw new Error('Failed to move any tasks');
526
- invalidateDeadlineTasks();
527
-
528
- if (failedTaskIds.length > 0) {
529
- toast.warning('Partial completion update', {
530
- description: `${successCount}/${tasksToMove.length} tasks updated`,
531
- });
532
- dispatchTaskActionSound('complete', successCount);
533
- return;
534
- }
535
-
536
- const taskCount = successCount;
537
- toast.success(
538
- taskCount > 1 ? `${taskCount} tasks completed` : 'Task completed',
539
- {
540
- description:
541
- taskCount > 1
542
- ? `Tasks marked as ${targetCompletionList.status === 'done' ? 'done' : 'closed'}`
543
- : `Task marked as ${targetCompletionList.status === 'done' ? 'done' : 'closed'} and moved to ${targetCompletionList.name}`,
544
- }
545
- );
546
- dispatchTaskActionSound('complete', taskCount);
547
- } catch (error) {
548
- // Rollback on error
549
- if (previousTasks) {
550
- queryClient.setQueryData(['tasks', boardId], previousTasks);
551
- }
552
- console.error('Failed to move task to completion:', error);
553
- toast.error('Error', {
554
- description: 'Failed to complete task. Please try again.',
555
- });
556
- } finally {
557
- setIsLoading(false);
558
- setMenuOpen(false);
559
- }
560
- }, [
561
- targetCompletionList,
562
- onUpdate,
563
- task?.id,
564
- setIsLoading,
565
- setMenuOpen,
566
- getEffectiveTaskIds,
567
- queryClient,
568
- boardId,
569
- task,
570
- broadcast,
571
- getWorkspaceId,
572
- markLocallyMutatedTask,
573
- rollbackTaskIds,
574
- invalidateDeadlineTasks,
575
- ]);
576
-
577
- const handleMoveToClose = useCallback(async () => {
578
- if (!task || !targetClosedList || !onUpdate) return;
579
-
580
- setIsLoading(true);
581
-
582
- const tasksToMove = getEffectiveTaskIds(task);
583
- const shouldBulkMove = tasksToMove.length > 1;
584
-
585
- // Store previous state for rollback
586
- const previousTasks = queryClient.getQueryData<Task[]>(['tasks', boardId]);
587
-
588
- try {
589
- if (isPersonalExternalTask(task) && !shouldBulkMove) {
590
- await moveExternalTaskToPersonalList({
591
- boardId,
592
- markLocallyMutatedTask,
593
- queryClient,
594
- task,
595
- targetList: targetClosedList,
596
- sourceStatus: 'closed',
597
- placementPosition: 'top',
598
- });
599
-
600
- toast.success('Success', {
601
- description: 'Task marked as closed',
602
- });
603
- dispatchTaskActionSound('complete');
604
- invalidateDeadlineTasks();
605
- return;
606
- }
607
-
608
- // Optimistic update: move tasks to closed list and set closed_at
609
- queryClient.setQueryData(
610
- ['tasks', boardId],
611
- (old: Task[] | undefined) => {
612
- if (!old) return old;
613
- const now = new Date().toISOString();
614
- return old.map((t) =>
615
- tasksToMove.includes(t.id)
616
- ? markLocallyMutatedTask({
617
- ...t,
618
- list_id: targetClosedList.id,
619
- closed_at: now,
620
- } as Task)
621
- : t
622
- );
623
- }
624
- );
625
-
626
- // Move tasks one by one to ensure triggers fire for each task
627
- let successCount = 0;
628
- const workspaceId = await getWorkspaceId();
629
- const failedTaskIds: string[] = [];
630
- const previousTaskMap = new Map(
631
- previousTasks?.map((item) => [item.id, item]) ?? []
632
- );
633
-
634
- for (const taskId of tasksToMove) {
635
- try {
636
- const { task: movedTask } = await updateWorkspaceTask(
637
- workspaceId,
638
- taskId,
639
- {
640
- list_id: targetClosedList.id,
641
- }
642
- );
643
- broadcast?.('task:upsert', {
644
- task: {
645
- id: taskId,
646
- list_id: targetClosedList.id,
647
- completed_at: movedTask?.completed_at,
648
- closed_at: movedTask?.closed_at,
649
- },
650
- });
651
- successCount++;
652
- } catch (error) {
653
- failedTaskIds.push(taskId);
654
- rollbackTaskIds(previousTasks, [taskId]);
655
- const previousTask = previousTaskMap.get(taskId);
656
- if (previousTask) {
657
- broadcast?.('task:upsert', { task: previousTask });
658
- }
659
- console.error(`Failed to move task ${taskId}:`, error);
660
- }
661
- }
662
- if (successCount === 0) throw new Error('Failed to move any tasks');
663
- invalidateDeadlineTasks();
664
-
665
- if (failedTaskIds.length > 0) {
666
- toast.warning('Partial close update', {
667
- description: `${successCount}/${tasksToMove.length} tasks updated`,
668
- });
669
- dispatchTaskActionSound('complete', successCount);
670
- return;
671
- }
672
-
673
- const taskCount = successCount;
674
- toast.success('Success', {
675
- description:
676
- taskCount > 1
677
- ? `${taskCount} tasks marked as closed`
678
- : 'Task marked as closed',
679
- });
680
- dispatchTaskActionSound('complete', taskCount);
681
- } catch (error) {
682
- // Rollback on error
683
- if (previousTasks) {
684
- queryClient.setQueryData(['tasks', boardId], previousTasks);
685
- }
686
- console.error('Failed to move task to closed:', error);
687
- toast.error('Error', {
688
- description: 'Failed to close task. Please try again.',
689
- });
690
- } finally {
691
- setIsLoading(false);
692
- setMenuOpen(false);
693
- }
694
- }, [
695
- targetClosedList,
696
- onUpdate,
697
- task?.id,
698
- setIsLoading,
699
- setMenuOpen,
700
- getEffectiveTaskIds,
701
- queryClient,
702
- boardId,
703
- task,
704
- broadcast,
705
- getWorkspaceId,
706
- markLocallyMutatedTask,
707
- rollbackTaskIds,
708
- invalidateDeadlineTasks,
709
- ]);
710
-
711
- const handleDelete = useCallback(async () => {
712
- if (!task) return;
713
- setIsLoading(true);
714
-
715
- const tasksToDelete = getEffectiveTaskIds(task);
716
-
717
- const now = new Date().toISOString();
718
-
719
- // Store previous state for rollback
720
- const previousTasks = queryClient.getQueryData<Task[]>(['tasks', boardId]);
721
- const previousDeletedTasks = queryClient.getQueryData<Task[]>([
722
- 'deleted-tasks',
723
- boardId,
724
- ]);
725
-
726
- // Optimistic update: remove tasks from cache
727
- queryClient.setQueryData(['tasks', boardId], (old: Task[] | undefined) => {
728
- if (!old) return old;
729
- return old.filter((t) => !tasksToDelete.includes(t.id));
730
- });
731
-
732
- // Optimistic update: add tasks to recycle bin cache
733
- if (previousTasks?.length) {
734
- const deletedTaskRecords = tasksToDelete
735
- .map((id) => previousTasks.find((t) => t.id === id))
736
- .filter((t): t is Task => Boolean(t))
737
- .map((t) => ({ ...t, deleted_at: now }));
738
-
739
- if (deletedTaskRecords.length) {
740
- queryClient.setQueryData(
741
- ['deleted-tasks', boardId],
742
- (old: Task[] | undefined) => {
743
- const existingIds = new Set(old?.map((t) => t.id) ?? []);
744
- const toAdd = deletedTaskRecords.filter(
745
- (t) => !existingIds.has(t.id)
746
- );
747
- if (!old) return toAdd;
748
- return [...toAdd, ...old];
749
- }
750
- );
751
- }
752
- }
753
-
754
- try {
755
- let successCount = 0;
756
- const workspaceId = await getWorkspaceId();
757
- const failedTaskIds: string[] = [];
758
- const previousTaskMap = new Map(
759
- previousTasks?.map((item) => [item.id, item]) ?? []
760
- );
761
-
762
- for (const tid of tasksToDelete) {
763
- try {
764
- await updateWorkspaceTask(workspaceId, tid, {
765
- deleted: true,
766
- });
767
- broadcast?.('task:delete', { taskId: tid });
768
- successCount++;
769
- } catch (error) {
770
- failedTaskIds.push(tid);
771
- restoreDeletedTaskIds(previousTasks, previousDeletedTasks, [tid]);
772
- const previousTask = previousTaskMap.get(tid);
773
- if (previousTask) {
774
- broadcast?.('task:upsert', { task: previousTask });
775
- }
776
- console.error(`Failed to delete task ${tid}:`, error);
777
- }
778
- }
779
-
780
- if (successCount === 0) throw new Error('Failed to delete any tasks');
781
- invalidateDeadlineTasks();
782
-
783
- if (failedTaskIds.length > 0) {
784
- toast.warning('Partial delete update', {
785
- description: `${successCount}/${tasksToDelete.length} tasks deleted`,
786
- });
787
- dispatchTaskActionSound('delete', successCount);
788
- return;
789
- }
790
-
791
- const taskCount = tasksToDelete.length;
792
- toast.success('Success', {
793
- description:
794
- taskCount > 1
795
- ? `${taskCount} tasks deleted`
796
- : 'Task deleted successfully',
797
- });
798
- dispatchTaskActionSound('delete', taskCount);
799
-
800
- setDeleteDialogOpen?.(false);
801
- } catch (error) {
802
- // Rollback on error
803
- if (previousTasks) {
804
- queryClient.setQueryData(['tasks', boardId], previousTasks);
805
- }
806
- if (previousDeletedTasks) {
807
- queryClient.setQueryData(
808
- ['deleted-tasks', boardId],
809
- previousDeletedTasks
810
- );
811
- }
812
- console.error('Failed to delete task(s):', error);
813
- toast.error('Error', {
814
- description: 'Failed to delete task(s). Please try again.',
815
- });
816
- } finally {
817
- setIsLoading(false);
818
- }
819
- }, [
820
- task?.id,
821
- setIsLoading,
822
- setDeleteDialogOpen,
823
- getEffectiveTaskIds,
824
- queryClient,
825
- boardId,
826
- task,
827
- broadcast,
828
- getWorkspaceId,
829
- restoreDeletedTaskIds,
830
- invalidateDeadlineTasks,
831
- ]);
832
-
833
- const handleRemoveAllAssignees = useCallback(async () => {
834
- if (!task?.assignees || task.assignees.length === 0) return;
835
-
836
- setIsLoading(true);
837
-
838
- await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
839
- const previousTasks = queryClient.getQueryData<Task[]>(['tasks', boardId]);
840
-
841
- queryClient.setQueryData<Task[] | undefined>(
842
- ['tasks', boardId],
843
- (old: Task[] | undefined) => {
844
- if (!old) return old;
845
- return old.map((t) => {
846
- if (t.id === task.id) {
847
- return { ...t, assignees: [] };
848
- }
849
- return t;
850
- });
851
- }
852
- );
853
-
854
- try {
855
- const workspaceId = await getWorkspaceId();
856
-
857
- await updateWorkspaceTask(workspaceId, task.id, {
858
- assignee_ids: [],
859
- });
860
-
861
- broadcast?.('task:relations-changed', { taskId: task.id });
862
-
863
- toast.success('Success', {
864
- description: 'All assignees removed from task',
865
- });
866
- dispatchTaskActionSound('update');
867
- } catch (error) {
868
- queryClient.setQueryData(['tasks', boardId], previousTasks);
869
- console.error('Failed to remove all assignees:', error);
870
- toast.error('Error', {
871
- description: 'Failed to remove assignees. Please try again.',
872
- });
873
- } finally {
874
- setIsLoading(false);
875
- setMenuOpen(false);
876
- }
877
- }, [
878
- task?.id,
879
- task?.assignees,
880
- boardId,
881
- queryClient,
882
- setIsLoading,
883
- setMenuOpen,
884
- task,
885
- broadcast,
886
- getWorkspaceId,
887
- ]);
888
-
889
- const handleRemoveAssignee = useCallback(
890
- async (assigneeId: string) => {
891
- if (!task) return;
892
- setIsLoading(true);
893
-
894
- await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
895
- const previousTasks = queryClient.getQueryData<Task[]>([
896
- 'tasks',
897
- boardId,
898
- ]);
899
-
900
- queryClient.setQueryData<Task[] | undefined>(
901
- ['tasks', boardId],
902
- (old: Task[] | undefined) => {
903
- if (!old) return old;
904
- return old.map((t) => {
905
- if (t.id === task.id) {
906
- return {
907
- ...t,
908
- assignees:
909
- t.assignees?.filter((a) => a.id !== assigneeId) || [],
910
- };
911
- }
912
- return t;
913
- });
914
- }
915
- );
916
-
917
- try {
918
- const newIds =
919
- previousTasks
920
- ?.find((t) => t.id === task.id)
921
- ?.assignees?.map((a) => a.id)
922
- .filter(Boolean) ??
923
- task.assignees?.map((a) => a.id).filter(Boolean) ??
924
- [];
925
- const filteredIds = newIds.filter((id) => id !== assigneeId);
926
-
927
- const workspaceId = await getWorkspaceId();
928
-
929
- await updateWorkspaceTask(workspaceId, task.id, {
930
- assignee_ids: filteredIds,
931
- });
932
-
933
- const assignee = task.assignees?.find((a) => a.id === assigneeId);
934
- toast.success('Success', {
935
- description: `${assignee?.display_name || assignee?.email || 'Assignee'} removed from task`,
936
- });
937
- broadcast?.('task:relations-changed', { taskId: task.id });
938
- dispatchTaskActionSound('update');
939
- } catch (error) {
940
- queryClient.setQueryData(['tasks', boardId], previousTasks);
941
- console.error('Failed to remove assignee:', error);
942
- toast.error('Error', {
943
- description: 'Failed to remove assignee. Please try again.',
944
- });
945
- } finally {
946
- setIsLoading(false);
947
- setMenuOpen(false);
948
- }
949
- },
950
- [
951
- task,
952
- boardId,
953
- queryClient,
954
- setIsLoading,
955
- setMenuOpen,
956
- broadcast,
957
- getWorkspaceId,
958
- ]
959
- );
960
-
961
- const handleMoveToList = useCallback(
962
- async (targetListId: string) => {
963
- if (!task) return;
964
- if (targetListId === task.list_id) {
965
- setMenuOpen(false);
966
- return;
967
- }
968
-
969
- setIsLoading(true);
970
-
971
- const tasksToMove = getEffectiveTaskIds(task);
972
- const shouldBulkMove = tasksToMove.length > 1;
973
-
974
- // Store previous state for rollback
975
- const previousTasks = queryClient.getQueryData<Task[]>([
976
- 'tasks',
977
- boardId,
978
- ]);
979
-
980
- // Determine if target list represents resolved workflow state
981
- const targetList = availableLists.find(
982
- (list) => list.id === targetListId
983
- );
984
- const isTargetCompletedList = isTaskBoardCompletedStatus(
985
- targetList?.status
986
- );
987
- const isTargetTerminalList = isTaskBoardTerminalStatus(
988
- targetList?.status
989
- );
990
- const now = new Date().toISOString();
991
- const previousTaskMap = new Map(
992
- previousTasks?.map((item) => [item.id, item]) ?? []
993
- );
994
- const selectedTaskById = new Map<string, Task>();
995
-
996
- for (const taskId of tasksToMove) {
997
- const selectedTask = previousTaskMap.get(taskId) ?? task;
998
- if (selectedTask.id === taskId) {
999
- selectedTaskById.set(taskId, selectedTask);
1000
- }
1001
- }
1002
-
1003
- const externalTaskById = new Map(
1004
- targetList
1005
- ? [...selectedTaskById.values()]
1006
- .filter((selectedTask) => isPersonalExternalTask(selectedTask))
1007
- .map((selectedTask) => [selectedTask.id, selectedTask] as const)
1008
- : []
1009
- );
1010
- const localTaskIdsToMove = tasksToMove.filter(
1011
- (taskId) => !externalTaskById.has(taskId)
1012
- );
1013
-
1014
- if (isPersonalExternalTask(task) && !shouldBulkMove && targetList) {
1015
- try {
1016
- await moveExternalTaskToPersonalList({
1017
- boardId,
1018
- markLocallyMutatedTask,
1019
- queryClient,
1020
- task,
1021
- targetList,
1022
- ...getExternalMoveOptions(targetList),
1023
- });
1024
-
1025
- toast.success('Success', {
1026
- description: `Task moved to ${targetList.name || 'selected list'}`,
1027
- });
1028
- dispatchTaskActionSound(getMoveSoundCue(targetList));
1029
- invalidateDeadlineTasks();
1030
- } catch (error) {
1031
- console.error('Failed to move external task:', error);
1032
- toast.error('Error', {
1033
- description: 'Failed to move task. Please try again.',
1034
- });
1035
- } finally {
1036
- setIsLoading(false);
1037
- setMenuOpen(false);
1038
- }
1039
- return;
1040
- }
1041
-
1042
- try {
1043
- // Optimistic update: move tasks to target list
1044
- queryClient.setQueryData(
1045
- ['tasks', boardId],
1046
- (old: Task[] | undefined) => {
1047
- if (!old) return old;
1048
- return old.map((t) => {
1049
- if (localTaskIdsToMove.includes(t.id)) {
1050
- const currentList = availableLists.find(
1051
- (list) => list.id === t.list_id
1052
- );
1053
- const wasInCompletionList = isTaskBoardResolvedStatus(
1054
- currentList?.status
1055
- );
1056
- const isMovingToReview = targetList?.status === 'review';
1057
-
1058
- return markLocallyMutatedTask({
1059
- ...t,
1060
- list_id: targetListId,
1061
- completed: isTargetCompletedList,
1062
- closed_at: isTargetTerminalList
1063
- ? (t.closed_at ?? now)
1064
- : wasInCompletionList || isMovingToReview
1065
- ? null
1066
- : t.closed_at,
1067
- completed_at: isTargetCompletedList
1068
- ? (t.completed_at ?? now)
1069
- : wasInCompletionList || isMovingToReview
1070
- ? null
1071
- : t.completed_at,
1072
- } as Task);
1073
- }
1074
- return t;
1075
- });
1076
- }
1077
- );
1078
-
1079
- // Move tasks one by one to ensure triggers fire for each task
1080
- let successCount = 0;
1081
- const failedTaskIds: string[] = [];
1082
- for (const taskId of tasksToMove) {
1083
- try {
1084
- const externalTask = externalTaskById.get(taskId);
1085
-
1086
- if (externalTask && targetList) {
1087
- await moveExternalTaskToPersonalList({
1088
- boardId,
1089
- markLocallyMutatedTask,
1090
- queryClient,
1091
- task: externalTask,
1092
- targetList,
1093
- ...getExternalMoveOptions(targetList),
1094
- });
1095
- broadcast?.('task:upsert', {
1096
- task: {
1097
- id: taskId,
1098
- list_id: targetListId,
1099
- },
1100
- });
1101
- successCount++;
1102
- continue;
1103
- }
1104
-
1105
- const selectedTask = selectedTaskById.get(taskId);
1106
- const taskWorkspaceId =
1107
- await resolveWorkspaceIdForTask(selectedTask);
1108
- const { task: movedTask } = await updateWorkspaceTask(
1109
- taskWorkspaceId,
1110
- taskId,
1111
- {
1112
- list_id: targetListId,
1113
- }
1114
- );
1115
- broadcast?.('task:upsert', {
1116
- task: {
1117
- id: taskId,
1118
- list_id: targetListId,
1119
- completed_at: movedTask?.completed_at,
1120
- closed_at: movedTask?.closed_at,
1121
- },
1122
- });
1123
- successCount++;
1124
- } catch (error) {
1125
- failedTaskIds.push(taskId);
1126
- rollbackTaskIds(previousTasks, [taskId]);
1127
- const previousTask = previousTaskMap.get(taskId);
1128
- if (previousTask) {
1129
- broadcast?.('task:upsert', { task: previousTask });
1130
- }
1131
- console.error(`Failed to move task ${taskId}:`, error);
1132
- }
1133
- }
1134
- if (successCount === 0) throw new Error('Failed to move any tasks');
1135
- invalidateDeadlineTasks();
1136
-
1137
- if (failedTaskIds.length > 0) {
1138
- toast.warning('Partial move update', {
1139
- description: `${successCount}/${tasksToMove.length} tasks updated`,
1140
- });
1141
- dispatchTaskActionSound(getMoveSoundCue(targetList), successCount);
1142
- return;
1143
- }
1144
-
1145
- const taskCount = successCount;
1146
- toast.success('Success', {
1147
- description:
1148
- taskCount > 1
1149
- ? `${taskCount} tasks moved to ${targetList?.name || 'selected list'}`
1150
- : `Task moved to ${targetList?.name || 'selected list'}`,
1151
- });
1152
- dispatchTaskActionSound(getMoveSoundCue(targetList), taskCount);
1153
- } catch (error) {
1154
- // Rollback on error
1155
- if (previousTasks) {
1156
- queryClient.setQueryData(['tasks', boardId], previousTasks);
1157
- }
1158
- console.error('Failed to move task:', error);
1159
- toast.error('Error', {
1160
- description: 'Failed to move task. Please try again.',
1161
- });
1162
- } finally {
1163
- setIsLoading(false);
1164
- setMenuOpen(false);
1165
- }
1166
- },
1167
- [
1168
- task?.id,
1169
- task?.list_id,
1170
- availableLists,
1171
- setIsLoading,
1172
- setMenuOpen,
1173
- getEffectiveTaskIds,
1174
- queryClient,
1175
- boardId,
1176
- task,
1177
- broadcast,
1178
- getExternalMoveOptions,
1179
- markLocallyMutatedTask,
1180
- resolveWorkspaceIdForTask,
1181
- rollbackTaskIds,
1182
- invalidateDeadlineTasks,
1183
- ]
1184
- );
1185
-
1186
- const handleDueDateChange = useCallback(
1187
- async (days: number | null) => {
1188
- if (!task) return;
1189
- let newDate: string | null = null;
1190
- if (days !== null) {
1191
- const target = addDays(new Date(), days);
1192
- target.setHours(23, 59, 59, 999);
1193
- newDate = target.toISOString();
1194
- }
1195
-
1196
- const tasksToUpdate = getEffectiveTaskIds(task);
1197
-
1198
- setIsLoading(true);
1199
-
1200
- // Store previous state for rollback
1201
- const previousTasks = queryClient.getQueryData<Task[]>([
1202
- 'tasks',
1203
- boardId,
1204
- ]);
1205
-
1206
- try {
1207
- // Optimistic update
1208
- queryClient.setQueryData(
1209
- ['tasks', boardId],
1210
- (old: Task[] | undefined) => {
1211
- if (!old) return old;
1212
- return old.map((t) =>
1213
- tasksToUpdate.includes(t.id) ? { ...t, end_date: newDate } : t
1214
- );
1215
- }
1216
- );
1217
-
1218
- const succeededTaskIds: string[] = [];
1219
- const workspaceId = await getWorkspaceId();
1220
-
1221
- for (const taskId of tasksToUpdate) {
1222
- try {
1223
- await updateWorkspaceTask(workspaceId, taskId, {
1224
- end_date: newDate,
1225
- });
1226
- succeededTaskIds.push(taskId);
1227
- } catch (error) {
1228
- console.error(
1229
- `Failed to update due date for task ${taskId}:`,
1230
- error
1231
- );
1232
- }
1233
- }
1234
-
1235
- if (succeededTaskIds.length === 0) {
1236
- throw new Error('Failed to update any tasks');
1237
- }
1238
-
1239
- const failedTaskIds = tasksToUpdate.filter(
1240
- (taskId) => !succeededTaskIds.includes(taskId)
1241
- );
1242
-
1243
- if (failedTaskIds.length > 0 && previousTasks) {
1244
- const previousTaskMap = new Map(previousTasks.map((t) => [t.id, t]));
1245
- queryClient.setQueryData(
1246
- ['tasks', boardId],
1247
- (current: Task[] | undefined) => {
1248
- if (!current) return current;
1249
- return current.map((task) => {
1250
- if (!failedTaskIds.includes(task.id)) {
1251
- return task;
1252
- }
1253
-
1254
- return previousTaskMap.get(task.id) || task;
1255
- });
1256
- }
1257
- );
1258
- }
1259
-
1260
- for (const tid of succeededTaskIds) {
1261
- broadcast?.('task:upsert', {
1262
- task: { id: tid, end_date: newDate },
1263
- });
1264
- }
1265
- invalidateDeadlineTasks();
1266
-
1267
- if (failedTaskIds.length > 0) {
1268
- toast.warning('Partial due date update', {
1269
- description: `${succeededTaskIds.length}/${tasksToUpdate.length} tasks updated`,
1270
- });
1271
- dispatchTaskActionSound('update', succeededTaskIds.length);
1272
- } else {
1273
- const taskCount = tasksToUpdate.length;
1274
- toast.success('Due date updated', {
1275
- description:
1276
- taskCount > 1
1277
- ? `${taskCount} tasks updated`
1278
- : newDate
1279
- ? 'Due date set successfully'
1280
- : 'Due date removed',
1281
- });
1282
- dispatchTaskActionSound('update', taskCount);
1283
- }
1284
- } catch (error) {
1285
- console.error('Failed to update due date:', error);
1286
- // Rollback on error
1287
- if (previousTasks) {
1288
- queryClient.setQueryData(['tasks', boardId], previousTasks);
1289
- }
1290
- toast.error('Error', {
1291
- description: 'Failed to update due date. Please try again.',
1292
- });
1293
- } finally {
1294
- setIsLoading(false);
1295
- }
1296
- },
1297
- [
1298
- task?.id,
1299
- setIsLoading,
1300
- getWorkspaceId,
1301
- getEffectiveTaskIds,
1302
- queryClient,
1303
- boardId,
1304
- task,
1305
- broadcast,
1306
- invalidateDeadlineTasks,
1307
- ]
1308
- );
1309
-
1310
- const handlePriorityChange = useCallback(
1311
- async (newPriority: TaskPriority | null) => {
1312
- if (!task) return;
1313
- if (newPriority === task.priority && !isMultiSelectMode) return;
1314
-
1315
- const tasksToUpdate = getEffectiveTaskIds(task);
1316
-
1317
- setIsLoading(true);
1318
-
1319
- // Store previous state for rollback
1320
- const previousTasks = queryClient.getQueryData<Task[]>([
1321
- 'tasks',
1322
- boardId,
1323
- ]);
1324
-
1325
- try {
1326
- // Optimistic update
1327
- queryClient.setQueryData(
1328
- ['tasks', boardId],
1329
- (old: Task[] | undefined) => {
1330
- if (!old) return old;
1331
- return old.map((t) =>
1332
- tasksToUpdate.includes(t.id) ? { ...t, priority: newPriority } : t
1333
- );
1334
- }
1335
- );
1336
-
1337
- const succeededTaskIds: string[] = [];
1338
- const workspaceId = await getWorkspaceId();
1339
-
1340
- for (const taskId of tasksToUpdate) {
1341
- try {
1342
- await updateWorkspaceTask(workspaceId, taskId, {
1343
- priority: newPriority,
1344
- });
1345
- succeededTaskIds.push(taskId);
1346
- } catch (error) {
1347
- console.error(
1348
- `Failed to update priority for task ${taskId}:`,
1349
- error
1350
- );
1351
- }
1352
- }
1353
-
1354
- if (succeededTaskIds.length === 0) {
1355
- throw new Error('Failed to update any tasks');
1356
- }
1357
-
1358
- const failedTaskIds = tasksToUpdate.filter(
1359
- (taskId) => !succeededTaskIds.includes(taskId)
1360
- );
1361
-
1362
- if (failedTaskIds.length > 0 && previousTasks) {
1363
- const previousTaskMap = new Map(previousTasks.map((t) => [t.id, t]));
1364
- queryClient.setQueryData(
1365
- ['tasks', boardId],
1366
- (current: Task[] | undefined) => {
1367
- if (!current) return current;
1368
- return current.map((task) => {
1369
- if (!failedTaskIds.includes(task.id)) {
1370
- return task;
1371
- }
1372
-
1373
- return previousTaskMap.get(task.id) || task;
1374
- });
1375
- }
1376
- );
1377
- }
1378
-
1379
- for (const tid of succeededTaskIds) {
1380
- broadcast?.('task:upsert', {
1381
- task: { id: tid, priority: newPriority },
1382
- });
1383
- }
1384
-
1385
- if (failedTaskIds.length > 0) {
1386
- toast.warning('Partial priority update', {
1387
- description: `${succeededTaskIds.length}/${tasksToUpdate.length} tasks updated`,
1388
- });
1389
- dispatchTaskActionSound('update', succeededTaskIds.length);
1390
- } else {
1391
- const taskCount = tasksToUpdate.length;
1392
- toast.success('Priority updated', {
1393
- description:
1394
- taskCount > 1
1395
- ? `${taskCount} tasks updated`
1396
- : newPriority
1397
- ? 'Priority changed'
1398
- : 'Priority cleared',
1399
- });
1400
- dispatchTaskActionSound('update', taskCount);
1401
- }
1402
-
1403
- // Don't auto-clear selection - let user manually clear with "Clear" button
1404
- } catch (error) {
1405
- console.error('Failed to update priority:', error);
1406
- // Rollback on error
1407
- if (previousTasks) {
1408
- queryClient.setQueryData(['tasks', boardId], previousTasks);
1409
- }
1410
- toast.error('Error', {
1411
- description: 'Failed to update priority. Please try again.',
1412
- });
1413
- } finally {
1414
- setIsLoading(false);
1415
- }
1416
- },
1417
- [
1418
- task?.id,
1419
- task?.priority,
1420
- setIsLoading,
1421
- getWorkspaceId,
1422
- getEffectiveTaskIds,
1423
- queryClient,
1424
- boardId,
1425
- task,
1426
- broadcast,
1427
- isMultiSelectMode,
1428
- ]
1429
- );
1430
-
1431
- const updateEstimationPoints = useCallback(
1432
- async (points: number | null) => {
1433
- if (!task) return;
1434
- if (points === task.estimation_points && !isMultiSelectMode) return;
1435
-
1436
- // Get current tasks from cache to filter out ones that already have the target value
1437
- const currentTasks = queryClient.getQueryData<Task[]>(['tasks', boardId]);
1438
-
1439
- // Filter tasks that actually need updating (don't already have the target estimation)
1440
- const candidateTasks = getEffectiveTaskIds(task);
1441
-
1442
- const tasksToUpdate = currentTasks
1443
- ? candidateTasks.filter((taskId) => {
1444
- const taskData = currentTasks.find((t) => t.id === taskId);
1445
- return taskData?.estimation_points !== points;
1446
- })
1447
- : candidateTasks;
1448
-
1449
- // If no tasks actually need updating, skip
1450
- if (tasksToUpdate.length === 0) {
1451
- return;
1452
- }
1453
-
1454
- setEstimationSaving?.(true);
1455
-
1456
- // Store previous state for rollback
1457
- const previousTasks = currentTasks;
1458
- const rollbackFailedTasks = (failedIds: string[]) => {
1459
- if (!previousTasks || failedIds.length === 0) return;
1460
- const previousTaskMap = new Map(previousTasks.map((t) => [t.id, t]));
1461
- queryClient.setQueryData<Task[]>(['tasks', boardId], (current) => {
1462
- if (!current) return current;
1463
- return current.map((task: Task) =>
1464
- failedIds.includes(task.id)
1465
- ? previousTaskMap.get(task.id) || task
1466
- : task
1467
- );
1468
- });
1469
- };
1470
-
1471
- try {
1472
- // Optimistic update
1473
- queryClient.setQueryData(
1474
- ['tasks', boardId],
1475
- (old: Task[] | undefined) => {
1476
- if (!old) return old;
1477
- return old.map((t) =>
1478
- tasksToUpdate.includes(t.id)
1479
- ? { ...t, estimation_points: points }
1480
- : t
1481
- );
1482
- }
1483
- );
1484
-
1485
- const succeededIds: string[] = [];
1486
- const workspaceId = await getWorkspaceId();
1487
-
1488
- for (const taskId of tasksToUpdate) {
1489
- try {
1490
- await updateWorkspaceTask(workspaceId, taskId, {
1491
- estimation_points: points,
1492
- });
1493
- succeededIds.push(taskId);
1494
- } catch (error) {
1495
- console.error(
1496
- `Failed to update estimation for task ${taskId}:`,
1497
- error
1498
- );
1499
- }
1500
- }
1501
-
1502
- if (succeededIds.length === 0) {
1503
- throw new Error('Failed to update any tasks');
1504
- }
1505
-
1506
- const failedIds = tasksToUpdate.filter(
1507
- (taskId) => !succeededIds.includes(taskId)
1508
- );
1509
-
1510
- if (failedIds.length > 0) {
1511
- rollbackFailedTasks(failedIds);
1512
-
1513
- for (const tid of succeededIds) {
1514
- broadcast?.('task:upsert', {
1515
- task: { id: tid, estimation_points: points },
1516
- });
1517
- }
1518
-
1519
- toast.warning('Partial estimation update', {
1520
- description: `${succeededIds.length}/${tasksToUpdate.length} tasks updated`,
1521
- });
1522
- dispatchTaskActionSound('update', succeededIds.length);
1523
-
1524
- return;
1525
- }
1526
-
1527
- for (const tid of succeededIds) {
1528
- broadcast?.('task:upsert', {
1529
- task: { id: tid, estimation_points: points },
1530
- });
1531
- }
1532
-
1533
- const taskCount = succeededIds.length;
1534
- toast.success('Estimation updated', {
1535
- description:
1536
- taskCount > 1
1537
- ? `${taskCount} tasks updated`
1538
- : 'Estimation points updated successfully',
1539
- });
1540
- dispatchTaskActionSound('update', taskCount);
1541
-
1542
- return;
1543
- } catch (e: any) {
1544
- console.error('Failed to update estimation', e);
1545
- // Rollback on error
1546
- if (previousTasks) {
1547
- queryClient.setQueryData(['tasks', boardId], previousTasks);
1548
- }
1549
- toast.error('Failed to update estimation', {
1550
- description: e.message || 'Please try again',
1551
- });
1552
- } finally {
1553
- setEstimationSaving?.(false);
1554
- }
1555
- },
1556
- [
1557
- task?.id,
1558
- task?.estimation_points,
1559
- setEstimationSaving,
1560
- getWorkspaceId,
1561
- isMultiSelectMode,
1562
- getEffectiveTaskIds,
1563
- queryClient,
1564
- boardId,
1565
- task,
1566
- broadcast,
1567
- ]
1568
- );
1569
-
1570
- const handleCustomDateChange = useCallback(
1571
- async (date: Date | undefined) => {
1572
- if (!task) return;
1573
- let newDate: string | null = null;
1574
-
1575
- if (date) {
1576
- const selectedDate = new Date(date);
1577
-
1578
- if (
1579
- selectedDate.getHours() === 0 &&
1580
- selectedDate.getMinutes() === 0 &&
1581
- selectedDate.getSeconds() === 0 &&
1582
- selectedDate.getMilliseconds() === 0
1583
- ) {
1584
- selectedDate.setHours(23, 59, 59, 999);
1585
- }
1586
-
1587
- newDate = selectedDate.toISOString();
1588
- }
1589
-
1590
- setIsLoading(true);
1591
- setCustomDateDialogOpen?.(false); // Close dialog immediately when date is selected
1592
-
1593
- const shouldBulkUpdate = getEffectiveTaskIds(task).length > 1;
1594
-
1595
- if (shouldBulkUpdate && bulkUpdateCustomDueDate) {
1596
- // Use the centralized bulk update function from useBulkOperations
1597
- try {
1598
- await bulkUpdateCustomDueDate(date || null);
1599
- invalidateDeadlineTasks();
1600
- } catch (error) {
1601
- console.error('Bulk custom date update failed', error);
1602
- toast.error('Failed to update due date for selected tasks');
1603
- } finally {
1604
- setIsLoading(false);
1605
- }
1606
- } else {
1607
- // Single task update via workspace API
1608
- const previousTasks = queryClient.getQueryData<Task[]>([
1609
- 'tasks',
1610
- boardId,
1611
- ]);
1612
-
1613
- try {
1614
- queryClient.setQueryData(
1615
- ['tasks', boardId],
1616
- (old: Task[] | undefined) => {
1617
- if (!old) {
1618
- return old;
1619
- }
1620
-
1621
- return old.map((item) =>
1622
- item.id === task.id ? { ...item, end_date: newDate } : item
1623
- );
1624
- }
1625
- );
1626
-
1627
- const workspaceId = await getWorkspaceId();
1628
- await updateWorkspaceTask(workspaceId, task.id, {
1629
- end_date: newDate,
1630
- });
1631
-
1632
- broadcast?.('task:upsert', {
1633
- task: { id: task.id, end_date: newDate },
1634
- });
1635
- invalidateDeadlineTasks();
1636
-
1637
- toast.success('Due date updated', {
1638
- description: newDate
1639
- ? 'Custom due date set successfully'
1640
- : 'Due date removed',
1641
- });
1642
- dispatchTaskActionSound('update');
1643
- } catch (error) {
1644
- if (previousTasks) {
1645
- queryClient.setQueryData(['tasks', boardId], previousTasks);
1646
- }
1647
- console.error('Failed to update due date:', error);
1648
- toast.error('Failed to update due date. Please try again.');
1649
- } finally {
1650
- setIsLoading(false);
1651
- }
1652
- }
1653
- },
1654
- [
1655
- task?.id,
1656
- getWorkspaceId,
1657
- setIsLoading,
1658
- setCustomDateDialogOpen,
1659
- getEffectiveTaskIds,
1660
- bulkUpdateCustomDueDate,
1661
- task,
1662
- queryClient,
1663
- boardId,
1664
- broadcast,
1665
- invalidateDeadlineTasks,
1666
- ]
1667
- );
1668
-
1669
- const handleToggleAssignee = useCallback(
1670
- async (assigneeId: string) => {
1671
- if (!task) return;
1672
-
1673
- // CRITICAL: Get current task state from cache instead of stale prop
1674
- // This ensures we read the most up-to-date state after optimistic updates
1675
- const currentTask = taskId
1676
- ? ((queryClient.getQueryData(['task', taskId]) as Task | undefined) ??
1677
- task)
1678
- : task;
1679
-
1680
- const tasksToUpdate = getEffectiveTaskIds(currentTask);
1681
- const shouldBulkUpdate = tasksToUpdate.length > 1;
1682
-
1683
- setIsLoading(true);
1684
-
1685
- // Cancel any outgoing refetches
1686
- await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
1687
-
1688
- // Snapshot the previous value BEFORE optimistic update
1689
- const previousTasks = queryClient.getQueryData(['tasks', boardId]) as
1690
- | Task[]
1691
- | undefined;
1692
-
1693
- // Determine action: remove if ALL selected tasks have the assignee, add otherwise
1694
- // Use currentTask from cache, not stale task prop
1695
- let active = currentTask.assignees?.some((a) => a.id === assigneeId);
1696
-
1697
- if (shouldBulkUpdate && previousTasks) {
1698
- const selectedTasksData = previousTasks.filter((t) =>
1699
- tasksToUpdate.includes(t.id)
1700
- );
1701
- // Only mark as active (to remove) if ALL selected tasks have the assignee
1702
- active = selectedTasksData.every((t) =>
1703
- t.assignees?.some((a) => a.id === assigneeId)
1704
- );
1705
- }
1706
-
1707
- // Helper to get task from either board cache or individual cache
1708
- const getTaskState = (taskId: string): Task | undefined => {
1709
- // First try board cache
1710
- const fromBoardCache = previousTasks?.find((ct) => ct.id === taskId);
1711
- if (fromBoardCache) return fromBoardCache;
1712
-
1713
- // Fallback to individual task cache (for tasks not in board view)
1714
- if (taskId === currentTask.id) return currentTask;
1715
-
1716
- return undefined;
1717
- };
1718
-
1719
- // Pre-calculate which tasks actually need to change
1720
- const tasksNeedingAssignee = !active
1721
- ? tasksToUpdate.filter((taskId) => {
1722
- const t = getTaskState(taskId);
1723
- return !t?.assignees?.some((a) => a.id === assigneeId);
1724
- })
1725
- : [];
1726
-
1727
- const tasksToRemoveFrom = active
1728
- ? tasksToUpdate.filter((taskId) => {
1729
- const t = getTaskState(taskId);
1730
- return t?.assignees?.some((a) => a.id === assigneeId);
1731
- })
1732
- : [];
1733
-
1734
- // Get assignee details for optimistic update
1735
- let assigneeDetails = null;
1736
- if (!active) {
1737
- // First try from board cache
1738
- if (previousTasks) {
1739
- for (const t of previousTasks) {
1740
- const found = t.assignees?.find((a) => a.id === assigneeId);
1741
- if (found) {
1742
- assigneeDetails = found;
1743
- break;
1744
- }
1745
- }
1746
- }
1747
- // Fallback to current task cache
1748
- if (!assigneeDetails && currentTask.assignees) {
1749
- assigneeDetails =
1750
- currentTask.assignees.find((a) => a.id === assigneeId) || null;
1751
- }
1752
- }
1753
-
1754
- // Optimistically update the cache - only update tasks that actually change
1755
- queryClient.setQueryData(
1756
- ['tasks', boardId],
1757
- (old: Task[] | undefined) => {
1758
- if (!old) return old;
1759
- return old.map((t) => {
1760
- if (active && tasksToRemoveFrom.includes(t.id)) {
1761
- // Remove the assignee
1762
- return {
1763
- ...t,
1764
- assignees:
1765
- t.assignees?.filter((a) => a.id !== assigneeId) || [],
1766
- };
1767
- } else if (!active && tasksNeedingAssignee.includes(t.id)) {
1768
- // Add the assignee
1769
- return {
1770
- ...t,
1771
- assignees: [
1772
- ...(t.assignees || []),
1773
- assigneeDetails || {
1774
- id: assigneeId,
1775
- display_name: 'User',
1776
- email: '',
1777
- },
1778
- ],
1779
- };
1780
- }
1781
- return t;
1782
- });
1783
- }
1784
- );
1785
-
1786
- // CRITICAL: Also update the individual task cache if taskId is provided
1787
- // This ensures the chip menu's task cache stays in sync with the board cache
1788
- if (taskId) {
1789
- queryClient.setQueryData(['task', taskId], (old: Task | undefined) => {
1790
- if (!old) return old;
1791
- if (active && tasksToRemoveFrom.includes(taskId)) {
1792
- // Remove the assignee
1793
- return {
1794
- ...old,
1795
- assignees:
1796
- old.assignees?.filter((a) => a.id !== assigneeId) || [],
1797
- };
1798
- } else if (!active && tasksNeedingAssignee.includes(taskId)) {
1799
- // Add the assignee
1800
- return {
1801
- ...old,
1802
- assignees: [
1803
- ...(old.assignees || []),
1804
- assigneeDetails || {
1805
- id: assigneeId,
1806
- display_name: 'User',
1807
- email: '',
1808
- },
1809
- ],
1810
- };
1811
- }
1812
- return old;
1813
- });
1814
- }
1815
-
1816
- try {
1817
- const succeededTaskIds: string[] = [];
1818
- const targetTasks = active ? tasksToRemoveFrom : tasksNeedingAssignee;
1819
-
1820
- const workspaceId = await getWorkspaceId();
1821
-
1822
- for (const tid of targetTasks) {
1823
- const current = getTaskState(tid);
1824
- const existingIds =
1825
- current?.assignees
1826
- ?.map((assignee) => assignee.id)
1827
- .filter(Boolean) ?? [];
1828
- const newIds = active
1829
- ? existingIds.filter((id) => id !== assigneeId)
1830
- : Array.from(new Set([...existingIds, assigneeId]));
1831
-
1832
- try {
1833
- await updateWorkspaceTask(workspaceId, tid, {
1834
- assignee_ids: newIds,
1835
- });
1836
- succeededTaskIds.push(tid);
1837
- } catch (error) {
1838
- console.error(
1839
- `Failed to ${active ? 'remove' : 'add'} assignee from task ${tid}:`,
1840
- error
1841
- );
1842
- }
1843
- }
1844
-
1845
- if (targetTasks.length > 0 && succeededTaskIds.length === 0) {
1846
- throw new Error('Failed to update any tasks');
1847
- }
1848
-
1849
- for (const tid of succeededTaskIds) {
1850
- broadcast?.('task:relations-changed', { taskId: tid });
1851
- }
1852
-
1853
- const selectedAssignee = task.assignees?.find(
1854
- (a) => a.id === assigneeId
1855
- );
1856
- const assigneeName =
1857
- selectedAssignee?.display_name ||
1858
- selectedAssignee?.email ||
1859
- 'Assignee';
1860
- toast.success(active ? 'Assignee removed' : 'Assignee added', {
1861
- description:
1862
- succeededTaskIds.length > 1
1863
- ? `${succeededTaskIds.length} tasks updated`
1864
- : `${assigneeName} ${active ? 'removed' : 'added'} on task`,
1865
- });
1866
- if (succeededTaskIds.length > 0) {
1867
- dispatchTaskActionSound('update', succeededTaskIds.length);
1868
- }
1869
-
1870
- // Don't auto-clear selection - let user manually clear with "Clear" button
1871
- } catch (e: any) {
1872
- // Rollback on error
1873
- if (previousTasks) {
1874
- queryClient.setQueryData(['tasks', boardId], previousTasks);
1875
- }
1876
- console.error('Failed to toggle assignee:', e);
1877
- toast.error('Error', {
1878
- description: 'Failed to update assignee. Please try again.',
1879
- });
1880
- } finally {
1881
- setIsLoading(false);
1882
- }
1883
- },
1884
- [
1885
- task,
1886
- taskId,
1887
- boardId,
1888
- queryClient,
1889
- setIsLoading,
1890
- getEffectiveTaskIds,
1891
- broadcast,
1892
- getWorkspaceId,
1893
- ]
1894
- );
1895
-
1896
- return {
1897
- handleArchiveToggle,
1898
- handleMoveToCompletion,
1899
- handleMoveToClose,
1900
- handleDelete,
1901
- handleRemoveAllAssignees,
1902
- handleRemoveAssignee,
1903
- handleMoveToList,
1904
- handleDueDateChange,
1905
- handlePriorityChange,
1906
- updateEstimationPoints,
1907
- handleCustomDateChange,
1908
- handleToggleAssignee,
1909
- };
1910
- }