@tuturuuu/ui 0.8.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.
- package/CHANGELOG.md +40 -0
- package/biome.json +1 -1
- package/package.json +73 -71
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +1 -1
- package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
- package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
- package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
- package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/carousel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
- package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
- package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/color-picker.tsx +1 -1
- package/src/components/ui/command.tsx +1 -1
- package/src/components/ui/context-menu.tsx +5 -1
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
- package/src/components/ui/custom/combobox.test.tsx +195 -0
- package/src/components/ui/custom/combobox.tsx +273 -156
- package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
- package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
- package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
- package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
- package/src/components/ui/custom/theme-toggle.tsx +1 -1
- package/src/components/ui/custom/workspace-select.tsx +8 -3
- package/src/components/ui/dialog.test.tsx +52 -0
- package/src/components/ui/dialog.tsx +6 -2
- package/src/components/ui/dropdown-menu.tsx +5 -1
- package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
- package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
- package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
- package/src/components/ui/finance/debts/debts-page.tsx +15 -2
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
- package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/utils.ts +3 -1
- package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
- package/src/components/ui/finance/transactions/form-types.ts +1 -0
- package/src/components/ui/finance/transactions/form.tsx +2 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
- package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
- package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
- package/src/components/ui/finance/wallets/form.test.tsx +51 -3
- package/src/components/ui/finance/wallets/form.tsx +15 -4
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
- package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
- package/src/components/ui/input-otp.tsx +1 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
- package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
- package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
- package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
- package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
- package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
- package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
- package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
- package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
- package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
- package/src/components/ui/navigation-menu.tsx +1 -1
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/radio-group.tsx +1 -1
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/ui/storefront/cart-popover.tsx +61 -0
- package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
- package/src/components/ui/storefront/cart-summary.tsx +93 -154
- package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
- package/src/components/ui/storefront/listing-card.tsx +1 -1
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +1 -1
- package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
- package/src/components/ui/storefront/storefront-surface.tsx +101 -166
- package/src/components/ui/storefront/types.ts +4 -0
- package/src/components/ui/storefront/utils.ts +6 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/background-color-extension.ts +62 -0
- package/src/components/ui/text-editor/color-controls.tsx +284 -0
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +8 -2
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
- package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
- package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +112 -43
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +397 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +103 -13
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +26 -4
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +5 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
- package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
- package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
- package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
- package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
- package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
- package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
- package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
- package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +203 -2
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
- package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
- package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
- package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +464 -975
- package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
- package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
- package/src/components/ui/tu-do/shared/board-views.tsx +587 -75
- package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
- package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
- package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +2 -1
- package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
- package/src/declarations.d.ts +1 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
- package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
- package/src/hooks/use-calendar-sync.tsx +247 -243
- package/src/hooks/use-calendar.tsx +323 -138
- package/src/hooks/use-task-actions.ts +24 -0
- package/src/hooks/use-user-workspace-config.ts +75 -0
- package/src/hooks/use-workspace-currency.ts +8 -3
- package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
|
@@ -40,6 +40,7 @@ type CalendarConnection = {
|
|
|
40
40
|
export type CalendarEventWithHabitInfo = CalendarEvent & {
|
|
41
41
|
_isHabit?: boolean;
|
|
42
42
|
_habitCompleted?: boolean;
|
|
43
|
+
_optimisticStatus?: CalendarOptimisticStatus;
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
// Sync status type
|
|
@@ -50,13 +51,31 @@ type SyncStatus = {
|
|
|
50
51
|
direction?: 'google-to-tuturuuu' | 'tuturuuu-to-google' | 'both';
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
type
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
export type CalendarOptimisticStatus =
|
|
55
|
+
| 'creating'
|
|
56
|
+
| 'updating'
|
|
57
|
+
| 'deleting'
|
|
58
|
+
| 'error';
|
|
59
|
+
|
|
60
|
+
type OptimisticCalendarSyncEvent = Partial<
|
|
61
|
+
Omit<CalendarEvent, 'color' | 'description' | 'location'>
|
|
62
|
+
> &
|
|
63
|
+
Pick<CalendarEvent, 'id'> & {
|
|
64
|
+
color?: CalendarEvent['color'] | string | null;
|
|
65
|
+
description?: string | null;
|
|
66
|
+
location?: string | null;
|
|
67
|
+
_optimisticStatus?: CalendarOptimisticStatus;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type OptimisticCalendarPatchOptions = {
|
|
71
|
+
removeIds?: string[];
|
|
72
|
+
clearIds?: string[];
|
|
73
|
+
status?: CalendarOptimisticStatus;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type OptimisticCalendarState = {
|
|
77
|
+
events: Record<string, OptimisticCalendarSyncEvent>;
|
|
78
|
+
removedIds: string[];
|
|
60
79
|
};
|
|
61
80
|
|
|
62
81
|
const CalendarSyncContext = createContext<{
|
|
@@ -90,7 +109,7 @@ const CalendarSyncContext = createContext<{
|
|
|
90
109
|
refresh: () => void;
|
|
91
110
|
patchVisibleEvents: (
|
|
92
111
|
events: OptimisticCalendarSyncEvent[],
|
|
93
|
-
options?:
|
|
112
|
+
options?: OptimisticCalendarPatchOptions
|
|
94
113
|
) => void;
|
|
95
114
|
|
|
96
115
|
syncToGoogle: () => Promise<void>;
|
|
@@ -167,15 +186,21 @@ export const CalendarSyncProvider = ({
|
|
|
167
186
|
wsId,
|
|
168
187
|
experimentalGoogleToken: _experimentalGoogleToken,
|
|
169
188
|
initialCalendarConnections = [],
|
|
189
|
+
externalEvents,
|
|
190
|
+
externalEventsLoading = false,
|
|
191
|
+
externalRefresh,
|
|
170
192
|
}: {
|
|
171
193
|
children: React.ReactNode;
|
|
172
194
|
wsId: Workspace['id'];
|
|
173
195
|
experimentalGoogleToken?: WorkspaceCalendarGoogleTokenClient | null;
|
|
174
196
|
initialCalendarConnections?: CalendarConnection[];
|
|
197
|
+
externalEvents?: CalendarEvent[];
|
|
198
|
+
externalEventsLoading?: boolean;
|
|
199
|
+
externalRefresh?: () => void;
|
|
175
200
|
}) => {
|
|
176
|
-
const [data, setData] = useState<WorkspaceCalendarEvent[] | null>(null);
|
|
177
201
|
const [googleData] = useState<WorkspaceCalendarEvent[] | null>(null);
|
|
178
202
|
const [events, setEvents] = useState<CalendarEventWithHabitInfo[]>([]);
|
|
203
|
+
const hasExternalEvents = externalEvents !== undefined;
|
|
179
204
|
|
|
180
205
|
const [error, setError] = useState<Error | null>(null);
|
|
181
206
|
const [dates, setDates] = useState<Date[]>([]);
|
|
@@ -186,6 +211,11 @@ export const CalendarSyncProvider = ({
|
|
|
186
211
|
const [calendarCache, setCalendarCache] = useState<CalendarCache>({});
|
|
187
212
|
const [isSyncing, setIsSyncing] = useState(false);
|
|
188
213
|
const [syncStatus, setSyncStatus] = useState<SyncStatus>({ state: 'idle' });
|
|
214
|
+
const [optimisticState, setOptimisticState] =
|
|
215
|
+
useState<OptimisticCalendarState>({
|
|
216
|
+
events: {},
|
|
217
|
+
removedIds: [],
|
|
218
|
+
});
|
|
189
219
|
const prevDatesRef = useRef<string>('');
|
|
190
220
|
const isForcedRef = useRef<boolean>(false);
|
|
191
221
|
const lastSyncTimeRef = useRef<number>(0);
|
|
@@ -232,6 +262,14 @@ export const CalendarSyncProvider = ({
|
|
|
232
262
|
return `${dateRange[0]!.toISOString()}-${dateRange[dateRange.length - 1]!.toISOString()}`;
|
|
233
263
|
}, []);
|
|
234
264
|
|
|
265
|
+
const activeCacheKey = useMemo(
|
|
266
|
+
() => getCacheKey(dates),
|
|
267
|
+
[dates, getCacheKey]
|
|
268
|
+
);
|
|
269
|
+
const activeCachedDatabaseEvents = activeCacheKey
|
|
270
|
+
? calendarCache[activeCacheKey]?.dbEvents
|
|
271
|
+
: undefined;
|
|
272
|
+
|
|
235
273
|
// Helper to check if a date range includes today (current week issue)
|
|
236
274
|
const includesCurrentWeek = useCallback((dateRange: Date[]) => {
|
|
237
275
|
if (!dateRange || dateRange.length === 0) return false;
|
|
@@ -306,104 +344,112 @@ export const CalendarSyncProvider = ({
|
|
|
306
344
|
});
|
|
307
345
|
}, []);
|
|
308
346
|
|
|
309
|
-
const
|
|
310
|
-
(
|
|
311
|
-
|
|
312
|
-
options?: { removeIds?: string[] }
|
|
313
|
-
) => {
|
|
314
|
-
const cacheKey = getCacheKey(dates);
|
|
315
|
-
if (!cacheKey) return;
|
|
347
|
+
const isVisibleInCurrentRange = useCallback(
|
|
348
|
+
(event: { start_at?: string; end_at?: string }) => {
|
|
349
|
+
if (dates.length === 0) return true;
|
|
316
350
|
|
|
317
351
|
const firstDate = dates[0];
|
|
318
352
|
const lastDate = dates[dates.length - 1];
|
|
319
|
-
if (!firstDate || !lastDate) return;
|
|
353
|
+
if (!firstDate || !lastDate) return true;
|
|
354
|
+
|
|
355
|
+
if (!event.start_at && !event.end_at) return true;
|
|
320
356
|
|
|
321
357
|
const rangeStart = dayjs(firstDate).startOf('day').valueOf();
|
|
322
358
|
const rangeEnd = dayjs(lastDate).endOf('day').valueOf();
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
const isVisibleInRange = (event: {
|
|
326
|
-
start_at?: string;
|
|
327
|
-
end_at?: string;
|
|
328
|
-
}) => {
|
|
329
|
-
const startAt = event.start_at ? dayjs(event.start_at).valueOf() : NaN;
|
|
330
|
-
const endAt = event.end_at ? dayjs(event.end_at).valueOf() : startAt;
|
|
331
|
-
|
|
332
|
-
return !Number.isNaN(startAt) && !Number.isNaN(endAt)
|
|
333
|
-
? startAt <= rangeEnd && endAt >= rangeStart
|
|
334
|
-
: false;
|
|
335
|
-
};
|
|
359
|
+
const startAt = event.start_at ? dayjs(event.start_at).valueOf() : NaN;
|
|
360
|
+
const endAt = event.end_at ? dayjs(event.end_at).valueOf() : startAt;
|
|
336
361
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
362
|
+
if (Number.isNaN(startAt) || Number.isNaN(endAt)) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return startAt <= rangeEnd && endAt >= rangeStart;
|
|
367
|
+
},
|
|
368
|
+
[dates]
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
const patchVisibleEvents = useCallback(
|
|
372
|
+
(
|
|
373
|
+
incomingEvents: OptimisticCalendarSyncEvent[],
|
|
374
|
+
options?: OptimisticCalendarPatchOptions
|
|
375
|
+
) => {
|
|
376
|
+
setOptimisticState((prev) => {
|
|
377
|
+
const events = { ...prev.events };
|
|
378
|
+
const removedIds = new Set(prev.removedIds);
|
|
379
|
+
|
|
380
|
+
for (const id of options?.clearIds ?? []) {
|
|
381
|
+
delete events[id];
|
|
382
|
+
removedIds.delete(id);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const id of options?.removeIds ?? []) {
|
|
386
|
+
delete events[id];
|
|
387
|
+
removedIds.add(id);
|
|
388
|
+
}
|
|
344
389
|
|
|
345
390
|
for (const event of incomingEvents) {
|
|
346
|
-
if (!event.id
|
|
347
|
-
|
|
348
|
-
|
|
391
|
+
if (!event.id) continue;
|
|
392
|
+
|
|
393
|
+
if (!isVisibleInCurrentRange(event)) {
|
|
394
|
+
delete events[event.id];
|
|
395
|
+
removedIds.add(event.id);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
removedIds.delete(event.id);
|
|
400
|
+
|
|
401
|
+
const nextEvent: OptimisticCalendarSyncEvent = {
|
|
402
|
+
...(events[event.id] ?? {}),
|
|
349
403
|
...event,
|
|
350
|
-
}
|
|
351
|
-
}
|
|
404
|
+
};
|
|
352
405
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
};
|
|
406
|
+
if (options?.status) {
|
|
407
|
+
nextEvent._optimisticStatus = options.status;
|
|
408
|
+
} else {
|
|
409
|
+
delete nextEvent._optimisticStatus;
|
|
410
|
+
}
|
|
359
411
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
);
|
|
412
|
+
events[event.id] = nextEvent;
|
|
413
|
+
}
|
|
363
414
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
415
|
+
return {
|
|
416
|
+
events,
|
|
417
|
+
removedIds: [...removedIds],
|
|
418
|
+
};
|
|
367
419
|
});
|
|
368
|
-
setData(nextDbEvents);
|
|
369
|
-
queryClient.setQueryData(
|
|
370
|
-
['databaseCalendarEvents', wsId, cacheKey],
|
|
371
|
-
nextDbEvents
|
|
372
|
-
);
|
|
373
420
|
},
|
|
374
|
-
[
|
|
421
|
+
[isVisibleInCurrentRange]
|
|
375
422
|
);
|
|
376
423
|
|
|
377
424
|
// Fetch database events with caching
|
|
378
425
|
const { data: fetchedData, isLoading: isDatabaseLoading } = useQuery({
|
|
379
|
-
queryKey: ['databaseCalendarEvents', wsId,
|
|
380
|
-
enabled: !!wsId && dates.length > 0,
|
|
426
|
+
queryKey: ['databaseCalendarEvents', wsId, activeCacheKey],
|
|
427
|
+
enabled: !hasExternalEvents && !!wsId && dates.length > 0,
|
|
381
428
|
staleTime: 30000, // Consider data fresh for 30 seconds
|
|
382
429
|
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes
|
|
383
430
|
queryFn: async () => {
|
|
384
|
-
|
|
385
|
-
if (!cacheKey) return null;
|
|
431
|
+
if (!activeCacheKey) return null;
|
|
386
432
|
|
|
387
|
-
const cachedData = calendarCache[
|
|
433
|
+
const cachedData = calendarCache[activeCacheKey];
|
|
388
434
|
|
|
389
435
|
// If we have cached data and it's not stale, return it immediately
|
|
390
436
|
if (
|
|
391
|
-
cachedData
|
|
392
|
-
cachedData.dbEvents.length > 0 &&
|
|
437
|
+
cachedData &&
|
|
393
438
|
!isCacheStaleEnhanced(cachedData.dbLastUpdated, dates) &&
|
|
394
439
|
!isForcedRef.current
|
|
395
440
|
) {
|
|
396
|
-
setData(cachedData.dbEvents);
|
|
397
441
|
return cachedData.dbEvents;
|
|
398
442
|
}
|
|
399
443
|
|
|
400
444
|
// Otherwise fetch fresh data via API (which handles E2EE decryption)
|
|
401
445
|
const startDate = dayjs(dates[0]).startOf('day');
|
|
402
|
-
const endDate = dayjs(dates[dates.length - 1])
|
|
446
|
+
const endDate = dayjs(dates[dates.length - 1])
|
|
447
|
+
.add(1, 'day')
|
|
448
|
+
.startOf('day');
|
|
403
449
|
|
|
404
450
|
try {
|
|
405
451
|
const response = await fetch(
|
|
406
|
-
`/api/v1/workspaces/${wsId}/calendar/events?start_at=${startDate.toISOString()}&end_at=${endDate.
|
|
452
|
+
`/api/v1/workspaces/${wsId}/calendar/events?start_at=${startDate.toISOString()}&end_at=${endDate.toISOString()}`,
|
|
407
453
|
{ cache: 'no-store' }
|
|
408
454
|
);
|
|
409
455
|
|
|
@@ -416,7 +462,7 @@ export const CalendarSyncProvider = ({
|
|
|
416
462
|
const fetchedData = result.data || [];
|
|
417
463
|
|
|
418
464
|
// Update cache with new data and reset isForced flag
|
|
419
|
-
updateCache(
|
|
465
|
+
updateCache(activeCacheKey, {
|
|
420
466
|
dbEvents: fetchedData,
|
|
421
467
|
dbLastUpdated: Date.now(),
|
|
422
468
|
});
|
|
@@ -424,7 +470,7 @@ export const CalendarSyncProvider = ({
|
|
|
424
470
|
// Reset the ref immediately (synchronous)
|
|
425
471
|
isForcedRef.current = false;
|
|
426
472
|
|
|
427
|
-
|
|
473
|
+
setError(null);
|
|
428
474
|
return fetchedData;
|
|
429
475
|
} catch (err) {
|
|
430
476
|
const errorMessage =
|
|
@@ -443,7 +489,7 @@ export const CalendarSyncProvider = ({
|
|
|
443
489
|
lastSyncTime: new Date(),
|
|
444
490
|
});
|
|
445
491
|
|
|
446
|
-
return
|
|
492
|
+
return cachedData?.dbEvents ?? [];
|
|
447
493
|
}
|
|
448
494
|
},
|
|
449
495
|
refetchInterval: 60000, // Reduced from 30s to 60s to lower load
|
|
@@ -452,7 +498,7 @@ export const CalendarSyncProvider = ({
|
|
|
452
498
|
// Legacy direct Google fetch/reconcile is disabled. Provider inbound sync is
|
|
453
499
|
// owned by the workspace sync route so account/calendar identity stays scoped.
|
|
454
500
|
const { isLoading: isGoogleLoading } = useQuery({
|
|
455
|
-
queryKey: ['googleCalendarEvents', wsId,
|
|
501
|
+
queryKey: ['googleCalendarEvents', wsId, activeCacheKey],
|
|
456
502
|
enabled: false,
|
|
457
503
|
staleTime: 30000, // Consider data fresh for 30 seconds
|
|
458
504
|
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes
|
|
@@ -462,17 +508,19 @@ export const CalendarSyncProvider = ({
|
|
|
462
508
|
|
|
463
509
|
// Fetch habit calendar events to identify which events are habits
|
|
464
510
|
const { data: habitEventData } = useQuery({
|
|
465
|
-
queryKey: ['habitCalendarEvents', wsId,
|
|
466
|
-
enabled: !!wsId && dates.length > 0,
|
|
511
|
+
queryKey: ['habitCalendarEvents', wsId, activeCacheKey],
|
|
512
|
+
enabled: !hasExternalEvents && !!wsId && dates.length > 0,
|
|
467
513
|
staleTime: 60000, // Consider data fresh for 1 minute
|
|
468
514
|
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes
|
|
469
515
|
queryFn: async () => {
|
|
470
516
|
const startDate = dayjs(dates[0]).startOf('day');
|
|
471
|
-
const endDate = dayjs(dates[dates.length - 1])
|
|
517
|
+
const endDate = dayjs(dates[dates.length - 1])
|
|
518
|
+
.add(1, 'day')
|
|
519
|
+
.startOf('day');
|
|
472
520
|
|
|
473
521
|
try {
|
|
474
522
|
const response = await fetch(
|
|
475
|
-
`/api/v1/workspaces/${wsId}/calendar/habit-events?start_at=${startDate.toISOString()}&end_at=${endDate.
|
|
523
|
+
`/api/v1/workspaces/${wsId}/calendar/habit-events?start_at=${startDate.toISOString()}&end_at=${endDate.toISOString()}`,
|
|
476
524
|
{ cache: 'no-store' }
|
|
477
525
|
);
|
|
478
526
|
|
|
@@ -516,15 +564,19 @@ export const CalendarSyncProvider = ({
|
|
|
516
564
|
|
|
517
565
|
// Invalidate and refetch events
|
|
518
566
|
const refresh = useCallback(() => {
|
|
519
|
-
|
|
520
|
-
|
|
567
|
+
if (hasExternalEvents) {
|
|
568
|
+
externalRefresh?.();
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!activeCacheKey) return null;
|
|
521
573
|
|
|
522
574
|
isForcedRef.current = true;
|
|
523
575
|
|
|
524
576
|
queryClient.invalidateQueries({
|
|
525
|
-
queryKey: ['databaseCalendarEvents', wsId,
|
|
577
|
+
queryKey: ['databaseCalendarEvents', wsId, activeCacheKey],
|
|
526
578
|
});
|
|
527
|
-
}, [queryClient, wsId,
|
|
579
|
+
}, [queryClient, wsId, activeCacheKey, hasExternalEvents, externalRefresh]);
|
|
528
580
|
|
|
529
581
|
// Sync Google events of current view to Tuturuuu database
|
|
530
582
|
const syncToTuturuuu = useCallback(
|
|
@@ -547,9 +599,6 @@ export const CalendarSyncProvider = ({
|
|
|
547
599
|
const SYNC_COOLDOWN_MS = 30000; // 30 seconds
|
|
548
600
|
|
|
549
601
|
if (!options?.skipCooldown && timeSinceLastSync < SYNC_COOLDOWN_MS) {
|
|
550
|
-
console.log(
|
|
551
|
-
`🔒 Sync skipped - cooldown active (${Math.ceil((SYNC_COOLDOWN_MS - timeSinceLastSync) / 1000)}s remaining)`
|
|
552
|
-
);
|
|
553
602
|
return;
|
|
554
603
|
}
|
|
555
604
|
|
|
@@ -650,13 +699,16 @@ export const CalendarSyncProvider = ({
|
|
|
650
699
|
|
|
651
700
|
// Trigger refetch from DB when changing views (optimized to reduce load)
|
|
652
701
|
useEffect(() => {
|
|
702
|
+
if (hasExternalEvents) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
653
706
|
// Skip if dates haven't actually changed
|
|
654
707
|
if (areDatesEqual(dates)) {
|
|
655
708
|
return;
|
|
656
709
|
}
|
|
657
710
|
|
|
658
|
-
const
|
|
659
|
-
const cacheData = calendarCache[cacheKey];
|
|
711
|
+
const cacheData = calendarCache[activeCacheKey];
|
|
660
712
|
|
|
661
713
|
// For current week, force a fresh database fetch
|
|
662
714
|
const isCurrentWeek = includesCurrentWeek(dates);
|
|
@@ -664,170 +716,137 @@ export const CalendarSyncProvider = ({
|
|
|
664
716
|
if (cacheData && isCurrentWeek) {
|
|
665
717
|
isForcedRef.current = true;
|
|
666
718
|
// For current week, reset database cache timestamp to force refresh
|
|
667
|
-
updateCache(
|
|
719
|
+
updateCache(activeCacheKey, {
|
|
668
720
|
dbLastUpdated: 0,
|
|
669
721
|
});
|
|
670
722
|
}
|
|
671
|
-
|
|
672
|
-
// Only invalidate database queries (cheap) - not Google queries (expensive)
|
|
673
|
-
// Google sync will happen automatically via the 30s cooldown mechanism
|
|
674
|
-
queryClient.invalidateQueries({
|
|
675
|
-
queryKey: ['databaseCalendarEvents', wsId, getCacheKey(dates)],
|
|
676
|
-
});
|
|
677
723
|
}, [
|
|
678
724
|
dates,
|
|
679
|
-
queryClient,
|
|
680
|
-
wsId,
|
|
681
725
|
calendarCache,
|
|
682
726
|
includesCurrentWeek,
|
|
683
727
|
areDatesEqual,
|
|
684
|
-
|
|
728
|
+
activeCacheKey,
|
|
685
729
|
updateCache,
|
|
730
|
+
hasExternalEvents,
|
|
686
731
|
]);
|
|
687
732
|
|
|
688
733
|
/*
|
|
689
734
|
Show data from database to Tuturuuu
|
|
690
735
|
*/
|
|
691
736
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
async (eventsData: CalendarEvent[]) => {
|
|
700
|
-
if (!wsId || !eventsData || eventsData.length === 0) return eventsData;
|
|
701
|
-
|
|
702
|
-
// Group events by their signature
|
|
703
|
-
const eventGroups = new Map<string, CalendarEvent[]>();
|
|
737
|
+
const visibleDatabaseEvents = useMemo(
|
|
738
|
+
() =>
|
|
739
|
+
hasExternalEvents
|
|
740
|
+
? ((externalEvents ?? []) as WorkspaceCalendarEvent[])
|
|
741
|
+
: (fetchedData ?? activeCachedDatabaseEvents ?? []),
|
|
742
|
+
[activeCachedDatabaseEvents, externalEvents, fetchedData, hasExternalEvents]
|
|
743
|
+
);
|
|
704
744
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
745
|
+
const visibleEventsWithOptimisticState = useMemo(() => {
|
|
746
|
+
const removedIds = new Set(optimisticState.removedIds);
|
|
747
|
+
|
|
748
|
+
// Filter external events by enabled provider calendars. Local Tuturuuu
|
|
749
|
+
// events are always shown here; native calendar visibility is handled
|
|
750
|
+
// by the workspace calendar endpoints.
|
|
751
|
+
const filteredEvents =
|
|
752
|
+
!hasExternalEvents && calendarConnections.length > 0
|
|
753
|
+
? (visibleDatabaseEvents as CalendarEvent[]).filter((event) => {
|
|
754
|
+
const eventCalendarId =
|
|
755
|
+
(event as any).external_calendar_id ||
|
|
756
|
+
(event as any).google_calendar_id;
|
|
757
|
+
return !eventCalendarId || enabledCalendarIds.has(eventCalendarId);
|
|
758
|
+
})
|
|
759
|
+
: (visibleDatabaseEvents as CalendarEvent[]);
|
|
760
|
+
|
|
761
|
+
const byId = new Map<string, CalendarEvent>();
|
|
762
|
+
|
|
763
|
+
for (const event of filteredEvents) {
|
|
764
|
+
if (!event.id || removedIds.has(event.id)) continue;
|
|
765
|
+
byId.set(event.id, event);
|
|
766
|
+
}
|
|
712
767
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
eventGroups.forEach((eventGroup) => {
|
|
718
|
-
if (eventGroup.length > 1) {
|
|
719
|
-
// Sort by creation time if available, otherwise by ID
|
|
720
|
-
// Keep the first/oldest event, delete the rest
|
|
721
|
-
const sortedEvents = [...eventGroup].sort((a, b) => {
|
|
722
|
-
// If we have created_at field, use it (check with optional chaining)
|
|
723
|
-
const aCreatedAt = (a as any)?.created_at;
|
|
724
|
-
const bCreatedAt = (b as any)?.created_at;
|
|
725
|
-
if (aCreatedAt && bCreatedAt) {
|
|
726
|
-
return (
|
|
727
|
-
new Date(aCreatedAt).getTime() - new Date(bCreatedAt).getTime()
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
// Otherwise sort by ID which is often sequential
|
|
731
|
-
return a.id.localeCompare(b.id);
|
|
732
|
-
});
|
|
768
|
+
for (const optimisticEvent of Object.values(optimisticState.events)) {
|
|
769
|
+
if (!optimisticEvent.id || removedIds.has(optimisticEvent.id)) continue;
|
|
770
|
+
if (!isVisibleInCurrentRange(optimisticEvent)) continue;
|
|
733
771
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
});
|
|
772
|
+
byId.set(optimisticEvent.id, {
|
|
773
|
+
...(byId.get(optimisticEvent.id) ?? {}),
|
|
774
|
+
...optimisticEvent,
|
|
775
|
+
} as CalendarEvent);
|
|
776
|
+
}
|
|
741
777
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
continue;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
deletionPerformed = true;
|
|
759
|
-
}
|
|
778
|
+
return [...byId.values()].sort(
|
|
779
|
+
(left, right) =>
|
|
780
|
+
new Date(left.start_at).getTime() - new Date(right.start_at).getTime()
|
|
781
|
+
);
|
|
782
|
+
}, [
|
|
783
|
+
calendarConnections.length,
|
|
784
|
+
enabledCalendarIds,
|
|
785
|
+
hasExternalEvents,
|
|
786
|
+
isVisibleInCurrentRange,
|
|
787
|
+
optimisticState.events,
|
|
788
|
+
optimisticState.removedIds,
|
|
789
|
+
visibleDatabaseEvents,
|
|
790
|
+
]);
|
|
760
791
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
792
|
+
useEffect(() => {
|
|
793
|
+
setOptimisticState((prev) => {
|
|
794
|
+
const serverEventsById = new Map(
|
|
795
|
+
(visibleDatabaseEvents as CalendarEvent[])
|
|
796
|
+
.filter((event) => event.id)
|
|
797
|
+
.map((event) => [event.id, event])
|
|
798
|
+
);
|
|
799
|
+
const nextEvents = { ...prev.events };
|
|
800
|
+
const nextRemovedIds = prev.removedIds.filter((id) =>
|
|
801
|
+
serverEventsById.has(id)
|
|
802
|
+
);
|
|
803
|
+
let changed = nextRemovedIds.length !== prev.removedIds.length;
|
|
804
|
+
|
|
805
|
+
for (const [id, event] of Object.entries(prev.events)) {
|
|
806
|
+
const serverEvent = serverEventsById.get(id);
|
|
807
|
+
|
|
808
|
+
if (
|
|
809
|
+
!event._optimisticStatus &&
|
|
810
|
+
serverEvent &&
|
|
811
|
+
(event.title === undefined || serverEvent.title === event.title) &&
|
|
812
|
+
(event.description === undefined ||
|
|
813
|
+
serverEvent.description === event.description) &&
|
|
814
|
+
(event.start_at === undefined ||
|
|
815
|
+
serverEvent.start_at === event.start_at) &&
|
|
816
|
+
(event.end_at === undefined || serverEvent.end_at === event.end_at) &&
|
|
817
|
+
(event.color === undefined || serverEvent.color === event.color) &&
|
|
818
|
+
(event.location === undefined ||
|
|
819
|
+
serverEvent.location === event.location) &&
|
|
820
|
+
(event.locked === undefined || serverEvent.locked === event.locked)
|
|
821
|
+
) {
|
|
822
|
+
delete nextEvents[id];
|
|
823
|
+
changed = true;
|
|
771
824
|
}
|
|
772
825
|
}
|
|
773
826
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
827
|
+
return changed
|
|
828
|
+
? {
|
|
829
|
+
events: nextEvents,
|
|
830
|
+
removedIds: nextRemovedIds,
|
|
831
|
+
}
|
|
832
|
+
: prev;
|
|
833
|
+
});
|
|
834
|
+
}, [visibleDatabaseEvents]);
|
|
781
835
|
|
|
782
836
|
useEffect(() => {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
visibleDatabaseEvents as CalendarEvent[]
|
|
787
|
-
);
|
|
837
|
+
const habitEventIds = habitEventData?.habitEventIds || new Set<string>();
|
|
838
|
+
const completedHabitEventIds =
|
|
839
|
+
habitEventData?.completedHabitEventIds || new Set<string>();
|
|
788
840
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const eventCalendarId =
|
|
796
|
-
(event as any).external_calendar_id ||
|
|
797
|
-
(event as any).google_calendar_id;
|
|
798
|
-
return (
|
|
799
|
-
!eventCalendarId || enabledCalendarIds.has(eventCalendarId)
|
|
800
|
-
);
|
|
801
|
-
})
|
|
802
|
-
: result;
|
|
803
|
-
|
|
804
|
-
// Merge habit info into events
|
|
805
|
-
const habitEventIds =
|
|
806
|
-
habitEventData?.habitEventIds || new Set<string>();
|
|
807
|
-
const completedHabitEventIds =
|
|
808
|
-
habitEventData?.completedHabitEventIds || new Set<string>();
|
|
809
|
-
|
|
810
|
-
const eventsWithHabitInfo: CalendarEventWithHabitInfo[] =
|
|
811
|
-
filteredEvents.map((event) => ({
|
|
812
|
-
...event,
|
|
813
|
-
_isHabit: habitEventIds.has(event.id),
|
|
814
|
-
_habitCompleted: completedHabitEventIds.has(event.id),
|
|
815
|
-
}));
|
|
841
|
+
const eventsWithHabitInfo: CalendarEventWithHabitInfo[] =
|
|
842
|
+
visibleEventsWithOptimisticState.map((event) => ({
|
|
843
|
+
...event,
|
|
844
|
+
_isHabit: habitEventIds.has(event.id),
|
|
845
|
+
_habitCompleted: completedHabitEventIds.has(event.id),
|
|
846
|
+
}));
|
|
816
847
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
setEvents([]);
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
|
|
823
|
-
processEvents();
|
|
824
|
-
}, [
|
|
825
|
-
visibleDatabaseEvents,
|
|
826
|
-
removeDuplicateEvents,
|
|
827
|
-
calendarConnections.length,
|
|
828
|
-
enabledCalendarIds,
|
|
829
|
-
habitEventData,
|
|
830
|
-
]);
|
|
848
|
+
setEvents(eventsWithHabitInfo);
|
|
849
|
+
}, [habitEventData, visibleEventsWithOptimisticState]);
|
|
831
850
|
|
|
832
851
|
const eventsWithoutAllDays = useMemo(() => {
|
|
833
852
|
// Process events immediately when they change
|
|
@@ -847,23 +866,6 @@ export const CalendarSyncProvider = ({
|
|
|
847
866
|
});
|
|
848
867
|
}, [events]);
|
|
849
868
|
|
|
850
|
-
// Add a ref to track if we've processed the initial data
|
|
851
|
-
const hasProcessedInitialData = useRef(false);
|
|
852
|
-
|
|
853
|
-
// Effect to process initial data
|
|
854
|
-
useEffect(() => {
|
|
855
|
-
if (fetchedData && !hasProcessedInitialData.current) {
|
|
856
|
-
hasProcessedInitialData.current = true;
|
|
857
|
-
// Force a re-render by updating the data state
|
|
858
|
-
setData(fetchedData);
|
|
859
|
-
}
|
|
860
|
-
}, [fetchedData]);
|
|
861
|
-
|
|
862
|
-
// Effect to reset the processed flag when dates change
|
|
863
|
-
useEffect(() => {
|
|
864
|
-
hasProcessedInitialData.current = false;
|
|
865
|
-
}, []);
|
|
866
|
-
|
|
867
869
|
const syncToGoogle = useCallback(async () => {
|
|
868
870
|
toast.info('Provider events sync when you create or edit them.');
|
|
869
871
|
setSyncStatus({
|
|
@@ -875,7 +877,9 @@ export const CalendarSyncProvider = ({
|
|
|
875
877
|
}, []);
|
|
876
878
|
|
|
877
879
|
const value = {
|
|
878
|
-
data
|
|
880
|
+
data: hasExternalEvents
|
|
881
|
+
? ((externalEvents ?? []) as WorkspaceCalendarEvent[])
|
|
882
|
+
: (fetchedData ?? activeCachedDatabaseEvents ?? null),
|
|
879
883
|
googleData,
|
|
880
884
|
error,
|
|
881
885
|
dates,
|
|
@@ -905,7 +909,7 @@ export const CalendarSyncProvider = ({
|
|
|
905
909
|
syncStatus,
|
|
906
910
|
|
|
907
911
|
// Loading states
|
|
908
|
-
isLoading: isDatabaseLoading || isGoogleLoading,
|
|
912
|
+
isLoading: externalEventsLoading || isDatabaseLoading || isGoogleLoading,
|
|
909
913
|
isSyncing,
|
|
910
914
|
};
|
|
911
915
|
|