/** * ViewSwitcher — Gestor Universal y Extensible de Modos de Visualización * * Permite alternar instantáneamente entre vista de Tabla (grilla densa) * y vista de Tarjetas (mosaico responsive), preservando filtros, búsquedas * y persistiendo la preferencia del usuario en localStorage. */ (function () { 'use strict'; class ViewSwitcherManager { constructor() { this.init(); } init() { document.addEventListener('DOMContentLoaded', () => { this.setupViewSwitchers(); }); } setupViewSwitchers() { const switchers = document.querySelectorAll('.view-switcher-group'); if (!switchers.length) return; switchers.forEach(group => { const context = group.getAttribute('data-context') || 'default'; const defaultView = group.getAttribute('data-default-view') || 'cards'; // Determinar vista inicial: // 1. URL search param (?display=...) // 2. localStorage // 3. defaultView del contenedor const urlParams = new URLSearchParams(window.location.search); const urlView = urlParams.get('display'); const storedView = localStorage.getItem(`view_preference_${context}`); const activeView = urlView || storedView || defaultView; this.applyView(context, activeView, false); // Asignar listeners a los botones del selector const buttons = group.querySelectorAll('.view-switcher-btn'); buttons.forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); const targetView = btn.getAttribute('data-view'); if (targetView) { this.applyView(context, targetView, true); } }); }); }); } /** * Aplica la vista seleccionada en el contexto dado. * @param {string} context - Identificador del módulo (ej: 'classrooms', 'reservations') * @param {string} viewName - Nombre de la vista ('table', 'cards', etc.) * @param {boolean} persist - Si debe persistirse en localStorage y actualizar URL */ applyView(context, viewName, persist = true) { // 1. Actualizar botones activos en el grupo selector const group = document.querySelector(`.view-switcher-group[data-context="${context}"]`); if (group) { const buttons = group.querySelectorAll('.view-switcher-btn'); buttons.forEach(btn => { const btnView = btn.getAttribute('data-view'); if (btnView === viewName) { btn.classList.remove('btn-outline-secondary'); btn.classList.add('btn-primary', 'active'); btn.setAttribute('aria-pressed', 'true'); } else { btn.classList.remove('btn-primary', 'active'); btn.classList.add('btn-outline-secondary'); btn.setAttribute('aria-pressed', 'false'); } }); } // 2. Conmutar visibilidad de los contenedores de vista const containers = document.querySelectorAll(`[data-view-context="${context}"]`); let matched = false; containers.forEach(container => { const containerView = container.getAttribute('data-view-container'); if (containerView === viewName) { container.classList.remove('d-none'); container.removeAttribute('aria-hidden'); matched = true; } else { container.classList.add('d-none'); container.setAttribute('aria-hidden', 'true'); } }); // Fallback: si la vista solicitada no tiene contenedor, restaurar a la primera visible if (!matched && containers.length > 0) { containers[0].classList.remove('d-none'); viewName = containers[0].getAttribute('data-view-container') || 'cards'; } // 3. Persistir preferencia y actualizar formularios y URL if (persist) { try { localStorage.setItem(`view_preference_${context}`, viewName); } catch (e) { console.warn('localStorage no disponible para guardar preferencia de vista:', e); } // Actualizar parámetro display en la URL sin recargar const url = new URL(window.location.href); url.searchParams.set('display', viewName); window.history.replaceState({}, '', url); } // 4. Sincronizar campo hidden 'display' en formularios de búsqueda/filtro const forms = document.querySelectorAll('form'); forms.forEach(form => { let hiddenInput = form.querySelector('input[name="display"]'); if (!hiddenInput) { hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = 'display'; form.appendChild(hiddenInput); } hiddenInput.value = viewName; }); // 5. Emitir evento personalizado para componentes dinámicos window.dispatchEvent(new CustomEvent('viewModeChanged', { detail: { context, view: viewName } })); } } // Instanciar singleton window.ViewSwitcher = new ViewSwitcherManager(); })();