diff --git a/pages/accountLists/[accountListId]/coaching/[coachingId].page.tsx b/pages/accountLists/[accountListId]/coaching/[coachingId].page.tsx
index 6da05b606..3cabdc268 100644
--- a/pages/accountLists/[accountListId]/coaching/[coachingId].page.tsx
+++ b/pages/accountLists/[accountListId]/coaching/[coachingId].page.tsx
@@ -3,7 +3,10 @@ import Head from 'next/head';
import { useTranslation } from 'react-i18next';
import { useRouter } from 'next/router';
import useGetAppSettings from 'src/hooks/useGetAppSettings';
-import { CoachingDetail } from 'src/components/Coaching/CoachingDetail/CoachingDetail';
+import {
+ AccountListTypeEnum,
+ CoachingDetail,
+} from 'src/components/Coaching/CoachingDetail/CoachingDetail';
import { useAccountListId } from 'src/hooks/useAccountListId';
import Loading from 'src/components/Loading';
@@ -23,8 +26,8 @@ const CoachingPage: React.FC = () => {
{accountListId && coachingId && isReady ? (
) : (
diff --git a/pages/accountLists/[accountListId]/reports/coaching.page.tsx b/pages/accountLists/[accountListId]/reports/coaching.page.tsx
index 235c21eac..b11f59eb8 100644
--- a/pages/accountLists/[accountListId]/reports/coaching.page.tsx
+++ b/pages/accountLists/[accountListId]/reports/coaching.page.tsx
@@ -4,7 +4,10 @@ import { useTranslation } from 'react-i18next';
import useGetAppSettings from 'src/hooks/useGetAppSettings';
import Loading from '../../../../src/components/Loading';
import { useAccountListId } from '../../../../src/hooks/useAccountListId';
-import { CoachingDetail } from 'src/components/Coaching/CoachingDetail/CoachingDetail';
+import {
+ AccountListTypeEnum,
+ CoachingDetail,
+} from 'src/components/Coaching/CoachingDetail/CoachingDetail';
import { suggestArticles } from 'src/lib/helpScout';
const CoachingReportPage = (): ReactElement => {
@@ -24,7 +27,10 @@ const CoachingReportPage = (): ReactElement => {
{accountListId ? (
-
+
) : (
)}
diff --git a/pages/api/Schema/AccountListCoachUser/accountListCoachUser.graphql b/pages/api/Schema/AccountListCoachUser/accountListCoachUser.graphql
deleted file mode 100644
index 2cfe2cc95..000000000
--- a/pages/api/Schema/AccountListCoachUser/accountListCoachUser.graphql
+++ /dev/null
@@ -1,11 +0,0 @@
-extend type Query {
- getAccountListCoachUsers(accountListId: ID!): [AccountListCoachUser]
-}
-type AccountListCoachUser {
- id: ID!
- createdAt: ISO8601DateTime
- firstName: String
- lastName: String
- updatedAt: ISO8601DateTime
- updatedInDbAt: ISO8601DateTime
-}
diff --git a/pages/api/Schema/AccountListCoachUser/dataHandler.ts b/pages/api/Schema/AccountListCoachUser/dataHandler.ts
deleted file mode 100644
index 5e1d75774..000000000
--- a/pages/api/Schema/AccountListCoachUser/dataHandler.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { AccountListCoachUser } from '../../../../graphql/types.generated';
-
-const getAccountListCoachUsers = (
- data: [
- {
- id: string;
- type: string;
- attributes: {
- created_at: string;
- first_name: string;
- last_name: string;
- updated_at: string;
- updated_in_db_at: string;
- };
- },
- ],
-): AccountListCoachUser[] => {
- const users: AccountListCoachUser[] = data.map(
- ({
- id,
- attributes: {
- created_at,
- first_name,
- last_name,
- updated_at,
- updated_in_db_at,
- },
- }) => {
- return {
- id,
- createdAt: created_at,
- firstName: first_name,
- lastName: last_name,
- updatedAt: updated_at,
- updatedInDbAt: updated_in_db_at,
- };
- },
- );
- return users;
-};
-
-export { getAccountListCoachUsers };
diff --git a/pages/api/Schema/AccountListCoachUser/resolvers.ts b/pages/api/Schema/AccountListCoachUser/resolvers.ts
deleted file mode 100644
index ea2fefe42..000000000
--- a/pages/api/Schema/AccountListCoachUser/resolvers.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Resolvers } from '../../graphql-rest.page.generated';
-
-const AccountListCoachUserResolvers: Resolvers = {
- Query: {
- getAccountListCoachUsers: async (
- _source,
- { accountListId },
- { dataSources },
- ) => {
- return dataSources.mpdxRestApi.getAccountListCoachUsers(accountListId);
- },
- },
-};
-
-export { AccountListCoachUserResolvers };
diff --git a/pages/api/Schema/index.ts b/pages/api/Schema/index.ts
index 2040bbf3e..df47c8bd1 100644
--- a/pages/api/Schema/index.ts
+++ b/pages/api/Schema/index.ts
@@ -33,8 +33,6 @@ import UpdateCommentTypeDefs from './Tasks/Comments/UpdateComments/updateComment
import { UpdateCommentResolvers } from './Tasks/Comments/UpdateComments/resolvers';
import AccountListDonorAccountsTypeDefs from './AccountListDonorAccounts/accountListDonorAccounts.graphql';
import { AccountListDonorAccountsResolvers } from './AccountListDonorAccounts/resolvers';
-import AccountListCoachUserTypeDefs from './AccountListCoachUser/accountListCoachUser.graphql';
-import { AccountListCoachUserResolvers } from './AccountListCoachUser/resolvers';
import AccountListCoachesTypeDefs from './AccountListCoaches/accountListCoaches.graphql';
import { AccountListCoachesResolvers } from './AccountListCoaches/resolvers';
import ReportsPledgeHistoriesTyeDefs from './reports/pledgeHistories/pledgeHistories.graphql';
@@ -65,10 +63,6 @@ const schema = buildSubgraphSchema([
typeDefs: AccountListDonorAccountsTypeDefs,
resolvers: AccountListDonorAccountsResolvers,
},
- {
- typeDefs: AccountListCoachUserTypeDefs,
- resolvers: AccountListCoachUserResolvers,
- },
{
typeDefs: AppointmentResultsTypeDefs,
resolvers: AppointmentResultsResolvers,
diff --git a/pages/api/graphql-rest.page.ts b/pages/api/graphql-rest.page.ts
index 1f8eea078..4c4ee1200 100644
--- a/pages/api/graphql-rest.page.ts
+++ b/pages/api/graphql-rest.page.ts
@@ -54,7 +54,6 @@ import {
UpdateComment,
} from './Schema/Tasks/Comments/UpdateComments/datahandler';
import { getAccountListDonorAccounts } from './Schema/AccountListDonorAccounts/dataHandler';
-import { getAccountListCoachUsers } from './Schema/AccountListCoachUser/dataHandler';
import { getAccountListCoaches } from './Schema/AccountListCoaches/dataHandler';
import { getReportsPledgeHistories } from './Schema/reports/pledgeHistories/dataHandler';
import { DateTime, Duration, Interval } from 'luxon';
@@ -173,11 +172,6 @@ class MpdxRestApi extends RESTDataSource {
return getAccountListAnalytics(data);
}
- async getAccountListCoachUsers(accountListId: string) {
- const { data } = await this.get(`account_lists/${accountListId}/coaches`);
- return getAccountListCoachUsers(data);
- }
-
async getAppointmentResults(
accountListId: string,
endDate: string,
diff --git a/src/components/Coaching/CoachingDetail/CoachingDetail.stories.tsx b/src/components/Coaching/CoachingDetail/CoachingDetail.stories.tsx
index 01f87b69f..be71d1dc2 100644
--- a/src/components/Coaching/CoachingDetail/CoachingDetail.stories.tsx
+++ b/src/components/Coaching/CoachingDetail/CoachingDetail.stories.tsx
@@ -1,6 +1,9 @@
import React, { ReactElement } from 'react';
-import { LoadCoachingDetailQuery } from './LoadCoachingDetail.generated';
-import { CoachingDetail } from './CoachingDetail';
+import {
+ LoadAccountListCoachingDetailQuery,
+ LoadCoachingDetailQuery,
+} from './LoadCoachingDetail.generated';
+import { AccountListTypeEnum, CoachingDetail } from './CoachingDetail';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
export default {
@@ -21,14 +24,19 @@ export const Default = (): ReactElement => {
},
}}
>
-
+
);
};
export const AccountListDetail = (): ReactElement => {
return (
-
+
mocks={{
LoadAccountListCoachingDetail: {
accountList: {
@@ -38,7 +46,10 @@ export const AccountListDetail = (): ReactElement => {
},
}}
>
-
+
);
};
diff --git a/src/components/Coaching/CoachingDetail/CoachingDetail.test.tsx b/src/components/Coaching/CoachingDetail/CoachingDetail.test.tsx
index d6e592c3c..cf3f28c99 100644
--- a/src/components/Coaching/CoachingDetail/CoachingDetail.test.tsx
+++ b/src/components/Coaching/CoachingDetail/CoachingDetail.test.tsx
@@ -1,13 +1,12 @@
import React from 'react';
-import { renderHook } from '@testing-library/react-hooks';
+import { DateTime } from 'luxon';
import {
LoadCoachingDetailQuery,
- useGetAccountListUsersQuery,
- useGetAccountListCoachUsersQuery,
+ LoadAccountListCoachingDetailQuery,
} from './LoadCoachingDetail.generated';
-import { CoachingDetail } from './CoachingDetail';
+import { AccountListTypeEnum, CoachingDetail } from './CoachingDetail';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
-import { render } from '__tests__/util/testingLibraryReactMock';
+import { render, waitFor } from '__tests__/util/testingLibraryReactMock';
import TestRouter from '__tests__/util/TestRouter';
import {
beforeTestResizeObserver,
@@ -22,7 +21,7 @@ const router = {
push,
};
-const coachingId = 'coaching-id';
+const accountListId = 'account-list-1';
describe('LoadCoachingDetail', () => {
beforeEach(() => {
beforeTestResizeObserver();
@@ -31,6 +30,7 @@ describe('LoadCoachingDetail', () => {
afterEach(() => {
afterTestResizeObserver();
});
+
it('view', async () => {
const { findByText } = render(
@@ -38,7 +38,7 @@ describe('LoadCoachingDetail', () => {
mocks={{
LoadCoachingDetail: {
coachingAccountList: {
- id: coachingId,
+ id: accountListId,
name: 'John Doe',
currency: 'USD',
monthlyGoal: 55,
@@ -46,7 +46,10 @@ describe('LoadCoachingDetail', () => {
},
}}
>
-
+
,
);
@@ -61,7 +64,7 @@ describe('LoadCoachingDetail', () => {
mocks={{
LoadCoachingDetail: {
coachingAccountList: {
- id: coachingId,
+ id: accountListId,
name: 'John Doe',
currency: 'USD',
monthlyGoal: null,
@@ -69,7 +72,10 @@ describe('LoadCoachingDetail', () => {
},
}}
>
-
+
,
);
@@ -81,11 +87,13 @@ describe('LoadCoachingDetail', () => {
it('view isAccountList', async () => {
const { findByText } = render(
-
+
mocks={{
LoadAccountListCoachingDetail: {
accountList: {
- id: coachingId,
+ id: accountListId,
name: 'John Doe',
currency: 'USD',
monthlyGoal: 55,
@@ -93,7 +101,10 @@ describe('LoadCoachingDetail', () => {
},
}}
>
-
+
,
);
@@ -104,11 +115,13 @@ describe('LoadCoachingDetail', () => {
it('null goal isAccountList', async () => {
const { findByText } = render(
-
+
mocks={{
LoadAccountListCoachingDetail: {
accountList: {
- id: coachingId,
+ id: accountListId,
name: 'John Doe',
currency: 'USD',
monthlyGoal: null,
@@ -116,7 +129,10 @@ describe('LoadCoachingDetail', () => {
},
}}
>
-
+
,
);
@@ -124,34 +140,369 @@ describe('LoadCoachingDetail', () => {
expect(await findByText('Monthly $0')).toBeVisible();
expect(await findByText('Monthly Activity')).toBeVisible();
});
- it('query Users', async () => {
- const { result, waitForNextUpdate } = renderHook(
- () =>
- useGetAccountListCoachUsersQuery({
- variables: { accountListId: 'account-list-id' },
- }),
- {
- wrapper: GqlMockedProvider,
- },
- );
- await waitForNextUpdate();
- expect(
- result.current.data?.getAccountListCoachUsers?.length,
- ).toMatchInlineSnapshot(`2`);
+
+ describe('balance', () => {
+ it('displays the account list balance', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ balance: 1000,
+ currency: 'USD',
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('Balance')).toHaveTextContent('Balance: $1,000'),
+ );
+ });
});
- it('query Coach Users', async () => {
- const { result, waitForNextUpdate } = renderHook(
- () =>
- useGetAccountListUsersQuery({
- variables: { accountListId: 'account-list-id' },
- }),
- {
- wrapper: GqlMockedProvider,
- },
- );
- await waitForNextUpdate();
- expect(
- result.current.data?.accountListUsers.nodes.length,
- ).toMatchInlineSnapshot(`1`);
+
+ describe('staff ids', () => {
+ it('displays comma-separated staff ids', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ designationAccounts: [
+ { accountNumber: '123' },
+ { accountNumber: '456' },
+ ],
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('StaffIds')).toHaveTextContent('123, 456'),
+ );
+ });
+
+ it('ignores empty staff ids', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ designationAccounts: [
+ { accountNumber: '' },
+ { accountNumber: '456' },
+ ],
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('StaffIds')).toHaveTextContent('456'),
+ );
+ });
+
+ it('displays none when there are no staff ids', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ designationAccounts: [
+ { accountNumber: '123' },
+ { accountNumber: '456' },
+ ],
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('StaffIds')).toHaveTextContent('None'),
+ );
+ });
+ });
+
+ describe('last prayer letter', () => {
+ it('formats the prayer letter date', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ },
+ },
+ GetTaskAnalytics: {
+ taskAnalytics: {
+ lastElectronicNewsletterCompletedAt: DateTime.local(
+ 2023,
+ 1,
+ 1,
+ ).toISO(),
+ lastPhysicalNewsletterCompletedAt: DateTime.local(
+ 2023,
+ 1,
+ 2,
+ ).toISO(),
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('LastPrayerLetter')).toHaveTextContent(
+ 'Last Prayer Letter: Jan 2, 2023',
+ ),
+ );
+ });
+
+ it('displays none when there are no prayer letters', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ },
+ },
+ GetTaskAnalytics: {
+ taskAnalytics: {
+ lastElectronicNewsletterCompletedAt: null,
+ lastPhysicalNewsletterCompletedAt: null,
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('LastPrayerLetter')).toHaveTextContent(
+ 'Last Prayer Letter: None',
+ ),
+ );
+ });
+ });
+
+ describe('MPD info', () => {
+ it('displays info', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ activeMpdStartAt: DateTime.local(2023, 1, 1).toISO(),
+ activeMpdFinishAt: DateTime.local(2024, 1, 1).toISO(),
+ activeMpdMonthlyGoal: 1000,
+ weeksOnMpd: 12,
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('WeeksOnMpd')).toHaveTextContent('Weeks on MPD: 12'),
+ );
+ expect(getByTestId('MpdStartDate')).toHaveTextContent(
+ 'Start Date: Jan 1, 2023',
+ );
+ expect(getByTestId('MpdEndDate')).toHaveTextContent(
+ 'End Date: Jan 1, 2024',
+ );
+ expect(getByTestId('MpdCommitmentGoal')).toHaveTextContent(
+ 'Commitment Goal: $1,000',
+ );
+ });
+
+ it('displays none when info is missing', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ activeMpdStartAt: null,
+ activeMpdFinishAt: null,
+ activeMpdMonthlyGoal: null,
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('MpdStartDate')).toHaveTextContent(
+ 'Start Date: None',
+ ),
+ );
+ expect(getByTestId('MpdEndDate')).toHaveTextContent('End Date: None');
+ expect(getByTestId('MpdCommitmentGoal')).toHaveTextContent(
+ 'Commitment Goal: None',
+ );
+ });
+ });
+
+ describe('users', () => {
+ it('shows the user names and contact info', async () => {
+ const { getByText } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ users: {
+ nodes: [
+ {
+ firstName: 'John',
+ lastName: 'Doe',
+ emailAddresses: {
+ nodes: [
+ {
+ email: 'john.doe@cru.org',
+ },
+ ],
+ },
+ phoneNumbers: {
+ nodes: [
+ {
+ number: '111-111-1111',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() => expect(getByText('John Doe')).toBeInTheDocument());
+ expect(getByText('john.doe@cru.org')).toBeInTheDocument();
+ expect(getByText('111-111-1111')).toBeInTheDocument();
+ });
+ });
+
+ describe('coaches', () => {
+ it('shows the user names and contact info', async () => {
+ const { getByText } = render(
+
+
+ mocks={{
+ LoadCoachingDetail: {
+ coachingAccountList: {
+ currency: 'USD',
+ coaches: {
+ nodes: [
+ {
+ firstName: 'John',
+ lastName: 'Coach',
+ emailAddresses: {
+ nodes: [
+ {
+ email: 'john.coach@cru.org',
+ },
+ ],
+ },
+ phoneNumbers: {
+ nodes: [
+ {
+ number: '222-222-2222',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ }}
+ >
+
+
+ ,
+ );
+
+ await waitFor(() => expect(getByText('John Coach')).toBeInTheDocument());
+ expect(getByText('john.coach@cru.org')).toBeInTheDocument();
+ expect(getByText('222-222-2222')).toBeInTheDocument();
+ });
});
});
diff --git a/src/components/Coaching/CoachingDetail/CoachingDetail.tsx b/src/components/Coaching/CoachingDetail/CoachingDetail.tsx
index da74beeeb..073b3ae0c 100644
--- a/src/components/Coaching/CoachingDetail/CoachingDetail.tsx
+++ b/src/components/Coaching/CoachingDetail/CoachingDetail.tsx
@@ -1,28 +1,39 @@
-import React, { Fragment, useState } from 'react';
+import React, { Fragment, useState, useMemo } from 'react';
import { Box, Button, ButtonGroup, Divider, Typography } from '@mui/material';
// TODO: EcoOutlined is not defined on @mui/icons-material, find replacement.
import AccountCircle from '@mui/icons-material/AccountCircle';
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import Skeleton from '@mui/material/Skeleton';
+import { DateTime } from 'luxon';
import { AppealProgress } from '../AppealProgress/AppealProgress';
import { MonthlyCommitment } from './MonthlyCommitment/MonthlyCommitment';
import {
- useGetAccountListCoachUsersQuery,
- useGetAccountListUsersQuery,
useGetCoachingDonationGraphQuery,
useLoadAccountListCoachingDetailQuery,
useLoadCoachingDetailQuery,
} from './LoadCoachingDetail.generated';
+import { useGetTaskAnalyticsQuery } from 'src/components/Dashboard/ThisWeek/NewsletterMenu/NewsletterMenu.generated';
import theme from 'src/theme';
import { currencyFormat } from 'src/lib/intlFormat';
+import { dateFormat } from 'src/lib/intlFormat/intlFormat';
import { useLocale } from 'src/hooks/useLocale';
import DonationHistories from 'src/components/Dashboard/DonationHistories';
import { useGetDonationGraphQuery } from 'src/components/Reports/DonationsReport/GetDonationGraph.generated';
+import { SideContainerText } from './StyledComponents';
+import { CollapsibleEmailList } from './CollapsibleEmailList';
+import { CollapsiblePhoneList } from './CollapsiblePhoneList';
+import { getLastNewsletter } from './helpers';
+
+export enum AccountListTypeEnum {
+ Own = 'Own',
+ Coaching = 'Coaching',
+}
interface CoachingDetailProps {
- coachingId: string;
- isAccountListId: boolean;
+ accountListId: string;
+ // Whether the account list belongs to the user or someone that the user coaches
+ accountListType: AccountListTypeEnum;
}
const CoachingLoadingSkeleton = styled(Skeleton)(({ theme }) => ({
@@ -32,14 +43,14 @@ const CoachingLoadingSkeleton = styled(Skeleton)(({ theme }) => ({
}));
const CoachingDetailContainer = styled(Box)(({}) => ({
- width: '100&',
+ width: '100%',
minHeight: '100%',
display: 'flex',
}));
const CoachingSideContainer = styled(Box)(({ theme }) => ({
+ width: '20rem',
minHeight: '100%',
- flexGrow: 1,
padding: theme.spacing(1),
}));
@@ -61,6 +72,7 @@ const CoachingItemContainer = styled(Box)(({ theme }) => ({
}));
const CoachingMainTitleContainer = styled(Box)(({ theme }) => ({
+ flexGrow: 1,
display: 'flex',
margin: theme.spacing(1),
alignItems: 'center',
@@ -72,66 +84,74 @@ const CoachingMonthYearButtonGroup = styled(ButtonGroup)(({ theme }) => ({
color: theme.palette.primary.contrastText,
}));
-const SideContainerText = styled(Typography)(({ theme }) => ({
- color: theme.palette.primary.contrastText,
- margin: theme.spacing(0, 1),
-}));
-
const SideContainerIcon = styled(AccountCircle)(({ theme }) => ({
color: theme.palette.primary.contrastText,
margin: theme.spacing(0, 1),
}));
export const CoachingDetail: React.FC = ({
- coachingId,
- isAccountListId = false,
+ accountListId,
+ accountListType,
}) => {
const { t } = useTranslation();
const locale = useLocale();
- const { data: ownData, loading } = useLoadAccountListCoachingDetailQuery({
- variables: { coachingId },
- skip: !isAccountListId,
- });
+
+ const { data: ownData, loading: ownLoading } =
+ useLoadAccountListCoachingDetailQuery({
+ variables: { accountListId },
+ skip: accountListType !== AccountListTypeEnum.Own,
+ });
const { data: coachingData, loading: coachingLoading } =
useLoadCoachingDetailQuery({
- variables: { coachingId },
- skip: isAccountListId,
+ variables: { coachingAccountListId: accountListId },
+ skip: accountListType !== AccountListTypeEnum.Coaching,
});
- const { data: coachingUsersData, loading: coachingUsersLoading } =
- useGetAccountListCoachUsersQuery({
- variables: { accountListId: coachingId },
- });
+ const loading =
+ accountListType === AccountListTypeEnum.Own ? ownLoading : coachingLoading;
+ const accountListData =
+ accountListType === AccountListTypeEnum.Own
+ ? ownData?.accountList
+ : coachingData?.coachingAccountList;
- const { data: accountListUsersData, loading: accountListUsersLoading } =
- useGetAccountListUsersQuery({
- variables: { accountListId: coachingId },
- });
+ const staffIds = useMemo(
+ () =>
+ accountListData?.designationAccounts
+ .map((account) => account.accountNumber)
+ .filter((number) => number.length > 0) ?? [],
+ [accountListData],
+ );
const { data: ownDonationGraphData } = useGetDonationGraphQuery({
variables: {
- accountListId: coachingId,
+ accountListId,
},
- skip: !isAccountListId,
+ skip: accountListType !== AccountListTypeEnum.Own,
});
const { data: coachingDonationGraphData } = useGetCoachingDonationGraphQuery({
variables: {
- coachingAccountListId: coachingId,
+ coachingAccountListId: accountListId,
},
- skip: isAccountListId,
+ skip: accountListType !== AccountListTypeEnum.Coaching,
});
- const accountListData = isAccountListId
- ? ownData?.accountList
- : coachingData?.coachingAccountList;
+ const donationGraphData =
+ accountListType === AccountListTypeEnum.Own
+ ? ownDonationGraphData
+ : coachingDonationGraphData;
+
+ const { data: taskAnalyticsData } = useGetTaskAnalyticsQuery({
+ variables: {
+ accountListId,
+ },
+ });
- const donationGraphData = isAccountListId
- ? ownDonationGraphData
- : coachingDonationGraphData;
+ const formatOptionalDate = (isoDate: string | null | undefined): string =>
+ isoDate ? dateFormat(DateTime.fromISO(isoDate), locale) : t('None');
- const [isMonthly, setIsMonthly] = useState(true);
+ const [isMonthly, setIsMonthly] = useState(false);
return (
@@ -156,50 +176,76 @@ export const CoachingDetail: React.FC = ({
size="large"
>
+
+ {t('Balance:')}{' '}
+ {accountListData &&
+ currencyFormat(
+ accountListData.balance,
+ accountListData.currency,
+ locale,
+ )}
+
{t('Staff IDs:')}
- None
-
- {t('Last Prayer Letter:') /* TODO: Add value */}
+
+ {staffIds.length ? staffIds.join(', ') : t('None')}
+
+
+ {t('Last Prayer Letter:')}{' '}
+ {taskAnalyticsData &&
+ formatOptionalDate(
+ getLastNewsletter(
+ taskAnalyticsData.taskAnalytics
+ .lastElectronicNewsletterCompletedAt,
+ taskAnalyticsData.taskAnalytics
+ .lastPhysicalNewsletterCompletedAt,
+ ),
+ )}
{t('MPD Info')}
-
- {t('Week on MPD:') /* TODO: Add Value */}
+
+ {t('Weeks on MPD:')} {accountListData?.weeksOnMpd}
-
- {t('Start Date:') /* TODO: Add Value */}
+
+ {t('Start Date:')}{' '}
+ {accountListData &&
+ formatOptionalDate(accountListData?.activeMpdStartAt)}
-
- {t('End Date:') /* TODO: Add Value */}
+
+ {t('End Date:')}{' '}
+ {accountListData &&
+ formatOptionalDate(accountListData?.activeMpdFinishAt)}
-
- {t('Commitment Goal:') +
- ' ' +
- currencyFormat(
- accountListData?.monthlyGoal ? accountListData?.monthlyGoal : 0,
- accountListData?.currency,
- locale,
- )}
+
+ {t('Commitment Goal:')}{' '}
+ {accountListData &&
+ (typeof accountListData.activeMpdMonthlyGoal === 'number'
+ ? currencyFormat(
+ accountListData.activeMpdMonthlyGoal,
+ accountListData?.currency,
+ locale,
+ )
+ : t('None'))}
{t('Users')}
- {accountListUsersLoading ? (
+ {loading ? (
<>
@@ -207,27 +253,23 @@ export const CoachingDetail: React.FC = ({
>
) : (
- accountListUsersData?.accountListUsers.nodes.map(
- (accountList, _index) => {
- return (
-
-
-
- {accountList.user.firstName +
- ' ' +
- accountList.user.lastName}
-
-
-
- );
- },
- )
+ accountListData?.users.nodes.map((user) => (
+
+
+
+ {user.firstName + ' ' + user.lastName}
+
+
+
+
+
+ ))
)}
{t('Coaches')}
- {coachingUsersLoading ? (
+ {loading ? (
<>
@@ -235,21 +277,21 @@ export const CoachingDetail: React.FC = ({
>
) : (
- coachingUsersData?.getAccountListCoachUsers?.map((user, _index) => {
- return (
- <>
-
-
- {user?.firstName + ' ' + user?.lastName}
-
-
- >
- );
- })
+ accountListData?.coaches.nodes.map((coach) => (
+
+
+
+ {coach.firstName + ' ' + coach.lastName}
+
+
+
+
+
+ ))
)}
- {loading || coachingLoading ? (
+ {ownLoading || coachingLoading ? (
<>
@@ -272,7 +314,7 @@ export const CoachingDetail: React.FC = ({
= ({
/>
diff --git a/src/components/Coaching/CoachingDetail/CollapsibleEmailList.test.tsx b/src/components/Coaching/CoachingDetail/CollapsibleEmailList.test.tsx
new file mode 100644
index 000000000..d739ce65f
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/CollapsibleEmailList.test.tsx
@@ -0,0 +1,96 @@
+import { render } from '@testing-library/react';
+import { CollapsibleEmailList } from './CollapsibleEmailList';
+
+describe('CollapsibleEmailList', () => {
+ it('renders the primary email address and location', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ expect(getByTestId('EmailAddress')).toHaveTextContent(
+ 'example2@example.com - Home',
+ );
+ expect(getByTestId('ExpandMoreIcon')).toBeInTheDocument();
+ });
+
+ it('renders the first email address if none are primary', () => {
+ const { getByTestId, getByText } = render(
+ ,
+ );
+
+ expect(getByText('example1@example.com')).toBeInTheDocument();
+ expect(getByTestId('ExpandMoreIcon')).toBeInTheDocument();
+ });
+
+ it('renders missing email locations', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ expect(getByTestId('EmailAddress')).toHaveTextContent(
+ 'example1@example.com',
+ );
+ });
+
+ it('hides the toggle when there are zero emails', () => {
+ const { queryByTestId } = render();
+
+ expect(queryByTestId('ExpandMoreIcon')).not.toBeInTheDocument();
+ });
+
+ it('hides the toggle when there is only one email', () => {
+ const { queryByTestId } = render(
+ ,
+ );
+
+ expect(queryByTestId('ExpandMoreIcon')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/Coaching/CoachingDetail/CollapsibleEmailList.tsx b/src/components/Coaching/CoachingDetail/CollapsibleEmailList.tsx
new file mode 100644
index 000000000..5ab6a0855
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/CollapsibleEmailList.tsx
@@ -0,0 +1,47 @@
+import { EmailAddress } from '../../../../graphql/types.generated';
+import {
+ ContrastLink,
+ ContactInfoText,
+ SideContainerText,
+} from './StyledComponents';
+import { CollapsibleList } from './CollapsibleList';
+
+interface EmailProps {
+ email: Pick;
+}
+
+const Email: React.FC = ({ email }) => (
+
+ {email.email}
+ {email.location ? ` - ${email.location}` : null}
+
+);
+
+interface CollapsibleEmailListProps {
+ emails: Array>;
+}
+
+export const CollapsibleEmailList: React.FC = ({
+ emails,
+}) => {
+ const primaryEmail = emails.find((email) => email.primary) ?? emails[0];
+ if (!primaryEmail) {
+ return null;
+ }
+ const secondaryEmails = emails.filter((email) => email !== primaryEmail);
+
+ return (
+ }
+ secondaryItems={
+ secondaryEmails.length
+ ? secondaryEmails.map((email) => (
+
+
+
+ ))
+ : null
+ }
+ />
+ );
+};
diff --git a/src/components/Coaching/CoachingDetail/CollapsibleList.test.tsx b/src/components/Coaching/CoachingDetail/CollapsibleList.test.tsx
new file mode 100644
index 000000000..d438a0838
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/CollapsibleList.test.tsx
@@ -0,0 +1,37 @@
+import { render } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { CollapsibleList } from './CollapsibleList';
+
+describe('CollapsibleList', () => {
+ it('renders the primary items', () => {
+ const { getByText } = render();
+
+ expect(getByText('Primary')).toBeInTheDocument();
+ });
+
+ it('hides the toggle if there are no secondary items', () => {
+ const { queryByTestId } = render();
+
+ expect(queryByTestId('ExpandMoreIcon')).not.toBeInTheDocument();
+ });
+
+ it('initially hides the secondary items', () => {
+ const { queryByText } = render(
+ ,
+ );
+
+ expect(queryByText('Secondary')).not.toBeInTheDocument();
+ });
+
+ it('toggles the secondary items visible', () => {
+ const { getByTestId, getByText, queryByText } = render(
+ ,
+ );
+
+ userEvent.click(getByTestId('ExpandMoreIcon'));
+ expect(getByText('Secondary')).toBeInTheDocument();
+
+ userEvent.click(getByTestId('ExpandLessIcon'));
+ expect(queryByText('Secondary')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/Coaching/CoachingDetail/CollapsibleList.tsx b/src/components/Coaching/CoachingDetail/CollapsibleList.tsx
new file mode 100644
index 000000000..7a7f7fee4
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/CollapsibleList.tsx
@@ -0,0 +1,42 @@
+import { useState } from 'react';
+import { styled } from '@mui/material/styles';
+import ExpandMore from '@mui/icons-material/ExpandMore';
+import ExpandLess from '@mui/icons-material/ExpandLess';
+import { SideContainerText } from './StyledComponents';
+
+const ExpandMoreIcon = styled(ExpandMore)(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ cursor: 'pointer',
+}));
+
+const ExpandLessIcon = styled(ExpandLess)(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ cursor: 'pointer',
+}));
+
+interface CollapsibleListProps {
+ primaryItem: React.ReactNode;
+ secondaryItems?: React.ReactNode;
+}
+
+export const CollapsibleList: React.FC = ({
+ primaryItem,
+ secondaryItems,
+}) => {
+ const [moreVisible, setMoreVisible] = useState(false);
+
+ return (
+ <>
+
+ {primaryItem}
+ {secondaryItems &&
+ (moreVisible ? (
+ setMoreVisible(false)} />
+ ) : (
+ setMoreVisible(true)} />
+ ))}
+
+ {moreVisible && secondaryItems}
+ >
+ );
+};
diff --git a/src/components/Coaching/CoachingDetail/CollapsiblePhoneList.test.tsx b/src/components/Coaching/CoachingDetail/CollapsiblePhoneList.test.tsx
new file mode 100644
index 000000000..9d099c67f
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/CollapsiblePhoneList.test.tsx
@@ -0,0 +1,92 @@
+import { render } from '@testing-library/react';
+import { CollapsiblePhoneList } from './CollapsiblePhoneList';
+
+describe('CollapsiblePhoneList', () => {
+ it('renders the primary phone address and location', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ expect(getByTestId('PhoneNumber')).toHaveTextContent('222-222-2222 - Home');
+ expect(getByTestId('ExpandMoreIcon')).toBeInTheDocument();
+ });
+
+ it('renders the first phone address if none are primary', () => {
+ const { getByTestId, getByText } = render(
+ ,
+ );
+
+ expect(getByText('111-111-1111')).toBeInTheDocument();
+ expect(getByTestId('ExpandMoreIcon')).toBeInTheDocument();
+ });
+
+ it('renders missing phone locations', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ expect(getByTestId('PhoneNumber')).toHaveTextContent('111-111-1111');
+ });
+
+ it('hides the toggle when there are zero phones', () => {
+ const { queryByTestId } = render();
+
+ expect(queryByTestId('ExpandMoreIcon')).not.toBeInTheDocument();
+ });
+
+ it('hides the toggle when there is only one phone', () => {
+ const { queryByTestId } = render(
+ ,
+ );
+
+ expect(queryByTestId('ExpandMoreIcon')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/Coaching/CoachingDetail/CollapsiblePhoneList.tsx b/src/components/Coaching/CoachingDetail/CollapsiblePhoneList.tsx
new file mode 100644
index 000000000..691b937b8
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/CollapsiblePhoneList.tsx
@@ -0,0 +1,47 @@
+import { PhoneNumber } from '../../../../graphql/types.generated';
+import {
+ ContrastLink,
+ ContactInfoText,
+ SideContainerText,
+} from './StyledComponents';
+import { CollapsibleList } from './CollapsibleList';
+
+interface PhoneProps {
+ phone: Pick;
+}
+
+const Phone: React.FC = ({ phone }) => (
+
+ {phone.number}
+ {phone.location ? ` - ${phone.location}` : null}
+
+);
+
+interface CollapsiblePhoneListProps {
+ phones: Array>;
+}
+
+export const CollapsiblePhoneList: React.FC = ({
+ phones,
+}) => {
+ const primaryPhone = phones.find((phone) => phone.primary) ?? phones[0];
+ if (!primaryPhone) {
+ return null;
+ }
+ const secondaryPhones = phones.filter((phone) => phone !== primaryPhone);
+
+ return (
+ }
+ secondaryItems={
+ secondaryPhones.length
+ ? secondaryPhones.map((phone) => (
+
+
+
+ ))
+ : null
+ }
+ />
+ );
+};
diff --git a/src/components/Coaching/CoachingDetail/LoadCoachingDetail.graphql b/src/components/Coaching/CoachingDetail/LoadCoachingDetail.graphql
index 841407701..005688f28 100644
--- a/src/components/Coaching/CoachingDetail/LoadCoachingDetail.graphql
+++ b/src/components/Coaching/CoachingDetail/LoadCoachingDetail.graphql
@@ -1,7 +1,33 @@
-query LoadCoachingDetail($coachingId: ID!) {
- coachingAccountList(id: $coachingId) {
+fragment UserContactInfo on UserScopedToAccountList {
+ id
+ firstName
+ lastName
+ emailAddresses {
+ nodes {
+ id
+ email
+ location
+ primary
+ }
+ }
+ phoneNumbers {
+ nodes {
+ id
+ number
+ location
+ primary
+ }
+ }
+}
+
+query LoadCoachingDetail($coachingAccountListId: ID!) {
+ coachingAccountList(id: $coachingAccountListId) {
id
name
+ designationAccounts {
+ id
+ accountNumber
+ }
primaryAppeal {
active
amount
@@ -15,15 +41,33 @@ query LoadCoachingDetail($coachingId: ID!) {
currency
monthlyGoal
balance
+ activeMpdStartAt
+ activeMpdFinishAt
+ activeMpdMonthlyGoal
+ weeksOnMpd
receivedPledges
totalPledges
+ coaches {
+ nodes {
+ ...UserContactInfo
+ }
+ }
+ users {
+ nodes {
+ ...UserContactInfo
+ }
+ }
}
}
-query LoadAccountListCoachingDetail($coachingId: ID!) {
- accountList(id: $coachingId) {
+query LoadAccountListCoachingDetail($accountListId: ID!) {
+ accountList(id: $accountListId) {
id
name
+ designationAccounts {
+ id
+ accountNumber
+ }
primaryAppeal {
active
amount
@@ -37,36 +81,20 @@ query LoadAccountListCoachingDetail($coachingId: ID!) {
currency
monthlyGoal
balance
+ activeMpdStartAt
+ activeMpdFinishAt
+ activeMpdMonthlyGoal
+ weeksOnMpd
receivedPledges
totalPledges
- }
-}
-
-query GetAccountListCoachUsers($accountListId: ID!) {
- getAccountListCoachUsers(accountListId: $accountListId) {
- id
- firstName
- lastName
- createdAt
- updatedAt
- updatedInDbAt
- }
-}
-
-query GetAccountListUsers($accountListId: ID!) {
- accountListUsers(accountListId: $accountListId) {
- pageInfo {
- endCursor
- startCursor
- hasNextPage
- hasPreviousPage
+ coaches {
+ nodes {
+ ...UserContactInfo
+ }
}
- nodes {
- id
- user {
- id
- firstName
- lastName
+ users {
+ nodes {
+ ...UserContactInfo
}
}
}
diff --git a/src/components/Coaching/CoachingDetail/StyledComponents.tsx b/src/components/Coaching/CoachingDetail/StyledComponents.tsx
new file mode 100644
index 000000000..3836321e4
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/StyledComponents.tsx
@@ -0,0 +1,16 @@
+import { Link, Typography } from '@mui/material';
+import { styled } from '@mui/material/styles';
+
+export const SideContainerText = styled(Typography)(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ margin: theme.spacing(0, 1),
+}));
+
+export const ContrastLink = styled(Link)(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ textDecorationColor: theme.palette.primary.contrastText,
+}));
+
+export const ContactInfoText = styled('span')({
+ overflow: 'hidden',
+});
diff --git a/src/components/Coaching/CoachingDetail/helpers.test.tsx b/src/components/Coaching/CoachingDetail/helpers.test.tsx
new file mode 100644
index 000000000..c8a135e2a
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/helpers.test.tsx
@@ -0,0 +1,22 @@
+import { getLastNewsletter } from './helpers';
+
+describe('getLastNewsletter', () => {
+ it('returns the latest date when two dates are provided', () => {
+ expect(
+ getLastNewsletter('2023-01-01T00:00:00Z', '2023-01-02T00:00:00Z'),
+ ).toBe('2023-01-02T00:00:00Z');
+ });
+
+ it('returns the other date when one is missing', () => {
+ expect(getLastNewsletter('2023-01-01T00:00:00Z', null)).toBe(
+ '2023-01-01T00:00:00Z',
+ );
+ expect(getLastNewsletter(null, '2023-01-01T00:00:00Z')).toBe(
+ '2023-01-01T00:00:00Z',
+ );
+ });
+
+ it('returns null when both dates are missing', () => {
+ expect(getLastNewsletter(null, null)).toBeNull();
+ });
+});
diff --git a/src/components/Coaching/CoachingDetail/helpers.ts b/src/components/Coaching/CoachingDetail/helpers.ts
new file mode 100644
index 000000000..2e640368c
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/helpers.ts
@@ -0,0 +1,14 @@
+export const getLastNewsletter = (
+ electronicDate: string | null | undefined,
+ physicalDate: string | null | undefined,
+): string | null => {
+ if (electronicDate && physicalDate) {
+ return electronicDate > physicalDate ? electronicDate : physicalDate;
+ } else if (electronicDate) {
+ return electronicDate;
+ } else if (physicalDate) {
+ return physicalDate;
+ } else {
+ return null;
+ }
+};
diff --git a/src/components/Layouts/Primary/NavBar/NavBar.tsx b/src/components/Layouts/Primary/NavBar/NavBar.tsx
index 1fab21adf..acfa204d0 100644
--- a/src/components/Layouts/Primary/NavBar/NavBar.tsx
+++ b/src/components/Layouts/Primary/NavBar/NavBar.tsx
@@ -139,14 +139,14 @@ export const NavBar: FC = ({ onMobileClose, openMobile }) => {
},
{
title: t('Tools'),
- items: ToolsList.flatMap((toolsGroup) => [
- ...toolsGroup.items.map((tool) => ({
+ items: ToolsList.flatMap((toolsGroup) =>
+ toolsGroup.items.map((tool) => ({
title: tool.tool,
href: `https://${process.env.REWRITE_DOMAIN}/tools/${
toolsRedirectLinks[tool.id]
}`,
})),
- ]),
+ ),
},
{
title: t('Coaches'),
diff --git a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx
index 9b276bdbc..2b694912f 100644
--- a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx
+++ b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { ThemeProvider } from '@mui/material/styles';
import {
DesignationAccountsDocument,
@@ -154,4 +155,26 @@ describe('DesignationAccountsReport', () => {
expect(getByText(title)).toBeInTheDocument();
expect(queryByTestId('EmptyReport')).toBeInTheDocument();
});
+
+ it('renders nav list icon and onclick triggers onNavListToggle', async () => {
+ onNavListToggle.mockClear();
+ const { getByTestId } = render(
+
+
+ mocks={mocks}
+ >
+
+
+ ,
+ );
+
+ expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument();
+ userEvent.click(getByTestId('ReportsFilterIcon'));
+ await waitFor(() => expect(onNavListToggle).toHaveBeenCalled());
+ });
});
diff --git a/src/components/Reports/DonationsReport/DonationsReport.test.tsx b/src/components/Reports/DonationsReport/DonationsReport.test.tsx
index b9cb0b27f..1054cf003 100644
--- a/src/components/Reports/DonationsReport/DonationsReport.test.tsx
+++ b/src/components/Reports/DonationsReport/DonationsReport.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { ThemeProvider } from '@mui/material/styles';
import { DateTime } from 'luxon';
import theme from '../../../theme';
@@ -235,4 +236,26 @@ describe('DonationsReport', () => {
}),
);
});
+ it('renders nav list icon and onclick triggers onNavListToggle', async () => {
+ onNavListToggle.mockClear();
+ const { getByTestId } = render(
+
+
+ mocks={mocks}>
+
+
+
+ ,
+ );
+
+ expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument();
+ userEvent.click(getByTestId('ReportsFilterIcon'));
+ await waitFor(() => expect(onNavListToggle).toHaveBeenCalled());
+ });
});
diff --git a/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx b/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx
index 8e452ea76..f4bae0025 100644
--- a/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx
+++ b/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { ThemeProvider } from '@mui/material/styles';
import theme from '../../../theme';
import { GetExpectedMonthlyTotalsQuery } from '../../../../pages/accountLists/[accountListId]/reports/GetExpectedMonthlyTotals.generated';
@@ -142,4 +143,25 @@ describe('ExpectedMonthlyTotalReport', () => {
}),
);
});
+ it('renders nav list icon and onclick triggers onNavListToggle', async () => {
+ onNavListToggle.mockClear();
+ const { getByTestId } = render(
+
+
+
+
+
+
+ ,
+ );
+
+ expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument();
+ userEvent.click(getByTestId('ReportsFilterIcon'));
+ await waitFor(() => expect(onNavListToggle).toHaveBeenCalled());
+ });
});
diff --git a/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx b/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx
index 15ae8206f..211953ea8 100644
--- a/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx
+++ b/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx
@@ -213,6 +213,9 @@ const mocks: Mocks = {
};
describe('PartnerGivingAnalysisReport', () => {
+ beforeEach(() => {
+ onNavListToggle.mockClear();
+ });
it('loading', async () => {
const { queryByTestId, queryByText } = render(
@@ -581,4 +584,26 @@ describe('PartnerGivingAnalysisReport', () => {
// Test that it rounds to two decimal points
expect(getByText('CA$86.47')).toBeInTheDocument();
});
+
+ it('renders nav list icon and onclick triggers onNavListToggle', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={mocks}
+ >
+
+
+ ,
+ );
+
+ expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument();
+ userEvent.click(getByTestId('ReportsFilterIcon'));
+ await waitFor(() => expect(onNavListToggle).toHaveBeenCalled());
+ });
});
diff --git a/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.test.tsx b/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.test.tsx
index e3fa91838..ad0f0f196 100644
--- a/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.test.tsx
+++ b/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { ThemeProvider } from '@mui/material/styles';
import {
FinancialAccountsDocument,
@@ -64,6 +65,9 @@ const emptyMocks = {
};
describe('ResponsibilityCentersReport', () => {
+ beforeEach(() => {
+ onNavListToggle.mockClear();
+ });
it('default', async () => {
const { getByText, getByTestId, queryByTestId } = render(
@@ -93,6 +97,27 @@ describe('ResponsibilityCentersReport', () => {
expect(getByTestId('ResponsibilityCentersScrollBox')).toBeInTheDocument();
});
+ it('renders nav list icon and onclick triggers onNavListToggle', async () => {
+ const { getByTestId } = render(
+
+
+ mocks={mocks}
+ >
+
+
+ ,
+ );
+
+ expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument();
+ userEvent.click(getByTestId('ReportsFilterIcon'));
+ await waitFor(() => expect(onNavListToggle).toHaveBeenCalled());
+ });
+
it('loading', async () => {
const { queryByTestId, getByText } = render(
diff --git a/src/components/Shared/Forms/Accordions/AccordianGroup.test.tsx b/src/components/Shared/Forms/Accordions/AccordianGroup.test.tsx
new file mode 100644
index 000000000..682243e09
--- /dev/null
+++ b/src/components/Shared/Forms/Accordions/AccordianGroup.test.tsx
@@ -0,0 +1,13 @@
+import { render } from '@testing-library/react';
+import { AccordionGroup } from './AccordionGroup';
+
+describe('AccordionGroup', () => {
+ it('Should load title and children', () => {
+ const { getByText } = render(
+ Children,
+ );
+
+ expect(getByText('AccordionGroupTitle')).toBeInTheDocument();
+ expect(getByText('Children')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Shared/Forms/Accordions/AccordionGroup.tsx b/src/components/Shared/Forms/Accordions/AccordionGroup.tsx
new file mode 100644
index 000000000..3c3a93cb4
--- /dev/null
+++ b/src/components/Shared/Forms/Accordions/AccordionGroup.tsx
@@ -0,0 +1,21 @@
+import { Box, Typography } from '@mui/material';
+import React from 'react';
+
+interface AccordionGroupProps {
+ title: string;
+ children?: React.ReactNode;
+}
+
+export const AccordionGroup: React.FC = ({
+ title,
+ children,
+}) => {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+};
diff --git a/src/components/Shared/Forms/Accordions/AccordionItem.test.tsx b/src/components/Shared/Forms/Accordions/AccordionItem.test.tsx
new file mode 100644
index 000000000..03b6f4fbc
--- /dev/null
+++ b/src/components/Shared/Forms/Accordions/AccordionItem.test.tsx
@@ -0,0 +1,152 @@
+import { render } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { ThemeProvider } from '@mui/material/styles';
+import theme from 'src/theme';
+import { AccordionItem } from './AccordionItem';
+
+const expandedPanel = 'expandedPanel';
+
+describe('AccordionItem', () => {
+ const onAccordionChange = jest.fn();
+ beforeEach(() => {
+ onAccordionChange.mockClear();
+ });
+ it('Should not render Accordian Details', () => {
+ const { queryByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(queryByText('Children')).not.toBeInTheDocument();
+ });
+ it('Should not render value', () => {
+ const { queryByTestId } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(queryByTestId('AccordionSummaryValue')).not.toBeInTheDocument();
+ });
+
+ it('Should render label', () => {
+ const { getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(getByText('expandedPanel')).toBeInTheDocument();
+ });
+
+ it('Should render value', () => {
+ const { getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(getByText('AccordianValue')).toBeInTheDocument();
+ });
+
+ it('Should render Accordian Details', () => {
+ const { getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(getByText('Children')).toBeInTheDocument();
+ });
+
+ it('Should render Children with FullWidth', () => {
+ const { getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(getByText('Children')).toBeInTheDocument();
+ });
+
+ it('Should render Children and Image', () => {
+ const { getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(getByText('Children')).toBeInTheDocument();
+ expect(getByText('image.png')).toBeInTheDocument();
+ });
+ it('Should run onAccordionChange()', () => {
+ const { getByTestId } = render(
+
+
+ Children
+
+ ,
+ );
+
+ userEvent.click(getByTestId('AccordionSummaryValue'));
+ expect(onAccordionChange).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/Shared/Forms/Accordions/AccordionItem.tsx b/src/components/Shared/Forms/Accordions/AccordionItem.tsx
new file mode 100644
index 000000000..47981f7b6
--- /dev/null
+++ b/src/components/Shared/Forms/Accordions/AccordionItem.tsx
@@ -0,0 +1,169 @@
+import React, { useMemo } from 'react';
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ Box,
+ Typography,
+} from '@mui/material';
+import { styled } from '@mui/material/styles';
+import { ExpandMore } from '@mui/icons-material';
+
+export const accordionShared = {
+ '&:before': {
+ content: 'none',
+ },
+ '& .MuiAccordionSummary-root.Mui-expanded': {
+ minHeight: 'unset',
+ },
+};
+
+const StyledAccordion = styled(Accordion)(() => ({
+ overflow: 'hidden',
+ ...accordionShared,
+}));
+
+const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
+ '&.Mui-expanded': {
+ backgroundColor: theme.palette.mpdxYellow.main,
+ },
+ '& .MuiAccordionSummary-content': {
+ [theme.breakpoints.only('xs')]: {
+ flexDirection: 'column',
+ },
+ },
+}));
+
+const StyledAccordionColumn = styled(Box)(({ theme }) => ({
+ paddingRight: theme.spacing(2),
+ flexBasis: '100%',
+ [theme.breakpoints.only('xs')]: {
+ '&:nth-child(2)': {
+ fontStyle: 'italic',
+ },
+ },
+ [theme.breakpoints.up('md')]: {
+ '&:first-child:not(:last-child)': {
+ flexBasis: '33.33%',
+ },
+ '&:nth-child(2)': {
+ flexBasis: '66.66%',
+ },
+ },
+}));
+
+const StyledAccordionDetails = styled(Box)(({ theme }) => ({
+ [theme.breakpoints.up('md')]: {
+ flexBasis: 'calc((100% - 36px) * 0.661)',
+ marginLeft: 'calc((100% - 36px) * 0.338)',
+ },
+}));
+
+const AccordionLeftDetails = styled(Box)(({ theme }) => ({
+ [theme.breakpoints.up('md')]: {
+ width: 'calc((100% - 36px) * 0.338)',
+ },
+ [theme.breakpoints.down('md')]: {
+ width: '200px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ marginBottom: '10px',
+ width: '100%',
+ },
+}));
+
+const AccordionRightDetails = styled(Box)(({ theme }) => ({
+ [theme.breakpoints.up('md')]: {
+ width: 'calc((100% - 36px) * 0.661)',
+ },
+ [theme.breakpoints.down('md')]: {
+ width: 'calc(100% - 200px)',
+ },
+ [theme.breakpoints.down('sm')]: {
+ width: '100%',
+ },
+}));
+
+const AccordionLImageDetails = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ [theme.breakpoints.down('sm')]: {
+ flexWrap: 'wrap',
+ },
+}));
+
+const AccordionLeftDetailsImage = styled(Box)(({ theme }) => ({
+ maxWidth: '200px',
+ ' & > img': {
+ width: '100%',
+ },
+
+ [theme.breakpoints.down('md')]: {
+ ' & > img': {
+ maxWidth: '150px',
+ },
+ },
+ [theme.breakpoints.down('sm')]: {
+ ' & > img': {
+ maxWidth: '100px',
+ },
+ },
+}));
+
+interface AccordionItemProps {
+ onAccordionChange: (label: string) => void;
+ expandedPanel: string;
+ label: string;
+ value: string;
+ children?: React.ReactNode;
+ fullWidth?: boolean;
+ image?: React.ReactNode;
+}
+
+export const AccordionItem: React.FC = ({
+ onAccordionChange,
+ expandedPanel,
+ label,
+ value,
+ children,
+ fullWidth = false,
+ image,
+}) => {
+ const expanded = useMemo(
+ () => expandedPanel.toLowerCase() === label.toLowerCase(),
+ [expandedPanel, label],
+ );
+ return (
+ onAccordionChange(label)}
+ expanded={expanded}
+ disableGutters
+ >
+ }>
+
+ {label}
+
+ {value && (
+
+ {value}
+
+ )}
+
+ {expanded && (
+
+ {!fullWidth && !image && (
+ {children}
+ )}
+ {fullWidth && !image && {children}}
+ {image && (
+
+
+ {image}
+
+ {children}
+
+ )}
+
+ )}
+
+ );
+};
diff --git a/src/components/Shared/Forms/DialogActions.test.tsx b/src/components/Shared/Forms/DialogActions.test.tsx
new file mode 100644
index 000000000..cd549b1bb
--- /dev/null
+++ b/src/components/Shared/Forms/DialogActions.test.tsx
@@ -0,0 +1,17 @@
+import { render } from '@testing-library/react';
+import { ThemeProvider } from '@mui/material/styles';
+import theme from 'src/theme';
+import { DialogActionsLeft } from './DialogActions';
+
+describe('DialogActionsLeft', () => {
+ it('Should render children and pass down args', () => {
+ const { getByText, getByTestId } = render(
+
+ Children
+ ,
+ );
+
+ expect(getByText('Children')).toBeInTheDocument();
+ expect(getByTestId('dataTestId')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Shared/Forms/DialogActions.tsx b/src/components/Shared/Forms/DialogActions.tsx
new file mode 100644
index 000000000..8f9ac2e4d
--- /dev/null
+++ b/src/components/Shared/Forms/DialogActions.tsx
@@ -0,0 +1,16 @@
+import { DialogActions, DialogActionsProps } from '@mui/material';
+import React from 'react';
+
+export const DialogActionsLeft: React.FC = ({
+ children,
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/components/Shared/Forms/FieldHelper.tsx b/src/components/Shared/Forms/FieldHelper.tsx
new file mode 100644
index 000000000..912cf12fb
--- /dev/null
+++ b/src/components/Shared/Forms/FieldHelper.tsx
@@ -0,0 +1,33 @@
+import { FormLabel, FormHelperText, OutlinedInput, Theme } from '@mui/material';
+import { styled } from '@mui/material/styles';
+
+export enum HelperPositionEnum {
+ Top = 'top',
+ Bottom = 'bottom',
+}
+
+const SharedFieldStyles = ({ theme }: { theme: Theme }) => ({
+ '&:not(:first-child)': {
+ marginTop: theme.spacing(1),
+ },
+});
+
+export const StyledOutlinedInput = styled(OutlinedInput)(SharedFieldStyles);
+
+export const StyledFormLabel = styled(FormLabel)(({ theme }) => ({
+ color: theme.palette.text.primary,
+ fontWeight: 700,
+ marginBottom: theme.spacing(1),
+ '& .MuiFormControlLabel-label': {
+ fontWeight: '700',
+ },
+}));
+
+export const StyledFormHelperText = styled(FormHelperText)(({ theme }) => ({
+ margin: 0,
+ fontSize: 16,
+ color: theme.palette.text.primary,
+ '&:not(:first-child)': {
+ marginTop: theme.spacing(1),
+ },
+}));
diff --git a/src/components/Shared/Forms/FieldWrapper.test.tsx b/src/components/Shared/Forms/FieldWrapper.test.tsx
new file mode 100644
index 000000000..7c26c66f2
--- /dev/null
+++ b/src/components/Shared/Forms/FieldWrapper.test.tsx
@@ -0,0 +1,69 @@
+import { render } from '@testing-library/react';
+import { ThemeProvider } from '@mui/material/styles';
+import theme from 'src/theme';
+import { FieldWrapper } from './FieldWrapper';
+import { HelperPositionEnum } from './FieldHelper';
+
+describe('FieldWrapper', () => {
+ it('Should render children', () => {
+ const { getByText } = render(
+
+ Children
+ ,
+ );
+
+ expect(getByText('Children')).toBeInTheDocument();
+ });
+
+ it('Should render labelText', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText('labelText')).toBeInTheDocument();
+ });
+
+ it('Should render Helper Text before Children', () => {
+ const { getByTestId, getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(
+ getByTestId('helper-text-top').compareDocumentPosition(
+ getByText('Children'),
+ ),
+ ).toBe(4);
+ });
+ it('Should render Helper Text after Children', () => {
+ const { getByTestId, getByText } = render(
+
+
+ Children
+
+ ,
+ );
+
+ expect(
+ getByTestId('helper-text-bottom').compareDocumentPosition(
+ getByText('Children'),
+ ),
+ ).toBe(2);
+ });
+});
diff --git a/src/components/Shared/Forms/FieldWrapper.tsx b/src/components/Shared/Forms/FieldWrapper.tsx
new file mode 100644
index 000000000..7eebbfc1c
--- /dev/null
+++ b/src/components/Shared/Forms/FieldWrapper.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { FormControl, FormControlProps } from '@mui/material';
+import {
+ HelperPositionEnum,
+ StyledFormHelperText,
+ StyledFormLabel,
+} from './FieldHelper';
+
+interface FieldWrapperProps {
+ labelText?: string;
+ helperText?: string;
+ helperPosition?: HelperPositionEnum;
+ formControlDisabled?: FormControlProps['disabled'];
+ formControlError?: FormControlProps['error'];
+ formControlFullWidth?: FormControlProps['fullWidth'];
+ formControlRequired?: FormControlProps['required'];
+ formControlVariant?: FormControlProps['variant'];
+ formHelperTextProps?: object;
+ children?: React.ReactNode;
+}
+
+export const FieldWrapper: React.FC = ({
+ labelText = '',
+ helperText = '',
+ helperPosition = HelperPositionEnum.Top,
+ formControlDisabled = false,
+ formControlError = false,
+ formControlFullWidth = true,
+ formControlRequired = false,
+ formControlVariant = 'outlined',
+ formHelperTextProps = { variant: 'standard' },
+ children,
+}) => {
+ const { t } = useTranslation();
+ const labelOutput = labelText ? (
+
+ {t(labelText)}
+
+ ) : (
+ ''
+ );
+
+ const helperTextOutput = helperText ? (
+
+ {t(helperText)}
+
+ ) : (
+ ''
+ );
+
+ return (
+
+ {labelOutput}
+ {helperPosition === HelperPositionEnum.Top && helperTextOutput}
+ {children}
+ {helperPosition === HelperPositionEnum.Bottom && helperTextOutput}
+
+ );
+};
diff --git a/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx
index 0681c19ff..9ef1d1b04 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx
@@ -46,4 +46,21 @@ describe('MultiPageHeader', () => {
expect(queryByText('CA111')).toBeNull();
});
+
+ it('should render the Settings menu', async () => {
+ const { getByTestId, getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText('Toggle Preferences Menu')).toBeInTheDocument();
+ expect(getByTestId('SettingsMenuIcon')).toBeInTheDocument();
+ });
});
diff --git a/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx b/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx
index 9ce75ed00..8693299d3 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx
@@ -84,10 +84,16 @@ export const MultiPageHeader: FC = ({
>
{headerType === HeaderTypeEnum.Report && (
-
+
)}
{headerType === HeaderTypeEnum.Settings && (
-
+
)}
diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx
index bbd8016fb..c1f587efa 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx
@@ -1,8 +1,9 @@
import React from 'react';
import { Item } from './Item';
-import { render } from '__tests__/util/testingLibraryReactMock';
+import { render, waitFor } from '__tests__/util/testingLibraryReactMock';
import TestWrapper from '__tests__/util/TestWrapper';
import { NavTypeEnum } from '../MultiPageMenu';
+import userEvent from '@testing-library/user-event';
const item = {
id: 'testItem',
@@ -10,14 +11,74 @@ const item = {
subTitle: 'test subTitle',
};
+const itemWithSubitems = {
+ id: 'testItem',
+ title: 'test title',
+ subTitle: 'test subTitle',
+ subItems: [
+ {
+ id: 'organizations',
+ title: 'Impersonate & Share',
+ grantedAccess: ['admin'],
+ },
+ {
+ id: 'organizations/accountLists',
+ title: 'Account Lists',
+ grantedAccess: ['admin'],
+ },
+ ],
+};
+
describe('Item', () => {
it('default', () => {
- const { queryByText } = render(
+ const { queryByText, queryByTestId } = render(
,
);
expect(queryByText(item.title)).toBeInTheDocument();
expect(queryByText(item.subTitle)).toBeInTheDocument();
+ expect(queryByTestId('multiPageMenuCollapser')).not.toBeInTheDocument();
+ });
+
+ it('should render subItems', async () => {
+ const { getByTestId, queryByText } = render(
+
+
+ ,
+ );
+
+ const arrowForwardIosIcon = getByTestId('ArrowForwardIosIcon');
+
+ expect(arrowForwardIosIcon).toBeInTheDocument();
+
+ expect(queryByText('Impersonate & Share')).not.toBeInTheDocument();
+ expect(queryByText('Account Lists')).not.toBeInTheDocument();
+
+ userEvent.click(arrowForwardIosIcon);
+
+ await waitFor(() => {
+ expect(queryByText('Impersonate & Share')).toBeInTheDocument();
+ expect(queryByText('Account Lists')).toBeInTheDocument();
+ });
+ });
+
+ it('should show subItems as one if selected', async () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText('Impersonate & Share')).toBeInTheDocument();
+ expect(getByText('Account Lists')).toBeInTheDocument();
});
});
diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx
index 98fc06f9b..1ef0863ab 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx
@@ -7,6 +7,7 @@ import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
import theme from 'src/theme';
import { GetDesignationAccountsQuery } from 'src/components/Reports/DonationsReport/Table/Modal/EditDonation.generated';
+import { GetUserAccessQuery } from './MultiPageMenuItems.generated';
const accountListId = 'account-list-1';
const selected = 'salaryCurrency';
@@ -142,4 +143,92 @@ describe('MultiPageMenu', () => {
queryByRole('combobox', { name: 'Designation Account' }),
).not.toBeInTheDocument();
});
+
+ it('shows the developer tools', async () => {
+ const mutationSpy = jest.fn();
+ const { queryByText, getByText } = render(
+
+
+
+ mocks={{
+ GetUserAccess: {
+ user: {
+ admin: false,
+ developer: true,
+ },
+ },
+ }}
+ onCall={mutationSpy}
+ >
+ {}}
+ designationAccounts={[]}
+ setDesignationAccounts={jest.fn()}
+ navType={NavTypeEnum.Settings}
+ />
+
+
+ ,
+ );
+
+ await waitFor(() => expect(mutationSpy).toHaveBeenCalled());
+
+ await waitFor(() => {
+ expect(queryByText('Manage Organizations')).not.toBeInTheDocument();
+ });
+
+ await waitFor(() => {
+ expect(getByText('Admin Console')).toBeInTheDocument();
+ expect(getByText('Backend Admin')).toBeInTheDocument();
+ expect(getByText('Sidekiq')).toBeInTheDocument();
+ });
+ });
+
+ it('shows the admin tools', async () => {
+ const mutationSpy = jest.fn();
+ const { queryByText, getByText } = render(
+
+
+
+ mocks={{
+ GetUserAccess: {
+ user: {
+ admin: true,
+ developer: false,
+ },
+ },
+ }}
+ onCall={mutationSpy}
+ >
+ {}}
+ designationAccounts={[]}
+ setDesignationAccounts={jest.fn()}
+ navType={NavTypeEnum.Settings}
+ />
+
+
+ ,
+ );
+
+ await waitFor(() => expect(mutationSpy).toHaveBeenCalled());
+
+ await waitFor(() => {
+ expect(queryByText('Sidekiq')).not.toBeInTheDocument();
+ expect(queryByText('Backend Admin')).not.toBeInTheDocument();
+ });
+
+ await waitFor(() => {
+ expect(getByText('Manage Organizations')).toBeInTheDocument();
+ expect(getByText('Admin Console')).toBeInTheDocument();
+ });
+ });
});
diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx
index bc030e15c..17b8f4e2e 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx
@@ -139,7 +139,7 @@ export const MultiPageMenu: React.FC = ({
}
} else return true;
return false;
- }, [item]);
+ }, [item, userPrivileges]);
if (!showItem) return null;
return (