Skip to content

Commit

Permalink
MPDX-8121 Add Excluded Reason (#1024)
Browse files Browse the repository at this point in the history
* Adding Excluded reason to List contact row. Adding functionality to grab reason in hook so we can use in grid view and adding localized function for Excluded reasons.

* Fixing List contact row header

* Adding reason to the flows view

* using useMemo over useEffect and useState

* Improving and simplifying code

* Adding more tests
  • Loading branch information
dr-bizz authored Sep 3, 2024
1 parent 01b23a2 commit 5750ca7
Show file tree
Hide file tree
Showing 14 changed files with 564 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const title = 'Test Column';
const onContactSelected = jest.fn();
const changeContactStatus = jest.fn();
const contact = {
id: '123',
id: 'contactID',
name: 'Test Person',
status: StatusEnum.NotInterested,
primaryAddress: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from 'src/components/Tool/Appeal/AppealsContext/AppealsContext';
import { appealHeaderInfoHeight } from '../../AppealDetails/AppealHeaderInfo/AppealHeaderInfo';
import { useContactsQuery } from '../../AppealsContext/contacts.generated';
import { useExcludedAppealContactsQuery } from '../../Shared/AppealExcludedContacts.generated';
import { ContactFlowDropZone } from '../ContactFlowDropZone/ContactFlowDropZone';
import { ContactFlowRow } from '../ContactFlowRow/ContactFlowRow';

Expand Down Expand Up @@ -67,6 +68,14 @@ export const ContactFlowColumn: React.FC<Props> = ({
skip: !accountListId || !appealStatus,
});

const { data: excludedContacts } = useExcludedAppealContactsQuery({
variables: {
appealId: appealId ?? '',
accountListId: accountListId ?? '',
},
skip: appealStatus !== AppealStatusEnum.Excluded,
});

const cardContentRef = useRef<HTMLDivElement>();

const [{ canDrop }, drop] = useDrop(() => ({
Expand Down Expand Up @@ -165,9 +174,11 @@ export const ContactFlowColumn: React.FC<Props> = ({
accountListId={accountListId}
contact={contact}
appealStatus={appealStatus}
contactStatus={contact.status}
onContactSelected={onContactSelected}
columnWidth={cardContentRef.current?.offsetWidth}
excludedContacts={
excludedContacts?.appeal?.excludedAppealContacts ?? []
}
/>
)}
endReached={() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from '../../AppealsContext/AppealsContext';
import { AppealContactInfoFragment } from '../../AppealsContext/contacts.generated';
import { defaultContact } from '../../List/ContactRow/ContactRowMock';
import { ExcludedAppealContactInfoFragment } from '../../Shared/AppealExcludedContacts.generated';
import { defaultExcludedContacts } from '../../Shared/useGetExcludedReasons/useGetExcludedReasonsMock';
import { ContactFlowRow } from './ContactFlowRow';

const accountListId = 'account-list-1';
Expand All @@ -26,12 +28,14 @@ const toggleSelectionById = jest.fn();
const isChecked = jest.fn().mockImplementation(() => false);

type ComponentsProps = {
contact?: AppealContactInfoFragment;
appealStatus?: AppealStatusEnum;
contact?: AppealContactInfoFragment;
excludedContacts?: ExcludedAppealContactInfoFragment[];
};
const Components = ({
appealStatus = AppealStatusEnum.Processed,
contact = defaultContact,
appealStatus = AppealStatusEnum.Asked,
excludedContacts = [],
}: ComponentsProps) => (
<I18nextProvider i18n={i18n}>
<LocalizationProvider dateAdapter={AdapterLuxon}>
Expand All @@ -52,6 +56,7 @@ const Components = ({
contact={contact}
appealStatus={appealStatus}
onContactSelected={onContactSelected}
excludedContacts={excludedContacts}
/>
</AppealsContext.Provider>
</TestWrapper>
Expand Down Expand Up @@ -176,4 +181,25 @@ describe('ContactFlowRow', () => {
expect(await findByText('Remove Commitment')).toBeInTheDocument();
});
});

describe('Excluded Reason', () => {
it('should not display excluded reason if not excluded contact', async () => {
const { queryByText } = render(
<Components excludedContacts={defaultExcludedContacts} />,
);

expect(queryByText('Send Appeals?" set to No')).not.toBeInTheDocument();
});

it('should display excluded reason', async () => {
const { findByText } = render(
<Components
excludedContacts={defaultExcludedContacts}
appealStatus={AppealStatusEnum.Excluded}
/>,
);

expect(await findByText('Send Appeals?" set to No')).toBeInTheDocument();
});
});
});
25 changes: 19 additions & 6 deletions src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
} from 'src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow';
import { StarContactIconButton } from 'src/components/Contacts/StarContactIconButton/StarContactIconButton';
import { useGetPledgeOrDonation } from 'src/components/Tool/Appeal/Shared/useGetPledgeOrDonation/useGetPledgeOrDonation';
import { StatusEnum } from 'src/graphql/types.generated';
import theme from 'src/theme';
import { getLocalizedContactStatus } from 'src/utils/functions/getLocalizedContactStatus';
import {
Expand All @@ -39,14 +38,16 @@ import {
preloadPledgeModal,
} from '../../Modals/PledgeModal/DynamicPledgeModal';
import { AmountAndFrequency } from '../../Shared/AmountAndFrequency/AmountAndFrequency';
import { ExcludedAppealContactInfoFragment } from '../../Shared/AppealExcludedContacts.generated';
import { useGetExcludedReasons } from '../../Shared/useGetExcludedReasons/useGetExcludedReasons';

// When making changes in this file, also check to see if you don't need to make changes to the below file
// src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.tsx

interface Props extends Omit<ContactFlowRowProps, 'status' | 'contact'> {
contact: AppealContactInfoFragment;
contactStatus?: StatusEnum | null;
appealStatus: AppealStatusEnum;
excludedContacts: ExcludedAppealContactInfoFragment[];
}

export interface DraggedContact extends Omit<ContactsDraggedContact, 'status'> {
Expand Down Expand Up @@ -86,10 +87,10 @@ const CommitmentActionsBox = styled(Box)(() => ({
export const ContactFlowRow: React.FC<Props> = ({
accountListId,
contact,
contactStatus,
appealStatus,
onContactSelected,
columnWidth,
excludedContacts,
}) => {
const { id, name, starred } = contact;
const { t } = useTranslation();
Expand All @@ -104,14 +105,19 @@ export const ContactFlowRow: React.FC<Props> = ({
const { pledgeValues, amountAndFrequency, pledgeDonations, pledgeOverdue } =
useGetPledgeOrDonation({ appealStatus, contact, appealId: appealId ?? '' });

const reasons = useGetExcludedReasons({
excludedContacts,
contactId: contact.id,
});

const [{ isDragging }, drag, preview] = useDrag(
() => ({
type: 'contact',
item: {
id,
appealStatus,
status: contactStatus,
contactStatus,
status: contact.status,
contactStatus: contact.status,
name,
starred,
width: columnWidth,
Expand All @@ -134,6 +140,8 @@ export const ContactFlowRow: React.FC<Props> = ({
setDeletePledgeModalOpen(true);
};

const isExcludedContact = appealStatus === AppealStatusEnum.Excluded;

return (
<>
<ContainerBox isDragging={isDragging} ref={drag}>
Expand All @@ -156,7 +164,7 @@ export const ContactFlowRow: React.FC<Props> = ({
{name}
</ContactLink>
<Typography variant="body2">
{getLocalizedContactStatus(t, contactStatus)}
{getLocalizedContactStatus(t, contact.status)}
</Typography>
</Box>
</FlexCenterAlignedBox>
Expand All @@ -172,6 +180,11 @@ export const ContactFlowRow: React.FC<Props> = ({
</Box>
</FlexCenterAlignedBox>
<FlexCenterAlignedBox>
{isExcludedContact && reasons && (
<Box mt={2}>
<Typography variant="body2">{reasons}</Typography>
</Box>
)}
<CommitmentsBox>
<Box>
{appealStatus !== AppealStatusEnum.Processed && (
Expand Down
43 changes: 34 additions & 9 deletions src/components/Tool/Appeal/List/ContactRow/ContactRow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
AppealsType,
} from '../../AppealsContext/AppealsContext';
import { AppealContactInfoFragment } from '../../AppealsContext/contacts.generated';
import { ExcludedAppealContactInfoFragment } from '../../Shared/AppealExcludedContacts.generated';
import { defaultExcludedContacts } from '../../Shared/useGetExcludedReasons/useGetExcludedReasonsMock';
import { ContactRow } from './ContactRow';
import { defaultContact } from './ContactRowMock';

Expand All @@ -31,10 +33,12 @@ const isRowChecked = jest.fn();
type ComponentsProps = {
appealStatus?: AppealStatusEnum;
contact?: AppealContactInfoFragment;
excludedContacts?: ExcludedAppealContactInfoFragment[];
};
const Components = ({
appealStatus = AppealStatusEnum.Asked,
contact = defaultContact,
excludedContacts = [],
}: ComponentsProps) => (
<TestRouter router={router}>
<GqlMockedProvider>
Expand All @@ -51,7 +55,11 @@ const Components = ({
} as unknown as AppealsType
}
>
<ContactRow contact={contact} appealStatus={appealStatus} />
<ContactRow
contact={contact}
appealStatus={appealStatus}
excludedContacts={excludedContacts}
/>
</AppealsContext.Provider>
</AppealsWrapper>
</ThemeProvider>
Expand Down Expand Up @@ -104,38 +112,35 @@ describe('ContactsRow', () => {
});

describe('Contact Row by status type', () => {
it('Excluded', () => {
it('Excluded', async () => {
isRowChecked.mockImplementationOnce(() => true);

const { getByText } = render(
<Components appealStatus={AppealStatusEnum.Excluded} />,
);

expect(getByText('Reason')).toBeInTheDocument();
expect(getByText('CA$500')).toBeInTheDocument();
expect(getByText('Monthly')).toBeInTheDocument();
});

it('Asked', () => {
isRowChecked.mockImplementationOnce(() => true);

const { getByText, queryByText } = render(
const { getByText } = render(
<Components appealStatus={AppealStatusEnum.Asked} />,
);

expect(queryByText('Reason')).not.toBeInTheDocument();
expect(getByText('CA$500')).toBeInTheDocument();
expect(getByText('Monthly')).toBeInTheDocument();
});

it('Committed', () => {
isRowChecked.mockImplementationOnce(() => true);

const { getByText, queryByText } = render(
const { getByText } = render(
<Components appealStatus={AppealStatusEnum.NotReceived} />,
);

expect(queryByText('Reason')).not.toBeInTheDocument();
expect(getByText('$3,000')).toBeInTheDocument();
expect(getByText('(Aug 8, 2024)')).toBeInTheDocument();
});
Expand Down Expand Up @@ -170,12 +175,32 @@ describe('ContactsRow', () => {
it('Given', () => {
isRowChecked.mockImplementationOnce(() => true);

const { getByText, queryByText } = render(
const { getByText } = render(
<Components appealStatus={AppealStatusEnum.Processed} />,
);

expect(queryByText('Reason')).not.toBeInTheDocument();
expect(getByText('$3,000 ($50) (Jun 25, 2019)')).toBeInTheDocument();
});
});

describe('Excluded Reason', () => {
it('should not display excluded reason if not excluded contact', async () => {
const { queryByText } = render(
<Components appealStatus={AppealStatusEnum.NotReceived} />,
);

expect(queryByText('Send Appeals?" set to No')).not.toBeInTheDocument();
});

it('should display excluded reason', async () => {
const { findByText } = render(
<Components
excludedContacts={defaultExcludedContacts}
appealStatus={AppealStatusEnum.Excluded}
/>,
);

expect(await findByText('Send Appeals?" set to No')).toBeInTheDocument();
});
});
});
44 changes: 29 additions & 15 deletions src/components/Tool/Appeal/List/ContactRow/ContactRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import {
preloadPledgeModal,
} from '../../Modals/PledgeModal/DynamicPledgeModal';
import { AmountAndFrequency } from '../../Shared/AmountAndFrequency/AmountAndFrequency';
import { ExcludedAppealContactInfoFragment } from '../../Shared/AppealExcludedContacts.generated';
import { useGetExcludedReasons } from '../../Shared/useGetExcludedReasons/useGetExcludedReasons';

// When making changes in this file, also check to see if you don't need to make changes to the below file
// src/components/Contacts/ContactRow/ContactRow.tsx
Expand All @@ -64,12 +66,14 @@ interface Props {
contact: AppealContactInfoFragment;
appealStatus: AppealStatusEnum;
useTopMargin?: boolean;
excludedContacts: ExcludedAppealContactInfoFragment[];
}

export const ContactRow: React.FC<Props> = ({
contact,
appealStatus,
useTopMargin,
excludedContacts,
}) => {
const {
appealId,
Expand All @@ -84,6 +88,11 @@ export const ContactRow: React.FC<Props> = ({
useState(false);
const [removeContactModalOpen, setRemoveContactModalOpen] = useState(false);

const reasons = useGetExcludedReasons({
excludedContacts,
contactId: contact.id,
});

const handleContactClick = () => {
onContactSelected(contact.id);
};
Expand Down Expand Up @@ -131,23 +140,24 @@ export const ContactRow: React.FC<Props> = ({
})}
data-testid="rowButton"
>
<Hidden xsDown>
<ListItemIcon>
<StyledCheckbox
checked={isChecked(contact.id)}
color="secondary"
onClick={(event) => event.stopPropagation()}
onChange={() => onContactCheckToggle(contact.id)}
value={isChecked}
/>
</ListItemIcon>
</Hidden>
<Grid container alignItems="center">
<Grid
item
xs={isExcludedContact ? 5 : 6}
style={{ paddingRight: 16 }}
display={'flex'}
>
<Hidden xsDown>
<ListItemIcon>
<StyledCheckbox
checked={isChecked(contact.id)}
color="secondary"
onClick={(event) => event.stopPropagation()}
onChange={() => onContactCheckToggle(contact.id)}
value={isChecked}
/>
</ListItemIcon>
</Hidden>
<ListItemText
primary={
<Typography component="span" variant="h6" noWrap>
Expand All @@ -166,10 +176,14 @@ export const ContactRow: React.FC<Props> = ({
flexDirection="column"
justifyContent="center"
>
<Typography component="span">
{/* TODO */}
Reason
</Typography>
{reasons.map((reason, idx) => (
<Typography
key={`${contactId}-${reason}-${idx}`}
component="span"
>
{reason}
</Typography>
))}
</Box>
</Box>
</Grid>
Expand Down
Loading

0 comments on commit 5750ca7

Please sign in to comment.