-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #341 from dhis2/feat/usemodelgist
feat: data element list
- Loading branch information
Showing
40 changed files
with
1,769 additions
and
446 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { Loader } from './loading' | ||
export { HidePreventUnmount } from './HidePreventUnmount' | ||
export * from './sectionList' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { CircularLoader } from '@dhis2/ui' | ||
import cx from 'classnames' | ||
import React from 'react' | ||
import styles from './Loader.module.css' | ||
|
||
export const LoadingSpinner = ({ | ||
centered = true, | ||
...rest | ||
}: { | ||
centered?: boolean | ||
}) => ( | ||
<CircularLoader | ||
{...rest} | ||
className={cx(styles.loadingSpinner, { [styles.centered]: centered })} | ||
/> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
.listHeaderNormal { | ||
background-color: #fff; | ||
width: 100%; | ||
height: 24px; | ||
display: flex; | ||
align-items: center; | ||
height: 48px; | ||
padding: var(--spacers-dp8); | ||
margin-top: var(--spacers-dp8); | ||
gap: var(--spacers-dp8); | ||
} | ||
|
||
.listHeaderNormal a { | ||
line-height: var(--spacers-dp16); | ||
} | ||
|
||
.listRow td { | ||
padding-top: var(--spacers-dp8); | ||
padding-bottom: var(--spacers-dp8); | ||
} | ||
|
||
.listActions { | ||
display: flex; | ||
gap: var(--spacers-dp8); | ||
} | ||
|
||
.listActions button { | ||
padding: 0 2px !important; | ||
} | ||
|
||
.listEmpty { | ||
text-align: center; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import i18n from '@dhis2/d2-i18n' | ||
import { | ||
DataTable, | ||
DataTableColumnHeader, | ||
DataTableRow, | ||
TableBody, | ||
TableHead, | ||
Checkbox, | ||
} from '@dhis2/ui' | ||
import React, { PropsWithChildren } from 'react' | ||
import { CheckBoxOnChangeObject } from '../../types' | ||
import { IdentifiableObject } from '../../types/generated' | ||
import { SelectedColumns } from './types' | ||
|
||
type SectionListProps<Model extends IdentifiableObject> = { | ||
headerColumns: SelectedColumns<Model> | ||
onSelectAll: (checked: boolean) => void | ||
allSelected?: boolean | ||
} | ||
|
||
export const SectionList = <Model extends IdentifiableObject>({ | ||
allSelected, | ||
headerColumns, | ||
children, | ||
onSelectAll, | ||
}: PropsWithChildren<SectionListProps<Model>>) => { | ||
return ( | ||
<DataTable> | ||
<TableHead> | ||
<DataTableRow> | ||
<DataTableColumnHeader width="48px"> | ||
<Checkbox | ||
checked={allSelected} | ||
onChange={({ checked }: CheckBoxOnChangeObject) => | ||
onSelectAll(checked) | ||
} | ||
/> | ||
</DataTableColumnHeader> | ||
{headerColumns.map((headerColumn) => ( | ||
<DataTableColumnHeader | ||
key={headerColumn.modelPropertyName} | ||
> | ||
{headerColumn.label} | ||
</DataTableColumnHeader> | ||
))} | ||
<DataTableColumnHeader> | ||
{i18n.t('Actions')} | ||
</DataTableColumnHeader> | ||
</DataTableRow> | ||
</TableHead> | ||
<TableBody>{children}</TableBody> | ||
</DataTable> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { DataTableRow, DataTableCell } from '@dhis2/ui' | ||
import React from 'react' | ||
import { LoadingSpinner } from '../loading/LoadingSpinner' | ||
|
||
export const SectionListLoader = () => { | ||
return ( | ||
<DataTableRow> | ||
<DataTableCell colSpan="100%"> | ||
<LoadingSpinner /> | ||
</DataTableCell> | ||
</DataTableRow> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import i18n from '@dhis2/d2-i18n' | ||
import { DataTableRow, DataTableCell, NoticeBox } from '@dhis2/ui' | ||
import React, { PropsWithChildren } from 'react' | ||
import css from './SectionList.module.css' | ||
|
||
export const SectionListMessage = ({ children }: PropsWithChildren) => { | ||
return ( | ||
<DataTableRow> | ||
<DataTableCell colSpan="100%">{children}</DataTableCell> | ||
</DataTableRow> | ||
) | ||
} | ||
|
||
export const SectionListEmpty = () => { | ||
return ( | ||
<SectionListMessage> | ||
<p className={css.listEmpty}> | ||
{i18n.t("There aren't any items that match your filter.")} | ||
</p> | ||
</SectionListMessage> | ||
) | ||
} | ||
|
||
export const SectionListError = () => { | ||
return ( | ||
<SectionListMessage> | ||
<NoticeBox error={true} title={i18n.t('An error occurred')}> | ||
{i18n.t('An error occurred while loading the items.')} | ||
</NoticeBox> | ||
</SectionListMessage> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { Pagination, DataTableRow, DataTableCell } from '@dhis2/ui' | ||
import React, { useEffect, useCallback, useMemo } from 'react' | ||
import { | ||
useQueryParam, | ||
NumericObjectParam, | ||
withDefault, | ||
} from 'use-query-params' | ||
import { GistPaginator } from '../../lib/' | ||
import { GistCollectionResponse } from '../../types/generated' | ||
|
||
type SectionListPaginationProps = { | ||
data: GistCollectionResponse | undefined | ||
} | ||
|
||
type PaginationQueryParams = { | ||
page: number | ||
pageSize: number | ||
} | ||
|
||
const defaultPaginationQueryParams = { | ||
page: 1, | ||
pageSize: 20, | ||
} | ||
|
||
const PAGE_SIZES = [5, 10, 20, 30, 40, 50, 75, 100] | ||
|
||
const paginationQueryParams = withDefault( | ||
NumericObjectParam, | ||
defaultPaginationQueryParams | ||
) | ||
|
||
export const usePaginationQueryParams = () => { | ||
const [params, setParams] = useQueryParam('pager', paginationQueryParams, { | ||
removeDefaultsFromUrl: true, | ||
}) | ||
|
||
return useMemo( | ||
() => [validatePagerParams(params), setParams] as const, | ||
[params, setParams] | ||
) | ||
} | ||
|
||
const validatePagerParams = ( | ||
params: typeof paginationQueryParams.default | ||
): PaginationQueryParams => { | ||
if (!params) { | ||
return defaultPaginationQueryParams | ||
} | ||
const isValid = Object.values(params).every( | ||
(value) => value && !isNaN(value) | ||
) | ||
if (!isValid) { | ||
return defaultPaginationQueryParams | ||
} | ||
|
||
const pageSize = params.pageSize as number | ||
const page = params.page as number | ||
|
||
// since pageSize can be changed in URL, find the closest valid pageSize | ||
const validatedPageSize = PAGE_SIZES.reduce((prev, curr) => | ||
Math.abs(curr - pageSize) < Math.abs(prev - pageSize) ? curr : prev | ||
) | ||
|
||
return { | ||
page, | ||
pageSize: validatedPageSize, | ||
} | ||
} | ||
|
||
function useUpdatePaginationParams( | ||
data?: GistCollectionResponse | ||
): GistPaginator { | ||
const pager = data?.pager | ||
const [, setParams] = usePaginationQueryParams() | ||
|
||
const getNextPage = useCallback(() => { | ||
if (!pager?.nextPage) { | ||
return false | ||
} | ||
setParams((prevPager) => ({ ...prevPager, page: pager.page + 1 })) | ||
return true | ||
}, [pager, setParams]) | ||
|
||
const getPrevPage = useCallback(() => { | ||
if (!pager?.prevPage) { | ||
return false | ||
} | ||
setParams((prevPager) => ({ ...prevPager, page: pager.page - 1 })) | ||
return true | ||
}, [pager, setParams]) | ||
|
||
const goToPage = useCallback( | ||
(page: number) => { | ||
if (!pager?.pageCount || page > pager.pageCount) { | ||
return false | ||
} | ||
setParams((prevPager) => ({ ...prevPager, page })) | ||
return true | ||
}, | ||
[pager, setParams] | ||
) | ||
|
||
const changePageSize = useCallback( | ||
(pageSize: number) => { | ||
setParams((prevPager) => ({ ...prevPager, pageSize: pageSize })) | ||
return true | ||
}, | ||
[setParams] | ||
) | ||
|
||
return { | ||
getNextPage, | ||
getPrevPage, | ||
goToPage, | ||
changePageSize, | ||
pager, | ||
} | ||
} | ||
|
||
/** clamps a number between min and max, | ||
*resulting in a number between min and max (inclusive). | ||
*/ | ||
const clamp = (value: number, min: number, max: number) => | ||
Math.max(min, Math.min(value, max)) | ||
|
||
export const SectionListPagination = ({ data }: SectionListPaginationProps) => { | ||
const [paginationParams] = usePaginationQueryParams() | ||
const pagination = useUpdatePaginationParams(data) | ||
|
||
useEffect(() => { | ||
// since page can be controlled by params | ||
// do a refetch if page is out of bounds | ||
const page = paginationParams.page | ||
|
||
const clamped = clamp(page, 1, pagination.pager?.pageCount || 1) | ||
if (page !== clamped) { | ||
pagination.goToPage(clamped) | ||
} | ||
}, [pagination, paginationParams.page]) | ||
|
||
if (!pagination.pager?.total) { | ||
return null | ||
} | ||
|
||
// Prevent out of bounds for page-selector | ||
// note that this will make the UI-selector out of sync with the actual data | ||
// but paginator throws error if page is out of bounds | ||
// useEffect above will refetch last page - so this should only be for a very brief render | ||
const page = clamp( | ||
paginationParams.page, | ||
1, | ||
pagination.pager?.pageCount || 1 | ||
) | ||
|
||
return ( | ||
<DataTableRow> | ||
<DataTableCell colSpan="100%"> | ||
<Pagination | ||
pageSizes={PAGE_SIZES.map((s) => s.toString())} | ||
page={page} | ||
pageSize={paginationParams.pageSize} | ||
pageCount={pagination.pager?.pageCount} | ||
total={pagination.pager?.total} | ||
onPageSizeChange={pagination.changePageSize} | ||
onPageChange={pagination.goToPage} | ||
/> | ||
</DataTableCell> | ||
</DataTableRow> | ||
) | ||
} |
Oops, something went wrong.