@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
|
@@ -22,7 +22,7 @@ import { cn } from '@tuturuuu/utils/format';
|
|
|
22
22
|
import { containsHtml, sanitizeHtml } from '@tuturuuu/utils/html-sanitizer';
|
|
23
23
|
import dayjs from 'dayjs';
|
|
24
24
|
import timezone from 'dayjs/plugin/timezone';
|
|
25
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
25
|
+
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
|
26
26
|
import {
|
|
27
27
|
ContextMenu,
|
|
28
28
|
ContextMenuContent,
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
ContextMenuTrigger,
|
|
35
35
|
} from '../../context-menu';
|
|
36
36
|
import { GRID_SNAP, HOUR_HEIGHT, MAX_HOURS, MIN_EVENT_HEIGHT } from './config';
|
|
37
|
+
import { CalendarEventProviderIcon } from './event-provider-display';
|
|
37
38
|
import { useCalendarSettings } from './settings/settings-context';
|
|
38
39
|
|
|
39
40
|
dayjs.extend(timezone);
|
|
@@ -58,7 +59,7 @@ interface EventCardProps {
|
|
|
58
59
|
level?: number; // Level for stacking events
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
function EventCardComponent({ dates, event, level = 0 }: EventCardProps) {
|
|
62
63
|
const {
|
|
63
64
|
id,
|
|
64
65
|
title,
|
|
@@ -83,6 +84,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
83
84
|
_isReused,
|
|
84
85
|
_previewType,
|
|
85
86
|
_warning,
|
|
87
|
+
_optimisticStatus,
|
|
86
88
|
} = event as CalendarEvent & {
|
|
87
89
|
_isHabit?: boolean;
|
|
88
90
|
_habitCompleted?: boolean;
|
|
@@ -90,6 +92,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
90
92
|
_isReused?: boolean;
|
|
91
93
|
_previewType?: 'habit' | 'task';
|
|
92
94
|
_warning?: string;
|
|
95
|
+
_optimisticStatus?: 'creating' | 'updating' | 'deleting' | 'error';
|
|
93
96
|
};
|
|
94
97
|
|
|
95
98
|
// Default values for overlap properties if not provided
|
|
@@ -107,6 +110,11 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
107
110
|
hoveredEventColumn,
|
|
108
111
|
setHoveredEventColumn,
|
|
109
112
|
affectedEventIds,
|
|
113
|
+
disableBuiltInEventUi,
|
|
114
|
+
preservePastEventOpacity,
|
|
115
|
+
renderEventContextMenu,
|
|
116
|
+
isEventReadOnly,
|
|
117
|
+
readOnly,
|
|
110
118
|
} = useCalendar();
|
|
111
119
|
|
|
112
120
|
// NOTE: Event filtering for hideNonPreviewEvents is handled in CalendarEventMatrix
|
|
@@ -114,6 +122,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
114
122
|
const { settings } = useCalendarSettings();
|
|
115
123
|
const queryClient = useQueryClient();
|
|
116
124
|
const tz = settings?.timezone?.timezone;
|
|
125
|
+
const isReadOnlyEvent = readOnly || isEventReadOnly(event);
|
|
117
126
|
|
|
118
127
|
// Local state for immediate UI updates
|
|
119
128
|
const [localEvent, setLocalEvent] = useState<CalendarEvent>(event);
|
|
@@ -168,6 +177,12 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
168
177
|
isDragging: false,
|
|
169
178
|
isResizing: false,
|
|
170
179
|
});
|
|
180
|
+
const isOptimisticallyPending =
|
|
181
|
+
_optimisticStatus === 'creating' ||
|
|
182
|
+
_optimisticStatus === 'updating' ||
|
|
183
|
+
_optimisticStatus === 'deleting';
|
|
184
|
+
const isOptimisticallyMutating =
|
|
185
|
+
_optimisticStatus === 'updating' || _optimisticStatus === 'deleting';
|
|
171
186
|
|
|
172
187
|
// Status feedback timeout
|
|
173
188
|
const statusTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -443,7 +458,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
443
458
|
if (shouldBeTransparent) {
|
|
444
459
|
cardEl.style.opacity = '0.05';
|
|
445
460
|
cardEl.style.pointerEvents = 'none';
|
|
446
|
-
} else if (isPastEvent) {
|
|
461
|
+
} else if (isPastEvent && !preservePastEventOpacity) {
|
|
447
462
|
cardEl.style.opacity = '0.5';
|
|
448
463
|
cardEl.style.pointerEvents = 'all';
|
|
449
464
|
} else {
|
|
@@ -467,12 +482,14 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
467
482
|
hoveredBaseEventId,
|
|
468
483
|
hoveredEventColumn,
|
|
469
484
|
endDate,
|
|
485
|
+
preservePastEventOpacity,
|
|
470
486
|
]);
|
|
471
487
|
|
|
472
488
|
// Event resizing - only enable for non-multi-day events or the start/end segments
|
|
473
489
|
useEffect(() => {
|
|
474
490
|
// Disable resizing for middle segments of multi-day events
|
|
475
491
|
// Note: locked events CAN still be resized - locked only prevents auto-scheduling
|
|
492
|
+
if (isReadOnlyEvent) return;
|
|
476
493
|
if (_isMultiDay && _dayPosition === 'middle') return;
|
|
477
494
|
|
|
478
495
|
const handleEl = handleRef.current;
|
|
@@ -663,12 +680,14 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
663
680
|
showStatusFeedback, // Update visual state
|
|
664
681
|
updateVisualState,
|
|
665
682
|
queryClient,
|
|
683
|
+
isReadOnlyEvent,
|
|
666
684
|
]);
|
|
667
685
|
|
|
668
686
|
// Event dragging - only enable for non-multi-day events
|
|
669
687
|
useEffect(() => {
|
|
670
688
|
// Disable dragging for multi-day events only
|
|
671
689
|
// Note: locked events CAN still be dragged - locked only prevents auto-scheduling
|
|
690
|
+
if (isReadOnlyEvent) return;
|
|
672
691
|
if (_isMultiDay) return;
|
|
673
692
|
|
|
674
693
|
const contentEl = contentRef.current;
|
|
@@ -914,6 +933,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
914
933
|
scheduleUpdate,
|
|
915
934
|
showStatusFeedback, // Update visual state for immediate feedback
|
|
916
935
|
updateVisualState,
|
|
936
|
+
isReadOnlyEvent,
|
|
917
937
|
]);
|
|
918
938
|
|
|
919
939
|
// Color styles based on event color
|
|
@@ -989,13 +1009,8 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
989
1009
|
const eventId = event._originalId || id;
|
|
990
1010
|
const newLockedState = !locked;
|
|
991
1011
|
|
|
992
|
-
console.log(
|
|
993
|
-
`Toggling lock status for event ${eventId} from ${locked} to ${newLockedState}`
|
|
994
|
-
);
|
|
995
|
-
|
|
996
1012
|
updateEvent(eventId, { locked: newLockedState })
|
|
997
1013
|
.then(() => {
|
|
998
|
-
console.log(`Successfully updated lock status to ${newLockedState}`);
|
|
999
1014
|
// Update local state immediately for better UX
|
|
1000
1015
|
setLocalEvent((prev) => ({
|
|
1001
1016
|
...prev,
|
|
@@ -1038,6 +1053,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1038
1053
|
// For visual effects, check if this is likely a shorter event (higher in stack)
|
|
1039
1054
|
// Shorter events get enhanced visual treatment
|
|
1040
1055
|
const isLikelyTopEvent = hasOverlaps && duration < 1.5; // Events < 1.5 hours likely on top
|
|
1056
|
+
const customContextMenu = renderEventContextMenu?.(event);
|
|
1041
1057
|
|
|
1042
1058
|
return (
|
|
1043
1059
|
<ContextMenu>
|
|
@@ -1046,6 +1062,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1046
1062
|
type="button"
|
|
1047
1063
|
ref={cardRef}
|
|
1048
1064
|
id={`event-${id}`}
|
|
1065
|
+
data-testid={`calendar-event-${event._originalId || id}`}
|
|
1049
1066
|
className={cn(
|
|
1050
1067
|
'pointer-events-auto absolute max-w-none select-none overflow-hidden rounded-r-md rounded-l transition-all duration-300',
|
|
1051
1068
|
'group hover:ring-1 focus:outline-none',
|
|
@@ -1053,7 +1070,10 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1053
1070
|
'transform shadow-md': isDragging || isResizing, // Subtle transform during interaction
|
|
1054
1071
|
'shadow-sm': hasOverlaps && !isDragging && !isResizing, // Subtle shadow for stacked events
|
|
1055
1072
|
'hover:shadow-md': hasOverlaps, // Enhanced shadow on hover for stacked events
|
|
1056
|
-
'opacity-50':
|
|
1073
|
+
'opacity-50':
|
|
1074
|
+
isPastEvent &&
|
|
1075
|
+
!isAffectedByPreview &&
|
|
1076
|
+
!preservePastEventOpacity, // Lower opacity for past events
|
|
1057
1077
|
'opacity-30 grayscale transition-all duration-500':
|
|
1058
1078
|
isAffectedByPreview, // Dim affected events during preview
|
|
1059
1079
|
'rounded-l-none border-l-4': showStartIndicator, // Special styling for continuation from previous day
|
|
@@ -1063,6 +1083,10 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1063
1083
|
'opacity-60': _isHabit && _habitCompleted, // Dimmed for completed habits
|
|
1064
1084
|
// Preview-specific styling - dashed border only for NEW/MOVED events (not reused)
|
|
1065
1085
|
'border-2 border-dashed': _isPreview && !_isReused,
|
|
1086
|
+
'opacity-60 outline outline-dashed outline-1 outline-primary/50':
|
|
1087
|
+
isOptimisticallyMutating,
|
|
1088
|
+
'opacity-80 ring-1 ring-primary/30':
|
|
1089
|
+
isOptimisticallyPending && !isOptimisticallyMutating,
|
|
1066
1090
|
},
|
|
1067
1091
|
level ? 'border border-l-2' : 'border-l-2',
|
|
1068
1092
|
border,
|
|
@@ -1118,6 +1142,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1118
1142
|
wasResizedRef.current = false;
|
|
1119
1143
|
}}
|
|
1120
1144
|
aria-label={`Event: ${title || 'Untitled event'}${hasCalendarInfo ? ` from ${calendarDisplayName}` : ''}`}
|
|
1145
|
+
aria-busy={isOptimisticallyPending || updateStatus === 'syncing'}
|
|
1121
1146
|
title={
|
|
1122
1147
|
hasCalendarInfo ? `Calendar: ${calendarDisplayName}` : undefined
|
|
1123
1148
|
}
|
|
@@ -1163,27 +1188,32 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1163
1188
|
)}
|
|
1164
1189
|
|
|
1165
1190
|
{/* Edit button overlay */}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1191
|
+
{!disableBuiltInEventUi && (
|
|
1192
|
+
<div
|
|
1193
|
+
className={cn(
|
|
1194
|
+
'absolute top-2 rounded-full p-0.5 opacity-0 shadow-sm',
|
|
1195
|
+
_isHabit ? 'right-5' : 'right-2', // Offset if habit icon is shown
|
|
1196
|
+
'z-10 transition-opacity group-hover:opacity-100', // Higher z-index
|
|
1197
|
+
{
|
|
1198
|
+
'opacity-0!':
|
|
1199
|
+
isDragging ||
|
|
1200
|
+
isResizing ||
|
|
1201
|
+
updateStatus !== 'idle' ||
|
|
1202
|
+
isOptimisticallyPending,
|
|
1203
|
+
} // Hide during interaction or status updates
|
|
1204
|
+
)}
|
|
1205
|
+
onClick={(e) => {
|
|
1206
|
+
e.stopPropagation();
|
|
1207
|
+
e.preventDefault();
|
|
1208
|
+
openModal(event._originalId || id);
|
|
1209
|
+
}}
|
|
1210
|
+
>
|
|
1211
|
+
<Pencil className="h-3 w-3" />
|
|
1212
|
+
</div>
|
|
1213
|
+
)}
|
|
1184
1214
|
|
|
1185
1215
|
{/* Status indicators */}
|
|
1186
|
-
{updateStatus === 'syncing' && (
|
|
1216
|
+
{updateStatus === 'syncing' && !isOptimisticallyPending && (
|
|
1187
1217
|
<div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center bg-background/5">
|
|
1188
1218
|
{/* <div
|
|
1189
1219
|
className="animate-shimmer h-full w-full bg-linear-to-r from-transparent via-background/10 to-transparent"
|
|
@@ -1243,6 +1273,10 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1243
1273
|
{localEvent.locked && !_isPreview && (
|
|
1244
1274
|
<Lock className="mr-1 inline-block h-3 w-3 align-middle opacity-70" />
|
|
1245
1275
|
)}
|
|
1276
|
+
<CalendarEventProviderIcon
|
|
1277
|
+
event={localEvent}
|
|
1278
|
+
className="mr-1 h-3 w-3 opacity-80"
|
|
1279
|
+
/>
|
|
1246
1280
|
<span>{localEvent.title || 'Untitled event'}</span>
|
|
1247
1281
|
{_isPreview && !_isReused && (
|
|
1248
1282
|
<span
|
|
@@ -1299,7 +1333,7 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1299
1333
|
</div>
|
|
1300
1334
|
|
|
1301
1335
|
{/* Only show resize handle for non-multi-day events or start/end segments */}
|
|
1302
|
-
{(!_isMultiDay || _dayPosition !== 'middle') && (
|
|
1336
|
+
{!isReadOnlyEvent && (!_isMultiDay || _dayPosition !== 'middle') && (
|
|
1303
1337
|
<div
|
|
1304
1338
|
ref={handleRef}
|
|
1305
1339
|
className={cn(
|
|
@@ -1310,121 +1344,176 @@ export function EventCard({ dates, event, level = 0 }: EventCardProps) {
|
|
|
1310
1344
|
)}
|
|
1311
1345
|
</button>
|
|
1312
1346
|
</ContextMenuTrigger>
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
className="flex items-center gap-2"
|
|
1317
|
-
>
|
|
1318
|
-
<Edit className="h-4 w-4" />
|
|
1319
|
-
<span>Edit Event</span>
|
|
1320
|
-
</ContextMenuItem>
|
|
1321
|
-
|
|
1322
|
-
<ContextMenuItem
|
|
1323
|
-
onClick={handleLockToggle}
|
|
1324
|
-
className="flex items-center gap-2"
|
|
1325
|
-
>
|
|
1326
|
-
{locked ? (
|
|
1327
|
-
<>
|
|
1328
|
-
<Unlock className="h-4 w-4" />
|
|
1329
|
-
<span>Unlock Event</span>
|
|
1330
|
-
</>
|
|
1331
|
-
) : (
|
|
1332
|
-
<>
|
|
1333
|
-
<Lock className="h-4 w-4" />
|
|
1334
|
-
<span>Lock Event</span>
|
|
1335
|
-
</>
|
|
1336
|
-
)}
|
|
1337
|
-
</ContextMenuItem>
|
|
1338
|
-
|
|
1339
|
-
<ContextMenuSub>
|
|
1340
|
-
<ContextMenuSubTrigger className="flex items-center gap-2">
|
|
1341
|
-
<Palette className="h-4 w-4" />
|
|
1342
|
-
<span className="text-foreground">Change Color</span>
|
|
1343
|
-
</ContextMenuSubTrigger>
|
|
1344
|
-
<ContextMenuSubContent className="grid w-56 grid-cols-2 gap-1 p-2">
|
|
1345
|
-
<ContextMenuItem
|
|
1346
|
-
onClick={() => handleColorChange('RED')}
|
|
1347
|
-
className="flex items-center gap-2"
|
|
1348
|
-
>
|
|
1349
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-red/80 bg-calendar-bg-red"></div>
|
|
1350
|
-
<span>Red</span>
|
|
1351
|
-
</ContextMenuItem>
|
|
1352
|
-
<ContextMenuItem
|
|
1353
|
-
onClick={() => handleColorChange('BLUE')}
|
|
1354
|
-
className="flex items-center gap-2"
|
|
1355
|
-
>
|
|
1356
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-blue/80 bg-calendar-bg-blue"></div>
|
|
1357
|
-
<span>Blue</span>
|
|
1358
|
-
</ContextMenuItem>
|
|
1359
|
-
<ContextMenuItem
|
|
1360
|
-
onClick={() => handleColorChange('GREEN')}
|
|
1361
|
-
className="flex items-center gap-2"
|
|
1362
|
-
>
|
|
1363
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-green/80 bg-calendar-bg-green"></div>
|
|
1364
|
-
<span>Green</span>
|
|
1365
|
-
</ContextMenuItem>
|
|
1366
|
-
<ContextMenuItem
|
|
1367
|
-
onClick={() => handleColorChange('YELLOW')}
|
|
1368
|
-
className="flex items-center gap-2"
|
|
1369
|
-
>
|
|
1370
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-yellow/80 bg-calendar-bg-yellow"></div>
|
|
1371
|
-
<span>Yellow</span>
|
|
1372
|
-
</ContextMenuItem>
|
|
1347
|
+
{customContextMenu ??
|
|
1348
|
+
(!disableBuiltInEventUi && (
|
|
1349
|
+
<ContextMenuContent className="w-48">
|
|
1373
1350
|
<ContextMenuItem
|
|
1374
|
-
onClick={() =>
|
|
1351
|
+
onClick={() => openModal(event._originalId || id)}
|
|
1375
1352
|
className="flex items-center gap-2"
|
|
1376
1353
|
>
|
|
1377
|
-
<
|
|
1378
|
-
<span>
|
|
1379
|
-
</ContextMenuItem>
|
|
1380
|
-
<ContextMenuItem
|
|
1381
|
-
onClick={() => handleColorChange('PINK')}
|
|
1382
|
-
className="flex items-center gap-2"
|
|
1383
|
-
>
|
|
1384
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-pink/80 bg-calendar-bg-pink"></div>
|
|
1385
|
-
<span>Pink</span>
|
|
1386
|
-
</ContextMenuItem>
|
|
1387
|
-
<ContextMenuItem
|
|
1388
|
-
onClick={() => handleColorChange('ORANGE')}
|
|
1389
|
-
className="flex items-center gap-2"
|
|
1390
|
-
>
|
|
1391
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-orange/80 bg-calendar-bg-orange"></div>
|
|
1392
|
-
<span>Orange</span>
|
|
1393
|
-
</ContextMenuItem>
|
|
1394
|
-
<ContextMenuItem
|
|
1395
|
-
onClick={() => handleColorChange('INDIGO')}
|
|
1396
|
-
className="flex items-center gap-2"
|
|
1397
|
-
>
|
|
1398
|
-
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-indigo/80 bg-calendar-bg-indigo"></div>
|
|
1399
|
-
<span>Indigo</span>
|
|
1354
|
+
<Edit className="h-4 w-4" />
|
|
1355
|
+
<span>Edit Event</span>
|
|
1400
1356
|
</ContextMenuItem>
|
|
1357
|
+
|
|
1401
1358
|
<ContextMenuItem
|
|
1402
|
-
onClick={
|
|
1359
|
+
onClick={handleLockToggle}
|
|
1403
1360
|
className="flex items-center gap-2"
|
|
1404
1361
|
>
|
|
1405
|
-
|
|
1406
|
-
|
|
1362
|
+
{locked ? (
|
|
1363
|
+
<>
|
|
1364
|
+
<Unlock className="h-4 w-4" />
|
|
1365
|
+
<span>Unlock Event</span>
|
|
1366
|
+
</>
|
|
1367
|
+
) : (
|
|
1368
|
+
<>
|
|
1369
|
+
<Lock className="h-4 w-4" />
|
|
1370
|
+
<span>Lock Event</span>
|
|
1371
|
+
</>
|
|
1372
|
+
)}
|
|
1407
1373
|
</ContextMenuItem>
|
|
1374
|
+
|
|
1375
|
+
<ContextMenuSub>
|
|
1376
|
+
<ContextMenuSubTrigger className="flex items-center gap-2">
|
|
1377
|
+
<Palette className="h-4 w-4" />
|
|
1378
|
+
<span className="text-foreground">Change Color</span>
|
|
1379
|
+
</ContextMenuSubTrigger>
|
|
1380
|
+
<ContextMenuSubContent className="grid w-56 grid-cols-2 gap-1 p-2">
|
|
1381
|
+
<ContextMenuItem
|
|
1382
|
+
onClick={() => handleColorChange('RED')}
|
|
1383
|
+
className="flex items-center gap-2"
|
|
1384
|
+
>
|
|
1385
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-red/80 bg-calendar-bg-red"></div>
|
|
1386
|
+
<span>Red</span>
|
|
1387
|
+
</ContextMenuItem>
|
|
1388
|
+
<ContextMenuItem
|
|
1389
|
+
onClick={() => handleColorChange('BLUE')}
|
|
1390
|
+
className="flex items-center gap-2"
|
|
1391
|
+
>
|
|
1392
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-blue/80 bg-calendar-bg-blue"></div>
|
|
1393
|
+
<span>Blue</span>
|
|
1394
|
+
</ContextMenuItem>
|
|
1395
|
+
<ContextMenuItem
|
|
1396
|
+
onClick={() => handleColorChange('GREEN')}
|
|
1397
|
+
className="flex items-center gap-2"
|
|
1398
|
+
>
|
|
1399
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-green/80 bg-calendar-bg-green"></div>
|
|
1400
|
+
<span>Green</span>
|
|
1401
|
+
</ContextMenuItem>
|
|
1402
|
+
<ContextMenuItem
|
|
1403
|
+
onClick={() => handleColorChange('YELLOW')}
|
|
1404
|
+
className="flex items-center gap-2"
|
|
1405
|
+
>
|
|
1406
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-yellow/80 bg-calendar-bg-yellow"></div>
|
|
1407
|
+
<span>Yellow</span>
|
|
1408
|
+
</ContextMenuItem>
|
|
1409
|
+
<ContextMenuItem
|
|
1410
|
+
onClick={() => handleColorChange('PURPLE')}
|
|
1411
|
+
className="flex items-center gap-2"
|
|
1412
|
+
>
|
|
1413
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-purple/80 bg-calendar-bg-purple"></div>
|
|
1414
|
+
<span>Purple</span>
|
|
1415
|
+
</ContextMenuItem>
|
|
1416
|
+
<ContextMenuItem
|
|
1417
|
+
onClick={() => handleColorChange('PINK')}
|
|
1418
|
+
className="flex items-center gap-2"
|
|
1419
|
+
>
|
|
1420
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-pink/80 bg-calendar-bg-pink"></div>
|
|
1421
|
+
<span>Pink</span>
|
|
1422
|
+
</ContextMenuItem>
|
|
1423
|
+
<ContextMenuItem
|
|
1424
|
+
onClick={() => handleColorChange('ORANGE')}
|
|
1425
|
+
className="flex items-center gap-2"
|
|
1426
|
+
>
|
|
1427
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-orange/80 bg-calendar-bg-orange"></div>
|
|
1428
|
+
<span>Orange</span>
|
|
1429
|
+
</ContextMenuItem>
|
|
1430
|
+
<ContextMenuItem
|
|
1431
|
+
onClick={() => handleColorChange('INDIGO')}
|
|
1432
|
+
className="flex items-center gap-2"
|
|
1433
|
+
>
|
|
1434
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-indigo/80 bg-calendar-bg-indigo"></div>
|
|
1435
|
+
<span>Indigo</span>
|
|
1436
|
+
</ContextMenuItem>
|
|
1437
|
+
<ContextMenuItem
|
|
1438
|
+
onClick={() => handleColorChange('CYAN')}
|
|
1439
|
+
className="flex items-center gap-2"
|
|
1440
|
+
>
|
|
1441
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-cyan/80 bg-calendar-bg-cyan"></div>
|
|
1442
|
+
<span>Cyan</span>
|
|
1443
|
+
</ContextMenuItem>
|
|
1444
|
+
<ContextMenuItem
|
|
1445
|
+
onClick={() => handleColorChange('GRAY')}
|
|
1446
|
+
className="flex items-center gap-2"
|
|
1447
|
+
>
|
|
1448
|
+
<div className="h-4 w-4 flex-none rounded-full border border-dynamic-light-gray/80 bg-calendar-bg-gray"></div>
|
|
1449
|
+
<span>Gray</span>
|
|
1450
|
+
</ContextMenuItem>
|
|
1451
|
+
</ContextMenuSubContent>
|
|
1452
|
+
</ContextMenuSub>
|
|
1453
|
+
|
|
1454
|
+
<ContextMenuSeparator />
|
|
1455
|
+
|
|
1408
1456
|
<ContextMenuItem
|
|
1409
|
-
onClick={
|
|
1457
|
+
onClick={handleDelete}
|
|
1410
1458
|
className="flex items-center gap-2"
|
|
1411
1459
|
>
|
|
1412
|
-
<
|
|
1413
|
-
<span>
|
|
1460
|
+
<Trash2 className="h-4 w-4" />
|
|
1461
|
+
<span>Delete Event</span>
|
|
1414
1462
|
</ContextMenuItem>
|
|
1415
|
-
</
|
|
1416
|
-
|
|
1463
|
+
</ContextMenuContent>
|
|
1464
|
+
))}
|
|
1465
|
+
</ContextMenu>
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1417
1468
|
|
|
1418
|
-
|
|
1469
|
+
function datesEqual(left: Date[], right: Date[]) {
|
|
1470
|
+
if (left.length !== right.length) return false;
|
|
1471
|
+
return left.every(
|
|
1472
|
+
(date, index) => date.getTime() === right[index]?.getTime()
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1419
1475
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1476
|
+
function eventCardPropsEqual(previous: EventCardProps, next: EventCardProps) {
|
|
1477
|
+
if (previous.wsId !== next.wsId || previous.level !== next.level) {
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
if (!datesEqual(previous.dates, next.dates)) return false;
|
|
1482
|
+
|
|
1483
|
+
const left = previous.event as CalendarEvent & {
|
|
1484
|
+
_optimisticStatus?: string;
|
|
1485
|
+
_warning?: string;
|
|
1486
|
+
};
|
|
1487
|
+
const right = next.event as CalendarEvent & {
|
|
1488
|
+
_optimisticStatus?: string;
|
|
1489
|
+
_warning?: string;
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
return (
|
|
1493
|
+
left.id === right.id &&
|
|
1494
|
+
left.title === right.title &&
|
|
1495
|
+
left.description === right.description &&
|
|
1496
|
+
left.start_at === right.start_at &&
|
|
1497
|
+
left.end_at === right.end_at &&
|
|
1498
|
+
left.color === right.color &&
|
|
1499
|
+
left.locked === right.locked &&
|
|
1500
|
+
left.provider === right.provider &&
|
|
1501
|
+
left.external_event_id === right.external_event_id &&
|
|
1502
|
+
left.external_calendar_id === right.external_calendar_id &&
|
|
1503
|
+
left.google_event_id === right.google_event_id &&
|
|
1504
|
+
left.google_calendar_id === right.google_calendar_id &&
|
|
1505
|
+
left.location === right.location &&
|
|
1506
|
+
left._originalId === right._originalId &&
|
|
1507
|
+
left._isMultiDay === right._isMultiDay &&
|
|
1508
|
+
left._dayPosition === right._dayPosition &&
|
|
1509
|
+
left._overlapCount === right._overlapCount &&
|
|
1510
|
+
left._column === right._column &&
|
|
1511
|
+
left._calendarName === right._calendarName &&
|
|
1512
|
+
left._calendarColor === right._calendarColor &&
|
|
1513
|
+
left._level === right._level &&
|
|
1514
|
+
left._optimisticStatus === right._optimisticStatus &&
|
|
1515
|
+
left._warning === right._warning
|
|
1429
1516
|
);
|
|
1430
1517
|
}
|
|
1518
|
+
|
|
1519
|
+
export const EventCard = memo(EventCardComponent, eventCardPropsEqual);
|
|
@@ -73,7 +73,6 @@ import { format } from 'date-fns';
|
|
|
73
73
|
import dayjs from 'dayjs';
|
|
74
74
|
import ts from 'dayjs/plugin/timezone';
|
|
75
75
|
import utc from 'dayjs/plugin/utc';
|
|
76
|
-
import Image from 'next/image';
|
|
77
76
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
78
77
|
import { z } from 'zod';
|
|
79
78
|
import { Alert, AlertDescription, AlertTitle } from '../../alert';
|
|
@@ -89,6 +88,10 @@ import {
|
|
|
89
88
|
EventToggleSwitch,
|
|
90
89
|
OverlapWarning,
|
|
91
90
|
} from './event-form-components';
|
|
91
|
+
import {
|
|
92
|
+
CalendarEventProviderIcon,
|
|
93
|
+
getCalendarEventProviderDisplay,
|
|
94
|
+
} from './event-provider-display';
|
|
92
95
|
import { useCalendarSettings } from './settings/settings-context';
|
|
93
96
|
|
|
94
97
|
dayjs.extend(ts);
|
|
@@ -955,28 +958,25 @@ export function EventModal() {
|
|
|
955
958
|
}
|
|
956
959
|
};
|
|
957
960
|
|
|
961
|
+
const providerDisplay = getCalendarEventProviderDisplay(event);
|
|
962
|
+
|
|
958
963
|
return (
|
|
959
964
|
<Dialog open={isModalOpen} onOpenChange={(open) => !open && closeModal()}>
|
|
960
965
|
<DialogContent className="max-h-[90vh] max-w-3xl overflow-hidden p-0">
|
|
961
966
|
<DialogHeader className="border-b px-6 pt-6 pb-4">
|
|
962
967
|
<DialogTitle className="flex items-center gap-2 font-semibold text-xl">
|
|
963
968
|
<span>{isEditing ? 'Edit Event' : 'Create Event'}</span>
|
|
964
|
-
{
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
height={18}
|
|
976
|
-
/>
|
|
977
|
-
<span className="font-medium text-xs">Google Calendar</span>
|
|
978
|
-
</div>
|
|
979
|
-
)}
|
|
969
|
+
{providerDisplay && (
|
|
970
|
+
<div className="ml-3 flex items-center gap-2 rounded-md border bg-muted/40 px-3 py-1 text-sm">
|
|
971
|
+
<CalendarEventProviderIcon
|
|
972
|
+
event={event}
|
|
973
|
+
className="h-4.5 w-4.5"
|
|
974
|
+
/>
|
|
975
|
+
<span className="font-medium text-xs">
|
|
976
|
+
{providerDisplay.label}
|
|
977
|
+
</span>
|
|
978
|
+
</div>
|
|
979
|
+
)}
|
|
980
980
|
</DialogTitle>
|
|
981
981
|
<DialogDescription>
|
|
982
982
|
{isEditing
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CalendarEvent } from '@tuturuuu/types/primitives/calendar-event';
|
|
2
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
3
|
+
import Image from 'next/image';
|
|
4
|
+
|
|
5
|
+
type ExternalCalendarProvider = 'google' | 'microsoft';
|
|
6
|
+
|
|
7
|
+
const PROVIDER_DISPLAY: Record<
|
|
8
|
+
ExternalCalendarProvider,
|
|
9
|
+
{
|
|
10
|
+
alt: string;
|
|
11
|
+
label: string;
|
|
12
|
+
src: string;
|
|
13
|
+
testId: string;
|
|
14
|
+
title: string;
|
|
15
|
+
}
|
|
16
|
+
> = {
|
|
17
|
+
google: {
|
|
18
|
+
alt: 'Google Calendar',
|
|
19
|
+
label: 'Google Calendar',
|
|
20
|
+
src: '/media/google-calendar-icon.png',
|
|
21
|
+
testId: 'google-calendar-logo',
|
|
22
|
+
title: 'Synced from Google Calendar',
|
|
23
|
+
},
|
|
24
|
+
microsoft: {
|
|
25
|
+
alt: 'Microsoft Outlook',
|
|
26
|
+
label: 'Microsoft Outlook',
|
|
27
|
+
src: '/media/logos/microsoft.svg',
|
|
28
|
+
testId: 'microsoft-outlook-logo',
|
|
29
|
+
title: 'Synced from Microsoft Outlook',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function getCalendarEventProvider(
|
|
34
|
+
event: Partial<
|
|
35
|
+
Pick<CalendarEvent, 'provider' | 'external_event_id' | 'google_event_id'>
|
|
36
|
+
>
|
|
37
|
+
): ExternalCalendarProvider | null {
|
|
38
|
+
if (event.provider === 'google' || event.google_event_id) return 'google';
|
|
39
|
+
if (event.provider === 'microsoft') return 'microsoft';
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getCalendarEventProviderDisplay(event: Partial<CalendarEvent>) {
|
|
44
|
+
const provider = getCalendarEventProvider(event);
|
|
45
|
+
return provider ? PROVIDER_DISPLAY[provider] : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function CalendarEventProviderIcon({
|
|
49
|
+
className,
|
|
50
|
+
event,
|
|
51
|
+
}: {
|
|
52
|
+
className?: string;
|
|
53
|
+
event: Partial<CalendarEvent>;
|
|
54
|
+
}) {
|
|
55
|
+
const display = getCalendarEventProviderDisplay(event);
|
|
56
|
+
if (!display) return null;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Image
|
|
60
|
+
src={display.src}
|
|
61
|
+
alt={display.alt}
|
|
62
|
+
className={cn('inline-block align-middle', className)}
|
|
63
|
+
title={display.title}
|
|
64
|
+
data-testid={display.testId}
|
|
65
|
+
height={18}
|
|
66
|
+
width={18}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|