Documentation Index
Fetch the complete documentation index at: https://mintlify.com/lucavallini/aibar-frontend/llms.txt
Use this file to discover all available pages before exploring further.
Aibar SRL App organises its reusable interface building blocks under src/app/shared/. The layout/ subdirectory contains the authenticated shell that every protected route is rendered inside, while components/ contains the individual standalone components that compose that shell — and that can also be used independently in feature pages. All components are declared with standalone: true, so they are imported directly by the consuming component rather than through a shared module.
Layout
File: src/app/shared/layout/layout.tsLayout is the authenticated shell that wraps every protected route. It composes Header and Sidebar, manages the sidebar open/closed state with a Signal, and exposes a <router-outlet> for the active feature page.import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Header } from '../components/header/header';
import { Sidebar } from '../components/sidebar/sidebar';
@Component({
selector: 'app-layout',
standalone: true,
imports: [RouterOutlet, Header, Sidebar],
templateUrl: './layout.html',
styleUrl: './layout.css'
})
export class Layout {
sidebarAbierta = signal(false);
toggleSidebar(): void {
this.sidebarAbierta.set(!this.sidebarAbierta());
}
}
| Member | Type | Description |
|---|
sidebarAbierta | Signal<boolean> | Controls whether the sidebar drawer is open. Toggled by Header’s toggleSidebar output. |
toggleSidebar() | void | Inverts sidebarAbierta. Bound to the (toggleSidebar) event emitted by Header. |
Layout is used as the parent route component for all protected routes in app.routes.ts. Feature pages are rendered inside its <router-outlet>.// app.routes.ts (excerpt)
{
path: '',
component: Layout,
canActivate: [authGuard],
children: [
{ path: 'viajes', component: Viajes },
{ path: 'choferes', component: Choferes },
// ...
]
}
Inside layout.html, the sidebar visibility and toggle callback are wired together:<!-- layout.html (conceptual structure) -->
<app-header (toggleSidebar)="toggleSidebar()" />
<app-sidebar [abierta]="sidebarAbierta()" />
<main>
<router-outlet />
</main>
File: src/app/shared/components/header/header.tsHeader renders the top navigation bar. It injects AuthService to perform logout and ThemeService to toggle the visual theme. It emits a toggleSidebar event so the parent Layout can control the sidebar state.import { Component, EventEmitter, Output } from '@angular/core';
import { AuthService } from '../../../core/services/auth.service';
import { ThemeService } from '../../../core/services/theme.service';
@Component({
selector: 'app-header',
standalone: true,
imports: [],
templateUrl: './header.html',
styleUrl: './header.css'
})
export class Header {
@Output() toggleSidebar = new EventEmitter<void>();
constructor(
private authService: AuthService,
public themeService: ThemeService
) {}
onToggleSidebar(): void {
this.toggleSidebar.emit();
}
onToggleTema(): void {
this.themeService.alternar();
}
cerrarSesion(): void {
this.authService.logout();
}
}
| Member | Type | Description |
|---|
toggleSidebar | @Output() EventEmitter<void> | Emitted when the hamburger / menu button is clicked. |
onToggleSidebar() | void | Handler that emits toggleSidebar. |
onToggleTema() | void | Delegates to ThemeService.alternar() to switch dark/light mode. |
cerrarSesion() | void | Delegates to AuthService.logout(), clearing the token and redirecting to /login. |
Header is used exclusively inside Layout. The parent binds to the (toggleSidebar) output to open and close the sidebar.<!-- layout.html -->
<app-header (toggleSidebar)="toggleSidebar()" />
themeService is declared public so the template can read the current theme state:<!-- header.html (conceptual) -->
<button (click)="onToggleTema()">
{{ themeService.esOscuro() ? '☀️ Claro' : '🌙 Oscuro' }}
</button>
<button (click)="cerrarSesion()">Cerrar sesión</button>
File: src/app/shared/components/sidebar/sidebar.tsSidebar renders the main navigation menu. It accepts an abierta input to control its visible/hidden state (used for the mobile drawer animation) and filters menu items by the logged-in user’s role through AuthService.getRol().ItemMenu interface:interface ItemMenu {
label: string;
ruta: string;
soloAdmin: boolean;
}
Full menu item list:itemsMenu: ItemMenu[] = [
{ label: 'Viajes', ruta: '/viajes', soloAdmin: false },
{ label: 'Choferes', ruta: '/choferes', soloAdmin: false },
{ label: 'Camiones', ruta: '/camiones', soloAdmin: false },
{ label: 'Multas', ruta: '/multas', soloAdmin: false },
{ label: 'Combustible', ruta: '/combustible', soloAdmin: false },
{ label: 'Auditoría', ruta: '/auditoria', soloAdmin: true },
{ label: 'Usuarios', ruta: '/usuarios', soloAdmin: true },
];
| Member | Type | Description |
|---|
abierta | @Input() boolean | When true, applies the open/visible CSS state to the sidebar drawer. |
itemsVisibles | get ItemMenu[] | Computed getter that returns only items the current user is allowed to see. Items with soloAdmin: true are excluded unless the user’s role is 'administrador'. |
get itemsVisibles(): ItemMenu[] {
const esAdmin = this.authService.getRol() === 'administrador';
return this.itemsMenu.filter(item => !item.soloAdmin || esAdmin);
}
Sidebar is used inside Layout, receiving the sidebarAbierta signal value as its [abierta] input.<!-- layout.html -->
<app-sidebar [abierta]="sidebarAbierta()" />
Inside sidebar.html, the visible items are rendered with routerLink and routerLinkActive:<!-- sidebar.html (conceptual) -->
@for (item of itemsVisibles; track item.ruta) {
<a
[routerLink]="item.ruta"
routerLinkActive="activo"
>
{{ item.label }}
</a>
}
Paginacion
File: src/app/shared/components/paginacion/paginacion.tsPaginacion is a stateless pagination bar. It receives the current page, total pages, and total record count as inputs, and emits a cambioPagina event with the new page number whenever the user navigates.import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-paginacion',
standalone: true,
imports: [],
templateUrl: './paginacion.html',
styleUrl: './paginacion.css'
})
export class Paginacion {
@Input() pagina = 1;
@Input() totalPaginas = 1;
@Input() total = 0;
@Output() cambioPagina = new EventEmitter<number>();
irAPagina(nueva: number): void {
if (nueva < 1 || nueva > this.totalPaginas || nueva === this.pagina) return;
this.cambioPagina.emit(nueva);
}
}
| Member | Type | Description |
|---|
pagina | @Input() number | The currently active page (1-based). Default: 1. |
totalPaginas | @Input() number | Total number of pages. Default: 1. |
total | @Input() number | Total record count across all pages (used for display). Default: 0. |
cambioPagina | @Output() EventEmitter<number> | Emitted when the user requests a different page. The payload is the target page number. |
irAPagina(nueva) | void | Guards against out-of-range or same-page navigation before emitting. |
Bind Paginacion directly to the fields of a RespuestaPaginada<T> response. The parent component holds the current page in a property and reloads data whenever cambioPagina fires.// viajes.ts (excerpt)
pagina = 1;
totalPaginas = 1;
total = 0;
cargarViajes(): void {
this.viajesService.listar(undefined, this.pagina).subscribe(resp => {
this.viajes = resp.items;
this.total = resp.total;
this.totalPaginas = resp.total_paginas;
});
}
onCambioPagina(nueva: number): void {
this.pagina = nueva;
this.cargarViajes();
}
<!-- viajes.html (excerpt) -->
<app-paginacion
[pagina]="pagina"
[totalPaginas]="totalPaginas"
[total]="total"
(cambioPagina)="onCambioPagina($event)"
/>
Confirmar
File: src/app/shared/components/confirmar/confirmar.tsConfirmar is a modal dialog component for destructive or irreversible actions. It renders a customisable title, message, and confirm button label, and emits either a confirmar or cancelar event depending on what the user clicks. The parent is responsible for showing and hiding the modal based on these events.import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-confirmar',
standalone: true,
imports: [],
templateUrl: './confirmar.html',
styleUrl: './confirmar.css'
})
export class Confirmar {
@Input() titulo = '¿Confirmás esta acción?';
@Input() mensaje = 'Esta acción no se puede deshacer.';
@Input() textoConfirmar = 'Confirmar';
@Output() confirmar = new EventEmitter<void>();
@Output() cancelar = new EventEmitter<void>();
}
| Member | Type | Description |
|---|
titulo | @Input() string | Modal heading. Default: '¿Confirmás esta acción?' |
mensaje | @Input() string | Body text describing the action. Default: 'Esta acción no se puede deshacer.' |
textoConfirmar | @Input() string | Label for the confirm button. Default: 'Confirmar' |
confirmar | @Output() EventEmitter<void> | Emitted when the user clicks the confirm button. |
cancelar | @Output() EventEmitter<void> | Emitted when the user clicks the cancel button or dismisses the modal. |
The parent component conditionally renders Confirmar with @if, passing context-specific strings and wiring both outputs.// choferes.ts (example usage)
mostrarConfirmarBaja = false;
idChoferADarDeBaja: string | null = null;
abrirConfirmacionBaja(id: string): void {
this.idChoferADarDeBaja = id;
this.mostrarConfirmarBaja = true;
}
onConfirmarBaja(): void {
if (!this.idChoferADarDeBaja) return;
this.choferesService.darDeBaja(this.idChoferADarDeBaja).subscribe(() => {
this.mostrarConfirmarBaja = false;
this.cargarChoferes();
});
}
onCancelarBaja(): void {
this.mostrarConfirmarBaja = false;
}
<!-- choferes.html (excerpt) -->
@if (mostrarConfirmarBaja) {
<app-confirmar
titulo="¿Dar de baja al chofer?"
mensaje="El chofer quedará inactivo y no podrá ser asignado a nuevos viajes."
textoConfirmar="Dar de baja"
(confirmar)="onConfirmarBaja()"
(cancelar)="onCancelarBaja()"
/>
}