diff --git a/source/frontend/src/components/common/Section/SectionField.tsx b/source/frontend/src/components/common/Section/SectionField.tsx index c511a2be74..0b24c43331 100644 --- a/source/frontend/src/components/common/Section/SectionField.tsx +++ b/source/frontend/src/components/common/Section/SectionField.tsx @@ -9,6 +9,7 @@ export interface ISectionFieldProps { /** It accepts either a string or a custom React tooltip component */ tooltip?: React.ReactNode; className?: string; + labelClassName?: string; valueClassName?: string; required?: boolean; noGutters?: boolean; @@ -22,7 +23,10 @@ export const SectionField: React.FunctionComponent< > = props => { return ( - + {props.label && ( {props.label}:{props.tooltip && {renderTooltip(props.tooltip)}} diff --git a/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelectorView.tsx b/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelectorView.tsx index 9105f2de83..7144938ea4 100644 --- a/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelectorView.tsx +++ b/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelectorView.tsx @@ -1,5 +1,5 @@ +import { ColProps } from 'react-bootstrap'; import { FaExternalLinkAlt } from 'react-icons/fa'; -import styled from 'styled-components'; import { SectionField } from '../../Section/SectionField'; import { StyledLink } from '../../styles'; @@ -13,6 +13,7 @@ interface PrimaryContactSelectorDetailsProps { showPrimaryContact: boolean; index?: number; tooltip?: string; + labelWidth?: ColProps; } export const PrimaryContactSelectorDetails = ({ @@ -22,44 +23,45 @@ export const PrimaryContactSelectorDetails = ({ primaryContactName, primaryContactUrl, showPrimaryContact, - index, + index = 0, tooltip, + labelWidth, }: PrimaryContactSelectorDetailsProps) => ( <> - - - {teamMemberName && teamMemberUrl ? ( - - {teamMemberName} + + {teamMemberName && teamMemberUrl ? ( + + {teamMemberName} + + + ) : ( + '' + )} + + + {showPrimaryContact && ( + + {primaryContactName && primaryContactUrl ? ( + + {primaryContactName} ) : ( - '' + 'No contacts available' )} - - - {showPrimaryContact && ( - - - {primaryContactName && primaryContactUrl ? ( - - {primaryContactName} - - - ) : ( - 'No contacts available' - )} - - )} ); - -export const StyledDiv = styled.div` - font-style: italic; -`; diff --git a/source/frontend/src/features/mapSideBar/acquisition/__snapshots__/AcquisitionView.test.tsx.snap b/source/frontend/src/features/mapSideBar/acquisition/__snapshots__/AcquisitionView.test.tsx.snap index d9107f452d..5647b2a988 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/__snapshots__/AcquisitionView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/acquisition/__snapshots__/AcquisitionView.test.tsx.snap @@ -314,7 +314,7 @@ exports[`AcquisitionView component > renders as expected 1`] = ` margin-bottom: 2.4rem; } -.c44 { +.c43 { color: #474543; font-size: 1.6rem; -webkit-text-decoration: none; @@ -338,7 +338,7 @@ exports[`AcquisitionView component > renders as expected 1`] = ` cursor: pointer; } -.c43 { +.c42 { padding: 0 0.4rem; display: block; } @@ -673,15 +673,11 @@ exports[`AcquisitionView component > renders as expected 1`] = ` max-width: 60rem; } -.c42 { - font-style: italic; -} - -.c45 { +.c44 { white-space: pre-line; } -.c46 { +.c45 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1875,95 +1871,87 @@ exports[`AcquisitionView component > renders as expected 1`] = ` class="collapse show" >
-
- -
-
+
+ + + +
-
- -
-
+
+ + + +
@@ -1988,7 +1976,7 @@ exports[`AcquisitionView component > renders as expected 1`] = ` class="collapse show" >

Each property in this file should be owned by the owner(s) in this section

@@ -2099,7 +2087,7 @@ exports[`AcquisitionView component > renders as expected 1`] = ` class="c41 text-left col" >
456 Souris Street PO Box 250 @@ -2253,7 +2241,7 @@ Canada class="c41 text-left col" >
123 Main Street PO Box 123 @@ -2301,72 +2289,64 @@ Canada
-
- -
-
+
+ + + +
-
- -
-
- No contacts available -
+ Primary contact: + +
+
+ No contacts available
@@ -891,77 +879,69 @@ exports[`AcquisitionFileTabs component > matches snapshot 1`] = ` class="collapse show" >

Each property in this file should be owned by the owner(s) in this section

-
- -
-
+
+ + + +
-
- -
-
- No contacts available -
+ Primary contact: + +
+
+ No contacts available
matches snapshot 1`] = ` class="c7 text-left col" > matches snapshot 1`] = ` class="Toastify" />
- .c8 { + .c7 { color: #474543; font-size: 1.6rem; -webkit-text-decoration: none; text-decoration: none; } -.c7 { +.c6 { padding: 0 0.4rem; display: block; } @@ -29,10 +29,6 @@ exports[`AcquisitionSummaryView component > matches snapshot 1`] = ` font-weight: bold; } -.c6 { - font-style: italic; -} - .c0 { background-color: #f2f2f2; padding-top: 1rem; @@ -58,11 +54,11 @@ exports[`AcquisitionSummaryView component > matches snapshot 1`] = ` margin-bottom: 2.4rem; } -.c9 { +.c8 { white-space: pre-line; } -.c10 { +.c9 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -661,95 +657,87 @@ exports[`AcquisitionSummaryView component > matches snapshot 1`] = ` class="collapse show" >
-
- -
-
+
+ + + +
@@ -774,7 +762,7 @@ exports[`AcquisitionSummaryView component > matches snapshot 1`] = ` class="collapse show" >

Each property in this file should be owned by the owner(s) in this section

@@ -885,7 +873,7 @@ exports[`AcquisitionSummaryView component > matches snapshot 1`] = ` class="c5 text-left col" >
456 Souris Street PO Box 250 @@ -1039,7 +1027,7 @@ Canada class="c5 text-left col" >
123 Main Street PO Box 123 @@ -1087,95 +1075,87 @@ Canada
-
- -
-
+
+ + + +
-
- -
-
+
+ + + +
diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap b/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap index fa9f9f0753..030fd8e5bf 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap @@ -82,7 +82,7 @@ exports[`DispositionFileTabs component > matches snapshot 1`] = ` margin-bottom: 2.4rem; } -.c9 { +.c8 { padding: 0 0.4rem; display: block; } @@ -98,10 +98,6 @@ exports[`DispositionFileTabs component > matches snapshot 1`] = ` font-weight: bold; } -.c8 { - font-style: italic; -} -
@@ -603,95 +599,87 @@ exports[`DispositionFileTabs component > matches snapshot 1`] = ` class="collapse show" >
-
- -
-
+
+ + + +
-
- -
-
+
+ + + +
diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx index 96bad6e386..dd30f1836a 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx @@ -151,6 +151,7 @@ export const DispositionSummaryView: React.FunctionComponent ))} diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/__snapshots__/DispositionSummaryView.test.tsx.snap b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/__snapshots__/DispositionSummaryView.test.tsx.snap index c5e9c334e5..bb2499e9e1 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/__snapshots__/DispositionSummaryView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/__snapshots__/DispositionSummaryView.test.tsx.snap @@ -17,15 +17,11 @@ exports[`DispositionSummaryView component > matches snapshot 1`] = ` font-weight: bold; } -.c7 { +.c6 { padding: 0 0.4rem; display: block; } -.c6 { - font-style: italic; -} - .c0 { background-color: #f2f2f2; padding-top: 1rem; @@ -482,95 +478,87 @@ exports[`DispositionSummaryView component > matches snapshot 1`] = ` class="collapse show" >
-
- -
-
+
+ + + +
-
- -
-
+
+ + + +
diff --git a/source/frontend/src/features/mapSideBar/management/__snapshots__/ManagementView.test.tsx.snap b/source/frontend/src/features/mapSideBar/management/__snapshots__/ManagementView.test.tsx.snap index f4af127bd8..c6cc40849e 100644 --- a/source/frontend/src/features/mapSideBar/management/__snapshots__/ManagementView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/management/__snapshots__/ManagementView.test.tsx.snap @@ -1440,7 +1440,7 @@ exports[`ManagementView component > renders as expected 1`] = `
renders as expected 1`] = `
renders as expected 1`] = ` class="collapse show" >
matches snapshot 1`] = `
matches snapshot 1`] = ` class="collapse show" >
{ const history = useHistory(); + const [person, setPerson] = useState(null); + const [organization, setOrganization] = useState(null); + const [primaryContact, setPrimaryContact] = useState(null); const statusSolver = new ManagementStatusUpdateSolver(managementFile); const [fileContacts, setFileContacts] = useState([]); @@ -42,6 +49,68 @@ const ManagementSummaryContainer: React.FunctionComponent { + const result = await deleteContact(managementFile.id, contactId); + if (result) { + fetchContacts(); + } + }, + [deleteContact, fetchContacts, managementFile.id], + ); + + const { + getPersonDetail: { execute: getPerson, loading: getPersonLoading }, + } = usePersonRepository(); + + const { + getOrganizationDetail: { execute: getOrganization, loading: getOrganizationLoading }, + } = useOrganizationRepository(); + + const fetchData = useCallback(async () => { + if (isValidId(managementFile.responsiblePayerPersonId)) { + const returnedPerson = await getPerson(managementFile.responsiblePayerPersonId); + if (exists(returnedPerson)) { + setPerson(returnedPerson); + } + } + if (isValidId(managementFile.responsiblePayerOrganizationId)) { + const returnedOrganization = await getOrganization( + managementFile.responsiblePayerOrganizationId, + ); + if (exists(returnedOrganization)) { + setOrganization(returnedOrganization); + } + } + + if (isValidId(managementFile.responsiblePayerPrimaryContactId)) { + const returnedPrimaryContact = await getPerson( + managementFile.responsiblePayerPrimaryContactId, + ); + if (exists(returnedPrimaryContact)) { + setPrimaryContact(returnedPrimaryContact); + } + } + }, [ + getOrganization, + getPerson, + managementFile.responsiblePayerOrganizationId, + managementFile.responsiblePayerPersonId, + managementFile.responsiblePayerPrimaryContactId, + ]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + useEffect(() => { + fetchContacts(); + }, [fetchContacts]); + + if (getPersonLoading || getOrganizationLoading) { + return <>; + } + const handleAddContact = (): void => { const path = generatePath(matchFile.path, { id: matchFile.params.id, @@ -60,20 +129,6 @@ const ManagementSummaryContainer: React.FunctionComponent { - const result = await deleteContact(managementFile.id, contactId); - if (result) { - fetchContacts(); - } - }, - [deleteContact, fetchContacts, managementFile.id], - ); - - useEffect(() => { - fetchContacts(); - }, [fetchContacts]); - return managementFile ? ( ) : null; }; diff --git a/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.test.tsx b/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.test.tsx index b48c9cfef1..333bb770c2 100644 --- a/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.test.tsx +++ b/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.test.tsx @@ -12,8 +12,6 @@ import { mockManagementFileResponse, } from '@/mocks/managementFiles.mock'; import ManagementStatusUpdateSolver from './ManagementStatusUpdateSolver'; -import { usePersonRepository } from '@/features/contacts/repositories/usePersonRepository'; -import { useOrganizationRepository } from '@/features/contacts/repositories/useOrganizationRepository'; const onEdit = vi.fn(); const onAddContact = vi.fn(); @@ -40,16 +38,6 @@ const mockGetOrganizationApi = { status: 200, }; -vi.mock('@/features/contacts/repositories/useOrganizationRepository'); -vi.mocked(useOrganizationRepository).mockImplementation(() => ({ - getOrganizationDetail: mockGetOrganizationApi, -})); - -vi.mock('@/features/contacts/repositories/usePersonRepository'); -vi.mocked(usePersonRepository).mockImplementation(() => ({ - getPersonDetail: mockGetPersonApi, -})); - describe('ManagementSummaryView component', () => { // render component under test const setup = async ( @@ -67,6 +55,9 @@ describe('ManagementSummaryView component', () => { onAddContact={onAddContact} onEditContact={onEditContact} onDeleteContact={onDeleteContact} + responsiblePayerPerson={renderOptions?.props?.responsiblePayerPerson} + responsiblePayerOrganization={renderOptions?.props?.responsiblePayerOrganization} + primaryContact={renderOptions?.props?.primaryContact} />, { ...renderOptions, @@ -211,26 +202,8 @@ describe('ManagementSummaryView component', () => { expect(await findByText(/Bob Billy Smith/)).toBeVisible(); }); - it('renders reponsible payer person', async () => { + it('renders responsible payer person', async () => { const apiMock = mockManagementFileResponse(); - mockGetPersonApi.execute.mockResolvedValueOnce({ - id: 100, - surname: 'Monga', - firstName: 'Aman', - middleNames: null, - nameSuffix: null, - preferredName: null, - birthDate: null, - comment: null, - addressComment: null, - useOrganizationAddress: false, - isDisabled: false, - managementActivityId: null, - contactMethods: [], - personAddresses: [], - personOrganizations: [], - rowVersion: 1, - }); const { findByText } = await setup({ props: { @@ -238,94 +211,28 @@ describe('ManagementSummaryView component', () => { ...apiMock, responsiblePayerPersonId: 100, }, + responsiblePayerPerson: { + ...getEmptyPerson(), + id: 100, + surname: 'Monga', + firstName: 'Aman', + middleNames: null, + personOrganizations: [], + personAddresses: [], + contactMethods: [], + rowVersion: 1, + }, }, claims: [], }); + await waitForEffects(); - expect(mockGetPersonApi.execute).toHaveBeenCalledTimes(1); expect(await findByText(/Aman Monga/)).toBeVisible(); }); - it('renders reponsible payer organization', async () => { + it('renders responsible payer organization', async () => { const apiMock = mockManagementFileResponse(); - mockGetOrganizationApi.execute.mockResolvedValueOnce({ - id: 1000, - parentOrganizationId: null, - regionCode: null, - districtCode: null, - organizationTypeCode: 'REALTOR', - identifierTypeCode: 'OTHINCORPNO', - organizationIdentifier: 'DQ4EVA', - name: 'TEST COMANY INC.', - alias: null, - incorporationNumber: null, - website: null, - comment: null, - isDisabled: false, - contactMethods: [ - { - id: 7, - personId: null, - organizationId: 3, - contactMethodType: { - id: 'WORKPHONE', - description: 'Work phone', - isDisabled: false, - displayOrder: null, - }, - value: '6049983251', - rowVersion: 1, - }, - ], - organizationAddresses: [ - { - id: 2, - organizationId: 3, - address: { - id: 2, - streetAddress1: 'PO Box 2', - streetAddress2: 'Stealth Camping', - streetAddress3: 'Walmart Parking Lot', - municipality: 'South Podunk', - provinceStateId: 1, - province: { - id: 1, - code: 'BC', - description: 'British Columbia', - displayOrder: 10, - }, - countryId: 1, - country: { - id: 1, - code: 'CA', - description: 'Canada', - displayOrder: 1, - }, - districtCode: null, - district: null, - region: null, - regionCode: null, - countryOther: null, - postal: 'H1I B0B', - latitude: null, - longitude: null, - comment: null, - rowVersion: 1, - }, - addressUsageType: { - id: 'MAILADDR', - description: 'Mailing address', - isDisabled: true, - displayOrder: null, - }, - rowVersion: 1, - }, - ], - organizationPersons: [], - parentOrganization: null, - rowVersion: 1, - }); const { findByText } = await setup({ props: { @@ -335,13 +242,18 @@ describe('ManagementSummaryView component', () => { responsiblePayerOrganizationId: 1000, responsiblePayerPrimaryContactId: null, }, + responsiblePayerOrganization: { + ...getEmptyOrganization(), + id: 1000, + name: 'TEST COMPANY INC.', + }, }, claims: [], }); + await waitForEffects(); - expect(mockGetOrganizationApi.execute).toHaveBeenCalledTimes(1); - expect(await findByText(/TEST COMANY INC./)).toBeVisible(); + expect(await findByText(/TEST COMPANY INC./)).toBeVisible(); }); it('renders management team member organization', async () => { diff --git a/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.tsx b/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.tsx index bfef2601ef..54f148fd57 100644 --- a/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.tsx +++ b/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/ManagementSummaryView.tsx @@ -1,22 +1,23 @@ import { first } from 'lodash'; import { Fragment } from 'react'; -import { FaExternalLinkAlt, FaUserPlus } from 'react-icons/fa'; +import { FaUserPlus } from 'react-icons/fa'; import EditButton from '@/components/common/buttons/EditButton'; -import ContactFieldContainer from '@/components/common/ContactFieldContainer'; +import { PrimaryContactSelectorDetails } from '@/components/common/form/PrimaryContactSelector/PrimaryContactSelectorView'; import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; import { StyledEditWrapper, StyledSummarySection } from '@/components/common/Section/SectionStyles'; import { SectionListHeader } from '@/components/common/SectionListHeader'; -import { StyledLink } from '@/components/common/styles'; import TooltipIcon from '@/components/common/TooltipIcon'; import { Claims, Roles } from '@/constants'; import { cannotEditMessage } from '@/features/mapSideBar/acquisition/common/constants'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; import { ApiGen_Concepts_ManagementFile } from '@/models/api/generated/ApiGen_Concepts_ManagementFile'; import { ApiGen_Concepts_ManagementFileContact } from '@/models/api/generated/ApiGen_Concepts_ManagementFileContact'; -import { exists, isValidId, prettyFormatDate } from '@/utils'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { exists, prettyFormatDate } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import ManagementFileContactsList from './contacts/ManagementFileContactsList'; @@ -27,6 +28,9 @@ export interface IManagementSummaryViewProps { managementFileContacts: ApiGen_Concepts_ManagementFileContact[]; fileStatusSolver: ManagementStatusUpdateSolver; isLoading: boolean; + responsiblePayerPerson: ApiGen_Concepts_Person | null; + responsiblePayerOrganization: ApiGen_Concepts_Organization | null; + primaryContact: ApiGen_Concepts_Person | null; onFileEdit: () => void; onAddContact: () => void; onEditContact: (contactId: number) => void; @@ -38,6 +42,9 @@ export const ManagementSummaryView: React.FunctionComponent - + teamMemberName={ + exists(responsiblePayerOrganization) + ? responsiblePayerOrganization.name + : exists(responsiblePayerPerson) + ? formatApiPersonNames(responsiblePayerPerson) + : '' + } + teamMemberUrl={ + exists(managementFile.responsiblePayerOrganizationId) + ? `/contact/O${managementFile.responsiblePayerOrganizationId}` + : `/contact/P${managementFile.responsiblePayerPersonId}` + } + primaryContactName={exists(primaryContact) ? formatApiPersonNames(primaryContact) : ''} + primaryContactUrl={`/contact/P${managementFile.responsiblePayerPrimaryContactId}`} + showPrimaryContact={!!managementFile.responsiblePayerOrganizationId} + > {managementFile.managementTeam?.map((teamMember, index) => ( - - - - {teamMember?.personId - ? formatApiPersonNames(teamMember?.person) - : teamMember?.organization?.name ?? ''} - - - - - {isValidId(teamMember?.organizationId) && ( - - {teamMember?.primaryContactId ? ( - - {formatApiPersonNames(teamMember?.primaryContact)} - - - ) : ( - 'No contacts available' - )} - - )} + teamMemberName={ + teamMember?.personId + ? formatApiPersonNames(teamMember?.person) + : teamMember?.organization?.name ?? '' + } + teamMemberUrl={ + teamMember?.personId + ? `/contact/P${teamMember?.personId}` + : `/contact/O${teamMember?.organizationId}` + } + primaryContactName={formatApiPersonNames(teamMember?.primaryContact)} + primaryContactUrl={`/contact/P${teamMember?.primaryContactId}`} + showPrimaryContact={!!teamMember?.organizationId} + index={index} + > ))} diff --git a/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/__snapshots__/ManagementSummaryView.test.tsx.snap b/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/__snapshots__/ManagementSummaryView.test.tsx.snap index 9712ee6c23..f943065abe 100644 --- a/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/__snapshots__/ManagementSummaryView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/management/tabs/fileDetails/detail/__snapshots__/ManagementSummaryView.test.tsx.snap @@ -6,7 +6,18 @@ exports[`ManagementSummaryView component > matches snapshot 1`] = ` class="Toastify" />
- .c10 { + .c4.required::before { + content: '*'; + position: absolute; + top: 0.75rem; + left: 0rem; +} + +.c3 { + font-weight: bold; +} + +.c10 { margin-top: 0.3rem; } @@ -42,17 +53,6 @@ exports[`ManagementSummaryView component > matches snapshot 1`] = ` display: block; } -.c4.required::before { - content: '*'; - position: absolute; - top: 0.75rem; - left: 0rem; -} - -.c3 { - font-weight: bold; -} - .c6 { float: right; cursor: pointer; @@ -288,7 +288,7 @@ exports[`ManagementSummaryView component > matches snapshot 1`] = `
matches snapshot 1`] = `
matches snapshot 1`] = ` class="collapse show" >
({ }, })); +const mockGetPersonApi = { + error: undefined, + response: undefined, + execute: vi.fn(), + loading: false, +}; + +const mockGetOrganizationApi = { + error: undefined, + response: undefined, + execute: vi.fn(), + loading: false, +}; + +vi.mock('@/features/contacts/repositories/usePersonRepository', () => ({ + usePersonRepository: () => ({ + getPersonDetail: mockGetPersonApi, + }), +})); + +vi.mock('@/features/contacts/repositories/useOrganizationRepository', () => ({ + useOrganizationRepository: () => ({ + getOrganizationDetail: mockGetOrganizationApi, + }), +})); + describe('PropertyManagementDetailContainer component', () => { let viewProps: IPropertyManagementDetailViewProps; @@ -82,4 +108,26 @@ describe('PropertyManagementDetailContainer component', () => { expect(viewProps.isLoading).toBe(false); expect(viewProps.propertyManagement).toBe(apiManagement); }); + + it('fetches responsible payer person and organization details', async () => { + const apiManagement = { + ...getMockApiPropertyManagement(1), + responsiblePayerPersonId: 100, + responsiblePayerOrganizationId: 200, + responsiblePayerPrimaryContactId: 300, + }; + + mockGetApi.response = apiManagement; + + mockGetPersonApi.execute.mockResolvedValue({}); + mockGetOrganizationApi.execute.mockResolvedValue({}); + + setup({ props: { propertyId: 1 } }); + + await waitFor(() => { + expect(mockGetPersonApi.execute).toHaveBeenCalledWith(100); + expect(mockGetOrganizationApi.execute).toHaveBeenCalledWith(200); + expect(mockGetPersonApi.execute).toHaveBeenCalledWith(300); + }); + }); }); diff --git a/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailContainer.tsx b/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailContainer.tsx index b9f3783d65..a3ee4db766 100644 --- a/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailContainer.tsx +++ b/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailContainer.tsx @@ -1,6 +1,11 @@ -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { useOrganizationRepository } from '@/features/contacts/repositories/useOrganizationRepository'; +import { usePersonRepository } from '@/features/contacts/repositories/usePersonRepository'; import { usePropertyManagementRepository } from '@/hooks/repositories/usePropertyManagementRepository'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { exists, isValidId } from '@/utils'; import { IPropertyManagementDetailViewProps } from './PropertyManagementDetailView'; @@ -20,6 +25,17 @@ export const PropertyManagementDetailContainer: React.FunctionComponent< }, } = usePropertyManagementRepository(); + const [person, setPerson] = useState(null); + const [organization, setOrganization] = useState(null); + const [primaryContact, setPrimaryContact] = useState(null); + const { + getPersonDetail: { execute: getPerson, loading: getPersonLoading }, + } = usePersonRepository(); + + const { + getOrganizationDetail: { execute: getOrganization, loading: getOrganizationLoading }, + } = useOrganizationRepository(); + const fetchPropertyManagement = useCallback(async () => { if (!propertyId) { return; @@ -27,9 +43,56 @@ export const PropertyManagementDetailContainer: React.FunctionComponent< await getPropertyManagement(propertyId); }, [getPropertyManagement, propertyId]); + const fetchData = useCallback(async () => { + if (isValidId(propertyManagement?.responsiblePayerPersonId)) { + const returnedPerson = await getPerson(propertyManagement?.responsiblePayerPersonId); + if (exists(returnedPerson)) { + setPerson(returnedPerson); + } + } + if (isValidId(propertyManagement?.responsiblePayerOrganizationId)) { + const returnedOrganization = await getOrganization( + propertyManagement?.responsiblePayerOrganizationId, + ); + if (exists(returnedOrganization)) { + setOrganization(returnedOrganization); + } + } + + if (isValidId(propertyManagement?.responsiblePayerPrimaryContactId)) { + const returnedPrimaryContact = await getPerson( + propertyManagement?.responsiblePayerPrimaryContactId, + ); + if (exists(returnedPrimaryContact)) { + setPrimaryContact(returnedPrimaryContact); + } + } + }, [ + getOrganization, + getPerson, + propertyManagement?.responsiblePayerOrganizationId, + propertyManagement?.responsiblePayerPersonId, + propertyManagement?.responsiblePayerPrimaryContactId, + ]); + useEffect(() => { fetchPropertyManagement(); }, [fetchPropertyManagement]); - return ; + useEffect(() => { + fetchData(); + }, [fetchData]); + if (getPersonLoading || getOrganizationLoading) { + return <>; + } + + return ( + + ); }; diff --git a/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.test.tsx b/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.test.tsx index 674976017a..90dd845257 100644 --- a/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.test.tsx +++ b/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.test.tsx @@ -1,8 +1,6 @@ import { createMemoryHistory } from 'history'; import { Claims } from '@/constants'; -import { useOrganizationRepository } from '@/features/contacts/repositories/useOrganizationRepository'; -import { usePersonRepository } from '@/features/contacts/repositories/usePersonRepository'; import { mockLookups } from '@/mocks/lookups.mock'; import { getMockApiPropertyManagement, @@ -24,6 +22,7 @@ import { IPropertyManagementDetailViewProps, PropertyManagementDetailView, } from './PropertyManagementDetailView'; +import { getMockOrganization } from '@/mocks/organization.mock'; const storeState = { [lookupCodesSlice.name]: { lookupCodes: mockLookups }, @@ -31,19 +30,6 @@ const storeState = { const history = createMemoryHistory(); -const mockGetPersonApi = getMockRepositoryObj(); -const mockGetOrganizationApi = getMockRepositoryObj(); - -vi.mock('@/features/contacts/repositories/useOrganizationRepository'); -vi.mocked(useOrganizationRepository).mockImplementation(() => ({ - getOrganizationDetail: mockGetOrganizationApi, -})); - -vi.mock('@/features/contacts/repositories/usePersonRepository'); -vi.mocked(usePersonRepository).mockImplementation(() => ({ - getPersonDetail: mockGetPersonApi, -})); - describe('PropertyManagementDetailView component', () => { const setup = async ( renderOptions?: RenderOptions & { props?: Partial }, @@ -56,6 +42,11 @@ describe('PropertyManagementDetailView component', () => { renderOptions.props?.propertyManagement ?? getMockApiPropertyManagement() } isLoading={renderOptions.props?.isLoading ?? false} + responsiblePayerPerson={renderOptions.props?.responsiblePayerPerson ?? undefined} + responsiblePayerOrganization={ + renderOptions.props?.responsiblePayerOrganization ?? undefined + } + primaryContact={renderOptions.props?.primaryContact ?? undefined} />, { ...renderOptions, @@ -104,25 +95,6 @@ describe('PropertyManagementDetailView component', () => { }); it('displays responsible payer person', async () => { - mockGetPersonApi.execute.mockResolvedValueOnce({ - id: 100, - surname: 'Monga', - firstName: 'Aman', - middleNames: null, - nameSuffix: null, - preferredName: null, - birthDate: null, - comment: null, - addressComment: null, - useOrganizationAddress: false, - isDisabled: false, - managementActivityId: null, - contactMethods: [], - personAddresses: [], - personOrganizations: [], - rowVersion: 1, - }); - const apiManagement: ApiGen_Concepts_PropertyManagement = { ...getMockApiPropertyManagement(), responsiblePayerPersonId: 100, @@ -130,92 +102,34 @@ describe('PropertyManagementDetailView component', () => { }; const { getByText } = await setup({ - props: { propertyManagement: apiManagement, isLoading: false }, + props: { + propertyManagement: apiManagement, + isLoading: false, + responsiblePayerPerson: { + id: 100, + surname: 'Monga', + firstName: 'Aman', + middleNames: null, + nameSuffix: null, + preferredName: null, + birthDate: null, + comment: null, + addressComment: null, + useOrganizationAddress: false, + isDisabled: false, + managementActivityId: null, + contactMethods: [], + personAddresses: [], + personOrganizations: [], + rowVersion: 1, + }, + }, }); - expect(mockGetPersonApi.execute).toHaveBeenCalledTimes(1); expect(getByText(/Aman Monga/)).toBeVisible(); }); it('displays responsible payer organization', async () => { - mockGetOrganizationApi.execute.mockResolvedValueOnce({ - id: 1000, - parentOrganizationId: null, - regionCode: null, - districtCode: null, - organizationTypeCode: 'REALTOR', - identifierTypeCode: 'OTHINCORPNO', - organizationIdentifier: 'DQ4EVA', - name: 'TEST COMANY INC.', - alias: null, - incorporationNumber: null, - website: null, - comment: null, - isDisabled: false, - contactMethods: [ - { - id: 7, - personId: null, - organizationId: 3, - contactMethodType: { - id: 'WORKPHONE', - description: 'Work phone', - isDisabled: false, - displayOrder: null, - }, - value: '6049983251', - rowVersion: 1, - }, - ], - organizationAddresses: [ - { - id: 2, - organizationId: 3, - address: { - id: 2, - streetAddress1: 'PO Box 2', - streetAddress2: 'Stealth Camping', - streetAddress3: 'Walmart Parking Lot', - municipality: 'South Podunk', - provinceStateId: 1, - province: { - id: 1, - code: 'BC', - description: 'British Columbia', - displayOrder: 10, - }, - countryId: 1, - country: { - id: 1, - code: 'CA', - description: 'Canada', - displayOrder: 1, - }, - districtCode: null, - district: null, - region: null, - regionCode: null, - countryOther: null, - postal: 'H1I B0B', - latitude: null, - longitude: null, - comment: null, - rowVersion: 1, - }, - addressUsageType: { - id: 'MAILADDR', - description: 'Mailing address', - isDisabled: true, - displayOrder: null, - }, - rowVersion: 1, - }, - ], - organizationPersons: [], - parentOrganization: null, - rowVersion: 1, - }); - const apiManagement: ApiGen_Concepts_PropertyManagement = { ...getMockApiPropertyManagement(), responsiblePayerPersonId: null, @@ -224,11 +138,18 @@ describe('PropertyManagementDetailView component', () => { }; const { getByText } = await setup({ - props: { propertyManagement: apiManagement, isLoading: false }, + props: { + propertyManagement: apiManagement, + isLoading: false, + responsiblePayerOrganization: { + ...getMockOrganization(), + id: 1000, + name: 'TEST COMPANY INC.', + }, + }, }); - expect(mockGetOrganizationApi.execute).toHaveBeenCalledTimes(1); - expect(getByText(/TEST COMANY INC./)).toBeVisible(); + expect(getByText(/TEST COMPANY INC./)).toBeVisible(); }); it('does not throw an exception for an invalid data object', async () => { diff --git a/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.tsx b/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.tsx index 3a61219360..57d8ac0365 100644 --- a/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.tsx +++ b/source/frontend/src/features/mapSideBar/property/tabs/propertyDetailsManagement/detail/summary/PropertyManagementDetailView.tsx @@ -2,7 +2,7 @@ import { Col, Row } from 'react-bootstrap'; import { useHistory } from 'react-router-dom'; import { EditButton } from '@/components/common/buttons/EditButton'; -import ContactFieldContainer from '@/components/common/ContactFieldContainer'; +import { PrimaryContactSelectorDetails } from '@/components/common/form/PrimaryContactSelector/PrimaryContactSelectorView'; import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { MultiselectTextList } from '@/components/common/MultiselectTextList'; import { Section } from '@/components/common/Section/Section'; @@ -11,22 +11,32 @@ import { StyledEditWrapper } from '@/components/common/Section/SectionStyles'; import { Claims } from '@/constants/index'; import { useQuery } from '@/hooks/use-query'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; import { ApiGen_Concepts_PropertyManagement } from '@/models/api/generated/ApiGen_Concepts_PropertyManagement'; -import { formatApiPropertyManagementLease } from '@/utils'; +import { exists, formatApiPropertyManagementLease } from '@/utils'; import { booleanToYesNoUnknownString } from '@/utils/formUtils'; +import { formatApiPersonNames } from '@/utils/personUtils'; export interface IPropertyManagementDetailViewProps { isLoading: boolean; propertyManagement: ApiGen_Concepts_PropertyManagement | null; + responsiblePayerPerson: ApiGen_Concepts_Person | null; + responsiblePayerOrganization: ApiGen_Concepts_Organization | null; + primaryContact: ApiGen_Concepts_Person | null; } export const PropertyManagementDetailView: React.FC = ({ isLoading, propertyManagement, + responsiblePayerPerson, + responsiblePayerOrganization, + primaryContact, }) => { const { hasClaim } = useKeycloakWrapper(); const query = useQuery(); const history = useHistory(); + return (
{booleanToYesNoUnknownString(propertyManagement?.isTaxesPayable)} - + + renders as expected when provi
renders as expected when provi