Skip to content

Commit

Permalink
feat: add new payments list tab in invoices
Browse files Browse the repository at this point in the history
  • Loading branch information
keellyp committed Jan 28, 2025
1 parent 678ae5f commit bbdbe87
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 5 deletions.
182 changes: 182 additions & 0 deletions src/components/invoices/PaymentsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { ApolloError, gql, LazyQueryHookOptions } from '@apollo/client'
import { FC, useEffect, useRef } from 'react'
import { useSearchParams } from 'react-router-dom'

import { addToast } from '~/core/apolloClient'
import { paymentStatusMapping } from '~/core/constants/statusInvoiceMapping'
import { intlFormatNumber } from '~/core/formats/intlFormatNumber'
import { deserializeAmount } from '~/core/serializers/serializeAmount'
import { formatDateToTZ } from '~/core/timezone'
import { copyToClipboard } from '~/core/utils/copyToClipboard'
import {
CurrencyEnum,
GetPaymentRequestsListQuery,
GetPaymentRequestsListQueryHookResult,
PaymentRequestsForPaymentsListFragment,
} from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'

import { InfiniteScroll, Status, Typography } from '../designSystem'
import { Table } from '../designSystem/Table'

gql`
fragment PaymentRequestsForPaymentsList on PaymentRequest {
id
paymentStatus
invoices {
id
status
}
amountCents
amountCurrency
customer {
name
displayName
applicableTimezone
}
createdAt
}
`

interface PaymentsListProps {
isLoading: boolean
paymentRequests?: PaymentRequestsForPaymentsListFragment[]
metadata?: GetPaymentRequestsListQuery['paymentRequests']['metadata']
error?: ApolloError
variables?: LazyQueryHookOptions['variables']
fetchMore?: GetPaymentRequestsListQueryHookResult['fetchMore']
}

