diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js new file mode 100644 index 0000000..24e2e6c --- /dev/null +++ b/src/components/Sidebar.js @@ -0,0 +1,48 @@ +import { auth } from '../services/auth.js'; +import { getPages } from '../pages/pagesRegistry.js'; + +export function renderSidebar() { + const sidebar = document.createElement('aside'); + sidebar.className = 'sidebar'; + + const appName = document.createElement('div'); + appName.className = 'sidebar-brand'; + appName.textContent = 'Aventa'; + + const nav = document.createElement('nav'); + nav.className = 'sidebar-nav'; + + const items = getPages().map(p => ({ label: p.label, hash: p.hash })); + + items.forEach(({ label, hash }) => { + const link = document.createElement('a'); + link.className = 'nav-link'; + link.href = hash; + link.textContent = label; + + if (window.location.hash === hash) { + link.classList.add('active'); + } + + nav.appendChild(link); + }); + + const footer = document.createElement('div'); + footer.className = 'sidebar-footer'; + + const logoutBtn = document.createElement('button'); + logoutBtn.className = 'logout-button'; + logoutBtn.textContent = 'Log out'; + logoutBtn.addEventListener('click', () => { + auth.logout(); + window.location.hash = '#login'; + }); + + footer.appendChild(logoutBtn); + + sidebar.appendChild(appName); + sidebar.appendChild(nav); + sidebar.appendChild(footer); + + return sidebar; +} diff --git a/src/pages/pagesRegistry.js b/src/pages/pagesRegistry.js new file mode 100644 index 0000000..e68ffe9 --- /dev/null +++ b/src/pages/pagesRegistry.js @@ -0,0 +1,53 @@ +function toLabel(name) { + return name + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/^./, s => s.toUpperCase()); +} + +function toHash(name) { + return `#${name.toLowerCase()}`; +} + +function findRender(mod) { + if (typeof mod.renderPage === 'function') return mod.renderPage; + for (const key of Object.keys(mod)) { + const val = mod[key]; + if (typeof val === 'function' && /^render/i.test(key)) return val; + } + if (typeof mod.default === 'function') return mod.default; + return null; +} + +const modules = import.meta.glob('./*.js', { eager: true }); + +const bannedPages = ['LoginPage.js']; + +const pages = Object.entries(modules) + .map(([path, mod]) => { + const file = path.split('/').pop(); + const base = file.replace('.js', ''); + if (bannedPages.includes(file)) return null; + + // Derive page name from filename, strip common suffix "Page" + const name = base.replace(/Page$/, ''); + const render = findRender(mod); + if (!render) return null; + + return { + path, + name, + label: toLabel(name), + hash: toHash(name), + render + }; + }) + .filter(Boolean) + .sort((a, b) => a.label.localeCompare(b.label)); + +export function getPages() { + return pages.slice(); +} + +export function getPageByHash(hash) { + return pages.find(p => p.hash === hash) || null; +} diff --git a/src/pages/test.js b/src/pages/test.js new file mode 100644 index 0000000..5579423 --- /dev/null +++ b/src/pages/test.js @@ -0,0 +1,11 @@ +export function renderDashboard() { + const container = document.createElement('div'); + container.className = 'dashboard'; + + container.innerHTML = ` +
+

esto es un test

+
+ `; + return container; +} diff --git a/src/router.js b/src/router.js index a565ba9..4055b12 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import { auth } from './services/auth.js'; import { renderLoginPage } from './pages/LoginPage.js'; -import { renderDashboard } from './pages/DashboardPage.js'; +import { getPageByHash } from './pages/pagesRegistry.js'; +import { renderSidebar } from './components/Sidebar.js'; export function initRouter() { const app = document.querySelector('#app'); @@ -21,11 +22,23 @@ export function initRouter() { window.location.hash = '#dashboard'; })); break; - case '#dashboard': - app.appendChild(renderDashboard()); + default: { + // Dynamic page rendering with sidebar + const page = getPageByHash(hash); + if (!page) { + window.location.hash = '#dashboard'; + return; + } + const layout = document.createElement('div'); + layout.className = 'app-layout'; + const sidebar = renderSidebar(); + const main = document.createElement('main'); + main.appendChild(page.render()); + layout.appendChild(sidebar); + layout.appendChild(main); + app.appendChild(layout); break; - default: - window.location.hash = '#login'; + } } } diff --git a/src/style.css b/src/style.css index 16aad82..26d6b27 100644 --- a/src/style.css +++ b/src/style.css @@ -194,10 +194,16 @@ button:focus-visible { justify-content: space-between; align-items: center; margin-bottom: 2rem; + padding: 1.5rem; + background: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + min-height: 100px; } .dashboard-header h1 { - font-size: 2rem; + font-size: 2.4rem; margin: 0; } @@ -283,3 +289,58 @@ button:focus-visible { .card-stat.negative .card-stat-value { color: var(--danger); } + +/* Layout with sidebar */ +.app-layout { + display: grid; + grid-template-columns: 260px 1fr; + gap: 1.5rem; + align-items: start; +} + +.sidebar { + position: sticky; + top: 1rem; + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + height: fit-content; +} + +.sidebar-brand { + font-weight: 700; + font-size: 1.2rem; + margin-bottom: 1rem; +} + +.sidebar-nav { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.nav-link { + display: block; + padding: 0.6rem 0.8rem; + border-radius: 8px; + color: var(--text-primary); + text-decoration: none; + border: 1px solid transparent; +} + +.nav-link:hover { + background: #f1f5f9; +} + +.nav-link.active { + background: #eff6ff; + border-color: #bfdbfe; + color: #1e40af; +} + +.sidebar-footer { + margin-top: 1rem; + display: flex; + justify-content: flex-start; +}