Add audit logs.
ci-cd-vm / ci-cd (push) Successful in 4m2s

This commit is contained in:
Chris Chen
2026-06-23 12:13:47 -07:00
parent 870eeec82a
commit 62592c29ae
106 changed files with 2522 additions and 311 deletions
@@ -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 {