export const PaymentsList: FC<PaymentsListProps> = ({
isLoading,
paymentRequests,
metadata,
error,
variables,
fetchMore,
}) => {
const [searchParams] = useSearchParams()
const { translate } = useInternationalization()

const listContainerElementRef = useRef<HTMLDivElement>(null)

useEffect(() => {
// Scroll to top of list when switching tabs
listContainerElementRef?.current?.scrollTo({ top: 0 })
}, [searchParams])

return (
<div ref={listContainerElementRef}>
<InfiniteScroll
onBottom={() => {
const { currentPage = 0, totalPages = 0 } = metadata || {}

currentPage < totalPages &&
!isLoading &&
fetchMore?.({
variables: { page: currentPage + 1 },
})
}}
>
<Table
name="payments-list"
data={paymentRequests || []}
containerSize={{
default: 16,
md: 48,
}}
isLoading={isLoading}
hasError={!!error}
actionColumn={() => {
return [
{
startIcon: 'duplicate',
title: translate('text_1737029625089rtcf3ah5khq'),
onAction: ({ id }) => {
copyToClipboard(id)
addToast({
severity: 'info',
translateKey: translate('text_17370296250897n2pakp5v33'),
})
},
},
]
}}
columns={[
{
key: 'paymentStatus',
title: translate('text_63ac86d797f728a87b2f9fa7'),
minWidth: 80,
content: ({ paymentStatus }) => (
<Status {...paymentStatusMapping({ paymentStatus })} />
),
},
{
key: 'invoices.length',
title: translate('text_63ac86d797f728a87b2f9fad'),
minWidth: 160,
content: ({ invoices }) => {
if (invoices.length > 1) {
return translate('text_17370296250898eqj4qe4qg9', { count: invoices.length })
}
return invoices[0]?.id
},
},
{
key: 'amountCents',
title: translate('text_6419c64eace749372fc72b3e'),
content: ({ amountCents, amountCurrency }) => (
<Typography variant="bodyHl" color="textSecondary" noWrap>
{intlFormatNumber(
deserializeAmount(amountCents, amountCurrency || CurrencyEnum.Usd),
{
currency: amountCurrency || CurrencyEnum.Usd,
},
)}
</Typography>
),
},
{
key: 'customer.displayName',
title: translate('text_63ac86d797f728a87b2f9fb3'),
maxSpace: true,
content: ({ customer }) => customer?.displayName || customer?.name,
},
{
key: 'createdAt',
title: translate('text_664cb90097bfa800e6efa3f5'),
content: ({ createdAt, customer }) =>
formatDateToTZ(createdAt, customer.applicableTimezone),
},
]}
placeholder={{
errorState: variables?.searchTerm
? {
title: translate('text_623b53fea66c76017eaebb6e'),
subtitle: translate('text_63bab307a61c62af497e0599'),
}
: {
title: translate('text_63ac86d797f728a87b2f9fea'),
subtitle: translate('text_63ac86d797f728a87b2f9ff2'),
buttonTitle: translate('text_63ac86d797f728a87b2f9ffa'),
buttonAction: () => location.reload(),
buttonVariant: 'primary',
},
emptyState: variables?.searchTerm
? {
title: translate('text_623b53fea66c76017eaebb6e'),
subtitle: translate('text_63bab307a61c62af497e0599'),
}
: {
title: translate('text_63ac86d797f728a87b2f9fea'),
subtitle: translate('text_63ac86d797f728a87b2f9ff2'),
buttonTitle: translate('text_63ac86d797f728a87b2f9ffa'),
buttonAction: () => location.reload(),
buttonVariant: 'primary',
},
}}
/>
</InfiniteScroll>
</div>
)
}
76 changes: 76 additions & 0 deletions src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7223,6 +7223,8 @@ export type CustomerMetadatasForInvoiceOverviewFragment = { __typename?: 'Custom

export type InvoiceMetadatasForInvoiceOverviewFragment = { __typename?: 'Invoice', id: string, metadata?: Array<{ __typename?: 'InvoiceMetadata', id: string, key: string, value: string }> | null };

export type PaymentRequestsForPaymentsListFragment = { __typename?: 'PaymentRequest', id: string, paymentStatus: InvoicePaymentStatusTypeEnum, amountCents: any, amountCurrency: CurrencyEnum, createdAt: any, invoices: Array<{ __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum }>, customer: { __typename?: 'Customer', name?: string | null, displayName: string, applicableTimezone: TimezoneEnum } };

export type InvoiceForVoidInvoiceDialogFragment = { __typename?: 'Invoice', id: string, number: string };

export type VoidInvoiceMutationVariables = Exact<{
Expand Down Expand Up @@ -8905,6 +8907,14 @@ export type GetInvoicesListQueryVariables = Exact<{

export type GetInvoicesListQuery = { __typename?: 'Query', invoices: { __typename?: 'InvoiceCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum, taxStatus?: InvoiceTaxStatusTypeEnum | null, paymentStatus: InvoicePaymentStatusTypeEnum, paymentOverdue: boolean, number: string, issuingDate: any, totalAmountCents: any, currency?: CurrencyEnum | null, voidable: boolean, paymentDisputeLostAt?: any | null, taxProviderVoidable: boolean, invoiceType: InvoiceTypeEnum, creditableAmountCents: any, refundableAmountCents: any, associatedActiveWalletPresent: boolean, customer: { __typename?: 'Customer', id: string, name?: string | null, displayName: string, applicableTimezone: TimezoneEnum }, errorDetails?: Array<{ __typename?: 'ErrorDetail', errorCode: ErrorCodesEnum, errorDetails?: string | null }> | null }> } };

export type GetPaymentRequestsListQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']['input']>;
page?: InputMaybe<Scalars['Int']['input']>;
}>;


export type GetPaymentRequestsListQuery = { __typename?: 'Query', paymentRequests: { __typename?: 'PaymentRequestCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'PaymentRequest', id: string, paymentStatus: InvoicePaymentStatusTypeEnum, amountCents: any, amountCurrency: CurrencyEnum, createdAt: any, invoices: Array<{ __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum }>, customer: { __typename?: 'Customer', name?: string | null, displayName: string, applicableTimezone: TimezoneEnum } }> } };

export type GetCreditNotesListQueryVariables = Exact<{
amountFrom?: InputMaybe<Scalars['Int']['input']>;
amountTo?: InputMaybe<Scalars['Int']['input']>;
Expand Down Expand Up @@ -9983,6 +9993,24 @@ export const AddOnForInvoiceEditTaxDialogFragmentDoc = gql`
}
}
${TaxForInvoiceEditTaxDialogFragmentDoc}`;
export const PaymentRequestsForPaymentsListFragmentDoc = gql`
fragment PaymentRequestsForPaymentsList on PaymentRequest {
id
paymentStatus
invoices {
id
status
}
amountCents
amountCurrency
customer {
name
displayName
applicableTimezone
}
createdAt
}
`;
export const InvoiceForVoidInvoiceDialogFragmentDoc = gql`
fragment InvoiceForVoidInvoiceDialog on Invoice {
id
Expand Down Expand Up @@ -23635,6 +23663,54 @@ export type GetInvoicesListQueryHookResult = ReturnType<typeof useGetInvoicesLis
export type GetInvoicesListLazyQueryHookResult = ReturnType<typeof useGetInvoicesListLazyQuery>;
export type GetInvoicesListSuspenseQueryHookResult = ReturnType<typeof useGetInvoicesListSuspenseQuery>;
export type GetInvoicesListQueryResult = Apollo.QueryResult<GetInvoicesListQuery, GetInvoicesListQueryVariables>;
export const GetPaymentRequestsListDocument = gql`
query getPaymentRequestsList($limit: Int, $page: Int) {
paymentRequests(limit: $limit, page: $page) {
metadata {
currentPage
totalPages
totalCount
}
collection {
...PaymentRequestsForPaymentsList
}
}
}
${PaymentRequestsForPaymentsListFragmentDoc}`;

/**
* __useGetPaymentRequestsListQuery__
*
* To run a query within a React component, call `useGetPaymentRequestsListQuery` and pass it any options that fit your needs.
* When your component renders, `useGetPaymentRequestsListQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetPaymentRequestsListQuery({
* variables: {
* limit: // value for 'limit'
* page: // value for 'page'
* },
* });
*/
export function useGetPaymentRequestsListQuery(baseOptions?: Apollo.QueryHookOptions<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>(GetPaymentRequestsListDocument, options);
}
export function useGetPaymentRequestsListLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>(GetPaymentRequestsListDocument, options);
}
export function useGetPaymentRequestsListSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>(GetPaymentRequestsListDocument, options);
}
export type GetPaymentRequestsListQueryHookResult = ReturnType<typeof useGetPaymentRequestsListQuery>;
export type GetPaymentRequestsListLazyQueryHookResult = ReturnType<typeof useGetPaymentRequestsListLazyQuery>;
export type GetPaymentRequestsListSuspenseQueryHookResult = ReturnType<typeof useGetPaymentRequestsListSuspenseQuery>;
export type GetPaymentRequestsListQueryResult = Apollo.QueryResult<GetPaymentRequestsListQuery, GetPaymentRequestsListQueryVariables>;
export const GetCreditNotesListDocument = gql`
query getCreditNotesList($amountFrom: Int, $amountTo: Int, $creditStatus: [CreditNoteCreditStatusEnum!], $currency: CurrencyEnum, $customerExternalId: String, $invoiceNumber: String, $issuingDateFrom: ISO8601Date, $issuingDateTo: ISO8601Date, $reason: [CreditNoteReasonEnum!], $refundStatus: [CreditNoteRefundStatusEnum!], $limit: Int, $page: Int, $searchTerm: String) {
creditNotes(
Expand Down
Loading

0 comments on commit bbdbe87

Please sign in to comment.