jQuery(document).ready(function ($) {
let page = 1;
let searchTimeout = null;
const $container = $('#pecb-events-container');
const $loadMore = $('#pecb-events-load-more');
const $filterForm = $('#pecb-events-filter');
const $searchInput = $('#pecb-events-search');
// Initialize delivery format from URL parameter or default to Classroom
const urlParams = new URLSearchParams(window.location.search);
let currentDeliveryFormat = urlParams.get('deliveryFormat') || 'Classroom';
let currentScheme = urlParams.get('scheme') || '';
let partnersData = {}; // Store partner data: { id: name }
let coursesData = {}; // Cache course data
let eventsCache = {}; // Cache events by filter key
let lastFilters = null; // Track filter changes
// Dynamic sticky filter position based on scroll direction
let lastScrollTop = 0;
const $filter = $('.events-api-container .filter');
$(window).on('scroll', function() {
const scrollTop = $(this).scrollTop();
// Prevent negative scrollTop values
if (scrollTop <= 0) {
lastScrollTop = scrollTop;
return;
}
if (scrollTop > lastScrollTop) {
// Scrolling down
$filter.css('top', '20px');
} else if (scrollTop < lastScrollTop) {
// Scrolling up
$filter.css('top', '130px');
}
lastScrollTop = scrollTop;
});
// Filter toggle for mobile (max-width 981px)
const $filterToggle = $('.events-api-container .filter-toggle');
const $filterContent = $('.events-api-container .filter');
$filterToggle.on('click', function() {
$(this).toggleClass('open');
$filterContent.toggleClass('open');
});
// SKELETON (CLEAN + MODERN) - Define early
const showSkeleton = (count = 6) => Array(count).fill(`
`).join('');
// Helper function to normalize website URLs
const normalizeWebsiteUrl = (url) => {
if (!url) return '';
url = url.trim();
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
return 'https://' + url;
};
// Country list for contact form
const countryOptions = `
`;
// Equalize heights of event card sections
const equalizeCardHeights = () => {
// Reset heights first
$('.pecb-event-card .title').css('height', 'auto');
$('.pecb-event-card .other > div:first-of-type').css('height', 'auto');
// Find max height for title sections
let maxTitleHeight = 0;
$('.pecb-event-card .title').each(function() {
const h = $(this).outerHeight();
if (h > maxTitleHeight) maxTitleHeight = h;
});
// Find max height for other first div sections
let maxOtherHeight = 0;
$('.pecb-event-card .other > div:first-of-type').each(function() {
const h = $(this).outerHeight();
if (h > maxOtherHeight) maxOtherHeight = h;
});
// Apply max heights
if (maxTitleHeight > 0) {
$('.pecb-event-card .title').css('height', maxTitleHeight + 'px');
}
if (maxOtherHeight > 0) {
$('.pecb-event-card .other > div:first-of-type').css('height', maxOtherHeight + 'px');
}
};
// Check if PHP rendered any cards
const hasPhpCards = $container.find('.pecb-event-card').length > 0;
// If no PHP cards, show skeleton while loading
if (!hasPhpCards) {
$container.html(showSkeleton(6));
}
// Initialize flatpickr for single range input if present
const $eventDate = $('#event-date');
let eventDatePicker = null;
if ($eventDate.length && typeof flatpickr !== 'undefined') {
eventDatePicker = $eventDate.flatpickr({
mode: 'range',
dateFormat: 'Y-m-d',
allowInput: true
});
}
// Initialize Select2 across form selects
const initSelect2 = () => {
if (typeof $.fn.select2 === 'undefined') {
return;
}
$filterForm.find('.pecb-select').each(function () {
const $select = $(this);
$select.select2({
width: '100%',
placeholder: $select.data('placeholder') || '',
allowClear: true,
});
// Apply URL parameter value immediately after Select2 init
const urlValue = $select.data('url-value');
if (urlValue) {
$select.val(urlValue).trigger('change.select2');
}
});
};
initSelect2();
// Refresh Select2 display helper
const refreshSelect2 = ($element) => {
if ($element && $element.length && $element.hasClass('pecb-select') && typeof $.fn.select2 !== 'undefined') {
$element.trigger('change.select2');
}
};
// UPDATE URL with current filters and modal ID
function updateURL(modalId = null) {
const params = new URLSearchParams();
// Get all form fields including partnerId
$filterForm.serializeArray().forEach(item => {
if (item.value) {
params.set(item.name, item.value);
}
});
// Add date range
const val = $eventDate.val();
if (val) {
if (val.includes(' to ')) {
const [start, end] = val.split(' to ').map(s => s.trim());
if (start && end) {
params.set('startDate', start);
params.set('endDate', end);
}
} else {
params.set('startDate', val);
params.set('endDate', val);
}
}
// Add delivery format if selected
if (currentDeliveryFormat) {
params.set('deliveryFormat', currentDeliveryFormat);
}
// Add search/scheme if entered
if (currentScheme) {
params.set('scheme', currentScheme);
}
// Add modal ID if provided
if (modalId) {
params.set('modal', modalId);
}
// Update URL without page reload
const newUrl = params.toString() ? `${window.location.pathname}?${params.toString()}` : window.location.pathname;
window.history.pushState({}, '', newUrl);
}
// OPEN MODAL by ID (for direct links)
function openModalById(eventId) {
// Try to get event data from DOM first
const $card = $(`.pecb-event-card[data-event-id="${eventId}"]`);
const cardData = $card.length ? $card.data('event') : null;
$modal.addClass('active');
// If we have data from DOM, show overview immediately then load full details
if (cardData) {
renderModalContent(cardData, true); // true = partial data (no description/partner details)
$modalContent.show();
$modalLoading.hide();
// Fetch full event details in background for description and partner info
loadEventDetails(eventId);
} else {
// Fetch full event data if not in DOM (e.g., direct URL access)
$modalContent.hide();
$modalLoading.show();
$.post(pecbEvents.ajax_url, {
action: 'pecb_get_event',
nonce: pecbEvents.nonce,
event_id: eventId
}, function (resp) {
if (resp && resp.success && resp.data) {
renderModalContent(resp.data, false);
$modalLoading.hide();
$modalContent.show();
} else {
$modalLoading.html('
`;
}
$partnerSection.html(partnerHtml);
}
// GET FILTERS - combines form filters, delivery format, and search
function getFilters() {
const filters = {};
// Get all form filters
$filterForm.serializeArray().forEach(item => {
if (!item.value || item.name === 'eventDate') return;
if (item.name === 'partnerId') {
// Send partnerId directly - backend will convert to partner name
filters.partnerId = parseInt(item.value);
} else if (item.name === 'course' || item.name === 'trainingEventStatusId') {
filters[item.name] = parseInt(item.value);
} else {
filters[item.name] = item.value;
}
});
// Read range from flatpickr value (or raw input fallback)
const val = $eventDate.val();
if (val) {
if (val.includes(' to ')) {
const [start, end] = val.split(' to ').map(s => s.trim());
if (start && end) {
filters.startDate = start;
filters.endDate = end;
}
} else {
filters.startDate = filters.endDate = val; // Single date
}
}
// Add delivery format and search if present
if (currentDeliveryFormat) filters.deliveryFormat = currentDeliveryFormat;
if (currentScheme) filters.scheme = currentScheme;
return filters;
}
function formatDate(dateStr) {
if (!dateStr) return '';
// Parse as UTC by appending 'Z' or using UTC format
const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr.replace(' ', 'T') + 'Z';
const dateObj = new Date(utcDateStr);
const day = dateObj.toLocaleDateString('en-US', { weekday: 'short', timeZone: 'UTC' });
const number = dateObj.toLocaleDateString('en-US', { day: '2-digit', timeZone: 'UTC' });
return `${day}${number}`;
}
// Format date string to UTC date and time (YYYY-MM-DD HH:mm UTC)
function formatDateUTC(dateStr) {
if (!dateStr) return 'N/A';
// Parse as UTC by appending 'Z' or using UTC format
const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr.replace(' ', 'T') + 'Z';
const dateObj = new Date(utcDateStr);
const date = dateObj.toLocaleDateString('en-CA', { timeZone: 'UTC' }); // YYYY-MM-DD format
const time = dateObj.toLocaleTimeString('en-GB', { timeZone: 'UTC', hour: '2-digit', minute: '2-digit', hour12: false });
return `${date} ${time} UTC`;
}
// Render event card HTML
const renderEventCard = (ev) => {
// Escape JSON for HTML attribute
const eventJson = JSON.stringify(ev).replace(/'/g, ''').replace(/"/g, '"');
return `
Thank you for the interest shown in interacting with our network. For security reasons, note that your message will be delivered to the recipient during the next 48 hours.