diff --git a/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.graphql b/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.graphql
new file mode 100644
index 000000000..da49c7779
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.graphql
@@ -0,0 +1,16 @@
+query ActivitySummary($accountListId: ID!, $range: String!) {
+ reportsActivityResults(accountListId: $accountListId, range: $range) {
+ periods {
+ callsWithAppointmentNext
+ completedCall
+ completedPreCallLetter
+ completedReminderLetter
+ completedSupportLetter
+ completedThank
+ dials
+ electronicMessageSent
+ electronicMessageWithAppointmentNext
+ startDate
+ }
+ }
+}
diff --git a/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.test.tsx b/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.test.tsx
new file mode 100644
index 000000000..f06afd6c3
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.test.tsx
@@ -0,0 +1,186 @@
+import { render, waitFor } from '@testing-library/react';
+import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import { CoachingPeriodEnum } from '../CoachingDetail';
+import { ActivitySummary } from './ActivitySummary';
+
+const mocks = {
+ ActivitySummary: {
+ reportsActivityResults: {
+ periods: [
+ {
+ startDate: '2023-09-01',
+ callsWithAppointmentNext: 1,
+ completedCall: 2,
+ completedPreCallLetter: 3,
+ completedReminderLetter: 4,
+ completedSupportLetter: 5,
+ completedThank: 6,
+ dials: 77,
+ electronicMessageSent: 8,
+ electronicMessageWithAppointmentNext: 9,
+ },
+ {
+ startDate: '2023-10-01',
+ callsWithAppointmentNext: 11,
+ completedCall: 12,
+ completedPreCallLetter: 13,
+ completedReminderLetter: 14,
+ completedSupportLetter: 15,
+ completedThank: 16,
+ dials: 97,
+ electronicMessageSent: 18,
+ electronicMessageWithAppointmentNext: 19,
+ },
+ {
+ startDate: '2023-11-01',
+ callsWithAppointmentNext: 31,
+ completedCall: 32,
+ completedPreCallLetter: 33,
+ completedReminderLetter: 34,
+ completedSupportLetter: 35,
+ completedThank: 36,
+ dials: 107,
+ electronicMessageSent: 38,
+ electronicMessageWithAppointmentNext: 39,
+ },
+ ],
+ },
+ },
+};
+
+const mutationSpy = jest.fn();
+
+describe('ActivitySummary', () => {
+ it('renders the table data', async () => {
+ const { findByRole, getAllByRole } = render(
+
+
+ ,
+ );
+
+ expect(
+ await findByRole('cell', { name: 'Phone Dials' }),
+ ).toBeInTheDocument();
+
+ const headers = getAllByRole('rowheader');
+ const phoneRow = headers[0];
+ expect(phoneRow.children[0]).toHaveTextContent('Phone Dials');
+ expect(phoneRow.children[1]).toHaveTextContent('Sep 1');
+ expect(phoneRow.children[2]).toHaveTextContent('Oct 1');
+ expect(phoneRow.children[3]).toHaveTextContent('Nov 1');
+ expect(phoneRow.children[4]).toHaveTextContent('Average');
+ expect(headers[1]).toHaveTextContent('Electronic Messages');
+ expect(headers[2]).toHaveTextContent('Correspondence');
+
+ const rows = getAllByRole('row');
+
+ const dialsRow = rows[0];
+ expect(dialsRow.children[0]).toHaveTextContent('Dials (Weekly Goal: 100)');
+ expect(dialsRow.children[1]).toHaveTextContent('77');
+ expect(dialsRow.children[1].firstChild).toHaveStyle(
+ 'background-color: #A94442',
+ );
+ expect(dialsRow.children[2]).toHaveTextContent('97');
+ expect(dialsRow.children[2].firstChild).toHaveStyle(
+ 'background-color: #8A6D3B',
+ );
+ expect(dialsRow.children[3]).toHaveTextContent('107');
+ expect(dialsRow.children[3].firstChild).toHaveStyle(
+ 'background-color: #5CB85C',
+ );
+ expect(dialsRow.children[4]).toHaveTextContent('94');
+ expect(dialsRow.children[4].firstChild).toHaveStyle(
+ 'background-color: #8A6D3B',
+ );
+
+ const completedRow = rows[1];
+ expect(completedRow.children[0]).toHaveTextContent('Completed');
+ expect(completedRow.children[1]).toHaveTextContent('2');
+ expect(completedRow.children[2]).toHaveTextContent('12');
+ expect(completedRow.children[3]).toHaveTextContent('32');
+ expect(completedRow.children[4]).toHaveTextContent('15');
+
+ const phoneAppointmentsRow = rows[2];
+ expect(phoneAppointmentsRow.children[0]).toHaveTextContent(
+ 'Resulting Appointments',
+ );
+ expect(phoneAppointmentsRow.children[1]).toHaveTextContent('1');
+ expect(phoneAppointmentsRow.children[2]).toHaveTextContent('11');
+ expect(phoneAppointmentsRow.children[3]).toHaveTextContent('31');
+ expect(phoneAppointmentsRow.children[4]).toHaveTextContent('14');
+
+ const electronicRow = rows[4];
+ expect(electronicRow.children[0]).toHaveTextContent('Sent');
+ expect(electronicRow.children[1]).toHaveTextContent('8');
+ expect(electronicRow.children[2]).toHaveTextContent('18');
+ expect(electronicRow.children[3]).toHaveTextContent('38');
+ expect(electronicRow.children[4]).toHaveTextContent('21');
+
+ const electronicAppointmentsRow = rows[5];
+ expect(electronicAppointmentsRow.children[0]).toHaveTextContent(
+ 'Resulting Appointments',
+ );
+ expect(electronicAppointmentsRow.children[1]).toHaveTextContent('9');
+ expect(electronicAppointmentsRow.children[2]).toHaveTextContent('19');
+ expect(electronicAppointmentsRow.children[3]).toHaveTextContent('39');
+ expect(electronicAppointmentsRow.children[4]).toHaveTextContent('22');
+
+ const preCallRow = rows[7];
+ expect(preCallRow.children[0]).toHaveTextContent('Pre-Call Letters');
+ expect(preCallRow.children[1]).toHaveTextContent('3');
+ expect(preCallRow.children[2]).toHaveTextContent('13');
+ expect(preCallRow.children[3]).toHaveTextContent('33');
+ expect(preCallRow.children[4]).toHaveTextContent('16');
+
+ const supportRow = rows[8];
+ expect(supportRow.children[0]).toHaveTextContent('Support Letters');
+ expect(supportRow.children[1]).toHaveTextContent('5');
+ expect(supportRow.children[2]).toHaveTextContent('15');
+ expect(supportRow.children[3]).toHaveTextContent('35');
+ expect(supportRow.children[4]).toHaveTextContent('18');
+
+ const thankYouRow = rows[9];
+ expect(thankYouRow.children[0]).toHaveTextContent('Thank Yous');
+ expect(thankYouRow.children[1]).toHaveTextContent('6');
+ expect(thankYouRow.children[2]).toHaveTextContent('16');
+ expect(thankYouRow.children[3]).toHaveTextContent('36');
+ expect(thankYouRow.children[4]).toHaveTextContent('19');
+ });
+
+ it('loads data for the weekly period', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(mutationSpy.mock.calls[0][0].operation.variables).toMatchObject({
+ range: '4w',
+ }),
+ );
+ });
+
+ it('loads data for the monthly period', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(mutationSpy.mock.calls[0][0].operation.variables).toMatchObject({
+ range: '4m',
+ }),
+ );
+ });
+});
diff --git a/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.tsx b/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.tsx
new file mode 100644
index 000000000..499df8178
--- /dev/null
+++ b/src/components/Coaching/CoachingDetail/ActivitySummary/ActivitySummary.tsx
@@ -0,0 +1,258 @@
+import { useMemo } from 'react';
+import {
+ Box,
+ CardContent,
+ CardHeader,
+ Divider,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableRow,
+} from '@mui/material';
+import { styled } from '@mui/material/styles';
+import { DateTime } from 'luxon';
+import { useTranslation } from 'react-i18next';
+import { useLocale } from 'src/hooks/useLocale';
+import { dateFormatWithoutYear } from 'src/lib/intlFormat';
+import AnimatedCard from 'src/components/AnimatedCard';
+import { MultilineSkeleton } from '../../../Shared/MultilineSkeleton';
+import { CoachingPeriodEnum } from '../CoachingDetail';
+import { getResultColor } from '../helpers';
+import { HeaderRow, AlignedTableCell, DividerRow } from '../StyledComponents';
+import { HelpButton } from '../HelpButton';
+import { useActivitySummaryQuery } from './ActivitySummary.generated';
+
+const DialsLabel = styled('span')(({ theme }) => ({
+ padding: `${theme.spacing(0.375)} ${theme.spacing(0.75)}`,
+ borderRadius: theme.spacing(0.5),
+ color: theme.palette.primary.contrastText,
+ fontSize: '80%',
+ fontWeight: 'bold',
+}));
+
+interface DialCountProps {
+ dials: number;
+ goal: number;
+}
+
+const DialCount: React.FC = ({ dials, goal }) => (
+
+ {dials}
+
+);
+
+const ContentContainer = styled(CardContent)(({ theme }) => ({
+ padding: theme.spacing(2),
+ overflowX: 'scroll',
+}));
+
+interface ActivitySummaryProps {
+ accountListId: string;
+ period: CoachingPeriodEnum;
+}
+
+export const ActivitySummary: React.FC = ({
+ accountListId,
+ period,
+}) => {
+ const { t } = useTranslation();
+ const locale = useLocale();
+
+ const { data, loading } = useActivitySummaryQuery({
+ variables: {
+ accountListId,
+ range: period === CoachingPeriodEnum.Weekly ? '4w' : '4m',
+ },
+ });
+ const periods = data?.reportsActivityResults.periods ?? [];
+
+ const averages = useMemo(
+ () =>
+ [
+ 'callsWithAppointmentNext',
+ 'completedCall',
+ 'completedPreCallLetter',
+ 'completedReminderLetter',
+ 'completedSupportLetter',
+ 'completedThank',
+ 'dials',
+ 'electronicMessageSent',
+ 'electronicMessageWithAppointmentNext',
+ ].reduce>(
+ (averages, field) => ({
+ ...averages,
+ [field]: periods.reduce(
+ (total, period) => total + period[field] / periods.length,
+ 0,
+ ),
+ }),
+ {},
+ ),
+ [data],
+ );
+
+ const dialGoal = period === CoachingPeriodEnum.Weekly ? 100 : 400;
+
+ return (
+
+
+ {t('Activity Summary')}
+
+
+ }
+ />
+
+ {loading ? (
+
+ ) : (
+
+
+
+
+ {t('Phone Dials')}
+ {periods.map(({ startDate }) => (
+
+ {dateFormatWithoutYear(
+ DateTime.fromISO(startDate),
+ locale,
+ )}
+
+ ))}
+ {t('Average')}
+
+
+
+ {t('Dials ({{goalText}}: {{goal}})', {
+ goalText:
+ period === CoachingPeriodEnum.Weekly
+ ? t('Weekly Goal')
+ : t('Monthly Goal'),
+ goal: dialGoal,
+ })}
+
+ {periods.map(({ startDate, dials }) => (
+
+
+
+ ))}
+
+
+
+
+
+ {t('Completed')}
+ {periods.map(({ startDate, completedCall }) => (
+
+ {completedCall}
+
+ ))}
+
+ {Math.round(averages.completedCall)}
+
+
+
+
+ {t('Resulting Appointments')}
+
+ {periods.map(({ startDate, callsWithAppointmentNext }) => (
+
+ {callsWithAppointmentNext}
+
+ ))}
+
+ {Math.round(averages.callsWithAppointmentNext)}
+
+
+
+
+
+
+
+
+
+ {t('Electronic Messages')}
+
+
+
+ {t('Sent')}
+ {periods.map(({ startDate, electronicMessageSent }) => (
+
+ {electronicMessageSent}
+
+ ))}
+
+ {Math.round(averages.electronicMessageSent)}
+
+
+
+
+ {t('Resulting Appointments')}
+
+ {periods.map(
+ ({ startDate, electronicMessageWithAppointmentNext }) => (
+
+ {electronicMessageWithAppointmentNext}
+
+ ),
+ )}
+
+ {Math.round(averages.electronicMessageWithAppointmentNext)}
+
+
+
+
+
+
+
+
+
+ {t('Correspondence')}
+
+
+
+ {t('Pre-Call Letters')}
+ {periods.map(({ startDate, completedPreCallLetter }) => (
+
+ {completedPreCallLetter}
+
+ ))}
+
+ {Math.round(averages.completedPreCallLetter)}
+
+
+
+ {t('Support Letters')}
+ {periods.map(({ startDate, completedSupportLetter }) => (
+
+ {completedSupportLetter}
+
+ ))}
+
+ {Math.round(averages.completedSupportLetter)}
+
+
+
+ {t('Thank Yous')}
+ {periods.map(({ startDate, completedThank }) => (
+
+ {completedThank}
+
+ ))}
+
+ {Math.round(averages.completedThank)}
+
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/src/components/Coaching/CoachingDetail/CoachingDetail.tsx b/src/components/Coaching/CoachingDetail/CoachingDetail.tsx
index a78c39311..404816cd3 100644
--- a/src/components/Coaching/CoachingDetail/CoachingDetail.tsx
+++ b/src/components/Coaching/CoachingDetail/CoachingDetail.tsx
@@ -25,6 +25,7 @@ import { SideContainerText } from './StyledComponents';
import { CollapsibleEmailList } from './CollapsibleEmailList';
import { CollapsiblePhoneList } from './CollapsiblePhoneList';
import { getLastNewsletter } from './helpers';
+import { ActivitySummary } from './ActivitySummary/ActivitySummary';
export enum CoachingPeriodEnum {
Weekly = 'Weekly',
@@ -64,6 +65,7 @@ const CoachingSideTitleContainer = styled(Box)(({ theme }) => ({
const CoachingMainContainer = styled(Box)(({ theme }) => ({
padding: theme.spacing(1),
+ paddingBottom: theme.spacing(6), // prevent the HelpScout beacon from obscuring content at the bottom
width: 'calc(100vw - 20rem)',
}));
@@ -335,6 +337,7 @@ export const CoachingDetail: React.FC = ({
currency={accountListData?.currency}
period={period}
/>
+
>
)}