Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/components/Contractor/Payments/Detail/Detail.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { StoryDefault, Story } from '@ladle/react'
import { action } from '@ladle/react'
import { DetailPresentation } from './DetailPresentation'

export default {
title: 'Domain/Contractor/Payments',
} satisfies StoryDefault

export const PaymentDetailDefault: Story = () => {
const mockPayments = [
{
id: '1',
name: 'Fitzgerald, Ella',
hours: 10.0,
wage: 0,
bonus: 0,
reimbursement: 0,
paymentMethod: 'Direct Deposit',
total: 180,
},
{
id: '2',
name: 'Armstrong, Louis',
hours: 0,
wage: 1000,
bonus: 0,
reimbursement: 0,
paymentMethod: 'Direct Deposit',
total: 1000,
},
]

return (
<DetailPresentation
date="Wed, Sep 17, 2025"
payments={mockPayments}
onBack={action('onBack')}
onViewPayment={action('onViewPayment')}
onCancelPayment={action('onCancelPayment')}
/>
)
}

PaymentDetailDefault.meta = {
description:
'Payment Statement Detail showing detailed payment breakdown for a specific date with all contractors and payment components',
}

export const PaymentDetailEmpty: Story = () => {
return (
<DetailPresentation
date="Wed, Sep 20, 2025"
payments={[]}
onBack={action('onBack')}
onViewPayment={action('onViewPayment')}
onCancelPayment={action('onCancelPayment')}
/>
)
}

PaymentDetailEmpty.meta = {
description:
'Payment Statement Detail with no payments on the selected date - displays empty state',
}
136 changes: 136 additions & 0 deletions src/components/Contractor/Payments/Detail/DetailPresentation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useTranslation } from 'react-i18next'
import { DataView, Flex, EmptyData, ActionsLayout } from '@/components/Common'
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
import { HamburgerMenu } from '@/components/Common/HamburgerMenu'
import { useI18n } from '@/i18n'
import { formatNumberAsCurrency } from '@/helpers/formattedStrings'
import { useLocale } from '@/contexts/LocaleProvider/useLocale'
import { formatHoursDisplay } from '@/components/Payroll/helpers'

interface PaymentData {
id: string
name: string
hours: number
wage: number
bonus: number
reimbursement: number
paymentMethod: string
total: number
}

interface ContractorPaymentDetailPresentationProps {
date: string
payments: PaymentData[]
onBack: () => void
onViewPayment: (paymentId: string) => void
onCancelPayment: (paymentId: string) => void
}

export const DetailPresentation = ({
date,
payments,
onBack,
onViewPayment,
onCancelPayment,
}: ContractorPaymentDetailPresentationProps) => {
const { Button, Text, Heading } = useComponentContext()
useI18n('ContractorPayment.ContractorPaymentDetail')
const { t } = useTranslation('ContractorPayment.ContractorPaymentDetail')
const { locale } = useLocale()

return (
<Flex flexDirection="column" gap={32}>
<Heading as="h1">{t('title')}</Heading>

<Flex flexDirection="column" gap={16}>
<Heading as="h2">{t('paymentsOnDateTitle', { date })}</Heading>

{payments.length === 0 ? (
<EmptyData title={t('noPaymentsFound')} description={t('noPaymentsDescription')}>
<ActionsLayout justifyContent="center">
<Button variant="primary" onClick={onBack}>
{t('backButton')}
</Button>
</ActionsLayout>
</EmptyData>
) : (
<>
<DataView
columns={[
{
title: t('tableHeaders.contractor'),
render: ({ name, id }) => (
<Button
variant="tertiary"
onClick={() => {
onViewPayment(id)
}}
>
{name}
</Button>
),
},
{
title: t('tableHeaders.hours'),
render: ({ hours }) => <Text>{formatHoursDisplay(hours)}</Text>,
},
{
title: t('tableHeaders.wage'),
render: ({ wage }) => <Text>{formatNumberAsCurrency(wage, locale)}</Text>,
},
{
title: t('tableHeaders.bonus'),
render: ({ bonus }) => <Text>{formatNumberAsCurrency(bonus, locale)}</Text>,
},
{
title: t('tableHeaders.reimbursement'),
render: ({ reimbursement }) => (
<Text>{formatNumberAsCurrency(reimbursement, locale)}</Text>
),
},
{
title: t('tableHeaders.paymentMethod'),
render: ({ paymentMethod }) => <Text>{paymentMethod}</Text>,
},
{
title: t('tableHeaders.total'),
render: ({ total }) => <Text>{formatNumberAsCurrency(total, locale)}</Text>,
},
{
title: t('tableHeaders.action'),
render: ({ id, name }) => (
<HamburgerMenu
items={[
{
label: t('actions.view'),
onClick: () => {
onViewPayment(id)
},
},
{
label: t('actions.cancel'),
onClick: () => {
onCancelPayment(id)
},
},
]}
triggerLabel={t('tableHeaders.action')}
/>
),
},
]}
data={payments}
label={t('title')}
/>

<Flex>
<Button onClick={onBack} variant="secondary">
{t('backButton')}
</Button>
</Flex>
</>
)}
</Flex>
</Flex>
)
}
7 changes: 7 additions & 0 deletions src/components/Contractor/Payments/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { ContractorPaymentGroup } from '@gusto/embedded-api/models/components/contractorpaymentgroup'
import type { ContractorPaymentForGroup } from '@gusto/embedded-api/models/components/contractorpaymentforgroup'
import type { ContractorPaymentGroupTotals } from '@gusto/embedded-api/models/components/contractorpaymentgroup'

