diff --git a/src/app/pages/circular-heatmap/circular-heatmap.component.html b/src/app/pages/circular-heatmap/circular-heatmap.component.html index 8ed4dfac..7d35f8f0 100644 --- a/src/app/pages/circular-heatmap/circular-heatmap.component.html +++ b/src/app/pages/circular-heatmap/circular-heatmap.component.html @@ -106,7 +106,9 @@

Nothing to show

Dependencies - + @@ -248,9 +250,7 @@

Nothing to show

(closed)="onPanelClosed(activity)"> - diff --git a/src/app/pages/circular-heatmap/circular-heatmap.component.ts b/src/app/pages/circular-heatmap/circular-heatmap.component.ts index 08f6a6c4..bfdcd6d1 100644 --- a/src/app/pages/circular-heatmap/circular-heatmap.component.ts +++ b/src/app/pages/circular-heatmap/circular-heatmap.component.ts @@ -1,9 +1,12 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { equalArray } from 'src/app/util/util'; import { LoaderService } from 'src/app/service/loader/data-loader.service'; import * as d3 from 'd3'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; +import { Location } from '@angular/common'; import { MatChip } from '@angular/material/chips'; +import { Subject } from 'rxjs'; +import { takeUntil, distinctUntilChanged } from 'rxjs/operators'; import * as md from 'markdown-it'; import { ModalMessageComponent, @@ -22,7 +25,7 @@ import { ThemeService } from '../../service/theme.service'; templateUrl: './circular-heatmap.component.html', styleUrls: ['./circular-heatmap.component.css'], }) -export class CircularHeatmapComponent implements OnInit { +export class CircularHeatmapComponent implements OnInit, OnDestroy { Routing: string = '/activity-description'; markdown: md = md(); maxLevelOfMaturity: number = -1; @@ -52,10 +55,15 @@ export class CircularHeatmapComponent implements OnInit { theme: string; theme_colors!: Record; + private destroy$ = new Subject(); + constructor( private loader: LoaderService, private sectorService: SectorService, private themeService: ThemeService, + private router: Router, + private route: ActivatedRoute, + private location: Location, public modal: ModalMessageComponent ) { this.theme = this.themeService.getTheme(); @@ -110,6 +118,9 @@ export class CircularHeatmapComponent implements OnInit { // For now, just draw the sectors (no activities yet) this.loadCircularHeatMap('#chart', this.allSectors, this.dimensionLabels, this.maxLevel); console.log(`${perfNow()}: Page loaded: Circular Heatmap`); + + // Check if there's a URL fragment and open the corresponding activity + this.checkUrlFragmentForActivity(); }) .catch(err => { this.displayMessage(new DialogInfo(err.message, 'An error occurred')); @@ -119,7 +130,7 @@ export class CircularHeatmapComponent implements OnInit { }); }); // Reactively handle theme changes (if user toggles later) - this.themeService.theme$.subscribe((theme: string) => { + this.themeService.theme$.pipe(takeUntil(this.destroy$)).subscribe((theme: string) => { const css = getComputedStyle(document.body); this.theme_colors = { background: css.getPropertyValue('--heatmap-background').trim(), @@ -133,6 +144,22 @@ export class CircularHeatmapComponent implements OnInit { }); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + checkUrlFragmentForActivity() { + // Check if there's a URL fragment that might be an activity UUID + this.route.fragment + .pipe(takeUntil(this.destroy$), distinctUntilChanged()) + .subscribe(fragment => { + if (fragment && this.dataStore) { + this.navigateToActivityByUuid(fragment); + } + }); + } + displayMessage(dialogInfo: DialogInfo) { this.modal.openDialog(dialogInfo); } @@ -237,6 +264,14 @@ export class CircularHeatmapComponent implements OnInit { return this.sectorService.getSectorProgress(sector.activities); } + onDependencyClicked(activityName: string) { + console.log(`${perfNow()}: Heat: Dependency clicked: '${activityName}'`); + const activity = this.dataStore?.activityStore?.getActivityByName(activityName); + if (activity?.uuid) { + this.navigateToActivityByUuid(activity.uuid); + } + } + loadCircularHeatMap( dom_element_to_append_to: string, dataset: any, @@ -540,38 +575,71 @@ export class CircularHeatmapComponent implements OnInit { console.log(`${perfNow()}: Heat: Card Panel closed: '${activity.name}'`); } - openActivityDetails(dimension: string, activityName: string) { + openActivityDetails(uuid: string) { // Find the activity in the selected sector - console.log(`${perfNow()}: Heat: Open Overlay: '${activityName}'`); - if (!this.dataStore) { - console.error(`Data store is not initialized. Cannot open activity ${activityName}`); + if (!this.dataStore || !this.dataStore.activityStore) { + console.error(`Data store is not initialized. Cannot open activity ${uuid}`); return; } if (!this.showActivityCard || !this.showActivityCard.activities) { this.showOverlay = true; return; } - const activity = this.showActivityCard.activities.find( - (a: any) => a.activityName === activityName || a.name === activityName - ); + + const activity: Activity = this.dataStore.activityStore.getActivityByUuid(uuid); if (!activity) { this.showOverlay = true; return; } + // Prepare navigationExtras and details /* eslint-disable */ + console.log(`${perfNow()}: Heat: Open Overlay: '${activity.name}'`); this.showActivityDetails = activity; this.KnowledgeLabel = this.dataStore.getMetaString('knowledgeLabels', activity.difficultyOfImplementation.knowledge); this.TimeLabel = this.dataStore.getMetaString('labels', activity.difficultyOfImplementation.time); this.ResourceLabel = this.dataStore.getMetaString('labels', activity.difficultyOfImplementation.resources); this.UsefulnessLabel = this.dataStore.getMetaString('labels', activity.usefulness); this.showOverlay = true; + + // Update URL with activity UUID as fragment + if (activity.uuid) { + this.router.navigate([], { + relativeTo: this.route, + fragment: activity.uuid, + queryParamsHandling: 'preserve' + }); + } /* eslint-enable */ } + navigateToActivityByUuid(uuid: string) { + console.log(`${perfNow()}: Heat: Attempting to open activity with UUID: ${uuid}`); + if (!this.dataStore || !this.dataStore.activityStore) { + console.error('Data store is not initialized. Cannot open activity by UUID'); + return; + } + const activity: Activity = this.dataStore.activityStore.getActivityByUuid(uuid); + const sector = this.allSectors.find(s => s.activities.some(a => a.uuid === uuid)); + if (activity && sector) { + this.selectedSector = sector; + this.showActivityCard = sector; + this.openActivityDetails(activity.uuid); + } else { + // Only close the overlay, do not update the URL + this.showOverlay = false; + console.warn(`Heat: Activity with UUID ${uuid} not found.`); + } + } + closeOverlay() { + // Clear the URL fragment when closing overlay + this.router.navigate([], { + relativeTo: this.route, + fragment: undefined, + queryParamsHandling: 'preserve', + }); this.showOverlay = false; - // console.log(`${perfNow()}: Heat: Close Overlay: '${this.old_activityDetails.name}'`); } toggleFilters() { diff --git a/src/assets/Markdown Files/TODO-v4.md b/src/assets/Markdown Files/TODO-v4.md index 6f047e8f..5caedc98 100644 --- a/src/assets/Markdown Files/TODO-v4.md +++ b/src/assets/Markdown Files/TODO-v4.md @@ -20,7 +20,6 @@ - Teams: Bug: Editing name, pushes the item last - Teams: Allow editing dates for progress stages ### Heatmap: -- Heatmap: Add #uuid to URL, and allow navigation on clicks in dependencies - Heatmap: Fix: asterisk marks when modified - ViewController needs to know about changes vs temp storage - Heatmap: Bug: Clicking on grey sector leaves cursor on that sector @@ -69,6 +68,7 @@ - Meta.yaml: Allow admins to customize the terms 'Team' and 'Group' (e.g. to 'App' and 'Portfolio') # Done +- Heatmap: Add #uuid to URL, and allow navigation on clicks in dependencies - Dependency: Make connecting nodes clickable for navigation - Dependency: Handle dependsOn uuid, not just name - Matrix: Dependency graph: Render in center of page