WEB-624 refactor: remove hard-coded back-routing for component reload (#3045)

improve

improve
This commit is contained in:
Shubham Kumar
2026-01-24 08:18:30 -06:00
committed by GitHub
parent 47951f241d
commit d43c79c311
3 changed files with 183 additions and 101 deletions
@@ -7,7 +7,7 @@
*/ */
/** Angular Imports */ /** Angular Imports */
import { Component, OnInit, ViewChild, inject } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, inject } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatSort, MatSortHeader } from '@angular/material/sort'; import { MatSort, MatSortHeader } from '@angular/material/sort';
@@ -26,9 +26,12 @@ import {
} from '@angular/material/table'; } from '@angular/material/table';
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, switchMap } from 'rxjs/operators';
/** Services Import */ /** Services Import */
import { CollectionsService } from '../collections.service'; import { CollectionsService } from '../collections.service';
import { DataReloadService } from 'app/core/services/data-reload.service';
/** Custom Dialogs */ /** Custom Dialogs */
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
@@ -70,34 +73,30 @@ import { OrganizationService } from 'app/organization/organization.service';
MatPaginator MatPaginator
] ]
}) })
export class IndividualCollectionSheetComponent implements OnInit { export class IndividualCollectionSheetComponent implements OnInit, OnDestroy {
private formBuilder = inject(UntypedFormBuilder); private formBuilder = inject(UntypedFormBuilder);
private collectionsService = inject(CollectionsService); private collectionsService = inject(CollectionsService);
private organizationService = inject(OrganizationService); private organizationService = inject(OrganizationService);
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
private dateUtils = inject(Dates); private dateUtils = inject(Dates);
dialog = inject(MatDialog); private dialog = inject(MatDialog);
private router = inject(Router); private router = inject(Router);
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
private dataReloadService = inject(DataReloadService);
/** Offices Data */
officesData: any; officesData: any;
/** Loan Officer Data */
loanOfficerData: any = []; loanOfficerData: any = [];
/** Loans Data */
loansData: any = []; loansData: any = [];
/** Savings Data */
savingsData: any = []; savingsData: any = [];
/** Minimum Date allowed. */
minDate = new Date(2000, 0, 1); minDate = new Date(2000, 0, 1);
/** Maximum Date allowed. */
maxDate = new Date(); maxDate = new Date();
/** Collection Sheet form. */
collectionSheetForm: UntypedFormGroup; collectionSheetForm: UntypedFormGroup;
/** Toggles b/w form and table */
isCollapsed = false; isCollapsed = false;
/** Collections Sheet Data */
collectionSheetData: any; collectionSheetData: any;
private reloadContext = 'individual-collection-sheet';
private destroy$ = new Subject<void>();
private buildDependencies$ = new Subject<void>();
/** checks and stores the local storage values */ /** checks and stores the local storage values */
Success: boolean; Success: boolean;
/** Bulk Disbursement Transactions Data */ /** Bulk Disbursement Transactions Data */
@@ -151,13 +150,19 @@ export class IndividualCollectionSheetComponent implements OnInit {
* @param {Router} router Router for navigation. * @param {Router} router Router for navigation.
* @param {SettingsService} settingsService Settings Service * @param {SettingsService} settingsService Settings Service
*/ */
constructor() { ngOnInit(): void {
this.route.data.subscribe((data: { officesData: any }) => { this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data: { officesData: any }) => {
this.officesData = data.officesData; this.officesData = data.officesData;
}); });
}
ngOnInit() { // Subscribe to reload events
this.dataReloadService
.getReloadObservable(this.reloadContext)
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.refreshData();
});
this.maxDate = this.settingsService.businessDate; this.maxDate = this.settingsService.businessDate;
if (localStorage.getItem('Success')) { if (localStorage.getItem('Success')) {
localStorage.removeItem('Success'); localStorage.removeItem('Success');
@@ -170,6 +175,16 @@ export class IndividualCollectionSheetComponent implements OnInit {
this.buildDependencies(); this.buildDependencies();
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.buildDependencies$.next();
this.buildDependencies$.complete();
if (this.reloadContext) {
this.dataReloadService.cleanup(this.reloadContext);
}
}
/** /**
* Creates the Individual Collection Sheet Form * Creates the Individual Collection Sheet Form
*/ */
@@ -191,11 +206,19 @@ export class IndividualCollectionSheetComponent implements OnInit {
* Checks for the office id value change * Checks for the office id value change
*/ */
buildDependencies() { buildDependencies() {
this.collectionSheetForm.get('officeId').valueChanges.subscribe((value: any) => { // Complete previous subscription before creating new one
this.organizationService.getStaffs(value).subscribe((response: any) => { this.buildDependencies$.next();
this.collectionSheetForm
.get('officeId')
.valueChanges.pipe(
takeUntil(this.buildDependencies$),
takeUntil(this.destroy$),
switchMap((value: any) => this.organizationService.getStaffs(value))
)
.subscribe((response: any) => {
this.loanOfficerData = response; this.loanOfficerData = response;
}); });
});
} }
/** /**
@@ -418,11 +441,30 @@ export class IndividualCollectionSheetComponent implements OnInit {
} }
/** /**
* Refetches data for the component * Triggers a reload event for the collection sheet.
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
*/ */
reload() { reload(): void {
const url: string = this.router.url; this.dataReloadService.triggerReload(this.reloadContext);
this.router.navigateByUrl(`/collections`, { skipLocationChange: true }).then(() => this.router.navigate([url])); }
/**
* Refreshes the collection sheet data when reload is triggered.
*/
private refreshData(): void {
// Re-create the form and rebuild dependencies
this.isCollapsed = false;
this.collectionSheetData = null;
// Clear bulk transaction arrays to prevent stale data
this.bulkRepaymentTransactions = [];
this.bulkSavingsDueTransactions = [];
this.bulkDisbursementTransactionsData = {};
// Clear table data arrays
this.loansData = [];
this.savingsData = [];
this.createCollectionSheetForm();
this.buildDependencies();
} }
} }
@@ -7,9 +7,11 @@
*/ */
/** Angular Imports */ /** Angular Imports */
import { Component, inject } from '@angular/core'; import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { ActivatedRoute, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; import { ActivatedRoute, Router, RouterLinkActive, RouterOutlet } from '@angular/router';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
/** Custom Dialogs */ /** Custom Dialogs */
import { UnassignStaffDialogComponent } from './custom-dialogs/unassign-staff-dialog/unassign-staff-dialog.component'; import { UnassignStaffDialogComponent } from './custom-dialogs/unassign-staff-dialog/unassign-staff-dialog.component';
@@ -17,14 +19,13 @@ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.co
/** Custom Services */ /** Custom Services */
import { GroupsService } from '../groups.service'; import { GroupsService } from '../groups.service';
import { DataReloadService } from 'app/core/services/data-reload.service';
import { import {
MatCard,
MatCardHeader, MatCardHeader,
MatCardTitleGroup, MatCardTitleGroup,
MatCardMdImage, MatCardMdImage,
MatCardTitle, MatCardTitle,
MatCardSubtitle, MatCardSubtitle
MatCardContent
} from '@angular/material/card'; } from '@angular/material/card';
import { NgClass, LowerCasePipe } from '@angular/common'; import { NgClass, LowerCasePipe } from '@angular/common';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
@@ -69,31 +70,43 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
DateFormatPipe DateFormatPipe
] ]
}) })
export class GroupsViewComponent { export class GroupsViewComponent implements OnInit, OnDestroy {
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
private groupsService = inject(GroupsService);
private router = inject(Router); private router = inject(Router);
dialog = inject(MatDialog); private dialog = inject(MatDialog);
private groupsService = inject(GroupsService);
private dataReloadService = inject(DataReloadService);
/** Group view data */
groupViewData: any; groupViewData: any;
/** Group datatables data */
groupDatatables: any; groupDatatables: any;
/** private reloadContext!: string;
* Fetches group data from `resolve` private destroy$ = new Subject<void>();
* @param {ActivatedRoute} route Activated Route
* @param {GroupsService} groupsService Groups Service ngOnInit(): void {
* @param {Router} router Router this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data: { groupViewData: any; groupDatatables: any }) => {
* @param {MatDialog} dialog Dialog
*/
constructor() {
this.route.data.subscribe((data: { groupViewData: any; groupDatatables: any }) => {
this.groupViewData = data.groupViewData; this.groupViewData = data.groupViewData;
this.groupDatatables = data.groupDatatables; this.groupDatatables = data.groupDatatables;
this.reloadContext = `group-${this.groupViewData.id}`;
// Subscribe to reload events after we have the group ID
this.dataReloadService
.getReloadObservable(this.reloadContext)
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.refreshData();
});
}); });
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
if (this.reloadContext) {
this.dataReloadService.cleanup(this.reloadContext);
}
}
/** /**
* Performs action button/option action. * Performs action button/option action.
* @param {string} name action name. * @param {string} name action name.
@@ -116,8 +129,12 @@ export class GroupsViewComponent {
} }
break; break;
case 'Edit Meeting': case 'Edit Meeting':
const queryParams: any = { calendarId: this.groupViewData.collectionMeetingCalendar.id }; this.router.navigate([`actions/${name}`], {
this.router.navigate([`actions/${name}`], { relativeTo: this.route, queryParams: queryParams }); relativeTo: this.route,
queryParams: {
calendarId: this.groupViewData.collectionMeetingCalendar.id
}
});
break; break;
case 'Edit': case 'Edit':
this.router.navigate(['edit'], { relativeTo: this.route }); this.router.navigate(['edit'], { relativeTo: this.route });
@@ -136,36 +153,43 @@ export class GroupsViewComponent {
/** /**
* Checks if meeting is editable. * Checks if meeting is editable.
*/ */
get editMeeting() { get editMeeting(): boolean {
if (this.groupViewData.collectionMeetingCalendar) { if (!this.groupViewData?.collectionMeetingCalendar) {
const entityType = this.groupViewData.collectionMeetingCalendar.entityType.value; return false;
if (entityType === 'GROUPS' && this.groupViewData.hierarchy === '.' + this.groupViewData.id + '.') {
return true;
}
} }
return false;
const entityType = this.groupViewData.collectionMeetingCalendar.entityType.value;
return entityType === 'GROUPS' && this.groupViewData.hierarchy === `.${this.groupViewData.id}.`;
} }
/** /**
* Refetches data for the component * Triggers a reload event for this group.
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
*/ */
reload() { reload(): void {
const url: string = this.router.url; this.dataReloadService.triggerReload(this.reloadContext);
this.router.navigateByUrl(`/groups`, { skipLocationChange: true }).then(() => this.router.navigate([url])); }
/**
* Refreshes the group data when reload is triggered.
*/
private refreshData(): void {
this.groupsService
.getGroupData(this.groupViewData.id)
.pipe(takeUntil(this.destroy$))
.subscribe((data: any) => {
this.groupViewData = data;
});
} }
/** /**
* Unassign's the group's staff. * Unassign's the group's staff.
*/ */
private unassignStaff() { private unassignStaff(): void {
const unAssignStaffDialogRef = this.dialog.open(UnassignStaffDialogComponent); const dialogRef = this.dialog.open(UnassignStaffDialogComponent);
unAssignStaffDialogRef.afterClosed().subscribe((response: { confirm: any }) => { dialogRef.afterClosed().subscribe((response: { confirm: boolean }) => {
if (response.confirm) { if (response?.confirm) {
this.groupsService this.groupsService
.executeGroupCommand(this.groupViewData.id, 'unassignStaff', { staffId: this.groupViewData.staffId }) .executeGroupCommand(this.groupViewData.id, 'unassignStaff', { staffId: this.groupViewData.staffId })
.subscribe(() => { .subscribe(() => this.reload());
this.reload();
});
} }
}); });
} }
@@ -173,14 +197,17 @@ export class GroupsViewComponent {
/** /**
* Deletes the group * Deletes the group
*/ */
private deleteGroup() { private deleteGroup(): void {
const deleteGroupDialogRef = this.dialog.open(DeleteDialogComponent, { const dialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: `group with id: ${this.groupViewData.id}` } data: {
deleteContext: `group with id: ${this.groupViewData.id}`
}
}); });
deleteGroupDialogRef.afterClosed().subscribe((response: any) => {
if (response.delete) { dialogRef.afterClosed().subscribe((response: any) => {
if (response?.delete) {
this.groupsService.deleteGroup(this.groupViewData.id).subscribe(() => { this.groupsService.deleteGroup(this.groupViewData.id).subscribe(() => {
this.router.navigate(['/groups'], { relativeTo: this.route }); this.router.navigate(['/groups']);
}); });
} }
}); });
@@ -7,9 +7,11 @@
*/ */
/** Angular Imports */ /** Angular Imports */
import { Component, OnInit, ViewChild, inject } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, ReactiveFormsModule } from '@angular/forms'; import { UntypedFormGroup, UntypedFormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { import {
MatTableDataSource, MatTableDataSource,
MatTable, MatTable,
@@ -37,6 +39,7 @@ import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicke
import { OrganizationService } from '../../organization.service'; import { OrganizationService } from '../../organization.service';
import { SettingsService } from 'app/settings/settings.service'; import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates'; import { Dates } from 'app/core/utils/dates';
import { DataReloadService } from 'app/core/services/data-reload.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatTabGroup, MatTab } from '@angular/material/tabs'; import { MatTabGroup, MatTab } from '@angular/material/tabs';
import { MatList, MatListItem } from '@angular/material/list'; import { MatList, MatListItem } from '@angular/material/list';
@@ -70,35 +73,32 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
DateFormatPipe DateFormatPipe
] ]
}) })
export class ViewCampaignComponent implements OnInit { export class ViewCampaignComponent implements OnInit, OnDestroy {
private router = inject(Router); private router = inject(Router);
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
dialog = inject(MatDialog); private dialog = inject(MatDialog);
private formBuilder = inject(UntypedFormBuilder); private formBuilder = inject(UntypedFormBuilder);
private dateUtils = inject(Dates); private dateUtils = inject(Dates);
private organizationService = inject(OrganizationService); private organizationService = inject(OrganizationService);
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
private dataReloadService = inject(DataReloadService);
/** Minimum date allowed. */
minDate = new Date(2000, 0, 1); minDate = new Date(2000, 0, 1);
/** Maximum date allowed. */
maxDate = new Date(); maxDate = new Date();
/** SMS form. */
smsForm: UntypedFormGroup; smsForm: UntypedFormGroup;
/** SMS Campaign data. */
smsCampaignData: any; smsCampaignData: any;
/** Message Status */
status: any; status: any;
/** Data Table Columns */
displayedColumns: string[] = [ displayedColumns: string[] = [
'Message', 'Message',
'Status', 'Status',
'Mobile No.', 'Mobile No.',
'Campaign Name' 'Campaign Name'
]; ];
/** Data source for SMS campaigns table. */
dataSource = new MatTableDataSource(); dataSource = new MatTableDataSource();
private reloadContext!: string;
private destroy$ = new Subject<void>();
/** Message Table Reference */ /** Message Table Reference */
@ViewChild('messageTable') messageTableRef: MatTable<Element>; @ViewChild('messageTable') messageTableRef: MatTable<Element>;
@@ -126,27 +126,32 @@ export class ViewCampaignComponent implements OnInit {
} }
]; ];
/** ngOnInit(): void {
* Retrieves the SMS Campaign data from `resolve this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data: { smsCampaign: any }) => {
* @param {Router} router Router
* @param {ActivatedRoute} route Activated Route
* @param {MatDialog} dialog Mat Dialog
* @param {FormBuilder} formBuilder Form Builder
* @param {Dates} dateUtils Date Utils
* @param {OrganizationService} organizationService Organization Service
* @param {SettingsService} settingsService Setting Service
*/
constructor() {
this.route.data.subscribe((data: { smsCampaign: any }) => {
this.smsCampaignData = data.smsCampaign; this.smsCampaignData = data.smsCampaign;
this.reloadContext = `sms-campaign-${this.smsCampaignData.id}`;
// Subscribe to reload events after we have the campaign ID
this.dataReloadService
.getReloadObservable(this.reloadContext)
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.refreshData();
});
}); });
}
ngOnInit() {
this.maxDate = this.settingsService.businessDate; this.maxDate = this.settingsService.businessDate;
this.createSMSForm(); this.createSMSForm();
} }
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
if (this.reloadContext) {
this.dataReloadService.cleanup(this.reloadContext);
}
}
/** /**
* Creates the SMS form. * Creates the SMS form.
*/ */
@@ -300,14 +305,22 @@ export class ViewCampaignComponent implements OnInit {
} }
/** /**
* Refetches data fot the component * Triggers a reload event for this campaign.
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
*/ */
private reload() { private reload(): void {
const url: string = this.router.url; this.dataReloadService.triggerReload(this.reloadContext);
this.router }
.navigateByUrl(`/organization/sms-campaigns`, { skipLocationChange: true })
.then(() => this.router.navigate([url])); /**
* Refreshes the campaign data when reload is triggered.
*/
private refreshData(): void {
this.organizationService
.getSmsCampaign(this.smsCampaignData.id)
.pipe(takeUntil(this.destroy$))
.subscribe((data: any) => {
this.smsCampaignData = data;
});
} }
/** /**