469 lines
14 KiB
JavaScript
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; |