Files
carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
2026-03-25 00:41:16 +01:00

592 lines
23 KiB
JavaScript

(function () {
'use strict';
var REST_URL = (window.careiReservation && window.careiReservation.restUrl) || '/wp-json/carei/v1/';
var NONCE = (window.careiReservation && window.careiReservation.nonce) || '';
// ─── API Helpers ──────────────────────────────────────────────
function apiGet(endpoint) {
return fetch(REST_URL + endpoint, {
method: 'GET',
headers: {
'X-WP-Nonce': NONCE,
'Content-Type': 'application/json'
}
}).then(function (r) {
if (!r.ok) throw new Error('API error: ' + r.status);
return r.json();
});
}
function apiPost(endpoint, data) {
return fetch(REST_URL + endpoint, {
method: 'POST',
headers: {
'X-WP-Nonce': NONCE,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(function (r) {
if (!r.ok) throw new Error('API error: ' + r.status);
return r.json();
});
}
// ─── DOM Refs ─────────────────────────────────────────────────
var overlay, form, segmentSelect, dateFrom, dateTo, daysCount;
var pickupSelect, returnSelect, returnWrap, sameReturnCheck;
var extrasContainer, errorSummary;
function initRefs() {
overlay = document.querySelector('[data-carei-modal]');
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');
extrasContainer = document.getElementById('carei-extras-container');
errorSummary = document.getElementById('carei-error-summary');
}
// ─── Modal Open/Close ─────────────────────────────────────────
function initModal() {
var triggers = document.querySelectorAll('[data-carei-open-modal]');
var closeBtns = document.querySelectorAll('[data-carei-close-modal]');
triggers.forEach(function (btn) {
btn.addEventListener('click', function (e) {
e.preventDefault();
openModal();
});
});
closeBtns.forEach(function (btn) {
btn.addEventListener('click', closeModal);
});
if (overlay) {
overlay.addEventListener('click', function (e) {
if (e.target === overlay) closeModal();
});
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && overlay && overlay.classList.contains('is-open')) {
closeModal();
}
});
}
var dataLoaded = false;
function openModal() {
if (!overlay) return;
overlay.classList.add('is-open');
document.body.style.overflow = 'hidden';
if (!dataLoaded) {
loadBranches();
loadAllCarClasses();
setDefaultDates();
dataLoaded = true;
}
}
function closeModal() {
if (!overlay) return;
overlay.classList.remove('is-open');
document.body.style.overflow = '';
}
// ─── Default Dates ────────────────────────────────────────────
function setDefaultDates() {
if (!dateFrom || !dateTo) return;
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(10, 0, 0, 0);
var dayAfter = new Date();
dayAfter.setDate(dayAfter.getDate() + 2);
dayAfter.setHours(10, 0, 0, 0);
dateFrom.value = formatDatetimeLocal(tomorrow);
dateTo.value = formatDatetimeLocal(dayAfter);
updateDaysCount();
}
function formatDatetimeLocal(d) {
var y = d.getFullYear();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
var h = String(d.getHours()).padStart(2, '0');
var min = String(d.getMinutes()).padStart(2, '0');
return y + '-' + m + '-' + day + 'T' + h + ':' + min;
}
// ─── Days Count ───────────────────────────────────────────────
function updateDaysCount() {
if (!dateFrom || !dateTo || !daysCount) return;
var from = new Date(dateFrom.value);
var 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) / (1000 * 60 * 60 * 24));
var label = diff === 1 ? 'dzień' : 'dni';
daysCount.innerHTML = 'Wybrano: <strong>' + diff + ' ' + label + '</strong>';
}
// ─── Load Branches ────────────────────────────────────────────
function loadBranches() {
if (!pickupSelect) return;
setSelectLoading(pickupSelect, true);
apiGet('branches')
.then(function (branches) {
if (!Array.isArray(branches)) {
branches = [];
}
populateSelect(pickupSelect, branches.map(function (b) {
var label = b.description || b.name;
if (b.city) label += ' — ' + b.city;
return { value: b.name, label: label };
}), 'Miejsce odbioru');
// Copy to return branch
if (returnSelect) {
populateSelect(returnSelect, branches.map(function (b) {
var label = b.description || b.name;
if (b.city) label += ' — ' + b.city;
return { value: b.name, label: label };
}), 'Miejsce zwrotu');
}
setSelectLoading(pickupSelect, false);
})
.catch(function (err) {
console.error('Failed to load branches:', err);
setSelectLoading(pickupSelect, false);
});
}
// ─── Load All Car Classes (on modal open) ────────────────────
function loadAllCarClasses() {
if (!segmentSelect) return;
setSelectLoading(segmentSelect, true);
apiGet('car-classes-all')
.then(function (classes) {
if (!Array.isArray(classes) || classes.length === 0) {
populateSelect(segmentSelect, [], 'Brak segmentów');
setSelectLoading(segmentSelect, false);
return;
}
populateSelect(segmentSelect, 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 };
}), 'Wybierz segment pojazdu');
setSelectLoading(segmentSelect, false);
})
.catch(function (err) {
console.error('Failed to load car classes:', err);
populateSelect(segmentSelect, [], 'Błąd ładowania');
setSelectLoading(segmentSelect, false);
});
}
// ─── Load Available Car Classes (filtered by dates+branch) ───
function loadCarClasses() {
if (!segmentSelect || !dateFrom || !dateTo || !pickupSelect) return;
var fromVal = dateFrom.value;
var toVal = dateTo.value;
var branch = pickupSelect.value;
if (!fromVal || !toVal || !branch) return;
// Convert datetime-local to API format: YYYY-MM-DDTHH:MM:SS
var apiFrom = fromVal.replace('T', 'T') + ':00';
var apiTo = toVal.replace('T', 'T') + ':00';
setSelectLoading(segmentSelect, true);
apiPost('car-classes', {
dateFrom: apiFrom,
dateTo: apiTo,
branchName: branch
})
.then(function (classes) {
if (!Array.isArray(classes) || classes.length === 0) {
populateSelect(segmentSelect, [], 'Brak dostępnych klas');
setSelectLoading(segmentSelect, false);
return;
}
populateSelect(segmentSelect, classes.map(function (c) {
// classes might be strings or objects
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 };
}), 'Wybierz segment pojazdu');
setSelectLoading(segmentSelect, false);
})
.catch(function (err) {
console.error('Failed to load car classes:', err);
populateSelect(segmentSelect, [], 'Błąd ładowania');
setSelectLoading(segmentSelect, false);
});
}
// ─── Load Extras from Pricelist ───────────────────────────────
function loadExtras() {
if (!segmentSelect || !dateFrom || !dateTo || !pickupSelect || !extrasContainer) return;
var category = segmentSelect.value;
var fromVal = dateFrom.value;
var toVal = dateTo.value;
var branch = pickupSelect.value;
if (!category || !fromVal || !toVal || !branch) return;
var apiFrom = fromVal + ':00';
var apiTo = toVal + ':00';
apiPost('pricelist', {
category: category,
dateFrom: apiFrom,
dateTo: apiTo,
pickUpLocation: branch
})
.then(function (pricelists) {
if (!Array.isArray(pricelists) || pricelists.length === 0) return;
var pricelist = pricelists[0];
var items = pricelist.additionalItems;
if (!Array.isArray(items) || items.length === 0) return;
extrasContainer.innerHTML = '';
items.forEach(function (item) {
var price = parseFloat(item.price || item.minPrice || 0);
var priceLabel = price > 0 ? (price.toFixed(0) + ' zł') : '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 + '">' +
'<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(item.name) + '</strong>' +
'<span class="carei-form__extra-price">' + escHtml(priceLabel) + '</span>' +
'</span>' +
'</label>';
extrasContainer.appendChild(card);
});
})
.catch(function (err) {
console.error('Failed to load pricelist:', err);
// Keep static fallback extras
});
}
// ─── Select Helpers ───────────────────────────────────────────
function populateSelect(select, options, placeholder) {
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) {
var wrap = select.closest('.carei-form__select-wrap');
if (!wrap) return;
if (loading) {
wrap.classList.add('carei-form__select-wrap--loading');
} else {
wrap.classList.remove('carei-form__select-wrap--loading');
}
}
// ─── Same Return Location ─────────────────────────────────────
function initSameReturn() {
if (!sameReturnCheck || !returnWrap) return;
sameReturnCheck.addEventListener('change', function () {
if (sameReturnCheck.checked) {
returnWrap.classList.remove('is-visible');
} else {
returnWrap.classList.add('is-visible');
}
});
}
// ─── 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;
// Clear previous errors
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') {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!el.value.trim() || !emailRegex.test(el.value.trim())) hasError = true;
} else if (f.type === 'phone') {
var digits = el.value.replace(/\D/g, '');
if (digits.length < 9) 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);
}
});
// Check dateTo > dateFrom
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');
}
}
// Return branch required if different location
if (sameReturnCheck && !sameReturnCheck.checked && returnSelect) {
if (!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) {
// For phone in wrap
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();
}
// Checkbox
var label = e.target.closest('.carei-form__checkbox-label');
if (label) label.classList.remove('carei-form__checkbox-label--error');
});
// Also on change for checkboxes
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 ──────────────────────────────────────────────
function initSubmit() {
if (!form) return;
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!validateForm()) return;
var formData = collectFormData();
console.log('Carei Reservation — Form data:', formData);
// Phase 3 will handle actual API submission
var submitBtn = form.querySelector('.carei-form__submit');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.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> Wysyłanie...';
// Re-enable after 2s (temporary — Phase 3 will handle properly)
setTimeout(function () {
submitBtn.disabled = false;
submitBtn.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';
}, 2000);
}
});
}
function collectFormData() {
var extras = [];
form.querySelectorAll('input[name="extras[]"]:checked').forEach(function (cb) {
extras.push({
id: cb.value,
price: parseFloat(cb.getAttribute('data-price') || 0)
});
});
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 : '',
extras: extras,
firstName: val('carei-firstname'),
lastName: val('carei-lastname'),
email: val('carei-email'),
phone: '+48' + (document.getElementById('carei-phone') ? document.getElementById('carei-phone').value.replace(/\D/g, '') : ''),
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() : '';
}
// ─── Event Listeners for Dynamic Loading ──────────────────────
function initDynamicLoading() {
// Date change → update days count
if (dateFrom) dateFrom.addEventListener('change', updateDaysCount);
if (dateTo) dateTo.addEventListener('change', updateDaysCount);
// When segment + dates + branch all set → load extras from pricelist
if (segmentSelect) segmentSelect.addEventListener('change', loadExtras);
if (pickupSelect) pickupSelect.addEventListener('change', loadExtras);
if (dateFrom) dateFrom.addEventListener('change', loadExtras);
if (dateTo) dateTo.addEventListener('change', loadExtras);
}
// ─── HTML Escape Helpers ──────────────────────────────────────
function escHtml(str) {
var div = document.createElement('div');
div.textContent = str || '';
return div.innerHTML;
}
function escAttr(str) {
return (str || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// ─── Init ─────────────────────────────────────────────────────
function init() {
initRefs();
if (!overlay || !form) return;
initModal();
initSameReturn();
initDynamicLoading();
initClearErrors();
initSubmit();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();