2026-02-07 10:23:29 -04:00
|
|
|
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: `
|
|
|
|
|
<div class="page-container">
|
|
|
|
|
<!-- Welcome Section -->
|
|
|
|
|
<section class="welcome-section">
|
|
|
|
|
<div class="welcome-content">
|
|
|
|
|
<div class="welcome-text">
|
|
|
|
|
<span class="greeting">Admin Dashboard</span>
|
|
|
|
|
<h1>Platform Overview</h1>
|
|
|
|
|
<p class="subtitle">Monitor and manage the Goa GEL Blockchain Platform</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="quick-actions">
|
|
|
|
|
<button mat-raised-button class="action-btn primary" routerLink="/admin">
|
|
|
|
|
<mat-icon>admin_panel_settings</mat-icon>
|
|
|
|
|
Admin Portal
|
|
|
|
|
</button>
|
|
|
|
|
<button mat-stroked-button class="action-btn" routerLink="/requests">
|
|
|
|
|
<mat-icon>list_alt</mat-icon>
|
|
|
|
|
All Requests
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
@if (loading()) {
|
|
|
|
|
<div class="loading-container">
|
|
|
|
|
<mat-spinner diameter="48"></mat-spinner>
|
|
|
|
|
<p>Loading dashboard...</p>
|
|
|
|
|
</div>
|
|
|
|
|
} @else if (stats()) {
|
|
|
|
|
<!-- Stats Cards -->
|
|
|
|
|
<section class="stats-section">
|
|
|
|
|
<div class="stats-grid">
|
|
|
|
|
<mat-card class="stat-card requests" routerLink="/requests">
|
|
|
|
|
<div class="stat-icon-wrapper">
|
|
|
|
|
<mat-icon>description</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats()!.totalRequests }}</div>
|
|
|
|
|
<div class="stat-label">Total Requests</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-decoration"></div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
|
|
|
|
|
<mat-card class="stat-card approvals">
|
|
|
|
|
<div class="stat-icon-wrapper">
|
|
|
|
|
<mat-icon>check_circle</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats()!.totalApprovals }}</div>
|
|
|
|
|
<div class="stat-label">Approvals</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-decoration"></div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
|
|
|
|
|
<mat-card class="stat-card documents">
|
|
|
|
|
<div class="stat-icon-wrapper">
|
|
|
|
|
<mat-icon>folder_open</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats()!.totalDocuments }}</div>
|
|
|
|
|
<div class="stat-label">Documents</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-decoration"></div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
|
|
|
|
|
<mat-card class="stat-card departments" routerLink="/departments">
|
|
|
|
|
<div class="stat-icon-wrapper">
|
|
|
|
|
<mat-icon>business</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats()!.totalDepartments }}</div>
|
|
|
|
|
<div class="stat-label">Departments</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-decoration"></div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
|
|
|
|
|
<mat-card class="stat-card applicants">
|
|
|
|
|
<div class="stat-icon-wrapper">
|
|
|
|
|
<mat-icon>people</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats()!.totalApplicants }}</div>
|
|
|
|
|
<div class="stat-label">Applicants</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-decoration"></div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
|
|
|
|
|
<mat-card class="stat-card blockchain">
|
|
|
|
|
<div class="stat-icon-wrapper">
|
|
|
|
|
<mat-icon>link</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats()!.totalBlockchainTransactions }}</div>
|
|
|
|
|
<div class="stat-label">Blockchain Tx</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-decoration"></div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- Main Content Grid -->
|
|
|
|
|
<div class="content-grid">
|
|
|
|
|
<!-- Left Column -->
|
|
|
|
|
<div class="content-main">
|
|
|
|
|
<!-- Requests by Status -->
|
|
|
|
|
<mat-card class="section-card">
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
<mat-icon>pie_chart</mat-icon>
|
|
|
|
|
<h2>Requests by Status</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<button mat-button color="primary" routerLink="/requests">
|
|
|
|
|
View All
|
|
|
|
|
<mat-icon>arrow_forward</mat-icon>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-content">
|
|
|
|
|
<div class="status-grid">
|
2026-02-08 18:44:05 -04:00
|
|
|
@for (item of (stats()?.requestsByStatus || []); track item.status) {
|
2026-02-07 10:23:29 -04:00
|
|
|
<div class="status-item" [routerLink]="['/requests']" [queryParams]="{ status: item.status }">
|
|
|
|
|
<app-status-badge [status]="item.status" />
|
|
|
|
|
<span class="count">{{ item.count }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
|
|
|
|
|
<!-- Quick Actions -->
|
|
|
|
|
<mat-card class="section-card">
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
<mat-icon>flash_on</mat-icon>
|
|
|
|
|
<h2>Quick Actions</h2>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-content">
|
|
|
|
|
<div class="actions-grid">
|
|
|
|
|
<div class="action-item" routerLink="/departments">
|
|
|
|
|
<div class="action-icon departments">
|
|
|
|
|
<mat-icon>business</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<span>Manage Departments</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="action-item" routerLink="/workflows">
|
|
|
|
|
<div class="action-icon workflows">
|
|
|
|
|
<mat-icon>account_tree</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<span>Manage Workflows</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="action-item" routerLink="/audit">
|
|
|
|
|
<div class="action-icon audit">
|
|
|
|
|
<mat-icon>history</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<span>View Audit Logs</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="action-item" routerLink="/webhooks">
|
|
|
|
|
<div class="action-icon webhooks">
|
|
|
|
|
<mat-icon>webhook</mat-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<span>Webhooks</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</mat-card>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Right Column: Blockchain Activity -->
|
|
|
|
|
<div class="content-sidebar">
|
|
|
|
|
<app-blockchain-explorer-mini [showViewAll]="true" [refreshInterval]="15000"></app-blockchain-explorer-mini>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
|
|
|
|
styles: [`
|
|
|
|
|
.page-container {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Welcome Section */
|
|
|
|
|
.welcome-section {
|
2026-02-08 18:44:05 -04:00
|
|
|
background: linear-gradient(135deg, #1D0A69 0%, #2563EB 100%) !important;
|
|
|
|
|
color: white !important;
|
2026-02-07 10:23:29 -04:00
|
|
|
padding: 32px;
|
2026-02-08 18:44:05 -04:00
|
|
|
margin: 0 0 24px 0;
|
|
|
|
|
border-radius: 16px;
|
2026-02-07 10:23:29 -04:00
|
|
|
position: relative;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.welcome-content {
|
|
|
|
|
max-width: 1400px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 24px;
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.welcome-text .greeting {
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
color: rgba(255, 255, 255, 0.9) !important;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.welcome-text h1 {
|
|
|
|
|
margin: 8px 0;
|
|
|
|
|
font-size: 2rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: white !important;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.welcome-text .subtitle {
|
|
|
|
|
margin: 0;
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
color: rgba(255, 255, 255, 0.9) !important;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.quick-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-btn.primary {
|
|
|
|
|
background: white !important;
|
|
|
|
|
color: #1D0A69 !important;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-btn:not(.primary) {
|
|
|
|
|
color: white;
|
|
|
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-btn:not(.primary):hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-btn mat-icon {
|
|
|
|
|
margin-right: 8px;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.loading-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 64px;
|
|
|
|
|
gap: 16px;
|
2026-02-08 18:44:05 -04:00
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.loading-container p {
|
|
|
|
|
color: #8E8E8E;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
2026-02-08 18:44:05 -04:00
|
|
|
background: white !important;
|
|
|
|
|
color: #150202;
|
|
|
|
|
border: 1px solid #EBEAEA;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.stat-card:hover {
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon-wrapper {
|
2026-02-08 18:44:05 -04:00
|
|
|
width: 48px;
|
|
|
|
|
height: 48px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
background: rgba(29, 10, 105, 0.08);
|
2026-02-07 10:23:29 -04:00
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-shrink: 0;
|
2026-02-08 18:44:05 -04:00
|
|
|
color: #1D0A69;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.stat-icon-wrapper mat-icon {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card.approvals .stat-icon-wrapper {
|
|
|
|
|
background: rgba(5, 150, 105, 0.1);
|
|
|
|
|
color: #059669;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card.documents .stat-icon-wrapper {
|
|
|
|
|
background: rgba(245, 158, 11, 0.1);
|
|
|
|
|
color: #f59e0b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card.departments .stat-icon-wrapper {
|
|
|
|
|
background: rgba(124, 58, 237, 0.1);
|
|
|
|
|
color: #7c3aed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card.applicants .stat-icon-wrapper {
|
|
|
|
|
background: rgba(8, 145, 178, 0.1);
|
|
|
|
|
color: #0891b2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card.blockchain .stat-icon-wrapper {
|
|
|
|
|
background: rgba(71, 85, 105, 0.1);
|
|
|
|
|
color: #475569;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 1.75rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
line-height: 1.2;
|
2026-02-08 18:44:05 -04:00
|
|
|
color: #150202;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
font-size: 0.8rem;
|
2026-02-08 18:44:05 -04:00
|
|
|
color: #8E8E8E;
|
2026-02-07 10:23:29 -04:00
|
|
|
margin-top: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-decoration {
|
2026-02-08 18:44:05 -04:00
|
|
|
display: none;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Content Grid */
|
|
|
|
|
.content-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 400px;
|
|
|
|
|
gap: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
2026-02-08 18:44:05 -04:00
|
|
|
border-bottom: 1px solid #EBEAEA;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.card-header .header-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-header .header-left mat-icon {
|
|
|
|
|
color: #2563EB;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-header .header-left h2 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #150202;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-header button mat-icon {
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
width: 18px;
|
|
|
|
|
height: 18px;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
2026-02-08 18:44:05 -04:00
|
|
|
background: #F5F5F5;
|
2026-02-07 10:23:29 -04:00
|
|
|
border-radius: 10px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
2026-02-08 18:44:05 -04:00
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.status-item:hover {
|
|
|
|
|
background: rgba(0, 0, 0, 0.08);
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.status-item .count {
|
|
|
|
|
font-size: 1.25rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: #150202;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Quick Actions */
|
|
|
|
|
.actions-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
2026-02-08 18:44:05 -04:00
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-item:hover {
|
|
|
|
|
background: #F5F5F5;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-item span {
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
color: #150202;
|
|
|
|
|
font-weight: 500;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-icon {
|
|
|
|
|
width: 56px;
|
|
|
|
|
height: 56px;
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
2026-02-08 18:44:05 -04:00
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-icon mat-icon {
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
width: 26px;
|
|
|
|
|
height: 26px;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-icon.departments {
|
|
|
|
|
background: linear-gradient(135deg, #7c3aed, #a78bfa);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-icon.workflows {
|
|
|
|
|
background: linear-gradient(135deg, #1D0A69, #2563EB);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-icon.audit {
|
|
|
|
|
background: linear-gradient(135deg, #f59e0b, #fbbf24);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
|
2026-02-08 18:44:05 -04:00
|
|
|
.action-icon.webhooks {
|
|
|
|
|
background: linear-gradient(135deg, #059669, #10b981);
|
|
|
|
|
color: white;
|
2026-02-07 10:23:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-sidebar {
|
2026-02-08 18:44:05 -04:00
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Responsive adjustments */
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
|
|
.content-grid {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
.content-sidebar {
|
2026-02-07 10:23:29 -04:00
|
|
|
order: -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-08 18:44:05 -04:00
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.welcome-content {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
.actions-grid {
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-07 10:23:29 -04:00
|
|
|
`],
|
|
|
|
|
})
|
|
|
|
|
export class AdminDashboardComponent implements OnInit {
|
|
|
|
|
private readonly api = inject(ApiService);
|
|
|
|
|
|
|
|
|
|
readonly loading = signal(true);
|
|
|
|
|
readonly stats = signal<AdminStatsDto | null>(null);
|
|
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
|
|
this.loadStats();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadStats(): void {
|
|
|
|
|
this.api.get<AdminStatsDto>('/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);
|
|
|
|
|
}
|
|
|
|
|
}
|