diff --git a/src/components/customerPortal/PortalInvoiceListItem.tsx b/src/components/customerPortal/PortalInvoiceListItem.tsx
deleted file mode 100644
index 63d2144fc..000000000
--- a/src/components/customerPortal/PortalInvoiceListItem.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import { gql } from '@apollo/client'
-import { DateTime } from 'luxon'
-import { memo } from 'react'
-import styled, { css } from 'styled-components'
-
-import {
- Button,
- Skeleton,
- Status,
- StatusProps,
- StatusType,
- Tooltip,
- Typography,
-} from '~/components/designSystem'
-import { addToast } from '~/core/apolloClient'
-import { intlFormatNumber } from '~/core/formats/intlFormatNumber'
-import { deserializeAmount } from '~/core/serializers/serializeAmount'
-import { LocaleEnum } from '~/core/translations'
-import {
- CurrencyEnum,
- InvoiceForFinalizeInvoiceFragmentDoc,
- InvoiceForUpdateInvoicePaymentStatusFragmentDoc,
- InvoicePaymentStatusTypeEnum,
- PortalInvoiceListItemFragment,
- useDownloadCustomerPortalInvoiceMutation,
-} from '~/generated/graphql'
-import { NAV_HEIGHT, theme } from '~/styles'
-
-gql`
- fragment PortalInvoiceListItem on Invoice {
- id
- paymentStatus
- paymentOverdue
- paymentDisputeLosable
- number
- issuingDate
- totalAmountCents
- currency
- }
-
- mutation downloadCustomerPortalInvoice($input: DownloadCustomerPortalInvoiceInput!) {
- downloadCustomerPortalInvoice(input: $input) {
- id
- fileUrl
- }
- }
-
- ${InvoiceForFinalizeInvoiceFragmentDoc}
- ${InvoiceForUpdateInvoicePaymentStatusFragmentDoc}
-`
-
-interface PortalInvoiceListItemProps {
- invoice: PortalInvoiceListItemFragment
- translate: Function
- className?: string
- documentLocale: LocaleEnum
-}
-
-const mapStatusConfig = ({
- paymentStatus,
- paymentOverdue,
- paymentDisputeLosable,
-}: {
- paymentStatus: InvoicePaymentStatusTypeEnum
- paymentOverdue: boolean
- paymentDisputeLosable: boolean
-}): StatusProps => {
- if (paymentOverdue) {
- return { label: 'overdue', type: StatusType.danger }
- }
-
- if (paymentStatus === InvoicePaymentStatusTypeEnum.Succeeded) {
- return { label: 'pay', type: StatusType.success }
- }
-
- if (paymentDisputeLosable) {
- return { label: 'disputed', type: StatusType.danger }
- }
-
- return { label: 'toPay', type: StatusType.default }
-}
-
-export const PortalInvoiceListItem = memo(
- ({ className, invoice, translate, documentLocale }: PortalInvoiceListItemProps) => {
- const {
- id,
- issuingDate,
- number,
- paymentStatus,
- paymentOverdue,
- totalAmountCents,
- currency,
- paymentDisputeLosable,
- } = invoice
- const statusConfig = mapStatusConfig({ paymentStatus, paymentOverdue, paymentDisputeLosable })
-
- const [downloadInvoice] = useDownloadCustomerPortalInvoiceMutation({
- onCompleted(data) {
- const fileUrl = data?.downloadCustomerPortalInvoice?.fileUrl
-
- if (fileUrl) {
- // We open a window, add url then focus on different lines, in order to prevent browsers to block page opening
- // It could be seen as unexpected popup as not immediatly done on user action
- // https://stackoverflow.com/questions/2587677/avoid-browser-popup-blockers
- const myWindow = window.open('', '_blank')
-
- if (myWindow?.location?.href) {
- myWindow.location.href = fileUrl
- return myWindow?.focus()
- }
-
- myWindow?.close()
- } else {
- addToast({
- severity: 'danger',
- translateKey: 'text_62b31e1f6a5b8b1b745ece48',
- })
- }
- },
- })
-
- return (
- -
-
-
- {DateTime.fromISO(issuingDate).toLocaleString(DateTime.DATE_MED, {
- locale: documentLocale,
- })}
-
-
- {number}
-
-
- {intlFormatNumber(deserializeAmount(totalAmountCents, currency || CurrencyEnum.Usd), {
- currency: currency || CurrencyEnum.Usd,
- locale: documentLocale,
- currencyDisplay: 'narrowSymbol',
- })}
-
-
-
-
-
-
- )
- },
-)
-
-PortalInvoiceListItem.displayName = 'PortalInvoiceListItem'
-
-interface PortalInvoiceListItemSkeletonProps {
- className?: string
-}
-
-export const PortalInvoiceListItemSkeleton = ({
- className,
-}: PortalInvoiceListItemSkeletonProps) => {
- return (
-
-
-
-
-
-
-
- )
-}
-
-export const PortalInvoiceListItemGridTemplate = () => css`
- display: grid;
- grid-template-columns: 147px 1fr 1fr 104px 40px;
- gap: ${theme.spacing(3)};
-`
-
-const Grid = () => css`
- position: relative;
- align-items: center;
- width: 100%;
- ${PortalInvoiceListItemGridTemplate()}
-`
-
-const GridItem = styled.div`
- ${Grid()}
-`
-
-const Item = styled.div`
- width: 100%;
- box-sizing: border-box;
- height: ${NAV_HEIGHT}px;
- box-shadow: ${theme.shadows[7]};
- display: flex;
- align-items: center;
- padding: 0 ${theme.spacing(4)};
-`
-
-const SkeletonWrapper = styled(Item)`
- ${Grid()}
-
- > *:nth-child(3) {
- justify-self: flex-end;
- }
-`
diff --git a/src/components/customerPortal/PortalInvoicesList.tsx b/src/components/customerPortal/PortalInvoicesList.tsx
index 147346b76..de8399eb2 100644
--- a/src/components/customerPortal/PortalInvoicesList.tsx
+++ b/src/components/customerPortal/PortalInvoicesList.tsx
@@ -1,27 +1,47 @@
import { gql } from '@apollo/client'
+import { DateTime } from 'luxon'
import styled, { css } from 'styled-components'
-import { InfiniteScroll, Typography } from '~/components/designSystem'
-import { GenericPlaceholder } from '~/components/GenericPlaceholder'
+import {
+ Button,
+ InfiniteScroll,
+ Status,
+ StatusProps,
+ StatusType,
+ Table,
+ Tooltip,
+ Typography,
+} from '~/components/designSystem'
import { SearchInput } from '~/components/SearchInput'
+import { addToast } from '~/core/apolloClient'
+import { intlFormatNumber } from '~/core/formats/intlFormatNumber'
+import { deserializeAmount } from '~/core/serializers/serializeAmount'
import { LocaleEnum } from '~/core/translations'
import {
+ CurrencyEnum,
+ InvoiceForFinalizeInvoiceFragmentDoc,
+ InvoiceForUpdateInvoicePaymentStatusFragmentDoc,
+ InvoicePaymentStatusTypeEnum,
InvoiceStatusTypeEnum,
PortalInvoiceListItemFragmentDoc,
useCustomerPortalInvoicesLazyQuery,
+ useDownloadCustomerPortalInvoiceMutation,
} from '~/generated/graphql'
import { useDebouncedSearch } from '~/hooks/useDebouncedSearch'
-import EmptyImage from '~/public/images/maneki/empty.svg'
-import ErrorImage from '~/public/images/maneki/error.svg'
-import { HEADER_TABLE_HEIGHT, NAV_HEIGHT, theme } from '~/styles'
-
-import {
- PortalInvoiceListItem,
- PortalInvoiceListItemGridTemplate,
- PortalInvoiceListItemSkeleton,
-} from './PortalInvoiceListItem'
+import { NAV_HEIGHT, theme } from '~/styles'
gql`
+ fragment PortalInvoiceListItem on Invoice {
+ id
+ paymentStatus
+ paymentOverdue
+ paymentDisputeLosable
+ number
+ issuingDate
+ totalAmountCents
+ currency
+ }
+
query customerPortalInvoices(
$limit: Int
$page: Int
@@ -41,9 +61,42 @@ gql`
}
}
+ mutation downloadCustomerPortalInvoice($input: DownloadCustomerPortalInvoiceInput!) {
+ downloadCustomerPortalInvoice(input: $input) {
+ id
+ fileUrl
+ }
+ }
+
${PortalInvoiceListItemFragmentDoc}
+ ${InvoiceForFinalizeInvoiceFragmentDoc}
+ ${InvoiceForUpdateInvoicePaymentStatusFragmentDoc}
`
+const mapStatusConfig = ({
+ paymentStatus,
+ paymentOverdue,
+ paymentDisputeLosable,
+}: {
+ paymentStatus: InvoicePaymentStatusTypeEnum
+ paymentOverdue: boolean
+ paymentDisputeLosable: boolean
+}): StatusProps => {
+ if (paymentOverdue) {
+ return { label: 'overdue', type: StatusType.danger }
+ }
+
+ if (paymentStatus === InvoicePaymentStatusTypeEnum.Succeeded) {
+ return { label: 'pay', type: StatusType.success }
+ }
+
+ if (paymentDisputeLosable) {
+ return { label: 'disputed', type: StatusType.danger }
+ }
+
+ return { label: 'toPay', type: StatusType.default }
+}
+
interface PortalCustomerInvoicesProps {
translate: Function
documentLocale: LocaleEnum
@@ -60,13 +113,39 @@ const PortalInvoicesList = ({ translate, documentLocale }: PortalCustomerInvoice
status: [InvoiceStatusTypeEnum.Finalized],
},
})
+
+ const [downloadInvoice] = useDownloadCustomerPortalInvoiceMutation({
+ onCompleted(localData) {
+ const fileUrl = localData?.downloadCustomerPortalInvoice?.fileUrl
+
+ if (fileUrl) {
+ // We open a window, add url then focus on different lines, in order to prevent browsers to block page opening
+ // It could be seen as unexpected popup as not immediatly done on user action
+ // https://stackoverflow.com/questions/2587677/avoid-browser-popup-blockers
+ const myWindow = window.open('', '_blank')
+
+ if (myWindow?.location?.href) {
+ myWindow.location.href = fileUrl
+ return myWindow?.focus()
+ }
+
+ myWindow?.close()
+ } else {
+ addToast({
+ severity: 'danger',
+ translateKey: 'text_62b31e1f6a5b8b1b745ece48',
+ })
+ }
+ },
+ })
+
const { debouncedSearch, isLoading } = useDebouncedSearch(getInvoices, loading)
const { metadata, collection } = data?.customerPortalInvoices || {}
const hasSearchTerm = !!variables?.searchTerm
const hasNoInvoices = !loading && !error && !metadata?.totalCount && !hasSearchTerm
return (
-
+
{translate('text_6419c64eace749372fc72b37')}
@@ -85,86 +164,115 @@ const PortalInvoicesList = ({ translate, documentLocale }: PortalCustomerInvoice
{translate('text_6419c64eace749372fc72b3b')}
) : (
-
-
-
- {translate('text_6419c64eace749372fc72b39')}
-
-
- {translate('text_6419c64eace749372fc72b3c')}
-
-
- {translate('text_6419c64eace749372fc72b3e')}
-
-
- {translate('text_6419c64eace749372fc72b40')}
-
-
- {isLoading && hasSearchTerm ? (
- <>
- {[0, 1, 2].map((i) => (
-
- ))}
- >
- ) : !isLoading && !!error ? (
- location.reload()}
- image={}
- />
- ) : !isLoading && !collection?.length ? (
- }
- />
- ) : (
- {
- if (!fetchMore) return
- const { currentPage = 0, totalPages = 0 } = metadata || {}
-
- currentPage < totalPages &&
- !isLoading &&
- fetchMore({
- variables: { page: currentPage + 1 },
- })
- }}
- >
- {!!collection &&
- collection.map((invoice, i) => {
+ {
+ if (!fetchMore) return
+ const { currentPage = 0, totalPages = 0 } = metadata || {}
+
+ currentPage < totalPages &&
+ !isLoading &&
+ fetchMore({
+ variables: { page: currentPage + 1 },
+ })
+ }}
+ >
+ location.reload(),
+ },
+ emptyState: {
+ title: translate('text_641d6b0c5a725b00af12bd76'),
+ subtitle: translate('text_641d6b1ae9019c00b59fe250'),
+ },
+ }}
+ data={collection ?? []}
+ columns={[
+ {
+ key: 'issuingDate',
+ title: translate('text_6419c64eace749372fc72b39'),
+ minWidth: 104,
+ content: ({ issuingDate }) => (
+
+ {DateTime.fromISO(issuingDate).toLocaleString(DateTime.DATE_MED, {
+ locale: documentLocale,
+ })}
+
+ ),
+ },
+ {
+ key: 'number',
+ title: translate('text_6419c64eace749372fc72b3c'),
+ maxSpace: true,
+ minWidth: 160,
+ content: ({ number }) => (
+
+ {number}
+
+ ),
+ },
+ {
+ key: 'totalAmountCents',
+ title: translate('text_6419c64eace749372fc72b3e'),
+ textAlign: 'right',
+ minWidth: 160,
+ content: ({ totalAmountCents, currency }) => (
+
+ {intlFormatNumber(
+ deserializeAmount(totalAmountCents, currency || CurrencyEnum.Usd),
+ {
+ currency: currency || CurrencyEnum.Usd,
+ locale: documentLocale,
+ currencyDisplay: 'narrowSymbol',
+ },
+ )}
+
+ ),
+ },
+ {
+ key: 'paymentStatus',
+ title: translate('text_6419c64eace749372fc72b40'),
+ minWidth: 80,
+ content: ({ paymentStatus, paymentOverdue, paymentDisputeLosable }) => {
return (
-
)
- })}
- {isLoading &&
- [0, 1, 2].map((_, i) => (
- {
+ return (
+
+
+ )
+ }}
+ />
+
)}
@@ -191,32 +299,7 @@ const HeaderRigthBlock = styled.div`
align-items: center;
`
-const HeaderLine = styled.div`
- height: ${HEADER_TABLE_HEIGHT}px;
- display: grid;
- align-items: center;
- box-shadow: ${theme.shadows[7]};
- border-radius: 12px 12px 0 0;
- ${PortalInvoiceListItemGridTemplate()}
- padding: 0 ${theme.spacing(4)};
-`
-
-const StyledGenericPlaceholder = styled(GenericPlaceholder)`
- margin: ${theme.spacing(6)} auto;
- text-align: center;
-`
-
-const ListWrapper = styled.div`
- border: 1px solid ${theme.palette.grey[400]};
- border-radius: 12px;
- min-width: 510px;
-
- .last-invoice-item--no-border {
- box-shadow: none;
- border-radius: 0 0 12px 12px;
- }
-`
-
const ScrollWrapper = styled.div`
overflow: auto;
+ height: 100%;
`
diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx
index 82b1dfbe1..fc547f580 100644
--- a/src/generated/graphql.tsx
+++ b/src/generated/graphql.tsx
@@ -5404,13 +5404,6 @@ export type GetPortalCustomerInfosQuery = { __typename?: 'Query', customerPortal
export type PortalInvoiceListItemFragment = { __typename?: 'Invoice', id: string, paymentStatus: InvoicePaymentStatusTypeEnum, paymentOverdue: boolean, paymentDisputeLosable: boolean, number: string, issuingDate: any, totalAmountCents: any, currency?: CurrencyEnum | null };
-export type DownloadCustomerPortalInvoiceMutationVariables = Exact<{
- input: DownloadCustomerPortalInvoiceInput;
-}>;
-
-
-export type DownloadCustomerPortalInvoiceMutation = { __typename?: 'Mutation', downloadCustomerPortalInvoice?: { __typename?: 'Invoice', id: string, fileUrl?: string | null } | null };
-
export type CustomerPortalInvoicesQueryVariables = Exact<{
limit?: InputMaybe;
page?: InputMaybe;
@@ -5421,6 +5414,13 @@ export type CustomerPortalInvoicesQueryVariables = Exact<{
export type CustomerPortalInvoicesQuery = { __typename?: 'Query', customerPortalInvoices: { __typename?: 'InvoiceCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number, totalCount: number }, collection: Array<{ __typename?: 'Invoice', id: string, paymentStatus: InvoicePaymentStatusTypeEnum, paymentOverdue: boolean, paymentDisputeLosable: boolean, number: string, issuingDate: any, totalAmountCents: any, currency?: CurrencyEnum | null }> } };
+export type DownloadCustomerPortalInvoiceMutationVariables = Exact<{
+ input: DownloadCustomerPortalInvoiceInput;
+}>;
+
+
+export type DownloadCustomerPortalInvoiceMutation = { __typename?: 'Mutation', downloadCustomerPortalInvoice?: { __typename?: 'Invoice', id: string, fileUrl?: string | null } | null };
+
export type GetCustomerPortalInvoicesCollectionQueryVariables = Exact<{
expireCache?: InputMaybe;
}>;
@@ -10702,40 +10702,6 @@ export type GetPortalCustomerInfosQueryHookResult = ReturnType;
export type GetPortalCustomerInfosSuspenseQueryHookResult = ReturnType;
export type GetPortalCustomerInfosQueryResult = Apollo.QueryResult;
-export const DownloadCustomerPortalInvoiceDocument = gql`
- mutation downloadCustomerPortalInvoice($input: DownloadCustomerPortalInvoiceInput!) {
- downloadCustomerPortalInvoice(input: $input) {
- id
- fileUrl
- }
-}
- `;
-export type DownloadCustomerPortalInvoiceMutationFn = Apollo.MutationFunction;
-
-/**
- * __useDownloadCustomerPortalInvoiceMutation__
- *
- * To run a mutation, you first call `useDownloadCustomerPortalInvoiceMutation` within a React component and pass it any options that fit your needs.
- * When your component renders, `useDownloadCustomerPortalInvoiceMutation` 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 [downloadCustomerPortalInvoiceMutation, { data, loading, error }] = useDownloadCustomerPortalInvoiceMutation({
- * variables: {
- * input: // value for 'input'
- * },
- * });
- */
-export function useDownloadCustomerPortalInvoiceMutation(baseOptions?: Apollo.MutationHookOptions) {
- const options = {...defaultOptions, ...baseOptions}
- return Apollo.useMutation(DownloadCustomerPortalInvoiceDocument, options);
- }
-export type DownloadCustomerPortalInvoiceMutationHookResult = ReturnType;
-export type DownloadCustomerPortalInvoiceMutationResult = Apollo.MutationResult;
-export type DownloadCustomerPortalInvoiceMutationOptions = Apollo.BaseMutationOptions;
export const CustomerPortalInvoicesDocument = gql`
query customerPortalInvoices($limit: Int, $page: Int, $searchTerm: String, $status: [InvoiceStatusTypeEnum!]) {
customerPortalInvoices(
@@ -10792,6 +10758,40 @@ export type CustomerPortalInvoicesQueryHookResult = ReturnType;
export type CustomerPortalInvoicesSuspenseQueryHookResult = ReturnType;
export type CustomerPortalInvoicesQueryResult = Apollo.QueryResult;
+export const DownloadCustomerPortalInvoiceDocument = gql`
+ mutation downloadCustomerPortalInvoice($input: DownloadCustomerPortalInvoiceInput!) {
+ downloadCustomerPortalInvoice(input: $input) {
+ id
+ fileUrl
+ }
+}
+ `;
+export type DownloadCustomerPortalInvoiceMutationFn = Apollo.MutationFunction;
+
+/**
+ * __useDownloadCustomerPortalInvoiceMutation__
+ *
+ * To run a mutation, you first call `useDownloadCustomerPortalInvoiceMutation` within a React component and pass it any options that fit your needs.
+ * When your component renders, `useDownloadCustomerPortalInvoiceMutation` 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 [downloadCustomerPortalInvoiceMutation, { data, loading, error }] = useDownloadCustomerPortalInvoiceMutation({
+ * variables: {
+ * input: // value for 'input'
+ * },
+ * });
+ */
+export function useDownloadCustomerPortalInvoiceMutation(baseOptions?: Apollo.MutationHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useMutation(DownloadCustomerPortalInvoiceDocument, options);
+ }
+export type DownloadCustomerPortalInvoiceMutationHookResult = ReturnType;
+export type DownloadCustomerPortalInvoiceMutationResult = Apollo.MutationResult;
+export type DownloadCustomerPortalInvoiceMutationOptions = Apollo.BaseMutationOptions;
export const GetCustomerPortalInvoicesCollectionDocument = gql`
query getCustomerPortalInvoicesCollection($expireCache: Boolean) {
customerPortalInvoiceCollections(expireCache: $expireCache) {