export type { ContractorPaymentGroup, ContractorPaymentForGroup, ContractorPaymentGroupTotals }

export type ContractorPaymentGroupMinimal = Omit<ContractorPaymentGroup, 'contractorPayments'>
27 changes: 27 additions & 0 deletions src/i18n/en/ContractorPayment.ContractorPaymentDetail.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"title": "Contractor payment history",
"paymentsOnDateTitle": "Payments done on {{date}}",
"noPaymentsFound": "No payments found",
"noPaymentsDescription": "No payments were found for this date",
"tableHeaders": {
"contractor": "Contractor",
"hours": "Hours",
"wage": "Wage",
"bonus": "Bonus",
"reimbursement": "Reimbursement",
"paymentMethod": "Payment method",
"total": "Total",
"action": "Action"
},
"actions": {
"view": "View",
"cancel": "Cancel"
},
"cancelConfirmation": "Canceling a contractor payment cannot be undone. A new payment will have to be created if you want to pay this contractor. Are you sure?",
"backButton": "Back",
"paymentMethods": {
"directDeposit": "Direct Deposit",
"check": "Check",
"historicalPayment": "Historical Payment"
}
}
29 changes: 28 additions & 1 deletion src/types/i18next.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,33 @@ export interface ContractorSubmit{
"successMessage":string;
};
};
export interface ContractorPaymentContractorPaymentDetail{
"title":string;
"paymentsOnDateTitle":string;
"noPaymentsFound":string;
"noPaymentsDescription":string;
"tableHeaders":{
"contractor":string;
"hours":string;
"wage":string;
"bonus":string;
"reimbursement":string;
"paymentMethod":string;
"total":string;
"action":string;
};
"actions":{
"view":string;
"cancel":string;
};
"cancelConfirmation":string;
"backButton":string;
"paymentMethods":{
"directDeposit":string;
"check":string;
"historicalPayment":string;
};
};
export interface EmployeeBankAccount{
"accountNumberLabel":string;
"accountTypeChecking":string;
Expand Down Expand Up @@ -1608,6 +1635,6 @@ export interface common{

interface CustomTypeOptions {
defaultNS: 'common';
resources: { 'Company.AddBank': CompanyAddBank, 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.Landing': EmployeeLanding, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Payroll.PayrollBlocker': PayrollPayrollBlocker, 'Payroll.PayrollConfiguration': PayrollPayrollConfiguration, 'Payroll.PayrollEditEmployee': PayrollPayrollEditEmployee, 'Payroll.PayrollFlow': PayrollPayrollFlow, 'Payroll.PayrollHistory': PayrollPayrollHistory, 'Payroll.PayrollLanding': PayrollPayrollLanding, 'Payroll.PayrollList': PayrollPayrollList, 'Payroll.PayrollOverview': PayrollPayrollOverview, 'Payroll.PayrollReceipts': PayrollPayrollReceipts, 'Payroll.PayrollSchedule': PayrollPayrollSchedule, 'common': common, }
resources: { 'Company.AddBank': CompanyAddBank, 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'ContractorPayment.ContractorPaymentDetail': ContractorPaymentContractorPaymentDetail, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.Landing': EmployeeLanding, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Payroll.PayrollBlocker': PayrollPayrollBlocker, 'Payroll.PayrollConfiguration': PayrollPayrollConfiguration, 'Payroll.PayrollEditEmployee': PayrollPayrollEditEmployee, 'Payroll.PayrollFlow': PayrollPayrollFlow, 'Payroll.PayrollHistory': PayrollPayrollHistory, 'Payroll.PayrollLanding': PayrollPayrollLanding, 'Payroll.PayrollList': PayrollPayrollList, 'Payroll.PayrollOverview': PayrollPayrollOverview, 'Payroll.PayrollReceipts': PayrollPayrollReceipts, 'Payroll.PayrollSchedule': PayrollPayrollSchedule, 'common': common, }
};
}