@licklist/design 0.78.5-dev.107 → 0.78.5-dev.109
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/bitbucket-pipelines.yml +4 -13
- package/dist/Maintenance/Maintenance.scss.js +1 -1
- package/dist/index.js +2 -0
- package/dist/product-set/form/ProductsControl.d.ts +1 -2
- package/dist/product-set/form/ProductsControl.d.ts.map +1 -1
- package/dist/product-set/form/ProductsControl.js +24 -0
- 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 +2 -86
- 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 +11 -13
- 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/PeriodCard/PeriodCard.d.ts +66 -0
- package/dist/v2/components/PeriodCard/PeriodCard.d.ts.map +1 -0
- package/dist/v2/components/PeriodCard/PeriodCard.js +351 -0
- package/dist/v2/components/PeriodCard/PeriodCard.scss.js +6 -0
- package/dist/v2/components/PeriodCard/index.d.ts +3 -0
- package/dist/v2/components/PeriodCard/index.d.ts.map +1 -0
- package/dist/v2/components/ReorderRow/ReorderRow.d.ts +24 -0
- package/dist/v2/components/ReorderRow/ReorderRow.d.ts.map +1 -0
- package/dist/v2/components/ReorderRow/ReorderRow.js +109 -0
- package/dist/v2/components/ReorderRow/ReorderRow.scss.js +6 -0
- package/dist/v2/components/ReorderRow/index.d.ts +3 -0
- package/dist/v2/components/ReorderRow/index.d.ts.map +1 -0
- 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 +5 -8
- 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/components/index.d.ts +4 -0
- package/dist/v2/components/index.d.ts.map +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 +16 -1
- package/dist/v2/index.d.ts +8 -0
- 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 +5 -0
- package/dist/v2/pages/Settings/components/SidebarCustomisation.scss.js +1 -1
- package/dist/v2/pages/Settings/components/SidebarNavItem.js +5 -0
- package/dist/v2/pages/auth/AuthLayout/AuthLayout.scss.js +1 -1
- package/dist/v2/shadcn/components/ui/accordion.d.ts +8 -0
- package/dist/v2/shadcn/components/ui/accordion.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/alert-dialog.d.ts +21 -0
- package/dist/v2/shadcn/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/alert.d.ts +9 -0
- package/dist/v2/shadcn/components/ui/alert.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts +4 -0
- package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/avatar.d.ts +7 -0
- package/dist/v2/shadcn/components/ui/avatar.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/badge.d.ts +10 -0
- package/dist/v2/shadcn/components/ui/badge.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/breadcrumb.d.ts +20 -0
- package/dist/v2/shadcn/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/button.d.ts +14 -0
- package/dist/v2/shadcn/components/ui/button.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/calendar.d.ts +9 -0
- package/dist/v2/shadcn/components/ui/calendar.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/card.d.ts +9 -0
- package/dist/v2/shadcn/components/ui/card.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/carousel.d.ts +19 -0
- package/dist/v2/shadcn/components/ui/carousel.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/checkbox.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/checkbox.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/checkbox.js +115 -0
- package/dist/v2/shadcn/components/ui/checkbox.scss.js +6 -0
- package/dist/v2/shadcn/components/ui/collapsible.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/collapsible.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/command.d.ts +83 -0
- package/dist/v2/shadcn/components/ui/command.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/context-menu.d.ts +28 -0
- package/dist/v2/shadcn/components/ui/context-menu.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/dialog.d.ts +20 -0
- package/dist/v2/shadcn/components/ui/dialog.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/dialog.js +169 -0
- package/dist/v2/shadcn/components/ui/drawer.d.ts +23 -0
- package/dist/v2/shadcn/components/ui/drawer.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts +28 -0
- package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/form.d.ts +24 -0
- package/dist/v2/shadcn/components/ui/form.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/hover-card.d.ts +7 -0
- package/dist/v2/shadcn/components/ui/hover-card.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/input-otp.d.ts +35 -0
- package/dist/v2/shadcn/components/ui/input-otp.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/input.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/input.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/label.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/label.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/menubar.d.ts +34 -0
- package/dist/v2/shadcn/components/ui/menubar.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/navigation-menu.d.ts +13 -0
- package/dist/v2/shadcn/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/pagination.d.ts +29 -0
- package/dist/v2/shadcn/components/ui/pagination.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/popover.d.ts +7 -0
- package/dist/v2/shadcn/components/ui/popover.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/progress.d.ts +5 -0
- package/dist/v2/shadcn/components/ui/progress.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/radio-card.d.ts +12 -0
- package/dist/v2/shadcn/components/ui/radio-card.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/radio-group.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/radio-group.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/scroll-area.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/select.d.ts +14 -0
- package/dist/v2/shadcn/components/ui/select.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/separator.d.ts +5 -0
- package/dist/v2/shadcn/components/ui/separator.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/sheet.d.ts +26 -0
- package/dist/v2/shadcn/components/ui/sheet.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/sidebar.d.ts +67 -0
- package/dist/v2/shadcn/components/ui/sidebar.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/skeleton.d.ts +3 -0
- package/dist/v2/shadcn/components/ui/skeleton.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/slider.d.ts +5 -0
- package/dist/v2/shadcn/components/ui/slider.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/switch.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/switch.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/switch.js +115 -0
- package/dist/v2/shadcn/components/ui/switch.scss.js +6 -0
- package/dist/v2/shadcn/components/ui/table-pagination.d.ts +11 -0
- package/dist/v2/shadcn/components/ui/table-pagination.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/table.d.ts +11 -0
- package/dist/v2/shadcn/components/ui/table.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/tabs.d.ts +8 -0
- package/dist/v2/shadcn/components/ui/tabs.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/textarea.d.ts +6 -0
- package/dist/v2/shadcn/components/ui/textarea.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/toast.d.ts +16 -0
- package/dist/v2/shadcn/components/ui/toast.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/toaster.d.ts +2 -0
- package/dist/v2/shadcn/components/ui/toaster.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/toggle-group.d.ts +13 -0
- package/dist/v2/shadcn/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/toggle.d.ts +13 -0
- package/dist/v2/shadcn/components/ui/toggle.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/tooltip.d.ts +8 -0
- package/dist/v2/shadcn/components/ui/tooltip.d.ts.map +1 -0
- package/dist/v2/shadcn/components/ui/use-toast.d.ts +3 -0
- package/dist/v2/shadcn/components/ui/use-toast.d.ts.map +1 -0
- package/dist/v2/shadcn/hooks/use-mobile.d.ts +2 -0
- package/dist/v2/shadcn/hooks/use-mobile.d.ts.map +1 -0
- package/dist/v2/shadcn/hooks/use-toast.d.ts +45 -0
- package/dist/v2/shadcn/hooks/use-toast.d.ts.map +1 -0
- package/dist/v2/shadcn/index.d.ts +20 -0
- package/dist/v2/shadcn/index.d.ts.map +1 -0
- package/dist/v2/shadcn/lib/utils.d.ts +3 -0
- package/dist/v2/shadcn/lib/utils.d.ts.map +1 -0
- package/dist/v2/shadcn/lib/utils.js +11 -0
- package/dist/v2/shadcn/styles/globals.css +112 -0
- package/dist/v2/styles/form/NewInput.scss.js +1 -1
- package/package.json +6 -6
- package/rollup.config.js +2 -16
- package/src/iframe/payment/payment-status-page/PaymentStatusPage.tsx +1 -1
- package/src/product-set/form/ProductsControl.tsx +1 -2
- package/src/v2/components/DataTable/DataTable.tsx +1 -23
- package/src/v2/components/Modal/DeleteModal.tsx +20 -12
- package/src/v2/components/PeriodCard/PeriodCard.scss +157 -0
- package/src/v2/components/PeriodCard/PeriodCard.stories.tsx +245 -0
- package/src/v2/components/PeriodCard/PeriodCard.tsx +350 -0
- package/src/v2/components/PeriodCard/index.ts +8 -0
- package/src/v2/components/ReorderRow/ReorderRow.scss +68 -0
- package/src/v2/components/ReorderRow/ReorderRow.stories.tsx +124 -0
- package/src/v2/components/ReorderRow/ReorderRow.tsx +88 -0
- package/src/v2/components/ReorderRow/index.ts +2 -0
- package/src/v2/components/Toggle/Toggle.tsx +5 -6
- package/src/v2/components/index.ts +6 -0
- package/src/v2/index.ts +82 -0
- package/src/v2/shadcn/_reference/AccountManagerCard.tsx +45 -0
- package/src/v2/shadcn/_reference/AffiliatesTable.tsx +178 -0
- package/src/v2/shadcn/_reference/AuditArchive.tsx +165 -0
- package/src/v2/shadcn/_reference/AuditContent.tsx +270 -0
- package/src/v2/shadcn/_reference/AutomationsGeneralSettings.tsx +251 -0
- package/src/v2/shadcn/_reference/AvatarUpload.tsx +150 -0
- package/src/v2/shadcn/_reference/BookingsSummaryCard.tsx +268 -0
- package/src/v2/shadcn/_reference/CodeCleanUpAudit.tsx +274 -0
- package/src/v2/shadcn/_reference/CompaniesTable.tsx +387 -0
- package/src/v2/shadcn/_reference/ComponentAudit.tsx +239 -0
- package/src/v2/shadcn/_reference/ConfigureSettingsCard.tsx +95 -0
- package/src/v2/shadcn/_reference/CustomerCard.tsx +155 -0
- package/src/v2/shadcn/_reference/DashboardCards.tsx +50 -0
- package/src/v2/shadcn/_reference/DashboardFooter.tsx +18 -0
- package/src/v2/shadcn/_reference/DiarySettings.tsx +187 -0
- package/src/v2/shadcn/_reference/DiaryView.tsx +998 -0
- package/src/v2/shadcn/_reference/EmptyState.tsx +76 -0
- package/src/v2/shadcn/_reference/EntityInfoCard.tsx +48 -0
- package/src/v2/shadcn/_reference/ExistingUserAssignments.tsx +131 -0
- package/src/v2/shadcn/_reference/FeatureToggle.tsx +72 -0
- package/src/v2/shadcn/_reference/FlowCard.tsx +170 -0
- package/src/v2/shadcn/_reference/FlowsContent.tsx +688 -0
- package/src/v2/shadcn/_reference/FlowsGeneralSettings.tsx +27 -0
- package/src/v2/shadcn/_reference/GeneralSettings.tsx +33 -0
- package/src/v2/shadcn/_reference/InventoryGeneralSettings.tsx +82 -0
- package/src/v2/shadcn/_reference/LanguageSelector.tsx +97 -0
- package/src/v2/shadcn/_reference/LoadingScreen.tsx +25 -0
- package/src/v2/shadcn/_reference/LoadingSpinner.tsx +41 -0
- package/src/v2/shadcn/_reference/ManagedClientsList.tsx +121 -0
- package/src/v2/shadcn/_reference/NPSScore.tsx +379 -0
- package/src/v2/shadcn/_reference/NPSSummaryCard.tsx +181 -0
- package/src/v2/shadcn/_reference/NotificationBanner.tsx +129 -0
- package/src/v2/shadcn/_reference/NotificationPanel.tsx +208 -0
- package/src/v2/shadcn/_reference/OnlineUsersCard.tsx +73 -0
- package/src/v2/shadcn/_reference/ProtectedRoute.tsx +39 -0
- package/src/v2/shadcn/_reference/ProvidersTable.tsx +353 -0
- package/src/v2/shadcn/_reference/QuickAddPanel.tsx +1057 -0
- package/src/v2/shadcn/_reference/QuickFilters.tsx +112 -0
- package/src/v2/shadcn/_reference/ScheduleView.tsx +410 -0
- package/src/v2/shadcn/_reference/ScrollToTop.tsx +14 -0
- package/src/v2/shadcn/_reference/SecondaryNav.tsx +50 -0
- package/src/v2/shadcn/_reference/SecuritySettings.tsx +258 -0
- package/src/v2/shadcn/_reference/SessionDetailView.tsx +294 -0
- package/src/v2/shadcn/_reference/Sidebar.tsx +14 -0
- package/src/v2/shadcn/_reference/SidebarAwareLayout.tsx +30 -0
- package/src/v2/shadcn/_reference/SidebarLabelCustomization.tsx +285 -0
- package/src/v2/shadcn/_reference/SimulationBanner.tsx +57 -0
- package/src/v2/shadcn/_reference/SortControls.tsx +65 -0
- package/src/v2/shadcn/_reference/StatusBadge.tsx +49 -0
- package/src/v2/shadcn/_reference/StyleGuideContent.tsx +331 -0
- package/src/v2/shadcn/_reference/TableActionMenu.tsx +126 -0
- package/src/v2/shadcn/_reference/ThemeProvider.tsx +119 -0
- package/src/v2/shadcn/_reference/ThemeSettings.tsx +73 -0
- package/src/v2/shadcn/_reference/TopNavigation.tsx +332 -0
- package/src/v2/shadcn/_reference/UserActivityHistory.tsx +209 -0
- package/src/v2/shadcn/_reference/UserLanguageSettings.tsx +94 -0
- package/src/v2/shadcn/_reference/UserPanel.tsx +472 -0
- package/src/v2/shadcn/_reference/UsersTable.tsx +1023 -0
- package/src/v2/shadcn/_reference/WaiverForm.tsx +301 -0
- package/src/v2/shadcn/_reference/WaiversGeneralSettings.tsx +46 -0
- package/src/v2/shadcn/_reference/WaiversTable.tsx +290 -0
- package/src/v2/shadcn/_reference/WaiversTemplatesSettings.tsx +416 -0
- package/src/v2/shadcn/_reference/ai/AIChatPanel.tsx +313 -0
- package/src/v2/shadcn/_reference/ai/AIChatSearchBar.tsx +36 -0
- package/src/v2/shadcn/_reference/ai/ChatInteractiveBlock.tsx +298 -0
- package/src/v2/shadcn/_reference/ai/ChatMessageContent.tsx +40 -0
- package/src/v2/shadcn/_reference/ai/parseInteractiveBlocks.ts +142 -0
- package/src/v2/shadcn/_reference/auth/AuthLayout.tsx +55 -0
- package/src/v2/shadcn/_reference/auth/CreatePasswordForm.tsx +285 -0
- package/src/v2/shadcn/_reference/auth/CreatePasswordPanel.tsx +20 -0
- package/src/v2/shadcn/_reference/auth/LoginFooter.tsx +14 -0
- package/src/v2/shadcn/_reference/auth/LoginForm.tsx +205 -0
- package/src/v2/shadcn/_reference/auth/LoginPanel.tsx +41 -0
- package/src/v2/shadcn/_reference/auth/ResetPasswordForm.tsx +102 -0
- package/src/v2/shadcn/_reference/auth/ResetPasswordPanel.tsx +20 -0
- package/src/v2/shadcn/_reference/auth/VerifyEmailForm.tsx +95 -0
- package/src/v2/shadcn/_reference/auth/VerifyEmailPanel.tsx +20 -0
- package/src/v2/shadcn/_reference/email/EmailAttachment.tsx +119 -0
- package/src/v2/shadcn/_reference/email/EmailAutomation.tsx +92 -0
- package/src/v2/shadcn/_reference/email/EmailPlaceholders.tsx +64 -0
- package/src/v2/shadcn/_reference/email/UnlayerEmailEditor.tsx +41 -0
- package/src/v2/shadcn/_reference/email/emailTemplateData.ts +53 -0
- package/src/v2/shadcn/_reference/emptyStateIcons.tsx +103 -0
- package/src/v2/shadcn/_reference/games/MazeGame.tsx +394 -0
- package/src/v2/shadcn/_reference/games/RunnerGame.tsx +497 -0
- package/src/v2/shadcn/_reference/logos/BookedLogoFull.tsx +36 -0
- package/src/v2/shadcn/_reference/logos/BookedLogoMark.tsx +31 -0
- package/src/v2/shadcn/_reference/logos/BookedLogoNew.tsx +36 -0
- package/src/v2/shadcn/_reference/pricing/DynamicPricingRulesEditor.tsx +401 -0
- package/src/v2/shadcn/_reference/pricing/DynamicPricingTierCard.tsx +77 -0
- package/src/v2/shadcn/_reference/pricing/DynamicPricingTiersList.tsx +218 -0
- package/src/v2/shadcn/_reference/pricing/PricingCalendar.tsx +810 -0
- package/src/v2/shadcn/_reference/pricing/PricingPeriodCard.tsx +152 -0
- package/src/v2/shadcn/_reference/pricing/PricingPeriodForm.tsx +377 -0
- package/src/v2/shadcn/_reference/pricing/PricingPeriodsList.tsx +213 -0
- package/src/v2/shadcn/_reference/pricing/getRuleSummary.ts +39 -0
- package/src/v2/shadcn/_reference/products/AvailabilityRulesSection.tsx +184 -0
- package/src/v2/shadcn/_reference/products/AvailabilitySection.tsx +677 -0
- package/src/v2/shadcn/_reference/products/BookingTypeConfigOptions.tsx +40 -0
- package/src/v2/shadcn/_reference/products/CapacityPeriodsSection.tsx +238 -0
- package/src/v2/shadcn/_reference/products/DynamicPricingTiersSection.tsx +131 -0
- package/src/v2/shadcn/_reference/products/GiftCardOrdersTab.tsx +192 -0
- package/src/v2/shadcn/_reference/products/GiftCardSettings.tsx +342 -0
- package/src/v2/shadcn/_reference/products/PackageProductsSection.tsx +322 -0
- package/src/v2/shadcn/_reference/products/PricingSection.tsx +173 -0
- package/src/v2/shadcn/_reference/products/ProductTypeFields.tsx +353 -0
- package/src/v2/shadcn/_reference/products/ProductTypeIcon.tsx +95 -0
- package/src/v2/shadcn/_reference/products/VariablePricingSection.tsx +140 -0
- package/src/v2/shadcn/_reference/products/productTypeConfig.ts +182 -0
- package/src/v2/shadcn/_reference/shared/BackButton.tsx +50 -0
- package/src/v2/shadcn/_reference/shared/CancelConfirmationDialog.tsx +18 -0
- package/src/v2/shadcn/_reference/shared/ConfirmationDialog.tsx +136 -0
- package/src/v2/shadcn/_reference/shared/DeleteConfirmationDialog.tsx +18 -0
- package/src/v2/shadcn/_reference/shared/DeleteEntityPage.tsx +221 -0
- package/src/v2/shadcn/_reference/shared/SidebarIcons.tsx +108 -0
- package/src/v2/shadcn/_reference/shared/UnifiedSidebar.tsx +722 -0
- package/src/v2/shadcn/_reference/tables/BulkActionsBar.tsx +68 -0
- package/src/v2/shadcn/_reference/tables/DataTable.tsx +221 -0
- package/src/v2/shadcn/_reference/tables/TableControls.tsx +94 -0
- package/src/v2/shadcn/_reference/tables/index.ts +3 -0
- package/src/v2/shadcn/_reference/tables/types.ts +79 -0
- package/src/v2/shadcn/_reference/zones/LegacyZoneSettings.tsx +299 -0
- package/src/v2/shadcn/components/ui/accordion.stories.tsx +63 -0
- package/src/v2/shadcn/components/ui/accordion.tsx +52 -0
- package/src/v2/shadcn/components/ui/alert-dialog.stories.tsx +44 -0
- package/src/v2/shadcn/components/ui/alert-dialog.tsx +104 -0
- package/src/v2/shadcn/components/ui/alert.stories.tsx +44 -0
- package/src/v2/shadcn/components/ui/alert.tsx +43 -0
- package/src/v2/shadcn/components/ui/aspect-ratio.stories.tsx +46 -0
- package/src/v2/shadcn/components/ui/aspect-ratio.tsx +5 -0
- package/src/v2/shadcn/components/ui/avatar.stories.tsx +39 -0
- package/src/v2/shadcn/components/ui/avatar.tsx +38 -0
- package/src/v2/shadcn/components/ui/badge.stories.tsx +17 -0
- package/src/v2/shadcn/components/ui/badge.tsx +30 -0
- package/src/v2/shadcn/components/ui/breadcrumb.stories.tsx +91 -0
- package/src/v2/shadcn/components/ui/breadcrumb.tsx +90 -0
- package/src/v2/shadcn/components/ui/button.stories.tsx +20 -0
- package/src/v2/shadcn/components/ui/button.tsx +60 -0
- package/src/v2/shadcn/components/ui/calendar.stories.tsx +61 -0
- package/src/v2/shadcn/components/ui/calendar.tsx +54 -0
- package/src/v2/shadcn/components/ui/card.stories.tsx +37 -0
- package/src/v2/shadcn/components/ui/card.tsx +43 -0
- package/src/v2/shadcn/components/ui/carousel.stories.tsx +92 -0
- package/src/v2/shadcn/components/ui/carousel.tsx +224 -0
- package/src/v2/shadcn/components/ui/checkbox.scss +38 -0
- package/src/v2/shadcn/components/ui/checkbox.stories.tsx +23 -0
- package/src/v2/shadcn/components/ui/checkbox.tsx +24 -0
- package/src/v2/shadcn/components/ui/collapsible.stories.tsx +59 -0
- package/src/v2/shadcn/components/ui/collapsible.tsx +9 -0
- package/src/v2/shadcn/components/ui/command.stories.tsx +70 -0
- package/src/v2/shadcn/components/ui/command.tsx +132 -0
- package/src/v2/shadcn/components/ui/context-menu.stories.tsx +72 -0
- package/src/v2/shadcn/components/ui/context-menu.tsx +178 -0
- package/src/v2/shadcn/components/ui/dialog.stories.tsx +67 -0
- package/src/v2/shadcn/components/ui/dialog.tsx +95 -0
- package/src/v2/shadcn/components/ui/drawer.stories.tsx +50 -0
- package/src/v2/shadcn/components/ui/drawer.tsx +87 -0
- package/src/v2/shadcn/components/ui/dropdown-menu.stories.tsx +73 -0
- package/src/v2/shadcn/components/ui/dropdown-menu.tsx +179 -0
- package/src/v2/shadcn/components/ui/form.stories.tsx +105 -0
- package/src/v2/shadcn/components/ui/form.tsx +129 -0
- package/src/v2/shadcn/components/ui/hover-card.stories.tsx +35 -0
- package/src/v2/shadcn/components/ui/hover-card.tsx +27 -0
- package/src/v2/shadcn/components/ui/input-otp.stories.tsx +72 -0
- package/src/v2/shadcn/components/ui/input-otp.tsx +61 -0
- package/src/v2/shadcn/components/ui/input.stories.tsx +16 -0
- package/src/v2/shadcn/components/ui/input.tsx +25 -0
- package/src/v2/shadcn/components/ui/label.stories.tsx +13 -0
- package/src/v2/shadcn/components/ui/label.tsx +17 -0
- package/src/v2/shadcn/components/ui/menubar.stories.tsx +86 -0
- package/src/v2/shadcn/components/ui/menubar.tsx +207 -0
- package/src/v2/shadcn/components/ui/navigation-menu.stories.tsx +68 -0
- package/src/v2/shadcn/components/ui/navigation-menu.tsx +120 -0
- package/src/v2/shadcn/components/ui/pagination.stories.tsx +78 -0
- package/src/v2/shadcn/components/ui/pagination.tsx +81 -0
- package/src/v2/shadcn/components/ui/popover.stories.tsx +44 -0
- package/src/v2/shadcn/components/ui/popover.tsx +29 -0
- package/src/v2/shadcn/components/ui/progress.stories.tsx +17 -0
- package/src/v2/shadcn/components/ui/progress.tsx +23 -0
- package/src/v2/shadcn/components/ui/radio-card.stories.tsx +68 -0
- package/src/v2/shadcn/components/ui/radio-card.tsx +52 -0
- package/src/v2/shadcn/components/ui/radio-group.stories.tsx +77 -0
- package/src/v2/shadcn/components/ui/radio-group.tsx +35 -0
- package/src/v2/shadcn/components/ui/scroll-area.stories.tsx +56 -0
- package/src/v2/shadcn/components/ui/scroll-area.tsx +38 -0
- package/src/v2/shadcn/components/ui/select.stories.tsx +60 -0
- package/src/v2/shadcn/components/ui/select.tsx +148 -0
- package/src/v2/shadcn/components/ui/separator.stories.tsx +30 -0
- package/src/v2/shadcn/components/ui/separator.tsx +20 -0
- package/src/v2/shadcn/components/ui/sheet.stories.tsx +115 -0
- package/src/v2/shadcn/components/ui/sheet.tsx +107 -0
- package/src/v2/shadcn/components/ui/sidebar.stories.tsx +167 -0
- package/src/v2/shadcn/components/ui/sidebar.tsx +637 -0
- package/src/v2/shadcn/components/ui/skeleton.stories.tsx +36 -0
- package/src/v2/shadcn/components/ui/skeleton.tsx +7 -0
- package/src/v2/shadcn/components/ui/slider.stories.tsx +16 -0
- package/src/v2/shadcn/components/ui/slider.tsx +23 -0
- package/src/v2/shadcn/components/ui/switch.scss +63 -0
- package/src/v2/shadcn/components/ui/switch.stories.tsx +23 -0
- package/src/v2/shadcn/components/ui/switch.tsx +24 -0
- package/src/v2/shadcn/components/ui/table-pagination.stories.tsx +81 -0
- package/src/v2/shadcn/components/ui/table-pagination.tsx +61 -0
- package/src/v2/shadcn/components/ui/table.stories.tsx +40 -0
- package/src/v2/shadcn/components/ui/table.tsx +72 -0
- package/src/v2/shadcn/components/ui/tabs.stories.tsx +85 -0
- package/src/v2/shadcn/components/ui/tabs.tsx +53 -0
- package/src/v2/shadcn/components/ui/textarea.stories.tsx +15 -0
- package/src/v2/shadcn/components/ui/textarea.tsx +21 -0
- package/src/v2/shadcn/components/ui/toast.stories.tsx +77 -0
- package/src/v2/shadcn/components/ui/toast.tsx +111 -0
- package/src/v2/shadcn/components/ui/toaster.stories.tsx +46 -0
- package/src/v2/shadcn/components/ui/toaster.tsx +24 -0
- package/src/v2/shadcn/components/ui/toggle-group.stories.tsx +95 -0
- package/src/v2/shadcn/components/ui/toggle-group.tsx +49 -0
- package/src/v2/shadcn/components/ui/toggle.stories.tsx +18 -0
- package/src/v2/shadcn/components/ui/toggle.tsx +37 -0
- package/src/v2/shadcn/components/ui/tooltip.stories.tsx +57 -0
- package/src/v2/shadcn/components/ui/tooltip.tsx +28 -0
- package/src/v2/shadcn/components/ui/use-toast.ts +3 -0
- package/src/v2/shadcn/hooks/use-mobile.tsx +19 -0
- package/src/v2/shadcn/hooks/use-toast.ts +184 -0
- package/src/v2/shadcn/index.ts +76 -0
- package/src/v2/shadcn/lib/utils.ts +6 -0
- package/src/v2/shadcn/styles/globals.css +112 -0
- package/.vscode/settings.json +0 -3
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { supabase } from '@/integrations/supabase/client';
|
|
3
|
+
import { ScrollArea } from './ui/scroll-area';
|
|
4
|
+
import { Button } from './ui/button';
|
|
5
|
+
import { Input } from './ui/input';
|
|
6
|
+
import { Label } from './ui/label';
|
|
7
|
+
import { RadioGroup, RadioGroupItem } from './ui/radio-group';
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
|
9
|
+
import { IconCross, IconLocation } from '../../icons';
|
|
10
|
+
import { useNotify } from '../hooks/useNotify';
|
|
11
|
+
import { isAvailableAtDateTime, parseAvailabilityRules, type AvailabilityRule } from '@/lib/availabilityUtils';
|
|
12
|
+
|
|
13
|
+
interface Zone {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
resources: Resource[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Resource {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
instances: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ResourceInstance {
|
|
26
|
+
resourceId: string;
|
|
27
|
+
resourceName: string;
|
|
28
|
+
instanceNumber: number;
|
|
29
|
+
displayName: string;
|
|
30
|
+
uniqueKey: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PriceTier {
|
|
34
|
+
name: string;
|
|
35
|
+
price: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Activity {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
price: number | null;
|
|
42
|
+
duration: number | null;
|
|
43
|
+
zone_id: string | null;
|
|
44
|
+
resource_id: string | null;
|
|
45
|
+
price_tiers: PriceTier[] | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface TierQuantity {
|
|
49
|
+
tierName: string;
|
|
50
|
+
tierPrice: number;
|
|
51
|
+
quantity: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface QuickAddPanelProps {
|
|
55
|
+
isOpen: boolean;
|
|
56
|
+
onClose: () => void;
|
|
57
|
+
onSuccess: () => void;
|
|
58
|
+
providerId: string;
|
|
59
|
+
providerType: 'venue' | 'promoter';
|
|
60
|
+
// Pre-filled data from diary cell click
|
|
61
|
+
prefilledDate?: string;
|
|
62
|
+
prefilledTime?: string;
|
|
63
|
+
prefilledZoneId?: string;
|
|
64
|
+
prefilledZoneName?: string;
|
|
65
|
+
prefilledResourceId?: string;
|
|
66
|
+
prefilledResourceName?: string;
|
|
67
|
+
prefilledResourceInstance?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const QuickAddPanel: React.FC<QuickAddPanelProps> = ({
|
|
71
|
+
isOpen,
|
|
72
|
+
onClose,
|
|
73
|
+
onSuccess,
|
|
74
|
+
providerId,
|
|
75
|
+
providerType,
|
|
76
|
+
prefilledDate,
|
|
77
|
+
prefilledTime,
|
|
78
|
+
prefilledZoneId,
|
|
79
|
+
prefilledZoneName,
|
|
80
|
+
prefilledResourceId,
|
|
81
|
+
prefilledResourceInstance,
|
|
82
|
+
}) => {
|
|
83
|
+
const { showSuccess, showError } = useNotify();
|
|
84
|
+
// Step state
|
|
85
|
+
const [step, setStep] = useState<'details' | 'payment'>('details');
|
|
86
|
+
|
|
87
|
+
// Form state
|
|
88
|
+
const [flowType, setFlowType] = useState<'booking' | 'enquiry'>('booking');
|
|
89
|
+
const [customerFirstName, setCustomerFirstName] = useState('');
|
|
90
|
+
const [customerLastName, setCustomerLastName] = useState('');
|
|
91
|
+
const [customerEmail, setCustomerEmail] = useState('');
|
|
92
|
+
const [customerPhone, setCustomerPhone] = useState('');
|
|
93
|
+
const [bookingDate, setBookingDate] = useState(prefilledDate || '');
|
|
94
|
+
const [bookingTimeStart, setBookingTimeStart] = useState(prefilledTime || '');
|
|
95
|
+
const [bookingTimeEnd, setBookingTimeEnd] = useState('');
|
|
96
|
+
const [totalAmount, setTotalAmount] = useState('');
|
|
97
|
+
const [selectedZoneId, setSelectedZoneId] = useState(prefilledZoneId || '');
|
|
98
|
+
const [selectedResourceKey, setSelectedResourceKey] = useState(
|
|
99
|
+
prefilledResourceId && prefilledResourceInstance
|
|
100
|
+
? `${prefilledResourceId}-${prefilledResourceInstance}`
|
|
101
|
+
: ''
|
|
102
|
+
);
|
|
103
|
+
const [quantity, setQuantity] = useState(1);
|
|
104
|
+
const [zoneName, setZoneName] = useState(prefilledZoneName || '');
|
|
105
|
+
|
|
106
|
+
// Payment state
|
|
107
|
+
const [paymentMethod, setPaymentMethod] = useState<'cash' | 'card' | 'payment_link'>('cash');
|
|
108
|
+
const [paymentLinkEmail, setPaymentLinkEmail] = useState('');
|
|
109
|
+
|
|
110
|
+
// Data state
|
|
111
|
+
const [zones, setZones] = useState<Zone[]>([]);
|
|
112
|
+
const [activities, setActivities] = useState<Activity[]>([]);
|
|
113
|
+
const [selectedActivityId, setSelectedActivityId] = useState<string>('');
|
|
114
|
+
const [activityName, setActivityName] = useState('');
|
|
115
|
+
const [tierQuantities, setTierQuantities] = useState<TierQuantity[]>([]);
|
|
116
|
+
const [showEndTime, setShowEndTime] = useState(false);
|
|
117
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
118
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
119
|
+
const [isEditingResource, setIsEditingResource] = useState(false);
|
|
120
|
+
|
|
121
|
+
// Reset form when panel opens with new data
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (isOpen) {
|
|
124
|
+
setStep('details');
|
|
125
|
+
setFlowType('booking');
|
|
126
|
+
setCustomerFirstName('');
|
|
127
|
+
setCustomerLastName('');
|
|
128
|
+
setCustomerEmail('');
|
|
129
|
+
setCustomerPhone('');
|
|
130
|
+
setBookingDate(prefilledDate || '');
|
|
131
|
+
setBookingTimeStart(prefilledTime || '');
|
|
132
|
+
setBookingTimeEnd('');
|
|
133
|
+
setTotalAmount('');
|
|
134
|
+
setSelectedZoneId(prefilledZoneId || '');
|
|
135
|
+
setSelectedResourceKey(
|
|
136
|
+
prefilledResourceId && prefilledResourceInstance
|
|
137
|
+
? `${prefilledResourceId}-${prefilledResourceInstance}`
|
|
138
|
+
: ''
|
|
139
|
+
);
|
|
140
|
+
setQuantity(1);
|
|
141
|
+
setZoneName(prefilledZoneName || '');
|
|
142
|
+
setPaymentMethod('cash');
|
|
143
|
+
setPaymentLinkEmail('');
|
|
144
|
+
setSelectedActivityId('');
|
|
145
|
+
setActivityName('');
|
|
146
|
+
setActivities([]);
|
|
147
|
+
setTierQuantities([]);
|
|
148
|
+
setShowEndTime(false);
|
|
149
|
+
setIsEditingResource(!prefilledResourceId); // Start in edit mode if no prefilled resource
|
|
150
|
+
}
|
|
151
|
+
}, [isOpen, prefilledDate, prefilledTime, prefilledZoneId, prefilledZoneName, prefilledResourceId, prefilledResourceInstance]);
|
|
152
|
+
|
|
153
|
+
// Fetch zones and resources
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (isOpen && providerId) {
|
|
156
|
+
fetchZones();
|
|
157
|
+
}
|
|
158
|
+
}, [isOpen, providerId]);
|
|
159
|
+
|
|
160
|
+
// Fetch activities when zone, resource, date, or time changes
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (isOpen && providerId && selectedZoneId) {
|
|
163
|
+
fetchActivities();
|
|
164
|
+
} else {
|
|
165
|
+
setActivities([]);
|
|
166
|
+
setSelectedActivityId('');
|
|
167
|
+
}
|
|
168
|
+
}, [isOpen, providerId, selectedZoneId, selectedResourceKey, bookingDate, bookingTimeStart]);
|
|
169
|
+
|
|
170
|
+
const fetchActivities = async () => {
|
|
171
|
+
try {
|
|
172
|
+
// Parse the selected resource key to get resource_id (UUID-safe)
|
|
173
|
+
const resourceId = (() => {
|
|
174
|
+
if (!selectedResourceKey) return null;
|
|
175
|
+
const lastDash = selectedResourceKey.lastIndexOf('-');
|
|
176
|
+
if (lastDash <= 0) return null;
|
|
177
|
+
return selectedResourceKey.slice(0, lastDash);
|
|
178
|
+
})();
|
|
179
|
+
|
|
180
|
+
// Build query - get products for this provider that match the zone
|
|
181
|
+
const [productsRes, itemsRes] = await Promise.all([
|
|
182
|
+
supabase
|
|
183
|
+
.from('products')
|
|
184
|
+
.select('id, name, price, duration, zone_id, resource_id, price_tiers')
|
|
185
|
+
.eq('provider_id', providerId)
|
|
186
|
+
.eq('status', 'active')
|
|
187
|
+
.eq('zone_id', selectedZoneId)
|
|
188
|
+
.order('name'),
|
|
189
|
+
supabase
|
|
190
|
+
.from('product_items')
|
|
191
|
+
.select('product_id, availability_rules')
|
|
192
|
+
.eq('provider_id', providerId)
|
|
193
|
+
.eq('status', 'active'),
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
if (productsRes.error) throw productsRes.error;
|
|
197
|
+
|
|
198
|
+
// Filter by resource: include products with matching resource_id OR zone-wide products (no resource_id)
|
|
199
|
+
const filteredData = resourceId
|
|
200
|
+
? (productsRes.data || []).filter(product => !product.resource_id || product.resource_id === resourceId)
|
|
201
|
+
: productsRes.data || [];
|
|
202
|
+
|
|
203
|
+
// Build a map of product_id -> availability rules from product_items
|
|
204
|
+
const productAvailability = new Map<string, AvailabilityRule[][]>();
|
|
205
|
+
if (itemsRes.data) {
|
|
206
|
+
for (const item of itemsRes.data) {
|
|
207
|
+
const rules = parseAvailabilityRules(item.availability_rules);
|
|
208
|
+
if (!productAvailability.has(item.product_id)) {
|
|
209
|
+
productAvailability.set(item.product_id, []);
|
|
210
|
+
}
|
|
211
|
+
productAvailability.get(item.product_id)!.push(rules);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Filter out products where ALL items are unavailable for the selected date/time
|
|
216
|
+
const bookingDateObj = bookingDate ? new Date(bookingDate + 'T00:00:00') : null;
|
|
217
|
+
const availableProducts = filteredData.filter(product => {
|
|
218
|
+
const itemRules = productAvailability.get(product.id);
|
|
219
|
+
// No product items → product is available (no restrictions)
|
|
220
|
+
if (!itemRules || itemRules.length === 0) return true;
|
|
221
|
+
// At least one item must be available
|
|
222
|
+
return itemRules.some(rules => {
|
|
223
|
+
if (!bookingDateObj) return true;
|
|
224
|
+
return isAvailableAtDateTime(rules, bookingDateObj, bookingTimeStart || undefined);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Map data to include price_tiers properly typed
|
|
229
|
+
const activitiesData: Activity[] = availableProducts.map(item => ({
|
|
230
|
+
...item,
|
|
231
|
+
price_tiers: Array.isArray(item.price_tiers) ? item.price_tiers as unknown as PriceTier[] : null
|
|
232
|
+
}));
|
|
233
|
+
setActivities(activitiesData);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('Error fetching activities:', error);
|
|
236
|
+
setActivities([]);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const fetchZones = async () => {
|
|
241
|
+
setIsLoading(true);
|
|
242
|
+
try {
|
|
243
|
+
const { data: zonesData, error: zonesError } = await supabase
|
|
244
|
+
.from('zones')
|
|
245
|
+
.select('id, name')
|
|
246
|
+
.eq('provider_id', providerId)
|
|
247
|
+
.eq('status', 'active')
|
|
248
|
+
.order('display_order');
|
|
249
|
+
|
|
250
|
+
if (zonesError) throw zonesError;
|
|
251
|
+
|
|
252
|
+
const zonesWithResources: Zone[] = [];
|
|
253
|
+
for (const zone of zonesData || []) {
|
|
254
|
+
const { data: resourcesData, error: resourcesError } = await supabase
|
|
255
|
+
.from('resources')
|
|
256
|
+
.select('id, name, instances')
|
|
257
|
+
.eq('zone_id', zone.id)
|
|
258
|
+
.order('display_order', { ascending: true });
|
|
259
|
+
|
|
260
|
+
if (resourcesError) throw resourcesError;
|
|
261
|
+
|
|
262
|
+
zonesWithResources.push({
|
|
263
|
+
...zone,
|
|
264
|
+
resources: resourcesData || []
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
setZones(zonesWithResources);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('Error fetching zones:', error);
|
|
271
|
+
} finally {
|
|
272
|
+
setIsLoading(false);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const generateReferenceNumber = () => {
|
|
277
|
+
const prefix = flowType === 'booking' ? 'BKG' : 'ENQ';
|
|
278
|
+
const timestamp = Date.now().toString(36).toUpperCase();
|
|
279
|
+
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
280
|
+
return `${prefix}-${timestamp}${random}`;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Get all resource instances for the selected zone
|
|
284
|
+
const getResourceInstances = (): ResourceInstance[] => {
|
|
285
|
+
const selectedZone = zones.find(z => z.id === selectedZoneId);
|
|
286
|
+
if (!selectedZone) return [];
|
|
287
|
+
|
|
288
|
+
const instances: ResourceInstance[] = [];
|
|
289
|
+
for (const resource of selectedZone.resources) {
|
|
290
|
+
for (let i = 1; i <= resource.instances; i++) {
|
|
291
|
+
instances.push({
|
|
292
|
+
resourceId: resource.id,
|
|
293
|
+
resourceName: resource.name,
|
|
294
|
+
instanceNumber: i,
|
|
295
|
+
displayName: resource.instances > 1 ? `${resource.name} ${i}` : resource.name,
|
|
296
|
+
uniqueKey: `${resource.id}-${i}`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return instances;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Calculate end time based on activity duration and quantity
|
|
304
|
+
const getCalculatedEndTime = (): string => {
|
|
305
|
+
if (bookingTimeEnd) return bookingTimeEnd; // Use manual end time if set
|
|
306
|
+
|
|
307
|
+
const selectedActivity = activities.find(a => a.id === selectedActivityId);
|
|
308
|
+
const duration = selectedActivity?.duration;
|
|
309
|
+
|
|
310
|
+
if (!duration || !bookingTimeStart) return '';
|
|
311
|
+
|
|
312
|
+
const [hours, minutes] = bookingTimeStart.split(':').map(Number);
|
|
313
|
+
const startMinutes = hours * 60 + minutes;
|
|
314
|
+
// Multiply duration by quantity for per-person activities
|
|
315
|
+
const totalDuration = duration * quantity;
|
|
316
|
+
const endMinutes = startMinutes + totalDuration;
|
|
317
|
+
const endHours = Math.floor(endMinutes / 60) % 24;
|
|
318
|
+
const endMins = endMinutes % 60;
|
|
319
|
+
return `${endHours.toString().padStart(2, '0')}:${endMins.toString().padStart(2, '0')}`;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Handle activity selection
|
|
323
|
+
const handleActivitySelect = (activityId: string) => {
|
|
324
|
+
setSelectedActivityId(activityId);
|
|
325
|
+
const activity = activities.find(a => a.id === activityId);
|
|
326
|
+
if (activity) {
|
|
327
|
+
setActivityName(activity.name);
|
|
328
|
+
|
|
329
|
+
// Initialize tier quantities based on activity's price tiers
|
|
330
|
+
const tiers = activity.price_tiers;
|
|
331
|
+
if (tiers && tiers.length > 0) {
|
|
332
|
+
// Has multiple tiers - set up tier quantities (all start at 0 except first at 1)
|
|
333
|
+
const initialTiers: TierQuantity[] = tiers.map((tier, index) => ({
|
|
334
|
+
tierName: tier.name || `Tier ${index + 1}`,
|
|
335
|
+
tierPrice: tier.price,
|
|
336
|
+
quantity: index === 0 ? 1 : 0
|
|
337
|
+
}));
|
|
338
|
+
setTierQuantities(initialTiers);
|
|
339
|
+
setQuantity(1);
|
|
340
|
+
// Calculate initial total
|
|
341
|
+
setTotalAmount(initialTiers[0].tierPrice.toString());
|
|
342
|
+
} else {
|
|
343
|
+
// Single price - use simple quantity
|
|
344
|
+
setTierQuantities([]);
|
|
345
|
+
setQuantity(1);
|
|
346
|
+
if (activity.price !== null) {
|
|
347
|
+
setTotalAmount(activity.price.toString());
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Update tier quantity
|
|
354
|
+
const handleTierQuantityChange = (tierIndex: number, newQty: number) => {
|
|
355
|
+
const clampedQty = Math.max(0, newQty);
|
|
356
|
+
const newTiers = [...tierQuantities];
|
|
357
|
+
newTiers[tierIndex] = { ...newTiers[tierIndex], quantity: clampedQty };
|
|
358
|
+
setTierQuantities(newTiers);
|
|
359
|
+
|
|
360
|
+
// Calculate total from all tiers
|
|
361
|
+
const total = newTiers.reduce((sum, tier) => sum + (tier.tierPrice * tier.quantity), 0);
|
|
362
|
+
setTotalAmount(total.toString());
|
|
363
|
+
|
|
364
|
+
// Update main quantity (total of all tier quantities)
|
|
365
|
+
const totalQty = newTiers.reduce((sum, tier) => sum + tier.quantity, 0);
|
|
366
|
+
setQuantity(Math.max(1, totalQty));
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Update price when quantity changes (for single-price activities)
|
|
370
|
+
const handleQuantityChange = (newQty: number) => {
|
|
371
|
+
const clampedQty = Math.max(1, newQty);
|
|
372
|
+
setQuantity(clampedQty);
|
|
373
|
+
|
|
374
|
+
// Update total amount if activity has a price (and no tiers)
|
|
375
|
+
if (tierQuantities.length === 0) {
|
|
376
|
+
const selectedActivity = activities.find(a => a.id === selectedActivityId);
|
|
377
|
+
if (selectedActivity?.price !== null && selectedActivity?.price !== undefined) {
|
|
378
|
+
setTotalAmount((selectedActivity.price * clampedQty).toString());
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Get total quantity from tiers
|
|
384
|
+
const getTotalQuantity = (): number => {
|
|
385
|
+
if (tierQuantities.length > 0) {
|
|
386
|
+
return tierQuantities.reduce((sum, tier) => sum + tier.quantity, 0);
|
|
387
|
+
}
|
|
388
|
+
return quantity;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const handleNext = () => {
|
|
392
|
+
if (!bookingDate) {
|
|
393
|
+
showError('Booking date is required.');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!bookingTimeStart) {
|
|
398
|
+
showError('Start time is required.');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!selectedResourceKey) {
|
|
403
|
+
showError('Please select a resource.');
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
setStep('payment');
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const handleBack = () => {
|
|
411
|
+
setStep('details');
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const handleSubmit = async () => {
|
|
415
|
+
if (paymentMethod === 'payment_link' && !paymentLinkEmail.trim()) {
|
|
416
|
+
showError('Email is required for payment link.');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
setIsSaving(true);
|
|
421
|
+
try {
|
|
422
|
+
const referenceNumber = generateReferenceNumber();
|
|
423
|
+
const amount = parseFloat(totalAmount) || 0;
|
|
424
|
+
const fullName = [customerFirstName.trim(), customerLastName.trim()].filter(Boolean).join(' ') || 'Walk-in Customer';
|
|
425
|
+
|
|
426
|
+
// Determine status based on payment method
|
|
427
|
+
let status: 'active' | 'initial_contact' = 'active';
|
|
428
|
+
if (paymentMethod === 'payment_link') {
|
|
429
|
+
status = 'initial_contact';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Get selected resource details
|
|
433
|
+
const resourceInstances = getResourceInstances();
|
|
434
|
+
const selectedInstance = resourceInstances.find(r => r.uniqueKey === selectedResourceKey);
|
|
435
|
+
// Use selected activity name, custom activity name, zone name, or resource name as fallback
|
|
436
|
+
const activityNameForFlow = activityName.trim() || zoneName || selectedInstance?.resourceName || 'Booking';
|
|
437
|
+
|
|
438
|
+
const { error } = await supabase
|
|
439
|
+
.from('bookings')
|
|
440
|
+
.insert({
|
|
441
|
+
provider_id: providerId,
|
|
442
|
+
reference_number: referenceNumber,
|
|
443
|
+
type: flowType,
|
|
444
|
+
customer_name: fullName,
|
|
445
|
+
customer_email: paymentMethod === 'payment_link' ? paymentLinkEmail.trim() : (customerEmail.trim() || null),
|
|
446
|
+
customer_phone: customerPhone.trim() || null,
|
|
447
|
+
activity_name: activityNameForFlow,
|
|
448
|
+
booking_date: bookingDate,
|
|
449
|
+
booking_time_start: bookingTimeStart,
|
|
450
|
+
booking_time_end: getCalculatedEndTime() || null,
|
|
451
|
+
total_amount: amount,
|
|
452
|
+
net_amount: amount * 0.9,
|
|
453
|
+
status: status,
|
|
454
|
+
zone_id: selectedZoneId || null,
|
|
455
|
+
resource_id: selectedInstance?.resourceId || null,
|
|
456
|
+
resource_instance: selectedInstance?.instanceNumber || null
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (error) throw error;
|
|
460
|
+
|
|
461
|
+
const successMessage = paymentMethod === 'payment_link'
|
|
462
|
+
? `Payment link sent to ${paymentLinkEmail}. Booking pending payment.`
|
|
463
|
+
: paymentMethod === 'card'
|
|
464
|
+
? 'Payment sent to PDQ machine. Booking confirmed.'
|
|
465
|
+
: 'Cash payment recorded. Booking confirmed.';
|
|
466
|
+
|
|
467
|
+
showSuccess(`${flowType === 'booking' ? 'Booking' : 'Enquiry'} #${referenceNumber} created`, successMessage);
|
|
468
|
+
|
|
469
|
+
onSuccess();
|
|
470
|
+
onClose();
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error('Error creating flow:', error);
|
|
473
|
+
showError('Failed to create flow. Please try again.');
|
|
474
|
+
} finally {
|
|
475
|
+
setIsSaving(false);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const selectedZone = zones.find(z => z.id === selectedZoneId);
|
|
480
|
+
const resourceInstances = getResourceInstances();
|
|
481
|
+
|
|
482
|
+
if (!isOpen) return null;
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
<>
|
|
486
|
+
{/* Overlay */}
|
|
487
|
+
<div
|
|
488
|
+
className="fixed inset-0 z-40 animate-fade-in"
|
|
489
|
+
style={{ backgroundColor: 'var(--overlay-curtain)' }}
|
|
490
|
+
onClick={onClose}
|
|
491
|
+
/>
|
|
492
|
+
|
|
493
|
+
{/* Panel */}
|
|
494
|
+
<div
|
|
495
|
+
className="fixed right-0 top-0 h-screen w-full sm:w-[480px] bg-surface-primary shadow-xl z-50 animate-slide-in-right flex flex-col"
|
|
496
|
+
onClick={(e) => e.stopPropagation()}
|
|
497
|
+
>
|
|
498
|
+
{/* Header */}
|
|
499
|
+
<div className="flex items-start justify-between p-4 border-b border-border-primary shrink-0">
|
|
500
|
+
<div className="flex flex-col gap-1">
|
|
501
|
+
<h2 className="text-lg font-semibold text-label-primary">
|
|
502
|
+
{step === 'details' ? 'Quick Add' : 'Payment'}
|
|
503
|
+
</h2>
|
|
504
|
+
{zoneName && (
|
|
505
|
+
<div className="flex items-center gap-1.5">
|
|
506
|
+
<IconLocation className="w-4 h-4 text-fill-secondary" />
|
|
507
|
+
<span className="text-sm text-label-secondary">{zoneName}</span>
|
|
508
|
+
</div>
|
|
509
|
+
)}
|
|
510
|
+
</div>
|
|
511
|
+
<Button
|
|
512
|
+
variant="ghost"
|
|
513
|
+
size="icon"
|
|
514
|
+
onClick={onClose}
|
|
515
|
+
aria-label="Close panel"
|
|
516
|
+
>
|
|
517
|
+
<IconCross className="w-5 h-5 text-label-secondary" />
|
|
518
|
+
</Button>
|
|
519
|
+
</div>
|
|
520
|
+
|
|
521
|
+
<ScrollArea className="flex-1">
|
|
522
|
+
<div className="p-4">
|
|
523
|
+
{step === 'details' ? (
|
|
524
|
+
<div className="flex flex-col gap-6">
|
|
525
|
+
{/* Flow Type */}
|
|
526
|
+
<div className="flex flex-col gap-3">
|
|
527
|
+
<Label className="text-label-primary font-semibold">Flow Type</Label>
|
|
528
|
+
<RadioGroup
|
|
529
|
+
value={flowType}
|
|
530
|
+
onValueChange={(value) => setFlowType(value as 'booking' | 'enquiry')}
|
|
531
|
+
className="flex gap-4"
|
|
532
|
+
>
|
|
533
|
+
<div className="flex items-center gap-2">
|
|
534
|
+
<RadioGroupItem value="booking" id="panel-booking" />
|
|
535
|
+
<Label htmlFor="panel-booking" className="text-label-secondary cursor-pointer">Booking</Label>
|
|
536
|
+
</div>
|
|
537
|
+
<div className="flex items-center gap-2">
|
|
538
|
+
<RadioGroupItem value="enquiry" id="panel-enquiry" />
|
|
539
|
+
<Label htmlFor="panel-enquiry" className="text-label-secondary cursor-pointer">Enquiry</Label>
|
|
540
|
+
</div>
|
|
541
|
+
</RadioGroup>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
{/* Booking Details */}
|
|
545
|
+
<div className="flex flex-col gap-4">
|
|
546
|
+
<h3 className="text-label-primary font-semibold">Booking Details</h3>
|
|
547
|
+
|
|
548
|
+
<div className="grid grid-cols-2 gap-4">
|
|
549
|
+
<div className="flex flex-col gap-2">
|
|
550
|
+
<Label htmlFor="panel-bookingDate" className="text-label-primary">Date</Label>
|
|
551
|
+
<Input
|
|
552
|
+
id="panel-bookingDate"
|
|
553
|
+
type="date"
|
|
554
|
+
value={bookingDate}
|
|
555
|
+
onChange={(e) => setBookingDate(e.target.value)}
|
|
556
|
+
/>
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<div className="flex flex-col gap-2">
|
|
560
|
+
<Label htmlFor="panel-bookingTimeStart" className="text-label-primary">Start Time</Label>
|
|
561
|
+
<Input
|
|
562
|
+
id="panel-bookingTimeStart"
|
|
563
|
+
type="time"
|
|
564
|
+
value={bookingTimeStart}
|
|
565
|
+
onChange={(e) => setBookingTimeStart(e.target.value)}
|
|
566
|
+
/>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
{/* End Time - conditional */}
|
|
571
|
+
{showEndTime ? (
|
|
572
|
+
<div className="flex flex-col gap-2">
|
|
573
|
+
<Label htmlFor="panel-bookingTimeEnd" className="text-label-primary">End Time</Label>
|
|
574
|
+
<Input
|
|
575
|
+
id="panel-bookingTimeEnd"
|
|
576
|
+
type="time"
|
|
577
|
+
value={bookingTimeEnd}
|
|
578
|
+
onChange={(e) => setBookingTimeEnd(e.target.value)}
|
|
579
|
+
/>
|
|
580
|
+
</div>
|
|
581
|
+
) : (
|
|
582
|
+
<div className="flex items-center gap-2">
|
|
583
|
+
<Button
|
|
584
|
+
type="button"
|
|
585
|
+
variant="link"
|
|
586
|
+
onClick={() => setShowEndTime(true)}
|
|
587
|
+
className="text-sm h-auto p-0"
|
|
588
|
+
>
|
|
589
|
+
+ Add end time
|
|
590
|
+
</Button>
|
|
591
|
+
{getCalculatedEndTime() && (
|
|
592
|
+
<span className="text-xs text-label-tertiary">
|
|
593
|
+
(Auto: {getCalculatedEndTime()})
|
|
594
|
+
</span>
|
|
595
|
+
)}
|
|
596
|
+
</div>
|
|
597
|
+
)}
|
|
598
|
+
</div>
|
|
599
|
+
|
|
600
|
+
{/* Resource Selection - Inline with edit */}
|
|
601
|
+
{selectedZone && resourceInstances.length > 0 && (
|
|
602
|
+
<div className="flex flex-col gap-3">
|
|
603
|
+
<h3 className="text-label-primary font-semibold">Resource</h3>
|
|
604
|
+
|
|
605
|
+
{selectedResourceKey && !isEditingResource ? (
|
|
606
|
+
// Compact view with edit button
|
|
607
|
+
<div className="flex items-center justify-between p-3 bg-surface-secondary rounded-lg border border-border-primary">
|
|
608
|
+
<span className="text-label-primary">
|
|
609
|
+
{resourceInstances.find(r => r.uniqueKey === selectedResourceKey)?.displayName}
|
|
610
|
+
</span>
|
|
611
|
+
<Button
|
|
612
|
+
type="button"
|
|
613
|
+
variant="ghost"
|
|
614
|
+
size="sm"
|
|
615
|
+
onClick={() => setIsEditingResource(true)}
|
|
616
|
+
className="text-label-action hover:text-label-action-hover"
|
|
617
|
+
>
|
|
618
|
+
Edit
|
|
619
|
+
</Button>
|
|
620
|
+
</div>
|
|
621
|
+
) : (
|
|
622
|
+
// Full selection view
|
|
623
|
+
<div className="flex flex-col gap-2">
|
|
624
|
+
<RadioGroup
|
|
625
|
+
value={selectedResourceKey}
|
|
626
|
+
onValueChange={(value) => {
|
|
627
|
+
setSelectedResourceKey(value);
|
|
628
|
+
setIsEditingResource(false);
|
|
629
|
+
}}
|
|
630
|
+
className="grid grid-cols-2 gap-2"
|
|
631
|
+
>
|
|
632
|
+
{resourceInstances.map((instance) => (
|
|
633
|
+
<div
|
|
634
|
+
key={instance.uniqueKey}
|
|
635
|
+
className={`flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${
|
|
636
|
+
selectedResourceKey === instance.uniqueKey
|
|
637
|
+
? 'border-border-action bg-surface-action-soft'
|
|
638
|
+
: 'border-border-primary hover:border-border-secondary'
|
|
639
|
+
}`}
|
|
640
|
+
onClick={() => {
|
|
641
|
+
setSelectedResourceKey(instance.uniqueKey);
|
|
642
|
+
setIsEditingResource(false);
|
|
643
|
+
}}
|
|
644
|
+
>
|
|
645
|
+
<RadioGroupItem value={instance.uniqueKey} id={`panel-${instance.uniqueKey}`} />
|
|
646
|
+
<Label htmlFor={`panel-${instance.uniqueKey}`} className="text-label-primary cursor-pointer flex-1 text-sm">
|
|
647
|
+
{instance.displayName}
|
|
648
|
+
</Label>
|
|
649
|
+
</div>
|
|
650
|
+
))}
|
|
651
|
+
</RadioGroup>
|
|
652
|
+
{selectedResourceKey && (
|
|
653
|
+
<Button
|
|
654
|
+
type="button"
|
|
655
|
+
variant="ghost"
|
|
656
|
+
size="sm"
|
|
657
|
+
onClick={() => setIsEditingResource(false)}
|
|
658
|
+
className="self-start text-label-secondary"
|
|
659
|
+
>
|
|
660
|
+
Cancel
|
|
661
|
+
</Button>
|
|
662
|
+
)}
|
|
663
|
+
</div>
|
|
664
|
+
)}
|
|
665
|
+
</div>
|
|
666
|
+
)}
|
|
667
|
+
|
|
668
|
+
{/* Activity Selection - Show when zone is selected */}
|
|
669
|
+
{selectedZoneId && (
|
|
670
|
+
<div className="flex flex-col gap-4">
|
|
671
|
+
<h3 className="text-label-primary font-semibold">Activity</h3>
|
|
672
|
+
|
|
673
|
+
{activities.length > 0 ? (
|
|
674
|
+
<>
|
|
675
|
+
<Select value={selectedActivityId} onValueChange={handleActivitySelect}>
|
|
676
|
+
<SelectTrigger className="w-full">
|
|
677
|
+
<SelectValue placeholder="Select an activity" />
|
|
678
|
+
</SelectTrigger>
|
|
679
|
+
<SelectContent>
|
|
680
|
+
{activities.map((activity) => (
|
|
681
|
+
<SelectItem key={activity.id} value={activity.id}>
|
|
682
|
+
<div className="flex items-center justify-between w-full gap-4">
|
|
683
|
+
<span>{activity.name}</span>
|
|
684
|
+
<span className="text-label-tertiary text-xs">
|
|
685
|
+
{activity.price !== null && `£${activity.price.toFixed(2)}`}
|
|
686
|
+
{activity.duration !== null && ` • ${activity.duration} mins`}
|
|
687
|
+
</span>
|
|
688
|
+
</div>
|
|
689
|
+
</SelectItem>
|
|
690
|
+
))}
|
|
691
|
+
</SelectContent>
|
|
692
|
+
</Select>
|
|
693
|
+
|
|
694
|
+
{/* Tier quantities - show when activity has multiple tiers */}
|
|
695
|
+
{tierQuantities.length > 0 && (
|
|
696
|
+
<div className="flex flex-col gap-3 p-3 bg-surface-secondary rounded-lg border border-border-primary">
|
|
697
|
+
<Label className="text-label-secondary text-sm">Quantity per tier</Label>
|
|
698
|
+
{tierQuantities.map((tier, index) => (
|
|
699
|
+
<div key={index} className="flex items-center justify-between gap-3">
|
|
700
|
+
<div className="flex-1">
|
|
701
|
+
<span className="text-label-primary text-sm">{tier.tierName}</span>
|
|
702
|
+
<span className="text-label-tertiary text-xs ml-2">£{tier.tierPrice.toFixed(2)}</span>
|
|
703
|
+
</div>
|
|
704
|
+
<div className="flex items-center gap-1">
|
|
705
|
+
<Button
|
|
706
|
+
type="button"
|
|
707
|
+
variant="outline"
|
|
708
|
+
size="icon"
|
|
709
|
+
className="h-8 w-8 shrink-0"
|
|
710
|
+
onClick={() => handleTierQuantityChange(index, tier.quantity - 1)}
|
|
711
|
+
disabled={tier.quantity <= 0}
|
|
712
|
+
>
|
|
713
|
+
<span className="text-sm">−</span>
|
|
714
|
+
</Button>
|
|
715
|
+
<Input
|
|
716
|
+
type="number"
|
|
717
|
+
min="0"
|
|
718
|
+
value={tier.quantity}
|
|
719
|
+
onChange={(e) => handleTierQuantityChange(index, parseInt(e.target.value) || 0)}
|
|
720
|
+
className="text-center w-14 h-8"
|
|
721
|
+
/>
|
|
722
|
+
<Button
|
|
723
|
+
type="button"
|
|
724
|
+
variant="outline"
|
|
725
|
+
size="icon"
|
|
726
|
+
className="h-8 w-8 shrink-0"
|
|
727
|
+
onClick={() => handleTierQuantityChange(index, tier.quantity + 1)}
|
|
728
|
+
>
|
|
729
|
+
<span className="text-sm">+</span>
|
|
730
|
+
</Button>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
))}
|
|
734
|
+
<div className="flex justify-between pt-2 border-t border-border-primary">
|
|
735
|
+
<span className="text-label-secondary text-sm">Total</span>
|
|
736
|
+
<span className="text-label-primary font-semibold">
|
|
737
|
+
{getTotalQuantity()} × £{parseFloat(totalAmount || '0').toFixed(2)}
|
|
738
|
+
</span>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
)}
|
|
742
|
+
|
|
743
|
+
{/* Simple qty picker - show when activity has no tiers */}
|
|
744
|
+
{selectedActivityId && tierQuantities.length === 0 && (
|
|
745
|
+
<div className="flex flex-col gap-2">
|
|
746
|
+
<Label className="text-label-primary">Quantity</Label>
|
|
747
|
+
<div className="flex items-center gap-1 w-fit">
|
|
748
|
+
<Button
|
|
749
|
+
type="button"
|
|
750
|
+
variant="outline"
|
|
751
|
+
size="icon"
|
|
752
|
+
className="h-10 w-10 shrink-0"
|
|
753
|
+
onClick={() => handleQuantityChange(quantity - 1)}
|
|
754
|
+
disabled={quantity <= 1}
|
|
755
|
+
>
|
|
756
|
+
<span className="text-lg">−</span>
|
|
757
|
+
</Button>
|
|
758
|
+
<Input
|
|
759
|
+
type="text"
|
|
760
|
+
inputMode="numeric"
|
|
761
|
+
pattern="[0-9]*"
|
|
762
|
+
value={quantity}
|
|
763
|
+
onChange={(e) => handleQuantityChange(parseInt(e.target.value) || 1)}
|
|
764
|
+
className="text-center w-16 [appearance:textfield]"
|
|
765
|
+
/>
|
|
766
|
+
<Button
|
|
767
|
+
type="button"
|
|
768
|
+
variant="outline"
|
|
769
|
+
size="icon"
|
|
770
|
+
className="h-10 w-10 shrink-0"
|
|
771
|
+
onClick={() => handleQuantityChange(quantity + 1)}
|
|
772
|
+
>
|
|
773
|
+
<span className="text-lg">+</span>
|
|
774
|
+
</Button>
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
)}
|
|
778
|
+
</>
|
|
779
|
+
) : (
|
|
780
|
+
// No activities - show custom input with qty
|
|
781
|
+
<>
|
|
782
|
+
<div className="flex flex-col gap-2">
|
|
783
|
+
<Label htmlFor="panel-activityName" className="text-label-primary">Activity Name</Label>
|
|
784
|
+
<Input
|
|
785
|
+
id="panel-activityName"
|
|
786
|
+
value={activityName}
|
|
787
|
+
onChange={(e) => setActivityName(e.target.value)}
|
|
788
|
+
|
|
789
|
+
/>
|
|
790
|
+
</div>
|
|
791
|
+
|
|
792
|
+
{/* Simple qty picker for custom activities */}
|
|
793
|
+
<div className="flex flex-col gap-2">
|
|
794
|
+
<Label className="text-label-primary">Quantity</Label>
|
|
795
|
+
<div className="flex items-center gap-1 w-fit">
|
|
796
|
+
<Button
|
|
797
|
+
type="button"
|
|
798
|
+
variant="outline"
|
|
799
|
+
size="icon"
|
|
800
|
+
className="h-10 w-10 shrink-0"
|
|
801
|
+
onClick={() => handleQuantityChange(quantity - 1)}
|
|
802
|
+
disabled={quantity <= 1}
|
|
803
|
+
>
|
|
804
|
+
<span className="text-lg">−</span>
|
|
805
|
+
</Button>
|
|
806
|
+
<Input
|
|
807
|
+
type="text"
|
|
808
|
+
inputMode="numeric"
|
|
809
|
+
pattern="[0-9]*"
|
|
810
|
+
value={quantity}
|
|
811
|
+
onChange={(e) => handleQuantityChange(parseInt(e.target.value) || 1)}
|
|
812
|
+
className="text-center w-16 [appearance:textfield]"
|
|
813
|
+
/>
|
|
814
|
+
<Button
|
|
815
|
+
type="button"
|
|
816
|
+
variant="outline"
|
|
817
|
+
size="icon"
|
|
818
|
+
className="h-10 w-10 shrink-0"
|
|
819
|
+
onClick={() => handleQuantityChange(quantity + 1)}
|
|
820
|
+
>
|
|
821
|
+
<span className="text-lg">+</span>
|
|
822
|
+
</Button>
|
|
823
|
+
</div>
|
|
824
|
+
</div>
|
|
825
|
+
</>
|
|
826
|
+
)}
|
|
827
|
+
</div>
|
|
828
|
+
)}
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
<div className="flex flex-col gap-4">
|
|
832
|
+
<h3 className="text-label-primary font-semibold">Financials</h3>
|
|
833
|
+
|
|
834
|
+
<div className="flex flex-col gap-2">
|
|
835
|
+
<Label htmlFor="panel-totalAmount" className="text-label-primary">Total Amount (£)</Label>
|
|
836
|
+
<Input
|
|
837
|
+
id="panel-totalAmount"
|
|
838
|
+
type="number"
|
|
839
|
+
step="0.01"
|
|
840
|
+
min="0"
|
|
841
|
+
value={totalAmount}
|
|
842
|
+
onChange={(e) => setTotalAmount(e.target.value)}
|
|
843
|
+
|
|
844
|
+
className="max-w-[200px]"
|
|
845
|
+
/>
|
|
846
|
+
</div>
|
|
847
|
+
</div>
|
|
848
|
+
|
|
849
|
+
{/* Customer Details - Optional */}
|
|
850
|
+
<div className="flex flex-col gap-4">
|
|
851
|
+
<h3 className="text-label-primary font-semibold">Customer Details (Optional)</h3>
|
|
852
|
+
|
|
853
|
+
<div className="grid grid-cols-2 gap-4">
|
|
854
|
+
<div className="flex flex-col gap-2">
|
|
855
|
+
<Label htmlFor="panel-customerFirstName" className="text-label-primary">First Name</Label>
|
|
856
|
+
<Input
|
|
857
|
+
id="panel-customerFirstName"
|
|
858
|
+
value={customerFirstName}
|
|
859
|
+
onChange={(e) => setCustomerFirstName(e.target.value)}
|
|
860
|
+
|
|
861
|
+
/>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
<div className="flex flex-col gap-2">
|
|
865
|
+
<Label htmlFor="panel-customerLastName" className="text-label-primary">Last Name</Label>
|
|
866
|
+
<Input
|
|
867
|
+
id="panel-customerLastName"
|
|
868
|
+
value={customerLastName}
|
|
869
|
+
onChange={(e) => setCustomerLastName(e.target.value)}
|
|
870
|
+
|
|
871
|
+
/>
|
|
872
|
+
</div>
|
|
873
|
+
|
|
874
|
+
<div className="flex flex-col gap-2">
|
|
875
|
+
<Label htmlFor="panel-customerEmail" className="text-label-primary">Email</Label>
|
|
876
|
+
<Input
|
|
877
|
+
id="panel-customerEmail"
|
|
878
|
+
type="email"
|
|
879
|
+
value={customerEmail}
|
|
880
|
+
onChange={(e) => setCustomerEmail(e.target.value)}
|
|
881
|
+
|
|
882
|
+
/>
|
|
883
|
+
</div>
|
|
884
|
+
|
|
885
|
+
<div className="flex flex-col gap-2">
|
|
886
|
+
<Label htmlFor="panel-customerPhone" className="text-label-primary">Phone</Label>
|
|
887
|
+
<Input
|
|
888
|
+
id="panel-customerPhone"
|
|
889
|
+
type="tel"
|
|
890
|
+
value={customerPhone}
|
|
891
|
+
onChange={(e) => setCustomerPhone(e.target.value)}
|
|
892
|
+
|
|
893
|
+
/>
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
</div>
|
|
898
|
+
) : (
|
|
899
|
+
<div className="flex flex-col gap-6">
|
|
900
|
+
{/* Payment Method Selection */}
|
|
901
|
+
<div className="flex flex-col gap-4">
|
|
902
|
+
<h3 className="text-label-primary font-semibold">Payment Method</h3>
|
|
903
|
+
<RadioGroup
|
|
904
|
+
value={paymentMethod}
|
|
905
|
+
onValueChange={(value) => setPaymentMethod(value as 'cash' | 'card' | 'payment_link')}
|
|
906
|
+
className="flex flex-col gap-2"
|
|
907
|
+
>
|
|
908
|
+
<div
|
|
909
|
+
className={`flex items-center gap-3 p-4 rounded-lg border cursor-pointer transition-colors ${
|
|
910
|
+
paymentMethod === 'cash'
|
|
911
|
+
? 'border-border-action bg-surface-action-soft'
|
|
912
|
+
: 'border-border-primary hover:border-border-secondary'
|
|
913
|
+
}`}
|
|
914
|
+
onClick={() => setPaymentMethod('cash')}
|
|
915
|
+
>
|
|
916
|
+
<RadioGroupItem value="cash" id="panel-cash" />
|
|
917
|
+
<div className="flex flex-col flex-1">
|
|
918
|
+
<Label htmlFor="panel-cash" className="text-label-primary cursor-pointer font-medium">
|
|
919
|
+
Cash
|
|
920
|
+
</Label>
|
|
921
|
+
<span className="text-label-secondary text-sm">
|
|
922
|
+
Complete booking and add to till manually
|
|
923
|
+
</span>
|
|
924
|
+
</div>
|
|
925
|
+
</div>
|
|
926
|
+
|
|
927
|
+
<div
|
|
928
|
+
className={`flex items-center gap-3 p-4 rounded-lg border cursor-pointer transition-colors ${
|
|
929
|
+
paymentMethod === 'card'
|
|
930
|
+
? 'border-border-action bg-surface-action-soft'
|
|
931
|
+
: 'border-border-primary hover:border-border-secondary'
|
|
932
|
+
}`}
|
|
933
|
+
onClick={() => setPaymentMethod('card')}
|
|
934
|
+
>
|
|
935
|
+
<RadioGroupItem value="card" id="panel-card" />
|
|
936
|
+
<div className="flex flex-col flex-1">
|
|
937
|
+
<Label htmlFor="panel-card" className="text-label-primary cursor-pointer font-medium">
|
|
938
|
+
Card
|
|
939
|
+
</Label>
|
|
940
|
+
<span className="text-label-secondary text-sm">
|
|
941
|
+
Send payment to PDQ machine
|
|
942
|
+
</span>
|
|
943
|
+
</div>
|
|
944
|
+
</div>
|
|
945
|
+
|
|
946
|
+
<div
|
|
947
|
+
className={`flex items-center gap-3 p-4 rounded-lg border cursor-pointer transition-colors ${
|
|
948
|
+
paymentMethod === 'payment_link'
|
|
949
|
+
? 'border-border-action bg-surface-action-soft'
|
|
950
|
+
: 'border-border-primary hover:border-border-secondary'
|
|
951
|
+
}`}
|
|
952
|
+
onClick={() => setPaymentMethod('payment_link')}
|
|
953
|
+
>
|
|
954
|
+
<RadioGroupItem value="payment_link" id="panel-payment_link" />
|
|
955
|
+
<div className="flex flex-col flex-1">
|
|
956
|
+
<Label htmlFor="panel-payment_link" className="text-label-primary cursor-pointer font-medium">
|
|
957
|
+
Payment Link
|
|
958
|
+
</Label>
|
|
959
|
+
<span className="text-label-secondary text-sm">
|
|
960
|
+
Send payment link via email - booking pending until paid
|
|
961
|
+
</span>
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
</RadioGroup>
|
|
965
|
+
</div>
|
|
966
|
+
|
|
967
|
+
{/* Payment Link Email Field */}
|
|
968
|
+
{paymentMethod === 'payment_link' && (
|
|
969
|
+
<div className="flex flex-col gap-2">
|
|
970
|
+
<Label htmlFor="panel-paymentLinkEmail" className="text-label-primary">
|
|
971
|
+
Email Address *
|
|
972
|
+
</Label>
|
|
973
|
+
<Input
|
|
974
|
+
id="panel-paymentLinkEmail"
|
|
975
|
+
type="email"
|
|
976
|
+
value={paymentLinkEmail}
|
|
977
|
+
onChange={(e) => setPaymentLinkEmail(e.target.value)}
|
|
978
|
+
|
|
979
|
+
/>
|
|
980
|
+
<p className="text-label-secondary text-sm">
|
|
981
|
+
Payment link will be sent to this email address
|
|
982
|
+
</p>
|
|
983
|
+
</div>
|
|
984
|
+
)}
|
|
985
|
+
|
|
986
|
+
{/* Summary */}
|
|
987
|
+
<div className="flex flex-col gap-3 p-4 bg-surface-secondary rounded-lg border border-border-primary">
|
|
988
|
+
<h3 className="text-label-primary font-semibold">Summary</h3>
|
|
989
|
+
{zoneName && (
|
|
990
|
+
<div className="flex justify-between text-sm">
|
|
991
|
+
<span className="text-label-secondary">Zone</span>
|
|
992
|
+
<span className="text-label-primary">{zoneName}</span>
|
|
993
|
+
</div>
|
|
994
|
+
)}
|
|
995
|
+
{selectedResourceKey && (
|
|
996
|
+
<div className="flex justify-between text-sm">
|
|
997
|
+
<span className="text-label-secondary">Resource</span>
|
|
998
|
+
<span className="text-label-primary">{resourceInstances.find(r => r.uniqueKey === selectedResourceKey)?.displayName}</span>
|
|
999
|
+
</div>
|
|
1000
|
+
)}
|
|
1001
|
+
{activityName && (
|
|
1002
|
+
<div className="flex justify-between text-sm">
|
|
1003
|
+
<span className="text-label-secondary">Activity</span>
|
|
1004
|
+
<span className="text-label-primary">{activityName}</span>
|
|
1005
|
+
</div>
|
|
1006
|
+
)}
|
|
1007
|
+
<div className="flex justify-between text-sm">
|
|
1008
|
+
<span className="text-label-secondary">Date & Time</span>
|
|
1009
|
+
<span className="text-label-primary">
|
|
1010
|
+
{bookingDate} at {bookingTimeStart}
|
|
1011
|
+
{getCalculatedEndTime() && ` - ${getCalculatedEndTime()}`}
|
|
1012
|
+
</span>
|
|
1013
|
+
</div>
|
|
1014
|
+
{quantity > 1 && (
|
|
1015
|
+
<div className="flex justify-between text-sm">
|
|
1016
|
+
<span className="text-label-secondary">Quantity</span>
|
|
1017
|
+
<span className="text-label-primary">{quantity}</span>
|
|
1018
|
+
</div>
|
|
1019
|
+
)}
|
|
1020
|
+
{totalAmount && (
|
|
1021
|
+
<div className="flex justify-between text-sm">
|
|
1022
|
+
<span className="text-label-secondary">Total</span>
|
|
1023
|
+
<span className="text-label-primary font-semibold">£{parseFloat(totalAmount).toFixed(2)}</span>
|
|
1024
|
+
</div>
|
|
1025
|
+
)}
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
)}
|
|
1029
|
+
</div>
|
|
1030
|
+
</ScrollArea>
|
|
1031
|
+
|
|
1032
|
+
{/* Footer with buttons */}
|
|
1033
|
+
<div className="p-4 border-t border-border-primary shrink-0">
|
|
1034
|
+
{step === 'details' ? (
|
|
1035
|
+
<div className="flex gap-3">
|
|
1036
|
+
<Button onClick={handleNext} className="flex-1">
|
|
1037
|
+
Next
|
|
1038
|
+
</Button>
|
|
1039
|
+
<Button variant="outline" onClick={onClose}>
|
|
1040
|
+
Cancel
|
|
1041
|
+
</Button>
|
|
1042
|
+
</div>
|
|
1043
|
+
) : (
|
|
1044
|
+
<div className="flex gap-3">
|
|
1045
|
+
<Button onClick={handleSubmit} disabled={isSaving} className="flex-1">
|
|
1046
|
+
{isSaving ? 'Saving...' : 'Save & Add'}
|
|
1047
|
+
</Button>
|
|
1048
|
+
<Button variant="outline" onClick={handleBack}>
|
|
1049
|
+
Back
|
|
1050
|
+
</Button>
|
|
1051
|
+
</div>
|
|
1052
|
+
)}
|
|
1053
|
+
</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
</>
|
|
1056
|
+
);
|
|
1057
|
+
};
|