Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

psp-10020 AOSLCTR backwards-compatibility #4676

Merged
merged 4 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading