Files
notytex/static/js/notytex-unified.js

469 lines
14 KiB
JavaScript

/**
* NOTYTEX UNIFIED - Point d'entrée unifié
* Charge et initialise tous les composants modernes
* Remplace l'ancien notytex.js
*/
import NotytexCore from './notytex-core.js';
import NotificationSystem from './components/notification-system.js';
import ModalManager from './components/modal-manager.js';
import FilterManager from './components/filter-manager.js';
/**
* Gestionnaire d'initialisation unifié
*/
class NotytexApp {
constructor() {
this.core = null;
this.initialized = false;
this.loadingPromise = null;
}
/**
* Initialisation asynchrone
*/
async init() {
if (this.initialized) return this.core;
if (this.loadingPromise) return this.loadingPromise;
this.loadingPromise = this.performInit();
return this.loadingPromise;
}
async performInit() {
try {
// 1. Initialiser le core
this.core = NotytexCore.getInstance();
// 2. Enregistrer les composants essentiels
await this.registerCoreComponents();
// 3. Initialiser les modules page-spécifiques
await this.initializePageModules();
// 4. Configuration des intégrations legacy
this.setupLegacyIntegration();
// 5. Optimisations performance
this.setupPerformanceOptimizations();
this.initialized = true;
// Émission de l'événement ready
this.core.emit('notytex:ready');
return this.core;
} catch (error) {
console.error('❌ Failed to initialize Notytex:', error);
throw error;
}
}
/**
* Enregistrement des composants essentiels
*/
async registerCoreComponents() {
const components = [
['notifications', NotificationSystem],
['modals', ModalManager],
['filters', FilterManager]
];
for (const [name, ComponentClass] of components) {
try {
this.core.registerModule(name, ComponentClass);
} catch (error) {
console.error(`✗ Failed to register ${name}:`, error);
}
}
}
/**
* Initialisation des modules spécifiques aux pages
*/
async initializePageModules() {
const currentPage = this.detectCurrentPage();
// Modules spécifiques par page
const pageModules = {
'assessments': ['ClassDashboard'],
'classes': ['ClassManagement'],
'grading': ['GradingInterface'],
'council': ['CouncilPreparation']
};
if (pageModules[currentPage]) {
for (const moduleName of pageModules[currentPage]) {
await this.loadPageModule(moduleName, currentPage);
}
}
// Modules universels (présents sur toutes les pages)
await this.initializeUniversalModules();
}
/**
* Détection de la page actuelle
*/
detectCurrentPage() {
const path = window.location.pathname;
const body = document.body;
// Détection par URL
if (path.includes('/assessments')) return 'assessments';
if (path.includes('/classes')) return 'classes';
if (path.includes('/grading')) return 'grading';
if (path.includes('/council')) return 'council';
if (path.includes('/students')) return 'students';
if (path.includes('/config')) return 'config';
// Détection par data attributes
if (body.hasAttribute('data-page')) {
return body.getAttribute('data-page');
}
// Détection par title ou meta
const title = document.title.toLowerCase();
if (title.includes('évaluation')) return 'assessments';
if (title.includes('classe')) return 'classes';
if (title.includes('note')) return 'grading';
return 'general';
}
/**
* Chargement des modules de page
*/
async loadPageModule(moduleName, pageName) {
try {
// Mapping des noms de modules aux fichiers
const moduleFiles = {
'ClassDashboard': './ClassDashboard.js',
'CouncilPreparation': './CouncilPreparation.js'
};
const filePath = moduleFiles[moduleName];
if (!filePath) {
console.warn(`Module file not found for ${moduleName}`);
return;
}
// Vérifier si le fichier existe (fallback)
try {
await this.core.loadModule(moduleName, filePath);
} catch (loadError) {
console.warn(`Module ${moduleName} not available, creating placeholder`);
this.createModulePlaceholder(moduleName);
}
} catch (error) {
console.error(`Failed to load page module ${moduleName}:`, error);
}
}
/**
* Initialisation des modules universels
*/
async initializeUniversalModules() {
// Animation au scroll pour tous les éléments
this.initializeScrollAnimations();
// Gestion des filtres sur toutes les pages avec filtres
this.initializePageFilters();
// Tooltips et popovers
this.initializeTooltips();
// Raccourcis clavier
this.initializeKeyboardShortcuts();
}
/**
* Animations au scroll
*/
initializeScrollAnimations() {
const elements = this.core.utils.queryAll('[data-animate]');
if (elements.length === 0) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const animationType = entry.target.dataset.animate || 'fade-in-up';
entry.target.classList.add(animationType);
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
elements.forEach(el => observer.observe(el));
}
/**
* Filtres de page
*/
initializePageFilters() {
const filterElements = this.core.utils.queryAll('[data-filter], .filter-btn');
if (filterElements.length === 0) return;
const filterManager = this.core.getModule('filters');
if (!filterManager) return;
// Configuration spécifique pour les pages de liste
filterManager.onApply((filters) => {
this.handleFilterApplication(filters);
});
}
/**
* Application des filtres avec feedback visuel
*/
handleFilterApplication(filters) {
// Afficher un indicateur de chargement
this.showFilterLoading();
// Mettre à jour le compteur si présent
this.updateFilterCounts(filters);
// Par défaut, recharger après un court délai pour l'UX
setTimeout(() => {
window.location.href = window.location.href;
}, 150);
}
/**
* Indicateur de chargement pour filtres
*/
showFilterLoading() {
const container = this.core.utils.query('#filtered-count');
if (container) {
container.innerHTML = '<span class="animate-pulse">...</span>';
}
}
/**
* Mise à jour des compteurs de filtres
*/
updateFilterCounts(filters) {
// Cette méthode peut être étendue pour faire des appels AJAX
// pour obtenir les compteurs en temps réel
}
/**
* Tooltips simples
*/
initializeTooltips() {
const tooltipElements = this.core.utils.queryAll('[title], [data-tooltip]');
tooltipElements.forEach(element => {
const tooltipText = element.getAttribute('data-tooltip') || element.getAttribute('title');
if (!tooltipText) return;
// Retirer le title natif pour éviter les doublons
element.removeAttribute('title');
let tooltip = null;
const showTooltip = (event) => {
tooltip = this.core.utils.createElement('div', {
className: 'absolute z-50 px-2 py-1 text-xs text-white bg-gray-900 rounded shadow-lg pointer-events-none',
style: 'top: -9999px; left: -9999px;'
});
tooltip.textContent = tooltipText;
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const top = rect.top - tooltipRect.height - 5;
const left = rect.left + (rect.width - tooltipRect.width) / 2;
tooltip.style.top = `${top}px`;
tooltip.style.left = `${left}px`;
};
const hideTooltip = () => {
if (tooltip) {
tooltip.remove();
tooltip = null;
}
};
element.addEventListener('mouseenter', showTooltip);
element.addEventListener('mouseleave', hideTooltip);
element.addEventListener('focus', showTooltip);
element.addEventListener('blur', hideTooltip);
});
}
/**
* Raccourcis clavier
*/
initializeKeyboardShortcuts() {
document.addEventListener('keydown', (event) => {
// Ctrl/Cmd + K pour recherche rapide
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
event.preventDefault();
const searchInput = this.core.utils.query('input[type="search"], [data-search-filter]');
if (searchInput) {
searchInput.focus();
}
}
// Échap pour fermer les overlays
if (event.key === 'Escape') {
// Fermer les dropdowns
this.core.utils.queryAll('.dropdown-menu.show').forEach(dropdown => {
dropdown.classList.remove('show');
});
}
});
}
/**
* Intégration avec le code legacy
*/
setupLegacyIntegration() {
// Maintenir la compatibilité avec l'ancien namespace
window.Notytex = {
// API de transition
utils: this.core.utils,
state: this.core.state,
emit: this.core.emit.bind(this.core),
on: this.core.on.bind(this.core),
// Composants
notifications: this.core.getModule('notifications'),
modals: this.core.getModule('modals'),
filters: this.core.getModule('filters'),
// Méthodes legacy
showToast: (message, type = 'info') => {
this.core.getModule('notifications')?.show({ message, type });
},
showModal: (modalId) => {
this.core.getModule('modals')?.open(modalId);
},
closeModal: (modalId) => {
this.core.getModule('modals')?.close(modalId);
}
};
// Maintenir les anciennes fonctions globales si utilisées
if (!window.showToast) {
window.showToast = window.Notytex.showToast;
}
}
/**
* Optimisations de performance
*/
setupPerformanceOptimizations() {
// Lazy loading des images
this.setupLazyLoading();
// Preload des liens importants
this.setupLinkPreloading();
// Monitoring des performances
this.setupPerformanceMonitoring();
}
/**
* Lazy loading des images
*/
setupLazyLoading() {
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.getAttribute('data-src');
if (src) {
img.src = src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
}
});
});
this.core.utils.queryAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
}
}
/**
* Preload des liens
*/
setupLinkPreloading() {
// Preload au hover pour les liens internes
const links = this.core.utils.queryAll('a[href^="/"], a[href^="./"]');
const preloadedUrls = new Set();
links.forEach(link => {
link.addEventListener('mouseenter', () => {
const href = link.href;
if (!preloadedUrls.has(href)) {
const preloadLink = document.createElement('link');
preloadLink.rel = 'prefetch';
preloadLink.href = href;
document.head.appendChild(preloadLink);
preloadedUrls.add(href);
}
}, { once: true });
});
}
/**
* Monitoring des performances
*/
setupPerformanceMonitoring() {
if ('PerformanceObserver' in window) {
// Observer les métriques de performance
const perfObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'measure') {
}
});
});
perfObserver.observe({ entryTypes: ['measure', 'navigation'] });
}
}
/**
* Créer un placeholder pour un module manquant
*/
createModulePlaceholder(moduleName) {
const placeholder = {
name: moduleName,
placeholder: true,
init: () => {},
destroy: () => {}
};
this.core.modules.set(moduleName.toLowerCase(), placeholder);
}
}
// Instance globale
const notytexApp = new NotytexApp();
// Auto-initialisation
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => notytexApp.init());
} else {
notytexApp.init();
}
// Export pour utilisation
export default notytexApp;