From 5aaef9e08b488a546513c166885a4a73b1bc11d4 Mon Sep 17 00:00:00 2001 From: Alejandro Sanchez Date: Tue, 9 Jul 2024 12:52:14 -0700 Subject: [PATCH] PSP-7158 Show markers positioned corresponding to that file (#4147) * Backend changes to support property locations per file * Fix null pointer error * Frontend changes * Regenerate TS api models * Update file models with marker location per file * Update mocks * Test updates * Bug fix * Display file marker location when opening sidebar * Test updates --- .../api/Services/AcquisitionFileService.cs | 37 +++++++++++--- .../backend/api/Services/IPropertyService.cs | 5 ++ .../backend/api/Services/PropertyService.cs | 40 +++++++++++++++ .../AcquisitionFilePropertyMap.cs | 2 + .../Models/Concepts/File/FilePropertyModel.cs | 7 ++- .../Models/Concepts/Property/GeometryModel.cs | 2 +- .../Services/AcquisitionFileServiceTest.cs | 1 + .../mapFSM/useLocationFeatureLoader.tsx | 2 + .../LayerPopup/LayerPopupView.test.tsx | 10 +++- .../src/components/propertySelector/models.ts | 2 + .../PropertySelectorSearchContainer.tsx | 9 +--- .../list/DispositionListView.test.tsx | 1 + .../DispositionSearchResults.test.tsx | 3 ++ .../detail/LeaseHeaderAddresses.test.tsx | 5 ++ .../LeasePages/surplus/Surplus.test.tsx | 3 ++ source/frontend/src/features/leases/models.ts | 1 + .../add/AddAcquisitionContainer.test.tsx | 2 + .../mapSideBar/acquisition/add/models.ts | 27 +++++----- .../hooks/useGenerateH0443.test.tsx | 2 + .../mapSideBar/context/sidebarContext.tsx | 5 +- .../models/DispositionFormModel.ts | 27 +++++----- .../detail/PropertyResearchTabView.test.tsx | 3 +- .../update/UpdatePropertyForm.test.tsx | 1 + .../tabs/propertyResearch/update/models.ts | 4 +- .../add/AddResearchContainer.test.tsx | 1 + .../mapSideBar/research/add/models.ts | 36 ++++++++------ .../src/features/mapSideBar/shared/models.ts | 22 ++++++--- .../properties/UpdateProperties.test.tsx | 1 + .../research/list/ResearchListView.test.tsx | 2 + .../src/hooks/useDraftMarkerSynchronizer.ts | 5 +- .../src/mocks/acquisitionFiles.mock.ts | 2 + source/frontend/src/mocks/featureset.mock.ts | 4 ++ .../frontend/src/mocks/fileProperty.mock.ts | 1 + source/frontend/src/mocks/properties.mock.ts | 3 ++ .../frontend/src/mocks/researchFile.mock.ts | 1 + .../generated/ApiGen_Concepts_FileProperty.ts | 2 + .../src/utils/mapPropertyUtils.test.tsx | 49 +++++++++++-------- source/frontend/src/utils/mapPropertyUtils.ts | 27 ++++++++++ 38 files changed, 265 insertions(+), 92 deletions(-) diff --git a/source/backend/api/Services/AcquisitionFileService.cs b/source/backend/api/Services/AcquisitionFileService.cs index f48ecad9fb..c190a0a871 100644 --- a/source/backend/api/Services/AcquisitionFileService.cs +++ b/source/backend/api/Services/AcquisitionFileService.cs @@ -223,6 +223,12 @@ public PimsAcquisitionFile Add(PimsAcquisitionFile acquisitionFile, IEnumerable< PopulateAcquisitionChecklist(acquisitionFile); + // Update file specific marker locations + foreach (var incomingAcquisitionProperty in acquisitionFile.PimsPropertyAcquisitionFiles) + { + _propertyService.PopulateNewFileProperty(incomingAcquisitionProperty); + } + acquisitionFile.AcquisitionFileStatusTypeCode = AcquisitionStatusTypes.ACTIVE.ToString(); var newAcqFile = _acqFileRepository.Add(acquisitionFile); _acqFileRepository.CommitTransaction(); @@ -282,7 +288,7 @@ public PimsAcquisitionFile Update(PimsAcquisitionFile acquisitionFile, IEnumerab public PimsAcquisitionFile UpdateProperties(PimsAcquisitionFile acquisitionFile, IEnumerable userOverrides) { - _logger.LogInformation("Updating acquisition file properties..."); + _logger.LogInformation("Updating acquisition file properties with AcquisitionFile id: {id}", acquisitionFile.Internal_Id); _user.ThrowIfNotAllAuthorized(Permissions.AcquisitionFileEdit, Permissions.PropertyView, Permissions.PropertyAdd); _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFile.Internal_Id); @@ -299,7 +305,7 @@ public PimsAcquisitionFile UpdateProperties(PimsAcquisitionFile acquisitionFile, } // Get the current properties in the research file - var currentProperties = _acquisitionFilePropertyRepository.GetPropertiesByAcquisitionFileId(acquisitionFile.Internal_Id); + var currentFileProperties = _acquisitionFilePropertyRepository.GetPropertiesByAcquisitionFileId(acquisitionFile.Internal_Id); // Check if the property is new or if it is being updated foreach (var incomingAcquisitionProperty in acquisitionFile.PimsPropertyAcquisitionFiles) @@ -307,22 +313,37 @@ public PimsAcquisitionFile UpdateProperties(PimsAcquisitionFile acquisitionFile, // If the property is not new, check if the name has been updated. if (incomingAcquisitionProperty.Internal_Id != 0) { - PimsPropertyAcquisitionFile existingProperty = currentProperties.FirstOrDefault(x => x.Internal_Id == incomingAcquisitionProperty.Internal_Id); - if (existingProperty.PropertyName != incomingAcquisitionProperty.PropertyName) + var needsUpdate = false; + PimsPropertyAcquisitionFile existingFileProperty = currentFileProperties.FirstOrDefault(x => x.Internal_Id == incomingAcquisitionProperty.Internal_Id); + if (existingFileProperty.PropertyName != incomingAcquisitionProperty.PropertyName) + { + existingFileProperty.PropertyName = incomingAcquisitionProperty.PropertyName; + needsUpdate = true; + } + + var incomingGeom = incomingAcquisitionProperty.Location; + var existingGeom = existingFileProperty.Location; + if (existingGeom is null || (incomingGeom is not null && !existingGeom.EqualsExact(incomingGeom))) + { + _propertyService.UpdateFilePropertyLocation(incomingAcquisitionProperty, existingFileProperty); + needsUpdate = true; + } + + if (needsUpdate) { - existingProperty.PropertyName = incomingAcquisitionProperty.PropertyName; - _acquisitionFilePropertyRepository.Update(existingProperty); + _acquisitionFilePropertyRepository.Update(existingFileProperty); } } else { // New property needs to be added - _acquisitionFilePropertyRepository.Add(incomingAcquisitionProperty); + var newFileProperty = _propertyService.PopulateNewFileProperty(incomingAcquisitionProperty); + _acquisitionFilePropertyRepository.Add(newFileProperty); } } // The ones not on the new set should be deleted - List differenceSet = currentProperties.Where(x => !acquisitionFile.PimsPropertyAcquisitionFiles.Any(y => y.Internal_Id == x.Internal_Id)).ToList(); + List differenceSet = currentFileProperties.Where(x => !acquisitionFile.PimsPropertyAcquisitionFiles.Any(y => y.Internal_Id == x.Internal_Id)).ToList(); foreach (var deletedProperty in differenceSet) { var acqFileProperties = _acquisitionFilePropertyRepository.GetPropertiesByAcquisitionFileId(acquisitionFile.Internal_Id).FirstOrDefault(ap => ap.PropertyId == deletedProperty.PropertyId); diff --git a/source/backend/api/Services/IPropertyService.cs b/source/backend/api/Services/IPropertyService.cs index c826778e1b..b2b40d39fb 100644 --- a/source/backend/api/Services/IPropertyService.cs +++ b/source/backend/api/Services/IPropertyService.cs @@ -47,6 +47,11 @@ public interface IPropertyService void UpdateLocation(PimsProperty incomingProperty, ref PimsProperty propertyToUpdate, IEnumerable overrideCodes); + T PopulateNewFileProperty(T fileProperty); + + void UpdateFilePropertyLocation(T incomingFileProperty, T filePropertyToUpdate) + where T : IWithPropertyEntity; + IList GetHistoricalNumbersForPropertyId(long propertyId); IList UpdateHistoricalFileNumbers(long propertyId, IEnumerable pimsHistoricalNumbers); diff --git a/source/backend/api/Services/PropertyService.cs b/source/backend/api/Services/PropertyService.cs index 696eb9a711..969671c42d 100644 --- a/source/backend/api/Services/PropertyService.cs +++ b/source/backend/api/Services/PropertyService.cs @@ -377,6 +377,40 @@ public void UpdateLocation(PimsProperty incomingProperty, ref PimsProperty prope } } + public T PopulateNewFileProperty(T fileProperty) + { + // TODO: Remove this casting when LOCATION gets added to all remaining file-property types (research, disposition, lease) + if (fileProperty is PimsPropertyAcquisitionFile acquisitionFileProperty) + { + // convert spatial location from lat/long (4326) to BC Albers (3005) for database storage + var geom = acquisitionFileProperty.Location; + if (geom is not null && geom.SRID != SpatialReference.BCALBERS) + { + var newCoords = _coordinateService.TransformCoordinates(geom.SRID, SpatialReference.BCALBERS, geom.Coordinate); + acquisitionFileProperty.Location = GeometryHelper.CreatePoint(newCoords, SpatialReference.BCALBERS); + } + } + + return fileProperty; + } + + public void UpdateFilePropertyLocation(T incomingFileProperty, T filePropertyToUpdate) + where T : IWithPropertyEntity + { + // TODO: Remove this casting when LOCATION gets added to all remaining file-property types (research, disposition, lease) + if (incomingFileProperty is PimsPropertyAcquisitionFile incomingAcquisitionProperty + && filePropertyToUpdate is PimsPropertyAcquisitionFile acquisitionPropertyToUpdate) + { + // convert spatial location from lat/long (4326) to BC Albers (3005) for database storage + var geom = incomingAcquisitionProperty.Location; + if (geom is not null && geom.SRID != SpatialReference.BCALBERS) + { + var newCoords = _coordinateService.TransformCoordinates(geom.SRID, SpatialReference.BCALBERS, geom.Coordinate); + acquisitionPropertyToUpdate.Location = GeometryHelper.CreatePoint(newCoords, SpatialReference.BCALBERS); + } + } + } + public IList GetHistoricalNumbersForPropertyId(long propertyId) { @@ -415,6 +449,12 @@ public List TransformAllPropertiesToLatLong(List fileProperties) { foreach (var fileProperty in fileProperties) { + // TODO: Remove this casting when LOCATION gets added to all remaining file-property types (research, disposition, lease) + if (fileProperty is PimsPropertyAcquisitionFile acquisitionFileProperty && acquisitionFileProperty.Location is not null) + { + acquisitionFileProperty.Location = TransformCoordinates(acquisitionFileProperty.Location); + } + TransformPropertyToLatLong(fileProperty.Property); } diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs index 438dc81333..7b13a6d484 100644 --- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs +++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs @@ -13,6 +13,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.Id, src => src.PropertyAcquisitionFileId) .Map(dest => dest.PropertyName, src => src.PropertyName) .Map(dest => dest.DisplayOrder, src => src.DisplayOrder) + .Map(dest => dest.Location, src => src.Location) .Map(dest => dest.Property, src => src.Property) .Map(dest => dest.PropertyId, src => src.PropertyId) .Map(dest => dest.File, src => src.AcquisitionFile) @@ -27,6 +28,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.AcquisitionFileId, src => src.FileId) .Map(dest => dest.PropertyName, src => src.PropertyName) .Map(dest => dest.DisplayOrder, src => src.DisplayOrder) + .Map(dest => dest.Location, src => src.Location) .Inherits(); } } diff --git a/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs b/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs index f0e6be807a..e1722df790 100644 --- a/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs +++ b/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs @@ -13,10 +13,15 @@ public class FilePropertyModel : BaseConcurrentModel public long Id { get; set; } /// - /// get/set - The descriptive name of the property for this acquisition file. + /// get/set - The descriptive name of the property for this file. /// public string PropertyName { get; set; } + /// + /// get/set - The location of the property in the context of this file. + /// + public GeometryModel Location { get; set; } + /// /// get/set - The order to display the relationship. /// diff --git a/source/backend/apimodels/Models/Concepts/Property/GeometryModel.cs b/source/backend/apimodels/Models/Concepts/Property/GeometryModel.cs index 3269fdd33d..daf66d9225 100644 --- a/source/backend/apimodels/Models/Concepts/Property/GeometryModel.cs +++ b/source/backend/apimodels/Models/Concepts/Property/GeometryModel.cs @@ -5,7 +5,7 @@ public class GeometryModel #region Properties /// - /// get/set - The cordinate for the geometry. + /// get/set - The coordinate for the geometry. /// public CoordinateModel Coordinate { get; set; } diff --git a/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs b/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs index 3242bbfcaa..489b7c278e 100644 --- a/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs +++ b/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs @@ -1344,6 +1344,7 @@ public void UpdateProperties_MatchProperties_NewProperty_Success() RegionCode = 1, } ); + propertyService.Setup(x => x.PopulateNewFileProperty(It.IsAny())).Returns(x => x); var userRepository = this._helper.GetService>(); userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(EntityHelper.CreateUser(1, Guid.NewGuid(), "Test", regionCode: 1)); diff --git a/source/frontend/src/components/common/mapFSM/useLocationFeatureLoader.tsx b/source/frontend/src/components/common/mapFSM/useLocationFeatureLoader.tsx index 215b59247e..980500cc85 100644 --- a/source/frontend/src/components/common/mapFSM/useLocationFeatureLoader.tsx +++ b/source/frontend/src/components/common/mapFSM/useLocationFeatureLoader.tsx @@ -16,6 +16,7 @@ import { PIMS_Property_Location_View } from '@/models/layers/pimsPropertyLocatio export interface LocationFeatureDataset { selectingComponentId: string | null; location: LatLngLiteral; + fileLocation: LatLngLiteral | null; pimsFeature: Feature | null; parcelFeature: Feature | null; regionFeature: Feature | null; @@ -44,6 +45,7 @@ const useLocationFeatureLoader = () => { const result: LocationFeatureDataset = { selectingComponentId: null, location: latLng, + fileLocation: latLng, pimsFeature: null, parcelFeature: null, regionFeature: null, diff --git a/source/frontend/src/components/maps/leaflet/LayerPopup/LayerPopupView.test.tsx b/source/frontend/src/components/maps/leaflet/LayerPopup/LayerPopupView.test.tsx index 1b701d6e36..ff7e4d491b 100644 --- a/source/frontend/src/components/maps/leaflet/LayerPopup/LayerPopupView.test.tsx +++ b/source/frontend/src/components/maps/leaflet/LayerPopup/LayerPopupView.test.tsx @@ -86,6 +86,7 @@ describe('LayerPopupView component', () => { geometry: { type: 'Point', coordinates: [] }, }, location: { lat: 0, lng: 0 }, + fileLocation: null, parcelFeature: null, regionFeature: null, districtFeature: null, @@ -112,6 +113,7 @@ describe('LayerPopupView component', () => { geometry: { type: 'Point', coordinates: [] }, }, location: { lat: 0, lng: 0 }, + fileLocation: null, pimsFeature: null, regionFeature: null, districtFeature: null, @@ -139,6 +141,7 @@ describe('LayerPopupView component', () => { geometry: { type: 'Point', coordinates: [] }, }, location: { lat: 0, lng: 0 }, + fileLocation: null, pimsFeature: { type: 'Feature', properties: null as any, @@ -184,12 +187,13 @@ describe('LayerPopupView component', () => { expect(history.location.pathname).toBe('/mapview/sidebar/acquisition/new'); }); - it('Hides subdivision and consolidation if not in the pims system', async () => { + it('hides subdivision and consolidation if not in the pims system', async () => { const { getByTestId, getByText, queryByText } = setup({ layerPopup: { data: {} } as any, featureDataset: { pimsFeature: null, location: { lat: 0, lng: 0 }, + fileLocation: null, parcelFeature: null, regionFeature: null, districtFeature: null, @@ -206,7 +210,7 @@ describe('LayerPopupView component', () => { expect(consolidationLink).not.toBeInTheDocument(); }); - it('handles create create subdivision action', async () => { + it('handles create subdivision action', async () => { const propertyId = 1; const { getByTestId, getByText } = setup({ @@ -218,6 +222,7 @@ describe('LayerPopupView component', () => { geometry: { type: 'Point', coordinates: [] }, }, location: { lat: 0, lng: 0 }, + fileLocation: null, parcelFeature: null, regionFeature: null, districtFeature: null, @@ -245,6 +250,7 @@ describe('LayerPopupView component', () => { geometry: { type: 'Point', coordinates: [] }, }, location: { lat: 0, lng: 0 }, + fileLocation: null, parcelFeature: null, regionFeature: null, districtFeature: null, diff --git a/source/frontend/src/components/propertySelector/models.ts b/source/frontend/src/components/propertySelector/models.ts index 41a19021bc..e8ae0e6786 100644 --- a/source/frontend/src/components/propertySelector/models.ts +++ b/source/frontend/src/components/propertySelector/models.ts @@ -1,4 +1,5 @@ import { MultiPolygon, Polygon } from 'geojson'; +import { LatLngLiteral } from 'leaflet'; import { AreaUnitTypes } from '@/constants'; @@ -8,6 +9,7 @@ export interface IMapProperty { pin?: string; latitude?: number; longitude?: number; + fileLocation?: LatLngLiteral; polygon?: Polygon | MultiPolygon; planNumber?: string; address?: string; diff --git a/source/frontend/src/components/propertySelector/search/PropertySelectorSearchContainer.tsx b/source/frontend/src/components/propertySelector/search/PropertySelectorSearchContainer.tsx index a0a3f8c95f..7a02fbe5c3 100644 --- a/source/frontend/src/components/propertySelector/search/PropertySelectorSearchContainer.tsx +++ b/source/frontend/src/components/propertySelector/search/PropertySelectorSearchContainer.tsx @@ -207,14 +207,7 @@ export const PropertySelectorSearchContainer: React.FC, -) => { +export const featureToLocationFeatureDataset = (feature: Feature) => { const center = getFeatureBoundedCenter(feature); return { parcelFeature: feature, diff --git a/source/frontend/src/features/disposition/list/DispositionListView.test.tsx b/source/frontend/src/features/disposition/list/DispositionListView.test.tsx index dd2c65b110..f59b7b6cc2 100644 --- a/source/frontend/src/features/disposition/list/DispositionListView.test.tsx +++ b/source/frontend/src/features/disposition/list/DispositionListView.test.tsx @@ -121,6 +121,7 @@ describe('Disposition List View', () => { displayOrder: null, file: null, propertyName: null, + location: null, rowVersion: null, }, ], diff --git a/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx b/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx index 6dafee09ec..7c2e422ef7 100644 --- a/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx +++ b/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx @@ -89,6 +89,7 @@ describe('Disposition search results table', () => { displayOrder: null, file: null, propertyName: null, + location: null, rowVersion: null, }, { @@ -99,6 +100,7 @@ describe('Disposition search results table', () => { displayOrder: null, file: null, propertyName: null, + location: null, rowVersion: null, }, { @@ -109,6 +111,7 @@ describe('Disposition search results table', () => { displayOrder: null, file: null, propertyName: null, + location: null, rowVersion: null, }, ], diff --git a/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx b/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx index f18ab83aaf..71a35ca5c7 100644 --- a/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx +++ b/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx @@ -65,6 +65,7 @@ describe('LeaseHeaderAddresses component', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, { @@ -77,6 +78,7 @@ describe('LeaseHeaderAddresses component', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, { @@ -89,6 +91,7 @@ describe('LeaseHeaderAddresses component', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, { @@ -101,6 +104,7 @@ describe('LeaseHeaderAddresses component', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, { @@ -113,6 +117,7 @@ describe('LeaseHeaderAddresses component', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, ], diff --git a/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx index 3aad54c314..ea545c79cb 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx @@ -70,6 +70,7 @@ describe('Lease Surplus Declaration', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, ], @@ -103,6 +104,7 @@ describe('Lease Surplus Declaration', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, ], @@ -144,6 +146,7 @@ describe('Lease Surplus Declaration', () => { id: 0, propertyId: 0, propertyName: null, + location: null, rowVersion: null, }, ], diff --git a/source/frontend/src/features/leases/models.ts b/source/frontend/src/features/leases/models.ts index 9453316ebe..3de0a0f711 100644 --- a/source/frontend/src/features/leases/models.ts +++ b/source/frontend/src/features/leases/models.ts @@ -245,6 +245,7 @@ export class FormLeaseProperty { ? toTypeCodeNullable(formLeaseProperty.areaUnitTypeCode) ?? null : null, displayOrder: null, + location: null, // TODO: Add proper file location values when DB schema gets added ...getEmptyBaseAudit(formLeaseProperty.rowVersion), }; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx index 8c8c90e226..ec7f1cf141 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx @@ -230,6 +230,7 @@ describe('AddAcquisitionContainer component', () => { ...mapMachineBaseMock, selectedFeatureDataset: { location: { lng: -120.69195885, lat: 50.25163372 }, + fileLocation: null, pimsFeature: null, parcelFeature: null, regionFeature: { @@ -258,6 +259,7 @@ describe('AddAcquisitionContainer component', () => { ...mapMachineBaseMock, selectedFeatureDataset: { location: { lng: -120.69195885, lat: 50.25163372 }, + fileLocation: null, pimsFeature: null, parcelFeature: null, regionFeature: { diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/models.ts b/source/frontend/src/features/mapSideBar/acquisition/add/models.ts index d08b1b216a..89975cd409 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/add/models.ts @@ -4,6 +4,7 @@ import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_C import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; import { ApiGen_Concepts_AcquisitionFileProperty } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileProperty'; import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { latLngToApiLocation } from '@/utils'; import { fromTypeCode, stringToNumberOrNull, toTypeCodeNullable } from '@/utils/formUtils'; import { exists, isValidId, isValidIsoDateTime } from '@/utils/utils'; @@ -62,17 +63,7 @@ export class AcquisitionForm implements WithAcquisitionTeam, WithAcquisitionOwne fundingTypeCode: toTypeCodeNullable(this.fundingTypeCode), fundingOther: this.fundingTypeOtherDescription, // ACQ file properties - fileProperties: this.properties.map(ap => ({ - id: ap.id ?? 0, - propertyName: ap.name ?? null, - displayOrder: ap.displayOrder ?? null, - rowVersion: ap.rowVersion ?? null, - property: ap.toApi(), - propertyId: ap.apiId ?? 0, - fileId: this.id ?? 0, - acquisitionFile: null, - file: null, - })), + fileProperties: this.properties.map(x => this.toPropertyApi(x)), acquisitionFileOwners: this.owners .filter(x => !x.isEmpty()) .map(x => x.toApi()), @@ -95,6 +86,20 @@ export class AcquisitionForm implements WithAcquisitionTeam, WithAcquisitionOwne }; } + private toPropertyApi(x: PropertyForm): ApiGen_Concepts_AcquisitionFileProperty { + return { + id: x.id ?? 0, + fileId: this.id ?? 0, + property: x.toApi(), + propertyId: x.apiId ?? 0, + propertyName: x.name ?? null, + location: latLngToApiLocation(x.fileLocation?.lat, x.fileLocation?.lng), + displayOrder: x.displayOrder ?? null, + rowVersion: x.rowVersion ?? null, + file: null, + }; + } + static fromApi(model: ApiGen_Concepts_AcquisitionFile): AcquisitionForm { const newForm = new AcquisitionForm(); newForm.id = model.id; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx index c1607c0a43..fd89ee6abf 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx @@ -283,6 +283,7 @@ describe('useGenerateH0443 functions', () => { id: 0, property: null, propertyName: null, + location: null, rowVersion: null, }, { @@ -293,6 +294,7 @@ describe('useGenerateH0443 functions', () => { id: 0, property: null, propertyName: null, + location: null, rowVersion: null, }, ], diff --git a/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx b/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx index 71bd143b06..58bd43b52f 100644 --- a/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx +++ b/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx @@ -7,7 +7,7 @@ import { Api_LastUpdatedBy } from '@/models/api/File'; import { ApiGen_Concepts_File } from '@/models/api/generated/ApiGen_Concepts_File'; import { ApiGen_Concepts_Project } from '@/models/api/generated/ApiGen_Concepts_Project'; import { exists } from '@/utils'; -import { getLatLng } from '@/utils/mapPropertyUtils'; +import { getLatLng, locationFromFileProperty } from '@/utils/mapPropertyUtils'; export interface TypedFile extends ApiGen_Concepts_File { fileType: FileTypes; @@ -120,7 +120,8 @@ export const SideBarContextProvider = (props: { const resetFilePropertyLocations = useCallback(() => { if (exists(fileProperties)) { const propertyLocations = fileProperties - .map(x => getLatLng(x.property?.location)) + .map(x => locationFromFileProperty(x)) + .map(y => getLatLng(y)) .filter(exists); setFilePropertyLocations && setFilePropertyLocations(propertyLocations); diff --git a/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts b/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts index 6fb77c2464..5519190eba 100644 --- a/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts +++ b/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts @@ -2,6 +2,7 @@ import { IAutocompletePrediction } from '@/interfaces/IAutocomplete'; import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { ApiGen_Concepts_DispositionFileProperty } from '@/models/api/generated/ApiGen_Concepts_DispositionFileProperty'; import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { latLngToApiLocation } from '@/utils'; import { emptyStringtoNullable, fromTypeCode, toTypeCodeNullable } from '@/utils/formUtils'; import { exists, isValidIsoDateTime } from '@/utils/utils'; @@ -83,17 +84,7 @@ export class DispositionFormModel implements WithDispositionTeam { .filter(x => !!x.contact && !!x.teamProfileTypeCode) .map(x => x.toApi(this.id || 0)) .filter(exists), - fileProperties: this.fileProperties.map(ap => ({ - id: ap.id ?? 0, - propertyName: ap.name ?? null, - displayOrder: ap.displayOrder ?? null, - rowVersion: ap.rowVersion ?? null, - property: ap.toApi(), - propertyId: ap.apiId ?? 0, - file: null, - fileId: 0, - })), - + fileProperties: this.fileProperties.map(x => this.toPropertyApi(x)), dispositionOffers: this.offers.map(x => x.toApi()), dispositionSale: this.sale ? this.sale.toApi() : null, dispositionAppraisal: this.appraisal ? this.appraisal.toApi() : null, @@ -102,6 +93,20 @@ export class DispositionFormModel implements WithDispositionTeam { }; } + private toPropertyApi(x: PropertyForm): ApiGen_Concepts_DispositionFileProperty { + return { + id: x.id ?? 0, + fileId: this.id ?? 0, + property: x.toApi(), + propertyId: x.apiId ?? 0, + propertyName: x.name ?? null, + location: latLngToApiLocation(x.fileLocation?.lat, x.fileLocation?.lng), + displayOrder: x.displayOrder ?? null, + rowVersion: x.rowVersion ?? null, + file: null, + }; + } + static fromApi(model: ApiGen_Concepts_DispositionFile): DispositionFormModel { const dispositionForm = new DispositionFormModel( model.id, diff --git a/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/detail/PropertyResearchTabView.test.tsx b/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/detail/PropertyResearchTabView.test.tsx index 61c52e0b12..9843fa2e10 100644 --- a/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/detail/PropertyResearchTabView.test.tsx +++ b/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/detail/PropertyResearchTabView.test.tsx @@ -72,7 +72,8 @@ const fakePropertyResearch: ApiGen_Concepts_ResearchFileProperty = { rowVersion: null, }, ], - displayOrder: null, fileId: 0, + displayOrder: null, + location: null, rowVersion: null, }; diff --git a/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/UpdatePropertyForm.test.tsx b/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/UpdatePropertyForm.test.tsx index 6e452bb597..39648bd817 100644 --- a/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/UpdatePropertyForm.test.tsx +++ b/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/UpdatePropertyForm.test.tsx @@ -47,6 +47,7 @@ const testResearchFile: ApiGen_Concepts_ResearchFileProperty = { file: null, displayOrder: null, property: null, + location: null, fileId: 0, }; diff --git a/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/models.ts b/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/models.ts index 9a3e7c26e2..47ea175c7a 100644 --- a/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/models.ts +++ b/source/frontend/src/features/mapSideBar/property/tabs/propertyResearch/update/models.ts @@ -1,9 +1,8 @@ import { ApiGen_Concepts_PropertyPurpose } from '@/models/api/generated/ApiGen_Concepts_PropertyPurpose'; import { ApiGen_Concepts_ResearchFileProperty } from '@/models/api/generated/ApiGen_Concepts_ResearchFileProperty'; +import { getEmptyResearchFile } from '@/models/defaultInitializers'; import { exists } from '@/utils/utils'; -import { getEmptyResearchFile } from './../../../../../../models/defaultInitializers'; - export class PropertyResearchFilePurposeFormModel { public id?: number; public propertyPurposeTypeCode?: string; @@ -101,6 +100,7 @@ export class UpdatePropertyFormModel { documentReference: this.documentReference ?? null, researchSummary: this.researchSummary ?? null, property: null, + location: null, // TODO: Add proper file location values when DB schema gets added fileId: this.researchFileId ?? 0, file: { ...getEmptyResearchFile(), rowVersion: this.researchFileRowVersion }, purposeTypes: this.purposeTypes?.map(x => x.toApi()) ?? null, diff --git a/source/frontend/src/features/mapSideBar/research/add/AddResearchContainer.test.tsx b/source/frontend/src/features/mapSideBar/research/add/AddResearchContainer.test.tsx index c2fa74c739..4498aaed3d 100644 --- a/source/frontend/src/features/mapSideBar/research/add/AddResearchContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/research/add/AddResearchContainer.test.tsx @@ -96,6 +96,7 @@ describe('AddResearchContainer component', () => { ...mapMachineBaseMock, selectedFeatureDataset: { location: { lat: 0, lng: 0 }, + fileLocation: null, pimsFeature: null, parcelFeature: selectedFeature, regionFeature: null, diff --git a/source/frontend/src/features/mapSideBar/research/add/models.ts b/source/frontend/src/features/mapSideBar/research/add/models.ts index ef38fd01cb..dc89e21ed2 100644 --- a/source/frontend/src/features/mapSideBar/research/add/models.ts +++ b/source/frontend/src/features/mapSideBar/research/add/models.ts @@ -1,6 +1,7 @@ import { ApiGen_Concepts_ResearchFile } from '@/models/api/generated/ApiGen_Concepts_ResearchFile'; import { ApiGen_Concepts_ResearchFileProperty } from '@/models/api/generated/ApiGen_Concepts_ResearchFileProperty'; import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { latLngToApiLocation } from '@/utils'; import { PropertyForm } from '../../shared/models'; import { ResearchFileProjectFormModel } from '../common/models'; @@ -22,22 +23,7 @@ export class ResearchForm { return { id: this.id ?? 0, fileName: this.name, - fileProperties: this.properties.map(x => ({ - id: x.id ?? 0, - property: x.toApi(), - propertyId: x.apiId ?? 0, - researchFile: { id: this.id }, - propertyName: x.name ?? null, - rowVersion: x.rowVersion ?? null, - displayOrder: null, - documentReference: null, - file: null, - fileId: this.id ?? 0, - isLegalOpinionObtained: null, - isLegalOpinionRequired: null, - purposeTypes: null, - researchSummary: null, - })), + fileProperties: this.properties.map(x => this.toPropertyApi(x)), researchFileProjects: ResearchFileProjectFormModel.toApiList(this.researchFileProjects), ...getEmptyBaseAudit(this.rowVersion), expropriationNotes: null, @@ -58,6 +44,24 @@ export class ResearchForm { }; } + private toPropertyApi(x: PropertyForm): ApiGen_Concepts_ResearchFileProperty { + return { + id: x.id ?? 0, + property: x.toApi(), + propertyId: x.apiId ?? 0, + propertyName: x.name ?? null, + rowVersion: x.rowVersion ?? null, + displayOrder: x.displayOrder ?? null, + location: latLngToApiLocation(x.fileLocation?.lat, x.fileLocation?.lng), + documentReference: null, + file: null, + fileId: this.id ?? 0, + isLegalOpinionObtained: null, + isLegalOpinionRequired: null, + purposeTypes: null, + researchSummary: null, + }; + } public static fromApi(model: ApiGen_Concepts_ResearchFile): ResearchForm { const newForm = new ResearchForm(); newForm.id = model.id; diff --git a/source/frontend/src/features/mapSideBar/shared/models.ts b/source/frontend/src/features/mapSideBar/shared/models.ts index fbf2e1aa0b..e1c35d7ba8 100644 --- a/source/frontend/src/features/mapSideBar/shared/models.ts +++ b/source/frontend/src/features/mapSideBar/shared/models.ts @@ -1,4 +1,5 @@ import { MultiPolygon, Polygon } from 'geojson'; +import { LatLngLiteral } from 'leaflet'; import { isNumber } from 'lodash'; import { LocationFeatureDataset } from '@/components/common/mapFSM/useLocationFeatureLoader'; @@ -21,6 +22,8 @@ import { exists, formatApiAddress, formatBcaAddress, + getLatLng, + latLngToApiLocation, pidFromFeatureSet, pidParser, pinFromFeatureSet, @@ -55,6 +58,7 @@ export class FileForm { property: x.toApi(), propertyId: x.apiId ?? 0, propertyName: x.name ?? null, + location: latLngToApiLocation(x.fileLocation?.lat, x.fileLocation?.lng), rowVersion: x.rowVersion ?? null, displayOrder: null, file: null, @@ -80,6 +84,7 @@ export class PropertyForm { public pin?: string; public latitude?: number; public longitude?: number; + public fileLocation?: LatLngLiteral; public polygon?: Polygon | MultiPolygon; public planNumber?: string; public name?: string; @@ -109,6 +114,7 @@ export class PropertyForm { pin: model.pin, latitude: model.latitude, longitude: model.longitude, + fileLocation: model.fileLocation, polygon: model.polygon, planNumber: model.planNumber, region: model.region, @@ -124,14 +130,15 @@ export class PropertyForm { public static fromFeatureDataset(model: LocationFeatureDataset): PropertyForm { return new PropertyForm({ - apiId: +(model.pimsFeature?.properties?.PROPERTY_ID ?? 0), + apiId: +(model?.pimsFeature?.properties?.PROPERTY_ID ?? 0), pid: pidFromFeatureSet(model), pin: pinFromFeatureSet(model), - latitude: model.location?.lat, - longitude: model.location?.lng, + latitude: model?.location?.lat, + longitude: model?.location?.lng, + fileLocation: model?.fileLocation ?? model?.location ?? undefined, planNumber: - model.pimsFeature?.properties?.SURVEY_PLAN_NUMBER ?? - model.parcelFeature?.properties?.PLAN_NUMBER ?? + model?.pimsFeature?.properties?.SURVEY_PLAN_NUMBER ?? + model?.parcelFeature?.properties?.PLAN_NUMBER ?? '', polygon: model?.parcelFeature?.geometry?.type === ApiGen_CodeTypes_GeoJsonTypes.Polygon @@ -165,6 +172,7 @@ export class PropertyForm { pin: this.pin, latitude: this.latitude, longitude: this.longitude, + fileLocation: this.fileLocation, planNumber: this.planNumber, polygon: this.polygon, region: this.region, @@ -205,6 +213,7 @@ export class PropertyForm { geometry: this.polygon ? this.polygon : null, }, location: { lat: this.latitude, lng: this.longitude }, + fileLocation: this.fileLocation ?? { lat: this.latitude, lng: this.longitude }, regionFeature: { properties: { REGION_NAME: this.regionName, @@ -245,6 +254,7 @@ export class PropertyForm { newForm.pin = model.property?.pin?.toString(); newForm.latitude = model.property?.latitude ?? undefined; newForm.longitude = model.property?.longitude ?? undefined; + newForm.fileLocation = getLatLng(model.location) ?? undefined; newForm.planNumber = model.property?.planNumber ?? undefined; newForm.region = model.property?.region?.id ?? undefined; newForm.district = model.property?.district?.id ?? undefined; @@ -295,7 +305,7 @@ export class PropertyForm { pid: pidParser(this.pid) ?? null, pin: this.pin !== undefined ? Number(this.pin) : null, planNumber: this.planNumber ?? null, - location: { coordinate: { x: this.longitude ?? 0, y: this.latitude ?? 0 } }, + location: latLngToApiLocation(this.latitude, this.longitude), boundary: this.polygon ? this.polygon : null, region: toTypeCodeNullable(this.region), district: toTypeCodeNullable(this.district), diff --git a/source/frontend/src/features/mapSideBar/shared/update/properties/UpdateProperties.test.tsx b/source/frontend/src/features/mapSideBar/shared/update/properties/UpdateProperties.test.tsx index bd691b88c8..fdf8bf2c6f 100644 --- a/source/frontend/src/features/mapSideBar/shared/update/properties/UpdateProperties.test.tsx +++ b/source/frontend/src/features/mapSideBar/shared/update/properties/UpdateProperties.test.tsx @@ -123,6 +123,7 @@ describe('UpdateProperties component', () => { displayOrder: null, fileId: 1, propertyName: null, + location: null, file: null, }, ], diff --git a/source/frontend/src/features/research/list/ResearchListView.test.tsx b/source/frontend/src/features/research/list/ResearchListView.test.tsx index 22dd0e96ae..ab7d615f46 100644 --- a/source/frontend/src/features/research/list/ResearchListView.test.tsx +++ b/source/frontend/src/features/research/list/ResearchListView.test.tsx @@ -571,6 +571,7 @@ const mockResearchListViewResponse: ApiGen_Concepts_ResearchFile[] = [ propertyName: null, purposeTypes: null, researchSummary: null, + location: null, file: null, }, { @@ -603,6 +604,7 @@ const mockResearchListViewResponse: ApiGen_Concepts_ResearchFile[] = [ propertyName: null, purposeTypes: null, researchSummary: null, + location: null, file: null, }, ], diff --git a/source/frontend/src/hooks/useDraftMarkerSynchronizer.ts b/source/frontend/src/hooks/useDraftMarkerSynchronizer.ts index afe69260a5..1dd618f11c 100644 --- a/source/frontend/src/hooks/useDraftMarkerSynchronizer.ts +++ b/source/frontend/src/hooks/useDraftMarkerSynchronizer.ts @@ -6,6 +6,7 @@ import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineCo import { IMapProperty } from '@/components/propertySelector/models'; import useDeepCompareEffect from '@/hooks/util/useDeepCompareEffect'; import useIsMounted from '@/hooks/util/useIsMounted'; +import { latLngFromMapProperty } from '@/utils'; /** * Get a list of file property markers from the current form values. @@ -13,9 +14,7 @@ import useIsMounted from '@/hooks/util/useIsMounted'; * @param modifiedProperties the current form values to extract lat/lngs from. */ const getFilePropertyLocations = (modifiedProperties: IMapProperty[]): LatLngLiteral[] => { - return modifiedProperties.map((property: IMapProperty) => { - return { lat: Number(property?.latitude ?? 0), lng: Number(property?.longitude ?? 0) }; - }); + return modifiedProperties.map((property: IMapProperty) => latLngFromMapProperty(property)); }; /** diff --git a/source/frontend/src/mocks/acquisitionFiles.mock.ts b/source/frontend/src/mocks/acquisitionFiles.mock.ts index 07a6a1b35d..f6be177da2 100644 --- a/source/frontend/src/mocks/acquisitionFiles.mock.ts +++ b/source/frontend/src/mocks/acquisitionFiles.mock.ts @@ -112,6 +112,7 @@ export const mockAcquisitionFileResponse = ( fileId: 1, file: null, propertyName: null, + location: null, rowVersion: 1, }, { @@ -156,6 +157,7 @@ export const mockAcquisitionFileResponse = ( fileId: 1, file: null, propertyName: null, + location: null, }, ], acquisitionTeam: [ diff --git a/source/frontend/src/mocks/featureset.mock.ts b/source/frontend/src/mocks/featureset.mock.ts index 357fcb0c80..f42b46e80c 100644 --- a/source/frontend/src/mocks/featureset.mock.ts +++ b/source/frontend/src/mocks/featureset.mock.ts @@ -6,6 +6,10 @@ export const getMockLocationFeatureDataset = (): LocationFeatureDataset => lat: 48.432802005, lng: -123.310041775, }, + fileLocation: { + lat: 48.432802005, + lng: -123.310041775, + }, pimsFeature: { properties: null, type: 'Feature', diff --git a/source/frontend/src/mocks/fileProperty.mock.ts b/source/frontend/src/mocks/fileProperty.mock.ts index fd38c572a2..bb3d953470 100644 --- a/source/frontend/src/mocks/fileProperty.mock.ts +++ b/source/frontend/src/mocks/fileProperty.mock.ts @@ -10,6 +10,7 @@ export const getEmptyFileProperty = (): ApiGen_Concepts_FileProperty => { propertyId: 0, fileId: 0, file: null, + location: null, ...getEmptyBaseAudit(), }; }; diff --git a/source/frontend/src/mocks/properties.mock.ts b/source/frontend/src/mocks/properties.mock.ts index ffd1e4c5da..2e64f45353 100644 --- a/source/frontend/src/mocks/properties.mock.ts +++ b/source/frontend/src/mocks/properties.mock.ts @@ -307,6 +307,7 @@ export const getMockApiPropertyFiles = (): ApiGen_Concepts_FileProperty[] => [ rowVersion: 3, }, displayOrder: null, + location: null, rowVersion: null, }, { @@ -351,6 +352,7 @@ export const getMockApiPropertyFiles = (): ApiGen_Concepts_FileProperty[] => [ }, displayOrder: null, propertyName: null, + location: null, rowVersion: null, }, ]; @@ -366,6 +368,7 @@ export const getEmptyPropertyLease = (): ApiGen_Concepts_PropertyLease => { displayOrder: null, property: null, propertyId: 0, + location: null, rowVersion: null, }; }; diff --git a/source/frontend/src/mocks/researchFile.mock.ts b/source/frontend/src/mocks/researchFile.mock.ts index 423225ce94..92db0c2d06 100644 --- a/source/frontend/src/mocks/researchFile.mock.ts +++ b/source/frontend/src/mocks/researchFile.mock.ts @@ -65,6 +65,7 @@ export const getMockResearchFile = (): ApiGen_Concepts_ResearchFile => ({ documentReference: null, isLegalOpinionObtained: null, file: null, + location: null, }, ], requestDate: '2022-04-14T00:00:00', diff --git a/source/frontend/src/models/api/generated/ApiGen_Concepts_FileProperty.ts b/source/frontend/src/models/api/generated/ApiGen_Concepts_FileProperty.ts index 7e66649250..304905a423 100644 --- a/source/frontend/src/models/api/generated/ApiGen_Concepts_FileProperty.ts +++ b/source/frontend/src/models/api/generated/ApiGen_Concepts_FileProperty.ts @@ -4,12 +4,14 @@ */ import { ApiGen_Base_BaseConcurrent } from './ApiGen_Base_BaseConcurrent'; import { ApiGen_Concepts_File } from './ApiGen_Concepts_File'; +import { ApiGen_Concepts_Geometry } from './ApiGen_Concepts_Geometry'; import { ApiGen_Concepts_Property } from './ApiGen_Concepts_Property'; // LINK: @backend/apimodels/Models/Concepts/File/FilePropertyModel.cs export interface ApiGen_Concepts_FileProperty extends ApiGen_Base_BaseConcurrent { id: number; propertyName: string | null; + location: ApiGen_Concepts_Geometry | null; displayOrder: number | null; property: ApiGen_Concepts_Property | null; propertyId: number; diff --git a/source/frontend/src/utils/mapPropertyUtils.test.tsx b/source/frontend/src/utils/mapPropertyUtils.test.tsx index f0ff513d25..fa67062d77 100644 --- a/source/frontend/src/utils/mapPropertyUtils.test.tsx +++ b/source/frontend/src/utils/mapPropertyUtils.test.tsx @@ -31,6 +31,7 @@ const expectedMapProperty = { landArea: 647.4646, latitude: 48.432802005, longitude: -123.310041775, + legalDescription: undefined, name: undefined, pid: '000002500', pin: undefined, @@ -39,6 +40,10 @@ const expectedMapProperty = { coordinates: [[[-123.31014591, 48.43274258]]], type: 'Polygon', }, + fileLocation: { + lat: 48.432802005, + lng: -123.310041775, + }, propertyId: undefined, region: 1, regionName: 'South Coast', @@ -90,7 +95,7 @@ describe('mapPropertyUtils', () => { [{ address: '' }, { label: NameSourceType.NONE, value: '' }], [{ address: '1234 fake st' }, { label: NameSourceType.ADDRESS, value: '1234 fake st' }], ])( - 'getPropertyName test with source %p expecting %p', + 'getPropertyName test with source %o expecting %o', (mapProperty: IMapProperty, expectedName: PropertyName) => { const actualName = getPropertyName(mapProperty); expect(actualName.label).toEqual(expectedName.label); @@ -98,24 +103,20 @@ describe('mapPropertyUtils', () => { }, ); - it('getPrettyLatLng, empty', () => { - const prettyLatLng = getPrettyLatLng(undefined); - expect(prettyLatLng).toEqual(''); - }); - - it('getPrettyLatLng, valued', () => { - const prettyLatLng = getPrettyLatLng({ coordinate: { x: 1, y: 2 } }); - expect(prettyLatLng).toEqual('1.000000, 2.000000'); - }); - - it('getLatLng, empty', () => { - const latLng = getLatLng(undefined); - expect(latLng).toEqual(null); + it.each([ + ['empty', undefined, ''], + ['valued', { coordinate: { x: 1, y: 2 } }, '1.000000, 2.000000'], + ])('getPrettyLatLng - %s', (_, value, expected) => { + const prettyLatLng = getPrettyLatLng(value); + expect(prettyLatLng).toEqual(expected); }); - it('getLatLng, valued', () => { - const latLng = getLatLng({ coordinate: { x: 1, y: 2 } }); - expect(latLng).toEqual({ lat: 2, lng: 1 }); + it.each([ + ['empty', undefined, null], + ['valued', { coordinate: { x: 1, y: 2 } }, { lat: 2, lng: 1 }], + ])('getLatLng - %s', (_, value, expected) => { + const latLng = getLatLng(value); + expect(latLng).toEqual(expected); }); it.each([ @@ -152,7 +153,7 @@ describe('mapPropertyUtils', () => { { ...getEmptyFileProperty(), property: { ...getEmptyProperty(), pid: 1 } }, ], ])( - 'getFilePropertyName test with ignore name flag %p expecting %p source %p', + 'getFilePropertyName test with ignore name flag %o expecting %s source %o', (skipName: boolean, expectedName: PropertyName, mapProperty?: ApiGen_Concepts_FileProperty) => { const fileName = getFilePropertyName(mapProperty, skipName); expect(fileName.label).toEqual(expectedName.label); @@ -183,6 +184,10 @@ describe('mapPropertyUtils', () => { propertyId: undefined, region: undefined, regionName: undefined, + fileLocation: { + lat: 48.76613749999999, + lng: -123.46163749999998, + }, }, ], ], @@ -207,11 +212,15 @@ describe('mapPropertyUtils', () => { propertyId: undefined, region: undefined, regionName: undefined, + fileLocation: { + lat: 48.76613749999999, + lng: -123.46163749999998, + }, }, ], ], ])( - 'featuresToIdentifiedMapProperty test with feature values %p and address %p and expected map properties %p', + 'featuresToIdentifiedMapProperty test with feature values %o and address %o and expected map properties %o', ( values: FeatureCollection | undefined, address?: string, @@ -242,7 +251,7 @@ describe('mapPropertyUtils', () => { { ...expectedMapProperty, address: 'address' }, ], ])( - 'featuresetToMapProperty test with feature set %p address %p expectedPropertyFile %p', + 'featuresetToMapProperty test with feature set %o address %o expectedPropertyFile %o', ( featureSet: LocationFeatureDataset, address: string = 'unknown', diff --git a/source/frontend/src/utils/mapPropertyUtils.ts b/source/frontend/src/utils/mapPropertyUtils.ts index 81e39dd555..e7e1833bdd 100644 --- a/source/frontend/src/utils/mapPropertyUtils.ts +++ b/source/frontend/src/utils/mapPropertyUtils.ts @@ -76,6 +76,16 @@ export const getLatLng = ( return null; }; +export function latLngToApiLocation( + latitude?: number, + longitude?: number, +): ApiGen_Concepts_Geometry | null { + if (isNumber(latitude) && isNumber(longitude)) { + return { coordinate: { x: longitude, y: latitude } }; + } + return null; +} + export const getFilePropertyName = ( fileProperty: ApiGen_Concepts_FileProperty | undefined | null, skipName = false, @@ -172,6 +182,7 @@ function toMapProperty( pin: feature?.properties?.PIN?.toString() ?? undefined, latitude: latitude, longitude: longitude, + fileLocation: { lat: latitude, lng: longitude }, planNumber: feature?.properties?.PLAN_NUMBER?.toString() ?? undefined, address: address, legalDescription: feature?.properties?.LEGAL_DESCRIPTION, @@ -209,6 +220,7 @@ export function featuresetToMapProperty( pin: pin ?? undefined, latitude: featureSet?.location?.lat, longitude: featureSet?.location?.lng, + fileLocation: featureSet?.fileLocation ?? featureSet?.location ?? undefined, polygon: parcelFeature?.geometry?.type === ApiGen_CodeTypes_GeoJsonTypes.Polygon ? (parcelFeature.geometry as Polygon) @@ -254,3 +266,18 @@ export function pinFromFeatureSet(featureset: LocationFeatureDataset): string | null ); } + +export function locationFromFileProperty( + fileProperty: ApiGen_Concepts_FileProperty | undefined | null, +): ApiGen_Concepts_Geometry | null { + return fileProperty?.location ?? fileProperty?.property?.location ?? null; +} + +export function latLngFromMapProperty( + mapProperty: IMapProperty | undefined | null, +): LatLngLiteral | null { + return { + lat: Number(mapProperty?.fileLocation?.lat ?? mapProperty?.latitude ?? 0), + lng: Number(mapProperty?.fileLocation?.lng ?? mapProperty?.longitude ?? 0), + }; +}