@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,810 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { startOfWeek, endOfWeek, eachDayOfInterval, format, isToday, addWeeks, subWeeks, isWithinInterval, getDay } from 'date-fns';
|
|
3
|
-
import { supabase } from '@/integrations/supabase/client';
|
|
4
|
-
import { useProvider } from '@/contexts/ProviderContext';
|
|
5
|
-
|
|
6
|
-
import { IconArrowLeft, IconArrowRight } from '../../../icons';
|
|
7
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
|
8
|
-
import { useSearchParams } from 'react-router-dom';
|
|
9
|
-
import { isAvailableOnDate, parseAvailabilityRules, type AvailabilityRule } from '@/lib/availabilityUtils';
|
|
10
|
-
import { QuickFilters } from '../QuickFilters';
|
|
11
|
-
|
|
12
|
-
interface OpeningHour {
|
|
13
|
-
day_of_week: number;
|
|
14
|
-
is_open: boolean;
|
|
15
|
-
open_time: string;
|
|
16
|
-
close_time: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface PricingPeriodRule {
|
|
20
|
-
type: 'range' | 'date_time';
|
|
21
|
-
start_date?: string;
|
|
22
|
-
end_date?: string;
|
|
23
|
-
days?: string[];
|
|
24
|
-
is_24_hours?: boolean;
|
|
25
|
-
start_time?: string;
|
|
26
|
-
end_time?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface PricingPeriod {
|
|
30
|
-
id: string;
|
|
31
|
-
name: string;
|
|
32
|
-
colour: string;
|
|
33
|
-
priority: number;
|
|
34
|
-
is_active: boolean;
|
|
35
|
-
rules: PricingPeriodRule[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface PeriodPricing {
|
|
39
|
-
name: string;
|
|
40
|
-
price: number;
|
|
41
|
-
colour?: string;
|
|
42
|
-
id?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface DynamicTierAdjustment {
|
|
46
|
-
tier_id: string;
|
|
47
|
-
adjustment: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface DynamicTier {
|
|
51
|
-
id: string;
|
|
52
|
-
name: string;
|
|
53
|
-
display_order: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface ProductItem {
|
|
57
|
-
id: string;
|
|
58
|
-
product_id: string;
|
|
59
|
-
title: string;
|
|
60
|
-
capacity: number;
|
|
61
|
-
duration_minutes: number;
|
|
62
|
-
period_pricing: PeriodPricing[] | null;
|
|
63
|
-
availability_rules: AvailabilityRule[];
|
|
64
|
-
dynamic_tier_pricing: DynamicTierAdjustment[] | null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface Product {
|
|
68
|
-
id: string;
|
|
69
|
-
name: string;
|
|
70
|
-
type: string;
|
|
71
|
-
product_group_id: string | null;
|
|
72
|
-
items: ProductItem[];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface ProductGroup {
|
|
76
|
-
id: string;
|
|
77
|
-
name: string;
|
|
78
|
-
products: Product[];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Represents a time segment within a day with its matching period */
|
|
82
|
-
interface TimeSegment {
|
|
83
|
-
period: PricingPeriod | null;
|
|
84
|
-
startTime: string | null; // HH:mm or null for all-day
|
|
85
|
-
endTime: string | null;
|
|
86
|
-
label?: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const DAY_MAP: Record<string, number> = {
|
|
90
|
-
'Monday': 1, 'Tuesday': 2, 'Wednesday': 3, 'Thursday': 4,
|
|
91
|
-
'Friday': 5, 'Saturday': 6, 'Sunday': 0,
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get all matching periods for a given date, considering time-based rules.
|
|
96
|
-
* Returns time segments showing which period applies at which time.
|
|
97
|
-
*/
|
|
98
|
-
const getTimeSegments = (date: Date, periods: PricingPeriod[], openingHours?: OpeningHour[]): TimeSegment[] => {
|
|
99
|
-
const dateStr = format(date, 'yyyy-MM-dd');
|
|
100
|
-
const dayOfWeek = date.getDay();
|
|
101
|
-
|
|
102
|
-
// Determine opening window for this day
|
|
103
|
-
const dayHours = openingHours?.find(h => h.day_of_week === dayOfWeek);
|
|
104
|
-
const hasOpeningHours = !!dayHours;
|
|
105
|
-
const openTime = dayHours?.open_time || '00:00';
|
|
106
|
-
const closeTime = (dayHours?.close_time && dayHours.close_time !== '00:00') ? dayHours.close_time : '23:59';
|
|
107
|
-
|
|
108
|
-
// Collect all period matches with their time constraints
|
|
109
|
-
const matches: { period: PricingPeriod; startTime: string | null; endTime: string | null }[] = [];
|
|
110
|
-
|
|
111
|
-
for (const period of periods) {
|
|
112
|
-
if (!period.is_active) continue;
|
|
113
|
-
|
|
114
|
-
for (const rule of period.rules) {
|
|
115
|
-
if (rule.type === 'range') {
|
|
116
|
-
if (rule.start_date && rule.end_date && dateStr >= rule.start_date && dateStr <= rule.end_date) {
|
|
117
|
-
matches.push({ period, startTime: null, endTime: null }); // all-day
|
|
118
|
-
}
|
|
119
|
-
} else if (rule.type === 'date_time') {
|
|
120
|
-
const matchingDays = (rule.days || []).map(d => DAY_MAP[d]);
|
|
121
|
-
if (matchingDays.includes(dayOfWeek)) {
|
|
122
|
-
if (rule.is_24_hours) {
|
|
123
|
-
matches.push({ period, startTime: null, endTime: null });
|
|
124
|
-
} else if (rule.start_time && rule.end_time) {
|
|
125
|
-
matches.push({ period, startTime: rule.start_time, endTime: rule.end_time });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (matches.length === 0) {
|
|
133
|
-
return [{ period: null, startTime: null, endTime: null }];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Check if any match has time constraints
|
|
137
|
-
const hasTimeConstraints = matches.some(m => m.startTime !== null);
|
|
138
|
-
|
|
139
|
-
if (!hasTimeConstraints) {
|
|
140
|
-
// All matches are all-day — return the highest priority (first in sorted list)
|
|
141
|
-
return [{ period: matches[0].period, startTime: null, endTime: null }];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// We have time-based periods. Build time segments bounded by opening hours.
|
|
145
|
-
const timedMatches = matches.filter(m => m.startTime !== null);
|
|
146
|
-
const allDayMatches = matches.filter(m => m.startTime === null);
|
|
147
|
-
|
|
148
|
-
// The fallback period is the highest-priority all-day match, or null
|
|
149
|
-
const fallbackPeriod = allDayMatches.length > 0 ? allDayMatches[0].period : null;
|
|
150
|
-
|
|
151
|
-
// Sort timed matches by priority (lower number = higher priority)
|
|
152
|
-
// Then resolve overlaps: higher-priority periods carve out their time from lower ones
|
|
153
|
-
const resolvedTimedMatches: { period: PricingPeriod; startTime: string; endTime: string }[] = [];
|
|
154
|
-
|
|
155
|
-
// Sort by priority ascending (highest priority first)
|
|
156
|
-
const sortedTimed = [...timedMatches].sort((a, b) => a.period.priority - b.period.priority);
|
|
157
|
-
|
|
158
|
-
for (const match of sortedTimed) {
|
|
159
|
-
const mStart = match.startTime!;
|
|
160
|
-
const mEnd = match.endTime!;
|
|
161
|
-
|
|
162
|
-
// Split this match around any already-resolved higher-priority segments
|
|
163
|
-
let remainingSlots = [{ start: mStart, end: mEnd }];
|
|
164
|
-
|
|
165
|
-
for (const existing of resolvedTimedMatches) {
|
|
166
|
-
const newSlots: { start: string; end: string }[] = [];
|
|
167
|
-
for (const slot of remainingSlots) {
|
|
168
|
-
// No overlap
|
|
169
|
-
if (slot.end <= existing.startTime || slot.start >= existing.endTime) {
|
|
170
|
-
newSlots.push(slot);
|
|
171
|
-
} else {
|
|
172
|
-
// Overlap — carve out the existing segment
|
|
173
|
-
if (slot.start < existing.startTime) {
|
|
174
|
-
newSlots.push({ start: slot.start, end: existing.startTime });
|
|
175
|
-
}
|
|
176
|
-
if (slot.end > existing.endTime) {
|
|
177
|
-
newSlots.push({ start: existing.endTime, end: slot.end });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
remainingSlots = newSlots;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
for (const slot of remainingSlots) {
|
|
185
|
-
if (slot.start < slot.end) {
|
|
186
|
-
resolvedTimedMatches.push({ period: match.period, startTime: slot.start, endTime: slot.end });
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Sort resolved matches by start time
|
|
192
|
-
resolvedTimedMatches.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
|
193
|
-
|
|
194
|
-
// If no opening hours configured, don't fill gaps — just show timed periods directly
|
|
195
|
-
if (!hasOpeningHours) {
|
|
196
|
-
if (resolvedTimedMatches.length === 0) {
|
|
197
|
-
return [{ period: fallbackPeriod, startTime: null, endTime: null }];
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const segments: TimeSegment[] = resolvedTimedMatches.map(m => ({
|
|
201
|
-
period: m.period,
|
|
202
|
-
startTime: m.startTime,
|
|
203
|
-
endTime: m.endTime,
|
|
204
|
-
label: m.period.name,
|
|
205
|
-
}));
|
|
206
|
-
|
|
207
|
-
// If we also have an all-day fallback and timed matches, combine them
|
|
208
|
-
if (fallbackPeriod && segments.length > 0) {
|
|
209
|
-
// Return the highest-priority all-day match since we can't meaningfully split without hours
|
|
210
|
-
return [{ period: fallbackPeriod, startTime: null, endTime: null }];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return segments.length > 0 ? segments : [{ period: fallbackPeriod, startTime: null, endTime: null }];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Clamp timed matches to opening hours
|
|
217
|
-
const clampedMatches = resolvedTimedMatches
|
|
218
|
-
.map(m => ({
|
|
219
|
-
...m,
|
|
220
|
-
startTime: m.startTime < openTime ? openTime : m.startTime,
|
|
221
|
-
endTime: m.endTime > closeTime ? closeTime : m.endTime,
|
|
222
|
-
}))
|
|
223
|
-
.filter(m => m.startTime < m.endTime);
|
|
224
|
-
|
|
225
|
-
const segments: TimeSegment[] = [];
|
|
226
|
-
let cursor = openTime;
|
|
227
|
-
|
|
228
|
-
for (const match of clampedMatches) {
|
|
229
|
-
if (match.startTime > cursor) {
|
|
230
|
-
segments.push({
|
|
231
|
-
period: fallbackPeriod,
|
|
232
|
-
startTime: cursor,
|
|
233
|
-
endTime: match.startTime,
|
|
234
|
-
label: fallbackPeriod ? fallbackPeriod.name : 'Standard',
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
segments.push({
|
|
239
|
-
period: match.period,
|
|
240
|
-
startTime: match.startTime,
|
|
241
|
-
endTime: match.endTime,
|
|
242
|
-
label: match.period.name,
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
cursor = match.endTime;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (cursor < closeTime) {
|
|
249
|
-
segments.push({
|
|
250
|
-
period: fallbackPeriod,
|
|
251
|
-
startTime: cursor,
|
|
252
|
-
endTime: closeTime,
|
|
253
|
-
label: fallbackPeriod ? fallbackPeriod.name : 'Standard',
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Merge adjacent segments with the same effective period
|
|
258
|
-
const merged: TimeSegment[] = [];
|
|
259
|
-
for (const seg of segments) {
|
|
260
|
-
const prev = merged[merged.length - 1];
|
|
261
|
-
const segPeriodId = seg.period?.id ?? '_standard';
|
|
262
|
-
const prevPeriodId = prev?.period?.id ?? '_standard';
|
|
263
|
-
if (prev && segPeriodId === prevPeriodId) {
|
|
264
|
-
prev.endTime = seg.endTime;
|
|
265
|
-
} else {
|
|
266
|
-
merged.push({ ...seg });
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// If all segments merged into one covering the full opening window, simplify
|
|
271
|
-
if (merged.length === 1) {
|
|
272
|
-
return [{ period: merged[0].period, startTime: null, endTime: null }];
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return merged.length > 0 ? merged : [{ period: null, startTime: null, endTime: null }];
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Legacy helper — returns the single highest-priority matching period for a date.
|
|
280
|
-
*/
|
|
281
|
-
const getMatchingPeriod = (date: Date, periods: PricingPeriod[]): PricingPeriod | null => {
|
|
282
|
-
const segments = getTimeSegments(date, periods);
|
|
283
|
-
if (segments.length === 1) return segments[0].period;
|
|
284
|
-
// If multiple segments, return the first timed period (for backward compat)
|
|
285
|
-
const timed = segments.find(s => s.period !== null && s.startTime !== null);
|
|
286
|
-
return timed?.period || segments[0].period;
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get the price for an item under a specific period.
|
|
291
|
-
* Returns { price, isPeriodPrice } to distinguish period vs standard fallback.
|
|
292
|
-
*/
|
|
293
|
-
const getPriceForPeriod = (item: ProductItem, period: PricingPeriod | null): { price: number | null; isPeriodPrice: boolean } => {
|
|
294
|
-
if (!item.period_pricing || item.period_pricing.length === 0) return { price: null, isPeriodPrice: false };
|
|
295
|
-
|
|
296
|
-
const standard = item.period_pricing.find(pp => pp.id === 'standard' || pp.name === 'Standard' || pp.name === 'standard');
|
|
297
|
-
const standardPrice = (standard && standard.price > 0) ? standard.price : null;
|
|
298
|
-
|
|
299
|
-
if (period) {
|
|
300
|
-
const match = item.period_pricing.find(pp => pp.id === period.id || pp.name === period.name);
|
|
301
|
-
if (match && match.price != null && match.price > 0) {
|
|
302
|
-
return { price: match.price, isPeriodPrice: true };
|
|
303
|
-
}
|
|
304
|
-
return { price: standardPrice, isPeriodPrice: false };
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return { price: standardPrice, isPeriodPrice: false };
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Apply a dynamic tier adjustment to a base price.
|
|
312
|
-
*/
|
|
313
|
-
const applyTierAdjustment = (basePrice: number | null, item: ProductItem, tierId: string | null): number | null => {
|
|
314
|
-
if (basePrice === null || !tierId) return basePrice;
|
|
315
|
-
const tierPricing = item.dynamic_tier_pricing;
|
|
316
|
-
if (!tierPricing || !Array.isArray(tierPricing)) return basePrice;
|
|
317
|
-
const tier = tierPricing.find(t => t.tier_id === tierId);
|
|
318
|
-
if (!tier) return basePrice;
|
|
319
|
-
const adjusted = basePrice + tier.adjustment;
|
|
320
|
-
return Math.max(0, adjusted); // Don't go below 0
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
const PricingCalendar: React.FC = () => {
|
|
324
|
-
const { provider } = useProvider();
|
|
325
|
-
const [searchParams] = useSearchParams();
|
|
326
|
-
const highlightProductId = searchParams.get('product');
|
|
327
|
-
|
|
328
|
-
const dateInputRef = useRef<HTMLInputElement>(null);
|
|
329
|
-
const [currentDate, setCurrentDate] = useState(new Date());
|
|
330
|
-
const [periods, setPeriods] = useState<PricingPeriod[]>([]);
|
|
331
|
-
const [groups, setGroups] = useState<ProductGroup[]>([]);
|
|
332
|
-
const [openingHours, setOpeningHours] = useState<OpeningHour[]>([]);
|
|
333
|
-
const [dynamicTiers, setDynamicTiers] = useState<DynamicTier[]>([]);
|
|
334
|
-
const [selectedTier, setSelectedTier] = useState<string>('base');
|
|
335
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
336
|
-
|
|
337
|
-
const variablePricingEnabled = provider?.variable_pricing_enabled ?? false;
|
|
338
|
-
const dynamicPricingEnabled = provider?.dynamic_pricing_enabled ?? false;
|
|
339
|
-
|
|
340
|
-
// Fetch pricing periods and products
|
|
341
|
-
useEffect(() => {
|
|
342
|
-
if (!provider?.id) return;
|
|
343
|
-
|
|
344
|
-
const fetchData = async () => {
|
|
345
|
-
setIsLoading(true);
|
|
346
|
-
|
|
347
|
-
const [periodsRes, productsRes, itemsRes, groupsRes, hoursRes] = await Promise.all([
|
|
348
|
-
supabase.from('pricing_periods').select('*').eq('provider_id', provider.id).eq('status', 'active').order('priority', { ascending: true }),
|
|
349
|
-
supabase.from('products').select('*').eq('provider_id', provider.id).eq('status', 'active').order('display_order'),
|
|
350
|
-
supabase.from('product_items').select('*').eq('provider_id', provider.id).eq('status', 'active').order('display_order'),
|
|
351
|
-
supabase.from('product_groups').select('*').eq('provider_id', provider.id).eq('status', 'active').order('display_order'),
|
|
352
|
-
supabase.from('provider_opening_hours').select('*').eq('provider_id', provider.id).order('day_of_week'),
|
|
353
|
-
]);
|
|
354
|
-
|
|
355
|
-
// Fetch dynamic pricing tiers separately (uses 'as any' cast)
|
|
356
|
-
let tiersRes: any = null;
|
|
357
|
-
if (dynamicPricingEnabled) {
|
|
358
|
-
tiersRes = await supabase.from('dynamic_pricing_tiers' as any).select('id, name, display_order').eq('provider_id', provider.id).eq('status', 'active').eq('is_active', true).order('display_order', { ascending: true });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (periodsRes.data) {
|
|
362
|
-
setPeriods(periodsRes.data.map((p: any) => ({
|
|
363
|
-
...p,
|
|
364
|
-
rules: (Array.isArray(p.rules) ? p.rules : []) as unknown as PricingPeriodRule[],
|
|
365
|
-
})));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (groupsRes.data && productsRes.data && itemsRes.data) {
|
|
369
|
-
const productItems = itemsRes.data.map((item: any) => ({
|
|
370
|
-
...item,
|
|
371
|
-
period_pricing: (Array.isArray(item.period_pricing) ? item.period_pricing : []) as unknown as PeriodPricing[],
|
|
372
|
-
availability_rules: parseAvailabilityRules(item.availability_rules),
|
|
373
|
-
dynamic_tier_pricing: (Array.isArray(item.dynamic_tier_pricing) ? item.dynamic_tier_pricing : []) as DynamicTierAdjustment[],
|
|
374
|
-
}));
|
|
375
|
-
|
|
376
|
-
const products = productsRes.data.map((prod: any) => ({
|
|
377
|
-
id: prod.id,
|
|
378
|
-
name: prod.name,
|
|
379
|
-
type: prod.type,
|
|
380
|
-
product_group_id: prod.product_group_id,
|
|
381
|
-
items: productItems.filter((item: any) => item.product_id === prod.id),
|
|
382
|
-
}));
|
|
383
|
-
|
|
384
|
-
const groupedData: ProductGroup[] = groupsRes.data.map((g: any) => ({
|
|
385
|
-
id: g.id,
|
|
386
|
-
name: g.name,
|
|
387
|
-
products: products.filter((p: any) => p.product_group_id === g.id),
|
|
388
|
-
}));
|
|
389
|
-
|
|
390
|
-
// Add ungrouped products
|
|
391
|
-
const ungroupedProducts = products.filter((p: any) => !p.product_group_id);
|
|
392
|
-
if (ungroupedProducts.length > 0) {
|
|
393
|
-
groupedData.push({ id: '_ungrouped', name: 'Ungrouped', products: ungroupedProducts });
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
setGroups(groupedData);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (hoursRes.data) {
|
|
400
|
-
setOpeningHours(hoursRes.data as OpeningHour[]);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (dynamicPricingEnabled && tiersRes?.data) {
|
|
404
|
-
setDynamicTiers(tiersRes.data as DynamicTier[]);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
setIsLoading(false);
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
fetchData();
|
|
411
|
-
}, [provider?.id, dynamicPricingEnabled]);
|
|
412
|
-
|
|
413
|
-
const days = useMemo(() => {
|
|
414
|
-
const start = startOfWeek(currentDate, { weekStartsOn: 1 });
|
|
415
|
-
return eachDayOfInterval({ start, end: endOfWeek(currentDate, { weekStartsOn: 1 }) });
|
|
416
|
-
}, [currentDate]);
|
|
417
|
-
|
|
418
|
-
const navigateBack = () => {
|
|
419
|
-
setCurrentDate(prev => subWeeks(prev, 1));
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
const navigateForward = () => {
|
|
423
|
-
setCurrentDate(prev => addWeeks(prev, 1));
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
const goToToday = () => setCurrentDate(new Date());
|
|
427
|
-
|
|
428
|
-
// Build pricing quick filters (tiers only — base is the default when none selected)
|
|
429
|
-
const pricingFilters = useMemo(() => {
|
|
430
|
-
if (!dynamicPricingEnabled || dynamicTiers.length === 0) return [];
|
|
431
|
-
return dynamicTiers.map(t => ({ value: t.id, label: t.name }));
|
|
432
|
-
}, [dynamicPricingEnabled, dynamicTiers]);
|
|
433
|
-
|
|
434
|
-
const activeTierId = selectedTier === 'base' ? null : selectedTier;
|
|
435
|
-
|
|
436
|
-
if (isLoading) {
|
|
437
|
-
return <p className="text-label-secondary text-sm py-8 text-center">Loading pricing calendar...</p>;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const hasProducts = groups.some(g => g.products.some(p => p.items.length > 0));
|
|
441
|
-
if (!hasProducts) {
|
|
442
|
-
return (
|
|
443
|
-
<p className="text-label-tertiary text-sm py-8 text-left">
|
|
444
|
-
No booking types with product items found. Add products with pricing to see the calendar view.
|
|
445
|
-
</p>
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const headerTitle = `${format(days[0], 'd MMM')} – ${format(days[days.length - 1], 'd MMM yyyy')}`;
|
|
450
|
-
|
|
451
|
-
return (
|
|
452
|
-
<div className="flex flex-col gap-4 w-full min-w-0 overflow-hidden">
|
|
453
|
-
{/* Controls */}
|
|
454
|
-
<div className="flex items-center justify-between">
|
|
455
|
-
<div className="flex items-center gap-2">
|
|
456
|
-
<div className="flex items-center gap-1">
|
|
457
|
-
<button
|
|
458
|
-
onClick={navigateBack}
|
|
459
|
-
className="w-8 h-8 rounded-full bg-surface-action-soft hover:bg-surface-action-soft-hover flex items-center justify-center transition-colors"
|
|
460
|
-
>
|
|
461
|
-
<IconArrowLeft className="w-4 h-4 text-fill-action" size={16} />
|
|
462
|
-
</button>
|
|
463
|
-
<button
|
|
464
|
-
onClick={navigateForward}
|
|
465
|
-
className="w-8 h-8 rounded-full bg-surface-action-soft hover:bg-surface-action-soft-hover flex items-center justify-center transition-colors"
|
|
466
|
-
>
|
|
467
|
-
<IconArrowRight className="w-4 h-4 text-fill-action" size={16} />
|
|
468
|
-
</button>
|
|
469
|
-
</div>
|
|
470
|
-
<div className="flex flex-col h-10 justify-center">
|
|
471
|
-
<div className="relative">
|
|
472
|
-
<input
|
|
473
|
-
ref={dateInputRef}
|
|
474
|
-
type="date"
|
|
475
|
-
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
476
|
-
value={format(currentDate, 'yyyy-MM-dd')}
|
|
477
|
-
onChange={(e) => {
|
|
478
|
-
if (e.target.value) {
|
|
479
|
-
setCurrentDate(new Date(e.target.value + 'T00:00:00'));
|
|
480
|
-
}
|
|
481
|
-
}}
|
|
482
|
-
/>
|
|
483
|
-
<span className="text-label-primary font-semibold text-sm hover:text-label-action transition-colors cursor-pointer text-left">
|
|
484
|
-
{headerTitle}
|
|
485
|
-
</span>
|
|
486
|
-
</div>
|
|
487
|
-
{!isWithinInterval(new Date(), { start: days[0], end: days[days.length - 1] }) && (
|
|
488
|
-
<button onClick={goToToday} className="text-label-action text-xs font-medium hover:underline transition-colors text-left leading-tight">
|
|
489
|
-
Back to this week
|
|
490
|
-
</button>
|
|
491
|
-
)}
|
|
492
|
-
</div>
|
|
493
|
-
</div>
|
|
494
|
-
</div>
|
|
495
|
-
|
|
496
|
-
{/* Period Legend */}
|
|
497
|
-
{variablePricingEnabled && periods.length > 0 && (
|
|
498
|
-
<div className="flex items-center gap-3 flex-wrap">
|
|
499
|
-
<span className="text-label-secondary text-xs">Periods:</span>
|
|
500
|
-
{periods.filter(p => p.is_active).map(period => (
|
|
501
|
-
<div key={period.id} className="flex items-center gap-1.5">
|
|
502
|
-
<span className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: period.colour }} />
|
|
503
|
-
<span className="text-label-secondary text-xs">{period.name}</span>
|
|
504
|
-
</div>
|
|
505
|
-
))}
|
|
506
|
-
<div className="flex items-center gap-1.5">
|
|
507
|
-
<span className="w-2.5 h-2.5 rounded-full flex-shrink-0 bg-surface-secondary border border-border-primary" />
|
|
508
|
-
<span className="text-label-secondary text-xs">Standard</span>
|
|
509
|
-
</div>
|
|
510
|
-
</div>
|
|
511
|
-
)}
|
|
512
|
-
|
|
513
|
-
{/* Dynamic Pricing Tier Quick Filter */}
|
|
514
|
-
{pricingFilters.length > 0 && (
|
|
515
|
-
<QuickFilters
|
|
516
|
-
label="Dynamic Prices:"
|
|
517
|
-
filters={pricingFilters}
|
|
518
|
-
activeFilter={selectedTier}
|
|
519
|
-
onFilterChange={setSelectedTier}
|
|
520
|
-
allFilterValue="base"
|
|
521
|
-
/>
|
|
522
|
-
)}
|
|
523
|
-
|
|
524
|
-
{/* Calendar Grid */}
|
|
525
|
-
<div className="border border-border-primary rounded-lg overflow-x-auto bg-surface-primary">
|
|
526
|
-
<div className="min-w-[700px]">
|
|
527
|
-
{/* Day Headers */}
|
|
528
|
-
<div className="grid border-b border-border-primary" style={{ gridTemplateColumns: `180px repeat(7, 1fr)` }}>
|
|
529
|
-
<div className="p-2 text-label-secondary text-xs font-medium border-r border-border-primary bg-surface-secondary">
|
|
530
|
-
Activity
|
|
531
|
-
</div>
|
|
532
|
-
{days.map((day, i) => (
|
|
533
|
-
<div
|
|
534
|
-
key={i}
|
|
535
|
-
className={`p-2 text-center text-xs font-medium border-r border-border-primary last:border-r-0 bg-surface-secondary ${isToday(day) ? 'text-brand-primary' : 'text-label-secondary'}`}
|
|
536
|
-
>
|
|
537
|
-
<div className="flex flex-col">
|
|
538
|
-
<span>{format(day, 'EEE')}</span>
|
|
539
|
-
<span className={`text-sm font-semibold ${isToday(day) ? 'text-brand-primary' : 'text-label-primary'}`}>{format(day, 'd')}</span>
|
|
540
|
-
</div>
|
|
541
|
-
</div>
|
|
542
|
-
))}
|
|
543
|
-
</div>
|
|
544
|
-
|
|
545
|
-
{/* Data Rows */}
|
|
546
|
-
<WeekView
|
|
547
|
-
groups={groups}
|
|
548
|
-
days={days}
|
|
549
|
-
periods={variablePricingEnabled ? periods : []}
|
|
550
|
-
highlightProductId={highlightProductId}
|
|
551
|
-
openingHours={openingHours}
|
|
552
|
-
activeTierId={activeTierId}
|
|
553
|
-
/>
|
|
554
|
-
</div>
|
|
555
|
-
</div>
|
|
556
|
-
</div>
|
|
557
|
-
);
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
// === Week View ===
|
|
561
|
-
const WeekView: React.FC<{
|
|
562
|
-
groups: ProductGroup[];
|
|
563
|
-
days: Date[];
|
|
564
|
-
periods: PricingPeriod[];
|
|
565
|
-
highlightProductId: string | null;
|
|
566
|
-
openingHours: OpeningHour[];
|
|
567
|
-
activeTierId: string | null;
|
|
568
|
-
}> = ({ groups, days, periods, highlightProductId, openingHours, activeTierId }) => (
|
|
569
|
-
<>
|
|
570
|
-
{groups.map((group, groupIdx) => (
|
|
571
|
-
<React.Fragment key={group.id}>
|
|
572
|
-
{/* Gap between groups */}
|
|
573
|
-
{groupIdx > 0 && <div className="h-2 bg-surface-secondary border-b border-border-primary" />}
|
|
574
|
-
|
|
575
|
-
{/* Product Group Header */}
|
|
576
|
-
<div
|
|
577
|
-
className="grid border-b border-border-primary"
|
|
578
|
-
style={{ gridTemplateColumns: `180px repeat(7, 1fr)` }}
|
|
579
|
-
>
|
|
580
|
-
<div className="px-4 py-2 flex items-center border-r border-border-primary bg-surface-tertiary">
|
|
581
|
-
<span className="text-label-primary text-xs font-semibold truncate">{group.name}</span>
|
|
582
|
-
</div>
|
|
583
|
-
<div className="col-span-7 bg-surface-tertiary" />
|
|
584
|
-
</div>
|
|
585
|
-
|
|
586
|
-
{/* Products (Activities) within group */}
|
|
587
|
-
{group.products.map(product => (
|
|
588
|
-
<React.Fragment key={product.id}>
|
|
589
|
-
{/* Activity Section Header */}
|
|
590
|
-
<div
|
|
591
|
-
className="grid border-b border-border-primary bg-surface-secondary"
|
|
592
|
-
style={{ gridTemplateColumns: `180px repeat(7, 1fr)` }}
|
|
593
|
-
>
|
|
594
|
-
<div className="px-4 py-1.5 border-r border-border-primary flex items-center">
|
|
595
|
-
<span className="text-label-secondary text-[11px] font-medium truncate">{product.name}</span>
|
|
596
|
-
</div>
|
|
597
|
-
<div className="col-span-7" />
|
|
598
|
-
</div>
|
|
599
|
-
|
|
600
|
-
{/* Product Items */}
|
|
601
|
-
{product.items.map(item => (
|
|
602
|
-
<div
|
|
603
|
-
key={item.id}
|
|
604
|
-
className={`grid border-b border-border-primary last:border-b-0 ${highlightProductId === product.id ? 'bg-brand-primary/5' : ''}`}
|
|
605
|
-
style={{ gridTemplateColumns: `180px repeat(7, 1fr)` }}
|
|
606
|
-
>
|
|
607
|
-
<div className="px-4 py-2 border-r border-border-primary flex items-center h-16">
|
|
608
|
-
<span className="text-label-primary text-xs line-clamp-2 block">{item.title}</span>
|
|
609
|
-
</div>
|
|
610
|
-
{days.map((day, i) => {
|
|
611
|
-
const available = isAvailableOnDate(item.availability_rules, day);
|
|
612
|
-
|
|
613
|
-
// If not available, render a greyed-out cell
|
|
614
|
-
if (!available) {
|
|
615
|
-
return (
|
|
616
|
-
<UnavailableCell key={i} isLast={i === days.length - 1} />
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const segments = getTimeSegments(day, periods, openingHours);
|
|
621
|
-
|
|
622
|
-
// Check if multiple segments actually produce different prices for this item
|
|
623
|
-
if (segments.length > 1) {
|
|
624
|
-
const prices = segments.map(seg => {
|
|
625
|
-
const result = getPriceForPeriod(item, seg.period);
|
|
626
|
-
return {
|
|
627
|
-
...result,
|
|
628
|
-
price: applyTierAdjustment(result.price, item, activeTierId),
|
|
629
|
-
};
|
|
630
|
-
});
|
|
631
|
-
const allSamePrice = prices.every(p => p.price === prices[0].price && p.isPeriodPrice === prices[0].isPeriodPrice);
|
|
632
|
-
|
|
633
|
-
if (!allSamePrice) {
|
|
634
|
-
return (
|
|
635
|
-
<MultiSegmentCell
|
|
636
|
-
key={i}
|
|
637
|
-
item={item}
|
|
638
|
-
segments={segments}
|
|
639
|
-
isLast={i === days.length - 1}
|
|
640
|
-
activeTierId={activeTierId}
|
|
641
|
-
/>
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Single segment or all segments produce the same price — render single cell
|
|
647
|
-
const period = segments[0].period;
|
|
648
|
-
const result = getPriceForPeriod(item, period);
|
|
649
|
-
const adjustedPrice = applyTierAdjustment(result.price, item, activeTierId);
|
|
650
|
-
return (
|
|
651
|
-
<PriceCell
|
|
652
|
-
key={i}
|
|
653
|
-
price={adjustedPrice}
|
|
654
|
-
period={result.isPeriodPrice ? period : null}
|
|
655
|
-
isLast={i === days.length - 1}
|
|
656
|
-
isAdjusted={activeTierId !== null && adjustedPrice !== result.price}
|
|
657
|
-
/>
|
|
658
|
-
);
|
|
659
|
-
})}
|
|
660
|
-
</div>
|
|
661
|
-
))}
|
|
662
|
-
</React.Fragment>
|
|
663
|
-
))}
|
|
664
|
-
</React.Fragment>
|
|
665
|
-
))}
|
|
666
|
-
</>
|
|
667
|
-
);
|
|
668
|
-
|
|
669
|
-
// === Multi-Segment Cell (time-split day) ===
|
|
670
|
-
const MultiSegmentCell: React.FC<{
|
|
671
|
-
item: ProductItem;
|
|
672
|
-
segments: TimeSegment[];
|
|
673
|
-
isLast: boolean;
|
|
674
|
-
activeTierId: string | null;
|
|
675
|
-
}> = ({ item, segments, isLast, activeTierId }) => {
|
|
676
|
-
return (
|
|
677
|
-
<div className={`border-r border-border-primary flex flex-col h-16 ${isLast ? 'border-r-0' : ''}`}>
|
|
678
|
-
{segments.map((segment, idx) => {
|
|
679
|
-
const result = getPriceForPeriod(item, segment.period);
|
|
680
|
-
const basePrice = result.price;
|
|
681
|
-
const adjustedPrice = applyTierAdjustment(basePrice, item, activeTierId);
|
|
682
|
-
const colour = segment.period?.colour;
|
|
683
|
-
const hasPeriodColour = colour && result.isPeriodPrice;
|
|
684
|
-
const bgStyle = hasPeriodColour
|
|
685
|
-
? { backgroundColor: `${colour}15`, '--hover-bg': `${colour}25` } as React.CSSProperties
|
|
686
|
-
: {};
|
|
687
|
-
|
|
688
|
-
const timeLabel = segment.startTime && segment.endTime
|
|
689
|
-
? `${segment.startTime}–${segment.endTime}`
|
|
690
|
-
: segment.startTime
|
|
691
|
-
? `${segment.startTime}+`
|
|
692
|
-
: segment.endTime
|
|
693
|
-
? `Until ${segment.endTime}`
|
|
694
|
-
: '';
|
|
695
|
-
|
|
696
|
-
const isAdjusted = activeTierId !== null && adjustedPrice !== basePrice;
|
|
697
|
-
|
|
698
|
-
return (
|
|
699
|
-
<Tooltip key={idx}>
|
|
700
|
-
<TooltipTrigger asChild>
|
|
701
|
-
<div
|
|
702
|
-
className={`px-4 py-1 flex flex-col items-center justify-center flex-1 min-h-[32px] cursor-default transition-colors ${idx < segments.length - 1 ? 'border-b border-border-secondary' : ''} ${hasPeriodColour ? '' : 'hover:bg-surface-primary-hover'}`}
|
|
703
|
-
style={bgStyle}
|
|
704
|
-
onMouseEnter={(e) => {
|
|
705
|
-
if (hasPeriodColour) e.currentTarget.style.backgroundColor = `${colour}25`;
|
|
706
|
-
}}
|
|
707
|
-
onMouseLeave={(e) => {
|
|
708
|
-
if (hasPeriodColour) e.currentTarget.style.backgroundColor = `${colour}15`;
|
|
709
|
-
}}
|
|
710
|
-
>
|
|
711
|
-
{adjustedPrice !== null ? (
|
|
712
|
-
<span className="text-[10px] font-medium leading-tight text-label-primary">
|
|
713
|
-
£{adjustedPrice.toFixed(2)}
|
|
714
|
-
</span>
|
|
715
|
-
) : (
|
|
716
|
-
<span className="text-label-quaternary text-[9px]">—</span>
|
|
717
|
-
)}
|
|
718
|
-
</div>
|
|
719
|
-
</TooltipTrigger>
|
|
720
|
-
<TooltipContent>
|
|
721
|
-
{segment.period && (
|
|
722
|
-
<div className="flex items-center gap-1.5">
|
|
723
|
-
{colour && <span className="w-2 h-2 rounded-full flex-shrink-0" style={{ backgroundColor: colour }} />}
|
|
724
|
-
<span className="text-xs">{segment.period.name}</span>
|
|
725
|
-
</div>
|
|
726
|
-
)}
|
|
727
|
-
{timeLabel && <p className="text-xs text-label-secondary">{timeLabel}</p>}
|
|
728
|
-
{isAdjusted && basePrice !== null && (
|
|
729
|
-
<p className="text-xs text-label-secondary line-through">£{basePrice.toFixed(2)}</p>
|
|
730
|
-
)}
|
|
731
|
-
{adjustedPrice !== null && <p className="text-xs font-semibold">£{adjustedPrice.toFixed(2)}</p>}
|
|
732
|
-
</TooltipContent>
|
|
733
|
-
</Tooltip>
|
|
734
|
-
);
|
|
735
|
-
})}
|
|
736
|
-
</div>
|
|
737
|
-
);
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
// === Unavailable Cell (greyed out) ===
|
|
742
|
-
const UnavailableCell: React.FC<{ isLast: boolean }> = ({ isLast }) => (
|
|
743
|
-
<div
|
|
744
|
-
className={`p-1.5 border-r border-border-primary flex flex-col items-center justify-center h-16 bg-surface-secondary/50 ${isLast ? 'border-r-0' : ''}`}
|
|
745
|
-
>
|
|
746
|
-
<span className="text-label-quaternary text-[10px]">—</span>
|
|
747
|
-
</div>
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
// === Price Cell ===
|
|
751
|
-
const PriceCell: React.FC<{
|
|
752
|
-
price: number | null;
|
|
753
|
-
period: PricingPeriod | null;
|
|
754
|
-
isLast: boolean;
|
|
755
|
-
outsideMonth?: boolean;
|
|
756
|
-
dayNumber?: string;
|
|
757
|
-
showDayNumber?: boolean;
|
|
758
|
-
isAdjusted?: boolean;
|
|
759
|
-
}> = ({ price, period, isLast, outsideMonth, dayNumber, showDayNumber, isAdjusted }) => {
|
|
760
|
-
const colour = period?.colour;
|
|
761
|
-
const bgStyle = period
|
|
762
|
-
? { backgroundColor: `${colour}15` }
|
|
763
|
-
: {};
|
|
764
|
-
|
|
765
|
-
const content = (
|
|
766
|
-
<div
|
|
767
|
-
className={`p-1.5 border-r border-border-primary flex flex-col items-center justify-center h-16 transition-colors cursor-default ${isLast ? 'border-r-0' : ''} ${outsideMonth ? 'opacity-40' : ''} ${period ? '' : 'hover:bg-surface-primary-hover'}`}
|
|
768
|
-
style={bgStyle}
|
|
769
|
-
onMouseEnter={(e) => {
|
|
770
|
-
if (period && colour) e.currentTarget.style.backgroundColor = `${colour}25`;
|
|
771
|
-
}}
|
|
772
|
-
onMouseLeave={(e) => {
|
|
773
|
-
if (period && colour) e.currentTarget.style.backgroundColor = `${colour}15`;
|
|
774
|
-
}}
|
|
775
|
-
>
|
|
776
|
-
{showDayNumber && <span className="text-[9px] text-label-tertiary">{dayNumber}</span>}
|
|
777
|
-
{price !== null ? (
|
|
778
|
-
<>
|
|
779
|
-
<span className="text-[11px] font-medium text-label-primary">£{price.toFixed(2)}</span>
|
|
780
|
-
{period && (
|
|
781
|
-
<span className="text-[8px] font-medium truncate max-w-full" style={{ color: period.colour }}>
|
|
782
|
-
{period.name}
|
|
783
|
-
</span>
|
|
784
|
-
)}
|
|
785
|
-
</>
|
|
786
|
-
) : (
|
|
787
|
-
<span className="text-label-quaternary text-[10px]">—</span>
|
|
788
|
-
)}
|
|
789
|
-
</div>
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
if (period) {
|
|
793
|
-
return (
|
|
794
|
-
<Tooltip>
|
|
795
|
-
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
|
796
|
-
<TooltipContent>
|
|
797
|
-
<div className="flex items-center gap-1.5">
|
|
798
|
-
{colour && <span className="w-2 h-2 rounded-full flex-shrink-0" style={{ backgroundColor: colour }} />}
|
|
799
|
-
<span className="text-xs">{period.name}</span>
|
|
800
|
-
</div>
|
|
801
|
-
{price !== null && <p className="text-xs font-semibold">£{price.toFixed(2)}</p>}
|
|
802
|
-
</TooltipContent>
|
|
803
|
-
</Tooltip>
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return content;
|
|
808
|
-
};
|
|
809
|
-
|
|
810
|
-
export default PricingCalendar;
|