Files
carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
Jacek Pyziak 6f6c1fcf17 fix(14-mobile-modal-fix): Modal rezerwacji działa na mobile/tablet
Sekcja Elementor zawierająca modal miała elementor-hidden-mobile/tablet,
co powodowało display:none na rodzicu. Modal position:fixed wewnątrz
ukrytego elementu miał zerowe wymiary.

Fix: przeniesienie overlay do document.body w initRefs().
Plan Phase 13 (pakiety ochronne) utworzony, BLOCKED — czeka na klienta.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 00:05:53 +02:00

1458 lines
65 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
'use strict';
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) || ('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('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('Przekroczono czas oczekiwania. Spróbuj ponownie.');
}
if (err instanceof TypeError && !isRetry) {
return retryFn();
}
if (err instanceof TypeError) {
throw new Error('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, insuranceContainer, abroadSection, abroadToggle, abroadSearch, abroadInput, abroadResults, abroadSelected, errorSummary;
var summaryOverlay, summaryDetails, summaryTable, summaryTotal, summaryError;
var summaryBack, summaryConfirm;
var successView, successNumber, successClose;
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');
insuranceContainer = document.getElementById('carei-insurance-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');
}
// ─── 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' ? ('Segment ' + c) : (c.description || c.name || c);
return { value: val, label: label };
});
populateSelect(segmentSelect, allSegments, 'Wybierz segment pojazdu');
} else {
populateSelect(segmentSelect, [], 'Brak segmentów');
}
if (segmentSelect) setSelectLoading(segmentSelect, false);
if (pickupSelect) {
populateSelect(pickupSelect, [], 'Najpierw wybierz segment');
pickupSelect.disabled = true;
}
}).catch(function (err) {
console.error('Failed to load initial data:', err);
if (segmentSelect) {
populateSelect(segmentSelect, [], '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, [], '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, 'Miejsce odbioru');
pickupSelect.disabled = false;
} else {
populateSelect(pickupSelect, [], '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, '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, 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, 'Rozpoczęcie');
if (dateTo && dateFrom.value) dateTo.setAttribute('min', dateFrom.value);
});
}
if (dateTo) {
dateTo.addEventListener('change', function () {
checkPastAndWarn(dateTo, '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 = 'Wybrano: <strong>0 dni</strong>'; return;
}
var diff = Math.ceil((to - from) / 86400000);
daysCount.innerHTML = 'Wybrano: <strong>' + diff + ' ' + (diff === 1 ? 'dzień' : 'dni') + '</strong>';
}
// ─── 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 insuranceItems = [], 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;
if (name.indexOf('wyjazd za granic') !== -1) {
item._countryName = parseCountryName(item.name);
item._countryFlag = getCountryFlag(item._countryName);
abroadItems.push(item);
} else if (name.indexOf('ubezp') !== -1 || name.indexOf('ochrony') !== -1 ||
name.indexOf('zniesienie') !== -1 || name.indexOf('insurance') !== -1) {
insuranceItems.push(item);
} else {
extraItems.push(item);
}
});
}
if (insuranceContainer) { insuranceContainer.innerHTML = ''; insuranceItems.forEach(function (item) { insuranceContainer.appendChild(buildExtraCard(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)
? 'od ' + price.toFixed(0) + ' do ' + maxPrice.toFixed(0) + ' zł'
: (price > 0 ? price.toFixed(0) + ' zł' + (item.unit === 'doba' ? '/doba' : '') : 'Gratis');
var card = document.createElement('div');
card.className = 'carei-form__extra-card';
card.innerHTML =
'<label class="carei-form__checkbox-label carei-form__checkbox-label--card">' +
'<input type="checkbox" name="extras[]" value="' + escAttr(item.id || item.code) + '" data-price="' + price + '" data-name="' + escAttr(toSentenceCase(item.name)) + '" data-unit="' + escAttr(item.unit || 'szt.') + '">' +
'<span class="carei-form__checkbox-box"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7l3.5 3.5L12 4" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>' +
'<span class="carei-form__extra-content"><strong>' + escHtml(toSentenceCase(item.name)) + '</strong>' +
(item.description ? '<span class="carei-form__extra-desc">' + escHtml(toSentenceCase(item.description)) + '</span>' : '') +
'<span class="carei-form__extra-price"><strong>' + escHtml(priceLabel) + '</strong></span></span></label>';
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 = '<span class="carei-abroad__price-val">' + (price > 0 ? price.toFixed(0) + ' zł' : 'Gratis') + '</span>';
var card = document.createElement('div');
card.className = 'carei-abroad__card' + (isSelected ? ' carei-abroad__card--selected' : '');
card.innerHTML =
'<span class="carei-abroad__flag">' + escHtml(item._countryFlag) + '</span>' +
'<span class="carei-abroad__name">' + escHtml(item._countryName) + '</span>' +
'<span class="carei-abroad__price">' + priceHtml + '</span>' +
'<span role="button" tabindex="0" class="carei-abroad__action" data-abroad-id="' + escAttr(id) + '" title="' + (isSelected ? 'Usuń' : 'Dodaj') + '">' +
(isSelected
? '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'
: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'
) + '</span>';
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 || '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', msg: 'Wybierz segment pojazdu' },
{ id: 'carei-date-from', type: 'input', msg: 'Podaj datę rozpoczęcia' },
{ id: 'carei-date-to', type: 'input', msg: 'Podaj datę zakończenia' },
{ id: 'carei-pickup-branch', type: 'select', msg: 'Wybierz miejsce odbioru' },
{ id: 'carei-firstname', type: 'input', msg: 'Podaj imię' },
{ id: 'carei-lastname', type: 'input', msg: 'Podaj nazwisko' },
{ id: 'carei-email', type: 'email', msg: 'Podaj poprawny adres e-mail' },
{ id: 'carei-phone', type: 'phone', msg: 'Podaj numer telefonu (min. 9 cyfr)' },
{ id: 'carei-privacy', type: 'checkbox', msg: '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, f.msg, f.type); }
});
var now = new Date();
if (dateFrom && dateFrom.value && new Date(dateFrom.value) < now) {
valid = false; markFieldError(dateFrom, 'Data lub godzina rozpoczęcia już minęły', 'input');
}
if (dateTo && dateTo.value && new Date(dateTo.value) < now) {
valid = false; markFieldError(dateTo, '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, 'Data zakończenia musi być po dacie rozpoczęcia', 'input');
}
}
if (sameReturnCheck && !sameReturnCheck.checked && returnSelect && !returnSelect.value) {
valid = false; markFieldError(returnSelect, '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 = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> Przetwarzanie...';
} else {
btn.disabled = false;
btn.setAttribute('aria-busy', 'false');
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> 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('Ł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 || 'Nie udało się utworzyć klienta');
}).catch(function (err) {
console.error('Customer creation failed:', err);
setSubmitState('ready');
showFormError('Błąd tworzenia klienta: ' + err.message);
});
}
// ─── 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('Błąd pobierania podsumowania: ' + err.message);
});
}
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
});
});
return items;
}
// ─── 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('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 =
'<div><strong>Segment:</strong> ' + escHtml(segLabel) + '</div>' +
'<div><strong>Od:</strong> ' + escHtml(fd.dateFrom.replace('T', ' ')) + '</div>' +
'<div><strong>Do:</strong> ' + escHtml(fd.dateTo.replace('T', ' ')) + '</div>' +
'<div><strong>Miejsce odbioru:</strong> ' + escHtml(pickupLabel) + '</div>';
if (returnLabel) {
html += '<div><strong>Miejsce zwrotu:</strong> ' + escHtml(returnLabel) + '</div>';
}
html += '<div><strong>Najemca:</strong> ' + escHtml(fd.firstName + ' ' + fd.lastName) + '</div>' +
'<div><strong>Email:</strong> ' + escHtml(fd.email) + '</div>' +
'<div><strong>Telefon:</strong> ' + escHtml(fd.phone) + '</div>';
// Selected extras
var selectedExtras = getSelectedExtrasForApi();
if (selectedExtras.length > 0) {
html += '<div style="margin-top:8px"><strong>Wybrane opcje:</strong></div><ul style="margin:4px 0 0 16px;padding:0;list-style:disc;">';
selectedExtras.forEach(function (ex) {
var totalPrice = ex.priceAfterDiscount * (ex.amount || 1);
var priceInfo = ex.unit === 'doba' && ex.amount > 1
? fmtPrice(ex.priceAfterDiscount) + ' zł/doba × ' + ex.amount + ' = ' + fmtPrice(totalPrice) + ' zł'
: fmtPrice(totalPrice) + ' zł';
html += '<li>' + escHtml(toSentenceCase(ex.name)) + ' — ' + priceInfo + '</li>';
});
html += '</ul>';
}
if (fd.message) {
html += '<div style="margin-top:8px"><strong>Wiadomość:</strong> ' + escHtml(fd.message) + '</div>';
}
summaryDetails.innerHTML = html;
}
// Price table
if (summaryTable && summary.pricelist) {
var html = '<table><thead><tr><th>Nazwa</th><th>Ilość</th><th>Netto</th><th>Brutto</th></tr></thead><tbody>';
summary.pricelist.forEach(function (item) {
var rowClass = item.addedBySystem ? ' class="carei-summary__auto-item"' : '';
html += '<tr' + rowClass + '>' +
'<td>' + escHtml(toSentenceCase(item.name)) + (item.addedBySystem ? ' <small>(auto)</small>' : '') + '</td>' +
'<td>' + (item.amount || 1) + ' ' + escHtml(item.unit || '') + '</td>' +
'<td>' + fmtPrice(item.netValue) + '</td>' +
'<td>' + fmtPrice(item.grossValue) + '</td></tr>';
});
html += '</tbody></table>';
summaryTable.innerHTML = html;
}
// Totals
if (summaryTotal) {
summaryTotal.innerHTML =
'<div class="carei-summary__total-row"><span class="carei-summary__total-label">Netto:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalNetValue) + '</span></div>' +
'<div class="carei-summary__total-row"><span class="carei-summary__total-label">VAT:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalVatValue) + '</span></div>' +
'<div class="carei-summary__total-row carei-summary__total-row--gross"><span class="carei-summary__total-label">Do zapłaty:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalGrossValue) + ' zł</span></div>';
}
}
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 = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> 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: fd.message || ''
};
// 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) || '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': 'Brak dostępnego pojazdu w wybranym terminie. Zmień daty lub segment.',
'INVALID_DATE_RANGE': 'Nieprawidłowy zakres dat',
'BRANCH_NOT_FOUND': 'Nie znaleziono oddziału',
'CUSTOMER_ALREADY_EXISTS': 'Klient o tych danych już istnieje w systemie',
'INVALID_PESEL': 'Nieprawidłowy numer PESEL',
'PRICE_LIST_EXPIRED': '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 = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> Potwierdź rezerwację';
}
}
// ─── Success View ─────────────────────────────────────────────
function showSuccessView(reservationNo) {
if (successNumber) successNumber.textContent = 'Nr zamówienia: ' + reservationNo;
transitionStep(summaryOverlay, successView, function () {
announce('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, [], '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, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
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' ? ('Segment ' + c) : (c.description || c.name || c);
return { value: val, label: label };
});
populateSelect(searchSegment, segments, 'Wybierz segment');
}
if (searchPickup) {
populateSelect(searchPickup, [], '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, 'Miejsce odbioru');
searchPickup.disabled = false;
} else {
populateSelect(searchPickup, [], '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();
if (!overlay || !form) return;
initModal();
initLiveRegion();
initSameReturn();
initDynamicLoading();
initClearErrors();
initAbroad();
initSubmit();
initMap();
}
/* ═══════════════════════════════════════════
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, '<br>');
} 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();
}
})();