| @@ -5,8 +5,21 @@ | ||
| 5 | 5 | /* jshint esversion: 6 */ |
| 6 | 6 | |
| 7 | 7 | // Translation helpers (booking.js installs the same shims if it runs first; |
| 8 | 8 | // idempotent setup so either load order works). |
| 9 | +// Parse a numeric min/max attribute while preserving a legitimate 0. | |
| 10 | +// `parseInt(el.getAttribute('max')) || 99` treated max="0" — what a sold-out | |
| 11 | +// departure renders — as "unset" and fell back to the placeholder ceiling, so | |
| 12 | +// the guest selector stayed usable and ignored the departure's real capacity. | |
| 13 | +// Only an absent or non-numeric value may fall back. Installed once and shared | |
| 14 | +// by every frontend script, so load order does not matter. | |
| 15 | +if (typeof window.yatraNumOr !== 'function') { | |
| 16 | + window.yatraNumOr = function (raw, fallback) { | |
| 17 | + var parsed = parseInt(raw, 10); | |
| 18 | + return isNaN(parsed) ? fallback : parsed; | |
| 19 | + }; | |
| 20 | +} | |
| 21 | + | |
| 9 | 22 | (function () { |
| 10 | 23 | var i18n = (window.wp && window.wp.i18n) ? window.wp.i18n : null; |
| 11 | 24 | if (i18n && typeof i18n.__ === 'function') { |
| 12 | 25 | window.__ = function (text, domain) { return i18n.__(text, domain || 'yatra'); }; |
| @@ -192,9 +205,14 @@ | ||
| 192 | 205 | const decimalSep = (typeof cfg.decimalSeparator === 'string') ? cfg.decimalSeparator : '.'; |
| 193 | 206 | |
| 194 | 207 | const num = Number(amount) || 0; |
| 195 | 208 | |
| 196 | - const formatted = new Intl.NumberFormat(undefined, { | |
| 209 | + // Format with a FIXED 'en-US' base (grouping ',', decimal '.') so the | |
| 210 | + // replace-with-configured-separators below is deterministic. With the | |
| 211 | + // viewer's locale (undefined), a European browser already yields | |
| 212 | + // '1.234,56' and the naive comma/dot replacement SWAPS the separators — | |
| 213 | + // making the configured decimal separator appear ignored. | |
| 214 | + const formatted = new Intl.NumberFormat('en-US', { | |
| 197 | 215 | minimumFractionDigits: decimals, |
| 198 | 216 | maximumFractionDigits: decimals, |
| 199 | 217 | }).format(num) |
| 200 | 218 | .replace(/,/g, 'TEMP_THOUSAND') |
| @@ -1591,10 +1609,10 @@ | ||
| 1591 | 1609 | if (!minusBtn || !plusBtn) return; |
| 1592 | 1610 | |
| 1593 | 1611 | const updateButtonStates = () => { |
| 1594 | 1612 | const current = parseInt(input.value) || 1; |
| 1595 | - const min = parseInt(input.getAttribute('min')) || 1; | |
| 1596 | - const max = parseInt(input.getAttribute('max')) || 20; | |
| 1613 | + const min = window.yatraNumOr(input.getAttribute('min'), 1); | |
| 1614 | + const max = window.yatraNumOr(input.getAttribute('max'), 20); | |
| 1597 | 1615 | |
| 1598 | 1616 | minusBtn.disabled = current <= min; |
| 1599 | 1617 | plusBtn.disabled = current >= max; |
| 1600 | 1618 | }; |
| @@ -1604,9 +1622,9 @@ | ||
| 1604 | 1622 | e.preventDefault(); |
| 1605 | 1623 | e.stopPropagation(); |
| 1606 | 1624 | |
| 1607 | 1625 | const current = parseInt(input.value) || 1; |
| 1608 | - const min = parseInt(input.getAttribute('min')) || 1; | |
| 1626 | + const min = window.yatraNumOr(input.getAttribute('min'), 1); | |
| 1609 | 1627 | |
| 1610 | 1628 | if (current > min) { |
| 1611 | 1629 | input.value = current - 1; |
| 1612 | 1630 | updateButtonStates(); |
| @@ -1619,9 +1637,9 @@ | ||
| 1619 | 1637 | e.preventDefault(); |
| 1620 | 1638 | e.stopPropagation(); |
| 1621 | 1639 | |
| 1622 | 1640 | const current = parseInt(input.value) || 1; |
| 1623 | - const max = parseInt(input.getAttribute('max')) || 20; | |
| 1641 | + const max = window.yatraNumOr(input.getAttribute('max'), 20); | |
| 1624 | 1642 | |
| 1625 | 1643 | if (current < max) { |
| 1626 | 1644 | input.value = current + 1; |
| 1627 | 1645 | updateButtonStates(); |
| @@ -2365,15 +2383,15 @@ | ||
| 2365 | 2383 | const row = travelerSelector.querySelector('.yatra-quantity-row[data-category-id="' + categoryId + '"]'); |
| 2366 | 2384 | if (row) { |
| 2367 | 2385 | const cardInput = row.querySelector('input[type="number"]'); |
| 2368 | 2386 | if (cardInput) { |
| 2369 | - const max = parseInt(cardInput.getAttribute('max')) || 99; | |
| 2387 | + const max = window.yatraNumOr(cardInput.getAttribute('max'), 99); | |
| 2370 | 2388 | cardInput.value = Math.min(value, max); |
| 2371 | 2389 | |
| 2372 | 2390 | // Update button states |
| 2373 | 2391 | const minusBtn = row.querySelector('.yatra-quantity-minus'); |
| 2374 | 2392 | const plusBtn = row.querySelector('.yatra-quantity-plus'); |
| 2375 | - const min = parseInt(cardInput.getAttribute('min')) || 0; | |
| 2393 | + const min = window.yatraNumOr(cardInput.getAttribute('min'), 0); | |
| 2376 | 2394 | if (minusBtn) minusBtn.disabled = cardInput.value <= min; |
| 2377 | 2395 | if (plusBtn) plusBtn.disabled = cardInput.value >= max; |
| 2378 | 2396 | } |
| 2379 | 2397 | } |
| @@ -2384,9 +2402,9 @@ | ||
| 2384 | 2402 | |
| 2385 | 2403 | } else if (simpleTravelers && numTravelersInput) { |
| 2386 | 2404 | // Simple pricing - sync total count |
| 2387 | 2405 | const totalCount = parseInt(numTravelersInput.value) || 1; |
| 2388 | - const max = parseInt(simpleTravelers.getAttribute('max')) || 99; | |
| 2406 | + const max = window.yatraNumOr(simpleTravelers.getAttribute('max'), 99); | |
| 2389 | 2407 | simpleTravelers.value = Math.min(totalCount, max); |
| 2390 | 2408 | |
| 2391 | 2409 | // Update button states |
| 2392 | 2410 | const controls = simpleTravelers.closest('.yatra-quantity-controls-inline'); |
| @@ -2392,9 +2410,9 @@ | ||
| 2392 | 2410 | const controls = simpleTravelers.closest('.yatra-quantity-controls-inline'); |
| 2393 | 2411 | if (controls) { |
| 2394 | 2412 | const minusBtn = controls.querySelector('.yatra-quantity-minus'); |
| 2395 | 2413 | const plusBtn = controls.querySelector('.yatra-quantity-plus'); |
| 2396 | - const min = parseInt(simpleTravelers.getAttribute('min')) || 1; | |
| 2414 | + const min = window.yatraNumOr(simpleTravelers.getAttribute('min'), 1); | |
| 2397 | 2415 | if (minusBtn) minusBtn.disabled = simpleTravelers.value <= min; |
| 2398 | 2416 | if (plusBtn) plusBtn.disabled = simpleTravelers.value >= max; |
| 2399 | 2417 | } |
| 2400 | 2418 | } |
| @@ -3690,10 +3708,10 @@ | ||
| 3690 | 3708 | const isMinus = btn.classList.contains('yatra-quantity-minus'); |
| 3691 | 3709 | const isPlus = btn.classList.contains('yatra-quantity-plus'); |
| 3692 | 3710 | |
| 3693 | 3711 | let current = parseInt(input.value) || 0; |
| 3694 | - const min = parseInt(input.getAttribute('min')) || 0; | |
| 3695 | - const max = parseInt(input.getAttribute('max')) || 999; | |
| 3712 | + const min = window.yatraNumOr(input.getAttribute('min'), 0); | |
| 3713 | + const max = window.yatraNumOr(input.getAttribute('max'), 999); | |
| 3696 | 3714 | let newValue = current; |
| 3697 | 3715 | |
| 3698 | 3716 | if (isPlus && current < max) { |
| 3699 | 3717 | newValue = current + 1; |
| @@ -3765,10 +3783,10 @@ | ||
| 3765 | 3783 | const isMinus = btn.classList.contains('yatra-quantity-minus'); |
| 3766 | 3784 | const isPlus = btn.classList.contains('yatra-quantity-plus'); |
| 3767 | 3785 | |
| 3768 | 3786 | let current = parseInt(input.value) || 1; |
| 3769 | - const min = parseInt(input.getAttribute('min')) || 1; | |
| 3770 | - const max = parseInt(input.getAttribute('max')) || 999; | |
| 3787 | + const min = window.yatraNumOr(input.getAttribute('min'), 1); | |
| 3788 | + const max = window.yatraNumOr(input.getAttribute('max'), 999); | |
| 3771 | 3789 | let newValue = current; |
| 3772 | 3790 | |
| 3773 | 3791 | if (isPlus && current < max) { |
| 3774 | 3792 | newValue = current + 1; |
| @@ -3878,8 +3896,9 @@ | ||
| 3878 | 3896 | // Check for traveler-based pricing first |
| 3879 | 3897 | const categoryInputs = this.section.querySelectorAll(`.yatra-availability-category[data-item="${itemIndex}"]`); |
| 3880 | 3898 | let totalPrice = 0; |
| 3881 | 3899 | let totalTravelers = 0; |
| 3900 | + let totalGroups = 0; // per-group categories: number of group prices charged | |
| 3882 | 3901 | |
| 3883 | 3902 | if (categoryInputs.length > 0) { |
| 3884 | 3903 | // Traveler-based pricing: sum up each category's price * quantity (respecting pricing mode) |
| 3885 | 3904 | categoryInputs.forEach((input) => { |
| @@ -3891,11 +3910,11 @@ | ||
| 3891 | 3910 | const maxPax = parseInt((row ? row.getAttribute('data-max-pax') : null) || input.getAttribute('data-max-pax')) || 0; |
| 3892 | 3911 | |
| 3893 | 3912 | if (pricingMode === 'per_group') { |
| 3894 | 3913 | if (quantity > 0) { |
| 3895 | - totalPrice += (groupOverflow === 'per_block' && maxPax > 0) | |
| 3896 | - ? price * Math.ceil(quantity / maxPax) | |
| 3897 | - : price; | |
| 3914 | + const blocks = (groupOverflow === 'per_block' && maxPax > 0) ? Math.ceil(quantity / maxPax) : 1; | |
| 3915 | + totalPrice += price * blocks; | |
| 3916 | + totalGroups += blocks; | |
| 3898 | 3917 | } |
| 3899 | 3918 | } else { |
| 3900 | 3919 | totalPrice += quantity * price; |
| 3901 | 3920 | } |
| @@ -3923,14 +3942,21 @@ | ||
| 3923 | 3942 | } else { |
| 3924 | 3943 | totalAmountElement.textContent = String(finalPrice.toFixed(2)); |
| 3925 | 3944 | } |
| 3926 | 3945 | |
| 3927 | - // Header "per person" must match chargable average (row totals + group discount), not static PHP. | |
| 3946 | + // Header price must match the chargeable amount (row totals + group discount), not static PHP. | |
| 3947 | + // Its unit follows the card's price label: "per person" → average per traveller; | |
| 3948 | + // "per group" → the price per group actually charged (never divided by headcount, | |
| 3949 | + // which would turn a flat $1,100 group price into "$550 per group" for two travellers). | |
| 3928 | 3950 | const cardRoot = this.section.querySelector('.yatra-availability-card[data-item="' + itemIndex + '"]'); |
| 3929 | - const headerSaleEl = cardRoot ? cardRoot.querySelector('.yatra-card-header-price .yatra-sale-price') : null; | |
| 3951 | + const headerPriceWrap = cardRoot ? cardRoot.querySelector('.yatra-card-header-price') : null; | |
| 3952 | + const headerSaleEl = headerPriceWrap ? headerPriceWrap.querySelector('.yatra-sale-price') : null; | |
| 3930 | 3953 | if (headerSaleEl) { |
| 3954 | + const headerUnit = headerPriceWrap.getAttribute('data-price-unit') || 'per_person'; | |
| 3931 | 3955 | let perPerson = 0; |
| 3932 | - if (totalTravelers > 0) { | |
| 3956 | + if (headerUnit === 'per_group') { | |
| 3957 | + perPerson = totalGroups > 0 ? finalPrice / totalGroups : (totalPrice > 0 ? totalPrice : 0); | |
| 3958 | + } else if (totalTravelers > 0) { | |
| 3933 | 3959 | perPerson = finalPrice / totalTravelers; |
| 3934 | 3960 | } else if (totalPrice > 0) { |
| 3935 | 3961 | perPerson = totalPrice; |
| 3936 | 3962 | } |