From 6c4d74a635bc115846ad41d1f80dd5474079c0e5 Mon Sep 17 00:00:00 2001 From: Alejandro Sanchez Date: Fri, 8 Mar 2024 16:30:42 -0800 Subject: [PATCH] PSP-7907 Add representation of RETIRED properties on the map view and advance filter (#3853) * Add retired map pin image * Update map legend * Add retired property toggle to advanced map filters * Update backend filter queries * Implement separate service call for retiring properties * Map state-machine updates and frontend updates * backend logic fix * Update map clustering logic to not display retired properties * Test corrections * Update snapshots * Properly exclude mockServiceWorker from any linting rules --- .../backend/api/Services/IPropertyService.cs | 2 + .../api/Services/PropertyOperationService.cs | 18 ++++---- .../backend/api/Services/PropertyService.cs | 14 +++++- .../dal/Models/PropertyFilterCriteria.cs | 5 +++ .../Interfaces/IPropertyRepository.cs | 2 + .../dal/Repositories/PropertyRepository.cs | 42 +++++++++++++++--- .../Services/PropertyOperationServiceTest.cs | 16 +++---- source/frontend/.eslintignore | 1 + source/frontend/.prettierignore | 2 +- .../src/assets/images/pins/retired.png | Bin 0 -> 2365 bytes .../common/mapFSM/MapStateMachineContext.tsx | 11 +++++ .../mapFSM/machineDefinition/mapMachine.ts | 6 ++- .../common/mapFSM/machineDefinition/types.ts | 1 + .../AdvancedFilter/FilterContentContainer.tsx | 5 ++- .../AdvancedFilter/FilterContentForm.tsx | 20 +++++++-- .../leaflet/Control/AdvancedFilter/models.ts | 2 + .../maps/leaflet/Control/Legend/Legend.scss | 2 +- .../maps/leaflet/Control/Legend/Legend.tsx | 4 ++ .../Legend/__snapshots__/Legend.test.tsx.snap | 18 ++++++++ .../maps/leaflet/Layers/PointClusterer.tsx | 21 ++++++++- .../components/maps/leaflet/Layers/util.tsx | 29 +++++++++--- .../maps/leaflet/Markers/SingleMarker.tsx | 5 ++- .../form/DispositionSaleForm.test.tsx | 4 +- source/frontend/src/mocks/mapFSM.mock.ts | 2 + .../src/models/api/ProjectFilterCriteria.ts | 1 + 25 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 source/frontend/src/assets/images/pins/retired.png diff --git a/source/backend/api/Services/IPropertyService.cs b/source/backend/api/Services/IPropertyService.cs index 6c32d522af..ee126f4d21 100644 --- a/source/backend/api/Services/IPropertyService.cs +++ b/source/backend/api/Services/IPropertyService.cs @@ -15,6 +15,8 @@ public interface IPropertyService PimsProperty Update(PimsProperty property, bool commitTransaction = true); + PimsProperty RetireProperty(PimsProperty property, bool commitTransaction = true); + IList GetContacts(long propertyId); PimsPropertyContact GetContact(long propertyId, long contactId); diff --git a/source/backend/api/Services/PropertyOperationService.cs b/source/backend/api/Services/PropertyOperationService.cs index 8668ebc7cc..96ed9d1d15 100644 --- a/source/backend/api/Services/PropertyOperationService.cs +++ b/source/backend/api/Services/PropertyOperationService.cs @@ -56,12 +56,12 @@ public IEnumerable SubdivideProperty(IEnumerable op.SourcePropertyId != operations.FirstOrDefault().SourcePropertyId)) { - throw new BusinessRuleViolationException("All property operations must have the same PIMS parent property."); + throw new BusinessRuleViolationException("All property operations must have the same PIMS parent property."); } if (operations.Select(o => o.DestinationProperty).Count() < 2) { - throw new BusinessRuleViolationException("Subdivisions must contain at least two child properties."); + throw new BusinessRuleViolationException("Subdivisions must contain at least two child properties."); } foreach (var operation in operations) @@ -75,15 +75,15 @@ public IEnumerable SubdivideProperty(IEnumerable ConsolidateProperty(IEnumerable dbSourceProperties = _propertyService.GetMultipleById(sourceProperties.Select(sp => sp.PropertyId).ToList()); CommonPropertyOperationValidation(operations); - if(destinationProperty?.Pid == null) + if (destinationProperty?.Pid == null) { throw new BusinessRuleViolationException("Consolidation child must have a property with a valid PID."); } @@ -153,13 +153,13 @@ public IEnumerable ConsolidateProperty(IEnumerable { + operations.ForEach(op => + { op.DestinationProperty = newProperty; op.DestinationPropertyId = newProperty.PropertyId; op.SourceProperty = null; // do not allow the property operation to modify the source in the add range operation. diff --git a/source/backend/api/Services/PropertyService.cs b/source/backend/api/Services/PropertyService.cs index 1493e94b02..0eaab44521 100644 --- a/source/backend/api/Services/PropertyService.cs +++ b/source/backend/api/Services/PropertyService.cs @@ -9,7 +9,6 @@ using Pims.Api.Helpers.Exceptions; using Pims.Api.Models.CodeTypes; using Pims.Api.Models.Concepts.Property; -using Pims.Core.Exceptions; using Pims.Core.Extensions; using Pims.Dal.Entities; using Pims.Dal.Exceptions; @@ -121,6 +120,19 @@ public PimsProperty Update(PimsProperty property, bool commitTransaction = true) return GetById(newProperty.Internal_Id); } + public PimsProperty RetireProperty(PimsProperty property, bool commitTransaction = true) + { + _logger.LogInformation("Retiring property with id {id}", property.Internal_Id); + _user.ThrowIfNotAuthorized(Permissions.PropertyEdit); + + var retiredProperty = _propertyRepository.RetireProperty(property); + if (commitTransaction) + { + _propertyRepository.CommitTransaction(); + } + return GetById(retiredProperty.Internal_Id); ; + } + public IList GetContacts(long propertyId) { _logger.LogInformation("Retrieving property contacts..."); diff --git a/source/backend/dal/Models/PropertyFilterCriteria.cs b/source/backend/dal/Models/PropertyFilterCriteria.cs index 318b3dd1f2..89e52b3bbe 100644 --- a/source/backend/dal/Models/PropertyFilterCriteria.cs +++ b/source/backend/dal/Models/PropertyFilterCriteria.cs @@ -74,6 +74,11 @@ public class PropertyFilterCriteria /// public bool IsDisposed { get; set; } = false; + /// + /// get/set - Whether or not to show retired properties. + /// + public bool IsRetired { get; set; } = false; + #endregion } } diff --git a/source/backend/dal/Repositories/Interfaces/IPropertyRepository.cs b/source/backend/dal/Repositories/Interfaces/IPropertyRepository.cs index 09a1036fb2..1eacb25cf7 100644 --- a/source/backend/dal/Repositories/Interfaces/IPropertyRepository.cs +++ b/source/backend/dal/Repositories/Interfaces/IPropertyRepository.cs @@ -34,6 +34,8 @@ public interface IPropertyRepository : IRepository PimsProperty TransferFileProperty(PimsProperty property, PropertyOwnershipState state); + PimsProperty RetireProperty(PimsProperty property); + HashSet GetMatchingIds(PropertyFilterCriteria filter); short GetPropertyRegion(long id); diff --git a/source/backend/dal/Repositories/PropertyRepository.cs b/source/backend/dal/Repositories/PropertyRepository.cs index 4ac9cfd384..52b42feeb5 100644 --- a/source/backend/dal/Repositories/PropertyRepository.cs +++ b/source/backend/dal/Repositories/PropertyRepository.cs @@ -87,9 +87,9 @@ public Paged GetPage(PropertyFilter filter) /// public PimsProperty GetById(long id) { - this.User.ThrowIfNotAllAuthorized(Permissions.PropertyView); + User.ThrowIfNotAllAuthorized(Permissions.PropertyView); - var property = this.Context.PimsProperties + var property = Context.PimsProperties.AsNoTracking() .Include(p => p.DistrictCodeNavigation) .Include(p => p.RegionCodeNavigation) .Include(p => p.PropertyTypeCodeNavigation) @@ -421,6 +421,17 @@ public PimsProperty TransferFileProperty(PimsProperty property, PropertyOwnershi return existingProperty; } + public PimsProperty RetireProperty(PimsProperty property) + { + property.ThrowIfNull(nameof(property)); + + var existingProperty = Context.PimsProperties + .FirstOrDefault(p => p.PropertyId == property.Internal_Id) ?? throw new KeyNotFoundException(); + + existingProperty.IsRetired = true; + return existingProperty; + } + public HashSet GetMatchingIds(PropertyFilterCriteria filter) { var predicate = PredicateBuilder.New(p => true); @@ -493,10 +504,29 @@ public HashSet GetMatchingIds(PropertyFilterCriteria filter) } // Property ownership filters - predicate.And(p => (p.IsOwned && filter.IsCoreInventory) || - (p.IsPropertyOfInterest && filter.IsPropertyOfInterest) || - (p.IsOtherInterest && filter.IsOtherInterest) || - (p.IsDisposed && filter.IsDisposed)); + var ownershipBuilder = PredicateBuilder.New(p => false); + if (filter.IsCoreInventory) + { + ownershipBuilder.Or(p => p.IsOwned); + } + if (filter.IsPropertyOfInterest) + { + ownershipBuilder.Or(p => p.IsPropertyOfInterest); + } + if (filter.IsOtherInterest) + { + ownershipBuilder.Or(p => p.IsOtherInterest); + } + if (filter.IsDisposed) + { + ownershipBuilder.Or(p => p.IsDisposed); + } + if (filter.IsRetired) + { + ownershipBuilder.Or(p => p.IsRetired.HasValue && p.IsRetired.Value); + } + + predicate.And(ownershipBuilder); return Context.PimsProperties.AsNoTracking() .Where(predicate) diff --git a/source/backend/tests/unit/api/Services/PropertyOperationServiceTest.cs b/source/backend/tests/unit/api/Services/PropertyOperationServiceTest.cs index 35da14c0d4..f277194b0b 100644 --- a/source/backend/tests/unit/api/Services/PropertyOperationServiceTest.cs +++ b/source/backend/tests/unit/api/Services/PropertyOperationServiceTest.cs @@ -191,7 +191,7 @@ public void Subdivide_Success() var sameProperty = EntityHelper.CreateProperty(3); propertyService.Setup(x => x.GetById(It.IsAny())).Returns(sameProperty); propertyService.Setup(x => x.GetByPid(It.IsAny())).Throws(new KeyNotFoundException()); - propertyService.Setup(x => x.Update(It.IsAny(), false)).Returns(sameProperty); + propertyService.Setup(x => x.RetireProperty(It.IsAny(), false)).Returns(sameProperty); propertyService.Setup(x => x.PopulateNewProperty(It.IsAny(), true, false)).Returns(sameProperty); var operations = new List() { EntityHelper.CreatePropertyOperation(), EntityHelper.CreatePropertyOperation() }; @@ -204,7 +204,7 @@ public void Subdivide_Success() // Assert repository.Verify(x => x.AddRange(It.IsAny>()), Times.Once); - propertyService.Verify(x => x.Update(It.IsAny(), false), Times.Once); + propertyService.Verify(x => x.RetireProperty(It.IsAny(), false), Times.Once); propertyService.Verify(x => x.PopulateNewProperty(It.IsAny(), true, false), Times.Exactly(2)); } @@ -217,7 +217,7 @@ public void Subdivide_Success_SameSourceDestinationPid() var sameProperty = EntityHelper.CreateProperty(3); propertyService.Setup(x => x.GetById(It.IsAny())).Returns(sameProperty); propertyService.Setup(x => x.GetByPid(It.IsAny())).Returns(sameProperty); - propertyService.Setup(x => x.Update(It.IsAny(), false)).Returns(sameProperty); + propertyService.Setup(x => x.RetireProperty(It.IsAny(), false)).Returns(sameProperty); propertyService.Setup(x => x.PopulateNewProperty(It.IsAny(), true, false)).Returns(sameProperty); var operationWithSameDestSource = EntityHelper.CreatePropertyOperation(); @@ -233,7 +233,7 @@ public void Subdivide_Success_SameSourceDestinationPid() // Assert repository.Verify(x => x.AddRange(It.IsAny>()), Times.Once); - propertyService.Verify(x => x.Update(It.IsAny(), false), Times.Once); + propertyService.Verify(x => x.RetireProperty(It.IsAny(), false), Times.Once); propertyService.Verify(x => x.PopulateNewProperty(It.IsAny(), true, false), Times.Exactly(2)); } @@ -434,7 +434,7 @@ public void Consolidate_Success() var otherProperty = EntityHelper.CreateProperty(4); propertyService.Setup(x => x.GetMultipleById(It.IsAny>())).Returns(new List { sameProperty, otherProperty }); propertyService.Setup(x => x.GetByPid(It.IsAny())).Throws(new KeyNotFoundException()); - propertyService.Setup(x => x.Update(It.IsAny(), false)).Returns(sameProperty); + propertyService.Setup(x => x.RetireProperty(It.IsAny(), false)).Returns((PimsProperty p, bool b) => p); propertyService.Setup(x => x.PopulateNewProperty(It.IsAny(), true, false)).Returns(sameProperty); var operationOne = EntityHelper.CreatePropertyOperation(); @@ -451,7 +451,7 @@ public void Consolidate_Success() // Assert repository.Verify(x => x.AddRange(It.IsAny>()), Times.Once); - propertyService.Verify(x => x.Update(It.IsAny(), false), Times.Exactly(2)); + propertyService.Verify(x => x.RetireProperty(It.IsAny(), false), Times.Exactly(2)); propertyService.Verify(x => x.PopulateNewProperty(It.IsAny(), true, false), Times.Once); } @@ -465,7 +465,7 @@ public void Consolidate_Success_SameSourceDestinationPid() var otherProperty = EntityHelper.CreateProperty(4); propertyService.Setup(x => x.GetMultipleById(It.IsAny>())).Returns(new List { sameProperty, otherProperty }); propertyService.Setup(x => x.GetByPid(It.IsAny())).Returns(sameProperty); - propertyService.Setup(x => x.Update(It.IsAny(), false)).Returns(sameProperty); + propertyService.Setup(x => x.RetireProperty(It.IsAny(), false)).Returns((PimsProperty p, bool b) => p); propertyService.Setup(x => x.PopulateNewProperty(It.IsAny(), true, false)).Returns(sameProperty); var operationWithSameDestSource = EntityHelper.CreatePropertyOperation(); @@ -484,7 +484,7 @@ public void Consolidate_Success_SameSourceDestinationPid() // Assert repository.Verify(x => x.AddRange(It.IsAny>()), Times.Once); - propertyService.Verify(x => x.Update(It.IsAny(), false), Times.Exactly(2)); + propertyService.Verify(x => x.RetireProperty(It.IsAny(), false), Times.Exactly(2)); propertyService.Verify(x => x.PopulateNewProperty(It.IsAny(), true, false), Times.Once); } diff --git a/source/frontend/.eslintignore b/source/frontend/.eslintignore index 80662a9371..e6d1a1fd18 100644 --- a/source/frontend/.eslintignore +++ b/source/frontend/.eslintignore @@ -1,2 +1,3 @@ src/serviceWorker.ts +mockServiceWorker.js node_modules diff --git a/source/frontend/.prettierignore b/source/frontend/.prettierignore index 9086a2e051..2b5dbe7d71 100644 --- a/source/frontend/.prettierignore +++ b/source/frontend/.prettierignore @@ -6,4 +6,4 @@ node_modules build *.yaml *.yml -mockServiceWork.js \ No newline at end of file +mockServiceWorker.js diff --git a/source/frontend/src/assets/images/pins/retired.png b/source/frontend/src/assets/images/pins/retired.png new file mode 100644 index 0000000000000000000000000000000000000000..57fe672778b3730d7aa901f43bddd42f50462ff8 GIT binary patch literal 2365 zcmV-D3BvY?P)l48aE%il2oMn?!o`5?jbq2Lch}4Rotd+qot@peu46vQ+S&2W z?)m2X|NngdIg>z4$o8YBEs1Q>Ko(`(m`I>sPG@R9SVq1Kj3a?FrDfz6O33BLkw*+j zAOc8Wy@8>GfjeaIS%gHnjNI}vj-GfMqlkb|WJsbTg?r@UVflt0eLiIXtt~)HQvOo% zK3RKhu>>p?&A-KhykD;R_k<7|Ef#U~rEyFkAOe%w{UZ!XC>_)!4Ecu6HlSmj`{2Dm za{2ihViTnj4g^)oH<_LN$fQy@qQi+Qbgwr_#D%%5z@_;WQVc&)=}x~Y`69EsAN|QR zj>Agm?slN7-BhI+pi-BvRNhXAczaH~Aw|Ojp2PX&Pw>0)G7d4UsK&Ru&3tG!7=Dxa z6_q3+hMs*LM;l$!Fv=AXNk}2Gu1ymA`kruk5eA0bxb{}FP?StUgEw!%#CxCMOucLB z4sF+M7?d!2g2x@LrXqFE1ID>}!Mw z!OF?;SQRwWnNs7dLRFZl@JizXk9#}gokQZn>;f<|XC6y&+VT9G$m#!8DU&ki60C1s zn?#5%aT~4a%G)Y+Dr9I=L?$|2agXqY>>b0n|EU@L=Y=x<^1x|qye@+v*!7$JmFxHJ zJA$_LJ-)8n*kQiXjr`)rD+aexnx%#xl$VTBrOo>b`NQ}8>m<@Gri5J^H(}uCLwNVC zmoYo@x##sHvU~4H<@zn(?MGp05$E5@d0s2tP)(|Zl6sqY|5I}^Rmw0|0mSbv4{56oa?cb+-H+_}7<4tq9Vhr@=1 zl^O+Q3v20*Jv5AqF7f{BKO9sfnM)+n>&PYM9f?dia?dZA5-F=5cX0h->lXY`I1JFC z(iw+AQNrWg+b?7L?SnPt^_JUqB6G_C`nCwgZXo-B)t<)KfF(cXjG)FaSM-bLzaEit!#;*PhqG%^6xu6KlkEe z2XOM4Lx=;m>YFuW9cu$(1j}oq5=%8sJUW87V4q41sa`iaPe#G_q)cyj2`dejaoV6} zz(9;(`MvA!$2~vCD(9S=;y`R<^gPn;Viui6fgtIi#?%Xj^aLOAV7ZLY&_sG&2FGMS z_HeE3(Q}Vqr|buY)U+8M6iWyN4(2W~m-UD++3CA@>_o416QW?LV7^k6~JZu+BjZuNqQCny5ngVFUGbYOoc0$>uGcV<3_4zl3~sS$(vn!_qz_(%Q)7vJp`2w z0@{D8aOJtMVT_r(Rym%T5717ym0(&KKMt6-$F0J(2dr#Y7OjIaRD7Dv*=Dgck~d`8 z>7)NACO>`;ed2~$%{VwFdyr0%E%NjiA7RT){gv-{cJ!~R;q!~>IZK%Y?w96lTy-#m z(nBxHdKpb87suCysA{VXF4c0$R*=73Kp*!-l>=a?NkLF69Ng^rCSYzQCC2myZ^h1t z(NvzCNp&?~Wm|2Kh_1fUhgD|d3YoD}vN`$e4po`+ zp$g&W35^T#C7GUf^I4hIhTbx#DpHF38hzGtbw(eyA$<7fm=GS+Txjzm`5 z=IGy($avMht6UGh*3Im><9OJuX|Ssz6XcMeYkZN>>hO4K7OR4}~-wVFUD0RxFv1@kj%&dIBZ&ipuYt6|N)G9q73iK9iZ=Sm}s?>Q!fglZ9Bmg*=bl&aV~SZGy<$Pf2MZ4rW4 zd@n@appDWZ1ZlkdATpUKErQ+RN68h9Shmi?vEy8fU7Qt>?;#P%-os83j+7Z>_dm(l*>PsyX%4b^iT%zWnh!)@TchLkm_jpLk zdj2>T+fomajb=TlXA4?znkJNw=+f@QVI~`)DasJ)*#gUiDk2hghJhVuBATX*#^3-l jr6LApnQE*VNpHv3aW3W7xD%G800000NkvXXu0mjfVknqW literal 0 HcmV?d00001 diff --git a/source/frontend/src/components/common/mapFSM/MapStateMachineContext.tsx b/source/frontend/src/components/common/mapFSM/MapStateMachineContext.tsx index ecca3996e2..39e464c2e4 100644 --- a/source/frontend/src/components/common/mapFSM/MapStateMachineContext.tsx +++ b/source/frontend/src/components/common/mapFSM/MapStateMachineContext.tsx @@ -38,6 +38,7 @@ export interface IMapStateMachineContext { isShowingMapLayers: boolean; activePimsPropertyIds: number[]; showDisposed: boolean; + showRetired: boolean; requestFlyToLocation: (latlng: LatLngLiteral) => void; requestFlyToBounds: (bounds: LatLngBounds) => void; @@ -61,6 +62,7 @@ export interface IMapStateMachineContext { setVisiblePimsProperties: (propertyIds: number[]) => void; setShowDisposed: (show: boolean) => void; + setShowRetired: (show: boolean) => void; } const MapStateMachineContext = React.createContext( @@ -265,6 +267,13 @@ export const MapStateMachineProvider: React.FC> [serviceSend], ); + const setShowRetired = useCallback( + (show: boolean) => { + serviceSend({ type: 'SET_SHOW_RETIRED', show }); + }, + [serviceSend], + ); + const toggleMapFilter = useCallback(() => { serviceSend({ type: 'TOGGLE_FILTER' }); }, [serviceSend]); @@ -316,6 +325,7 @@ export const MapStateMachineProvider: React.FC> isShowingMapLayers: isShowingMapLayers, activePimsPropertyIds: state.context.activePimsPropertyIds, showDisposed: state.context.showDisposed, + showRetired: state.context.showRetired, setMapSearchCriteria, refreshMapProperties, @@ -336,6 +346,7 @@ export const MapStateMachineProvider: React.FC> setFilePropertyLocations, setVisiblePimsProperties, setShowDisposed, + setShowRetired, }} > {children} diff --git a/source/frontend/src/components/common/mapFSM/machineDefinition/mapMachine.ts b/source/frontend/src/components/common/mapFSM/machineDefinition/mapMachine.ts index 8bd76b154a..d8308192d6 100644 --- a/source/frontend/src/components/common/mapFSM/machineDefinition/mapMachine.ts +++ b/source/frontend/src/components/common/mapFSM/machineDefinition/mapMachine.ts @@ -53,7 +53,7 @@ const featureViewStates = { on: { TOGGLE_FILTER: { target: 'browsing', - actions: assign({ showDisposed: () => false }), + actions: [assign({ showDisposed: () => false }), assign({ showRetired: () => false })], }, TOGGLE_LAYERS: { target: 'layerControl', @@ -64,6 +64,9 @@ const featureViewStates = { SET_SHOW_DISPOSED: { actions: assign({ showDisposed: (_, event: any) => event.show }), }, + SET_SHOW_RETIRED: { + actions: assign({ showRetired: (_, event: any) => event.show }), + }, }, }, }, @@ -334,6 +337,7 @@ export const mapMachine = createMachine({ filePropertyLocations: [], activePimsPropertyIds: [], showDisposed: false, + showRetired: false, }, // State definitions diff --git a/source/frontend/src/components/common/mapFSM/machineDefinition/types.ts b/source/frontend/src/components/common/mapFSM/machineDefinition/types.ts index 4080a71caf..5b4ef2bf96 100644 --- a/source/frontend/src/components/common/mapFSM/machineDefinition/types.ts +++ b/source/frontend/src/components/common/mapFSM/machineDefinition/types.ts @@ -38,6 +38,7 @@ export type MachineContext = { filePropertyLocations: LatLngLiteral[]; activePimsPropertyIds: number[]; showDisposed: boolean; + showRetired: boolean; }; // Possible state machine states diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx index b5ebcae8a7..0da8793e6b 100644 --- a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx +++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentContainer.tsx @@ -16,7 +16,7 @@ export const FilterContentContainer: React.FC< > = ({ View }) => { const mapMachine = useMapStateMachine(); - const { setVisiblePimsProperties, setShowDisposed } = mapMachine; + const { setVisiblePimsProperties, setShowDisposed, setShowRetired } = mapMachine; const { getMatchingProperties } = usePimsPropertyRepository(); @@ -37,8 +37,9 @@ export const FilterContentContainer: React.FC< (model: PropertyFilterFormModel) => { filterProperties(model.toApi()); setShowDisposed(model.isDisposed); + setShowRetired(model.isRetired); }, - [filterProperties, setShowDisposed], + [filterProperties, setShowDisposed, setShowRetired], ); return ; diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx index 9eba310939..84e06fcf6a 100644 --- a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx +++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.tsx @@ -10,6 +10,7 @@ import DisposedPng from '@/assets/images/pins/disposed.png'; import PropertyOfInterestPng from '@/assets/images/pins/land-poi.png'; import CoreInventoryPng from '@/assets/images/pins/land-reg.png'; import OtherInterestPng from '@/assets/images/pins/other-interest.png'; +import RetiredPng from '@/assets/images/pins/retired.png'; import { Check, Select, SelectOption } from '@/components/common/form'; import { Multiselect } from '@/components/common/form/Multiselect'; import { ProjectSelector } from '@/components/common/form/ProjectSelector/ProjectSelector'; @@ -186,7 +187,7 @@ export const FilterContentForm: React.FC
- + @@ -197,7 +198,7 @@ export const FilterContentForm: React.FCCore Inventory - + @@ -208,7 +209,7 @@ export const FilterContentForm: React.FCProperty of Interest - + @@ -219,7 +220,7 @@ export const FilterContentForm: React.FCOther Interest - + @@ -230,6 +231,17 @@ export const FilterContentForm: React.FCDisposed + + + + + + + + + Retired + +
diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/models.ts b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/models.ts index 9765c26f12..211710f40a 100644 --- a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/models.ts +++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/models.ts @@ -29,6 +29,7 @@ export class PropertyFilterFormModel { public isPropertyOfInterest = true; public isOtherInterest = true; public isDisposed = false; + public isRetired = false; public toApi(): Api_PropertyFilterCriteria { return { @@ -49,6 +50,7 @@ export class PropertyFilterFormModel { isPropertyOfInterest: this.isPropertyOfInterest, isOtherInterest: this.isOtherInterest, isDisposed: this.isDisposed, + isRetired: this.isRetired, }; } } diff --git a/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.scss b/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.scss index f4387518c1..2181d084d2 100644 --- a/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.scss +++ b/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.scss @@ -4,7 +4,7 @@ &.card { font-family: 'BCSans', Fallback, sans-serif; font-size: 1.6rem; - min-width: 20rem; + min-width: 33rem; .card-header { font-family: 'BCSans-Bold'; font-size: 1.8rem; diff --git a/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.tsx b/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.tsx index 5a203061c5..9dac94ff54 100644 --- a/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.tsx +++ b/source/frontend/src/components/maps/leaflet/Control/Legend/Legend.tsx @@ -25,6 +25,10 @@ export const Legend = () => { pin: require('@/assets/images/pins/disposed.png'), label: 'Disposed', }, + { + pin: require('@/assets/images/pins/retired.png'), + label: 'Retired (Subdivided/consolidated)', + }, { pin: require('@/assets/images/pins/marker-info-orange.png'), label: 'Search result (not in inventory)', diff --git a/source/frontend/src/components/maps/leaflet/Control/Legend/__snapshots__/Legend.test.tsx.snap b/source/frontend/src/components/maps/leaflet/Control/Legend/__snapshots__/Legend.test.tsx.snap index b02fdfec57..a1add207d2 100644 --- a/source/frontend/src/components/maps/leaflet/Control/Legend/__snapshots__/Legend.test.tsx.snap +++ b/source/frontend/src/components/maps/leaflet/Control/Legend/__snapshots__/Legend.test.tsx.snap @@ -85,6 +85,24 @@ exports[`Map Legend View renders correctly 1`] = ` Disposed +
+
+ +
+
+ Retired (Subdivided/consolidated) +
+
diff --git a/source/frontend/src/components/maps/leaflet/Layers/PointClusterer.tsx b/source/frontend/src/components/maps/leaflet/Layers/PointClusterer.tsx index 6c002cf15e..7e78a62855 100644 --- a/source/frontend/src/components/maps/leaflet/Layers/PointClusterer.tsx +++ b/source/frontend/src/components/maps/leaflet/Layers/PointClusterer.tsx @@ -95,20 +95,39 @@ export const PointClusterer: React.FC = useMemo(() => { if (mapMachine.isFiltering && mapMachine.mapFeatureData.pimsLocationFeatures !== null) { - const filteredFeatures = mapMachine.mapFeatureData.pimsLocationFeatures.features.filter(x => + let filteredFeatures = mapMachine.mapFeatureData.pimsLocationFeatures.features.filter(x => mapMachine.activePimsPropertyIds.includes(Number(x.properties.PROPERTY_ID)), ); + + // allow clustering of retired properties when advanced filter is open + if (!mapMachine.showRetired) { + filteredFeatures = filteredFeatures.filter(x => !x.properties.IS_RETIRED); + } + return { type: mapMachine.mapFeatureData.pimsLocationFeatures.type, features: filteredFeatures, }; } else { + if (mapMachine.mapFeatureData.pimsLocationFeatures !== null) { + // By default, all properties that are marked as retired, are not displayed on the map, regardless of other states on the property + const filteredFeatures = mapMachine.mapFeatureData.pimsLocationFeatures.features.filter( + x => !x.properties.IS_RETIRED, + ); + + return { + type: mapMachine.mapFeatureData.pimsLocationFeatures.type, + features: filteredFeatures, + }; + } + return mapMachine.mapFeatureData.pimsLocationFeatures; } }, [ mapMachine.activePimsPropertyIds, mapMachine.isFiltering, mapMachine.mapFeatureData.pimsLocationFeatures, + mapMachine.showRetired, ]); const pimsBoundaryFeatures = mapMachine.mapFeatureData.pimsBoundaryFeatures; diff --git a/source/frontend/src/components/maps/leaflet/Layers/util.tsx b/source/frontend/src/components/maps/leaflet/Layers/util.tsx index 48a7ad6c46..4a20ad1026 100644 --- a/source/frontend/src/components/maps/leaflet/Layers/util.tsx +++ b/source/frontend/src/components/maps/leaflet/Layers/util.tsx @@ -100,6 +100,16 @@ export const disposedIconSelect = L.icon({ shadowSize: [41, 41], }); +// retired icon (grey) +export const retiredIcon = L.icon({ + iconUrl: require('@/assets/images/pins/retired.png') ?? 'assets/images/pins/retired.png', + shadowUrl: require('@/assets/images/pins/marker-shadow.png') ?? 'marker-shadow.png', + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41], +}); + // not owned property icon (orange) export const notOwnedPropertyIcon = L.icon({ iconUrl: @@ -153,17 +163,26 @@ export function pointToLayer

, selected: boolean, showDisposed = false, + showRetired = false, ): L.Icon | null { - if (feature?.properties?.IS_OWNED === true) { + if (feature?.properties?.IS_RETIRED === true) { + if (showRetired) { + return retiredIcon; + } else { + return null; + } + } else if (feature?.properties?.IS_OWNED === true) { if (selected) { return parcelIconSelect; } diff --git a/source/frontend/src/components/maps/leaflet/Markers/SingleMarker.tsx b/source/frontend/src/components/maps/leaflet/Markers/SingleMarker.tsx index 8032443a89..d8e923521b 100644 --- a/source/frontend/src/components/maps/leaflet/Markers/SingleMarker.tsx +++ b/source/frontend/src/components/maps/leaflet/Markers/SingleMarker.tsx @@ -39,10 +39,11 @@ const SinglePropertyMarker: React.FC, isSelected: boolean, showDisposed: boolean, + showRetired: boolean, ): L.Icon | null => { const isOwned = isPimsFeature(feature); if (isOwned) { - return getMarkerIcon(feature, isSelected, showDisposed); + return getMarkerIcon(feature, isSelected, showDisposed, showRetired); } else { return getNotOwnerMarkerIcon(isSelected); } @@ -80,7 +81,7 @@ const SinglePropertyMarker: React.FC { fillInput(container, 'isGstRequired', 'false', 'select'); }); await waitForEffects(); - expect(getByText(/The GST, if provided, will be cleared. Do you wish to proceed/i)).toBeVisible(); + expect( + getByText(/The GST, if provided, will be cleared. Do you wish to proceed/i), + ).toBeVisible(); await act(async () => userEvent.click(getByTitle('ok-modal'))); expect(getGSTCollectedAmountTextbox()).toBeNull(); diff --git a/source/frontend/src/mocks/mapFSM.mock.ts b/source/frontend/src/mocks/mapFSM.mock.ts index 378782f6b3..2e1dd39f72 100644 --- a/source/frontend/src/mocks/mapFSM.mock.ts +++ b/source/frontend/src/mocks/mapFSM.mock.ts @@ -38,6 +38,7 @@ export const mapMachineBaseMock: IMapStateMachineContext = { isFiltering: false, isShowingMapLayers: false, showDisposed: false, + showRetired: false, requestFlyToLocation: jest.fn(), @@ -59,4 +60,5 @@ export const mapMachineBaseMock: IMapStateMachineContext = { toggleMapLayer: jest.fn(), setShowDisposed: jest.fn(), + setShowRetired: jest.fn(), }; diff --git a/source/frontend/src/models/api/ProjectFilterCriteria.ts b/source/frontend/src/models/api/ProjectFilterCriteria.ts index cc8a5e998f..8a9e303c63 100644 --- a/source/frontend/src/models/api/ProjectFilterCriteria.ts +++ b/source/frontend/src/models/api/ProjectFilterCriteria.ts @@ -16,4 +16,5 @@ export interface Api_PropertyFilterCriteria { isPropertyOfInterest: boolean; isOtherInterest: boolean; isDisposed: boolean; + isRetired: boolean; }