Conversation
Login should work as per the roles
Release 3.4.1
Release 3.5.0
WalkthroughProject version bumped to 3.6.0; multiple inventory report components refactored to (1) move Excel save logic into msSaveBlob branches and (2) convert header transformation from index-based to value-based with added Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/app/app-modules/inventory/reports/beneficiary-drug-issue-report/beneficiary-drug-issue-report.component.ts (2)
250-262: Critical: Double download triggered in IE/Edge browsers.Calling both
saveAs(blob, wb_name + '.xlsx')on Line 251 andnavigator.msSaveBlob(blob, wb_name)on Line 252 will trigger two separate download prompts in browsers that supportmsSaveBlob(older IE/Edge). Thefile-saverlibrary'ssaveAsfunction already handles browser detection internally and will usemsSaveBlobwhen available.Additionally, Line 252 is missing the
.xlsxfile extension, which could cause issues if that code path executes.Apply this diff to fix the issue:
workbook.xlsx.writeBuffer().then((buffer) => { const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }); - if (navigator.msSaveBlob) { - saveAs(blob, wb_name + '.xlsx'); - navigator.msSaveBlob(blob, wb_name); - } else { - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.setAttribute('visibility', 'hidden'); - link.download = wb_name.replace(/ /g, '_') + '.xlsx'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } + saveAs(blob, wb_name.replace(/ /g, '_') + '.xlsx'); });Note: According to the AI summary, this same pattern exists in multiple report components (consumption-report, daily-stock-details-report, expiry-report, inward-stock-report, monthly-report, yearly-report, short-expiry-report, transit-report). Please ensure this fix is applied consistently across all affected files.
190-218: Header modification logic never applied to the worksheet.The code computes modified header names using
modifyHeader()on Line 211 but never applies them to the worksheet. Line 212 shows commented code that was likely intended to apply the headers, but it's disabled. As a result, the entire block (lines 190-218) is effectively dead code, and the raw unmodified headers from theheadarray are added to the worksheet on Line 225.Either remove this dead code block or uncomment and fix Line 212 to properly apply the modified headers. If using ExcelJS (as imported on Line 24), you should modify the headers before adding them:
const wb_name = 'Beneficiary Drug Issue Report'; - // below code added to modify the headers - - let i = 65; // starting from 65 since it is the ASCII code of 'A'. - let count = 0; - while (i < head.length + 65) { - let j; - if (count > 0) { - j = i - 26 * count; - } else { - j = i; - } - const cellPosition = String.fromCharCode(j); - let finalCellName: any; - if (count === 0) { - finalCellName = cellPosition + '1'; - console.log(finalCellName); - } else { - const newcellPosition = String.fromCharCode(64 + count); - finalCellName = newcellPosition + cellPosition + '1'; - console.log(finalCellName); - } - const newName = this.modifyHeader(head, i); - // delete report_worksheet[finalCellName].w; report_worksheet[finalCellName].v = newName; - i++; - if (i === 91 + count * 26) { - // i = 65; - count++; - } - } - // --------end-------- + // Modify headers for better readability + const modifiedHeaders = head.map((_: any, index: number) => + this.modifyHeader(head, index + 65) + ); const workbook = new ExcelJS.Workbook(); const criteria_worksheet = workbook.addWorksheet('Criteria'); const report_worksheet = workbook.addWorksheet('Report'); - report_worksheet.addRow(head); + report_worksheet.addRow(modifiedHeaders); criteria_worksheet.addRow(this.criteriaHead);src/app/app-modules/inventory/reports/consumption-report/consumption-report.component.ts (1)
271-279: Remove redundantsaveAscall to prevent double downloads on IE/EdgeBoth
saveAs(blob, wb_name + '.xlsx')andnavigator.msSaveBlob(blob, wb_name)are called in the sameifblock. Sincefile-saver'ssaveAsinternally delegates tonavigator.msSaveBlobon IE/Edge, this creates a double-download issue on those browsers.Use only
navigator.msSaveBlobdirectly with the filename extension included for consistency:if (navigator.msSaveBlob) { - saveAs(blob, wb_name + '.xlsx'); navigator.msSaveBlob(blob, wb_name + '.xlsx'); } else {src/app/login/login.component.ts (1)
292-337: Fix services array filtering to prevent null entries and runtime errorsThe
map()with conditionalreturncreatesnullentries for items that don't match the serviceID filter. When stored in sessionStorage and retrieved inservice.component.ts, the parsed array containsnullvalues. Accessing properties likeservice.providerServiceIDinselectService()will then throw "Cannot read property of null" errors.The fix is to filter out invalid entries after safely checking the nested structure:
- const services = loginDataResponse.previlegeObj.map((item: any) => { - if ( - item.roles[0].serviceRoleScreenMappings[0].providerServiceMapping - .serviceID === 4 || - item.roles[0].serviceRoleScreenMappings[0].providerServiceMapping - .serviceID === 2 - ) { - return { - serviceID: - item.roles[0].serviceRoleScreenMappings[0].providerServiceMapping - .serviceID, - providerServiceID: item.serviceID, - serviceName: item.serviceName, - apimanClientKey: item.apimanClientKey, - }; - } - }); - if (services.length > 0) { + const services = (loginDataResponse.previlegeObj || []) + .map((item: any) => { + if ( + !item.roles || + !item.roles[0] || + !item.roles[0].serviceRoleScreenMappings || + !item.roles[0].serviceRoleScreenMappings[0] || + !item.roles[0].serviceRoleScreenMappings[0].providerServiceMapping + ) { + return null; + } + + const mapping = + item.roles[0].serviceRoleScreenMappings[0].providerServiceMapping; + + if (mapping.serviceID === 4 || mapping.serviceID === 2) { + return { + serviceID: mapping.serviceID, + providerServiceID: item.serviceID, + serviceName: item.serviceName, + apimanClientKey: item.apimanClientKey, + }; + } + + return null; + }) + .filter((service: any) => service !== null); + + if (services.length > 0) { this.sessionstorage.setItem('services', JSON.stringify(services));Also add
'error'severity to the alert for consistency with other error alerts in the codebase:- this.confirmationService.alert( - "User doesn't have previlege to access the application", - ); + this.confirmationService.alert( + "User doesn't have previlege to access the application", + 'error', + );src/app/app-modules/inventory/reports/transit-report/transit-report.component.ts (1)
235-251: Redundant save mechanism in IE branch: calling bothsaveAsandnavigator.msSaveBlobIn the
writeBuffercallback, the IE path invokes both:if (navigator.msSaveBlob) { saveAs(blob, wb_name + '.xlsx'); navigator.msSaveBlob(blob, wb_name); }The
saveAsfunction from file-saver already feature-detects and internally usesnavigator.msSaveBlobwhen available, making the second call redundant. This pattern is duplicated across all 9 report components (transit-report, yearly-report, short-expiry-report, monthly-report, inward-stock-report, daily-stock-summary-report, daily-stock-details-report, expiry-report, consumption-report, beneficiary-drug-issue-report).Additionally, there is an inconsistency in filename handling: the IE branch uses
wb_name + '.xlsx'while the fallback branch useswb_name.replace(/ /g, '_') + '.xlsx'.Remove the
navigator.msSaveBlobcall from the IE branch and standardize filename handling across both branches.
♻️ Duplicate comments (8)
src/app/app-modules/inventory/reports/expiry-report/expiry-report.component.ts (1)
230-247: Excel export block mirrors TransitReportComponent behaviorThis
writeBuffer/download block is identical in structure to the one inTransitReportComponent: the IE path calls bothsaveAsandnavigator.msSaveBlob, and non‑IE browsers rely only on the anchor.Please apply whichever resolution you choose there (single call in IE branch, consistent use of
file-savervs anchor) here as well to keep behavior aligned.src/app/app-modules/inventory/reports/inward-stock-report/inward-stock-report.component.ts (1)
267-283: Excel export block matches prior pattern in other reportsThe
navigator.msSaveBlobbranch here also calls bothsaveAsandnavigator.msSaveBlob, with non‑IE browsers using only the anchor fallback.Please keep this in sync with the chosen pattern from
TransitReportComponentto avoid inconsistent behavior (and potential double prompts) across reports.src/app/app-modules/inventory/reports/short-expiry-report/short-expiry-report.component.ts (1)
207-223: Excel export: keepsaveAs/msSaveBlobusage consistent with other reportsSame observation as in the other report components: IE branch calls both
saveAsandnavigator.msSaveBlob, while non‑IE browsers rely only on the anchor.Once you settle on a single strategy for the IE path (and for whether to use
file-savervs the manual anchor in non‑IE), mirror that decision here for consistency.src/app/app-modules/inventory/reports/yearly-report/yearly-report.component.ts (1)
236-247: Excel export block: samesaveAs/msSaveBlobconcern as other reportsThis
writeBufferblock uses the same pattern asTransitReportComponent(calling bothsaveAsandnavigator.msSaveBlobin the IE path).Please align this with the final decision taken there to avoid duplicate prompts in IE and to keep behavior consistent across reports.
src/app/app-modules/inventory/reports/daily-stock-summary-report/daily-stock-summary-report.component.ts (2)
188-200: Header transformation matches YearlyReportComponent; keep helper shared/consistentThe introduction of:
const prettyHeaders = headers.map((h) => this.modifyHeader(h)); report_worksheet.addRow(prettyHeaders);plus the new
modifyHeader(header: string)implementation mirrors the pattern inYearlyReportComponentand improves header readability in the export.Same suggestions apply here: consider centralizing
modifyHeaderin a shared place, and handle the SonarreplacevsreplaceAllwarning consistently across all components based on your actual browser support policy.Also applies to: 216-248, 300-305
271-287: Excel export: samesaveAs/msSaveBlobbehavior as other reportsThe
writeBufferdownload logic is identical to the other report components, including the dualsaveAs+navigator.msSaveBlobcalls in the IE path.Please keep this snippet in lockstep with whichever fix/refactor you adopt in one canonical place (e.g., Transit/Yearly report).
src/app/app-modules/inventory/reports/daily-stock-details-report/daily-stock-details-report.component.ts (1)
272-289: Excel export: samesaveAs/msSaveBlobquestion as other reportsThe
writeBufferblock here matches the pattern already discussed in other components (dualsaveAs+navigator.msSaveBlobin IE, anchor fallback elsewhere). Please align any fix/refactor you make there with this file as well.src/app/app-modules/inventory/reports/monthly-report/monthly-report.component.ts (1)
267-283: Excel export: samesaveAs/msSaveBlobpattern as other reportsThe
writeBuffer→ download logic here is identical to the other report components. Please keep this in sync with your chosen resolution for the IE branch andfile-savervs anchor behavior.
🧹 Nitpick comments (7)
pom.xml (1)
58-58: Update stale build artifact name property.The
final-nameproperty still referencesv3.0.0while the project version has been bumped to3.6.0. Consider updating this to reflect the current release version, or clarify if this property is intentionally decoupled from the project version.Apply this diff if the artifact name should reflect the current version:
- <final-name>inventory-ui-v3.0.0</final-name> + <final-name>inventory-ui-${project.version}</final-name>Alternatively, update it explicitly:
- <final-name>inventory-ui-v3.0.0</final-name> + <final-name>inventory-ui-v3.6.0</final-name>src/app/app-modules/inventory/reports/beneficiary-drug-issue-report/beneficiary-drug-issue-report.component.ts (2)
283-283: Useslice()instead of deprecatedsubstr().The
substr()method is deprecated. Useslice()orsubstring()instead.Apply this diff:
- modifiedHeader.charAt(0).toUpperCase() + modifiedHeader.substr(1); + modifiedHeader.charAt(0).toUpperCase() + modifiedHeader.slice(1);
88-88: Consider removing debug console.log statements for production.Multiple
console.logstatements are present throughout the file (lines 88, 92, 117, 129, 137, 172, 187, 205, 209). These debug statements should typically be removed before a production release to avoid cluttering the console and potentially exposing sensitive data.Also applies to: 92-92, 117-117, 129-137, 172-172, 187-187, 205-205, 209-209
src/app/login/login.component.ts (1)
221-240: Pharmacist-only guard: confirm business rule and align error messageThe new
hasPharmacistcheck is technically fine, but it hard-codesRoleName === 'Pharmacist'as the only allowed role. That will block any user whose role name differs even slightly (e.g., “Senior Pharmacist”, different casing, etc.) while still showing a generic “Designation is not matched with your roles” message, even though we never reach the designation check here.If the intent is truly “only Pharmacist role can access this UI”, consider:
- Either broadening the predicate (e.g., case-insensitive, supporting multiple allowed role names), or
- Updating the alert text to clearly state that the user lacks the required Pharmacist role/privilege.
If other role names should still be allowed, this guard will be too restrictive.
src/app/app-modules/inventory/reports/yearly-report/yearly-report.component.ts (1)
152-215: Header prettification viaprettyHeaders/modifyHeaderlooks good; consider dedup + Sonar contextUsing:
const prettyHeaders = headers.map((h) => this.modifyHeader(h)); report_worksheet.addRow(prettyHeaders);combined with:
modifyHeader(header: string): string { let modifiedHeader = header.replace(/([A-Z])/g, ' $1').trim(); modifiedHeader = modifiedHeader.charAt(0).toUpperCase() + modifiedHeader.substr(1); return modifiedHeader.replace(/I D/g, 'ID'); }is a clear improvement over per-cell mutation and produces sensible labels (e.g.
unitCostPrice→Unit Cost Price,facilityName→Facility Name).Two follow‑ups to consider:
- The same
modifyHeaderimplementation now exists in several components (yearly, monthly, daily stock summary, daily stock details). Pulling this into a shared utility/service would avoid future drift.- Sonar’s “prefer
replaceAlloverreplace” warning is tricky here:String.prototype.replaceAllis not available in IE, and this codebase still has IE‑specific handling vianavigator.msSaveBlob. If IE support is still required, it’s safer to keepreplacewith a global regex; if IE is officially dropped, you can switch toreplaceAllacross all these helpers in one go.Also applies to: 265-270
src/app/app-modules/inventory/reports/daily-stock-details-report/daily-stock-details-report.component.ts (1)
188-205: NewitemEnteredDatecolumn and header formattingAdding
'itemEnteredDate'to theheadersarray and driving the header row via:const prettyHeaders = headers.map((h) => this.modifyHeader(h)); report_worksheet.addRow(prettyHeaders);is consistent with the other reports and will surface the entry timestamp cleanly as
Item Entered Date.A few points to verify/consider:
- Ensure the objects in
this.consumptionListalways include anitemEnteredDatefield; otherwise the new column will export as blank. If the field is optional, you may want to default it (e.g., to''or a formatted date) when building the list.- The
modifyHeaderimplementation is identical to the one in yearly/monthly/daily summary; consider centralizing it to avoid duplication and Sonar warnings being fixed in only one place.Also applies to: 219-253, 302-307
src/app/app-modules/inventory/reports/monthly-report/monthly-report.component.ts (1)
181-199: Monthly export: addeditemEnteredDatecolumn and shared header helperThe monthly report now exports an
itemEnteredDatecolumn and formats headers via:const prettyHeaders = headers.map((h) => this.modifyHeader(h)); report_worksheet.addRow(prettyHeaders);with
modifyHeader(header: string)matching the implementation used in the other refactored reports.That all looks consistent; two small follow‑ups:
- Confirm the backend monthly report payload actually includes
itemEnteredDatefor each row so this column is populated rather than empty.- As with the other components, consider extracting
modifyHeaderto a shared utility so any future tweaks (or a possible switch toreplaceAllif IE support is dropped) are made in one place.Also applies to: 215-248, 296-301
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
pom.xml(1 hunks)src/app/app-modules/inventory/reports/beneficiary-drug-issue-report/beneficiary-drug-issue-report.component.ts(1 hunks)src/app/app-modules/inventory/reports/consumption-report/consumption-report.component.ts(1 hunks)src/app/app-modules/inventory/reports/daily-stock-details-report/daily-stock-details-report.component.ts(5 hunks)src/app/app-modules/inventory/reports/daily-stock-summary-report/daily-stock-summary-report.component.ts(4 hunks)src/app/app-modules/inventory/reports/expiry-report/expiry-report.component.ts(1 hunks)src/app/app-modules/inventory/reports/inward-stock-report/inward-stock-report.component.ts(1 hunks)src/app/app-modules/inventory/reports/monthly-report/monthly-report.component.ts(4 hunks)src/app/app-modules/inventory/reports/short-expiry-report/short-expiry-report.component.ts(1 hunks)src/app/app-modules/inventory/reports/transit-report/transit-report.component.ts(1 hunks)src/app/app-modules/inventory/reports/yearly-report/yearly-report.component.ts(3 hunks)src/app/login/login.component.ts(2 hunks)
🧰 Additional context used
🪛 GitHub Check: SonarCloud Code Analysis
src/app/app-modules/inventory/reports/monthly-report/monthly-report.component.ts
[warning] 297-297: Prefer String#replaceAll() over String#replace().
src/app/app-modules/inventory/reports/daily-stock-summary-report/daily-stock-summary-report.component.ts
[warning] 301-301: Prefer String#replaceAll() over String#replace().
src/app/app-modules/inventory/reports/daily-stock-details-report/daily-stock-details-report.component.ts
[warning] 303-303: Prefer String#replaceAll() over String#replace().
src/app/app-modules/inventory/reports/yearly-report/yearly-report.component.ts
[warning] 266-266: Prefer String#replaceAll() over String#replace().
🔇 Additional comments (2)
pom.xml (2)
8-8: Version bump to 3.6.0 is correct.The production release version has been properly updated from 3.4.0 to 3.6.0 in the POM. Version syntax and format are correct.
1-185: Incomplete review context: Only pom.xml provided.The AI summary indicates extensive changes across multiple components (beneficiary-drug-issue-report, consumption-report, daily-stock-details-report, daily-stock-summary-report, expiry-report, inward-stock-report, monthly-report, short-expiry-report, transit-report, yearly-report, and login.ts), but only pom.xml is provided for review.
For a production release PR, a complete review of all changed files is recommended to ensure:
- Excel export logic refactoring is consistent across all report components
- Header transformation changes are properly applied
- Login privilege and service validation are correctly integrated
- No regressions in export flows or access control
Please provide the remaining changed files for comprehensive review.
fix: amm-1931 handled error messages and cleared the storage
|
| if (error.status === 401 || error.status === 403) { | ||
| this.confirmationService.alert( | ||
| 'Session expired. Please log in again to continue', | ||
| 'error', | ||
| ); | ||
| } else | ||
| this.confirmationService.alert( | ||
| error.error.errorMessage || | ||
| 'Something went wrong. Please try again later.', | ||
| 'error', | ||
| ); | ||
| this.router.navigate(['/login']); | ||
| sessionStorage.clear(); | ||
| localStorage.clear(); |
There was a problem hiding this comment.
Move logout logic inside the auth error branch only.
Currently, the code clears storage and navigates to login for ALL HTTP errors, not just authentication errors. While the conditional distinguishes 401/403 from other errors with different messages, both paths proceed to log the user out. This means temporary network errors, server errors (500), or validation errors will all terminate the user's session, leading to poor UX and potential data loss.
Apply this diff to only logout on authentication errors:
- catchError((error: HttpErrorResponse) => {
- console.error(error);
- if (error.status === 401 || error.status === 403) {
- this.confirmationService.alert(
- 'Session expired. Please log in again to continue',
- 'error',
- );
- } else
- this.confirmationService.alert(
- error.error.errorMessage ||
- 'Something went wrong. Please try again later.',
- 'error',
- );
- this.router.navigate(['/login']);
- sessionStorage.clear();
- localStorage.clear();
- this.spinnerService.show();
- return throwError(error.error);
- }),
+ catchError((error: HttpErrorResponse) => {
+ console.error(error);
+ if (error.status === 401 || error.status === 403) {
+ this.confirmationService.alert(
+ 'Session expired. Please log in again to continue',
+ 'error',
+ );
+ this.router.navigate(['/login']);
+ sessionStorage.clear();
+ localStorage.clear();
+ } else {
+ this.confirmationService.alert(
+ error.error.errorMessage ||
+ 'Something went wrong. Please try again later.',
+ 'error',
+ );
+ }
+ this.spinnerService.show();
+ return throwError(error.error);
+ }),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (error.status === 401 || error.status === 403) { | |
| this.confirmationService.alert( | |
| 'Session expired. Please log in again to continue', | |
| 'error', | |
| ); | |
| } else | |
| this.confirmationService.alert( | |
| error.error.errorMessage || | |
| 'Something went wrong. Please try again later.', | |
| 'error', | |
| ); | |
| this.router.navigate(['/login']); | |
| sessionStorage.clear(); | |
| localStorage.clear(); | |
| if (error.status === 401 || error.status === 403) { | |
| this.confirmationService.alert( | |
| 'Session expired. Please log in again to continue', | |
| 'error', | |
| ); | |
| this.router.navigate(['/login']); | |
| sessionStorage.clear(); | |
| localStorage.clear(); | |
| } else { | |
| this.confirmationService.alert( | |
| error.error.errorMessage || | |
| 'Something went wrong. Please try again later.', | |
| 'error', | |
| ); | |
| } |
🤖 Prompt for AI Agents
In src/app/app-modules/core/services/http-interceptor.service.ts around lines 83
to 96, the logout (router.navigate to '/login' and clearing
sessionStorage/localStorage) is executed for all HTTP errors; move the logout
logic so it only runs inside the 401/403 auth-error branch. Concretely: keep the
current confirmationService.alert messages as-is, but relocate the
this.router.navigate(['/login']); sessionStorage.clear(); localStorage.clear();
statements into the if (error.status === 401 || error.status === 403) block so
non-auth errors do not log out the user; ensure other branches only show the
error alert and return/exit without performing navigation or storage clearing.
| this.confirmationService.alert( | ||
| 'Session expired. Please log in again to continue', | ||
| 'error', | ||
| ); | ||
| } else | ||
| this.confirmationService.alert( | ||
| error.error.errorMessage || | ||
| 'Something went wrong. Please try again later.', | ||
| 'error', | ||
| ); | ||
| this.router.navigate(['/login']); |
There was a problem hiding this comment.
Consider awaiting alert dismissal before navigation.
The confirmation alert is shown but navigation to /login happens immediately without waiting for the user to see or dismiss the alert. This race condition could result in the alert being destroyed during navigation, preventing the user from seeing the error message.
Consider subscribing to the alert's afterClosed() observable before navigating:
catchError((error: HttpErrorResponse) => {
console.error(error);
+ let alertMessage: string;
if (error.status === 401 || error.status === 403) {
- this.confirmationService.alert(
- 'Session expired. Please log in again to continue',
- 'error',
- );
+ alertMessage = 'Session expired. Please log in again to continue';
} else {
- this.confirmationService.alert(
- error.error.errorMessage ||
- 'Something went wrong. Please try again later.',
- 'error',
- );
+ alertMessage = error.error.errorMessage ||
+ 'Something went wrong. Please try again later.';
}
+ this.confirmationService.alert(alertMessage, 'error')
+ .afterClosed()
+ .subscribe(() => {
- this.router.navigate(['/login']);
- sessionStorage.clear();
- localStorage.clear();
+ this.router.navigate(['/login']);
+ sessionStorage.clear();
+ localStorage.clear();
+ });
this.spinnerService.show();
return throwError(error.error);
}),Note: This assumes the logout logic is moved inside the auth error branch as suggested in the previous comment.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/app/app-modules/core/services/http-interceptor.service.ts around lines
84-94, the code calls confirmationService.alert(...) and immediately calls
router.navigate(['/login']) causing the alert to be destroyed before the user
can see/dismiss it; change the flow to capture the alert/dialog reference
returned by confirmationService.alert and wait for its afterClosed() observable
(e.g., subscribe and take(1) or use firstValueFrom) and only then call
router.navigate(['/login']) (and perform any logout cleanup inside that
subscription) so navigation happens after the alert is dismissed.


Prodcution Pr for release-3.6.0 to main
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.