@tuturuuu/ui 0.8.0 → 0.10.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 (245) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/biome.json +1 -1
  3. package/package.json +74 -71
  4. package/src/components/ui/accordion.tsx +1 -1
  5. package/src/components/ui/breadcrumb.tsx +1 -1
  6. package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
  8. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
  9. package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
  10. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
  12. package/src/components/ui/calendar.tsx +1 -1
  13. package/src/components/ui/carousel.tsx +1 -1
  14. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
  15. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
  16. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
  17. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
  18. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
  19. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
  20. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
  21. package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
  22. package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
  23. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
  24. package/src/components/ui/checkbox.tsx +1 -1
  25. package/src/components/ui/color-picker.tsx +1 -1
  26. package/src/components/ui/command.tsx +1 -1
  27. package/src/components/ui/context-menu.tsx +5 -1
  28. package/src/components/ui/custom/__tests__/settings-dialog-search.test.ts +78 -0
  29. package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
  30. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  31. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +46 -1
  32. package/src/components/ui/custom/combobox.test.tsx +195 -0
  33. package/src/components/ui/custom/combobox.tsx +273 -156
  34. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  35. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  36. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  37. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  38. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  39. package/src/components/ui/custom/nav-link.test.tsx +165 -0
  40. package/src/components/ui/custom/nav-link.tsx +69 -11
  41. package/src/components/ui/custom/navigation.tsx +1 -0
  42. package/src/components/ui/custom/settings/task-settings.tsx +104 -0
  43. package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
  44. package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
  45. package/src/components/ui/custom/settings-dialog-search.ts +75 -0
  46. package/src/components/ui/custom/settings-dialog-shell.tsx +65 -28
  47. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  48. package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
  49. package/src/components/ui/custom/workspace-select.tsx +25 -19
  50. package/src/components/ui/dialog.test.tsx +52 -0
  51. package/src/components/ui/dialog.tsx +6 -2
  52. package/src/components/ui/dropdown-menu.tsx +5 -1
  53. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  54. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  55. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  56. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  57. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  58. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  59. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  60. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  61. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  62. package/src/components/ui/finance/invoices/utils.ts +3 -1
  63. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  64. package/src/components/ui/finance/transactions/form-types.ts +1 -0
  65. package/src/components/ui/finance/transactions/form.tsx +2 -0
  66. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  67. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  68. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  69. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  70. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  71. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  72. package/src/components/ui/finance/wallets/form.tsx +15 -4
  73. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  75. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  76. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  77. package/src/components/ui/input-otp.tsx +1 -1
  78. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  79. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  80. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  81. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  82. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  83. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  84. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  85. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  86. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  87. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  88. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  89. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  90. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  91. package/src/components/ui/navigation-menu.tsx +1 -1
  92. package/src/components/ui/pagination.tsx +1 -1
  93. package/src/components/ui/radio-group.tsx +1 -1
  94. package/src/components/ui/select.tsx +5 -1
  95. package/src/components/ui/sheet.tsx +1 -1
  96. package/src/components/ui/sidebar.tsx +1 -1
  97. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  98. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  99. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  100. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  101. package/src/components/ui/storefront/listing-card.tsx +1 -1
  102. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  103. package/src/components/ui/storefront/product-detail.tsx +1 -1
  104. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  105. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  106. package/src/components/ui/storefront/types.ts +4 -0
  107. package/src/components/ui/storefront/utils.ts +6 -0
  108. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  109. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  110. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  111. package/src/components/ui/text-editor/editor.tsx +69 -14
  112. package/src/components/ui/text-editor/extensions.ts +8 -2
  113. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  114. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  115. package/src/components/ui/toast.tsx +1 -1
  116. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +286 -0
  117. package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
  118. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  119. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +15 -226
  120. package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
  121. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +121 -39
  122. package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
  127. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  128. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
  129. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  131. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
  132. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  133. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  134. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  135. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  136. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  139. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  141. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  142. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  143. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +642 -5
  148. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +224 -15
  149. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +535 -53
  150. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +101 -33
  151. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +235 -113
  152. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +50 -5
  153. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +12 -2
  154. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +10 -1
  155. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  156. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
  157. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
  158. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +271 -36
  159. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  160. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  161. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +22 -0
  162. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
  163. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
  164. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
  165. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
  166. package/src/components/ui/tu-do/boards/form.tsx +1 -1
  167. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  168. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  169. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  170. package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
  171. package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
  172. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
  173. package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
  174. package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
  175. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  176. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  177. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  178. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  179. package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
  180. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
  181. package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
  182. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
  183. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
  184. package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
  185. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +141 -1
  186. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +377 -36
  187. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +374 -0
  188. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +419 -5
  189. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +38 -0
  190. package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
  191. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  192. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
  193. package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
  194. package/src/components/ui/tu-do/shared/board-client.tsx +15 -10
  195. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  196. package/src/components/ui/tu-do/shared/board-header.tsx +471 -975
  197. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  198. package/src/components/ui/tu-do/shared/board-switcher.tsx +244 -220
  199. package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
  200. package/src/components/ui/tu-do/shared/board-views.tsx +577 -85
  201. package/src/components/ui/tu-do/shared/list-view.tsx +246 -2
  202. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  203. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  204. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  205. package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
  206. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
  210. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  211. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
  212. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
  213. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
  214. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
  215. package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
  216. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
  217. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
  218. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
  219. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
  220. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
  221. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  222. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  223. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  224. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
  225. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
  226. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
  227. package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
  228. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  229. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +11 -1
  230. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  231. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
  232. package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
  233. package/src/declarations.d.ts +1 -0
  234. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  235. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  236. package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
  237. package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
  238. package/src/hooks/use-calendar-sync.tsx +247 -243
  239. package/src/hooks/use-calendar.tsx +323 -138
  240. package/src/hooks/use-task-actions.ts +24 -0
  241. package/src/hooks/use-user-workspace-config.ts +75 -0
  242. package/src/hooks/use-workspace-currency.ts +8 -3
  243. package/src/hooks/useBoardPresence.ts +364 -0
  244. package/src/hooks/useBoardRealtimeEventHandler.ts +45 -90
  245. package/src/lib/workspace-actions.ts +2 -6
