(function () { 'use strict'; // ─── i18n Helpers ───────────────────────────────────────────── var I18N = (window.careiI18n || {}); function t(key, fallback) { return (key in I18N) ? I18N[key] : (fallback || key); } function tFmt(key, params, fallback) { var str = t(key, fallback); if (params && typeof str === 'string') { Object.keys(params).forEach(function (k) { str = str.replace(new RegExp('%' + k + '%', 'g'), params[k]); }); } return str; } function pluralPl(n, one, few, many) { if (n === 1) return one; if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) return few; return many; } var REST_URL = (window.careiReservation && window.careiReservation.restUrl) || '/wp-json/carei/v1/'; var NONCE = (window.careiReservation && window.careiReservation.nonce) || ''; // ─── API Helpers ────────────────────────────────────────────── var API_TIMEOUT_MS = 15000; function apiGet(endpoint, isRetry) { var controller = new AbortController(); var timer = setTimeout(function () { controller.abort(); }, API_TIMEOUT_MS); return fetch(REST_URL + endpoint, { method: 'GET', headers: { 'X-WP-Nonce': NONCE, 'Content-Type': 'application/json' }, signal: controller.signal }).then(function (r) { clearTimeout(timer); return handleResponse(r); }).catch(function (err) { clearTimeout(timer); return handleFetchError(err, function () { return apiGet(endpoint, true); }, isRetry); }); } function apiPost(endpoint, data, isRetry) { var controller = new AbortController(); var timer = setTimeout(function () { controller.abort(); }, API_TIMEOUT_MS); return fetch(REST_URL + endpoint, { method: 'POST', headers: { 'X-WP-Nonce': NONCE, 'Content-Type': 'application/json' }, body: JSON.stringify(data), signal: controller.signal }).then(function (r) { clearTimeout(timer); if ((r.status === 401 || r.status === 403) && !isRetry) { return apiPost(endpoint, data, true); } return handleResponse(r); }).catch(function (err) { clearTimeout(timer); return handleFetchError(err, function () { return apiPost(endpoint, data, true); }, isRetry); }); } function handleResponse(r) { if (!r.ok) { var status = r.status; return r.json().then(function (body) { var msg = (body && body.message) || (body && body.detail) || tFmt('errorApiHttp', {status: status}, 'Błąd API: HTTP %status%'); var err = new Error(msg); err.httpStatus = status; throw err; }).catch(function (parseErr) { if (parseErr.httpStatus) throw parseErr; var err = new Error(tFmt('errorApiHttp', {status: status}, 'Błąd API: HTTP %status%')); err.httpStatus = status; throw err; }); } return r.json(); } function handleFetchError(err, retryFn, isRetry) { if (err.name === 'AbortError') { throw new Error(t('errorTimeout', 'Przekroczono czas oczekiwania. Spróbuj ponownie.')); } if (err instanceof TypeError && !isRetry) { return retryFn(); } if (err instanceof TypeError) { throw new Error(t('errorNetwork', 'Brak połączenia z serwerem. Sprawdź internet i spróbuj ponownie.')); } throw err; } // ─── DOM Refs ───────────────────────────────────────────────── var overlay, form, segmentSelect, dateFrom, dateTo, daysCount; var pickupSelect, returnSelect, returnWrap, sameReturnCheck; var extrasWrapper, extrasContainer, abroadSection, abroadToggle, abroadSearch, abroadInput, abroadResults, abroadSelected, errorSummary; var summaryOverlay, summaryDetails, summaryTable, summaryTotal, summaryError; var summaryBack, summaryConfirm; var successView, successNumber, successClose; var protectionContainer; var protectionPackages = { soft: null, premium: null }; var selectedProtectionKey = null; function initRefs() { overlay = document.querySelector('[data-carei-modal]'); // Move overlay to body so it's not trapped inside Elementor hidden sections on mobile/tablet if (overlay && overlay.parentElement !== document.body) { document.body.appendChild(overlay); } form = document.getElementById('carei-reservation-form'); segmentSelect = document.getElementById('carei-segment'); dateFrom = document.getElementById('carei-date-from'); dateTo = document.getElementById('carei-date-to'); daysCount = document.getElementById('carei-days-count'); pickupSelect = document.getElementById('carei-pickup-branch'); returnSelect = document.getElementById('carei-return-branch'); returnWrap = document.getElementById('carei-return-wrap'); sameReturnCheck = document.getElementById('carei-same-return'); extrasWrapper = document.getElementById('carei-extras-wrapper'); extrasContainer = document.getElementById('carei-extras-container'); protectionContainer = document.getElementById('carei-protection-packages-container'); abroadSection = document.getElementById('carei-abroad-section'); abroadToggle = document.getElementById('carei-abroad-toggle'); abroadSearch = document.getElementById('carei-abroad-search'); abroadInput = document.getElementById('carei-abroad-input'); abroadResults = document.getElementById('carei-abroad-results'); abroadSelected = document.getElementById('carei-abroad-selected'); errorSummary = document.getElementById('carei-error-summary'); // Summary overlay summaryOverlay = document.getElementById('carei-summary-overlay'); summaryDetails = document.getElementById('carei-summary-details'); summaryTable = document.getElementById('carei-summary-table'); summaryTotal = document.getElementById('carei-summary-total'); summaryError = document.getElementById('carei-summary-error'); summaryBack = document.getElementById('carei-summary-back'); summaryConfirm = document.getElementById('carei-summary-confirm'); // Success view successView = document.getElementById('carei-success-view'); successNumber = document.getElementById('carei-success-number'); successClose = document.getElementById('carei-success-close'); } // ─── Flatpickr date pickers (cross-browser, locale-aware) ───── // If Flatpickr fails to load (CDN blocked, network, etc.), fall back gracefully // to native — never break date picking. function initDatePickers() { if (typeof window.flatpickr !== 'function') { console.warn('[carei] Flatpickr not loaded — keeping native datetime-local picker.'); return; } var isEn = (document.documentElement.lang || '').toLowerCase().indexOf('en') === 0; var locale = 'default'; if (!isEn && window.flatpickr.l10ns && window.flatpickr.l10ns.pl) { locale = window.flatpickr.l10ns.pl; } var baseOpts = { enableTime: true, time_24hr: true, dateFormat: 'Y-m-d\\TH:i', altInput: true, altFormat: isEn ? 'Y-m-d H:i' : 'd.m.Y H:i', minuteIncrement: 15, minDate: 'today', locale: locale, allowInput: false, // Force Flatpickr on mobile too (otherwise falls back to native iOS/Android picker — inconsistent look, locale = OS) disableMobile: true, // With enableTime, flatpickr doesn't auto-close on date click (time still editable). // Auto-close 1.5s after last change — gives user time to adjust time, then dismisses. onChange: function (selectedDates, dateStr, instance) { if (instance._careiCloseTimer) clearTimeout(instance._careiCloseTimer); instance._careiCloseTimer = setTimeout(function () { instance.close(); }, 1500); } }; // Modal inputs — use static:true so popup renders INSIDE input container // (bypasses focus trap + z-index conflicts with modal overlay). var modalTargets = [dateFrom, dateTo]; // Hero search inputs — render popup to body (default behavior is fine, no modal). var heroTargets = [ document.getElementById('carei-search-date-from'), document.getElementById('carei-search-date-to') ]; function attach(el, opts) { if (!el) return; // If already initialized, destroy previous instance (idempotent re-init) if (el._flatpickr) { try { el._flatpickr.destroy(); } catch (e) {} } try { el.setAttribute('type', 'text'); window.flatpickr(el, opts); // Bind click on the whole field container — any click (icon, label, padding, input) // opens the flatpickr. Simplest UX. var field = el.closest('.carei-form__field--date') || el.closest('.carei-search-form__field'); if (field && !field.dataset.careiFpBound) { field.dataset.careiFpBound = '1'; field.style.cursor = 'pointer'; var lastCloseAt = 0; if (el._flatpickr) { var origOnClose = el._flatpickr.config.onClose || []; el._flatpickr.config.onClose = (Array.isArray(origOnClose) ? origOnClose.slice() : [origOnClose]).concat([function () { lastCloseAt = Date.now(); }]); } field.addEventListener('click', function (e) { // Ignore clicks that originated inside the flatpickr popup itself. if (e.target.closest && e.target.closest('.flatpickr-calendar')) return; // Debounce: if we just closed (within 300ms, e.g. via date selection), don't reopen. if (Date.now() - lastCloseAt < 300) return; if (el._flatpickr) el._flatpickr.open(); }); } } catch (err) { console.error('[carei] Flatpickr init failed for', el.id, err); try { el.setAttribute('type', 'datetime-local'); } catch (e) {} } } var modalOpts = Object.assign({}, baseOpts, { static: true }); modalTargets.forEach(function (el) { attach(el, modalOpts); }); heroTargets.forEach(function (el) { attach(el, baseOpts); }); } // ─── State ──────────────────────────────────────────────────── var mapData = null; var allSegments = []; var currentPriceListId = null; var currentCustomerId = null; var currentReservationId = null; var agreementDefs = []; var lastFocusedElement = null; var abroadItems = []; var selectedCountries = {}; // ─── Modal Open/Close ───────────────────────────────────────── function initModal() { document.querySelectorAll('[data-carei-open-modal]').forEach(function (btn) { btn.addEventListener('click', function (e) { e.preventDefault(); openModal(btn); }); }); document.querySelectorAll('[data-carei-close-modal]').forEach(function (btn) { btn.addEventListener('click', closeModal); }); if (overlay) { overlay.addEventListener('click', function (e) { if (e.target === overlay) closeModal(); }); overlay.addEventListener('keydown', handleFocusTrap); } document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && overlay && overlay.classList.contains('is-open')) closeModal(); }); } var dataLoaded = false; function openModal(triggerBtn) { if (!overlay) return; lastFocusedElement = triggerBtn || document.activeElement; overlay.classList.add('is-open'); document.body.style.overflow = 'hidden'; if (!dataLoaded) { loadInitialData(); initDateLabels(); dataLoaded = true; } enforceDateMin(); // Pre-select segment from trigger attribute var presetSegment = triggerBtn && triggerBtn.getAttribute('segment'); if (presetSegment && segmentSelect) { var trySet = function () { if (segmentSelect.querySelector('option[value="' + presetSegment + '"]')) { segmentSelect.value = presetSegment; segmentSelect.dispatchEvent(new Event('change')); return true; } return false; }; if (!trySet()) { var attempts = 0; var iv = setInterval(function () { if (trySet() || ++attempts > 20) clearInterval(iv); }, 100); } } setTimeout(function () { if (segmentSelect) segmentSelect.focus(); }, 350); } function closeModal() { if (!overlay) return; overlay.classList.remove('is-open'); document.body.style.overflow = ''; if (lastFocusedElement && lastFocusedElement.focus) { setTimeout(function () { lastFocusedElement.focus(); }, 300); } } // ─── Focus Trap ───────────────────────────────────────────── var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; function handleFocusTrap(e) { if (e.key !== 'Tab') return; var modal = overlay.querySelector('.carei-modal'); if (!modal) return; var focusable = Array.prototype.slice.call(modal.querySelectorAll(FOCUSABLE_SELECTOR)).filter(function (el) { return el.offsetParent !== null; }); if (focusable.length === 0) return; var first = focusable[0]; var last = focusable[focusable.length - 1]; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } } // ─── Initial Data Loading ───────────────────────────────────── function loadInitialData() { if (segmentSelect) setSelectLoading(segmentSelect, true); Promise.all([ apiGet('car-classes-all'), apiGet('segments-branches-map'), apiGet('agreements') ]).then(function (results) { var classes = results[0]; mapData = results[1]; agreementDefs = Array.isArray(results[2]) ? results[2] : []; if (Array.isArray(classes) && classes.length > 0) { allSegments = classes.map(function (c) { var val = typeof c === 'string' ? c : (c.name || c); var label = typeof c === 'string' ? tFmt('segmentLabel', {name: c}, 'Segment %name%') : (c.description || c.name || c); return { value: val, label: label }; }); populateSelect(segmentSelect, allSegments, t('selectSegment', 'Wybierz segment pojazdu')); } else { populateSelect(segmentSelect, [], t('noSegments', 'Brak segmentów')); } if (segmentSelect) setSelectLoading(segmentSelect, false); if (pickupSelect) { populateSelect(pickupSelect, [], t('pickupPlaceholder', 'Najpierw wybierz segment')); pickupSelect.disabled = true; } }).catch(function (err) { console.error('Failed to load initial data:', err); if (segmentSelect) { populateSelect(segmentSelect, [], t('errorLoading', 'Błąd ładowania')); setSelectLoading(segmentSelect, false); } }); } // ─── Segment Change → Filter Pickup Locations ───────────────── function onSegmentChange() { var selectedSegment = segmentSelect ? segmentSelect.value : ''; if (!selectedSegment || !mapData || !pickupSelect) { if (pickupSelect) { populateSelect(pickupSelect, [], t('pickupPlaceholder', 'Najpierw wybierz segment')); pickupSelect.disabled = true; } hideExtras(); return; } var segBranches = mapData.segmentToBranches[selectedSegment] || []; var allBranches = mapData.branches || []; var filteredOptions = []; allBranches.forEach(function (b) { if (segBranches.indexOf(b.name || '') !== -1) { var label = b.description || b.name; if (b.city) label += ' — ' + b.city; filteredOptions.push({ value: b.name, label: label }); } }); if (filteredOptions.length > 0) { populateSelect(pickupSelect, filteredOptions, t('pickupLabel', 'Miejsce odbioru')); pickupSelect.disabled = false; } else { populateSelect(pickupSelect, [], t('noPickupForSegment', 'Brak lokalizacji dla tego segmentu')); pickupSelect.disabled = true; } if (returnSelect) { var returnOptions = allBranches.map(function (b) { var label = b.description || b.name; if (b.city) label += ' — ' + b.city; return { value: b.name, label: label }; }); populateSelect(returnSelect, returnOptions, t('returnLabel', 'Miejsce zwrotu')); } hideExtras(); } function onPickupChange() { maybeShowExtras(); } function maybeShowExtras() { var segment = segmentSelect ? segmentSelect.value : ''; var pickup = pickupSelect ? pickupSelect.value : ''; var from = dateFrom ? dateFrom.value : ''; var to = dateTo ? dateTo.value : ''; if (segment && pickup && from && to) { showExtras(); loadExtras(); } else { hideExtras(); } } function showExtras() { if (extrasWrapper) extrasWrapper.style.display = ''; } function hideExtras() { if (extrasWrapper) extrasWrapper.style.display = 'none'; } // ─── Past Date Prevention ──────────────────────────────────── function getNowLocal() { var d = new Date(); return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes()); } var dateMinListenersBound = false; function warnPastDate(input, msg) { var wrap = input.closest('.carei-form__date-wrap') || input.parentNode; var existing = wrap.querySelector('.carei-form__error-msg'); if (existing) existing.remove(); var span = document.createElement('span'); span.className = 'carei-form__error-msg'; span.textContent = msg; wrap.appendChild(span); input.classList.add('carei-form__field--error'); input.value = ''; updateDateEmpty(input); } function clearDateError(input) { var wrap = input.closest('.carei-form__date-wrap') || input.parentNode; var existing = wrap.querySelector('.carei-form__error-msg'); if (existing) existing.remove(); input.classList.remove('carei-form__field--error'); } function checkPastAndWarn(input, label) { if (!input || !input.value) return; if (input.value < getNowLocal()) { warnPastDate(input, tFmt('warnPastDate', {label: label}, '%label% — data lub godzina już minęły')); } else { clearDateError(input); } } function enforceDateMin() { var now = getNowLocal(); if (dateFrom) dateFrom.setAttribute('min', now); if (dateTo) dateTo.setAttribute('min', now); if (!dateMinListenersBound) { if (dateFrom) { dateFrom.addEventListener('change', function () { checkPastAndWarn(dateFrom, t('dateStart', 'Rozpoczęcie')); if (dateTo && dateFrom.value) dateTo.setAttribute('min', dateFrom.value); }); } if (dateTo) { dateTo.addEventListener('change', function () { checkPastAndWarn(dateTo, t('dateEnd', 'Zakończenie')); }); } dateMinListenersBound = true; } } // ─── Date Labels ────────────────────────────────────────────── function initDateLabels() { [dateFrom, dateTo].forEach(function (input) { if (!input) return; updateDateEmpty(input); input.addEventListener('change', function () { updateDateEmpty(input); updateDaysCount(); }); input.addEventListener('input', function () { updateDateEmpty(input); }); }); } function updateDateEmpty(input) { var wrap = input.closest('.carei-form__date-wrap'); if (input.value) { input.classList.remove('is-empty'); if (wrap) wrap.classList.add('has-value'); } else { input.classList.add('is-empty'); if (wrap) wrap.classList.remove('has-value'); } } function pad(n) { return String(n).padStart(2, '0'); } // ─── Days Count ─────────────────────────────────────────────── function getRentalDays() { if (!dateFrom || !dateTo) return 1; var from = new Date(dateFrom.value), to = new Date(dateTo.value); if (isNaN(from.getTime()) || isNaN(to.getTime()) || to <= from) return 1; return Math.ceil((to - from) / 86400000); } function updateDaysCount() { if (!dateFrom || !dateTo || !daysCount) return; var from = new Date(dateFrom.value), to = new Date(dateTo.value); if (isNaN(from.getTime()) || isNaN(to.getTime()) || to <= from) { daysCount.innerHTML = tFmt('daysCount', {count: 0, unit: t('dayMany', 'dni')}, 'Wybrano: %count% %unit%'); return; } var diff = Math.ceil((to - from) / 86400000); var dayUnit = diff === 1 ? t('dayWord', 'dzień') : t('daysWord', 'dni'); daysCount.innerHTML = tFmt('daysCount', {count: diff, unit: dayUnit}, 'Wybrano: %count% %unit%'); } // ─── Protection Packages (WP-managed: SOFT, PREMIUM) ────────── function loadProtectionPackages() { var lang = (document.documentElement.lang || '').toLowerCase().indexOf('en') === 0 ? 'en' : 'pl'; return fetch(REST_URL + 'protection-packages?lang=' + lang, { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }) .then(function (data) { if (!data || typeof data !== 'object') return; protectionPackages.soft = data.soft || null; protectionPackages.premium = data.premium || null; renderProtectionPackages(); }).catch(function (err) { console.error('Failed to load protection packages:', err); }); } function renderProtectionPackages() { if (!protectionContainer) return; protectionContainer.innerHTML = ''; ['soft', 'premium'].forEach(function (key) { var pkg = protectionPackages[key]; if (!pkg) return; var price = parseFloat(pkg.pricePerDay || 0); var priceLabel = price > 0 ? tFmt('pricePerDay', {price: price.toFixed(0)}, '%price% zł/doba') : t('free', 'Gratis'); var descHtml = pkg.description ? '' + escHtml(pkg.description) + '' : ''; var card = document.createElement('label'); card.className = 'carei-form__protection-package'; card.setAttribute('data-key', key); card.setAttribute('data-price', price); card.innerHTML = '' + '' + '' + escHtml(pkg.name || '') + '' + '' + escHtml(priceLabel) + '' + '' + descHtml; card.addEventListener('click', function (e) { e.preventDefault(); onProtectionCardClick(key); }); protectionContainer.appendChild(card); }); } function onProtectionCardClick(key) { if (!protectionContainer) return; if (selectedProtectionKey === key) { selectedProtectionKey = null; } else { selectedProtectionKey = key; } var cards = protectionContainer.querySelectorAll('.carei-form__protection-package'); cards.forEach(function (card) { var k = card.getAttribute('data-key'); var input = card.querySelector('input'); if (k === selectedProtectionKey) { card.classList.add('is-selected'); if (input) input.checked = true; } else { card.classList.remove('is-selected'); if (input) input.checked = false; } }); } // ─── Load Extras from Pricelist ─────────────────────────────── function loadExtras() { if (!segmentSelect || !dateFrom || !dateTo || !pickupSelect) return; var category = segmentSelect.value, fromVal = dateFrom.value, toVal = dateTo.value, branch = pickupSelect.value; if (!category || !fromVal || !toVal || !branch) return; apiPost('pricelist', { category: category, dateFrom: fromVal + ':00', dateTo: toVal + ':00', pickUpLocation: branch }).then(function (pricelists) { if (!Array.isArray(pricelists) || pricelists.length === 0) return; var pricelist = pricelists[0]; currentPriceListId = pricelist.id; var items = pricelist.additionalItems; var extraItems = []; abroadItems = []; selectedCountries = {}; if (Array.isArray(items)) { items.forEach(function (item) { var name = (item.name || '').toLowerCase(); // Skip penalty/return items var code = (item.code || '').toUpperCase(); if (code.indexOf('BRAK') === 0 || code.indexOf('BRUD') === 0 || code.indexOf('KARA') === 0 || code.indexOf('MYCIE USŁU') === 0 || code === 'MYJ WEW') return; // Drop Softra-insurance items — pakiety ochronne są zarządzane w panelu WP (SOFT/PREMIUM). if (name.indexOf('ubezp') !== -1 || name.indexOf('ochrony') !== -1 || name.indexOf('zniesienie') !== -1 || name.indexOf('insurance') !== -1) { return; } if (name.indexOf('wyjazd za granic') !== -1) { item._countryName = parseCountryName(item.name); item._countryFlag = getCountryFlag(item._countryName); abroadItems.push(item); } else { extraItems.push(item); } }); } if (extrasContainer) { extrasContainer.innerHTML = ''; extraItems.forEach(function (item) { extrasContainer.appendChild(buildExtraCard(item)); }); } if (abroadSection) { abroadSection.style.display = abroadItems.length > 0 ? '' : 'none'; } renderAbroadSelected(); }).catch(function (err) { console.error('Failed to load pricelist:', err); }); } function buildExtraCard(item) { var price = parseFloat(item.price || item.minPrice || 0); var maxPrice = parseFloat(item.maxPrice || 0); var priceLabel = (maxPrice > 0 && maxPrice !== price) ? tFmt('priceRange', {min: price.toFixed(0), max: maxPrice.toFixed(0)}, 'od %min% do %max% zł') : (price > 0 ? (item.unit === 'doba' ? tFmt('pricePerDay', {price: price.toFixed(0)}, '%price% zł/doba') : tFmt('priceSimple', {price: price.toFixed(0)}, '%price% zł')) : t('free', 'Gratis')); var card = document.createElement('div'); card.className = 'carei-form__extra-card'; card.innerHTML = ''; return card; } // ─── Abroad Country Search ───────────────────────────────────── var COUNTRY_FLAGS = { 'niemcy': '\u{1F1E9}\u{1F1EA}', 'czechy': '\u{1F1E8}\u{1F1FF}', 'słowacja': '\u{1F1F8}\u{1F1F0}', 'austria': '\u{1F1E6}\u{1F1F9}', 'francja': '\u{1F1EB}\u{1F1F7}', 'włochy': '\u{1F1EE}\u{1F1F9}', 'hiszpania': '\u{1F1EA}\u{1F1F8}', 'holandia': '\u{1F1F3}\u{1F1F1}', 'belgia': '\u{1F1E7}\u{1F1EA}', 'dania': '\u{1F1E9}\u{1F1F0}', 'szwecja': '\u{1F1F8}\u{1F1EA}', 'norwegia': '\u{1F1F3}\u{1F1F4}', 'finlandia': '\u{1F1EB}\u{1F1EE}', 'szwajcaria': '\u{1F1E8}\u{1F1ED}', 'węgry': '\u{1F1ED}\u{1F1FA}', 'chorwacja': '\u{1F1ED}\u{1F1F7}', 'słowenia': '\u{1F1F8}\u{1F1EE}', 'litwa': '\u{1F1F1}\u{1F1F9}', 'łotwa': '\u{1F1F1}\u{1F1FB}', 'estonia': '\u{1F1EA}\u{1F1EA}', 'rumunia': '\u{1F1F7}\u{1F1F4}', 'bułgaria': '\u{1F1E7}\u{1F1EC}', 'portugalia': '\u{1F1F5}\u{1F1F9}', 'grecja': '\u{1F1EC}\u{1F1F7}', 'wielka brytania': '\u{1F1EC}\u{1F1E7}', 'irlandia': '\u{1F1EE}\u{1F1EA}', 'luksemburg': '\u{1F1F1}\u{1F1FA}', 'serbia': '\u{1F1F7}\u{1F1F8}', 'czarnogóra': '\u{1F1F2}\u{1F1EA}', 'albania': '\u{1F1E6}\u{1F1F1}', 'turcja': '\u{1F1F9}\u{1F1F7}', 'ukraina': '\u{1F1FA}\u{1F1E6}', 'mołdawia': '\u{1F1F2}\u{1F1E9}' }; function parseCountryName(name) { var raw = (name || '').replace(/wyjazd za granic[eę]\s*[-–—]?\s*/i, '').trim(); if (!raw) return name || ''; return raw.split(/\s+/).map(function (w) { return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); }).join(' '); } function getCountryFlag(countryName) { var key = (countryName || '').toLowerCase(); return COUNTRY_FLAGS[key] || '\u{1F3F3}\u{FE0F}'; } function initAbroad() { if (!abroadToggle || !abroadSearch) return; abroadToggle.addEventListener('change', function () { if (abroadToggle.checked) { abroadSearch.style.display = ''; } else { abroadSearch.style.display = 'none'; selectedCountries = {}; if (abroadInput) abroadInput.value = ''; renderAbroadResults([]); renderAbroadSelected(); } }); var abroadClear = document.getElementById('carei-abroad-clear'); if (abroadInput) { abroadInput.addEventListener('input', function () { updateAbroadClear(); var query = abroadInput.value.trim().toLowerCase(); if (!query) { renderAbroadResults([]); return; } var filtered = abroadItems.filter(function (item) { return item._countryName.toLowerCase().indexOf(query) !== -1; }); renderAbroadResults(filtered); }); abroadInput.addEventListener('focus', function () { var query = abroadInput.value.trim().toLowerCase(); if (query) { var filtered = abroadItems.filter(function (item) { return item._countryName.toLowerCase().indexOf(query) !== -1; }); renderAbroadResults(filtered); } }); } if (abroadClear) { abroadClear.addEventListener('click', function () { if (abroadInput) { abroadInput.value = ''; abroadInput.focus(); } renderAbroadResults([]); updateAbroadClear(); }); } function updateAbroadClear() { var wrap = abroadInput ? abroadInput.closest('.carei-abroad__input-wrap') : null; if (wrap) { if (abroadInput.value.trim()) { wrap.classList.add('has-text'); } else { wrap.classList.remove('has-text'); } } } } function renderAbroadResults(items) { if (!abroadResults) return; abroadResults.innerHTML = ''; if (!items || items.length === 0) return; items.forEach(function (item) { var id = item.id || item.code; if (selectedCountries[id]) return; // already selected — show only in "added" section abroadResults.appendChild(buildCountryCard(item, false)); }); } function renderAbroadSelected() { if (!abroadSelected) return; abroadSelected.innerHTML = ''; var keys = Object.keys(selectedCountries); if (keys.length === 0) return; keys.forEach(function (id) { var item = selectedCountries[id]; abroadSelected.appendChild(buildCountryCard(item, true)); }); } function buildCountryCard(item, isSelected) { var id = item.id || item.code; var price = parseFloat(item.price || item.minPrice || 0); var priceHtml = '' + (price > 0 ? tFmt('priceSimple', {price: price.toFixed(0)}, '%price% zł') : t('free', 'Gratis')) + ''; var card = document.createElement('div'); card.className = 'carei-abroad__card' + (isSelected ? ' carei-abroad__card--selected' : ''); card.innerHTML = '' + escHtml(item._countryFlag) + '' + '' + escHtml(item._countryName) + '' + '' + priceHtml + '' + '' + (isSelected ? '' : '' ) + ''; card.querySelector('.carei-abroad__action').addEventListener('click', function (e) { e.preventDefault(); if (selectedCountries[id]) { delete selectedCountries[id]; } else { selectedCountries[id] = item; } var query = abroadInput ? abroadInput.value.trim().toLowerCase() : ''; if (query) { var filtered = abroadItems.filter(function (it) { return it._countryName.toLowerCase().indexOf(query) !== -1; }); renderAbroadResults(filtered); } else { renderAbroadResults([]); } renderAbroadSelected(); }); return card; } // ─── Select Helpers ─────────────────────────────────────────── function populateSelect(select, options, placeholder) { if (!select) return; select.innerHTML = ''; var ph = document.createElement('option'); ph.value = ''; ph.disabled = true; ph.selected = true; ph.textContent = placeholder || t('selectPlaceholder', 'Wybierz...'); select.appendChild(ph); options.forEach(function (opt) { var o = document.createElement('option'); o.value = opt.value; o.textContent = opt.label; select.appendChild(o); }); } function setSelectLoading(select, loading) { if (!select) return; var wrap = select.closest('.carei-form__select-wrap'); if (wrap) wrap.classList.toggle('carei-form__select-wrap--loading', loading); } // ─── Same Return Location ───────────────────────────────────── function initSameReturn() { if (!sameReturnCheck || !returnWrap) return; sameReturnCheck.addEventListener('change', function () { returnWrap.classList.toggle('is-visible', !sameReturnCheck.checked); }); } // ─── Validation ─────────────────────────────────────────────── var requiredFields = [ { id: 'carei-segment', type: 'select', msgKey: 'selectSegment', msgFallback: 'Wybierz segment pojazdu' }, { id: 'carei-date-from', type: 'input', msgKey: 'enterDateFrom', msgFallback: 'Podaj datę rozpoczęcia' }, { id: 'carei-date-to', type: 'input', msgKey: 'enterDateTo', msgFallback: 'Podaj datę zakończenia' }, { id: 'carei-pickup-branch', type: 'select', msgKey: 'selectPickup', msgFallback: 'Wybierz miejsce odbioru' }, { id: 'carei-firstname', type: 'input', msgKey: 'enterFirstName', msgFallback: 'Podaj imię' }, { id: 'carei-lastname', type: 'input', msgKey: 'enterLastName', msgFallback: 'Podaj nazwisko' }, { id: 'carei-email', type: 'email', msgKey: 'enterEmail', msgFallback: 'Podaj poprawny adres e-mail' }, { id: 'carei-phone', type: 'phone', msgKey: 'enterPhone', msgFallback: 'Podaj numer telefonu (min. 9 cyfr)' }, { id: 'carei-privacy', type: 'checkbox', msgKey: 'privacyRequired', msgFallback: 'Wymagana zgoda na Politykę Prywatności' } ]; function validateForm() { var valid = true; form.querySelectorAll('.carei-form__field--error').forEach(function (el) { el.classList.remove('carei-form__field--error'); }); form.querySelectorAll('.carei-form__checkbox-label--error').forEach(function (el) { el.classList.remove('carei-form__checkbox-label--error'); }); form.querySelectorAll('.carei-form__error-msg').forEach(function (el) { el.remove(); }); requiredFields.forEach(function (f) { var el = document.getElementById(f.id); if (!el) return; var hasError = false; if (f.type === 'checkbox') { if (!el.checked) hasError = true; } else if (f.type === 'email') { if (!el.value.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(el.value.trim())) hasError = true; } else if (f.type === 'phone') { if (el.value.replace(/\D/g, '').length < 9) hasError = true; } else if (f.type === 'pesel') { if (!/^\d{11}$/.test(el.value.trim())) hasError = true; } else if (f.type === 'select') { if (!el.value) hasError = true; } else { if (!el.value.trim()) hasError = true; } if (hasError) { valid = false; markFieldError(el, t(f.msgKey, f.msgFallback), f.type); } }); var now = new Date(); if (dateFrom && dateFrom.value && new Date(dateFrom.value) < now) { valid = false; markFieldError(dateFrom, t('dateStartPast', 'Data lub godzina rozpoczęcia już minęły'), 'input'); } if (dateTo && dateTo.value && new Date(dateTo.value) < now) { valid = false; markFieldError(dateTo, t('dateEndPast', 'Data lub godzina zakończenia już minęły'), 'input'); } if (dateFrom && dateTo && dateFrom.value && dateTo.value) { if (new Date(dateTo.value) <= new Date(dateFrom.value)) { valid = false; markFieldError(dateTo, t('dateEndAfterStart', 'Data zakończenia musi być po dacie rozpoczęcia'), 'input'); } } if (sameReturnCheck && !sameReturnCheck.checked && returnSelect && !returnSelect.value) { valid = false; markFieldError(returnSelect, t('selectReturn', 'Wybierz miejsce zwrotu'), 'select'); } if (errorSummary) errorSummary.style.display = valid ? 'none' : 'block'; return valid; } function markFieldError(el, msg, type) { if (type === 'checkbox') { var label = el.closest('.carei-form__checkbox-label'); if (label) label.classList.add('carei-form__checkbox-label--error'); } else { var field = el.closest('.carei-form__field'); if (!field) { field = el.closest('.carei-form__phone-wrap'); if (field) field = field.closest('.carei-form__field'); } if (field) { field.classList.add('carei-form__field--error'); var errEl = document.createElement('span'); errEl.className = 'carei-form__error-msg'; errEl.textContent = msg; field.appendChild(errEl); } } } function initClearErrors() { if (!form) return; form.addEventListener('focusin', function (e) { var field = e.target.closest('.carei-form__field'); if (field && field.classList.contains('carei-form__field--error')) { field.classList.remove('carei-form__field--error'); var errMsg = field.querySelector('.carei-form__error-msg'); if (errMsg) errMsg.remove(); } var label = e.target.closest('.carei-form__checkbox-label'); if (label) label.classList.remove('carei-form__checkbox-label--error'); }); form.addEventListener('change', function (e) { if (e.target.type === 'checkbox') { var label = e.target.closest('.carei-form__checkbox-label'); if (label) label.classList.remove('carei-form__checkbox-label--error'); } }); } // ─── Form Submit → Booking Flow ─────────────────────────────── function initSubmit() { if (!form) return; form.addEventListener('submit', function (e) { e.preventDefault(); if (!validateForm()) return; setSubmitState('loading'); createCustomerAndShowSummary(); }); if (summaryBack) summaryBack.addEventListener('click', handleSummaryBack); if (summaryConfirm) summaryConfirm.addEventListener('click', handleSummaryConfirm); if (successClose) successClose.addEventListener('click', handleSuccessClose); } function setSubmitState(state) { var btn = form ? form.querySelector('.carei-form__submit') : null; if (!btn) return; if (state === 'loading') { btn.disabled = true; btn.setAttribute('aria-busy', 'true'); btn.innerHTML = ' ' + escHtml(t('btnProcessing', 'Przetwarzanie...')); } else { btn.disabled = false; btn.setAttribute('aria-busy', 'false'); btn.innerHTML = ' ' + escHtml(t('btnSubmit', 'Wyślij')); } } function collectFormData() { var phoneRaw = document.getElementById('carei-phone') ? document.getElementById('carei-phone').value.replace(/\D/g, '') : ''; return { segment: segmentSelect ? segmentSelect.value : '', dateFrom: dateFrom ? dateFrom.value : '', dateTo: dateTo ? dateTo.value : '', pickupBranch: pickupSelect ? pickupSelect.value : '', sameReturn: sameReturnCheck ? sameReturnCheck.checked : true, returnBranch: (sameReturnCheck && !sameReturnCheck.checked && returnSelect) ? returnSelect.value : '', firstName: val('carei-firstname'), lastName: val('carei-lastname'), email: val('carei-email'), phone: '+48' + phoneRaw, message: val('carei-message'), privacy: document.getElementById('carei-privacy') ? document.getElementById('carei-privacy').checked : false }; } function val(id) { var el = document.getElementById(id); return el ? el.value.trim() : ''; } // ─── Step 1: Create Customer ────────────────────────────────── function createCustomerAndShowSummary() { var fd = collectFormData(); hideSummaryError(); announce(t('loadingSummary', 'Ładowanie podsumowania...')); apiPost('customer', { firstName: fd.firstName, lastName: fd.lastName, name: fd.firstName + ' ' + fd.lastName, isCompany: false, address: { city: '-', zipCode: '00-000', street: '-', homeNo: '-' }, pesel: '00000000000', email: fd.email, phoneMobile: fd.phone, paymentMethod: 'GOTÓWKA', skipAccountCreate: true, emailVerified: true }).then(function (res) { if (res && res.customerId) { currentCustomerId = res.customerId; return loadPricingSummary(fd); } throw new Error(res.rejectReason || t('errorCustomerCreate', 'Nie udało się utworzyć klienta')); }).catch(function (err) { console.error('Customer creation failed:', err); setSubmitState('ready'); showFormError(tFmt('errorCustomerCreatePrefix', {msg: err.message}, 'Błąd tworzenia klienta: %msg%')); }); } // ─── Step 2: Pricing Summary ────────────────────────────────── function loadPricingSummary(fd) { var returnBranch = fd.sameReturn ? fd.pickupBranch : fd.returnBranch; return apiPost('pricing-summary', { dateFrom: fd.dateFrom + ':00', dateTo: fd.dateTo + ':00', customerId: currentCustomerId, pickUpLocation: { branchName: fd.pickupBranch, outOfBranch: 'N' }, returnLocation: { branchName: returnBranch, outOfBranch: 'N' }, carParameters: { categoryName: fd.segment }, priceListId: currentPriceListId, priceItems: getSelectedExtrasForApi() }).then(function (summary) { showSummaryOverlay(summary, fd); }).catch(function (err) { console.error('Pricing summary failed:', err); setSubmitState('ready'); showFormError(tFmt('errorSummaryPrefix', {msg: err.message}, 'Błąd pobierania podsumowania: %msg%')); }); } function getSelectedExtrasForApi() { var items = []; if (!form) return items; var days = getRentalDays(); form.querySelectorAll('input[name="extras[]"]:checked').forEach(function (cb) { var price = parseFloat(cb.getAttribute('data-price') || 0); var unit = cb.getAttribute('data-unit') || 'szt.'; var amount = unit === 'doba' ? days : 1; items.push({ id: cb.value, name: cb.getAttribute('data-name') || '', unit: unit, amount: amount, priceBeforeDiscount: price, discount: 0, priceAfterDiscount: price }); }); Object.keys(selectedCountries).forEach(function (id) { var item = selectedCountries[id]; var price = parseFloat(item.price || item.minPrice || 0); var unit = item.unit || 'szt.'; var amount = unit === 'doba' ? days : 1; items.push({ id: item.id || item.code, name: item.name, unit: unit, amount: amount, priceBeforeDiscount: price, discount: 0, priceAfterDiscount: price }); }); // UWAGA: pakiet ochronny (SOFT/PREMIUM) zarządzany w panelu WP // NIE jest dołączany do priceItems — Softra nie zna tych ID (zwróciłaby HTTP 400). // Jest liczony osobno w getSelectedProtectionPayload() i dodawany do podsumowania po stronie frontu. return items; } function getSelectedProtectionPayload() { if (!selectedProtectionKey || !protectionPackages[selectedProtectionKey]) return null; var pkg = protectionPackages[selectedProtectionKey]; var pricePerDay = parseFloat(pkg.pricePerDay || 0); var days = getRentalDays(); return { key: selectedProtectionKey, name: pkg.name, pricePerDay: pricePerDay, days: days, total: pricePerDay * days }; } function buildBookingComments(userMessage) { var pkg = getSelectedProtectionPayload(); if (!pkg) return userMessage || ''; var pkgLine = tFmt('protectionComment', { name: pkg.name, perDay: pkg.pricePerDay.toFixed(2), days: pkg.days, total: pkg.total.toFixed(2) }, 'Pakiet ochronny: %name% — %perDay% zł/doba × %days% = %total% zł (do doliczenia poza systemem)'); return userMessage ? (pkgLine + '\n\n' + userMessage) : pkgLine; } // ─── Step Transitions ────────────────────────────────────────── function hideStep(el) { if (!el) return; el.style.display = 'none'; el.classList.remove('carei-step--entering', 'carei-step--exiting'); } function showStep(el) { if (!el) return; el.style.display = ''; el.classList.remove('carei-step--hidden'); } function transitionStep(outEl, inEl, callback) { if (outEl) { outEl.classList.add('carei-step--exiting'); setTimeout(function () { hideStep(outEl); if (inEl) { inEl.classList.add('carei-step--entering'); showStep(inEl); requestAnimationFrame(function () { requestAnimationFrame(function () { inEl.classList.remove('carei-step--entering'); if (callback) callback(); }); }); } else if (callback) { callback(); } }, 250); } else if (inEl) { inEl.classList.add('carei-step--entering'); showStep(inEl); requestAnimationFrame(function () { requestAnimationFrame(function () { inEl.classList.remove('carei-step--entering'); if (callback) callback(); }); }); } } // ─── Step 3: Show Summary Overlay ───────────────────────────── function showSummaryOverlay(summary, fd) { if (!summaryOverlay) return; hideSummaryError(); // Populate content before transition populateSummaryContent(summary, fd); // Animated transition: form → summary transitionStep(form, summaryOverlay, function () { announce(t('announceSummary', 'Podsumowanie rezerwacji')); var title = summaryOverlay.querySelector('.carei-summary__title'); if (title) title.focus(); }); } function populateSummaryContent(summary, fd) { // Details if (summaryDetails) { var segLabel = segmentSelect ? segmentSelect.options[segmentSelect.selectedIndex].text : fd.segment; var pickupLabel = pickupSelect ? pickupSelect.options[pickupSelect.selectedIndex].text : fd.pickupBranch; var returnLabel = ''; if (!fd.sameReturn && returnSelect) { returnLabel = returnSelect.options[returnSelect.selectedIndex].text; } var html = '
' + escHtml(t('labelSegment', 'Segment')) + ': ' + escHtml(segLabel) + '
' + '
' + escHtml(t('labelFrom', 'Od')) + ': ' + escHtml(fd.dateFrom.replace('T', ' ')) + '
' + '
' + escHtml(t('labelTo', 'Do')) + ': ' + escHtml(fd.dateTo.replace('T', ' ')) + '
' + '
' + escHtml(t('labelPickup', 'Miejsce odbioru')) + ': ' + escHtml(pickupLabel) + '
'; if (returnLabel) { html += '
' + escHtml(t('labelReturn', 'Miejsce zwrotu')) + ': ' + escHtml(returnLabel) + '
'; } html += '
' + escHtml(t('labelRenter', 'Najemca')) + ': ' + escHtml(fd.firstName + ' ' + fd.lastName) + '
' + '
' + escHtml(t('labelEmail', 'Email')) + ': ' + escHtml(fd.email) + '
' + '
' + escHtml(t('labelPhone', 'Telefon')) + ': ' + escHtml(fd.phone) + '
'; // Selected extras var selectedExtras = getSelectedExtrasForApi(); var pkgForDetails = getSelectedProtectionPayload(); if (selectedExtras.length > 0 || pkgForDetails) { html += '
' + escHtml(t('labelSelectedOptions', 'Wybrane opcje')) + ':
'; } if (fd.message) { html += '
' + escHtml(t('labelMessage', 'Wiadomość')) + ': ' + escHtml(fd.message) + '
'; } summaryDetails.innerHTML = html; } var protectionPayload = getSelectedProtectionPayload(); // Price table if (summaryTable && summary.pricelist) { var html = ''; summary.pricelist.forEach(function (item) { var rowClass = item.addedBySystem ? ' class="carei-summary__auto-item"' : ''; html += '' + '' + '' + '' + ''; }); if (protectionPayload) { html += '' + '' + '' + '' + ''; } html += '
' + escHtml(t('thName', 'Nazwa')) + '' + escHtml(t('thQuantity', 'Ilość')) + '' + escHtml(t('thNet', 'Netto')) + '' + escHtml(t('thGross', 'Brutto')) + '
' + escHtml(toSentenceCase(item.name)) + (item.addedBySystem ? ' (' + escHtml(t('labelAuto', 'auto')) + ')' : '') + '' + (item.amount || 1) + ' ' + escHtml(item.unit || '') + '' + fmtPrice(item.netValue) + '' + fmtPrice(item.grossValue) + '
' + escHtml(protectionPayload.name) + ' (' + escHtml(t('labelExtraCharge', 'do doliczenia')) + ')' + protectionPayload.days + ' ' + escHtml(pluralPl(protectionPayload.days, t('dayOne', 'doba'), t('dayFew', 'doby'), t('dayMany', 'dób'))) + '' + fmtPrice(protectionPayload.total) + '
'; summaryTable.innerHTML = html; } // Totals if (summaryTotal) { var softraGross = parseFloat(summary.totalGrossValue || 0); var protectionTotal = protectionPayload ? protectionPayload.total : 0; var grandGross = softraGross + protectionTotal; var totalsHtml = '
' + escHtml(t('thNet', 'Netto')) + ':' + fmtPrice(summary.totalNetValue) + '
' + '
' + escHtml(t('labelVat', 'VAT')) + ':' + fmtPrice(summary.totalVatValue) + '
'; if (protectionPayload) { totalsHtml += '
' + escHtml(t('labelProtectionPackage', 'Pakiet ochronny')) + ':' + fmtPrice(protectionPayload.total) + '
'; } totalsHtml += '
' + escHtml(t('labelToPay', 'Do zapłaty')) + ':' + tFmt('priceSimpleFmt', {price: fmtPrice(grandGross)}, '%price% zł') + '
'; summaryTotal.innerHTML = totalsHtml; } } function fmtPrice(val) { if (val === null || val === undefined) return '-'; return parseFloat(val).toFixed(2).replace('.', ','); } // ─── Summary Actions ────────────────────────────────────────── function handleSummaryBack() { transitionStep(summaryOverlay, form, function () { setSubmitState('ready'); if (segmentSelect) segmentSelect.focus(); }); } function handleSummaryConfirm() { if (summaryConfirm) { summaryConfirm.disabled = true; summaryConfirm.setAttribute('aria-busy', 'true'); summaryConfirm.innerHTML = ' ' + escHtml(t('btnBookingInProgress', 'Rezerwuję...')); } hideSummaryError(); var fd = collectFormData(); var returnBranch = fd.sameReturn ? fd.pickupBranch : fd.returnBranch; var bookingData = { dateFrom: fd.dateFrom + ':00', dateTo: fd.dateTo + ':00', customerId: currentCustomerId, pickUpLocation: { branchName: fd.pickupBranch, outOfBranch: 'N' }, returnLocation: { branchName: returnBranch, outOfBranch: 'N' }, carParameters: { categoryName: fd.segment }, priceListId: currentPriceListId, validTime: 1440, priceItems: getSelectedExtrasForApi(), drivers: [{ firstName: fd.firstName, lastName: fd.lastName, address: { city: '-', zipCode: '00-000', street: '-' }, pesel: '00000000000', phone: fd.phone, email: fd.email }], comments: buildBookingComments(fd.message || ''), protectionPackage: getSelectedProtectionPayload() }; // Add agreement items if (agreementDefs.length > 0) { bookingData.agreementItems = agreementDefs.map(function (a) { return { id: a.id, value: true }; }); } apiPost('booking', bookingData).then(function (res) { if (res && res.success && res.reservationId) { currentReservationId = res.reservationId; showSuccessView(res.reservationNo || res.reservationId); return; } throw new Error(translateRejectReason(res.rejectReason) || t('errorBookingFailed', 'Rezerwacja nie powiodła się')); }).catch(function (err) { console.error('Booking failed:', err); showSummaryError(err.message); resetConfirmBtn(); }); } function translateRejectReason(reason) { if (!reason) return null; var map = { 'CAR_NOT_FOUND': t('rejectCarNotFound', 'Brak dostępnego pojazdu w wybranym terminie. Zmień daty lub segment.'), 'INVALID_DATE_RANGE': t('rejectInvalidDateRange', 'Nieprawidłowy zakres dat'), 'BRANCH_NOT_FOUND': t('rejectBranchNotFound', 'Nie znaleziono oddziału'), 'CUSTOMER_ALREADY_EXISTS': t('rejectCustomerExists', 'Klient o tych danych już istnieje w systemie'), 'INVALID_PESEL': t('rejectInvalidPesel', 'Nieprawidłowy numer PESEL'), 'PRICE_LIST_EXPIRED': t('rejectPriceListExpired', 'Cennik wygasł. Odśwież formularz i spróbuj ponownie.') }; return map[reason] || reason; } function resetConfirmBtn() { if (summaryConfirm) { summaryConfirm.disabled = false; summaryConfirm.setAttribute('aria-busy', 'false'); summaryConfirm.innerHTML = ' ' + escHtml(t('btnConfirmBooking', 'Potwierdź rezerwację')); } } // ─── Success View ───────────────────────────────────────────── function showSuccessView(reservationNo) { if (successNumber) successNumber.textContent = tFmt('orderNumber', {no: reservationNo}, 'Nr zamówienia: %no%'); transitionStep(summaryOverlay, successView, function () { announce(t('announceBookingConfirmed', 'Rezerwacja potwierdzona')); var title = successView.querySelector('.carei-success__title'); if (title) title.focus(); }); } function handleSuccessClose() { hideStep(successView); if (form) { showStep(form); form.reset(); } setSubmitState('ready'); resetConfirmBtn(); currentCustomerId = null; currentReservationId = null; hideExtras(); if (pickupSelect) { populateSelect(pickupSelect, [], t('pickupPlaceholder', 'Najpierw wybierz segment')); pickupSelect.disabled = true; } closeModal(); } // ─── Error Helpers ──────────────────────────────────────────── function showFormError(msg) { announce(msg); if (errorSummary) { errorSummary.textContent = msg; errorSummary.style.display = 'block'; } } function showSummaryError(msg) { announce(msg); if (summaryError) { summaryError.textContent = msg; summaryError.style.display = 'block'; } } function hideSummaryError() { if (summaryError) { summaryError.textContent = ''; summaryError.style.display = 'none'; } } // ─── Event Listeners ────────────────────────────────────────── function initDynamicLoading() { if (dateFrom) dateFrom.addEventListener('change', updateDaysCount); if (dateTo) dateTo.addEventListener('change', updateDaysCount); if (segmentSelect) segmentSelect.addEventListener('change', onSegmentChange); if (pickupSelect) pickupSelect.addEventListener('change', onPickupChange); if (dateFrom) dateFrom.addEventListener('change', maybeShowExtras); if (dateTo) dateTo.addEventListener('change', maybeShowExtras); } // ─── Aria Live Announcements ─────────────────────────────────── var liveRegion = null; function initLiveRegion() { liveRegion = document.createElement('div'); liveRegion.setAttribute('aria-live', 'polite'); liveRegion.setAttribute('role', 'status'); liveRegion.className = 'carei-sr-only'; if (overlay) overlay.appendChild(liveRegion); } function announce(msg) { if (!liveRegion) return; liveRegion.textContent = ''; setTimeout(function () { liveRegion.textContent = msg; }, 100); } // ─── HTML Escape Helpers ────────────────────────────────────── function escHtml(str) { var d = document.createElement('div'); d.textContent = str || ''; return d.innerHTML; } function escAttr(str) { return (str || '').replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>'); } function toSentenceCase(str) { if (!str) return ''; var s = str.toLowerCase(); return s.charAt(0).toUpperCase() + s.slice(1); } // ─── Search Form (Hero Mini Form) ────────────────────────────── function initSearchForm() { var searchForm = document.querySelector('.carei-search-form'); if (!searchForm) return; var searchSegment = document.getElementById('carei-search-segment'); var searchDateFrom = document.getElementById('carei-search-date-from'); var searchDateTo = document.getElementById('carei-search-date-to'); var searchPickup = document.getElementById('carei-search-pickup'); var searchSameReturn = document.getElementById('carei-search-same-return'); var searchSubmit = document.getElementById('carei-search-submit'); var searchMapData = null; // Enforce min dates on search form (no error messages — compact UI) var now = getNowLocal(); if (searchDateFrom) { searchDateFrom.setAttribute('min', now); searchDateFrom.addEventListener('change', function () { if (searchDateFrom.value && searchDateFrom.value < getNowLocal()) searchDateFrom.value = ''; if (searchDateTo && searchDateFrom.value) searchDateTo.setAttribute('min', searchDateFrom.value); }); } if (searchDateTo) { searchDateTo.setAttribute('min', now); searchDateTo.addEventListener('change', function () { if (searchDateTo.value && searchDateTo.value < getNowLocal()) searchDateTo.value = ''; }); } // Ładowanie danych do mini formularza function loadSearchData() { Promise.all([ apiGet('car-classes-all'), apiGet('segments-branches-map') ]).then(function (results) { var classes = results[0]; searchMapData = results[1]; if (Array.isArray(classes) && classes.length > 0) { var segments = classes.map(function (c) { var val = typeof c === 'string' ? c : (c.name || c); var label = typeof c === 'string' ? tFmt('segmentLabel', {name: c}, 'Segment %name%') : (c.description || c.name || c); return { value: val, label: label }; }); populateSelect(searchSegment, segments, t('selectSegmentShort', 'Wybierz segment')); } if (searchPickup) { populateSelect(searchPickup, [], t('pickupPlaceholder', 'Najpierw wybierz segment')); searchPickup.disabled = true; } }).catch(function (err) { console.error('Search form: failed to load data:', err); }); } // Zmiana segmentu → filtr lokalizacji if (searchSegment) { searchSegment.addEventListener('change', function () { var sel = searchSegment.value; if (!sel || !searchMapData || !searchPickup) return; var segBranches = searchMapData.segmentToBranches[sel] || []; var allBranches = searchMapData.branches || []; var opts = []; allBranches.forEach(function (b) { if (segBranches.indexOf(b.name || '') !== -1) { var label = b.description || b.name; if (b.city) label += ' — ' + b.city; opts.push({ value: b.name, label: label }); } }); if (opts.length > 0) { populateSelect(searchPickup, opts, t('pickupLabel', 'Miejsce odbioru')); searchPickup.disabled = false; } else { populateSelect(searchPickup, [], t('noLocations', 'Brak lokalizacji')); searchPickup.disabled = true; } }); } // Date label behavior [searchDateFrom, searchDateTo].forEach(function (input) { if (!input) return; var wrap = input.closest('.carei-search-form__date-wrap'); function updateLabel() { if (wrap) wrap.classList.toggle('has-value', !!input.value); } updateLabel(); input.addEventListener('change', updateLabel); input.addEventListener('input', updateLabel); }); // Submit → otwórz modal z pre-fill if (searchSubmit) { searchSubmit.addEventListener('click', function () { if (!overlay) return; var valSegment = searchSegment ? searchSegment.value : ''; var valDateFrom = searchDateFrom ? searchDateFrom.value : ''; var valDateTo = searchDateTo ? searchDateTo.value : ''; var valPickup = searchPickup ? searchPickup.value : ''; var valSameReturn = searchSameReturn ? searchSameReturn.checked : true; // Otwórz modal openModal(searchSubmit); // Pre-fill po załadowaniu danych modala function prefillModal() { // Segment if (segmentSelect && valSegment) { segmentSelect.value = valSegment; segmentSelect.dispatchEvent(new Event('change')); } // Daty if (dateFrom && valDateFrom) { dateFrom.value = valDateFrom; dateFrom.dispatchEvent(new Event('change')); } if (dateTo && valDateTo) { dateTo.value = valDateTo; dateTo.dispatchEvent(new Event('change')); } // Checkbox zwrotu if (sameReturnCheck) { sameReturnCheck.checked = valSameReturn; sameReturnCheck.dispatchEvent(new Event('change')); } // Pickup — poczekaj aż lokalizacje się załadują po change segmentu if (valPickup && pickupSelect) { var attempts = 0; var pickupInterval = setInterval(function () { attempts++; // Sprawdź czy opcja jest dostępna var optExists = Array.prototype.slice.call(pickupSelect.options).some(function (o) { return o.value === valPickup; }); if (optExists) { clearInterval(pickupInterval); pickupSelect.value = valPickup; pickupSelect.dispatchEvent(new Event('change')); } else if (attempts > 30) { clearInterval(pickupInterval); } }, 100); } } // Daj czas na loadInitialData w openModal setTimeout(prefillModal, 400); }); } loadSearchData(); } // ─── Init ───────────────────────────────────────────────────── function init() { initRefs(); // Inicjalizuj search form niezależnie od modala initSearchForm(); // Flatpickr on both modal + hero inputs — safe to call even if modal absent initDatePickers(); if (!overlay || !form) return; initModal(); initLiveRegion(); initSameReturn(); initDynamicLoading(); initClearErrors(); initAbroad(); initSubmit(); initMap(); loadProtectionPackages(); } /* ═══════════════════════════════════════════ Carei Map — dynamic pins & tooltips ═══════════════════════════════════════════ */ function initMap() { var mapEl = document.querySelector('.carei-map'); if (!mapEl) return; var pins; try { pins = JSON.parse(mapEl.getAttribute('data-pins') || '[]'); } catch (e) { return; } if (!pins.length) return; var svg = mapEl.querySelector('.carei-map__svg'); var pinsGroup = svg.querySelector('.carei-map__pins'); var tooltip = mapEl.querySelector('.carei-map__tooltip'); var tooltipContent = mapEl.querySelector('.carei-map__tooltip-content'); if (!svg || !pinsGroup || !tooltip || !tooltipContent) return; var activePin = null; var SVG_NS = 'http://www.w3.org/2000/svg'; pins.forEach(function (pin) { var circle = document.createElementNS(SVG_NS, 'circle'); circle.setAttribute('cx', pin.x); circle.setAttribute('cy', pin.y); circle.setAttribute('r', '6'); circle.setAttribute('fill', '#FF0000'); circle.setAttribute('class', 'carei-map__pin'); circle.setAttribute('data-city', pin.city); circle.setAttribute('data-address', pin.address || ''); circle.addEventListener('mouseenter', function () { showTooltip(pin, circle); }); circle.addEventListener('mouseleave', function () { if (activePin !== circle) { hideTooltip(); } }); circle.addEventListener('click', function (e) { e.stopPropagation(); if (activePin === circle) { activePin = null; circle.classList.remove('carei-map__pin--active'); hideTooltip(); } else { if (activePin) activePin.classList.remove('carei-map__pin--active'); activePin = circle; circle.classList.add('carei-map__pin--active'); showTooltip(pin, circle); } }); pinsGroup.appendChild(circle); }); // Click outside closes tooltip document.addEventListener('click', function () { if (activePin) { activePin.classList.remove('carei-map__pin--active'); activePin = null; hideTooltip(); } }); function showTooltip(pin, circle) { // Build tooltip text: address lines + bold city var addr = pin.address || ''; var lines = addr ? addr.replace(/\\n/g, '\n') : pin.city; tooltipContent.textContent = ''; // If address has postal code, format nicely if (addr) { tooltipContent.innerHTML = addr.replace(/\n/g, '
'); } else { tooltipContent.textContent = pin.city; } // Position tooltip relative to map container var svgRect = svg.getBoundingClientRect(); var mapRect = mapEl.getBoundingClientRect(); var svgWidth = svg.viewBox.baseVal.width || 600; var svgHeight = svg.viewBox.baseVal.height || 570; var scaleX = svgRect.width / svgWidth; var scaleY = svgRect.height / svgHeight; var left = (svgRect.left - mapRect.left) + pin.x * scaleX; var top = (svgRect.top - mapRect.top) + pin.y * scaleY; tooltip.style.display = 'block'; tooltip.style.left = left + 'px'; tooltip.style.top = top + 'px'; } function hideTooltip() { tooltip.style.display = 'none'; } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();