Skip to content

Commit c3c9cde

Browse files
committed
feat: payment history view
1 parent 2e47d78 commit c3c9cde

File tree

11 files changed

+155
-184
lines changed

11 files changed

+155
-184
lines changed

src/assets/icons/slash-circle.svg

Lines changed: 11 additions & 0 deletions
Loading

src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ export function CreatePaymentContextual() {
2929
}
3030

3131
export function PaymentHistoryContextual() {
32-
const { companyId, currentPaymentId, onEvent } = useFlow<PaymentFlowContextInterface>()
33-
return (
34-
<PaymentHistory
35-
onEvent={onEvent}
36-
companyId={ensureRequired(companyId)}
37-
paymentId={ensureRequired(currentPaymentId)}
38-
/>
39-
)
32+
const { currentPaymentId, onEvent } = useFlow<PaymentFlowContextInterface>()
33+
return <PaymentHistory onEvent={onEvent} paymentId={ensureRequired(currentPaymentId)} />
4034
}

src/components/Contractor/Payments/PaymentHistory/PaymentHistory.tsx

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import { useContractorPaymentGroupsGetSuspense } from '@gusto/embedded-api/react-query/contractorPaymentGroupsGet'
2+
import { useContractorsListSuspense } from '@gusto/embedded-api/react-query/contractorsList'
3+
import { useTranslation } from 'react-i18next'
14
import { PaymentHistoryPresentation } from './PaymentHistoryPresentation'
25
import { useComponentDictionary } from '@/i18n'
36
import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
7+
import { componentEvents, ContractorOnboardingStatus } from '@/shared/constants'
48

59
interface PaymentHistoryProps extends BaseComponentInterface<'Contractor.Payments.PaymentHistory'> {
6-
companyId: string
710
paymentId: string
811
}
912

@@ -15,17 +18,41 @@ export function PaymentHistory(props: PaymentHistoryProps) {
1518
)
1619
}
1720

