Skip to content

Commit

Permalink
Merge branch 'development' into 3931-migrations-stepsreset
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Oct 3, 2024
2 parents 4795bea + 693faab commit e533901
Show file tree
Hide file tree
Showing 35 changed files with 612 additions and 122 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import 'src/client/style/partials';

.table-paginated-empty-list {
color: $text-disabled;
font-weight: bold;
grid-column: 1 / -1;
padding: $spacing-xxl 0;
text-align: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import './DefaultEmptyList.scss'
import React from 'react'
import { useTranslation } from 'react-i18next'

const DefaultEmptyList: React.FC = () => {
const { t } = useTranslation()

return <div className="table-paginated-empty-list">{t('common.noItemsFound')}</div>
}

export default DefaultEmptyList
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './DefaultEmptyList'
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './ExportButton.scss'
import React from 'react'
import { Link } from 'react-router-dom'

import { useTablePaginatedCount } from 'client/store/ui/tablePaginated'
import { useButtonClassName } from 'client/components/Buttons/Button'
import Icon from 'client/components/Icon'

Expand All @@ -16,7 +17,11 @@ const ExportButton: React.FC<Props> = (props) => {

const exportUrl = useExportUrl({ path })

const className = useButtonClassName({ iconName: 'hit-down' })
const count = useTablePaginatedCount(path)
const disabled = count?.total === 0

const className = useButtonClassName({ disabled, iconName: 'hit-down' })

return (
<div className="table-paginated-export-button">
<Link className={className} target="_blank" to={exportUrl}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useMemo } from 'react'

import { TablePaginateds } from 'meta/tablePaginated'

import { useTablePaginatedOrderBy } from 'client/store/ui/tablePaginated'
import { useTablePaginatedFilters } from 'client/store/ui/tablePaginated/hooks'
import { useSectionRouteParams } from 'client/hooks/useRouteParams'

type Props = {
Expand All @@ -12,18 +15,21 @@ export const useExportUrl = (props: Props): string => {

const { assessmentName, countryIso, cycleName, sectionName } = useSectionRouteParams()
const orderBy = useTablePaginatedOrderBy(path)
const filters = useTablePaginatedFilters(path)

return useMemo<string>(() => {
const encodedFilters = TablePaginateds.encodeFilters(filters)
const queryParams = new URLSearchParams(
Object.entries({
assessmentName,
countryIso,
cycleName,
filters: encodedFilters,
orderBy: orderBy?.property,
orderByDirection: orderBy?.direction,
sectionName,
}).filter(([, value]) => value !== undefined)
)
return `${path}/export?${queryParams.toString()}`
}, [assessmentName, countryIso, cycleName, orderBy, path, sectionName])
}, [assessmentName, countryIso, cycleName, filters, orderBy, path, sectionName])
}
12 changes: 12 additions & 0 deletions src/client/components/TablePaginated/Filters/Filters.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import 'src/client/style/partials';

.table-paginated-filters {
align-items: center;
display: flex;
gap: $spacing-xxs;
width: 100%;

svg.icon_filter {
color: $ui-accent-dark;
}
}
45 changes: 45 additions & 0 deletions src/client/components/TablePaginated/Filters/Filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import './Filters.scss'
import React from 'react'

import { TablePaginatedFilterType } from 'meta/tablePaginated'

import Icon from 'client/components/Icon'
import Text from 'client/components/TablePaginated/Filters/Text/Text'
import { TablePaginatedFilter } from 'client/components/TablePaginated/types'

const componentsByFilterType: Record<
TablePaginatedFilterType,
React.FC<TablePaginatedFilter<TablePaginatedFilterType> & { path: string }>
> = {
[TablePaginatedFilterType.TEXT]: Text,
[TablePaginatedFilterType.SWITCH]: () => null,
}

type Props = {
filters: Array<TablePaginatedFilter<TablePaginatedFilterType>>
path: string
}

const Filters: React.FC<Props> = (props: Props) => {
const { filters, path } = props

return (
<div className="table-paginated-filters">
<Icon name="filter" />
{filters.map((filter) => {
if (filter.hidden) return null
const Component = componentsByFilterType[filter.type]
return (
<Component
key={filter.fieldName}
// eslint-disable-next-line react/jsx-props-no-spreading
{...filter}
path={path}
/>
)
})}
</div>
)
}

export default Filters
48 changes: 48 additions & 0 deletions src/client/components/TablePaginated/Filters/Text/Text.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@import 'src/client/style/partials';

.table-paginated-filter-input {
display: inline-block;
position: relative;

input {
border: 1px solid $ui-border;
border-radius: 2px;
font-size: $font-xxs;
padding: 6px;
transition: all 0.2s ease;
}

button.clear-button {
background-color: #ffffff;
border: none;
height: $spacing-xs;
padding: 0;
position: absolute;
right: $spacing-xxxs;
top: 50%;
transform: translateY(-50%);
width: $spacing-xs;

svg {
height: 14px;
width: 14px;
}

&:hover {
svg {
color: $ui-destructive;
}
}

&.disabled {
opacity: 0.6;
pointer-events: none;
}
}

&.active {
input {
border: 1px solid $ui-accent;
}
}
}
55 changes: 55 additions & 0 deletions src/client/components/TablePaginated/Filters/Text/Text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import './Text.scss'
import React from 'react'

import classNames from 'classnames'
import { Objects } from 'utils/objects'

import { TablePaginatedFilterType } from 'meta/tablePaginated'

import { useAppDispatch } from 'client/store'
import { TablePaginatedActions } from 'client/store/ui/tablePaginated'
import { useTablePaginatedFilterValue } from 'client/store/ui/tablePaginated/hooks'
import Icon from 'client/components/Icon'
import InputText from 'client/components/Inputs/InputText'
import { TablePaginatedFilter } from 'client/components/TablePaginated/types'

type Props = TablePaginatedFilter<TablePaginatedFilterType.TEXT> & {
path: string
}

const Text = (props: Props) => {
const { fieldName, label, path } = props
const dispatch = useAppDispatch()

const filterValue = useTablePaginatedFilterValue<string>(path, fieldName)

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target
dispatch(
TablePaginatedActions.setFilterValue({
fieldName,
path,
value,
})
)
}

const handleClearInput = () => {
dispatch(TablePaginatedActions.resetFilter({ fieldName, path }))
}

return (
<div className={classNames('table-paginated-filter-input', { active: !Objects.isEmpty(filterValue) })}>
<InputText onChange={handleChange} placeholder={label} value={filterValue ?? ''} />
<button
className={classNames('clear-button icon', { disabled: Objects.isEmpty(filterValue) })}
onClick={handleClearInput}
type="button"
>
<Icon className="icon-sub" name="remove" />
</button>
</div>
)
}

