WEB-623 refactor: remove hard-coded back-routing for component reload (#3044)
This commit is contained in:
@@ -7,25 +7,28 @@
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
/** Dialog Imports */
|
||||
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
||||
|
||||
/** Custom Services */
|
||||
import { CentersService } from '../centers.service';
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
|
||||
/** Services */
|
||||
import { CentersService } from '../centers.service';
|
||||
import { DataReloadService } from 'app/core/services/data-reload.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/** UI Imports */
|
||||
import {
|
||||
MatCard,
|
||||
MatCardHeader,
|
||||
MatCardTitleGroup,
|
||||
MatCardMdImage,
|
||||
MatCardTitle,
|
||||
MatCardSubtitle,
|
||||
MatCardContent
|
||||
MatCardSubtitle
|
||||
} from '@angular/material/card';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { NgClass, LowerCasePipe } from '@angular/common';
|
||||
@@ -38,8 +41,9 @@ import { MatTabNav, MatTabLink, MatTabNavPanel } from '@angular/material/tabs';
|
||||
import { StatusLookupPipe } from '../../pipes/status-lookup.pipe';
|
||||
import { DateFormatPipe } from '../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Create Center View
|
||||
* Centers View Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-centers-view',
|
||||
@@ -71,57 +75,63 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
DateFormatPipe
|
||||
]
|
||||
})
|
||||
export class CentersViewComponent implements OnInit {
|
||||
export class CentersViewComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
dialog = inject(MatDialog);
|
||||
centersService = inject(CentersService);
|
||||
private dialog = inject(MatDialog);
|
||||
private centersService = inject(CentersService);
|
||||
private translateService = inject(TranslateService);
|
||||
private dataReloadService = inject(DataReloadService);
|
||||
|
||||
/** Stores Center View Data */
|
||||
centerViewData: any;
|
||||
/** Center datatable */
|
||||
centerDatatables: any;
|
||||
/** Meeting data */
|
||||
meetingData: boolean;
|
||||
meetingData = false;
|
||||
|
||||
/**
|
||||
* Retrieves the data for center
|
||||
* @param route route Activated Route.
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { centerViewData: any; centerDatatables: any }) => {
|
||||
private reloadContext!: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data: { centerViewData: any; centerDatatables: any }) => {
|
||||
this.centerViewData = data.centerViewData;
|
||||
this.centerDatatables = data.centerDatatables;
|
||||
this.meetingData = !!this.centerViewData?.collectionMeetingCalendar;
|
||||
this.reloadContext = `center-${this.centerViewData.id}`;
|
||||
|
||||
// Subscribe to reload events after we have the center ID
|
||||
this.dataReloadService
|
||||
.getReloadObservable(this.reloadContext)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => {
|
||||
this.refreshData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.centerViewData.collectionMeetingCalendar) {
|
||||
this.meetingData = true;
|
||||
} else {
|
||||
this.meetingData = false;
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.reloadContext) {
|
||||
this.dataReloadService.cleanup(this.reloadContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if meeting is editable.
|
||||
*/
|
||||
get editMeeting() {
|
||||
if (this.centerViewData.collectionMeetingCalendar) {
|
||||
const entityType = this.centerViewData.collectionMeetingCalendar.entityType.value;
|
||||
if (entityType === 'CENTERS' && this.centerViewData.hierarchy === '.' + this.centerViewData.id + '.') {
|
||||
return true;
|
||||
}
|
||||
get editMeeting(): boolean {
|
||||
if (!this.centerViewData?.collectionMeetingCalendar) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
const entityType = this.centerViewData.collectionMeetingCalendar.entityType.value;
|
||||
return entityType === 'CENTERS' && this.centerViewData.hierarchy === `.${this.centerViewData.id}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs action button/option action.
|
||||
* @param {string} name action name.
|
||||
*/
|
||||
doAction(name: string) {
|
||||
doAction(name: string): void {
|
||||
switch (name) {
|
||||
case 'Activate':
|
||||
case 'Assign Staff':
|
||||
@@ -132,38 +142,46 @@ export class CentersViewComponent implements OnInit {
|
||||
case 'Staff Assignment History':
|
||||
this.router.navigate([`actions/${name}`], { relativeTo: this.route });
|
||||
break;
|
||||
|
||||
case 'Edit Meeting':
|
||||
const queryParams: any = { calendarId: this.centerViewData.collectionMeetingCalendar.id };
|
||||
this.router.navigate([`actions/${name}`], { relativeTo: this.route, queryParams: queryParams });
|
||||
this.router.navigate([`actions/${name}`], {
|
||||
relativeTo: this.route,
|
||||
queryParams: {
|
||||
calendarId: this.centerViewData.collectionMeetingCalendar.id
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'Unassign Staff':
|
||||
this.centersUnassignStaff();
|
||||
this.unassignStaff();
|
||||
break;
|
||||
|
||||
case 'Delete':
|
||||
this.deleteCenter();
|
||||
break;
|
||||
|
||||
case 'Edit':
|
||||
this.router.navigate(['edit'], { relativeTo: this.route });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unassign's the centers's staff.
|
||||
*/
|
||||
private centersUnassignStaff() {
|
||||
const unAssignStaffDialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
private unassignStaff(): void {
|
||||
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {
|
||||
heading: this.translateService.instant('labels.heading.Unassign Staff'),
|
||||
dialogContext: this.translateService.instant('labels.dialogContext.Are you sure you want Unassign Staff')
|
||||
}
|
||||
});
|
||||
unAssignStaffDialogRef.afterClosed().subscribe((response: { confirm: any }) => {
|
||||
if (response.confirm) {
|
||||
|
||||
dialogRef.afterClosed().subscribe((response: { confirm: boolean }) => {
|
||||
if (response?.confirm) {
|
||||
this.centersService
|
||||
.executeGroupActionCommand(this.centerViewData.id, 'unassignStaff', { staffId: this.centerViewData.staffId })
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
.subscribe(() => this.reload());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -171,25 +189,39 @@ export class CentersViewComponent implements OnInit {
|
||||
/**
|
||||
* Deletes the center
|
||||
*/
|
||||
private deleteCenter() {
|
||||
const deleteGroupDialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: `center with id: ${this.centerViewData.id}` }
|
||||
private deleteCenter(): void {
|
||||
const dialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: {
|
||||
deleteContext: `center with id: ${this.centerViewData.id}`
|
||||
}
|
||||
});
|
||||
deleteGroupDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response?.delete) {
|
||||
this.centersService.deleteCenter(this.centerViewData.id).subscribe(() => {
|
||||
this.router.navigate(['/centers'], { relativeTo: this.route });
|
||||
this.router.navigate(['/centers']);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refetches data for the component
|
||||
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
|
||||
* Triggers a reload event for this center.
|
||||
*/
|
||||
reload() {
|
||||
const url: string = this.router.url;
|
||||
this.router.navigateByUrl(`/centers`, { skipLocationChange: true }).then(() => this.router.navigate([url]));
|
||||
reload(): void {
|
||||
this.dataReloadService.triggerReload(this.reloadContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the center data when reload is triggered.
|
||||
*/
|
||||
private refreshData(): void {
|
||||
this.centersService
|
||||
.getCenterData(this.centerViewData.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((data: any) => {
|
||||
this.centerViewData = data;
|
||||
this.meetingData = !!data?.collectionMeetingCalendar;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Service to handle component data reloading without navigation hacks.
|
||||
* Provides a clean way to trigger data refreshes across the application.
|
||||
* Uses ReplaySubject to handle late subscribers and race conditions.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DataReloadService {
|
||||
private reloadSubjects = new Map<string, ReplaySubject<void>>();
|
||||
|
||||
/**
|
||||
* Gets or creates a reload observable for a specific component/context.
|
||||
* @param context Unique identifier for the reload context (e.g., 'savings-account', 'loan-view')
|
||||
* @returns Observable that emits when a reload is triggered
|
||||
*/
|
||||
getReloadObservable(context: string): Observable<void> {
|
||||
if (!this.reloadSubjects.has(context)) {
|
||||
this.reloadSubjects.set(context, new ReplaySubject<void>(1));
|
||||
}
|
||||
return this.reloadSubjects.get(context)!.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a reload for the specified context.
|
||||
* All components subscribed to this context will be notified.
|
||||
* Late subscribers will immediately receive the last reload notification.
|
||||
* @param context Unique identifier for the reload context
|
||||
*/
|
||||
triggerReload(context: string): void {
|
||||
if (!this.reloadSubjects.has(context)) {
|
||||
this.reloadSubjects.set(context, new ReplaySubject<void>(1));
|
||||
}
|
||||
this.reloadSubjects.get(context)!.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup method to remove subjects when they're no longer needed.
|
||||
* @param context Unique identifier for the reload context
|
||||
*/
|
||||
cleanup(context: string): void {
|
||||
if (this.reloadSubjects.has(context)) {
|
||||
this.reloadSubjects.get(context)!.complete();
|
||||
this.reloadSubjects.delete(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user