@voyant-travel/bookings-react 0.119.3
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/LICENSE +201 -0
- package/README.md +87 -0
- package/dist/admin/booking-contract-dialog.d.ts +22 -0
- package/dist/admin/booking-contract-dialog.d.ts.map +1 -0
- package/dist/admin/booking-contract-dialog.js +161 -0
- package/dist/admin/booking-detail-host.d.ts +103 -0
- package/dist/admin/booking-detail-host.d.ts.map +1 -0
- package/dist/admin/booking-detail-host.js +127 -0
- package/dist/admin/booking-detail-skeleton.d.ts +7 -0
- package/dist/admin/booking-detail-skeleton.d.ts.map +1 -0
- package/dist/admin/booking-detail-skeleton.js +24 -0
- package/dist/admin/booking-documents-table.d.ts +13 -0
- package/dist/admin/booking-documents-table.d.ts.map +1 -0
- package/dist/admin/booking-documents-table.js +259 -0
- package/dist/admin/booking-invoice-sheet.d.ts +18 -0
- package/dist/admin/booking-invoice-sheet.d.ts.map +1 -0
- package/dist/admin/booking-invoice-sheet.js +101 -0
- package/dist/admin/booking-journey-host.d.ts +24 -0
- package/dist/admin/booking-journey-host.d.ts.map +1 -0
- package/dist/admin/booking-journey-host.js +278 -0
- package/dist/admin/bookings-host.d.ts +26 -0
- package/dist/admin/bookings-host.d.ts.map +1 -0
- package/dist/admin/bookings-host.js +18 -0
- package/dist/admin/bookings-list-skeleton.d.ts +10 -0
- package/dist/admin/bookings-list-skeleton.d.ts.map +1 -0
- package/dist/admin/bookings-list-skeleton.js +25 -0
- package/dist/admin/index.d.ts +273 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +331 -0
- package/dist/admin/journey-billing-duplicate-warning.d.ts +3 -0
- package/dist/admin/journey-billing-duplicate-warning.d.ts.map +1 -0
- package/dist/admin/journey-billing-duplicate-warning.js +26 -0
- package/dist/admin/journey-departure-picker.d.ts +7 -0
- package/dist/admin/journey-departure-picker.d.ts.map +1 -0
- package/dist/admin/journey-departure-picker.js +100 -0
- package/dist/admin/journey-units-picker.d.ts +11 -0
- package/dist/admin/journey-units-picker.d.ts.map +1 -0
- package/dist/admin/journey-units-picker.js +60 -0
- package/dist/admin/journey-voucher-picker.d.ts +3 -0
- package/dist/admin/journey-voucher-picker.d.ts.map +1 -0
- package/dist/admin/journey-voucher-picker.js +71 -0
- package/dist/admin/pages/booking-compose-page.d.ts +9 -0
- package/dist/admin/pages/booking-compose-page.d.ts.map +1 -0
- package/dist/admin/pages/booking-compose-page.js +17 -0
- package/dist/admin/pages/booking-detail-page.d.ts +11 -0
- package/dist/admin/pages/booking-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/booking-detail-page.js +14 -0
- package/dist/admin/pages/booking-journey-page.d.ts +12 -0
- package/dist/admin/pages/booking-journey-page.d.ts.map +1 -0
- package/dist/admin/pages/booking-journey-page.js +26 -0
- package/dist/admin/pages/booking-new-page.d.ts +15 -0
- package/dist/admin/pages/booking-new-page.d.ts.map +1 -0
- package/dist/admin/pages/booking-new-page.js +50 -0
- package/dist/admin/pages/bookings-index-page.d.ts +20 -0
- package/dist/admin/pages/bookings-index-page.d.ts.map +1 -0
- package/dist/admin/pages/bookings-index-page.js +18 -0
- package/dist/admin/person-bookings-widget.d.ts +13 -0
- package/dist/admin/person-bookings-widget.d.ts.map +1 -0
- package/dist/admin/person-bookings-widget.js +48 -0
- package/dist/admin/slots.d.ts +31 -0
- package/dist/admin/slots.d.ts.map +1 -0
- package/dist/admin/slots.js +30 -0
- package/dist/admin/use-booking-action-ledger-events.d.ts +15 -0
- package/dist/admin/use-booking-action-ledger-events.d.ts.map +1 -0
- package/dist/admin/use-booking-action-ledger-events.js +66 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +59 -0
- package/dist/components/booking-activity-timeline.d.ts +32 -0
- package/dist/components/booking-activity-timeline.d.ts.map +1 -0
- package/dist/components/booking-activity-timeline.js +147 -0
- package/dist/components/booking-billing-dialog.d.ts +16 -0
- package/dist/components/booking-billing-dialog.d.ts.map +1 -0
- package/dist/components/booking-billing-dialog.js +315 -0
- package/dist/components/booking-cancellation-dialog.d.ts +18 -0
- package/dist/components/booking-cancellation-dialog.d.ts.map +1 -0
- package/dist/components/booking-cancellation-dialog.js +79 -0
- package/dist/components/booking-combobox.d.ts +13 -0
- package/dist/components/booking-combobox.d.ts.map +1 -0
- package/dist/components/booking-combobox.js +44 -0
- package/dist/components/booking-create-form-utils.d.ts +56 -0
- package/dist/components/booking-create-form-utils.d.ts.map +1 -0
- package/dist/components/booking-create-form-utils.js +216 -0
- package/dist/components/booking-create-page.d.ts +14 -0
- package/dist/components/booking-create-page.d.ts.map +1 -0
- package/dist/components/booking-create-page.js +11 -0
- package/dist/components/booking-create-preview-card.d.ts +26 -0
- package/dist/components/booking-create-preview-card.d.ts.map +1 -0
- package/dist/components/booking-create-preview-card.js +107 -0
- package/dist/components/booking-create-product-extras-picker.d.ts +18 -0
- package/dist/components/booking-create-product-extras-picker.d.ts.map +1 -0
- package/dist/components/booking-create-product-extras-picker.js +82 -0
- package/dist/components/booking-create-sheet.d.ts +34 -0
- package/dist/components/booking-create-sheet.d.ts.map +1 -0
- package/dist/components/booking-create-sheet.js +811 -0
- package/dist/components/booking-create-utils.d.ts +66 -0
- package/dist/components/booking-create-utils.d.ts.map +1 -0
- package/dist/components/booking-create-utils.js +185 -0
- package/dist/components/booking-detail-page.d.ts +126 -0
- package/dist/components/booking-detail-page.d.ts.map +1 -0
- package/dist/components/booking-detail-page.js +264 -0
- package/dist/components/booking-dialog.d.ts +28 -0
- package/dist/components/booking-dialog.d.ts.map +1 -0
- package/dist/components/booking-dialog.js +130 -0
- package/dist/components/booking-document-dialog.d.ts +8 -0
- package/dist/components/booking-document-dialog.d.ts.map +1 -0
- package/dist/components/booking-document-dialog.js +83 -0
- package/dist/components/booking-document-list.d.ts +5 -0
- package/dist/components/booking-document-list.d.ts.map +1 -0
- package/dist/components/booking-document-list.js +43 -0
- package/dist/components/booking-group-link-dialog.d.ts +10 -0
- package/dist/components/booking-group-link-dialog.d.ts.map +1 -0
- package/dist/components/booking-group-link-dialog.js +79 -0
- package/dist/components/booking-group-section.d.ts +27 -0
- package/dist/components/booking-group-section.d.ts.map +1 -0
- package/dist/components/booking-group-section.js +51 -0
- package/dist/components/booking-guarantee-dialog.d.ts +10 -0
- package/dist/components/booking-guarantee-dialog.d.ts.map +1 -0
- package/dist/components/booking-guarantee-dialog.js +123 -0
- package/dist/components/booking-guarantee-list.d.ts +5 -0
- package/dist/components/booking-guarantee-list.d.ts.map +1 -0
- package/dist/components/booking-guarantee-list.js +86 -0
- package/dist/components/booking-item-dialog.d.ts +10 -0
- package/dist/components/booking-item-dialog.d.ts.map +1 -0
- package/dist/components/booking-item-dialog.js +155 -0
- package/dist/components/booking-item-list.d.ts +12 -0
- package/dist/components/booking-item-list.d.ts.map +1 -0
- package/dist/components/booking-item-list.js +191 -0
- package/dist/components/booking-item-travelers.d.ts +6 -0
- package/dist/components/booking-item-travelers.d.ts.map +1 -0
- package/dist/components/booking-item-travelers.js +57 -0
- package/dist/components/booking-list-filters.d.ts +43 -0
- package/dist/components/booking-list-filters.d.ts.map +1 -0
- package/dist/components/booking-list-filters.js +192 -0
- package/dist/components/booking-list.d.ts +50 -0
- package/dist/components/booking-list.d.ts.map +1 -0
- package/dist/components/booking-list.js +352 -0
- package/dist/components/booking-note-dialog.d.ts +16 -0
- package/dist/components/booking-note-dialog.d.ts.map +1 -0
- package/dist/components/booking-note-dialog.js +41 -0
- package/dist/components/booking-notes.d.ts +5 -0
- package/dist/components/booking-notes.d.ts.map +1 -0
- package/dist/components/booking-notes.js +45 -0
- package/dist/components/booking-payment-reconciliation-banner.d.ts +5 -0
- package/dist/components/booking-payment-reconciliation-banner.d.ts.map +1 -0
- package/dist/components/booking-payment-reconciliation-banner.js +91 -0
- package/dist/components/booking-payment-schedule-dialog.d.ts +10 -0
- package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -0
- package/dist/components/booking-payment-schedule-dialog.js +117 -0
- package/dist/components/booking-payment-schedule-list.d.ts +10 -0
- package/dist/components/booking-payment-schedule-list.d.ts.map +1 -0
- package/dist/components/booking-payment-schedule-list.js +217 -0
- package/dist/components/booking-payments-summary.d.ts +83 -0
- package/dist/components/booking-payments-summary.d.ts.map +1 -0
- package/dist/components/booking-payments-summary.js +176 -0
- package/dist/components/booking-quick-view-sheet.d.ts +14 -0
- package/dist/components/booking-quick-view-sheet.d.ts.map +1 -0
- package/dist/components/booking-quick-view-sheet.js +283 -0
- package/dist/components/bookings-page.d.ts +19 -0
- package/dist/components/bookings-page.d.ts.map +1 -0
- package/dist/components/bookings-page.js +9 -0
- package/dist/components/file-dropzone.d.ts +25 -0
- package/dist/components/file-dropzone.d.ts.map +1 -0
- package/dist/components/file-dropzone.js +102 -0
- package/dist/components/icon-action-button.d.ts +18 -0
- package/dist/components/icon-action-button.d.ts.map +1 -0
- package/dist/components/icon-action-button.js +13 -0
- package/dist/components/option-units-stepper-section.d.ts +111 -0
- package/dist/components/option-units-stepper-section.d.ts.map +1 -0
- package/dist/components/option-units-stepper-section.js +276 -0
- package/dist/components/payment-schedule-section.d.ts +91 -0
- package/dist/components/payment-schedule-section.d.ts.map +1 -0
- package/dist/components/payment-schedule-section.js +206 -0
- package/dist/components/person-picker-section.d.ts +71 -0
- package/dist/components/person-picker-section.d.ts.map +1 -0
- package/dist/components/person-picker-section.js +160 -0
- package/dist/components/price-breakdown-section.d.ts +83 -0
- package/dist/components/price-breakdown-section.d.ts.map +1 -0
- package/dist/components/price-breakdown-section.js +278 -0
- package/dist/components/product-picker-section.d.ts +29 -0
- package/dist/components/product-picker-section.d.ts.map +1 -0
- package/dist/components/product-picker-section.js +74 -0
- package/dist/components/shared-room-section.d.ts +40 -0
- package/dist/components/shared-room-section.d.ts.map +1 -0
- package/dist/components/shared-room-section.js +99 -0
- package/dist/components/status-badge.d.ts +24 -0
- package/dist/components/status-badge.d.ts.map +1 -0
- package/dist/components/status-badge.js +65 -0
- package/dist/components/status-change-dialog.d.ts +10 -0
- package/dist/components/status-change-dialog.d.ts.map +1 -0
- package/dist/components/status-change-dialog.js +57 -0
- package/dist/components/supplier-status-dialog.d.ts +10 -0
- package/dist/components/supplier-status-dialog.d.ts.map +1 -0
- package/dist/components/supplier-status-dialog.js +98 -0
- package/dist/components/supplier-status-list.d.ts +5 -0
- package/dist/components/supplier-status-list.d.ts.map +1 -0
- package/dist/components/supplier-status-list.js +115 -0
- package/dist/components/traveler-category-buttons.d.ts +26 -0
- package/dist/components/traveler-category-buttons.d.ts.map +1 -0
- package/dist/components/traveler-category-buttons.js +35 -0
- package/dist/components/traveler-dialog.d.ts +10 -0
- package/dist/components/traveler-dialog.d.ts.map +1 -0
- package/dist/components/traveler-dialog.js +256 -0
- package/dist/components/traveler-list.d.ts +6 -0
- package/dist/components/traveler-list.d.ts.map +1 -0
- package/dist/components/traveler-list.js +295 -0
- package/dist/components/travelers-section-controls.d.ts +52 -0
- package/dist/components/travelers-section-controls.d.ts.map +1 -0
- package/dist/components/travelers-section-controls.js +206 -0
- package/dist/components/travelers-section.d.ts +159 -0
- package/dist/components/travelers-section.d.ts.map +1 -0
- package/dist/components/travelers-section.js +355 -0
- package/dist/components/voucher-picker-section.d.ts +50 -0
- package/dist/components/voucher-picker-section.d.ts.map +1 -0
- package/dist/components/voucher-picker-section.js +79 -0
- package/dist/extras/client.d.ts +14 -0
- package/dist/extras/client.d.ts.map +1 -0
- package/dist/extras/client.js +58 -0
- package/dist/extras/components/extra-catalog-card.d.ts +13 -0
- package/dist/extras/components/extra-catalog-card.d.ts.map +1 -0
- package/dist/extras/components/extra-catalog-card.js +52 -0
- package/dist/extras/components/product-combobox.d.ts +9 -0
- package/dist/extras/components/product-combobox.d.ts.map +1 -0
- package/dist/extras/components/product-combobox.js +46 -0
- package/dist/extras/components/slot-extras-manifest-panel.d.ts +6 -0
- package/dist/extras/components/slot-extras-manifest-panel.d.ts.map +1 -0
- package/dist/extras/components/slot-extras-manifest-panel.js +108 -0
- package/dist/extras/hooks/index.d.ts +5 -0
- package/dist/extras/hooks/index.d.ts.map +1 -0
- package/dist/extras/hooks/index.js +4 -0
- package/dist/extras/hooks/use-product-extra.d.ts +24 -0
- package/dist/extras/hooks/use-product-extra.d.ts.map +1 -0
- package/dist/extras/hooks/use-product-extra.js +12 -0
- package/dist/extras/hooks/use-product-extras.d.ts +30 -0
- package/dist/extras/hooks/use-product-extras.d.ts.map +1 -0
- package/dist/extras/hooks/use-product-extras.js +12 -0
- package/dist/extras/hooks/use-slot-extra-manifest-mutation.d.ts +48 -0
- package/dist/extras/hooks/use-slot-extra-manifest-mutation.d.ts.map +1 -0
- package/dist/extras/hooks/use-slot-extra-manifest-mutation.js +26 -0
- package/dist/extras/hooks/use-slot-extra-manifest.d.ts +68 -0
- package/dist/extras/hooks/use-slot-extra-manifest.d.ts.map +1 -0
- package/dist/extras/hooks/use-slot-extra-manifest.js +11 -0
- package/dist/extras/i18n/en.d.ts +52 -0
- package/dist/extras/i18n/en.d.ts.map +1 -0
- package/dist/extras/i18n/en.js +51 -0
- package/dist/extras/i18n/index.d.ts +5 -0
- package/dist/extras/i18n/index.d.ts.map +1 -0
- package/dist/extras/i18n/index.js +3 -0
- package/dist/extras/i18n/messages.d.ts +37 -0
- package/dist/extras/i18n/messages.d.ts.map +1 -0
- package/dist/extras/i18n/messages.js +1 -0
- package/dist/extras/i18n/provider.d.ts +126 -0
- package/dist/extras/i18n/provider.d.ts.map +1 -0
- package/dist/extras/i18n/provider.js +44 -0
- package/dist/extras/i18n/ro.d.ts +52 -0
- package/dist/extras/i18n/ro.d.ts.map +1 -0
- package/dist/extras/i18n/ro.js +51 -0
- package/dist/extras/index.d.ts +7 -0
- package/dist/extras/index.d.ts.map +1 -0
- package/dist/extras/index.js +6 -0
- package/dist/extras/provider.d.ts +2 -0
- package/dist/extras/provider.d.ts.map +1 -0
- package/dist/extras/provider.js +1 -0
- package/dist/extras/query-keys.d.ts +16 -0
- package/dist/extras/query-keys.d.ts.map +1 -0
- package/dist/extras/query-keys.js +8 -0
- package/dist/extras/query-options.d.ts +455 -0
- package/dist/extras/query-options.d.ts.map +1 -0
- package/dist/extras/query-options.js +44 -0
- package/dist/extras/schemas.d.ts +416 -0
- package/dist/extras/schemas.d.ts.map +1 -0
- package/dist/extras/schemas.js +89 -0
- package/dist/extras/ui.d.ts +4 -0
- package/dist/extras/ui.d.ts.map +1 -0
- package/dist/extras/ui.js +3 -0
- package/dist/extras.d.ts +10 -0
- package/dist/extras.d.ts.map +1 -0
- package/dist/extras.js +9 -0
- package/dist/hooks/index.d.ts +36 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +35 -0
- package/dist/hooks/use-booking-action-ledger.d.ts +63 -0
- package/dist/hooks/use-booking-action-ledger.d.ts.map +1 -0
- package/dist/hooks/use-booking-action-ledger.js +34 -0
- package/dist/hooks/use-booking-activity.d.ts +17 -0
- package/dist/hooks/use-booking-activity.d.ts.map +1 -0
- package/dist/hooks/use-booking-activity.js +12 -0
- package/dist/hooks/use-booking-cancel-mutation.d.ts +69 -0
- package/dist/hooks/use-booking-cancel-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-cancel-mutation.js +24 -0
- package/dist/hooks/use-booking-contract-generation.d.ts +31 -0
- package/dist/hooks/use-booking-contract-generation.d.ts.map +1 -0
- package/dist/hooks/use-booking-contract-generation.js +36 -0
- package/dist/hooks/use-booking-convert-mutation.d.ts +81 -0
- package/dist/hooks/use-booking-convert-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-convert-mutation.js +24 -0
- package/dist/hooks/use-booking-create-mutation.d.ts +337 -0
- package/dist/hooks/use-booking-create-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-create-mutation.js +43 -0
- package/dist/hooks/use-booking-documents.d.ts +41 -0
- package/dist/hooks/use-booking-documents.d.ts.map +1 -0
- package/dist/hooks/use-booking-documents.js +46 -0
- package/dist/hooks/use-booking-dual-create-mutation.d.ts +338 -0
- package/dist/hooks/use-booking-dual-create-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-dual-create-mutation.js +45 -0
- package/dist/hooks/use-booking-group-for-booking.d.ts +24 -0
- package/dist/hooks/use-booking-group-for-booking.d.ts.map +1 -0
- package/dist/hooks/use-booking-group-for-booking.js +12 -0
- package/dist/hooks/use-booking-group-member-mutation.d.ts +27 -0
- package/dist/hooks/use-booking-group-member-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-group-member-mutation.js +38 -0
- package/dist/hooks/use-booking-group-mutation.d.ts +40 -0
- package/dist/hooks/use-booking-group-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-group-mutation.js +32 -0
- package/dist/hooks/use-booking-group.d.ts +85 -0
- package/dist/hooks/use-booking-group.d.ts.map +1 -0
- package/dist/hooks/use-booking-group.js +12 -0
- package/dist/hooks/use-booking-groups.d.ts +21 -0
- package/dist/hooks/use-booking-groups.d.ts.map +1 -0
- package/dist/hooks/use-booking-groups.js +12 -0
- package/dist/hooks/use-booking-item-mutation.d.ts +101 -0
- package/dist/hooks/use-booking-item-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-item-mutation.js +42 -0
- package/dist/hooks/use-booking-item-travelers.d.ts +32 -0
- package/dist/hooks/use-booking-item-travelers.d.ts.map +1 -0
- package/dist/hooks/use-booking-item-travelers.js +48 -0
- package/dist/hooks/use-booking-items.d.ts +36 -0
- package/dist/hooks/use-booking-items.d.ts.map +1 -0
- package/dist/hooks/use-booking-items.js +12 -0
- package/dist/hooks/use-booking-mutation.d.ts +158 -0
- package/dist/hooks/use-booking-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-mutation.js +39 -0
- package/dist/hooks/use-booking-note-mutation.d.ts +39 -0
- package/dist/hooks/use-booking-note-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-note-mutation.js +44 -0
- package/dist/hooks/use-booking-notes.d.ts +15 -0
- package/dist/hooks/use-booking-notes.d.ts.map +1 -0
- package/dist/hooks/use-booking-notes.js +12 -0
- package/dist/hooks/use-booking-primary-product.d.ts +28 -0
- package/dist/hooks/use-booking-primary-product.d.ts.map +1 -0
- package/dist/hooks/use-booking-primary-product.js +20 -0
- package/dist/hooks/use-booking-status-mutation.d.ts +156 -0
- package/dist/hooks/use-booking-status-mutation.d.ts.map +1 -0
- package/dist/hooks/use-booking-status-mutation.js +54 -0
- package/dist/hooks/use-booking-tax-preview.d.ts +29 -0
- package/dist/hooks/use-booking-tax-preview.d.ts.map +1 -0
- package/dist/hooks/use-booking-tax-preview.js +21 -0
- package/dist/hooks/use-booking.d.ts +67 -0
- package/dist/hooks/use-booking.d.ts.map +1 -0
- package/dist/hooks/use-booking.js +12 -0
- package/dist/hooks/use-bookings.d.ts +71 -0
- package/dist/hooks/use-bookings.d.ts.map +1 -0
- package/dist/hooks/use-bookings.js +12 -0
- package/dist/hooks/use-pricing-preview.d.ts +61 -0
- package/dist/hooks/use-pricing-preview.d.ts.map +1 -0
- package/dist/hooks/use-pricing-preview.js +18 -0
- package/dist/hooks/use-public-booking-session-flow-mutation.d.ts +148 -0
- package/dist/hooks/use-public-booking-session-flow-mutation.d.ts.map +1 -0
- package/dist/hooks/use-public-booking-session-flow-mutation.js +35 -0
- package/dist/hooks/use-public-booking-session-state.d.ts +16 -0
- package/dist/hooks/use-public-booking-session-state.d.ts.map +1 -0
- package/dist/hooks/use-public-booking-session-state.js +12 -0
- package/dist/hooks/use-public-booking-session.d.ts +101 -0
- package/dist/hooks/use-public-booking-session.d.ts.map +1 -0
- package/dist/hooks/use-public-booking-session.js +12 -0
- package/dist/hooks/use-reveal-traveler.d.ts +54 -0
- package/dist/hooks/use-reveal-traveler.d.ts.map +1 -0
- package/dist/hooks/use-reveal-traveler.js +18 -0
- package/dist/hooks/use-sharing-groups.d.ts +41 -0
- package/dist/hooks/use-sharing-groups.d.ts.map +1 -0
- package/dist/hooks/use-sharing-groups.js +20 -0
- package/dist/hooks/use-supplier-status-mutation.d.ts +46 -0
- package/dist/hooks/use-supplier-status-mutation.d.ts.map +1 -0
- package/dist/hooks/use-supplier-status-mutation.js +39 -0
- package/dist/hooks/use-supplier-statuses.d.ts +20 -0
- package/dist/hooks/use-supplier-statuses.d.ts.map +1 -0
- package/dist/hooks/use-supplier-statuses.js +12 -0
- package/dist/hooks/use-traveler-mutation.d.ts +55 -0
- package/dist/hooks/use-traveler-mutation.d.ts.map +1 -0
- package/dist/hooks/use-traveler-mutation.js +42 -0
- package/dist/hooks/use-traveler-with-travel-details-mutation.d.ts +120 -0
- package/dist/hooks/use-traveler-with-travel-details-mutation.d.ts.map +1 -0
- package/dist/hooks/use-traveler-with-travel-details-mutation.js +43 -0
- package/dist/hooks/use-travelers.d.ts +23 -0
- package/dist/hooks/use-travelers.d.ts.map +1 -0
- package/dist/hooks/use-travelers.js +12 -0
- package/dist/i18n/en-base.d.ts +295 -0
- package/dist/i18n/en-base.d.ts.map +1 -0
- package/dist/i18n/en-base.js +294 -0
- package/dist/i18n/en-create-list.d.ts +327 -0
- package/dist/i18n/en-create-list.d.ts.map +1 -0
- package/dist/i18n/en-create-list.js +326 -0
- package/dist/i18n/en-journey.d.ts +229 -0
- package/dist/i18n/en-journey.d.ts.map +1 -0
- package/dist/i18n/en-journey.js +228 -0
- package/dist/i18n/en-operations.d.ts +382 -0
- package/dist/i18n/en-operations.d.ts.map +1 -0
- package/dist/i18n/en-operations.js +381 -0
- package/dist/i18n/en-sections.d.ts +360 -0
- package/dist/i18n/en-sections.d.ts.map +1 -0
- package/dist/i18n/en-sections.js +359 -0
- package/dist/i18n/en.d.ts +1581 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +12 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages-base.d.ts +251 -0
- package/dist/i18n/messages-base.d.ts.map +1 -0
- package/dist/i18n/messages-base.js +1 -0
- package/dist/i18n/messages-create-list.d.ts +310 -0
- package/dist/i18n/messages-create-list.d.ts.map +1 -0
- package/dist/i18n/messages-create-list.js +1 -0
- package/dist/i18n/messages-journey.d.ts +198 -0
- package/dist/i18n/messages-journey.d.ts.map +1 -0
- package/dist/i18n/messages-journey.js +1 -0
- package/dist/i18n/messages-operations.d.ts +362 -0
- package/dist/i18n/messages-operations.d.ts.map +1 -0
- package/dist/i18n/messages-operations.js +1 -0
- package/dist/i18n/messages-sections.d.ts +312 -0
- package/dist/i18n/messages-sections.d.ts.map +1 -0
- package/dist/i18n/messages-sections.js +1 -0
- package/dist/i18n/messages.d.ts +7 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +3185 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +45 -0
- package/dist/i18n/ro-base.d.ts +295 -0
- package/dist/i18n/ro-base.d.ts.map +1 -0
- package/dist/i18n/ro-base.js +294 -0
- package/dist/i18n/ro-create-list.d.ts +327 -0
- package/dist/i18n/ro-create-list.d.ts.map +1 -0
- package/dist/i18n/ro-create-list.js +326 -0
- package/dist/i18n/ro-journey.d.ts +229 -0
- package/dist/i18n/ro-journey.d.ts.map +1 -0
- package/dist/i18n/ro-journey.js +228 -0
- package/dist/i18n/ro-operations.d.ts +382 -0
- package/dist/i18n/ro-operations.d.ts.map +1 -0
- package/dist/i18n/ro-operations.js +381 -0
- package/dist/i18n/ro-sections.d.ts +360 -0
- package/dist/i18n/ro-sections.d.ts.map +1 -0
- package/dist/i18n/ro-sections.js +359 -0
- package/dist/i18n/ro.d.ts +1581 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/journey/components/booking-journey-rules.d.ts +48 -0
- package/dist/journey/components/booking-journey-rules.d.ts.map +1 -0
- package/dist/journey/components/booking-journey-rules.js +235 -0
- package/dist/journey/components/booking-journey.d.ts +3 -0
- package/dist/journey/components/booking-journey.d.ts.map +1 -0
- package/dist/journey/components/booking-journey.js +368 -0
- package/dist/journey/components/configure-step-skeleton.d.ts +8 -0
- package/dist/journey/components/configure-step-skeleton.d.ts.map +1 -0
- package/dist/journey/components/configure-step-skeleton.js +11 -0
- package/dist/journey/components/contract-preview-dialog.d.ts +47 -0
- package/dist/journey/components/contract-preview-dialog.d.ts.map +1 -0
- package/dist/journey/components/contract-preview-dialog.js +124 -0
- package/dist/journey/components/journey-steps/accommodation-step.d.ts +3 -0
- package/dist/journey/components/journey-steps/accommodation-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/accommodation-step.js +71 -0
- package/dist/journey/components/journey-steps/addons-step.d.ts +3 -0
- package/dist/journey/components/journey-steps/addons-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/addons-step.js +40 -0
- package/dist/journey/components/journey-steps/billing-step.d.ts +8 -0
- package/dist/journey/components/journey-steps/billing-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/billing-step.js +78 -0
- package/dist/journey/components/journey-steps/configure-steps.d.ts +28 -0
- package/dist/journey/components/journey-steps/configure-steps.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/configure-steps.js +232 -0
- package/dist/journey/components/journey-steps/documents-step.d.ts +11 -0
- package/dist/journey/components/journey-steps/documents-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/documents-step.js +36 -0
- package/dist/journey/components/journey-steps/payment-step.d.ts +29 -0
- package/dist/journey/components/journey-steps/payment-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/payment-step.js +225 -0
- package/dist/journey/components/journey-steps/review-step.d.ts +27 -0
- package/dist/journey/components/journey-steps/review-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/review-step.js +18 -0
- package/dist/journey/components/journey-steps/shared.d.ts +75 -0
- package/dist/journey/components/journey-steps/shared.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/shared.js +108 -0
- package/dist/journey/components/journey-steps/travelers-step.d.ts +7 -0
- package/dist/journey/components/journey-steps/travelers-step.d.ts.map +1 -0
- package/dist/journey/components/journey-steps/travelers-step.js +201 -0
- package/dist/journey/components/journey-steps.d.ts +21 -0
- package/dist/journey/components/journey-steps.d.ts.map +1 -0
- package/dist/journey/components/journey-steps.js +20 -0
- package/dist/journey/components/side-panel.d.ts +17 -0
- package/dist/journey/components/side-panel.d.ts.map +1 -0
- package/dist/journey/components/side-panel.js +245 -0
- package/dist/journey/components/stacked-journey.d.ts +30 -0
- package/dist/journey/components/stacked-journey.d.ts.map +1 -0
- package/dist/journey/components/stacked-journey.js +50 -0
- package/dist/journey/components/step-header.d.ts +7 -0
- package/dist/journey/components/step-header.d.ts.map +1 -0
- package/dist/journey/components/step-header.js +12 -0
- package/dist/journey/index.d.ts +18 -0
- package/dist/journey/index.d.ts.map +1 -0
- package/dist/journey/index.js +17 -0
- package/dist/journey/lib/draft-state.d.ts +35 -0
- package/dist/journey/lib/draft-state.d.ts.map +1 -0
- package/dist/journey/lib/draft-state.js +57 -0
- package/dist/journey/lib/pax-band-dependencies.d.ts +27 -0
- package/dist/journey/lib/pax-band-dependencies.d.ts.map +1 -0
- package/dist/journey/lib/pax-band-dependencies.js +50 -0
- package/dist/journey/lib/payment-schedule.d.ts +19 -0
- package/dist/journey/lib/payment-schedule.d.ts.map +1 -0
- package/dist/journey/lib/payment-schedule.js +90 -0
- package/dist/journey/types.d.ts +403 -0
- package/dist/journey/types.d.ts.map +1 -0
- package/dist/journey/types.js +19 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/query-keys.d.ts +74 -0
- package/dist/query-keys.d.ts.map +1 -0
- package/dist/query-keys.js +26 -0
- package/dist/query-options.d.ts +2534 -0
- package/dist/query-options.d.ts.map +1 -0
- package/dist/query-options.js +233 -0
- package/dist/requirements/client.d.ts +14 -0
- package/dist/requirements/client.d.ts.map +1 -0
- package/dist/requirements/client.js +59 -0
- package/dist/requirements/components/booking-requirements-contact-tab.d.ts +8 -0
- package/dist/requirements/components/booking-requirements-contact-tab.d.ts.map +1 -0
- package/dist/requirements/components/booking-requirements-contact-tab.js +8 -0
- package/dist/requirements/components/booking-requirements-questions-tab.d.ts +14 -0
- package/dist/requirements/components/booking-requirements-questions-tab.d.ts.map +1 -0
- package/dist/requirements/components/booking-requirements-questions-tab.js +17 -0
- package/dist/requirements/constants.d.ts +114 -0
- package/dist/requirements/constants.d.ts.map +1 -0
- package/dist/requirements/constants.js +45 -0
- package/dist/requirements/hooks/index.d.ts +6 -0
- package/dist/requirements/hooks/index.d.ts.map +1 -0
- package/dist/requirements/hooks/index.js +6 -0
- package/dist/requirements/hooks/use-booking-questions.d.ts +24 -0
- package/dist/requirements/hooks/use-booking-questions.d.ts.map +1 -0
- package/dist/requirements/hooks/use-booking-questions.js +9 -0
- package/dist/requirements/hooks/use-contact-requirements.d.ts +22 -0
- package/dist/requirements/hooks/use-contact-requirements.d.ts.map +1 -0
- package/dist/requirements/hooks/use-contact-requirements.js +9 -0
- package/dist/requirements/hooks/use-products.d.ts +16 -0
- package/dist/requirements/hooks/use-products.d.ts.map +1 -0
- package/dist/requirements/hooks/use-products.js +9 -0
- package/dist/requirements/hooks/use-question-options.d.ts +19 -0
- package/dist/requirements/hooks/use-question-options.d.ts.map +1 -0
- package/dist/requirements/hooks/use-question-options.js +9 -0
- package/dist/requirements/hooks/use-transport-requirements.d.ts +30 -0
- package/dist/requirements/hooks/use-transport-requirements.d.ts.map +1 -0
- package/dist/requirements/hooks/use-transport-requirements.js +9 -0
- package/dist/requirements/i18n/en.d.ts +94 -0
- package/dist/requirements/i18n/en.d.ts.map +1 -0
- package/dist/requirements/i18n/en.js +93 -0
- package/dist/requirements/i18n/index.d.ts +5 -0
- package/dist/requirements/i18n/index.d.ts.map +1 -0
- package/dist/requirements/i18n/index.js +3 -0
- package/dist/requirements/i18n/messages.d.ts +59 -0
- package/dist/requirements/i18n/messages.d.ts.map +1 -0
- package/dist/requirements/i18n/messages.js +1 -0
- package/dist/requirements/i18n/provider.d.ts +210 -0
- package/dist/requirements/i18n/provider.d.ts.map +1 -0
- package/dist/requirements/i18n/provider.js +44 -0
- package/dist/requirements/i18n/ro.d.ts +94 -0
- package/dist/requirements/i18n/ro.d.ts.map +1 -0
- package/dist/requirements/i18n/ro.js +93 -0
- package/dist/requirements/index.d.ts +9 -0
- package/dist/requirements/index.d.ts.map +1 -0
- package/dist/requirements/index.js +8 -0
- package/dist/requirements/provider.d.ts +2 -0
- package/dist/requirements/provider.d.ts.map +1 -0
- package/dist/requirements/provider.js +1 -0
- package/dist/requirements/query-keys.d.ts +33 -0
- package/dist/requirements/query-keys.d.ts.map +1 -0
- package/dist/requirements/query-keys.js +13 -0
- package/dist/requirements/query-options.d.ts +371 -0
- package/dist/requirements/query-options.d.ts.map +1 -0
- package/dist/requirements/query-options.js +80 -0
- package/dist/requirements/schemas.d.ts +320 -0
- package/dist/requirements/schemas.d.ts.map +1 -0
- package/dist/requirements/schemas.js +121 -0
- package/dist/requirements/ui.d.ts +4 -0
- package/dist/requirements/ui.d.ts.map +1 -0
- package/dist/requirements/ui.js +3 -0
- package/dist/requirements/utils.d.ts +2 -0
- package/dist/requirements/utils.d.ts.map +1 -0
- package/dist/requirements/utils.js +4 -0
- package/dist/schemas.d.ts +2070 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +507 -0
- package/dist/status-presentation.d.ts +34 -0
- package/dist/status-presentation.d.ts.map +1 -0
- package/dist/status-presentation.js +38 -0
- package/dist/ui.d.ts +43 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +42 -0
- package/package.json +256 -0
- package/src/requirements/styles.css +1 -0
- package/src/styles.css +13 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQueries } from "@tanstack/react-query";
|
|
4
|
+
import { getOptionUnitsQueryOptions, useProductOptions, useVoyantProductsContext, } from "@voyant-travel/inventory-react";
|
|
5
|
+
import { useSlotUnitAvailability } from "@voyant-travel/operations-react/availability";
|
|
6
|
+
import { Button, Label } from "@voyant-travel/ui/components";
|
|
7
|
+
import { Minus, Plus } from "lucide-react";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
|
|
10
|
+
export const emptyOptionUnitsStepperValue = { quantities: {} };
|
|
11
|
+
/**
|
|
12
|
+
* Rooms / per-unit stepper for booking-create flows. Drives
|
|
13
|
+
* `GET /v1/operations/availability/slots/:id/unit-availability` from #235 when a
|
|
14
|
+
* departure is selected, and product option-level units before departure
|
|
15
|
+
* selection, so operators can build "2 double rooms and 1 single" drafts.
|
|
16
|
+
*
|
|
17
|
+
* The section only tracks **intent** (how many of each unit the operator
|
|
18
|
+
* wants to book). Actual hold/reservation happens when the parent submits
|
|
19
|
+
* the booking — capacity drops the moment the reservation transaction
|
|
20
|
+
* commits; the next refetch of `useSlotUnitAvailability` reflects it.
|
|
21
|
+
*
|
|
22
|
+
* ### Stepper bounds
|
|
23
|
+
*
|
|
24
|
+
* - Minimum is 0 (operator can deselect).
|
|
25
|
+
* - Maximum is the unit's `remaining` count from the server. Unlimited
|
|
26
|
+
* pools (`remaining === null`) have no upper bound.
|
|
27
|
+
* - The server is the truth: entering `3 doubles` when only 2 remain just
|
|
28
|
+
* disables the "+" button — we don't let the UI submit a request that
|
|
29
|
+
* would 409 at insert time.
|
|
30
|
+
*/
|
|
31
|
+
export function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, restrictToOption = false, enabled = true, onUnitsChange, labels, slotHasFiniteCapacity = false, invalidOptionUnitIds = [], }) {
|
|
32
|
+
const productsClient = useVoyantProductsContext();
|
|
33
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
34
|
+
const merged = { ...messages.roomsStepperSection.labels, ...labels };
|
|
35
|
+
const availability = useSlotUnitAvailability({ slotId, enabled: enabled && Boolean(slotId) });
|
|
36
|
+
// Always fetch option-level units for the product. They're needed
|
|
37
|
+
// both before a slot is picked AND as a fallback after picking a slot
|
|
38
|
+
// whose `availability_slots` row has no per-unit allocation rows wired
|
|
39
|
+
// (the default for product-level slots seeded by the operator).
|
|
40
|
+
const optionsQuery = useProductOptions({
|
|
41
|
+
productId,
|
|
42
|
+
status: "active",
|
|
43
|
+
limit: 100,
|
|
44
|
+
enabled: enabled && Boolean(productId),
|
|
45
|
+
});
|
|
46
|
+
const productOptions = React.useMemo(() => {
|
|
47
|
+
const options = optionsQuery.data?.data ?? [];
|
|
48
|
+
if (!optionId)
|
|
49
|
+
return options;
|
|
50
|
+
const selected = options.find((option) => option.id === optionId);
|
|
51
|
+
const rest = options.filter((option) => option.id !== optionId);
|
|
52
|
+
return selected ? [selected, ...rest] : options;
|
|
53
|
+
}, [optionsQuery.data?.data, optionId]);
|
|
54
|
+
const optionUnitQueries = useQueries({
|
|
55
|
+
queries: productOptions.map((option) => ({
|
|
56
|
+
...getOptionUnitsQueryOptions(productsClient, {
|
|
57
|
+
optionId: option.id,
|
|
58
|
+
limit: 100,
|
|
59
|
+
}),
|
|
60
|
+
enabled: enabled && Boolean(productId),
|
|
61
|
+
})),
|
|
62
|
+
});
|
|
63
|
+
const optionUnitRows = React.useMemo(() => {
|
|
64
|
+
const rows = [];
|
|
65
|
+
productOptions.forEach((option, index) => {
|
|
66
|
+
const units = optionUnitQueries[index]?.data?.data ?? [];
|
|
67
|
+
rows.push(...units.map((unit) => optionUnitToStepperUnit(option, unit, units.length)));
|
|
68
|
+
});
|
|
69
|
+
return rows;
|
|
70
|
+
}, [productOptions, optionUnitQueries]);
|
|
71
|
+
// optionUnitId → optionId lookup, derived from the product's own option
|
|
72
|
+
// catalog. The slot-availability endpoint only returns option_unit rows
|
|
73
|
+
// for the slot's bound option and doesn't stamp the option_id on each
|
|
74
|
+
// row, so we look it up from the units we already fetched per option.
|
|
75
|
+
const optionByUnitId = React.useMemo(() => {
|
|
76
|
+
const map = new Map();
|
|
77
|
+
for (const unit of optionUnitRows) {
|
|
78
|
+
if (unit.optionId)
|
|
79
|
+
map.set(unit.optionUnitId, unit.optionId);
|
|
80
|
+
}
|
|
81
|
+
return map;
|
|
82
|
+
}, [optionUnitRows]);
|
|
83
|
+
const productUnitById = React.useMemo(() => {
|
|
84
|
+
return new Map(optionUnitRows.map((unit) => [unit.optionUnitId, unit]));
|
|
85
|
+
}, [optionUnitRows]);
|
|
86
|
+
// The slot's bound option, derived from the first availability row.
|
|
87
|
+
// `null` when the slot is product-level (no option_id) — that path goes
|
|
88
|
+
// through the product-level fallback below.
|
|
89
|
+
const slotOptionId = React.useMemo(() => resolveSlotOptionId(availability.data?.data ?? [], optionByUnitId, optionId ?? null), [availability.data?.data, optionByUnitId, optionId]);
|
|
90
|
+
const availabilityUnitRows = React.useMemo(() => (availability.data?.data ?? []).map((unit) => {
|
|
91
|
+
const productUnit = productUnitById.get(unit.optionUnitId);
|
|
92
|
+
return {
|
|
93
|
+
...unit,
|
|
94
|
+
optionId: productUnit?.optionId ?? slotOptionId ?? optionId ?? null,
|
|
95
|
+
unitCode: productUnit?.unitCode ?? null,
|
|
96
|
+
minAge: productUnit?.minAge ?? null,
|
|
97
|
+
maxAge: productUnit?.maxAge ?? null,
|
|
98
|
+
unitType: productUnit?.unitType ?? null,
|
|
99
|
+
};
|
|
100
|
+
}), [availability.data?.data, productUnitById, slotOptionId, optionId]);
|
|
101
|
+
// Slot-bound per-unit availability stays authoritative for the slot's
|
|
102
|
+
// option (real-time `remaining` from active bookings). For *other*
|
|
103
|
+
// options the same product offers, fall back to the product-level
|
|
104
|
+
// option_units so the operator can still pick a DBL/TWN even when the
|
|
105
|
+
// slot is option-scoped to SGL. Product-level slots (no option_id) hit
|
|
106
|
+
// the no-slot-rows branch and use the product fallback for everything.
|
|
107
|
+
// See issue #960.
|
|
108
|
+
const units = React.useMemo(() => {
|
|
109
|
+
const merged = mergeStepperUnits(availabilityUnitRows, optionUnitRows, slotOptionId, Boolean(slotId));
|
|
110
|
+
// Journey: the option is already chosen, so show ONLY its units. Other
|
|
111
|
+
// options' rooms aren't bookable under the selected option (an option
|
|
112
|
+
// with no rooms of its own correctly shows none).
|
|
113
|
+
if (restrictToOption && optionId)
|
|
114
|
+
return merged.filter((unit) => unit.optionId === optionId);
|
|
115
|
+
return merged;
|
|
116
|
+
}, [availabilityUnitRows, optionUnitRows, slotOptionId, slotId, restrictToOption, optionId]);
|
|
117
|
+
const invalidOptionUnitIdSet = React.useMemo(() => new Set(invalidOptionUnitIds), [invalidOptionUnitIds]);
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
onUnitsChange?.(units);
|
|
120
|
+
}, [onUnitsChange, units]);
|
|
121
|
+
// Person-priced options are grouped by option: operators choose pax
|
|
122
|
+
// count, then traveler rows split Adult / Child / Infant. Inventory
|
|
123
|
+
// options are different: rooms and vehicles are physical containers,
|
|
124
|
+
// so each room/vehicle unit must be selectable independently.
|
|
125
|
+
const optionRows = React.useMemo(() => {
|
|
126
|
+
const groups = new Map();
|
|
127
|
+
for (const unit of units) {
|
|
128
|
+
const key = unit.optionId ?? unit.optionUnitId;
|
|
129
|
+
const entry = groups.get(key);
|
|
130
|
+
if (entry) {
|
|
131
|
+
entry.allUnits.push(unit);
|
|
132
|
+
// Prefer an explicit ADULT unit as primary; fall back to whatever
|
|
133
|
+
// arrived first.
|
|
134
|
+
if (isAdultUnit(unit) && !isAdultUnit(entry.primary))
|
|
135
|
+
entry.primary = unit;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
groups.set(key, { primary: unit, allUnits: [unit] });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(groups.entries()).flatMap(([optionKey, group]) => {
|
|
142
|
+
const optionName = productOptions.find((option) => option.id === optionKey)?.name ?? group.primary.unitName;
|
|
143
|
+
const inventoryUnits = group.allUnits.filter(isInventoryUnit);
|
|
144
|
+
if (inventoryUnits.length > 0) {
|
|
145
|
+
return inventoryUnits.map((unit) => ({
|
|
146
|
+
optionKey: unit.optionUnitId,
|
|
147
|
+
optionName: unit.unitName,
|
|
148
|
+
primary: unit,
|
|
149
|
+
allUnits: [unit],
|
|
150
|
+
totalRemaining: unit.remaining,
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
const totalRemaining = group.allUnits.reduce((acc, unit) => {
|
|
154
|
+
if (unit.remaining === null)
|
|
155
|
+
return null;
|
|
156
|
+
if (acc === null)
|
|
157
|
+
return null;
|
|
158
|
+
return acc + unit.remaining;
|
|
159
|
+
}, 0);
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
optionKey,
|
|
163
|
+
optionName,
|
|
164
|
+
primary: group.primary,
|
|
165
|
+
allUnits: group.allUnits,
|
|
166
|
+
totalRemaining,
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
});
|
|
170
|
+
}, [units, productOptions]);
|
|
171
|
+
if (!slotId && !productId && !optionId) {
|
|
172
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.noOption })] }));
|
|
173
|
+
}
|
|
174
|
+
// Both data sources need to resolve before declaring an empty result
|
|
175
|
+
// — slot units may legitimately be empty (product-level slot), and
|
|
176
|
+
// we don't want to flash the empty state before option-level units
|
|
177
|
+
// finish loading.
|
|
178
|
+
const optionsLoaded = optionsQuery.isSuccess && optionUnitQueries.every((query) => query.isSuccess);
|
|
179
|
+
const loaded = slotId ? availability.isSuccess && optionsLoaded : optionsLoaded;
|
|
180
|
+
if (loaded && units.length === 0) {
|
|
181
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.noUnits })] }));
|
|
182
|
+
}
|
|
183
|
+
const setQuantity = (unitId, qty) => {
|
|
184
|
+
const next = { ...value.quantities };
|
|
185
|
+
if (qty <= 0) {
|
|
186
|
+
delete next[unitId];
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
next[unitId] = qty;
|
|
190
|
+
}
|
|
191
|
+
onChange({ quantities: next });
|
|
192
|
+
};
|
|
193
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("div", { className: "flex flex-col gap-2", children: optionRows.map(({ optionKey, optionName, primary, allUnits, totalRemaining }) => {
|
|
194
|
+
const qty = value.quantities[primary.optionUnitId] ?? 0;
|
|
195
|
+
const isInvalid = optionRowHasInvalidUnit(allUnits, invalidOptionUnitIdSet);
|
|
196
|
+
const remainingLabel = resolveOptionRemainingLabel({
|
|
197
|
+
totalRemaining,
|
|
198
|
+
units: allUnits,
|
|
199
|
+
slotHasFiniteCapacity,
|
|
200
|
+
remaining: merged.remaining,
|
|
201
|
+
unlimited: merged.unlimited,
|
|
202
|
+
fillsSlotCapacity: merged.fillsSlotCapacity,
|
|
203
|
+
});
|
|
204
|
+
const atMax = totalRemaining !== null && qty >= totalRemaining;
|
|
205
|
+
return (_jsxs("div", { className: `flex items-center gap-3 rounded-md border px-3 py-2 ${isInvalid ? "border-destructive/70 bg-destructive/5 ring-1 ring-destructive/20" : ""}`, "aria-invalid": isInvalid ? true : undefined, children: [_jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium", children: [_jsx("span", { children: optionName }), isInvalid ? (_jsx("span", { className: "rounded-sm bg-destructive/10 px-1.5 py-0.5 text-[10px] font-medium text-destructive", children: merged.reviewLine })) : null] }), _jsx("div", { className: "text-xs text-muted-foreground", children: remainingLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(primary.optionUnitId, Math.max(0, qty - 1)), disabled: qty <= 0, "aria-label": `${merged.decreaseUnitPrefix} ${optionName}`, children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "min-w-[1.5rem] text-center text-sm tabular-nums", children: qty }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(primary.optionUnitId, qty + 1), disabled: atMax, "aria-label": `${merged.increaseUnitPrefix} ${optionName}`, children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }, optionKey));
|
|
206
|
+
}) })] }));
|
|
207
|
+
}
|
|
208
|
+
export function resolveOptionRemainingLabel({ totalRemaining, units, slotHasFiniteCapacity, remaining, unlimited, fillsSlotCapacity, }) {
|
|
209
|
+
if (totalRemaining !== null)
|
|
210
|
+
return `${totalRemaining} ${remaining}`;
|
|
211
|
+
if (slotHasFiniteCapacity && units.length > 0 && units.every(isPersonUnit)) {
|
|
212
|
+
return fillsSlotCapacity ?? unlimited;
|
|
213
|
+
}
|
|
214
|
+
return unlimited;
|
|
215
|
+
}
|
|
216
|
+
export function optionRowHasInvalidUnit(units, invalidOptionUnitIds) {
|
|
217
|
+
return units.some((unit) => invalidOptionUnitIds.has(unit.optionUnitId));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Returns the `optionId` the slot is bound to, derived from the first
|
|
221
|
+
* slot-availability row whose `optionUnitId` we can map to a known
|
|
222
|
+
* product option. Falls back to the caller's `fallbackOptionId` (the
|
|
223
|
+
* dialog's currently-selected option) when no rows resolve — that lets
|
|
224
|
+
* the existing `optionId` prop drive the previous-behavior path for
|
|
225
|
+
* unit pickers that haven't loaded yet.
|
|
226
|
+
*/
|
|
227
|
+
export function resolveSlotOptionId(slotRows, optionByUnitId, fallbackOptionId) {
|
|
228
|
+
for (const row of slotRows) {
|
|
229
|
+
const resolved = optionByUnitId.get(row.optionUnitId);
|
|
230
|
+
if (resolved)
|
|
231
|
+
return resolved;
|
|
232
|
+
}
|
|
233
|
+
return fallbackOptionId;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Merges slot-bound per-unit availability with the product's option-unit
|
|
237
|
+
* catalog. Slot rows are authoritative for the slot's option (they carry
|
|
238
|
+
* real-time `remaining`); product-level rows fill in the other options
|
|
239
|
+
* the product offers so the operator can still pick mixes the slot isn't
|
|
240
|
+
* explicitly tracking. When the slot is product-level (no `option_id`)
|
|
241
|
+
* or hasn't loaded slot rows yet, the product-level rows cover everything.
|
|
242
|
+
*/
|
|
243
|
+
export function mergeStepperUnits(slotRows, productRows, slotOptionId, hasSlot) {
|
|
244
|
+
if (!hasSlot || slotRows.length === 0 || !slotOptionId) {
|
|
245
|
+
return [...productRows];
|
|
246
|
+
}
|
|
247
|
+
const otherOptionRows = productRows.filter((row) => row.optionId !== slotOptionId);
|
|
248
|
+
return [...slotRows, ...otherOptionRows];
|
|
249
|
+
}
|
|
250
|
+
function isAdultUnit(unit) {
|
|
251
|
+
// The seed creates ADULT / CHILD / SENIOR unit codes; the stepper
|
|
252
|
+
// unit object doesn't carry the code, so fall back to name-matching
|
|
253
|
+
// when the upstream code isn't surfaced.
|
|
254
|
+
return /\badult\b/i.test(unit.unitName);
|
|
255
|
+
}
|
|
256
|
+
function isPersonUnit(unit) {
|
|
257
|
+
return unit.unitType === "person";
|
|
258
|
+
}
|
|
259
|
+
function isInventoryUnit(unit) {
|
|
260
|
+
return unit.unitType === "room" || unit.unitType === "vehicle";
|
|
261
|
+
}
|
|
262
|
+
function optionUnitToStepperUnit(option, unit, unitCount) {
|
|
263
|
+
return {
|
|
264
|
+
optionId: option.id,
|
|
265
|
+
optionUnitId: unit.id,
|
|
266
|
+
unitName: unitCount === 1 ? option.name : `${option.name} - ${unit.name}`,
|
|
267
|
+
unitCode: unit.code,
|
|
268
|
+
minAge: unit.minAge,
|
|
269
|
+
maxAge: unit.maxAge,
|
|
270
|
+
unitType: unit.unitType,
|
|
271
|
+
occupancyMax: unit.occupancyMax,
|
|
272
|
+
initial: null,
|
|
273
|
+
reserved: 0,
|
|
274
|
+
remaining: unit.maxQuantity ?? null,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type PaymentScheduleMode = "full" | "split";
|
|
2
|
+
export interface PaymentInstallment {
|
|
3
|
+
/** Stable React key, regenerated on row add. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Amount for this installment. In Full mode the field is implicit (uses the booking total). */
|
|
6
|
+
amountCents: number | null;
|
|
7
|
+
dueDate: string | null;
|
|
8
|
+
alreadyPaid: boolean;
|
|
9
|
+
paymentDate: string | null;
|
|
10
|
+
paymentMethod: string;
|
|
11
|
+
paymentReference: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PaymentScheduleValue {
|
|
14
|
+
mode: PaymentScheduleMode;
|
|
15
|
+
/**
|
|
16
|
+
* Full mode keeps a single installment whose amount mirrors the
|
|
17
|
+
* booking total at submit time. Split keeps two or more installments
|
|
18
|
+
* the operator types amounts + due dates for.
|
|
19
|
+
*/
|
|
20
|
+
installments: PaymentInstallment[];
|
|
21
|
+
}
|
|
22
|
+
export declare function createInstallment(overrides?: Partial<PaymentInstallment>): PaymentInstallment;
|
|
23
|
+
export declare const emptyPaymentScheduleValue: PaymentScheduleValue;
|
|
24
|
+
/**
|
|
25
|
+
* Factory for the initial `PaymentScheduleValue` when the booking has a
|
|
26
|
+
* known departure (slot or product start date). The single Full-mode
|
|
27
|
+
* installment defaults to the departure day so operators don't have to
|
|
28
|
+
* re-pick it on every fresh form, and so the dueDate field doesn't
|
|
29
|
+
* surface today's date for a trip starting weeks/months later.
|
|
30
|
+
*
|
|
31
|
+
* When `departureDate` is null/undefined we fall back to `emptyPaymentScheduleValue`
|
|
32
|
+
* which uses today.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createPaymentScheduleValue(departureDate: string | null | undefined): PaymentScheduleValue;
|
|
35
|
+
export interface PaymentScheduleSectionProps {
|
|
36
|
+
value: PaymentScheduleValue;
|
|
37
|
+
onChange: (value: PaymentScheduleValue) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Booking total in cents. Used to drive the Full-mode amount (which
|
|
40
|
+
* mirrors the total at submit) and to default split-mode amounts to
|
|
41
|
+
* an even share when the operator switches modes / adds rows.
|
|
42
|
+
*/
|
|
43
|
+
totalAmountCents?: number;
|
|
44
|
+
/**
|
|
45
|
+
* ISO date of the booking's departure / service start. When provided,
|
|
46
|
+
* split-mode installments default to dates between today and the
|
|
47
|
+
* departure (rather than `today + 30·i`, which can land *after*
|
|
48
|
+
* departure for short lead times).
|
|
49
|
+
*/
|
|
50
|
+
departureDate?: string | null;
|
|
51
|
+
/** Used only for display formatting (e.g., "EUR"). No server-side effect. */
|
|
52
|
+
currency?: string;
|
|
53
|
+
labels?: {
|
|
54
|
+
heading?: string;
|
|
55
|
+
modeUnpaid?: string;
|
|
56
|
+
modeFull?: string;
|
|
57
|
+
modeAdvance?: string;
|
|
58
|
+
modeSplit?: string;
|
|
59
|
+
dueDate?: string;
|
|
60
|
+
amount?: string;
|
|
61
|
+
firstInstallment?: string;
|
|
62
|
+
secondInstallment?: string;
|
|
63
|
+
preset5050?: string;
|
|
64
|
+
unpaidHint?: string;
|
|
65
|
+
totalDue?: string;
|
|
66
|
+
scheduledTotal?: string;
|
|
67
|
+
remaining?: string;
|
|
68
|
+
alreadyPaid?: string;
|
|
69
|
+
paymentDate?: string;
|
|
70
|
+
paymentMethod?: string;
|
|
71
|
+
paymentReference?: string;
|
|
72
|
+
addInstallment?: string;
|
|
73
|
+
removeInstallment?: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Payment schedule picker for booking-create flows. The operator picks
|
|
78
|
+
* Full (one due date for the whole amount) or Split (N installments,
|
|
79
|
+
* default two with 50/50, but the "+" button adds as many as needed).
|
|
80
|
+
*
|
|
81
|
+
* Switching modes prefills sensible defaults: Full → today's due date;
|
|
82
|
+
* Split → today + today+30 with a 50/50 amount split. Everything
|
|
83
|
+
* remains editable so this is just a low-friction starting point.
|
|
84
|
+
*
|
|
85
|
+
* The section produces a controlled `PaymentScheduleValue` — actually
|
|
86
|
+
* creating `booking_payment_schedules` rows happens in the parent at
|
|
87
|
+
* submit time, after the booking exists (schedules have a FK to
|
|
88
|
+
* `bookings.id`).
|
|
89
|
+
*/
|
|
90
|
+
export declare function PaymentScheduleSection({ value, onChange, totalAmountCents, departureDate, currency, labels, }: PaymentScheduleSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
//# sourceMappingURL=payment-schedule-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-schedule-section.d.ts","sourceRoot":"","sources":["../../src/components/payment-schedule-section.tsx"],"names":[],"mappings":"AAsBA,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,CAAA;AAElD,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,EAAE,EAAE,MAAM,CAAA;IACV,gGAAgG;IAChG,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,OAAO,CAAA;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,mBAAmB,CAAA;IACzB;;;;OAIG;IACH,YAAY,EAAE,kBAAkB,EAAE,CAAA;CACnC;AAkBD,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,kBAAkB,CAAM,GAAG,kBAAkB,CAWjG;AAED,eAAO,MAAM,yBAAyB,EAAE,oBAGvC,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,oBAAoB,CAKtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,oBAAoB,CAAA;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAC3B,CAAA;CACF;AA+CD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,MAAM,GACP,EAAE,2BAA2B,2CAgQ7B"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button, Checkbox, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
|
|
4
|
+
import { CurrencyInput } from "@voyant-travel/ui/components/currency-input";
|
|
5
|
+
import { DatePicker } from "@voyant-travel/ui/components/date-picker";
|
|
6
|
+
import { Plus, X } from "lucide-react";
|
|
7
|
+
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
|
|
8
|
+
let installmentSeq = 0;
|
|
9
|
+
function nextInstallmentId() {
|
|
10
|
+
installmentSeq += 1;
|
|
11
|
+
return `inst_${installmentSeq}_${Math.random().toString(36).slice(2, 6)}`;
|
|
12
|
+
}
|
|
13
|
+
function todayIso() {
|
|
14
|
+
return new Date().toISOString().slice(0, 10);
|
|
15
|
+
}
|
|
16
|
+
function plusDaysIso(base, days) {
|
|
17
|
+
const d = new Date(base);
|
|
18
|
+
d.setDate(d.getDate() + days);
|
|
19
|
+
return d.toISOString().slice(0, 10);
|
|
20
|
+
}
|
|
21
|
+
export function createInstallment(overrides = {}) {
|
|
22
|
+
return {
|
|
23
|
+
id: nextInstallmentId(),
|
|
24
|
+
amountCents: null,
|
|
25
|
+
dueDate: null,
|
|
26
|
+
alreadyPaid: false,
|
|
27
|
+
paymentDate: null,
|
|
28
|
+
paymentMethod: "bank_transfer", // i18n-literal-ok payment-method enum
|
|
29
|
+
paymentReference: "",
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export const emptyPaymentScheduleValue = {
|
|
34
|
+
mode: "full",
|
|
35
|
+
installments: [createInstallment({ dueDate: todayIso() })],
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Factory for the initial `PaymentScheduleValue` when the booking has a
|
|
39
|
+
* known departure (slot or product start date). The single Full-mode
|
|
40
|
+
* installment defaults to the departure day so operators don't have to
|
|
41
|
+
* re-pick it on every fresh form, and so the dueDate field doesn't
|
|
42
|
+
* surface today's date for a trip starting weeks/months later.
|
|
43
|
+
*
|
|
44
|
+
* When `departureDate` is null/undefined we fall back to `emptyPaymentScheduleValue`
|
|
45
|
+
* which uses today.
|
|
46
|
+
*/
|
|
47
|
+
export function createPaymentScheduleValue(departureDate) {
|
|
48
|
+
return {
|
|
49
|
+
mode: "full",
|
|
50
|
+
installments: [createInstallment({ dueDate: departureDate ?? todayIso() })],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Distribute `total` evenly across `count` installments. Last row picks
|
|
55
|
+
* up the remainder so the sum is exact (e.g. 100 / 3 → 33, 33, 34).
|
|
56
|
+
*/
|
|
57
|
+
function distributeEvenly(total, count) {
|
|
58
|
+
if (count <= 0)
|
|
59
|
+
return [];
|
|
60
|
+
const base = Math.floor(total / count);
|
|
61
|
+
const out = Array.from({ length: count }, () => base);
|
|
62
|
+
const remainder = total - base * count;
|
|
63
|
+
if (remainder > 0 && out.length > 0) {
|
|
64
|
+
const last = out.length - 1;
|
|
65
|
+
out[last] = (out[last] ?? 0) + remainder;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Pick a default due date for installment #`index` (0-based) within a
|
|
71
|
+
* series of `count` installments. When the booking has a known
|
|
72
|
+
* departure date, the series spreads evenly between today and one day
|
|
73
|
+
* before departure (so every installment is collected pre-trip).
|
|
74
|
+
* Without a departure date we fall back to `today + 30·i`.
|
|
75
|
+
*/
|
|
76
|
+
function defaultDueDateForIndex(index, count, departureDate) {
|
|
77
|
+
const today = todayIso();
|
|
78
|
+
if (count <= 1)
|
|
79
|
+
return today;
|
|
80
|
+
const departure = (departureDate ?? "").trim();
|
|
81
|
+
const validDeparture = departure && Number.isFinite(new Date(departure).getTime());
|
|
82
|
+
if (!validDeparture)
|
|
83
|
+
return plusDaysIso(today, index * 30);
|
|
84
|
+
// Last installment lands one day before departure so the final
|
|
85
|
+
// collection cleared the rail before the customer travels.
|
|
86
|
+
const lastDay = plusDaysIso(departure, -1);
|
|
87
|
+
const span = (new Date(lastDay).getTime() - new Date(today).getTime()) / (1000 * 60 * 60 * 24);
|
|
88
|
+
if (span <= 0) {
|
|
89
|
+
// Departure is today or tomorrow — collapse every installment onto today.
|
|
90
|
+
return today;
|
|
91
|
+
}
|
|
92
|
+
const offset = Math.round((index * span) / (count - 1));
|
|
93
|
+
return plusDaysIso(today, offset);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Payment schedule picker for booking-create flows. The operator picks
|
|
97
|
+
* Full (one due date for the whole amount) or Split (N installments,
|
|
98
|
+
* default two with 50/50, but the "+" button adds as many as needed).
|
|
99
|
+
*
|
|
100
|
+
* Switching modes prefills sensible defaults: Full → today's due date;
|
|
101
|
+
* Split → today + today+30 with a 50/50 amount split. Everything
|
|
102
|
+
* remains editable so this is just a low-friction starting point.
|
|
103
|
+
*
|
|
104
|
+
* The section produces a controlled `PaymentScheduleValue` — actually
|
|
105
|
+
* creating `booking_payment_schedules` rows happens in the parent at
|
|
106
|
+
* submit time, after the booking exists (schedules have a FK to
|
|
107
|
+
* `bookings.id`).
|
|
108
|
+
*/
|
|
109
|
+
export function PaymentScheduleSection({ value, onChange, totalAmountCents, departureDate, currency, labels, }) {
|
|
110
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
111
|
+
const { formatCurrency, formatNumber } = useBookingsUiI18nOrDefault();
|
|
112
|
+
const merged = { ...messages.paymentScheduleSection.labels, ...labels };
|
|
113
|
+
const set = (patch) => onChange({ ...value, ...patch });
|
|
114
|
+
const total = typeof totalAmountCents === "number" ? totalAmountCents : null;
|
|
115
|
+
const modes = [
|
|
116
|
+
{ id: "full", label: merged.modeFull },
|
|
117
|
+
{ id: "split", label: merged.modeSplit },
|
|
118
|
+
];
|
|
119
|
+
const switchMode = (nextMode) => {
|
|
120
|
+
if (nextMode === value.mode)
|
|
121
|
+
return;
|
|
122
|
+
if (nextMode === "full") {
|
|
123
|
+
const preserved = value.installments[0];
|
|
124
|
+
onChange({
|
|
125
|
+
mode: "full",
|
|
126
|
+
installments: [
|
|
127
|
+
createInstallment({
|
|
128
|
+
dueDate: preserved?.dueDate ?? todayIso(),
|
|
129
|
+
alreadyPaid: preserved?.alreadyPaid ?? false,
|
|
130
|
+
paymentDate: preserved?.paymentDate ?? null,
|
|
131
|
+
paymentMethod: preserved?.paymentMethod ?? "bank_transfer",
|
|
132
|
+
paymentReference: preserved?.paymentReference ?? "",
|
|
133
|
+
}),
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// → split. Seed with two installments if we don't already have ≥2.
|
|
139
|
+
const seedCount = Math.max(2, value.installments.length);
|
|
140
|
+
const evenAmounts = total != null ? distributeEvenly(total, seedCount) : [];
|
|
141
|
+
const installments = Array.from({ length: seedCount }, (_, idx) => {
|
|
142
|
+
const existing = value.installments[idx];
|
|
143
|
+
return createInstallment({
|
|
144
|
+
amountCents: existing?.amountCents ?? evenAmounts[idx] ?? null,
|
|
145
|
+
dueDate: existing?.dueDate ?? defaultDueDateForIndex(idx, seedCount, departureDate),
|
|
146
|
+
alreadyPaid: existing?.alreadyPaid ?? false,
|
|
147
|
+
paymentDate: existing?.paymentDate ?? null,
|
|
148
|
+
paymentMethod: existing?.paymentMethod ?? "bank_transfer",
|
|
149
|
+
paymentReference: existing?.paymentReference ?? "",
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
onChange({ mode: "split", installments });
|
|
153
|
+
};
|
|
154
|
+
const updateInstallment = (idx, patch) => {
|
|
155
|
+
const next = value.installments.map((row, i) => (i === idx ? { ...row, ...patch } : row));
|
|
156
|
+
set({ installments: next });
|
|
157
|
+
};
|
|
158
|
+
const addInstallment = () => {
|
|
159
|
+
const count = value.installments.length + 1;
|
|
160
|
+
const evenAmounts = total != null ? distributeEvenly(total, count) : [];
|
|
161
|
+
const next = value.installments.map((row, i) => ({
|
|
162
|
+
...row,
|
|
163
|
+
amountCents: total != null ? (evenAmounts[i] ?? row.amountCents) : row.amountCents,
|
|
164
|
+
}));
|
|
165
|
+
next.push(createInstallment({
|
|
166
|
+
amountCents: total != null ? (evenAmounts[count - 1] ?? null) : null,
|
|
167
|
+
dueDate: defaultDueDateForIndex(count - 1, count, departureDate),
|
|
168
|
+
}));
|
|
169
|
+
set({ installments: next });
|
|
170
|
+
};
|
|
171
|
+
const removeInstallment = (idx) => {
|
|
172
|
+
if (value.installments.length <= 2)
|
|
173
|
+
return;
|
|
174
|
+
const remaining = value.installments.filter((_, i) => i !== idx);
|
|
175
|
+
// Redistribute amounts so the remaining rows still cover the booking
|
|
176
|
+
// total — otherwise the "Remaining" tracker reports a phantom gap
|
|
177
|
+
// that doesn't correspond to anything the operator can act on.
|
|
178
|
+
const evenAmounts = total != null ? distributeEvenly(total, remaining.length) : [];
|
|
179
|
+
const next = remaining.map((row, i) => ({
|
|
180
|
+
...row,
|
|
181
|
+
amountCents: total != null ? (evenAmounts[i] ?? row.amountCents) : row.amountCents,
|
|
182
|
+
}));
|
|
183
|
+
set({ installments: next });
|
|
184
|
+
};
|
|
185
|
+
const scheduledTotal = value.mode === "full"
|
|
186
|
+
? (total ?? 0)
|
|
187
|
+
: value.installments.reduce((sum, row) => sum + (row.amountCents ?? 0), 0);
|
|
188
|
+
const remaining = total === null ? null : Math.max(0, total - scheduledTotal);
|
|
189
|
+
const formatAmount = (cents) => {
|
|
190
|
+
if (cents === null)
|
|
191
|
+
return "-";
|
|
192
|
+
return currency
|
|
193
|
+
? formatCurrency(cents / 100, currency)
|
|
194
|
+
: formatNumber(cents / 100, {
|
|
195
|
+
minimumFractionDigits: 2,
|
|
196
|
+
maximumFractionDigits: 2,
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const paymentMethodLabels = messages.bookingPaymentsSummary.paymentMethodLabels;
|
|
200
|
+
const renderPaidFields = (idx, installment) => {
|
|
201
|
+
const checkboxId = `payment-schedule-installment-${idx}-already-paid`;
|
|
202
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-dashed p-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: checkboxId, checked: installment.alreadyPaid, onCheckedChange: (next) => updateInstallment(idx, { alreadyPaid: next === true }) }), _jsx(Label, { htmlFor: checkboxId, className: "cursor-pointer text-xs", children: merged.alreadyPaid })] }), installment.alreadyPaid ? (_jsxs("div", { className: "grid gap-2 sm:grid-cols-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: merged.paymentDate }), _jsx(DatePicker, { value: installment.paymentDate ?? "", onChange: (nextValue) => updateInstallment(idx, { paymentDate: nextValue }) })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: merged.paymentMethod }), _jsxs(Select, { value: installment.paymentMethod, onValueChange: (nextValue) => updateInstallment(idx, { paymentMethod: nextValue ?? "bank_transfer" }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ["bank_transfer", "credit_card", "cash", "voucher", "other"].map((method) => (_jsx(SelectItem, { value: method, children: paymentMethodLabels[method === "credit_card" ? "card" : method] }, method))) })] })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: merged.paymentReference }), _jsx(Input, { value: installment.paymentReference, onChange: (event) => updateInstallment(idx, { paymentReference: event.target.value }) })] })] })) : null] }));
|
|
203
|
+
};
|
|
204
|
+
const fullInstallment = value.installments[0] ?? createInstallment({ dueDate: todayIso() });
|
|
205
|
+
return (_jsxs("div", { className: "flex flex-col gap-3 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsxs("div", { className: "grid gap-2 rounded-md bg-muted/40 p-2 text-xs sm:grid-cols-3", children: [_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: merged.totalDue }), _jsx("span", { className: "font-medium tabular-nums", children: formatAmount(total) })] }), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: merged.scheduledTotal }), _jsx("span", { className: "font-medium tabular-nums", children: formatAmount(scheduledTotal) })] }), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: merged.remaining }), _jsx("span", { className: "font-medium tabular-nums", children: formatAmount(remaining) })] })] }), _jsx("div", { className: "flex flex-wrap items-center gap-2", children: modes.map((mode) => (_jsx(Button, { type: "button", size: "sm", variant: value.mode === mode.id ? "default" : "ghost", onClick: () => switchMode(mode.id), children: mode.label }, mode.id))) }), value.mode === "full" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: merged.dueDate }), _jsx(DatePicker, { value: fullInstallment.dueDate ?? "", onChange: (nextValue) => updateInstallment(0, { dueDate: nextValue }) })] }), renderPaidFields(0, fullInstallment)] })), value.mode === "split" && (_jsxs("div", { className: "flex flex-col gap-3", children: [value.installments.map((installment, idx) => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: merged.firstInstallment.replace(/\b1\b|first|primul|1st/i, String(idx + 1)) }), value.installments.length > 2 ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": merged.removeInstallment, onClick: () => removeInstallment(idx), children: _jsx(X, { className: "h-3.5 w-3.5" }) })) : null] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsx(CurrencyInput, { placeholder: merged.amount, value: installment.amountCents, onChange: (next) => updateInstallment(idx, { amountCents: next }), currency: currency }), _jsx(DatePicker, { value: installment.dueDate ?? "", onChange: (nextValue) => updateInstallment(idx, { dueDate: nextValue }) })] }), renderPaidFields(idx, installment)] }, installment.id))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addInstallment, className: "self-start", children: [_jsx(Plus, { className: "mr-1 h-3.5 w-3.5" }), merged.addInstallment] })] }))] }));
|
|
206
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type PersonPickerMode = "existing" | "new";
|
|
2
|
+
export type BillingTargetMode = "person" | "organization";
|
|
3
|
+
export interface NewPersonValue {
|
|
4
|
+
firstName: string;
|
|
5
|
+
lastName: string;
|
|
6
|
+
email: string;
|
|
7
|
+
phone: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PersonPickerValue {
|
|
10
|
+
billTo?: BillingTargetMode;
|
|
11
|
+
mode: PersonPickerMode;
|
|
12
|
+
/** Set when mode === "existing". */
|
|
13
|
+
personId: string;
|
|
14
|
+
/** Used when mode === "new". */
|
|
15
|
+
newPerson: NewPersonValue;
|
|
16
|
+
/** `null` = no organization attached. */
|
|
17
|
+
organizationId: string | null;
|
|
18
|
+
}
|
|
19
|
+
export declare const emptyNewPerson: NewPersonValue;
|
|
20
|
+
export declare const emptyPersonPickerValue: PersonPickerValue;
|
|
21
|
+
export interface PersonPickerSectionProps {
|
|
22
|
+
value: PersonPickerValue;
|
|
23
|
+
onChange: (value: PersonPickerValue) => void;
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
showOrganization?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Hide the person-vs-organization toggle while still allowing org mode
|
|
28
|
+
* (driven by `value.billTo`). Use when an outer control already chooses
|
|
29
|
+
* the target — e.g. the journey's Buyer type radio.
|
|
30
|
+
*/
|
|
31
|
+
hideTargetToggle?: boolean;
|
|
32
|
+
labels?: {
|
|
33
|
+
person?: string;
|
|
34
|
+
organization?: string;
|
|
35
|
+
billTo?: string;
|
|
36
|
+
billToPerson?: string;
|
|
37
|
+
billToOrganization?: string;
|
|
38
|
+
createNewPerson?: string;
|
|
39
|
+
createNewOrganization?: string;
|
|
40
|
+
createPersonSheetTitle?: string;
|
|
41
|
+
createOrganizationSheetTitle?: string;
|
|
42
|
+
editPerson?: string;
|
|
43
|
+
editOrganization?: string;
|
|
44
|
+
editPersonSheetTitle?: string;
|
|
45
|
+
editOrganizationSheetTitle?: string;
|
|
46
|
+
selectExistingPerson?: string;
|
|
47
|
+
personSearchPlaceholder?: string;
|
|
48
|
+
personSelectPlaceholder?: string;
|
|
49
|
+
personEmpty?: string;
|
|
50
|
+
firstName?: string;
|
|
51
|
+
firstNamePlaceholder?: string;
|
|
52
|
+
lastName?: string;
|
|
53
|
+
lastNamePlaceholder?: string;
|
|
54
|
+
email?: string;
|
|
55
|
+
emailPlaceholder?: string;
|
|
56
|
+
phone?: string;
|
|
57
|
+
phonePlaceholder?: string;
|
|
58
|
+
organizationSearchPlaceholder?: string;
|
|
59
|
+
organizationSelectPlaceholder?: string;
|
|
60
|
+
organizationEmpty?: string;
|
|
61
|
+
organizationNone?: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Billing target picker for booking create.
|
|
66
|
+
*
|
|
67
|
+
* State is fully controlled. The embedded create sheets use the CRM forms and
|
|
68
|
+
* select the newly-created person or organization after save.
|
|
69
|
+
*/
|
|
70
|
+
export declare function PersonPickerSection({ value, onChange, enabled, showOrganization, hideTargetToggle, labels, }: PersonPickerSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
//# sourceMappingURL=person-picker-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/person-picker-section.tsx"],"names":[],"mappings":"AAiCA,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,KAAK,CAAA;AACjD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,cAAc,CAAA;AAEzD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,IAAI,EAAE,gBAAgB,CAAA;IACtB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,gCAAgC;IAChC,SAAS,EAAE,cAAc,CAAA;IACzB,yCAAyC;IACzC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,eAAO,MAAM,cAAc,EAAE,cAK5B,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,iBAMpC,CAAA;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,iBAAiB,CAAA;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAA;QAC3B,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,qBAAqB,CAAC,EAAE,MAAM,CAAA;QAC9B,sBAAsB,CAAC,EAAE,MAAM,CAAA;QAC/B,4BAA4B,CAAC,EAAE,MAAM,CAAA;QACrC,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,oBAAoB,CAAC,EAAE,MAAM,CAAA;QAC7B,0BAA0B,CAAC,EAAE,MAAM,CAAA;QACnC,oBAAoB,CAAC,EAAE,MAAM,CAAA;QAC7B,uBAAuB,CAAC,EAAE,MAAM,CAAA;QAChC,uBAAuB,CAAC,EAAE,MAAM,CAAA;QAChC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,oBAAoB,CAAC,EAAE,MAAM,CAAA;QAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,6BAA6B,CAAC,EAAE,MAAM,CAAA;QACtC,6BAA6B,CAAC,EAAE,MAAM,CAAA;QACtC,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;CACF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,gBAAuB,EACvB,gBAAwB,EACxB,MAAM,GACP,EAAE,wBAAwB,2CAiV1B"}
|