/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Angular Imports */ import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core'; import { ActivatedRoute, Router, NavigationEnd, RouterLink } from '@angular/router'; import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; /** rxjs Imports */ import { Observable } from 'rxjs'; import { startWith, map } from 'rxjs/operators'; /** Custom Imports. */ import { activities } from './activities'; import { WarningDialogComponent } from './warning-dialog/warning-dialog.component'; /** Custom Services */ import { AuthenticationService } from '../core/authentication/authentication.service'; import { PopoverService } from '../configuration-wizard/popover/popover.service'; import { ConfigurationWizardService } from '../configuration-wizard/configuration-wizard.service'; import { SettingsService } from 'app/settings/settings.service'; import { DscService } from 'app/users/dsc/dsc.service'; /** Custom Components */ import { NextStepDialogComponent } from '../configuration-wizard/next-step-dialog/next-step-dialog.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatAutocompleteTrigger, MatAutocomplete, MatOption } from '@angular/material/autocomplete'; import { MatTooltip } from '@angular/material/tooltip'; import { AsyncPipe, DatePipe, TitleCasePipe } from '@angular/common'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; /** * Home component. */ @Component({ selector: 'mifosx-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, FaIconComponent, MatAutocompleteTrigger, MatAutocomplete, MatTooltip, AsyncPipe, DatePipe, TitleCasePipe ] }) export class HomeComponent implements OnInit, AfterViewInit { private authenticationService = inject(AuthenticationService); private activatedRoute = inject(ActivatedRoute); private router = inject(Router); private dialog = inject(MatDialog); private configurationWizardService = inject(ConfigurationWizardService); private popoverService = inject(PopoverService); private settingsService = inject(SettingsService); private dscService = inject(DscService); /** Username of authenticated user. */ username: string; /** Tenant name */ tenant: string; /** Today's date */ today = new Date(); /** True when the signed-in user has at least one ACTIVE DSC certificate mapped. */ dscMapped = false; /** Masked serial of the ACTIVE DSC, surfaced in the welcome badge tooltip. */ dscSerial = ''; /** Activity Form. */ activityForm: any; /** Search Text. */ searchText: UntypedFormControl = new UntypedFormControl(); /** Filtered Activities. */ filteredActivities: Observable; /** All User Activities. */ allActivities: any[] = activities; /* Reference of dashboard button */ @ViewChild('buttonDashboard', { static: false }) buttonDashboard: ElementRef; /* Template for popover on dashboard button */ @ViewChild('templateButtonDashboard', { static: false }) templateButtonDashboard: TemplateRef; /* Reference of search activity */ @ViewChild('searchActivity', { static: false }) searchActivity: ElementRef; /* Template for popover on search activity */ @ViewChild('templateSearchActivity', { static: false }) templateSearchActivity: TemplateRef; // All dependencies are injected using inject() above. No constructor needed. constructor() {} /** * Sets the username of the authenticated user. * Set Form. */ ngOnInit() { // Cashier users bypass the home dashboard entirely if (this.authenticationService.isCashierUser()) { this.router.navigate(['/cashier/transaction-entry'], { replaceUrl: true }); return; } const credentials = this.authenticationService.getCredentials(); this.username = credentials.username; this.tenant = this.tenantIdentifier(); this.setFilteredActivities(); this.loadDscStatus(); if (!this.authenticationService.hasDialogBeenShown()) { this.dialog.open(WarningDialogComponent, { disableClose: false, width: '500px' }); this.authenticationService.showDialog(); } } /** * Surface a small "DSC verified" badge in the welcome banner when the * signed-in user has at least one ACTIVE DSC certificate. Silent on * error / when nothing is mapped — the badge simply doesn't render. */ private loadDscStatus(): void { this.dscService.listMyDscs().subscribe({ next: (dscs) => { const active = (dscs || []).find((d) => (d?.certStatus || '').toUpperCase() === 'ACTIVE'); if (active) { this.dscMapped = true; this.dscSerial = active.dscSerialNoMasked || active.dscSerialNoFull || ''; } }, error: () => { // Silent — endpoint may be unavailable; we just don't show the badge. } }); } /** * Sets filtered activities for autocomplete. */ setFilteredActivities() { this.filteredActivities = this.searchText.valueChanges.pipe( map((activity: any) => (typeof activity === 'string' ? activity : activity.activity)), map((activityName: string) => (activityName ? this.filterActivity(activityName) : this.allActivities)) ); } /** * Filters activities. * @param activityName Activity name to filter activity by. * @returns {any} Filtered activities. */ private filterActivity(activityName: string): any { const filterValue = activityName.toLowerCase(); return this.allActivities.filter((activity) => activity.activity.toLowerCase().indexOf(filterValue) === 0); } /** * Popover function * @param template TemplateRef. * @param target HTMLElement | ElementRef. * @param position String. * @param backdrop Boolean. */ showPopover( template: TemplateRef, target: HTMLElement | ElementRef, position: string, backdrop: boolean ): void { setTimeout(() => this.popoverService.open(template, target, position, backdrop, {}), 200); } /** * To show popover. */ ngAfterViewInit() { if (this.configurationWizardService.showHome) { setTimeout(() => { this.showPopover(this.templateButtonDashboard, this.buttonDashboard.nativeElement, 'bottom', true); }); } if (this.configurationWizardService.showHomeSearchActivity) { setTimeout(() => { this.showPopover(this.templateSearchActivity, this.searchActivity.nativeElement, 'bottom', true); }); } } /** * Open Dialog for next step. * Next Step (Organization) Configuration Wizard. */ nextStep() { this.configurationWizardService.showHome = false; this.configurationWizardService.showHomeSearchActivity = false; this.openNextStepDialog(); } /** * Next Step (Organization) Dialog Configuration Wizard. */ openNextStepDialog() { const nextStepDialogRef = this.dialog.open(NextStepDialogComponent, { data: { nextStepName: 'Setup Organization', previousStepName: 'Home Tour', stepPercentage: 10 } }); nextStepDialogRef.afterClosed().subscribe((response: { nextStep: boolean }) => { if (response.nextStep) { this.configurationWizardService.showHome = false; this.configurationWizardService.showHomeSearchActivity = false; this.configurationWizardService.showCreateOffice = true; this.router.navigate(['/organization']); } else { this.configurationWizardService.showHome = false; this.configurationWizardService.showHomeSearchActivity = false; this.router.navigate(['/home']); } }); } /** * Previous Step (Breadcrumbs) Configuration Wizard. */ previousStep() { this.configurationWizardService.showHome = false; this.configurationWizardService.showHomeSearchActivity = false; this.configurationWizardService.showBreadcrumbs = true; this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.router.onSameUrlNavigation = 'reload'; this.router.navigate(['/home']); } tenantIdentifier() { if (!this.settingsService.tenantIdentifier || this.settingsService.tenantIdentifier === '') { return 'default'; } return this.settingsService.tenantIdentifier; } }