@licklist/design 0.78.5-dev.105 → 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.
Files changed (371) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/bitbucket-pipelines.yml +13 -4
  3. package/dist/index.js +1 -1
  4. package/dist/product-set/form/ProductsControl.d.ts +2 -1
  5. package/dist/product-set/form/ProductsControl.d.ts.map +1 -1
  6. package/dist/product-set/form/ProductsControl.js +0 -24
  7. package/dist/v2/components/DataTable/DataTable.d.ts.map +1 -1
  8. package/dist/v2/components/DataTable/DataTable.js +86 -2
  9. package/dist/v2/components/InputCheckbox/InputCheckbox.scss.js +1 -1
  10. package/dist/v2/components/Modal/DeleteModal.d.ts.map +1 -1
  11. package/dist/v2/components/Modal/DeleteModal.js +13 -11
  12. package/dist/v2/components/Toggle/Toggle.d.ts.map +1 -1
  13. package/dist/v2/components/Toggle/Toggle.js +8 -5
  14. package/dist/v2/components/ZoneCard/ZoneCard.scss.js +1 -1
  15. package/dist/v2/components/index.d.ts +1 -1
  16. package/dist/v2/components/index.d.ts.map +1 -1
  17. package/dist/v2/icons/index.d.ts +4 -0
  18. package/dist/v2/icons/index.d.ts.map +1 -1
  19. package/dist/v2/icons/index.js +47 -16
  20. package/dist/v2/index.d.ts +0 -4
  21. package/dist/v2/index.d.ts.map +1 -1
  22. package/dist/v2/pages/Settings/components/SidebarCustomisation.js +0 -3
  23. package/dist/v2/pages/Settings/components/SidebarNavItem.js +0 -3
  24. package/package.json +6 -6
  25. package/rollup.config.js +16 -2
  26. package/src/iframe/payment/payment-status-page/PaymentStatusPage.tsx +1 -1
  27. package/src/product-set/form/ProductsControl.tsx +2 -1
  28. package/src/v2/components/DataTable/DataTable.tsx +23 -1
  29. package/src/v2/components/InputCheckbox/InputCheckbox.scss +6 -6
  30. package/src/v2/components/Modal/DeleteModal.tsx +12 -20
  31. package/src/v2/components/Toggle/Toggle.tsx +6 -5
  32. package/src/v2/components/ZoneCard/ZoneCard.scss +78 -0
  33. package/src/v2/components/index.ts +1 -0
  34. package/src/v2/icons/index.tsx +10 -0
  35. package/src/v2/index.ts +0 -73
  36. package/src/v2/navigation/Navigation/Navigation.stories.tsx +137 -0
  37. package/dist/v2/shadcn/components/ui/accordion.d.ts +0 -8
  38. package/dist/v2/shadcn/components/ui/accordion.d.ts.map +0 -1
  39. package/dist/v2/shadcn/components/ui/alert-dialog.d.ts +0 -21
  40. package/dist/v2/shadcn/components/ui/alert-dialog.d.ts.map +0 -1
  41. package/dist/v2/shadcn/components/ui/alert.d.ts +0 -9
  42. package/dist/v2/shadcn/components/ui/alert.d.ts.map +0 -1
  43. package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts +0 -4
  44. package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts.map +0 -1
  45. package/dist/v2/shadcn/components/ui/avatar.d.ts +0 -7
  46. package/dist/v2/shadcn/components/ui/avatar.d.ts.map +0 -1
  47. package/dist/v2/shadcn/components/ui/badge.d.ts +0 -10
  48. package/dist/v2/shadcn/components/ui/badge.d.ts.map +0 -1
  49. package/dist/v2/shadcn/components/ui/breadcrumb.d.ts +0 -20
  50. package/dist/v2/shadcn/components/ui/breadcrumb.d.ts.map +0 -1
  51. package/dist/v2/shadcn/components/ui/button.d.ts +0 -14
  52. package/dist/v2/shadcn/components/ui/button.d.ts.map +0 -1
  53. package/dist/v2/shadcn/components/ui/calendar.d.ts +0 -9
  54. package/dist/v2/shadcn/components/ui/calendar.d.ts.map +0 -1
  55. package/dist/v2/shadcn/components/ui/card.d.ts +0 -9
  56. package/dist/v2/shadcn/components/ui/card.d.ts.map +0 -1
  57. package/dist/v2/shadcn/components/ui/carousel.d.ts +0 -19
  58. package/dist/v2/shadcn/components/ui/carousel.d.ts.map +0 -1
  59. package/dist/v2/shadcn/components/ui/checkbox.d.ts +0 -6
  60. package/dist/v2/shadcn/components/ui/checkbox.d.ts.map +0 -1
  61. package/dist/v2/shadcn/components/ui/checkbox.js +0 -115
  62. package/dist/v2/shadcn/components/ui/checkbox.scss.js +0 -6
  63. package/dist/v2/shadcn/components/ui/collapsible.d.ts +0 -6
  64. package/dist/v2/shadcn/components/ui/collapsible.d.ts.map +0 -1
  65. package/dist/v2/shadcn/components/ui/command.d.ts +0 -83
  66. package/dist/v2/shadcn/components/ui/command.d.ts.map +0 -1
  67. package/dist/v2/shadcn/components/ui/context-menu.d.ts +0 -28
  68. package/dist/v2/shadcn/components/ui/context-menu.d.ts.map +0 -1
  69. package/dist/v2/shadcn/components/ui/dialog.d.ts +0 -20
  70. package/dist/v2/shadcn/components/ui/dialog.d.ts.map +0 -1
  71. package/dist/v2/shadcn/components/ui/dialog.js +0 -169
  72. package/dist/v2/shadcn/components/ui/drawer.d.ts +0 -23
  73. package/dist/v2/shadcn/components/ui/drawer.d.ts.map +0 -1
  74. package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts +0 -28
  75. package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts.map +0 -1
  76. package/dist/v2/shadcn/components/ui/form.d.ts +0 -24
  77. package/dist/v2/shadcn/components/ui/form.d.ts.map +0 -1
  78. package/dist/v2/shadcn/components/ui/hover-card.d.ts +0 -7
  79. package/dist/v2/shadcn/components/ui/hover-card.d.ts.map +0 -1
  80. package/dist/v2/shadcn/components/ui/input-otp.d.ts +0 -35
  81. package/dist/v2/shadcn/components/ui/input-otp.d.ts.map +0 -1
  82. package/dist/v2/shadcn/components/ui/input.d.ts +0 -6
  83. package/dist/v2/shadcn/components/ui/input.d.ts.map +0 -1
  84. package/dist/v2/shadcn/components/ui/label.d.ts +0 -6
  85. package/dist/v2/shadcn/components/ui/label.d.ts.map +0 -1
  86. package/dist/v2/shadcn/components/ui/menubar.d.ts +0 -34
  87. package/dist/v2/shadcn/components/ui/menubar.d.ts.map +0 -1
  88. package/dist/v2/shadcn/components/ui/navigation-menu.d.ts +0 -13
  89. package/dist/v2/shadcn/components/ui/navigation-menu.d.ts.map +0 -1
  90. package/dist/v2/shadcn/components/ui/pagination.d.ts +0 -29
  91. package/dist/v2/shadcn/components/ui/pagination.d.ts.map +0 -1
  92. package/dist/v2/shadcn/components/ui/popover.d.ts +0 -7
  93. package/dist/v2/shadcn/components/ui/popover.d.ts.map +0 -1
  94. package/dist/v2/shadcn/components/ui/progress.d.ts +0 -5
  95. package/dist/v2/shadcn/components/ui/progress.d.ts.map +0 -1
  96. package/dist/v2/shadcn/components/ui/radio-card.d.ts +0 -12
  97. package/dist/v2/shadcn/components/ui/radio-card.d.ts.map +0 -1
  98. package/dist/v2/shadcn/components/ui/radio-group.d.ts +0 -6
  99. package/dist/v2/shadcn/components/ui/radio-group.d.ts.map +0 -1
  100. package/dist/v2/shadcn/components/ui/scroll-area.d.ts +0 -6
  101. package/dist/v2/shadcn/components/ui/scroll-area.d.ts.map +0 -1
  102. package/dist/v2/shadcn/components/ui/select.d.ts +0 -14
  103. package/dist/v2/shadcn/components/ui/select.d.ts.map +0 -1
  104. package/dist/v2/shadcn/components/ui/separator.d.ts +0 -5
  105. package/dist/v2/shadcn/components/ui/separator.d.ts.map +0 -1
  106. package/dist/v2/shadcn/components/ui/sheet.d.ts +0 -26
  107. package/dist/v2/shadcn/components/ui/sheet.d.ts.map +0 -1
  108. package/dist/v2/shadcn/components/ui/sidebar.d.ts +0 -67
  109. package/dist/v2/shadcn/components/ui/sidebar.d.ts.map +0 -1
  110. package/dist/v2/shadcn/components/ui/skeleton.d.ts +0 -3
  111. package/dist/v2/shadcn/components/ui/skeleton.d.ts.map +0 -1
  112. package/dist/v2/shadcn/components/ui/slider.d.ts +0 -5
  113. package/dist/v2/shadcn/components/ui/slider.d.ts.map +0 -1
  114. package/dist/v2/shadcn/components/ui/switch.d.ts +0 -6
  115. package/dist/v2/shadcn/components/ui/switch.d.ts.map +0 -1
  116. package/dist/v2/shadcn/components/ui/switch.js +0 -115
  117. package/dist/v2/shadcn/components/ui/switch.scss.js +0 -6
  118. package/dist/v2/shadcn/components/ui/table-pagination.d.ts +0 -11
  119. package/dist/v2/shadcn/components/ui/table-pagination.d.ts.map +0 -1
  120. package/dist/v2/shadcn/components/ui/table.d.ts +0 -11
  121. package/dist/v2/shadcn/components/ui/table.d.ts.map +0 -1
  122. package/dist/v2/shadcn/components/ui/tabs.d.ts +0 -8
  123. package/dist/v2/shadcn/components/ui/tabs.d.ts.map +0 -1
  124. package/dist/v2/shadcn/components/ui/textarea.d.ts +0 -6
  125. package/dist/v2/shadcn/components/ui/textarea.d.ts.map +0 -1
  126. package/dist/v2/shadcn/components/ui/toast.d.ts +0 -16
  127. package/dist/v2/shadcn/components/ui/toast.d.ts.map +0 -1
  128. package/dist/v2/shadcn/components/ui/toaster.d.ts +0 -2
  129. package/dist/v2/shadcn/components/ui/toaster.d.ts.map +0 -1
  130. package/dist/v2/shadcn/components/ui/toggle-group.d.ts +0 -13
  131. package/dist/v2/shadcn/components/ui/toggle-group.d.ts.map +0 -1
  132. package/dist/v2/shadcn/components/ui/toggle.d.ts +0 -13
  133. package/dist/v2/shadcn/components/ui/toggle.d.ts.map +0 -1
  134. package/dist/v2/shadcn/components/ui/tooltip.d.ts +0 -8
  135. package/dist/v2/shadcn/components/ui/tooltip.d.ts.map +0 -1
  136. package/dist/v2/shadcn/components/ui/use-toast.d.ts +0 -3
  137. package/dist/v2/shadcn/components/ui/use-toast.d.ts.map +0 -1
  138. package/dist/v2/shadcn/hooks/use-mobile.d.ts +0 -2
  139. package/dist/v2/shadcn/hooks/use-mobile.d.ts.map +0 -1
  140. package/dist/v2/shadcn/hooks/use-toast.d.ts +0 -45
  141. package/dist/v2/shadcn/hooks/use-toast.d.ts.map +0 -1
  142. package/dist/v2/shadcn/index.d.ts +0 -20
  143. package/dist/v2/shadcn/index.d.ts.map +0 -1
  144. package/dist/v2/shadcn/lib/utils.d.ts +0 -3
  145. package/dist/v2/shadcn/lib/utils.d.ts.map +0 -1
  146. package/dist/v2/shadcn/lib/utils.js +0 -11
  147. package/dist/v2/shadcn/styles/globals.css +0 -112
  148. package/src/v2/shadcn/_reference/AccountManagerCard.tsx +0 -45
  149. package/src/v2/shadcn/_reference/AffiliatesTable.tsx +0 -178
  150. package/src/v2/shadcn/_reference/AuditArchive.tsx +0 -165
  151. package/src/v2/shadcn/_reference/AuditContent.tsx +0 -270
  152. package/src/v2/shadcn/_reference/AutomationsGeneralSettings.tsx +0 -251
  153. package/src/v2/shadcn/_reference/AvatarUpload.tsx +0 -150
  154. package/src/v2/shadcn/_reference/BookingsSummaryCard.tsx +0 -268
  155. package/src/v2/shadcn/_reference/CodeCleanUpAudit.tsx +0 -274
  156. package/src/v2/shadcn/_reference/CompaniesTable.tsx +0 -387
  157. package/src/v2/shadcn/_reference/ComponentAudit.tsx +0 -239
  158. package/src/v2/shadcn/_reference/ConfigureSettingsCard.tsx +0 -95
  159. package/src/v2/shadcn/_reference/CustomerCard.tsx +0 -155
  160. package/src/v2/shadcn/_reference/DashboardCards.tsx +0 -50
  161. package/src/v2/shadcn/_reference/DashboardFooter.tsx +0 -18
  162. package/src/v2/shadcn/_reference/DiarySettings.tsx +0 -187
  163. package/src/v2/shadcn/_reference/DiaryView.tsx +0 -998
  164. package/src/v2/shadcn/_reference/EmptyState.tsx +0 -76
  165. package/src/v2/shadcn/_reference/EntityInfoCard.tsx +0 -48
  166. package/src/v2/shadcn/_reference/ExistingUserAssignments.tsx +0 -131
  167. package/src/v2/shadcn/_reference/FeatureToggle.tsx +0 -72
  168. package/src/v2/shadcn/_reference/FlowCard.tsx +0 -170
  169. package/src/v2/shadcn/_reference/FlowsContent.tsx +0 -688
  170. package/src/v2/shadcn/_reference/FlowsGeneralSettings.tsx +0 -27
  171. package/src/v2/shadcn/_reference/GeneralSettings.tsx +0 -33
  172. package/src/v2/shadcn/_reference/InventoryGeneralSettings.tsx +0 -82
  173. package/src/v2/shadcn/_reference/LanguageSelector.tsx +0 -97
  174. package/src/v2/shadcn/_reference/LoadingScreen.tsx +0 -25
  175. package/src/v2/shadcn/_reference/LoadingSpinner.tsx +0 -41
  176. package/src/v2/shadcn/_reference/ManagedClientsList.tsx +0 -121
  177. package/src/v2/shadcn/_reference/NPSScore.tsx +0 -379
  178. package/src/v2/shadcn/_reference/NPSSummaryCard.tsx +0 -181
  179. package/src/v2/shadcn/_reference/NotificationBanner.tsx +0 -129
  180. package/src/v2/shadcn/_reference/NotificationPanel.tsx +0 -208
  181. package/src/v2/shadcn/_reference/OnlineUsersCard.tsx +0 -73
  182. package/src/v2/shadcn/_reference/ProtectedRoute.tsx +0 -39
  183. package/src/v2/shadcn/_reference/ProvidersTable.tsx +0 -353
  184. package/src/v2/shadcn/_reference/QuickAddPanel.tsx +0 -1057
  185. package/src/v2/shadcn/_reference/QuickFilters.tsx +0 -112
  186. package/src/v2/shadcn/_reference/ScheduleView.tsx +0 -410
  187. package/src/v2/shadcn/_reference/ScrollToTop.tsx +0 -14
  188. package/src/v2/shadcn/_reference/SecondaryNav.tsx +0 -50
  189. package/src/v2/shadcn/_reference/SecuritySettings.tsx +0 -258
  190. package/src/v2/shadcn/_reference/SessionDetailView.tsx +0 -294
  191. package/src/v2/shadcn/_reference/Sidebar.tsx +0 -14
  192. package/src/v2/shadcn/_reference/SidebarAwareLayout.tsx +0 -30
  193. package/src/v2/shadcn/_reference/SidebarLabelCustomization.tsx +0 -285
  194. package/src/v2/shadcn/_reference/SimulationBanner.tsx +0 -57
  195. package/src/v2/shadcn/_reference/SortControls.tsx +0 -65
  196. package/src/v2/shadcn/_reference/StatusBadge.tsx +0 -49
  197. package/src/v2/shadcn/_reference/StyleGuideContent.tsx +0 -331
  198. package/src/v2/shadcn/_reference/TableActionMenu.tsx +0 -126
  199. package/src/v2/shadcn/_reference/ThemeProvider.tsx +0 -119
  200. package/src/v2/shadcn/_reference/ThemeSettings.tsx +0 -73
  201. package/src/v2/shadcn/_reference/TopNavigation.tsx +0 -332
  202. package/src/v2/shadcn/_reference/UserActivityHistory.tsx +0 -209
  203. package/src/v2/shadcn/_reference/UserLanguageSettings.tsx +0 -94
  204. package/src/v2/shadcn/_reference/UserPanel.tsx +0 -472
  205. package/src/v2/shadcn/_reference/UsersTable.tsx +0 -1023
  206. package/src/v2/shadcn/_reference/WaiverForm.tsx +0 -301
  207. package/src/v2/shadcn/_reference/WaiversGeneralSettings.tsx +0 -46
  208. package/src/v2/shadcn/_reference/WaiversTable.tsx +0 -290
  209. package/src/v2/shadcn/_reference/WaiversTemplatesSettings.tsx +0 -416
  210. package/src/v2/shadcn/_reference/ai/AIChatPanel.tsx +0 -313
  211. package/src/v2/shadcn/_reference/ai/AIChatSearchBar.tsx +0 -36
  212. package/src/v2/shadcn/_reference/ai/ChatInteractiveBlock.tsx +0 -298
  213. package/src/v2/shadcn/_reference/ai/ChatMessageContent.tsx +0 -40
  214. package/src/v2/shadcn/_reference/ai/parseInteractiveBlocks.ts +0 -142
  215. package/src/v2/shadcn/_reference/auth/AuthLayout.tsx +0 -55
  216. package/src/v2/shadcn/_reference/auth/CreatePasswordForm.tsx +0 -285
  217. package/src/v2/shadcn/_reference/auth/CreatePasswordPanel.tsx +0 -20
  218. package/src/v2/shadcn/_reference/auth/LoginFooter.tsx +0 -14
  219. package/src/v2/shadcn/_reference/auth/LoginForm.tsx +0 -205
  220. package/src/v2/shadcn/_reference/auth/LoginPanel.tsx +0 -41
  221. package/src/v2/shadcn/_reference/auth/ResetPasswordForm.tsx +0 -102
  222. package/src/v2/shadcn/_reference/auth/ResetPasswordPanel.tsx +0 -20
  223. package/src/v2/shadcn/_reference/auth/VerifyEmailForm.tsx +0 -95
  224. package/src/v2/shadcn/_reference/auth/VerifyEmailPanel.tsx +0 -20
  225. package/src/v2/shadcn/_reference/email/EmailAttachment.tsx +0 -119
  226. package/src/v2/shadcn/_reference/email/EmailAutomation.tsx +0 -92
  227. package/src/v2/shadcn/_reference/email/EmailPlaceholders.tsx +0 -64
  228. package/src/v2/shadcn/_reference/email/UnlayerEmailEditor.tsx +0 -41
  229. package/src/v2/shadcn/_reference/email/emailTemplateData.ts +0 -53
  230. package/src/v2/shadcn/_reference/emptyStateIcons.tsx +0 -103
  231. package/src/v2/shadcn/_reference/games/MazeGame.tsx +0 -394
  232. package/src/v2/shadcn/_reference/games/RunnerGame.tsx +0 -497
  233. package/src/v2/shadcn/_reference/logos/BookedLogoFull.tsx +0 -36
  234. package/src/v2/shadcn/_reference/logos/BookedLogoMark.tsx +0 -31
  235. package/src/v2/shadcn/_reference/logos/BookedLogoNew.tsx +0 -36
  236. package/src/v2/shadcn/_reference/pricing/DynamicPricingRulesEditor.tsx +0 -401
  237. package/src/v2/shadcn/_reference/pricing/DynamicPricingTierCard.tsx +0 -77
  238. package/src/v2/shadcn/_reference/pricing/DynamicPricingTiersList.tsx +0 -218
  239. package/src/v2/shadcn/_reference/pricing/PricingCalendar.tsx +0 -810
  240. package/src/v2/shadcn/_reference/pricing/PricingPeriodCard.tsx +0 -152
  241. package/src/v2/shadcn/_reference/pricing/PricingPeriodForm.tsx +0 -377
  242. package/src/v2/shadcn/_reference/pricing/PricingPeriodsList.tsx +0 -213
  243. package/src/v2/shadcn/_reference/pricing/getRuleSummary.ts +0 -39
  244. package/src/v2/shadcn/_reference/products/AvailabilityRulesSection.tsx +0 -184
  245. package/src/v2/shadcn/_reference/products/AvailabilitySection.tsx +0 -677
  246. package/src/v2/shadcn/_reference/products/BookingTypeConfigOptions.tsx +0 -40
  247. package/src/v2/shadcn/_reference/products/CapacityPeriodsSection.tsx +0 -238
  248. package/src/v2/shadcn/_reference/products/DynamicPricingTiersSection.tsx +0 -131
  249. package/src/v2/shadcn/_reference/products/GiftCardOrdersTab.tsx +0 -192
  250. package/src/v2/shadcn/_reference/products/GiftCardSettings.tsx +0 -342
  251. package/src/v2/shadcn/_reference/products/PackageProductsSection.tsx +0 -322
  252. package/src/v2/shadcn/_reference/products/PricingSection.tsx +0 -173
  253. package/src/v2/shadcn/_reference/products/ProductTypeFields.tsx +0 -353
  254. package/src/v2/shadcn/_reference/products/ProductTypeIcon.tsx +0 -95
  255. package/src/v2/shadcn/_reference/products/VariablePricingSection.tsx +0 -140
  256. package/src/v2/shadcn/_reference/products/productTypeConfig.ts +0 -182
  257. package/src/v2/shadcn/_reference/shared/BackButton.tsx +0 -50
  258. package/src/v2/shadcn/_reference/shared/CancelConfirmationDialog.tsx +0 -18
  259. package/src/v2/shadcn/_reference/shared/ConfirmationDialog.tsx +0 -136
  260. package/src/v2/shadcn/_reference/shared/DeleteConfirmationDialog.tsx +0 -18
  261. package/src/v2/shadcn/_reference/shared/DeleteEntityPage.tsx +0 -221
  262. package/src/v2/shadcn/_reference/shared/SidebarIcons.tsx +0 -108
  263. package/src/v2/shadcn/_reference/shared/UnifiedSidebar.tsx +0 -722
  264. package/src/v2/shadcn/_reference/tables/BulkActionsBar.tsx +0 -68
  265. package/src/v2/shadcn/_reference/tables/DataTable.tsx +0 -221
  266. package/src/v2/shadcn/_reference/tables/TableControls.tsx +0 -94
  267. package/src/v2/shadcn/_reference/tables/index.ts +0 -3
  268. package/src/v2/shadcn/_reference/tables/types.ts +0 -79
  269. package/src/v2/shadcn/_reference/zones/LegacyZoneSettings.tsx +0 -299
  270. package/src/v2/shadcn/components/ui/accordion.stories.tsx +0 -63
  271. package/src/v2/shadcn/components/ui/accordion.tsx +0 -52
  272. package/src/v2/shadcn/components/ui/alert-dialog.stories.tsx +0 -44
  273. package/src/v2/shadcn/components/ui/alert-dialog.tsx +0 -104
  274. package/src/v2/shadcn/components/ui/alert.stories.tsx +0 -44
  275. package/src/v2/shadcn/components/ui/alert.tsx +0 -43
  276. package/src/v2/shadcn/components/ui/aspect-ratio.stories.tsx +0 -46
  277. package/src/v2/shadcn/components/ui/aspect-ratio.tsx +0 -5
  278. package/src/v2/shadcn/components/ui/avatar.stories.tsx +0 -39
  279. package/src/v2/shadcn/components/ui/avatar.tsx +0 -38
  280. package/src/v2/shadcn/components/ui/badge.stories.tsx +0 -17
  281. package/src/v2/shadcn/components/ui/badge.tsx +0 -30
  282. package/src/v2/shadcn/components/ui/breadcrumb.stories.tsx +0 -91
  283. package/src/v2/shadcn/components/ui/breadcrumb.tsx +0 -90
  284. package/src/v2/shadcn/components/ui/button.stories.tsx +0 -20
  285. package/src/v2/shadcn/components/ui/button.tsx +0 -60
  286. package/src/v2/shadcn/components/ui/calendar.stories.tsx +0 -61
  287. package/src/v2/shadcn/components/ui/calendar.tsx +0 -54
  288. package/src/v2/shadcn/components/ui/card.stories.tsx +0 -37
  289. package/src/v2/shadcn/components/ui/card.tsx +0 -43
  290. package/src/v2/shadcn/components/ui/carousel.stories.tsx +0 -92
  291. package/src/v2/shadcn/components/ui/carousel.tsx +0 -224
  292. package/src/v2/shadcn/components/ui/checkbox.scss +0 -38
  293. package/src/v2/shadcn/components/ui/checkbox.stories.tsx +0 -23
  294. package/src/v2/shadcn/components/ui/checkbox.tsx +0 -24
  295. package/src/v2/shadcn/components/ui/collapsible.stories.tsx +0 -59
  296. package/src/v2/shadcn/components/ui/collapsible.tsx +0 -9
  297. package/src/v2/shadcn/components/ui/command.stories.tsx +0 -70
  298. package/src/v2/shadcn/components/ui/command.tsx +0 -132
  299. package/src/v2/shadcn/components/ui/context-menu.stories.tsx +0 -72
  300. package/src/v2/shadcn/components/ui/context-menu.tsx +0 -178
  301. package/src/v2/shadcn/components/ui/dialog.stories.tsx +0 -67
  302. package/src/v2/shadcn/components/ui/dialog.tsx +0 -95
  303. package/src/v2/shadcn/components/ui/drawer.stories.tsx +0 -50
  304. package/src/v2/shadcn/components/ui/drawer.tsx +0 -87
  305. package/src/v2/shadcn/components/ui/dropdown-menu.stories.tsx +0 -73
  306. package/src/v2/shadcn/components/ui/dropdown-menu.tsx +0 -179
  307. package/src/v2/shadcn/components/ui/form.stories.tsx +0 -105
  308. package/src/v2/shadcn/components/ui/form.tsx +0 -129
  309. package/src/v2/shadcn/components/ui/hover-card.stories.tsx +0 -35
  310. package/src/v2/shadcn/components/ui/hover-card.tsx +0 -27
  311. package/src/v2/shadcn/components/ui/input-otp.stories.tsx +0 -72
  312. package/src/v2/shadcn/components/ui/input-otp.tsx +0 -61
  313. package/src/v2/shadcn/components/ui/input.stories.tsx +0 -16
  314. package/src/v2/shadcn/components/ui/input.tsx +0 -25
  315. package/src/v2/shadcn/components/ui/label.stories.tsx +0 -13
  316. package/src/v2/shadcn/components/ui/label.tsx +0 -17
  317. package/src/v2/shadcn/components/ui/menubar.stories.tsx +0 -86
  318. package/src/v2/shadcn/components/ui/menubar.tsx +0 -207
  319. package/src/v2/shadcn/components/ui/navigation-menu.stories.tsx +0 -68
  320. package/src/v2/shadcn/components/ui/navigation-menu.tsx +0 -120
  321. package/src/v2/shadcn/components/ui/pagination.stories.tsx +0 -78
  322. package/src/v2/shadcn/components/ui/pagination.tsx +0 -81
  323. package/src/v2/shadcn/components/ui/popover.stories.tsx +0 -44
  324. package/src/v2/shadcn/components/ui/popover.tsx +0 -29
  325. package/src/v2/shadcn/components/ui/progress.stories.tsx +0 -17
  326. package/src/v2/shadcn/components/ui/progress.tsx +0 -23
  327. package/src/v2/shadcn/components/ui/radio-card.stories.tsx +0 -68
  328. package/src/v2/shadcn/components/ui/radio-card.tsx +0 -52
  329. package/src/v2/shadcn/components/ui/radio-group.stories.tsx +0 -77
  330. package/src/v2/shadcn/components/ui/radio-group.tsx +0 -35
  331. package/src/v2/shadcn/components/ui/scroll-area.stories.tsx +0 -56
  332. package/src/v2/shadcn/components/ui/scroll-area.tsx +0 -38
  333. package/src/v2/shadcn/components/ui/select.stories.tsx +0 -60
  334. package/src/v2/shadcn/components/ui/select.tsx +0 -148
  335. package/src/v2/shadcn/components/ui/separator.stories.tsx +0 -30
  336. package/src/v2/shadcn/components/ui/separator.tsx +0 -20
  337. package/src/v2/shadcn/components/ui/sheet.stories.tsx +0 -115
  338. package/src/v2/shadcn/components/ui/sheet.tsx +0 -107
  339. package/src/v2/shadcn/components/ui/sidebar.stories.tsx +0 -167
  340. package/src/v2/shadcn/components/ui/sidebar.tsx +0 -637
  341. package/src/v2/shadcn/components/ui/skeleton.stories.tsx +0 -36
  342. package/src/v2/shadcn/components/ui/skeleton.tsx +0 -7
  343. package/src/v2/shadcn/components/ui/slider.stories.tsx +0 -16
  344. package/src/v2/shadcn/components/ui/slider.tsx +0 -23
  345. package/src/v2/shadcn/components/ui/switch.scss +0 -63
  346. package/src/v2/shadcn/components/ui/switch.stories.tsx +0 -23
  347. package/src/v2/shadcn/components/ui/switch.tsx +0 -24
  348. package/src/v2/shadcn/components/ui/table-pagination.stories.tsx +0 -81
  349. package/src/v2/shadcn/components/ui/table-pagination.tsx +0 -61
  350. package/src/v2/shadcn/components/ui/table.stories.tsx +0 -40
  351. package/src/v2/shadcn/components/ui/table.tsx +0 -72
  352. package/src/v2/shadcn/components/ui/tabs.stories.tsx +0 -85
  353. package/src/v2/shadcn/components/ui/tabs.tsx +0 -53
  354. package/src/v2/shadcn/components/ui/textarea.stories.tsx +0 -15
  355. package/src/v2/shadcn/components/ui/textarea.tsx +0 -21
  356. package/src/v2/shadcn/components/ui/toast.stories.tsx +0 -77
  357. package/src/v2/shadcn/components/ui/toast.tsx +0 -111
  358. package/src/v2/shadcn/components/ui/toaster.stories.tsx +0 -46
  359. package/src/v2/shadcn/components/ui/toaster.tsx +0 -24
  360. package/src/v2/shadcn/components/ui/toggle-group.stories.tsx +0 -95
  361. package/src/v2/shadcn/components/ui/toggle-group.tsx +0 -49
  362. package/src/v2/shadcn/components/ui/toggle.stories.tsx +0 -18
  363. package/src/v2/shadcn/components/ui/toggle.tsx +0 -37
  364. package/src/v2/shadcn/components/ui/tooltip.stories.tsx +0 -57
  365. package/src/v2/shadcn/components/ui/tooltip.tsx +0 -28
  366. package/src/v2/shadcn/components/ui/use-toast.ts +0 -3
  367. package/src/v2/shadcn/hooks/use-mobile.tsx +0 -19
  368. package/src/v2/shadcn/hooks/use-toast.ts +0 -184
  369. package/src/v2/shadcn/index.ts +0 -76
  370. package/src/v2/shadcn/lib/utils.ts +0 -6
  371. 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;