From 50e82e1a2655b684f303b603fc783328d7fd534b Mon Sep 17 00:00:00 2001 From: Alexandre Monjol Date: Mon, 27 May 2024 18:16:03 +0200 Subject: [PATCH] misc: refactor invoice list and extract code in separate component --- src/components/designSystem/Button.tsx | 3 + .../invoices}/InvoicesList.tsx | 343 +++++++----------- src/core/router/ObjectsRoutes.tsx | 8 +- src/generated/graphql.tsx | 211 +++++++---- src/layouts/SideNavLayout.tsx | 2 +- src/pages/InvoicesPage.tsx | 256 +++++++++++++ 6 files changed, 546 insertions(+), 277 deletions(-) rename src/{pages => components/invoices}/InvoicesList.tsx (53%) create mode 100644 src/pages/InvoicesPage.tsx diff --git a/src/components/designSystem/Button.tsx b/src/components/designSystem/Button.tsx index 6d07ee651..16ed3e92e 100644 --- a/src/components/designSystem/Button.tsx +++ b/src/components/designSystem/Button.tsx @@ -70,6 +70,9 @@ const mapProperties = (variant: ButtonVariant, inheritColor: boolean) => { return { color: 'inherit' as MuiColor, variant: 'outlined' as MuiVariant, + sx: { + borderColor: 'inherit', + }, } case ButtonVariantEnum.quaternary: case ButtonVariantEnum['quaternary-light']: diff --git a/src/pages/InvoicesList.tsx b/src/components/invoices/InvoicesList.tsx similarity index 53% rename from src/pages/InvoicesList.tsx rename to src/components/invoices/InvoicesList.tsx index a12ecca44..0353bee3c 100644 --- a/src/pages/InvoicesList.tsx +++ b/src/components/invoices/InvoicesList.tsx @@ -1,9 +1,9 @@ -import { gql } from '@apollo/client' +import { ApolloError, LazyQueryHookOptions } from '@apollo/client' import { useEffect, useRef } from 'react' -import { generatePath, useNavigate, useParams } from 'react-router-dom' -import styled from 'styled-components' +import { generatePath, useNavigate } from 'react-router-dom' +import styled, { css } from 'styled-components' -import { Button, InfiniteScroll, NavigationTab, Typography } from '~/components/designSystem' +import { Button, InfiniteScroll, Typography } from '~/components/designSystem' import { GenericPlaceholder } from '~/components/GenericPlaceholder' import { UpdateInvoicePaymentStatusDialog, @@ -20,123 +20,44 @@ import { InvoiceListItemSkeleton, } from '~/components/invoices/InvoiceListItem' import { VoidInvoiceDialog, VoidInvoiceDialogRef } from '~/components/invoices/VoidInvoiceDialog' -import { SearchInput } from '~/components/SearchInput' -import { addToast, hasDefinedGQLError } from '~/core/apolloClient' -import { - CUSTOMER_INVOICE_DETAILS_ROUTE, - INVOICE_SETTINGS_ROUTE, - INVOICES_ROUTE, - INVOICES_TAB_ROUTE, -} from '~/core/router' -import { - InvoiceListItemFragmentDoc, - InvoicePaymentStatusTypeEnum, - InvoiceStatusTypeEnum, - LagoApiError, - useInvoicesListLazyQuery, - useRetryAllInvoicePaymentsMutation, -} from '~/generated/graphql' +import { CUSTOMER_INVOICE_DETAILS_ROUTE, INVOICE_SETTINGS_ROUTE } from '~/core/router' +import { InvoicesPageQuery } from '~/generated/graphql' import { useInternationalization } from '~/hooks/core/useInternationalization' import { useListKeysNavigation } from '~/hooks/ui/useListKeyNavigation' -import { useDebouncedSearch } from '~/hooks/useDebouncedSearch' import { CustomerInvoiceDetailsTabsOptionsEnum } from '~/layouts/CustomerInvoiceDetails' +import { InvoiceListStatusEnum } from '~/pages/InvoicesPage' import EmptyImage from '~/public/images/maneki/empty.svg' import ErrorImage from '~/public/images/maneki/error.svg' -import { ListContainer, ListHeader, NAV_HEIGHT, PageHeader, theme } from '~/styles' - -gql` - query invoicesList( - $limit: Int - $page: Int - $status: InvoiceStatusTypeEnum - $paymentStatus: [InvoicePaymentStatusTypeEnum!] - $searchTerm: String - $paymentDisputeLost: Boolean - ) { - invoices( - limit: $limit - page: $page - status: $status - paymentStatus: $paymentStatus - searchTerm: $searchTerm - paymentDisputeLost: $paymentDisputeLost - ) { - metadata { - currentPage - totalPages - totalCount - } - collection { - id - ...InvoiceListItem - } - } - } - - mutation retryAllInvoicePayments($input: RetryAllInvoicePaymentsInput!) { - retryAllInvoicePayments(input: $input) { - collection { - id - } - } - } - - ${InvoiceListItemFragmentDoc} -` - -enum InvoiceListTabEnum { - 'all' = 'all', - 'draft' = 'draft', - 'pendingFailed' = 'pendingFailed', - 'succeeded' = 'succeeded', - 'voided' = 'voided', - 'disputed' = 'disputed', -} +import { ListContainer, ListHeader, NAV_HEIGHT, palette, theme } from '~/styles' // Needed to be able to pass both ids to the keyboard navigation function const ID_SPLIT_KEY = '&-%-&' const NAVIGATION_KEY_BASE = 'invoice-item-' -const InvoicesList = () => { - const { tab } = useParams<{ tab?: InvoiceListTabEnum }>() +type TInvoiceListProps = { + error: ApolloError | undefined + fetchMore: Function + invoices: InvoicesPageQuery['invoices']['collection'] | undefined + invoiceType: InvoiceListStatusEnum + isLoading: boolean + metadata: InvoicesPageQuery['invoices']['metadata'] | undefined + variables: LazyQueryHookOptions['variables'] | undefined +} + +const InvoicesList = ({ + error, + fetchMore, + invoices, + invoiceType, + isLoading, + metadata, + variables, +}: TInvoiceListProps) => { const { translate } = useInternationalization() const navigate = useNavigate() const finalizeInvoiceRef = useRef(null) const updateInvoicePaymentStatusDialog = useRef(null) const voidInvoiceDialogRef = useRef(null) - const [getInvoices, { data, loading, error, fetchMore, variables }] = useInvoicesListLazyQuery({ - notifyOnNetworkStatusChange: true, - fetchPolicy: 'network-only', - nextFetchPolicy: 'network-only', - variables: { - limit: 20, - ...(tab === InvoiceListTabEnum.draft && { status: InvoiceStatusTypeEnum.Draft }), - ...(tab === InvoiceListTabEnum.voided && { status: InvoiceStatusTypeEnum.Voided }), - ...(tab === InvoiceListTabEnum.pendingFailed && { - status: InvoiceStatusTypeEnum.Finalized, - paymentStatus: [InvoicePaymentStatusTypeEnum.Failed, InvoicePaymentStatusTypeEnum.Pending], - }), - ...(tab === InvoiceListTabEnum.succeeded && { - paymentStatus: InvoicePaymentStatusTypeEnum.Succeeded, - status: InvoiceStatusTypeEnum.Finalized, - }), - ...(tab === InvoiceListTabEnum.disputed && { - paymentDisputeLost: true, - }), - }, - }) - const { debouncedSearch, isLoading } = useDebouncedSearch(getInvoices, loading) - const [retryAll] = useRetryAllInvoicePaymentsMutation({ - context: { silentErrorCodes: [LagoApiError.PaymentProcessorIsCurrentlyHandlingPayment] }, - onCompleted({ retryAllInvoicePayments }) { - if (retryAllInvoicePayments) { - addToast({ - severity: 'success', - translateKey: 'text_63ac86d897f728a87b2fa0a7', - }) - } - }, - }) const { onKeyDown } = useListKeysNavigation({ getElmId: (i) => `${NAVIGATION_KEY_BASE}${i}`, navigate: (id) => { @@ -156,73 +77,68 @@ const InvoicesList = () => { useEffect(() => { // Scroll to top of list when switching tabs listContainerElementRef?.current?.scrollTo({ top: 0 }) - }, [tab]) - let index = -1 + }, [invoiceType]) return ( -
- - - {translate('text_63ac86d797f728a87b2f9f85')} - - - - - {tab === InvoiceListTabEnum.pendingFailed && ( - - )} - - - - + <> + + navigate({ search: `?invoiceType=${InvoiceListStatusEnum.all}` })} + > + {translate('text_63ac86d797f728a87b2f9f8b')} + + navigate({ search: `?invoiceType=${InvoiceListStatusEnum.draft}` })} + > + {translate('text_63ac86d797f728a87b2f9f91')} + + + navigate({ search: `?invoiceType=${InvoiceListStatusEnum.pendingFailed}` }) + } + > + {translate('text_63ac86d797f728a87b2f9f97')} + + navigate({ search: `?invoiceType=${InvoiceListStatusEnum.succeeded}` })} + > + {translate('text_63ac86d797f728a87b2f9fa1')} + + navigate({ search: `?invoiceType=${InvoiceListStatusEnum.voided}` })} + > + {translate('text_6376641a2a9c70fff5bddcd5')} + + navigate({ search: `?invoiceType=${InvoiceListStatusEnum.disputed}` })} + > + {translate('text_66141e30699a0631f0b2ed32')} + + + @@ -269,18 +185,18 @@ const InvoicesList = () => { /> )} - ) : !isLoading && !data?.invoices?.collection?.length ? ( + ) : !isLoading && !invoices?.length ? ( <> {!!variables?.searchTerm ? ( { ) : ( - ) : tab === InvoiceListTabEnum.pendingFailed ? ( + ) : invoiceType === InvoiceListStatusEnum.pendingFailed ? ( translate('text_63b578e959c1366df5d14570') - ) : tab === InvoiceListTabEnum.voided ? ( + ) : invoiceType === InvoiceListStatusEnum.voided ? ( translate('text_65269cd46e7ec037a6823fda') - ) : tab === InvoiceListTabEnum.disputed ? ( + ) : invoiceType === InvoiceListStatusEnum.disputed ? ( translate('text_66141e30699a0631f0b2ec87') ) : ( translate('text_63b578e959c1366df5d1456d') @@ -328,7 +244,7 @@ const InvoicesList = () => { ) : ( { - const { currentPage = 0, totalPages = 0 } = data?.invoices?.metadata || {} + const { currentPage = 0, totalPages = 0 } = metadata || {} currentPage < totalPages && !isLoading && @@ -337,9 +253,7 @@ const InvoicesList = () => { }) }} > - {data?.invoices?.collection.map((invoice) => { - index += 1 - + {invoices?.map((invoice, index) => { return ( { )} - - - - -
+ + + + + ) } export default InvoicesList -const HeaderRigthBlock = styled.div` - display: flex; - align-items: center; - - > :first-child { - margin-right: ${theme.spacing(3)}; - } -` - const ScrollContainer = styled.div` overflow: auto; height: calc(100vh - ${NAV_HEIGHT * 2}px); @@ -412,3 +317,33 @@ const CustomerName = styled(Typography)` display: none; } ` + +const TabSwitchContainer = styled.div` + width: 100%; + display: flex; + align-items: center; + flex-wrap: wrap; + padding: ${theme.spacing(4)} ${theme.spacing(12)}; + box-sizing: border-box; + gap: ${theme.spacing(3)}; + + ${theme.breakpoints.down('md')} { + padding: ${theme.spacing(4)}; + } +` + +const InvoiceTypeSwitch = styled(Button)<{ $isSelected: boolean }>` + flex-grow: 1; + height: 44px; + + ${({ $isSelected }) => + $isSelected && + css` + color: ${palette.primary.main}; + `}; + + ${theme.breakpoints.down('md')} { + width: fit-content; + flex-grow: 0; + } +` diff --git a/src/core/router/ObjectsRoutes.tsx b/src/core/router/ObjectsRoutes.tsx index 50cd809ec..e1e0ded4c 100644 --- a/src/core/router/ObjectsRoutes.tsx +++ b/src/core/router/ObjectsRoutes.tsx @@ -13,8 +13,8 @@ const CouponsList = lazyLoad( const AddOnsList = lazyLoad( () => import(/* webpackChunkName: 'add-ons-list' */ '~/pages/AddOnsList'), ) -const InvoicesList = lazyLoad( - () => import(/* webpackChunkName: 'invoices-list' */ '~/pages/InvoicesList'), +const InvoicesPage = lazyLoad( + () => import(/* webpackChunkName: 'invoices-page' */ '~/pages/InvoicesPage'), ) // Creation @@ -128,8 +128,8 @@ export const objectListRoutes: CustomRouteObject[] = [ { path: [INVOICES_ROUTE, INVOICES_TAB_ROUTE], private: true, - element: , - permissions: ['invoicesView'], + element: , + permissions: ['invoicesView', 'creditNotesView'], }, ] diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index 865733d3f..2cd469e2b 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -1153,6 +1153,8 @@ export type CreatePlanInput = { export type CreateRecurringTransactionRuleInput = { interval?: InputMaybe; + method?: InputMaybe; + targetOngoingBalance?: InputMaybe; thresholdCredits?: InputMaybe; trigger: RecurringTransactionTriggerEnum; }; @@ -2231,6 +2233,7 @@ export type IntegrationCustomerInput = { externalCustomerId?: InputMaybe; id?: InputMaybe; integrationCode?: InputMaybe; + integrationId?: InputMaybe; integrationType?: InputMaybe; subsidiaryId?: InputMaybe; syncWithProvider?: InputMaybe; @@ -2299,10 +2302,12 @@ export type Invoice = { creditableAmountCents: Scalars['BigInt']['output']; currency?: Maybe; customer: Customer; + externalIntegrationId?: Maybe; fees?: Maybe>; feesAmountCents: Scalars['BigInt']['output']; fileUrl?: Maybe; id: Scalars['ID']['output']; + integrationSyncable: Scalars['Boolean']['output']; invoiceSubscriptions?: Maybe>; invoiceType: InvoiceTypeEnum; issuingDate: Scalars['ISO8601Date']['output']; @@ -2531,7 +2536,7 @@ export type Membership = { export type MembershipCollection = { __typename?: 'MembershipCollection'; collection: Array; - metadata: CollectionMetadata; + metadata: Metadata; }; export enum MembershipRole { @@ -2545,6 +2550,15 @@ export enum MembershipStatus { Revoked = 'revoked' } +export type Metadata = { + __typename?: 'Metadata'; + adminCount: Scalars['Int']['output']; + currentPage: Scalars['Int']['output']; + limitValue: Scalars['Int']['output']; + totalCount: Scalars['Int']['output']; + totalPages: Scalars['Int']['output']; +}; + export type Mrr = { __typename?: 'Mrr'; amountCents?: Maybe; @@ -2672,10 +2686,12 @@ export type Mutation = { retryInvoicePayment?: Maybe; /** Retry a Webhook */ retryWebhook?: Maybe; - /** Revokes a invite */ + /** Revokes an invite */ revokeInvite?: Maybe; /** Revoke a membership */ revokeMembership?: Maybe; + /** Sync integration invoice */ + syncIntegrationInvoice?: Maybe; /** Unassign a coupon from a customer */ terminateAppliedCoupon?: Maybe; /** Deletes a coupon */ @@ -2706,8 +2722,12 @@ export type Mutation = { updateIntegrationCollectionMapping?: Maybe; /** Update integration mapping */ updateIntegrationMapping?: Maybe; + /** Update an invite */ + updateInvite?: Maybe; /** Update an existing invoice */ updateInvoice?: Maybe; + /** Update a membership */ + updateMembership?: Maybe; /** Update Netsuite integration */ updateNetsuiteIntegration?: Maybe; /** Update Okta integration */ @@ -3026,6 +3046,11 @@ export type MutationRevokeMembershipArgs = { }; +export type MutationSyncIntegrationInvoiceArgs = { + input: SyncIntegrationInvoiceInput; +}; + + export type MutationTerminateAppliedCouponArgs = { input: TerminateAppliedCouponInput; }; @@ -3101,11 +3126,21 @@ export type MutationUpdateIntegrationMappingArgs = { }; +export type MutationUpdateInviteArgs = { + input: UpdateInviteInput; +}; + + export type MutationUpdateInvoiceArgs = { input: UpdateInvoiceInput; }; +export type MutationUpdateMembershipArgs = { + input: UpdateMembershipInput; +}; + + export type MutationUpdateNetsuiteIntegrationArgs = { input: UpdateNetsuiteIntegrationInput; }; @@ -3160,6 +3195,7 @@ export type NetsuiteCustomer = { externalCustomerId?: Maybe; id: Scalars['ID']['output']; integrationCode?: Maybe; + integrationId?: Maybe; integrationType?: Maybe; subsidiaryId?: Maybe; syncWithProvider?: Maybe; @@ -3879,13 +3915,20 @@ export enum RecurringTransactionIntervalEnum { Yearly = 'yearly' } +export enum RecurringTransactionMethodEnum { + Fixed = 'fixed', + Target = 'target' +} + export type RecurringTransactionRule = { __typename?: 'RecurringTransactionRule'; createdAt: Scalars['ISO8601DateTime']['output']; grantedCredits: Scalars['String']['output']; interval?: Maybe; lagoId: Scalars['ID']['output']; + method: RecurringTransactionMethodEnum; paidCredits: Scalars['String']['output']; + targetOngoingBalance?: Maybe; thresholdCredits?: Maybe; trigger: RecurringTransactionTriggerEnum; }; @@ -4028,6 +4071,21 @@ export type SubsidiaryCollection = { metadata: CollectionMetadata; }; +/** Autogenerated input type of SyncIntegrationInvoice */ +export type SyncIntegrationInvoiceInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + invoiceId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of SyncIntegrationInvoice */ +export type SyncIntegrationInvoicePayload = { + __typename?: 'SyncIntegrationInvoicePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + invoiceId?: Maybe; +}; + export type Tax = { __typename?: 'Tax'; /** Number of add ons using this tax */ @@ -4534,6 +4592,14 @@ export type UpdateIntegrationMappingInput = { mappableType?: InputMaybe; }; +/** Autogenerated input type of UpdateInvite */ +export type UpdateInviteInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + id: Scalars['ID']['input']; + role: MembershipRole; +}; + /** Update Invoice input arguments */ export type UpdateInvoiceInput = { /** A unique identifier for the client performing the mutation. */ @@ -4543,6 +4609,14 @@ export type UpdateInvoiceInput = { paymentStatus?: InputMaybe; }; +/** Autogenerated input type of UpdateMembership */ +export type UpdateMembershipInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + id: Scalars['ID']['input']; + role: MembershipRole; +}; + /** Autogenerated input type of UpdateNetsuiteIntegration */ export type UpdateNetsuiteIntegrationInput = { accountId?: InputMaybe; @@ -4622,7 +4696,9 @@ export type UpdateRecurringTransactionRuleInput = { grantedCredits?: InputMaybe; interval?: InputMaybe; lagoId?: InputMaybe; + method?: InputMaybe; paidCredits?: InputMaybe; + targetOngoingBalance?: InputMaybe; thresholdCredits?: InputMaybe; trigger?: InputMaybe; }; @@ -4836,7 +4912,7 @@ export enum WeightedIntervalEnum { export type UserIdentifierQueryVariables = Exact<{ [key: string]: never; }>; -export type UserIdentifierQuery = { __typename?: 'Query', me: { __typename?: 'User', id: string, email?: string | null, premium: boolean, memberships: Array<{ __typename?: 'Membership', id: string, organization: { __typename?: 'Organization', id: string, name: string, logoUrl?: string | null }, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesUpdate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }> }, organization?: { __typename?: 'CurrentOrganization', id: string, name: string, logoUrl?: string | null, timezone?: TimezoneEnum | null, defaultCurrency: CurrencyEnum } | null }; +export type UserIdentifierQuery = { __typename?: 'Query', me: { __typename?: 'User', id: string, email?: string | null, premium: boolean, memberships: Array<{ __typename?: 'Membership', id: string, organization: { __typename?: 'Organization', id: string, name: string, logoUrl?: string | null }, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }> }, organization?: { __typename?: 'CurrentOrganization', id: string, name: string, logoUrl?: string | null, timezone?: TimezoneEnum | null, defaultCurrency: CurrencyEnum } | null }; export type AddOnItemFragment = { __typename?: 'AddOn', id: string, name: string, amountCurrency: CurrencyEnum, amountCents: any, customersCount: number, createdAt: any }; @@ -5298,6 +5374,13 @@ export type RetryInvoicePaymentMutationVariables = Exact<{ export type RetryInvoicePaymentMutation = { __typename?: 'Mutation', retryInvoicePayment?: { __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum, paymentStatus: InvoicePaymentStatusTypeEnum, number: string, issuingDate: any, totalAmountCents: any, currency?: CurrencyEnum | null, voidable: boolean, paymentDisputeLostAt?: any | null, customer: { __typename?: 'Customer', id: string, name?: string | null, applicableTimezone: TimezoneEnum } } | null }; +export type RetryAllInvoicePaymentsMutationVariables = Exact<{ + input: RetryAllInvoicePaymentsInput; +}>; + + +export type RetryAllInvoicePaymentsMutation = { __typename?: 'Mutation', retryAllInvoicePayments?: { __typename?: 'InvoiceCollection', collection: Array<{ __typename?: 'Invoice', id: string }> } | null }; + export type CustomerMetadatasForInvoiceOverviewFragment = { __typename?: 'Customer', id: string, metadata?: Array<{ __typename?: 'CustomerMetadata', id: string, displayInInvoice: boolean, key: string, value: string }> | null }; export type InvoiceMetadatasForInvoiceOverviewFragment = { __typename?: 'Invoice', id: string, metadata?: Array<{ __typename?: 'InvoiceMetadata', id: string, key: string, value: string }> | null }; @@ -5946,12 +6029,12 @@ export type UpdateTaxMutationVariables = Exact<{ export type UpdateTaxMutation = { __typename?: 'Mutation', updateTax?: { __typename?: 'Tax', id: string, code: string, description?: string | null, name: string, rate: number, customersCount: number } | null }; -export type CurrentUserInfosFragment = { __typename?: 'User', id: string, email?: string | null, premium: boolean, memberships: Array<{ __typename?: 'Membership', id: string, organization: { __typename?: 'Organization', id: string, name: string, logoUrl?: string | null }, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesUpdate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }> }; +export type CurrentUserInfosFragment = { __typename?: 'User', id: string, email?: string | null, premium: boolean, memberships: Array<{ __typename?: 'Membership', id: string, organization: { __typename?: 'Organization', id: string, name: string, logoUrl?: string | null }, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }> }; export type GetCurrentUserInfosQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserInfosQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, email?: string | null, premium: boolean, memberships: Array<{ __typename?: 'Membership', id: string, organization: { __typename?: 'Organization', id: string, name: string, logoUrl?: string | null }, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesUpdate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }> } }; +export type GetCurrentUserInfosQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, email?: string | null, premium: boolean, memberships: Array<{ __typename?: 'Membership', id: string, organization: { __typename?: 'Organization', id: string, name: string, logoUrl?: string | null }, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }> } }; export type GetEmailSettingsQueryVariables = Exact<{ [key: string]: never; }>; @@ -5972,7 +6055,7 @@ export type GetOrganizationInfosQueryVariables = Exact<{ [key: string]: never; } export type GetOrganizationInfosQuery = { __typename?: 'Query', organization?: { __typename?: 'CurrentOrganization', id: string, name: string, logoUrl?: string | null, timezone?: TimezoneEnum | null, defaultCurrency: CurrencyEnum } | null }; -export type MembershipPermissionsFragment = { __typename?: 'Membership', id: string, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesUpdate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }; +export type MembershipPermissionsFragment = { __typename?: 'Membership', id: string, permissions: { __typename?: 'Permissions', addonsCreate: boolean, addonsDelete: boolean, addonsUpdate: boolean, addonsView: boolean, analyticsView: boolean, billableMetricsCreate: boolean, billableMetricsDelete: boolean, billableMetricsUpdate: boolean, billableMetricsView: boolean, couponsAttach: boolean, couponsCreate: boolean, couponsDelete: boolean, couponsDetach: boolean, couponsUpdate: boolean, couponsView: boolean, creditNotesCreate: boolean, creditNotesView: boolean, creditNotesVoid: boolean, customerSettingsUpdateGracePeriod: boolean, customerSettingsUpdateLang: boolean, customerSettingsUpdatePaymentTerms: boolean, customerSettingsUpdateTaxRates: boolean, customerSettingsView: boolean, customersCreate: boolean, customersDelete: boolean, customersUpdate: boolean, customersView: boolean, developersKeysManage: boolean, developersManage: boolean, draftInvoicesUpdate: boolean, invoicesCreate: boolean, invoicesSend: boolean, invoicesUpdate: boolean, invoicesView: boolean, invoicesVoid: boolean, organizationEmailsUpdate: boolean, organizationEmailsView: boolean, organizationIntegrationsCreate: boolean, organizationIntegrationsDelete: boolean, organizationIntegrationsUpdate: boolean, organizationIntegrationsView: boolean, organizationInvoicesUpdate: boolean, organizationInvoicesView: boolean, organizationMembersCreate: boolean, organizationMembersDelete: boolean, organizationMembersUpdate: boolean, organizationMembersView: boolean, organizationTaxesUpdate: boolean, organizationTaxesView: boolean, organizationUpdate: boolean, organizationView: boolean, plansCreate: boolean, plansDelete: boolean, plansUpdate: boolean, plansView: boolean, subscriptionsCreate: boolean, subscriptionsDelete: boolean, subscriptionsUpdate: boolean, subscriptionsView: boolean, walletsCreate: boolean, walletsTerminate: boolean, walletsTopUp: boolean, walletsUpdate: boolean } }; export type AllInvoiceDetailsForCustomerInvoiceDetailsFragment = { __typename?: 'Invoice', id: string, invoiceType: InvoiceTypeEnum, number: string, paymentStatus: InvoicePaymentStatusTypeEnum, status: InvoiceStatusTypeEnum, totalAmountCents: any, currency?: CurrencyEnum | null, refundableAmountCents: any, creditableAmountCents: any, voidable: boolean, paymentDisputeLostAt?: any | null, issuingDate: any, subTotalExcludingTaxesAmountCents: any, subTotalIncludingTaxesAmountCents: any, versionNumber: number, paymentDueDate: any, couponsAmountCents: any, creditNotesAmountCents: any, prepaidCreditAmountCents: any, customer: { __typename?: 'Customer', id: string, applicableTimezone: TimezoneEnum, currency?: CurrencyEnum | null, name?: string | null, legalNumber?: string | null, legalName?: string | null, taxIdentificationNumber?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, deletedAt?: any | null, metadata?: Array<{ __typename?: 'CustomerMetadata', id: string, displayInInvoice: boolean, key: string, value: string }> | null }, creditNotes?: Array<{ __typename?: 'CreditNote', id: string, couponsAdjustmentAmountCents: any, number: string, subTotalExcludingTaxesAmountCents: any, currency: CurrencyEnum, totalAmountCents: any, appliedTaxes?: Array<{ __typename?: 'CreditNoteAppliedTax', id: string, amountCents: any, baseAmountCents: any, taxRate: number, taxName: string }> | null, items: Array<{ __typename?: 'CreditNoteItem', amountCents: any, amountCurrency: CurrencyEnum, fee: { __typename?: 'Fee', id: string, amountCents: any, eventsCount?: any | null, units: number, feeType: FeeTypesEnum, groupedBy: any, itemName: string, invoiceName?: string | null, appliedTaxes?: Array<{ __typename?: 'FeeAppliedTax', id: string, tax: { __typename?: 'Tax', id: string, rate: number } }> | null, trueUpParentFee?: { __typename?: 'Fee', id: string } | null, charge?: { __typename?: 'Charge', id: string, billableMetric: { __typename?: 'BillableMetric', id: string, name: string, aggregationType: AggregationTypeEnum } } | null, subscription?: { __typename?: 'Subscription', id: string, name?: string | null, plan: { __typename?: 'Plan', id: string, name: string, invoiceDisplayName?: string | null } } | null, chargeFilter?: { __typename?: 'ChargeFilter', invoiceDisplayName?: string | null, values: any } | null } }> }> | null, fees?: Array<{ __typename?: 'Fee', id: string, amountCents: any, description?: string | null, feeType: FeeTypesEnum, invoiceDisplayName?: string | null, invoiceName?: string | null, itemName: string, units: number, preciseUnitAmount: number, eventsCount?: any | null, adjustedFee: boolean, adjustedFeeType?: AdjustedFeeTypeEnum | null, currency: CurrencyEnum, appliedTaxes?: Array<{ __typename?: 'FeeAppliedTax', id: string, taxRate: number }> | null, trueUpFee?: { __typename?: 'Fee', id: string } | null, trueUpParentFee?: { __typename?: 'Fee', id: string } | null, charge?: { __typename?: 'Charge', id: string, payInAdvance: boolean, invoiceDisplayName?: string | null, chargeModel: ChargeModelEnum, minAmountCents: any, prorated: boolean, billableMetric: { __typename?: 'BillableMetric', id: string, name: string, aggregationType: AggregationTypeEnum, recurring: boolean } } | null, chargeFilter?: { __typename?: 'ChargeFilter', invoiceDisplayName?: string | null, values: any } | null, amountDetails?: { __typename?: 'FeeAmountDetails', freeUnits?: string | null, fixedFeeUnitAmount?: string | null, flatUnitAmount?: string | null, perUnitAmount?: string | null, perUnitTotalAmount?: string | null, paidUnits?: string | null, perPackageSize?: number | null, perPackageUnitAmount?: string | null, fixedFeeTotalAmount?: string | null, freeEvents?: number | null, minMaxAdjustmentTotalAmount?: string | null, paidEvents?: number | null, rate?: string | null, units?: string | null, graduatedRanges?: Array<{ __typename?: 'FeeAmountDetailsGraduatedRange', toValue?: any | null, flatUnitAmount?: string | null, fromValue?: any | null, perUnitAmount?: string | null, perUnitTotalAmount?: string | null, totalWithFlatAmount?: string | null, units?: string | null }> | null, graduatedPercentageRanges?: Array<{ __typename?: 'FeeAmountDetailsGraduatedPercentageRange', toValue?: any | null, flatUnitAmount?: string | null, fromValue?: any | null, perUnitTotalAmount?: string | null, rate?: string | null, totalWithFlatAmount?: string | null, units?: string | null }> | null } | null }> | null, invoiceSubscriptions?: Array<{ __typename?: 'InvoiceSubscription', fromDatetime?: any | null, toDatetime?: any | null, chargesFromDatetime?: any | null, chargesToDatetime?: any | null, inAdvanceChargesFromDatetime?: any | null, inAdvanceChargesToDatetime?: any | null, subscription: { __typename?: 'Subscription', id: string, name?: string | null, plan: { __typename?: 'Plan', id: string, name: string, interval: PlanInterval, amountCents: any, amountCurrency: CurrencyEnum, invoiceDisplayName?: string | null } }, fees?: Array<{ __typename?: 'Fee', id: string, amountCents: any, invoiceName?: string | null, invoiceDisplayName?: string | null, units: number, groupedBy: any, description?: string | null, feeType: FeeTypesEnum, itemName: string, preciseUnitAmount: number, eventsCount?: any | null, adjustedFee: boolean, adjustedFeeType?: AdjustedFeeTypeEnum | null, currency: CurrencyEnum, subscription?: { __typename?: 'Subscription', id: string, name?: string | null, plan: { __typename?: 'Plan', id: string, name: string, invoiceDisplayName?: string | null, interval: PlanInterval } } | null, charge?: { __typename?: 'Charge', id: string, payInAdvance: boolean, minAmountCents: any, invoiceDisplayName?: string | null, chargeModel: ChargeModelEnum, prorated: boolean, billableMetric: { __typename?: 'BillableMetric', id: string, name: string, aggregationType: AggregationTypeEnum, recurring: boolean } } | null, chargeFilter?: { __typename?: 'ChargeFilter', invoiceDisplayName?: string | null, values: any } | null, appliedTaxes?: Array<{ __typename?: 'FeeAppliedTax', id: string, taxRate: number }> | null, trueUpFee?: { __typename?: 'Fee', id: string } | null, trueUpParentFee?: { __typename?: 'Fee', id: string } | null, amountDetails?: { __typename?: 'FeeAmountDetails', freeUnits?: string | null, fixedFeeUnitAmount?: string | null, flatUnitAmount?: string | null, perUnitAmount?: string | null, perUnitTotalAmount?: string | null, paidUnits?: string | null, perPackageSize?: number | null, perPackageUnitAmount?: string | null, fixedFeeTotalAmount?: string | null, freeEvents?: number | null, minMaxAdjustmentTotalAmount?: string | null, paidEvents?: number | null, rate?: string | null, units?: string | null, graduatedRanges?: Array<{ __typename?: 'FeeAmountDetailsGraduatedRange', toValue?: any | null, flatUnitAmount?: string | null, fromValue?: any | null, perUnitAmount?: string | null, perUnitTotalAmount?: string | null, totalWithFlatAmount?: string | null, units?: string | null }> | null, graduatedPercentageRanges?: Array<{ __typename?: 'FeeAmountDetailsGraduatedPercentageRange', toValue?: any | null, flatUnitAmount?: string | null, fromValue?: any | null, perUnitTotalAmount?: string | null, rate?: string | null, totalWithFlatAmount?: string | null, units?: string | null }> | null } | null }> | null, invoice: { __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum } }> | null, metadata?: Array<{ __typename?: 'InvoiceMetadata', id: string, key: string, value: string }> | null, appliedTaxes?: Array<{ __typename?: 'InvoiceAppliedTax', id: string, amountCents: any, feesAmountCents: any, taxRate: number, taxName: string }> | null }; @@ -6194,7 +6277,7 @@ export type GetInvoiceCreditNotesQuery = { __typename?: 'Query', invoiceCreditNo export type InvoiceDetailsForInvoiceOverviewFragment = { __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum, issuingDate: any, customer: { __typename?: 'Customer', id: string, applicableTimezone: TimezoneEnum } }; -export type InvoicesListQueryVariables = Exact<{ +export type InvoicesPageQueryVariables = Exact<{ limit?: InputMaybe; page?: InputMaybe; status?: InputMaybe; @@ -6204,14 +6287,7 @@ export type InvoicesListQueryVariables = Exact<{ }>; -export type InvoicesListQuery = { __typename?: 'Query', invoices: { __typename?: 'InvoiceCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum, paymentStatus: InvoicePaymentStatusTypeEnum, number: string, issuingDate: any, totalAmountCents: any, currency?: CurrencyEnum | null, voidable: boolean, paymentDisputeLostAt?: any | null, customer: { __typename?: 'Customer', id: string, name?: string | null, applicableTimezone: TimezoneEnum } }> } }; - -export type RetryAllInvoicePaymentsMutationVariables = Exact<{ - input: RetryAllInvoicePaymentsInput; -}>; - - -export type RetryAllInvoicePaymentsMutation = { __typename?: 'Mutation', retryAllInvoicePayments?: { __typename?: 'InvoiceCollection', collection: Array<{ __typename?: 'Invoice', id: string }> } | null }; +export type InvoicesPageQuery = { __typename?: 'Query', invoices: { __typename?: 'InvoiceCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'Invoice', id: string, status: InvoiceStatusTypeEnum, paymentStatus: InvoicePaymentStatusTypeEnum, number: string, issuingDate: any, totalAmountCents: any, currency?: CurrencyEnum | null, voidable: boolean, paymentDisputeLostAt?: any | null, customer: { __typename?: 'Customer', id: string, name?: string | null, applicableTimezone: TimezoneEnum } }> } }; export type GetPlanForDetailsQueryVariables = Exact<{ planId: Scalars['ID']['input']; @@ -6463,7 +6539,7 @@ export type GetMembersQueryVariables = Exact<{ }>; -export type GetMembersQuery = { __typename?: 'Query', memberships: { __typename?: 'MembershipCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'Membership', id: string, user: { __typename?: 'User', id: string, email?: string | null } }> } }; +export type GetMembersQuery = { __typename?: 'Query', memberships: { __typename?: 'MembershipCollection', metadata: { __typename?: 'Metadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'Membership', id: string, user: { __typename?: 'User', id: string, email?: string | null } }> } }; export type OrganizationInformationsFragment = { __typename?: 'CurrentOrganization', id: string, logoUrl?: string | null, name: string, legalName?: string | null, legalNumber?: string | null, taxIdentificationNumber?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, zipcode?: string | null, city?: string | null, state?: string | null, country?: CountryCode | null, timezone?: TimezoneEnum | null }; @@ -7482,7 +7558,6 @@ export const MembershipPermissionsFragmentDoc = gql` couponsUpdate couponsView creditNotesCreate - creditNotesUpdate creditNotesView creditNotesVoid customerSettingsUpdateGracePeriod @@ -10628,6 +10703,41 @@ export function useRetryInvoicePaymentMutation(baseOptions?: Apollo.MutationHook export type RetryInvoicePaymentMutationHookResult = ReturnType; export type RetryInvoicePaymentMutationResult = Apollo.MutationResult; export type RetryInvoicePaymentMutationOptions = Apollo.BaseMutationOptions; +export const RetryAllInvoicePaymentsDocument = gql` + mutation retryAllInvoicePayments($input: RetryAllInvoicePaymentsInput!) { + retryAllInvoicePayments(input: $input) { + collection { + id + } + } +} + `; +export type RetryAllInvoicePaymentsMutationFn = Apollo.MutationFunction; + +/** + * __useRetryAllInvoicePaymentsMutation__ + * + * To run a mutation, you first call `useRetryAllInvoicePaymentsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useRetryAllInvoicePaymentsMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [retryAllInvoicePaymentsMutation, { data, loading, error }] = useRetryAllInvoicePaymentsMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useRetryAllInvoicePaymentsMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(RetryAllInvoicePaymentsDocument, options); + } +export type RetryAllInvoicePaymentsMutationHookResult = ReturnType; +export type RetryAllInvoicePaymentsMutationResult = Apollo.MutationResult; +export type RetryAllInvoicePaymentsMutationOptions = Apollo.BaseMutationOptions; export const VoidInvoiceDocument = gql` mutation voidInvoice($input: VoidInvoiceInput!) { voidInvoice(input: $input) { @@ -14710,8 +14820,8 @@ export type GetInvoiceCreditNotesQueryHookResult = ReturnType; export type GetInvoiceCreditNotesSuspenseQueryHookResult = ReturnType; export type GetInvoiceCreditNotesQueryResult = Apollo.QueryResult; -export const InvoicesListDocument = gql` - query invoicesList($limit: Int, $page: Int, $status: InvoiceStatusTypeEnum, $paymentStatus: [InvoicePaymentStatusTypeEnum!], $searchTerm: String, $paymentDisputeLost: Boolean) { +export const InvoicesPageDocument = gql` + query invoicesPage($limit: Int, $page: Int, $status: InvoiceStatusTypeEnum, $paymentStatus: [InvoicePaymentStatusTypeEnum!], $searchTerm: String, $paymentDisputeLost: Boolean) { invoices( limit: $limit page: $page @@ -14734,16 +14844,16 @@ export const InvoicesListDocument = gql` ${InvoiceListItemFragmentDoc}`; /** - * __useInvoicesListQuery__ + * __useInvoicesPageQuery__ * - * To run a query within a React component, call `useInvoicesListQuery` and pass it any options that fit your needs. - * When your component renders, `useInvoicesListQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useInvoicesPageQuery` and pass it any options that fit your needs. + * When your component renders, `useInvoicesPageQuery` 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 } = useInvoicesListQuery({ + * const { data, loading, error } = useInvoicesPageQuery({ * variables: { * limit: // value for 'limit' * page: // value for 'page' @@ -14754,57 +14864,22 @@ export const InvoicesListDocument = gql` * }, * }); */ -export function useInvoicesListQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useInvoicesPageQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(InvoicesListDocument, options); + return Apollo.useQuery(InvoicesPageDocument, options); } -export function useInvoicesListLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useInvoicesPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(InvoicesListDocument, options); + return Apollo.useLazyQuery(InvoicesPageDocument, options); } -export function useInvoicesListSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { +export function useInvoicesPageSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useSuspenseQuery(InvoicesListDocument, options); + return Apollo.useSuspenseQuery(InvoicesPageDocument, options); } -export type InvoicesListQueryHookResult = ReturnType; -export type InvoicesListLazyQueryHookResult = ReturnType; -export type InvoicesListSuspenseQueryHookResult = ReturnType; -export type InvoicesListQueryResult = Apollo.QueryResult; -export const RetryAllInvoicePaymentsDocument = gql` - mutation retryAllInvoicePayments($input: RetryAllInvoicePaymentsInput!) { - retryAllInvoicePayments(input: $input) { - collection { - id - } - } -} - `; -export type RetryAllInvoicePaymentsMutationFn = Apollo.MutationFunction; - -/** - * __useRetryAllInvoicePaymentsMutation__ - * - * To run a mutation, you first call `useRetryAllInvoicePaymentsMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useRetryAllInvoicePaymentsMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [retryAllInvoicePaymentsMutation, { data, loading, error }] = useRetryAllInvoicePaymentsMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useRetryAllInvoicePaymentsMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(RetryAllInvoicePaymentsDocument, options); - } -export type RetryAllInvoicePaymentsMutationHookResult = ReturnType; -export type RetryAllInvoicePaymentsMutationResult = Apollo.MutationResult; -export type RetryAllInvoicePaymentsMutationOptions = Apollo.BaseMutationOptions; +export type InvoicesPageQueryHookResult = ReturnType; +export type InvoicesPageLazyQueryHookResult = ReturnType; +export type InvoicesPageSuspenseQueryHookResult = ReturnType; +export type InvoicesPageQueryResult = Apollo.QueryResult; export const GetPlanForDetailsDocument = gql` query getPlanForDetails($planId: ID!) { plan(id: $planId) { diff --git a/src/layouts/SideNavLayout.tsx b/src/layouts/SideNavLayout.tsx index 9335d6456..6be78eaeb 100644 --- a/src/layouts/SideNavLayout.tsx +++ b/src/layouts/SideNavLayout.tsx @@ -297,7 +297,7 @@ const SideNav = () => { icon: 'document', link: INVOICES_ROUTE, match: [INVOICES_ROUTE, INVOICES_TAB_ROUTE], - hidden: !hasPermissions(['invoicesView']), + hidden: !hasPermissions(['invoicesView', 'creditNotesView']), }, ]} orientation="vertical" diff --git a/src/pages/InvoicesPage.tsx b/src/pages/InvoicesPage.tsx new file mode 100644 index 000000000..7f2b9aa6d --- /dev/null +++ b/src/pages/InvoicesPage.tsx @@ -0,0 +1,256 @@ +import { gql } from '@apollo/client' +import { useEffect, useRef, useState } from 'react' +import { generatePath, useParams, useSearchParams } from 'react-router-dom' +import styled from 'styled-components' + +import { Button, NavigationTab, Typography } from '~/components/designSystem' +import { + UpdateInvoicePaymentStatusDialog, + UpdateInvoicePaymentStatusDialogRef, +} from '~/components/invoices/EditInvoicePaymentStatusDialog' +import { + FinalizeInvoiceDialog, + FinalizeInvoiceDialogRef, +} from '~/components/invoices/FinalizeInvoiceDialog' +import InvoicesList from '~/components/invoices/InvoicesList' +import { VoidInvoiceDialog, VoidInvoiceDialogRef } from '~/components/invoices/VoidInvoiceDialog' +import { SearchInput } from '~/components/SearchInput' +import { addToast, hasDefinedGQLError } from '~/core/apolloClient' +import { INVOICES_ROUTE, INVOICES_TAB_ROUTE } from '~/core/router' +import { + InvoiceListItemFragmentDoc, + InvoicePaymentStatusTypeEnum, + InvoiceStatusTypeEnum, + LagoApiError, + useInvoicesPageLazyQuery, + useRetryAllInvoicePaymentsMutation, +} from '~/generated/graphql' +import { useInternationalization } from '~/hooks/core/useInternationalization' +import { useDebouncedSearch } from '~/hooks/useDebouncedSearch' +import { usePermissions } from '~/hooks/usePermissions' +import { PageHeader, theme } from '~/styles' + +gql` + query invoicesPage( + $limit: Int + $page: Int + $status: InvoiceStatusTypeEnum + $paymentStatus: [InvoicePaymentStatusTypeEnum!] + $searchTerm: String + $paymentDisputeLost: Boolean + ) { + invoices( + limit: $limit + page: $page + status: $status + paymentStatus: $paymentStatus + searchTerm: $searchTerm + paymentDisputeLost: $paymentDisputeLost + ) { + metadata { + currentPage + totalPages + totalCount + } + collection { + id + ...InvoiceListItem + } + } + } + + mutation retryAllInvoicePayments($input: RetryAllInvoicePaymentsInput!) { + retryAllInvoicePayments(input: $input) { + collection { + id + } + } + } + + ${InvoiceListItemFragmentDoc} +` + +export enum InvoiceListStatusEnum { + 'all' = 'all', + 'draft' = 'draft', + 'pendingFailed' = 'pendingFailed', + 'succeeded' = 'succeeded', + 'voided' = 'voided', + 'disputed' = 'disputed', +} + +enum InvoiceListTabEnum { + 'invoices' = 'invoices', + 'creditNotes' = 'creditNotes', +} + +const InvoicesPage = () => { + const { translate } = useInternationalization() + const { hasPermissions } = usePermissions() + const { tab = InvoiceListTabEnum.invoices } = useParams<{ tab?: InvoiceListTabEnum }>() + let [searchParams, setSearchParams] = useSearchParams({ + invoiceType: tab === InvoiceListTabEnum.invoices ? InvoiceListStatusEnum.all : '', + }) + const urlInvoiceType = searchParams.get('invoiceType') || '' + const [invoiceType, setInvoiceType] = useState( + (urlInvoiceType as InvoiceListStatusEnum) || InvoiceListStatusEnum.all, + ) + const finalizeInvoiceRef = useRef(null) + const updateInvoicePaymentStatusDialog = useRef(null) + const voidInvoiceDialogRef = useRef(null) + + const [ + getInvoices, + { + data: dataInvoices, + loading: loadingInvoices, + error: errorInvoices, + fetchMore: fetchMoreInvoices, + variables: variableInvoices, + }, + ] = useInvoicesPageLazyQuery({ + notifyOnNetworkStatusChange: true, + fetchPolicy: 'network-only', + nextFetchPolicy: 'network-only', + variables: { + limit: 20, + ...(invoiceType === InvoiceListStatusEnum.draft && { status: InvoiceStatusTypeEnum.Draft }), + ...(invoiceType === InvoiceListStatusEnum.voided && { status: InvoiceStatusTypeEnum.Voided }), + ...(invoiceType === InvoiceListStatusEnum.pendingFailed && { + status: InvoiceStatusTypeEnum.Finalized, + paymentStatus: [InvoicePaymentStatusTypeEnum.Failed, InvoicePaymentStatusTypeEnum.Pending], + }), + ...(invoiceType === InvoiceListStatusEnum.succeeded && { + paymentStatus: InvoicePaymentStatusTypeEnum.Succeeded, + status: InvoiceStatusTypeEnum.Finalized, + }), + ...(invoiceType === InvoiceListStatusEnum.disputed && { + paymentDisputeLost: true, + }), + }, + }) + const { debouncedSearch, isLoading } = useDebouncedSearch(getInvoices, loadingInvoices) + const [retryAll] = useRetryAllInvoicePaymentsMutation({ + context: { silentErrorCodes: [LagoApiError.PaymentProcessorIsCurrentlyHandlingPayment] }, + onCompleted({ retryAllInvoicePayments }) { + if (retryAllInvoicePayments) { + addToast({ + severity: 'success', + translateKey: 'text_63ac86d897f728a87b2fa0a7', + }) + } + }, + }) + const listContainerElementRef = useRef(null) + + useEffect(() => { + // Scroll to top of list when switching tabs + listContainerElementRef?.current?.scrollTo({ top: 0 }) + }, [tab]) + + useEffect(() => { + let localUrlInvoiceType = Object.keys(InvoiceListStatusEnum).includes(urlInvoiceType) + ? urlInvoiceType + : InvoiceListStatusEnum.all + + setInvoiceType(localUrlInvoiceType as InvoiceListStatusEnum) + }, [urlInvoiceType]) + + useEffect(() => { + // If invoice page and no search params, set the default search param + if (tab === InvoiceListTabEnum.invoices && !searchParams.get('invoiceType')) { + setSearchParams({ invoiceType: InvoiceListStatusEnum.all }) + } + }, [searchParams, setSearchParams, tab]) + + return ( + <> + + + {translate('text_63ac86d797f728a87b2f9f85')} + + + + + {invoiceType === InvoiceListStatusEnum.pendingFailed && + hasPermissions(['invoicesSend']) && ( + + )} + + + { + // reset invoice type url params when switching tabs + setSearchParams() + }, + component: ( + + ), + hidden: !hasPermissions(['invoicesView']), + }, + { + title: translate('text_636bdef6565341dcb9cfb125'), + link: generatePath(INVOICES_TAB_ROUTE, { + tab: InvoiceListTabEnum.creditNotes, + }), + onClick: () => { + // Reset invoice search term when switching tabs + setSearchParams('') + }, + hidden: !hasPermissions(['creditNotesView']), + }, + ]} + /> + + + + + + ) +} + +export default InvoicesPage + +const HeaderRigthBlock = styled.div` + display: flex; + align-items: center; + + > :first-child { + margin-right: ${theme.spacing(3)}; + } +`