Skip to content

Commit

Permalink
psp-10020 AOSLCTR backwards-compatibility (#4676)
Browse files Browse the repository at this point in the history
* psp-10020

* psp-10118 override feature open event when marker double clicked.

* test corrections.
  • Loading branch information
devinleighsmith authored Mar 5, 2025
1 parent 14924fc commit 54cff4b
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 132 deletions.
4 changes: 2 additions & 2 deletions source/frontend/src/components/maps/MapLeafletView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ const MapLeafletView: React.FC<React.PropsWithChildren<MapLeafletViewProps>> = (
const [activeFeatureLayer, setActiveFeatureLayer] = useState<L.GeoJSON>();
const { doubleClickInterval } = useTenant();

const timer = useRef(null);

// add geojson layer to the map
if (!!mapRef.current && !activeFeatureLayer) {
setActiveFeatureLayer(geoJSON().addTo(mapRef.current));
}

const timer = useRef(null);

const handleMapClickEvent = (latlng: LatLng) => {
if (timer?.current !== null) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LatLngLiteral } from 'leaflet';
import { Marker } from 'react-leaflet';
import { LatLngLiteral, LeafletMouseEvent } from 'leaflet';
import { useRef } from 'react';
import { Marker, useMap } from 'react-leaflet';
import { PointFeature } from 'supercluster';

import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext';
Expand All @@ -8,6 +9,7 @@ import {
PIMS_Property_Boundary_View,
PIMS_Property_Location_View,
} from '@/models/layers/pimsPropertyLocationView';
import { useTenant } from '@/tenants';

import {
getMarkerIcon,
Expand All @@ -34,6 +36,7 @@ const SinglePropertyMarker: React.FC<React.PropsWithChildren<SinglePropertyMarke
isSelected,
}) => {
const mapMachine = useMapStateMachine();
const map = useMap();

const getIcon = (
feature: PointFeature<
Expand All @@ -53,6 +56,24 @@ const SinglePropertyMarker: React.FC<React.PropsWithChildren<SinglePropertyMarke
}
};

const timer = useRef(null);
const { doubleClickInterval } = useTenant();

const handleMarkerClickEvent = () => {
if (timer?.current !== null) {
return;
}
timer.current = setTimeout(() => {
onMarkerClicked();
timer.current = null;
}, doubleClickInterval ?? 250);
};

const handleDoubleClickEvent = () => {
clearTimeout(timer?.current);
timer.current = null;
};

const onMarkerClicked = () => {
const clusterId = pointFeature.id?.toString() || 'ERROR_NO_ID';
const [longitude, latitude] = pointFeature.geometry.coordinates;
Expand Down Expand Up @@ -95,8 +116,12 @@ const SinglePropertyMarker: React.FC<React.PropsWithChildren<SinglePropertyMarke
position={markerPosition}
icon={icon}
eventHandlers={{
dblclick: (event: LeafletMouseEvent) => {
map.fireEvent('dblclick', event); // bubble up double click events to the map.
handleDoubleClickEvent();
},
click: () => {
onMarkerClicked();
handleMarkerClickEvent();
},
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1799,7 +1799,9 @@ exports[`AcquisitionView component > renders as expected 1`] = `
rel="noopener noreferrer"
target="_blank"
>
<span />
<span>
Luke Skywalker
</span>
<svg
class="m1-2"
fill="currentColor"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { act, cleanup, render, RenderOptions, userEvent, waitForEffects } from '

import AcquisitionSummaryView, { IAcquisitionSummaryViewProps } from './AcquisitionSummaryView';
import { mockProjects } from '@/mocks/projects.mock';
import { InterestHolderType } from '@/constants/interestHolderTypes';

// mock auth library

Expand Down Expand Up @@ -270,11 +271,85 @@ describe('AcquisitionSummaryView component', () => {
expect(getByTestId('assigned-date')).toHaveTextContent('Dec 18, 2024');
});

it('renders multiple owner solicitor information with primary contact', async () => {
const { findByText, findAllByText } = setup(
{
acquisitionFile: {
...mockAcquisitionFileResponse(),
acquisitionFileInterestHolders: [
{
interestHolderId: 1,
interestHolderType: toTypeCodeNullable(InterestHolderType.OWNER_SOLICITOR),

acquisitionFileId: 1,
personId: null,
person: null,
organizationId: 1,
organization: {
...getEmptyOrganization(),
id: 1,
name: 'Millennium Inc',
alias: 'M Inc',
incorporationNumber: '1234',
comment: '',
contactMethods: null,
isDisabled: false,
organizationAddresses: null,
organizationPersons: null,
rowVersion: null,
},
interestHolderProperties: [],
primaryContactId: 1,
primaryContact: null,
comment: null,
isDisabled: false,
...getEmptyBaseAudit(),
},
{
interestHolderId: 2,
interestHolderType: toTypeCodeNullable(InterestHolderType.OWNER_SOLICITOR),

acquisitionFileId: 1,
personId: null,
person: null,
organizationId: 2,
organization: {
...getEmptyOrganization(),
id: 2,
name: 'Test Org',
alias: 'M Inc',
incorporationNumber: '12345',
comment: '',
contactMethods: null,
isDisabled: false,
organizationAddresses: null,
organizationPersons: null,
rowVersion: null,
},
interestHolderProperties: [],
primaryContactId: 2,
primaryContact: null,
comment: null,
isDisabled: false,
...getEmptyBaseAudit(),
},
],
},
},
{ claims: [] },
);
await waitForEffects();
expect(await findByText('Millennium Inc')).toBeVisible();
expect(await findByText('Test Org')).toBeVisible();
expect(await findAllByText(/Primary contact/)).toHaveLength(2);
});

it('renders owner solicitor information with primary contact', async () => {
const { findByText } = setup(
{ acquisitionFile: mockAcquisitionFileResponse() },
{ claims: [] },
);
await waitForEffects();
expect(await findByText('Millennium Inc')).toBeVisible();
expect(await findByText(/Primary contact/)).toBeVisible();
expect(await findByText('Foo Bar Baz')).toBeVisible();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Multiselect from 'multiselect-react-dropdown';
import React, { useEffect } from 'react';
import React from 'react';
import { FaExternalLinkAlt } from 'react-icons/fa';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
Expand All @@ -15,6 +15,7 @@ import { Claims, Roles } from '@/constants';
import { InterestHolderType } from '@/constants/interestHolderTypes';
import { usePersonRepository } from '@/features/contacts/repositories/usePersonRepository';
import useKeycloakWrapper from '@/hooks/useKeycloakWrapper';
import useDeepCompareEffect from '@/hooks/util/useDeepCompareEffect';
import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType';
import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile';
import { exists, prettyFormatDate } from '@/utils';
Expand All @@ -39,6 +40,10 @@ const AcquisitionSummaryView: React.FC<IAcquisitionSummaryViewProps> = ({
const detail: DetailAcquisitionFile = DetailAcquisitionFile.fromApi(acquisitionFile);
const { hasRole, hasClaim } = useKeycloakWrapper();

const {
getPersonDetail: { execute: fetchPerson },
} = usePersonRepository();

const projectName = exists(acquisitionFile?.project)
? formatMinistryProject(acquisitionFile?.project?.code, acquisitionFile?.project?.description)
: '';
Expand All @@ -47,13 +52,20 @@ const AcquisitionSummaryView: React.FC<IAcquisitionSummaryViewProps> = ({
? acquisitionFile?.product?.code + ' ' + acquisitionFile?.product?.description
: '';

const ownerSolicitor = acquisitionFile?.acquisitionFileInterestHolders?.find(
const ownerSolicitors = acquisitionFile?.acquisitionFileInterestHolders?.filter(
x => x.interestHolderType?.id === InterestHolderType.OWNER_SOLICITOR,
);

const {
getPersonDetail: { execute: fetchPerson, response: ownerSolicitorPrimaryContact },
} = usePersonRepository();
useDeepCompareEffect(() => {
const getSolicitorPrimaryContacts = async () => {
ownerSolicitors
?.filter(os => exists(os?.primaryContactId))
.map(async os => {
os.primaryContact = await fetchPerson(os.primaryContactId);
});
};
getSolicitorPrimaryContacts();
}, [ownerSolicitors, fetchPerson]);

const selectedProgressStatuses: ApiGen_Base_CodeType<string>[] =
acquisitionFile?.acquisitionFileProgressStatuses
Expand All @@ -65,13 +77,7 @@ const AcquisitionSummaryView: React.FC<IAcquisitionSummaryViewProps> = ({
.map(x => x.takingStatusTypeCode)
.filter(exists) ?? [];

useEffect(() => {
if (ownerSolicitor?.primaryContactId) {
fetchPerson(ownerSolicitor?.primaryContactId);
}
}, [ownerSolicitor?.primaryContactId, fetchPerson]);

const ownerRepresentative = acquisitionFile?.acquisitionFileInterestHolders?.find(
const ownerRepresentatives = acquisitionFile?.acquisitionFileInterestHolders?.filter(
x => x.interestHolderType?.id === InterestHolderType.OWNER_REPRESENTATIVE,
);

Expand Down Expand Up @@ -244,59 +250,64 @@ const AcquisitionSummaryView: React.FC<IAcquisitionSummaryViewProps> = ({
View={AcquisitionOwnersSummaryView}
></AcquisitionOwnersSummaryContainer>
)}
{!!ownerSolicitor && (
<SectionField label={detail.isSubFile ? 'Sub-interest solicitor' : 'Owner solicitor'}>
<StyledLink
target="_blank"
rel="noopener noreferrer"
to={
ownerSolicitor?.personId
? `/contact/P${ownerSolicitor?.personId}`
: `/contact/O${ownerSolicitor?.organizationId}`
}
>
<span>
{ownerSolicitor?.personId
? formatApiPersonNames(ownerSolicitor?.person)
: ownerSolicitor?.organization?.name ?? ''}
</span>
<FaExternalLinkAlt className="ml-2" size="1rem" />
</StyledLink>
</SectionField>
)}
{ownerSolicitor?.organization && (
<SectionField label="Primary contact">
{ownerSolicitor?.primaryContactId ? (
<StyledLink
target="_blank"
rel="noopener noreferrer"
to={`/contact/P${ownerSolicitor?.primaryContactId}`}
>
<span>{formatApiPersonNames(ownerSolicitorPrimaryContact)}</span>
<FaExternalLinkAlt className="m1-2" size="1rem" />
</StyledLink>
) : (
'No contacts available'
)}
</SectionField>
)}
{!!ownerRepresentative && (
<>
<SectionField
label={detail.isSubFile ? 'Sub-interest representative' : 'Owner representative'}
>
<StyledLink
target="_blank"
rel="noopener noreferrer"
to={`/contact/P${ownerRepresentative?.personId}`}
{!!ownerSolicitors?.length &&
ownerSolicitors.map(ownerSolicitor => (
<React.Fragment key={`owner-solicitor-${ownerSolicitor.interestHolderId}`}>
<SectionField label={detail.isSubFile ? 'Sub-interest solicitor' : 'Owner solicitor'}>
<StyledLink
target="_blank"
rel="noopener noreferrer"
to={
ownerSolicitor?.personId
? `/contact/P${ownerSolicitor?.personId}`
: `/contact/O${ownerSolicitor?.organizationId}`
}
>
<span>
{ownerSolicitor?.personId
? formatApiPersonNames(ownerSolicitor?.person)
: ownerSolicitor?.organization?.name ?? ''}
</span>
<FaExternalLinkAlt className="ml-2" size="1rem" />
</StyledLink>
</SectionField>

{ownerSolicitor?.organization && (
<SectionField label="Primary contact">
{ownerSolicitor?.primaryContactId ? (
<StyledLink
target="_blank"
rel="noopener noreferrer"
to={`/contact/P${ownerSolicitor?.primaryContactId}`}
>
<span>{formatApiPersonNames(ownerSolicitor.primaryContact)}</span>
<FaExternalLinkAlt className="m1-2" size="1rem" />
</StyledLink>
) : (
'No contacts available'
)}
</SectionField>
)}
</React.Fragment>
))}
{!!ownerRepresentatives?.length &&
ownerRepresentatives.map(ownerRepresentative => (
<React.Fragment key={`owner-representative-${ownerRepresentative.interestHolderId}`}>
<SectionField
label={detail.isSubFile ? 'Sub-interest representative' : 'Owner representative'}
>
<span>{formatApiPersonNames(ownerRepresentative?.person ?? undefined)}</span>
<FaExternalLinkAlt className="ml-2" size="1rem" />
</StyledLink>
</SectionField>
<SectionField label="Comment">{ownerRepresentative?.comment}</SectionField>
</>
)}
<StyledLink
target="_blank"
rel="noopener noreferrer"
to={`/contact/P${ownerRepresentative?.personId}`}
>
<span>{formatApiPersonNames(ownerRepresentative?.person ?? undefined)}</span>
<FaExternalLinkAlt className="ml-2" size="1rem" />
</StyledLink>
</SectionField>
<SectionField label="Comment">{ownerRepresentative?.comment}</SectionField>
</React.Fragment>
))}
</Section>
</StyledSummarySection>
);
Expand Down
Loading

0 comments on commit 54cff4b

Please sign in to comment.