Skip to content

Commit

Permalink
feat: Add filters panel in invoice page (#1633)
Browse files Browse the repository at this point in the history
* misc: minor style update and fix

* misc: DatePicker can now show error in tooltip

* misc: add filter elements

* misc: Add filter component

* misc: update invoice list and page to work with filters

* misc: add related copy and graph update
  • Loading branch information
ansmonjol committed Aug 7, 2024
1 parent 047710c commit 78c1051
Show file tree
Hide file tree
Showing 23 changed files with 1,338 additions and 161 deletions.
12 changes: 11 additions & 1 deletion ditto/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,6 @@
"text_63befc65efcd9374da45b813": "This customer cannot be found",
"text_63befc65efcd9374da45b817": "Could you enter another keyword?",
"text_63c67d2913c20b8d7d05c43e": "This invoice cannot be found",
"text_63c67d2913c20b8d7d05c446": "Could you enter another keyword?",
"text_63c67d2913c20b8d7d05c442": "This draft invoice cannot be found",
"text_63c67d2913c20b8d7d05c44c": "This succeded invoice cannot be found",
"text_63c67d8796db41749ada51ca": "This pending/failed invoice cannot be found",
Expand Down Expand Up @@ -1261,6 +1260,17 @@
"text_65269cd46e7ec037a6823fd6": "There are no voided invoices",
"text_65269cd46e7ec037a6823fda": "You can void an invoice to mark it as non due. All voided invoices will be listed here",
"text_65269cd46e7ec037a6823fd8": "This voided invoice cannot be found",
"text_66ab42d4ece7e6b7078993a9": "Clear all",
"text_66ab42d4ece7e6b7078993ad": "Filters",
"text_66ab42d4ece7e6b7078993b1": "Select value",
"text_66ab42d4ece7e6b7078993b5": "Where",
"text_66ab42d4ece7e6b7078993b9": "Add filter",
"text_66ab42d4ece7e6b7078993c1": "Apply",
"text_66ab42d4ece7e6b7078993d0": "is",
"text_66ab42d4ece7e6b7078993e2": "is between",
"text_66ab4886cc65a6006ee7258c": "Reset filters",
"text_66ab48ea4ed9cd01084c60b8": "Could you enter another keyword or adjust your filters?",
"text_66ab4ad87fc8510054f237c2": "Remove",
"text_65a6b4e3cb38d9b70ec54092": "Edit fee",
"text_65a6b4e2cb38d9b70ec53c25": "Edit {{name}}",
"text_65a6b4e2cb38d9b70ec53c2d": "Modify the total unit or amount of the fee, and Lago will automatically replace the existing fee with the new one.",
Expand Down
2 changes: 2 additions & 0 deletions ditto/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -391,5 +391,7 @@ sources:
fileName: ⚙️ [WIP] - Invoices - Regroup fee paid in advance in invoice
- name: 👍 [Ready for dev] - Wallets - Do not generate invoice at top-up
id: 66a8aecf06001261d60abe9f
- name: 👍 [Ready for dev] - Invoices - Export invoices in CSV
id: 66ab42d15b1e5416e2d013aa
format: flat
variants: true
4 changes: 4 additions & 0 deletions ditto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ready_for_dev___dashboards___add_financial_reporting_to_lago = require('./
const ready_for_dev___integration___connect_lago_to_xero = require('./ready-for-dev---integration---connect-lago-to-xero__base.json');
const ready_for_dev___invoices___display_unit_price_to_item_in_invoices = require('./ready-for-dev---invoices---display-unit-price-to-item-in-invoices__base.json');
const ready_for_dev___invoices___edit_a_draft_invoice = require('./ready-for-dev---invoices---edit-a-draft-invoice__base.json');
const ready_for_dev___invoices___export_invoices_in_csv = require('./ready-for-dev---invoices---export-invoices-in-csv__base.json');
const ready_for_dev___invoices___void_invoices = require('./ready-for-dev---invoices---void-invoices__base.json');
const ready_for_dev___invoices__invoice_list = require('./ready-for-dev---invoices--invoice-list__base.json');
const ready_for_dev___login___reset_password = require('./ready-for-dev---login---reset-password__base.json');
Expand Down Expand Up @@ -306,6 +307,9 @@ module.exports = {
"project_65a6b4dfc20f99078eb6e8fe": {
"base": {...ready_for_dev___invoices___edit_a_draft_invoice}
},
"project_66ab42d15b1e5416e2d013aa": {
"base": {...ready_for_dev___invoices___export_invoices_in_csv}
},
"project_65269b3f720470569cb17228": {
"base": {...ready_for_dev___invoices___void_invoices}
},
Expand Down
74 changes: 74 additions & 0 deletions src/components/designSystem/Filters/ActiveFiltersList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Typography } from '@mui/material'
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import styled from 'styled-components'

import { useInternationalization } from '~/hooks/core/useInternationalization'
import { theme } from '~/styles'

import { AvailableFiltersEnum, mapFilterToTranslationKey } from './types'
import { formatActiveFilterValueDisplay } from './utils'

interface ActiveFiltersListProps {
filters: AvailableFiltersEnum[]
hideBorderBottom?: boolean
noPadding?: boolean
}

export const ActiveFiltersList = ({ filters }: ActiveFiltersListProps) => {
const { translate } = useInternationalization()
let [searchParams] = useSearchParams()

const activeFilters = useMemo(() => {
const setFilters = Object.fromEntries(searchParams.entries())

const filtersToDisplay = Object.entries(setFilters).reduce(
(acc, cur) => {
const [key, value] = cur as [AvailableFiltersEnum, string]

if (!filters.includes(key)) {
return acc
}

return [
...acc,
{
label: translate(mapFilterToTranslationKey(key)),
value: formatActiveFilterValueDisplay(key, value),
},
]
},
[] as Record<string, string>[],
)

return filtersToDisplay

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters, searchParams])

if (!activeFilters.length) {
return null
}

return (
<>
{activeFilters.map(({ label, value }, index) => (
<ActiveFilterChip key={`active-filter-${index}`}>
<Typography variant="captionHl" color="grey600">
{translate(label)}: {value}
</Typography>
</ActiveFilterChip>
))}
</>
)
}

const ActiveFilterChip = styled.div`
display: flex;
align-items: center;
height: 32px;
background-color: ${theme.palette.grey[100]};
border-radius: 100px;
padding: 0 ${theme.spacing(3)};
box-sizing: border-box;
`
61 changes: 61 additions & 0 deletions src/components/designSystem/Filters/Filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useNavigate, useSearchParams } from 'react-router-dom'
import styled, { css } from 'styled-components'

import { Button } from '~/components/designSystem'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { theme } from '~/styles'

import { ActiveFiltersList } from './ActiveFiltersList'
import { FiltersPanelPoper } from './FiltersPanelPoper'
import { AvailableFiltersEnum } from './types'

interface FiltersProps {
filters: AvailableFiltersEnum[]
hideBorderBottom?: boolean
noPadding?: boolean
}

export const Filters = ({ filters, hideBorderBottom, noPadding, ...props }: FiltersProps) => {
const navigate = useNavigate()
const { translate } = useInternationalization()
let [searchParams] = useSearchParams()

return (
<FiltersContainer $hideBorderBottom={hideBorderBottom} $noPadding={noPadding} {...props}>
<FiltersPanelPoper filters={filters} />
<ActiveFiltersList filters={filters} />

{searchParams.size > 0 && (
<Button variant="quaternary" size="small" onClick={() => navigate({ search: '' })}>
{translate('text_66ab4886cc65a6006ee7258c')}
</Button>
)}
</FiltersContainer>
)
}

const FiltersContainer = styled.div<{ $hideBorderBottom?: boolean; $noPadding?: boolean }>`
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: ${theme.spacing(3)};
overflow-y: scroll;
box-sizing: border-box;
${({ $noPadding }) =>
!$noPadding &&
css`
padding: ${theme.spacing(3)} ${theme.spacing(12)};
`}
${({ $hideBorderBottom }) =>
!$hideBorderBottom &&
css`
box-shadow: ${theme.shadows[7]};
`}
${theme.breakpoints.down('md')} {
padding: ${theme.spacing(4)};
}
`
77 changes: 77 additions & 0 deletions src/components/designSystem/Filters/FiltersPanelItemTypeSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import styled from 'styled-components'

import { Typography } from '~/components/designSystem'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { HEADER_TABLE_HEIGHT, theme } from '~/styles'

import { FiltersItemCurrency } from './filtersElements/FiltersItemCurrency'
import { FiltersItemCustomer } from './filtersElements/FiltersItemCustomer'
import { FiltersItemInvoiceType } from './filtersElements/FiltersItemInvoiceType'
import { FiltersItemIssuingDate } from './filtersElements/FiltersItemIssuingDate'
import { FiltersItemPaymentDisputeLost } from './filtersElements/FiltersItemPaymentDisputeLost'
import { FiltersItemPaymentOverdue } from './filtersElements/FiltersItemPaymentOverdue'
import { FiltersItemPaymentStatus } from './filtersElements/FiltersItemPaymentStatus'
import { FiltersItemStatus } from './filtersElements/FiltersItemStatus'
import { FiltersFormValues } from './FiltersPanelPoper'
import { AvailableFiltersEnum } from './types'

type FiltersPanelItemTypeSwitchProps = {
filterType: AvailableFiltersEnum | undefined
value: FiltersFormValues['filters'][0]['value']
setFilterValue: (value: string) => void
}

export const FiltersPanelItemTypeSwitch = ({
filterType,
value,
setFilterValue,
}: FiltersPanelItemTypeSwitchProps) => {
const { translate } = useInternationalization()

if (!filterType) {
return <NoFilterTypePlaceholder />
}

return (
<>
{filterType === AvailableFiltersEnum.issuingDate ? (
<Typography variant="body" color="grey700">
{translate('text_66ab42d4ece7e6b7078993e2')}
</Typography>
) : (
<Typography variant="body" color="grey700">
{translate('text_66ab42d4ece7e6b7078993d0')}
</Typography>
)}

{filterType === AvailableFiltersEnum.currency ? (
<FiltersItemCurrency value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.customerExternalId ? (
<FiltersItemCustomer value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.invoiceType ? (
<FiltersItemInvoiceType value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.issuingDate ? (
<FiltersItemIssuingDate value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.paymentDisputeLost ? (
<FiltersItemPaymentDisputeLost value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.paymentOverdue ? (
<FiltersItemPaymentOverdue value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.paymentStatus ? (
<FiltersItemPaymentStatus value={value} setFilterValue={setFilterValue} />
) : filterType === AvailableFiltersEnum.status ? (
<FiltersItemStatus value={value} setFilterValue={setFilterValue} />
) : null}
</>
)
}

const NoFilterTypePlaceholder = styled.div`
/* -2px stand for the border witdh included on both top and bottom */
height: ${HEADER_TABLE_HEIGHT - 2}px;
border: 1px dashed ${theme.palette.grey[300]};
border-radius: ${theme.shape.borderRadius}px;
${theme.breakpoints.up('lg')} {
flex: 1;
}
`
Loading

0 comments on commit 78c1051

Please sign in to comment.