@tuturuuu/ui 0.7.0 → 0.9.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 (226) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/biome.json +1 -1
  3. package/package.json +75 -73
  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/currency-input.test.tsx +43 -0
  29. package/src/components/ui/currency-input.tsx +1 -1
  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 +19 -0
  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/settings-dialog-shell.tsx +2 -1
  40. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  41. package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
  42. package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
  43. package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
  44. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
  45. package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
  46. package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
  47. package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
  48. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
  49. package/src/components/ui/custom/workspace-select.tsx +8 -3
  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 +3 -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-card.tsx +21 -9
  69. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  70. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  71. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  72. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  73. package/src/components/ui/finance/wallets/form.tsx +15 -4
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  75. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  76. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  77. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  78. package/src/components/ui/input-otp.tsx +1 -1
  79. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  80. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  81. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  82. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  83. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  84. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  85. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  86. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  87. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  88. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  89. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  90. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  91. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  92. package/src/components/ui/money-input.test.tsx +64 -0
  93. package/src/components/ui/money-input.tsx +63 -0
  94. package/src/components/ui/navigation-menu.tsx +1 -1
  95. package/src/components/ui/pagination.tsx +1 -1
  96. package/src/components/ui/radio-group.tsx +1 -1
  97. package/src/components/ui/select.tsx +5 -1
  98. package/src/components/ui/sheet.tsx +1 -1
  99. package/src/components/ui/sidebar.tsx +1 -1
  100. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  101. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  102. package/src/components/ui/storefront/cart-summary.tsx +104 -80
  103. package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
  104. package/src/components/ui/storefront/hero-panel.tsx +2 -8
  105. package/src/components/ui/storefront/image-panel.tsx +6 -0
  106. package/src/components/ui/storefront/index.ts +11 -0
  107. package/src/components/ui/storefront/listing-card.tsx +84 -22
  108. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  109. package/src/components/ui/storefront/product-detail.tsx +289 -0
  110. package/src/components/ui/storefront/product-dialog.tsx +72 -0
  111. package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
  112. package/src/components/ui/storefront/storefront-surface.tsx +288 -153
  113. package/src/components/ui/storefront/types.ts +27 -1
  114. package/src/components/ui/storefront/utils.ts +117 -27
  115. package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
  116. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  117. package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
  118. package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
  119. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  120. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  121. package/src/components/ui/text-editor/content-migration.ts +41 -18
  122. package/src/components/ui/text-editor/editor.tsx +69 -14
  123. package/src/components/ui/text-editor/extensions.ts +9 -3
  124. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  125. package/src/components/ui/text-editor/image-extension.ts +40 -18
  126. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  127. package/src/components/ui/text-editor/video-extension.ts +11 -2
  128. package/src/components/ui/toast.tsx +1 -1
  129. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  130. package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
  131. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  132. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  133. package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
  134. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
  135. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  136. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  139. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  141. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +51 -9
  142. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  143. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  148. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  149. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  150. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  151. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  152. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  153. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  154. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  155. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  156. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  157. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +410 -4
  158. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
  159. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  160. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  161. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  162. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +186 -0
  163. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -2
  164. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  165. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  166. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  167. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  168. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  169. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  170. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
  171. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
  172. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
  173. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
  174. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
  175. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  176. package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
  177. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  178. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  179. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  180. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  181. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  182. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  183. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  184. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  185. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  186. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +237 -3
  187. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  188. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  189. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  190. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  191. package/src/components/ui/tu-do/shared/board-header.tsx +465 -937
  192. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  193. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  194. package/src/components/ui/tu-do/shared/board-views.tsx +596 -82
  195. package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
  196. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  197. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  198. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  199. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  200. package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
  201. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
  202. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
  203. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
  204. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
  205. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  206. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  210. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +44 -15
  211. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  212. package/src/declarations.d.ts +1 -0
  213. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  214. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  215. package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
  216. package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
  217. package/src/hooks/use-calendar-sync.tsx +247 -243
  218. package/src/hooks/use-calendar.tsx +323 -138
  219. package/src/hooks/use-task-actions.ts +24 -0
  220. package/src/hooks/use-user-workspace-config.ts +75 -0
  221. package/src/hooks/use-workspace-currency.ts +8 -3
  222. package/src/hooks/useBoardRealtime.ts +6 -3
  223. package/src/hooks/useBoardRealtime.types.ts +11 -0
  224. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
  225. package/src/hooks/useCursorTracking.ts +91 -27
  226. package/src/hooks/useTaskUserRealtime.ts +5 -3