export default Text
14 changes: 14 additions & 0 deletions src/client/components/TablePaginated/TablePaginated.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
grid-row-gap: $spacing-xs;
}

.table-paginated-actions {
align-items: center;
display: flex;
gap: $spacing-xs;
height: $spacing-l;
padding: $spacing-xxxs;
}

.table-paginated-datagrid {
.data-grid-column {
border: none;
Expand Down Expand Up @@ -49,3 +57,9 @@
}
}
}

.table-paginated-actions-sep {
background: radial-gradient($ui-accent-dark, $ui-bg);
height: $spacing-s;
width: 1px;
}
51 changes: 36 additions & 15 deletions src/client/components/TablePaginated/TablePaginated.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import './TablePaginated.scss'
import React, { HTMLAttributes } from 'react'
import React, { HTMLAttributes, useMemo, useRef } from 'react'
import Skeleton from 'react-loading-skeleton'

import classNames from 'classnames'
import { Objects } from 'utils/objects'

import { useTablePaginatedCount } from 'client/store/ui/tablePaginated'
import { useTablePaginatedCount, useTablePaginatedData, useTablePaginatedPage } from 'client/store/ui/tablePaginated'
import { useOnUpdate } from 'client/hooks'
import DataGrid from 'client/components/DataGridDeprecated'
import { PaginatorProps } from 'client/components/Paginator'
import Filters from 'client/components/TablePaginated/Filters/Filters'

import ExportButton from './ExportButton/ExportButton'
import { useFetchData } from './hooks/useFetchData'
import { useInitTablePaginated } from './hooks/useInitTablePaginated'
import Body from './Body'
import Count from './Count'
import DefaultEmptyList from './DefaultEmptyList'
import Header from './Header'
import Paginator from './Paginator'
import { Props as BaseProps, TablePaginatedCounter, TablePaginatedSkeleton } from './types'
Expand All @@ -31,30 +36,44 @@ type Props<Datum extends object> = Pick<HTMLAttributes<HTMLDivElement>, 'classNa
const TablePaginated = <Datum extends object>(props: Props<Datum>) => {
const { className, gridTemplateColumns } = props // HTMLDivElement Props
const { marginPagesDisplayed, pageRangeDisplayed } = props // Paginator Props
const { columns, path, limit } = props // Base Props
const { columns, filters, limit, path } = props // Base Props
const { counter, EmptyListComponent, export: exportTable, header, skeleton, wrapCells } = props // Component Props

useFetchData({ path, limit, counter })
useInitTablePaginated({ filters, path })
useFetchData({ counter, limit, path })
const count = useTablePaginatedCount(path)
const data = useTablePaginatedData(path)
const page = useTablePaginatedPage(path)

if (count?.total === 0) {
return (
<div className={className}>
<EmptyListComponent />
{counter.show && <Count counter={counter} path={path} />}
</div>
)
}
const withFilters = useMemo<boolean>(() => filters.filter((filter) => !filter.hidden).length > 0, [filters])
const divRef = useRef<HTMLDivElement>()

// on page update -> scroll on top
useOnUpdate(() => {
if (!Objects.isNil(data)) {
setTimeout(() => {
const opts: ScrollIntoViewOptions = { behavior: 'smooth', block: 'start', inline: 'nearest' }
divRef.current?.parentElement?.parentElement?.scrollIntoView(opts)
})
}
}, [page])

return (
<div className={classNames('table-paginated', className)}>
<div ref={divRef} className={classNames('table-paginated', className)}>
<div>
{exportTable && <ExportButton path={path} />}
{(exportTable || withFilters) && (
<div className="table-paginated-actions">
{exportTable && <ExportButton path={path} />}
{exportTable && withFilters && <div className="table-paginated-actions-sep" />}
{withFilters && <Filters filters={filters} path={path} />}
</div>
)}
<DataGrid
className="table-paginated-datagrid"
style={{ gridTemplateColumns: gridTemplateColumns ?? `repeat(${columns.length}, auto)` }}
>
{header && <Header columns={columns} path={path} />}
{count?.total === 0 && <EmptyListComponent />}
<Body columns={columns} limit={limit} path={path} skeleton={skeleton} wrapCells={wrapCells} />
</DataGrid>
</div>
Expand All @@ -72,9 +91,11 @@ const TablePaginated = <Datum extends object>(props: Props<Datum>) => {
}

TablePaginated.defaultProps = {
EmptyListComponent: () => <div />,
counter: { show: true },
EmptyListComponent: DefaultEmptyList,
export: false,
// eslint-disable-next-line react/default-props-match-prop-types
filters: [],
header: true,
// eslint-disable-next-line react/default-props-match-prop-types
limit: 30,
Expand Down
Loading

0 comments on commit e533901

Please sign in to comment.