Files
Hi-School-Slide-Manager/assets/js/dashboard.js
T
stack_admn 9bb2171b92 audit clean-up
add 'no dates' logic
2026-02-22 19:05:49 -08:00

1479 lines
66 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Hi-School Slide Manager Dashboard JavaScript
*
* Handles AJAX interactions, form management, and dynamic UI updates
*
* @package Hi_School_Slide_Manager
* @since 1.0.0
*/
(function($) {
'use strict';
// Dashboard object
const HSSMDashboard = {
formCounter: 0,
maxForms: 5,
currentTab: 'create-slides',
// Manage slides state
currentPage: 1,
perPage: 25,
totalSlides: 0,
hasMore: false,
filters: {
status: '',
site: '',
client: ''
},
// Edit mode state
isEditMode: false,
editingSlideId: null,
currentSlides: null,
// Initialize the dashboard
init: function() {
this.bindEvents();
this.initializeTabs();
this.addInitialSlideForm();
this.loadExistingSlides();
// Initialize site data
window.hssmSites = window.hssmSites || [];
},
// Bind event handlers
bindEvents: function() {
// Tab navigation - use direct binding for reliability
setTimeout(() => {
$('.hssm-nav-tabs a').off('click.hssm').on('click.hssm', this.handleTabClick.bind(this));
}, 100);
// Form management
$(document).on('click', '#add-slide-form', this.addSlideForm.bind(this));
$(document).on('click', '.hssm-remove-form', this.removeSlideForm.bind(this));
$(document).on('click', '#clear-all-forms', this.clearAllForms.bind(this));
// Image upload
$(document).on('click', '.hssm-upload-btn', this.triggerImageUpload.bind(this));
$(document).on('click', '.hssm-image-preview-small', this.triggerImageUpload.bind(this));
$(document).on('click', '.hssm-remove-image', this.removeImage.bind(this));
// Button management
$(document).on('click', '.hssm-add-button', this.addButtonRow.bind(this));
$(document).on('click', '.hssm-remove-button', this.removeButtonRow.bind(this));
// Slide actions
$(document).on('click', '.hssm-save-slide', this.saveSlide.bind(this));
$(document).on('click', '#save-all-slides', this.saveAllSlides.bind(this));
$(document).on('click', '.hssm-preview-slide', this.previewSlide.bind(this));
$(document).on('click', '.hssm-schedule-slide', this.scheduleSlide.bind(this));
$(document).on('click', '.hssm-publish-slide', this.publishSlide.bind(this));
// Clear field errors when user starts typing/selecting
$(document).on('input change', '.hssm-input, .hssm-textarea, .hssm-select', function() {
const $field = $(this);
const $fieldContainer = $field.closest('.hssm-field');
$fieldContainer.find('.hssm-field-error').remove();
$field.removeClass('hssm-field-has-error');
$fieldContainer.find('.hssm-checkboxes-compact').removeClass('hssm-field-has-error');
});
// Clear target sites error when a checkbox is checked
$(document).on('change', 'input[name="target_sites[]"], input[name*="target_sites[]"]', function() {
const $form = $(this).closest('.hssm-slide-form');
const $targetSitesField = $form.find('.hssm-field:has(.hssm-checkboxes-compact)').first();
$targetSitesField.find('.hssm-field-error').remove();
$targetSitesField.find('.hssm-checkboxes-compact').removeClass('hssm-field-has-error');
});
// Filtering and management
$(document).on('click', '#apply-filters', this.applyFilters.bind(this));
$(document).on('click', '.hssm-edit-slide', this.editSlide.bind(this));
$(document).on('click', '.hssm-delete-slide', function(e) {
e.preventDefault();
const slideId = $(e.currentTarget).data('slide-id');
HSSMDashboard.deleteSlide(slideId);
});
// Analytics functionality
$(document).on('click', '#generate-report', this.generateAnalyticsReport.bind(this));
// Additional functionality
$(document).on('click', '#import-slides', this.importSlides.bind(this));
$(document).on('click', '#export-slides', this.exportSlides.bind(this));
$(document).on('click', '#save-draft-all', this.saveAllAsDrafts.bind(this));
// Notification Settings
$(document).on('submit', '#notification-settings-form', this.saveNotificationSettings.bind(this));
},
// Initialize tab functionality
initializeTabs: function() {
// Check for saved tab in sessionStorage
const savedTab = sessionStorage.getItem('hssm_active_tab');
if (savedTab && $(`[data-tab="${savedTab}"]`).length > 0) {
// Restore saved tab
$('.hssm-nav-tabs li').removeClass('active');
$('.hssm-tab-content').removeClass('active');
$(`[data-tab="${savedTab}"]`).parent().addClass('active');
$(`#${savedTab}`).addClass('active');
this.currentTab = savedTab;
// Load tab-specific data for restored tab
if (savedTab === 'manage-slides') {
this.loadExistingSlides();
} else if (savedTab === 'analytics') {
this.loadAnalyticsData();
} else if (savedTab === 'notifications') {
this.loadNotificationSettings();
}
} else {
// Default to first tab
$('.hssm-nav-tabs a').first().parent().addClass('active');
$('.hssm-tab-content').first().addClass('active');
this.currentTab = $('.hssm-nav-tabs a').first().data('tab') || 'create-slides';
}
},
// Handle tab switching
handleTabClick: function(e) {
e.preventDefault();
const $tab = $(e.currentTarget);
const targetTab = $tab.data('tab');
// Update active states for navigation tabs
$('.hssm-nav-tabs li').removeClass('active');
$tab.parent().addClass('active');
// Update active states for tab content
$('.hssm-tab-content').removeClass('active');
$('#' + targetTab).addClass('active');
// Set currentTab BEFORE calling functions
this.currentTab = targetTab;
// Save active tab to sessionStorage
sessionStorage.setItem('hssm_active_tab', targetTab);
// Load tab-specific data
if (targetTab === 'manage-slides') {
this.loadExistingSlides();
} else if (targetTab === 'analytics') {
this.loadAnalyticsData();
} else if (targetTab === 'notifications') {
this.loadNotificationSettings();
}
},
// Add initial slide form
addInitialSlideForm: function() {
if ($('#slide-forms-container .hssm-slide-form').length === 0) {
this.addSlideForm();
}
},
// Add a new slide form
addSlideForm: function(e) {
if (e) e.preventDefault();
if ($('.hssm-slide-form').length >= this.maxForms) {
this.showMessage(hssmDashboard.strings.max_forms, 'warning');
return;
}
this.formCounter++;
// Get available sites for checkboxes
const sitesHtml = window.hssmSites ? window.hssmSites.map(site => `
<label class="hssm-checkbox-compact">
<input type="checkbox" name="slides[${this.formCounter}][target_sites][]" value="${site.id}">
${site.name}
</label>
`).join('') : '<p>No sites available</p>';
const formHtml = `
<div class="hssm-slide-form" data-form-id="${this.formCounter}">
<div class="hssm-form-header">
<h3>Slide ${this.formCounter}</h3>
<span class="hssm-remove-form">× Remove</span>
</div>
<div class="hssm-form-grid">
<!-- Left Column: Basic Information + Site Targeting -->
<div class="hssm-form-section">
<h4>Basic Information</h4>
<div class="hssm-field">
<label>Title</label>
<input type="text" name="slides[${this.formCounter}][title]" class="hssm-input" required>
</div>
<div class="hssm-field">
<label>Subtitle</label>
<input type="text" name="slides[${this.formCounter}][subtitle]" class="hssm-input">
</div>
<div class="hssm-field">
<label>Content</label>
<textarea name="slides[${this.formCounter}][content]" class="hssm-textarea" rows="3"></textarea>
</div>
<div class="hssm-field-row">
<div class="hssm-field">
<label>Start Date</label>
<input type="date" name="slides[${this.formCounter}][start_date]" id="start_date_${this.formCounter}" class="hssm-input">
</div>
<div class="hssm-field">
<label>End Date</label>
<input type="date" name="slides[${this.formCounter}][end_date]" id="end_date_${this.formCounter}" class="hssm-input">
</div>
</div>
<h4 style="margin-top: 20px;">Site Targeting</h4>
<div class="hssm-field">
<label>Target Sites</label>
<div class="hssm-checkboxes-compact">
${sitesHtml}
</div>
</div>
<div class="hssm-field" style="margin-top: 15px;">
<label class="hssm-checkbox-compact hssm-hide-text-option">
<input type="checkbox" name="slides[${this.formCounter}][hide_all_text]" value="1">
Hide All Text (Image Only)
<small style="display: block; color: #666; margin-top: 5px;">Check this to display only the image without any overlay text</small>
</label>
</div>
</div>
<!-- Right Column: Image & CTA Buttons -->
<div class="hssm-form-section">
<h4>Image</h4>
<div class="hssm-field">
<div class="hssm-image-upload-compact">
<div class="hssm-image-preview-small" id="image_preview_${this.formCounter}" data-target="${this.formCounter}">
<span>Click to upload image</span>
</div>
<div class="hssm-image-controls">
<button type="button" class="hssm-btn hssm-btn-sm hssm-upload-btn" data-target="${this.formCounter}">Upload</button>
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-sm hssm-remove-image" data-target="${this.formCounter}" style="display: none;">Remove</button>
<input type="hidden" name="slides[${this.formCounter}][featured_image]" id="image_id_${this.formCounter}" class="hssm-image-id">
</div>
</div>
</div>
<h4 style="margin-top: 20px;">Call-to-Action Buttons</h4>
<div class="hssm-field">
<div class="hssm-buttons-container-compact" id="buttons_${this.formCounter}" data-form="${this.formCounter}">
<div class="hssm-button-row-compact">
<input type="text" name="slides[${this.formCounter}][buttons][0][text]" placeholder="Button text" class="hssm-input hssm-input-small">
<input type="url" name="slides[${this.formCounter}][buttons][0][url]" placeholder="Button URL" class="hssm-input hssm-input-small">
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-tiny hssm-remove-button">×</button>
</div>
</div>
<button type="button" class="hssm-btn hssm-btn-sm hssm-add-button" data-form="${this.formCounter}">+ Add Button</button>
</div>
</div>
</div>
<div class="hssm-form-actions">
<button type="button" class="hssm-btn hssm-schedule-slide" data-form="${this.formCounter}">Schedule for Approval</button>
<button type="button" class="hssm-btn hssm-publish-slide" data-form="${this.formCounter}">Publish Immediately</button>
</div>
</div>
`;
$('#slide-forms-container').append(formHtml);
this.initializeFormElements(this.formCounter);
},
// Initialize form elements for a specific form
initializeFormElements: function(formId) {
// Set default target site to first available site
const $form = $(`.hssm-slide-form[data-form-id="${formId}"]`);
$form.find('input[name="target_sites[]"], input[name*="target_sites[]"]').first().prop('checked', true);
},
// Remove a slide form
removeSlideForm: function(e) {
e.preventDefault();
const $form = $(e.currentTarget).closest('.hssm-slide-form');
if ($('.hssm-slide-form').length <= 1) {
this.showMessage('At least one form must remain.', 'warning');
return;
}
$form.fadeOut(300, function() {
$(this).remove();
});
},
// Clear all forms
clearAllForms: function(e) {
if (e) e.preventDefault();
if (confirm('Are you sure you want to clear all forms? This action cannot be undone.')) {
$('#slide-forms-container').empty();
this.formCounter = 0;
this.addSlideForm();
}
},
// Trigger image upload
// Handle image upload
triggerImageUpload: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('target') ||
$(e.currentTarget).closest('.hssm-slide-form').data('form-id');
if (!formId) {
console.error('Could not determine form ID for image upload');
return;
}
console.log('Opening WordPress media uploader for form:', formId);
// Use WordPress media uploader
const mediaUploader = wp.media({
title: 'Select Slide Image',
button: {
text: 'Use This Image'
},
multiple: false,
library: {
type: 'image'
}
});
mediaUploader.on('select', function() {
const attachment = mediaUploader.state().get('selection').first().toJSON();
const $preview = $(`#image_preview_${formId}`);
console.log('Image selected:', attachment);
// Update preview
$preview.css('background-image', `url(${attachment.url})`);
$preview.addClass('has-image');
$preview.html(''); // Remove the "Click to upload" text
// Store image ID
$(`#image_id_${formId}`).val(attachment.id);
// Show remove button
$(`.hssm-remove-image[data-target="${formId}"]`).show();
this.showMessage('Image selected successfully!', 'success');
}.bind(this));
mediaUploader.open();
},
// Remove uploaded image
removeImage: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('target');
const $preview = $(`#image_preview_${formId}`);
$preview.css('background-image', '');
$preview.removeClass('has-image');
$preview.html('<span>Click to upload image</span>'); // Restore the placeholder text
$(`#image_id_${formId}`).val('');
$(e.currentTarget).hide();
},
// Add button row
addButtonRow: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('form');
const $container = $(`#buttons_${formId}`);
const buttonIndex = $container.find('.hssm-button-row-compact').length;
if (buttonIndex >= 5) {
this.showMessage('Maximum 5 buttons allowed per slide.', 'warning');
return;
}
const buttonHtml = `
<div class="hssm-button-row-compact">
<input type="text" name="slides[${formId}][buttons][${buttonIndex}][text]" placeholder="Button text" class="hssm-input hssm-input-small">
<input type="url" name="slides[${formId}][buttons][${buttonIndex}][url]" placeholder="Button URL" class="hssm-input hssm-input-small">
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-tiny hssm-remove-button">×</button>
</div>
`;
$container.append(buttonHtml);
},
// Remove button row
removeButtonRow: function(e) {
e.preventDefault();
const $container = $(e.currentTarget).closest('.hssm-buttons-container-compact');
if ($container.find('.hssm-button-row-compact').length <= 1) {
this.showMessage('At least one button is required.', 'warning');
return;
}
$(e.currentTarget).closest('.hssm-button-row-compact').fadeOut(300, function() {
$(this).remove();
});
},
// Save individual slide
saveSlide: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('form');
const $form = $(`.hssm-slide-form[data-form-id="${formId}"]`);
this.submitSlideForm($form);
},
// Save all slides
saveAllSlides: function(e) {
if (e) e.preventDefault();
const $forms = $('.hssm-slide-form');
let savedCount = 0;
let errorCount = 0;
$forms.each((index, form) => {
this.submitSlideForm($(form), () => {
savedCount++;
this.checkAllSaved(savedCount, errorCount, $forms.length);
}, () => {
errorCount++;
this.checkAllSaved(savedCount, errorCount, $forms.length);
});
});
},
// Check if all forms have been processed
checkAllSaved: function(savedCount, errorCount, totalCount) {
if (savedCount + errorCount === totalCount) {
if (errorCount === 0) {
this.showMessage(`All ${savedCount} slides saved successfully!`, 'success');
} else {
this.showMessage(`${savedCount} slides saved, ${errorCount} failed.`, 'warning');
}
}
},
// Submit individual slide form
submitSlideForm: function($form, successCallback, errorCallback) {
if (!this.validateForm($form)) {
if (errorCallback) errorCallback();
return;
}
const formData = this.collectFormData($form);
$form.addClass('hssm-loading');
formData.append('nonce', hssmDashboard.nonce);
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: (response) => {
$form.removeClass('hssm-loading');
if (response.success) {
this.showMessage(response.data.message, 'success');
this.clearForm($form);
if (successCallback) successCallback();
} else {
this.showMessage(response.data.message || 'Save failed', 'error');
if (errorCallback) errorCallback();
}
},
error: () => {
$form.removeClass('hssm-loading');
this.showMessage('Save failed. Please try again.', 'error');
if (errorCallback) errorCallback();
}
});
},
// Clear all field errors in a form
clearFieldErrors: function($form) {
$form.find('.hssm-field-error').remove();
$form.find('.hssm-input, .hssm-textarea, .hssm-checkboxes-compact').removeClass('hssm-field-has-error');
},
// Show error for a specific field
showFieldError: function($field, message) {
// Remove any existing error for this field
const $fieldContainer = $field.closest('.hssm-field');
$fieldContainer.find('.hssm-field-error').remove();
// Add error class to field
$field.addClass('hssm-field-has-error');
// Create error message element
const $error = $('<div class="hssm-field-error"></div>').text(message);
// Insert error above the field (after label, before input)
const $label = $fieldContainer.find('label').first();
if ($label.length) {
$error.insertAfter($label);
} else {
// If no label, insert at the beginning of the field container
$error.prependTo($fieldContainer);
}
},
// Validate form data
validateForm: function($form) {
// Clear previous errors
this.clearFieldErrors($form);
const title = $form.find('input[name*="[title]"]').val().trim();
const startDate = $form.find('input[name*="[start_date]"]').val();
const endDate = $form.find('input[name*="[end_date]"]').val();
// Find target sites checkboxes - handle all possible name formats
// Matches: name="target_sites[]", name="slides[X][target_sites][]", etc.
const $targetSiteCheckboxes = $form.find('input[type="checkbox"][name*="target_sites"]');
const targetSitesCount = $targetSiteCheckboxes.filter(':checked').length;
let hasErrors = false;
console.log('Validating form data:');
console.log('Title:', title);
console.log('Start Date:', startDate);
console.log('End Date:', endDate);
console.log('Target Sites Checkboxes Found:', $targetSiteCheckboxes.length);
console.log('Target Sites Selected:', targetSitesCount);
console.log('Target Sites Checkbox Names:', $targetSiteCheckboxes.map(function() { return $(this).attr('name'); }).get());
// Validate title
if (!title) {
const $titleField = $form.find('input[name*="[title]"]');
this.showFieldError($titleField, 'Title is required');
hasErrors = true;
}
// Validate target sites - check all checkbox formats
if (targetSitesCount === 0) {
const $targetSitesContainer = $form.find('.hssm-checkboxes-compact').first();
$targetSitesContainer.addClass('hssm-field-has-error');
const $targetSitesField = $form.find('.hssm-field:has(.hssm-checkboxes-compact)').first();
const $error = $('<div class="hssm-field-error"></div>').text('At least one target site must be selected');
$targetSitesField.find('label').first().after($error);
hasErrors = true;
}
// Validate date logic
if (startDate && endDate && new Date(startDate) > new Date(endDate)) {
const $endDateField = $form.find('input[name*="[end_date]"]');
this.showFieldError($endDateField, 'End Date must be after Start Date');
hasErrors = true;
}
if (hasErrors) {
console.log('Validation errors found');
// Scroll to first error
const $firstError = $form.find('.hssm-field-error').first();
if ($firstError.length) {
$('html, body').animate({
scrollTop: $firstError.offset().top - 100
}, 500);
}
return false;
}
console.log('Form validation passed');
return true;
},
// Collect form data
collectFormData: function($form) {
const formData = new FormData();
const formId = $form.data('form-id');
formData.append('nonce', hssmDashboard.nonce);
// Basic fields - using the new compact form structure
formData.append('title', $form.find('input[name*="[title]"]').val() || '');
formData.append('subtitle', $form.find('input[name*="[subtitle]"]').val() || '');
formData.append('content', $form.find('textarea[name*="[content]"]').val() || '');
formData.append('start_date', $form.find('input[name*="[start_date]"]').val() || '');
formData.append('end_date', $form.find('input[name*="[end_date]"]').val() || '');
formData.append('featured_image_id', $form.find('.hssm-image-id').val() || '');
// Target sites - using the new compact form structure
// Handle both name="target_sites[]" and name="slides[X][target_sites][]" formats
const targetSitesCollected = [];
$form.find('input[type="checkbox"][name*="target_sites"]').filter(':checked').each(function() {
const siteValue = $(this).val();
formData.append('target_sites[]', siteValue);
targetSitesCollected.push(siteValue);
});
console.log('Collected target sites:', targetSitesCollected);
// Hide all text option
const hideAllText = $form.find('input[name*="[hide_all_text]"]:checked').length > 0;
formData.append('hide_all_text', hideAllText ? '1' : '0');
// Buttons - using the new compact form structure
const buttons = [];
$form.find('.hssm-button-row-compact').each(function() {
const text = $(this).find('input[name*="[text]"]').val();
const url = $(this).find('input[name*="[url]"]').val();
if (text || url) {
buttons.push({ text: text || 'Learn More', url: url || '' });
}
});
formData.append('buttons', JSON.stringify(buttons));
return formData;
},
// Schedule slide for approval
scheduleSlide: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('form');
const $form = $(`.hssm-slide-form[data-form-id="${formId}"]`);
console.log('Schedule slide clicked for form:', formId);
console.log('Form found:', $form.length);
if (!this.validateForm($form)) {
return;
}
const formData = this.collectFormData($form);
formData.append('action', 'hssm_schedule_slide');
formData.append('publication_action', 'schedule');
console.log('Sending AJAX request...');
console.log('Form data:', Array.from(formData.entries()));
$form.addClass('hssm-loading');
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: (response) => {
console.log('AJAX success response:', response);
$form.removeClass('hssm-loading');
if (response.success) {
this.showMessage('Slide scheduled for approval. Email notifications have been sent.', 'success');
this.clearForm($form);
} else {
this.showMessage(response.data.message || 'Schedule failed', 'error');
}
},
error: (xhr, status, error) => {
console.log('AJAX error:', xhr, status, error);
$form.removeClass('hssm-loading');
this.showMessage('Schedule failed. Please try again.', 'error');
}
});
},
// Publish slide immediately
publishSlide: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('form');
const $form = $(`.hssm-slide-form[data-form-id="${formId}"]`);
if (!this.validateForm($form)) {
return;
}
const formData = this.collectFormData($form);
formData.append('action', 'hssm_publish_slide');
formData.append('publication_action', 'publish');
$form.addClass('hssm-loading');
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: (response) => {
$form.removeClass('hssm-loading');
if (response.success) {
this.showMessage('Slide published and approval notifications sent.', 'success');
this.clearForm($form);
} else {
this.showMessage(response.data.message || 'Publish failed', 'error');
}
},
error: () => {
$form.removeClass('hssm-loading');
this.showMessage('Publish failed. Please try again.', 'error');
}
});
},
// Clear form after successful save
clearForm: function($form) {
const formId = $form.data('form-id');
$form.find('input[type="text"], input[type="url"], input[type="number"], input[type="date"], textarea').val('');
$form.find('input[type="checkbox"]').prop('checked', false);
// Reset target sites to first site by default
$form.find('input[name="target_sites[]"], input[name*="target_sites[]"]').first().prop('checked', true);
// Clear image
const $imagePreview = $form.find('.hssm-image-preview-small');
$imagePreview.css('background-image', '');
$imagePreview.removeClass('has-image');
$imagePreview.html('<span>Click to upload image</span>');
$form.find('.hssm-image-id').val('');
$form.find('.hssm-remove-image').hide();
// Reset buttons to one row
const $buttonsContainer = $form.find('.hssm-buttons-container-compact');
$buttonsContainer.html(`
<div class="hssm-button-row-compact">
<input type="text" name="slides[${formId}][buttons][0][text]" placeholder="Button text" class="hssm-input hssm-input-small">
<input type="url" name="slides[${formId}][buttons][0][url]" placeholder="Button URL" class="hssm-input hssm-input-small">
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-tiny hssm-remove-button">×</button>
</div>
`);
},
// Load existing slides
loadExistingSlides: function() {
const $slidesList = $('#slides-list');
if ($slidesList.length === 0) {
console.error('slides-list element not found!');
return;
}
$slidesList.html('<div class="hssm-loading">Loading slides...</div>');
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_get_slides',
nonce: hssmDashboard.nonce
},
beforeSend: function(xhr, settings) {
// Optional: Add loading indicators
},
success: (response) => {
if (response.success && response.data && response.data.slides) {
this.renderSlidesList(response.data.slides);
} else {
console.error('Failed to get slides:', response);
$slidesList.html('<p>Failed to load slides. Response format unexpected.</p>');
}
},
error: (xhr, status, error) => {
console.error('AJAX error getting slides:', xhr.responseText, status, error);
console.error('Status code:', xhr.status);
console.error('Status text:', xhr.statusText);
$slidesList.html('<p>Failed to load slides.</p>');
}
});
},
// Render slides list
renderSlidesList: function(slides) {
const $slidesList = $('#slides-list');
// Store slides for editing functionality
this.currentSlides = slides;
if (!slides || slides.length === 0) {
$slidesList.html(`
<div class="hssm-empty-state">
<div class="hssm-empty-icon">
<span class="dashicons dashicons-slides"></span>
</div>
<h3>No slides found</h3>
<p>Create your first slide to get started!</p>
<button class="hssm-btn hssm-btn-primary" onclick="$('.hssm-nav-tabs a[data-tab=create-slides]').click()">
<span class="dashicons dashicons-plus-alt"></span> Create First Slide
</button>
</div>
`);
return;
}
let html = `
<div class="hssm-slides-table">
<div class="hssm-table-header">
<div class="hssm-col-image">Image</div>
<div class="hssm-col-title">Title</div>
<div class="hssm-col-status">Status</div>
<div class="hssm-col-dates">Schedule</div>
<div class="hssm-col-sites">Target Sites</div>
<div class="hssm-col-actions">Actions</div>
</div>
<div class="hssm-table-body">
`;
slides.forEach(slide => {
const imageUrl = slide.featured_image_url || '';
const statusClass = this.getStatusClass(slide.status);
const sitesText = this.formatSitesList(slide.target_sites);
const scheduleText = this.formatScheduleText(slide);
html += `
<div class="hssm-slide-row" data-slide-id="${slide.id}">
<div class="hssm-col-image">
<div class="hssm-slide-thumbnail" style="background-image: url('${imageUrl}');">
${!imageUrl ? '<span class="dashicons dashicons-format-image"></span>' : ''}
</div>
</div>
<div class="hssm-col-title">
<div class="hssm-slide-title">${slide.title}</div>
<div class="hssm-slide-subtitle">${slide.subtitle || ''}</div>
</div>
<div class="hssm-col-status">
<span class="hssm-status-badge hssm-status-${statusClass}">${slide.status}</span>
</div>
<div class="hssm-col-dates">
<div class="hssm-schedule-info">${scheduleText}</div>
</div>
<div class="hssm-col-sites">
<div class="hssm-sites-info">${sitesText}</div>
</div>
<div class="hssm-col-actions">
<div class="hssm-slide-actions">
<button class="hssm-btn hssm-btn-sm hssm-btn-secondary hssm-edit-slide" data-slide-id="${slide.id}" title="Edit">
<span class="dashicons dashicons-edit"></span>
</button>
<button class="hssm-btn hssm-btn-sm hssm-btn-secondary hssm-duplicate-slide" data-slide-id="${slide.id}" title="Duplicate">
<span class="dashicons dashicons-admin-page"></span>
</button>
<button class="hssm-btn hssm-btn-sm hssm-btn-success hssm-toggle-status" data-slide-id="${slide.id}" title="Toggle Status">
<span class="dashicons dashicons-${slide.status === 'publish' ? 'pause' : 'controls-play'}"></span>
</button>
<button class="hssm-btn hssm-btn-sm hssm-btn-danger hssm-delete-slide" data-slide-id="${slide.id}" title="Delete">
<span class="dashicons dashicons-trash"></span>
</button>
</div>
</div>
</div>
`;
});
html += `
</div>
</div>
`;
$slidesList.html(html);
this.bindSlideActions();
},
// Helper function to get status CSS class
getStatusClass: function(status) {
const statusMap = {
'publish': 'published',
'draft': 'draft',
'future': 'scheduled',
'private': 'private'
};
return statusMap[status] || 'unknown';
},
// Helper function to format sites list
formatSitesList: function(sites) {
if (!sites || sites.length === 0) return 'No sites';
if (sites.length === 1) return sites[0];
if (sites.length <= 3) return sites.join(', ');
return `${sites.slice(0, 2).join(', ')} +${sites.length - 2} more`;
},
// Helper function to format schedule text
formatScheduleText: function(slide) {
if (!slide.start_date && !slide.end_date) return 'No schedule';
const start = slide.start_date ? new Date(slide.start_date).toLocaleDateString() : '';
const end = slide.end_date ? new Date(slide.end_date).toLocaleDateString() : '';
if (start && end) return `${start} - ${end}`;
if (start) return `From ${start}`;
if (end) return `Until ${end}`;
return 'No schedule';
},
// Bind slide action events
bindSlideActions: function() {
// Edit slide
$(document).off('click', '.hssm-edit-slide').on('click', '.hssm-edit-slide', (e) => {
e.preventDefault();
const slideId = $(e.currentTarget).data('slide-id');
this.editSlide(slideId);
});
// Delete slide
$(document).off('click', '.hssm-delete-slide').on('click', '.hssm-delete-slide', (e) => {
e.preventDefault();
const slideId = $(e.currentTarget).data('slide-id');
this.deleteSlide(slideId);
});
// Duplicate slide
$(document).off('click', '.hssm-duplicate-slide').on('click', '.hssm-duplicate-slide', (e) => {
e.preventDefault();
const slideId = $(e.currentTarget).data('slide-id');
this.duplicateSlide(slideId);
});
// Toggle status
$(document).off('click', '.hssm-toggle-status').on('click', '.hssm-toggle-status', (e) => {
e.preventDefault();
const slideId = $(e.currentTarget).data('slide-id');
this.toggleSlideStatus(slideId);
});
},
// Edit slide function
editSlide: function(slideId) {
// Switch to create tab and populate with slide data
$('.hssm-nav-tabs a[data-tab="create-slides"]').click();
this.loadSlideForEditing(slideId);
},
// Load slide data for editing
loadSlideForEditing: function(slideId) {
// Show loading state
$('#slide-forms-container').html('<div class="hssm-loading">Loading slide for editing...</div>');
// First, get the slide data from our existing slides list
const slideData = this.findSlideById(slideId);
if (slideData) {
this.populateEditForm(slideData);
} else {
// Fallback: fetch slide data via AJAX if not found in current list
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_get_slide_for_edit',
nonce: hssmDashboard.nonce,
slide_id: slideId
},
success: (response) => {
if (response.success && response.data.slide) {
this.populateEditForm(response.data.slide);
} else {
this.showMessage('Failed to load slide for editing', 'error');
this.addInitialSlideForm(); // Fallback to empty form
}
},
error: () => {
this.showMessage('Failed to load slide for editing', 'error');
this.addInitialSlideForm(); // Fallback to empty form
}
});
}
},
// Find slide by ID in current slides list
findSlideById: function(slideId) {
// Check if we have slides loaded in memory
if (!this.currentSlides) {
return null;
}
return this.currentSlides.find(slide => slide.id == slideId);
},
// Populate form with slide data for editing
populateEditForm: function(slideData) {
// Clear existing forms
$('#slide-forms-container').empty();
// Reset form counter and add edit form
this.formCounter = 1;
this.isEditMode = true;
this.editingSlideId = slideData.id;
// Get available sites for checkboxes
const sitesHtml = window.hssmSites ? window.hssmSites.map(site => {
const isSelected = slideData.sites && slideData.sites.includes(site.name);
return `
<label class="hssm-checkbox-compact">
<input type="checkbox" name="slides[${this.formCounter}][target_sites][]" value="${site.id}" ${isSelected ? 'checked' : ''}>
${site.name}
</label>
`;
}).join('') : '<p>No sites available</p>';
const formHtml = `
<div class="hssm-slide-form" data-form-id="${this.formCounter}" data-editing-slide-id="${slideData.id}">
<div class="hssm-form-header">
<h3>Editing: ${slideData.title} <span class="hssm-edit-badge">EDIT MODE</span></h3>
<span class="hssm-cancel-edit">× Cancel Edit</span>
</div>
<div class="hssm-form-grid">
<!-- Left Column: Basic Information + Site Targeting -->
<div class="hssm-form-section">
<h4>Basic Information</h4>
<div class="hssm-field">
<label>Title</label>
<input type="text" name="slides[${this.formCounter}][title]" class="hssm-input" value="${this.escapeHtml(slideData.title)}" required>
</div>
<div class="hssm-field">
<label>Subtitle</label>
<input type="text" name="slides[${this.formCounter}][subtitle]" class="hssm-input" value="${this.escapeHtml(slideData.subtitle || '')}">
</div>
<div class="hssm-field">
<label>Content</label>
<textarea name="slides[${this.formCounter}][content]" class="hssm-textarea" rows="3">${this.escapeHtml(slideData.content || '')}</textarea>
</div>
<div class="hssm-field-row">
<div class="hssm-field">
<label>Start Date</label>
<input type="date" name="slides[${this.formCounter}][start_date]" id="start_date_${this.formCounter}" class="hssm-input" value="${slideData.start_date || ''}">
</div>
<div class="hssm-field">
<label>End Date</label>
<input type="date" name="slides[${this.formCounter}][end_date]" id="end_date_${this.formCounter}" class="hssm-input" value="${slideData.end_date || ''}">
</div>
</div>
<h4 style="margin-top: 20px;">Site Targeting</h4>
<div class="hssm-field">
<label>Target Sites</label>
<div class="hssm-checkboxes-compact">
${sitesHtml}
</div>
</div>
<div class="hssm-field" style="margin-top: 15px;">
<label class="hssm-checkbox-compact hssm-hide-text-option">
<input type="checkbox" name="slides[${this.formCounter}][hide_all_text]" value="1" ${slideData.hide_all_text ? 'checked' : ''}>
Hide All Text (Image Only)
<small style="display: block; color: #666; margin-top: 5px;">Check this to display only the image without any overlay text</small>
</label>
</div>
</div>
<!-- Right Column: Image & CTA Buttons -->
<div class="hssm-form-section">
<h4>Image</h4>
<div class="hssm-field">
<div class="hssm-image-upload-compact">
<div class="hssm-image-preview-small ${slideData.featured_image_url ? 'has-image' : ''}"
id="image_preview_${this.formCounter}"
data-target="${this.formCounter}"
style="${slideData.featured_image_url ? `background-image: url('${slideData.featured_image_url}')` : ''}">
${!slideData.featured_image_url ? '<span>Click to upload image</span>' : ''}
</div>
<div class="hssm-image-controls">
<button type="button" class="hssm-btn hssm-btn-sm hssm-upload-btn" data-target="${this.formCounter}">Upload</button>
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-sm hssm-remove-image" data-target="${this.formCounter}" style="${slideData.featured_image_url ? '' : 'display: none;'}">Remove</button>
<input type="hidden" name="slides[${this.formCounter}][featured_image]" id="image_id_${this.formCounter}" class="hssm-image-id" value="">
</div>
</div>
</div>
<h4 style="margin-top: 20px;">Call-to-Action Buttons</h4>
<div class="hssm-field">
<div class="hssm-buttons-container-compact" id="buttons_${this.formCounter}" data-form="${this.formCounter}">
${this.generateButtonsHtml(slideData.buttons || [], this.formCounter)}
</div>
<button type="button" class="hssm-btn hssm-btn-sm hssm-add-button" data-form="${this.formCounter}">+ Add Button</button>
</div>
</div>
</div>
<div class="hssm-form-actions">
<button type="button" class="hssm-btn hssm-btn-secondary hssm-schedule-updated-slide" data-form="${this.formCounter}" data-slide-id="${slideData.id}">
<span class="dashicons dashicons-calendar-alt"></span> Schedule for Approval
</button>
<button type="button" class="hssm-btn hssm-update-slide" data-form="${this.formCounter}" data-slide-id="${slideData.id}">Update as Draft</button>
<button type="button" class="hssm-btn hssm-btn-outline hssm-cancel-edit-btn" data-form="${this.formCounter}">Cancel Edit</button>
</div>
</div>
`;
$('#slide-forms-container').html(formHtml);
// Bind cancel edit events
$(document).on('click', '.hssm-cancel-edit, .hssm-cancel-edit-btn', this.cancelEdit.bind(this));
$(document).on('click', '.hssm-update-slide', this.updateSlide.bind(this));
$(document).on('click', '.hssm-schedule-updated-slide', this.scheduleUpdatedSlide.bind(this));
this.showMessage('Slide loaded for editing', 'success');
},
// Generate buttons HTML for editing
generateButtonsHtml: function(buttons, formId) {
if (!buttons || buttons.length === 0) {
return `
<div class="hssm-button-row-compact">
<input type="text" name="slides[${formId}][buttons][0][text]" placeholder="Button text" class="hssm-input hssm-input-small">
<input type="url" name="slides[${formId}][buttons][0][url]" placeholder="Button URL" class="hssm-input hssm-input-small">
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-tiny hssm-remove-button">×</button>
</div>
`;
}
return buttons.map((button, index) => `
<div class="hssm-button-row-compact">
<input type="text" name="slides[${formId}][buttons][${index}][text]" placeholder="Button text" class="hssm-input hssm-input-small" value="${this.escapeHtml(button.text || '')}">
<input type="url" name="slides[${formId}][buttons][${index}][url]" placeholder="Button URL" class="hssm-input hssm-input-small" value="${this.escapeHtml(button.url || '')}">
<button type="button" class="hssm-btn hssm-btn-danger hssm-btn-tiny hssm-remove-button">×</button>
</div>
`).join('');
},
// Cancel edit mode
cancelEdit: function(e) {
if (e) e.preventDefault();
this.isEditMode = false;
this.editingSlideId = null;
// Return to normal create form
$('#slide-forms-container').empty();
this.formCounter = 0;
this.addInitialSlideForm();
this.showMessage('Edit cancelled', 'info');
},
// Update slide function
updateSlide: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('form');
const slideId = $(e.currentTarget).data('slide-id');
const $form = $(`.hssm-slide-form[data-form-id="${formId}"]`);
if (!this.validateForm($form)) {
return;
}
const formData = this.collectFormData($form);
formData.append('action', 'hssm_update_slide');
formData.append('slide_id', slideId);
formData.append('post_status', 'draft'); // Explicitly set as draft
$form.addClass('hssm-loading');
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: (response) => {
$form.removeClass('hssm-loading');
if (response.success) {
this.showMessage('Slide updated successfully as draft!', 'success');
// Exit edit mode and refresh manage slides if that tab is available
this.cancelEdit();
// If manage slides tab exists, refresh it
if ($('#manage-slides').length > 0) {
$('.hssm-nav-tabs a[data-tab="manage-slides"]').click();
}
} else {
this.showMessage(response.data?.message || 'Update failed', 'error');
}
},
error: () => {
$form.removeClass('hssm-loading');
this.showMessage('Update failed. Please try again.', 'error');
}
});
},
// Schedule updated slide for approval
scheduleUpdatedSlide: function(e) {
e.preventDefault();
const formId = $(e.currentTarget).data('form');
const slideId = $(e.currentTarget).data('slide-id');
const $form = $(`.hssm-slide-form[data-form-id="${formId}"]`);
if (!this.validateForm($form)) {
return;
}
const formData = this.collectFormData($form);
formData.append('action', 'hssm_update_slide');
formData.append('slide_id', slideId);
formData.append('post_status', 'pending'); // Set to pending for approval
$form.addClass('hssm-loading');
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: (response) => {
$form.removeClass('hssm-loading');
if (response.success) {
this.showMessage('Slide updated and scheduled for approval!', 'success');
// Exit edit mode and refresh manage slides if that tab is available
this.cancelEdit();
// If manage slides tab exists, refresh it
if ($('#manage-slides').length > 0) {
$('.hssm-nav-tabs a[data-tab="manage-slides"]').click();
}
} else {
this.showMessage(response.data?.message || 'Schedule failed', 'error');
}
},
error: () => {
$form.removeClass('hssm-loading');
this.showMessage('Schedule failed. Please try again.', 'error');
}
});
},
// Escape HTML for safe insertion
escapeHtml: function(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
},
// Delete slide function
deleteSlide: function(slideId) {
if (!confirm('Are you sure you want to delete this slide? This action cannot be undone.')) {
return;
}
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_delete_slide',
nonce: hssmDashboard.nonce,
slide_id: slideId
},
success: (response) => {
if (response.success) {
this.showMessage('Slide deleted successfully!', 'success');
this.loadExistingSlides(); // Refresh the list
} else {
this.showMessage('Failed to delete slide: ' + (response.data?.message || 'Unknown error'), 'error');
}
},
error: () => {
this.showMessage('Failed to delete slide. Please try again.', 'error');
}
});
},
// Duplicate slide function
duplicateSlide: function(slideId) {
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_duplicate_slide',
nonce: hssmDashboard.nonce,
slide_id: slideId
},
success: (response) => {
if (response.success) {
this.showMessage('Slide duplicated successfully!', 'success');
this.loadExistingSlides(); // Refresh the list
} else {
this.showMessage('Failed to duplicate slide: ' + (response.data?.message || 'Unknown error'), 'error');
}
},
error: () => {
this.showMessage('Failed to duplicate slide. Please try again.', 'error');
}
});
},
// Toggle slide status
toggleSlideStatus: function(slideId) {
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_toggle_slide_status',
nonce: hssmDashboard.nonce,
slide_id: slideId
},
success: (response) => {
if (response.success) {
this.showMessage('Slide status updated!', 'success');
this.loadExistingSlides(); // Refresh the list
} else {
this.showMessage('Failed to update slide status: ' + (response.data?.message || 'Unknown error'), 'error');
}
},
error: () => {
this.showMessage('Failed to update slide status. Please try again.', 'error');
}
});
},
// Show message to user
showMessage: function(message, type = 'info') {
const $messages = $('#hssm-messages');
const messageHtml = `
<div class="hssm-message ${type}">
${message}
</div>
`;
$messages.append(messageHtml);
// Auto-remove after 5 seconds
setTimeout(() => {
$messages.find('.hssm-message').last().fadeOut(300, function() {
$(this).remove();
});
}, 5000);
// Scroll to messages
$('html, body').animate({
scrollTop: $messages.offset().top - 20
}, 300);
},
// Placeholder methods for future implementation
previewSlide: function(e) {
e.preventDefault();
this.showMessage('Preview functionality coming soon!', 'info');
},
applyFilters: function(e) {
e.preventDefault();
this.showMessage('Filter functionality coming soon!', 'info');
},
// Load analytics data
loadAnalyticsData: function() {
if (this.currentTab !== 'analytics') return;
// Simulate analytics data loading
setTimeout(() => {
$('.hssm-analytics-number').each(function(index) {
const values = ['24', '3', '7'];
$(this).text(values[index] || '0');
});
}, 500);
},
// Generate analytics report
generateAnalyticsReport: function(e) {
e.preventDefault();
this.showMessage('Analytics report generation coming soon!', 'info');
},
// Import slides
importSlides: function(e) {
e.preventDefault();
this.showMessage('Slide import functionality coming soon!', 'info');
},
// Export slides
exportSlides: function(e) {
e.preventDefault();
this.showMessage('Slide export functionality coming soon!', 'info');
},
// Save all slides as drafts
saveAllAsDrafts: function(e) {
e.preventDefault();
const $forms = $('.hssm-slide-form');
let savedCount = 0;
let errorCount = 0;
$forms.each((index, form) => {
const $form = $(form);
// Override status to draft
const formData = this.collectFormData($form);
formData.append('post_status', 'draft');
this.submitSlideForm($form, () => {
savedCount++;
this.checkAllSaved(savedCount, errorCount, $forms.length);
}, () => {
errorCount++;
this.checkAllSaved(savedCount, errorCount, $forms.length);
});
});
},
// Load notification settings
loadNotificationSettings: function() {
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_get_notification_settings',
nonce: hssmDashboard.nonce
},
success: (response) => {
if (response.success && response.data) {
$('#editor_emails').val(response.data.editor_emails || '');
$('#itc_emails').val(response.data.itc_emails || '');
}
}
});
},
// Save notification settings
saveNotificationSettings: function(e) {
e.preventDefault();
const $form = $(e.currentTarget);
$.ajax({
url: hssmDashboard.ajaxurl,
type: 'POST',
data: {
action: 'hssm_save_notification_settings',
nonce: hssmDashboard.nonce,
editor_emails: $('#editor_emails').val(),
itc_emails: $('#itc_emails').val()
},
success: (response) => {
if (response.success) {
this.showMessage('Notification settings saved successfully!', 'success');
} else {
this.showMessage('Failed to save settings.', 'error');
}
},
error: () => {
this.showMessage('Failed to save settings. Please try again.', 'error');
}
});
}
};
// Initialize when document is ready
$(document).ready(function() {
HSSMDashboard.init();
});
})(jQuery);