/** * WP BnB Admin JavaScript * * @package Magdev\WpBnb */ (function($) { 'use strict'; /** * Initialize license management functionality. */ function initLicenseManagement() { var $validateBtn = $('#wp-bnb-validate-license'); var $activateBtn = $('#wp-bnb-activate-license'); var $spinner = $('#wp-bnb-license-spinner'); var $message = $('#wp-bnb-license-message'); if (!$validateBtn.length) { return; } // Validate license button click. $validateBtn.on('click', function(e) { e.preventDefault(); performLicenseAction('wp_bnb_validate_license', wpBnbAdmin.i18n.validating); }); // Activate license button click. $activateBtn.on('click', function(e) { e.preventDefault(); performLicenseAction('wp_bnb_activate_license', wpBnbAdmin.i18n.activating); }); /** * Perform a license AJAX action. * * @param {string} action AJAX action name. * @param {string} loadingText Loading text to display. */ function performLicenseAction(action, loadingText) { // Disable buttons and show spinner. $validateBtn.prop('disabled', true); $activateBtn.prop('disabled', true); $spinner.addClass('is-active'); $message.hide(); $.ajax({ url: wpBnbAdmin.ajaxUrl, type: 'POST', data: { action: action, nonce: wpBnbAdmin.nonce }, success: function(response) { $spinner.removeClass('is-active'); $validateBtn.prop('disabled', false); $activateBtn.prop('disabled', false); if (response.success) { showMessage('success', response.data.message); // Reload page after short delay to show updated status. setTimeout(function() { window.location.reload(); }, 1500); } else { showMessage('error', response.data.message || wpBnbAdmin.i18n.error); } }, error: function() { $spinner.removeClass('is-active'); $validateBtn.prop('disabled', false); $activateBtn.prop('disabled', false); showMessage('error', wpBnbAdmin.i18n.error); } }); } /** * Show a message. * * @param {string} type Message type (success or error). * @param {string} message Message text. */ function showMessage(type, message) { $message .removeClass('success error') .addClass(type) .text(message) .fadeIn(); } } /** * Initialize update check functionality. */ function initUpdateCheck() { var $checkBtn = $('#wp-bnb-check-updates'); var $spinner = $('#wp-bnb-update-spinner'); var $message = $('#wp-bnb-update-message'); var $latestVersion = $('#wp-bnb-latest-version'); var $lastCheck = $('#wp-bnb-update-last-check'); if (!$checkBtn.length) { return; } $checkBtn.on('click', function(e) { e.preventDefault(); // Disable button and show spinner. $checkBtn.prop('disabled', true); $spinner.addClass('is-active'); $message.hide(); $.ajax({ url: wpBnbAdmin.ajaxUrl, type: 'POST', data: { action: 'wp_bnb_check_updates', nonce: wpBnbAdmin.nonce }, success: function(response) { $spinner.removeClass('is-active'); $checkBtn.prop('disabled', false); if (response.success) { var data = response.data; // Update last check time. $lastCheck.text(wpBnbAdmin.i18n.justNow || 'Just now'); // Update version display. if (data.update_available) { $latestVersion.html( '' + data.latest_version + ' ' + ' ' + '' + (wpBnbAdmin.i18n.updateAvailable || 'Update available!') + '' ); showUpdateMessage('success', data.message); } else { $latestVersion.html( data.latest_version + ' ' + (wpBnbAdmin.i18n.upToDate || '(You are up to date)') + '' ); showUpdateMessage('success', data.message); } } else { showUpdateMessage('error', response.data.message || wpBnbAdmin.i18n.error); } }, error: function() { $spinner.removeClass('is-active'); $checkBtn.prop('disabled', false); showUpdateMessage('error', wpBnbAdmin.i18n.error); } }); }); /** * Show an update message. * * @param {string} type Message type (success or error). * @param {string} message Message text. */ function showUpdateMessage(type, message) { $message .removeClass('success error') .addClass(type) .text(message) .fadeIn(); } } /** * Initialize room gallery functionality. */ function initRoomGallery() { var $container = $('#bnb-room-gallery'); var $addButton = $('#bnb-add-gallery-images'); var $input = $('#bnb_room_gallery'); var $imagesContainer = $container.find('.bnb-gallery-images'); if (!$addButton.length) { return; } // Media frame for selecting images. var mediaFrame; // Add images button click. $addButton.on('click', function(e) { e.preventDefault(); // If frame exists, reopen it. if (mediaFrame) { mediaFrame.open(); return; } // Create media frame. mediaFrame = wp.media({ title: wpBnbAdmin.i18n.selectImages, button: { text: wpBnbAdmin.i18n.addToGallery }, multiple: true, library: { type: 'image' } }); // Handle selection. mediaFrame.on('select', function() { var selection = mediaFrame.state().get('selection'); selection.each(function(attachment) { var data = attachment.toJSON(); var thumbnail = data.sizes.thumbnail ? data.sizes.thumbnail.url : data.url; // Check if already in gallery. if ($imagesContainer.find('[data-id="' + data.id + '"]').length) { return; } // Add image to gallery. var $image = $(''); $imagesContainer.append($image); }); updateGalleryInput(); }); mediaFrame.open(); }); // Remove image button click. $imagesContainer.on('click', '.bnb-remove-image', function(e) { e.preventDefault(); $(this).closest('.bnb-gallery-image').remove(); updateGalleryInput(); }); // Make gallery sortable. $imagesContainer.sortable({ items: '.bnb-gallery-image', cursor: 'move', update: function() { updateGalleryInput(); } }); /** * Update the hidden input with gallery image IDs. */ function updateGalleryInput() { var ids = []; $imagesContainer.find('.bnb-gallery-image').each(function() { ids.push($(this).data('id')); }); $input.val(ids.join(',')); } } /** * Initialize pricing settings functionality. */ function initPricingSettings() { var $midTermInput = $('#wp_bnb_mid_term_max_nights'); var $longTermMin = $('#wp-bnb-long-term-min'); if (!$midTermInput.length || !$longTermMin.length) { return; } // Update long-term minimum display when mid-term max changes. $midTermInput.on('input', function() { $longTermMin.text($(this).val()); }); } /** * Initialize season form validation. */ function initSeasonForm() { var $form = $('.bnb-season-form'); if (!$form.length) { return; } // Validate date format on input. $form.find('#season_start_date, #season_end_date').on('input', function() { var value = $(this).val(); var isValid = /^\d{2}-\d{2}$/.test(value); if (value.length > 0 && !isValid) { $(this).css('border-color', '#d63638'); } else { $(this).css('border-color', ''); } }); // Validate modifier range. $form.find('#season_modifier').on('input', function() { var value = parseFloat($(this).val()); var $preview = $(this).siblings('.bnb-modifier-preview'); if ($preview.length === 0) { $preview = $(''); $(this).after($preview); } if (!isNaN(value) && value > 0) { var percentage = ((value - 1) * 100).toFixed(0); var text = ''; if (percentage > 0) { text = '+' + percentage + '% ' + (wpBnbAdmin.i18n.increase || 'increase'); $preview.css('color', '#d63638'); } else if (percentage < 0) { text = percentage + '% ' + (wpBnbAdmin.i18n.discount || 'discount'); $preview.css('color', '#00a32a'); } else { text = wpBnbAdmin.i18n.normalPrice || 'Normal price'; $preview.css('color', '#646970'); } $preview.text(' = ' + text); } else { $preview.text(''); } }).trigger('input'); } /** * Initialize pricing meta box interactions. */ function initPricingMetaBox() { var $pricingTable = $('.bnb-pricing-table'); if (!$pricingTable.length) { return; } // Highlight empty required prices. $pricingTable.find('input[type="number"]').on('blur', function() { var $input = $(this); var value = $input.val(); var isShortTerm = $input.attr('id').indexOf('short_term') !== -1; // Short-term price is recommended. if (isShortTerm && (value === '' || parseFloat(value) <= 0)) { $input.css('background-color', '#fcf0f1'); } else { $input.css('background-color', ''); } }); } /** * Initialize booking form functionality. */ function initBookingForm() { var $roomSelect = $('#bnb_booking_room_id'); var $checkInInput = $('#bnb_booking_check_in'); var $checkOutInput = $('#bnb_booking_check_out'); var $nightsDisplay = $('#bnb-booking-nights-display'); var $availabilityDisplay = $('#bnb-booking-availability-display'); var $priceDisplay = $('#bnb-booking-price-display'); var $calculatedPriceInput = $('#bnb_booking_calculated_price'); var $priceBreakdownInput = $('#bnb_booking_price_breakdown'); var $breakdownDisplay = $('#bnb-booking-breakdown-display'); var $recalculateBtn = $('#bnb-recalculate-price'); var $statusSelect = $('#bnb_booking_status'); var $statusPreview = $('#bnb-status-preview .bnb-status-badge'); // Check if we're on a booking edit page. if (!$roomSelect.length || !$checkInInput.length) { return; } // Get current booking ID if editing. var bookingId = null; var $postId = $('input[name="post_ID"]'); if ($postId.length) { bookingId = parseInt($postId.val(), 10); } // Debounce timer for availability check. var availabilityTimer = null; /** * Update nights display based on selected dates. */ function updateNightsDisplay() { var checkIn = $checkInInput.val(); var checkOut = $checkOutInput.val(); if (checkIn && checkOut) { var startDate = new Date(checkIn); var endDate = new Date(checkOut); var nights = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)); if (nights > 0) { var nightText = nights === 1 ? wpBnbAdmin.i18n.night : wpBnbAdmin.i18n.nights; $nightsDisplay.text(nights + ' ' + nightText); } else { $nightsDisplay.text(wpBnbAdmin.i18n.error || 'Invalid date range'); $nightsDisplay.css('color', '#d63638'); } } else { $nightsDisplay.text(wpBnbAdmin.i18n.selectRoomAndDates); $nightsDisplay.css('color', ''); } } /** * Check availability via AJAX. */ function checkAvailability() { var roomId = $roomSelect.val(); var checkIn = $checkInInput.val(); var checkOut = $checkOutInput.val(); if (!roomId || !checkIn || !checkOut) { $availabilityDisplay .text(wpBnbAdmin.i18n.selectRoomAndDates) .removeClass('bnb-available bnb-not-available') .addClass('bnb-checking'); return; } // Validate dates. var startDate = new Date(checkIn); var endDate = new Date(checkOut); if (endDate <= startDate) { $availabilityDisplay .text(wpBnbAdmin.i18n.error || 'Check-out must be after check-in') .removeClass('bnb-available bnb-checking') .addClass('bnb-not-available'); return; } // Show checking status. $availabilityDisplay .text(wpBnbAdmin.i18n.checking) .removeClass('bnb-available bnb-not-available') .addClass('bnb-checking'); // Make AJAX request. $.ajax({ url: wpBnbAdmin.ajaxUrl, type: 'POST', data: { action: 'wp_bnb_check_availability', nonce: wpBnbAdmin.nonce, room_id: roomId, check_in: checkIn, check_out: checkOut, exclude_booking: bookingId }, success: function(response) { if (response.success) { var data = response.data; if (data.available) { $availabilityDisplay .html(' ' + wpBnbAdmin.i18n.available) .removeClass('bnb-not-available bnb-checking') .addClass('bnb-available'); // Update price display. if (data.price_formatted) { $priceDisplay.html('' + data.price_formatted + ''); $calculatedPriceInput.val(data.price); if (data.breakdown) { $priceBreakdownInput.val(JSON.stringify(data.breakdown)); updateBreakdownDisplay(data.breakdown); } } } else { var conflictText = wpBnbAdmin.i18n.notAvailable; if (data.conflicts && data.conflicts.length > 0) { conflictText += ' (' + data.conflicts[0].reference + ')'; } $availabilityDisplay .html(' ' + conflictText) .removeClass('bnb-available bnb-checking') .addClass('bnb-not-available'); } // Update nights display with response data. if (data.nights) { var nightText = data.nights === 1 ? wpBnbAdmin.i18n.night : wpBnbAdmin.i18n.nights; $nightsDisplay.text(data.nights + ' ' + nightText); } } else { $availabilityDisplay .text(response.data.message || wpBnbAdmin.i18n.error) .removeClass('bnb-available bnb-checking') .addClass('bnb-not-available'); } }, error: function() { $availabilityDisplay .text(wpBnbAdmin.i18n.error) .removeClass('bnb-available bnb-checking') .addClass('bnb-not-available'); } }); } /** * Update breakdown display with formatted data. * * @param {Object} breakdown Price breakdown data. */ function updateBreakdownDisplay(breakdown) { if (!$breakdownDisplay.length) { return; } var html = ''; $breakdownDisplay.html(html); } /** * Format price for display. * * @param {number} price Price value. * @return {string} Formatted price. */ function formatPrice(price) { // Simple formatting - server-side Calculator::formatPrice is more complete. return parseFloat(price).toFixed(2); } /** * Debounced availability check. */ function debouncedAvailabilityCheck() { updateNightsDisplay(); // Clear existing timer. if (availabilityTimer) { clearTimeout(availabilityTimer); } // Set new timer to check availability after 500ms. availabilityTimer = setTimeout(checkAvailability, 500); } // Bind change events to trigger availability check. $roomSelect.on('change', debouncedAvailabilityCheck); $checkInInput.on('change', debouncedAvailabilityCheck); $checkOutInput.on('change', debouncedAvailabilityCheck); // Recalculate price button. if ($recalculateBtn.length) { $recalculateBtn.on('click', function(e) { e.preventDefault(); checkAvailability(); }); } // Status preview update. if ($statusSelect.length && $statusPreview.length) { $statusSelect.on('change', function() { var $selected = $(this).find('option:selected'); var color = $selected.data('color') || '#ccc'; var text = $selected.text(); $statusPreview .css('background-color', color) .text(text); }); } // Set min date for check-in to today. var today = new Date().toISOString().split('T')[0]; $checkInInput.attr('min', today); // Update check-out min date when check-in changes. $checkInInput.on('change', function() { var checkIn = $(this).val(); if (checkIn) { var nextDay = new Date(checkIn); nextDay.setDate(nextDay.getDate() + 1); var minCheckOut = nextDay.toISOString().split('T')[0]; $checkOutInput.attr('min', minCheckOut); // If check-out is before new min, update it. if ($checkOutInput.val() && $checkOutInput.val() <= checkIn) { $checkOutInput.val(minCheckOut); } } }); } /** * Initialize calendar page functionality. */ function initCalendarPage() { var $calendar = $('.bnb-calendar-grid'); if (!$calendar.length) { return; } // Add hover effect for booking cells. $calendar.on('mouseenter', '.bnb-calendar-day.booked', function() { var bookingId = $(this).data('booking-id'); if (bookingId) { $calendar.find('.bnb-calendar-day[data-booking-id="' + bookingId + '"]') .addClass('booking-hover'); } }).on('mouseleave', '.bnb-calendar-day.booked', function() { $calendar.find('.bnb-calendar-day.booking-hover') .removeClass('booking-hover'); }); // Click to edit booking. $calendar.on('click', '.bnb-calendar-day.booked', function() { var bookingId = $(this).data('booking-id'); if (bookingId) { window.location.href = wpBnbAdmin.ajaxUrl.replace('admin-ajax.php', 'post.php?post=' + bookingId + '&action=edit'); } }); } /** * Initialize guest search functionality for booking form. */ function initGuestSearch() { var $searchInput = $('#bnb_booking_guest_search'); var $searchResults = $('#bnb-guest-search-results'); var $guestIdInput = $('#bnb_booking_guest_id'); var $linkedGuestInfo = $('#bnb-linked-guest-info'); var $searchContainer = $('#bnb-guest-search-container'); var $fieldsContainer = $('#bnb-guest-fields-container'); var $unlinkBtn = $('#bnb-unlink-guest'); var $guestNameInput = $('#bnb_booking_guest_name'); var $guestEmailInput = $('#bnb_booking_guest_email'); var $guestPhoneInput = $('#bnb_booking_guest_phone'); // Exit if not on booking form. if (!$searchInput.length) { return; } var searchTimer = null; /** * Perform guest search via AJAX. */ function searchGuests() { var query = $searchInput.val().trim(); if (query.length < 2) { $searchResults.hide().empty(); return; } $searchResults.html('
' + wpBnbAdmin.i18n.searchingGuests + '
').show(); $.ajax({ url: wpBnbAdmin.ajaxUrl, type: 'POST', data: { action: 'wp_bnb_search_guest', nonce: wpBnbAdmin.nonce, search: query }, success: function(response) { if (response.success && response.data.guests.length > 0) { var html = '
'; $.each(response.data.guests, function(i, guest) { var isBlocked = guest.status === 'blocked'; var statusClass = isBlocked ? 'bnb-guest-blocked' : ''; var statusLabel = isBlocked ? ' ' + wpBnbAdmin.i18n.guestBlocked + '' : ''; html += '
'; html += '
'; html += '' + escapeHtml(guest.name) + '' + statusLabel + '
'; html += '' + escapeHtml(guest.email || '') + ''; if (guest.phone) { html += ' (' + escapeHtml(guest.phone) + ')'; } html += '
'; if (!isBlocked) { html += ''; } html += '
'; }); html += '
'; $searchResults.html(html); } else { $searchResults.html('
' + wpBnbAdmin.i18n.noGuestsFound + '
'); } }, error: function() { $searchResults.html('
' + wpBnbAdmin.i18n.error + '
'); } }); } /** * Escape HTML entities. * * @param {string} text Text to escape. * @return {string} Escaped text. */ function escapeHtml(text) { if (!text) return ''; var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Select a guest from search results. * * @param {Object} guest Guest data. */ function selectGuest(guest) { // Set hidden guest ID. $guestIdInput.val(guest.id); // Populate guest fields (for display/fallback). $guestNameInput.val(guest.name).prop('readonly', true); $guestEmailInput.val(guest.email).prop('readonly', true); $guestPhoneInput.val(guest.phone).prop('readonly', true); // Update linked guest display. var infoHtml = '

'; infoHtml += ' '; infoHtml += '' + escapeHtml(guest.name) + ' '; infoHtml += 'View Guest Profile '; infoHtml += ''; infoHtml += '

'; if (guest.email) { infoHtml += '

' + escapeHtml(guest.email) + '

'; } $linkedGuestInfo.html(infoHtml).show(); $searchContainer.hide(); $fieldsContainer.hide(); $searchResults.hide().empty(); $searchInput.val(''); // Re-bind unlink button. bindUnlinkButton(); } /** * Unlink guest from booking. */ function unlinkGuest() { $guestIdInput.val(''); $guestNameInput.val('').prop('readonly', false); $guestEmailInput.val('').prop('readonly', false); $guestPhoneInput.val('').prop('readonly', false); $linkedGuestInfo.hide(); $searchContainer.show(); $fieldsContainer.show(); } /** * Bind unlink button event. */ function bindUnlinkButton() { $('#bnb-unlink-guest').off('click').on('click', function(e) { e.preventDefault(); unlinkGuest(); }); } // Search input with debounce. $searchInput.on('input', function() { if (searchTimer) { clearTimeout(searchTimer); } searchTimer = setTimeout(searchGuests, 300); }); // Select guest from results. $searchResults.on('click', '.bnb-select-guest', function(e) { e.preventDefault(); var guest = $(this).closest('.bnb-guest-search-item').data('guest'); if (guest) { selectGuest(guest); } }); // Initial unlink button binding. bindUnlinkButton(); // Close search results when clicking outside. $(document).on('click', function(e) { if (!$(e.target).closest('#bnb_booking_guest_search, #bnb-guest-search-results').length) { $searchResults.hide(); } }); } /** * Initialize service pricing type toggle. */ function initServicePricing() { var $pricingTypeInputs = $('input[name="bnb_service_pricing_type"]'); var $priceRow = $('#bnb-service-price-row'); var $priceSuffix = $('#bnb-service-price-suffix'); var $priceDescription = $('#bnb-service-price-description'); if (!$pricingTypeInputs.length) { return; } function updatePriceRowVisibility() { var pricingType = $('input[name="bnb_service_pricing_type"]:checked').val(); if (pricingType === 'included') { $priceRow.hide(); } else { $priceRow.show(); if (pricingType === 'per_night') { $priceSuffix.text(' / ' + (wpBnbAdmin.i18n.night || 'night')); $priceDescription.text(wpBnbAdmin.i18n.perNightDescription || 'This price will be charged per night of the stay.'); } else { $priceSuffix.text(''); $priceDescription.text(wpBnbAdmin.i18n.perBookingDescription || 'This price will be charged once for the booking.'); } } } $pricingTypeInputs.on('change', updatePriceRowVisibility); // Set initial visibility state on page load. updatePriceRowVisibility(); } /** * Initialize booking services selector. */ function initBookingServices() { var $servicesSelector = $('.bnb-services-selector'); var $servicesList = $servicesSelector.find('.bnb-services-list'); var $totalDisplay = $('#bnb-services-total-amount'); if (!$servicesSelector.length) { return; } /** * Get current number of nights from booking form. * * @return {number} Number of nights. */ function getNights() { var checkIn = $('#bnb_booking_check_in').val(); var checkOut = $('#bnb_booking_check_out').val(); if (checkIn && checkOut) { var startDate = new Date(checkIn); var endDate = new Date(checkOut); var nights = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)); return Math.max(1, nights); } return parseInt($servicesSelector.data('nights'), 10) || 1; } /** * Calculate service line total. * * @param {jQuery} $item Service item element. * @param {number} nights Number of nights. * @return {number} Calculated price. */ function calculateServiceTotal($item, nights) { var price = parseFloat($item.data('price')) || 0; var pricingType = $item.data('pricing-type'); var quantity = parseInt($item.find('.bnb-service-qty-input').val(), 10) || 1; if (pricingType === 'included') { return 0; } if (pricingType === 'per_night') { return price * quantity * nights; } return price * quantity; } /** * Update service line total display. * * @param {jQuery} $item Service item element. */ function updateServiceLineTotal($item) { var nights = getNights(); var total = calculateServiceTotal($item, nights); var $lineTotal = $item.find('.bnb-service-line-total'); var $totalValue = $item.find('.bnb-service-total-value'); var isSelected = $item.find('input[type="checkbox"]').is(':checked'); var pricingType = $item.data('pricing-type'); if (isSelected && pricingType !== 'included' && total > 0) { $totalValue.text(formatPrice(total)); $lineTotal.show(); } else { $lineTotal.hide(); } } /** * Update total services amount. */ function updateServicesTotal() { var nights = getNights(); var total = 0; $servicesList.find('.bnb-service-item').each(function() { var $item = $(this); var isSelected = $item.find('input[type="checkbox"]').is(':checked'); if (isSelected) { total += calculateServiceTotal($item, nights); } }); $totalDisplay.text(formatPrice(total)); } /** * Format price for display (simple formatting). * * @param {number} price Price value. * @return {string} Formatted price. */ function formatPrice(price) { return parseFloat(price).toFixed(2); } // Handle service checkbox change. $servicesList.on('change', 'input[type="checkbox"]', function() { var $item = $(this).closest('.bnb-service-item'); var isSelected = $(this).is(':checked'); $item.toggleClass('selected', isSelected); // Show/hide quantity input. var $quantity = $item.find('.bnb-service-quantity'); if ($quantity.length) { $quantity.toggle(isSelected); } updateServiceLineTotal($item); updateServicesTotal(); }); // Handle quantity change. $servicesList.on('change input', '.bnb-service-qty-input', function() { var $item = $(this).closest('.bnb-service-item'); var maxQty = parseInt($item.data('max-quantity'), 10) || 1; var value = parseInt($(this).val(), 10) || 1; // Enforce min/max. value = Math.max(1, Math.min(value, maxQty)); $(this).val(value); updateServiceLineTotal($item); updateServicesTotal(); }); // Update when booking dates change. $('#bnb_booking_check_in, #bnb_booking_check_out').on('change', function() { $servicesList.find('.bnb-service-item.selected').each(function() { updateServiceLineTotal($(this)); }); updateServicesTotal(); }); // Initial calculation. updateServicesTotal(); } /** * Initialize dashboard charts. */ function initDashboardCharts() { // Only run on dashboard page. if (!wpBnbAdmin.isDashboard || typeof Chart === 'undefined') { return; } var chartData = wpBnbAdmin.chartData; if (!chartData) { return; } // Chart.js default configuration. Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif'; Chart.defaults.font.size = 12; Chart.defaults.color = '#50575e'; // Initialize Occupancy Chart. var occupancyCtx = document.getElementById('wp-bnb-occupancy-chart'); if (occupancyCtx && chartData.occupancy) { new Chart(occupancyCtx, { type: 'line', data: { labels: chartData.occupancy.labels, datasets: [{ label: wpBnbAdmin.i18n.occupancy, data: chartData.occupancy.data, borderColor: '#2271b1', backgroundColor: 'rgba(34, 113, 177, 0.1)', fill: true, tension: 0.3, pointRadius: 3, pointHoverRadius: 5, pointBackgroundColor: '#2271b1', pointBorderColor: '#fff', pointBorderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { backgroundColor: '#1d2327', titleColor: '#fff', bodyColor: '#fff', padding: 12, displayColors: false, callbacks: { label: function(context) { return context.parsed.y.toFixed(1) + '%'; } } } }, scales: { y: { beginAtZero: true, max: 100, ticks: { callback: function(value) { return value + '%'; } }, grid: { color: 'rgba(0, 0, 0, 0.05)' } }, x: { grid: { display: false } } }, interaction: { intersect: false, mode: 'index' } } }); } // Initialize Revenue Chart. var revenueCtx = document.getElementById('wp-bnb-revenue-chart'); if (revenueCtx && chartData.revenue) { new Chart(revenueCtx, { type: 'bar', data: { labels: chartData.revenue.labels, datasets: [{ label: wpBnbAdmin.i18n.revenue, data: chartData.revenue.data, backgroundColor: 'rgba(0, 163, 42, 0.8)', borderColor: '#00a32a', borderWidth: 1, borderRadius: 4, barPercentage: 0.6 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { backgroundColor: '#1d2327', titleColor: '#fff', bodyColor: '#fff', padding: 12, displayColors: false, callbacks: { label: function(context) { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' }).format(context.parsed.y); } } } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF', maximumFractionDigits: 0 }).format(value); } }, grid: { color: 'rgba(0, 0, 0, 0.05)' } }, x: { grid: { display: false } } } } }); } } /** * Initialize reports page functionality. */ function initReportsPage() { var $periodSelect = $('.wp-bnb-period-select'); var $customDates = $('.wp-bnb-custom-dates'); if (!$periodSelect.length) { return; } // Toggle custom date fields based on period selection. $periodSelect.on('change', function() { if ($(this).val() === 'custom') { $customDates.show(); } else { $customDates.hide(); } }); } /** * Initialize WooCommerce sync button. */ function initWooCommerceSync() { var $syncBtn = $('.bnb-sync-rooms-btn'); if (!$syncBtn.length) { return; } $syncBtn.on('click', function(e) { e.preventDefault(); var $btn = $(this); var $status = $btn.parent().find('.sync-status'); $btn.prop('disabled', true); $btn.find('.dashicons').addClass('bnb-spin'); $status.text(wpBnbAdmin.i18n.syncing || 'Syncing...'); $.ajax({ url: wpBnbAdmin.ajaxUrl, type: 'POST', data: { action: 'wp_bnb_sync_all_rooms', nonce: wpBnbAdmin.nonce }, success: function(response) { if (response.success) { $status.html('' + response.data.message + ''); } else { var errorMsg = response.data && response.data.message ? response.data.message : 'Unknown error'; $status.html('' + errorMsg + ''); } }, error: function(xhr, status, error) { var errorMsg = wpBnbAdmin.i18n.error || 'Error occurred'; if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) { errorMsg = xhr.responseJSON.data.message; } else if (error) { errorMsg = error; } $status.html('' + errorMsg + ''); console.error('WP-BnB Sync Error:', status, error, xhr.responseText); }, complete: function() { $btn.prop('disabled', false); $btn.find('.dashicons').removeClass('bnb-spin'); } }); }); } // Initialize on document ready. $(document).ready(function() { initLicenseManagement(); initUpdateCheck(); initRoomGallery(); initPricingSettings(); initSeasonForm(); initPricingMetaBox(); initBookingForm(); initCalendarPage(); initGuestSearch(); initServicePricing(); initBookingServices(); initDashboardCharts(); initReportsPage(); initWooCommerceSync(); }); })(jQuery);