diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts index 46599d7da46..d96e2cbb6c0 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts @@ -1,3 +1,4 @@ +import { OverlayModule } from "@angular/cdk/overlay"; import { NgModule } from "@angular/core"; import { ReportsSharedModule } from "../../../dirt/reports"; @@ -8,7 +9,13 @@ import { OrganizationReportingRoutingModule } from "./organization-reporting-rou import { ReportsHomeComponent } from "./reports-home.component"; @NgModule({ - imports: [SharedModule, ReportsSharedModule, OrganizationReportingRoutingModule, HeaderModule], + imports: [ + SharedModule, + OverlayModule, + ReportsSharedModule, + OrganizationReportingRoutingModule, + HeaderModule, + ], declarations: [ReportsHomeComponent], }) export class OrganizationReportingModule {} diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html index 59eac5b6300..9a931f66af9 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html @@ -8,9 +8,26 @@ -
- - - {{ "backToReports" | i18n }} - -
+@if (!(homepage$ | async)) { + + +} + + +
+ + {{ "backToReports" | i18n }} + +
+
diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 6043bfd3193..503a4f88050 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -1,6 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { Overlay, OverlayRef } from "@angular/cdk/overlay"; +import { TemplatePortal } from "@angular/cdk/portal"; +import { + AfterViewInit, + Component, + inject, + OnDestroy, + OnInit, + TemplateRef, + viewChild, + ViewContainerRef, +} from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; import { filter, map, Observable, startWith, concatMap, firstValueFrom } from "rxjs"; @@ -21,16 +33,30 @@ import { ReportVariant, reports, ReportType, ReportEntry } from "../../../dirt/r templateUrl: "reports-home.component.html", standalone: false, }) -export class ReportsHomeComponent implements OnInit { +export class ReportsHomeComponent implements OnInit, AfterViewInit, OnDestroy { reports$: Observable; homepage$: Observable; + private readonly backButtonTemplate = + viewChild.required>("backButtonTemplate"); + + private overlayRef: OverlayRef | null = null; + private overlay = inject(Overlay); + private viewContainerRef = inject(ViewContainerRef); + constructor( private route: ActivatedRoute, private organizationService: OrganizationService, private accountService: AccountService, private router: Router, - ) {} + ) { + this.router.events + .pipe( + takeUntilDestroyed(), + filter((event) => event instanceof NavigationEnd), + ) + .subscribe(() => this.updateOverlay()); + } async ngOnInit() { this.homepage$ = this.router.events.pipe( @@ -51,6 +77,46 @@ export class ReportsHomeComponent implements OnInit { ); } + ngAfterViewInit(): void { + this.updateOverlay(); + } + + ngOnDestroy(): void { + this.overlayRef?.dispose(); + } + + returnFocusToPage(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const firstFocusable = document.querySelector( + "[cdktrapfocus] a:not([tabindex='-1'])", + ) as HTMLElement; + firstFocusable?.focus(); + } + + focusOverlayButton(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const button = this.overlayRef?.overlayElement?.querySelector("a") as HTMLElement; + button?.focus(); + } + + private updateOverlay(): void { + if (this.isReportsHomepageRouteUrl(this.router.url)) { + this.overlayRef?.dispose(); + this.overlayRef = null; + } else if (!this.overlayRef) { + this.overlayRef = this.overlay.create({ + positionStrategy: this.overlay.position().global().bottom("20px").right("32px"), + }); + this.overlayRef.attach(new TemplatePortal(this.backButtonTemplate(), this.viewContainerRef)); + } + } + private buildReports(productType: ProductTierType): ReportEntry[] { const reportRequiresUpgrade = productType == ProductTierType.Free ? ReportVariant.RequiresUpgrade : ReportVariant.Enabled; diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.html b/apps/web/src/app/dirt/reports/reports-layout.component.html index 0cb5d304a34..c290fc88335 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.html +++ b/apps/web/src/app/dirt/reports/reports-layout.component.html @@ -1,11 +1,25 @@ -
-
- @if (!homepage) { - - {{ "backToReports" | i18n }} - - } +@if (!homepage) { + + +} + + + -
+ diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts index a6d84ccb037..136b70c81e4 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.ts +++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts @@ -1,4 +1,14 @@ -import { Component } from "@angular/core"; +import { Overlay, OverlayRef } from "@angular/cdk/overlay"; +import { TemplatePortal } from "@angular/cdk/portal"; +import { + AfterViewInit, + Component, + inject, + OnDestroy, + TemplateRef, + viewChild, + ViewContainerRef, +} from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; import { filter } from "rxjs/operators"; @@ -10,20 +20,65 @@ import { filter } from "rxjs/operators"; templateUrl: "reports-layout.component.html", standalone: false, }) -export class ReportsLayoutComponent { +export class ReportsLayoutComponent implements AfterViewInit, OnDestroy { homepage = true; - constructor(router: Router) { - const reportsHomeRoute = "/reports"; + private readonly backButtonTemplate = + viewChild.required>("backButtonTemplate"); - this.homepage = router.url === reportsHomeRoute; - router.events + private overlayRef: OverlayRef | null = null; + private overlay = inject(Overlay); + private viewContainerRef = inject(ViewContainerRef); + private router = inject(Router); + + constructor() { + this.router.events .pipe( takeUntilDestroyed(), filter((event) => event instanceof NavigationEnd), ) - .subscribe((event) => { - this.homepage = (event as NavigationEnd).url == reportsHomeRoute; + .subscribe(() => this.updateOverlay()); + } + + ngAfterViewInit(): void { + this.updateOverlay(); + } + + ngOnDestroy(): void { + this.overlayRef?.dispose(); + } + + returnFocusToPage(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const firstFocusable = document.querySelector( + "[cdktrapfocus] a:not([tabindex='-1'])", + ) as HTMLElement; + firstFocusable?.focus(); + } + + focusOverlayButton(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const button = this.overlayRef?.overlayElement?.querySelector("a") as HTMLElement; + button?.focus(); + } + + private updateOverlay(): void { + if (this.router.url === "/reports") { + this.homepage = true; + this.overlayRef?.dispose(); + this.overlayRef = null; + } else if (!this.overlayRef) { + this.homepage = false; + this.overlayRef = this.overlay.create({ + positionStrategy: this.overlay.position().global().bottom("20px").right("32px"), }); + this.overlayRef.attach(new TemplatePortal(this.backButtonTemplate(), this.viewContainerRef)); + } } } diff --git a/apps/web/src/app/dirt/reports/reports.module.ts b/apps/web/src/app/dirt/reports/reports.module.ts index 4fc152917f4..c4bd9fef809 100644 --- a/apps/web/src/app/dirt/reports/reports.module.ts +++ b/apps/web/src/app/dirt/reports/reports.module.ts @@ -1,3 +1,4 @@ +import { OverlayModule } from "@angular/cdk/overlay"; import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; @@ -29,6 +30,7 @@ import { ReportsSharedModule } from "./shared"; @NgModule({ imports: [ CommonModule, + OverlayModule, SharedModule, ReportsSharedModule, ReportsRoutingModule,