Poner una barra lateral adaptable

This commit is contained in:
Алекс 2026-01-16 20:52:39 +01:00
parent 6932c71717
commit ac84ee86c1
5 changed files with 192 additions and 6 deletions

48
src/components/Sidebar.js Normal file
View File

@ -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;
}

View File

@ -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;
}

11
src/pages/test.js Normal file
View File

@ -0,0 +1,11 @@
export function renderDashboard() {
const container = document.createElement('div');
container.className = 'dashboard';
container.innerHTML = `
<div class="dashboard-content">
<p>esto es un test</p>
</div>
`;
return container;
}

View File

@ -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';
}
}
}

View File

@ -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;
}