@@ -69,7 +69,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="nav-section" *ngIf="showMemberAdminSection || showUserAdminSection">
|
||||
<div class="nav-section" *ngIf="showMemberAdminSection || showUserAdminSection || showLogsAdminSection">
|
||||
<h4 *ngIf="!sidebarCollapsed">Administration</h4>
|
||||
<ng-container *ngFor="let item of memberAdminNavItems">
|
||||
<a class="nav-item" [class.active]="item.active" *ngIf="isVisible(item)"
|
||||
@@ -89,6 +89,15 @@
|
||||
<span *ngIf="!sidebarCollapsed">{{ item.text }}</span>
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of logsAdminNavItems">
|
||||
<a class="nav-item" [class.active]="item.active" *ngIf="isVisible(item)"
|
||||
[title]="item.text" (click)="navigateTo(item.path)">
|
||||
<div class="nav-icon">
|
||||
<kendo-svgicon [icon]="item.icon"></kendo-svgicon>
|
||||
</div>
|
||||
<span *ngIf="!sidebarCollapsed">{{ item.text }}</span>
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="nav-section" *ngIf="showFinanceSection">
|
||||
@@ -163,7 +172,7 @@
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main [class]="mainContentClass">
|
||||
<!-- Top Header -->
|
||||
<!-- Unified system header (shared by every child page) -->
|
||||
<header class="top-header">
|
||||
<div class="header-left">
|
||||
<button class="mobile-menu-btn" (click)="toggleSidebar()" *ngIf="isMobile" title="Toggle menu"
|
||||
@@ -174,29 +183,17 @@
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="breadcrumb">
|
||||
<span class="breadcrumb-item">{{ currentPageTitle }}</span>
|
||||
<div class="app-header">
|
||||
<span class="app-header__eyebrow">River of Life · {{ currentSection }}</span>
|
||||
<h1 class="app-header__title">
|
||||
{{ currentPageTitle }}
|
||||
<span *ngIf="currentPageTitleZh">{{ currentPageTitleZh }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-actions">
|
||||
<!-- <button class="action-btn" title="Notifications">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
||||
</svg>
|
||||
<div class="notification-badge" *ngIf="unreadNotifications > 0">{{ unreadNotifications }}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button class="action-btn" title="Search">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="M21 21l-4.35-4.35"></path>
|
||||
</svg>
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="header-right app-header__actions">
|
||||
<ng-container *ngTemplateOutlet="pageHeader.actions()"></ng-container>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -483,11 +483,12 @@
|
||||
}
|
||||
|
||||
.top-header {
|
||||
padding: 1.5rem 2rem;
|
||||
padding: 1.25rem 2rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
@@ -495,6 +496,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
@@ -518,14 +520,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
.breadcrumb-item {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router, NavigationEnd, RouterModule, RouterOutlet } from '@angular/router';
|
||||
import { ActivatedRoute, Router, NavigationEnd, RouterModule, RouterOutlet } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IconsModule } from '@progress/kendo-angular-icons';
|
||||
import {
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
lockIcon,
|
||||
} from '@progress/kendo-svg-icons';
|
||||
import { AuthService, UserInfo } from '../../shared/services/auth.service';
|
||||
import { PageHeaderService } from '../../shared/services/page-header.service';
|
||||
import { PermissionService } from '../../core/services/permission.service';
|
||||
import { PermissionAction, PermissionModules } from '../../core/models/permission.model';
|
||||
import { Subject, takeUntil, filter } from 'rxjs';
|
||||
@@ -61,6 +62,8 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
isMobile = false;
|
||||
currentUser: UserInfo | null = null;
|
||||
currentPageTitle = 'Dashboard';
|
||||
currentPageTitleZh = '';
|
||||
currentSection = 'Home';
|
||||
|
||||
public searchQuery = '';
|
||||
|
||||
@@ -86,6 +89,13 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
permission: { module: PermissionModules.Permissions, action: 'read' } },
|
||||
];
|
||||
|
||||
public logsAdminNavItems: NavItem[] = [
|
||||
{ text: 'System Logs', icon: fileReportIcon, path: '/user-portal/admin/logs/system',
|
||||
permission: { module: PermissionModules.SystemLogs, action: 'read' } },
|
||||
{ text: 'Audit Logs', icon: fileReportIcon, path: '/user-portal/admin/logs/audit',
|
||||
permission: { module: PermissionModules.AuditLogs, action: 'read' } },
|
||||
];
|
||||
|
||||
public financeGroups: NavGroup[] = [
|
||||
{
|
||||
text: 'Overview',
|
||||
@@ -139,6 +149,7 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
|
||||
public showMemberAdminSection = false;
|
||||
public showUserAdminSection = false;
|
||||
public showLogsAdminSection = false;
|
||||
public showFinanceSection = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
@@ -146,7 +157,9 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private permissions: PermissionService,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
public pageHeader: PageHeaderService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -183,6 +196,7 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
// Section visibility is derived from effective permissions (super_admin → all).
|
||||
this.showMemberAdminSection = this.memberAdminNavItems.some(item => this.canShow(item));
|
||||
this.showUserAdminSection = this.userAdminNavItems.some(item => this.canShow(item));
|
||||
this.showLogsAdminSection = this.logsAdminNavItems.some(item => this.canShow(item));
|
||||
this.showFinanceSection = this.financeGroups
|
||||
.some(group => group.items.some(item => this.canShow(item)));
|
||||
});
|
||||
@@ -223,6 +237,7 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
...this.mainNavItems,
|
||||
...this.memberAdminNavItems,
|
||||
...this.userAdminNavItems,
|
||||
...this.logsAdminNavItems,
|
||||
...financeItems,
|
||||
...this.personalNavItems,
|
||||
];
|
||||
@@ -273,49 +288,20 @@ export class UserPortalComponent implements OnInit, OnDestroy {
|
||||
this.searchQuery = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The unified header reads its title/subtitle/section from the active route's
|
||||
* `data` (see app.routes.ts). Walk to the deepest activated child so nested
|
||||
* routes win, and fall back gracefully when a route carries no metadata.
|
||||
*/
|
||||
private updatePageTitle(): void {
|
||||
const url = this.router.url;
|
||||
const segments = url.split('/').filter(s => s);
|
||||
const key = segments.length >= 3
|
||||
? `${segments[1]}/${segments[2]}` // e.g. 'admin/members'
|
||||
: segments[1] ?? '';
|
||||
this.currentPageTitle = this.getPageTitle(key);
|
||||
}
|
||||
|
||||
private getPageTitle(page: string): string {
|
||||
const titles: { [key: string]: string } = {
|
||||
'dashboard': 'Dashboard',
|
||||
'schedule': 'Schedule',
|
||||
'patients': 'Patients',
|
||||
'bed-management': 'Bed Management',
|
||||
'staff': 'Staff',
|
||||
'pharmacy': 'Pharmacy',
|
||||
'reports': 'Reports',
|
||||
'departments': 'Departments',
|
||||
'payments': 'Payments',
|
||||
'support': 'Support',
|
||||
'transactions': 'Escrow Transactions',
|
||||
'tasks': 'Tasks & Todos',
|
||||
'contacts': 'Contacts',
|
||||
'documents': 'Documents',
|
||||
'messages': 'Messages',
|
||||
'settings': 'Settings',
|
||||
'admin/members': 'Member Management',
|
||||
'admin/users': 'User Management',
|
||||
'admin/permissions': 'Role Permissions',
|
||||
'finance/dashboard': 'Finance Dashboard',
|
||||
'finance/offering-session': 'Sunday Offering Entry',
|
||||
'finance/givings': 'Givings',
|
||||
'finance/giving-categories': 'Giving Types',
|
||||
'reimbursements': 'My Reimbursements',
|
||||
'finance/expenses': 'Expenses',
|
||||
'finance/expense-categories': 'Expense Categories',
|
||||
'finance/disbursements': 'Disbursement Management',
|
||||
'finance/check-register': 'Check Register',
|
||||
'finance/monthly-statement': 'Monthly Statement',
|
||||
'finance/church-profile': 'Church Profile',
|
||||
};
|
||||
return titles[page] ?? 'Dashboard';
|
||||
let route = this.activatedRoute;
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
}
|
||||
const data = route.snapshot.data;
|
||||
this.currentPageTitle = data['title'] ?? 'Dashboard';
|
||||
this.currentPageTitleZh = data['titleZh'] ?? '';
|
||||
this.currentSection = data['section'] ?? 'Home';
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
|
||||
Reference in New Issue
Block a user