import { type ProductOptionRecord } from "@voyant-travel/inventory-react"; import * as React from "react"; /** Quantity per option_unit id; omitted ids are treated as 0. */ export interface OptionUnitsStepperValue { quantities: Record; } export declare const emptyOptionUnitsStepperValue: OptionUnitsStepperValue; export interface OptionUnitsStepperUnit { optionId: string | null; optionUnitId: string; unitName: string; /** Stable code from the products schema (`ADULT`, `CHILD`, `SENIOR`, …) when present. */ unitCode?: string | null; /** Inclusive lower age bound for this unit, when configured. */ minAge?: number | null; /** Inclusive upper age bound for this unit, when configured. */ maxAge?: number | null; /** Unit category from option_units.unitType — person/group/room/vehicle/service/other. */ unitType?: "person" | "group" | "room" | "vehicle" | "service" | "other" | null; occupancyMax: number | null; initial: number | null; reserved: number; remaining: number | null; } export interface OptionUnitsStepperSectionProps { value: OptionUnitsStepperValue; onChange: (value: OptionUnitsStepperValue) => void; /** Product whose options become selectable room quantity rows. */ productId?: string; /** * Departure the operator picked. Departure-specific availability wins * when present; otherwise the section falls back to option-level units. */ slotId?: string; /** * Product option whose units should be shown before a departure is picked. * Departure-specific availability wins when `slotId` is present. */ optionId?: string | null; /** * When true, only the SELECTED option's units are shown (no cross-option * fallback). Use when the option is already chosen and other options' * rooms aren't bookable — e.g. the booking journey, where rooms are * nested under the picked option. Defaults to false (show all options' * units, the create-sheet's cross-option behavior). */ restrictToOption?: boolean; enabled?: boolean; onUnitsChange?: (units: OptionUnitsStepperUnit[]) => void; labels?: { heading?: string; noOption?: string; noSlot?: string; noUnits?: string; remaining?: string; unlimited?: string; fillsSlotCapacity?: string; reviewLine?: string; }; slotHasFiniteCapacity?: boolean; invalidOptionUnitIds?: readonly string[]; /** Catalog-sourced products provide option/unit shape through the live quote. */ providedOptions?: ReadonlyArray<{ id: string; name: string; }>; /** When present (including an empty array), skip owned product/availability lookups. */ providedUnits?: ReadonlyArray; } export interface OptionUnitsStepperRow { optionKey: string; optionName: string; primary: OptionUnitsStepperUnit; allUnits: OptionUnitsStepperUnit[]; totalRemaining: number | null; } /** * Rooms / per-unit stepper for booking-create flows. Drives * `GET /v1/admin/operations/availability/slots/:id/unit-availability` from #235 when a * departure is selected, and product option-level units before departure * selection, so operators can build "2 double rooms and 1 single" drafts. * * The section only tracks **intent** (how many of each unit the operator * wants to book). Actual hold/reservation happens when the parent submits * the booking — capacity drops the moment the reservation transaction * commits; the next refetch of `useSlotUnitAvailability` reflects it. * * ### Stepper bounds * * - Minimum is 0 (operator can deselect). * - Maximum is the unit's `remaining` count from the server. Unlimited * pools (`remaining === null`) have no upper bound. * - The server is the truth: entering `3 doubles` when only 2 remain just * disables the "+" button — we don't let the UI submit a request that * would 409 at insert time. */ export declare function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, restrictToOption, enabled, onUnitsChange, labels, slotHasFiniteCapacity, invalidOptionUnitIds, providedOptions, providedUnits, }: OptionUnitsStepperSectionProps): React.JSX.Element; export declare function resolveOptionRemainingLabel({ totalRemaining, units, slotHasFiniteCapacity, remaining, unlimited, fillsSlotCapacity, }: { totalRemaining: number | null; units: ReadonlyArray>; slotHasFiniteCapacity: boolean; remaining: string; unlimited: string; fillsSlotCapacity?: string; }): string; export declare function optionRowHasInvalidUnit(units: ReadonlyArray>, invalidOptionUnitIds: ReadonlySet): boolean; /** * Returns the `optionId` the slot is bound to, derived from the first * slot-availability row whose `optionUnitId` we can map to a known * product option. Falls back to the caller's `fallbackOptionId` (the * dialog's currently-selected option) when no rows resolve — that lets * the existing `optionId` prop drive the previous-behavior path for * unit pickers that haven't loaded yet. */ export declare function resolveSlotOptionId(slotRows: ReadonlyArray<{ optionUnitId: string; }>, optionByUnitId: ReadonlyMap, fallbackOptionId: string | null): string | null; /** * Merges slot-bound per-unit availability with the product's option-unit * catalog. Slot rows are authoritative for the slot's option (they carry * real-time `remaining`); product-level rows fill in the other options * the product offers so the operator can still pick mixes the slot isn't * explicitly tracking. When the slot is product-level (no `option_id`) * or hasn't loaded slot rows yet, the product-level rows cover everything. */ export declare function mergeStepperUnits(slotRows: ReadonlyArray, productRows: ReadonlyArray, slotOptionId: string | null, hasSlot: boolean): OptionUnitsStepperUnit[]; export declare function buildOptionUnitsStepperRows(units: ReadonlyArray, productOptions: ReadonlyArray>): OptionUnitsStepperRow[];