From 7fca49ff3a3c0dbdbfa83bba2c607773cdf28ade Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Mon, 4 May 2026 15:20:44 -0500 Subject: [PATCH 1/3] Refactor GoalCard component and tests: implement tooltip for goal name, add loading state, and improve delete confirmation --- .../GoalCalculator/GoalCard/GoalCard.test.tsx | 105 -------- .../GoalCalculator/GoalCard/GoalCard.tsx | 249 ------------------ .../Reports/Shared/GoalCard/GoalCard.test.tsx | 100 +++++++ .../Reports/Shared/GoalCard/GoalCard.tsx | 179 +++++++++++++ 4 files changed, 279 insertions(+), 354 deletions(-) delete mode 100644 src/components/HrTools/GoalCalculator/GoalCard/GoalCard.test.tsx delete mode 100644 src/components/HrTools/GoalCalculator/GoalCard/GoalCard.tsx create mode 100644 src/components/Reports/Shared/GoalCard/GoalCard.test.tsx create mode 100644 src/components/Reports/Shared/GoalCard/GoalCard.tsx diff --git a/src/components/HrTools/GoalCalculator/GoalCard/GoalCard.test.tsx b/src/components/HrTools/GoalCalculator/GoalCard/GoalCard.test.tsx deleted file mode 100644 index 428f2ab213..0000000000 --- a/src/components/HrTools/GoalCalculator/GoalCard/GoalCard.test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { ThemeProvider } from '@mui/system'; -import { render, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import TestRouter from '__tests__/util/TestRouter'; -import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; -import { GoalCalculatorConstantsQuery } from 'src/hooks/goalCalculatorConstants.generated'; -import theme from 'src/theme'; -import { - constantsMock, - goalCalculationMock, -} from '../GoalCalculatorTestWrapper'; -import { GoalCard } from './GoalCard'; - -const mutationSpy = jest.fn(); - -interface TestComponentProps { - primary?: boolean; -} - -const TestComponent: React.FC = ({ primary = false }) => ( - - - - onCall={mutationSpy} - mocks={{ - GoalCalculatorConstants: { - constant: constantsMock, - }, - }} - > - - - - -); - -describe('GoalCard', () => { - it('renders goal title, amount, and date', () => { - const { getByTestId } = render(); - expect(getByTestId('goal-name')).toBeInTheDocument(); - expect(getByTestId('goal-amount-value')).toBeInTheDocument(); - expect(getByTestId('date-value')).toBeInTheDocument(); - }); - - it('calls update mutation when star button is clicked', async () => { - const { getByRole } = render(); - - userEvent.click(getByRole('button', { name: 'star-button' })); - - await waitFor(() => - expect(mutationSpy).toHaveGraphqlOperation('UpdateGoalCalculation', { - accountListId: 'account-list-1', - attributes: { - id: 'goal-1', - primary: true, - }, - }), - ); - }); - - it('shows filled star when primary', () => { - const { getByTestId } = render(); - expect(getByTestId('StarIcon')).toBeInTheDocument(); - }); - - it('shows outlined star when not primary', () => { - const { getByTestId } = render(); - expect(getByTestId('StarBorderOutlinedIcon')).toBeInTheDocument(); - }); - - it('opens confirmation dialog and calls delete mutation when delete button is clicked', async () => { - const { getByRole } = render(); - - userEvent.click(getByRole('button', { name: 'Delete' })); - userEvent.click(getByRole('button', { name: 'Delete Goal' })); - - await waitFor(() => - expect(mutationSpy).toHaveGraphqlOperation('DeleteGoalCalculation', { - accountListId: 'account-list-1', - id: 'goal-1', - }), - ); - }); - - it('renders view link', () => { - const { getByRole } = render(); - expect(getByRole('link', { name: 'View' })).toHaveAttribute( - 'href', - '/accountLists/account-list-1/hrTools/goalCalculator/goal-1', - ); - }); - - it('calculates goal total', async () => { - const { getByTestId } = render(); - await waitFor(() => - expect(getByTestId('goal-amount-value')).toHaveTextContent('$16,138.94'), - ); - }); -}); diff --git a/src/components/HrTools/GoalCalculator/GoalCard/GoalCard.tsx b/src/components/HrTools/GoalCalculator/GoalCard/GoalCard.tsx deleted file mode 100644 index 64104ea411..0000000000 --- a/src/components/HrTools/GoalCalculator/GoalCard/GoalCard.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import NextLink from 'next/link'; -import React, { useMemo, useState } from 'react'; -import { Star, StarBorderOutlined } from '@mui/icons-material'; -import { - Box, - Button, - Card, - Divider, - IconButton, - Typography, - styled, -} from '@mui/material'; -import { DateTime } from 'luxon'; -import { Trans, useTranslation } from 'react-i18next'; -import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation'; -import { useAccountListId } from 'src/hooks/useAccountListId'; -import { useGoalCalculatorConstants } from 'src/hooks/useGoalCalculatorConstants'; -import { useLocale } from 'src/hooks/useLocale'; -import { currencyFormat, dateFormat } from 'src/lib/intlFormat'; -import theme from 'src/theme'; -import { - ListGoalCalculationFragment, - useDeleteGoalCalculationMutation, - useUpdateGoalCalculationMutation, -} from '../GoalsList/GoalCalculations.generated'; -import { calculateGoalTotals } from '../Shared/calculateTotals'; - -const StyledCard = styled(Card)({ - minWidth: 350, - borderRadius: theme.shape.borderRadius, -}); - -const StyledHeaderBox = styled(Box)({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: theme.spacing(2), - marginTop: theme.spacing(2), -}); - -const StyledTitleBox = styled(Box)({ - flex: 1, - display: 'flex', - justifyContent: 'center', -}); - -const StyledStarButton = styled(IconButton)({ - position: 'absolute', - right: 0, - padding: theme.spacing(2), -}); - -const StyledContentBox = styled(Box)({ - marginTop: theme.spacing(2), -}); - -const StyledContentInnerBox = styled(Box)({ - marginRight: theme.spacing(2), - marginLeft: theme.spacing(2), -}); - -const StyledInfoRow = styled(Box)({ - display: 'flex', - justifyContent: 'flex-start', - gap: 72, -}); - -const StyledActionBox = styled(Box)({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: theme.spacing(1), - paddingRight: theme.spacing(1), - paddingLeft: theme.spacing(1), -}); - -export interface GoalCardProps { - goal: ListGoalCalculationFragment; - - /** Remove this prop and always render the star once we do something with the primary flag */ - renderStar?: boolean; -} - -export const GoalCard: React.FC = ({ - goal, - renderStar = false, -}) => { - const { t } = useTranslation(); - const locale = useLocale(); - const accountListId = useAccountListId() ?? ''; - const constants = useGoalCalculatorConstants(); - const [updateGoalCalculation] = useUpdateGoalCalculationMutation(); - const [deleteGoalCalculation] = useDeleteGoalCalculationMutation(); - const [deleting, setDeleting] = useState(false); - - const overallTotal = useMemo( - () => calculateGoalTotals(goal, constants).overallTotal, - [goal, constants], - ); - - const handleStarClick = async () => { - await updateGoalCalculation({ - variables: { - accountListId, - attributes: { - id: goal.id, - primary: !goal.primary, - }, - }, - optimisticResponse: { - updateGoalCalculation: { - goalCalculation: { - ...goal, - primary: !goal.primary, - }, - }, - }, - refetchQueries: ['GoalCalculations'], - }); - }; - - const handleDeleteClick = () => { - setDeleting(true); - }; - - const handleConfirmDelete = async () => { - await deleteGoalCalculation({ - variables: { - accountListId, - id: goal.id, - }, - update: (cache) => { - cache.evict({ id: `GoalCalculation:${goal.id}` }); - cache.gc(); - }, - }); - setDeleting(false); - }; - - const handleCancelDelete = () => { - setDeleting(false); - }; - - return ( - <> - - Are you sure you want to delete{' '} - {goal.name ?? t('Unnamed Goal')}? Deleting this - goal will remove it permanently. - - } - confirmButtonProps={{ - variant: 'contained', - color: 'error', - children: t('Delete Goal'), - }} - /> - - - - - - {goal.name ?? t('Unnamed Goal')} - - - {renderStar && ( - - {goal.primary ? ( - - ) : ( - - )} - - )} - - - - - - - - - {t('Goal Amount')} - - - {currencyFormat(overallTotal, 'USD', locale)} - - - - - - - - {t('Last Updated')} - - - {dateFormat(DateTime.fromISO(goal.updatedAt), locale, { - fullMonth: true, - })} - - - - - - - - - - - - - - ); -}; diff --git a/src/components/Reports/Shared/GoalCard/GoalCard.test.tsx b/src/components/Reports/Shared/GoalCard/GoalCard.test.tsx new file mode 100644 index 0000000000..d067cacff7 --- /dev/null +++ b/src/components/Reports/Shared/GoalCard/GoalCard.test.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { ThemeProvider } from '@mui/material/styles'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import theme from 'src/theme'; +import { GoalCard, GoalCardProps } from './GoalCard'; + +const mutationSpy = jest.fn(); + +const baseProps: GoalCardProps = { + name: 'Test Goal', + goalAmount: 1234.56, + currency: 'USD', + updatedAt: '2026-03-15T00:00:00Z', + viewHref: '/some/view/path', + onDelete: mutationSpy, +}; + +const renderCard = (props: Partial = {}) => + render( + + + , + ); + +describe('GoalCard', () => { + it('renders the goal name, amount, and last updated date', () => { + const { getByTestId } = renderCard(); + + expect(getByTestId('goal-name')).toHaveTextContent('Test Goal'); + expect(getByTestId('goal-amount-value')).toHaveTextContent('$1,234.56'); + expect(getByTestId('date-value')).toHaveTextContent('March 15'); + }); + + it('falls back to "Unnamed Goal" when name is null', () => { + const { getByTestId } = renderCard({ name: null }); + + expect(getByTestId('goal-name')).toHaveTextContent('Unnamed Goal'); + }); + + it('shows the full goal name in a tooltip on hover', async () => { + const longName = + 'A very long goal name that would otherwise stretch the card'; + const { getByTestId, findByRole } = renderCard({ name: longName }); + + userEvent.hover(getByTestId('goal-name')); + + expect((await findByRole('tooltip')).textContent).toBe(longName); + }); + + it('shows the unnamed goal fallback in a tooltip on hover', async () => { + const { getByTestId, findByRole } = renderCard({ name: null }); + + userEvent.hover(getByTestId('goal-name')); + + expect((await findByRole('tooltip')).textContent).toBe('Unnamed Goal'); + }); + + it('renders the View link with the provided href', () => { + const { getByRole } = renderCard({ viewHref: '/custom/href' }); + + expect(getByRole('link', { name: 'View' })).toHaveAttribute( + 'href', + '/custom/href', + ); + }); + + it('formats the goal amount in the supplied currency', () => { + const { getByTestId } = renderCard({ goalAmount: 2500, currency: 'EUR' }); + + expect(getByTestId('goal-amount-value')).toHaveTextContent('€2,500'); + }); + + it('renders a skeleton in place of the amount when loading', () => { + const { getByTestId, queryByText } = renderCard({ loading: true }); + + expect( + getByTestId('goal-amount-value').querySelector('.MuiSkeleton-root'), + ).toBeInTheDocument(); + expect(queryByText('$1,234.56')).not.toBeInTheDocument(); + }); + + it('opens the confirmation dialog and calls onDelete when confirmed', () => { + const { getByRole } = renderCard(); + + userEvent.click(getByRole('button', { name: 'Delete' })); + userEvent.click(getByRole('button', { name: 'Delete Goal' })); + + expect(mutationSpy).toHaveBeenCalledTimes(1); + }); + + it('does not call onDelete when the confirmation is cancelled', () => { + const { getByRole } = renderCard(); + + userEvent.click(getByRole('button', { name: 'Delete' })); + userEvent.click(getByRole('button', { name: 'Cancel' })); + + expect(mutationSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/Reports/Shared/GoalCard/GoalCard.tsx b/src/components/Reports/Shared/GoalCard/GoalCard.tsx new file mode 100644 index 0000000000..ce40126f39 --- /dev/null +++ b/src/components/Reports/Shared/GoalCard/GoalCard.tsx @@ -0,0 +1,179 @@ +import NextLink from 'next/link'; +import React, { useState } from 'react'; +import { + Box, + Button, + Card, + Divider, + Skeleton, + Tooltip, + Typography, + styled, +} from '@mui/material'; +import { DateTime } from 'luxon'; +import { Trans, useTranslation } from 'react-i18next'; +import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation'; +import { useLocale } from 'src/hooks/useLocale'; +import { currencyFormat, dateFormat } from 'src/lib/intlFormat'; + +const StyledCard = styled(Card)(({ theme }) => ({ + width: 350, + borderRadius: theme.shape.borderRadius, +})); + +const StyledHeaderBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.spacing(2), + marginTop: theme.spacing(2), + paddingInline: theme.spacing(2), +})); + +const StyledContentBox = styled(Box)(({ theme }) => ({ + marginTop: theme.spacing(2), +})); + +const StyledContentInnerBox = styled(Box)(({ theme }) => ({ + marginRight: theme.spacing(2), + marginLeft: theme.spacing(2), +})); + +const StyledInfoRow = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'flex-start', + gap: theme.spacing(9), +})); + +const StyledActionBox = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing(1), + paddingRight: theme.spacing(1), + paddingLeft: theme.spacing(1), +})); + +export interface GoalCardProps { + name: string | null | undefined; + goalAmount: number; + currency: string; + loading?: boolean; + updatedAt: string; + viewHref: string; + onDelete: () => Promise; +} + +export const GoalCard: React.FC = ({ + name, + goalAmount, + currency, + loading = false, + updatedAt, + viewHref, + onDelete, +}) => { + const { t } = useTranslation(); + const locale = useLocale(); + const [deleting, setDeleting] = useState(false); + + const displayName = name ?? t('Unnamed Goal'); + + const handleConfirmDelete = async () => onDelete(); + + return ( + <> + setDeleting(false)} + confirmLabel={t('Delete Goal')} + cancelLabel={t('Cancel')} + message={ + + } + confirmButtonProps={{ + variant: 'contained', + color: 'error', + children: t('Delete Goal'), + }} + /> + + + + + + {displayName} + + + + + + + + + + + {t('Goal Amount')} + + + {loading ? ( + + ) : ( + currencyFormat(goalAmount, currency, locale) + )} + + + + + + + + {t('Last Updated')} + + + {dateFormat(DateTime.fromISO(updatedAt), locale, { + fullMonth: true, + })} + + + + + + + + + + + + + + ); +}; From 58190b5812b7ce2dfb39146a18418369a17805e8 Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Mon, 4 May 2026 15:21:04 -0500 Subject: [PATCH 2/3] Remove unused primary field and UpdateGoalCalculation mutation from GoalCalculations.graphql --- .../GoalsList/GoalCalculations.graphql | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/HrTools/GoalCalculator/GoalsList/GoalCalculations.graphql b/src/components/HrTools/GoalCalculator/GoalsList/GoalCalculations.graphql index 539691fe92..8a8a2b2cac 100644 --- a/src/components/HrTools/GoalCalculator/GoalsList/GoalCalculations.graphql +++ b/src/components/HrTools/GoalCalculator/GoalsList/GoalCalculations.graphql @@ -2,7 +2,6 @@ fragment ListGoalCalculation on GoalCalculation { id name role - primary familySize benefitsPlan netPaycheckAmount @@ -45,19 +44,6 @@ mutation CreateGoalCalculation($accountListId: ID!) { } } -mutation UpdateGoalCalculation( - $accountListId: ID! - $attributes: GoalCalculationUpdateInput! -) { - updateGoalCalculation( - input: { accountListId: $accountListId, attributes: $attributes } - ) { - goalCalculation { - ...ListGoalCalculation - } - } -} - mutation DeleteGoalCalculation($accountListId: ID!, $id: ID!) { deleteGoalCalculation(input: { accountListId: $accountListId, id: $id }) { id From 42c363ce723c1c84c39f020e4a2ef99477b6f2ef Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Mon, 4 May 2026 15:21:21 -0500 Subject: [PATCH 3/3] Refactor goal card components: implement MpdGoalCard and update PdsGoalCard to use GoalCard for improved consistency and functionality --- .../GoalCard/MpdGoalCard.test.tsx | 73 +++++++ .../GoalCalculator/GoalCard/MpdGoalCard.tsx | 49 +++++ .../GoalCalculator/GoalsList/GoalsList.tsx | 4 +- .../GoalCard/PdsGoalCard.test.tsx | 101 +--------- .../GoalCard/PdsGoalCard.tsx | 190 +++--------------- .../GoalsList/PdsGoalsList.tsx | 18 +- 6 files changed, 159 insertions(+), 276 deletions(-) create mode 100644 src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.test.tsx create mode 100644 src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.tsx diff --git a/src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.test.tsx b/src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.test.tsx new file mode 100644 index 0000000000..3ff736be4f --- /dev/null +++ b/src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.test.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { ThemeProvider } from '@mui/system'; +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import TestRouter from '__tests__/util/TestRouter'; +import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; +import { GoalCalculatorConstantsQuery } from 'src/hooks/goalCalculatorConstants.generated'; +import theme from 'src/theme'; +import { + constantsMock, + goalCalculationMock, +} from '../GoalCalculatorTestWrapper'; +import { MpdGoalCard } from './MpdGoalCard'; + +const mutationSpy = jest.fn(); + +const TestComponent: React.FC = () => ( + + + + onCall={mutationSpy} + mocks={{ + GoalCalculatorConstants: { + constant: constantsMock, + }, + }} + > + + + + +); + +describe('MpdGoalCard', () => { + it('calls the delete mutation when the Delete button is confirmed', async () => { + const { getByRole } = render(); + + userEvent.click(getByRole('button', { name: 'Delete' })); + userEvent.click(getByRole('button', { name: 'Delete Goal' })); + + await waitFor(() => + expect(mutationSpy).toHaveGraphqlOperation('DeleteGoalCalculation', { + accountListId: 'account-list-1', + id: 'goal-1', + }), + ); + }); + + it('builds the View link with the goal calculator path', () => { + const { getByRole } = render(); + + expect(getByRole('link', { name: 'View' })).toHaveAttribute( + 'href', + '/accountLists/account-list-1/hrTools/goalCalculator/goal-1', + ); + }); + + it('calculates and displays the goal total', async () => { + const { findByText } = render(); + + expect(await findByText('$16,138.94')).toBeInTheDocument(); + }); + + it('shows the skeleton while goal calculator constants are loading', () => { + const { getByTestId } = render(); + + expect( + getByTestId('goal-amount-value').querySelector('.MuiSkeleton-root'), + ).toBeInTheDocument(); + }); +}); diff --git a/src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.tsx b/src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.tsx new file mode 100644 index 0000000000..268b3f9c7c --- /dev/null +++ b/src/components/HrTools/GoalCalculator/GoalCard/MpdGoalCard.tsx @@ -0,0 +1,49 @@ +import React, { useMemo } from 'react'; +import { GoalCard } from 'src/components/Reports/Shared/GoalCard/GoalCard'; +import { useAccountListId } from 'src/hooks/useAccountListId'; +import { useGoalCalculatorConstants } from 'src/hooks/useGoalCalculatorConstants'; +import { + ListGoalCalculationFragment, + useDeleteGoalCalculationMutation, +} from '../GoalsList/GoalCalculations.generated'; +import { calculateGoalTotals } from '../Shared/calculateTotals'; + +export interface MpdGoalCardProps { + goal: ListGoalCalculationFragment; +} + +export const MpdGoalCard: React.FC = ({ goal }) => { + const accountListId = useAccountListId() ?? ''; + const constants = useGoalCalculatorConstants(); + const [deleteGoalCalculation] = useDeleteGoalCalculationMutation(); + + const overallTotal = useMemo( + () => calculateGoalTotals(goal, constants).overallTotal, + [goal, constants], + ); + + const handleDelete = async () => { + await deleteGoalCalculation({ + variables: { + accountListId, + id: goal.id, + }, + update: (cache) => { + cache.evict({ id: `GoalCalculation:${goal.id}` }); + cache.gc(); + }, + }); + }; + + return ( + + ); +}; diff --git a/src/components/HrTools/GoalCalculator/GoalsList/GoalsList.tsx b/src/components/HrTools/GoalCalculator/GoalsList/GoalsList.tsx index 784cbf585e..f99e9ee94e 100644 --- a/src/components/HrTools/GoalCalculator/GoalsList/GoalsList.tsx +++ b/src/components/HrTools/GoalCalculator/GoalsList/GoalsList.tsx @@ -6,7 +6,7 @@ import { useGetUserQuery } from 'src/components/User/GetUser.generated'; import { useAccountListId } from 'src/hooks/useAccountListId'; import { useFetchAllPages } from 'src/hooks/useFetchAllPages'; import illustration6graybg from 'src/images/drawkit/grape/drawkit-grape-pack-illustration-6-gray-bg.svg'; -import { GoalCard } from '../GoalCard/GoalCard'; +import { MpdGoalCard } from '../GoalCard/MpdGoalCard'; import { useCreateGoalCalculationMutation, useGoalCalculationsQuery, @@ -73,7 +73,7 @@ export const GoalsList: React.FC = () => { ) : ( {goals?.map((goal) => ( - + ))} )} diff --git a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx index 4e257c3c1b..88e4901a1c 100644 --- a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx +++ b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx @@ -1,122 +1,39 @@ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { render } from '@testing-library/react'; import { PdsGoalsList } from '../GoalsList/PdsGoalsList'; import { PdsGoalCalculatorTestWrapper } from '../PdsGoalCalculatorTestWrapper'; describe('PdsGoalCard', () => { - it('renders the goal name in the card', async () => { + it('renders the calculated PDS goal amount', async () => { const { findByText } = render( - - , - ); - - expect(await findByText('Test PDS Goal')).toBeInTheDocument(); - }); - - it('renders unnamed goal when name is null', async () => { - const { findByTestId } = render( - , ); - expect(await findByTestId('goal-name')).toHaveTextContent('Unnamed Goal'); + expect(await findByText('$849.44')).toBeInTheDocument(); }); - it('renders the Delete and View buttons', async () => { + it('builds the View link with the PDS goal calculator path', async () => { const { findByRole } = render( - - , - ); - - expect(await findByRole('button', { name: 'Delete' })).toBeInTheDocument(); - expect(await findByRole('link', { name: 'View' })).toBeInTheDocument(); - }); - - it('renders the last updated date', async () => { - const { findByTestId } = render( - - - , - ); - - expect(await findByTestId('date-value')).toHaveTextContent('March 15'); - }); - - it('shows the full goal name in a tooltip on hover', async () => { - const longName = - 'A very long goal name that would otherwise stretch the card'; - const { findByTestId, findByRole } = render( - , ); - userEvent.hover(await findByTestId('goal-name')); - - expect(await findByRole('tooltip')).toHaveTextContent(longName); - }); - - it('shows the unnamed goal fallback in a tooltip on hover', async () => { - const { findByTestId, findByRole } = render( - - - , + expect(await findByRole('link', { name: 'View' })).toHaveAttribute( + 'href', + '/accountLists/abc123/hrTools/pdsGoalCalculator/pds-goal-1', ); - - userEvent.hover(await findByTestId('goal-name')); - - expect(await findByRole('tooltip')).toHaveTextContent('Unnamed Goal'); - }); - - it('renders the calculated goal amount', async () => { - const { findByTestId } = render( - - - , - ); - - const goalAmount = await findByTestId('goal-amount-value'); - await waitFor(() => { - expect(goalAmount).toHaveTextContent('$849.44'); - }); }); }); diff --git a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx index e2c5034e2f..285eb76ebe 100644 --- a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx +++ b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx @@ -1,77 +1,24 @@ -import NextLink from 'next/link'; -import React, { useMemo, useState } from 'react'; -import { - Box, - Button, - Card, - Divider, - Skeleton, - Tooltip, - Typography, - styled, -} from '@mui/material'; -import { DateTime } from 'luxon'; -import { Trans, useTranslation } from 'react-i18next'; -import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation'; +import React, { useMemo } from 'react'; +import { GoalCard } from 'src/components/Reports/Shared/GoalCard/GoalCard'; import { useAccountListId } from 'src/hooks/useAccountListId'; import { useGoalCalculatorConstants } from 'src/hooks/useGoalCalculatorConstants'; -import { useLocale } from 'src/hooks/useLocale'; -import { currencyFormat, dateFormat } from 'src/lib/intlFormat'; -import { PdsGoalCalculationFieldsFragment } from '../GoalsList/PdsGoalCalculations.generated'; +import { + PdsGoalCalculationFieldsFragment, + useDeletePdsGoalCalculationMutation, +} from '../GoalsList/PdsGoalCalculations.generated'; import { useHcmUserQuery } from '../Shared/HCM.generated'; import { buildPdsGoalConstants, calculatePdsGoalTotal, } from '../calculations/calculatePdsGoalTotal'; -const StyledCard = styled(Card)(({ theme }) => ({ - width: 350, - borderRadius: theme.shape.borderRadius, -})); - -const StyledHeaderBox = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: theme.spacing(2), - marginTop: theme.spacing(2), - paddingInline: theme.spacing(2), -})); - -const StyledContentBox = styled(Box)(({ theme }) => ({ - marginTop: theme.spacing(2), -})); - -const StyledContentInnerBox = styled(Box)(({ theme }) => ({ - marginRight: theme.spacing(2), - marginLeft: theme.spacing(2), -})); - -const StyledInfoRow = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-start', - gap: theme.spacing(9), -})); - -const StyledActionBox = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: theme.spacing(1), - paddingRight: theme.spacing(1), - paddingLeft: theme.spacing(1), -})); - export interface PdsGoalCardProps { goal: PdsGoalCalculationFieldsFragment; - onDelete: (id: string) => Promise; } -export const PdsGoalCard: React.FC = ({ goal, onDelete }) => { - const { t } = useTranslation(); - const locale = useLocale(); +export const PdsGoalCard: React.FC = ({ goal }) => { const accountListId = useAccountListId() ?? ''; - const [deleting, setDeleting] = useState(false); + const [deletePdsGoalCalculation] = useDeletePdsGoalCalculationMutation(); const { goalMiscConstants, @@ -91,112 +38,25 @@ export const PdsGoalCard: React.FC = ({ goal, onDelete }) => { return constants ? calculatePdsGoalTotal(goal, constants) : 0; }, [goal, goalMiscConstants, goalGeographicConstantMap, hcmUser]); - const handleDeleteClick = () => { - setDeleting(true); - }; - - const handleConfirmDelete = async () => { - await onDelete(goal.id); - setDeleting(false); - }; - - const handleCancelDelete = () => { - setDeleting(false); + const handleDelete = async () => { + await deletePdsGoalCalculation({ + variables: { id: goal.id }, + update: (cache) => { + cache.evict({ id: `DesignationSupportCalculation:${goal.id}` }); + cache.gc(); + }, + }); }; return ( - <> - - Are you sure you want to delete{' '} - {goal.name ?? t('Unnamed Goal')}? Deleting this - goal will remove it permanently. - - } - confirmButtonProps={{ - variant: 'contained', - color: 'error', - children: t('Delete Goal'), - }} - /> - - - - - - {goal.name ?? t('Unnamed Goal')} - - - - - - - - - - - {t('Goal Amount')} - - - {constantsLoading || hcmLoading ? ( - - ) : ( - currencyFormat(goalTotal, 'USD', locale) - )} - - - - - - - - {t('Last Updated')} - - - {dateFormat(DateTime.fromISO(goal.updatedAt), locale, { - fullMonth: true, - })} - - - - - - - - - - - - - + ); }; diff --git a/src/components/HrTools/PdsGoalCalculator/GoalsList/PdsGoalsList.tsx b/src/components/HrTools/PdsGoalCalculator/GoalsList/PdsGoalsList.tsx index b6175a06b5..32b908c746 100644 --- a/src/components/HrTools/PdsGoalCalculator/GoalsList/PdsGoalsList.tsx +++ b/src/components/HrTools/PdsGoalCalculator/GoalsList/PdsGoalsList.tsx @@ -10,7 +10,6 @@ import illustration6graybg from 'src/images/drawkit/grape/drawkit-grape-pack-ill import { PdsGoalCard } from '../GoalCard/PdsGoalCard'; import { useCreatePdsGoalCalculationMutation, - useDeletePdsGoalCalculationMutation, usePdsGoalCalculationsQuery, } from './PdsGoalCalculations.generated'; import { PdsGoalsListWelcome } from './PdsGoalsListWelcome'; @@ -40,22 +39,11 @@ export const PdsGoalsList: React.FC = () => { pageInfo: data?.designationSupportCalculations.pageInfo, }); const [createPdsGoalCalculation] = useCreatePdsGoalCalculationMutation(); - const [deletePdsGoalCalculation] = useDeletePdsGoalCalculationMutation(); const { goalMiscConstants, loading: constantsLoading } = useGoalCalculatorConstants(); const goals = data?.designationSupportCalculations.nodes; - const handleDeleteGoal = async (id: string) => { - await deletePdsGoalCalculation({ - variables: { id }, - update: (cache) => { - cache.evict({ id: `DesignationSupportCalculation:${id}` }); - cache.gc(); - }, - }); - }; - const handleCreateGoal = async () => { const { data } = await createPdsGoalCalculation({ variables: { @@ -101,11 +89,7 @@ export const PdsGoalsList: React.FC = () => { ) : ( {goals?.map((goal) => ( - + ))} )}