Implement adaptable sidebar navigation component

Co-authored-by: JavMB <114607632+JavMB@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-16 20:22:14 +00:00
parent 70e9cba2b7
commit d8779ca2f6
6 changed files with 355 additions and 17 deletions

1
package-lock.json generated
View File

@ -917,7 +917,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},

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

@ -0,0 +1,48 @@
export function createSidebar(pages, currentPage) {
const sidebar = document.createElement('aside');
sidebar.className = 'sidebar';
sidebar.id = 'sidebar';
const sidebarHeader = document.createElement('div');
sidebarHeader.className = 'sidebar-header';
sidebarHeader.innerHTML = /*html*/`
<h2>Doli App</h2>
<button class="sidebar-toggle" id="sidebar-toggle" aria-label="Toggle sidebar">
<span class="toggle-icon">☰</span>
</button>
`;
const nav = document.createElement('nav');
nav.className = 'sidebar-nav';
const navList = document.createElement('ul');
navList.className = 'sidebar-menu';
pages.forEach(page => {
const listItem = document.createElement('li');
const link = document.createElement('a');
link.href = `#${page.route}`;
link.className = 'sidebar-link';
if (page.route === currentPage) {
link.classList.add('active');
}
link.innerHTML = /*html*/`
<span class="sidebar-icon">${page.icon || '📄'}</span>
<span class="sidebar-text">${page.name}</span>
`;
listItem.appendChild(link);
navList.appendChild(listItem);
});
nav.appendChild(navList);
sidebar.appendChild(sidebarHeader);
sidebar.appendChild(nav);
// Toggle functionality
const toggleBtn = sidebarHeader.querySelector('#sidebar-toggle');
toggleBtn.addEventListener('click', () => {
sidebar.classList.toggle('collapsed');
});
return sidebar;
}

View File

@ -0,0 +1,46 @@
// Registry of all pages available in the application
export const pagesRegistry = [
{
route: 'dashboard',
name: 'Dashboard',
icon: '🏠',
requiresAuth: true
},
{
route: 'test',
name: 'Test Page',
icon: '🧪',
requiresAuth: true
},
{
route: 'invoices',
name: 'Facturas',
icon: '📄',
requiresAuth: true
},
{
route: 'clients',
name: 'Clientes',
icon: '👥',
requiresAuth: true
},
{
route: 'settings',
name: 'Configuración',
icon: '⚙️',
requiresAuth: true
}
];
// Get pages that the user can access
export function getAvailablePages(isAuthenticated) {
if (!isAuthenticated) {
return [];
}
return pagesRegistry.filter(page => !page.requiresAuth || isAuthenticated);
}
// Get page by route
export function getPageByRoute(route) {
return pagesRegistry.find(page => page.route === route);
}

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

@ -0,0 +1,19 @@
export function renderTestPage() {
const container = document.createElement('div');
container.className = 'test-page';
container.innerHTML = /*html*/`
<h1>Test Page</h1>
<p>This is a test page to verify sidebar navigation functionality.</p>
<div class="test-content">
<p>The sidebar should be visible and allow navigation between different pages.</p>
<button id="test-btn" class="test-button">Test Button</button>
</div>
`;
container.querySelector('#test-btn').addEventListener('click', () => {
alert('Test button clicked!');
});
return container;
}

View File

