@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.
- package/CHANGELOG.md +88 -0
- package/biome.json +1 -1
- package/package.json +75 -73
- 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/currency-input.test.tsx +43 -0
- package/src/components/ui/currency-input.tsx +1 -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-access/workspace-access-default-role-card.tsx +60 -35
- package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
- package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
- package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
- package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
- package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
- package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
- 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 +3 -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-card.tsx +21 -9
- 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/money-input.test.tsx +64 -0
- package/src/components/ui/money-input.tsx +63 -0
- 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 +104 -80
- package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
- package/src/components/ui/storefront/hero-panel.tsx +2 -8
- package/src/components/ui/storefront/image-panel.tsx +6 -0
- package/src/components/ui/storefront/index.ts +11 -0
- package/src/components/ui/storefront/listing-card.tsx +84 -22
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +289 -0
- package/src/components/ui/storefront/product-dialog.tsx +72 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
- package/src/components/ui/storefront/storefront-surface.tsx +288 -153
- package/src/components/ui/storefront/types.ts +27 -1
- package/src/components/ui/storefront/utils.ts +117 -27
- package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
- package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -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/content-migration.ts +41 -18
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +9 -3
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/image-extension.ts +40 -18
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/text-editor/video-extension.ts +11 -2
- 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/__tests__/workspace-projects-client-page.test.tsx +70 -1
- 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-external-retry.test.tsx +127 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
- 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 +51 -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.test.ts +63 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
- 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 +410 -4
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
- 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 +186 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -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/boardId/timeline/timeline-display.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
- package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
- 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 +237 -3
- 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 +465 -937
- 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 +596 -82
- package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
- 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-dialog-presentation.test.ts +53 -0
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
- 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 +44 -15
- 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/__tests__/useBoardRealtime.test.tsx +2 -2
- package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -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/useBoardRealtime.ts +6 -3
- package/src/hooks/useBoardRealtime.types.ts +11 -0
- package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
- package/src/hooks/useCursorTracking.ts +91 -27
- package/src/hooks/useTaskUserRealtime.ts +5 -3
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
612
|
-
return duplicates[0];
|
|
652
|
+
refresh();
|
|
653
|
+
return created;
|
|
613
654
|
}
|
|
614
655
|
|
|
615
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
679
|
+
patchVisibleEvents([optimisticEvent], { status: 'creating' });
|
|
640
680
|
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
645
|
-
|
|
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,
|
|
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
|
-
|
|
780
|
-
|
|
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 =
|
|
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
|
-
|
|
789
|
-
|
|
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
|
-
|
|
798
|
-
|
|
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:
|
|
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
|
|
842
|
-
|
|
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
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
947
|
+
_resolvers?.forEach(({ resolve }) => {
|
|
948
|
+
resolve(data as CalendarEvent);
|
|
949
|
+
});
|
|
881
950
|
} else {
|
|
882
|
-
|
|
883
|
-
|
|
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 (
|
|
890
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
1003
|
-
|
|
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
|
-
//
|
|
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
|
-
[
|
|
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
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
-
|
|
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
|
|