- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
482 lines
15 KiB
JavaScript
482 lines
15 KiB
JavaScript
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
/**
|
|
* Care Booking Block - Frontend JavaScript
|
|
*
|
|
* Provides graceful degradation and enhanced interaction for KiviCare integration
|
|
*
|
|
* @package CareBookingBlock
|
|
*/
|
|
|
|
(function($, config) {
|
|
'use strict';
|
|
|
|
// Global configuration
|
|
const CareBooking = {
|
|
config: config || {},
|
|
initialized: false,
|
|
retryCount: 0,
|
|
observers: [],
|
|
|
|
/**
|
|
* Initialize the Care Booking frontend functionality
|
|
*/
|
|
init: function() {
|
|
if (this.initialized) {
|
|
return;
|
|
}
|
|
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Initializing frontend scripts');
|
|
}
|
|
|
|
this.setupObservers();
|
|
this.enhanceExistingElements();
|
|
this.setupEventListeners();
|
|
this.setupFallbacks();
|
|
|
|
this.initialized = true;
|
|
},
|
|
|
|
/**
|
|
* Setup MutationObserver to watch for dynamically added content
|
|
*/
|
|
setupObservers: function() {
|
|
if (!window.MutationObserver) {
|
|
if (this.config.debug) {
|
|
console.warn('Care Booking Block: MutationObserver not supported');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const observer = new MutationObserver((mutations) => {
|
|
let hasNewContent = false;
|
|
|
|
mutations.forEach((mutation) => {
|
|
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
|
mutation.addedNodes.forEach((node) => {
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
// Check if new node contains KiviCare content
|
|
if (this.hasKiviCareContent(node)) {
|
|
hasNewContent = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (hasNewContent) {
|
|
this.enhanceNewContent();
|
|
}
|
|
});
|
|
|
|
// Start observing
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
|
|
this.observers.push(observer);
|
|
},
|
|
|
|
/**
|
|
* Check if element contains KiviCare content
|
|
* @param {Element} element
|
|
* @returns {boolean}
|
|
*/
|
|
hasKiviCareContent: function(element) {
|
|
const selectors = [
|
|
this.config.selectors.doctors,
|
|
this.config.selectors.services,
|
|
this.config.selectors.forms
|
|
].join(', ');
|
|
|
|
return $(element).find(selectors).length > 0 || $(element).is(selectors);
|
|
},
|
|
|
|
/**
|
|
* Enhance existing KiviCare elements on page load
|
|
*/
|
|
enhanceExistingElements: function() {
|
|
this.enhanceLoadingStates();
|
|
this.enhanceFormValidation();
|
|
this.enhanceFallbackElements();
|
|
},
|
|
|
|
/**
|
|
* Enhance newly added content
|
|
*/
|
|
enhanceNewContent: function() {
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Enhancing new content');
|
|
}
|
|
|
|
// Add a small delay to ensure DOM is stable
|
|
setTimeout(() => {
|
|
this.enhanceExistingElements();
|
|
}, 100);
|
|
},
|
|
|
|
/**
|
|
* Setup loading states for better UX
|
|
*/
|
|
enhanceLoadingStates: function() {
|
|
const $forms = $(this.config.selectors.forms);
|
|
|
|
$forms.each((index, form) => {
|
|
const $form = $(form);
|
|
|
|
// Add loading indicator
|
|
if (!$form.find('.care-booking-loading').length) {
|
|
$form.prepend('<div class="care-booking-loading" style="display: none;">Loading...</div>');
|
|
}
|
|
|
|
// Handle form submissions
|
|
$form.on('submit', (e) => {
|
|
this.showLoadingState($form);
|
|
});
|
|
|
|
// Handle AJAX requests
|
|
$(document).on('ajaxStart', () => {
|
|
if (this.isKiviCareAjax()) {
|
|
this.showLoadingState($form);
|
|
}
|
|
});
|
|
|
|
$(document).on('ajaxComplete', () => {
|
|
this.hideLoadingState($form);
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Show loading state
|
|
* @param {jQuery} $element
|
|
*/
|
|
showLoadingState: function($element) {
|
|
$element.addClass('care-booking-loading');
|
|
$element.find('.care-booking-loading').show();
|
|
},
|
|
|
|
/**
|
|
* Hide loading state
|
|
* @param {jQuery} $element
|
|
*/
|
|
hideLoadingState: function($element) {
|
|
$element.removeClass('care-booking-loading');
|
|
$element.find('.care-booking-loading').hide();
|
|
},
|
|
|
|
/**
|
|
* Check if current AJAX request is KiviCare related
|
|
* @returns {boolean}
|
|
*/
|
|
isKiviCareAjax: function() {
|
|
// This is a simplified check - could be enhanced based on KiviCare's AJAX patterns
|
|
return window.location.href.indexOf('kivicare') !== -1 ||
|
|
document.body.className.indexOf('kivicare') !== -1;
|
|
},
|
|
|
|
/**
|
|
* Enhance form validation
|
|
*/
|
|
enhanceFormValidation: function() {
|
|
const $forms = $(this.config.selectors.forms);
|
|
|
|
$forms.each((index, form) => {
|
|
const $form = $(form);
|
|
|
|
$form.on('submit', (e) => {
|
|
if (!this.validateBookingForm($form)) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Real-time validation for select fields
|
|
$form.find('select').on('change', (e) => {
|
|
this.validateSelectField($(e.target));
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Validate booking form
|
|
* @param {jQuery} $form
|
|
* @returns {boolean}
|
|
*/
|
|
validateBookingForm: function($form) {
|
|
let isValid = true;
|
|
const requiredFields = $form.find('select[required], input[required]');
|
|
|
|
requiredFields.each((index, field) => {
|
|
const $field = $(field);
|
|
if (!$field.val() || $field.val() === '0' || $field.val() === '') {
|
|
isValid = false;
|
|
this.showFieldError($field, 'This field is required');
|
|
} else {
|
|
this.clearFieldError($field);
|
|
}
|
|
});
|
|
|
|
return isValid;
|
|
},
|
|
|
|
/**
|
|
* Validate individual select field
|
|
* @param {jQuery} $field
|
|
*/
|
|
validateSelectField: function($field) {
|
|
const value = $field.val();
|
|
|
|
if ($field.attr('required') && (!value || value === '0' || value === '')) {
|
|
this.showFieldError($field, 'Please make a selection');
|
|
} else {
|
|
this.clearFieldError($field);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Show field error
|
|
* @param {jQuery} $field
|
|
* @param {string} message
|
|
*/
|
|
showFieldError: function($field, message) {
|
|
$field.addClass('error');
|
|
|
|
let $error = $field.siblings('.field-error');
|
|
if (!$error.length) {
|
|
$error = $('<div class="field-error"></div>');
|
|
$field.after($error);
|
|
}
|
|
|
|
$error.text(message).show();
|
|
},
|
|
|
|
/**
|
|
* Clear field error
|
|
* @param {jQuery} $field
|
|
*/
|
|
clearFieldError: function($field) {
|
|
$field.removeClass('error');
|
|
$field.siblings('.field-error').hide();
|
|
},
|
|
|
|
/**
|
|
* Setup fallback elements for graceful degradation
|
|
*/
|
|
enhanceFallbackElements: function() {
|
|
// Add fallback classes to elements that might be blocked
|
|
$(this.config.selectors.doctors).each((index, element) => {
|
|
const $element = $(element);
|
|
if (!$element.hasClass('care-booking-fallback')) {
|
|
$element.addClass('care-booking-enhanced');
|
|
}
|
|
});
|
|
|
|
$(this.config.selectors.services).each((index, element) => {
|
|
const $element = $(element);
|
|
if (!$element.hasClass('care-booking-fallback')) {
|
|
$element.addClass('care-booking-enhanced');
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Setup event listeners
|
|
*/
|
|
setupEventListeners: function() {
|
|
// Handle dynamic doctor selection
|
|
$(document).on('change', 'select[name="doctor_id"], .doctor-selection', (e) => {
|
|
this.handleDoctorChange($(e.target));
|
|
});
|
|
|
|
// Handle service selection
|
|
$(document).on('change', 'select[name="service_id"], .service-selection', (e) => {
|
|
this.handleServiceChange($(e.target));
|
|
});
|
|
|
|
// Handle retry buttons
|
|
$(document).on('click', '.care-booking-retry', (e) => {
|
|
e.preventDefault();
|
|
this.retryOperation($(e.target));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handle doctor selection change
|
|
* @param {jQuery} $select
|
|
*/
|
|
handleDoctorChange: function($select) {
|
|
const doctorId = $select.val();
|
|
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Doctor changed to', doctorId);
|
|
}
|
|
|
|
// Clear service selection if doctor changed
|
|
const $serviceSelect = $select.closest('form').find('select[name="service_id"], .service-selection');
|
|
if ($serviceSelect.length) {
|
|
$serviceSelect.val('').trigger('change');
|
|
this.updateServiceOptions($serviceSelect, doctorId);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle service selection change
|
|
* @param {jQuery} $select
|
|
*/
|
|
handleServiceChange: function($select) {
|
|
const serviceId = $select.val();
|
|
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Service changed to', serviceId);
|
|
}
|
|
|
|
// Additional service-specific logic can be added here
|
|
},
|
|
|
|
/**
|
|
* Update service options based on selected doctor
|
|
* @param {jQuery} $serviceSelect
|
|
* @param {string} doctorId
|
|
*/
|
|
updateServiceOptions: function($serviceSelect, doctorId) {
|
|
if (!doctorId || doctorId === '0') {
|
|
return;
|
|
}
|
|
|
|
// This would typically make an AJAX request to get services
|
|
// For now, we'll rely on KiviCare's existing functionality
|
|
$serviceSelect.trigger('doctor_changed', [doctorId]);
|
|
},
|
|
|
|
/**
|
|
* Setup fallback mechanisms
|
|
*/
|
|
setupFallbacks: function() {
|
|
if (!this.config.fallbackEnabled) {
|
|
return;
|
|
}
|
|
|
|
// Setup automatic retry for failed operations
|
|
this.setupAutoRetry();
|
|
|
|
// Setup offline detection
|
|
this.setupOfflineDetection();
|
|
},
|
|
|
|
/**
|
|
* Setup automatic retry for failed operations
|
|
*/
|
|
setupAutoRetry: function() {
|
|
$(document).on('ajaxError', (event, jqXHR, ajaxSettings, thrownError) => {
|
|
if (this.isKiviCareAjax() && this.retryCount < this.config.retryAttempts) {
|
|
setTimeout(() => {
|
|
this.retryCount++;
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Retrying operation, attempt', this.retryCount);
|
|
}
|
|
|
|
// Retry the failed request
|
|
$.ajax(ajaxSettings);
|
|
}, this.config.retryDelay);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Setup offline detection
|
|
*/
|
|
setupOfflineDetection: function() {
|
|
$(window).on('online offline', (e) => {
|
|
const isOnline = e.type === 'online';
|
|
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Connection status changed to', isOnline ? 'online' : 'offline');
|
|
}
|
|
|
|
if (isOnline) {
|
|
// Retry any pending operations
|
|
this.retryPendingOperations();
|
|
} else {
|
|
// Show offline message
|
|
this.showOfflineMessage();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Retry pending operations when back online
|
|
*/
|
|
retryPendingOperations: function() {
|
|
// Implementation would depend on what operations need to be retried
|
|
if (this.config.debug) {
|
|
console.log('Care Booking Block: Retrying pending operations');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Show offline message
|
|
*/
|
|
showOfflineMessage: function() {
|
|
const message = '<div class="care-booking-offline-message">You appear to be offline. Some features may not work properly.</div>';
|
|
|
|
if (!$('.care-booking-offline-message').length) {
|
|
$('body').prepend(message);
|
|
|
|
setTimeout(() => {
|
|
$('.care-booking-offline-message').fadeOut();
|
|
}, 5000);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Retry a specific operation
|
|
* @param {jQuery} $button
|
|
*/
|
|
retryOperation: function($button) {
|
|
const $container = $button.closest('.care-booking-container');
|
|
this.showLoadingState($container);
|
|
|
|
// Simulate retry - in practice, this would repeat the failed operation
|
|
setTimeout(() => {
|
|
this.hideLoadingState($container);
|
|
$button.closest('.error-message').fadeOut();
|
|
}, 1000);
|
|
},
|
|
|
|
/**
|
|
* Cleanup resources
|
|
*/
|
|
destroy: function() {
|
|
// Remove observers
|
|
this.observers.forEach(observer => observer.disconnect());
|
|
this.observers = [];
|
|
|
|
// Remove event listeners
|
|
$(document).off('.careBooking');
|
|
|
|
this.initialized = false;
|
|
}
|
|
};
|
|
|
|
// Initialize when DOM is ready
|
|
$(document).ready(() => {
|
|
CareBooking.init();
|
|
});
|
|
|
|
// Handle page unload
|
|
$(window).on('beforeunload', () => {
|
|
CareBooking.destroy();
|
|
});
|
|
|
|
// Expose to global scope for debugging
|
|
if (config && config.debug) {
|
|
window.CareBooking = CareBooking;
|
|
}
|
|
|
|
})(jQuery, window.careBookingConfig); |