update
This commit is contained in:
@@ -0,0 +1,541 @@
|
||||
/* ═══════════════════════════════════════════
|
||||
Carei Reservation — Design Tokens
|
||||
═══════════════════════════════════════════ */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--carei-blue: #2F2482;
|
||||
--carei-red: #FF0000;
|
||||
--carei-red-hover: #D60000;
|
||||
--carei-bg: #EDEDF3;
|
||||
--carei-gray: #505050;
|
||||
--carei-placeholder: #C7C7C7;
|
||||
--carei-border: #D0D0D0;
|
||||
--carei-white: #FFFFFF;
|
||||
--carei-radius: 8px;
|
||||
--carei-radius-lg: 16px;
|
||||
--carei-input-h: 48px;
|
||||
--carei-font: 'Albert Sans', sans-serif;
|
||||
--carei-gap-section: 24px;
|
||||
--carei-gap-inner: 16px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Trigger Button
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-reservation-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 14px 28px;
|
||||
background-color: var(--carei-red);
|
||||
color: var(--carei-white);
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: var(--carei-radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
}
|
||||
.carei-reservation-trigger:hover {
|
||||
background-color: var(--carei-red-hover);
|
||||
color: var(--carei-white);
|
||||
}
|
||||
.carei-reservation-trigger svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Modal Overlay & Container
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100000;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.carei-modal-overlay.is-open {
|
||||
display: flex;
|
||||
}
|
||||
.carei-modal {
|
||||
background: var(--carei-bg);
|
||||
border-radius: var(--carei-radius-lg);
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
padding: 40px 48px;
|
||||
position: relative;
|
||||
font-family: var(--carei-font);
|
||||
}
|
||||
.carei-modal-close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--carei-gray);
|
||||
line-height: 1;
|
||||
padding: 4px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.carei-modal-close:hover {
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-modal-title {
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
color: var(--carei-blue);
|
||||
text-align: center;
|
||||
margin: 0 0 var(--carei-gap-section) 0;
|
||||
}
|
||||
.carei-modal-title span {
|
||||
color: var(--carei-red);
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.carei-modal::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.carei-modal::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.carei-modal::-webkit-scrollbar-thumb {
|
||||
background: var(--carei-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Form Layout
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--carei-gap-section);
|
||||
}
|
||||
.carei-form__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--carei-gap-inner);
|
||||
}
|
||||
.carei-form__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--carei-gap-inner);
|
||||
}
|
||||
.carei-form__row--dates {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.carei-form__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.carei-form__field--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Inputs & Selects
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__input,
|
||||
.carei-form__textarea,
|
||||
.carei-form__select-wrap select {
|
||||
height: var(--carei-input-h);
|
||||
padding: 0 16px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--carei-radius);
|
||||
background: var(--carei-white);
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: var(--carei-blue);
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.carei-form__input::placeholder,
|
||||
.carei-form__textarea::placeholder {
|
||||
color: var(--carei-gray);
|
||||
font-weight: 400;
|
||||
}
|
||||
.carei-form__input:focus,
|
||||
.carei-form__textarea:focus,
|
||||
.carei-form__select-wrap select:focus {
|
||||
border-color: var(--carei-blue);
|
||||
}
|
||||
.carei-form__textarea {
|
||||
height: 143px;
|
||||
padding: 16px;
|
||||
resize: vertical;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.carei-form__label-small {
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--carei-gray);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.carei-form__icon-calendar {
|
||||
color: var(--carei-gray);
|
||||
}
|
||||
|
||||
/* Select wrapper */
|
||||
.carei-form__select-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.carei-form__select-wrap select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
padding-right: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.carei-form__select-wrap--icon select {
|
||||
padding-left: 40px;
|
||||
}
|
||||
.carei-form__select-arrow {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
pointer-events: none;
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-form__icon-pin {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
pointer-events: none;
|
||||
color: var(--carei-gray);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Days count */
|
||||
.carei-form__days-count {
|
||||
font-family: var(--carei-font);
|
||||
font-size: 13px;
|
||||
color: var(--carei-gray);
|
||||
text-align: right;
|
||||
}
|
||||
.carei-form__days-count strong {
|
||||
color: var(--carei-blue);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Phone Input
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__phone-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--carei-white);
|
||||
border-radius: var(--carei-radius);
|
||||
border: 1px solid transparent;
|
||||
transition: border-color 0.2s;
|
||||
height: var(--carei-input-h);
|
||||
}
|
||||
.carei-form__phone-wrap:focus-within {
|
||||
border-color: var(--carei-blue);
|
||||
}
|
||||
.carei-form__phone-prefix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 8px 0 12px;
|
||||
border-right: 1px solid var(--carei-border);
|
||||
height: 60%;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
color: var(--carei-gray);
|
||||
}
|
||||
.carei-form__phone-flag {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
.carei-form__phone-code {
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--carei-gray);
|
||||
}
|
||||
.carei-form__input--phone {
|
||||
border: none;
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.carei-form__input--phone::placeholder {
|
||||
color: var(--carei-placeholder);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.carei-form__input--phone:focus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Checkboxes (custom)
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__checkbox-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
font-family: var(--carei-font);
|
||||
font-size: 14px;
|
||||
color: var(--carei-blue);
|
||||
user-select: none;
|
||||
}
|
||||
.carei-form__checkbox-label input[type="checkbox"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.carei-form__checkbox-box {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
min-width: 24px;
|
||||
border: 2px solid var(--carei-border);
|
||||
border-radius: var(--carei-radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
background: var(--carei-white);
|
||||
margin-top: 1px;
|
||||
}
|
||||
.carei-form__checkbox-box svg {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.carei-form__checkbox-label input:checked + .carei-form__checkbox-box {
|
||||
background: var(--carei-blue);
|
||||
border-color: var(--carei-blue);
|
||||
}
|
||||
.carei-form__checkbox-label input:checked + .carei-form__checkbox-box svg {
|
||||
opacity: 1;
|
||||
}
|
||||
.carei-form__checkbox-text {
|
||||
line-height: 1.6;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.carei-form__checkbox-text a {
|
||||
color: var(--carei-blue);
|
||||
text-decoration: underline;
|
||||
font-weight: 600;
|
||||
}
|
||||
.carei-form__checkbox-text a:hover {
|
||||
color: var(--carei-red);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Dividers
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
font-family: 'Roboto', var(--carei-font), sans-serif;
|
||||
font-size: 12px;
|
||||
color: var(--carei-gray);
|
||||
text-transform: none;
|
||||
}
|
||||
.carei-form__divider::before,
|
||||
.carei-form__divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--carei-border);
|
||||
}
|
||||
.carei-form__divider span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Extra Cards (Ubezpieczenie, Fotelik)
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__extra-card {
|
||||
border: 1px solid var(--carei-border);
|
||||
border-radius: var(--carei-radius);
|
||||
padding: 16px;
|
||||
background: var(--carei-white);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.carei-form__extra-card:has(input:checked) {
|
||||
border-color: var(--carei-blue);
|
||||
}
|
||||
.carei-form__checkbox-label--card {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.carei-form__extra-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
.carei-form__extra-content strong {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-form__extra-desc {
|
||||
font-size: 12px;
|
||||
color: var(--carei-gray);
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.carei-form__extra-price {
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
color: var(--carei-red);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Footer (Privacy + Submit)
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--carei-gap-inner);
|
||||
padding-top: 8px;
|
||||
}
|
||||
.carei-form__checkbox-label--privacy {
|
||||
flex: 1;
|
||||
}
|
||||
.carei-form__submit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 32px;
|
||||
background-color: var(--carei-red);
|
||||
color: var(--carei-white);
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: var(--carei-radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.carei-form__submit:hover {
|
||||
background-color: var(--carei-red-hover);
|
||||
}
|
||||
.carei-form__submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.carei-form__submit svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Validation
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__field--error .carei-form__input,
|
||||
.carei-form__field--error .carei-form__textarea,
|
||||
.carei-form__field--error .carei-form__select-wrap select,
|
||||
.carei-form__field--error .carei-form__phone-wrap {
|
||||
border-color: var(--carei-red) !important;
|
||||
}
|
||||
.carei-form__checkbox-label--error .carei-form__checkbox-box {
|
||||
border-color: var(--carei-red) !important;
|
||||
}
|
||||
.carei-form__error-msg {
|
||||
font-family: var(--carei-font);
|
||||
font-size: 11px;
|
||||
color: var(--carei-red);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.carei-form__error-summary {
|
||||
font-family: var(--carei-font);
|
||||
font-size: 13px;
|
||||
color: var(--carei-red);
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
background: rgba(255, 0, 0, 0.05);
|
||||
border-radius: var(--carei-radius);
|
||||
}
|
||||
|
||||
/* Loading state for selects */
|
||||
.carei-form__select-wrap--loading select {
|
||||
color: var(--carei-placeholder);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Return branch slide */
|
||||
.carei-form__return-wrap {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease, opacity 0.3s ease;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.carei-form__return-wrap.is-visible {
|
||||
max-height: 80px;
|
||||
opacity: 1;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Mobile Responsive (<768px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 768px) {
|
||||
.carei-modal-overlay {
|
||||
padding: 0;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.carei-modal {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
border-radius: var(--carei-radius-lg) var(--carei-radius-lg) 0 0;
|
||||
padding: 32px 24px;
|
||||
border-radius: 0;
|
||||
}
|
||||
.carei-form__row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.carei-form__row--dates {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.carei-form__footer {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.carei-form__submit {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small screens */
|
||||
@media (max-width: 480px) {
|
||||
.carei-form__row--dates {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.carei-modal {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
(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, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// ─── Init ─────────────────────────────────────────────────────
|
||||
|
||||
function init() {
|
||||
initRefs();
|
||||
if (!overlay || !form) return;
|
||||
initModal();
|
||||
initSameReturn();
|
||||
initDynamicLoading();
|
||||
initClearErrors();
|
||||
initSubmit();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user