Initial commit: sap-intercompany-viewer

Frontend Angular 22 (standalone, signals, Material 3) para
sap-intercompany-api. Pantallas: log de eventos/intentos, CRUD de
databases, listado/alta/detalle de scenarios.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5 committed 2026-09-16 08:25:01 -05:00
commit cf793c88f2
57 files changed
+10675

No files matched your search

@@ -0,0 +1,51 @@
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.tile {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
border-radius: 8px;
background: var(--mat-sys-surface-container-low, rgba(0, 0, 0, 0.03));
border-left: 4px solid #757575;
}
.tile mat-icon {
font-size: 32px;
width: 32px;
height: 32px;
opacity: 0.7;
}
.tile-value {
font-size: 1.8rem;
font-weight: 600;
line-height: 1.1;
}
.tile-label {
opacity: 0.7;
font-size: 0.85rem;
}
.tile-success {
border-left-color: #2e7d32;
}
.tile-success mat-icon {
color: #2e7d32;
opacity: 1;
}
.tile-danger {
border-left-color: #c62828;
}
.tile-danger mat-icon {
color: #c62828;
opacity: 1;
}
@@ -0,0 +1,17 @@
<h1>Dashboard</h1>
@if (loading()) {
<mat-spinner diameter="32" />
} @else {
<div class="tiles">
@for (tile of tiles(); track tile.label) {
<div class="tile" [class.tile-success]="tile.tone === 'success'" [class.tile-danger]="tile.tone === 'danger'">
<mat-icon>{{ tile.icon }}</mat-icon>
<div class="tile-body">
<div class="tile-value">{{ tile.value }}</div>
<div class="tile-label">{{ tile.label }}</div>
</div>
</div>
}
</div>
}
@@ -0,0 +1,56 @@
import { Component, computed, inject, OnInit, signal } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Dashboard } from '../dashboard';
import { DashboardStats } from '../../core/models';
type TileTone = 'neutral' | 'success' | 'danger';
interface Tile {
label: string;
value: number;
icon: string;
tone: TileTone;
}
@Component({
imports: [MatIconModule, MatProgressSpinnerModule],
selector: 'app-dashboard-page',
styleUrl: './dashboard-page.css',
templateUrl: './dashboard-page.html',
})
export class DashboardPage implements OnInit {
private readonly dashboardApi = inject(Dashboard);
private readonly snackBar = inject(MatSnackBar);
protected readonly loading = signal(true);
protected readonly stats = signal<DashboardStats | null>(null);
protected readonly tiles = computed<Tile[]>(() => {
const s = this.stats();
if (!s) return [];
return [
{ label: 'Escenarios activos', value: s.activeScenarios, icon: 'schema', tone: 'neutral' },
{ label: 'Bases registradas', value: s.totalDatabases, icon: 'storage', tone: 'neutral' },
{ label: 'Mensajes sin procesar', value: s.pendingEvents, icon: 'inbox', tone: 'neutral' },
{ label: 'Procesados hoy', value: s.doneToday, icon: 'check_circle', tone: 'success' },
{ label: 'Fallas hoy', value: s.failedToday, icon: 'error', tone: 'danger' },
{ label: 'Procesados (últimos 7 días)', value: s.doneLast7Days, icon: 'check_circle', tone: 'success' },
{ label: 'Fallas (últimos 7 días)', value: s.failedLast7Days, icon: 'error', tone: 'danger' },
];
});
ngOnInit() {
this.dashboardApi.getStats().subscribe({
next: (stats) => {
this.stats.set(stats);
this.loading.set(false);
},
error: () => {
this.loading.set(false);
this.snackBar.open('No se pudieron cargar los indicadores', 'Cerrar', { duration: 4000 });
},
});
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Service, inject } from '@angular/core';
import { Api } from '../core/api';
import { DashboardStats } from '../core/models';
@Service()
export class Dashboard {
private readonly api = inject(Api);
getStats() {
return this.api.get<DashboardStats>('/dashboard/stats');
}
}