@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
@@ -10,6 +10,7 @@ import { useTranslations } from 'next-intl';
10
10
  import React, { useCallback, useEffect, useRef, useState } from 'react';
11
11
  import type { DragPreviewPosition } from './kanban/dnd/use-kanban-dnd';
12
12
  import { MeasuredTaskCard } from './task';
13
+ import type { TaskCardAssigneeMemberSource } from './task-card/task-card';
13
14
 
14
15
  const VIRTUALIZE_THRESHOLD = 60; // only virtualize for fairly large lists
15
16
  const ESTIMATED_ITEM_HEIGHT = 96; // px including margin (space-y-2 gap)
@@ -33,6 +34,8 @@ interface VirtualizedTaskListProps {
33
34
  isMultiSelectMode?: boolean;
34
35
  selectedTasks?: Set<string>;
35
36
  isPersonalWorkspace?: boolean;
37
+ canUseBoardAssignees?: boolean;
38
+ assigneeMemberSource?: TaskCardAssigneeMemberSource;
36
39
  onTaskSelect?: (taskId: string, event: React.MouseEvent) => void;
37
40
  onClearSelection?: () => void;
38
41
  dragPreviewPosition?: DragPreviewPosition | null;
@@ -43,6 +46,7 @@ interface VirtualizedTaskListProps {
43
46
  onLoadMore?: () => void;
44
47
  hasMore?: boolean;
45
48
  isLoadingMore?: boolean;
49
+ readOnly?: boolean;
46
50
  }
47
51
 
48
52
  interface TaskListContentProps {
@@ -55,6 +59,8 @@ interface TaskListContentProps {
55
59
  isMultiSelectMode?: boolean;
56
60
  selectedTasks?: Set<string>;
57
61
  isPersonalWorkspace?: boolean;
62
+ canUseBoardAssignees?: boolean;
63
+ assigneeMemberSource?: TaskCardAssigneeMemberSource;
58
64
  onTaskSelect?: (taskId: string, event: React.MouseEvent) => void;
59
65
  onClearSelection?: () => void;
60
66
  dragPreviewPosition?: DragPreviewPosition | null;
@@ -64,6 +70,7 @@ interface TaskListContentProps {
64
70
  bulkUpdateCustomDueDate?: (date: Date | null) => Promise<void>;
65
71
  startIndex?: number;
66
72
  taskOrder?: Pick<Task, 'id'>[];
73
+ readOnly?: boolean;
67
74
  }
68
75
 
69
76
  export function getTaskDragPreviewSlotIndex({
@@ -123,6 +130,8 @@ function TaskListContent({
123
130
  isMultiSelectMode,
124
131
  selectedTasks,
125
132
  isPersonalWorkspace,
133
+ canUseBoardAssignees,
134
+ assigneeMemberSource,
126
135
  onTaskSelect,
127
136
  onClearSelection,
128
137
  dragPreviewPosition,
@@ -132,6 +141,7 @@ function TaskListContent({
132
141
  bulkUpdateCustomDueDate,
133
142
  startIndex = 0,
134
143
  taskOrder = tasks,
144
+ readOnly = false,
135
145
  }: TaskListContentProps) {
136
146
  const slotIndex = getTaskDragPreviewSlotIndex({
137
147
  columnId: column.id,
@@ -162,6 +172,8 @@ function TaskListContent({
162
172
  )}
163
173
  isMultiSelectMode={isMultiSelectMode}
164
174
  isPersonalWorkspace={isPersonalWorkspace}
175
+ canUseBoardAssignees={canUseBoardAssignees}
176
+ assigneeMemberSource={assigneeMemberSource}
165
177
  onSelect={onTaskSelect}
166
178
  onClearSelection={onClearSelection}
167
179
  suppressSortableTransform={suppressSortableTransform}
@@ -170,6 +182,7 @@ function TaskListContent({
170
182
  optimisticUpdateInProgress={optimisticUpdateInProgress}
171
183
  selectedTasks={selectedTasks}
172
184
  bulkUpdateCustomDueDate={bulkUpdateCustomDueDate}
185
+ readOnly={readOnly}
173
186
  />
174
187
  {slotIndex === globalIndex + 1 && (
175
188
  <DragPreviewSlot
@@ -228,6 +241,8 @@ function VirtualizedTaskListInner({
228
241
  isMultiSelectMode,
229
242
  selectedTasks,
230
243
  isPersonalWorkspace,
244
+ canUseBoardAssignees,
245
+ assigneeMemberSource,
231
246
  onTaskSelect,
232
247
  onClearSelection,
233
248
  dragPreviewPosition,
@@ -238,6 +253,7 @@ function VirtualizedTaskListInner({
238
253
  onLoadMore,
239
254
  hasMore,
240
255
  isLoadingMore,
256
+ readOnly = false,
241
257
  }: VirtualizedTaskListProps) {
242
258
  const t = useTranslations('common');
243
259
  const tTasks = useTranslations('ws-tasks');
@@ -465,6 +481,8 @@ function VirtualizedTaskListInner({
465
481
  isMultiSelectMode={isMultiSelectMode}
466
482
  selectedTasks={selectedTasks}
467
483
  isPersonalWorkspace={isPersonalWorkspace}
484
+ canUseBoardAssignees={canUseBoardAssignees}
485
+ assigneeMemberSource={assigneeMemberSource}
468
486
  onTaskSelect={onTaskSelect}
469
487
  onClearSelection={onClearSelection}
470
488
  dragPreviewPosition={dragPreviewPosition}
@@ -474,6 +492,7 @@ function VirtualizedTaskListInner({
474
492
  bulkUpdateCustomDueDate={bulkUpdateCustomDueDate}
475
493
  startIndex={startIndex}
476
494
  taskOrder={tasks}
495
+ readOnly={readOnly}
477
496
  />
478
497
  </div>
479
498
  </div>
@@ -494,6 +513,8 @@ function VirtualizedTaskListInner({
494
513
  isMultiSelectMode={isMultiSelectMode}
495
514
  selectedTasks={selectedTasks}
496
515
  isPersonalWorkspace={isPersonalWorkspace}
516
+ canUseBoardAssignees={canUseBoardAssignees}
517
+ assigneeMemberSource={assigneeMemberSource}
497
518
  onTaskSelect={onTaskSelect}
498
519
  onClearSelection={onClearSelection}
499
520
  dragPreviewPosition={dragPreviewPosition}
@@ -502,6 +523,7 @@ function VirtualizedTaskListInner({
502
523
  optimisticUpdateInProgress={optimisticUpdateInProgress}
503
524
  bulkUpdateCustomDueDate={bulkUpdateCustomDueDate}
504
525
  taskOrder={tasks}
526
+ readOnly={readOnly}
505
527
  />
506
528
  {loadMoreSentinel}
507
529
  </SortableContext>
@@ -28,6 +28,9 @@ interface TimelineGridProps {
28
28
  localTasks: Task[];
29
29
  boardId?: string;
30
30
  wsId?: string;
31
+ isPersonalWorkspace?: boolean;
32
+ canUseBoardAssignees?: boolean;
33
+ assigneeMemberSource?: 'workspace' | 'board' | 'workspace-and-board';
31
34
  dayWidth: number;
32
35
  sidebarWidth: number;
33
36
  timelineWidth: number;
@@ -71,6 +74,9 @@ export function TimelineGrid({
71
74
  localTasks,
72
75
  boardId,
73
76
  wsId,
77
+ isPersonalWorkspace,
78
+ canUseBoardAssignees,
79
+ assigneeMemberSource,
74
80
  dayWidth,
75
81
  sidebarWidth,
76
82
  timelineWidth,
@@ -223,6 +229,9 @@ export function TimelineGrid({
223
229
  )}
224
230
  boardId={boardId}
225
231
  wsId={wsId}
232
+ isPersonalWorkspace={isPersonalWorkspace}
233
+ canUseBoardAssignees={canUseBoardAssignees}
234
+ assigneeMemberSource={assigneeMemberSource}
226
235
  dayWidth={dayWidth}
227
236
  timelineWidth={timelineWidth}
228
237
  sidebarWidth={sidebarWidth}
@@ -39,6 +39,9 @@ interface TimelineTaskRowProps {
39
39
  lists: TaskList[];
40
40
  boardId?: string;
41
41
  wsId?: string;
42
+ isPersonalWorkspace?: boolean;
43
+ canUseBoardAssignees?: boolean;
44
+ assigneeMemberSource?: 'workspace' | 'board' | 'workspace-and-board';
42
45
  dayWidth: number;
43
46
  timelineWidth: number;
44
47
  sidebarWidth: number;
@@ -75,6 +78,9 @@ export function TimelineTaskRow({
75
78
  lists,
76
79
  boardId,
77
80
  wsId,
81
+ isPersonalWorkspace,
82
+ canUseBoardAssignees,
83
+ assigneeMemberSource,
78
84
  dayWidth,
79
85
  timelineWidth,
80
86
  sidebarWidth,
@@ -235,6 +241,9 @@ export function TimelineTaskRow({
235
241
  boardId={boardId}
236
242
  workspaceId={wsId}
237
243
  lists={lists}
244
+ isPersonalWorkspace={isPersonalWorkspace}
245
+ canUseBoardAssignees={canUseBoardAssignees}
246
+ assigneeMemberSource={assigneeMemberSource}
238
247
  onUpdate={onActionsUpdate ?? (() => undefined)}
239
248
  open={actionsMenu.open}
240
249
  onOpenChange={(open) =>
@@ -34,6 +34,9 @@ interface TimelineToolbarProps {
34
34
  lists: TaskList[];
35
35
  boardId?: string;
36
36
  wsId?: string;
37
+ isPersonalWorkspace?: boolean;
38
+ canUseBoardAssignees?: boolean;
39
+ assigneeMemberSource?: 'workspace' | 'board' | 'workspace-and-board';
37
40
  primaryCreateListId: string | null;
38
41
  dayWidth: number;
39
42
  setDayWidth: (value: number) => void;
@@ -66,6 +69,9 @@ export function TimelineToolbar({
66
69
  lists,
67
70
  boardId,
68
71
  wsId,
72
+ isPersonalWorkspace,
73
+ canUseBoardAssignees,
74
+ assigneeMemberSource,
69
75
  primaryCreateListId,
70
76
  dayWidth,
71
77
  setDayWidth,
@@ -221,6 +227,9 @@ export function TimelineToolbar({
221
227
  boardId={boardId}
222
228
  workspaceId={wsId}
223
229
  lists={lists}
230
+ isPersonalWorkspace={isPersonalWorkspace}
231
+ canUseBoardAssignees={canUseBoardAssignees}
232
+ assigneeMemberSource={assigneeMemberSource}
224
233
  onUpdate={onActionsUpdate ?? (() => undefined)}
225
234
  open={openTaskMenu?.taskId === task.id}
226
235
  onOpenChange={(open) =>
@@ -65,6 +65,9 @@ export interface TimelineProps {
65
65
  lists: TaskList[];
66
66
  boardId?: string;
67
67
  wsId?: string;
68
+ isPersonalWorkspace?: boolean;
69
+ canUseBoardAssignees?: boolean;
70
+ assigneeMemberSource?: 'workspace' | 'board' | 'workspace-and-board';
68
71
  className?: string;
69
72
  onTaskPartialUpdate?: (taskId: string, updates: Partial<Task>) => void;
70
73
  }
@@ -110,6 +113,9 @@ export function TimelineBoard({
110
113
  lists,
111
114
  boardId,
112
115
  wsId,
116
+ isPersonalWorkspace = false,
117
+ canUseBoardAssignees,
118
+ assigneeMemberSource,
113
119
  className,
114
120
  onTaskPartialUpdate,
115
121
  }: TimelineProps) {
@@ -227,6 +233,9 @@ export function TimelineBoard({
227
233
  (task: Task) => task.source_workspace_id ?? wsId ?? null,
228
234
  [wsId]
229
235
  );
236
+ const showAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
237
+ const effectiveAssigneeMemberSource =
238
+ assigneeMemberSource ?? (isPersonalWorkspace ? 'board' : 'workspace');
230
239
 
231
240
  const openTimelineTask = useCallback(
232
241
  (task: Task) => {
@@ -240,7 +249,13 @@ export function TimelineBoard({
240
249
  boardId,
241
250
  availableLists: lists,
242
251
  effectiveWorkspaceId: wsId,
243
- isPersonalWorkspace: Boolean(wsId),
252
+ isPersonalWorkspace,
253
+ canUseBoardAssignees: task.source_workspace_id
254
+ ? true
255
+ : showAssignees,
256
+ assigneeMemberSource: task.source_workspace_id
257
+ ? 'workspace'
258
+ : effectiveAssigneeMemberSource,
244
259
  })
245
260
  );
246
261
  return;
@@ -248,10 +263,21 @@ export function TimelineBoard({
248
263
 
249
264
  openTask(task, boardId, lists, false, {
250
265
  taskWsId: wsId,
251
- taskWorkspacePersonal: Boolean(wsId),
266
+ taskWorkspacePersonal: isPersonalWorkspace,
267
+ canUseBoardAssignees: showAssignees,
268
+ assigneeMemberSource: effectiveAssigneeMemberSource,
252
269
  });
253
270
  },
254
- [boardId, lists, openTask, openTaskById, wsId]
271
+ [
272
+ boardId,
273
+ effectiveAssigneeMemberSource,
274
+ isPersonalWorkspace,
275
+ lists,
276
+ openTask,
277
+ openTaskById,
278
+ showAssignees,
279
+ wsId,
280
+ ]
255
281
  );
256
282
 
257
283
  const clearDraft = useCallback((taskId: string) => {
@@ -674,6 +700,9 @@ export function TimelineBoard({
674
700
  <TimelineToolbar
675
701
  boardId={boardId}
676
702
  wsId={wsId}
703
+ isPersonalWorkspace={isPersonalWorkspace}
704
+ canUseBoardAssignees={showAssignees}
705
+ assigneeMemberSource={effectiveAssigneeMemberSource}
677
706
  dayWidth={dayWidth}
678
707
  density={density}
679
708
  formatLongDate={formatLongDate}
@@ -703,6 +732,9 @@ export function TimelineBoard({
703
732
  barHeight={densityConfig.barHeight}
704
733
  boardId={boardId}
705
734
  wsId={wsId}
735
+ isPersonalWorkspace={isPersonalWorkspace}
736
+ canUseBoardAssignees={showAssignees}
737
+ assigneeMemberSource={effectiveAssigneeMemberSource}
706
738
  dayWidth={dayWidth}
707
739
  draggedUnscheduledTaskId={draggedUnscheduledTaskId}
708
740
  dropPreview={dropPreview}
@@ -209,7 +209,7 @@ export function TaskBoardForm({
209
209
  autoComplete="off"
210
210
  autoFocus
211
211
  {...field}
212
- className="h-10"
212
+ className="h-10 bg-background"
213
213
  />
214
214
  </FormControl>
215
215
  <FormMessage />
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+
3
+ import { ChevronDown, Info } from '@tuturuuu/icons';
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from '@tuturuuu/ui/collapsible';
9
+ import {
10
+ Tooltip,
11
+ TooltipContent,
12
+ TooltipProvider,
13
+ TooltipTrigger,
14
+ } from '@tuturuuu/ui/tooltip';
15
+ import { cn } from '@tuturuuu/utils/format';
16
+ import { useTranslations } from 'next-intl';
17
+ import type { ReactNode } from 'react';
18
+
19
+ function ShareInfoTooltip({
20
+ content,
21
+ label,
22
+ }: {
23
+ content: string;
24
+ label: string;
25
+ }) {
26
+ return (
27
+ <TooltipProvider delayDuration={0} skipDelayDuration={0}>
28
+ <Tooltip>
29
+ <TooltipTrigger asChild>
30
+ <button
31
+ type="button"
32
+ className="text-muted-foreground transition-colors hover:text-foreground"
33
+ aria-label={label}
34
+ >
35
+ <Info className="h-3.5 w-3.5" />
36
+ </button>
37
+ </TooltipTrigger>
38
+ <TooltipContent className="max-w-xs">{content}</TooltipContent>
39
+ </Tooltip>
40
+ </TooltipProvider>
41
+ );
42
+ }
43
+
44
+ interface ShareSectionProps {
45
+ children: ReactNode;
46
+ icon: ReactNode;
47
+ onOpenChange: (open: boolean) => void;
48
+ open: boolean;
49
+ statusBadge: ReactNode;
50
+ title: string;
51
+ tooltip: string;
52
+ }
53
+
54
+ export function ShareSection({
55
+ children,
56
+ icon,
57
+ onOpenChange,
58
+ open,
59
+ statusBadge,
60
+ title,
61
+ tooltip,
62
+ }: ShareSectionProps) {
63
+ const t = useTranslations();
64
+
65
+ return (
66
+ <Collapsible
67
+ open={open}
68
+ onOpenChange={onOpenChange}
69
+ className="rounded-md border"
70
+ >
71
+ <div className="flex min-h-11 items-center gap-2 px-3">
72
+ <CollapsibleTrigger asChild>
73
+ <button
74
+ type="button"
75
+ className="flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:text-foreground"
76
+ >
77
+ {icon}
78
+ <span className="min-w-0 flex-1 truncate font-medium text-sm">
79
+ {title}
80
+ </span>
81
+ {statusBadge}
82
+ <ChevronDown
83
+ className={cn(
84
+ 'h-4 w-4 shrink-0 text-muted-foreground transition-transform',
85
+ open && 'rotate-180'
86
+ )}
87
+ />
88
+ </button>
89
+ </CollapsibleTrigger>
90
+ <ShareInfoTooltip
91
+ label={t('ws-task-boards.share.note')}
92
+ content={tooltip}
93
+ />
94
+ </div>
95
+ <CollapsibleContent className="border-t p-3">
96
+ {children}
97
+ </CollapsibleContent>
98
+ </Collapsible>
99
+ );
100
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query';
4
4
  import {
5
+ convertWorkspaceTaskDraft,
5
6
  listWorkspaceTaskBoards,
6
7
  listWorkspaceTaskLists,
7
8
  } from '@tuturuuu/internal-api';
@@ -46,6 +47,10 @@ export function DraftConvertDialog({
46
47
  const [selectedBoardId, setSelectedBoardId] = useState<string>('');
47
48
  const [selectedListId, setSelectedListId] = useState<string>('');
48
49
  const [isConverting, setIsConverting] = useState(false);
50
+ const internalApiOptions =
51
+ typeof window !== 'undefined'
52
+ ? { baseUrl: window.location.origin }
53
+ : undefined;
49
54
 
50
55
  // Sync selections when draft changes (useState initializer only runs on mount)
51
56
  useEffect(() => {
@@ -107,20 +112,13 @@ export function DraftConvertDialog({
107
112
 
108
113
  setIsConverting(true);
109
114
  try {
110
- const res = await fetch(
111
- `/api/v1/workspaces/${wsId}/task-drafts/${draft.id}/convert`,
112
- {
113
- method: 'POST',
114
- headers: { 'Content-Type': 'application/json' },
115
- body: JSON.stringify({ listId: selectedListId }),
116
- }
115
+ await convertWorkspaceTaskDraft(
116
+ wsId,
117
+ draft.id,
118
+ { listId: selectedListId },
119
+ internalApiOptions
117
120
  );
118
121
 
119
- if (!res.ok) {
120
- const data = await res.json();
121
- throw new Error(data.error || 'Failed to convert draft');
122
- }
123
-
124
122
  toast.success(t('converted_success'));
125
123
  onConverted();
126
124
  onClose();
@@ -2,6 +2,10 @@
2
2
 
3
3
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
4
  import { FileText } from '@tuturuuu/icons';
5
+ import {
6
+ deleteWorkspaceTaskDraft,
7
+ listWorkspaceTaskDrafts,
8
+ } from '@tuturuuu/internal-api/tasks';
5
9
  import { toast } from '@tuturuuu/ui/sonner';
6
10
  import { useTranslations } from 'next-intl';
7
11
  import { useState } from 'react';
@@ -11,38 +15,51 @@ import { DraftConvertDialog } from './draft-convert-dialog';
11
15
 
12
16
  interface DraftsPageProps {
13
17
  wsId: string;
18
+ boardId?: string;
19
+ includeUnassignedForBoard?: boolean;
14
20
  }
15
21
 
16
- export function DraftsPage({ wsId }: DraftsPageProps) {
22
+ function getBrowserInternalApiOptions() {
23
+ return typeof window !== 'undefined'
24
+ ? { baseUrl: window.location.origin }
25
+ : undefined;
26
+ }
27
+
28
+ export function DraftsPage({
29
+ wsId,
30
+ boardId,
31
+ includeUnassignedForBoard = false,
32
+ }: DraftsPageProps) {
17
33
  const t = useTranslations('task-drafts');
18
34
  const queryClient = useQueryClient();
19
35
  const { editDraft } = useTaskDialogContext();
20
36
  const [convertDraft, setConvertDraft] = useState<TaskDraft | null>(null);
37
+ const draftQueryKey = [
38
+ 'task-drafts',
39
+ wsId,
40
+ boardId ?? 'all',
41
+ includeUnassignedForBoard,
42
+ ] as const;
21
43
 
22
44
  const { data: drafts = [], isLoading } = useQuery<TaskDraft[]>({
23
- queryKey: ['task-drafts', wsId],
24
- queryFn: async () => {
25
- const res = await fetch(`/api/v1/workspaces/${wsId}/task-drafts`);
26
- if (!res.ok) throw new Error('Failed to fetch drafts');
27
- const json = await res.json();
28
- return json.data;
29
- },
45
+ queryKey: draftQueryKey,
46
+ queryFn: () =>
47
+ listWorkspaceTaskDrafts(
48
+ wsId,
49
+ { boardId, includeUnassignedForBoard },
50
+ getBrowserInternalApiOptions()
51
+ ) as Promise<TaskDraft[]>,
30
52
  });
31
53
 
32
54
  const deleteMutation = useMutation({
33
- mutationFn: async (draftId: string) => {
34
- const res = await fetch(
35
- `/api/v1/workspaces/${wsId}/task-drafts/${draftId}`,
36
- { method: 'DELETE' }
37
- );
38
- if (!res.ok) throw new Error('Failed to delete draft');
39
- },
55
+ mutationFn: async (draftId: string) =>
56
+ deleteWorkspaceTaskDraft(wsId, draftId, getBrowserInternalApiOptions()),
40
57
  onSuccess: () => {
41
58
  queryClient.invalidateQueries({ queryKey: ['task-drafts', wsId] });
42
59
  toast.success(t('deleted_success'));
43
60
  },
44
61
  onError: () => {
45
- toast.error('Failed to delete draft');
62
+ toast.error(t('delete_failed'));
46
63
  },
47
64
  });
48
65
 
@@ -190,6 +190,46 @@ describe('useTaskLabelManagement', () => {
190
190
  );
191
191
  });
192
192
 
193
+ it('updates full-board and task detail caches without invalidating visible board queries', async () => {
194
+ queryClient.setQueryData(['tasks', 'board-1'], [mockTask]);
195
+ queryClient.setQueryData(['tasks-full', 'board-1', 'all'], [mockTask]);
196
+ queryClient.setQueryData(['task', 'task-1'], mockTask);
197
+ const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
198
+
199
+ const { result } = renderHook(
200
+ () =>
201
+ useTaskLabelManagement({
202
+ task: mockTask,
203
+ boardId: 'board-1',
204
+ workspaceLabels: mockWorkspaceLabels,
205
+ workspaceId: 'ws-1',
206
+ taskId: 'task-1',
207
+ }),
208
+ { wrapper }
209
+ );
210
+
211
+ await act(async () => {
212
+ await result.current.toggleTaskLabel('label-3');
213
+ });
214
+
215
+ expect(
216
+ queryClient
217
+ .getQueryData<Task[]>(['tasks-full', 'board-1', 'all'])?.[0]
218
+ ?.labels?.some((label) => label.id === 'label-3')
219
+ ).toBe(true);
220
+ expect(
221
+ queryClient
222
+ .getQueryData<Task>(['task', 'task-1'])
223
+ ?.labels?.some((label) => label.id === 'label-3')
224
+ ).toBe(true);
225
+ expect(invalidateSpy).not.toHaveBeenCalledWith({
226
+ queryKey: ['tasks', 'board-1'],
227
+ });
228
+ expect(invalidateSpy).not.toHaveBeenCalledWith({
229
+ queryKey: ['tasks-full', 'board-1'],
230
+ });
231
+ });
232
+
193
233
  it('should rollback on error and show toast', async () => {
194
234
  mockRemoveWorkspaceTaskLabel.mockRejectedValueOnce(
195
235
  new Error('Database error')
@@ -197,6 +237,8 @@ describe('useTaskLabelManagement', () => {
197
237
 
198
238
  const originalTasks = [mockTask];
199
239
  queryClient.setQueryData(['tasks', 'board-1'], originalTasks);
240
+ queryClient.setQueryData(['tasks-full', 'board-1', 'all'], originalTasks);
241
+ queryClient.setQueryData(['task', 'task-1'], mockTask);
200
242
 
201
243
  const { result } = renderHook(
202
244
  () =>
@@ -219,6 +261,12 @@ describe('useTaskLabelManagement', () => {
219
261
  'board-1',
220
262
  ]);
221
263
  expect(cachedTasks).toEqual(originalTasks);
264
+ expect(
265
+ queryClient.getQueryData<Task[]>(['tasks-full', 'board-1', 'all'])
266
+ ).toEqual(originalTasks);
267
+ expect(queryClient.getQueryData<Task>(['task', 'task-1'])).toEqual(
268
+ mockTask
269
+ );
222
270
 
223
271
  // Verify error toast was shown with the correct format
224
272
  expect(mockToast.error).toHaveBeenCalledWith('Error', {