@ -1,12 +1,16 @@
import { auth } from './services/auth.js';
import { renderLoginPage } from './pages/LoginPage.js';
import { renderDashboard } from './pages/DashboardPage.js';
import { renderTestPage } from './pages/test.js';
import { createSidebar } from './components/Sidebar.js';
import { getAvailablePages } from './pages/pagesRegistry.js';
export function initRouter() {
const app = document.querySelector('#app');
function navigate() {
const hash = window.location.hash || '#login';
const route = hash.substring(1); // Remove the # symbol
if (!auth.checkAuth() && hash !== '#login') {
window.location.hash = '#login';
@ -15,19 +19,50 @@ export function initRouter() {
app.innerHTML = '';
// For authenticated routes, add sidebar and main content wrapper
if (auth.checkAuth() && hash !== '#login') {
const layout = document.createElement('div');
layout.className = 'app-layout';
// Create sidebar
const pages = getAvailablePages(true);
const sidebar = createSidebar(pages, route);
// Create main content area
const mainContent = document.createElement('main');
mainContent.className = 'main-content';
let pageContent;
switch (hash) {
case '#dashboard':
pageContent = renderDashboard();
break;
case '#test':
pageContent = renderTestPage();
break;
default:
// For unregistered routes, redirect to dashboard
window.location.hash = '#dashboard';
return;
}
mainContent.appendChild(pageContent);
layout.appendChild(sidebar);
layout.appendChild(mainContent);
app.appendChild(layout);
} else {
// For login page, no sidebar
switch (hash) {
case '#login':
app.appendChild(renderLoginPage(() => {
window.location.hash = '#dashboard';
}));
break;
case '#dashboard':
app.appendChild(renderDashboard());
break;
default:
window.location.hash = '#login';
}
}
}
window.addEventListener('hashchange', navigate);
navigate();

View File

@ -37,8 +37,6 @@ a:hover {
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
@ -49,10 +47,8 @@ h1 {
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
width: 100%;
min-height: 100vh;
}
.logo {
@ -283,3 +279,198 @@ button:focus-visible {
.card-stat.negative .card-stat-value {
color: var(--danger);
}
/* App Layout with Sidebar */
.app-layout {
display: flex;
min-height: 100vh;
width: 100%;
}
/* Sidebar Styles */
.sidebar {
width: 250px;
background-color: var(--card-bg);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
transition: width 0.3s ease, transform 0.3s ease;
position: relative;
z-index: 100;
}
.sidebar-header {
padding: 1.5rem 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-header h2 {
margin: 0;
font-size: 1.5rem;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-toggle {
background: transparent;
border: none;
cursor: pointer;
padding: 0.5rem;
color: var(--text-secondary);
font-size: 1.25rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.sidebar-toggle:hover {
background-color: var(--border-color);
border-color: transparent;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 1rem 0;
}
.sidebar-menu {
list-style: none;
margin: 0;
padding: 0;
}
.sidebar-menu li {
margin: 0;
}
.sidebar-link {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
color: var(--text-secondary);
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
cursor: pointer;
}
.sidebar-link:hover {
background-color: var(--border-color);
color: var(--text-primary);
}
.sidebar-link.active {
background-color: rgba(37, 99, 235, 0.1);
color: var(--primary);
border-left: 3px solid var(--primary);
}
.sidebar-icon {
font-size: 1.25rem;
margin-right: 0.75rem;
min-width: 1.5rem;
text-align: center;
}
.sidebar-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Collapsed Sidebar State */
.sidebar.collapsed {
width: 70px;
}
.sidebar.collapsed .sidebar-header h2,
.sidebar.collapsed .sidebar-text {
opacity: 0;
width: 0;
overflow: hidden;
}
.sidebar.collapsed .sidebar-icon {
margin-right: 0;
}
/* Main Content Area */
.main-content {
flex: 1;
overflow-y: auto;
background-color: #f8fafc;
}
.main-content > div {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 1000;
transform: translateX(0);
}
.sidebar.collapsed {
transform: translateX(-100%);
width: 250px;
}
.main-content {
margin-left: 0;
}
}
/* Test Page Styles */
.test-page {
padding: 2rem;
}
.test-page h1 {
color: var(--text-primary);
margin-bottom: 1rem;
font-size: 2rem;
}
.test-content {
background: var(--card-bg);
padding: 2rem;
border-radius: 12px;
border: 1px solid var(--border-color);
margin-top: 1rem;
}
.test-button {
background-color: var(--success);
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
margin-top: 1rem;
}
.test-button:hover {
background-color: var(--success-hover);
border-color: transparent;
}
/* Adjust dashboard and login for new layout */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
width: 100%;
}