18-
export const Root = ({ companyId, paymentId, dictionary, onEvent }: PaymentHistoryProps) => {
21+
export const Root = ({ paymentId, dictionary, onEvent }: PaymentHistoryProps) => {
1922
useComponentDictionary('Contractor.Payments.PaymentHistory', dictionary)
23+
const { t } = useTranslation('Contractor.Payments.PaymentHistory')
24+
25+
const { data: paymentGroupResponse } = useContractorPaymentGroupsGetSuspense({
26+
contractorPaymentGroupUuid: paymentId,
27+
})
28+
if (!paymentGroupResponse.contractorPaymentGroup) {
29+
throw new Error(t('errors.paymentGroupNotFound'))
30+
}
31+
32+
const companyId = paymentGroupResponse.contractorPaymentGroup.companyUuid!
33+
34+
const { data: contractorList } = useContractorsListSuspense({ companyUuid: companyId })
35+
const contractors = (contractorList.contractorList || []).filter(
36+
contractor =>
37+
contractor.isActive &&
38+
contractor.onboardingStatus === ContractorOnboardingStatus.ONBOARDING_COMPLETED,
39+
)
40+
41+
const handleViewPayment = (paymentId: string) => {
42+
onEvent(componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, { paymentId })
43+
}
44+
45+
const handleCancelPayment = (paymentId: string) => {
46+
onEvent(componentEvents.CONTRACTOR_PAYMENT_CANCEL, { paymentId })
47+
}
2048

2149
return (
2250
<>
2351
<PaymentHistoryPresentation
24-
date={'2025-12-17'}
25-
payments={[]}
26-
onBack={() => {}}
27-
onViewPayment={() => {}}
28-
onCancelPayment={() => {}}
52+
paymentGroup={paymentGroupResponse.contractorPaymentGroup}
53+
contractors={contractors}
54+
onViewPayment={handleViewPayment}
55+
onCancelPayment={handleCancelPayment}
2956
/>
3057
</>
3158
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.icon {
2+
color: var(--g-colorBodySubContent);
3+
height: toRem(16);
4+
width: toRem(16);
5+
display: flex;
6+
align-items: center;
7+
justify-content: center;
8+
}

src/components/Contractor/Payments/PaymentHistory/PaymentHistoryPresentation.tsx

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,76 @@
1-
import { useTranslation } from 'react-i18next'
2-
import { DataView, Flex, EmptyData, ActionsLayout } from '@/components/Common'
1+
import { Trans, useTranslation } from 'react-i18next'
2+
import type { ContractorPaymentGroup } from '@gusto/embedded-api/models/components/contractorpaymentgroup'
3+
import type { Contractor } from '@gusto/embedded-api/models/components/contractor'
4+
import { getContractorDisplayName } from '../CreatePayment/helpers'
5+
import styles from './PaymentHistoryPresentation.module.scss'
6+
import { DataView, Flex, EmptyData } from '@/components/Common'
37
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
48
import { HamburgerMenu } from '@/components/Common/HamburgerMenu'
59
import { useI18n } from '@/i18n'
6-
import { formatNumberAsCurrency } from '@/helpers/formattedStrings'
7-
import { useLocale } from '@/contexts/LocaleProvider/useLocale'
810
import { formatHoursDisplay } from '@/components/Payroll/helpers'
9-
10-
interface PaymentData {
11-
id: string
12-
name: string
13-
wageType: string
14-
paymentMethod: string
15-
hours: number
16-
wage: string
17-
bonus: number
18-
reimbursement: number
19-
total: number
20-
}
11+
import useNumberFormatter from '@/hooks/useNumberFormatter'
12+
import { useDateFormatter } from '@/hooks/useDateFormatter'
13+
import EyeIcon from '@/assets/icons/eye.svg?react'
14+
import CancelIcon from '@/assets/icons/slash-circle.svg?react'
2115

2216
interface PaymentHistoryPresentationProps {
23-
date: string
24-
payments: PaymentData[]
25-
onBack: () => void
17+
paymentGroup: ContractorPaymentGroup
18+
contractors: Contractor[]
2619
onViewPayment: (paymentId: string) => void
2720
onCancelPayment: (paymentId: string) => void
2821
}
2922

3023
export const PaymentHistoryPresentation = ({
31-
date,
32-
payments,
33-
onBack,
24+
paymentGroup,
25+
contractors,
3426
onViewPayment,
3527
onCancelPayment,
3628
}: PaymentHistoryPresentationProps) => {
3729
const { Button, Text, Heading } = useComponentContext()
3830
useI18n('Contractor.Payments.PaymentHistory')
3931
const { t } = useTranslation('Contractor.Payments.PaymentHistory')
40-
const { locale } = useLocale()
32+
const currencyFormatter = useNumberFormatter('currency')
33+
const { formatLongWithYear } = useDateFormatter()
34+
35+
const payments = paymentGroup.contractorPayments || []
4136

4237
return (
4338
<Flex flexDirection="column" gap={32}>
4439
<Flex flexDirection="column" gap={8}>
4540
<Heading as="h1">{t('title')}</Heading>
46-
<Text>{t('subtitle', { date })}</Text>
41+
<Text>
42+
<Trans
43+
i18nKey={'subtitle'}
44+
t={t}
45+
values={{ date: formatLongWithYear(paymentGroup.debitDate) }}
46+
components={{
47+
strong: <Text weight="bold" as="span" />,
48+
}}
49+
/>
50+
</Text>
4751
</Flex>
4852

4953
<Flex flexDirection="column" gap={16}>
5054
<Heading as="h2">{t('paymentsSection')}</Heading>
5155

5256
{payments.length === 0 ? (
53-
<EmptyData title={t('noPaymentsFound')} description={t('noPaymentsDescription')}>
54-
<ActionsLayout justifyContent="center">
55-
<Button variant="primary" onClick={onBack}>
56-
{t('backButton')}
57-
</Button>
58-
</ActionsLayout>
59-
</EmptyData>
57+
<EmptyData title={t('noPaymentsFound')} description={t('noPaymentsDescription')} />
6058
) : (
6159
<>
6260
<DataView
6361
columns={[
6462
{
6563
title: t('tableHeaders.contractor'),
66-
render: ({ name, id }) => (
64+
render: ({ contractorUuid }) => (
6765
<Button
6866
variant="tertiary"
6967
onClick={() => {
70-
onViewPayment(id)
68+
onViewPayment(contractorUuid!)
7169
}}
7270
>
73-
{name}
71+
{getContractorDisplayName(
72+
contractors.find(contractor => contractor.uuid === contractorUuid),
73+
)}
7474
</Button>
7575
),
7676
},
@@ -84,7 +84,9 @@ export const PaymentHistoryPresentation = ({
8484
},
8585
{
8686
title: t('tableHeaders.hours'),
87-
render: ({ hours }) => <Text>{hours ? formatHoursDisplay(hours) : '–'}</Text>,
87+
render: ({ hours }) => (
88+
<Text>{hours ? formatHoursDisplay(Number(hours)) : '–'}</Text>
89+
),
8890
},
8991
{
9092
title: t('tableHeaders.wage'),
@@ -93,51 +95,61 @@ export const PaymentHistoryPresentation = ({
9395
{
9496
title: t('tableHeaders.bonus'),
9597
render: ({ bonus }) => (
96-
<Text>{bonus ? formatNumberAsCurrency(bonus, locale) : '–'}</Text>
98+
<Text>{bonus ? currencyFormatter(Number(bonus)) : '–'}</Text>
9799
),
98100
},
99101
{
100102
title: t('tableHeaders.reimbursements'),
101103
render: ({ reimbursement }) => (
102-
<Text>{formatNumberAsCurrency(reimbursement, locale)}</Text>
104+
<Text>{reimbursement ? currencyFormatter(Number(reimbursement)) : '–'}</Text>
103105
),
104106
},
105107
{
106108
title: t('tableHeaders.total'),
107-
render: ({ total }) => <Text>{formatNumberAsCurrency(total, locale)}</Text>,
108-
},
109-
{
110-
title: t('tableHeaders.action'),
111-
render: ({ id, name }) => (
112-
<HamburgerMenu
113-
items={[
114-
{
115-
label: t('actions.view'),
116-
onClick: () => {
117-
onViewPayment(id)
118-
},
119-
},
120-
{
121-
label: t('actions.cancel'),
122-
onClick: () => {
123-
onCancelPayment(id)
124-
},
125-
},
126-
]}
127-
triggerLabel={t('tableHeaders.action')}
128-
/>
109+
render: ({ wageTotal, reimbursement, bonus }) => (
110+
<Text>
111+
{wageTotal
112+
? currencyFormatter(
113+
Number(wageTotal) + Number(reimbursement) + Number(bonus),
114+
)
115+
: '–'}
116+
</Text>
129117
),
130118
},
131119
]}
120+
itemMenu={({ contractorUuid, mayCancel }) => {
121+
const items = [
122+
{
123+
label: t('actions.view'),
124+
onClick: () => {
125+
onViewPayment(contractorUuid!)
126+
},
127+
icon: (
128+
<span className={styles.icon}>
129+
<EyeIcon aria-hidden />
130+
</span>
131+
),
132+
},
133+
]
134+
135+
if (mayCancel) {
136+
items.push({
137+
label: t('actions.cancel'),
138+
onClick: () => {
139+
onCancelPayment(contractorUuid!)
140+
},
141+
icon: (
142+
<span className={styles.icon}>
143+
<CancelIcon aria-hidden />
144+
</span>
145+
),
146+
})
147+
}
148+
return <HamburgerMenu items={items} triggerLabel={t('tableHeaders.action')} />
149+
}}
132150
data={payments}
133151
label={t('title')}
134152
/>
135-
136-
<Flex>
137-
<Button onClick={onBack} variant="secondary">
138-
{t('backButton')}
139-
</Button>
140-
</Flex>
141153
</>
142154
)}
143155
</Flex>

src/components/Contractor/Payments/PaymentsList/PaymentsListPresentation.tsx

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,20 @@ export const PaymentsListPresentation = ({
5858
title: t('wageTotalColumnLabel'),
5959
render: ({ totals }) => <Text>{currencyFormatter(Number(totals?.wageAmount || 0))}</Text>,
6060
},
61-
{
62-
title: t('actionColumnLabel'),
63-
render: ({ uuid }) => (
64-
<Text>
65-
<ButtonIcon
66-
aria-label={t('viewPaymentCta')}
67-
variant="tertiary"
68-
onClick={() => {
69-
onViewPayment(uuid || '')
70-
}}
71-
>
72-
<EyeIcon aria-hidden />
73-
</ButtonIcon>
74-
</Text>
75-
),
76-
},
7761
],
62+
itemMenu: ({ uuid }) => (
63+
<Text>
64+
<ButtonIcon
65+
aria-label={t('viewPaymentCta')}
66+
variant="tertiary"
67+
onClick={() => {
68+
onViewPayment(uuid || '')
69+
}}
70+
>
71+
<EyeIcon aria-hidden />
72+
</ButtonIcon>
73+
</Text>
74+
),
7875
emptyState: () => (
7976
<EmptyData title={t('noPaymentsFound')} description={t('noPaymentsDescription')}>
8077
<ActionsLayout justifyContent="center">

src/i18n/en/Contractor.Payments.PaymentHistory.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
{
22
"title": "Contractor payment history",
3-
"subtitle": "Payments created on {{date}}",
3+
"subtitle": "Payments debited on <strong>{{date}}</strong>",
44
"paymentsSection": "Payments",
55
"breadcrumbLabel": "Payment history",
66
"noPaymentsFound": "No payments found",
77
"noPaymentsDescription": "There are no payments for this date.",
8-
"backButton": "Back",
98
"tableHeaders": {
109
"contractor": "Contractor",
1110
"wageType": "Wage type",
@@ -19,6 +18,9 @@
1918
},
2019
"actions": {
2120
"view": "View",
22-
"cancel": "Cancel"
21+
"cancel": "Cancel payment"
22+
},
23+
"errors": {
24+
"paymentGroupNotFound": "Contractor payment group not found"
2325
}
2426
}

src/shared/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ export const contractorPaymentEvents = {
119119
CONTRACTOR_PAYMENT_CREATED: 'contractor/payments/created',
120120
CONTRACTOR_PAYMENT_SUBMIT: 'contractor/payments/submit',
121121
CONTRACTOR_PAYMENT_VIEW: 'contractor/payments/view',
122+
CONTRACTOR_PAYMENT_VIEW_DETAILS: 'contractor/payments/view/details',
123+
CONTRACTOR_PAYMENT_CANCEL: 'contractor/payments/cancel',
122124
} as const
123125

124126
export const payScheduleEvents = {

0 commit comments

Comments
 (0)