diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.scss b/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.scss index 86af29062..c32be548c 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.scss +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.scss @@ -1,3 +1,8 @@ .map-container{ margin-top: 12px; -} \ No newline at end of file +} + +#fiscalMap { + height: 100%; + width: 100%; + } \ No newline at end of file diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.spec.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.spec.ts index 381f3fbbc..e4b89f40a 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.spec.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.spec.ts @@ -350,6 +350,54 @@ describe('FiscalMapComponent', () => { expect(plotSpy).not.toHaveBeenCalled(); expect(coordSpy).toHaveBeenCalled(); })); + + it('should correctly map activities with fiscal data', () => { + const fiscal = { fiscalYear: 2023, projectPlanFiscalGuid: 'fiscal-guid' }; + const response = { + _embedded: { + activities: [ + { activityGuid: 'a1', name: 'Activity 1' }, + { activityGuid: 'a2', name: 'Activity 2' } + ] + } + }; + + const result = (component as any).mapFiscalActivities(response, fiscal); + expect(result.length).toBe(2); + expect(result[0]).toEqual(jasmine.objectContaining({ + activityGuid: 'a1', + fiscalYear: 2023, + projectPlanFiscalGuid: 'fiscal-guid' + })); + }); + + it('should correctly map activity boundary if boundary exists', () => { + const activity = { + activityGuid: 'act-1', + fiscalYear: 2022 + }; + const boundary = { + _embedded: { + activityBoundary: [{ geometry: { type: 'Polygon' } }] + } + }; + + const result = (component as any).mapActivityBoundary(boundary, activity); + expect(result).toEqual({ + activityGuid: 'act-1', + fiscalYear: 2022, + boundary: boundary._embedded.activityBoundary + }); + }); + + it('should return null if boundary is null', () => { + const activity = { + activityGuid: 'act-1', + fiscalYear: 2022 + }; + const result = (component as any).mapActivityBoundary(null, activity); + expect(result).toBeNull(); + }); describe('openFullMap()', () => { diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.ts index d9eb418c0..f151e71c6 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/fiscal-map/fiscal-map.component.ts @@ -27,8 +27,8 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { }; constructor( - private projectService: ProjectService, - private route: ActivatedRoute, + readonly projectService: ProjectService, + readonly route: ActivatedRoute, protected router: Router, ) {} @@ -66,7 +66,7 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { const lat = parseFloat(this.projectLatitude); const lng = parseFloat(this.projectLongitude); - const marker = L.marker([lat, lng]).addTo(this.map); + L.marker([lat, lng]).addTo(this.map); this.map.setView([lat, lng], 14); } @@ -74,10 +74,10 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { }) } getProjectBoundary() { - this.projectGuid = this.route.snapshot?.queryParamMap?.get('projectGuid') || ''; + this.projectGuid = this.route.snapshot?.queryParamMap?.get('projectGuid') ?? ''; if (this.projectGuid) { this.projectService.getProjectBoundaries(this.projectGuid).subscribe((data) => { - const boundary = data?._embedded?.projectBoundary || []; + const boundary = data?._embedded?.projectBoundary ?? []; this.projectBoundary = boundary; if (this.map && boundary.length > 0) { @@ -86,71 +86,85 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { }); } } - getAllActivitiesBoundaries() { - this.projectGuid = this.route.snapshot?.queryParamMap?.get('projectGuid') || ''; - if (this.projectGuid) { - this.projectService.getProjectFiscalsByProjectGuid(this.projectGuid).subscribe((data) => { - this.projectFiscals = (data._embedded?.projectFiscals || []).sort( - (a: { fiscalYear: number }, b: { fiscalYear: number }) => a.fiscalYear - b.fiscalYear - ); - - const activityRequests = this.projectFiscals.map(fiscal => - this.projectService.getFiscalActivities(this.projectGuid, fiscal.projectPlanFiscalGuid).pipe( - map((response: any) => { - const activities = response?._embedded?.activities || []; - return activities.map((activity: any) => ({ - ...activity, - fiscalYear: fiscal.fiscalYear, - projectPlanFiscalGuid: fiscal.projectPlanFiscalGuid - })); - }) - ) - ); - - forkJoin(activityRequests).subscribe((allActivityArrays) => { - const allActivities = allActivityArrays.flat(); - - if (allActivities.length === 0) { - // no activites at all. - this.getProjectCoordinates(); - return; - } - const boundaryRequests = allActivities.map(activity => - this.projectService - .getActivityBoundaries(this.projectGuid, activity.projectPlanFiscalGuid, activity.activityGuid) - .pipe( - map(boundary => boundary ? ({ - activityGuid: activity.activityGuid, - fiscalYear: activity.fiscalYear, - boundary: boundary?._embedded?.activityBoundary - }) : null), - ) - ); - - forkJoin(boundaryRequests).subscribe((allResults) => { - this.allActivityBoundaries = allResults.filter(r => - r !== null && r.boundary && Object.keys(r.boundary).length > 0 - ); - const hasActivityPolygons = this.allActivityBoundaries.length > 0; - const hasProjectPolygons = this.projectBoundary?.length > 0; - - if (hasActivityPolygons && this.map) { - this.plotBoundariesOnMap(this.allActivityBoundaries); - } - - // Only show pin if NO polygons exist at all - if (!hasActivityPolygons && !hasProjectPolygons) { - this.getProjectCoordinates(); - } - }); - }); - }); + getAllActivitiesBoundaries(): void { + this.projectGuid = this.route.snapshot?.queryParamMap?.get('projectGuid') ?? ''; + if (!this.projectGuid) return; + + this.projectService.getProjectFiscalsByProjectGuid(this.projectGuid).subscribe(data => + this.handleFiscalsResponse(data) + ); + } + + private handleFiscalsResponse(data: any): void { + this.projectFiscals = (data._embedded?.projectFiscals ?? []).sort( + (a: { fiscalYear: number }, b: { fiscalYear: number }) => a.fiscalYear - b.fiscalYear + ); + + const activityRequests = this.projectFiscals.map(fiscal => + this.projectService.getFiscalActivities(this.projectGuid, fiscal.projectPlanFiscalGuid).pipe( + map(response => this.mapFiscalActivities(response, fiscal)) + ) + ); + + forkJoin(activityRequests).subscribe(allActivityArrays => + this.handleActivitiesResponse(allActivityArrays.flat()) + ); + } + + private mapFiscalActivities(response: any, fiscal: any): any[] { + const activities = response?._embedded?.activities ?? []; + return activities.map((activity: any) => ({ + ...activity, + fiscalYear: fiscal.fiscalYear, + projectPlanFiscalGuid: fiscal.projectPlanFiscalGuid + })); + } + + private handleActivitiesResponse(allActivities: any[]): void { + if (allActivities.length === 0) { + this.getProjectCoordinates(); + return; + } + + const boundaryRequests = allActivities.map(activity => + this.projectService + .getActivityBoundaries(this.projectGuid, activity.projectPlanFiscalGuid, activity.activityGuid) + .pipe( + map(boundary => this.mapActivityBoundary(boundary, activity)) + ) + ); + + forkJoin(boundaryRequests).subscribe(allResults => + this.handleBoundariesResponse(allResults) + ); + } + + private mapActivityBoundary(boundary: any, activity: any): any { + return boundary ? { + activityGuid: activity.activityGuid, + fiscalYear: activity.fiscalYear, + boundary: boundary?._embedded?.activityBoundary + } : null; + } + + private handleBoundariesResponse(results: any[]): void { + this.allActivityBoundaries = results.filter(r => r?.boundary && Object.keys(r.boundary).length > 0); + + const hasActivityPolygons = this.allActivityBoundaries.length > 0; + const hasProjectPolygons = this.projectBoundary?.length > 0; + + if (hasActivityPolygons && this.map) { + this.plotBoundariesOnMap(this.allActivityBoundaries); + } + + if (!hasActivityPolygons && !hasProjectPolygons) { + this.getProjectCoordinates(); } } plotBoundariesOnMap(boundaries: any[]): void { - const currentFiscalPolygons: L.Layer[] = []; + const allFiscalPolygons: L.Layer[] = []; boundaries.forEach(boundaryEntry => { const fiscalYear = boundaryEntry.fiscalYear; @@ -167,7 +181,7 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { for (const item of boundaryEntry.boundary) { const geometry = item.geometry; if (!geometry) continue; - + const geoJsonOptions: L.GeoJSONOptions = { style: { color, @@ -175,12 +189,10 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { fillOpacity: 0.1 } }; - + const addToMap = (geom: any) => { const layer = L.geoJSON(geom, geoJsonOptions).addTo(this.map!); - if (fiscalYear === this.currentFiscalYear) { - currentFiscalPolygons.push(layer); - } + allFiscalPolygons.push(layer); // Track all layers }; if (geometry.type === 'GeometryCollection') { @@ -191,12 +203,11 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { addToMap(geometry); } } - }); - // Zoom to current fiscal year polygons - if (currentFiscalPolygons.length > 0) { - const group = L.featureGroup(currentFiscalPolygons); + // Zoom to ALL fiscal year polygons + if (allFiscalPolygons.length > 0) { + const group = L.featureGroup(allFiscalPolygons); this.map!.fitBounds(group.getBounds(), { padding: [20, 20] }); } } @@ -247,17 +258,17 @@ export class FiscalMapComponent implements AfterViewInit, OnDestroy, OnInit { zoomControl: true, }); - (this.map.zoomControl as L.Control.Zoom).setPosition('topright'); + (this.map.zoomControl).setPosition('topright'); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(this.map); const legendHelper = new LeafletLegendService(); - legendHelper.addLegend(this.map!, this.fiscalColorMap); + legendHelper.addLegend(this.map, this.fiscalColorMap); const bcBounds = L.latLngBounds([48.3, -139.1], [60.0, -114.0]); this.map.fitBounds(bcBounds, { padding: [20, 20] }); - createFullPageControl(() => this.openFullMap()).addTo(this.map!); + createFullPageControl(() => this.openFullMap()).addTo(this.map); } openFullMap(): void {