@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
@@ -1,42 +1,110 @@
1
1
  'use client';
2
2
 
3
- import { Card, CardContent } from '@tuturuuu/ui/card';
3
+ import { Skeleton } from '@tuturuuu/ui/skeleton';
4
+ import { cn } from '@tuturuuu/utils/format';
4
5
 
5
- export function KanbanSkeleton() {
6
+ const RAILS = ['upcoming', 'external'];
7
+ const COLUMNS = [
8
+ { id: 'documents', cards: 3, width: 'w-[21rem] sm:w-[23rem]' },
9
+ { id: 'inactive', cards: 1, width: 'w-[20rem] sm:w-[22rem]' },
10
+ { id: 'todo', cards: 2, width: 'w-[21rem] sm:w-[23rem]' },
11
+ { id: 'blocked', cards: 1, width: 'w-[20rem] sm:w-[22rem]' },
12
+ ] as const;
13
+
14
+ function KanbanCardSkeleton({ compact = false }: { compact?: boolean }) {
6
15
  return (
7
- <div className="flex h-full flex-col">
8
- {/* Loading skeleton for search bar */}
9
- <Card className="mb-4 border-dynamic-blue/20 bg-dynamic-blue/5">
10
- <CardContent className="p-4">
11
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
12
- <div className="relative max-w-md flex-1">
13
- <div className="h-9 w-full animate-pulse rounded-md bg-dynamic-blue/10"></div>
14
- </div>
15
- <div className="flex items-center gap-2">
16
- <div className="h-8 w-16 animate-pulse rounded-md bg-dynamic-blue/10"></div>
17
- <div className="h-8 w-20 animate-pulse rounded-md bg-dynamic-blue/10"></div>
16
+ <div className="rounded-lg border bg-card/80 p-3 shadow-xs">
17
+ <div className="flex items-start justify-between gap-3">
18
+ <div className="min-w-0 flex-1 space-y-2">
19
+ <Skeleton className="h-4 w-4/5" />
20
+ {!compact && <Skeleton className="h-4 w-3/5" />}
21
+ </div>
22
+ <Skeleton className="h-4 w-4 rounded" />
23
+ </div>
24
+ <div className="mt-3 flex items-center gap-2">
25
+ <Skeleton className="h-4 w-20 rounded-full" />
26
+ <Skeleton className="h-4 w-14 rounded-full" />
27
+ {!compact && <Skeleton className="h-4 w-16 rounded-full" />}
28
+ </div>
29
+ <div className="mt-3 flex items-center gap-2">
30
+ <Skeleton className="h-3 w-24" />
31
+ <Skeleton className="h-3 w-16" />
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ function KanbanColumnSkeleton({
38
+ cards,
39
+ className,
40
+ }: {
41
+ cards: number;
42
+ className?: string;
43
+ }) {
44
+ return (
45
+ <div
46
+ className={cn(
47
+ 'flex h-full shrink-0 flex-col overflow-hidden rounded-xl border bg-muted/20',
48
+ className
49
+ )}
50
+ >
51
+ <div className="flex h-14 shrink-0 items-center justify-between border-b px-3">
52
+ <div className="flex min-w-0 items-center gap-2">
53
+ <Skeleton className="h-4 w-4 rounded" />
54
+ <Skeleton className="h-4 w-28" />
55
+ <Skeleton className="h-5 w-7 rounded-full" />
56
+ </div>
57
+ <Skeleton className="h-6 w-6 rounded-md" />
58
+ </div>
59
+ <div className="min-h-0 flex-1 space-y-3 overflow-hidden p-3">
60
+ {Array.from({ length: cards }).map((_, index) => (
61
+ <KanbanCardSkeleton
62
+ compact={index === cards - 1 && cards > 1}
63
+ key={`${cards}-${index}`}
64
+ />
65
+ ))}
66
+ <div className="mt-auto pt-3">
67
+ <Skeleton className="h-9 w-full rounded-lg border border-dashed bg-transparent" />
68
+ </div>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export function KanbanSkeleton({ root = false }: { root?: boolean }) {
75
+ return (
76
+ <div
77
+ aria-hidden="true"
78
+ className="h-full overflow-hidden bg-transparent"
79
+ data-testid="kanban-skeleton"
80
+ >
81
+ <div
82
+ className={cn(
83
+ 'flex h-full min-w-0 gap-2 overflow-hidden sm:gap-3',
84
+ root ? 'py-2 pr-0 pl-2' : 'p-2'
85
+ )}
86
+ data-testid="kanban-skeleton-frame"
87
+ >
88
+ <div className="hidden shrink-0 gap-2 sm:flex">
89
+ {RAILS.map((rail) => (
90
+ <div
91
+ className="flex h-full w-10 flex-col items-center rounded-xl border border-dashed bg-muted/10 p-2"
92
+ key={rail}
93
+ >
94
+ <Skeleton className="h-4 w-4 rounded" />
95
+ <Skeleton className="mt-5 h-5 w-5 rounded-full" />
96
+ <Skeleton className="mt-6 h-28 w-3 rounded-full" />
18
97
  </div>
19
- </div>
20
- </CardContent>
21
- </Card>
98
+ ))}
99
+ </div>
22
100
 
23
- {/* Loading skeleton for kanban columns */}
24
- <div className="flex-1 overflow-x-auto overflow-y-hidden">
25
- <div className="flex h-full gap-4 p-4">
26
- {[1, 2, 3, 4].map((i) => (
27
- <Card key={i} className="h-full w-87.5 animate-pulse">
28
- <div className="p-4">
29
- <div className="mb-4 h-6 w-32 rounded bg-muted"></div>
30
- <div className="space-y-3">
31
- {[1, 2, 3].map((j) => (
32
- <div
33
- key={j}
34
- className="h-24 w-full rounded bg-muted/50"
35
- ></div>
36
- ))}
37
- </div>
38
- </div>
39
- </Card>
101
+ <div className="flex min-w-0 flex-1 gap-3 overflow-hidden">
102
+ {COLUMNS.map((column) => (
103
+ <KanbanColumnSkeleton
104
+ cards={column.cards}
105
+ className={column.width}
106
+ key={column.id}
107
+ />
40
108
  ))}
41
109
  </div>
42
110
  </div>
@@ -15,6 +15,7 @@ import {
15
15
  } from '@dnd-kit/core';
16
16
  import { useQuery, useQueryClient } from '@tanstack/react-query';
17
17
  import { updateWorkspaceTaskList } from '@tuturuuu/internal-api';
18
+ import type { ListWorkspaceTasksOptions } from '@tuturuuu/internal-api/tasks';
18
19
  import type { Workspace, WorkspaceProductTier } from '@tuturuuu/types';
19
20
  import type { Task } from '@tuturuuu/types/primitives/Task';
20
21
  import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
@@ -28,12 +29,20 @@ import { useOptionalWorkspacePresenceContext } from '../../providers/workspace-p
28
29
  import { useBoardBroadcast } from '../../shared/board-broadcast-context';
29
30
  import type { ListStatusFilter } from '../../shared/board-header';
30
31
  import { buildEstimationIndices } from '../../shared/estimation-mapping';
32
+ import type {
33
+ SpecialTaskListPin,
34
+ SpecialTaskListPinState,
35
+ } from '../../shared/special-task-list-pins';
36
+ import { TaskBoardLoadingState } from '../../shared/task-board-loading-state';
31
37
  import { BoardSelector } from '../board-selector';
32
38
  import { BulkActionsIsland } from './kanban/bulk/bulk-actions-island';
33
39
  import { BulkCustomDateDialog } from './kanban/bulk/bulk-custom-date-dialog';
34
40
  import { BulkDeleteDialog } from './kanban/bulk/bulk-delete-dialog';
35
41
  import { useBulkOperations } from './kanban/bulk/bulk-operations';
36
- import { listKanbanDeadlineTasks } from './kanban/data/kanban-deadline-query';
42
+ import {
43
+ getKanbanDeadlineTasksQueryKey,
44
+ listKanbanDeadlineTasks,
45
+ } from './kanban/data/kanban-deadline-query';
37
46
  import { useAppliedSets } from './kanban/data/use-applied-sets';
38
47
  import { useBulkResources } from './kanban/data/use-bulk-resources';
39
48
  import { useFilteredResources } from './kanban/data/use-filtered-resources';
@@ -42,8 +51,11 @@ import { DragPreview } from './kanban/dnd/drag-preview';
42
51
  import { useKanbanDnd } from './kanban/dnd/use-kanban-dnd';
43
52
  import { DRAG_ACTIVATION_DISTANCE } from './kanban/kanban-constants';
44
53
  import { KanbanColumns } from './kanban/rendering/kanban-columns';
54
+ import type {
55
+ KanbanDeadlineCollapsedState,
56
+ KanbanDeadlineSection,
57
+ } from './kanban/rendering/kanban-deadline-panels';
45
58
  import { buildKanbanDeadlineSections } from './kanban/rendering/kanban-deadline-tasks';
46
- import { KanbanSkeleton } from './kanban/rendering/kanban-skeleton';
47
59
  import { useKeyboardShortcuts } from './kanban/selection/use-keyboard-shortcuts';
48
60
  import { useMultiSelect } from './kanban/selection/use-multi-select';
49
61
  import type { TaskFilters } from './task-filter';
@@ -61,6 +73,8 @@ const kanbanCollisionDetection: CollisionDetection = (args) => {
61
73
  return closestCenter(args);
62
74
  };
63
75
 
76
+ const DEADLINE_REFRESH_INTERVAL_MS = 60_000;
77
+
64
78
  interface Props {
65
79
  workspace: Workspace;
66
80
  workspaceTier?: WorkspaceProductTier | null;
@@ -72,11 +86,25 @@ interface Props {
72
86
  disableSort?: boolean;
73
87
  listStatusFilter?: ListStatusFilter;
74
88
  filters?: TaskFilters;
89
+ deadlineTaskQueryOptions?: ListWorkspaceTasksOptions;
75
90
  isMultiSelectMode: boolean;
76
91
  setIsMultiSelectMode: (enabled: boolean) => void;
77
92
  onExternalTasksCollapsedChange?: (collapsed: boolean) => void;
78
93
  onTaskListCollapsedChange?: (listId: string, collapsed: boolean) => void;
94
+ deadlineSectionsCollapsed?: KanbanDeadlineCollapsedState;
95
+ onDeadlineSectionCollapsedChange?: (
96
+ section: KanbanDeadlineSection,
97
+ collapsed: boolean
98
+ ) => void;
99
+ specialTaskListPins?: SpecialTaskListPinState;
100
+ onSpecialTaskListPinnedChange?: (
101
+ pin: SpecialTaskListPin,
102
+ pinned: boolean
103
+ ) => void;
79
104
  onBulkSelectionActiveChange?: (active: boolean) => void;
105
+ canUseBoardAssignees?: boolean;
106
+ assigneeMemberSource?: 'workspace' | 'board' | 'workspace-and-board';
107
+ readOnly?: boolean;
80
108
  }
81
109
 
82
110
  export function KanbanBoard({
@@ -89,12 +117,22 @@ export function KanbanBoard({
89
117
  disableSort = false,
90
118
  listStatusFilter = 'all',
91
119
  filters,
120
+ deadlineTaskQueryOptions,
92
121
  isMultiSelectMode,
93
122
  setIsMultiSelectMode,
94
123
  onExternalTasksCollapsedChange,
95
124
  onTaskListCollapsedChange,
125
+ deadlineSectionsCollapsed,
126
+ onDeadlineSectionCollapsedChange,
127
+ specialTaskListPins,
128
+ onSpecialTaskListPinnedChange,
96
129
  onBulkSelectionActiveChange,
130
+ canUseBoardAssignees,
131
+ assigneeMemberSource,
132
+ readOnly = false,
97
133
  }: Props) {
134
+ const tCommon = useTranslations('common');
135
+ const tBoards = useTranslations('ws-task-boards');
98
136
  const tLayout = useTranslations('ws-task-boards.layout_settings');
99
137
  const tTasks = useTranslations('ws-tasks');
100
138
  const invalidColumnMoveMessage = tLayout.has('cannot_reorder_across_statuses')
@@ -104,6 +142,7 @@ export function KanbanBoard({
104
142
  const [bulkWorking, setBulkWorking] = useState(false);
105
143
  const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
106
144
  const [bulkCustomDateOpen, setBulkCustomDateOpen] = useState(false);
145
+ const [deadlineNow, setDeadlineNow] = useState(() => Date.now());
107
146
 
108
147
  // Search state
109
148
  const [labelSearchQuery, setLabelSearchQuery] = useState('');
@@ -118,6 +157,7 @@ export function KanbanBoard({
118
157
  const queryClient = useQueryClient();
119
158
  const wsPresence = useOptionalWorkspacePresenceContext();
120
159
  const cursorsEnabled =
160
+ !readOnly &&
121
161
  !workspace.personal &&
122
162
  !!boardId &&
123
163
  !!wsPresence?.cursorsEnabled &&
@@ -126,21 +166,32 @@ export function KanbanBoard({
126
166
  const reorderTaskMutation = useReorderTask(boardId ?? '', workspaceId);
127
167
  const { createTask } = useTaskDialog();
128
168
  const { weekStartsOn } = useCalendarPreferences();
169
+ const boardAssigneesEnabled = canUseBoardAssignees ?? !workspace.personal;
129
170
 
130
- const { data: boardConfig } = useBoardConfig(boardId, workspaceId);
131
- const { data: deadlineTasks = [] } = useQuery({
132
- enabled: Boolean(boardId),
133
- queryFn: () =>
134
- listKanbanDeadlineTasks({
135
- boardId: boardId ?? '',
171
+ const { data: boardConfig } = useBoardConfig(
172
+ readOnly ? null : boardId,
173
+ readOnly ? null : workspaceId
174
+ );
175
+ const { data: deadlineTasks = [], isPending: deadlineTasksPending } =
176
+ useQuery({
177
+ enabled: Boolean(boardId) && !readOnly,
178
+ queryFn: () =>
179
+ listKanbanDeadlineTasks({
180
+ boardId: boardId ?? '',
181
+ taskQueryOptions: deadlineTaskQueryOptions,
182
+ workspaceId,
183
+ }),
184
+ queryKey: getKanbanDeadlineTasksQueryKey(
136
185
  workspaceId,
137
- }),
138
- queryKey: ['kanban-deadline-tasks', workspaceId, boardId],
139
- staleTime: 30_000,
140
- });
186
+ boardId,
187
+ deadlineTaskQueryOptions
188
+ ),
189
+ staleTime: 30_000,
190
+ });
141
191
  const persistListPositions = useCallback(
142
192
  async (updates: Array<{ listId: string; newPosition: number }>) => {
143
193
  if (!boardId || updates.length === 0) return;
194
+ if (readOnly) return;
144
195
 
145
196
  await Promise.all(
146
197
  updates.map(({ listId, newPosition }) =>
@@ -150,7 +201,7 @@ export function KanbanBoard({
150
201
  )
151
202
  );
152
203
  },
153
- [boardId, workspaceId]
204
+ [boardId, readOnly, workspaceId]
154
205
  );
155
206
 
156
207
  const columns: TaskList[] = lists.map((list) => ({
@@ -158,7 +209,28 @@ export function KanbanBoard({
158
209
  title: list.name,
159
210
  }));
160
211
 
161
- const orderedColumns = useMemo(() => sortKanbanColumns(columns), [columns]);
212
+ const orderedColumns = useMemo(() => {
213
+ const sortedColumns = sortKanbanColumns(columns);
214
+ const externalColumns = sortedColumns.filter(
215
+ (column) => column.is_external_staging
216
+ );
217
+ const realColumns = sortedColumns.filter(
218
+ (column) => !column.is_external_staging
219
+ );
220
+
221
+ if (!specialTaskListPins?.closed_tasks) {
222
+ return [...externalColumns, ...realColumns];
223
+ }
224
+
225
+ const closedColumns = realColumns.filter(
226
+ (column) => column.status === 'closed'
227
+ );
228
+ const otherColumns = realColumns.filter(
229
+ (column) => column.status !== 'closed'
230
+ );
231
+
232
+ return [...externalColumns, ...closedColumns, ...otherColumns];
233
+ }, [columns, specialTaskListPins?.closed_tasks]);
162
234
  const orderedRealColumns = useMemo(
163
235
  () => orderedColumns.filter((column) => !column.is_external_staging),
164
236
  [orderedColumns]
@@ -172,22 +244,38 @@ export function KanbanBoard({
172
244
  buildKanbanDeadlineSections({
173
245
  deadlineTasks,
174
246
  lists: orderedColumns,
247
+ now: new Date(deadlineNow),
175
248
  visibleTasks: tasks,
176
249
  }),
177
- [deadlineTasks, orderedColumns, tasks]
250
+ [deadlineNow, deadlineTasks, orderedColumns, tasks]
178
251
  );
179
252
  const deadlineLabels = useMemo(
180
253
  () => ({
254
+ collapseSection: (name: string) => tTasks('collapse_task_list', { name }),
255
+ expandSection: (name: string) => tTasks('expand_task_list', { name }),
256
+ filter: tCommon('filters'),
181
257
  overdue: tTasks('overdue'),
258
+ pinSection: (name: string) => tTasks('pin_task_list', { name }),
259
+ reset: tCommon('reset'),
260
+ showDocuments: tTasks('external_tasks_show_documents'),
261
+ showExternalTasks: tTasks('external_tasks'),
262
+ sort: tCommon('sort'),
263
+ sortCreatedAsc: tBoards('filters.sort_options.oldest_first'),
264
+ sortCreatedDesc: tBoards('filters.sort_options.newest_first'),
265
+ sortDueAsc: tBoards('filters.sort_options.soonest_first'),
266
+ sortDueDesc: tBoards('filters.sort_options.latest_first'),
267
+ sortNameAsc: tTasks('external_tasks_sort_name_asc'),
268
+ sortSourceAsc: tTasks('external_tasks_sort_source_asc'),
269
+ unpinSection: (name: string) => tTasks('unpin_task_list', { name }),
182
270
  upcoming: tTasks('upcoming'),
183
271
  }),
184
- [tTasks]
272
+ [tBoards, tCommon, tTasks]
185
273
  );
186
274
 
187
275
  // Selection Hook
188
276
  const { selectedTasks, handleTaskSelect, clearSelection } = useMultiSelect(
189
277
  tasks,
190
- isMultiSelectMode,
278
+ readOnly ? false : isMultiSelectMode,
191
279
  setIsMultiSelectMode
192
280
  );
193
281
 
@@ -195,6 +283,16 @@ export function KanbanBoard({
195
283
  onBulkSelectionActiveChange?.(selectedTasks.size > 0);
196
284
  }, [onBulkSelectionActiveChange, selectedTasks.size]);
197
285
 
286
+ useEffect(() => {
287
+ if (readOnly) return;
288
+
289
+ const interval = window.setInterval(() => {
290
+ setDeadlineNow(Date.now());
291
+ }, DEADLINE_REFRESH_INTERVAL_MS);
292
+
293
+ return () => window.clearInterval(interval);
294
+ }, [readOnly]);
295
+
198
296
  useEffect(
199
297
  () => () => {
200
298
  onBulkSelectionActiveChange?.(false);
@@ -205,6 +303,9 @@ export function KanbanBoard({
205
303
  // Resources Hooks
206
304
  const { workspaceLabels, workspaceProjects, workspaceMembers } =
207
305
  useBulkResources({
306
+ boardId,
307
+ canUseBoardAssignees: boardAssigneesEnabled,
308
+ assigneeMemberSource,
208
309
  workspace,
209
310
  isMultiSelectMode,
210
311
  selectedCount: selectedTasks.size,
@@ -233,6 +334,7 @@ export function KanbanBoard({
233
334
  columns: orderedRealColumns,
234
335
  workspaceLabels,
235
336
  workspaceProjects,
337
+ workspaceMembers,
236
338
  weekStartsOn,
237
339
  setBulkWorking,
238
340
  clearSelection,
@@ -270,12 +372,13 @@ export function KanbanBoard({
270
372
  columns: orderedRealColumns,
271
373
  boardId,
272
374
  filters,
273
- selectedTasks,
274
- isMultiSelectMode,
275
- setIsMultiSelectMode,
276
- createTask,
375
+ selectedTasks: readOnly ? new Set<string>() : selectedTasks,
376
+ isMultiSelectMode: readOnly ? false : isMultiSelectMode,
377
+ setIsMultiSelectMode: readOnly ? () => {} : setIsMultiSelectMode,
378
+ createTask: readOnly ? () => {} : createTask,
277
379
  clearSelection,
278
380
  handleCrossBoardMove: () => {
381
+ if (readOnly) return;
279
382
  if (selectedTasks.size > 0) {
280
383
  setBoardSelectorOpen(true);
281
384
  }
@@ -360,56 +463,58 @@ export function KanbanBoard({
360
463
  );
361
464
 
362
465
  if (isLoading) {
363
- return <KanbanSkeleton />;
466
+ return <TaskBoardLoadingState />;
364
467
  }
365
468
 
366
469
  return (
367
470
  <div className="flex h-full flex-col">
368
- <BulkActionsIsland
369
- selectedCount={selectedTasks.size}
370
- bulkWorking={bulkWorking}
371
- onClearSelection={clearSelection}
372
- onOpenBoardSelector={() => setBoardSelectorOpen(true)}
373
- menuProps={{
374
- workspace,
375
- boardConfig,
376
- columns: orderedRealColumns,
377
- bulkWorking,
378
- estimationOptions,
379
- appliedSets: appliedSetsMap,
380
- filtered: filteredMap,
381
- search: {
382
- labelQuery: labelSearchQuery,
383
- setLabelQuery: setLabelSearchQuery,
384
- projectQuery: projectSearchQuery,
385
- setProjectQuery: setProjectSearchQuery,
386
- assigneeQuery: assigneeSearchQuery,
387
- setAssigneeQuery: setAssigneeSearchQuery,
388
- },
389
- actions: {
390
- bulkMoveToStatus: (s) => bulkOps.bulkMoveToStatus(s as any),
391
- bulkUpdatePriority: (p) => bulkOps.bulkUpdatePriority(p as any),
392
- bulkUpdateDueDate: (t) => bulkOps.bulkUpdateDueDate(t as any),
393
- bulkUpdateEstimation: bulkOps.bulkUpdateEstimation,
394
- bulkAddLabel: bulkOps.bulkAddLabel,
395
- bulkRemoveLabel: bulkOps.bulkRemoveLabel,
396
- bulkClearLabels: bulkOps.bulkClearLabels,
397
- bulkAddProject: bulkOps.bulkAddProject,
398
- bulkRemoveProject: bulkOps.bulkRemoveProject,
399
- bulkClearProjects: bulkOps.bulkClearProjects,
400
- bulkMoveToList: bulkOps.bulkMoveToList,
401
- bulkAddAssignee: bulkOps.bulkAddAssignee,
402
- bulkRemoveAssignee: bulkOps.bulkRemoveAssignee,
403
- bulkClearAssignees: bulkOps.bulkClearAssignees,
404
- },
405
- onOpenCustomDate: () => setBulkCustomDateOpen(true),
406
- onConfirmDelete: () => setBulkDeleteOpen(true),
407
- }}
408
- />
471
+ {!readOnly && (
472
+ <BulkActionsIsland
473
+ selectedCount={selectedTasks.size}
474
+ bulkWorking={bulkWorking}
475
+ onClearSelection={clearSelection}
476
+ onOpenBoardSelector={() => setBoardSelectorOpen(true)}
477
+ menuProps={{
478
+ workspace,
479
+ boardConfig,
480
+ columns: orderedRealColumns,
481
+ bulkWorking,
482
+ estimationOptions,
483
+ appliedSets: appliedSetsMap,
484
+ filtered: filteredMap,
485
+ search: {
486
+ labelQuery: labelSearchQuery,
487
+ setLabelQuery: setLabelSearchQuery,
488
+ projectQuery: projectSearchQuery,
489
+ setProjectQuery: setProjectSearchQuery,
490
+ assigneeQuery: assigneeSearchQuery,
491
+ setAssigneeQuery: setAssigneeSearchQuery,
492
+ },
493
+ actions: {
494
+ bulkMoveToStatus: (s) => bulkOps.bulkMoveToStatus(s as any),
495
+ bulkUpdatePriority: (p) => bulkOps.bulkUpdatePriority(p as any),
496
+ bulkUpdateDueDate: (t) => bulkOps.bulkUpdateDueDate(t as any),
497
+ bulkUpdateEstimation: bulkOps.bulkUpdateEstimation,
498
+ bulkAddLabel: bulkOps.bulkAddLabel,
499
+ bulkRemoveLabel: bulkOps.bulkRemoveLabel,
500
+ bulkClearLabels: bulkOps.bulkClearLabels,
501
+ bulkAddProject: bulkOps.bulkAddProject,
502
+ bulkRemoveProject: bulkOps.bulkRemoveProject,
503
+ bulkClearProjects: bulkOps.bulkClearProjects,
504
+ bulkMoveToList: bulkOps.bulkMoveToList,
505
+ bulkAddAssignee: bulkOps.bulkAddAssignee,
506
+ bulkRemoveAssignee: bulkOps.bulkRemoveAssignee,
507
+ bulkClearAssignees: bulkOps.bulkClearAssignees,
508
+ },
509
+ onOpenCustomDate: () => setBulkCustomDateOpen(true),
510
+ onConfirmDelete: () => setBulkDeleteOpen(true),
511
+ }}
512
+ />
513
+ )}
409
514
 
410
515
  <div className="min-h-0 flex-1 overflow-x-auto overflow-y-hidden">
411
516
  <DndContext
412
- sensors={sensors}
517
+ sensors={readOnly ? [] : sensors}
413
518
  collisionDetection={kanbanCollisionDetection}
414
519
  onDragStart={onDragStart}
415
520
  onDragMove={onDragMove}
@@ -428,12 +533,14 @@ export function KanbanBoard({
428
533
  boardId={boardId ?? ''}
429
534
  workspaceId={workspaceId}
430
535
  isPersonalWorkspace={workspace.personal}
536
+ canUseBoardAssignees={boardAssigneesEnabled}
537
+ assigneeMemberSource={assigneeMemberSource}
431
538
  cursorsEnabled={cursorsEnabled}
432
539
  disableSort={disableSort}
433
- selectedTasks={selectedTasks}
434
- isMultiSelectMode={isMultiSelectMode}
435
- setIsMultiSelectMode={setIsMultiSelectMode}
436
- onTaskSelect={handleTaskSelect}
540
+ selectedTasks={readOnly ? new Set<string>() : selectedTasks}
541
+ isMultiSelectMode={readOnly ? false : isMultiSelectMode}
542
+ setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
543
+ onTaskSelect={readOnly ? () => {} : handleTaskSelect}
437
544
  onClearSelection={clearSelection}
438
545
  onUpdate={() => {}} // Optimistic updates handled in DnD
439
546
  dragPreviewPosition={dragPreviewPosition}
@@ -450,58 +557,73 @@ export function KanbanBoard({
450
557
  columnsId={columnsId}
451
558
  deadlineLabels={deadlineLabels}
452
559
  deadlineSections={deadlineSections}
560
+ deadlineSectionsLoading={deadlineTasksPending}
561
+ deadlineSectionsCollapsed={deadlineSectionsCollapsed}
562
+ deadlineNow={deadlineNow}
563
+ onDeadlineSectionCollapsedChange={onDeadlineSectionCollapsedChange}
453
564
  onExternalTasksCollapsedChange={onExternalTasksCollapsedChange}
454
565
  onTaskListCollapsedChange={onTaskListCollapsedChange}
566
+ specialTaskListPins={specialTaskListPins}
567
+ onSpecialTaskListPinnedChange={onSpecialTaskListPinnedChange}
568
+ readOnly={readOnly}
455
569
  />
456
570
 
457
- <DragOverlay dropAnimation={null}>
458
- <DragPreview
459
- activeTask={activeTask}
460
- activeColumn={activeColumn}
461
- tasks={tasks}
462
- columns={orderedColumns}
463
- boardId={boardId ?? ''}
464
- isPersonalWorkspace={workspace.personal}
465
- isMultiSelectMode={isMultiSelectMode}
466
- selectedTasks={selectedTasks}
467
- onUpdate={() => {}}
468
- wsId={workspaceId}
469
- />
470
- </DragOverlay>
571
+ {!readOnly && (
572
+ <DragOverlay dropAnimation={null}>
573
+ <DragPreview
574
+ activeTask={activeTask}
575
+ activeColumn={activeColumn}
576
+ tasks={tasks}
577
+ columns={orderedColumns}
578
+ boardId={boardId ?? ''}
579
+ isPersonalWorkspace={workspace.personal}
580
+ canUseBoardAssignees={boardAssigneesEnabled}
581
+ assigneeMemberSource={assigneeMemberSource}
582
+ isMultiSelectMode={isMultiSelectMode}
583
+ selectedTasks={selectedTasks}
584
+ onUpdate={() => {}}
585
+ wsId={workspaceId}
586
+ />
587
+ </DragOverlay>
588
+ )}
471
589
  </DndContext>
472
590
  </div>
473
591
 
474
- <BoardSelector
475
- open={boardSelectorOpen}
476
- onOpenChange={setBoardSelectorOpen}
477
- wsId={workspaceId}
478
- currentBoardId={boardId ?? ''}
479
- taskCount={selectedTasks.size}
480
- onMove={handleBoardMove}
481
- isMoving={bulkWorking}
482
- />
483
-
484
- <BulkDeleteDialog
485
- open={bulkDeleteOpen}
486
- onOpenChange={setBulkDeleteOpen}
487
- selectedCount={selectedTasks.size}
488
- onConfirm={bulkOps.bulkDeleteTasks}
489
- isLoading={bulkWorking}
490
- />
491
-
492
- <BulkCustomDateDialog
493
- open={bulkCustomDateOpen}
494
- onOpenChange={setBulkCustomDateOpen}
495
- onDateChange={(date) => {
496
- bulkOps.bulkUpdateCustomDueDate(date ?? null);
497
- setBulkCustomDateOpen(false);
498
- }}
499
- onClear={() => {
500
- bulkOps.bulkUpdateDueDate('clear');
501
- setBulkCustomDateOpen(false);
502
- }}
503
- isLoading={bulkWorking}
504
- />
592
+ {!readOnly && (
593
+ <>
594
+ <BoardSelector
595
+ open={boardSelectorOpen}
596
+ onOpenChange={setBoardSelectorOpen}
597
+ wsId={workspaceId}
598
+ currentBoardId={boardId ?? ''}
599
+ taskCount={selectedTasks.size}
600
+ onMove={handleBoardMove}
601
+ isMoving={bulkWorking}
602
+ />
603
+
604
+ <BulkDeleteDialog
605
+ open={bulkDeleteOpen}
606
+ onOpenChange={setBulkDeleteOpen}
607
+ selectedCount={selectedTasks.size}
608
+ onConfirm={bulkOps.bulkDeleteTasks}
609
+ isLoading={bulkWorking}
610
+ />
611
+
612
+ <BulkCustomDateDialog
613
+ open={bulkCustomDateOpen}
614
+ onOpenChange={setBulkCustomDateOpen}
615
+ onDateChange={(date) => {
616
+ bulkOps.bulkUpdateCustomDueDate(date ?? null);
617
+ setBulkCustomDateOpen(false);
618
+ }}
619
+ onClear={() => {
620
+ bulkOps.bulkUpdateDueDate('clear');
621
+ setBulkCustomDateOpen(false);
622
+ }}
623
+ isLoading={bulkWorking}
624
+ />
625
+ </>
626
+ )}
505
627
  </div>
506
628
  );
507
629
  }