Files
Goa-gel-fullstack/frontend/src/app/layouts/main-layout/main-layout.component.html
Mahi 80566bf0a2 feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with:

Backend:
- NestJS API with JWT authentication
- PostgreSQL database with Knex ORM
- Redis caching and session management
- MinIO document storage
- Hyperledger Besu blockchain integration
- Multi-department workflow system
- Comprehensive API tests (266/282 passing)

Frontend:
- Angular 21 with standalone components
- Angular Material + TailwindCSS UI
- Visual workflow builder
- Document upload with progress tracking
- Blockchain explorer integration
- Role-based dashboards (Admin, Department, Citizen)
- E2E tests with Playwright (37 tests)

Infrastructure:
- Docker Compose orchestration
- Blockscout blockchain explorer
- Development and production configurations
2026-02-07 10:23:29 -04:00

261 lines
8.8 KiB
HTML

<!-- Skip to main content - GIGW 3.0 Accessibility -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<div class="app-shell">
<!-- Sidebar -->
<aside
class="sidebar"
[class.sidebar-collapsed]="!sidenavOpened()"
role="navigation"
aria-label="Main navigation"
>
<!-- Logo Section -->
<div class="sidebar-header">
<div class="logo-section">
<div class="emblem-container">
<img
src="assets/images/goa-emblem.svg"
alt="Government of Goa Emblem"
class="goa-emblem"
onerror="this.style.display='none'"
/>
<div class="emblem-fallback" *ngIf="!emblemLoaded">
<mat-icon>account_balance</mat-icon>
</div>
</div>
@if (sidenavOpened()) {
<div class="logo-text">
<span class="govt-text">Government of Goa</span>
<span class="platform-text">Blockchain e-Licensing</span>
</div>
}
</div>
</div>
<!-- Navigation Links -->
<nav class="sidebar-nav">
<div class="nav-section">
@if (sidenavOpened()) {
<span class="nav-section-title">Main Menu</span>
}
@for (item of visibleNavItems(); track item.route) {
<a
class="nav-item"
[routerLink]="item.route"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: item.route === '/dashboard' }"
[attr.aria-label]="item.label"
[matTooltip]="!sidenavOpened() ? item.label : ''"
matTooltipPosition="right"
>
<div class="nav-icon">
<mat-icon>{{ item.icon }}</mat-icon>
</div>
@if (sidenavOpened()) {
<span class="nav-label">{{ item.label }}</span>
}
@if (item.badge && item.badge() > 0) {
<span class="nav-badge" [class.pulse]="item.badge() > 0">
{{ item.badge() }}
</span>
}
</a>
}
</div>
@if (userType() === 'ADMIN') {
<div class="nav-section">
@if (sidenavOpened()) {
<span class="nav-section-title">Administration</span>
}
<a
class="nav-item"
routerLink="/admin"
routerLinkActive="active"
[matTooltip]="!sidenavOpened() ? 'Admin Portal' : ''"
matTooltipPosition="right"
>
<div class="nav-icon">
<mat-icon>admin_panel_settings</mat-icon>
</div>
@if (sidenavOpened()) {
<span class="nav-label">Admin Portal</span>
}
</a>
</div>
}
</nav>
<!-- Sidebar Footer -->
<div class="sidebar-footer">
@if (sidenavOpened()) {
<div class="blockchain-status">
<div class="status-indicator online"></div>
<div class="status-text">
<span class="status-label">Blockchain</span>
<span class="status-value">Connected</span>
</div>
</div>
} @else {
<div class="blockchain-status-compact">
<div class="status-indicator online"></div>
</div>
}
</div>
</aside>
<!-- Main Content Area -->
<div class="main-wrapper">
<!-- Top Header -->
<header class="top-header" role="banner">
<div class="header-left">
<button
mat-icon-button
(click)="toggleSidenav()"
aria-label="Toggle navigation menu"
class="menu-toggle"
>
<mat-icon>{{ sidenavOpened() ? 'menu_open' : 'menu' }}</mat-icon>
</button>
<!-- Breadcrumb (optional) -->
<nav class="breadcrumb hide-mobile" aria-label="Breadcrumb">
<span class="breadcrumb-item">Dashboard</span>
</nav>
</div>
<div class="header-right">
<!-- Search (optional) -->
<button mat-icon-button class="header-action hide-mobile" aria-label="Search">
<mat-icon>search</mat-icon>
</button>
<!-- Notifications -->
<button
mat-icon-button
class="header-action"
[matMenuTriggerFor]="notificationMenu"
aria-label="Notifications"
>
<mat-icon [matBadge]="unreadNotifications()" matBadgeColor="warn" matBadgeSize="small">
notifications
</mat-icon>
</button>
<mat-menu #notificationMenu="matMenu" class="notification-menu">
<div class="notification-header">
<span class="notification-title">Notifications</span>
<button mat-button color="primary" class="mark-read-btn">Mark all read</button>
</div>
<mat-divider></mat-divider>
<div class="notification-list">
<div class="notification-item unread">
<div class="notification-icon success">
<mat-icon>check_circle</mat-icon>
</div>
<div class="notification-content">
<span class="notification-text">Request #1234 approved</span>
<span class="notification-time">2 minutes ago</span>
</div>
</div>
<div class="notification-item">
<div class="notification-icon info">
<mat-icon>info</mat-icon>
</div>
<div class="notification-content">
<span class="notification-text">New document uploaded</span>
<span class="notification-time">1 hour ago</span>
</div>
</div>
</div>
<mat-divider></mat-divider>
<button mat-menu-item class="view-all-btn">
View all notifications
</button>
</mat-menu>
<!-- User Profile -->
<div class="user-profile">
<button
mat-button
[matMenuTriggerFor]="userMenu"
class="user-button"
aria-label="User menu"
>
<div class="user-avatar" [style.background]="getAvatarColor()">
{{ getUserInitials() }}
</div>
@if (currentUser | async; as user) {
<div class="user-info hide-mobile">
<span class="user-name">{{ user.name }}</span>
<span class="user-role">{{ formatRole(user.type) }}</span>
</div>
}
<mat-icon class="dropdown-arrow hide-mobile">expand_more</mat-icon>
</button>
<mat-menu #userMenu="matMenu" class="user-menu">
@if (currentUser | async; as user) {
<div class="user-menu-header">
<div class="user-avatar-large" [style.background]="getAvatarColor()">
{{ getUserInitials() }}
</div>
<div class="user-details">
<span class="user-name">{{ user.name }}</span>
<span class="user-email">{{ user.email || (user.departmentCode ? user.departmentCode + '@goa.gov.in' : 'user@goa.gov.in') }}</span>
<span class="user-badge">{{ formatRole(user.type) }}</span>
</div>
</div>
<mat-divider></mat-divider>
}
<button mat-menu-item>
<mat-icon>person</mat-icon>
<span>My Profile</span>
</button>
<button mat-menu-item>
<mat-icon>settings</mat-icon>
<span>Settings</span>
</button>
@if (userType() === 'DEPARTMENT') {
<button mat-menu-item routerLink="/department/wallet">
<mat-icon>account_balance_wallet</mat-icon>
<span>My Wallet</span>
</button>
}
<mat-divider></mat-divider>
<button mat-menu-item (click)="logout()" class="logout-btn">
<mat-icon>logout</mat-icon>
<span>Logout</span>
</button>
</mat-menu>
</div>
</div>
</header>
<!-- Main Content -->
<main id="main-content" class="main-content" role="main">
<router-outlet></router-outlet>
</main>
<!-- Footer - DBIM Compliant -->
<footer class="main-footer" role="contentinfo">
<div class="footer-content">
<div class="footer-left">
<span class="footer-text">
This platform belongs to Government of Goa, India
</span>
<span class="footer-divider hide-mobile">|</span>
<span class="footer-text hide-mobile">
Last Updated: {{ lastUpdated }}
</span>
</div>
<div class="footer-right">
<a href="#" class="footer-link">Website Policies</a>
<a href="#" class="footer-link">Help</a>
<a href="#" class="footer-link">Feedback</a>
</div>
</div>
</footer>
</div>
</div>