Skip to content

Commit

Permalink
(feat) Credit note export & filtering (#1930)
Browse files Browse the repository at this point in the history
* feat(filters): create filter item for invoice numbers

* feat(filters): create filter item for credit note refund status

* feat(filters): create filter item for credit note reason

* feat(filters): create filter item for credit note credit status

* feat(filters): create filter item for amount

* feat(exports): create reusable ExportDialog

* delete(exports): remove old dialog for exporting invoices

* refactor(CreateCreditNote): extract reason map

* refactor(InvoicesPage): include new export dialog

* refactor(filters): abstract filter logic

* refactor(filters): include credit note filters

* refactor(filters): refactor filters panel switch

* feat(CreditNotesTable): add filters

* chore(graphql): generate types

* chore(translations): add translations

* fix: linting

* feat(filters): extract parse amount util, amounts as ints, translation map

* feat(filters): extract AmountFilterInterval type

* feat(filters): update amount filter values

* fix(filters): flatten object filter values

* chore(graphql): generate types

* feat(filters): pass filters to credit note query

* feat(filters): map specific keys to general ones

* refactor(filters): save invoice number directly

* refactor(filters): update filter formatting for credit notes

* fix(ExportDialog): remove typo

* temporary-fix(filters): fix amount serialization

* fix(tests): fix passing falsy value
  • Loading branch information
stephenlago99 authored Dec 23, 2024
1 parent 011e96e commit c23ce72
Show file tree
Hide file tree
Showing 15 changed files with 1,229 additions and 391 deletions.
354 changes: 189 additions & 165 deletions src/components/creditNote/CreditNotesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { generatePath, useNavigate } from 'react-router-dom'
import styled, { css } from 'styled-components'

import CreditNoteBadge from '~/components/creditNote/CreditNoteBadge'
import { Filters } from '~/components/designSystem/Filters/Filters'
import { AvailableFiltersEnum } from '~/components/designSystem/Filters/types'
import { addToast } from '~/core/apolloClient'
import { intlFormatNumber } from '~/core/formats/intlFormatNumber'
import {
Expand Down Expand Up @@ -172,188 +174,210 @@ const CreditNotesTable = ({
const showCustomerName = !customerTimezone

return (
<ScrollContainer ref={listContainerElementRef} role="grid" tabIndex={-1} onKeyDown={onKeyDown}>
<div>
{isLoading && !!variables?.searchTerm ? (
<>
{[1, 2, 3, 4].map((i) => (
<CreditNoteTableItemSkeleton key={`key-initial-loading-skeleton-line-${i}`} />
))}
</>
) : !isLoading && !!variables?.searchTerm && !creditNotes?.length ? (
<GenericPlaceholder
title={translate('text_63c6edd80c57d0dfaae389a4')}
subtitle={translate('text_63c6edd80c57d0dfaae389a8')}
image={<EmptyImage width="136" height="104" />}
/>
) : (
<InfiniteScroll
onBottom={() => {
const { currentPage = 0, totalPages = 0 } = metadata || {}
<>
<div className="box-border flex w-full flex-col gap-3 p-4 shadow-b md:px-12 md:py-3">
<Filters
filters={[
AvailableFiltersEnum.amount,
AvailableFiltersEnum.creditNoteCreditStatus,
AvailableFiltersEnum.currency,
AvailableFiltersEnum.customerExternalId,
AvailableFiltersEnum.invoiceNumber,
AvailableFiltersEnum.issuingDate,
AvailableFiltersEnum.creditNoteReason,
AvailableFiltersEnum.creditNoteRefundStatus,
]}
/>
</div>

currentPage < totalPages &&
!isLoading &&
fetchMore({
variables: { page: currentPage + 1 },
})
}}
>
<Table
name="credit-notes-list"
data={creditNotes || []}
containerSize={
tableContainerSize || {
default: 0,
}
}
isLoading={isLoading}
hasError={!!error}
placeholder={{
emptyState: {
title: translate('text_6663014df0a6be0098264dd9'),
subtitle: translate('text_6663014df0a6be0098264dda'),
},
<ScrollContainer
ref={listContainerElementRef}
role="grid"
tabIndex={-1}
onKeyDown={onKeyDown}
>
<div>
{isLoading && !!variables?.searchTerm ? (
<>
{[1, 2, 3, 4].map((i) => (
<CreditNoteTableItemSkeleton key={`key-initial-loading-skeleton-line-${i}`} />
))}
</>
) : !isLoading && !!variables?.searchTerm && !creditNotes?.length ? (
<GenericPlaceholder
title={translate('text_63c6edd80c57d0dfaae389a4')}
subtitle={translate('text_63c6edd80c57d0dfaae389a8')}
image={<EmptyImage width="136" height="104" />}
/>
) : (
<InfiniteScroll
onBottom={() => {
const { currentPage = 0, totalPages = 0 } = metadata || {}

currentPage < totalPages &&
!isLoading &&
fetchMore({
variables: { page: currentPage + 1 },
})
}}
actionColumnTooltip={(creditNote) =>
translate(
creditNote.canBeVoided && hasPermissions(['creditNotesVoid'])
? 'text_63728c6434e1344aea76347d'
: 'text_63728c6434e1344aea76347f',
)
}
actionColumn={(creditNote) => {
let actions: ActionItem<CreditNoteTableItemFragment>[] = []
>
<Table
name="credit-notes-list"
data={creditNotes || []}
containerSize={
tableContainerSize || {
default: 0,
}
}
isLoading={isLoading}
hasError={!!error}
placeholder={{
emptyState: {
title: translate('text_6663014df0a6be0098264dd9'),
subtitle: translate('text_6663014df0a6be0098264dda'),
},
}}
actionColumnTooltip={(creditNote) =>
translate(
creditNote.canBeVoided && hasPermissions(['creditNotesVoid'])
? 'text_63728c6434e1344aea76347d'
: 'text_63728c6434e1344aea76347f',
)
}
actionColumn={(creditNote) => {
let actions: ActionItem<CreditNoteTableItemFragment>[] = []

const canDownload = hasPermissions(['creditNotesView'])
const canVoid = creditNote.canBeVoided && hasPermissions(['creditNotesVoid'])
const canDownload = hasPermissions(['creditNotesView'])
const canVoid = creditNote.canBeVoided && hasPermissions(['creditNotesVoid'])

if (canDownload) {
actions = [
...actions,
{
title: translate('text_636d12ce54c41fccdf0ef72d'),
disabled: loadingCreditNoteDownload,
onAction: async ({ id }: { id: string }) => {
await downloadCreditNote({
variables: { input: { id } },
})
if (canDownload) {
actions = [
...actions,
{
title: translate('text_636d12ce54c41fccdf0ef72d'),
disabled: loadingCreditNoteDownload,
onAction: async ({ id }: { id: string }) => {
await downloadCreditNote({
variables: { input: { id } },
})
},
},
},
]
}
]
}

if (canVoid) {
actions = [
...actions,
{
title: translate('text_636d12ce54c41fccdf0ef72f'),
onAction: async ({ id, totalAmountCents, currency }) => {
voidCreditNoteDialogRef.current?.openDialog({
id,
totalAmountCents,
currency,
})
},
},
]
}

if (canVoid) {
actions = [
...actions,
{
title: translate('text_636d12ce54c41fccdf0ef72f'),
onAction: async ({ id, totalAmountCents, currency }) => {
voidCreditNoteDialogRef.current?.openDialog({
id,
totalAmountCents,
currency,
title: translate('text_636d12ce54c41fccdf0ef731'),
onAction: async ({ id }: { id: string }) => {
copyToClipboard(id)

addToast({
severity: 'info',
translateKey: 'text_63720bd734e1344aea75b82d',
})
},
},
]
}

actions = [
...actions,
return actions
}}
onRowAction={(creditNote) => {
navigate(
generatePath(CUSTOMER_INVOICE_CREDIT_NOTE_DETAILS_ROUTE, {
customerId: creditNote?.invoice?.customer?.id as string,
invoiceId: creditNote?.invoice?.id as string,
creditNoteId: creditNote?.id as string,
}),
)
}}
columns={[
{
title: translate('text_636d12ce54c41fccdf0ef731'),
onAction: async ({ id }: { id: string }) => {
copyToClipboard(id)

addToast({
severity: 'info',
translateKey: 'text_63720bd734e1344aea75b82d',
})
},
key: 'totalAmountCents',
title: translate('text_1727078012568v9460bmnh8a'),
content: (creditNote) => <CreditNoteBadge creditNote={creditNote} />,
},
]

return actions
}}
onRowAction={(creditNote) => {
navigate(
generatePath(CUSTOMER_INVOICE_CREDIT_NOTE_DETAILS_ROUTE, {
customerId: creditNote?.invoice?.customer?.id as string,
invoiceId: creditNote?.invoice?.id as string,
creditNoteId: creditNote?.id as string,
}),
)
}}
columns={[
{
key: 'totalAmountCents',
title: translate('text_1727078012568v9460bmnh8a'),
content: (creditNote) => <CreditNoteBadge creditNote={creditNote} />,
},
{
key: 'number',
title: translate('text_64188b3d9735d5007d71227f'),
minWidth: 160,
content: ({ number }) => (
<Typography variant="body" noWrap>
{number}
</Typography>
),
},
{
key: 'totalAmountCents',
title: translate('text_62544c1db13ca10187214d85'),
content: ({ totalAmountCents, currency }) => (
<Typography
className="font-medium"
variant="body"
color={showCustomerName ? 'grey700' : 'success600'}
align="right"
noWrap
>
{intlFormatNumber(deserializeAmount(totalAmountCents || 0, currency), {
currencyDisplay: 'symbol',
currency,
})}
</Typography>
),
maxSpace: !showCustomerName,
textAlign: 'right',
},
...(showCustomerName
? [
{
key: 'invoice.customer.displayName',
title: translate('text_63ac86d797f728a87b2f9fb3'),
content: (creditNote: CreditNoteTableItemFragment) => (
<Typography variant="body" color="grey600" noWrap>
{creditNote.invoice?.customer.displayName}
</Typography>
),
maxSpace: true,
tdCellClassName: 'hidden md:table-cell',
} as TableColumn<CreditNoteTableItemFragment>,
]
: []),
{
key: 'createdAt',
title: translate('text_62544c1db13ca10187214d7f'),
content: ({ createdAt }) => (
<Typography variant="body" color="grey600" noWrap>
{customerTimezone
? formatDateToTZ(createdAt, customerTimezone)
: formatTimeOrgaTZ(createdAt)}
</Typography>
),
},
]}
/>
</InfiniteScroll>
)}
</div>
{
key: 'number',
title: translate('text_64188b3d9735d5007d71227f'),
minWidth: 160,
content: ({ number }) => (
<Typography variant="body" noWrap>
{number}
</Typography>
),
},
{
key: 'totalAmountCents',
title: translate('text_62544c1db13ca10187214d85'),
content: ({ totalAmountCents, currency }) => (
<Typography
className="font-medium"
variant="body"
color={showCustomerName ? 'grey700' : 'success600'}
align="right"
noWrap
>
{intlFormatNumber(deserializeAmount(totalAmountCents || 0, currency), {
currencyDisplay: 'symbol',
currency,
})}
</Typography>
),
maxSpace: !showCustomerName,
textAlign: 'right',
},
...(showCustomerName
? [
{
key: 'invoice.customer.displayName',
title: translate('text_63ac86d797f728a87b2f9fb3'),
content: (creditNote: CreditNoteTableItemFragment) => (
<Typography variant="body" color="grey600" noWrap>
{creditNote.invoice?.customer.displayName}
</Typography>
),
maxSpace: true,
tdCellClassName: 'hidden md:table-cell',
} as TableColumn<CreditNoteTableItemFragment>,
]
: []),
{
key: 'createdAt',
title: translate('text_62544c1db13ca10187214d7f'),
content: ({ createdAt }) => (
<Typography variant="body" color="grey600" noWrap>
{customerTimezone
? formatDateToTZ(createdAt, customerTimezone)
: formatTimeOrgaTZ(createdAt)}
</Typography>
),
},
]}
/>
</InfiniteScroll>
)}
</div>

<VoidCreditNoteDialog ref={voidCreditNoteDialogRef} />
</ScrollContainer>
<VoidCreditNoteDialog ref={voidCreditNoteDialogRef} />
</ScrollContainer>
</>
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/designSystem/Filters/ActiveFiltersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const ActiveFiltersList = ({ filters }: ActiveFiltersListProps) => {
...acc,
{
label: translate(mapFilterToTranslationKey(key)),
value: formatActiveFilterValueDisplay(key, value),
value: formatActiveFilterValueDisplay(key, value, translate),
},
]
},
Expand Down
Loading

0 comments on commit c23ce72

Please sign in to comment.