Files
care-book-block-ultimate/care-booking-block/public/js/frontend.js
Emanuel Almeida 38bb926742 chore: add spec-kit and standardize signatures
- 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>
2025-09-12 01:27:34 +01:00

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);