@@ -6,7 +6,17 @@ import {
6
6
  import type { Task } from '@tuturuuu/types/primitives/Task';
7
7
  import { toast } from '@tuturuuu/ui/sonner';
8
8
  import { useState } from 'react';
9
- import { useBoardBroadcast } from '../shared/board-broadcast-context';
9
+ import {
10
+ getActiveBoardRefresh,
11
+ useBoardBroadcast,
12
+ } from '../shared/board-broadcast-context';
13
+ import {
14
+ getTaskFromVisibleCaches,
15
+ patchTaskInVisibleCaches,
16
+ restoreTasksFromVisibleCacheSnapshot,
17
+ restoreVisibleTaskCaches,
18
+ snapshotVisibleTaskCaches,
19
+ } from '../shared/task-cache-patches';
10
20
 
11
21
  function getInternalApiOptions() {
12
22
  if (typeof window === 'undefined') {
@@ -89,10 +99,14 @@ export function useTaskProjectManagement({
89
99
 
90
100
  // CRITICAL: Get current task state from cache instead of stale prop
91
101
  // This ensures we read the most up-to-date state after optimistic updates
92
- const currentTask = taskId
93
- ? ((queryClient.getQueryData(['task', taskId]) as Task | undefined) ??
94
- task)
95
- : task;
102
+ const canonicalTaskId = taskId ?? task.id;
103
+ const currentTask =
104
+ getTaskFromVisibleCaches({
105
+ queryClient,
106
+ boardId,
107
+ taskId: canonicalTaskId,
108
+ fallback: task,
109
+ }) ?? task;
96
110
 
97
111
  // Check if we're in multi-select mode with multiple tasks selected
98
112
  const shouldBulkUpdate =
@@ -107,11 +121,17 @@ export function useTaskProjectManagement({
107
121
 
108
122
  // Cancel any outgoing refetches
109
123
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
124
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
110
125
 
111
126
  // Snapshot the previous value BEFORE optimistic update
112
127
  const previousTasks = queryClient.getQueryData(['tasks', boardId]) as
113
128
  | Task[]
114
129
  | undefined;
130
+ const cacheSnapshot = snapshotVisibleTaskCaches(
131
+ queryClient,
132
+ boardId,
133
+ tasksToUpdate
134
+ );
115
135
 
116
136
  // Determine action: remove if ALL selected tasks have the project, add otherwise
117
137
  // Use currentTask from cache, not stale task prop
@@ -133,6 +153,13 @@ export function useTaskProjectManagement({
133
153
  const fromBoardCache = previousTasks?.find((ct) => ct.id === taskId);
134
154
  if (fromBoardCache) return fromBoardCache;
135
155
 
156
+ const fromVisibleCaches = getTaskFromVisibleCaches({
157
+ queryClient,
158
+ boardId,
159
+ taskId,
160
+ });
161
+ if (fromVisibleCaches) return fromVisibleCaches;
162
+
136
163
  // Fallback to individual task cache (for tasks not in board view)
137
164
  if (taskId === currentTask.id) return currentTask;
138
165
 
@@ -156,54 +183,44 @@ export function useTaskProjectManagement({
156
183
 
157
184
  // Get project details from workspace projects for optimistic update
158
185
  const project = workspaceProjects.find((p) => p.id === projectId);
186
+ const fallbackProject = project
187
+ ? {
188
+ id: project.id,
189
+ name: project.name,
190
+ status: project.status ?? 'unknown',
191
+ }
192
+ : {
193
+ id: projectId,
194
+ name: 'Unknown',
195
+ status: 'unknown',
196
+ };
159
197
 
160
198
  // Optimistically update the cache - only update tasks that actually change
161
- queryClient.setQueryData(['tasks', boardId], (old: Task[] | undefined) => {
162
- if (!old) return old;
163
- return old.map((t) => {
164
- if (active && tasksToRemoveFrom.includes(t.id)) {
165
- // Remove the project
166
- return {
167
- ...t,
168
- projects: t.projects?.filter((p: any) => p.id !== projectId) || [],
169
- };
170
- } else if (!active && tasksNeedingProject.includes(t.id)) {
171
- // Add the project
172
- return {
173
- ...t,
174
- projects: [
175
- ...(t.projects || []),
176
- project || { id: projectId, name: 'Unknown', status: 'unknown' },
177
- ],
178
- };
179
- }
180
- return t;
181
- });
182
- });
183
-
184
- // CRITICAL: Also update the individual task cache if taskId is provided
185
- // This ensures the chip menu's task cache stays in sync with the board cache
186
- if (taskId) {
187
- queryClient.setQueryData(['task', taskId], (old: Task | undefined) => {
188
- if (!old) return old;
189
- if (active && tasksToRemoveFrom.includes(taskId)) {
190
- // Remove the project
191
- return {
192
- ...old,
193
- projects:
194
- old.projects?.filter((p: any) => p.id !== projectId) || [],
195
- };
196
- } else if (!active && tasksNeedingProject.includes(taskId)) {
197
- // Add the project
199
+ for (const tid of active ? tasksToRemoveFrom : tasksNeedingProject) {
200
+ patchTaskInVisibleCaches({
201
+ queryClient,
202
+ boardId,
203
+ taskId: tid,
204
+ updater: (cachedTask) => {
205
+ if (active) {
206
+ return {
207
+ ...cachedTask,
208
+ projects:
209
+ cachedTask.projects?.filter(
210
+ (entry) => entry.id !== projectId
211
+ ) || [],
212
+ };
213
+ }
214
+
215
+ if (cachedTask.projects?.some((entry) => entry.id === projectId)) {
216
+ return cachedTask;
217
+ }
218
+
198
219
  return {
199
- ...old,
200
- projects: [
201
- ...(old.projects || []),
202
- project || { id: projectId, name: 'Unknown', status: 'unknown' },
203
- ],
220
+ ...cachedTask,
221
+ projects: [...(cachedTask.projects || []), fallbackProject],
204
222
  };
205
- }
206
- return old;
223
+ },
207
224
  });
208
225
  }
209
226
 
@@ -281,34 +298,19 @@ export function useTaskProjectManagement({
281
298
  (taskId) => !succeededTaskIds.includes(taskId)
282
299
  );
283
300
 
284
- if (failedTaskIds.length > 0 && previousTasks) {
285
- const previousTaskMap = new Map(previousTasks.map((t) => [t.id, t]));
286
- queryClient.setQueryData(
287
- ['tasks', boardId],
288
- (current: Task[] | undefined) => {
289
- if (!current) return current;
290
- return current.map((task) => {
291
- if (!failedTaskIds.includes(task.id)) {
292
- return task;
293
- }
294
-
295
- return previousTaskMap.get(task.id) || task;
296
- });
297
- }
298
- );
299
-
300
- if (taskId && failedTaskIds.includes(taskId)) {
301
- const previousTask = previousTaskMap.get(taskId);
302
- if (previousTask) {
303
- queryClient.setQueryData(['task', taskId], previousTask);
304
- }
305
- }
306
- }
301
+ restoreTasksFromVisibleCacheSnapshot({
302
+ queryClient,
303
+ snapshot: cacheSnapshot,
304
+ taskIds: failedTaskIds,
305
+ });
307
306
 
308
307
  // Broadcast relation changes for all affected tasks
309
308
  for (const tid of succeededTaskIds) {
310
309
  broadcast?.('task:relations-changed', { taskId: tid });
311
310
  }
311
+ if (succeededTaskIds.length > 0) {
312
+ getActiveBoardRefresh()?.({ invalidateTasks: false });
313
+ }
312
314
 
313
315
  if (failedTaskIds.length > 0) {
314
316
  toast.warning(
@@ -329,17 +331,7 @@ export function useTaskProjectManagement({
329
331
  // Don't auto-clear selection - let user manually clear with "Clear" button
330
332
  } catch (e: any) {
331
333
  // Rollback on error
332
- if (previousTasks) {
333
- queryClient.setQueryData(['tasks', boardId], previousTasks);
334
- if (taskId) {
335
- const previousTask = previousTasks.find(
336
- (entry) => entry.id === taskId
337
- );
338
- if (previousTask) {
339
- queryClient.setQueryData(['task', taskId], previousTask);
340
- }
341
- }
342
- }
334
+ restoreVisibleTaskCaches(queryClient, cacheSnapshot);
343
335
  console.error('Failed to toggle project:', e);
344
336
  toast.error('Error', {
345
337
  description: 'Failed to update project. Please try again.',
@@ -355,52 +347,55 @@ export function useTaskProjectManagement({
355
347
  const newProject =
356
348
  await createProjectMutation.mutateAsync(newProjectName);
357
349
  const canonicalTaskId = taskId ?? task.id;
350
+ const projectForCache = {
351
+ id: newProject.id,
352
+ name: newProject.name,
353
+ status: newProject.status ?? 'unknown',
354
+ };
358
355
 
359
356
  // Auto-apply the newly created project to this task
360
357
  let linkSucceeded = false;
361
- let previousTasks: Task[] | undefined;
358
+ let cacheSnapshot:
359
+ | ReturnType<typeof snapshotVisibleTaskCaches>
360
+ | undefined;
362
361
  try {
363
362
  // Cancel any outgoing refetches
364
363
  await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
364
+ await queryClient.cancelQueries({ queryKey: ['tasks-full', boardId] });
365
365
 
366
366
  // Snapshot the previous value
367
- previousTasks = queryClient.getQueryData(['tasks', boardId]);
367
+ cacheSnapshot = snapshotVisibleTaskCaches(queryClient, boardId, [
368
+ canonicalTaskId,
369
+ ]);
368
370
 
369
371
  // Optimistically update the cache
370
- queryClient.setQueryData(
371
- ['tasks', boardId],
372
- (old: Task[] | undefined) => {
373
- if (!old) return old;
374
- return old.map((t) => {
375
- if (t.id === canonicalTaskId) {
376
- return {
377
- ...t,
378
- projects: [...(t.projects || []), newProject],
379
- };
380
- }
381
- return t;
382
- });
383
- }
384
- );
385
-
386
- // CRITICAL: Also update individual task cache if taskId is provided
387
- if (canonicalTaskId) {
388
- queryClient.setQueryData(
389
- ['task', canonicalTaskId],
390
- (old: Task | undefined) => {
391
- if (!old) return old;
392
- return {
393
- ...old,
394
- projects: [...(old.projects || []), newProject],
395
- };
372
+ patchTaskInVisibleCaches({
373
+ queryClient,
374
+ boardId,
375
+ taskId: canonicalTaskId,
376
+ updater: (cachedTask) => {
377
+ if (
378
+ cachedTask.projects?.some(
379
+ (project) => project.id === newProject.id
380
+ )
381
+ ) {
382
+ return cachedTask;
396
383
  }
397
- );
398
- }
384
+
385
+ return {
386
+ ...cachedTask,
387
+ projects: [...(cachedTask.projects || []), projectForCache],
388
+ };
389
+ },
390
+ });
399
391
 
400
392
  const taskState =
401
- (queryClient.getQueryData(['task', canonicalTaskId]) as
402
- | Task
403
- | undefined) ?? task;
393
+ getTaskFromVisibleCaches({
394
+ queryClient,
395
+ boardId,
396
+ taskId: canonicalTaskId,
397
+ fallback: task,
398
+ }) ?? task;
404
399
  const nextProjectIds = [
405
400
  ...new Set([
406
401
  ...(taskState.projects ?? []).map((entry) => entry.id),
@@ -418,21 +413,19 @@ export function useTaskProjectManagement({
418
413
  );
419
414
  linkSucceeded = true;
420
415
  } catch (applyErr: any) {
421
- queryClient.setQueryData(['tasks', boardId], previousTasks);
416
+ if (cacheSnapshot) {
417
+ restoreVisibleTaskCaches(queryClient, cacheSnapshot);
418
+ }
422
419
  toast.error(
423
420
  'The project was created but could not be attached to the task. Refresh and try manually.'
424
421
  );
425
- if (canonicalTaskId) {
426
- queryClient.invalidateQueries({
427
- queryKey: ['task', canonicalTaskId],
428
- });
429
- }
430
422
  console.error('Failed to auto-apply new project', applyErr);
431
423
  }
432
424
 
433
425
  // Only show success toast and reset form if link succeeded
434
426
  if (linkSucceeded) {
435
427
  broadcast?.('task:relations-changed', { taskId: canonicalTaskId });
428
+ getActiveBoardRefresh()?.({ invalidateTasks: false });
436
429
 
437
430
  // Reset form and close dialog
438
431
  setNewProjectName('');
@@ -13,6 +13,15 @@ import {
13
13
  Trash2,
14
14
  User,
15
15
  } from '@tuturuuu/icons';
16
+ import {
17
+ createWorkspaceTaskInitiative,
18
+ deleteWorkspaceTaskInitiative,
19
+ linkWorkspaceTaskInitiativeProject,
20
+ listWorkspaceTaskInitiatives,
21
+ listWorkspaceTaskProjects,
22
+ unlinkWorkspaceTaskInitiativeProject,
23
+ updateWorkspaceTaskInitiative,
24
+ } from '@tuturuuu/internal-api/tasks';
16
25
  import { Badge } from '@tuturuuu/ui/badge';
17
26
  import { Button } from '@tuturuuu/ui/button';
18
27
  import {
@@ -98,6 +107,12 @@ const STATUS_BADGE_CLASS: Record<InitiativeStatus, string> = {
98
107
  cancelled: 'bg-dynamic-red/15 text-dynamic-red border-transparent',
99
108
  };
100
109
 
110
+ function getBrowserInternalApiOptions() {
111
+ return typeof window !== 'undefined'
112
+ ? { baseUrl: window.location.origin }
113
+ : undefined;
114
+ }
115
+
101
116
  export function TaskInitiativesClient({
102
117
  wsId,
103
118
  initialInitiatives,
@@ -136,16 +151,8 @@ export function TaskInitiativesClient({
136
151
  refetch: refetchInitiatives,
137
152
  } = useQuery<TaskInitiative[]>({
138
153
  queryKey: ['workspace', wsId, 'task-initiatives'],
139
- queryFn: async () => {
140
- const response = await fetch(
141
- `/api/v1/workspaces/${wsId}/task-initiatives`,
142
- { cache: 'no-store' }
143
- );
144
- if (!response.ok) {
145
- throw new Error(t('errors.fetch_initiatives'));
146
- }
147
- return response.json();
148
- },
154
+ queryFn: () =>
155
+ listWorkspaceTaskInitiatives(wsId, getBrowserInternalApiOptions()),
149
156
  initialData: initialInitiatives,
150
157
  staleTime: 30_000,
151
158
  });
@@ -157,20 +164,12 @@ export function TaskInitiativesClient({
157
164
  } = useQuery<TaskProjectOption[]>({
158
165
  queryKey: ['workspace', wsId, 'task-projects-for-initiatives'],
159
166
  queryFn: async () => {
160
- const response = await fetch(`/api/v1/workspaces/${wsId}/task-projects`, {
161
- cache: 'no-store',
162
- });
163
- if (!response.ok) {
164
- throw new Error(t('errors.fetch_projects'));
165
- }
166
- const rawProjects = await response.json();
167
- return (
168
- rawProjects as Array<{
169
- id: string;
170
- name: string;
171
- status?: string | null;
172
- }>
173
- ).map((project) => ({
167
+ const rawProjects = await listWorkspaceTaskProjects(
168
+ wsId,
169
+ getBrowserInternalApiOptions()
170
+ );
171
+
172
+ return rawProjects.map((project) => ({
174
173
  id: project.id,
175
174
  name: project.name,
176
175
  status: project.status ?? null,
@@ -218,19 +217,13 @@ export function TaskInitiativesClient({
218
217
  description?: string;
219
218
  status: InitiativeStatus;
220
219
  }) => {
221
- const response = await fetch(
222
- `/api/v1/workspaces/${wsId}/task-initiatives`,
223
- {
224
- method: 'POST',
225
- headers: { 'Content-Type': 'application/json' },
226
- body: JSON.stringify({ name, description, status }),
227
- }
220
+ const payload = { name, description: description ?? '', status };
221
+
222
+ return createWorkspaceTaskInitiative(
223
+ wsId,
224
+ payload,
225
+ getBrowserInternalApiOptions()
228
226
  );
229
- if (!response.ok) {
230
- const errorData = await response.json().catch(() => ({}));
231
- throw new Error(errorData.error || t('errors.create_initiative'));
232
- }
233
- return response.json();
234
227
  },
235
228
  onSuccess: () => {
236
229
  toast.success(t('success.initiative_created'));
@@ -257,19 +250,14 @@ export function TaskInitiativesClient({
257
250
  description?: string;
258
251
  status: InitiativeStatus;
259
252
  }) => {
260
- const response = await fetch(
261
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}`,
262
- {
263
- method: 'PUT',
264
- headers: { 'Content-Type': 'application/json' },
265
- body: JSON.stringify({ name, description, status }),
266
- }
253
+ const payload = { name, description: description ?? '', status };
254
+
255
+ return updateWorkspaceTaskInitiative(
256
+ wsId,
257
+ initiativeId,
258
+ payload,
259
+ getBrowserInternalApiOptions()
267
260
  );
268
- if (!response.ok) {
269
- const errorData = await response.json().catch(() => ({}));
270
- throw new Error(errorData.error || t('errors.update_initiative'));
271
- }
272
- return response.json();
273
261
  },
274
262
  onSuccess: () => {
275
263
  toast.success(t('success.initiative_updated'));
@@ -286,18 +274,12 @@ export function TaskInitiativesClient({
286
274
  });
287
275
 
288
276
  const deleteInitiativeMutation = useMutation({
289
- mutationFn: async (initiativeId: string) => {
290
- const response = await fetch(
291
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}`,
292
- {
293
- method: 'DELETE',
294
- }
295
- );
296
- if (!response.ok) {
297
- const errorData = await response.json().catch(() => ({}));
298
- throw new Error(errorData.error || t('errors.delete_initiative'));
299
- }
300
- },
277
+ mutationFn: (initiativeId: string) =>
278
+ deleteWorkspaceTaskInitiative(
279
+ wsId,
280
+ initiativeId,
281
+ getBrowserInternalApiOptions()
282
+ ),
301
283
  onSuccess: () => {
302
284
  toast.success(t('success.initiative_deleted'));
303
285
  refetchInitiatives();
@@ -314,21 +296,13 @@ export function TaskInitiativesClient({
314
296
  }: {
315
297
  initiativeId: string;
316
298
  projectId: string;
317
- }) => {
318
- const response = await fetch(
319
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}/projects`,
320
- {
321
- method: 'POST',
322
- headers: { 'Content-Type': 'application/json' },
323
- body: JSON.stringify({ projectId }),
324
- }
325
- );
326
- if (!response.ok) {
327
- const errorData = await response.json().catch(() => ({}));
328
- throw new Error(errorData.error || t('errors.link_project'));
329
- }
330
- return response.json();
331
- },
299
+ }) =>
300
+ linkWorkspaceTaskInitiativeProject(
301
+ wsId,
302
+ initiativeId,
303
+ projectId,
304
+ getBrowserInternalApiOptions()
305
+ ),
332
306
  onSuccess: (_data, variables) => {
333
307
  toast.success(t('success.project_linked'));
334
308
  setProjectToLink('');
@@ -362,19 +336,13 @@ export function TaskInitiativesClient({
362
336
  }: {
363
337
  initiativeId: string;
364
338
  projectId: string;
365
- }) => {
366
- const response = await fetch(
367
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}/projects/${projectId}`,
368
- {
369
- method: 'DELETE',
370
- }
371
- );
372
- if (!response.ok) {
373
- const errorData = await response.json().catch(() => ({}));
374
- throw new Error(errorData.error || t('errors.unlink_project'));
375
- }
376
- return response.json();
377
- },
339
+ }) =>
340
+ unlinkWorkspaceTaskInitiativeProject(
341
+ wsId,
342
+ initiativeId,
343
+ projectId,
344
+ getBrowserInternalApiOptions()
345
+ ),
378
346
  onSuccess: (_data, variables) => {
379
347
  toast.success(t('success.project_unlinked'));
380
348
  if (variables) {
@@ -20,12 +20,30 @@ import { TaskPreviewDialog } from './task-preview-dialog';
20
20
  import { useMyTasksState } from './use-my-tasks-state';
21
21
 
22
22
  interface MyTasksContentProps {
23
+ disableAutoCreateBoard?: boolean;
24
+ embedded?: boolean;
25
+ initialBoard?: {
26
+ id: string;
27
+ name: string | null;
28
+ };
29
+ initialListId?: string;
30
+ initialLists?: Array<{
31
+ deleted?: boolean | null;
32
+ id: string;
33
+ name: string | null;
34
+ position?: number | null;
35
+ }>;
23
36
  wsId: string;
24
37
  userId: string;
25
38
  isPersonal: boolean;
26
39
  }
27
40
 
28
41
  export default function MyTasksContent({
42
+ disableAutoCreateBoard = false,
43
+ embedded = false,
44
+ initialBoard,
45
+ initialListId,
46
+ initialLists,
29
47
  wsId,
30
48
  userId,
31
49
  isPersonal,
@@ -40,13 +58,17 @@ export default function MyTasksContent({
40
58
  'enter'
41
59
  );
42
60
  const state = useMyTasksState({
61
+ ...(disableAutoCreateBoard ? { disableAutoCreateBoard } : {}),
62
+ ...(initialBoard ? { initialBoard } : {}),
63
+ ...(initialListId ? { initialListId } : {}),
64
+ ...(initialLists ? { initialLists } : {}),
43
65
  wsId,
44
66
  userId,
45
67
  isPersonal,
46
68
  });
47
69
 
48
70
  return (
49
- <div className="space-y-4 md:space-y-6">
71
+ <div className={embedded ? 'space-y-4' : 'space-y-4 md:space-y-6'}>
50
72
  {/* Header with greeting + summary cards */}
51
73
  <MyTasksHeader
52
74
  overdueCount={state.filteredTasks.overdueTasks?.length ?? 0}
@@ -55,7 +77,9 @@ export default function MyTasksContent({
55
77
  />
56
78
 
57
79
  {/* Command Bar */}
58
- <div className="mx-auto mb-32 max-w-5xl">
80
+ <div
81
+ className={embedded ? 'mx-auto max-w-5xl' : 'mx-auto mb-32 max-w-5xl'}
82
+ >
59
83
  <CommandBar
60
84
  value={state.commandBarInput}
61
85
  onValueChange={state.setCommandBarInput}