import { Component, OnInit, inject, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { StatusBadgeComponent } from '../../../shared/components/status-badge/status-badge.component'; import { BlockchainExplorerMiniComponent } from '../../../shared/components/blockchain-explorer-mini/blockchain-explorer-mini.component'; import { ApiService } from '../../../core/services/api.service'; import { AdminStatsDto } from '../../../api/models'; @Component({ selector: 'app-admin-dashboard', standalone: true, imports: [ CommonModule, RouterModule, MatCardModule, MatIconModule, MatButtonModule, MatProgressSpinnerModule, StatusBadgeComponent, BlockchainExplorerMiniComponent, ], template: `
Admin Dashboard

Platform Overview

Monitor and manage the Goa GEL Blockchain Platform

@if (loading()) {

Loading dashboard...

} @else if (stats()) {
description
{{ stats()!.totalRequests }}
Total Requests
check_circle
{{ stats()!.totalApprovals }}
Approvals
folder_open
{{ stats()!.totalDocuments }}
Documents
business
{{ stats()!.totalDepartments }}
Departments
people
{{ stats()!.totalApplicants }}
Applicants
link
{{ stats()!.totalBlockchainTransactions }}
Blockchain Tx
pie_chart

Requests by Status

@for (item of stats()!.requestsByStatus; track item.status) {
{{ item.count }}
}
flash_on

Quick Actions

business
Manage Departments
account_tree
Manage Workflows
history
View Audit Logs
webhook
Webhooks
}
`, styles: [` .page-container { padding: 0; } /* Welcome Section */ .welcome-section { background: linear-gradient(135deg, var(--dbim-blue-dark, #1D0A69) 0%, var(--dbim-blue-mid, #2563EB) 100%); color: white; padding: 32px; margin: -24px -24px 24px -24px; position: relative; overflow: hidden; &::before { content: ''; position: absolute; top: -50%; right: -10%; width: 50%; height: 200%; background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 50%); pointer-events: none; } } .welcome-content { max-width: 1400px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; gap: 24px; position: relative; z-index: 1; @media (max-width: 768px) { flex-direction: column; align-items: flex-start; } } .welcome-text { .greeting { font-size: 0.9rem; opacity: 0.8; text-transform: uppercase; letter-spacing: 1px; } h1 { margin: 8px 0; font-size: 2rem; font-weight: 700; } .subtitle { margin: 0; opacity: 0.85; font-size: 0.95rem; } } .quick-actions { display: flex; gap: 12px; } .action-btn { &.primary { background: white; color: var(--dbim-blue-dark, #1D0A69); } &:not(.primary) { color: white; border-color: rgba(255, 255, 255, 0.5); &:hover { background: rgba(255, 255, 255, 0.1); } } mat-icon { margin-right: 8px; } } .loading-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 64px; gap: 16px; p { color: var(--dbim-grey-2, #8E8E8E); } } /* Stats Section */ .stats-section { margin-bottom: 24px; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; } .stat-card { display: flex; align-items: center; gap: 16px; padding: 24px; border-radius: 16px !important; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; color: white; &:hover { transform: translateY(-4px); box-shadow: var(--shadow-elevated, 0 8px 24px rgba(0, 0, 0, 0.15)); } &.requests { background: linear-gradient(135deg, var(--dbim-blue-dark, #1D0A69) 0%, var(--dbim-blue-mid, #2563EB) 100%); } &.approvals { background: linear-gradient(135deg, #059669 0%, #10b981 100%); } &.documents { background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%); } &.departments { background: linear-gradient(135deg, #7c3aed 0%, #a78bfa 100%); } &.applicants { background: linear-gradient(135deg, #0891b2 0%, #22d3ee 100%); } &.blockchain { background: linear-gradient(135deg, #475569 0%, #64748b 100%); } } .stat-icon-wrapper { width: 52px; height: 52px; border-radius: 14px; background: rgba(255, 255, 255, 0.2); display: flex; align-items: center; justify-content: center; backdrop-filter: blur(10px); flex-shrink: 0; mat-icon { font-size: 26px; width: 26px; height: 26px; } } .stat-content { flex: 1; z-index: 1; } .stat-value { font-size: 1.75rem; font-weight: 700; line-height: 1.2; } .stat-label { font-size: 0.8rem; opacity: 0.9; margin-top: 4px; } .stat-decoration { position: absolute; top: -20px; right: -20px; width: 80px; height: 80px; border-radius: 50%; background: rgba(255, 255, 255, 0.1); } /* Content Grid */ .content-grid { display: grid; grid-template-columns: 1fr 400px; gap: 24px; @media (max-width: 1200px) { grid-template-columns: 1fr; } } .content-main { display: flex; flex-direction: column; gap: 24px; } .section-card { border-radius: 16px !important; } .card-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; border-bottom: 1px solid var(--dbim-linen, #EBEAEA); .header-left { display: flex; align-items: center; gap: 12px; mat-icon { color: var(--dbim-blue-mid, #2563EB); } h2 { margin: 0; font-size: 1.1rem; font-weight: 600; color: var(--dbim-brown, #150202); } } button mat-icon { margin-left: 4px; font-size: 18px; width: 18px; height: 18px; } } .card-content { padding: 20px 24px; } .status-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; } .status-item { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; background: var(--dbim-linen, #EBEAEA); border-radius: 10px; cursor: pointer; transition: all 0.2s; &:hover { background: rgba(0, 0, 0, 0.08); } .count { font-size: 1.25rem; font-weight: 700; color: var(--dbim-brown, #150202); } } /* Quick Actions */ .actions-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; @media (max-width: 768px) { grid-template-columns: repeat(2, 1fr); } } .action-item { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 20px; border-radius: 12px; cursor: pointer; transition: all 0.2s; text-align: center; &:hover { background: var(--dbim-linen, #EBEAEA); } span { font-size: 0.85rem; color: var(--dbim-brown, #150202); font-weight: 500; } } .action-icon { width: 56px; height: 56px; border-radius: 14px; display: flex; align-items: center; justify-content: center; mat-icon { font-size: 26px; width: 26px; height: 26px; } &.departments { background: linear-gradient(135deg, #7c3aed, #a78bfa); color: white; } &.workflows { background: linear-gradient(135deg, var(--dbim-blue-dark, #1D0A69), var(--dbim-blue-mid, #2563EB)); color: white; } &.audit { background: linear-gradient(135deg, #f59e0b, #fbbf24); color: white; } &.webhooks { background: linear-gradient(135deg, #059669, #10b981); color: white; } } .content-sidebar { @media (max-width: 1200px) { order: -1; } } `], }) export class AdminDashboardComponent implements OnInit { private readonly api = inject(ApiService); readonly loading = signal(true); readonly stats = signal(null); ngOnInit(): void { this.loadStats(); } private loadStats(): void { this.api.get('/admin/stats').subscribe({ next: (data) => { this.stats.set(data); this.loading.set(false); }, error: () => { // Use mock data for demo when API is unavailable this.loadMockStats(); this.loading.set(false); }, }); } private loadMockStats(): void { const mockStats: AdminStatsDto = { totalRequests: 156, totalApprovals: 89, totalDocuments: 423, totalDepartments: 12, totalApplicants: 67, totalBlockchainTransactions: 234, averageProcessingTime: 4.5, requestsByStatus: [ { status: 'DRAFT', count: 12 }, { status: 'SUBMITTED', count: 23 }, { status: 'IN_REVIEW', count: 18 }, { status: 'APPROVED', count: 89 }, { status: 'REJECTED', count: 8 }, { status: 'COMPLETED', count: 6 }, ], requestsByType: [ { type: 'NEW_LICENSE', count: 98 }, { type: 'RENEWAL', count: 42 }, { type: 'AMENDMENT', count: 16 }, ], departmentStats: [], lastUpdated: new Date().toISOString(), }; this.stats.set(mockStats); } }