/** * Yacht Booking Calendar - Frontend JavaScript * * @package YachtBooking */ (function($) { 'use strict'; /** * Initialize Yacht Calendar */ function initYachtCalendar($wrapper) { const calendarId = $wrapper.data('calendar-id'); const bookingEnabled = $wrapper.data('booking-enabled') !== 0; const yachtsData = $wrapper.attr('data-yachts'); const primaryColor = $wrapper.data('primary-color') || '#2271b1'; const normalizeColor = function(color, fallbackColor, legacyColor) { const raw = (color || '').toString().trim().toLowerCase(); if (!raw || raw === legacyColor) { return fallbackColor; } return color; }; const availableBg = normalizeColor($wrapper.data('available-bg'), '#f5f9ff', '#d4edda'); const bookedBg = normalizeColor($wrapper.data('booked-bg'), '#bc1834', '#f8d7da'); // Expose colors as CSS custom properties so half-day gradients can read them. $wrapper.css({ '--yacht-available-bg': availableBg, '--yacht-booked-bg': bookedBg }); const yachtItems = yachtsData ? JSON.parse(yachtsData) : []; const yachtMap = {}; const state = { currentYachtId: parseInt($wrapper.attr('data-yacht-id'), 10) || 0 }; const $calendarEl = $wrapper.find('.yacht-calendar'); const $title = $wrapper.find('.yacht-calendar-title'); const $description = $wrapper.find('.yacht-calendar-description'); const $switcher = $wrapper.find('.yacht-calendar-switcher'); const $switcherButtonsContainer = $wrapper.find('.yacht-calendar-switcher-buttons'); const $form = $wrapper.find('.yacht-booking-form'); const $formContainer = $wrapper.find('.yacht-booking-form-container'); const $inquiryForm = $wrapper.find('.yacht-inquiry-form'); const $inquiryResponse = $wrapper.find('.yacht-inquiry-response'); if (!$calendarEl.length) { console.error('Calendar element not found'); return; } yachtItems.forEach(function(item) { yachtMap[item.id] = item; }); function getAvailabilityUrl() { return yachtBookingData.apiUrl + '/availability/' + state.currentYachtId; } function getTitleText(yachtTitle) { if (bookingEnabled) { return (yachtBookingData.i18n.bookingTitlePrefix || 'Rezerwacja: %s').replace('%s', yachtTitle); } return (yachtBookingData.i18n.availabilityTitlePrefix || 'Dostepnosc: %s').replace('%s', yachtTitle); } function renderSwitcherButtons() { if (!$switcher.length || !$switcherButtonsContainer.length) { return; } const otherYachts = yachtItems.filter(function(item) { return item.id !== state.currentYachtId; }); if (!otherYachts.length) { $switcher.hide(); $switcherButtonsContainer.empty(); return; } const buttonsHtml = otherYachts.map(function(item) { return ''; }).join(''); $switcherButtonsContainer.html(buttonsHtml); $switcher.show(); } function resetFormsForYachtSwitch() { if ($form.length) { $form.each(function() { this.reset(); }); $form.attr('data-yacht-id', state.currentYachtId); $form.find('.yacht-booking-response').empty(); } if ($formContainer.length) { $formContainer.stop(true, true).hide(); } if ($inquiryForm.length) { $inquiryForm.each(function() { this.reset(); }); $inquiryForm.attr('data-yacht-id', state.currentYachtId); } if ($inquiryResponse.length) { $inquiryResponse.empty(); } } function updateYachtDetails() { const yacht = yachtMap[state.currentYachtId]; if (!yacht) { return; } $title.text(getTitleText(yacht.title)); if ($description.length) { if (yacht.description) { $description.html(yacht.description).show(); } else { $description.empty().hide(); } } renderSwitcherButtons(); resetFormsForYachtSwitch(); } // FullCalendar initialization const calendar = new FullCalendar.Calendar($calendarEl[0], { initialView: 'dayGridMonth', contentHeight: bookingEnabled ? undefined : 'auto', locale: 'pl', firstDay: 1, // Poniedziałek headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth' }, buttonText: { today: 'Dzisiaj', month: 'Miesiąc' }, selectable: bookingEnabled, selectMirror: bookingEnabled, unselectAuto: false, selectLongPressDelay: 1, // Fetch events (availability) from REST API events: function(info, successCallback, failureCallback) { const startDate = formatDate(info.start); const endDate = formatDate(info.end); $.ajax({ url: getAvailabilityUrl(), method: 'GET', data: { start: startDate, end: endDate }, success: function(data) { // Index by date so we can probe neighbours for edge detection. const byDate = {}; data.forEach(function(day) { byDate[day.date] = day; }); function shiftDate(dateString, deltaDays) { const d = new Date(dateString + 'T00:00:00'); d.setDate(d.getDate() + deltaDays); return formatDate(d); } function sameSegment(a, b) { if (!a || !b) return false; if (a.status !== b.status) return false; // Different bookings (or booking vs blocked) break the segment. const aId = a.booking_id || null; const bId = b.booking_id || null; return aId === bId; } const events = data.map(function(day) { const status = day.status; const classes = ['yacht-day-' + status]; if (status !== 'available') { const prev = byDate[shiftDate(day.date, -1)]; const next = byDate[shiftDate(day.date, +1)]; const isStart = !sameSegment(day, prev); const isEnd = !sameSegment(day, next); if (isStart && isEnd) { // Single-day blockade — render as full booked, no half. classes.push('yacht-day-' + status + '-single'); } else if (isStart) { classes.push('yacht-day-' + status + '-start'); } else if (isEnd) { classes.push('yacht-day-' + status + '-end'); } else { classes.push('yacht-day-' + status + '-mid'); } } return { id: day.date, start: day.date, allDay: true, display: 'background', backgroundColor: status === 'available' ? availableBg : bookedBg, classNames: classes, extendedProps: { status: status, booking_id: day.booking_id || null } }; }); successCallback(events); }, error: function(xhr, status, error) { console.error('Error fetching availability:', error); failureCallback(error); } }); }, // Handle date selection select: function(info) { const startDate = formatDate(info.start); let endDate = formatDate(info.end); // FullCalendar end date is exclusive, so subtract 1 day endDate = subtractDay(endDate); // Check if any selected dates are unavailable const unavailableDates = calendar.getEvents().filter(function(event) { const eventDate = formatDate(event.start); return eventDate >= startDate && eventDate <= endDate && event.extendedProps.status !== 'available'; }); if (unavailableDates.length > 0) { alert(yachtBookingData.i18n.unavailableDatesSelected || 'Wybrane daty zawierają niedostępne terminy. Proszę wybrać inne daty.'); calendar.unselect(); return; } // Fill form dates $form.find('.booking-start-date').val(startDate); $form.find('.booking-end-date').val(endDate); // Show booking form $formContainer.slideDown(300); // Scroll to form $('html, body').animate({ scrollTop: $formContainer.offset().top - 100 }, 500); }, // Highlight selectable dates selectAllow: function(selectInfo) { // Must select at least 1 day const daysDiff = Math.ceil((selectInfo.end - selectInfo.start) / (1000 * 60 * 60 * 24)); return daysDiff >= 1; }, // Disable past dates selectConstraint: { start: new Date().toISOString().split('T')[0] }, // Day cell class names dayCellClassNames: function(arg) { const today = new Date(); today.setHours(0, 0, 0, 0); if (arg.date < today) { return ['fc-day-past']; } return []; } }); calendar.render(); updateYachtDetails(); $switcher.on('click', '.yacht-calendar-switcher-button', function() { const newYachtId = parseInt($(this).attr('data-yacht-id'), 10) || 0; if (!newYachtId || newYachtId === state.currentYachtId) { return; } state.currentYachtId = newYachtId; $wrapper.attr('data-yacht-id', newYachtId); calendar.unselect(); updateYachtDetails(); calendar.refetchEvents(); }); // Handle inquiry form submission if ($inquiryForm.length) { $inquiryForm.on('submit', function(e) { e.preventDefault(); var $submitBtn = $inquiryForm.find('.yacht-booking-submit'); var $response = $inquiryForm.find('.yacht-inquiry-response'); var originalBtnText = $submitBtn.text(); $submitBtn.prop('disabled', true).text(yachtBookingData.i18n.submitting || 'Wysyłanie...'); $response.html(''); var formData = { yacht_id: state.currentYachtId, customer_name: $inquiryForm.find('[name="customer_name"]').val(), customer_email: $inquiryForm.find('[name="customer_email"]').val(), customer_phone: $inquiryForm.find('[name="customer_phone"]').val(), preferred_dates: $inquiryForm.find('[name="preferred_dates"]').val(), message: $inquiryForm.find('[name="message"]').val() }; $.ajax({ url: yachtBookingData.apiUrl + '/inquiries', method: 'POST', beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', yachtBookingData.nonce); }, contentType: 'application/json', data: JSON.stringify(formData), success: function(response) { $response.html('