@licklist/design 0.78.5-dev.106 → 0.78.5-dev.107
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/.vscode/settings.json +3 -0
- package/bitbucket-pipelines.yml +13 -4
- package/dist/Maintenance/Maintenance.scss.js +1 -1
- package/dist/product-set/form/ProductsControl.d.ts +2 -1
- package/dist/product-set/form/ProductsControl.d.ts.map +1 -1
- package/dist/product-set/form/ProductsControl.js +0 -24
- package/dist/v2/components/ActionMenu/ActionMenu.scss.js +1 -1
- package/dist/v2/components/Badge/Badge.scss.js +1 -1
- package/dist/v2/components/Button/Button.scss.js +1 -1
- package/dist/v2/components/Button/GhostButton.scss.js +1 -1
- package/dist/v2/components/Checkbox/Checkbox.scss.js +1 -1
- package/dist/v2/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/v2/components/DataTable/DataTable.js +86 -2
- package/dist/v2/components/IconButton/IconButton.scss.js +1 -1
- package/dist/v2/components/Modal/DeleteModal.d.ts.map +1 -1
- package/dist/v2/components/Modal/DeleteModal.js +13 -11
- package/dist/v2/components/Modal/DeleteModal.scss.js +1 -1
- package/dist/v2/components/NPSScore/NPSScore.scss.js +1 -1
- package/dist/v2/components/NewTabs/NewTabs.scss.js +1 -1
- package/dist/v2/components/Select/Select.scss.js +1 -1
- package/dist/v2/components/StatusBadge/StatusBadge.scss.js +1 -1
- package/dist/v2/components/StepIndicator/StepIndicator.scss.js +1 -1
- package/dist/v2/components/Tabs/Tabs.scss.js +1 -1
- package/dist/v2/components/Toggle/Toggle.d.ts.map +1 -1
- package/dist/v2/components/Toggle/Toggle.js +8 -5
- package/dist/v2/components/Tooltip/Tooltip.scss.js +1 -1
- package/dist/v2/components/UserAvatar/UserAvatar.scss.js +1 -1
- package/dist/v2/components/UserPanel/UserPanel.scss.js +1 -1
- package/dist/v2/components/WYSIWYGEditor/WYSIWYGEditor.scss.js +1 -1
- package/dist/v2/components/ZoneCard/ZoneCard.scss.js +1 -1
- package/dist/v2/dashboard-analytics/chart/Chart.scss.js +1 -1
- package/dist/v2/dashboard-analytics/metric-card/MetricCard.scss.js +1 -1
- package/dist/v2/dashboard-analytics/venue-card/VenueCard.scss.js +1 -1
- package/dist/v2/dashboard-analytics/venue-closed-card/VenueClosedCard.scss.js +1 -1
- package/dist/v2/icons/index.js +1 -16
- package/dist/v2/index.d.ts +0 -4
- package/dist/v2/index.d.ts.map +1 -1
- package/dist/v2/navigation/DashboardLayout/AdminSidebar.scss.js +1 -1
- package/dist/v2/navigation/DashboardLayout/DashboardLayout.scss.js +1 -1
- package/dist/v2/navigation/DashboardLayout/ProviderSidebar.scss.js +1 -1
- package/dist/v2/navigation/DashboardLayout/TopNavigation.scss.js +1 -1
- package/dist/v2/pages/Settings/SettingsTabs.scss.js +1 -1
- package/dist/v2/pages/Settings/components/SidebarCustomisation.js +0 -3
- package/dist/v2/pages/Settings/components/SidebarCustomisation.scss.js +1 -1
- package/dist/v2/pages/Settings/components/SidebarNavItem.js +0 -3
- package/dist/v2/pages/auth/AuthLayout/AuthLayout.scss.js +1 -1
- package/dist/v2/styles/form/NewInput.scss.js +1 -1
- package/package.json +6 -6
- package/rollup.config.js +16 -2
- package/src/iframe/payment/payment-status-page/PaymentStatusPage.tsx +1 -1
- package/src/product-set/form/ProductsControl.tsx +2 -1
- package/src/v2/components/DataTable/DataTable.tsx +23 -1
- package/src/v2/components/Modal/DeleteModal.tsx +12 -20
- package/src/v2/components/Toggle/Toggle.tsx +6 -5
- package/src/v2/index.ts +0 -73
- package/dist/v2/shadcn/components/ui/accordion.d.ts +0 -8
- package/dist/v2/shadcn/components/ui/accordion.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/alert-dialog.d.ts +0 -21
- package/dist/v2/shadcn/components/ui/alert-dialog.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/alert.d.ts +0 -9
- package/dist/v2/shadcn/components/ui/alert.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts +0 -4
- package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/avatar.d.ts +0 -7
- package/dist/v2/shadcn/components/ui/avatar.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/badge.d.ts +0 -10
- package/dist/v2/shadcn/components/ui/badge.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/breadcrumb.d.ts +0 -20
- package/dist/v2/shadcn/components/ui/breadcrumb.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/button.d.ts +0 -14
- package/dist/v2/shadcn/components/ui/button.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/calendar.d.ts +0 -9
- package/dist/v2/shadcn/components/ui/calendar.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/card.d.ts +0 -9
- package/dist/v2/shadcn/components/ui/card.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/carousel.d.ts +0 -19
- package/dist/v2/shadcn/components/ui/carousel.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/checkbox.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/checkbox.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/checkbox.js +0 -115
- package/dist/v2/shadcn/components/ui/checkbox.scss.js +0 -6
- package/dist/v2/shadcn/components/ui/collapsible.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/collapsible.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/command.d.ts +0 -83
- package/dist/v2/shadcn/components/ui/command.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/context-menu.d.ts +0 -28
- package/dist/v2/shadcn/components/ui/context-menu.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/dialog.d.ts +0 -20
- package/dist/v2/shadcn/components/ui/dialog.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/dialog.js +0 -169
- package/dist/v2/shadcn/components/ui/drawer.d.ts +0 -23
- package/dist/v2/shadcn/components/ui/drawer.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts +0 -28
- package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/form.d.ts +0 -24
- package/dist/v2/shadcn/components/ui/form.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/hover-card.d.ts +0 -7
- package/dist/v2/shadcn/components/ui/hover-card.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/input-otp.d.ts +0 -35
- package/dist/v2/shadcn/components/ui/input-otp.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/input.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/input.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/label.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/label.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/menubar.d.ts +0 -34
- package/dist/v2/shadcn/components/ui/menubar.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/navigation-menu.d.ts +0 -13
- package/dist/v2/shadcn/components/ui/navigation-menu.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/pagination.d.ts +0 -29
- package/dist/v2/shadcn/components/ui/pagination.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/popover.d.ts +0 -7
- package/dist/v2/shadcn/components/ui/popover.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/progress.d.ts +0 -5
- package/dist/v2/shadcn/components/ui/progress.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/radio-card.d.ts +0 -12
- package/dist/v2/shadcn/components/ui/radio-card.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/radio-group.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/radio-group.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/scroll-area.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/scroll-area.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/select.d.ts +0 -14
- package/dist/v2/shadcn/components/ui/select.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/separator.d.ts +0 -5
- package/dist/v2/shadcn/components/ui/separator.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/sheet.d.ts +0 -26
- package/dist/v2/shadcn/components/ui/sheet.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/sidebar.d.ts +0 -67
- package/dist/v2/shadcn/components/ui/sidebar.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/skeleton.d.ts +0 -3
- package/dist/v2/shadcn/components/ui/skeleton.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/slider.d.ts +0 -5
- package/dist/v2/shadcn/components/ui/slider.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/switch.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/switch.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/switch.js +0 -115
- package/dist/v2/shadcn/components/ui/switch.scss.js +0 -6
- package/dist/v2/shadcn/components/ui/table-pagination.d.ts +0 -11
- package/dist/v2/shadcn/components/ui/table-pagination.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/table.d.ts +0 -11
- package/dist/v2/shadcn/components/ui/table.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/tabs.d.ts +0 -8
- package/dist/v2/shadcn/components/ui/tabs.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/textarea.d.ts +0 -6
- package/dist/v2/shadcn/components/ui/textarea.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/toast.d.ts +0 -16
- package/dist/v2/shadcn/components/ui/toast.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/toaster.d.ts +0 -2
- package/dist/v2/shadcn/components/ui/toaster.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/toggle-group.d.ts +0 -13
- package/dist/v2/shadcn/components/ui/toggle-group.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/toggle.d.ts +0 -13
- package/dist/v2/shadcn/components/ui/toggle.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/tooltip.d.ts +0 -8
- package/dist/v2/shadcn/components/ui/tooltip.d.ts.map +0 -1
- package/dist/v2/shadcn/components/ui/use-toast.d.ts +0 -3
- package/dist/v2/shadcn/components/ui/use-toast.d.ts.map +0 -1
- package/dist/v2/shadcn/hooks/use-mobile.d.ts +0 -2
- package/dist/v2/shadcn/hooks/use-mobile.d.ts.map +0 -1
- package/dist/v2/shadcn/hooks/use-toast.d.ts +0 -45
- package/dist/v2/shadcn/hooks/use-toast.d.ts.map +0 -1
- package/dist/v2/shadcn/index.d.ts +0 -20
- package/dist/v2/shadcn/index.d.ts.map +0 -1
- package/dist/v2/shadcn/lib/utils.d.ts +0 -3
- package/dist/v2/shadcn/lib/utils.d.ts.map +0 -1
- package/dist/v2/shadcn/lib/utils.js +0 -11
- package/dist/v2/shadcn/styles/globals.css +0 -112
- package/src/v2/shadcn/_reference/AccountManagerCard.tsx +0 -45
- package/src/v2/shadcn/_reference/AffiliatesTable.tsx +0 -178
- package/src/v2/shadcn/_reference/AuditArchive.tsx +0 -165
- package/src/v2/shadcn/_reference/AuditContent.tsx +0 -270
- package/src/v2/shadcn/_reference/AutomationsGeneralSettings.tsx +0 -251
- package/src/v2/shadcn/_reference/AvatarUpload.tsx +0 -150
- package/src/v2/shadcn/_reference/BookingsSummaryCard.tsx +0 -268
- package/src/v2/shadcn/_reference/CodeCleanUpAudit.tsx +0 -274
- package/src/v2/shadcn/_reference/CompaniesTable.tsx +0 -387
- package/src/v2/shadcn/_reference/ComponentAudit.tsx +0 -239
- package/src/v2/shadcn/_reference/ConfigureSettingsCard.tsx +0 -95
- package/src/v2/shadcn/_reference/CustomerCard.tsx +0 -155
- package/src/v2/shadcn/_reference/DashboardCards.tsx +0 -50
- package/src/v2/shadcn/_reference/DashboardFooter.tsx +0 -18
- package/src/v2/shadcn/_reference/DiarySettings.tsx +0 -187
- package/src/v2/shadcn/_reference/DiaryView.tsx +0 -998
- package/src/v2/shadcn/_reference/EmptyState.tsx +0 -76
- package/src/v2/shadcn/_reference/EntityInfoCard.tsx +0 -48
- package/src/v2/shadcn/_reference/ExistingUserAssignments.tsx +0 -131
- package/src/v2/shadcn/_reference/FeatureToggle.tsx +0 -72
- package/src/v2/shadcn/_reference/FlowCard.tsx +0 -170
- package/src/v2/shadcn/_reference/FlowsContent.tsx +0 -688
- package/src/v2/shadcn/_reference/FlowsGeneralSettings.tsx +0 -27
- package/src/v2/shadcn/_reference/GeneralSettings.tsx +0 -33
- package/src/v2/shadcn/_reference/InventoryGeneralSettings.tsx +0 -82
- package/src/v2/shadcn/_reference/LanguageSelector.tsx +0 -97
- package/src/v2/shadcn/_reference/LoadingScreen.tsx +0 -25
- package/src/v2/shadcn/_reference/LoadingSpinner.tsx +0 -41
- package/src/v2/shadcn/_reference/ManagedClientsList.tsx +0 -121
- package/src/v2/shadcn/_reference/NPSScore.tsx +0 -379
- package/src/v2/shadcn/_reference/NPSSummaryCard.tsx +0 -181
- package/src/v2/shadcn/_reference/NotificationBanner.tsx +0 -129
- package/src/v2/shadcn/_reference/NotificationPanel.tsx +0 -208
- package/src/v2/shadcn/_reference/OnlineUsersCard.tsx +0 -73
- package/src/v2/shadcn/_reference/ProtectedRoute.tsx +0 -39
- package/src/v2/shadcn/_reference/ProvidersTable.tsx +0 -353
- package/src/v2/shadcn/_reference/QuickAddPanel.tsx +0 -1057
- package/src/v2/shadcn/_reference/QuickFilters.tsx +0 -112
- package/src/v2/shadcn/_reference/ScheduleView.tsx +0 -410
- package/src/v2/shadcn/_reference/ScrollToTop.tsx +0 -14
- package/src/v2/shadcn/_reference/SecondaryNav.tsx +0 -50
- package/src/v2/shadcn/_reference/SecuritySettings.tsx +0 -258
- package/src/v2/shadcn/_reference/SessionDetailView.tsx +0 -294
- package/src/v2/shadcn/_reference/Sidebar.tsx +0 -14
- package/src/v2/shadcn/_reference/SidebarAwareLayout.tsx +0 -30
- package/src/v2/shadcn/_reference/SidebarLabelCustomization.tsx +0 -285
- package/src/v2/shadcn/_reference/SimulationBanner.tsx +0 -57
- package/src/v2/shadcn/_reference/SortControls.tsx +0 -65
- package/src/v2/shadcn/_reference/StatusBadge.tsx +0 -49
- package/src/v2/shadcn/_reference/StyleGuideContent.tsx +0 -331
- package/src/v2/shadcn/_reference/TableActionMenu.tsx +0 -126
- package/src/v2/shadcn/_reference/ThemeProvider.tsx +0 -119
- package/src/v2/shadcn/_reference/ThemeSettings.tsx +0 -73
- package/src/v2/shadcn/_reference/TopNavigation.tsx +0 -332
- package/src/v2/shadcn/_reference/UserActivityHistory.tsx +0 -209
- package/src/v2/shadcn/_reference/UserLanguageSettings.tsx +0 -94
- package/src/v2/shadcn/_reference/UserPanel.tsx +0 -472
- package/src/v2/shadcn/_reference/UsersTable.tsx +0 -1023
- package/src/v2/shadcn/_reference/WaiverForm.tsx +0 -301
- package/src/v2/shadcn/_reference/WaiversGeneralSettings.tsx +0 -46
- package/src/v2/shadcn/_reference/WaiversTable.tsx +0 -290
- package/src/v2/shadcn/_reference/WaiversTemplatesSettings.tsx +0 -416
- package/src/v2/shadcn/_reference/ai/AIChatPanel.tsx +0 -313
- package/src/v2/shadcn/_reference/ai/AIChatSearchBar.tsx +0 -36
- package/src/v2/shadcn/_reference/ai/ChatInteractiveBlock.tsx +0 -298
- package/src/v2/shadcn/_reference/ai/ChatMessageContent.tsx +0 -40
- package/src/v2/shadcn/_reference/ai/parseInteractiveBlocks.ts +0 -142
- package/src/v2/shadcn/_reference/auth/AuthLayout.tsx +0 -55
- package/src/v2/shadcn/_reference/auth/CreatePasswordForm.tsx +0 -285
- package/src/v2/shadcn/_reference/auth/CreatePasswordPanel.tsx +0 -20
- package/src/v2/shadcn/_reference/auth/LoginFooter.tsx +0 -14
- package/src/v2/shadcn/_reference/auth/LoginForm.tsx +0 -205
- package/src/v2/shadcn/_reference/auth/LoginPanel.tsx +0 -41
- package/src/v2/shadcn/_reference/auth/ResetPasswordForm.tsx +0 -102
- package/src/v2/shadcn/_reference/auth/ResetPasswordPanel.tsx +0 -20
- package/src/v2/shadcn/_reference/auth/VerifyEmailForm.tsx +0 -95
- package/src/v2/shadcn/_reference/auth/VerifyEmailPanel.tsx +0 -20
- package/src/v2/shadcn/_reference/email/EmailAttachment.tsx +0 -119
- package/src/v2/shadcn/_reference/email/EmailAutomation.tsx +0 -92
- package/src/v2/shadcn/_reference/email/EmailPlaceholders.tsx +0 -64
- package/src/v2/shadcn/_reference/email/UnlayerEmailEditor.tsx +0 -41
- package/src/v2/shadcn/_reference/email/emailTemplateData.ts +0 -53
- package/src/v2/shadcn/_reference/emptyStateIcons.tsx +0 -103
- package/src/v2/shadcn/_reference/games/MazeGame.tsx +0 -394
- package/src/v2/shadcn/_reference/games/RunnerGame.tsx +0 -497
- package/src/v2/shadcn/_reference/logos/BookedLogoFull.tsx +0 -36
- package/src/v2/shadcn/_reference/logos/BookedLogoMark.tsx +0 -31
- package/src/v2/shadcn/_reference/logos/BookedLogoNew.tsx +0 -36
- package/src/v2/shadcn/_reference/pricing/DynamicPricingRulesEditor.tsx +0 -401
- package/src/v2/shadcn/_reference/pricing/DynamicPricingTierCard.tsx +0 -77
- package/src/v2/shadcn/_reference/pricing/DynamicPricingTiersList.tsx +0 -218
- package/src/v2/shadcn/_reference/pricing/PricingCalendar.tsx +0 -810
- package/src/v2/shadcn/_reference/pricing/PricingPeriodCard.tsx +0 -152
- package/src/v2/shadcn/_reference/pricing/PricingPeriodForm.tsx +0 -377
- package/src/v2/shadcn/_reference/pricing/PricingPeriodsList.tsx +0 -213
- package/src/v2/shadcn/_reference/pricing/getRuleSummary.ts +0 -39
- package/src/v2/shadcn/_reference/products/AvailabilityRulesSection.tsx +0 -184
- package/src/v2/shadcn/_reference/products/AvailabilitySection.tsx +0 -677
- package/src/v2/shadcn/_reference/products/BookingTypeConfigOptions.tsx +0 -40
- package/src/v2/shadcn/_reference/products/CapacityPeriodsSection.tsx +0 -238
- package/src/v2/shadcn/_reference/products/DynamicPricingTiersSection.tsx +0 -131
- package/src/v2/shadcn/_reference/products/GiftCardOrdersTab.tsx +0 -192
- package/src/v2/shadcn/_reference/products/GiftCardSettings.tsx +0 -342
- package/src/v2/shadcn/_reference/products/PackageProductsSection.tsx +0 -322
- package/src/v2/shadcn/_reference/products/PricingSection.tsx +0 -173
- package/src/v2/shadcn/_reference/products/ProductTypeFields.tsx +0 -353
- package/src/v2/shadcn/_reference/products/ProductTypeIcon.tsx +0 -95
- package/src/v2/shadcn/_reference/products/VariablePricingSection.tsx +0 -140
- package/src/v2/shadcn/_reference/products/productTypeConfig.ts +0 -182
- package/src/v2/shadcn/_reference/shared/BackButton.tsx +0 -50
- package/src/v2/shadcn/_reference/shared/CancelConfirmationDialog.tsx +0 -18
- package/src/v2/shadcn/_reference/shared/ConfirmationDialog.tsx +0 -136
- package/src/v2/shadcn/_reference/shared/DeleteConfirmationDialog.tsx +0 -18
- package/src/v2/shadcn/_reference/shared/DeleteEntityPage.tsx +0 -221
- package/src/v2/shadcn/_reference/shared/SidebarIcons.tsx +0 -108
- package/src/v2/shadcn/_reference/shared/UnifiedSidebar.tsx +0 -722
- package/src/v2/shadcn/_reference/tables/BulkActionsBar.tsx +0 -68
- package/src/v2/shadcn/_reference/tables/DataTable.tsx +0 -221
- package/src/v2/shadcn/_reference/tables/TableControls.tsx +0 -94
- package/src/v2/shadcn/_reference/tables/index.ts +0 -3
- package/src/v2/shadcn/_reference/tables/types.ts +0 -79
- package/src/v2/shadcn/_reference/zones/LegacyZoneSettings.tsx +0 -299
- package/src/v2/shadcn/components/ui/accordion.stories.tsx +0 -63
- package/src/v2/shadcn/components/ui/accordion.tsx +0 -52
- package/src/v2/shadcn/components/ui/alert-dialog.stories.tsx +0 -44
- package/src/v2/shadcn/components/ui/alert-dialog.tsx +0 -104
- package/src/v2/shadcn/components/ui/alert.stories.tsx +0 -44
- package/src/v2/shadcn/components/ui/alert.tsx +0 -43
- package/src/v2/shadcn/components/ui/aspect-ratio.stories.tsx +0 -46
- package/src/v2/shadcn/components/ui/aspect-ratio.tsx +0 -5
- package/src/v2/shadcn/components/ui/avatar.stories.tsx +0 -39
- package/src/v2/shadcn/components/ui/avatar.tsx +0 -38
- package/src/v2/shadcn/components/ui/badge.stories.tsx +0 -17
- package/src/v2/shadcn/components/ui/badge.tsx +0 -30
- package/src/v2/shadcn/components/ui/breadcrumb.stories.tsx +0 -91
- package/src/v2/shadcn/components/ui/breadcrumb.tsx +0 -90
- package/src/v2/shadcn/components/ui/button.stories.tsx +0 -20
- package/src/v2/shadcn/components/ui/button.tsx +0 -60
- package/src/v2/shadcn/components/ui/calendar.stories.tsx +0 -61
- package/src/v2/shadcn/components/ui/calendar.tsx +0 -54
- package/src/v2/shadcn/components/ui/card.stories.tsx +0 -37
- package/src/v2/shadcn/components/ui/card.tsx +0 -43
- package/src/v2/shadcn/components/ui/carousel.stories.tsx +0 -92
- package/src/v2/shadcn/components/ui/carousel.tsx +0 -224
- package/src/v2/shadcn/components/ui/checkbox.scss +0 -38
- package/src/v2/shadcn/components/ui/checkbox.stories.tsx +0 -23
- package/src/v2/shadcn/components/ui/checkbox.tsx +0 -24
- package/src/v2/shadcn/components/ui/collapsible.stories.tsx +0 -59
- package/src/v2/shadcn/components/ui/collapsible.tsx +0 -9
- package/src/v2/shadcn/components/ui/command.stories.tsx +0 -70
- package/src/v2/shadcn/components/ui/command.tsx +0 -132
- package/src/v2/shadcn/components/ui/context-menu.stories.tsx +0 -72
- package/src/v2/shadcn/components/ui/context-menu.tsx +0 -178
- package/src/v2/shadcn/components/ui/dialog.stories.tsx +0 -67
- package/src/v2/shadcn/components/ui/dialog.tsx +0 -95
- package/src/v2/shadcn/components/ui/drawer.stories.tsx +0 -50
- package/src/v2/shadcn/components/ui/drawer.tsx +0 -87
- package/src/v2/shadcn/components/ui/dropdown-menu.stories.tsx +0 -73
- package/src/v2/shadcn/components/ui/dropdown-menu.tsx +0 -179
- package/src/v2/shadcn/components/ui/form.stories.tsx +0 -105
- package/src/v2/shadcn/components/ui/form.tsx +0 -129
- package/src/v2/shadcn/components/ui/hover-card.stories.tsx +0 -35
- package/src/v2/shadcn/components/ui/hover-card.tsx +0 -27
- package/src/v2/shadcn/components/ui/input-otp.stories.tsx +0 -72
- package/src/v2/shadcn/components/ui/input-otp.tsx +0 -61
- package/src/v2/shadcn/components/ui/input.stories.tsx +0 -16
- package/src/v2/shadcn/components/ui/input.tsx +0 -25
- package/src/v2/shadcn/components/ui/label.stories.tsx +0 -13
- package/src/v2/shadcn/components/ui/label.tsx +0 -17
- package/src/v2/shadcn/components/ui/menubar.stories.tsx +0 -86
- package/src/v2/shadcn/components/ui/menubar.tsx +0 -207
- package/src/v2/shadcn/components/ui/navigation-menu.stories.tsx +0 -68
- package/src/v2/shadcn/components/ui/navigation-menu.tsx +0 -120
- package/src/v2/shadcn/components/ui/pagination.stories.tsx +0 -78
- package/src/v2/shadcn/components/ui/pagination.tsx +0 -81
- package/src/v2/shadcn/components/ui/popover.stories.tsx +0 -44
- package/src/v2/shadcn/components/ui/popover.tsx +0 -29
- package/src/v2/shadcn/components/ui/progress.stories.tsx +0 -17
- package/src/v2/shadcn/components/ui/progress.tsx +0 -23
- package/src/v2/shadcn/components/ui/radio-card.stories.tsx +0 -68
- package/src/v2/shadcn/components/ui/radio-card.tsx +0 -52
- package/src/v2/shadcn/components/ui/radio-group.stories.tsx +0 -77
- package/src/v2/shadcn/components/ui/radio-group.tsx +0 -35
- package/src/v2/shadcn/components/ui/scroll-area.stories.tsx +0 -56
- package/src/v2/shadcn/components/ui/scroll-area.tsx +0 -38
- package/src/v2/shadcn/components/ui/select.stories.tsx +0 -60
- package/src/v2/shadcn/components/ui/select.tsx +0 -148
- package/src/v2/shadcn/components/ui/separator.stories.tsx +0 -30
- package/src/v2/shadcn/components/ui/separator.tsx +0 -20
- package/src/v2/shadcn/components/ui/sheet.stories.tsx +0 -115
- package/src/v2/shadcn/components/ui/sheet.tsx +0 -107
- package/src/v2/shadcn/components/ui/sidebar.stories.tsx +0 -167
- package/src/v2/shadcn/components/ui/sidebar.tsx +0 -637
- package/src/v2/shadcn/components/ui/skeleton.stories.tsx +0 -36
- package/src/v2/shadcn/components/ui/skeleton.tsx +0 -7
- package/src/v2/shadcn/components/ui/slider.stories.tsx +0 -16
- package/src/v2/shadcn/components/ui/slider.tsx +0 -23
- package/src/v2/shadcn/components/ui/switch.scss +0 -63
- package/src/v2/shadcn/components/ui/switch.stories.tsx +0 -23
- package/src/v2/shadcn/components/ui/switch.tsx +0 -24
- package/src/v2/shadcn/components/ui/table-pagination.stories.tsx +0 -81
- package/src/v2/shadcn/components/ui/table-pagination.tsx +0 -61
- package/src/v2/shadcn/components/ui/table.stories.tsx +0 -40
- package/src/v2/shadcn/components/ui/table.tsx +0 -72
- package/src/v2/shadcn/components/ui/tabs.stories.tsx +0 -85
- package/src/v2/shadcn/components/ui/tabs.tsx +0 -53
- package/src/v2/shadcn/components/ui/textarea.stories.tsx +0 -15
- package/src/v2/shadcn/components/ui/textarea.tsx +0 -21
- package/src/v2/shadcn/components/ui/toast.stories.tsx +0 -77
- package/src/v2/shadcn/components/ui/toast.tsx +0 -111
- package/src/v2/shadcn/components/ui/toaster.stories.tsx +0 -46
- package/src/v2/shadcn/components/ui/toaster.tsx +0 -24
- package/src/v2/shadcn/components/ui/toggle-group.stories.tsx +0 -95
- package/src/v2/shadcn/components/ui/toggle-group.tsx +0 -49
- package/src/v2/shadcn/components/ui/toggle.stories.tsx +0 -18
- package/src/v2/shadcn/components/ui/toggle.tsx +0 -37
- package/src/v2/shadcn/components/ui/tooltip.stories.tsx +0 -57
- package/src/v2/shadcn/components/ui/tooltip.tsx +0 -28
- package/src/v2/shadcn/components/ui/use-toast.ts +0 -3
- package/src/v2/shadcn/hooks/use-mobile.tsx +0 -19
- package/src/v2/shadcn/hooks/use-toast.ts +0 -184
- package/src/v2/shadcn/index.ts +0 -76
- package/src/v2/shadcn/lib/utils.ts +0 -6
- package/src/v2/shadcn/styles/globals.css +0 -112
|
@@ -1,998 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
2
|
-
import { useNavigate, useParams } from 'react-router-dom';
|
|
3
|
-
import { supabase } from '@/integrations/supabase/client';
|
|
4
|
-
import { useNotify } from '../hooks/useNotify';
|
|
5
|
-
import { format, addDays, getDay } from 'date-fns';
|
|
6
|
-
import { cn } from '../lib/utils';
|
|
7
|
-
import { Button } from './ui/button';
|
|
8
|
-
import { IconArrowLeft, IconArrowRight, IconPlus, IconDetailed, IconOverview } from '../../icons';
|
|
9
|
-
import { QuickFilters } from './QuickFilters';
|
|
10
|
-
import { useProvider } from '@/contexts/ProviderContext';
|
|
11
|
-
import LoadingSpinner from './LoadingSpinner';
|
|
12
|
-
import { QuickAddPanel } from './QuickAddPanel';
|
|
13
|
-
import { HoverCard, HoverCardContent, HoverCardTrigger } from './ui/hover-card';
|
|
14
|
-
interface Zone {
|
|
15
|
-
id: string;
|
|
16
|
-
name: string;
|
|
17
|
-
description: string | null;
|
|
18
|
-
display_order: number;
|
|
19
|
-
availability_start: string | null;
|
|
20
|
-
availability_end: string | null;
|
|
21
|
-
use_venue_hours: boolean;
|
|
22
|
-
resources: Resource[];
|
|
23
|
-
opening_hours?: ZoneOpeningHour[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface Resource {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
max_capacity: number;
|
|
30
|
-
instances: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface Flow {
|
|
34
|
-
id: string;
|
|
35
|
-
type: 'booking' | 'enquiry';
|
|
36
|
-
reference_number: string;
|
|
37
|
-
customer_name: string;
|
|
38
|
-
activity_name: string;
|
|
39
|
-
booking_date: string;
|
|
40
|
-
booking_time_start: string;
|
|
41
|
-
booking_time_end: string | null;
|
|
42
|
-
status: string;
|
|
43
|
-
total_amount: number;
|
|
44
|
-
zone_id: string | null;
|
|
45
|
-
resource_id: string | null;
|
|
46
|
-
resource_instance: number | null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface OpeningHour {
|
|
50
|
-
day_of_week: number;
|
|
51
|
-
is_open: boolean;
|
|
52
|
-
open_time: string;
|
|
53
|
-
close_time: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface ZoneOpeningHour {
|
|
57
|
-
zone_id: string;
|
|
58
|
-
day_of_week: number;
|
|
59
|
-
is_open: boolean;
|
|
60
|
-
open_time: string;
|
|
61
|
-
close_time: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface DiaryViewProps {
|
|
65
|
-
providerId: string;
|
|
66
|
-
selectedDate: 'today' | 'tomorrow';
|
|
67
|
-
selectedZoneId?: string;
|
|
68
|
-
zoneFilters?: { id: string; name: string }[];
|
|
69
|
-
onZoneFilterChange?: (zoneId: string) => void;
|
|
70
|
-
onFlowClick?: (flowId: string) => void;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Expand resources into individual instances with numbered names and capacity
|
|
74
|
-
// Guard rail: a misconfigured resource.instances (e.g. thousands) can generate a massive DOM and freeze the app.
|
|
75
|
-
const MAX_RESOURCE_INSTANCES_TO_RENDER = 50;
|
|
76
|
-
|
|
77
|
-
const expandResources = (
|
|
78
|
-
resource: Resource
|
|
79
|
-
): { id: string; resourceId: string; name: string; instanceNumber: number; maxCapacity: number }[] => {
|
|
80
|
-
const instances: { id: string; resourceId: string; name: string; instanceNumber: number; maxCapacity: number }[] = [];
|
|
81
|
-
const instancesToRender = Math.min(Math.max(resource.instances ?? 0, 0), MAX_RESOURCE_INSTANCES_TO_RENDER);
|
|
82
|
-
|
|
83
|
-
for (let i = 1; i <= instancesToRender; i++) {
|
|
84
|
-
instances.push({
|
|
85
|
-
id: `${resource.id}-${i}`,
|
|
86
|
-
resourceId: resource.id, // Store original resource ID
|
|
87
|
-
name: `${resource.name} ${i}`,
|
|
88
|
-
instanceNumber: i,
|
|
89
|
-
maxCapacity: resource.max_capacity,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return instances;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Time slot used by the diary grid. `totalMinutes` can exceed 1440 when opening hours run past midnight.
|
|
97
|
-
interface TimeSlot {
|
|
98
|
-
key: string;
|
|
99
|
-
label: string;
|
|
100
|
-
totalMinutes: number;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const formatSlotLabel = (totalMinutes: number): string => {
|
|
104
|
-
const normalized = ((totalMinutes % 1440) + 1440) % 1440;
|
|
105
|
-
const hour = Math.floor(normalized / 60);
|
|
106
|
-
const minute = normalized % 60;
|
|
107
|
-
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// Generate 30-minute time slots from startMinutes (inclusive) to endMinutes (exclusive)
|
|
111
|
-
const generateTimeSlots = (startMinutes: number, endMinutes: number): TimeSlot[] => {
|
|
112
|
-
const slots: TimeSlot[] = [];
|
|
113
|
-
const step = 30;
|
|
114
|
-
|
|
115
|
-
for (let m = startMinutes; m < endMinutes; m += step) {
|
|
116
|
-
slots.push({
|
|
117
|
-
key: String(m),
|
|
118
|
-
label: formatSlotLabel(m),
|
|
119
|
-
totalMinutes: m,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return slots;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Parse time string to get hour and minute
|
|
127
|
-
const parseTime = (timeStr: string): { hour: number; minute: number } => {
|
|
128
|
-
const [hour, minute] = timeStr.split(':').map(Number);
|
|
129
|
-
return { hour, minute };
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export const DiaryView: React.FC<DiaryViewProps> = ({ providerId, selectedDate, selectedZoneId, zoneFilters, onZoneFilterChange, onFlowClick }) => {
|
|
133
|
-
const navigate = useNavigate();
|
|
134
|
-
const { showError } = useNotify();
|
|
135
|
-
const params = useParams<{ providerId?: string; companyId?: string }>();
|
|
136
|
-
const { provider } = useProvider();
|
|
137
|
-
const [zones, setZones] = useState<Zone[]>([]);
|
|
138
|
-
const [flows, setFlows] = useState<Flow[]>([]);
|
|
139
|
-
const [openingHours, setOpeningHours] = useState<OpeningHour[]>([]);
|
|
140
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
141
|
-
const [viewMode, setViewMode] = useState<'detailed' | 'overview'>('detailed');
|
|
142
|
-
const [dayOffset, setDayOffset] = useState(0);
|
|
143
|
-
|
|
144
|
-
// Hover state removed – CSS-only hover to prevent expensive re-renders
|
|
145
|
-
|
|
146
|
-
// V2 Quick Add Panel state
|
|
147
|
-
const [quickAddPanelOpen, setQuickAddPanelOpen] = useState(false);
|
|
148
|
-
const [quickAddData, setQuickAddData] = useState<{
|
|
149
|
-
date: string;
|
|
150
|
-
time: string;
|
|
151
|
-
zoneId?: string;
|
|
152
|
-
zoneName?: string;
|
|
153
|
-
resourceId?: string;
|
|
154
|
-
resourceName?: string;
|
|
155
|
-
resourceInstance?: number;
|
|
156
|
-
} | null>(null);
|
|
157
|
-
|
|
158
|
-
// Refs for synchronized scrolling
|
|
159
|
-
const headerScrollRef = useRef<HTMLDivElement>(null);
|
|
160
|
-
const bodyScrollRef = useRef<HTMLDivElement>(null);
|
|
161
|
-
const isScrolling = useRef(false);
|
|
162
|
-
|
|
163
|
-
const truncatedResources = useMemo(() => {
|
|
164
|
-
const items: Array<{ zoneName: string; resourceName: string; instances: number }> = [];
|
|
165
|
-
for (const zone of zones) {
|
|
166
|
-
for (const resource of zone.resources) {
|
|
167
|
-
if (resource.instances > MAX_RESOURCE_INSTANCES_TO_RENDER) {
|
|
168
|
-
items.push({ zoneName: zone.name, resourceName: resource.name, instances: resource.instances });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return items;
|
|
173
|
-
}, [zones]);
|
|
174
|
-
|
|
175
|
-
// Build base path for navigation
|
|
176
|
-
const getBasePath = () => {
|
|
177
|
-
const providerType = provider?.type === 'venue' ? 'venue' : 'promoter';
|
|
178
|
-
if (params.companyId) {
|
|
179
|
-
return `/company/${params.companyId}/${providerType}/${params.providerId}`;
|
|
180
|
-
}
|
|
181
|
-
return `/${providerType}/${params.providerId}`;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Handle quick-add cell click
|
|
185
|
-
const handleQuickAdd = (
|
|
186
|
-
date: string,
|
|
187
|
-
timeSlot: string,
|
|
188
|
-
zone?: Zone,
|
|
189
|
-
resourceName?: string,
|
|
190
|
-
resourceId?: string,
|
|
191
|
-
instanceNumber?: number
|
|
192
|
-
) => {
|
|
193
|
-
// =====================================================
|
|
194
|
-
// V1 Quick Add - Navigate to AddFlow page (COMMENTED OUT)
|
|
195
|
-
// =====================================================
|
|
196
|
-
// const basePath = getBasePath();
|
|
197
|
-
// const searchParams = new URLSearchParams();
|
|
198
|
-
// searchParams.set('date', date);
|
|
199
|
-
// searchParams.set('time', timeSlot);
|
|
200
|
-
// if (zone) {
|
|
201
|
-
// searchParams.set('zoneId', zone.id);
|
|
202
|
-
// searchParams.set('zoneName', zone.name);
|
|
203
|
-
// }
|
|
204
|
-
// if (resourceId) {
|
|
205
|
-
// searchParams.set('resourceId', resourceId);
|
|
206
|
-
// }
|
|
207
|
-
// if (resourceName) {
|
|
208
|
-
// searchParams.set('resourceName', resourceName);
|
|
209
|
-
// }
|
|
210
|
-
// if (instanceNumber) {
|
|
211
|
-
// searchParams.set('resourceInstance', instanceNumber.toString());
|
|
212
|
-
// }
|
|
213
|
-
// navigate(`${basePath}/flows/add?${searchParams.toString()}`);
|
|
214
|
-
|
|
215
|
-
// =====================================================
|
|
216
|
-
// V2 Quick Add - Open side panel (ACTIVE)
|
|
217
|
-
// =====================================================
|
|
218
|
-
setQuickAddData({
|
|
219
|
-
date,
|
|
220
|
-
time: timeSlot,
|
|
221
|
-
zoneId: zone?.id,
|
|
222
|
-
zoneName: zone?.name,
|
|
223
|
-
resourceId,
|
|
224
|
-
resourceName,
|
|
225
|
-
resourceInstance: instanceNumber
|
|
226
|
-
});
|
|
227
|
-
setQuickAddPanelOpen(true);
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// Calculate target date based on selectedDate + dayOffset
|
|
231
|
-
const baseDate = selectedDate === 'today' ? new Date() : addDays(new Date(), 1);
|
|
232
|
-
const targetDate = addDays(baseDate, dayOffset);
|
|
233
|
-
const formattedDate = format(targetDate, 'yyyy-MM-dd');
|
|
234
|
-
const nextFormattedDate = format(addDays(targetDate, 1), 'yyyy-MM-dd');
|
|
235
|
-
const displayDate = format(targetDate, 'EEEE, d MMMM yyyy');
|
|
236
|
-
const dayOfWeek = getDay(targetDate);
|
|
237
|
-
|
|
238
|
-
// Get opening hours for this day
|
|
239
|
-
const todaysHours = openingHours.find((h) => h.day_of_week === dayOfWeek);
|
|
240
|
-
|
|
241
|
-
// Check for 24/7 mode (both open and close time are 00:00)
|
|
242
|
-
const is24_7 =
|
|
243
|
-
!!todaysHours?.is_open &&
|
|
244
|
-
todaysHours.open_time.startsWith('00:00') &&
|
|
245
|
-
todaysHours.close_time.startsWith('00:00');
|
|
246
|
-
|
|
247
|
-
const openTime = todaysHours?.is_open ? parseTime(todaysHours.open_time) : { hour: 9, minute: 0 };
|
|
248
|
-
const closeTime = todaysHours?.is_open ? parseTime(todaysHours.close_time) : { hour: 22, minute: 0 };
|
|
249
|
-
|
|
250
|
-
const openTotalMinutes = openTime.hour * 60 + openTime.minute;
|
|
251
|
-
const closeTotalMinutes = closeTime.hour * 60 + closeTime.minute;
|
|
252
|
-
|
|
253
|
-
// Venue runs into the next day when close time is earlier/equal to open time (e.g., 22:00 -> 02:00)
|
|
254
|
-
const isNextDayClose = !!todaysHours?.is_open && !is24_7 && closeTotalMinutes <= openTotalMinutes;
|
|
255
|
-
|
|
256
|
-
const startMinutes = todaysHours?.is_open ? openTotalMinutes : 9 * 60;
|
|
257
|
-
|
|
258
|
-
// Actual closing time in minutes (used to determine "closed" slots)
|
|
259
|
-
const actualCloseMinutes = todaysHours?.is_open
|
|
260
|
-
? is24_7
|
|
261
|
-
? 24 * 60
|
|
262
|
-
: isNextDayClose
|
|
263
|
-
? closeTotalMinutes + 24 * 60
|
|
264
|
-
: closeTotalMinutes
|
|
265
|
-
: 22 * 60;
|
|
266
|
-
|
|
267
|
-
// Extend display by 1 hour past closing to show the closing time + buffer
|
|
268
|
-
const displayBufferMinutes = 60;
|
|
269
|
-
const endMinutes = actualCloseMinutes + displayBufferMinutes;
|
|
270
|
-
|
|
271
|
-
const timeSlots = useMemo(() => generateTimeSlots(startMinutes, endMinutes), [startMinutes, endMinutes]);
|
|
272
|
-
|
|
273
|
-
// For overview mode, show only hourly slots
|
|
274
|
-
const overviewSlots = useMemo(() => timeSlots.filter((slot) => slot.totalMinutes % 60 === 0), [timeSlots]);
|
|
275
|
-
const displaySlots: TimeSlot[] = viewMode === 'overview' ? overviewSlots : timeSlots;
|
|
276
|
-
|
|
277
|
-
// Pre-compute a flow lookup map: key = "resourceId-instanceNumber-slotMinutes" => Flow
|
|
278
|
-
// This replaces the O(flows) search inside every cell render
|
|
279
|
-
const flowLookup = useMemo(() => {
|
|
280
|
-
const map = new Map<string, Flow>();
|
|
281
|
-
|
|
282
|
-
for (const flow of flows) {
|
|
283
|
-
const startTime = parseTime(flow.booking_time_start);
|
|
284
|
-
const endTime = flow.booking_time_end
|
|
285
|
-
? parseTime(flow.booking_time_end)
|
|
286
|
-
: { hour: startTime.hour + 1, minute: startTime.minute };
|
|
287
|
-
|
|
288
|
-
let flowStartMinutes = startTime.hour * 60 + startTime.minute;
|
|
289
|
-
let flowEndMinutes = endTime.hour * 60 + endTime.minute;
|
|
290
|
-
if (flowEndMinutes <= flowStartMinutes) flowEndMinutes += 1440;
|
|
291
|
-
|
|
292
|
-
// For each slot this flow occupies, add to the map
|
|
293
|
-
for (const slot of timeSlots) {
|
|
294
|
-
let fStart = flowStartMinutes;
|
|
295
|
-
let fEnd = flowEndMinutes;
|
|
296
|
-
|
|
297
|
-
// Handle next-day territory
|
|
298
|
-
if (slot.totalMinutes >= 1440 && fStart < startMinutes) {
|
|
299
|
-
fStart += 1440;
|
|
300
|
-
fEnd += 1440;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (slot.totalMinutes >= fStart && slot.totalMinutes < fEnd) {
|
|
304
|
-
if (flow.resource_id && flow.resource_instance) {
|
|
305
|
-
const key = `${flow.resource_id}-${flow.resource_instance}-${slot.totalMinutes}`;
|
|
306
|
-
map.set(key, flow);
|
|
307
|
-
}
|
|
308
|
-
// Also store by zone for legacy/fallback matching
|
|
309
|
-
if (flow.zone_id) {
|
|
310
|
-
const legacyKey = `legacy-${flow.zone_id}-${flow.activity_name}-${slot.totalMinutes}`;
|
|
311
|
-
map.set(legacyKey, flow);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return map;
|
|
317
|
-
}, [flows, timeSlots, startMinutes]);
|
|
318
|
-
|
|
319
|
-
// Helper to find a flow at a given slot for a given instance
|
|
320
|
-
const getFlowAtSlot = useCallback((
|
|
321
|
-
instance: { resourceId: string; instanceNumber: number; name: string },
|
|
322
|
-
zone: Zone,
|
|
323
|
-
slotMinutes: number
|
|
324
|
-
): Flow | undefined => {
|
|
325
|
-
// Direct lookup by resource + instance
|
|
326
|
-
const directKey = `${instance.resourceId}-${instance.instanceNumber}-${slotMinutes}`;
|
|
327
|
-
const direct = flowLookup.get(directKey);
|
|
328
|
-
if (direct) return direct;
|
|
329
|
-
|
|
330
|
-
// Fallback: check legacy flows that match by activity name
|
|
331
|
-
for (const flow of flows) {
|
|
332
|
-
if (flow.resource_id && flow.resource_instance) continue; // already handled above
|
|
333
|
-
|
|
334
|
-
const matchesName = flow.activity_name.toLowerCase().includes(zone.name.toLowerCase()) ||
|
|
335
|
-
flow.activity_name.toLowerCase().includes(instance.name.split(' ')[0].toLowerCase());
|
|
336
|
-
if (!matchesName) continue;
|
|
337
|
-
|
|
338
|
-
const st = parseTime(flow.booking_time_start);
|
|
339
|
-
const et = flow.booking_time_end ? parseTime(flow.booking_time_end) : { hour: st.hour + 1, minute: st.minute };
|
|
340
|
-
let fStart = st.hour * 60 + st.minute;
|
|
341
|
-
let fEnd = et.hour * 60 + et.minute;
|
|
342
|
-
if (fEnd <= fStart) fEnd += 1440;
|
|
343
|
-
if (slotMinutes >= 1440 && fStart < startMinutes) { fStart += 1440; fEnd += 1440; }
|
|
344
|
-
if (slotMinutes >= fStart && slotMinutes < fEnd) return flow;
|
|
345
|
-
}
|
|
346
|
-
return undefined;
|
|
347
|
-
}, [flowLookup, flows, startMinutes]);
|
|
348
|
-
|
|
349
|
-
// Column sizing (must match between header + body tables)
|
|
350
|
-
const firstColWidthPx = viewMode === 'overview' ? 120 : 192; // matches w-[120px] and w-48
|
|
351
|
-
const detailedSlotWidthPx = 50;
|
|
352
|
-
const tableWidthPx = viewMode === 'detailed'
|
|
353
|
-
? firstColWidthPx + displaySlots.length * detailedSlotWidthPx
|
|
354
|
-
: undefined;
|
|
355
|
-
|
|
356
|
-
// Current time info for "past slot" detection
|
|
357
|
-
const now = new Date();
|
|
358
|
-
const isToday = format(now, 'yyyy-MM-dd') === formattedDate;
|
|
359
|
-
const currentHour = now.getHours();
|
|
360
|
-
const currentMinute = now.getMinutes();
|
|
361
|
-
const nowTotalMinutes = currentHour * 60 + currentMinute;
|
|
362
|
-
|
|
363
|
-
// Helper: check if a slot is in the past (only matters if viewing today)
|
|
364
|
-
const isSlotInPast = (slot: TimeSlot): boolean => {
|
|
365
|
-
if (!isToday) return false;
|
|
366
|
-
return slot.totalMinutes < nowTotalMinutes;
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
// Helper: check if a slot is after closing time (venue closed)
|
|
370
|
-
const isSlotClosed = (slot: TimeSlot): boolean => {
|
|
371
|
-
return slot.totalMinutes >= actualCloseMinutes;
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
// Helper: check if a slot is closed for a specific zone (considers zone-specific hours)
|
|
375
|
-
const isSlotClosedForZone = (slot: TimeSlot, zone: Zone): boolean => {
|
|
376
|
-
// If zone uses venue hours, use the venue's closed check
|
|
377
|
-
if (zone.use_venue_hours) {
|
|
378
|
-
return isSlotClosed(slot);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Zone has custom hours - check against zone opening hours
|
|
382
|
-
const zoneHoursForDay = zone.opening_hours?.find(h => h.day_of_week === dayOfWeek);
|
|
383
|
-
|
|
384
|
-
// If no hours defined for this day or zone is closed, the slot is closed
|
|
385
|
-
if (!zoneHoursForDay || !zoneHoursForDay.is_open) {
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Parse zone hours
|
|
390
|
-
const zoneOpenTime = parseTime(zoneHoursForDay.open_time);
|
|
391
|
-
const zoneCloseTime = parseTime(zoneHoursForDay.close_time);
|
|
392
|
-
const zoneOpenMinutes = zoneOpenTime.hour * 60 + zoneOpenTime.minute;
|
|
393
|
-
const zoneCloseMinutes = zoneCloseTime.hour * 60 + zoneCloseTime.minute;
|
|
394
|
-
|
|
395
|
-
// Check if slot is outside zone availability
|
|
396
|
-
// Also check if slot is outside venue hours (can't be open when venue is closed)
|
|
397
|
-
const isOutsideZoneHours = slot.totalMinutes < zoneOpenMinutes || slot.totalMinutes >= zoneCloseMinutes;
|
|
398
|
-
const isOutsideVenueHours = isSlotClosed(slot);
|
|
399
|
-
|
|
400
|
-
return isOutsideZoneHours || isOutsideVenueHours;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
// Scroll to show current time as second segment on load
|
|
404
|
-
const getNowColumnIndex = useMemo(() => {
|
|
405
|
-
if (!isToday) return null;
|
|
406
|
-
if (nowTotalMinutes < startMinutes || nowTotalMinutes >= endMinutes) return null;
|
|
407
|
-
|
|
408
|
-
const slotIndex = displaySlots.findIndex((slot, i) => {
|
|
409
|
-
const next = displaySlots[i + 1];
|
|
410
|
-
const upperBound = next ? next.totalMinutes : endMinutes;
|
|
411
|
-
return nowTotalMinutes >= slot.totalMinutes && nowTotalMinutes < upperBound;
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
return slotIndex >= 0 ? slotIndex : null;
|
|
415
|
-
}, [isToday, nowTotalMinutes, startMinutes, endMinutes, displaySlots]);
|
|
416
|
-
|
|
417
|
-
// Calculate "now" line position in pixels from the start of the time columns (detailed mode)
|
|
418
|
-
const nowLinePosition = useMemo(() => {
|
|
419
|
-
if (!isToday) return null;
|
|
420
|
-
if (viewMode !== 'detailed') return null;
|
|
421
|
-
if (nowTotalMinutes < startMinutes || nowTotalMinutes >= endMinutes) return null;
|
|
422
|
-
|
|
423
|
-
const minutesFromStart = nowTotalMinutes - startMinutes;
|
|
424
|
-
|
|
425
|
-
// Each 30-min slot is 50px wide
|
|
426
|
-
const pixelsPerMinute = detailedSlotWidthPx / 30;
|
|
427
|
-
return firstColWidthPx + minutesFromStart * pixelsPerMinute;
|
|
428
|
-
}, [isToday, nowTotalMinutes, startMinutes, endMinutes, viewMode, firstColWidthPx, detailedSlotWidthPx]);
|
|
429
|
-
|
|
430
|
-
// For overview mode, calculate percentage position
|
|
431
|
-
const nowLinePercentage = useMemo(() => {
|
|
432
|
-
if (!isToday || viewMode !== 'overview') return null;
|
|
433
|
-
if (nowTotalMinutes < startMinutes || nowTotalMinutes >= endMinutes) return null;
|
|
434
|
-
|
|
435
|
-
const minutesFromStart = nowTotalMinutes - startMinutes;
|
|
436
|
-
const totalMinutes = endMinutes - startMinutes;
|
|
437
|
-
if (totalMinutes <= 0) return null;
|
|
438
|
-
|
|
439
|
-
return (minutesFromStart / totalMinutes) * 100;
|
|
440
|
-
}, [isToday, nowTotalMinutes, startMinutes, endMinutes, viewMode]);
|
|
441
|
-
|
|
442
|
-
// Sync scroll position on initial load to show current time
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
if (isToday && !isLoading && bodyScrollRef.current && getNowColumnIndex !== null && getNowColumnIndex > 0) {
|
|
445
|
-
const slotWidth = viewMode === 'detailed' ? 50 : 80;
|
|
446
|
-
const scrollPosition = Math.max(0, (getNowColumnIndex - 1) * slotWidth);
|
|
447
|
-
bodyScrollRef.current.scrollLeft = scrollPosition;
|
|
448
|
-
if (headerScrollRef.current) {
|
|
449
|
-
headerScrollRef.current.scrollLeft = scrollPosition;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}, [isLoading, isToday, getNowColumnIndex, viewMode]);
|
|
453
|
-
|
|
454
|
-
// Synchronized scroll handler
|
|
455
|
-
const handleBodyScroll = useCallback(() => {
|
|
456
|
-
if (isScrolling.current) return;
|
|
457
|
-
isScrolling.current = true;
|
|
458
|
-
if (headerScrollRef.current && bodyScrollRef.current) {
|
|
459
|
-
headerScrollRef.current.scrollLeft = bodyScrollRef.current.scrollLeft;
|
|
460
|
-
}
|
|
461
|
-
requestAnimationFrame(() => { isScrolling.current = false; });
|
|
462
|
-
}, []);
|
|
463
|
-
|
|
464
|
-
const handleHeaderScroll = useCallback(() => {
|
|
465
|
-
if (isScrolling.current) return;
|
|
466
|
-
isScrolling.current = true;
|
|
467
|
-
if (headerScrollRef.current && bodyScrollRef.current) {
|
|
468
|
-
bodyScrollRef.current.scrollLeft = headerScrollRef.current.scrollLeft;
|
|
469
|
-
}
|
|
470
|
-
requestAnimationFrame(() => { isScrolling.current = false; });
|
|
471
|
-
}, []);
|
|
472
|
-
|
|
473
|
-
// Reset day offset when selectedDate changes
|
|
474
|
-
useEffect(() => {
|
|
475
|
-
setDayOffset(0);
|
|
476
|
-
}, [selectedDate]);
|
|
477
|
-
|
|
478
|
-
useEffect(() => {
|
|
479
|
-
fetchData();
|
|
480
|
-
}, [providerId, formattedDate]);
|
|
481
|
-
|
|
482
|
-
const fetchData = async () => {
|
|
483
|
-
setIsLoading(true);
|
|
484
|
-
try {
|
|
485
|
-
// Fetch opening hours for this provider
|
|
486
|
-
const { data: hoursData, error: hoursError } = await supabase
|
|
487
|
-
.from('provider_opening_hours')
|
|
488
|
-
.select('*')
|
|
489
|
-
.eq('provider_id', providerId);
|
|
490
|
-
|
|
491
|
-
if (hoursError) throw hoursError;
|
|
492
|
-
setOpeningHours(hoursData || []);
|
|
493
|
-
|
|
494
|
-
// Fetch zones with their resources
|
|
495
|
-
const { data: zonesData, error: zonesError } = await supabase
|
|
496
|
-
.from('zones')
|
|
497
|
-
.select('*')
|
|
498
|
-
.eq('provider_id', providerId)
|
|
499
|
-
.eq('status', 'active')
|
|
500
|
-
.order('display_order', { ascending: true });
|
|
501
|
-
|
|
502
|
-
if (zonesError) throw zonesError;
|
|
503
|
-
|
|
504
|
-
// Fetch all zone opening hours for zones that have custom hours
|
|
505
|
-
const zoneIds = (zonesData || []).filter(z => !z.use_venue_hours).map(z => z.id);
|
|
506
|
-
let zoneOpeningHoursMap: Record<string, ZoneOpeningHour[]> = {};
|
|
507
|
-
|
|
508
|
-
if (zoneIds.length > 0) {
|
|
509
|
-
const { data: zoneHoursData, error: zoneHoursError } = await supabase
|
|
510
|
-
.from('zone_opening_hours')
|
|
511
|
-
.select('*')
|
|
512
|
-
.in('zone_id', zoneIds);
|
|
513
|
-
|
|
514
|
-
if (zoneHoursError) throw zoneHoursError;
|
|
515
|
-
|
|
516
|
-
// Group by zone_id
|
|
517
|
-
(zoneHoursData || []).forEach(h => {
|
|
518
|
-
if (!zoneOpeningHoursMap[h.zone_id]) {
|
|
519
|
-
zoneOpeningHoursMap[h.zone_id] = [];
|
|
520
|
-
}
|
|
521
|
-
zoneOpeningHoursMap[h.zone_id].push(h);
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Fetch resources for each zone
|
|
526
|
-
const zonesWithResources: Zone[] = [];
|
|
527
|
-
for (const zone of zonesData || []) {
|
|
528
|
-
const { data: resourcesData, error: resourcesError } = await supabase
|
|
529
|
-
.from('resources')
|
|
530
|
-
.select('*')
|
|
531
|
-
.eq('zone_id', zone.id)
|
|
532
|
-
.order('display_order', { ascending: true });
|
|
533
|
-
|
|
534
|
-
if (resourcesError) throw resourcesError;
|
|
535
|
-
|
|
536
|
-
zonesWithResources.push({
|
|
537
|
-
...zone,
|
|
538
|
-
resources: resourcesData || [],
|
|
539
|
-
opening_hours: zoneOpeningHoursMap[zone.id] || []
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
setZones(zonesWithResources);
|
|
544
|
-
|
|
545
|
-
// Fetch flows for the selected date
|
|
546
|
-
const { data: flowsData, error: flowsError } = await supabase
|
|
547
|
-
.from('bookings')
|
|
548
|
-
.select('*')
|
|
549
|
-
.eq('provider_id', providerId)
|
|
550
|
-
.eq('booking_date', formattedDate)
|
|
551
|
-
.order('booking_time_start');
|
|
552
|
-
|
|
553
|
-
if (flowsError) throw flowsError;
|
|
554
|
-
setFlows(flowsData || []);
|
|
555
|
-
} catch (error) {
|
|
556
|
-
console.error('Error fetching diary data:', error);
|
|
557
|
-
showError('Failed to load diary data');
|
|
558
|
-
} finally {
|
|
559
|
-
setIsLoading(false);
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
// Day pagination handlers
|
|
564
|
-
const handlePrevDay = () => setDayOffset(prev => prev - 1);
|
|
565
|
-
const handleNextDay = () => setDayOffset(prev => prev + 1);
|
|
566
|
-
|
|
567
|
-
if (isLoading) {
|
|
568
|
-
return (
|
|
569
|
-
<div className="flex items-center justify-center py-12">
|
|
570
|
-
<LoadingSpinner size="sm" />
|
|
571
|
-
</div>
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (zones.length === 0) {
|
|
576
|
-
return (
|
|
577
|
-
<div className="text-center py-12 text-label-tertiary">
|
|
578
|
-
<p className="text-lg font-medium mb-2">No zones configured</p>
|
|
579
|
-
<p className="text-sm">Add zones in Settings → Zones to use the diary view.</p>
|
|
580
|
-
</div>
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
return (
|
|
585
|
-
<div className="flex flex-col gap-4 w-full min-w-0 max-w-full overflow-hidden">
|
|
586
|
-
{/* Header with Date and Day Pagination */}
|
|
587
|
-
<div className="flex flex-col gap-3">
|
|
588
|
-
<div className="flex items-center justify-between">
|
|
589
|
-
<div className="text-label-primary text-lg font-semibold">
|
|
590
|
-
{displayDate}
|
|
591
|
-
</div>
|
|
592
|
-
{/* Day Pagination Controls - matching table pagination style */}
|
|
593
|
-
<div className="flex items-center gap-2">
|
|
594
|
-
<Button
|
|
595
|
-
variant="ghost"
|
|
596
|
-
size="icon"
|
|
597
|
-
onClick={handlePrevDay}
|
|
598
|
-
className="h-8 w-8 rounded-full bg-surface-action-soft hover:bg-surface-action-soft-hover text-fill-action"
|
|
599
|
-
>
|
|
600
|
-
<IconArrowLeft className="h-4 w-4" />
|
|
601
|
-
</Button>
|
|
602
|
-
<Button
|
|
603
|
-
variant="ghost"
|
|
604
|
-
size="icon"
|
|
605
|
-
onClick={handleNextDay}
|
|
606
|
-
className="h-8 w-8 rounded-full bg-surface-action-soft hover:bg-surface-action-soft-hover text-fill-action"
|
|
607
|
-
>
|
|
608
|
-
<IconArrowRight className="h-4 w-4" />
|
|
609
|
-
</Button>
|
|
610
|
-
</div>
|
|
611
|
-
</div>
|
|
612
|
-
|
|
613
|
-
{/* Zone Quick Filters - Below Date */}
|
|
614
|
-
{zoneFilters && zoneFilters.length > 0 && onZoneFilterChange && (
|
|
615
|
-
<QuickFilters
|
|
616
|
-
filters={zoneFilters.map(zone => ({ value: zone.id, label: zone.name }))}
|
|
617
|
-
activeFilter={selectedZoneId || 'all'}
|
|
618
|
-
onFilterChange={(filter) => onZoneFilterChange(filter)}
|
|
619
|
-
/>
|
|
620
|
-
)}
|
|
621
|
-
|
|
622
|
-
{truncatedResources.length > 0 && (
|
|
623
|
-
<div className="rounded-lg border border-border-primary bg-surface-secondary p-3">
|
|
624
|
-
<p className="text-sm text-label-secondary">
|
|
625
|
-
Some resources have very high instance counts and are capped to {MAX_RESOURCE_INSTANCES_TO_RENDER} instances in the diary to prevent freezing.
|
|
626
|
-
</p>
|
|
627
|
-
</div>
|
|
628
|
-
)}
|
|
629
|
-
|
|
630
|
-
</div>
|
|
631
|
-
|
|
632
|
-
{/* Diary Grid - Two-part structure for sticky header */}
|
|
633
|
-
<div className="border border-border-primary rounded-lg bg-surface-primary w-full overflow-hidden">
|
|
634
|
-
{/* Sticky Header Section */}
|
|
635
|
-
<div
|
|
636
|
-
className="sticky top-12 z-20 bg-surface-primary border-b border-border-primary"
|
|
637
|
-
>
|
|
638
|
-
<div
|
|
639
|
-
ref={headerScrollRef}
|
|
640
|
-
onScroll={handleHeaderScroll}
|
|
641
|
-
className={cn(
|
|
642
|
-
'w-full',
|
|
643
|
-
viewMode === 'detailed' ? 'overflow-x-auto' : 'overflow-x-hidden'
|
|
644
|
-
)}
|
|
645
|
-
>
|
|
646
|
-
<div className={cn('relative', viewMode === 'detailed' ? 'min-w-max' : 'w-full')}>
|
|
647
|
-
<table
|
|
648
|
-
className="w-full table-fixed border-separate border-spacing-0"
|
|
649
|
-
style={viewMode === 'detailed' ? { width: tableWidthPx } : undefined}
|
|
650
|
-
>
|
|
651
|
-
<colgroup>
|
|
652
|
-
<col style={{ width: firstColWidthPx }} />
|
|
653
|
-
{displaySlots.map((slot) => (
|
|
654
|
-
<col
|
|
655
|
-
key={slot.key}
|
|
656
|
-
className={viewMode === 'overview' ? 'w-auto' : ''}
|
|
657
|
-
style={viewMode === 'detailed' ? { width: detailedSlotWidthPx } : undefined}
|
|
658
|
-
/>
|
|
659
|
-
))}
|
|
660
|
-
</colgroup>
|
|
661
|
-
<thead>
|
|
662
|
-
<tr className="bg-surface-secondary">
|
|
663
|
-
<th
|
|
664
|
-
style={{ width: firstColWidthPx }}
|
|
665
|
-
className="p-3 border-r border-border-primary text-left sticky left-0 bg-surface-secondary z-30"
|
|
666
|
-
>
|
|
667
|
-
<div className="flex items-center gap-2">
|
|
668
|
-
<button
|
|
669
|
-
onClick={() => setViewMode(viewMode === 'detailed' ? 'overview' : 'detailed')}
|
|
670
|
-
className="p-1 rounded-md bg-surface-primary text-label-secondary hover:bg-surface-primary-hover transition-colors"
|
|
671
|
-
title={viewMode === 'detailed' ? 'Switch to Overview' : 'Switch to Detailed'}
|
|
672
|
-
>
|
|
673
|
-
{viewMode === 'detailed' ? (
|
|
674
|
-
<IconOverview className="w-5 h-5" />
|
|
675
|
-
) : (
|
|
676
|
-
<IconDetailed className="w-5 h-5" />
|
|
677
|
-
)}
|
|
678
|
-
</button>
|
|
679
|
-
<span className="text-label-secondary text-xs font-medium uppercase tracking-wider">
|
|
680
|
-
Zone
|
|
681
|
-
</span>
|
|
682
|
-
</div>
|
|
683
|
-
</th>
|
|
684
|
-
{displaySlots.map((slot) => {
|
|
685
|
-
const isPast = isSlotInPast(slot);
|
|
686
|
-
const isHourDivider = slot.totalMinutes % 60 === 0;
|
|
687
|
-
return (
|
|
688
|
-
<th
|
|
689
|
-
key={slot.key}
|
|
690
|
-
className={cn(
|
|
691
|
-
'p-2 text-center border-r border-border-primary last:border-r-0 relative bg-surface-secondary',
|
|
692
|
-
isHourDivider &&
|
|
693
|
-
'before:absolute before:left-0 before:top-0 before:bottom-0 before:w-px before:bg-border-secondary',
|
|
694
|
-
isPast && 'opacity-60'
|
|
695
|
-
)}
|
|
696
|
-
>
|
|
697
|
-
<span
|
|
698
|
-
className={cn(
|
|
699
|
-
'text-xs font-medium',
|
|
700
|
-
isPast ? 'text-label-tertiary' : 'text-label-secondary'
|
|
701
|
-
)}
|
|
702
|
-
>
|
|
703
|
-
{slot.label}
|
|
704
|
-
</span>
|
|
705
|
-
</th>
|
|
706
|
-
);
|
|
707
|
-
})}
|
|
708
|
-
</tr>
|
|
709
|
-
</thead>
|
|
710
|
-
</table>
|
|
711
|
-
</div>
|
|
712
|
-
</div>
|
|
713
|
-
</div>
|
|
714
|
-
|
|
715
|
-
{/* Scrollable Body Section */}
|
|
716
|
-
<div
|
|
717
|
-
ref={bodyScrollRef}
|
|
718
|
-
onScroll={handleBodyScroll}
|
|
719
|
-
className={cn(
|
|
720
|
-
'w-full relative',
|
|
721
|
-
viewMode === 'detailed' ? 'overflow-x-auto' : 'overflow-x-hidden'
|
|
722
|
-
)}
|
|
723
|
-
>
|
|
724
|
-
<div className={cn('relative', viewMode === 'detailed' ? 'min-w-max' : 'w-full')}>
|
|
725
|
-
{/* "Now" line indicator - z-[5] to render below sticky cells z-[9] */}
|
|
726
|
-
{isToday && viewMode === 'detailed' && nowLinePosition !== null && (
|
|
727
|
-
<div
|
|
728
|
-
className="absolute top-0 bottom-0 w-0.5 bg-fill-action z-[5] pointer-events-none"
|
|
729
|
-
style={{ left: nowLinePosition }}
|
|
730
|
-
/>
|
|
731
|
-
)}
|
|
732
|
-
{isToday && viewMode === 'overview' && nowLinePercentage !== null && (
|
|
733
|
-
<div
|
|
734
|
-
className="absolute top-0 bottom-0 w-0.5 bg-fill-action z-[5] pointer-events-none"
|
|
735
|
-
style={{ left: `calc(${firstColWidthPx}px + (100% - ${firstColWidthPx}px) * ${nowLinePercentage / 100})` }}
|
|
736
|
-
/>
|
|
737
|
-
)}
|
|
738
|
-
<table
|
|
739
|
-
className="w-full table-fixed border-separate border-spacing-0"
|
|
740
|
-
style={viewMode === 'detailed' ? { width: tableWidthPx } : undefined}
|
|
741
|
-
>
|
|
742
|
-
<colgroup>
|
|
743
|
-
<col style={{ width: firstColWidthPx }} />
|
|
744
|
-
{displaySlots.map((slot) => (
|
|
745
|
-
<col
|
|
746
|
-
key={slot.key}
|
|
747
|
-
className={viewMode === 'overview' ? 'w-auto' : ''}
|
|
748
|
-
style={viewMode === 'detailed' ? { width: detailedSlotWidthPx } : undefined}
|
|
749
|
-
/>
|
|
750
|
-
))}
|
|
751
|
-
</colgroup>
|
|
752
|
-
<tbody>
|
|
753
|
-
{zones.filter(zone => !selectedZoneId || zone.id === selectedZoneId).map((zone) => {
|
|
754
|
-
const expandedResources = zone.resources.flatMap(expandResources);
|
|
755
|
-
|
|
756
|
-
return (
|
|
757
|
-
<React.Fragment key={zone.id}>
|
|
758
|
-
{/* Zone Header - solid color */}
|
|
759
|
-
<tr className="bg-surface-tertiary">
|
|
760
|
-
<td
|
|
761
|
-
style={{ width: firstColWidthPx }}
|
|
762
|
-
className="p-3 border-r border-b border-border-primary sticky left-0 bg-surface-tertiary z-[9]"
|
|
763
|
-
>
|
|
764
|
-
<span className="text-label-primary text-sm font-semibold">{zone.name}</span>
|
|
765
|
-
{zone.description && (
|
|
766
|
-
<p className="text-label-tertiary text-xs mt-0.5 truncate">{zone.description}</p>
|
|
767
|
-
)}
|
|
768
|
-
</td>
|
|
769
|
-
{displaySlots.map((slot) => {
|
|
770
|
-
const isHourDivider = slot.totalMinutes % 60 === 0;
|
|
771
|
-
return (
|
|
772
|
-
<td
|
|
773
|
-
key={slot.key}
|
|
774
|
-
className={cn(
|
|
775
|
-
'border-r border-b border-border-primary last:border-r-0 bg-surface-tertiary relative',
|
|
776
|
-
isHourDivider && 'before:absolute before:left-0 before:top-0 before:bottom-0 before:w-px before:bg-border-secondary'
|
|
777
|
-
)}
|
|
778
|
-
/>
|
|
779
|
-
);
|
|
780
|
-
})}
|
|
781
|
-
</tr>
|
|
782
|
-
|
|
783
|
-
{/* Expanded Resources (each instance as a row) */}
|
|
784
|
-
{expandedResources.length > 0 ? (
|
|
785
|
-
expandedResources.map((instance, instanceIndex) => {
|
|
786
|
-
// Check if this is the last instance of the current resource
|
|
787
|
-
const currentResourceId = instance.resourceId;
|
|
788
|
-
const nextInstance = expandedResources[instanceIndex + 1];
|
|
789
|
-
const isLastInstanceOfResource = !nextInstance || nextInstance.resourceId !== currentResourceId;
|
|
790
|
-
|
|
791
|
-
return (
|
|
792
|
-
<React.Fragment key={instance.id}>
|
|
793
|
-
<tr>
|
|
794
|
-
<td
|
|
795
|
-
style={{ width: firstColWidthPx }}
|
|
796
|
-
className="p-3 border-r border-b border-border-primary sticky left-0 z-[9] bg-surface-primary"
|
|
797
|
-
>
|
|
798
|
-
<div className="flex flex-col">
|
|
799
|
-
<span className="text-label-primary text-sm">{instance.name}</span>
|
|
800
|
-
<span className="text-label-secondary text-xs">Cap: {instance.maxCapacity}</span>
|
|
801
|
-
</div>
|
|
802
|
-
</td>
|
|
803
|
-
|
|
804
|
-
{displaySlots.map((slot) => {
|
|
805
|
-
const bookingAtSlot = getFlowAtSlot(instance, zone, slot.totalMinutes);
|
|
806
|
-
|
|
807
|
-
const isPastSlot = isSlotInPast(slot);
|
|
808
|
-
const isClosedSlot = isSlotClosedForZone(slot, zone);
|
|
809
|
-
const isUnavailable = isPastSlot || isClosedSlot;
|
|
810
|
-
const canQuickAdd = !bookingAtSlot && !isUnavailable;
|
|
811
|
-
const isHourDivider = slot.totalMinutes % 60 === 0;
|
|
812
|
-
|
|
813
|
-
return (
|
|
814
|
-
<td
|
|
815
|
-
key={slot.key}
|
|
816
|
-
onClick={() => canQuickAdd && handleQuickAdd(
|
|
817
|
-
slot.totalMinutes >= 1440 ? nextFormattedDate : formattedDate,
|
|
818
|
-
slot.label,
|
|
819
|
-
zone,
|
|
820
|
-
instance.name,
|
|
821
|
-
instance.resourceId,
|
|
822
|
-
instance.instanceNumber
|
|
823
|
-
)}
|
|
824
|
-
className={cn(
|
|
825
|
-
'border-r border-b border-border-primary last:border-r-0 h-10 p-0.5 relative group',
|
|
826
|
-
isHourDivider && 'before:absolute before:left-0 before:top-0 before:bottom-0 before:w-px before:bg-border-secondary',
|
|
827
|
-
isUnavailable && !bookingAtSlot && 'bg-surface-secondary',
|
|
828
|
-
bookingAtSlot && bookingAtSlot.type === 'booking' && 'bg-fill-highlight',
|
|
829
|
-
bookingAtSlot && bookingAtSlot.type === 'enquiry' && 'bg-fill-warning',
|
|
830
|
-
canQuickAdd && 'cursor-pointer hover:bg-surface-primary-hover'
|
|
831
|
-
)}
|
|
832
|
-
>
|
|
833
|
-
{bookingAtSlot ? (
|
|
834
|
-
<HoverCard openDelay={200} closeDelay={100}>
|
|
835
|
-
<HoverCardTrigger asChild>
|
|
836
|
-
<div
|
|
837
|
-
className="absolute inset-0 cursor-pointer"
|
|
838
|
-
onClick={() => onFlowClick?.(bookingAtSlot.id)}
|
|
839
|
-
>
|
|
840
|
-
{viewMode === 'detailed' && (
|
|
841
|
-
<div
|
|
842
|
-
className={cn(
|
|
843
|
-
'text-xs truncate px-1 pt-0.5',
|
|
844
|
-
bookingAtSlot.type === 'booking' ? 'text-label-white' : 'text-label-primary'
|
|
845
|
-
)}
|
|
846
|
-
>
|
|
847
|
-
{bookingAtSlot.customer_name.split(' ')[0]}
|
|
848
|
-
</div>
|
|
849
|
-
)}
|
|
850
|
-
</div>
|
|
851
|
-
</HoverCardTrigger>
|
|
852
|
-
<HoverCardContent className="w-64 p-3" side="top" align="start">
|
|
853
|
-
<div className="flex flex-col gap-2">
|
|
854
|
-
<div className="flex items-center justify-between">
|
|
855
|
-
<span className={cn(
|
|
856
|
-
'text-xs font-medium px-2 py-0.5 rounded',
|
|
857
|
-
bookingAtSlot.type === 'booking'
|
|
858
|
-
? 'bg-fill-highlight text-label-white'
|
|
859
|
-
: 'bg-fill-warning text-label-primary'
|
|
860
|
-
)}>
|
|
861
|
-
{bookingAtSlot.type === 'booking' ? 'Booking' : 'Enquiry'}
|
|
862
|
-
</span>
|
|
863
|
-
<span className="text-xs text-label-tertiary">#{bookingAtSlot.reference_number}</span>
|
|
864
|
-
</div>
|
|
865
|
-
<div className="text-sm font-semibold text-label-primary">
|
|
866
|
-
{bookingAtSlot.customer_name}
|
|
867
|
-
</div>
|
|
868
|
-
<div className="text-sm text-label-secondary">
|
|
869
|
-
{bookingAtSlot.activity_name}
|
|
870
|
-
</div>
|
|
871
|
-
<div className="flex items-center gap-2 text-xs text-label-tertiary">
|
|
872
|
-
<span>{bookingAtSlot.booking_time_start.slice(0, 5)}</span>
|
|
873
|
-
{bookingAtSlot.booking_time_end && (
|
|
874
|
-
<>
|
|
875
|
-
<span>→</span>
|
|
876
|
-
<span>{bookingAtSlot.booking_time_end.slice(0, 5)}</span>
|
|
877
|
-
</>
|
|
878
|
-
)}
|
|
879
|
-
</div>
|
|
880
|
-
{bookingAtSlot.total_amount > 0 && (
|
|
881
|
-
<div className="text-sm font-medium text-label-primary">
|
|
882
|
-
£{bookingAtSlot.total_amount.toFixed(2)}
|
|
883
|
-
</div>
|
|
884
|
-
)}
|
|
885
|
-
</div>
|
|
886
|
-
</HoverCardContent>
|
|
887
|
-
</HoverCard>
|
|
888
|
-
) : (
|
|
889
|
-
<>
|
|
890
|
-
{/* Icon overlay - uses group-hover from parent td */}
|
|
891
|
-
{canQuickAdd && (
|
|
892
|
-
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-150 pointer-events-none">
|
|
893
|
-
<IconPlus size={32} className="text-fill-action" />
|
|
894
|
-
</div>
|
|
895
|
-
)}
|
|
896
|
-
</>
|
|
897
|
-
)}
|
|
898
|
-
</td>
|
|
899
|
-
);
|
|
900
|
-
})}
|
|
901
|
-
</tr>
|
|
902
|
-
{/* 4px gap divider between resources (but not at end of zone) */}
|
|
903
|
-
{isLastInstanceOfResource && nextInstance && (
|
|
904
|
-
<tr className="h-1 bg-surface-tertiary">
|
|
905
|
-
<td
|
|
906
|
-
style={{ width: firstColWidthPx }}
|
|
907
|
-
className="bg-surface-tertiary sticky left-0 z-[9]"
|
|
908
|
-
/>
|
|
909
|
-
{displaySlots.map((slot) => (
|
|
910
|
-
<td key={slot.key} className="bg-surface-tertiary" />
|
|
911
|
-
))}
|
|
912
|
-
</tr>
|
|
913
|
-
)}
|
|
914
|
-
</React.Fragment>
|
|
915
|
-
);
|
|
916
|
-
})
|
|
917
|
-
) : (
|
|
918
|
-
<tr>
|
|
919
|
-
<td
|
|
920
|
-
style={{ width: firstColWidthPx }}
|
|
921
|
-
className="p-3 border-r border-b border-border-primary pl-6 sticky left-0 bg-surface-primary z-[9]"
|
|
922
|
-
>
|
|
923
|
-
<span className="text-label-tertiary text-sm italic">No resources</span>
|
|
924
|
-
</td>
|
|
925
|
-
{displaySlots.map((slot) => {
|
|
926
|
-
const isPastSlot = isSlotInPast(slot);
|
|
927
|
-
const isClosedSlot = isSlotClosed(slot);
|
|
928
|
-
const isUnavailable = isPastSlot || isClosedSlot;
|
|
929
|
-
const canQuickAdd = !isUnavailable;
|
|
930
|
-
const isHourDivider = slot.totalMinutes % 60 === 0;
|
|
931
|
-
return (
|
|
932
|
-
<td
|
|
933
|
-
key={slot.key}
|
|
934
|
-
onClick={() => canQuickAdd && handleQuickAdd(
|
|
935
|
-
slot.totalMinutes >= 1440 ? nextFormattedDate : formattedDate,
|
|
936
|
-
slot.label,
|
|
937
|
-
zone
|
|
938
|
-
)}
|
|
939
|
-
className={cn(
|
|
940
|
-
'border-r border-b border-border-primary last:border-r-0 h-10 relative group',
|
|
941
|
-
isHourDivider && 'before:absolute before:left-0 before:top-0 before:bottom-0 before:w-px before:bg-border-secondary',
|
|
942
|
-
isUnavailable && 'bg-surface-secondary',
|
|
943
|
-
canQuickAdd && 'cursor-pointer hover:bg-surface-primary-hover'
|
|
944
|
-
)}
|
|
945
|
-
title={isClosedSlot ? 'Venue closed' : undefined}
|
|
946
|
-
>
|
|
947
|
-
{canQuickAdd && (
|
|
948
|
-
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-150 pointer-events-none">
|
|
949
|
-
<IconPlus size={32} className="text-fill-action" />
|
|
950
|
-
</div>
|
|
951
|
-
)}
|
|
952
|
-
</td>
|
|
953
|
-
);
|
|
954
|
-
})}
|
|
955
|
-
</tr>
|
|
956
|
-
)}
|
|
957
|
-
</React.Fragment>
|
|
958
|
-
);
|
|
959
|
-
})}
|
|
960
|
-
</tbody>
|
|
961
|
-
</table>
|
|
962
|
-
</div>
|
|
963
|
-
</div>
|
|
964
|
-
</div>
|
|
965
|
-
|
|
966
|
-
{/* Legend */}
|
|
967
|
-
<div className="flex items-center gap-6 text-sm">
|
|
968
|
-
<div className="flex items-center gap-2">
|
|
969
|
-
<div className="w-4 h-4 rounded bg-fill-highlight" />
|
|
970
|
-
<span className="text-label-secondary">Booking</span>
|
|
971
|
-
</div>
|
|
972
|
-
<div className="flex items-center gap-2">
|
|
973
|
-
<div className="w-4 h-4 rounded bg-fill-warning" />
|
|
974
|
-
<span className="text-label-secondary">Enquiry</span>
|
|
975
|
-
</div>
|
|
976
|
-
</div>
|
|
977
|
-
|
|
978
|
-
{/* V2 Quick Add Panel */}
|
|
979
|
-
<QuickAddPanel
|
|
980
|
-
isOpen={quickAddPanelOpen}
|
|
981
|
-
onClose={() => setQuickAddPanelOpen(false)}
|
|
982
|
-
onSuccess={() => {
|
|
983
|
-
// Refresh flows after successful creation
|
|
984
|
-
fetchData();
|
|
985
|
-
}}
|
|
986
|
-
providerId={providerId}
|
|
987
|
-
providerType={provider?.type || 'venue'}
|
|
988
|
-
prefilledDate={quickAddData?.date}
|
|
989
|
-
prefilledTime={quickAddData?.time}
|
|
990
|
-
prefilledZoneId={quickAddData?.zoneId}
|
|
991
|
-
prefilledZoneName={quickAddData?.zoneName}
|
|
992
|
-
prefilledResourceId={quickAddData?.resourceId}
|
|
993
|
-
prefilledResourceName={quickAddData?.resourceName}
|
|
994
|
-
prefilledResourceInstance={quickAddData?.resourceInstance}
|
|
995
|
-
/>
|
|
996
|
-
</div>
|
|
997
|
-
);
|
|
998
|
-
};
|