@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
@@ -1,4 +1,10 @@
1
- import { createWorkspaceCalendarEvent } from '@tuturuuu/internal-api';
1
+ import {
2
+ createWorkspaceCalendarEvent,
3
+ deleteWorkspaceCalendarEvent,
4
+ updateWorkspaceCalendarEvent,
5
+ type WorkspaceCalendarEventCreatePayload,
6
+ type WorkspaceCalendarEventUpdatePayload,
7
+ } from '@tuturuuu/internal-api';
2
8
  import { createClient } from '@tuturuuu/supabase/next/client';
3
9
  import type {
4
10
  Workspace,
@@ -38,17 +44,28 @@ const roundToNearest15Minutes = (date: Date): Date => {
38
44
  return roundedDate;
39
45
  };
40
46
 
41
- // Function to create a unique signature for an event based on its content
42
- const createEventSignature = (event: CalendarEvent): string => {
43
- return `${event.title}|${event.description || ''}|${event.start_at}|${event.end_at}`;
44
- };
45
-
46
47
  type TaskDragData = {
47
48
  name?: string;
48
49
  priority?: string | null;
49
50
  totalDuration?: number;
50
51
  };
51
52
 
53
+ const createOptimisticEventId = () => {
54
+ if (
55
+ typeof crypto !== 'undefined' &&
56
+ typeof crypto.randomUUID === 'function'
57
+ ) {
58
+ return `optimistic-${crypto.randomUUID()}`;
59
+ }
60
+
61
+ return `optimistic-${Date.now()}-${Math.random().toString(36).slice(2)}`;
62
+ };
63
+
64
+ const noStoreFetchOptions = {
65
+ fetch: (input: RequestInfo | URL, init?: RequestInit) =>
66
+ fetch(input, { ...init, cache: 'no-store' }),
67
+ };
68
+
52
69
  function patchWorkspaceCalendarEventCache(
53
70
  queryClient: any,
54
71
  wsId: string,
@@ -165,6 +182,10 @@ const CalendarContext = createContext<{
165
182
  setHideNonPreviewEvents: (hide: boolean) => void;
166
183
  // UX: allow callers (e.g. create button) to influence default tab for *new* events.
167
184
  defaultNewEventTab: 'manual' | 'ai';
185
+ disableBuiltInEventUi: boolean;
186
+ preservePastEventOpacity: boolean;
187
+ renderEventContextMenu?: (event: CalendarEvent) => ReactNode;
188
+ isEventReadOnly: (event: CalendarEvent) => boolean;
168
189
  readOnly: boolean;
169
190
  }>({
170
191
  getEvent: () => undefined,
@@ -218,6 +239,10 @@ const CalendarContext = createContext<{
218
239
  hideNonPreviewEvents: false,
219
240
  setHideNonPreviewEvents: () => undefined,
220
241
  defaultNewEventTab: 'manual',
242
+ disableBuiltInEventUi: false,
243
+ preservePastEventOpacity: false,
244
+ renderEventContextMenu: undefined,
245
+ isEventReadOnly: () => false,
221
246
  readOnly: false,
222
247
  });
223
248
 
@@ -226,10 +251,31 @@ interface PendingEventUpdate extends Partial<CalendarEvent> {
226
251
  _updateId?: string;
227
252
  _timestamp: number;
228
253
  _eventId: string;
229
- _resolve?: (value: CalendarEvent) => void;
230
- _reject?: (reason: any) => void;
254
+ _previousEvent?: CalendarEvent;
255
+ _resolvers?: Array<{
256
+ resolve: (value: CalendarEvent) => void;
257
+ reject: (reason: unknown) => void;
258
+ }>;
231
259
  }
232
260
 
261
+ export type CalendarEventAdapter = {
262
+ disableBuiltInEventUi?: boolean;
263
+ preservePastEventOpacity?: boolean;
264
+ renderContextMenu?: (event: CalendarEvent) => ReactNode;
265
+ isEventReadOnly?: (event: CalendarEvent) => boolean;
266
+ onCreate?: (
267
+ event: Omit<CalendarEvent, 'id'>
268
+ ) => Promise<CalendarEvent | undefined> | CalendarEvent | undefined;
269
+ onCreateDraft?: (event: CalendarEvent) => void;
270
+ onDelete?: (eventId: string, event?: CalendarEvent) => Promise<void> | void;
271
+ onOpen?: (eventId?: string, event?: CalendarEvent) => void;
272
+ onUpdate?: (
273
+ eventId: string,
274
+ updates: Partial<CalendarEvent>,
275
+ event?: CalendarEvent
276
+ ) => Promise<CalendarEvent | undefined> | CalendarEvent | undefined;
277
+ };
278
+
233
279
  /**
234
280
  * Syncs task total_duration after a calendar event is resized or moved.
235
281
  * - Uses canonical workspace calendar events as the source of truth
@@ -376,6 +422,7 @@ export const CalendarProvider = ({
376
422
  useQueryClient,
377
423
  children,
378
424
  experimentalGoogleToken: _experimentalGoogleToken,
425
+ eventAdapter,
379
426
  readOnly = false,
380
427
  }: {
381
428
  ws?: Workspace;
@@ -383,6 +430,7 @@ export const CalendarProvider = ({
383
430
  useQueryClient: any;
384
431
  children: ReactNode;
385
432
  experimentalGoogleToken?: WorkspaceCalendarGoogleTokenClient | null;
433
+ eventAdapter?: CalendarEventAdapter;
386
434
  readOnly?: boolean;
387
435
  }) => {
388
436
  const queryClient = useQueryClient();
@@ -397,7 +445,7 @@ export const CalendarProvider = ({
397
445
  const updateQueueRef = useRef<PendingEventUpdate[]>([]);
398
446
  const isProcessingQueueRef = useRef<boolean>(false);
399
447
 
400
- const { events, refresh } = useCalendarSync();
448
+ const { events, refresh, patchVisibleEvents } = useCalendarSync();
401
449
 
402
450
  // Modal state
403
451
  const [activeEventId, setActiveEventId] = useState<string | null>(null);
@@ -586,7 +634,6 @@ export const CalendarProvider = ({
586
634
  console.warn('Calendar is in read-only mode');
587
635
  return undefined;
588
636
  }
589
- if (!ws) throw new Error('No workspace selected');
590
637
 
591
638
  // Round start and end times to nearest 15-minute interval
592
639
  const startDate = roundToNearest15Minutes(new Date(event.start_at));
@@ -594,62 +641,77 @@ export const CalendarProvider = ({
594
641
 
595
642
  const eventColor = event.color || 'BLUE';
596
643
 
597
- // Create an event signature to check for duplicates
598
- const newEventSignature = `${event.title || ''}|${event.description || ''}|${startDate.toISOString()}|${endDate.toISOString()}`;
599
-
600
- // Check existing events for potential duplicates to prevent race condition
601
- const duplicates = events.filter((e: CalendarEvent) => {
602
- const existingSignature = createEventSignature(e);
603
- return existingSignature === newEventSignature;
604
- });
605
-
606
- // If duplicates already exist, return the first one
607
- if (duplicates.length > 0) {
608
- // Clear any pending new event
644
+ if (eventAdapter?.onCreate) {
645
+ const created = await eventAdapter.onCreate({
646
+ ...event,
647
+ start_at: startDate.toISOString(),
648
+ end_at: endDate.toISOString(),
649
+ color: eventColor as SupportedColor,
650
+ });
609
651
  setPendingNewEvent(null);
610
-
611
- // Return the existing event
612
- return duplicates[0];
652
+ refresh();
653
+ return created;
613
654
  }
614
655
 
615
- // No duplicates, proceed with creating the event via API (handles E2EE encryption)
616
- const response = await fetch(
617
- `/api/v1/workspaces/${ws.id}/calendar/events`,
618
- {
619
- method: 'POST',
620
- headers: { 'Content-Type': 'application/json' },
621
- body: JSON.stringify({
622
- title: event.title || '',
623
- description: event.description || '',
624
- start_at: startDate.toISOString(),
625
- end_at: endDate.toISOString(),
626
- color: eventColor as SupportedColor,
627
- location: event.location || '',
628
- locked: true,
629
- source: event.source,
630
- }),
631
- }
632
- );
656
+ if (!ws) throw new Error('No workspace selected');
633
657
 
634
- if (!response.ok) {
635
- const errorData = await response.json();
636
- throw new Error(errorData.error || 'Failed to create event');
637
- }
658
+ const payload: WorkspaceCalendarEventCreatePayload = {
659
+ title: event.title || '',
660
+ description: event.description || '',
661
+ start_at: startDate.toISOString(),
662
+ end_at: endDate.toISOString(),
663
+ color: eventColor as SupportedColor,
664
+ location: event.location || '',
665
+ locked: true,
666
+ task_id: (event as CalendarEvent & { task_id?: string | null }).task_id,
667
+ source: event.source,
668
+ };
669
+ const optimisticId = createOptimisticEventId();
670
+ const optimisticEvent = {
671
+ ...event,
672
+ ...payload,
673
+ id: optimisticId,
674
+ ws_id: ws.id,
675
+ color: eventColor as SupportedColor,
676
+ _optimisticStatus: 'creating' as const,
677
+ };
638
678
 
639
- const data = (await response.json()) as CalendarEvent;
679
+ patchVisibleEvents([optimisticEvent], { status: 'creating' });
640
680
 
641
- // Refresh the query cache after adding an event
642
- refresh();
681
+ try {
682
+ const data = await createWorkspaceCalendarEvent(
683
+ ws.id,
684
+ payload,
685
+ noStoreFetchOptions
686
+ );
687
+
688
+ patchVisibleEvents([data], { clearIds: [optimisticId] });
689
+ patchWorkspaceCalendarEventCache(queryClient, ws.id, (existing) => {
690
+ if (existing.some((item) => item.id === data.id)) {
691
+ return existing.map((item) =>
692
+ item.id === data.id
693
+ ? ({ ...item, ...data } as CalendarEvent)
694
+ : item
695
+ );
696
+ }
643
697
 
644
- if (data) {
645
- // Clear any pending new event
698
+ return [...existing, data].sort(
699
+ (left, right) =>
700
+ new Date(left.start_at).getTime() -
701
+ new Date(right.start_at).getTime()
702
+ );
703
+ });
704
+
705
+ // Refresh the query cache after adding an event
706
+ refresh();
646
707
  setPendingNewEvent(null);
647
708
  return data as CalendarEvent;
709
+ } catch (error) {
710
+ patchVisibleEvents([], { clearIds: [optimisticId] });
711
+ throw error;
648
712
  }
649
-
650
- return {} as CalendarEvent;
651
713
  },
652
- [ws, refresh, events, readOnly]
714
+ [ws, readOnly, eventAdapter, patchVisibleEvents, queryClient, refresh]
653
715
  );
654
716
 
655
717
  const addEmptyEvent = useCallback(
@@ -699,6 +761,12 @@ export const CalendarProvider = ({
699
761
  ws_id: ws?.id || '',
700
762
  };
701
763
 
764
+ if (eventAdapter) {
765
+ eventAdapter.onCreateDraft?.(newEvent);
766
+ eventAdapter.onOpen?.(undefined, newEvent);
767
+ return newEvent as CalendarEvent;
768
+ }
769
+
702
770
  // Store the pending new event
703
771
  setPendingNewEvent(newEvent);
704
772
  setActiveEventId('new');
@@ -709,7 +777,7 @@ export const CalendarProvider = ({
709
777
  // Return the pending event object
710
778
  return newEvent as CalendarEvent;
711
779
  },
712
- [ws?.id]
780
+ [ws?.id, eventAdapter]
713
781
  );
714
782
 
715
783
  const addEmptyEventWithDuration = useCallback(
@@ -732,6 +800,12 @@ export const CalendarProvider = ({
732
800
  ws_id: ws?.id || '',
733
801
  };
734
802
 
803
+ if (eventAdapter) {
804
+ eventAdapter.onCreateDraft?.(newEvent);
805
+ eventAdapter.onOpen?.(undefined, newEvent);
806
+ return newEvent as CalendarEvent;
807
+ }
808
+
735
809
  // Store the pending new event
736
810
  setPendingNewEvent(newEvent);
737
811
  setActiveEventId('new');
@@ -742,7 +816,7 @@ export const CalendarProvider = ({
742
816
  // Return the pending event object
743
817
  return newEvent as CalendarEvent;
744
818
  },
745
- [ws?.id]
819
+ [ws?.id, eventAdapter]
746
820
  );
747
821
 
748
822
  // Process the update queue
@@ -776,33 +850,35 @@ export const CalendarProvider = ({
776
850
  _updateId,
777
851
  _timestamp,
778
852
  _eventId,
779
- _resolve,
780
- _reject,
853
+ _previousEvent,
854
+ _resolvers,
781
855
  ...updateData
782
856
  } = update;
857
+ pendingUpdatesRef.current.delete(eventId);
783
858
 
784
859
  // Check if the event exists before trying to update
785
- const existingEvent = events.find((e: CalendarEvent) => e.id === eventId);
860
+ const existingEvent =
861
+ _previousEvent ?? events.find((e: CalendarEvent) => e.id === eventId);
786
862
  if (!existingEvent) {
787
863
  const errorMsg = `Event with ID ${eventId} not found in local events`;
788
- if (_reject) {
789
- _reject(new Error(errorMsg));
790
- }
864
+ _resolvers?.forEach(({ reject }) => {
865
+ reject(new Error(errorMsg));
866
+ });
791
867
  return;
792
868
  }
793
869
 
794
870
  // Validate workspace ownership
795
871
  if (existingEvent.ws_id !== ws?.id) {
796
872
  const errorMsg = `Event ${eventId} does not belong to current workspace (${ws?.id})`;
797
- if (_reject) {
798
- _reject(new Error(errorMsg));
799
- }
873
+ _resolvers?.forEach(({ reject }) => {
874
+ reject(new Error(errorMsg));
875
+ });
800
876
  return;
801
877
  }
802
878
 
803
879
  try {
804
880
  // Clean up the update data to ensure no undefined values and exclude system fields
805
- const cleanUpdateData: Partial<CalendarEvent> = {
881
+ const cleanUpdateData: WorkspaceCalendarEventUpdatePayload = {
806
882
  ...(updateData.title !== undefined && { title: updateData.title }),
807
883
  ...(updateData.description !== undefined && {
808
884
  description: updateData.description,
@@ -822,25 +898,14 @@ export const CalendarProvider = ({
822
898
  // ws is guaranteed to be defined here (validated above at line 732)
823
899
  const wsId = ws!.id;
824
900
 
825
- // Use API endpoint which handles E2EE encryption
826
- const response = await fetch(
827
- `/api/v1/workspaces/${wsId}/calendar/events/${eventId}`,
828
- {
829
- method: 'PUT',
830
- headers: { 'Content-Type': 'application/json' },
831
- body: JSON.stringify(cleanUpdateData),
832
- }
833
- );
834
-
835
- if (!response.ok) {
836
- const errorData = await response.json();
837
- throw new Error(errorData.error || 'Failed to update event');
838
- }
839
-
840
901
  // The API response includes task_id from the database which is not in CalendarEvent type
841
- const data = (await response.json()) as CalendarEvent & {
842
- task_id?: string | null;
843
- };
902
+ const data = (await updateWorkspaceCalendarEvent(
903
+ wsId,
904
+ eventId,
905
+ cleanUpdateData,
906
+ noStoreFetchOptions
907
+ )) as CalendarEvent & { task_id?: string | null };
908
+ const hasNewerPendingUpdate = pendingUpdatesRef.current.has(eventId);
844
909
 
845
910
  // If event times changed, sync task's total_duration
846
911
  if (data) {
@@ -851,6 +916,10 @@ export const CalendarProvider = ({
851
916
  : event
852
917
  )
853
918
  );
919
+
920
+ if (!hasNewerPendingUpdate) {
921
+ patchVisibleEvents([data]);
922
+ }
854
923
  }
855
924
 
856
925
  if (data && (cleanUpdateData.start_at || cleanUpdateData.end_at)) {
@@ -875,20 +944,23 @@ export const CalendarProvider = ({
875
944
 
876
945
  if (data) {
877
946
  // Resolve the promise for this update
878
- if (_resolve) {
879
- _resolve(data as CalendarEvent);
880
- }
947
+ _resolvers?.forEach(({ resolve }) => {
948
+ resolve(data as CalendarEvent);
949
+ });
881
950
  } else {
882
- if (_reject) {
883
- _reject(
951
+ _resolvers?.forEach(({ reject }) => {
952
+ reject(
884
953
  new Error(`Failed to update event ${eventId} - no data returned`)
885
954
  );
886
- }
955
+ });
887
956
  }
888
957
  } catch (err) {
889
- if (_reject) {
890
- _reject(err);
958
+ if (!pendingUpdatesRef.current.has(eventId) && _previousEvent) {
959
+ patchVisibleEvents([_previousEvent]);
891
960
  }
961
+ _resolvers?.forEach(({ reject }) => {
962
+ reject(err);
963
+ });
892
964
  }
893
965
  } finally {
894
966
  isProcessingQueueRef.current = false;
@@ -898,7 +970,15 @@ export const CalendarProvider = ({
898
970
  setTimeout(processUpdateQueue, 50); // Small delay to prevent blocking
899
971
  }
900
972
  }
901
- }, [refresh, events, ws, queryClient, onTaskScheduled, readOnly]);
973
+ }, [
974
+ refresh,
975
+ events,
976
+ ws,
977
+ queryClient,
978
+ onTaskScheduled,
979
+ readOnly,
980
+ patchVisibleEvents,
981
+ ]);
902
982
 
903
983
  const updateEvent = useCallback(
904
984
  async (eventId: string, eventUpdates: Partial<CalendarEvent>) => {
@@ -906,7 +986,6 @@ export const CalendarProvider = ({
906
986
  console.warn('Calendar is in read-only mode');
907
987
  return undefined;
908
988
  }
909
- if (!ws) throw new Error('No workspace selected');
910
989
 
911
990
  // Clean and validate the event updates - only allow known CalendarEvent fields
912
991
  const allowedFields: (keyof CalendarEvent)[] = [
@@ -947,38 +1026,35 @@ export const CalendarProvider = ({
947
1026
  cleanedUpdates.locked = true;
948
1027
  }
949
1028
 
1029
+ if (eventAdapter?.onUpdate || eventAdapter?.onCreate) {
1030
+ if (pendingNewEvent && eventId === 'new') {
1031
+ const result = await addEvent({
1032
+ ...pendingNewEvent,
1033
+ ...cleanedUpdates,
1034
+ } as Omit<CalendarEvent, 'id'>);
1035
+ return result;
1036
+ }
1037
+
1038
+ const existingEvent = events.find(
1039
+ (event: CalendarEvent) => event.id === eventId
1040
+ );
1041
+ const result = await eventAdapter.onUpdate?.(
1042
+ eventId,
1043
+ cleanedUpdates,
1044
+ existingEvent
1045
+ );
1046
+ refresh();
1047
+ return result;
1048
+ }
1049
+
1050
+ if (!ws) throw new Error('No workspace selected');
1051
+
950
1052
  // If this is a newly created event that hasn't been saved to the database yet
951
1053
  if (pendingNewEvent && eventId === 'new') {
952
1054
  const newEventData = {
953
1055
  ...pendingNewEvent,
954
1056
  ...cleanedUpdates,
955
1057
  };
956
- // Check for potential duplicates before creating a new event
957
- if (cleanedUpdates.title || pendingNewEvent.title) {
958
- const startDate = roundToNearest15Minutes(
959
- new Date(newEventData.start_at || new Date())
960
- );
961
- const endDate = roundToNearest15Minutes(
962
- new Date(newEventData.end_at || new Date())
963
- );
964
-
965
- const newEventSignature = `${newEventData.title || ''}|${newEventData.description || ''}|${startDate.toISOString()}|${endDate.toISOString()}`;
966
-
967
- // Check existing events for potential duplicates
968
- const duplicates = events.filter((e: CalendarEvent) => {
969
- const existingSignature = createEventSignature(e);
970
- return existingSignature === newEventSignature;
971
- });
972
-
973
- // If duplicates already exist, return the first one
974
- if (duplicates.length > 0) {
975
- // Clear any pending new event
976
- setPendingNewEvent(null);
977
-
978
- // Return the existing event
979
- return duplicates[0];
980
- }
981
- }
982
1058
 
983
1059
  // Create a new event instead of updating
984
1060
  const result = await addEvent(
@@ -987,26 +1063,55 @@ export const CalendarProvider = ({
987
1063
  return result;
988
1064
  }
989
1065
 
1066
+ const existingEvent = events.find(
1067
+ (event: CalendarEvent) => event.id === eventId
1068
+ );
1069
+ if (!existingEvent) {
1070
+ throw new Error(`Event with ID ${eventId} not found in local events`);
1071
+ }
1072
+
1073
+ patchVisibleEvents(
1074
+ [
1075
+ {
1076
+ ...existingEvent,
1077
+ ...cleanedUpdates,
1078
+ _optimisticStatus: 'updating',
1079
+ } as CalendarEvent & { _optimisticStatus: 'updating' },
1080
+ ],
1081
+ { status: 'updating' }
1082
+ );
1083
+
990
1084
  // Generate a unique update ID to track this specific update request
991
1085
  const updateId = `${eventId}-${Date.now()}`;
992
1086
  const timestamp = Date.now();
993
1087
 
994
1088
  // Create a promise that will resolve when the update is actually performed
995
1089
  return new Promise<CalendarEvent>((resolve, reject) => {
1090
+ const existingPending = pendingUpdatesRef.current.get(eventId);
1091
+ const resolvers = [
1092
+ ...(existingPending?._resolvers ?? []),
1093
+ { resolve, reject },
1094
+ ];
1095
+
996
1096
  // Create the update object with the promise callbacks
997
1097
  const updateObject: PendingEventUpdate = {
1098
+ ...(existingPending ?? {}),
998
1099
  ...cleanedUpdates,
999
1100
  _updateId: updateId,
1000
1101
  _timestamp: timestamp,
1001
1102
  _eventId: eventId,
1002
- _resolve: resolve,
1003
- _reject: reject,
1103
+ _previousEvent: existingPending?._previousEvent ?? existingEvent,
1104
+ _resolvers: resolvers,
1004
1105
  };
1005
1106
 
1006
1107
  // Store the latest update for this event
1007
1108
  pendingUpdatesRef.current.set(eventId, updateObject);
1008
1109
 
1009
- // Add to the queue
1110
+ // Keep only the newest queued payload per event. Promise callers are
1111
+ // retained in _resolvers and settle from the single coalesced request.
1112
+ updateQueueRef.current = updateQueueRef.current.filter(
1113
+ (queuedUpdate) => queuedUpdate._eventId !== eventId
1114
+ );
1010
1115
  updateQueueRef.current.push(updateObject);
1011
1116
 
1012
1117
  // Clear any existing timer
@@ -1021,7 +1126,17 @@ export const CalendarProvider = ({
1021
1126
  }, 250); // Reduced from 2000ms to 250ms for better responsiveness
1022
1127
  });
1023
1128
  },
1024
- [ws, processUpdateQueue, pendingNewEvent, addEvent, events, readOnly]
1129
+ [
1130
+ ws,
1131
+ processUpdateQueue,
1132
+ pendingNewEvent,
1133
+ addEvent,
1134
+ events,
1135
+ readOnly,
1136
+ eventAdapter,
1137
+ refresh,
1138
+ patchVisibleEvents,
1139
+ ]
1025
1140
  );
1026
1141
 
1027
1142
  const deleteEvent = useCallback(
@@ -1039,6 +1154,17 @@ export const CalendarProvider = ({
1039
1154
  return;
1040
1155
  }
1041
1156
 
1157
+ if (eventAdapter?.onDelete) {
1158
+ await eventAdapter.onDelete(
1159
+ eventId,
1160
+ events.find((event: CalendarEvent) => event.id === eventId)
1161
+ );
1162
+ refresh();
1163
+ setActiveEventId(null);
1164
+ setPreviewEventId(null);
1165
+ return;
1166
+ }
1167
+
1042
1168
  if (!ws) throw new Error('No workspace selected');
1043
1169
 
1044
1170
  const eventToDelete = events.find(
@@ -1049,23 +1175,38 @@ export const CalendarProvider = ({
1049
1175
  throw new Error('No workspace selected');
1050
1176
  }
1051
1177
 
1052
- const deleteResponse = await fetch(
1053
- `/api/v1/workspaces/${ws.id}/calendar/events/${eventId}`,
1054
- {
1055
- method: 'DELETE',
1056
- }
1057
- );
1058
-
1059
- if (!deleteResponse.ok) {
1060
- const errorData = await deleteResponse.json().catch(() => null);
1061
- throw new Error(errorData?.error || 'Failed to delete event');
1178
+ if (eventToDelete) {
1179
+ patchVisibleEvents(
1180
+ [
1181
+ {
1182
+ ...eventToDelete,
1183
+ _optimisticStatus: 'deleting',
1184
+ } as CalendarEvent & { _optimisticStatus: 'deleting' },
1185
+ ],
1186
+ { status: 'deleting' }
1187
+ );
1062
1188
  }
1063
1189
 
1064
- const deleteResult = (await deleteResponse.json()) as {
1190
+ let deleteResult: {
1065
1191
  linkedTaskId?: string | null;
1066
1192
  skippedHabitId?: string | null;
1067
1193
  };
1068
1194
 
1195
+ try {
1196
+ deleteResult = await deleteWorkspaceCalendarEvent(
1197
+ ws.id,
1198
+ eventId,
1199
+ noStoreFetchOptions
1200
+ );
1201
+ } catch (error) {
1202
+ if (eventToDelete) {
1203
+ patchVisibleEvents([eventToDelete]);
1204
+ }
1205
+ throw error;
1206
+ }
1207
+
1208
+ patchVisibleEvents([], { removeIds: [eventId] });
1209
+
1069
1210
  const hasLinkedTask =
1070
1211
  !!deleteResult.linkedTaskId || !!eventToDelete?.task_id;
1071
1212
  const hasLinkedHabit = !!deleteResult.skippedHabitId;
@@ -1093,6 +1234,8 @@ export const CalendarProvider = ({
1093
1234
  queryClient,
1094
1235
  onTaskScheduled,
1095
1236
  readOnly,
1237
+ eventAdapter,
1238
+ patchVisibleEvents,
1096
1239
  ]
1097
1240
  );
1098
1241
 
@@ -1104,6 +1247,36 @@ export const CalendarProvider = ({
1104
1247
  (eventId?: string, options?: { defaultNewEventTab?: 'manual' | 'ai' }) => {
1105
1248
  setPreviewEventId(null);
1106
1249
 
1250
+ if (eventAdapter?.onOpen) {
1251
+ if (eventId) {
1252
+ eventAdapter.onOpen(
1253
+ eventId,
1254
+ events.find((event: CalendarEvent) => event.id === eventId)
1255
+ );
1256
+ return;
1257
+ }
1258
+
1259
+ setDefaultNewEventTab(options?.defaultNewEventTab ?? 'manual');
1260
+
1261
+ const now = roundToNearest15Minutes(new Date());
1262
+ const oneHourLater = new Date(now);
1263
+ oneHourLater.setHours(oneHourLater.getHours() + 1);
1264
+
1265
+ const newEvent: CalendarEvent = {
1266
+ id: 'new',
1267
+ title: '',
1268
+ description: '',
1269
+ start_at: now.toISOString(),
1270
+ end_at: oneHourLater.toISOString(),
1271
+ color: 'BLUE',
1272
+ ws_id: ws?.id || '',
1273
+ };
1274
+
1275
+ eventAdapter.onCreateDraft?.(newEvent);
1276
+ eventAdapter.onOpen(undefined, newEvent);
1277
+ return;
1278
+ }
1279
+
1107
1280
  if (eventId) {
1108
1281
  setActiveEventId(eventId);
1109
1282
  setPendingNewEvent(null);
@@ -1130,7 +1303,7 @@ export const CalendarProvider = ({
1130
1303
  setActiveEventId('new');
1131
1304
  setModalHidden(false);
1132
1305
  },
1133
- [ws?.id]
1306
+ [ws?.id, eventAdapter, events]
1134
1307
  );
1135
1308
 
1136
1309
  const openModal = useCallback(
@@ -1139,6 +1312,14 @@ export const CalendarProvider = ({
1139
1312
  _modalType?: 'all-day' | 'event',
1140
1313
  options?: { defaultNewEventTab?: 'manual' | 'ai' }
1141
1314
  ) => {
1315
+ if (eventAdapter?.onOpen && eventId) {
1316
+ eventAdapter.onOpen(
1317
+ eventId,
1318
+ events.find((event: CalendarEvent) => event.id === eventId)
1319
+ );
1320
+ return;
1321
+ }
1322
+
1142
1323
  if (eventId) {
1143
1324
  setPendingNewEvent(null);
1144
1325
  setActiveEventId(null);
@@ -1148,7 +1329,7 @@ export const CalendarProvider = ({
1148
1329
 
1149
1330
  openEventEditor(undefined, options);
1150
1331
  },
1151
- [openEventEditor]
1332
+ [openEventEditor, eventAdapter, events]
1152
1333
  );
1153
1334
 
1154
1335
  const closeModal = useCallback(() => {
@@ -1511,6 +1692,10 @@ export const CalendarProvider = ({
1511
1692
  hideNonPreviewEvents,
1512
1693
  setHideNonPreviewEvents,
1513
1694
  defaultNewEventTab,
1695
+ disableBuiltInEventUi: eventAdapter?.disableBuiltInEventUi ?? false,
1696
+ preservePastEventOpacity: eventAdapter?.preservePastEventOpacity ?? false,
1697
+ renderEventContextMenu: eventAdapter?.renderContextMenu,
1698
+ isEventReadOnly: eventAdapter?.isEventReadOnly ?? (() => false),
1514
1699
  readOnly,
1515
1700
  };
1516
1701