diff --git a/assets/css/hvac-searchable-selectors.css b/assets/css/hvac-searchable-selectors.css
new file mode 100644
index 00000000..4dababbd
--- /dev/null
+++ b/assets/css/hvac-searchable-selectors.css
@@ -0,0 +1,407 @@
+/**
+ * HVAC Searchable Selectors Styling
+ *
+ * Styles for dynamic multi-select organizers, categories, and single-select venue
+ * with autocomplete search and modal integration.
+ */
+
+/* Main selector container */
+.hvac-searchable-selector {
+ position: relative;
+ width: 100%;
+}
+
+/* Input wrapper with arrow */
+.selector-input-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ border: 2px solid #ddd;
+ border-radius: 4px;
+ background: #fff;
+ transition: border-color 0.3s ease;
+}
+
+.hvac-searchable-selector.dropdown-open .selector-input-wrapper {
+ border-color: #0274be;
+ box-shadow: 0 0 0 3px rgba(2, 116, 190, 0.1);
+}
+
+.selector-search-input {
+ flex: 1;
+ border: none;
+ padding: 12px 16px;
+ font-size: 16px;
+ outline: none;
+ background: transparent;
+}
+
+.selector-search-input::placeholder {
+ color: #999;
+}
+
+.selector-arrow {
+ padding: 0 12px;
+ color: #666;
+ cursor: pointer;
+ user-select: none;
+ transition: transform 0.3s ease;
+}
+
+.hvac-searchable-selector.dropdown-open .selector-arrow {
+ transform: rotate(180deg);
+}
+
+/* Selected items display */
+.selected-items {
+ margin-top: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ min-height: 20px;
+}
+
+.selected-item {
+ display: inline-flex;
+ align-items: center;
+ background: #0274be;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 16px;
+ font-size: 14px;
+ line-height: 1.2;
+}
+
+.selected-item-text {
+ margin-right: 6px;
+}
+
+.remove-item {
+ background: none;
+ border: none;
+ color: white;
+ font-size: 16px;
+ font-weight: bold;
+ cursor: pointer;
+ padding: 0;
+ width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: background-color 0.2s ease;
+}
+
+.remove-item:hover {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+/* Dropdown */
+.selector-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ background: white;
+ border: 2px solid #0274be;
+ border-top: none;
+ border-radius: 0 0 4px 4px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ max-height: 300px;
+ overflow: hidden;
+}
+
+.dropdown-content {
+ display: flex;
+ flex-direction: column;
+ max-height: 300px;
+}
+
+/* Loading spinner */
+.loading-spinner {
+ padding: 20px;
+ text-align: center;
+ color: #666;
+ font-style: italic;
+}
+
+/* No results message */
+.no-results {
+ padding: 20px;
+ text-align: center;
+ color: #666;
+ font-style: italic;
+}
+
+/* Dropdown items */
+.dropdown-items {
+ overflow-y: auto;
+ flex: 1;
+}
+
+.dropdown-item {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ border-bottom: 1px solid #f0f0f0;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.dropdown-item:hover {
+ background-color: #f8f9fa;
+}
+
+.dropdown-item.selected {
+ background-color: #e6f3fb;
+ color: #0274be;
+}
+
+.item-content {
+ flex: 1;
+}
+
+.item-title {
+ font-weight: 500;
+ margin-bottom: 2px;
+}
+
+.item-subtitle {
+ font-size: 14px;
+ color: #666;
+}
+
+.item-selected {
+ color: #0274be;
+ font-weight: bold;
+ margin-left: 8px;
+}
+
+/* Create new section */
+.create-new-section {
+ border-top: 1px solid #e0e0e0;
+ padding: 8px;
+}
+
+.create-new-btn {
+ width: 100%;
+ background: #f8f9fa;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 8px 12px;
+ color: #0274be;
+ font-size: 14px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ transition: all 0.2s ease;
+}
+
+.create-new-btn:hover {
+ background: #e9ecef;
+ border-color: #0274be;
+}
+
+.create-new-btn .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+.create-new-disabled {
+ padding: 8px 12px;
+ text-align: center;
+ color: #999;
+ font-size: 12px;
+ font-style: italic;
+ border-top: 1px solid #e0e0e0;
+}
+
+/* Hidden inputs container */
+.hidden-inputs {
+ display: none;
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .selector-search-input {
+ font-size: 16px; /* Prevent zoom on iOS */
+ }
+
+ .selected-items {
+ gap: 4px;
+ }
+
+ .selected-item {
+ font-size: 13px;
+ padding: 3px 6px;
+ }
+
+ .dropdown-item {
+ padding: 10px 12px;
+ }
+}
+
+/* Focus states for accessibility */
+.selector-search-input:focus {
+ outline: none;
+}
+
+.dropdown-item:focus {
+ outline: 2px solid #0274be;
+ outline-offset: -2px;
+}
+
+.create-new-btn:focus {
+ outline: 2px solid #0274be;
+ outline-offset: -2px;
+}
+
+.remove-item:focus {
+ outline: 2px solid white;
+ outline-offset: -2px;
+}
+
+/* Error states */
+.hvac-searchable-selector.error .selector-input-wrapper {
+ border-color: #d63638;
+}
+
+.hvac-searchable-selector.error .selector-input-wrapper:focus-within {
+ box-shadow: 0 0 0 3px rgba(214, 54, 56, 0.1);
+}
+
+/* Disabled state */
+.hvac-searchable-selector.disabled {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+/* Animation for dropdown */
+.selector-dropdown {
+ opacity: 0;
+ transform: translateY(-5px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+}
+
+.hvac-searchable-selector.dropdown-open .selector-dropdown {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+/* High contrast mode support */
+@media (prefers-contrast: high) {
+ .selector-input-wrapper {
+ border-width: 3px;
+ }
+
+ .dropdown-item {
+ border-bottom-width: 2px;
+ }
+
+ .selected-item {
+ border: 2px solid white;
+ }
+}
+
+/* Advanced Options Toggle */
+.hvac-advanced-options-toggle {
+ margin: 20px 0;
+ text-align: center;
+}
+
+.toggle-advanced-options {
+ background: #f8f9fa;
+ border: 2px solid #e0e0e0;
+ border-radius: 6px;
+ padding: 12px 20px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ transition: all 0.3s ease;
+}
+
+.toggle-advanced-options:hover {
+ background: #e9ecef;
+ border-color: #0274be;
+ color: #0274be;
+}
+
+.toggle-advanced-options .dashicons {
+ font-size: 16px;
+ transition: transform 0.3s ease;
+}
+
+.toggle-description {
+ display: block;
+ margin-top: 8px;
+ color: #666;
+ font-size: 12px;
+ font-style: italic;
+}
+
+/* Advanced fields */
+.advanced-field {
+ border-left: 4px solid #0274be;
+ padding-left: 16px;
+ margin-left: 8px;
+ background: #f8f9fa;
+ border-radius: 0 4px 4px 0;
+ position: relative;
+}
+
+.advanced-field::before {
+ content: "Advanced";
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ background: #0274be;
+ color: white;
+ font-size: 10px;
+ font-weight: bold;
+ padding: 2px 6px;
+ border-radius: 2px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+/* Override advanced field styling for better integration */
+.advanced-field .form-row {
+ background: transparent;
+ margin: 0;
+ padding: 0;
+}
+
+/* Focus states for advanced options toggle */
+.toggle-advanced-options:focus {
+ outline: 2px solid #0274be;
+ outline-offset: 2px;
+}
+
+/* Responsive adjustments for advanced options */
+@media (max-width: 768px) {
+ .toggle-advanced-options {
+ width: 100%;
+ justify-content: center;
+ padding: 10px 16px;
+ }
+
+ .advanced-field {
+ margin-left: 0;
+ border-left-width: 3px;
+ padding-left: 12px;
+ }
+
+ .advanced-field::before {
+ font-size: 9px;
+ padding: 1px 4px;
+ }
+}
\ No newline at end of file
diff --git a/assets/js/hvac-ai-assist.js b/assets/js/hvac-ai-assist.js
new file mode 100644
index 00000000..252445ef
--- /dev/null
+++ b/assets/js/hvac-ai-assist.js
@@ -0,0 +1,722 @@
+/**
+ * HVAC AI Assist JavaScript
+ *
+ * Handles AI-powered event population modal interface and form integration
+ *
+ * @package HVAC_Community_Events
+ * @since 3.2.0
+ */
+
+jQuery(document).ready(function($) {
+ 'use strict';
+
+ /**
+ * AI Assist functionality object
+ */
+ const HVACAIAssist = {
+ // Properties
+ modal: null,
+ isProcessing: false,
+ currentInput: '',
+ currentInputType: 'auto',
+
+ // Initialize
+ init: function() {
+ this.createModal();
+ this.bindEvents();
+ this.enableAIButton();
+ },
+
+ /**
+ * Create the AI modal interface
+ */
+ createModal: function() {
+ const modalHTML = `
+
+
+
+
+
+
+
Let AI help you create your event! Provide any of the following:
+
+ URL: EventBrite, Facebook Events, or any event webpage
+ Text: Copy/paste from emails, documents, or flyers
+ Description: Brief description with key event details
+
+
+
+
+
+
+
+
+
Analyzing your input...
+
+
Parsing content
+
Extracting details
+
Validating data
+
Preparing form
+
+
+
+
+
+
+
+
+
+ `;
+
+ $('body').append(modalHTML);
+ this.modal = $('#hvac-ai-modal');
+ },
+
+ /**
+ * Bind event handlers
+ */
+ bindEvents: function() {
+ const self = this;
+
+ // AI Assist button click
+ $(document).on('click', '#ai-assist-btn', function(e) {
+ e.preventDefault();
+ if (!$(this).prop('disabled')) {
+ self.openModal();
+ }
+ });
+
+ // Modal close handlers
+ $(document).on('click', '#ai-modal-close, #ai-modal-cancel, .modal-overlay', function() {
+ self.closeModal();
+ });
+
+ // Tab switching
+ $(document).on('click', '.tab-btn', function() {
+ const type = $(this).data('type');
+ self.switchTab(type);
+ });
+
+ // Process button
+ $(document).on('click', '#ai-process-btn', function() {
+ self.processInput();
+ });
+
+ // Apply button
+ $(document).on('click', '#ai-apply-btn', function() {
+ self.applyToForm();
+ });
+
+ // Input change handlers for validation
+ $(document).on('input', '#ai-input-auto, #ai-input-url, #ai-input-text, #ai-input-description', function() {
+ self.validateInput();
+ });
+
+ // ESC key handler
+ $(document).on('keyup', function(e) {
+ if (e.keyCode === 27 && self.modal.is(':visible')) {
+ self.closeModal();
+ }
+ });
+ },
+
+ /**
+ * Enable the AI Assist button (remove placeholder status)
+ */
+ enableAIButton: function() {
+ const $aiBtn = $('#ai-assist-btn');
+ $aiBtn.removeClass('placeholder-btn')
+ .prop('disabled', false)
+ .attr('title', 'AI-powered event creation assistant')
+ .text('AI Assist');
+ },
+
+ /**
+ * Open the AI modal
+ */
+ openModal: function() {
+ this.modal.fadeIn(300);
+ this.resetModal();
+ $('#ai-input-auto').focus();
+ },
+
+ /**
+ * Close the AI modal
+ */
+ closeModal: function() {
+ if (!this.isProcessing) {
+ this.modal.fadeOut(300);
+ this.resetModal();
+ }
+ },
+
+ /**
+ * Reset modal to initial state
+ */
+ resetModal: function() {
+ // Reset tabs
+ $('.tab-btn').removeClass('active');
+ $('.tab-btn[data-type="auto"]').addClass('active');
+ $('.input-tab-content').removeClass('active');
+ $('.input-tab-content[data-type="auto"]').addClass('active');
+
+ // Clear inputs
+ $('#ai-input-auto, #ai-input-url, #ai-input-text, #ai-input-description').val('');
+
+ // Hide sections
+ $('#ai-processing, #ai-results').hide();
+ $('.ai-input-section').show();
+
+ // Reset buttons
+ $('#ai-process-btn').show().prop('disabled', true);
+ $('#ai-apply-btn').hide();
+
+ // Reset progress steps
+ $('.progress-steps .step').removeClass('active');
+ $('.status-message').text('Analyzing your input...');
+
+ // Reset confidence indicator
+ $('.confidence-fill').css('width', '0%');
+ $('.confidence-percent').text('0%');
+ $('.confidence-bar').removeClass('confidence-low confidence-medium confidence-high');
+
+ // Clear results content
+ $('.result-fields').html('');
+ $('.warning-list').html('');
+ $('.result-warnings').hide();
+
+ // Clear stored data
+ this.extractedData = null;
+
+ // Reset properties
+ this.currentInput = '';
+ this.currentInputType = 'auto';
+ this.isProcessing = false;
+ },
+
+ /**
+ * Switch input tabs
+ */
+ switchTab: function(type) {
+ $('.tab-btn').removeClass('active');
+ $(`.tab-btn[data-type="${type}"]`).addClass('active');
+
+ $('.input-tab-content').removeClass('active');
+ $(`.input-tab-content[data-type="${type}"]`).addClass('active');
+
+ this.currentInputType = type;
+
+ // Focus on the input field
+ $(`#ai-input-${type}`).focus();
+
+ this.validateInput();
+ },
+
+ /**
+ * Validate current input
+ */
+ validateInput: function() {
+ const input = this.getCurrentInput();
+ const $processBtn = $('#ai-process-btn');
+
+ if (input.length >= 10) {
+ $processBtn.prop('disabled', false);
+ } else {
+ $processBtn.prop('disabled', true);
+ }
+ },
+
+ /**
+ * Get current input value
+ */
+ getCurrentInput: function() {
+ const activeTab = $('.input-tab-content.active');
+ const inputElement = activeTab.find('input, textarea');
+ return inputElement.val().trim();
+ },
+
+ /**
+ * Process input through AI
+ */
+ processInput: function() {
+ const input = this.getCurrentInput();
+
+ if (input.length < 10) {
+ this.showError('Please provide at least 10 characters of event information.');
+ return;
+ }
+
+ this.isProcessing = true;
+ this.currentInput = input;
+
+ // Show processing UI
+ $('.ai-input-section').hide();
+ $('#ai-processing').show();
+ $('#ai-process-btn').hide();
+
+ // Start progress animation
+ this.animateProgress();
+
+ // Make AJAX request
+ this.makeAIRequest(input, this.currentInputType);
+ },
+
+ /**
+ * Animate processing steps
+ */
+ animateProgress: function() {
+ const isUrl = this.currentInputType === 'url';
+ const steps = isUrl ? [
+ { step: 1, message: 'Fetching webpage content...', delay: 500 },
+ { step: 2, message: 'Processing webpage data (this may take up to 40 seconds)...', delay: 3000 },
+ { step: 3, message: 'Extracting event details with AI...', delay: 15000 },
+ { step: 4, message: 'Preparing form data...', delay: 25000 }
+ ] : [
+ { step: 1, message: 'Analyzing your input...', delay: 500 },
+ { step: 2, message: 'Extracting event details...', delay: 2000 },
+ { step: 3, message: 'Validating information...', delay: 4000 },
+ { step: 4, message: 'Preparing form data...', delay: 6000 }
+ ];
+
+ steps.forEach(({ step, message, delay }) => {
+ setTimeout(() => {
+ if (this.isProcessing) {
+ $(`.progress-steps .step[data-step="${step}"]`).addClass('active');
+ $('.status-message').text(message);
+ }
+ }, delay);
+ });
+ },
+
+ /**
+ * Make AJAX request to AI endpoint
+ */
+ makeAIRequest: function(input, inputType) {
+ const self = this;
+
+ const requestData = {
+ action: 'hvac_ai_populate_event',
+ input: input,
+ input_type: inputType,
+ nonce: hvacAjaxVars.nonce // Assuming nonce is available
+ };
+
+ $.ajax({
+ url: hvacAjaxVars.ajaxUrl,
+ type: 'POST',
+ data: requestData,
+ timeout: inputType === 'url' ? 60000 : 35000, // 60 seconds for URLs, 35 for text
+ success: function(response) {
+ self.handleAISuccess(response);
+ },
+ error: function(xhr, status, error) {
+ self.handleAIError(xhr, status, error);
+ }
+ });
+ },
+
+ /**
+ * Handle successful AI response
+ */
+ handleAISuccess: function(response) {
+ this.isProcessing = false;
+
+ if (response.success && response.data && response.data.event_data) {
+ this.displayResults(response.data.event_data);
+ } else {
+ const message = response.data && response.data.message
+ ? response.data.message
+ : 'Unexpected response format from AI service.';
+ this.showError(message);
+ }
+ },
+
+ /**
+ * Handle AI request error
+ */
+ handleAIError: function(xhr, status, error) {
+ this.isProcessing = false;
+
+ let message = 'AI service temporarily unavailable. Please try again later.';
+
+ if (status === 'timeout') {
+ message = 'Request timed out. The AI might be processing a complex input. Please try with simpler content.';
+ } else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
+ message = xhr.responseJSON.data.message;
+ }
+
+ this.showError(message);
+ },
+
+ /**
+ * Display AI extraction results
+ */
+ displayResults: function(eventData) {
+ $('#ai-processing').hide();
+ $('#ai-results').show();
+
+ // Update confidence indicator
+ const confidence = eventData.confidence && eventData.confidence.overall
+ ? eventData.confidence.overall
+ : 0;
+ const confidencePercent = Math.round(confidence * 100);
+
+ $('.confidence-fill').css('width', confidencePercent + '%');
+ $('.confidence-percent').text(confidencePercent + '%');
+
+ // Color code confidence
+ let confidenceClass = 'confidence-low';
+ if (confidencePercent >= 80) confidenceClass = 'confidence-high';
+ else if (confidencePercent >= 60) confidenceClass = 'confidence-medium';
+
+ $('.confidence-bar').removeClass('confidence-low confidence-medium confidence-high')
+ .addClass(confidenceClass);
+
+ // Display extracted fields
+ this.displayExtractedFields(eventData);
+
+ // Check for warnings
+ this.checkAndDisplayWarnings(eventData);
+
+ // Show apply button
+ $('#ai-apply-btn').show();
+
+ // Store data for form application
+ this.extractedData = eventData;
+ },
+
+ /**
+ * Display extracted fields summary
+ */
+ displayExtractedFields: function(eventData) {
+ const fieldsHtml = [];
+
+ // Title
+ if (eventData.title) {
+ fieldsHtml.push(`
+ Title:
+ ${this.escapeHtml(eventData.title)}
+
`);
+ }
+
+ // Description (truncated for display)
+ if (eventData.description) {
+ let descriptionPreview = eventData.description;
+ // Truncate if too long for modal display
+ if (descriptionPreview.length > 200) {
+ descriptionPreview = descriptionPreview.substring(0, 200) + '...';
+ }
+ fieldsHtml.push(`
+ Description:
+ ${this.escapeHtml(descriptionPreview)}
+
`);
+ }
+
+ // Date and time
+ if (eventData.start_date) {
+ let dateDisplay = eventData.start_date;
+ if (eventData.start_time) {
+ dateDisplay += ` at ${eventData.start_time}`;
+ }
+ fieldsHtml.push(`
+ Start:
+ ${this.escapeHtml(dateDisplay)}
+
`);
+ }
+
+ if (eventData.end_date) {
+ let dateDisplay = eventData.end_date;
+ if (eventData.end_time) {
+ dateDisplay += ` at ${eventData.end_time}`;
+ }
+ fieldsHtml.push(`
+ End:
+ ${this.escapeHtml(dateDisplay)}
+
`);
+ }
+
+ // Venue
+ if (eventData.venue_name) {
+ let venueDisplay = eventData.venue_name;
+ if (eventData.venue_address) {
+ venueDisplay += ` (${eventData.venue_address})`;
+ }
+ fieldsHtml.push(`
+ Venue:
+ ${this.escapeHtml(venueDisplay)}
+
`);
+ }
+
+ // Cost
+ if (eventData.cost !== null && eventData.cost !== undefined) {
+ fieldsHtml.push(`
+ Cost:
+ $${eventData.cost}
+
`);
+ }
+
+ $('.result-fields').html(fieldsHtml.join(''));
+ },
+
+ /**
+ * Check for and display warnings
+ */
+ checkAndDisplayWarnings: function(eventData) {
+ const warnings = [];
+
+ // Check confidence levels
+ if (eventData.confidence && eventData.confidence.per_field) {
+ Object.entries(eventData.confidence.per_field).forEach(([field, confidence]) => {
+ if (confidence < 0.7) {
+ warnings.push(`${field} information may need review (${Math.round(confidence * 100)}% confidence)`);
+ }
+ });
+ }
+
+ // Check for missing critical fields
+ if (!eventData.title) warnings.push('Event title not found');
+ if (!eventData.start_date) warnings.push('Event date not found');
+
+ if (warnings.length > 0) {
+ const warningsHtml = warnings.map(warning => `${this.escapeHtml(warning)} `).join('');
+ $('.warning-list').html(warningsHtml);
+ $('.result-warnings').show();
+ }
+ },
+
+ /**
+ * Apply extracted data to the event form
+ */
+ applyToForm: function() {
+ if (!this.extractedData) return;
+
+ const data = this.extractedData;
+
+ try {
+ // Apply title
+ if (data.title) {
+ $('#event_title, [name="event_title"]').val(data.title);
+ }
+
+ // Apply description (handle TinyMCE, regular textarea, and rich text editor)
+ if (data.description) {
+ // Try TinyMCE first if available
+ if (typeof tinyMCE !== 'undefined' && tinyMCE.get('event_description')) {
+ tinyMCE.get('event_description').setContent(data.description);
+ } else {
+ // Update the hidden textarea
+ $('#event_description, [name="event_description"]').val(data.description);
+
+ // Also update the visible rich text editor div if it exists
+ const $richEditor = $('#event-description-editor');
+ if ($richEditor.length && $richEditor.is('[contenteditable]')) {
+ $richEditor.html(data.description);
+ }
+ }
+ }
+
+ // Apply start date and time (combine into datetime-local format)
+ if (data.start_date) {
+ let startDateTime = data.start_date;
+ if (data.start_time) {
+ startDateTime += 'T' + data.start_time;
+ } else {
+ // Default to 9:00 AM if no time specified
+ startDateTime += 'T09:00';
+ }
+ $('#event_start_datetime, [name="event_start_datetime"]').val(startDateTime);
+ }
+
+ // Apply end date and time (combine into datetime-local format)
+ if (data.end_date) {
+ let endDateTime = data.end_date;
+ if (data.end_time) {
+ endDateTime += 'T' + data.end_time;
+ } else {
+ // Default to 5:00 PM if no time specified
+ endDateTime += 'T17:00';
+ }
+ $('#event_end_datetime, [name="event_end_datetime"]').val(endDateTime);
+ }
+
+ // Apply cost
+ if (data.cost !== null && data.cost !== undefined) {
+ $('#event_cost, [name="event_cost"]').val(data.cost);
+ }
+
+ // Apply capacity
+ if (data.capacity) {
+ $('#event_capacity, [name="event_capacity"]').val(data.capacity);
+ }
+
+ // Apply URL
+ if (data.url) {
+ $('#event_url, [name="event_url"]').val(data.url);
+ }
+
+ // Apply venue fields (flatter structure)
+ if (data.venue_name) {
+ $('#venue_name, [name="venue_name"]').val(data.venue_name);
+ }
+ if (data.venue_address) {
+ $('#venue_address, [name="venue_address"]').val(data.venue_address);
+ }
+ if (data.venue_city) {
+ $('#venue_city, [name="venue_city"]').val(data.venue_city);
+ }
+ if (data.venue_state) {
+ $('#venue_state, [name="venue_state"]').val(data.venue_state);
+ }
+ if (data.venue_zip) {
+ $('#venue_zip, [name="venue_zip"]').val(data.venue_zip);
+ }
+
+ // Apply organizer fields (flatter structure)
+ if (data.organizer_name) {
+ $('#organizer_name, [name="organizer_name"]').val(data.organizer_name);
+ }
+ if (data.organizer_email) {
+ $('#organizer_email, [name="organizer_email"]').val(data.organizer_email);
+ }
+ if (data.organizer_phone) {
+ $('#organizer_phone, [name="organizer_phone"]').val(data.organizer_phone);
+ }
+
+ // Apply website URL
+ if (data.website) {
+ $('#website, [name="website"]').val(data.website);
+ }
+
+ // Apply event URL
+ if (data.event_url) {
+ $('#event_url, [name="event_url"]').val(data.event_url);
+ }
+
+ // Apply timezone (if provided)
+ if (data.timezone) {
+ $('#event_timezone, [name="event_timezone"]').val(data.timezone);
+ }
+
+ // Apply event image (only if >= 200x200px)
+ if (data.event_image_url) {
+ $('#event_image_url, [name="event_image_url"]').val(data.event_image_url);
+ }
+
+ // Trigger autosave if available
+ if (typeof performAutoSave === 'function') {
+ setTimeout(performAutoSave, 1000);
+ }
+
+ // Close modal and show success message
+ this.closeModal();
+ this.showSuccess('Event information applied successfully! Please review and adjust as needed before submitting.');
+
+ } catch (error) {
+ console.error('Error applying AI data to form:', error);
+ this.showError('Error applying data to form. Please try filling the fields manually.');
+ }
+ },
+
+ /**
+ * Show error message
+ */
+ showError: function(message) {
+ // Reset processing UI
+ $('#ai-processing').hide();
+ $('.ai-input-section').show();
+ $('#ai-process-btn').show();
+ this.isProcessing = false;
+
+ // Show error in modal or as alert
+ alert('Error: ' + message);
+ },
+
+ /**
+ * Show success message
+ */
+ showSuccess: function(message) {
+ // You might want to show this in a nicer way, like a toast notification
+ alert(message);
+ },
+
+ /**
+ * Escape HTML for safe display
+ */
+ escapeHtml: function(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+ };
+
+ // Initialize when document is ready
+ HVACAIAssist.init();
+});
\ No newline at end of file
diff --git a/assets/js/hvac-searchable-selectors.js b/assets/js/hvac-searchable-selectors.js
new file mode 100644
index 00000000..09404b2c
--- /dev/null
+++ b/assets/js/hvac-searchable-selectors.js
@@ -0,0 +1,304 @@
+/**
+ * HVAC Searchable Selectors
+ *
+ * Handles dynamic multi-select organizers, categories, and single-select venue
+ * with autocomplete search, "Add New" modal integration, and role-based permissions.
+ */
+
+(function($) {
+ 'use strict';
+
+ class HVACSearchableSelector {
+ constructor(element) {
+ this.$element = $(element);
+ this.type = this.$element.data('type');
+ this.maxSelections = this.$element.data('max-selections') || 1;
+ this.selectedItems = [];
+
+ this.init();
+ }
+
+ init() {
+ this.bindEvents();
+ this.loadInitialData();
+ }
+
+ bindEvents() {
+ const $input = this.$element.find('.selector-search-input');
+ const $dropdown = this.$element.find('.selector-dropdown');
+
+ // Input focus/blur events
+ $input.on('focus', () => this.showDropdown());
+ $input.on('blur', (e) => {
+ // Delay hiding to allow clicking on dropdown items
+ setTimeout(() => {
+ if (!this.$element.find(':hover').length) {
+ this.hideDropdown();
+ }
+ }, 150);
+ });
+
+ // Search input
+ $input.on('input', (e) => this.handleSearch(e.target.value));
+
+ // Arrow click
+ this.$element.find('.selector-arrow').on('click', () => {
+ if ($dropdown.is(':visible')) {
+ this.hideDropdown();
+ } else {
+ $input.focus();
+ }
+ });
+
+ // Create new button
+ this.$element.find('.create-new-btn').on('click', (e) => {
+ e.preventDefault();
+ this.showCreateModal();
+ });
+
+ // Document click to close dropdown
+ $(document).on('click', (e) => {
+ if (!this.$element.has(e.target).length) {
+ this.hideDropdown();
+ }
+ });
+ }
+
+ async loadInitialData() {
+ try {
+ this.showLoading();
+ const data = await this.fetchData();
+ this.renderDropdownItems(data);
+ } catch (error) {
+ console.error(`Error loading ${this.type} data:`, error);
+ this.showError('Failed to load data');
+ } finally {
+ this.hideLoading();
+ }
+ }
+
+ async handleSearch(query) {
+ if (query.length < 2) {
+ await this.loadInitialData();
+ return;
+ }
+
+ try {
+ this.showLoading();
+ const data = await this.fetchData(query);
+ this.renderDropdownItems(data);
+ } catch (error) {
+ console.error(`Error searching ${this.type}:`, error);
+ this.showError('Search failed');
+ } finally {
+ this.hideLoading();
+ }
+ }
+
+ async fetchData(search = '') {
+ const params = new URLSearchParams({
+ action: `hvac_search_${this.type}s`,
+ nonce: hvacSelectors.nonce,
+ search: search
+ });
+
+ const response = await fetch(hvacSelectors.ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: params
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+ if (!result.success) {
+ throw new Error(result.data || 'Request failed');
+ }
+
+ return result.data;
+ }
+
+ renderDropdownItems(items) {
+ const $container = this.$element.find('.dropdown-items');
+ $container.empty();
+
+ if (!items || items.length === 0) {
+ this.showNoResults();
+ return;
+ }
+
+ this.hideNoResults();
+
+ items.forEach(item => {
+ const isSelected = this.selectedItems.some(selected => selected.id === item.id);
+ const $item = $(`
+
+
+
${this.escapeHtml(item.title)}
+ ${item.subtitle ? `
${this.escapeHtml(item.subtitle)}
` : ''}
+
+ ${isSelected ? '
✓ ' : ''}
+
+ `);
+
+ $item.on('click', () => this.selectItem(item));
+ $container.append($item);
+ });
+ }
+
+ selectItem(item) {
+ // Check if already selected
+ if (this.selectedItems.some(selected => selected.id === item.id)) {
+ return;
+ }
+
+ // Check selection limit
+ if (this.selectedItems.length >= this.maxSelections) {
+ alert(`You can only select up to ${this.maxSelections} ${this.type}(s).`);
+ return;
+ }
+
+ // Add to selected items
+ this.selectedItems.push(item);
+ this.renderSelectedItems();
+ this.updateHiddenInputs();
+ this.hideDropdown();
+ this.clearSearch();
+
+ // Mark item as selected in dropdown
+ this.$element.find(`.dropdown-item[data-id="${item.id}"]`).addClass('selected').append('✓ ');
+ }
+
+ removeItem(itemId) {
+ this.selectedItems = this.selectedItems.filter(item => item.id !== itemId);
+ this.renderSelectedItems();
+ this.updateHiddenInputs();
+
+ // Unmark item in dropdown
+ this.$element.find(`.dropdown-item[data-id="${itemId}"]`).removeClass('selected').find('.item-selected').remove();
+ }
+
+ renderSelectedItems() {
+ const $container = this.$element.find('.selected-items');
+ $container.empty();
+
+ this.selectedItems.forEach(item => {
+ const $selectedItem = $(`
+
+ ${this.escapeHtml(item.title)}
+ ×
+
+ `);
+
+ $selectedItem.find('.remove-item').on('click', () => this.removeItem(item.id));
+ $container.append($selectedItem);
+ });
+ }
+
+ updateHiddenInputs() {
+ const $container = this.$element.find('.hidden-inputs');
+ $container.empty();
+
+ this.selectedItems.forEach((item, index) => {
+ const $input = $(` `);
+ $container.append($input);
+ });
+ }
+
+ showDropdown() {
+ this.$element.find('.selector-dropdown').show();
+ this.$element.addClass('dropdown-open');
+ }
+
+ hideDropdown() {
+ this.$element.find('.selector-dropdown').hide();
+ this.$element.removeClass('dropdown-open');
+ }
+
+ clearSearch() {
+ this.$element.find('.selector-search-input').val('');
+ }
+
+ showLoading() {
+ this.$element.find('.loading-spinner').show();
+ this.$element.find('.dropdown-items, .no-results').hide();
+ }
+
+ hideLoading() {
+ this.$element.find('.loading-spinner').hide();
+ this.$element.find('.dropdown-items').show();
+ }
+
+ showNoResults() {
+ this.$element.find('.no-results').show();
+ this.$element.find('.dropdown-items').hide();
+ }
+
+ hideNoResults() {
+ this.$element.find('.no-results').hide();
+ }
+
+ showError(message) {
+ this.$element.find('.no-results').text(message).show();
+ }
+
+ showCreateModal() {
+ // Check permissions
+ if (!this.$element.find('.create-new-btn').length) {
+ return;
+ }
+
+ // Trigger create modal event
+ $(document).trigger('hvac:create-new-modal', {
+ type: this.type,
+ callback: (newItem) => {
+ if (newItem) {
+ this.selectItem(newItem);
+ this.loadInitialData(); // Refresh the list
+ }
+ }
+ });
+ }
+
+ escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+ }
+
+ // Advanced Options Toggle Function
+ window.hvacToggleAdvancedOptions = function() {
+ const $toggle = $('.toggle-advanced-options');
+ const $icon = $toggle.find('.toggle-icon');
+ const $text = $toggle.find('.toggle-text');
+ const $advancedFields = $('.advanced-field');
+
+ if ($advancedFields.is(':visible')) {
+ // Hide advanced fields
+ $advancedFields.slideUp(300);
+ $icon.removeClass('dashicons-arrow-up-alt2').addClass('dashicons-arrow-down-alt2');
+ $text.text('Show Advanced Options');
+ } else {
+ // Show advanced fields
+ $advancedFields.slideDown(300);
+ $icon.removeClass('dashicons-arrow-down-alt2').addClass('dashicons-arrow-up-alt2');
+ $text.text('Hide Advanced Options');
+ }
+ };
+
+ // Initialize searchable selectors when document is ready
+ $(document).ready(function() {
+ $('.hvac-searchable-selector').each(function() {
+ new HVACSearchableSelector(this);
+ });
+
+ // Hide advanced fields by default
+ $('.advanced-field').hide();
+ });
+
+})(jQuery);
\ No newline at end of file
diff --git a/includes/class-hvac-event-form-builder.php b/includes/class-hvac-event-form-builder.php
index 2e5b51f7..e61fe74a 100644
--- a/includes/class-hvac-event-form-builder.php
+++ b/includes/class-hvac-event-form-builder.php
@@ -348,13 +348,7 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
'wrapper_class' => 'form-row event-description-field'
];
- // Original description field for fallback
- $description_field_fallback = array_merge($this->event_field_defaults['event-description'], [
- 'name' => 'event_description',
- 'label' => 'Event Description',
- 'placeholder' => 'Describe your event...',
- 'rows' => 6,
- ]);
+ // Description field uses rich text editor above
$this->add_field($title_field);
$this->add_field($description_field);
@@ -421,20 +415,16 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
* Add venue selection and management fields
*/
public function add_venue_fields(): self {
- // Simplified venue selector using regular select field
- $venue_field = array_merge($this->event_field_defaults['venue-select'], [
+ // Dynamic single-select venue selector with autocomplete and modal creation
+ $venue_field = [
+ 'type' => 'custom',
'name' => 'event_venue',
- 'label' => 'Venue',
- 'options' => $this->get_venue_options(),
+ 'custom_html' => $this->render_searchable_venue_selector(),
'wrapper_class' => 'form-row venue-field',
- 'description' => 'Select an existing venue or create a new one'
- ]);
+ ];
$this->add_field($venue_field);
- // Add venue creation fields (initially hidden)
- $this->add_venue_creation_fields();
-
return $this;
}
@@ -442,20 +432,16 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
* Add organizer selection and management fields
*/
public function add_organizer_fields(): self {
- // Simplified organizer selector using regular select field
- $organizer_field = array_merge($this->event_field_defaults['organizer-select'], [
+ // Dynamic multi-select organizer selector with autocomplete
+ $organizer_field = [
+ 'type' => 'custom',
'name' => 'event_organizer',
- 'label' => 'Organizer',
- 'options' => $this->get_organizer_options(),
+ 'custom_html' => $this->render_searchable_organizer_selector(),
'wrapper_class' => 'form-row organizer-field',
- 'description' => 'Select an existing organizer or create a new one'
- ]);
+ ];
$this->add_field($organizer_field);
- // Add organizer creation fields (initially hidden)
- $this->add_organizer_creation_fields();
-
return $this;
}
@@ -463,42 +449,12 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
* Add categories field with multi-select search functionality
*/
public function add_categories_fields(): self {
- // Get event categories from TEC taxonomy
- $category_options = ['0' => '-- Select Category --'];
-
- // Get TEC event categories
- $categories = get_terms([
- 'taxonomy' => 'tribe_events_cat',
- 'hide_empty' => false,
- 'orderby' => 'name',
- 'order' => 'ASC'
- ]);
-
- if (!is_wp_error($categories) && !empty($categories)) {
- foreach ($categories as $category) {
- $category_options[$category->term_id] = $category->name;
- }
- }
-
- // Add default categories if none exist
- if (count($category_options) === 1) {
- $category_options['general'] = 'General';
- $category_options['training'] = 'Training';
- $category_options['workshop'] = 'Workshop';
- $category_options['certification'] = 'Certification';
- }
-
- // Simplified categories selector using regular select field
+ // Dynamic multi-select category selector with autocomplete (limited for trainers)
$categories_field = [
- 'type' => 'select',
+ 'type' => 'custom',
'name' => 'event_categories',
- 'label' => 'Category',
- 'options' => $category_options,
+ 'custom_html' => $this->render_searchable_category_selector(),
'wrapper_class' => 'form-row categories-field',
- 'description' => 'Select an event category',
- 'class' => 'hvac-categories-select',
- 'validate' => [],
- 'sanitize' => 'int',
];
$this->add_field($categories_field);
@@ -1229,17 +1185,11 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
return $field['custom_html'];
}
- // Fallback for custom fields without custom_html
- $output = sprintf('', esc_attr($field['wrapper_class'] ?? 'form-row'));
+ // Error: custom fields must have custom_html defined
+ error_log("HVAC Event Form Builder: Custom field '{$field['name']}' missing required custom_html property");
- if (isset($field['label'])) {
- $output .= sprintf('
%s ', esc_html($field['label']));
- }
-
- $output .= sprintf('
Custom field "%s" missing custom_html
', esc_html($field['name']));
- $output .= '
';
-
- return $output;
+ // Return empty string to avoid breaking the form
+ return '';
}
/**
@@ -1504,4 +1454,167 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
return $sanitized;
}
+
+ /**
+ * Render searchable organizer selector with multi-select and "Add New" functionality
+ */
+ private function render_searchable_organizer_selector(): string {
+ $current_user = wp_get_current_user();
+ $can_create = in_array('hvac_trainer', $current_user->roles) || in_array('hvac_master_trainer', $current_user->roles);
+
+ return <<
+ Organizers *
+
+
+
+
+
+
+
+
+
+
Loading...
+
No organizers found
+
+
+
+ {$this->render_create_new_button('organizer', $can_create)}
+
+
+
+
+
+
+
+
+ Select up to 3 organizers for this event. You can search by name or email.
+
+HTML;
+ }
+
+ /**
+ * Render searchable category selector with multi-select (no create for trainers)
+ */
+ private function render_searchable_category_selector(): string {
+ $current_user = wp_get_current_user();
+ $can_create = in_array('hvac_master_trainer', $current_user->roles); // Only master trainers can create categories
+
+ return <<
+ Categories
+
+
+
+
+
+
+
+
+
+
Loading...
+
No categories found
+
+
+
+ {$this->render_create_new_button('category', $can_create)}
+
+
+
+
+
+
+
+
+ Select up to 3 categories for this event.
+
+HTML;
+ }
+
+ /**
+ * Render searchable venue selector with single-select and "Add New" functionality
+ */
+ private function render_searchable_venue_selector(): string {
+ $current_user = wp_get_current_user();
+ $can_create = in_array('hvac_trainer', $current_user->roles) || in_array('hvac_master_trainer', $current_user->roles);
+
+ return <<
+ Venue *
+
+
+
+
+
+
+
+
+
+
Loading...
+
No venues found
+
+
+
+ {$this->render_create_new_button('venue', $can_create)}
+
+
+
+
+
+
+
+
+ Select a venue for this event. You can search by name or address.
+
+HTML;
+ }
+
+ /**
+ * Render "Create New" button with role-based permissions
+ */
+ private function render_create_new_button(string $type, bool $can_create): string {
+ if (!$can_create) {
+ if ($type === 'category') {
+ return 'Only Master Trainers can create new categories
';
+ }
+ return '';
+ }
+
+ $label = ucfirst($type);
+ return <<
+
+
+ Add New {$label}
+
+
+HTML;
+ }
}
\ No newline at end of file