Merge pull request #1 from JavMB/copilot/review-aside-bar-implementation
[WIP] Review implementation of aside sidebar
This commit is contained in:
commit
90f2ba378b
|
|
@ -917,7 +917,6 @@
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// 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 [];
|
||||||
|
}
|
||||||
|
// Filter pages based on authentication requirement
|
||||||
|
return pagesRegistry.filter(page => !page.requiresAuth || isAuthenticated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get page by route
|
||||||
|
export function getPageByRoute(route) {
|
||||||
|
return pagesRegistry.find(page => page.route === route);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
import { auth } from './services/auth.js';
|
import { auth } from './services/auth.js';
|
||||||
import { renderLoginPage } from './pages/LoginPage.js';
|
import { renderLoginPage } from './pages/LoginPage.js';
|
||||||
import { renderDashboard } from './pages/DashboardPage.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() {
|
export function initRouter() {
|
||||||
const app = document.querySelector('#app');
|
const app = document.querySelector('#app');
|
||||||
|
|
||||||
function navigate() {
|
function navigate() {
|
||||||
const hash = window.location.hash || '#login';
|
const hash = window.location.hash || '#login';
|
||||||
|
const route = hash.substring(1); // Remove the # symbol
|
||||||
|
|
||||||
if (!auth.checkAuth() && hash !== '#login') {
|
if (!auth.checkAuth() && hash !== '#login') {
|
||||||
window.location.hash = '#login';
|
window.location.hash = '#login';
|
||||||
|
|
@ -15,19 +19,50 @@ export function initRouter() {
|
||||||
|
|
||||||
app.innerHTML = '';
|
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) {
|
switch (hash) {
|
||||||
case '#login':
|
case '#login':
|
||||||
app.appendChild(renderLoginPage(() => {
|
app.appendChild(renderLoginPage(() => {
|
||||||
window.location.hash = '#dashboard';
|
window.location.hash = '#dashboard';
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
case '#dashboard':
|
|
||||||
app.appendChild(renderDashboard());
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
window.location.hash = '#login';
|
window.location.hash = '#login';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('hashchange', navigate);
|
window.addEventListener('hashchange', navigate);
|
||||||
navigate();
|
navigate();
|
||||||
|
|
|
||||||
203
src/style.css
203
src/style.css
|
|
@ -37,8 +37,6 @@ a:hover {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
@ -49,10 +47,8 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
max-width: 1280px;
|
width: 100%;
|
||||||
margin: 0 auto;
|
min-height: 100vh;
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
|
@ -283,3 +279,198 @@ button:focus-visible {
|
||||||
.card-stat.negative .card-stat-value {
|
.card-stat.negative .card-stat-value {
|
||||||
color: var(--danger);
|
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%;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue