From 67ec7880d670dcc246300ec4597ff86688b97e94 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 16 Oct 2024 08:13:13 -0700 Subject: [PATCH 01/11] Add hover to check box and show Selected Count --- .../ContactTasksTab/ContactTasksTab.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx index fa35fe685..6315bae06 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import Add from '@mui/icons-material/Add'; import CheckCircleOutline from '@mui/icons-material/CheckCircleOutline'; -import { Box, Button, Checkbox, Divider, Typography } from '@mui/material'; +import { Box, Button, Divider, Hidden, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { DateTime } from 'luxon'; import { useTranslation } from 'react-i18next'; @@ -18,6 +18,7 @@ import { TaskFilterSetInput } from 'src/graphql/types.generated'; import { useGetTaskIdsForMassSelectionQuery } from 'src/hooks/GetIdsForMassSelection.generated'; import { useMassSelection } from 'src/hooks/useMassSelection'; import useTaskModal from 'src/hooks/useTaskModal'; +import { StyledCheckbox } from '../../ContactRow/ContactRow'; import { ContactTaskRow } from './ContactTaskRow/ContactTaskRow'; import { useContactPhaseQuery, @@ -220,7 +221,7 @@ export const ContactTasksTab: React.FC = ({ - = ({ placeholder={t('Search Tasks')} page={PageEnum.Task} /> + {!!ids.length && ( + + + {t('{{count}} Selected', { count: ids.length })} + + + )} From b1fe485d74063f8c2b4e6b0f120e5b2fc662be12 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 16 Oct 2024 08:14:05 -0700 Subject: [PATCH 02/11] Make spacing smaller around date and delete icon --- .../ContactTasksTab/ContactTaskRow/TaskDate/TaskDate.tsx | 4 ++-- .../DeleteTaskIconButton/DeleteTaskIconButton.tsx | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskDate/TaskDate.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskDate/TaskDate.tsx index 2eb79a3ac..836e5aead 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskDate/TaskDate.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskDate/TaskDate.tsx @@ -10,7 +10,7 @@ const TaskRowWrap = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', - margin: theme.spacing(1), + margin: theme.spacing(1, 0.5), })); const TaskCommentIcon = styled(CalendarToday, { @@ -32,7 +32,7 @@ const DateText = styled(Typography, { : isComplete ? theme.palette.text.secondary : theme.palette.text.primary, - marginLeft: theme.spacing(1), + marginLeft: small ? theme.spacing(0.5) : theme.spacing(1), }), ); diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/DeleteTaskIconButton/DeleteTaskIconButton.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/DeleteTaskIconButton/DeleteTaskIconButton.tsx index d901d53e7..1b877de3b 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/DeleteTaskIconButton/DeleteTaskIconButton.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/DeleteTaskIconButton/DeleteTaskIconButton.tsx @@ -5,8 +5,10 @@ import { useTranslation } from 'react-i18next'; import { DeletedItemIcon } from '../../../../common/DeleteItemIcon/DeleteItemIcon'; import { DeleteConfirmation } from '../../../../common/Modal/DeleteConfirmation/DeleteConfirmation'; -const DeleteButton = styled(IconButton)(({ theme }) => ({ - margin: theme.spacing(1), +const DeleteButton = styled(IconButton, { + shouldForwardProp: (prop) => prop !== 'small', +})<{ small?: boolean }>(({ small, theme }) => ({ + margin: small ? theme.spacing(0.5) : theme.spacing(1), })); interface DeleteTaskIconButtonProps { @@ -14,6 +16,7 @@ interface DeleteTaskIconButtonProps { taskId: string; onDeleteConfirm?: () => void; removeSelectedIds?: (id: string[]) => void; + small?: boolean; } export const DeleteTaskIconButton: React.FC = ({ @@ -21,6 +24,7 @@ export const DeleteTaskIconButton: React.FC = ({ taskId, onDeleteConfirm, removeSelectedIds, + small = false, }) => { const { t } = useTranslation(); @@ -31,6 +35,7 @@ export const DeleteTaskIconButton: React.FC = ({ setRemoveDialogOpen(true)} data-testid={`DeleteIconButton-${taskId}`} + small={small} > From e4e40f4a43eec073d1270a73e8ebeb195013fab1 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 16 Oct 2024 18:17:13 -0700 Subject: [PATCH 03/11] When editing tasks, stop autofilling assignee --- src/components/Task/Modal/Form/TaskModalForm.test.tsx | 2 +- src/components/Task/Modal/Form/TaskModalForm.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Task/Modal/Form/TaskModalForm.test.tsx b/src/components/Task/Modal/Form/TaskModalForm.test.tsx index 51e205be6..3154b82a0 100644 --- a/src/components/Task/Modal/Form/TaskModalForm.test.tsx +++ b/src/components/Task/Modal/Form/TaskModalForm.test.tsx @@ -375,7 +375,7 @@ describe('TaskModalForm', () => { diff --git a/src/components/Task/Modal/Form/TaskModalForm.tsx b/src/components/Task/Modal/Form/TaskModalForm.tsx index 5e5fd4f1b..e5a1f71a7 100644 --- a/src/components/Task/Modal/Form/TaskModalForm.tsx +++ b/src/components/Task/Modal/Form/TaskModalForm.tsx @@ -241,7 +241,7 @@ const TaskModalForm = ({ nextAction: task.nextAction ?? null, tagList: additionalTags ?? [], contactIds: task.contacts.nodes.map(({ id }) => id), - userId: task.user?.id ?? session.data?.user.userID ?? null, + userId: task.user?.id ?? null, notificationTimeBefore: task.notificationTimeBefore, notificationType: task.notificationType, notificationTimeUnit: task.notificationTimeUnit, From ab6f9de3424a1fdc10ab08a7d8d027915ede433b Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 16 Oct 2024 18:42:13 -0700 Subject: [PATCH 04/11] Add assignee, tags, responsive layout --- .../tasks/[[...contactId]].page.tsx | 2 +- .../Task/TaskRow/TaskActionPhase.tsx | 64 +++++++ .../Task/TaskRow/TaskRow.stories.tsx | 5 + src/components/Task/TaskRow/TaskRow.test.tsx | 34 ++-- src/components/Task/TaskRow/TaskRow.tsx | 164 ++++++++++-------- 5 files changed, 180 insertions(+), 89 deletions(-) create mode 100644 src/components/Task/TaskRow/TaskActionPhase.tsx diff --git a/pages/accountLists/[accountListId]/tasks/[[...contactId]].page.tsx b/pages/accountLists/[accountListId]/tasks/[[...contactId]].page.tsx index 18565adc9..9f36be5cb 100644 --- a/pages/accountLists/[accountListId]/tasks/[[...contactId]].page.tsx +++ b/pages/accountLists/[accountListId]/tasks/[[...contactId]].page.tsx @@ -406,8 +406,8 @@ const TasksPage: React.FC = () => { onTaskCheckToggle={toggleSelectionById} isChecked={isRowChecked(task.id)} useTopMargin={index === 0} - contactDetailsOpen={contactDetailsOpen} removeSelectedIds={deselectMultipleIds} + filterPanelOpen={filterPanelOpen} /> )} diff --git a/src/components/Task/TaskRow/TaskActionPhase.tsx b/src/components/Task/TaskRow/TaskActionPhase.tsx new file mode 100644 index 000000000..a6f07a27d --- /dev/null +++ b/src/components/Task/TaskRow/TaskActionPhase.tsx @@ -0,0 +1,64 @@ +import { Box, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; +import { ActivityTypeEnum } from 'src/graphql/types.generated'; +import { ActivityData } from 'src/hooks/usePhaseData'; +import { getLocalizedTaskType } from 'src/utils/functions/getLocalizedTaskType'; + +export const SubjectWrapOuter = styled(Box)(({ theme }) => ({ + width: 'fit-content', + alignItems: 'center', + marginRight: theme.spacing(1), +})); + +export const SubjectWrapInner = styled(Box)(({}) => ({ + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + '&:hover': { + textDecoration: 'underline', + }, +})); + +const TaskPhase = styled(Typography)(() => ({ + fontSize: '14px', + letterSpacing: '0.25', + whiteSpace: 'nowrap', + fontWeight: 700, +})); + +const TaskType = styled(Typography)(({ theme }) => ({ + fontSize: '14px', + letterSpacing: '0.25', + whiteSpace: 'nowrap', + fontWeight: 400, + marginRight: theme.spacing(0.5), +})); + +interface TaskActionPhaseProps { + activityData: ActivityData | null | undefined; + activityType: ActivityTypeEnum | undefined | null; +} + +export const TaskActionPhase: React.FC = ({ + activityData, + activityType, +}) => { + const { t } = useTranslation(); + return ( + + + {activityData?.phase ? activityData.phase : ''} + + {activityType ? getLocalizedTaskType(t, activityType) : ''} + + + + ); +}; diff --git a/src/components/Task/TaskRow/TaskRow.stories.tsx b/src/components/Task/TaskRow/TaskRow.stories.tsx index d427d9739..58ff37b4e 100644 --- a/src/components/Task/TaskRow/TaskRow.stories.tsx +++ b/src/components/Task/TaskRow/TaskRow.stories.tsx @@ -31,6 +31,7 @@ export const Default = (): ReactElement => { isChecked={false} onContactSelected={onContactSelected} onTaskCheckToggle={onTaskCheckSelected} + filterPanelOpen={false} /> ); }; @@ -49,6 +50,7 @@ export const Starred = (): ReactElement => { isChecked={false} onContactSelected={onContactSelected} onTaskCheckToggle={onTaskCheckSelected} + filterPanelOpen={false} /> ); }; @@ -63,6 +65,7 @@ export const Checked = (): ReactElement => { isChecked={true} onContactSelected={onContactSelected} onTaskCheckToggle={onTaskCheckSelected} + filterPanelOpen={false} /> ); }; @@ -82,6 +85,7 @@ export const Complete = (): ReactElement => { isChecked={false} onContactSelected={onContactSelected} onTaskCheckToggle={onTaskCheckSelected} + filterPanelOpen={false} /> ); }; @@ -101,6 +105,7 @@ export const Late = (): ReactElement => { isChecked={false} onContactSelected={onContactSelected} onTaskCheckToggle={onTaskCheckSelected} + filterPanelOpen={false} /> ); }; diff --git a/src/components/Task/TaskRow/TaskRow.test.tsx b/src/components/Task/TaskRow/TaskRow.test.tsx index 77bcec096..8e60bcaaf 100644 --- a/src/components/Task/TaskRow/TaskRow.test.tsx +++ b/src/components/Task/TaskRow/TaskRow.test.tsx @@ -61,6 +61,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , @@ -89,6 +90,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , @@ -119,6 +121,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , @@ -156,6 +159,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , @@ -166,7 +170,7 @@ describe('TaskRow', () => { expect(await findByText(task.contacts.nodes[0].name)).toBeVisible(); expect( - await findByText(`${assignee.firstName} ${assignee.lastName}`), + await findByText(`${assignee.firstName[0]}${assignee.lastName[0]}`), ).toBeVisible(); }); @@ -179,7 +183,7 @@ describe('TaskRow', () => { }, }); - const { getByText, getByRole } = render( + const { getAllByText, getByRole } = render( { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , ); - expect(getByText(task.subject)).toBeVisible(); + expect(getAllByText(task.subject).length).toBe(2); userEvent.click(getByRole('checkbox', { hidden: true })); expect(onTaskCheckSelected).toHaveBeenCalledWith(task.id); }); @@ -205,7 +210,7 @@ describe('TaskRow', () => { }, }); - const { findByText, getByTestId } = render( + const { getAllByText, getByTestId } = render( { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , ); - expect(await findByText(task.subject)).toBeVisible(); + expect(getAllByText(task.subject).length).toBe(2); userEvent.click(getByTestId('task-row')); expect(onTaskCheckSelected).toHaveBeenCalledWith(task.id); }); @@ -231,7 +237,7 @@ describe('TaskRow', () => { }, }); - const { findByText, getByRole } = render( + const { getAllByText, getByRole } = render( { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , ); - expect(await findByText(task.subject)).toBeVisible(); + expect(getAllByText(task.subject).length).toBe(2); userEvent.click(getByRole('img', { hidden: true, name: 'Check' })); userEvent.click(getByRole('img', { hidden: true, name: 'Check' })); expect(openTaskModal).toHaveBeenCalledWith({ @@ -274,6 +281,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , @@ -292,7 +300,7 @@ describe('TaskRow', () => { }, }); - const { findByText, getByRole } = render( + const { getAllByText, getByRole } = render( { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , ); - expect(await findByText(task.subject)).toBeVisible(); + expect(getAllByText(task.subject).length).toBe(2); userEvent.click(getByRole('img', { hidden: true, name: 'Comment' })); userEvent.click(getByRole('img', { hidden: true, name: 'Comment' })); @@ -325,7 +334,7 @@ describe('TaskRow', () => { }, }); - const { findByText, getByTestId } = render( + const { getAllByText, getByTestId } = render( { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , ); - expect(await findByText(task.subject)).toBeVisible(); + expect(getAllByText(task.subject).length).toBe(2); userEvent.click(getByTestId('subject-wrap')); expect(openTaskModal).toHaveBeenCalledWith({ taskId: task.id, @@ -364,6 +374,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , @@ -407,6 +418,7 @@ describe('TaskRow', () => { onTaskCheckToggle={onTaskCheckSelected} onContactSelected={onContactSelected} isChecked={false} + filterPanelOpen={false} /> , diff --git a/src/components/Task/TaskRow/TaskRow.tsx b/src/components/Task/TaskRow/TaskRow.tsx index 9753b97ee..f12f8c933 100644 --- a/src/components/Task/TaskRow/TaskRow.tsx +++ b/src/components/Task/TaskRow/TaskRow.tsx @@ -1,7 +1,9 @@ import React from 'react'; +import LocalOffer from '@mui/icons-material/LocalOffer'; import { + Avatar, Box, - Checkbox, + Chip, Hidden, Tooltip, Typography, @@ -10,8 +12,8 @@ import { import { styled, useTheme } from '@mui/material/styles'; import { DateTime } from 'luxon'; import { useTranslation } from 'react-i18next'; +import { StyledCheckbox } from 'src/components/Contacts/ContactRow/ContactRow'; import { usePhaseData } from 'src/hooks/usePhaseData'; -import { getLocalizedTaskType } from 'src/utils/functions/getLocalizedTaskType'; import useTaskModal from '../../../hooks/useTaskModal'; import { TaskCommentsButton } from '../../Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCommentsButton/TaskCommentsButton'; import { TaskCompleteButton } from '../../Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton'; @@ -19,54 +21,29 @@ import { TaskDate } from '../../Contacts/ContactDetails/ContactTasksTab/ContactT import { DeleteTaskIconButton } from '../../Contacts/ContactDetails/ContactTasksTab/DeleteTaskIconButton/DeleteTaskIconButton'; import { StarTaskIconButton } from '../../Contacts/ContactDetails/ContactTasksTab/StarTaskIconButton/StarTaskIconButton'; import { TaskModalEnum } from '../Modal/TaskModal'; +import { TaskActionPhase } from './TaskActionPhase'; import { TaskRowFragment } from './TaskRow.generated'; -const SubjectWrapOuter = styled(Box)(({ theme }) => ({ - width: 'fit-content', - display: 'flex', - alignItems: 'center', - marginRight: theme.spacing(1), -})); - -const SubjectWrapInner = styled(Box)(({}) => ({ - display: 'flex', - '&:hover': { - textDecoration: 'underline', - }, -})); - const ContactText = styled(Typography)(({ theme }) => ({ margin: '0px', fontFamily: theme.typography.fontFamily, color: theme.palette.text.primary, fontSize: '14px', letterSpacing: '0.25', -})); - -const TaskPhase = styled(Typography)(() => ({ - fontSize: '14px', - letterSpacing: '0.25', + overflow: 'hidden', whiteSpace: 'nowrap', - fontWeight: 700, -})); - -const TaskType = styled(Typography, { - shouldForwardProp: (prop) => prop !== 'condensed', -})<{ condensed?: boolean }>(({ theme, condensed = false }) => ({ - fontSize: '14px', - letterSpacing: '0.25', - whiteSpace: 'nowrap', - fontWeight: condensed ? 400 : 700, - marginRight: theme.spacing(0.5), + textOverflow: 'ellipsis', + flexGrow: 1, + display: 'inline', + '&:hover': { + textDecoration: 'underline', + cursor: 'pointer', + }, })); const TaskContactName = styled(ContactText)(({ theme }) => ({ fontWeight: 700, - whiteSpace: 'nowrap', marginRight: theme.spacing(0.5), - '&:hover': { - textDecoration: 'underline', - }, })); interface TaskRowProps { @@ -76,8 +53,8 @@ interface TaskRowProps { onContactSelected: (taskId: string) => void; onTaskCheckToggle: (taskId: string) => void; useTopMargin?: boolean; - contactDetailsOpen?: boolean; removeSelectedIds?: (id: string[]) => void; + filterPanelOpen: boolean; } export const TaskRow: React.FC = ({ @@ -87,8 +64,8 @@ export const TaskRow: React.FC = ({ onContactSelected, onTaskCheckToggle, useTopMargin, - contactDetailsOpen, removeSelectedIds, + filterPanelOpen, }) => { const { activityType, @@ -105,8 +82,9 @@ export const TaskRow: React.FC = ({ const activityData = activityType ? activityTypes.get(activityType) : null; const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); - const condensed = isSmallScreen || contactDetailsOpen; + const isLarge = useMediaQuery(theme.breakpoints.down('lg')); + const isMedium = useMediaQuery(theme.breakpoints.down('md')); + const condensed = (filterPanelOpen && isLarge) || isMedium; const TaskRowWrapper = styled(Box)(({ theme }) => ({ ...(isChecked ? { backgroundColor: theme.palette.cruGrayLight.main } : {}), @@ -161,6 +139,11 @@ export const TaskRow: React.FC = ({ const assigneeName = `${task?.user?.firstName ?? ''} ${ task.user?.lastName ?? '' }`; + const tagListString = !!task.tagList.length + ? t('Tags: ') + task.tagList.join(', ') + : ''; + const tagsToShow = 3; + const areMoreTags = tagsToShow < task.tagList.length; return ( @@ -182,7 +165,7 @@ export const TaskRow: React.FC = ({ > - = ({ }} > {(activityData?.phase || activityType) && ( - - { - handleSubjectPressed(); - e.stopPropagation(); - }} - onMouseEnter={() => preloadTaskModal(TaskModalEnum.Edit)} - style={{ - minWidth: condensed ? '92px' : 'none', - flexDirection: condensed ? 'column' : 'row', - }} - > - - {activityData?.phase && condensed - ? activityData.phase - : activityData?.phase - ? activityData.phase + ' - ' + '\u00A0' - : ''} - - - {activityType ? getLocalizedTaskType(t, activityType) : ''} - - - + { + handleSubjectPressed(); + e.stopPropagation(); + }} + onMouseEnter={() => preloadTaskModal(TaskModalEnum.Edit)} + > + + )} - - + { handleSubjectPressed(); @@ -254,11 +223,10 @@ export const TaskRow: React.FC = ({ }} onMouseEnter={() => preloadTaskModal(TaskModalEnum.Edit)} > - - {subject} - - - + {subject} + + + = ({ - {assigneeName} + {!!task?.tagList.length && + (condensed ? ( + + {} + + ) : ( + + + {task.tagList.slice(0, tagsToShow).map((task, idx) => ( + + ))} + {areMoreTags && '...'} + + + ))} + {task?.user && ( + + + {(task?.user?.firstName?.[0] || '') + + task?.user?.lastName?.[0] || ''} + + + )} + + e.stopPropagation()}> = ({ /> - + + @@ -317,6 +326,7 @@ export const TaskRow: React.FC = ({ accountListId={accountListId} taskId={taskId} removeSelectedIds={removeSelectedIds} + small={condensed} /> e.stopPropagation()}> From f1fb96e727d86a8ea03fe4511807129fc66dd8da Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 16 Oct 2024 18:42:40 -0700 Subject: [PATCH 05/11] ContactTaskRow assignee, tags, responsive layout --- .../ContactTaskRow/ContactTaskRow.test.tsx | 4 +- .../ContactTaskRow/ContactTaskRow.tsx | 105 ++++++++++++------ 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx index c6792a496..7e9846d40 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx @@ -74,7 +74,9 @@ describe('ContactTaskRow', () => { expect(await findByText(task.subject)).toBeVisible(); expect( - await findByText(`${task.user?.firstName} ${task.user?.lastName}`), + await findByText( + `${task.user?.firstName?.[0]}${task.user?.lastName?.[0]}`, + ), ).toBeVisible(); expect(queryByTestId('loadingRow')).toBeNull(); diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx index 867d06c2d..3c714dc7d 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx @@ -1,15 +1,24 @@ import React, { useState } from 'react'; -import { Box, Checkbox, Skeleton, Typography } from '@mui/material'; +import LocalOffer from '@mui/icons-material/LocalOffer'; +import { + Avatar, + Box, + Checkbox, + Skeleton, + Tooltip, + Typography, +} from '@mui/material'; import { styled } from '@mui/material/styles'; import { DateTime } from 'luxon'; import { useTranslation } from 'react-i18next'; +import { StyledCheckbox } from 'src/components/Contacts/ContactRow/ContactRow'; import { TaskModalEnum } from 'src/components/Task/Modal/TaskModal'; +import { TaskActionPhase } from 'src/components/Task/TaskRow/TaskActionPhase'; import { TaskRowFragment } from 'src/components/Task/TaskRow/TaskRow.generated'; import { StarredItemIcon } from 'src/components/common/StarredItemIcon/StarredItemIcon'; import { usePhaseData } from 'src/hooks/usePhaseData'; import useTaskModal from 'src/hooks/useTaskModal'; import theme from 'src/theme'; -import { getLocalizedTaskType } from 'src/utils/functions/getLocalizedTaskType'; import { DeleteTaskIconButton } from '../DeleteTaskIconButton/DeleteTaskIconButton'; import { StarTaskIconButton } from '../StarTaskIconButton/StarTaskIconButton'; import { TaskCommentsButton } from './TaskCommentsButton/TaskCommentsButton'; @@ -36,12 +45,6 @@ const TaskItemWrap = styled(Box)(({ theme }) => ({ height: '100%', })); -const TaskType = styled(Typography)(({ theme }) => ({ - fontSize: 14, - fontWeight: 700, - color: theme.palette.text.primary, -})); - const TaskDescription = styled(Typography)(({ theme }) => ({ fontSize: 14, color: theme.palette.text.primary, @@ -52,6 +55,10 @@ const TaskDescription = styled(Typography)(({ theme }) => ({ WebkitBoxOrient: 'vertical', })); +const TooltipTypography = styled(Typography)(() => ({ + fontSize: 11, +})); + const SubjectWrap = styled(Box)(({}) => ({ width: '100%', display: 'flex', @@ -66,14 +73,6 @@ const SubjectWrap = styled(Box)(({}) => ({ }, })); -const AssigneeName = styled(Typography)(({ theme }) => ({ - fontSize: 14, - color: theme.palette.text.primary, - margin: theme.spacing(1), - overflow: 'hidden', - textOverflow: 'ellipsis', -})); - const StarIconWrap = styled(Box)(({ theme }) => ({ margin: theme.spacing(1), })); @@ -156,12 +155,13 @@ export const ContactTaskRow: React.FC = ({ const dateToShow = completedAt ?? startAt; const taskDate = (dateToShow && DateTime.fromISO(dateToShow)) || null; const assigneeName = user ? `${user.firstName} ${user.lastName}` : ''; + const tagList = !!task.tagList.length ? task.tagList.join(', ') : ''; const activityData = activityType ? activityTypes.get(activityType) : null; return !hasBeenDeleted ? ( - onTaskCheckToggle(task.id)} @@ -177,30 +177,71 @@ export const ContactTaskRow: React.FC = ({ onClick={handleSubjectPressed} onMouseEnter={() => preloadTaskModal(TaskModalEnum.Edit)} > - - {activityData && `${activityData.phase} - `} - {getLocalizedTaskType(t, activityType)} - - {subject} + + + + {subject} + - {assigneeName} - - + {(assigneeName || tagList) && ( + + {assigneeName && ( + + {t('Assignee: ') + assigneeName} + + )} + {tagList && ( + {t('Tags: ') + tagList} + )} + + } + placement="top" + arrow + > + {assigneeName ? ( + + {(task?.user?.firstName?.[0] || '') + + task?.user?.lastName?.[0] || ''} + + ) : ( + + )} + + )} + + + + preloadTaskModal(TaskModalEnum.Comments)} + small + /> + - preloadTaskModal(TaskModalEnum.Comments)} - detailsPage - /> Date: Wed, 23 Oct 2024 14:52:34 -0700 Subject: [PATCH 06/11] clean up code --- src/components/Task/TaskRow/TaskActionPhase.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/Task/TaskRow/TaskActionPhase.tsx b/src/components/Task/TaskRow/TaskActionPhase.tsx index a6f07a27d..cc2498d75 100644 --- a/src/components/Task/TaskRow/TaskActionPhase.tsx +++ b/src/components/Task/TaskRow/TaskActionPhase.tsx @@ -37,7 +37,7 @@ const TaskType = styled(Typography)(({ theme }) => ({ interface TaskActionPhaseProps { activityData: ActivityData | null | undefined; - activityType: ActivityTypeEnum | undefined | null; + activityType: ActivityTypeEnum | null | undefined; } export const TaskActionPhase: React.FC = ({ @@ -55,9 +55,7 @@ export const TaskActionPhase: React.FC = ({ }} > {activityData?.phase ? activityData.phase : ''} - - {activityType ? getLocalizedTaskType(t, activityType) : ''} - + {getLocalizedTaskType(t, activityType)} ); From 2bafd3fcd6f71ae92b77f82603eeabe1ee96e17f Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 23 Oct 2024 17:18:24 -0700 Subject: [PATCH 07/11] Add tooltip to comments --- .../ContactTaskRow/ContactTaskRow.tsx | 36 +++++--- .../Task/TaskRow/CommentTooltipText.test.tsx | 46 +++++++++++ .../Task/TaskRow/CommentTooltipText.tsx | 30 +++++++ src/components/Task/TaskRow/TaskRow.graphql | 5 ++ src/components/Task/TaskRow/TaskRow.test.tsx | 10 +-- src/components/Task/TaskRow/TaskRow.tsx | 82 ++++++++++++------- 6 files changed, 162 insertions(+), 47 deletions(-) create mode 100644 src/components/Task/TaskRow/CommentTooltipText.test.tsx create mode 100644 src/components/Task/TaskRow/CommentTooltipText.tsx diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx index 3c714dc7d..18868178f 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx @@ -13,6 +13,10 @@ import { DateTime } from 'luxon'; import { useTranslation } from 'react-i18next'; import { StyledCheckbox } from 'src/components/Contacts/ContactRow/ContactRow'; import { TaskModalEnum } from 'src/components/Task/Modal/TaskModal'; +import { + CommentTooltipText, + TooltipTypography, +} from 'src/components/Task/TaskRow/CommentTooltipText'; import { TaskActionPhase } from 'src/components/Task/TaskRow/TaskActionPhase'; import { TaskRowFragment } from 'src/components/Task/TaskRow/TaskRow.generated'; import { StarredItemIcon } from 'src/components/common/StarredItemIcon/StarredItemIcon'; @@ -55,10 +59,6 @@ const TaskDescription = styled(Typography)(({ theme }) => ({ WebkitBoxOrient: 'vertical', })); -const TooltipTypography = styled(Typography)(() => ({ - fontSize: 11, -})); - const SubjectWrap = styled(Box)(({}) => ({ width: '100%', display: 'flex', @@ -226,15 +226,25 @@ export const ContactTaskRow: React.FC = ({ )} - - preloadTaskModal(TaskModalEnum.Comments)} - small - /> - + + ) : null + } + placement="top" + arrow + > + + preloadTaskModal(TaskModalEnum.Comments)} + small + /> + + { + const comment = gqlMock( + TaskModalCommentFragmentDoc, + { + mocks: { + id: commentId, + body: 'Comment', + updatedAt: DateTime.local(2020, 1, 2).toISODate() ?? '', + }, + }, + ); + + return ( + + + + + + ); +}; + +describe('CommentTooltipText', () => { + it('should render', async () => { + const { findByText, getByText } = render(); + + expect(await findByText('Comment')).toBeInTheDocument(); + expect(getByText('Jan 2, 2020')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Task/TaskRow/CommentTooltipText.tsx b/src/components/Task/TaskRow/CommentTooltipText.tsx new file mode 100644 index 000000000..45fd92e72 --- /dev/null +++ b/src/components/Task/TaskRow/CommentTooltipText.tsx @@ -0,0 +1,30 @@ +import { Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { DateTime } from 'luxon'; +import { useLocale } from 'src/hooks/useLocale'; +import { dateFormat } from 'src/lib/intlFormat'; + +export const TooltipTypography = styled(Typography)(() => ({ + fontSize: 11, +})); + +interface CommentTooltipTextProps { + comments: { id: string; body: string; updatedAt: string }[]; +} +export const CommentTooltipText: React.FC = ({ + comments, +}) => { + const locale = useLocale(); + + const latestComment = !!comments.length + ? comments[comments.length - 1] + : null; + return latestComment ? ( + <> + {latestComment.body} + + {dateFormat(DateTime.fromISO(latestComment.updatedAt), locale)} + + + ) : null; +}; diff --git a/src/components/Task/TaskRow/TaskRow.graphql b/src/components/Task/TaskRow/TaskRow.graphql index 682e941c2..f298e543e 100644 --- a/src/components/Task/TaskRow/TaskRow.graphql +++ b/src/components/Task/TaskRow/TaskRow.graphql @@ -4,6 +4,11 @@ fragment TaskRow on Task { startAt completedAt comments { + nodes { + id + body + updatedAt + } totalCount } contacts(first: 25) { diff --git a/src/components/Task/TaskRow/TaskRow.test.tsx b/src/components/Task/TaskRow/TaskRow.test.tsx index 8e60bcaaf..47462d1d8 100644 --- a/src/components/Task/TaskRow/TaskRow.test.tsx +++ b/src/components/Task/TaskRow/TaskRow.test.tsx @@ -197,7 +197,7 @@ describe('TaskRow', () => { , ); - expect(getAllByText(task.subject).length).toBe(2); + expect(getAllByText(task.subject).length).toBe(1); userEvent.click(getByRole('checkbox', { hidden: true })); expect(onTaskCheckSelected).toHaveBeenCalledWith(task.id); }); @@ -224,7 +224,7 @@ describe('TaskRow', () => { , ); - expect(getAllByText(task.subject).length).toBe(2); + expect(getAllByText(task.subject).length).toBe(1); userEvent.click(getByTestId('task-row')); expect(onTaskCheckSelected).toHaveBeenCalledWith(task.id); }); @@ -252,7 +252,7 @@ describe('TaskRow', () => { , ); - expect(getAllByText(task.subject).length).toBe(2); + expect(getAllByText(task.subject).length).toBe(1); userEvent.click(getByRole('img', { hidden: true, name: 'Check' })); userEvent.click(getByRole('img', { hidden: true, name: 'Check' })); expect(openTaskModal).toHaveBeenCalledWith({ @@ -315,7 +315,7 @@ describe('TaskRow', () => { , ); - expect(getAllByText(task.subject).length).toBe(2); + expect(getAllByText(task.subject).length).toBe(1); userEvent.click(getByRole('img', { hidden: true, name: 'Comment' })); userEvent.click(getByRole('img', { hidden: true, name: 'Comment' })); @@ -349,7 +349,7 @@ describe('TaskRow', () => { , ); - expect(getAllByText(task.subject).length).toBe(2); + expect(getAllByText(task.subject).length).toBe(1); userEvent.click(getByTestId('subject-wrap')); expect(openTaskModal).toHaveBeenCalledWith({ taskId: task.id, diff --git a/src/components/Task/TaskRow/TaskRow.tsx b/src/components/Task/TaskRow/TaskRow.tsx index f12f8c933..671fbab7b 100644 --- a/src/components/Task/TaskRow/TaskRow.tsx +++ b/src/components/Task/TaskRow/TaskRow.tsx @@ -21,6 +21,7 @@ import { TaskDate } from '../../Contacts/ContactDetails/ContactTasksTab/ContactT import { DeleteTaskIconButton } from '../../Contacts/ContactDetails/ContactTasksTab/DeleteTaskIconButton/DeleteTaskIconButton'; import { StarTaskIconButton } from '../../Contacts/ContactDetails/ContactTasksTab/StarTaskIconButton/StarTaskIconButton'; import { TaskModalEnum } from '../Modal/TaskModal'; +import { CommentTooltipText } from './CommentTooltipText'; import { TaskActionPhase } from './TaskActionPhase'; import { TaskRowFragment } from './TaskRow.generated'; @@ -254,17 +255,17 @@ export const TaskRow: React.FC = ({ - {!!task?.tagList.length && - (condensed ? ( - - {} - - ) : ( - + {!!task?.tagList.length && ( + + {condensed ? ( + + ) : ( {task.tagList.slice(0, tagsToShow).map((task, idx) => ( = ({ ))} {areMoreTags && '...'} - - ))} + )} + + )} {task?.user && ( = ({ sx={{ width: 30, height: 30, - mx: 1, + mx: 0.5, }} > {(task?.user?.firstName?.[0] || '') + @@ -296,28 +298,50 @@ export const TaskRow: React.FC = ({ - e.stopPropagation()}> - preloadTaskModal(TaskModalEnum.Comments)} - /> - - - - - - - + + ) : null + } + placement="top" + arrow + > + e.stopPropagation()}> preloadTaskModal(TaskModalEnum.Comments)} - small /> + + + + + + + + ) : null + } + placement="top" + arrow + > + + + preloadTaskModal(TaskModalEnum.Comments) + } + small + /> + + From d07041c45aa3c5a8f55f6d6e8b8cb7229411add2 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 23 Oct 2024 17:20:26 -0700 Subject: [PATCH 08/11] Allow mobile users to tap to see tooltip --- .../ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx | 1 + src/components/Task/TaskRow/TaskRow.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx index 18868178f..6d4f4c052 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.tsx @@ -204,6 +204,7 @@ export const ContactTaskRow: React.FC = ({ } placement="top" arrow + enterTouchDelay={0} > {assigneeName ? ( = ({ {!!task?.tagList.length && ( @@ -281,7 +282,12 @@ export const TaskRow: React.FC = ({ )} {task?.user && ( - + Date: Wed, 23 Oct 2024 18:09:49 -0700 Subject: [PATCH 09/11] fix test --- .../ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx index bc52686f9..bfdfe5d43 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx @@ -168,7 +168,7 @@ describe('ContactTaskRow', () => { const { getByText } = render(); - expect(getByText(activity?.value)).toBeVisible(); + expect(getByText(activity?.value.split(' - ')[1].trim())).toBeVisible(); }, ); }); From 426cdc7b07954bada08d530d60d30622181f0423 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 23 Oct 2024 21:20:14 -0700 Subject: [PATCH 10/11] Add tests --- .../ContactTaskRow/ContactTaskRow.test.tsx | 26 +++++++++++++++++++ src/components/Task/TaskRow/TaskRow.test.tsx | 12 ++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx index bfdfe5d43..6e44ef64e 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/ContactTaskRow.test.tsx @@ -103,8 +103,34 @@ describe('ContactTaskRow', () => { mocks: { startAt, result: ResultEnum.None, + user: { + firstName: 'John', + lastName: 'Wayne', + }, }, }); + const taskWithTags = gqlMock(TaskRowFragmentDoc, { + mocks: { + id: '123', + startAt, + result: ResultEnum.None, + tagList: ['testTag'], + user: null, + }, + }); + + it('renders the assignee avatar', async () => { + const { findByText } = render(); + + expect(await findByText('JW')).toBeVisible(); + }); + + it('renders the tag icon', async () => { + const { findByTestId } = render(); + + expect(await findByTestId('tagIcon-123')).toBeVisible(); + }); + it('handles complete button click', async () => { const { findByText, getByRole } = render(); diff --git a/src/components/Task/TaskRow/TaskRow.test.tsx b/src/components/Task/TaskRow/TaskRow.test.tsx index 47462d1d8..9465a344f 100644 --- a/src/components/Task/TaskRow/TaskRow.test.tsx +++ b/src/components/Task/TaskRow/TaskRow.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { GqlMockedProvider, gqlMock } from '__tests__/util/graphqlMocking'; import { ActivityTypeEnum, ResultEnum } from 'src/graphql/types.generated'; @@ -101,18 +101,20 @@ describe('TaskRow', () => { expect(await findByText(task.contacts.nodes[0].name)).toBeVisible(); }); - it('should render late', async () => { + it('should render late and without assignee', async () => { const task = gqlMock(TaskRowFragmentDoc, { mocks: { + id: '123', startAt: lateStartAt, result: ResultEnum.None, + user: null, contacts: { nodes: [{}], }, }, }); - const { findByText } = render( + const { findByText, queryByTestId } = render( { expect(await findByText(task.subject)).toBeVisible(); expect(await findByText(task.contacts.nodes[0].name)).toBeVisible(); + + await waitFor(() => { + expect(queryByTestId('assigneeAvatar-123')).not.toBeInTheDocument(); + }); }); it('should render with Assignee', async () => { From 44471f028dfd1726e679ab355cc24833f99dd5ae Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Thu, 24 Oct 2024 11:56:32 -0700 Subject: [PATCH 11/11] Comment box remove duplication --- .../Task/TaskRow/CommentTooltipText.tsx | 5 +-- src/components/Task/TaskRow/TaskRow.tsx | 40 +++++-------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/components/Task/TaskRow/CommentTooltipText.tsx b/src/components/Task/TaskRow/CommentTooltipText.tsx index 45fd92e72..c6590b61d 100644 --- a/src/components/Task/TaskRow/CommentTooltipText.tsx +++ b/src/components/Task/TaskRow/CommentTooltipText.tsx @@ -15,10 +15,7 @@ export const CommentTooltipText: React.FC = ({ comments, }) => { const locale = useLocale(); - - const latestComment = !!comments.length - ? comments[comments.length - 1] - : null; + const latestComment = comments.at(-1) ?? null; return latestComment ? ( <> {latestComment.body} diff --git a/src/components/Task/TaskRow/TaskRow.tsx b/src/components/Task/TaskRow/TaskRow.tsx index 0d1e6e3b4..aee9e14a9 100644 --- a/src/components/Task/TaskRow/TaskRow.tsx +++ b/src/components/Task/TaskRow/TaskRow.tsx @@ -299,8 +299,14 @@ export const TaskRow: React.FC = ({ )} - - + + = ({ numberOfComments={comments?.totalCount} onClick={handleCommentButtonPressed} onMouseEnter={() => preloadTaskModal(TaskModalEnum.Comments)} + small={isMedium} + detailsPage={isMedium} /> - + - - - - - ) : null - } - placement="top" - arrow - > - - - preloadTaskModal(TaskModalEnum.Comments) - } - small - /> - - - - e.stopPropagation()}>