@@ -48,6 +48,13 @@ import { Separator } from '../../separator';
48
48
  import { Switch } from '../../switch';
49
49
  import type { CalendarConnectionsManagerState } from './use-calendar-connections-manager';
50
50
 
51
+ function isWritableProviderAccess(accessRole?: string | null) {
52
+ if (!accessRole) return true;
53
+ return ['owner', 'writer', 'write', 'editor'].includes(
54
+ accessRole.toLowerCase()
55
+ );
56
+ }
57
+
51
58
  export function CalendarConnectionsSettingsContent({
52
59
  state,
53
60
  className,
@@ -75,13 +82,26 @@ export function CalendarConnectionsSettingsContent({
75
82
  setNewCalendarName,
76
83
  setShowCreateCalendarDialog,
77
84
  showCreateCalendarDialog,
85
+ syncPreferencesData,
86
+ syncPreferencesMutation,
78
87
  systemCalendars,
79
88
  t,
80
89
  togglingIds,
81
90
  togglingTuturuuuIds,
82
91
  toggleAccountExpanded,
83
92
  toggleWorkspaceCalendarMutation,
93
+ updateConnectionSyncSettingsMutation,
84
94
  } = state;
95
+ const externalSyncOptions =
96
+ syncPreferencesData?.options.filter(
97
+ (option) => option.provider !== 'tuturuuu'
98
+ ) ?? [];
99
+ const syncSettingsUnavailable =
100
+ syncPreferencesData?.settingsAvailable === false;
101
+ const syncSettingsDisabled =
102
+ !syncPreferencesData ||
103
+ syncSettingsUnavailable ||
104
+ syncPreferencesMutation.isPending;
85
105
 
86
106
  return (
87
107
  <div className={className ? `space-y-4 ${className}` : 'space-y-4'}>
@@ -128,6 +148,126 @@ export function CalendarConnectionsSettingsContent({
128
148
  </Select>
129
149
  </div>
130
150
 
151
+ <div className="space-y-3 rounded-md border p-3">
152
+ <div>
153
+ <h4 className="font-medium text-sm">
154
+ {t('two_way_sync') || 'Two-way sync'}
155
+ </h4>
156
+ <p className="text-muted-foreground text-xs">
157
+ {t('two_way_sync_desc') ||
158
+ 'Control how Tuturuuu imports provider changes and mirrors native events outward.'}
159
+ </p>
160
+ </div>
161
+
162
+ {syncSettingsUnavailable && (
163
+ <p className="rounded-md bg-muted px-3 py-2 text-muted-foreground text-xs">
164
+ {t('calendar_sync_settings_unavailable') ||
165
+ 'Sync controls will be available after the latest calendar migration is applied.'}
166
+ </p>
167
+ )}
168
+
169
+ <div className="space-y-2">
170
+ <div className="flex items-center justify-between gap-3">
171
+ <div className="min-w-0">
172
+ <p className="font-medium text-sm">
173
+ {t('import_external_changes') || 'Import external changes'}
174
+ </p>
175
+ <p className="text-muted-foreground text-xs">
176
+ {t('import_external_changes_desc') ||
177
+ 'Bring Google and Outlook event changes into Tuturuuu.'}
178
+ </p>
179
+ </div>
180
+ <Switch
181
+ checked={syncPreferencesData?.inboundSyncEnabled ?? true}
182
+ onCheckedChange={(checked) =>
183
+ syncPreferencesMutation.mutate({
184
+ inboundSyncEnabled: checked,
185
+ })
186
+ }
187
+ disabled={syncSettingsDisabled}
188
+ />
189
+ </div>
190
+
191
+ <div className="flex items-center justify-between gap-3">
192
+ <div className="min-w-0">
193
+ <p className="font-medium text-sm">
194
+ {t('sync_tuturuuu_events_outward') ||
195
+ 'Sync Tuturuuu events outward'}
196
+ </p>
197
+ <p className="text-muted-foreground text-xs">
198
+ {t('sync_tuturuuu_events_outward_desc') ||
199
+ 'Mirror native Tuturuuu event creates and edits to a connected provider calendar.'}
200
+ </p>
201
+ </div>
202
+ <Switch
203
+ checked={syncPreferencesData?.outboundSyncEnabled ?? false}
204
+ onCheckedChange={(checked) =>
205
+ syncPreferencesMutation.mutate({
206
+ outboundSyncEnabled: checked,
207
+ })
208
+ }
209
+ disabled={
210
+ syncSettingsDisabled || externalSyncOptions.length === 0
211
+ }
212
+ />
213
+ </div>
214
+ </div>
215
+
216
+ <Select
217
+ value={syncPreferencesData?.defaultOutboundConnectionId ?? 'none'}
218
+ onValueChange={(value) =>
219
+ syncPreferencesMutation.mutate({
220
+ defaultOutboundConnectionId: value === 'none' ? null : value,
221
+ })
222
+ }
223
+ disabled={syncSettingsDisabled || externalSyncOptions.length === 0}
224
+ >
225
+ <SelectTrigger className="w-full">
226
+ <SelectValue
227
+ placeholder={
228
+ t('choose_outbound_calendar') || 'Choose outbound calendar'
229
+ }
230
+ />
231
+ </SelectTrigger>
232
+ <SelectContent>
233
+ <SelectItem value="none">
234
+ {t('no_outbound_calendar') || 'No outbound calendar'}
235
+ </SelectItem>
236
+ {externalSyncOptions.map((option) => (
237
+ <SelectItem key={option.id} value={option.connectionId}>
238
+ <div className="flex min-w-0 items-center gap-2">
239
+ <span
240
+ className="h-2.5 w-2.5 shrink-0 rounded-full"
241
+ style={{
242
+ backgroundColor: option.color ?? undefined,
243
+ }}
244
+ />
245
+ <span className="truncate">{option.label}</span>
246
+ <Badge variant="secondary" className="capitalize">
247
+ {option.provider}
248
+ </Badge>
249
+ </div>
250
+ </SelectItem>
251
+ ))}
252
+ </SelectContent>
253
+ </Select>
254
+
255
+ <div className="flex items-center justify-between gap-3 rounded-md bg-muted/40 px-3 py-2">
256
+ <div className="min-w-0">
257
+ <p className="font-medium text-xs">
258
+ {t('conflict_policy') || 'Conflict policy'}
259
+ </p>
260
+ <p className="text-muted-foreground text-xs">
261
+ {t('latest_write_wins_desc') ||
262
+ 'When both sides changed, the most recently written event wins.'}
263
+ </p>
264
+ </div>
265
+ <Badge variant="outline">
266
+ {t('latest_write_wins') || 'Latest write wins'}
267
+ </Badge>
268
+ </div>
269
+ </div>
270
+
131
271
  {/* Tuturuuu Calendars Section */}
132
272
  <div className="space-y-3">
133
273
  <div className="flex items-center justify-between">
@@ -370,40 +510,106 @@ export function CalendarConnectionsSettingsContent({
370
510
  <Separator />
371
511
  <div className="space-y-2 p-3">
372
512
  {(calendarsByAccount[account.id] || []).length > 0 ? (
373
- (calendarsByAccount[account.id] || []).map((cal) => (
374
- <div
375
- key={cal.id}
376
- className="flex items-center justify-between gap-2 rounded-md px-2 py-1.5 hover:bg-muted/50"
377
- >
378
- <div className="flex min-w-0 flex-1 items-center gap-2">
379
- <div
380
- className="h-3 w-3 shrink-0 rounded-full"
381
- style={{
382
- backgroundColor: cal.color || '#4285f4',
383
- }}
384
- />
385
- <span
386
- className="line-clamp-1 break-all text-sm"
387
- title={cal.calendar_name}
388
- >
389
- {cal.calendar_name}
390
- </span>
513
+ (calendarsByAccount[account.id] || []).map((cal) => {
514
+ const syncControlsDisabled =
515
+ !cal.connectionExists ||
516
+ !cal.is_enabled ||
517
+ syncSettingsDisabled ||
518
+ updateConnectionSyncSettingsMutation.isPending;
519
+ const canWriteToProvider = isWritableProviderAccess(
520
+ cal.accessRole
521
+ );
522
+
523
+ return (
524
+ <div
525
+ key={cal.id}
526
+ className="rounded-md px-2 py-1.5 hover:bg-muted/50"
527
+ >
528
+ <div className="flex items-center justify-between gap-2">
529
+ <div className="flex min-w-0 flex-1 items-center gap-2">
530
+ <div
531
+ className="h-3 w-3 shrink-0 rounded-full"
532
+ style={{
533
+ backgroundColor: cal.color || '#4285f4',
534
+ }}
535
+ />
536
+ <span
537
+ className="line-clamp-1 break-all text-sm"
538
+ title={cal.calendar_name}
539
+ >
540
+ {cal.calendar_name}
541
+ </span>
542
+ </div>
543
+ <Switch
544
+ checked={cal.is_enabled}
545
+ onCheckedChange={() =>
546
+ handleToggle(cal.id, cal.is_enabled, {
547
+ calendar_id: cal.calendar_id,
548
+ calendar_name: cal.calendar_name,
549
+ color: cal.color,
550
+ connectionExists: cal.connectionExists,
551
+ accountId: cal.accountId,
552
+ accessRole: cal.accessRole,
553
+ })
554
+ }
555
+ disabled={togglingIds.has(cal.id)}
556
+ />
557
+ </div>
558
+
559
+ {cal.connectionExists && (
560
+ <div className="mt-2 grid grid-cols-3 gap-2 pl-5">
561
+ <label className="flex items-center justify-between gap-2 rounded-md bg-muted/40 px-2 py-1 text-muted-foreground text-xs">
562
+ <span>{t('import') || 'Import'}</span>
563
+ <Switch
564
+ checked={cal.syncInboundEnabled}
565
+ onCheckedChange={(checked) =>
566
+ updateConnectionSyncSettingsMutation.mutate(
567
+ {
568
+ id: cal.id,
569
+ syncInboundEnabled: checked,
570
+ }
571
+ )
572
+ }
573
+ disabled={syncControlsDisabled}
574
+ />
575
+ </label>
576
+ <label className="flex items-center justify-between gap-2 rounded-md bg-muted/40 px-2 py-1 text-muted-foreground text-xs">
577
+ <span>{t('send') || 'Send'}</span>
578
+ <Switch
579
+ checked={cal.syncOutboundEnabled}
580
+ onCheckedChange={(checked) =>
581
+ updateConnectionSyncSettingsMutation.mutate(
582
+ {
583
+ id: cal.id,
584
+ syncOutboundEnabled: checked,
585
+ }
586
+ )
587
+ }
588
+ disabled={
589
+ syncControlsDisabled || !canWriteToProvider
590
+ }
591
+ />
592
+ </label>
593
+ <label className="flex items-center justify-between gap-2 rounded-md bg-muted/40 px-2 py-1 text-muted-foreground text-xs">
594
+ <span>{t('deletes') || 'Deletes'}</span>
595
+ <Switch
596
+ checked={cal.syncDeleteEnabled}
597
+ onCheckedChange={(checked) =>
598
+ updateConnectionSyncSettingsMutation.mutate(
599
+ {
600
+ id: cal.id,
601
+ syncDeleteEnabled: checked,
602
+ }
603
+ )
604
+ }
605
+ disabled={syncControlsDisabled}
606
+ />
607
+ </label>
608
+ </div>
609
+ )}
391
610
  </div>
392
- <Switch
393
- checked={cal.is_enabled}
394
- onCheckedChange={() =>
395
- handleToggle(cal.id, cal.is_enabled, {
396
- calendar_id: cal.calendar_id,
397
- calendar_name: cal.calendar_name,
398
- color: cal.color,
399
- connectionExists: cal.connectionExists,
400
- accountId: cal.accountId,
401
- })
402
- }
403
- disabled={togglingIds.has(cal.id)}
404
- />
405
- </div>
406
- ))
611
+ );
612
+ })
407
613
  ) : (
408
614
  <p className="py-2 text-center text-muted-foreground text-xs">
409
615
  {t('no_calendars_found')}
@@ -0,0 +1,143 @@
1
+ import { createAdminClient } from '@tuturuuu/supabase/next/server';
2
+ import type { ExtendedWorkspaceTask } from '../../time-tracker/types';
3
+
4
+ interface LoadSmartSchedulingTasksOptions {
5
+ resolvedWsId: string;
6
+ userId: string;
7
+ }
8
+
9
+ interface AccessibleTaskRow {
10
+ task_id: string;
11
+ task_name: string | null;
12
+ task_description: string | null;
13
+ task_creator_id: string | null;
14
+ task_list_id: string | null;
15
+ task_start_date: string | null;
16
+ task_end_date: string | null;
17
+ task_priority: ExtendedWorkspaceTask['priority'];
18
+ task_completed_at: string | null;
19
+ task_closed_at: string | null;
20
+ task_deleted_at: string | null;
21
+ task_estimation_points: number | null;
22
+ task_created_at: string | null;
23
+ }
24
+
25
+ interface TaskListWorkspaceRow {
26
+ id?: string | null;
27
+ workspace_boards?: {
28
+ ws_id?: string | null;
29
+ } | null;
30
+ }
31
+
32
+ interface TaskSchedulingRow {
33
+ task_id?: string | null;
34
+ total_duration: number | null;
35
+ is_splittable: boolean | null;
36
+ min_split_duration_minutes: number | null;
37
+ max_split_duration_minutes: number | null;
38
+ calendar_hours: ExtendedWorkspaceTask['calendar_hours'];
39
+ auto_schedule: boolean | null;
40
+ }
41
+
42
+ export async function loadSmartSchedulingTasks({
43
+ resolvedWsId,
44
+ userId,
45
+ }: LoadSmartSchedulingTasksOptions): Promise<ExtendedWorkspaceTask[]> {
46
+ const supabase = await createAdminClient({ noCookie: true });
47
+ const { data: rpcTasks } = await supabase.rpc('get_user_accessible_tasks', {
48
+ p_user_id: userId,
49
+ p_ws_id: resolvedWsId,
50
+ p_include_deleted: false,
51
+ p_list_statuses: ['not_started', 'active'],
52
+ });
53
+
54
+ const tasksBase = ((rpcTasks ?? []) as AccessibleTaskRow[]).map((task) => ({
55
+ id: task.task_id,
56
+ name: task.task_name,
57
+ description: task.task_description,
58
+ creator_id: task.task_creator_id,
59
+ list_id: task.task_list_id,
60
+ start_date: task.task_start_date,
61
+ end_date: task.task_end_date,
62
+ due_date: task.task_end_date,
63
+ priority: task.task_priority,
64
+ completed_at: task.task_completed_at,
65
+ closed_at: task.task_closed_at,
66
+ deleted_at: task.task_deleted_at,
67
+ estimation_points: task.task_estimation_points,
68
+ created_at: task.task_created_at,
69
+ })) as ExtendedWorkspaceTask[];
70
+
71
+ const listIds = Array.from(
72
+ new Set(tasksBase.map((task) => task.list_id).filter(Boolean))
73
+ ) as string[];
74
+
75
+ const wsIdByListId = new Map<string, string>();
76
+ if (listIds.length > 0) {
77
+ const { data: lists } = await supabase
78
+ .from('task_lists')
79
+ .select(
80
+ `
81
+ id,
82
+ workspace_boards!inner (
83
+ ws_id
84
+ )
85
+ `
86
+ )
87
+ .in('id', listIds);
88
+
89
+ (lists as TaskListWorkspaceRow[] | null)?.forEach((list) => {
90
+ const taskWsId = list.workspace_boards?.ws_id;
91
+ if (list.id && taskWsId) wsIdByListId.set(list.id, taskWsId);
92
+ });
93
+ }
94
+
95
+ const tasks = tasksBase.map((task) => ({
96
+ ...task,
97
+ ws_id:
98
+ (task.list_id ? wsIdByListId.get(task.list_id) : undefined) ??
99
+ resolvedWsId,
100
+ })) as ExtendedWorkspaceTask[];
101
+
102
+ const taskIds = tasks.map((task) => task.id).filter(Boolean);
103
+ const settingsByTaskId = new Map<string, TaskSchedulingRow>();
104
+
105
+ if (taskIds.length > 0) {
106
+ const { data: schedulingRows } = await supabase
107
+ .from('task_user_scheduling_settings')
108
+ .select(
109
+ `
110
+ task_id,
111
+ total_duration,
112
+ is_splittable,
113
+ min_split_duration_minutes,
114
+ max_split_duration_minutes,
115
+ calendar_hours,
116
+ auto_schedule
117
+ `
118
+ )
119
+ .eq('user_id', userId)
120
+ .in('task_id', taskIds);
121
+
122
+ (schedulingRows as TaskSchedulingRow[] | null)?.forEach((row) => {
123
+ if (row.task_id) settingsByTaskId.set(row.task_id, row);
124
+ });
125
+ }
126
+
127
+ return tasks.map((task) => {
128
+ const settings = settingsByTaskId.get(task.id);
129
+ return settings
130
+ ? ({
131
+ ...task,
132
+ total_duration: settings.total_duration,
133
+ is_splittable: settings.is_splittable ?? false,
134
+ min_split_duration_minutes:
135
+ settings.min_split_duration_minutes ?? null,
136
+ max_split_duration_minutes:
137
+ settings.max_split_duration_minutes ?? null,
138
+ calendar_hours: settings.calendar_hours ?? null,
139
+ auto_schedule: settings.auto_schedule ?? false,
140
+ } as ExtendedWorkspaceTask)
141
+ : task;
142
+ });
143
+ }
@@ -23,6 +23,7 @@ import {
23
23
  } from '@tuturuuu/icons';
24
24
  import {
25
25
  listWorkspaceTaskLists,
26
+ listWorkspaceTasks,
26
27
  updateWorkspaceTask,
27
28
  } from '@tuturuuu/internal-api/tasks';
28
29
  import type { TaskPriority } from '@tuturuuu/types/primitives/Priority';
@@ -40,7 +41,6 @@ import ActionsDropdown from './actions-dropdown';
40
41
  import PriorityDropdown from './priority-dropdown';
41
42
  import { QuickTaskDialog } from './quick-task-dialog';
42
43
  import { SchedulingDialog } from './scheduling-dialog';
43
- import { getAssignedTasks } from './task-fetcher';
44
44
 
45
45
  // Priority labels (matching task-properties-section.tsx)
46
46
  const PRIORITY_LABELS: Record<TaskPriority, string> = {
@@ -356,8 +356,15 @@ export default function PriorityView({
356
356
  setSearchError(null);
357
357
 
358
358
  try {
359
- const results = await getAssignedTasks(assigneeId, searchQuery.trim());
360
- setSearchResults(results);
359
+ const results = await listWorkspaceTasks(wsId, {
360
+ assigneeIds: [assigneeId],
361
+ closed: 'exclude',
362
+ completed: 'exclude',
363
+ includeArchivedBoards: true,
364
+ limit: 50,
365
+ q: searchQuery.trim(),
366
+ });
367
+ setSearchResults(results.tasks as ExtendedWorkspaceTask[]);
361
368
  } catch (error) {
362
369
  console.error('Error searching tasks:', error);
363
370
  setSearchError('Failed to search tasks');
@@ -1,4 +1,3 @@
1
- import { createAdminClient } from '@tuturuuu/supabase/next/server';
2
1
  import type { ExtendedWorkspaceTask } from '../../time-tracker/types';
3
2
  import { CalendarSidebar } from './sidebar';
4
3
 
@@ -6,126 +5,15 @@ interface TasksSidebarProps {
6
5
  resolvedWsId: string;
7
6
  locale: string;
8
7
  userId: string;
8
+ tasks?: ExtendedWorkspaceTask[];
9
9
  }
10
10
 
11
- export default async function TasksSidebar({
11
+ export default function TasksSidebar({
12
12
  resolvedWsId,
13
13
  locale,
14
14
  userId,
15
+ tasks = [],
15
16
  }: TasksSidebarProps) {
16
- // Use the same RPC as the tasks page to get accessible tasks
17
- const supabase = await createAdminClient({ noCookie: true });
18
- const { data: rpcTasks } = await supabase.rpc('get_user_accessible_tasks', {
19
- p_user_id: userId,
20
- p_ws_id: resolvedWsId,
21
- p_include_deleted: false,
22
- p_list_statuses: ['not_started', 'active'],
23
- });
24
-
25
- // Map RPC results to match expected structure (same as my-tasks-data-loader.tsx)
26
- const tasksBase = (rpcTasks?.map((task) => ({
27
- id: task.task_id,
28
- name: task.task_name,
29
- description: task.task_description,
30
- creator_id: task.task_creator_id,
31
- list_id: task.task_list_id,
32
- start_date: task.task_start_date,
33
- end_date: task.task_end_date,
34
- due_date: task.task_end_date, // Map end_date to due_date for display
35
- priority: task.task_priority,
36
- completed_at: task.task_completed_at,
37
- closed_at: task.task_closed_at,
38
- deleted_at: task.task_deleted_at,
39
- estimation_points: task.task_estimation_points,
40
- created_at: task.task_created_at,
41
- // Scheduling fields are per-user now. These legacy RPC fields may not exist after migration.
42
- })) || []) as ExtendedWorkspaceTask[];
43
-
44
- // Enrich tasks with the *actual* workspace UUID (ws_id) via list -> board relation.
45
- // This is critical for personal workspace views where tasks may belong to other workspaces.
46
- const listIds = Array.from(
47
- new Set(tasksBase.map((t) => t.list_id).filter(Boolean))
48
- ) as string[];
49
-
50
- const wsIdByListId = new Map<string, string>();
51
- if (listIds.length > 0) {
52
- const { data: lists } = await supabase
53
- .from('task_lists')
54
- .select(
55
- `
56
- id,
57
- workspace_boards!inner (
58
- ws_id
59
- )
60
- `
61
- )
62
- .in('id', listIds);
63
-
64
- (lists as any[] | null)?.forEach((l) => {
65
- const resolvedTaskWsId = l?.workspace_boards?.ws_id;
66
- if (l?.id && resolvedTaskWsId) wsIdByListId.set(l.id, resolvedTaskWsId);
67
- });
68
- }
69
-
70
- const tasks = tasksBase.map((t) => ({
71
- ...t,
72
- ws_id:
73
- (t.list_id ? wsIdByListId.get(t.list_id) : undefined) ??
74
- // Fallback: if a task is list-less, treat it as current workspace-scoped.
75
- resolvedWsId,
76
- })) as ExtendedWorkspaceTask[];
77
-
78
- // Merge per-user scheduling settings so the calendar UI can still show duration/hour type.
79
- const taskIds = tasks.map((t) => t.id).filter(Boolean);
80
- const settingsByTaskId = new Map<
81
- string,
82
- {
83
- total_duration: number | null;
84
- is_splittable: boolean | null;
85
- min_split_duration_minutes: number | null;
86
- max_split_duration_minutes: number | null;
87
- calendar_hours: any;
88
- auto_schedule: boolean | null;
89
- }
90
- >();
91
-
92
- if (taskIds.length > 0) {
93
- const { data: schedulingRows } = await (supabase as any)
94
- .from('task_user_scheduling_settings')
95
- .select(
96
- `
97
- task_id,
98
- total_duration,
99
- is_splittable,
100
- min_split_duration_minutes,
101
- max_split_duration_minutes,
102
- calendar_hours,
103
- auto_schedule
104
- `
105
- )
106
- .eq('user_id', userId)
107
- .in('task_id', taskIds);
108
-
109
- (schedulingRows as any[] | null)?.forEach((r) => {
110
- if (r?.task_id) settingsByTaskId.set(r.task_id, r);
111
- });
112
- }
113
-
114
- const tasksWithScheduling = tasks.map((t) => {
115
- const s = settingsByTaskId.get(t.id);
116
- return s
117
- ? ({
118
- ...t,
119
- total_duration: s.total_duration,
120
- is_splittable: s.is_splittable ?? false,
121
- min_split_duration_minutes: s.min_split_duration_minutes ?? null,
122
- max_split_duration_minutes: s.max_split_duration_minutes ?? null,
123
- calendar_hours: s.calendar_hours ?? null,
124
- auto_schedule: s.auto_schedule ?? false,
125
- } as ExtendedWorkspaceTask)
126
- : t;
127
- });
128
-
129
17
  // Personal workspace = workspace ID matches user ID (no need for auto-assignment)
130
18
  const isPersonalWorkspace = resolvedWsId === userId;
131
19
 
@@ -135,7 +23,7 @@ export default async function TasksSidebar({
135
23
  // Always pass the resolved workspace UUID, not the route slug (e.g. "personal").
136
24
  wsId={resolvedWsId}
137
25
  assigneeId={userId}
138
- tasks={tasksWithScheduling}
26
+ tasks={tasks}
139
27
  locale={locale}
140
28
  isPersonalWorkspace={isPersonalWorkspace}
141
29
  />