diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8ebef074..76e188a08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: env: # Use production API for codegen to make sure production is compatible with the code to be merged - API_URL: 'https://api.mpdx.org/graphql' + API_URL: 'https://api.stage.mpdx.org/graphql' SITE_URL: 'http://next-stage.mpdx.org' jobs: diff --git a/__tests__/util/TestWrapper.tsx b/__tests__/util/TestWrapper.tsx index b75c7b58e..984906778 100644 --- a/__tests__/util/TestWrapper.tsx +++ b/__tests__/util/TestWrapper.tsx @@ -1,27 +1,53 @@ import React, { ReactElement, ReactNode } from 'react'; -import { InMemoryCache } from '@apollo/client'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { ApolloLink, InMemoryCache } from '@apollo/client'; +import { onError } from '@apollo/client/link/error'; +import { + MockLink, + MockedProvider, + MockedResponse, +} from '@apollo/client/testing'; import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { SnackbarProvider } from 'notistack'; import TestRouter from './TestRouter'; +export type OnErrorFunction = (error: string) => void; + interface Props { mocks?: MockedResponse[]; children: ReactNode; cache?: InMemoryCache; + onErrorFunction?: OnErrorFunction; } const TestWrapper = ({ mocks = [], children, cache = new InMemoryCache({ addTypename: false }), + onErrorFunction, }: Props): ReactElement => { + const mockLink = new MockLink(mocks); + const errorLoggingLink = onError(({ networkError }) => { + if (networkError) { + // eslint-disable-next-line no-console + console.warn(`[Network error]: ${networkError}`); + if (onErrorFunction) { + onErrorFunction(JSON.stringify(networkError.message)); + } + } + }); + const link = ApolloLink.from([errorLoggingLink, mockLink]); + return ( - + {children} diff --git a/src/components/Constants/LoadConstants.graphql b/src/components/Constants/LoadConstants.graphql index 1caaf2101..db4eaa4ee 100644 --- a/src/components/Constants/LoadConstants.graphql +++ b/src/components/Constants/LoadConstants.graphql @@ -3,6 +3,7 @@ query LoadConstants { activities { id value + name } languages { id @@ -58,9 +59,26 @@ query LoadConstants { key value } - # userNotificationTitles { - # id - # value - # } + phases { + id + name + results { + resultOptions { + dbResult { + task + result + } + name + suggestedContactStatus + suggestedNextActions + } + tags { + value + id + } + } + contactStatuses + tasks + } } } diff --git a/src/components/Constants/LoadConstantsMock.ts b/src/components/Constants/LoadConstantsMock.ts new file mode 100644 index 000000000..f926797bb --- /dev/null +++ b/src/components/Constants/LoadConstantsMock.ts @@ -0,0 +1,860 @@ +import { MockedResponse } from '@apollo/client/testing'; +import { + LoadConstantsDocument, + LoadConstantsQuery, +} from './LoadConstants.generated'; + +const LoadConstantsMock = (): MockedResponse => { + return { + request: { + query: LoadConstantsDocument, + }, + result: { + data: loadConstantsMockData, + }, + }; +}; + +export const loadConstantsMockData = { + constant: { + __typename: 'Constant', + statuses: [ + { + id: 'NEVER_CONTACTED', + value: 'New Connection', + }, + { + id: 'ASK_IN_FUTURE', + value: 'Ask in Future', + }, + { + id: 'CULTIVATE_RELATIONSHIP', + value: 'Cultivate Relationship', + }, + { + id: 'CONTACT_FOR_APPOINTMENT', + value: 'Initiate for Appointment', + }, + { + id: 'APPOINTMENT_SCHEDULED', + value: 'Appointment Scheduled', + }, + { + id: 'CALL_FOR_DECISION', + value: 'Follow Up for Decision', + }, + { + id: 'PARTNER_FINANCIAL', + value: 'Partner - Financial', + }, + { + id: 'PARTNER_SPECIAL', + value: 'Partner - Special', + }, + { + id: 'PARTNER_PRAY', + value: 'Partner - Pray', + }, + { + id: 'NOT_INTERESTED', + value: 'Not Interested', + }, + { + id: 'UNRESPONSIVE', + value: 'Unresponsive', + }, + { + id: 'NEVER_ASK', + value: 'Never Ask', + }, + { + id: 'RESEARCH_ABANDONED', + value: 'Research Abandoned', + }, + { + id: 'EXPIRED_REFERRAL', + value: 'Expired Connection', + }, + { + id: 'RESEARCH_CONTACT_INFO', + value: 'Research Contact Info', + }, + ], + activities: [ + { + id: 'INITIATION_PHONE_CALL', + name: 'Phone call to initiate appointment', + value: 'Initiation - Phone Call', + }, + { + id: 'INITIATION_EMAIL', + name: 'Email to initiate', + value: 'Initiation - Email', + }, + { + id: 'INITIATION_TEXT_MESSAGE', + name: 'Text message to initiate', + value: 'Initiation - Text Message', + }, + { + id: 'INITIATION_SOCIAL_MEDIA', + name: 'Social media message to initiate', + value: 'Initiation - Social Media', + }, + { + id: 'INITIATION_LETTER', + name: 'Letter to initiate', + value: 'Initiation - Letter', + }, + { + id: 'INITIATION_SPECIAL_GIFT_APPEAL', + name: 'Special gift appeal', + value: 'Initiation - Special Gift Appeal', + }, + { + id: 'INITIATION_IN_PERSON', + name: 'Initiate in person', + value: 'Initiation - In Person', + }, + { + id: 'APPOINTMENT_IN_PERSON', + name: 'In person appointment', + value: 'Appointment - In Person', + }, + { + id: 'APPOINTMENT_PHONE_CALL', + name: 'phone appointment', + value: 'Appointment - Phone Call', + }, + { + id: 'APPOINTMENT_VIDEO_CALL', + name: 'video appointment', + value: 'Appointment - Video Call', + }, + { + id: 'FOLLOW_UP_PHONE_CALL', + name: 'phone call to follow up', + value: 'Follow Up - Phone Call', + }, + { + id: 'FOLLOW_UP_EMAIL', + name: 'email to follow up', + value: 'Follow Up - Email', + }, + { + id: 'FOLLOW_UP_TEXT_MESSAGE', + name: 'text message to follow up', + value: 'Follow Up - Text Message', + }, + { + id: 'FOLLOW_UP_SOCIAL_MEDIA', + name: 'social media message to follow up', + value: 'Follow Up - Social Media', + }, + { + id: 'FOLLOW_UP_IN_PERSON', + name: 'follow up in person', + value: 'Follow Up - In Person', + }, + { + id: 'PARTNER_CARE_PHONE_CALL', + name: 'call partner for cultivation', + value: 'Partner Care - Phone Call', + }, + { + id: 'PARTNER_CARE_EMAIL', + name: 'email partner for cultivation', + value: 'Partner Care - Email', + }, + { + id: 'PARTNER_CARE_TEXT_MESSAGE', + name: 'text message partner for cultivation', + value: 'Partner Care - Text Message', + }, + { + id: 'PARTNER_CARE_SOCIAL_MEDIA', + name: 'social media message for cultivation', + value: 'Partner Care - Social Media', + }, + { + id: 'PARTNER_CARE_IN_PERSON', + name: 'connect in person for cultivation', + value: 'Partner Care - In Person', + }, + { + id: 'PARTNER_CARE_THANK', + name: 'send thank you note', + value: 'Partner Care - Thank', + }, + { + id: 'PARTNER_CARE_DIGITAL_NEWSLETTER', + name: 'send digital newsletter', + value: 'Partner Care - Digital Newsletter', + }, + { + id: 'PARTNER_CARE_PHYSICAL_NEWSLETTER', + name: 'send physical newsletter', + value: 'Partner Care - Physical Newsletter', + }, + { + id: 'PARTNER_CARE_PRAYER_REQUEST', + name: 'ask for or receive prayer request', + value: 'Partner Care - Prayer Request', + }, + { + id: 'PARTNER_CARE_UPDATE_INFORMATION', + name: 'update partner information', + value: 'Partner Care - Update Information', + }, + { + id: 'PARTNER_CARE_TO_DO', + name: '', + value: 'Partner Care - To Do', + }, + ], + + phases: [ + { + contactStatuses: [ + 'NEVER_CONTACTED', + 'ASK_IN_FUTURE', + 'RESEARCH_CONTACT_INFO', + 'CULTIVATE_RELATIONSHIP', + ], + id: 'CONNECTION', + name: 'Connection', + results: { + resultOptions: [], + tags: null, + }, + tasks: [], + }, + { + contactStatuses: ['CONTACT_FOR_APPOINTMENT'], + id: 'INITIATION', + name: 'Initiation', + results: { + resultOptions: [ + { + dbResult: [ + { + result: 'ATTEMPTED', + task: 'INITIATION_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_EMAIL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'INITIATION_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'INITIATION_LETTER', + }, + { + result: 'COMPLETED', + task: 'INITIATION_SPECIAL_GIFT_APPEAL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_IN_PERSON', + }, + ], + name: 'INITIATION_RESULT_NO_RESPONSE', + suggestedContactStatus: null, + suggestedNextActions: [ + 'INITIATION_PHONE_CALL', + 'INITIATION_EMAIL', + 'INITIATION_TEXT_MESSAGE', + 'INITIATION_SOCIAL_MEDIA', + 'INITIATION_LETTER', + 'INITIATION_SPECIAL_GIFT_APPEAL', + 'INITIATION_IN_PERSON', + ], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'INITIATION_PHONE_CALL', + }, + { + result: 'RECEIVED', + task: 'INITIATION_EMAIL', + }, + { + result: 'RECEIVED', + task: 'INITIATION_TEXT_MESSAGE', + }, + { + result: 'RECEIVED', + task: 'INITIATION_SOCIAL_MEDIA', + }, + { + result: 'RECEIVED', + task: 'INITIATION_LETTER', + }, + { + result: 'RECEIVED', + task: 'INITIATION_SPECIAL_GIFT_APPEAL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_IN_PERSON', + }, + ], + name: 'INITIATION_RESULT_CIRCLE_BACK', + suggestedContactStatus: null, + suggestedNextActions: [ + 'INITIATION_PHONE_CALL', + 'INITIATION_EMAIL', + 'INITIATION_TEXT_MESSAGE', + 'INITIATION_SOCIAL_MEDIA', + 'INITIATION_LETTER', + 'INITIATION_SPECIAL_GIFT_APPEAL', + 'INITIATION_IN_PERSON', + ], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'INITIATION_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_EMAIL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'INITIATION_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'INITIATION_LETTER', + }, + { + result: 'COMPLETED', + task: 'INITIATION_SPECIAL_GIFT_APPEAL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_IN_PERSON', + }, + ], + name: 'INITIATION_RESULT_APPOINTMENT_SCHEDULED', + suggestedContactStatus: 'APPOINTMENT_SCHEDULED', + suggestedNextActions: [ + 'APPOINTMENT_IN_PERSON', + 'APPOINTMENT_PHONE_CALL', + 'APPOINTMENT_VIDEO_CALL', + ], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'INITIATION_PHONE_CALL', + }, + { + result: 'RECEIVED', + task: 'INITIATION_EMAIL', + }, + { + result: 'RECEIVED', + task: 'INITIATION_TEXT_MESSAGE', + }, + { + result: 'RECEIVED', + task: 'INITIATION_SOCIAL_MEDIA', + }, + { + result: 'RECEIVED', + task: 'INITIATION_LETTER', + }, + { + result: 'RECEIVED', + task: 'INITIATION_SPECIAL_GIFT_APPEAL', + }, + { + result: 'COMPLETED', + task: 'INITIATION_IN_PERSON', + }, + ], + name: 'INITIATION_RESULT_NOT_INTERESTED', + suggestedContactStatus: 'NOT_INTERESTED', + suggestedNextActions: null, + }, + ], + tags: null, + }, + tasks: [ + 'INITIATION_PHONE_CALL', + 'INITIATION_EMAIL', + 'INITIATION_TEXT_MESSAGE', + 'INITIATION_SOCIAL_MEDIA', + 'INITIATION_LETTER', + 'INITIATION_SPECIAL_GIFT_APPEAL', + 'INITIATION_IN_PERSON', + ], + }, + { + contactStatuses: ['APPOINTMENT_SCHEDULED'], + id: 'APPOINTMENT', + name: 'Appointment', + results: { + resultOptions: [ + { + dbResult: [ + { + result: 'ATTEMPTED', + task: 'APPOINTMENT_IN_PERSON', + }, + { + result: 'ATTEMPTED', + task: 'APPOINTMENT_VIDEO_CALL', + }, + { + result: 'ATTEMPTED', + task: 'APPOINTMENT_PHONE_CALL', + }, + ], + name: 'APPOINTMENT_RESULT_CANCELLED', + suggestedContactStatus: 'CONTACT_FOR_APPOINTMENT', + suggestedNextActions: [ + 'INITIATION_PHONE_CALL', + 'INITIATION_EMAIL', + 'INITIATION_TEXT_MESSAGE', + 'INITIATION_SOCIAL_MEDIA', + 'INITIATION_LETTER', + 'INITIATION_SPECIAL_GIFT_APPEAL', + 'INITIATION_IN_PERSON', + ], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'APPOINTMENT_IN_PERSON', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_VIDEO_CALL', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_PHONE_CALL', + }, + ], + name: 'APPOINTMENT_RESULT_FOLLOW_UP', + suggestedContactStatus: 'CALL_FOR_DECISION', + suggestedNextActions: [ + 'FOLLOW_UP_PHONE_CALL', + 'FOLLOW_UP_EMAIL', + 'FOLLOW_UP_TEXT_MESSAGE', + 'FOLLOW_UP_SOCIAL_MEDIA', + 'FOLLOW_UP_IN_PERSON', + ], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'APPOINTMENT_IN_PERSON', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_VIDEO_CALL', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_PHONE_CALL', + }, + ], + name: 'FOLLOW_UP_RESULT_PARTNER_FINANCIAL', + suggestedContactStatus: 'PARTNER_FINANCIAL', + suggestedNextActions: ['PARTNER_CARE_THANK'], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'APPOINTMENT_IN_PERSON', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_VIDEO_CALL', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_PHONE_CALL', + }, + ], + name: 'FOLLOW_UP_RESULT_PARTNER_SPECIAL', + suggestedContactStatus: 'PARTNER_SPECIAL', + suggestedNextActions: ['PARTNER_CARE_THANK'], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'APPOINTMENT_IN_PERSON', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_VIDEO_CALL', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_PHONE_CALL', + }, + ], + name: 'FOLLOW_UP_RESULT_PARTNER_PRAY', + suggestedContactStatus: 'PARTNER_PRAY', + suggestedNextActions: ['PARTNER_CARE_THANK'], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'APPOINTMENT_IN_PERSON', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_VIDEO_CALL', + }, + { + result: 'COMPLETED', + task: 'APPOINTMENT_PHONE_CALL', + }, + ], + name: 'FOLLOW_UP_RESULT_NOT_INTERESTED', + suggestedContactStatus: 'NOT_INTERESTED', + suggestedNextActions: null, + }, + ], + tags: [ + { + id: 'asked for support', + value: 'asked for support', + }, + { + id: 'asked for connections', + value: 'asked for connections', + }, + { + id: 'asked for advocacy', + value: 'asked for advocacy', + }, + { + id: 'asked for increase', + value: 'asked for increase', + }, + ], + }, + tasks: [ + 'APPOINTMENT_IN_PERSON', + 'APPOINTMENT_PHONE_CALL', + 'APPOINTMENT_VIDEO_CALL', + ], + }, + { + contactStatuses: ['CALL_FOR_DECISION'], + id: 'FOLLOW_UP', + name: 'Follow Up', + results: { + resultOptions: [ + { + dbResult: [ + { + result: 'ATTEMPTED', + task: 'FOLLOW_UP_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_EMAIL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_IN_PERSON', + }, + ], + name: 'INITIATION_RESULT_NO_RESPONSE', + suggestedContactStatus: null, + suggestedNextActions: [ + 'FOLLOW_UP_PHONE_CALL', + 'FOLLOW_UP_EMAIL', + 'FOLLOW_UP_TEXT_MESSAGE', + 'FOLLOW_UP_SOCIAL_MEDIA', + 'FOLLOW_UP_IN_PERSON', + ], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'FOLLOW_UP_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_EMAIL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_IN_PERSON', + }, + ], + name: 'FOLLOW_UP_RESULT_PARTNER_FINANCIAL', + suggestedContactStatus: 'PARTNER_FINANCIAL', + suggestedNextActions: ['PARTNER_CARE_THANK'], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'FOLLOW_UP_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_EMAIL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_IN_PERSON', + }, + ], + name: 'FOLLOW_UP_RESULT_PARTNER_SPECIAL', + suggestedContactStatus: 'PARTNER_SPECIAL', + suggestedNextActions: ['PARTNER_CARE_THANK'], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'FOLLOW_UP_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_EMAIL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_IN_PERSON', + }, + ], + name: 'FOLLOW_UP_RESULT_PARTNER_PRAY', + suggestedContactStatus: 'PARTNER_PRAY', + suggestedNextActions: ['PARTNER_CARE_THANK'], + }, + { + dbResult: [ + { + result: 'COMPLETED', + task: 'FOLLOW_UP_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_EMAIL', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'FOLLOW_UP_IN_PERSON', + }, + ], + name: 'FOLLOW_UP_RESULT_NOT_INTERESTED', + suggestedContactStatus: 'NOT_INTERESTED', + suggestedNextActions: null, + }, + ], + tags: [ + { + id: 'Financial Support', + value: 'Financial Support', + }, + { + id: 'Gift not Started', + value: 'Gift not Started', + }, + { + id: 'Special Gift', + value: 'Special Gift', + }, + { + id: 'Connections', + value: 'Connections', + }, + { + id: 'Increase', + value: 'Increase', + }, + ], + }, + tasks: [ + 'FOLLOW_UP_PHONE_CALL', + 'FOLLOW_UP_EMAIL', + 'FOLLOW_UP_TEXT_MESSAGE', + 'FOLLOW_UP_SOCIAL_MEDIA', + 'FOLLOW_UP_IN_PERSON', + ], + }, + { + contactStatuses: [ + 'PARTNER_FINANCIAL', + 'PARTNER_SPECIAL', + 'PARTNER_PRAY', + ], + id: 'PARTNER_CARE', + name: 'Partner Care', + results: { + resultOptions: [ + { + dbResult: [ + { + result: 'COMPLETED', + task: 'PARTNER_CARE_PHONE_CALL', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_EMAIL', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_TEXT_MESSAGE', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_SOCIAL_MEDIA', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_IN_PERSON', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_THANK', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_DIGITAL_NEWSLETTER', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_PHYSICAL_NEWSLETTER', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_PRAYER_REQUEST', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_UPDATE_INFORMATION', + }, + { + result: 'COMPLETED', + task: 'PARTNER_CARE_TO_DO', + }, + ], + name: 'PARTNER_CARE_COMPLETED', + suggestedContactStatus: null, + suggestedNextActions: [ + 'PARTNER_CARE_PHONE_CALL', + 'PARTNER_CARE_EMAIL', + 'PARTNER_CARE_TEXT_MESSAGE', + 'PARTNER_CARE_SOCIAL_MEDIA', + 'PARTNER_CARE_IN_PERSON', + 'PARTNER_CARE_THANK', + 'PARTNER_CARE_DIGITAL_NEWSLETTER', + 'PARTNER_CARE_PHYSICAL_NEWSLETTER', + 'PARTNER_CARE_PRAYER_REQUEST', + 'PARTNER_CARE_UPDATE_INFORMATION', + 'PARTNER_CARE_TO_DO', + ], + }, + ], + tags: null, + }, + tasks: [ + 'PARTNER_CARE_PHONE_CALL', + 'PARTNER_CARE_EMAIL', + 'PARTNER_CARE_TEXT_MESSAGE', + 'PARTNER_CARE_SOCIAL_MEDIA', + 'PARTNER_CARE_IN_PERSON', + 'PARTNER_CARE_THANK', + 'PARTNER_CARE_DIGITAL_NEWSLETTER', + 'PARTNER_CARE_PHYSICAL_NEWSLETTER', + 'PARTNER_CARE_PRAYER_REQUEST', + 'PARTNER_CARE_UPDATE_INFORMATION', + 'PARTNER_CARE_TO_DO', + ], + }, + { + contactStatuses: [ + 'NOT_INTERESTED', + 'UNRESPONSIVE', + 'NEVER_ASK', + 'RESEARCH_ABANDONED', + 'EXPIRED_REFERRAL', + ], + id: 'ARCHIVE', + name: 'Archive', + results: { + resultOptions: [], + tags: null, + }, + tasks: [], + }, + ], + }, +} as unknown as LoadConstantsQuery; + +export default LoadConstantsMock; diff --git a/src/components/Dashboard/DonationHistories/DonationHistories.tsx b/src/components/Dashboard/DonationHistories/DonationHistories.tsx index 581685c8b..d1278c234 100644 --- a/src/components/Dashboard/DonationHistories/DonationHistories.tsx +++ b/src/components/Dashboard/DonationHistories/DonationHistories.tsx @@ -156,7 +156,7 @@ const DonationHistories = ({
- {t('Monthly Activity')} + {t('Monthly Giving')} diff --git a/src/components/Dashboard/ThisWeek/GetThisWeek.graphql b/src/components/Dashboard/ThisWeek/GetThisWeek.graphql index 5d3ed44ea..dc5ee0941 100644 --- a/src/components/Dashboard/ThisWeek/GetThisWeek.graphql +++ b/src/components/Dashboard/ThisWeek/GetThisWeek.graphql @@ -40,7 +40,7 @@ query GetThisWeek( prayerRequestTasks: tasks( accountListId: $accountListId first: 2 - tasksFilter: { activityType: PRAYER_REQUEST, completed: false } + tasksFilter: { activityType: PARTNER_CARE_PRAYER_REQUEST, completed: false } ) { nodes { id diff --git a/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.test.tsx b/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.test.tsx index 4f7fd08d3..0d0c339f8 100644 --- a/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.test.tsx +++ b/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.test.tsx @@ -156,7 +156,7 @@ describe('LogNewsletter', () => { mocks={[ createNewsletterTaskMutationMock( 'task-1', - ActivityTypeEnum.NewsletterPhysical, + ActivityTypeEnum.PartnerCarePhysicalNewsletter, ), ]} > @@ -190,11 +190,11 @@ describe('LogNewsletter', () => { mocks={[ createNewsletterTaskMutationMock( 'task-1', - ActivityTypeEnum.NewsletterPhysical, + ActivityTypeEnum.PartnerCarePhysicalNewsletter, ), createNewsletterTaskMutationMock( 'task-2', - ActivityTypeEnum.NewsletterEmail, + ActivityTypeEnum.PartnerCareDigitalNewsletter, ), ]} > diff --git a/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.tsx b/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.tsx index d86ea07bc..54e30b8af 100644 --- a/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.tsx +++ b/src/components/Dashboard/ThisWeek/NewsletterMenu/MenuItems/LogNewsLetter/LogNewsletter.tsx @@ -101,7 +101,7 @@ const LogNewsletter = ({ const [createTasks, { loading: creating }] = useCreateTasksMutation(); const initialTask = { - activityType: ActivityTypeEnum.NewsletterPhysical, + activityType: ActivityTypeEnum.PartnerCarePhysicalNewsletter, completedAt: null, subject: '', }; @@ -113,8 +113,8 @@ const LogNewsletter = ({ const taskTypes = attributes.activityType === 'BOTH' ? [ - ActivityTypeEnum.NewsletterPhysical, - ActivityTypeEnum.NewsletterEmail, + ActivityTypeEnum.PartnerCarePhysicalNewsletter, + ActivityTypeEnum.PartnerCareDigitalNewsletter, ] : [attributes.activityType]; await Promise.all( @@ -199,12 +199,12 @@ const LogNewsletter = ({ value={activityType} > } label={t('Newsletter - Physical')} /> } label={t('Newsletter - Email')} /> diff --git a/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.stories.tsx b/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.stories.tsx index bed94fc79..c955dc3de 100644 --- a/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.stories.tsx +++ b/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.stories.tsx @@ -17,7 +17,7 @@ export const Default = (): ReactElement => { const task = { id: 'task', subject: 'the quick brown fox jumps over the lazy dog', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [{ name: 'Roger Smith' }, { name: 'Sarah Smith' }] }, startAt: null, completedAt: null, diff --git a/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.test.tsx b/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.test.tsx index 3dc0e690f..f262966f8 100644 --- a/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.test.tsx +++ b/src/components/Dashboard/ThisWeek/PartnerCare/PartnerCare.test.tsx @@ -24,7 +24,7 @@ const prayerRequestTasks: GetThisWeekQuery['prayerRequestTasks'] = { { id: 'task_1', subject: 'the quick brown fox jumps over the lazy dog', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [ { hidden: true, name: 'Roger Smith' }, @@ -37,7 +37,7 @@ const prayerRequestTasks: GetThisWeekQuery['prayerRequestTasks'] = { { id: 'task_2', subject: 'on the boat to see uncle johnny', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [ { hidden: true, name: 'Roger Parker' }, diff --git a/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.stories.tsx b/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.stories.tsx index f23c3322c..697eb864c 100644 --- a/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.stories.tsx +++ b/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.stories.tsx @@ -13,7 +13,7 @@ export const Default = (): ReactElement => { const task: GetThisWeekQuery['dueTasks']['nodes'][0] = { id: 'task', subject: 'the quick brown fox jumps over the lazy dog', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [{ id: '1', name: 'Smith, Roger' }], totalCount: 1 }, startAt: null, completedAt: null, diff --git a/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.test.tsx b/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.test.tsx index ad0980db9..4108c4240 100644 --- a/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.test.tsx +++ b/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.test.tsx @@ -79,7 +79,7 @@ describe('TasksDueThisWeek', () => { { id: 'task_1', subject: 'the quick brown fox jumps over the lazy dog', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [{ hidden: true, name: 'Smith, Roger', id: '1' }], totalCount: 1, @@ -90,7 +90,7 @@ describe('TasksDueThisWeek', () => { { id: 'task_2', subject: 'the quick brown fox jumps over the lazy dog', - activityType: ActivityTypeEnum.Appointment, + activityType: ActivityTypeEnum.AppointmentInPerson, contacts: { nodes: [{ hidden: true, name: 'Smith, Sarah', id: '2' }], totalCount: 1, @@ -151,7 +151,7 @@ describe('TasksDueThisWeek', () => { { id: 'task_1', subject: 'subject_1', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [ { hidden: true, name: 'Smith, Roger', id: '1' }, diff --git a/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.tsx b/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.tsx index 0f6ffd716..55646fce3 100644 --- a/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.tsx +++ b/src/components/Dashboard/ThisWeek/TasksDueThisWeek/TasksDueThisWeek.tsx @@ -27,7 +27,7 @@ import { useLocale } from 'src/hooks/useLocale'; import useTaskModal from 'src/hooks/useTaskModal'; import illustration8 from 'src/images/drawkit/grape/drawkit-grape-pack-illustration-8.svg'; import { numberFormat } from 'src/lib/intlFormat'; -import { constantIdFromActivityType } from 'src/utils/tasks/taskActivity'; +import { getLocalizedTaskType } from 'src/utils/functions/getLocalizedTaskType'; import { GetThisWeekQuery } from '../GetThisWeek.generated'; const useStyles = makeStyles()((theme: Theme) => ({ @@ -89,7 +89,7 @@ const TasksDueThisWeek = ({ const translatedActivityType = (type: ActivityTypeEnum): string => { return ( - activityTypes?.find(({ id }) => id === constantIdFromActivityType(type)) + activityTypes?.find(({ id }) => id === getLocalizedTaskType(t, type)) ?.value ?? '' ); }; diff --git a/src/components/Dashboard/ThisWeek/ThisWeek.mock.ts b/src/components/Dashboard/ThisWeek/ThisWeek.mock.ts index 45c337ab8..c4bd616a7 100644 --- a/src/components/Dashboard/ThisWeek/ThisWeek.mock.ts +++ b/src/components/Dashboard/ThisWeek/ThisWeek.mock.ts @@ -14,7 +14,7 @@ export const GetThisWeekDefaultMocks = (): MockedResponse[] => { const task = { id: 'task', subject: 'the quick brown fox jumps over the lazy dog', - activityType: ActivityTypeEnum.PrayerRequest, + activityType: ActivityTypeEnum.PartnerCarePrayerRequest, contacts: { nodes: [{ name: 'Smith, Roger', id: '1' }], totalCount: 1 }, startAt: DateTime.local(2012, 1, 5, 1, 2).toISODate(), completedAt: null, diff --git a/src/components/Dashboard/ThisWeek/WeeklyActivity/GetWeeklyActivity.graphql b/src/components/Dashboard/ThisWeek/WeeklyActivity/GetWeeklyActivity.graphql index 8cf56b805..fe118e246 100644 --- a/src/components/Dashboard/ThisWeek/WeeklyActivity/GetWeeklyActivity.graphql +++ b/src/components/Dashboard/ThisWeek/WeeklyActivity/GetWeeklyActivity.graphql @@ -3,11 +3,18 @@ query GetWeeklyActivity( $startOfWeek: ISO8601DateTime! $endOfWeek: ISO8601DateTime! ) { - completedCalls: tasks( + completedInitiations: tasks( accountListId: $accountListId tasksFilter: { completedAt: { min: $startOfWeek, max: $endOfWeek } - activityType: CALL + activityType: [ + INITIATION_PHONE_CALL + INITIATION_IN_PERSON + INITIATION_LETTER + INITIATION_SOCIAL_MEDIA + INITIATION_SPECIAL_GIFT_APPEAL + INITIATION_TEXT_MESSAGE + ] result: [COMPLETED, DONE] } ) { @@ -17,18 +24,30 @@ query GetWeeklyActivity( accountListId: $accountListId tasksFilter: { completedAt: { min: $startOfWeek, max: $endOfWeek } - activityType: CALL + activityType: [ + APPOINTMENT_PHONE_CALL + APPOINTMENT_VIDEO_CALL + FOLLOW_UP_PHONE_CALL + INITIATION_PHONE_CALL + PARTNER_CARE_PHONE_CALL + ] result: [COMPLETED, DONE] - nextAction: APPOINTMENT + nextAction: APPOINTMENT_IN_PERSON } ) { totalCount } - completedMessages: tasks( + completedFollowUps: tasks( accountListId: $accountListId tasksFilter: { completedAt: { min: $startOfWeek, max: $endOfWeek } - activityType: [EMAIL, FACEBOOK_MESSAGE, TEXT_MESSAGE] + activityType: [ + FOLLOW_UP_EMAIL + FOLLOW_UP_SOCIAL_MEDIA + FOLLOW_UP_TEXT_MESSAGE + FOLLOW_UP_IN_PERSON + FOLLOW_UP_PHONE_CALL + ] result: [COMPLETED, DONE] } ) { @@ -38,9 +57,17 @@ query GetWeeklyActivity( accountListId: $accountListId tasksFilter: { completedAt: { min: $startOfWeek, max: $endOfWeek } - activityType: [EMAIL, FACEBOOK_MESSAGE, TEXT_MESSAGE] + activityType: [ + FOLLOW_UP_SOCIAL_MEDIA + FOLLOW_UP_TEXT_MESSAGE + INITIATION_SOCIAL_MEDIA + INITIATION_TEXT_MESSAGE + PARTNER_CARE_EMAIL + PARTNER_CARE_SOCIAL_MEDIA + PARTNER_CARE_TEXT_MESSAGE + ] result: [COMPLETED, DONE] - nextAction: APPOINTMENT + nextAction: APPOINTMENT_IN_PERSON } ) { totalCount @@ -49,17 +76,32 @@ query GetWeeklyActivity( accountListId: $accountListId tasksFilter: { completedAt: { min: $startOfWeek, max: $endOfWeek } - activityType: APPOINTMENT + activityType: [ + APPOINTMENT_IN_PERSON + APPOINTMENT_PHONE_CALL + APPOINTMENT_VIDEO_CALL + ] result: [COMPLETED, DONE] } ) { totalCount } - completedCorrespondence: tasks( + completedPartnerCare: tasks( accountListId: $accountListId tasksFilter: { completedAt: { min: $startOfWeek, max: $endOfWeek } - activityType: [PRE_CALL_LETTER, REMINDER_LETTER, SUPPORT_LETTER, THANK] + activityType: [ + PARTNER_CARE_IN_PERSON + PARTNER_CARE_PHONE_CALL + PARTNER_CARE_TEXT_MESSAGE + PARTNER_CARE_TO_DO + PARTNER_CARE_UPDATE_INFORMATION + PARTNER_CARE_SOCIAL_MEDIA + PARTNER_CARE_EMAIL + PARTNER_CARE_DIGITAL_NEWSLETTER + PARTNER_CARE_PHYSICAL_NEWSLETTER + PARTNER_CARE_THANK + ] result: [COMPLETED, DONE] } ) { diff --git a/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.mock.ts b/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.mock.ts index ddb9f4b55..8db687d13 100644 --- a/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.mock.ts +++ b/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.mock.ts @@ -6,20 +6,20 @@ import { } from './GetWeeklyActivity.generated'; const data: GetWeeklyActivityQuery = { - completedCalls: { totalCount: 1234 }, + completedInitiations: { totalCount: 1234 }, callsThatProducedAppointments: { totalCount: 5678 }, - completedMessages: { totalCount: 9012 }, + completedFollowUps: { totalCount: 9012 }, messagesThatProducedAppointments: { totalCount: 3456 }, completedAppointments: { totalCount: 7890 }, - completedCorrespondence: { totalCount: 1234 }, + completedPartnerCare: { totalCount: 1234 }, }; const dataPreviousWeek: GetWeeklyActivityQuery = { - completedCalls: { totalCount: 5678 }, + completedInitiations: { totalCount: 5678 }, callsThatProducedAppointments: { totalCount: 9012 }, - completedMessages: { totalCount: 3456 }, + completedFollowUps: { totalCount: 3456 }, messagesThatProducedAppointments: { totalCount: 7890 }, completedAppointments: { totalCount: 1234 }, - completedCorrespondence: { totalCount: 5678 }, + completedPartnerCare: { totalCount: 5678 }, }; export const GetWeeklyActivityQueryDefaultMocks = (): MockedResponse[] => { @@ -75,12 +75,12 @@ export const GetWeeklyActivityQueryDefaultMocks = (): MockedResponse[] => { }; const emptyData: GetWeeklyActivityQuery = { - completedCalls: { totalCount: 0 }, + completedInitiations: { totalCount: 0 }, callsThatProducedAppointments: { totalCount: 0 }, - completedMessages: { totalCount: 0 }, + completedFollowUps: { totalCount: 0 }, messagesThatProducedAppointments: { totalCount: 0 }, completedAppointments: { totalCount: 0 }, - completedCorrespondence: { totalCount: 0 }, + completedPartnerCare: { totalCount: 0 }, }; export const GetWeeklyActivityQueryEmptyMocks = (): MockedResponse[] => { diff --git a/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.test.tsx b/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.test.tsx index e6b5a683c..4108f6102 100644 --- a/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.test.tsx +++ b/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.test.tsx @@ -36,27 +36,19 @@ describe('WeeklyActivity', () => { ); expect( - getByTestId('WeeklyActivityTableCellCompletedCalls').children[0] + getByTestId('WeeklyActivityTableCellCompletedInitiations').children[0] .className, ).toContain('MuiSkeleton-root'); expect( - getByTestId('WeeklyActivityTableCellCallsThatProducedAppointments') - .children[0].className, - ).toContain('MuiSkeleton-root'); - expect( - getByTestId('WeeklyActivityTableCellCompletedMessages').children[0] + getByTestId('WeeklyActivityTableCellCompletedAppointments').children[0] .className, ).toContain('MuiSkeleton-root'); expect( - getByTestId('WeeklyActivityTableCellMessagesThatProducedAppointments') - .children[0].className, - ).toContain('MuiSkeleton-root'); - expect( - getByTestId('WeeklyActivityTableCellCompletedAppointments').children[0] + getByTestId('WeeklyActivityTableCellCompletedFollowUps').children[0] .className, ).toContain('MuiSkeleton-root'); expect( - getByTestId('WeeklyActivityTableCellCompletedCorrespondence').children[0] + getByTestId('WeeklyActivityTableCellCompletedPartnerCare').children[0] .className, ).toContain('MuiSkeleton-root'); }); @@ -81,24 +73,16 @@ describe('WeeklyActivity', () => { ).not.toBeInTheDocument(), ); expect( - getByTestId('WeeklyActivityTableCellCompletedCalls').textContent, + getByTestId('WeeklyActivityTableCellCompletedInitiations').textContent, ).toEqual('1,234'); - expect( - getByTestId('WeeklyActivityTableCellCallsThatProducedAppointments') - .textContent, - ).toEqual('5,678'); - expect( - getByTestId('WeeklyActivityTableCellCompletedMessages').textContent, - ).toEqual('9,012'); - expect( - getByTestId('WeeklyActivityTableCellMessagesThatProducedAppointments') - .textContent, - ).toEqual('3,456'); expect( getByTestId('WeeklyActivityTableCellCompletedAppointments').textContent, ).toEqual('7,890'); expect( - getByTestId('WeeklyActivityTableCellCompletedCorrespondence').textContent, + getByTestId('WeeklyActivityTableCellCompletedFollowUps').textContent, + ).toEqual('9,012'); + expect( + getByTestId('WeeklyActivityTableCellCompletedPartnerCare').textContent, ).toEqual('1,234'); fireEvent.click(getByTestId('WeeklyActivityIconButtonSubtractWeek')); await waitFor(() => @@ -110,7 +94,7 @@ describe('WeeklyActivity', () => { 'Dec 22 - Dec 28', ); expect( - getByTestId('WeeklyActivityTableCellCompletedCalls').textContent, + getByTestId('WeeklyActivityTableCellCompletedInitiations').textContent, ).toEqual('5,678'); fireEvent.click(getByTestId('WeeklyActivityIconButtonAddWeek')); await waitFor(() => @@ -122,7 +106,7 @@ describe('WeeklyActivity', () => { 'Dec 29 - Jan 4', ); expect( - getByTestId('WeeklyActivityTableCellCompletedCalls').textContent, + getByTestId('WeeklyActivityTableCellCompletedInitiations').textContent, ).toEqual('1,234'); expect( getByRole('link', { hidden: true, name: 'View Activity Detail' }), diff --git a/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.tsx b/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.tsx index 8780953b5..ed3b87a2d 100644 --- a/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.tsx +++ b/src/components/Dashboard/ThisWeek/WeeklyActivity/WeeklyActivity.tsx @@ -5,6 +5,7 @@ import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { Button, CardActions, + CardContent, CardHeader, IconButton, Skeleton, @@ -53,6 +54,20 @@ const useStyles = makeStyles()((theme: Theme) => ({ alignSelf: 'inherit', marginTop: 0, }, + cardContent: { + display: 'flex', + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: theme.spacing(2), + }, + cardContentExpanded: { + padding: theme.spacing(0, 2), + [theme.breakpoints.down('xs')]: { + padding: theme.spacing(2), + }, + }, tableContainer: { flex: 1, }, @@ -143,113 +158,100 @@ const WeeklyActivity = ({ accountListId }: Props): ReactElement => { exit={{ opacity: 0 }} className={classes.div} > - - - - - - {`${intlDateFormat(interval.start)} - ${intlDateFormat( - interval.end, - )}`} - - - {t('Completed')} - - - {t('Appt Produced')} - - - - - - {t('Calls')} - - {loading || !data ? ( - - ) : ( - numberFormat(data.completedCalls.totalCount, locale) - )} - - - {loading || !data ? ( - - ) : ( - numberFormat( - data.callsThatProducedAppointments.totalCount, - locale, - ) - )} - - - - {t('Messages')} - - {loading || !data ? ( - - ) : ( - numberFormat(data.completedMessages.totalCount, locale) - )} - - - {loading || !data ? ( - - ) : ( - numberFormat( - data.messagesThatProducedAppointments.totalCount, - locale, - ) - )} - - - - {t('Appointments')} - - {loading || !data ? ( - - ) : ( - numberFormat(data.completedAppointments.totalCount, locale) - )} - - - - - {t('Correspondence')} - - {loading || !data ? ( - - ) : ( - numberFormat( - data.completedCorrespondence.totalCount, - locale, - ) - )} - - - - -
-
+ + + + + + + {`${intlDateFormat(interval.start)} - ${intlDateFormat( + interval.end, + )}`} + + + {t('Completed')} + + + + + + {t('Initiations')} + + {loading || !data ? ( + + ) : ( + numberFormat(data.completedInitiations.totalCount, locale) + )} + + + + {t('Appointments')} + + {loading || !data ? ( + + ) : ( + numberFormat( + data.completedAppointments.totalCount, + locale, + ) + )} + + + + {t('Follow-Up')} + + {loading || !data ? ( + + ) : ( + numberFormat(data.completedFollowUps.totalCount, locale) + )} + + + + + {t('Partner Care')} + + {loading || !data ? ( + + ) : ( + numberFormat(data.completedPartnerCare.totalCount, locale) + )} + + + + +
+
+
= ({ handleClose }) => { const accountListId = useAccountListId(); const { enqueueSnackbar } = useSnackbar(); const { push } = useRouter(); + const { contactStatuses } = useContactPartnershipStatuses(); //#region Search const [wildcardSearch, setWildcardSearch] = useState(''); @@ -305,7 +306,8 @@ export const SearchDialog: React.FC = ({ handleClose }) => { {option.name} - {option.status && contactPartnershipStatus[option.status]} + {option.status && + contactStatuses[option.status].translated} diff --git a/src/components/Layouts/Primary/TopBar/Items/SearchMenu/SearchMenu.test.tsx b/src/components/Layouts/Primary/TopBar/Items/SearchMenu/SearchMenu.test.tsx index 045c9b462..61184d9c8 100644 --- a/src/components/Layouts/Primary/TopBar/Items/SearchMenu/SearchMenu.test.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/SearchMenu/SearchMenu.test.tsx @@ -5,6 +5,8 @@ import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; +import { LoadConstantsQuery } from 'src/components/Constants/LoadConstants.generated'; +import { loadConstantsMockData } from 'src/components/Constants/LoadConstantsMock'; import { StatusEnum } from 'src/graphql/types.generated'; import i18n from 'src/lib/i18n'; import theme from 'src/theme'; @@ -34,7 +36,11 @@ jest.mock('notistack', () => ({ describe('SearchMenu', () => { it('default', async () => { const { getByRole, getByPlaceholderText } = render( - + + mocks={{ LoadConstants: loadConstantsMockData }} + > @@ -89,6 +95,7 @@ describe('SearchMenu', () => { totalCount: 8, }, }, + LoadConstants: loadConstantsMockData, }} > @@ -140,6 +147,7 @@ describe('SearchMenu', () => { ], }, }, + LoadConstants: loadConstantsMockData, }} > diff --git a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx index 9fdd91096..b55da30b2 100644 --- a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx +++ b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx @@ -3,6 +3,7 @@ import { MockedProvider, MockedResponse } from '@apollo/client/testing'; import { ThemeProvider } from '@mui/material/styles'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { SnackbarProvider } from 'notistack'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import theme from 'src/theme'; import { DesignationAccountsReport } from './DesignationAccountsReport'; @@ -133,16 +134,18 @@ describe('DesignationAccountsReport', () => { it('empty', async () => { const { queryByTestId, getByText } = render( - - mocks={emptyMocks} - > - - + + + mocks={emptyMocks} + > + + + , ); diff --git a/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx b/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx index e20eb9882..92b02c768 100644 --- a/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx +++ b/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { GetExpectedMonthlyTotalsQuery } from 'pages/accountLists/[accountListId]/reports/GetExpectedMonthlyTotals.generated'; @@ -98,20 +99,22 @@ describe('ExpectedMonthlyTotalReport', () => { const { getByText, queryByRole } = render( - - - mocks={mocks} - > - - - + + + + mocks={mocks} + > + + + + , ); diff --git a/src/components/Settings/Accounts/InviteForm/InviteForm.test.tsx b/src/components/Settings/Accounts/InviteForm/InviteForm.test.tsx index 8fe3aac8c..250999a62 100644 --- a/src/components/Settings/Accounts/InviteForm/InviteForm.test.tsx +++ b/src/components/Settings/Accounts/InviteForm/InviteForm.test.tsx @@ -41,14 +41,7 @@ const Components = ({ children }: PropsWithChildren) => ( describe('InviteForm', () => { it('should invite a user', async () => { const mutationSpy = jest.fn(); - const { - getByText, - getByTestId, - getByRole, - queryByText, - findByRole, - queryByTestId, - } = render( + const { getByText, getByRole, queryByText, findByRole } = render( @@ -59,7 +52,8 @@ describe('InviteForm', () => { expect( getByText('Invite someone to share this account'), ).toBeInTheDocument(); - expect(getByTestId('action-button')).toBeDisabled(); + const shareAccountButton = getByRole('button', { name: 'Share Account' }); + expect(shareAccountButton).toBeDisabled(); userEvent.type(getByRole('textbox'), 'test@'); await waitFor(() => { @@ -70,10 +64,10 @@ describe('InviteForm', () => { expect( queryByText(/email must be a valid email/i), ).not.toBeInTheDocument(); - expect(queryByTestId('action-button')).not.toBeDisabled(); + expect(shareAccountButton).not.toBeDisabled(); }); - userEvent.click(getByTestId('action-button')); + userEvent.click(shareAccountButton); await waitFor(() => { expect(getByText('Confirm')).toBeInTheDocument(); expect( @@ -107,21 +101,20 @@ describe('InviteForm', () => { it('should invite a coach', async () => { const mutationSpy = jest.fn(); - const { getByText, getByTestId, getByRole, findByRole, queryByText } = - render( - - - - - , - ); + const { getByText, getByRole, findByRole, queryByText } = render( + + + + + , + ); expect( getByText('Invite someone to share this account'), ).toBeInTheDocument(); userEvent.type(getByRole('textbox'), 'test@test.org'); - userEvent.click(getByTestId('action-button')); + userEvent.click(getByRole('button', { name: 'Share Account' })); await waitFor(() => { expect(getByText('Confirm')).toBeInTheDocument(); diff --git a/src/components/Settings/integrations/Google/Modals/EditGoogleAccountModal.test.tsx b/src/components/Settings/integrations/Google/Modals/EditGoogleAccountModal.test.tsx index 70f77ea24..84e5f24a0 100644 --- a/src/components/Settings/integrations/Google/Modals/EditGoogleAccountModal.test.tsx +++ b/src/components/Settings/integrations/Google/Modals/EditGoogleAccountModal.test.tsx @@ -202,16 +202,16 @@ describe('EditGoogleAccountModal', () => { constant: { activities: [ { - id: 'Call', - value: 'Call', + value: Types.ActivityTypeEnum.AppointmentVideoCall, + id: Types.ActivityTypeEnum.AppointmentVideoCall, }, { - id: 'Appointment', - value: 'Appointment', + value: Types.ActivityTypeEnum.AppointmentInPerson, + id: Types.ActivityTypeEnum.AppointmentInPerson, }, { - id: 'Email', - value: 'Email', + value: Types.ActivityTypeEnum.FollowUpEmail, + id: Types.ActivityTypeEnum.FollowUpEmail, }, ], }, @@ -299,16 +299,16 @@ describe('EditGoogleAccountModal', () => { constant: { activities: [ { - id: 'Call', - value: 'Call', + value: Types.ActivityTypeEnum.AppointmentVideoCall, + id: Types.ActivityTypeEnum.AppointmentVideoCall, }, { - id: 'Appointment', - value: 'Appointment', + value: Types.ActivityTypeEnum.AppointmentInPerson, + id: Types.ActivityTypeEnum.AppointmentInPerson, }, { - id: 'Email', - value: 'Email', + value: Types.ActivityTypeEnum.FollowUpEmail, + id: Types.ActivityTypeEnum.FollowUpEmail, }, ], }, @@ -332,11 +332,13 @@ describe('EditGoogleAccountModal', () => { ); await waitFor(() => - expect(getByTestId('Call-Checkbox')).toBeInTheDocument(), + expect( + getByTestId('APPOINTMENT_VIDEO_CALL-Checkbox'), + ).toBeInTheDocument(), ); await act(async () => { - userEvent.click(getByTestId('Call-Checkbox')); + userEvent.click(getByTestId('APPOINTMENT_VIDEO_CALL-Checkbox')); userEvent.click(getByRole('button', { name: /update/i })); await waitFor(() => { @@ -354,7 +356,7 @@ describe('EditGoogleAccountModal', () => { googleAccountId: googleAccount.id, googleIntegration: { calendarId: 'calendarsID', - calendarIntegrations: ['Appointment', 'Call'], + calendarIntegrations: ['Appointment', 'APPOINTMENT_VIDEO_CALL'], overwrite: true, }, googleIntegrationId: googleIntegration.id, diff --git a/src/components/Tool/FixCommitmentInfo/FixCommitmentInfo.tsx b/src/components/Tool/FixCommitmentInfo/FixCommitmentInfo.tsx index 7a1910f63..c481d300c 100644 --- a/src/components/Tool/FixCommitmentInfo/FixCommitmentInfo.tsx +++ b/src/components/Tool/FixCommitmentInfo/FixCommitmentInfo.tsx @@ -17,8 +17,8 @@ import { PledgeFrequencyEnum, StatusEnum, } from 'src/graphql/types.generated'; +import { useContactPartnershipStatuses } from 'src/hooks/useContactPartnershipStatuses'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { contactPartnershipStatus } from 'src/utils/contacts/contactPartnershipStatus'; import theme from '../../../theme'; import NoData from '../NoData'; import Contact from './Contact'; @@ -88,8 +88,10 @@ const FixCommitmentInfo: React.FC = ({ accountListId }: Props) => { }); const [updateInvalidStatus, { loading: updating }] = useUpdateInvalidStatusMutation(); + const { contactStatuses } = useContactPartnershipStatuses(); - const contactStatuses = contactFilterGroups?.accountList?.contactFilterGroups + const contactFilterStatuses = contactFilterGroups?.accountList + ?.contactFilterGroups ? ( contactFilterGroups.accountList.contactFilterGroups .find((group) => group?.filters[0]?.filterKey === 'status') @@ -201,7 +203,7 @@ const FixCommitmentInfo: React.FC = ({ accountListId }: Props) => { key={contact.name} statusTitle={ contact.status - ? contactPartnershipStatus[contact.status] + ? contactStatuses[contact.status]?.translated : '' } statusValue={contact.status || ''} @@ -215,7 +217,9 @@ const FixCommitmentInfo: React.FC = ({ accountListId }: Props) => { frequencyValue={contact.pledgeFrequency || ''} hideFunction={hideContact} updateFunction={updateContact} - statuses={contactStatuses || [{ name: '', value: '' }]} + statuses={ + contactFilterStatuses || [{ name: '', value: '' }] + } /> ))} diff --git a/src/components/Tool/MergeContacts/Contact.tsx b/src/components/Tool/MergeContacts/Contact.tsx index d758f2b02..dc80f946c 100644 --- a/src/components/Tool/MergeContacts/Contact.tsx +++ b/src/components/Tool/MergeContacts/Contact.tsx @@ -18,9 +18,9 @@ import { import { DateTime } from 'luxon'; import { useTranslation } from 'react-i18next'; import { makeStyles } from 'tss-react/mui'; +import { useContactPartnershipStatuses } from 'src/hooks/useContactPartnershipStatuses'; import { useLocale } from 'src/hooks/useLocale'; import { dateFormatShort } from 'src/lib/intlFormat'; -import { contactPartnershipStatus } from 'src/utils/contacts/contactPartnershipStatus'; import theme from '../../../theme'; import { RecordInfoFragment } from './GetContactDuplicates.generated'; @@ -83,6 +83,7 @@ const Contact: React.FC = ({ contact1, contact2, update }) => { const { t } = useTranslation(); const locale = useLocale(); const { classes } = useStyles(); + const { contactStatuses } = useContactPartnershipStatuses(); //TODO: Add button functionality //TODO: Make contact title a link to contact page @@ -152,7 +153,7 @@ const Contact: React.FC = ({ contact1, contact2, update }) => { {contact1.status && ( {t('Status: {{status}}', { - status: contactPartnershipStatus[contact1.status], + status: contactStatuses[contact1.status]?.translated, })} )} @@ -294,7 +295,7 @@ const Contact: React.FC = ({ contact1, contact2, update }) => { {contact2.status && ( {t('Status: {{status}}', { - status: contactPartnershipStatus[contact2.status], + status: contactStatuses[contact2.status]?.translated, })} )} diff --git a/src/components/common/Modal/Modal.tsx b/src/components/common/Modal/Modal.tsx index bebb26884..c2ed43f62 100644 --- a/src/components/common/Modal/Modal.tsx +++ b/src/components/common/Modal/Modal.tsx @@ -1,27 +1,42 @@ -import React, { ReactElement, ReactNode } from 'react'; +import React, { JSXElementConstructor, ReactElement, ReactNode } from 'react'; import CloseIcon from '@mui/icons-material/Close'; -import { Dialog, DialogProps, DialogTitle, IconButton } from '@mui/material'; +import { + Dialog, + DialogProps, + DialogTitle, + IconButton, + Stack, +} from '@mui/material'; import { styled } from '@mui/material/styles'; +import { TransitionProps } from '@mui/material/transitions'; import { useTranslation } from 'react-i18next'; -const ModalTitle = styled(DialogTitle)(({ theme }) => ({ - textTransform: 'uppercase', - paddingRight: theme.spacing(8), - [theme.breakpoints.up('sm')]: { - paddingLeft: theme.spacing(8), - textAlign: 'center', - }, -})); +const ModalTitle = styled(DialogTitle)<{ altColors: boolean }>( + ({ theme, altColors }) => ({ + textTransform: 'uppercase', + paddingRight: theme.spacing(8), + [theme.breakpoints.up('sm')]: { + paddingLeft: theme.spacing(8), + textAlign: 'center', + }, + backgroundColor: altColors ? theme.palette.cruGrayDark.main : 'white', + color: altColors ? 'white' : 'auto', + }), +); -const CloseButton = styled(IconButton)(({ theme }) => ({ - position: 'absolute', - top: theme.spacing(1), - right: theme.spacing(1), - color: theme.palette.text.primary, - '&:hover': { - backgroundColor: theme.palette.cruGrayLight.main, - }, -})); +const CloseButton = styled(IconButton)<{ altColors: boolean }>( + ({ theme, altColors }) => ({ + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(1), + color: altColors ? 'white' : theme.palette.text.primary, + '&:hover': { + backgroundColor: altColors + ? theme.palette.cruGrayMedium.main + : theme.palette.cruGrayLight.main, + }, + }), +); interface Props { /** determines whether the modal is currently open or not */ @@ -31,11 +46,15 @@ interface Props { /** determines the size of the modal, default is 'sm' */ size?: DialogProps['maxWidth']; /** title to be rendered in modal header */ - title: string; + title: string | ReactElement; /** function to be fired when close button is pressed */ handleClose: () => void; /** content to be rendered inside of modal */ children: ReactNode; + transition?: + | JSXElementConstructor + | undefined; + altColors?: boolean; } const Modal = ({ @@ -45,6 +64,9 @@ const Modal = ({ size = 'sm', fullWidth = true, children, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + transition, + altColors = false, }: Props): ReactElement => { const { t } = useTranslation(); return ( @@ -54,9 +76,23 @@ const Modal = ({ maxWidth={size} disableRestoreFocus={true} onClose={handleClose} + TransitionComponent={transition} > - {title} - + + + {title} + + + {children} diff --git a/src/hooks/useContactPartnershipStatuses.ts b/src/hooks/useContactPartnershipStatuses.ts new file mode 100644 index 000000000..6207ef4c8 --- /dev/null +++ b/src/hooks/useContactPartnershipStatuses.ts @@ -0,0 +1,88 @@ +import { useTranslation } from 'react-i18next'; +import { useApiConstants } from 'src/components/Constants/UseApiConstants'; +import { PhaseEnum } from 'src/graphql/types.generated'; +import { getLocalizedContactStatus } from 'src/utils/functions/getLocalizedContactStatus'; + +export type ContactStatuses = Record< + string, + { + name: string; + translated: string; + phase: PhaseEnum | null; + } +>; + +export type StatusArray = { + name: string; + translated: string; + phase: PhaseEnum | null; + id: string; +}[]; + +export const useContactPartnershipStatuses = () => { + const constants = useApiConstants(); + const { t } = useTranslation(); + const phases = constants?.phases; + const statuses = constants?.statuses; + + const otherStatuses = { + NULL: { + name: 'null', + translated: t('-- None --'), + phase: null, + }, + ACTIVE: { + name: 'active', + translated: t('-- All Active --'), + phase: null, + }, + HIDDEN: { + name: 'hidden', + translated: t('-- All Hidden --'), + phase: null, + }, + }; + + const contactStatuses: ContactStatuses = phases + ? phases?.reduce((acc, phase) => { + phase?.contactStatuses?.map((status) => { + const statusName = statuses?.find(({ id }) => status === id)?.value; + acc[status] = { + name: statusName, + translated: getLocalizedContactStatus(t, status), + phase: phase.id, + }; + }); + return acc; + }, otherStatuses) + : otherStatuses; + + const statusMap: { [statusKey: string]: string } = + contactStatuses && + Object.fromEntries( + Object.entries(contactStatuses) + .filter(([_, status]) => status?.phase) + .map(([statusKey, status]) => [status?.name, statusKey]), + ); + + const statusMapForFilters: { [statusKey: string]: string } = + Object.fromEntries( + Object.entries(contactStatuses).map(([statusKey, status]) => [ + status.name, + statusKey, + ]), + ); + + const statusArray = Object.entries(contactStatuses) + .filter(([_, status]) => status.phase) + .map(([statusKey, s]) => { + return { id: statusKey, ...s }; + }); + + return { + contactStatuses, + statusMap, + statusMapForFilters, + statusArray, + }; +}; diff --git a/src/hooks/usePhaseData.ts b/src/hooks/usePhaseData.ts new file mode 100644 index 000000000..dc7f68c23 --- /dev/null +++ b/src/hooks/usePhaseData.ts @@ -0,0 +1,104 @@ +import { useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { LoadConstantsQuery } from 'src/components/Constants/LoadConstants.generated'; +import { useApiConstants } from 'src/components/Constants/UseApiConstants'; +import { + ActivityTypeEnum, + Phase, + PhaseEnum, +} from 'src/graphql/types.generated'; +import { getLocalizedPhase } from 'src/utils/functions/getLocalizedPhase'; +import { getLocalizedTaskType } from 'src/utils/functions/getLocalizedTaskType'; + +export type SetPhaseId = (activity: PhaseEnum | null) => void; +export type Constants = LoadConstantsQuery['constant'] | undefined; + +type GetPhaseData = { + phaseData: Phase | null; + setPhaseId: SetPhaseId; + constants: LoadConstantsQuery['constant'] | undefined; + taskPhases: PhaseEnum[]; + activityTypes: Map; + activitiesByPhase: Map; +}; + +export type ActivityData = { + name: string; + phaseId: PhaseEnum; + phase: string; + title?: string; +}; + +const phaseFromActivity = ( + activity: PhaseEnum | null, + constants: LoadConstantsQuery['constant'] | undefined, +): Phase | null => { + const phases = constants?.phases; + if (!activity || !phases) { + return null; + } + return ( + phases.find((phase) => phase.id.toLowerCase() === activity.toLowerCase()) ?? + null + ); +}; + +export const usePhaseData = (phaseEnum?: PhaseEnum | null): GetPhaseData => { + const constants = useApiConstants(); + const { t } = useTranslation(); + const [phaseData, setPhaseData] = useState( + phaseFromActivity(phaseEnum ?? null, constants), + ); + + const setPhaseId = useCallback( + (activity) => { + setPhaseData(phaseFromActivity(activity, constants)); + }, + [constants], + ); + + const taskPhases = useMemo(() => { + return ( + constants?.phases + ?.filter((phase) => phase?.tasks?.length) + .map((phase) => phase?.id) || [] + ); + }, [constants]); + + const activityTypes: Map = useMemo(() => { + const activitiesMap = new Map(); + + constants?.phases?.forEach((phase) => { + phase?.tasks?.forEach((task) => { + activitiesMap.set(task, { + name: getLocalizedTaskType(t, task), + phaseId: phase.id, + phase: getLocalizedPhase(t, phase.id), + title: constants?.activities?.find((activity) => activity.id === task) + ?.value, + }); + }); + }); + + return activitiesMap; + }, [constants]); + + const activitiesByPhase: Map = useMemo(() => { + const phasesMap = new Map(); + + constants?.phases?.forEach((phase) => { + phasesMap.set(phase.id, phase.tasks); + }); + + return phasesMap; + }, [constants]); + + return { + phaseData, + setPhaseId, + constants, + taskPhases, + activityTypes, + activitiesByPhase, + }; +}; diff --git a/src/lib/intlFormat.test.ts b/src/lib/intlFormat.test.ts index f204d96a5..dc8d08235 100644 --- a/src/lib/intlFormat.test.ts +++ b/src/lib/intlFormat.test.ts @@ -2,6 +2,7 @@ import { DateTime } from 'luxon'; import { currencyFormat, dateFormat, + dateFormatMonthOnly, dateFormatWithoutYear, dateFromParts, dateTimeFormat, @@ -166,6 +167,17 @@ describe('intlFormat', () => { }); }); + describe('dateFormatMonthOnly', () => { + it('format month', () => { + expect(dateFormatMonthOnly(DateTime.local(2020, 1, 5), 'en-US')).toEqual( + 'Jan', + ); + }); + it('handles null date', () => { + expect(dateFormatMonthOnly(null, 'en-US')).toEqual(''); + }); + }); + describe('dateFromParts', () => { const locale = 'en-US'; it('returns formatted date with year, month and day', () => { diff --git a/src/lib/intlFormat.ts b/src/lib/intlFormat.ts index cb9dafaa0..5ec13683b 100644 --- a/src/lib/intlFormat.ts +++ b/src/lib/intlFormat.ts @@ -78,6 +78,18 @@ export const dateFormatWithoutYear = ( }).format(date.toJSDate()); }; +export const dateFormatMonthOnly = ( + date: DateTime | null, + locale: string, +): string => { + if (date === null) { + return ''; + } + return new Intl.DateTimeFormat(locale, { + month: 'short', + }).format(date.toJSDate()); +}; + export const dateFromParts = ( year: number | null | undefined, month: number | null | undefined, diff --git a/src/utils/functions/getLocalizedContactStatus.ts b/src/utils/functions/getLocalizedContactStatus.ts index 4b883e562..f6b8560c1 100644 --- a/src/utils/functions/getLocalizedContactStatus.ts +++ b/src/utils/functions/getLocalizedContactStatus.ts @@ -1,27 +1,32 @@ import { TFunction } from 'react-i18next'; -import { StatusEnum } from 'src/graphql/types.generated'; +import { + ContactFilterStatusEnum, + StatusEnum, +} from 'src/graphql/types.generated'; export const getLocalizedContactStatus = ( t: TFunction, - contactStatus: StatusEnum | null | undefined, + contactStatus: StatusEnum | ContactFilterStatusEnum | null | undefined, ): string => { switch (contactStatus) { case StatusEnum.AppointmentScheduled: return t('Appointment Scheduled'); case StatusEnum.AskInFuture: - return t('Ask In Future'); + return t('Ask in Future'); + case StatusEnum.ResearchContactInfo: + return t('Research Contact Info'); case StatusEnum.CallForDecision: - return t('Call For Decision'); + return t('Follow Up for Decision'); case StatusEnum.ContactForAppointment: - return t('Contact For Appointment'); + return t('Initiate for Appointment'); case StatusEnum.CultivateRelationship: return t('Cultivate Relationship'); case StatusEnum.ExpiredReferral: - return t('Expired Referral'); + return t('Expired Connection'); case StatusEnum.NeverAsk: return t('Never Ask'); case StatusEnum.NeverContacted: - return t('Never Contacted'); + return t('New Connection'); case StatusEnum.NotInterested: return t('Not Interested'); case StatusEnum.PartnerFinancial: @@ -36,6 +41,6 @@ export const getLocalizedContactStatus = ( return t('Unresponsive'); default: - return ''; + return contactStatus?.toLowerCase() || ''; } }; diff --git a/src/utils/functions/getLocalizedPhase.ts b/src/utils/functions/getLocalizedPhase.ts new file mode 100644 index 000000000..af48f58ec --- /dev/null +++ b/src/utils/functions/getLocalizedPhase.ts @@ -0,0 +1,46 @@ +import { TFunction } from 'react-i18next'; +import { PhaseEnum, StatusEnum } from 'src/graphql/types.generated'; +import { ContactStatuses } from 'src/hooks/useContactPartnershipStatuses'; + +export const getLocalizedPhase = ( + t: TFunction, + phase?: PhaseEnum | null, +): string => { + if (phase === undefined) { + return ''; + } + + switch (phase) { + case PhaseEnum.Appointment: + return t('Appointment'); + + case PhaseEnum.Archive: + return t('Archive'); + + case PhaseEnum.Connection: + return t('Connections'); + + case PhaseEnum.FollowUp: + return t('Follow-Up'); + + case PhaseEnum.Initiation: + return t('Initiation'); + + case PhaseEnum.PartnerCare: + return t('Partner Care'); + + default: + return ''; + } +}; + +export const getContactStatusesByPhase = ( + phase: PhaseEnum | null, + contactStatuses: ContactStatuses, +): { id: StatusEnum; translated: string }[] => { + return Object.entries(contactStatuses) + .filter(([_, status]) => status.phase === phase) + .map(([id, status]) => { + return { id: id as StatusEnum, translated: status.translated }; + }); +}; diff --git a/src/utils/functions/getLocalizedResultStrings.ts b/src/utils/functions/getLocalizedResultStrings.ts index 85c5c89e6..97d7316a0 100644 --- a/src/utils/functions/getLocalizedResultStrings.ts +++ b/src/utils/functions/getLocalizedResultStrings.ts @@ -1,9 +1,9 @@ import { TFunction } from 'react-i18next'; -import { ResultEnum } from 'src/graphql/types.generated'; +import { DisplayResultEnum, ResultEnum } from 'src/graphql/types.generated'; export const getLocalizedResultString = ( t: TFunction, - resultType: ResultEnum | null | undefined, + resultType: ResultEnum | DisplayResultEnum | null | undefined, ): string => { if (!resultType) { return ''; @@ -17,6 +17,7 @@ export const getLocalizedResultString = ( return t('Attempted - Left Message'); case ResultEnum.Completed: + case DisplayResultEnum.PartnerCareCompleted: return t('Completed'); // The Done and None branches should never be hit but are left for completeness @@ -30,5 +31,43 @@ export const getLocalizedResultString = ( case ResultEnum.Received: return t('Received'); + // TODO remove when new ResultEnum is added + case DisplayResultEnum.FollowUpResultNoResponse: + case DisplayResultEnum.InitiationResultNoResponse: + return t('No Response Yet'); + + case DisplayResultEnum.AppointmentResultNotInterested: + return t('Does not want to meet'); + + case DisplayResultEnum.InitiationResultCircleBack: + return t("Can't meet right now - circle back"); + + case DisplayResultEnum.InitiationResultAppointmentScheduled: + return t('Appointment Scheduled'); + + case DisplayResultEnum.AppointmentResultCancelled: + return t('Cancelled-Need to reschedule'); + + case DisplayResultEnum.AppointmentResultFollowUp: + return t('Follow up'); + + case DisplayResultEnum.FollowUpResultPartnerFinancial: + case DisplayResultEnum.AppointmentResultPartnerFinancial: + return t('Partner-Financial'); + + case DisplayResultEnum.FollowUpResultPartnerSpecial: + case DisplayResultEnum.AppointmentResultPartnerSpecial: + return t('Partner-Special'); + + case DisplayResultEnum.FollowUpResultPartnerPray: + case DisplayResultEnum.AppointmentResultPartnerPray: + return t('Partner-Pray'); + + case DisplayResultEnum.FollowUpResultNotInterested: + case DisplayResultEnum.InitiationResultNotInterested: + return t('Not Interested'); + + default: + return ''; } }; diff --git a/src/utils/functions/getLocalizedTaskType.ts b/src/utils/functions/getLocalizedTaskType.ts index e0985c32c..e7b2620b9 100644 --- a/src/utils/functions/getLocalizedTaskType.ts +++ b/src/utils/functions/getLocalizedTaskType.ts @@ -10,52 +10,55 @@ export const getLocalizedTaskType = ( } switch (taskType) { - case ActivityTypeEnum.Appointment: - return t('Appointment'); - - case ActivityTypeEnum.Call: - return t('Call'); - - case ActivityTypeEnum.Email: - return t('Email'); - - case ActivityTypeEnum.FacebookMessage: - return t('Facebook Message'); - - case ActivityTypeEnum.Letter: - return t('Letter'); - - case ActivityTypeEnum.NewsletterEmail: - return t('Newsletter - Email'); - - case ActivityTypeEnum.NewsletterPhysical: - return t('Newsletter - Physical'); - case ActivityTypeEnum.None: - return ''; - - case ActivityTypeEnum.PrayerRequest: - return t('Prayer Request'); - - case ActivityTypeEnum.PreCallLetter: - return t('Pre-Call Letter'); - - case ActivityTypeEnum.ReminderLetter: - return t('Reminder Letter'); - - case ActivityTypeEnum.SupportLetter: - return t('Support Letter'); - - case ActivityTypeEnum.TalkToInPerson: - return t('Talk To In Person'); + return t('None'); + + case ActivityTypeEnum.InitiationInPerson: + case ActivityTypeEnum.FollowUpInPerson: + case ActivityTypeEnum.AppointmentInPerson: + case ActivityTypeEnum.PartnerCareInPerson: + return t('In Person'); + + case ActivityTypeEnum.AppointmentPhoneCall: + case ActivityTypeEnum.InitiationPhoneCall: + case ActivityTypeEnum.FollowUpPhoneCall: + case ActivityTypeEnum.PartnerCarePhoneCall: + return t('Phone Call'); + + case ActivityTypeEnum.AppointmentVideoCall: + return t('Video Call'); + + case ActivityTypeEnum.InitiationEmail: + case ActivityTypeEnum.FollowUpEmail: + case ActivityTypeEnum.PartnerCareEmail: + return t('Email'); - case ActivityTypeEnum.TextMessage: + case ActivityTypeEnum.InitiationTextMessage: + case ActivityTypeEnum.FollowUpTextMessage: + case ActivityTypeEnum.PartnerCareTextMessage: return t('Text Message'); - case ActivityTypeEnum.Thank: - return t('Thank'); + case ActivityTypeEnum.FollowUpSocialMedia: + case ActivityTypeEnum.PartnerCareSocialMedia: + case ActivityTypeEnum.InitiationSocialMedia: + return t('Social Media'); - case ActivityTypeEnum.ToDo: + case ActivityTypeEnum.InitiationLetter: + return t('Letter'); + case ActivityTypeEnum.InitiationSpecialGiftAppeal: + return t('Special Gift Appeal'); + + case ActivityTypeEnum.PartnerCarePhysicalNewsletter: + return t('Physical Newsletter'); + case ActivityTypeEnum.PartnerCareDigitalNewsletter: + return t('Digital Newsletter'); + case ActivityTypeEnum.PartnerCareThank: + return t('Thank You Note'); + case ActivityTypeEnum.PartnerCarePrayerRequest: + return t('Prayer Request'); + case ActivityTypeEnum.PartnerCareUpdateInformation: + return t('Update Information'); + case ActivityTypeEnum.PartnerCareToDo: return t('To Do'); } }; diff --git a/src/utils/phases/getValueFromIdValue.ts b/src/utils/phases/getValueFromIdValue.ts new file mode 100644 index 000000000..c4720d6f3 --- /dev/null +++ b/src/utils/phases/getValueFromIdValue.ts @@ -0,0 +1,5 @@ +import { IdValue } from 'src/graphql/types.generated'; + +export const getValueFromIdValue = (idValue: IdValue) => { + return idValue.value ?? ''; +}; diff --git a/src/utils/phases/taskActivityTypes.ts b/src/utils/phases/taskActivityTypes.ts new file mode 100644 index 000000000..d71422d8d --- /dev/null +++ b/src/utils/phases/taskActivityTypes.ts @@ -0,0 +1,177 @@ +import { ActivityTypeEnum, PhaseEnum } from 'src/graphql/types.generated'; + +export const callActivityTypes = [ + ActivityTypeEnum.AppointmentPhoneCall, + ActivityTypeEnum.AppointmentVideoCall, + ActivityTypeEnum.FollowUpPhoneCall, + ActivityTypeEnum.InitiationPhoneCall, + ActivityTypeEnum.PartnerCarePhoneCall, +]; + +export const letterActivityTypes = [ + ActivityTypeEnum.InitiationLetter, + ActivityTypeEnum.PartnerCareDigitalNewsletter, + ActivityTypeEnum.PartnerCarePhysicalNewsletter, +]; + +export const emailActivityTypes = [ + ActivityTypeEnum.FollowUpEmail, + ActivityTypeEnum.InitiationEmail, + ActivityTypeEnum.PartnerCareEmail, +]; + +export const socialMediaActivityTypes = [ + ActivityTypeEnum.FollowUpSocialMedia, + ActivityTypeEnum.InitiationSocialMedia, + ActivityTypeEnum.PartnerCareSocialMedia, +]; + +export const textActivityTypes = [ + ActivityTypeEnum.FollowUpTextMessage, + ActivityTypeEnum.InitiationTextMessage, + ActivityTypeEnum.PartnerCareTextMessage, +]; + +export const inPersonActivityTypes = [ + ActivityTypeEnum.AppointmentInPerson, + ActivityTypeEnum.FollowUpInPerson, + ActivityTypeEnum.InitiationInPerson, + ActivityTypeEnum.PartnerCareInPerson, +]; + +export const electronicActivityTypes = [ + ActivityTypeEnum.FollowUpEmail, + ActivityTypeEnum.FollowUpSocialMedia, + ActivityTypeEnum.FollowUpTextMessage, + ActivityTypeEnum.InitiationEmail, + ActivityTypeEnum.InitiationSocialMedia, + ActivityTypeEnum.InitiationTextMessage, + ActivityTypeEnum.PartnerCareEmail, + ActivityTypeEnum.PartnerCareSocialMedia, + ActivityTypeEnum.PartnerCareTextMessage, +]; + +// Phase types + +export const appointmentActivityTypes = [ + ActivityTypeEnum.AppointmentInPerson, + ActivityTypeEnum.AppointmentVideoCall, + ActivityTypeEnum.AppointmentPhoneCall, +]; + +export const getActivitiesByPhaseType = ( + phase: PhaseEnum | string, +): ActivityTypeEnum[] => { + switch (phase) { + case PhaseEnum.Appointment: + return [ + ActivityTypeEnum.AppointmentInPerson, + ActivityTypeEnum.AppointmentPhoneCall, + ActivityTypeEnum.AppointmentVideoCall, + ]; + case PhaseEnum.FollowUp: + return [ + ActivityTypeEnum.FollowUpEmail, + ActivityTypeEnum.FollowUpInPerson, + ActivityTypeEnum.FollowUpPhoneCall, + ActivityTypeEnum.FollowUpSocialMedia, + ActivityTypeEnum.FollowUpTextMessage, + ]; + case PhaseEnum.Initiation: + return [ + ActivityTypeEnum.InitiationEmail, + ActivityTypeEnum.InitiationInPerson, + ActivityTypeEnum.InitiationLetter, + ActivityTypeEnum.InitiationPhoneCall, + ActivityTypeEnum.InitiationSocialMedia, + ActivityTypeEnum.InitiationSpecialGiftAppeal, + ActivityTypeEnum.InitiationTextMessage, + ]; + case PhaseEnum.PartnerCare: + return [ + ActivityTypeEnum.PartnerCareDigitalNewsletter, + ActivityTypeEnum.PartnerCareEmail, + ActivityTypeEnum.PartnerCareInPerson, + ActivityTypeEnum.PartnerCarePhoneCall, + ActivityTypeEnum.PartnerCarePhysicalNewsletter, + ActivityTypeEnum.PartnerCarePrayerRequest, + ActivityTypeEnum.PartnerCareSocialMedia, + ActivityTypeEnum.PartnerCareTextMessage, + ActivityTypeEnum.PartnerCareThank, + ActivityTypeEnum.PartnerCareToDo, + ActivityTypeEnum.PartnerCareUpdateInformation, + ]; + default: + return [ + ActivityTypeEnum.AppointmentInPerson, + ActivityTypeEnum.AppointmentPhoneCall, + ActivityTypeEnum.AppointmentVideoCall, + ActivityTypeEnum.FollowUpEmail, + ActivityTypeEnum.FollowUpInPerson, + ActivityTypeEnum.FollowUpPhoneCall, + ActivityTypeEnum.FollowUpSocialMedia, + ActivityTypeEnum.FollowUpTextMessage, + ActivityTypeEnum.InitiationEmail, + ActivityTypeEnum.InitiationInPerson, + ActivityTypeEnum.InitiationLetter, + ActivityTypeEnum.InitiationPhoneCall, + ActivityTypeEnum.InitiationSocialMedia, + ActivityTypeEnum.InitiationSpecialGiftAppeal, + ActivityTypeEnum.InitiationTextMessage, + ActivityTypeEnum.PartnerCareDigitalNewsletter, + ActivityTypeEnum.PartnerCareEmail, + ActivityTypeEnum.PartnerCareInPerson, + ActivityTypeEnum.PartnerCarePhoneCall, + ActivityTypeEnum.PartnerCarePhysicalNewsletter, + ActivityTypeEnum.PartnerCarePrayerRequest, + ActivityTypeEnum.PartnerCareSocialMedia, + ActivityTypeEnum.PartnerCareTextMessage, + ActivityTypeEnum.PartnerCareThank, + ActivityTypeEnum.PartnerCareToDo, + ActivityTypeEnum.PartnerCareUpdateInformation, + ]; + } +}; + +export const getPhaseByActivityType = ( + activity?: ActivityTypeEnum | null, +): PhaseEnum | null => { + switch (activity) { + case ActivityTypeEnum.AppointmentInPerson: + case ActivityTypeEnum.AppointmentPhoneCall: + case ActivityTypeEnum.AppointmentVideoCall: + return PhaseEnum.Appointment; + + case ActivityTypeEnum.FollowUpEmail: + case ActivityTypeEnum.FollowUpInPerson: + case ActivityTypeEnum.FollowUpPhoneCall: + case ActivityTypeEnum.FollowUpSocialMedia: + case ActivityTypeEnum.FollowUpTextMessage: + return PhaseEnum.FollowUp; + + case ActivityTypeEnum.InitiationEmail: + case ActivityTypeEnum.InitiationInPerson: + case ActivityTypeEnum.InitiationLetter: + case ActivityTypeEnum.InitiationPhoneCall: + case ActivityTypeEnum.InitiationSocialMedia: + case ActivityTypeEnum.InitiationSpecialGiftAppeal: + case ActivityTypeEnum.InitiationTextMessage: + return PhaseEnum.Initiation; + + case ActivityTypeEnum.PartnerCareDigitalNewsletter: + case ActivityTypeEnum.PartnerCareEmail: + case ActivityTypeEnum.PartnerCareInPerson: + case ActivityTypeEnum.PartnerCarePhoneCall: + case ActivityTypeEnum.PartnerCarePhysicalNewsletter: + case ActivityTypeEnum.PartnerCarePrayerRequest: + case ActivityTypeEnum.PartnerCareSocialMedia: + case ActivityTypeEnum.PartnerCareTextMessage: + case ActivityTypeEnum.PartnerCareThank: + case ActivityTypeEnum.PartnerCareToDo: + case ActivityTypeEnum.PartnerCareUpdateInformation: + return PhaseEnum.PartnerCare; + + default: + return null; + } +};