From fcd341c198e09cda0a2ef25302a603ec6353e386 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Tue, 6 Jun 2023 00:08:30 +0200 Subject: [PATCH 01/25] feat: useModelGist --- package.json | 4 +- src/lib/index.ts | 1 + src/lib/models/index.ts | 1 + src/lib/models/useModelGist.ts | 213 +++++++++++++++++++++++++++++++++ src/types/query.ts | 2 + yarn.lock | 36 +++++- 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 src/lib/models/index.ts create mode 100644 src/lib/models/useModelGist.ts diff --git a/package.json b/package.json index 0f687f59..984bb47c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "d2-app-scripts start", "test": "d2-app-scripts test", "deploy": "d2-app-scripts deploy", - "lint": "yarn check-types && yarn d2-style check", + "lint": "yarn d2-style check", "lint:staged": "yarn lint --staged", "format": "yarn d2-style apply", "format:staged": "yarn format --staged", @@ -26,7 +26,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "@dhis2/app-runtime": "^3.8.0", + "@dhis2/app-runtime": "^3.9.3", "react-router-dom": "^6.11.2" } } diff --git a/src/lib/index.ts b/src/lib/index.ts index 183e8bd0..b8419897 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1 +1,2 @@ export * from './errors' +export * from './models' diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts new file mode 100644 index 00000000..8f7a465f --- /dev/null +++ b/src/lib/models/index.ts @@ -0,0 +1 @@ +export { useModelGist } from './useModelGist' diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts new file mode 100644 index 00000000..7751f20a --- /dev/null +++ b/src/lib/models/useModelGist.ts @@ -0,0 +1,213 @@ +import { useDataQuery, useDataEngine } from '@dhis2/app-runtime' +import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react' +import { + GistModel, + GistParams as BaseGistParams, + GistPreferences, + IdentifiableObject, + DataElement, + GetReferencedModels, + GistResponse, + GistPager, + GistCollectionResponse, + GistPagedResponse, + GistObjectResponse, +} from '../../types/models' +import { QueryResponse, Query } from '../../types/query' + +// these normally are just strings, but useQuery supports arrays +type GistParams = Omit & { + fields: string | string[] + filter: string | string[] +} +enum GistResourceTypeEnum { + Collection = 'collection', + Object = 'object', + ObjectField = 'objectField', +} + +type GistResourceString = `${string}/gist` +type ResourceQuery = Query[number] +type GistResourceQuery = Omit & { + resource: GistResourceString +} + +// note that we do not support parallel queries +// this makes it significantly easier to implement +// and parallel use of gist queries shouldn't be a common use case +type GistQuery = { + data: GistResourceQuery +} + +function createGistResponse(queryResponse: ReturnType) { + const { loading, error, data } = queryResponse + data + return Object.entries(queryResponse).reduce((acc, [key, value]) => { + return acc + }, {} as GistResponse) +} + +function createGistQuery( + resource: GistResourceString, + params?: GistParams +): GistQuery { + return { + data: { + resource: `${resource}`, + params: ({ page }) => ({ + ...params, + page, + }), + }, + } +} + +function usePagination( + refetch: QueryResponse['refetch'], + data?: unknown +): GistResultPagination | null { + let pager: GistPager | undefined + if (isDataCollection(data)) { + pager = data.pager + } + const getNextPage = useCallback(() => { + if (!pager?.nextPage) { + return false + } + refetch({ page: pager.page + 1 }) + return true + }, [refetch, pager]) + const getPrevPage = useCallback(() => { + if (!pager?.prevPage) { + return false + } + refetch({ page: pager.page - 1 }) + return true + }, [refetch, pager]) + + return useMemo(() => { + if (!pager) { + return null + } + return { + ...pager, + getNextPage, + getPrevPage, + } + }, [pager, getNextPage, getPrevPage]) +} + +type GistPaginator = { + getNextPage: () => boolean + getPrevPage: () => boolean +} + +type GistResultPagination = GistPager & GistPaginator + +type Resz = string +type BaseUseGistModelResult = Pick< + QueryResponse, + 'loading' | 'error' | 'called' +> & { + data?: Response +} + +type UseGistModelResultPaginated = + BaseUseGistModelResult & { + pagination: GistResultPagination + } +type UseGistModelResult = + | BaseUseGistModelResult + | UseGistModelResultPaginated + +const paged = {} as UseGistModelResultPaginated< + GistCollectionResponse +> + +const isDataCollection = ( + data: unknown +): data is GistCollectionResponse => { + return (data as any).pager !== undefined +} + +//const createPagination = (data: GistPager): GistResultPagination => {} + +/** + * Takes a string like "dataElements/gist" and returns the type of the resource, eg. "dataElements" + * + * @param gistResource + */ +const resolveResourceFromGistString = ( + gistResource: GistResourceString +): string => { + const delimeter = '/' + const apiDelimeter = '/api' + const isAbsoluteResource = gistResource.includes(apiDelimeter) + const gistUrlPart = isAbsoluteResource + ? gistResource.split(apiDelimeter)[1] + : gistResource + return gistUrlPart.split(delimeter)[1] +} + +export function useModelGist< + Response extends GistPagedResponse, + R extends string = string + // ResourceType extends GistResourceTypeEnum = ResolveResourceTypeFromString +>( + gistResource: GistResourceString, + params?: GistParams +): UseGistModelResultPaginated +export function useModelGist< + Response extends GistObjectResponse + // ResourceType extends GistResourceTypeEnum = ResolveResourceTypeFromString +>( + gistResource: GistResourceString, + params?: GistParams +): UseGistModelResult +export function useModelGist< + Response extends GistResponse, + R extends string = string + // ResourceType extends GistResourceTypeEnum = ResolveResourceTypeFromString +>( + gistResource: GistResourceString, + params?: GistParams +): UseGistModelResult { + const [gistQuery] = useState( + createGistQuery(gistResource, params) + ) + const resource = resolveResourceFromGistString(gistResource) + // const resourceType = resolveResourceType(resource) as ResourceType + // const gistQuery = useRef(createGistQuery(gistResource, params)) + // const [apiEndpointQueries, setApiEndpointQueries] = React.useState({}) + const queryResponse = useDataQuery(gistQuery) + const pagination = usePagination(queryResponse.refetch, queryResponse.data) + // queryResponse.data?.pager + + //const pagination = useMemo(() => createPagination(queryResponse), []) + // queryResponse. + return React.useMemo(() => { + //const isCollection = isDataCollection(queryResponse.data) + + const baseResult: UseGistModelResult = { + loading: queryResponse.loading, + called: queryResponse.called, + error: queryResponse.error, + data: queryResponse.data, + } + if (pagination) { + const result: UseGistModelResultPaginated = { + ...baseResult, + pagination, + } + return result + } + return baseResult + }, [queryResponse, pagination]) +} + +// const data = +// useModelGist>( +// 'dataElements/gist' +// ) +// data. +// data?.data. diff --git a/src/types/query.ts b/src/types/query.ts index 3e2e7cf0..d4b9b55d 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -1,3 +1,5 @@ import type { useDataQuery } from '@dhis2/app-runtime' export type QueryResponse = ReturnType + +export type Query = Parameters[0] diff --git a/yarn.lock b/yarn.lock index f324bdf5..a9053691 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1959,7 +1959,7 @@ "@dhis2/pwa" "10.2.0" moment "^2.24.0" -"@dhis2/app-runtime@^3.6.1", "@dhis2/app-runtime@^3.8.0": +"@dhis2/app-runtime@^3.6.1": version "3.8.0" resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.8.0.tgz#4ec7fc4ec6647dc8428e3c0d2e14b2d188a993b9" integrity sha512-f5M1RfUJb4yZaPDywTfogVXjzWcuYGJ7JQzny6iWXrJu1+qrRKYbfFutYNhB+4bXD4bB59DelHWqVaHtrGvbVA== @@ -1969,16 +1969,36 @@ "@dhis2/app-service-data" "3.8.0" "@dhis2/app-service-offline" "3.8.0" +"@dhis2/app-runtime@^3.9.3": + version "3.9.3" + resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.9.3.tgz#6316aabf213be5ee33da36a015f85ef72bd54544" + integrity sha512-Exfp8EFa81uobEpFFKwxWi6KEioZlGhaQwIBb/dV9sxSjMEg3wO1ULdSi2VBdf9/UQKb/0YM+Ki7d9/bCsRrFw== + dependencies: + "@dhis2/app-service-alerts" "3.9.3" + "@dhis2/app-service-config" "3.9.3" + "@dhis2/app-service-data" "3.9.3" + "@dhis2/app-service-offline" "3.9.3" + "@dhis2/app-service-alerts@3.8.0": version "3.8.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.8.0.tgz#a899e3b392b6af49407be5251abe13eb0f2bdb60" integrity sha512-Y2gDxSLS91YwOyvRQXpZMMOF4GZeoswPypDFy1U1OV8S1HdNa4J3dFtKqwRaiC8KP2o9bi+MsJjDA5fwl/jaOA== +"@dhis2/app-service-alerts@3.9.3": + version "3.9.3" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.9.3.tgz#bb7d6ab98b2c2b49f2c1f84750f4015ee1e51058" + integrity sha512-Zp8J90c5Qbh9fqtDKT0QSI0jCOUst64cE0Y7rzCe7JUGUsD84FTYVHY6xPMnhDf65kQ2Amh4aiKVO45y40UjQA== + "@dhis2/app-service-config@3.8.0": version "3.8.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.8.0.tgz#59a9a6c1edb1cc094544a6f90f8c9cbd7ec6d565" integrity sha512-6xA065s85ry41OUuczKiqe4WD8wKqeDnuEBHApunJMWiLZ8o/3Y7KtmB2g5tcqIKnYSRBP3CY0naYPgdy25k6A== +"@dhis2/app-service-config@3.9.3": + version "3.9.3" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.9.3.tgz#f665c7858cea028866cc6ea6cea4119d000208b5" + integrity sha512-W2SPjWeCXKbr97PHz1DAHuRF/OKQ4jNUUmL3u4lAuCxnNJwQDJdV71fteevlQmT/tnl6tLeoy19Tlt0nDFP2jg== + "@dhis2/app-service-data@3.8.0": version "3.8.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.8.0.tgz#e666e71c8ac7921243b25495780502f49dcbbb30" @@ -1986,6 +2006,13 @@ dependencies: react-query "^3.13.11" +"@dhis2/app-service-data@3.9.3": + version "3.9.3" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.9.3.tgz#46758bf3f7863972e9f7db93bb61f0262281f16d" + integrity sha512-Ogyf/ORuO/si+u7G4nVlp0eFgU8Jg3Cvol0REvAzKkL20dEQ/ysgdqAzG7QuTWrk+QdvUTrF4ClKBPgQ9bqwzw== + dependencies: + react-query "^3.13.11" + "@dhis2/app-service-offline@3.8.0": version "3.8.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.8.0.tgz#5854342169c3214fccbbac0abbf6a8408aab3475" @@ -1993,6 +2020,13 @@ dependencies: lodash "^4.17.21" +"@dhis2/app-service-offline@3.9.3": + version "3.9.3" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.9.3.tgz#a4ec287e33f00768ff80dc5ceb59c674b50a80ab" + integrity sha512-CKbmy7nCvUwWsJU99Mqhwh7cctc+qib8OflkZthz+dgEIvz7a0H1k89Fgbu/4NAefWZa4+Dh46jnW6/CXeQeSg== + dependencies: + lodash "^4.17.21" + "@dhis2/app-shell@10.2.0": version "10.2.0" resolved "https://registry.yarnpkg.com/@dhis2/app-shell/-/app-shell-10.2.0.tgz#2f69aa047dedb6545c75052d8969a80502f0d4a6" From 74b7321ae66c75ac89927a87af0fc95d147093a9 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Tue, 6 Jun 2023 14:55:23 +0200 Subject: [PATCH 02/25] fix: cleanup --- src/lib/models/useModelGist.ts | 116 ++++++++++----------------------- src/types/generated/utility.ts | 20 +++--- 2 files changed, 47 insertions(+), 89 deletions(-) diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 7751f20a..2a84d863 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -1,12 +1,8 @@ -import { useDataQuery, useDataEngine } from '@dhis2/app-runtime' -import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react' +import { useDataQuery } from '@dhis2/app-runtime' +import React, { useState, useCallback } from 'react' import { - GistModel, GistParams as BaseGistParams, - GistPreferences, IdentifiableObject, - DataElement, - GetReferencedModels, GistResponse, GistPager, GistCollectionResponse, @@ -17,13 +13,8 @@ import { QueryResponse, Query } from '../../types/query' // these normally are just strings, but useQuery supports arrays type GistParams = Omit & { - fields: string | string[] - filter: string | string[] -} -enum GistResourceTypeEnum { - Collection = 'collection', - Object = 'object', - ObjectField = 'objectField', + fields?: string | string[] + filter?: string | string[] } type GistResourceString = `${string}/gist` @@ -36,15 +27,11 @@ type GistResourceQuery = Omit & { // this makes it significantly easier to implement // and parallel use of gist queries shouldn't be a common use case type GistQuery = { - data: GistResourceQuery + result: GistResourceQuery } -function createGistResponse(queryResponse: ReturnType) { - const { loading, error, data } = queryResponse - data - return Object.entries(queryResponse).reduce((acc, [key, value]) => { - return acc - }, {} as GistResponse) +type GistQueryResult = { + result: Response } function createGistQuery( @@ -52,7 +39,7 @@ function createGistQuery( params?: GistParams ): GistQuery { return { - data: { + result: { resource: `${resource}`, params: ({ page }) => ({ ...params, @@ -65,8 +52,9 @@ function createGistQuery( function usePagination( refetch: QueryResponse['refetch'], data?: unknown -): GistResultPagination | null { +): GistPaginator | null { let pager: GistPager | undefined + if (isDataCollection(data)) { pager = data.pager } @@ -77,6 +65,7 @@ function usePagination( refetch({ page: pager.page + 1 }) return true }, [refetch, pager]) + const getPrevPage = useCallback(() => { if (!pager?.prevPage) { return false @@ -85,16 +74,10 @@ function usePagination( return true }, [refetch, pager]) - return useMemo(() => { - if (!pager) { - return null - } - return { - ...pager, - getNextPage, - getPrevPage, - } - }, [pager, getNextPage, getPrevPage]) + return { + getNextPage, + getPrevPage, + } } type GistPaginator = { @@ -102,9 +85,6 @@ type GistPaginator = { getPrevPage: () => boolean } -type GistResultPagination = GistPager & GistPaginator - -type Resz = string type BaseUseGistModelResult = Pick< QueryResponse, 'loading' | 'error' | 'called' @@ -114,39 +94,17 @@ type BaseUseGistModelResult = Pick< type UseGistModelResultPaginated = BaseUseGistModelResult & { - pagination: GistResultPagination + pagination: GistPaginator } type UseGistModelResult = | BaseUseGistModelResult | UseGistModelResultPaginated -const paged = {} as UseGistModelResultPaginated< - GistCollectionResponse -> - const isDataCollection = ( data: unknown ): data is GistCollectionResponse => { - return (data as any).pager !== undefined -} - -//const createPagination = (data: GistPager): GistResultPagination => {} - -/** - * Takes a string like "dataElements/gist" and returns the type of the resource, eg. "dataElements" - * - * @param gistResource - */ -const resolveResourceFromGistString = ( - gistResource: GistResourceString -): string => { - const delimeter = '/' - const apiDelimeter = '/api' - const isAbsoluteResource = gistResource.includes(apiDelimeter) - const gistUrlPart = isAbsoluteResource - ? gistResource.split(apiDelimeter)[1] - : gistResource - return gistUrlPart.split(delimeter)[1] + // gist endpoints are always paged if they're collections + return (data as GistCollectionResponse)?.pager !== undefined } export function useModelGist< @@ -175,24 +133,27 @@ export function useModelGist< const [gistQuery] = useState( createGistQuery(gistResource, params) ) - const resource = resolveResourceFromGistString(gistResource) - // const resourceType = resolveResourceType(resource) as ResourceType - // const gistQuery = useRef(createGistQuery(gistResource, params)) - // const [apiEndpointQueries, setApiEndpointQueries] = React.useState({}) - const queryResponse = useDataQuery(gistQuery) - const pagination = usePagination(queryResponse.refetch, queryResponse.data) - // queryResponse.data?.pager - - //const pagination = useMemo(() => createPagination(queryResponse), []) - // queryResponse. - return React.useMemo(() => { - //const isCollection = isDataCollection(queryResponse.data) + // workaround to keep previous data while fetching, to prevent flickering + // "mimicks" react-query's 'keepPreviousData' + const [previousData, setPreviousData] = + useState>() + const queryResponse = useDataQuery>(gistQuery) + const stickyData = queryResponse.data || previousData + + if (queryResponse.data && previousData !== queryResponse.data) { + setPreviousData(queryResponse.data) + } + const pagination = usePagination( + queryResponse.refetch, + queryResponse.data?.result + ) + return React.useMemo(() => { const baseResult: UseGistModelResult = { loading: queryResponse.loading, called: queryResponse.called, error: queryResponse.error, - data: queryResponse.data, + data: stickyData?.result, } if (pagination) { const result: UseGistModelResultPaginated = { @@ -202,12 +163,5 @@ export function useModelGist< return result } return baseResult - }, [queryResponse, pagination]) + }, [queryResponse, stickyData, pagination]) } - -// const data = -// useModelGist>( -// 'dataElements/gist' -// ) -// data. -// data?.data. diff --git a/src/types/generated/utility.ts b/src/types/generated/utility.ts index d83f4ac8..c7ab2826 100644 --- a/src/types/generated/utility.ts +++ b/src/types/generated/utility.ts @@ -32,22 +32,26 @@ export type GistModelCollection = GistModel[] // need it's own type because the key is based on the resource export type GistModelCollectionPart< T extends IdentifiableObject, - Resource extends string + Resource extends string = string > = { [K in Resource]: GistModel[] } -export type GistCollectionResponse< - T extends IdentifiableObject, - Resource extends string -> = { +export type GistPagedResponse = { pager: GistPager -} & GistModelCollectionPart +} -export type GistObjectResponse = GistModel +export type GistCollectionResponse< + T extends IdentifiableObject = IdentifiableObject, + Resource extends string = string +> = GistPagedResponse & GistModelCollectionPart + +export type GistObjectResponse< + T extends IdentifiableObject = IdentifiableObject +> = GistModel export type GistResponse< - T extends IdentifiableObject, + T extends IdentifiableObject = IdentifiableObject, R extends string = string > = GistCollectionResponse | GistObjectResponse From 5e76a8c4a7baca0435d30a98082cfc1360a31481 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Tue, 6 Jun 2023 15:13:27 +0200 Subject: [PATCH 03/25] fix: add dynamicparams --- src/lib/models/useModelGist.ts | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 2a84d863..edb41824 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -41,8 +41,9 @@ function createGistQuery( return { result: { resource: `${resource}`, - params: ({ page }) => ({ + params: ({ page, ...dynamicParams }) => ({ ...params, + ...dynamicParams, page, }), }, @@ -87,7 +88,7 @@ type GistPaginator = { type BaseUseGistModelResult = Pick< QueryResponse, - 'loading' | 'error' | 'called' + 'loading' | 'error' | 'called' | 'refetch' > & { data?: Response } @@ -107,26 +108,17 @@ const isDataCollection = ( return (data as GistCollectionResponse)?.pager !== undefined } -export function useModelGist< - Response extends GistPagedResponse, - R extends string = string - // ResourceType extends GistResourceTypeEnum = ResolveResourceTypeFromString ->( +export function useModelGist( gistResource: GistResourceString, params?: GistParams ): UseGistModelResultPaginated -export function useModelGist< - Response extends GistObjectResponse - // ResourceType extends GistResourceTypeEnum = ResolveResourceTypeFromString ->( + +export function useModelGist( gistResource: GistResourceString, params?: GistParams ): UseGistModelResult -export function useModelGist< - Response extends GistResponse, - R extends string = string - // ResourceType extends GistResourceTypeEnum = ResolveResourceTypeFromString ->( + +export function useModelGist( gistResource: GistResourceString, params?: GistParams ): UseGistModelResult { @@ -154,6 +146,7 @@ export function useModelGist< called: queryResponse.called, error: queryResponse.error, data: stickyData?.result, + refetch: queryResponse.refetch, } if (pagination) { const result: UseGistModelResultPaginated = { From 7721bbb2f04c3442777867090518f0b4b7636163 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 19 Jun 2023 23:07:31 +0200 Subject: [PATCH 04/25] feat(sectionlist): add sectionList for data elements --- package.json | 1 + src/components/index.tsx | 1 + .../sectionList/SectionList.module.css | 16 + src/components/sectionList/SectionList.tsx | 44 + src/components/sectionList/SectionListRow.tsx | 63 + src/components/sectionList/index.ts | 3 + src/components/sectionList/types.ts | 9 + src/pages/dataElements/List.tsx | 72 +- yarn.lock | 1019 ++++++++--------- 9 files changed, 675 insertions(+), 553 deletions(-) create mode 100644 src/components/sectionList/SectionList.module.css create mode 100644 src/components/sectionList/SectionList.tsx create mode 100644 src/components/sectionList/SectionListRow.tsx create mode 100644 src/components/sectionList/index.ts create mode 100644 src/components/sectionList/types.ts diff --git a/package.json b/package.json index 984bb47c..81175fc7 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@dhis2/app-runtime": "^3.9.3", + "@dhis2/ui": "^8.13.10", "react-router-dom": "^6.11.2" } } diff --git a/src/components/index.tsx b/src/components/index.tsx index c77169ac..f3f2873c 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -1,2 +1,3 @@ export { Loader } from './loading' export { HidePreventUnmount } from './HidePreventUnmount' +export * from './sectionList' diff --git a/src/components/sectionList/SectionList.module.css b/src/components/sectionList/SectionList.module.css new file mode 100644 index 00000000..61bc13d8 --- /dev/null +++ b/src/components/sectionList/SectionList.module.css @@ -0,0 +1,16 @@ +.listRow { +} + +.listRow td { + padding-top: 8px; + padding-bottom: 8px; +} + +.listActions { + display: flex; + gap: 8px; +} + +.listActions button { + padding: 0 2px !important; +} diff --git a/src/components/sectionList/SectionList.tsx b/src/components/sectionList/SectionList.tsx new file mode 100644 index 00000000..d0a77d71 --- /dev/null +++ b/src/components/sectionList/SectionList.tsx @@ -0,0 +1,44 @@ +import i18n from '@dhis2/d2-i18n' +import { + DataTable, + DataTableColumnHeader, + DataTableRow, + TableBody, + TableHead, + Checkbox, +} from '@dhis2/ui' +import React, { PropsWithChildren } from 'react' +import { IdentifiableObject } from '../../types/generated' +import { SelectedColumns } from './types' + +type SectionListProps = { + headerColumns: SelectedColumns +} + +export const SectionList = ({ + headerColumns, + children, +}: PropsWithChildren>) => { + return ( + + + + + + + {headerColumns.map((headerColumn) => ( + + {headerColumn.label} + + ))} + + {i18n.t('Actions')} + + + + {children} + + ) +} diff --git a/src/components/sectionList/SectionListRow.tsx b/src/components/sectionList/SectionListRow.tsx new file mode 100644 index 00000000..538cbe8a --- /dev/null +++ b/src/components/sectionList/SectionListRow.tsx @@ -0,0 +1,63 @@ +import { DataTableRow, DataTableCell, Checkbox, Button } from '@dhis2/ui' +import { IconEdit24, IconMore24 } from '@dhis2/ui-icons' +import React from 'react' +import { Link } from 'react-router-dom' +import { IdentifiableObject } from '../../types/generated' +import css from './SectionList.module.css' +import { SelectedColumns } from './types' + +export type SectionListRowProps = { + modelData: Model + selectedColumns: SelectedColumns +} + +export function SectionListRow({ + selectedColumns, + modelData, +}: SectionListRowProps) { + return ( + + + + + {selectedColumns.map(({ modelPropertyName }) => ( + + {/* TODO: Handle constant translations and resolve displayvalues to components */} + {typeof modelData[modelPropertyName] === 'object' + ? modelPropertyName + : modelData[modelPropertyName]} + + ))} + + + + + ) +} + +const ListActions = ({ modelId }: { modelId: string }) => { + return ( +
+ + +
+ ) +} + +const ActionEdit = ({ modelId }: { modelId: string }) => { + return ( + + + + ) +} + +const ActionMore = () => { + return ( + + ) +} diff --git a/src/components/sectionList/index.ts b/src/components/sectionList/index.ts new file mode 100644 index 00000000..1457a992 --- /dev/null +++ b/src/components/sectionList/index.ts @@ -0,0 +1,3 @@ +export * from './SectionList' +export * from './SectionListRow' +export type * from './types' diff --git a/src/components/sectionList/types.ts b/src/components/sectionList/types.ts new file mode 100644 index 00000000..9081f3fc --- /dev/null +++ b/src/components/sectionList/types.ts @@ -0,0 +1,9 @@ +import { IdentifiableObject } from '../../types/generated' + +export type SelectedColumn = { + label: string + modelPropertyName: keyof Model +} + +export type SelectedColumns = + SelectedColumn[] diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index d58967cd..0a94ad43 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -1,5 +1,73 @@ -import React from 'react' +import i18n from '@dhis2/d2-i18n' +import React, { useEffect } from 'react' +import { SectionList, SectionListRow, SelectedColumns } from '../../components' +import { useModelGist } from '../../lib/' +import { DataElement, GistCollectionResponse } from '../../types/generated' + +const filterFields = [ + 'access', + 'id', + 'name', + 'domainType', + 'valueType', + 'lastUpdated', + 'sharing', +] as const + +type FilteredDataElement = Pick + +type DataElements = GistCollectionResponse + +const defaulHeaderColumns: SelectedColumns = [ + { + modelPropertyName: 'name', + label: i18n.t('Name'), + }, + { modelPropertyName: 'domainType', label: i18n.t('Domain') }, + { modelPropertyName: 'valueType', label: i18n.t('Value') }, + { modelPropertyName: 'lastUpdated', label: i18n.t('Last updated') }, + { modelPropertyName: 'sharing', label: i18n.t('Public access') }, +] export const Component = () => { - return
This is a list of Data Elements
+ const { refetch, data, pagination } = useModelGist( + 'dataElements/gist', + { + fields: filterFields.concat(), + pageListName: 'result', + order: 'name:ASC', + } + ) + + const [filter, setFilter] = React.useState('') + + useEffect(() => { + if (filter) { + console.log('refetch', filter) + refetch({ + filter: [`name:ilike:${filter}`, 'domainType:eq:AGGREGATE'], + }) + } else { + refetch({ filter: undefined }) + } + }, [refetch, filter]) + + return ( +
+ setFilter(val.target.value)} + > + + {data?.result.map((dataElement) => ( + + ))} + + +
+ ) } diff --git a/yarn.lock b/yarn.lock index a9053691..ddd3149b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,16 +68,7 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.20.7", "@babel/generator@^7.7.2": - version "7.20.14" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" - integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== - dependencies: - "@babel/types" "^7.20.7" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/generator@^7.21.0": +"@babel/generator@^7.20.7", "@babel/generator@^7.21.0", "@babel/generator@^7.7.2": version "7.21.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== @@ -159,15 +150,7 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-function-name@^7.21.0": +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== @@ -308,12 +291,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.13", "@babel/parser@^7.20.7": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.13.tgz#ddf1eb5a813588d2fb1692b70c6fce75b945c088" - integrity sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw== - -"@babel/parser@^7.21.0", "@babel/parser@^7.7.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.7.0": version "7.21.1" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.1.tgz#a8f81ee2fe872af23faea4b17a08fcc869de7bcc" integrity sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg== @@ -1077,23 +1055,7 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.2": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" - integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.13" - "@babel/types" "^7.20.7" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.7.0": +"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.0.tgz#0e1807abd5db98e6a19c204b80ed1e3f5bca0edc" integrity sha512-Xdt2P1H4LKTO8ApPfnO1KmzYMFpp7D/EinoXzLYN/cHcBNrVCAkAtGUcXnHXrl/VGktureU6fkQrHSBE2URfoA== @@ -1117,16 +1079,7 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" - integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.21.0", "@babel/types@^7.7.0": +"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819" integrity sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow== @@ -1388,566 +1341,583 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz#c9c61d9fe5ca5ac664e1153bb0aa0eba1c6d6308" integrity sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw== -"@dhis2-ui/alert@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/alert/-/alert-8.7.7.tgz#a909c6182b3c7f6a421e70d8f356a8053ca67a01" - integrity sha512-kAqG+/q1E9ZxOs7D8nw58bq8Q0A9Lle/1Y03qIWLWzz+TDZt5m/YkVzWhEwUQv+uFynZXh6QPROIbH5/a9Yg2Q== +"@dhis2-ui/alert@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/alert/-/alert-8.13.10.tgz#c298180208f7bc4ecf29940796a43f502f98915b" + integrity sha512-3XB0ftudQUvaNPSuIQDyIWdFsxUnhiS7zoQBM5nArLjiW0JTwAuGB2W3UxpAZcaFQbjRZlaN9qEnDdLZ1YF4oQ== dependencies: - "@dhis2-ui/portal" "8.7.7" + "@dhis2-ui/portal" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/box@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/box/-/box-8.7.7.tgz#0ba4cb61fa087e80ef67fec8043cf509ef61f3c5" - integrity sha512-inS/ymN/xQH79rPlUGnFEslLiWmHXXicFy192imQD6TaGTmdvsxOU8kF4jzic9Qbwf8pYC8bwsxmvxBqVupJCg== +"@dhis2-ui/box@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/box/-/box-8.13.10.tgz#bde5b658a80170f2cd56e265083ae99a1130e191" + integrity sha512-aHzFERPRdG1L2NHRUE0qKNnn7DbRxdCxQ9IWRDSf8BTZDkm3ueiLfkLeGqkBxhjQpW3juGRSiXGZUfoFtqkrlw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/button@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/button/-/button-8.7.7.tgz#7ddeb2b6e1d38ac5fe49f1cc688ea497e0c726d1" - integrity sha512-1BB+hFS2mdc0hc1F4FMgbH1Z8cQVT5193ibCNB1oaeT/lCtgGgpfplYAwKG7UriNI/WbDQMAmmW++QHan3+0Rg== +"@dhis2-ui/button@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/button/-/button-8.13.10.tgz#e230631bc4ede0afd7fee2696a5f0030710c9f2c" + integrity sha512-ZN36pQKc00HCqdCueT+uNf8kZNnm51YzusdiAbahIeX0OJMOK0YyxPfNamX7NoPYQ+6269wln37RmefSNBCVVw== dependencies: - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/popper" "8.7.7" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2/prop-types" "^3.1.2" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" + classnames "^2.3.1" + prop-types "^15.7.2" + +"@dhis2-ui/calendar@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/calendar/-/calendar-8.13.10.tgz#aaa84dfa6f703cdd356c93a83588fa20e8a6a398" + integrity sha512-8nWYRpMFzrO/bfvNK6N50OgyQe6mrdLcILxhXi/NwGmPB5F+cU1qI/BSCAy5Qp6Jbb6Z/TVbTffWBdqW1HTRrA== + dependencies: + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2/multi-calendar-dates" "1.0.2" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/card@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/card/-/card-8.7.7.tgz#cc0d953575aae4b0f8a669c2fe73ceaea5e6130e" - integrity sha512-mDDAhTqOIHy+pVNTp0NOyVR0nk2vE1oFaDWqacVZ+7mJFydjg7CMEQlZ4yZ5XmVl57D997pr8jSh/cnzyAATVQ== +"@dhis2-ui/card@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/card/-/card-8.13.10.tgz#938142f409460c2645cef0729b8290556171ba43" + integrity sha512-1LG8iSOGqlTlENP3O2Z8TKZ+TQfJhQZYrDED5z1zUDBbkxLSWLAD3cWbdvYtQTvgwKVhkNg1IVWF0+mB69a6LQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/center@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/center/-/center-8.7.7.tgz#f61ae07ef8a296922d91a35c04d39ce5deed0e00" - integrity sha512-3kPgCpmjO1kdexoooemM2e4jg98QPPFa6aypka+I4q+cQba5D/tYKVwIZAYN+QjzC15hCSbhf0k4yIZ9hs3M4A== +"@dhis2-ui/center@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/center/-/center-8.13.10.tgz#83a4f0e69e69185742e0ae960851df9efb199aac" + integrity sha512-kXijSnPd3ePZTKfnnPB9ufrdBH9Gxzl3Y4dThd4ZhZkKRlsEKvfgu/sN+cxlyojTnjJD/YiI5xAhyE5WhSvXHg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/checkbox@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/checkbox/-/checkbox-8.7.7.tgz#ad333b7201c7bbf1ae554d571c6b03e5e3094484" - integrity sha512-Oc2fFH85Xcc7qqJIlBgUbeA7TTFLKvnSeMv2VcLZpv8ApQG2Onm8MtlVK86T72ZvXj529ejex6XRCjYWdbp+dQ== +"@dhis2-ui/checkbox@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/checkbox/-/checkbox-8.13.10.tgz#e994154cab4bb51e142080567c964fcda95bb61e" + integrity sha512-srcJ+FzxuAnzOl+mzh846++FcFqNzH7eBzctFwp2s6JhuekoRGAQh30pgdxlTXMO8aEKfPY+5q1vrw3VeR+YjA== dependencies: - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/required" "8.7.7" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/required" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/chip@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/chip/-/chip-8.7.7.tgz#76ef229e8c452c2c8812666120be1128710f0614" - integrity sha512-fhfAg+y4gWKc7rntqGgNVD6Wth70sMybqrB11qykEhKLrZIPzqgYkfOTZmgS/ClV1ultnWXWOL+YZt3ak2FKqg== +"@dhis2-ui/chip@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/chip/-/chip-8.13.10.tgz#1a9aa5092950253cdaf2469b6036280b142ea698" + integrity sha512-If1odfD8dUHZ+s91H2auDTX6MaDqjW9DYhkczUYybDpZ3TVb7ohm5o5Anhlgv6mkwcgBKP7Xz9DtyjgkAZOwKw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/cover@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/cover/-/cover-8.7.7.tgz#7d244c3505d98184504d9363add87b4a55d15a84" - integrity sha512-tibwsokBCVUufKdtpgTzD/+Kaw2o7/dwIQmfeF+gJ+18a7yxaXu3taXIqPegtKNz6xnQYkDixyTu1QJsolidHQ== +"@dhis2-ui/cover@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/cover/-/cover-8.13.10.tgz#33360b9b32897bed334d2fd8db4f5e3ddfe14a93" + integrity sha512-DNIfBz/RMIa7q4woLG2LtsKGYPjG7OaYsqIuckfNIsYNyLjQp9+WdoXONY+NXXfomoAN0PoOKudLMs177Ejg0Q== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/css@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/css/-/css-8.7.7.tgz#d014d7a5e2380b7d58e10eaf890a8867a8c8ed5e" - integrity sha512-lRjXrIaxvc6wCRJMcK14ksNR53s1wNej10DBtOZCJRANltXR4Uf2inwEJhNzWtR7m6Wufv4tKYZ67LtN3GMSjw== +"@dhis2-ui/css@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/css/-/css-8.13.10.tgz#bc2a2e0603a34557d9fb137abc3fe5d875f6822f" + integrity sha512-+SGTmTVR1wrdVhteTl1bPUJJMbcM2tFc040AGHR1yWSsqH8rxWx/GSZ7ezfHu32PCHKW8wXpS8jcV8/bqijF/w== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/divider@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/divider/-/divider-8.7.7.tgz#7f5c3f39fbda5a25d1b764bdb572ae642910a81d" - integrity sha512-DTAuBpnfsDZbvIIfPAfu/7oURL74ezNQQs+F09ad9EszJECKG24A49daKCXzdYr+ytsM7krl8WF2a0NoG/v8uA== +"@dhis2-ui/divider@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/divider/-/divider-8.13.10.tgz#69a20c2157b54439a3b7fac3d0474fbd7a23cf18" + integrity sha512-B+1d2uE3S7AwzUqHKr2PdsnTVqllIxxNTSgIdbd0BW7INEqh/Da1fVVXEX7Y5mdhlbPg0MoIDUGQgc0llqVAvQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/field@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/field/-/field-8.7.7.tgz#31363edac1715035d6fd1358f1f1c6bc819c0a0e" - integrity sha512-9TzJGzddbLqWBWTcHBZaXimPMAfSsa9c6kTMBZUJVtgZE3y1E8SsoiVEMY35e+ZAiij2cmJHjg2h0++DTLJvlA== +"@dhis2-ui/field@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/field/-/field-8.13.10.tgz#dc7e8148d846ce9d7c9d25e42068d219e0017e90" + integrity sha512-nQdyM+4iPNhIiSVbjQ8mVbl9QZa5hJToYBia3CXjuzYpVUzvs5574smpkcJ9HLu7s8I5SyMl0Bjcev7F6KJIhg== dependencies: - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/help" "8.7.7" - "@dhis2-ui/label" "8.7.7" + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/help" "8.13.10" + "@dhis2-ui/label" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/file-input@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/file-input/-/file-input-8.7.7.tgz#809983cc1b91bfc2488fcf2c287ae3d8ab50cffa" - integrity sha512-hgFS1UQbTM2EFPca/UluBoYdTqkIP1FNlsdCqDyI2OgiPsT5X0a0wgjfTLDPOy7HPHhalkOCz7xOxgyB9IbWOw== +"@dhis2-ui/file-input@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/file-input/-/file-input-8.13.10.tgz#3e052270c9abc932299931770fef49519f93ec18" + integrity sha512-pXwMcHxCkUh14Zidso+gRh1k1wJUVC62U43+cAM9B/CNkhDzcp704NgrtlTDvdN6n3WJ9vHTDPzUd+grOuwNag== dependencies: - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/label" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/status-icon" "8.7.7" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/label" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/status-icon" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/header-bar@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/header-bar/-/header-bar-8.7.7.tgz#4850f9c1b760762ec9ba06fe165fc996f66de37a" - integrity sha512-S/Alqi4CCXIG49NorOCF3ItCGjZtHw6qS6J42yV7WNNMicDdz0y4bnQWZEibFrUSneVHbWLI/LE6afKKwZ+WHg== - dependencies: - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/center" "8.7.7" - "@dhis2-ui/divider" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/logo" "8.7.7" - "@dhis2-ui/menu" "8.7.7" - "@dhis2-ui/modal" "8.7.7" - "@dhis2-ui/user-avatar" "8.7.7" +"@dhis2-ui/header-bar@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/header-bar/-/header-bar-8.13.10.tgz#87981f1eff64d66b85b6f165e86ac9392134e4a0" + integrity sha512-QJ3740rxFd7ajYgphZ3tSnoQscCNtJdrNdpcQeZOQAkWprqA8Zb9smR3qlTRca6p/iLQ1jwWHwgGomD5WNn16Q== + dependencies: + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/center" "8.13.10" + "@dhis2-ui/divider" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/logo" "8.13.10" + "@dhis2-ui/menu" "8.13.10" + "@dhis2-ui/modal" "8.13.10" + "@dhis2-ui/user-avatar" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" moment "^2.29.1" prop-types "^15.7.2" -"@dhis2-ui/help@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/help/-/help-8.7.7.tgz#344e624c1fd42c2fdedac1271e7e9b1bc65ed465" - integrity sha512-r74kUbMdJbYviCJTpF56T2c1n+JohXEcd9886ArknSLQ2+zrtyzg2Imr32tHSHHyhjGRJN1oOZSghiWlfXu0Cw== +"@dhis2-ui/help@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/help/-/help-8.13.10.tgz#fab3d4980a8c92152755b753cd4a00c1bc10c2dd" + integrity sha512-mmZw0ON4krOzdDJKA31cLqWttCJgDn0oAQ1k7B5PqdeX/Gosc0TONjz0Rm/g5sZqkLC5l4IoWXW4Q+4LelbafA== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/input@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/input/-/input-8.7.7.tgz#e01e0407452d31fcb4df48c7ae74ef2e55c54d6f" - integrity sha512-AohkES9IEimDLKNyt/Ov2V0XUToW8QAmx1uPo0EucsStBeNvYb+4B7H5CKZrmRyKCd+BUh3ot2ZK/oaSnvUmEA== +"@dhis2-ui/input@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/input/-/input-8.13.10.tgz#2ad8f0d06d1d5f9e722bd9c308db18d2fa55b64e" + integrity sha512-PLBYdb/MislatP7gOjOjJc+YprzgnKW4mipMPJlQfIqk9qN9dd14yXl5wuSV60jdxe1Zra3HBw5NQtceN6v3gQ== dependencies: - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/status-icon" "8.7.7" + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/status-icon" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/intersection-detector@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/intersection-detector/-/intersection-detector-8.7.7.tgz#58f0a864c02e9abd0f6e8f0592c53e5b47f0ad0f" - integrity sha512-GvoGxVvmb/fsh4G+e42w4LZQ6qyMayRljDTwWfRUbgadwcK+3pTc4o2P3Oi39+fH9g5CDg44gjUEvb0d9wRj8g== +"@dhis2-ui/intersection-detector@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/intersection-detector/-/intersection-detector-8.13.10.tgz#e6dc60a9d305c772a53248decd23c296176be965" + integrity sha512-FcsZ8tNV4ufeVkLHzdH91dDPvAwK7VLcOwZRpnWkUNtboAox2wshAClPDfo6M+4u9P0ubWJRPnmvQu0WUrKV1w== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/label@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/label/-/label-8.7.7.tgz#d64ece6409e5091c0435b680fad360946a3b966b" - integrity sha512-H0UoiPeLy2eku74NQbbBQVc6ly1eXHUmP3JSPOb/utfPag4E2sVm4f8mMjbCvRJP37lOmVvoZP3nrkDJevWllw== +"@dhis2-ui/label@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/label/-/label-8.13.10.tgz#7d556513f6053e8d20ee5a3ca1a0174251b3c980" + integrity sha512-yQaK+53TZ12KJpztVTh7noLMwVgx4fWNfxGr5RwR1a2yfB7IfsaYdt+o9FElAK6Nbj6Yhzx3Fwj1uAfyQ7zCiA== dependencies: - "@dhis2-ui/required" "8.7.7" + "@dhis2-ui/required" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/layer@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/layer/-/layer-8.7.7.tgz#dd2c538885db52486a2da9c5cb1a35f4f91f1b04" - integrity sha512-hhZEL+iO1Tm6sdVuOUyjgXs20Bi8idpv4Grw8lMGUHVCTIAOzD0FoHReV7lmUYFK/aKWQHhr6s7wKOugv4wBng== +"@dhis2-ui/layer@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/layer/-/layer-8.13.10.tgz#ff1a462fb5b09f102b5999dbb96855b7efcefcf4" + integrity sha512-x6/HqcCJtBBSGdjkosIkC2mDxAlUq1/WxO7ze6jtL2Hxtuw3E1IKXf/+g+VxH8j/l9CJ6tVkZHhiunZ3AbpDoQ== dependencies: - "@dhis2-ui/portal" "8.7.7" + "@dhis2-ui/portal" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/legend@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/legend/-/legend-8.7.7.tgz#e66afa0d973e68c0d0166cc089e5258a333faf01" - integrity sha512-a2E+rdgGn6VNHXS0+OVu8V26kWaZ8HNnK7eo/o1swaKaEVQHC4jzQLPwWWBiZTDHxj6A5tevoMV1FduG1M2a6g== +"@dhis2-ui/legend@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/legend/-/legend-8.13.10.tgz#b8b0865f6485a26e6731cc9fa78d101e8b0fc834" + integrity sha512-FDQI4EwGe5nu8UXsgv/FEP7MqBPyn+iDXyHV8p5cWlg82vwg7VwivlmoavuhC+Xx7vp2Zb07Ipl6hZFhdRsmLA== dependencies: - "@dhis2-ui/required" "8.7.7" + "@dhis2-ui/required" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/loader@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/loader/-/loader-8.7.7.tgz#478b5290223e5b02669820ebb48301163313b45d" - integrity sha512-IA3AJMCkVj4G3mBIYvdzij1AsPYATI4ToMGhvOdwwTc0uGMbXU6yFs9GRXRB9DaR2qrSgsCO94f7cfRe31HR6g== +"@dhis2-ui/loader@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/loader/-/loader-8.13.10.tgz#4f0b29dc16bd417750ea78c3fd95544aa653f6d9" + integrity sha512-XUnwubWctfS/dym2bL5TgMLs/ReznSDfIyD1KRWTASU7bxToZeolkKOCDtpSsMBVCJwbpfpIRmN2b2xopj3+7A== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/logo@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/logo/-/logo-8.7.7.tgz#775850958b967111b7b9f6a2ac676fea7babe238" - integrity sha512-Dv3ui2Pcf2L9F42AyodgcFqEM/0P95WNEtJ3vnKSK1vaiNRIDyxPPLJQmmOMSS+fUx0WKoW+SzB5e4MYdK7k4g== +"@dhis2-ui/logo@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/logo/-/logo-8.13.10.tgz#f997fd5d695daf180f56f8389aa41366e739b38e" + integrity sha512-9JP9T5yaLTvnH8XblNPNVKnvVbDbomD3ah4jsKhk0Tok1KA/roMo5QTw7jcihucbPz6QTscjQbxxnWlTMDiCPg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/menu@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/menu/-/menu-8.7.7.tgz#8d35e0d55e111842b3aec64d80043ce2841927f5" - integrity sha512-wQo4LfMpIs6c87O20ix2PVeCfHBexSGFq5KMcNNlG9IFMuOgkfinCA3+Irw6AZ4a3iBGmABdqdszuFWo622Rvw== +"@dhis2-ui/menu@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/menu/-/menu-8.13.10.tgz#ecbaec3267f6451297dd7e1779f8b0ed4d26aee3" + integrity sha512-67f7x1ibxSXLhSqsdEBFi8DIaMwLzIs7BtrIT83i6Tb7+PI2okXdH8ItWAwKYgdQGWuWF5MDJ6V1PyJiNIfnpQ== dependencies: - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/divider" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/popper" "8.7.7" - "@dhis2-ui/portal" "8.7.7" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/divider" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2-ui/portal" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/modal@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/modal/-/modal-8.7.7.tgz#06de96053d46d29417cf091dac7a6daf6fb9e4a0" - integrity sha512-yCVHLzNixz/o65BAs9hjEddk7WQUEFVhEj1FswY0x7P0Jfnmkxf4jFAZ09GS18h4l3Df60x03s/vu558f7gW2w== +"@dhis2-ui/modal@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/modal/-/modal-8.13.10.tgz#a581a0036a4a7c1f6ed9ffd3a77b357bb2cf7ecb" + integrity sha512-uPjLJc4nRDrjyJFcwLhqrW94SgypX2FbHID98H2OFdFHVjR38PWkf87w08pnjEsFLf4M288wEq6rv75m6vsfSg== dependencies: - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/center" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/portal" "8.7.7" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/center" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/portal" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/node@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/node/-/node-8.7.7.tgz#398aa8d512310daa15b36ad1335e10121dd22bb4" - integrity sha512-RMidPD3ZqQ0e7Tm59Yhq7brylh9VdsqYZ7iv+NR+B+nCPJ6gylDb1HoiSekpCZjLlXFBHvS65l4HNAGsgmiNqA== +"@dhis2-ui/node@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/node/-/node-8.13.10.tgz#3f7e75c483eb63137b64c22d0fa2c23a51b741f7" + integrity sha512-63VdT8eQTuVJXI2ECVLayJcFH3qE1kD3EfrGdRI3+iSMJ6tJjtGm6ZpD1IeYm48FxrPkEJKoI1SYQpNLiYpR0Q== dependencies: - "@dhis2-ui/loader" "8.7.7" + "@dhis2-ui/loader" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/notice-box@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/notice-box/-/notice-box-8.7.7.tgz#86fc47914c068dfc53c28db0857b81a961d1e10f" - integrity sha512-H3X54BP7HzBXk/2DJvQMd3Sv4hG367WtGq6KTbx38puviYSWxcU8L3sbeaeGR1w6tQ5TK4szzehN7rsWKQB+Nw== +"@dhis2-ui/notice-box@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/notice-box/-/notice-box-8.13.10.tgz#1bf76c0da6c4bb70d1efbba56c65df3cd340da37" + integrity sha512-6h+oC5JA8vZuJNb7UB3vj6pL6UTAhC815AIjRyK0IvwYKb6Vke1p2fRk/reK3NJ7nz+3H1wkAPNAm8qf09oiEA== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/organisation-unit-tree@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/organisation-unit-tree/-/organisation-unit-tree-8.7.7.tgz#ad9cab66f7833bcc7c65e883539713c791d4a440" - integrity sha512-g84/VWnxaMMHNfrq80xBDp6/NVJqb/7MCtp+vfP3/cRzj6BW3/P6Ts0fM1gzr2Hvo4D8rGv9JPtECKa7Z0d5ow== +"@dhis2-ui/organisation-unit-tree@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/organisation-unit-tree/-/organisation-unit-tree-8.13.10.tgz#bd9e085641634e069b528633f37f12b5edbff385" + integrity sha512-tWfE3T+/ufDhsbQl2QnSmNq3aAn5a8OyWaYyQJRRjQ1kIhCgfeu2aNU8Lo/8o2u7iCiXzXoHXU1pNQSGMckLsQ== dependencies: - "@dhis2-ui/checkbox" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/node" "8.7.7" + "@dhis2-ui/checkbox" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/node" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/pagination@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/pagination/-/pagination-8.7.7.tgz#efa73285abfee67f21768be9175ffc4c44f88dd3" - integrity sha512-2jXs6XaatGR+Q77dNJWduguLytayYi2vmNdBkovTcfXMznuTQdIOY5uS5FIUWo2vb1nsr7LPcErNzo8r2Nf8Hw== +"@dhis2-ui/pagination@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/pagination/-/pagination-8.13.10.tgz#36e80072d96f0f9d528b738ad28aec598dfe2fb6" + integrity sha512-/EIbRjnWB8AwB/MpX93fV2zdF4YRSh0s8Mqr3CPDAh1A3iN7YXwbXvtMVXY6ll2snE1Iu4mTwrlDsuLxLncLog== dependencies: - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/select" "8.7.7" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/select" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/popover@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/popover/-/popover-8.7.7.tgz#e31d24bd9b7ac68ed1ee51d4781e0a8142551224" - integrity sha512-aeLAQx9Jy7PxJzC98fQo4s0MPKYqqLnFInLDgy5gDT6hI6IxkA3K1LRYS/2fVD/Hy1NB+UYIbyVr+8hFZz1UtA== +"@dhis2-ui/popover@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/popover/-/popover-8.13.10.tgz#b87f69197a70aa8d3838e0044e9a9ba7c58256a8" + integrity sha512-nF0pBUKvOxbCX24vChuiqX+jG3pyPdGoTNGgHixud+mShG7WIIeav8v/jj/KkRjfBdlay3UKNYbI+wIhy6zDww== dependencies: - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/popper" "8.7.7" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/popper" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/popper@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/popper/-/popper-8.7.7.tgz#9a31ab9509aee31988b568333c8c3c5ec0c5edde" - integrity sha512-FOJR7+SB6iOr28O/KDrm4cczv1IeQs7aX0NrbSH28vNzbjLMfT4YkkSSWV/dQbbu9FtMKaSLktOHfFTmB89ilw== +"@dhis2-ui/popper@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/popper/-/popper-8.13.10.tgz#b732808cc6dfa7e4874d5b36320a41e72a77c092" + integrity sha512-UsK9cl6S8KGkduDogVIB/ZK4BJn73ba0du3mQLKAejmNFvKdF88KQub4FZrU0acfFIdKwttQT34APVv80G98qA== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" "@popperjs/core" "^2.10.1" classnames "^2.3.1" prop-types "^15.7.2" react-popper "^2.2.5" resize-observer-polyfill "^1.5.1" -"@dhis2-ui/portal@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/portal/-/portal-8.7.7.tgz#0b25bbb39f4b63d4080bd77173b74e86d19af5c6" - integrity sha512-DAr62e5GLapTtftv6tkio8mDFDOmuNinkl72UuVJ75XIa2S+F2yIDN33bbABRZLN6Hi62h0Cei4oDxjQWpcdCw== +"@dhis2-ui/portal@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/portal/-/portal-8.13.10.tgz#05be783522f3f3e696804fa5dfb9d6d6bb77618f" + integrity sha512-DySgV5ADd0iacFckHwY20JTcCxVP7KgIkAfexcJGKTC2K8VDighguC8UxJNhVXUzNnapZeLupvpHW8m6x6seew== dependencies: classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/radio@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/radio/-/radio-8.7.7.tgz#6550048c17b52fbfabb01e737570bfa0c3788342" - integrity sha512-6/age99SmT9vi8Yg7fZFq6oKeEYMwZNrdLaTF/5fDZ7cuLOz+0YwR/H2rfn1WLqcCAmfJuOQVxFcFEHMDeRxAQ== +"@dhis2-ui/radio@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/radio/-/radio-8.13.10.tgz#04967451be5bbb45988305dcdd38f21adc7d7b50" + integrity sha512-wD04X+RBbfi3671g0j1PJ8n7GtSA66iAeKZrzWixgIEv4FdvuMAWQepELaVcs/Z38Rwj1UPh7DoXqfUienuezw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/required@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/required/-/required-8.7.7.tgz#bdd810b4386e09df412f6d71e1434edb7ccbe985" - integrity sha512-Ro1UgXGqRZizRKrPnDSoXAOzzLOmR//905JBthRO7CtQV7Uqyn/Vh2+dKnJ3Z8g6ee8zt/zLMh3sUzILSFUzgg== +"@dhis2-ui/required@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/required/-/required-8.13.10.tgz#438e2a5913f827e38d4e550ce72cafd5b500990f" + integrity sha512-aVOJ4rKgKH4b0xoYB1zsUXShLcTV+Out3mmBFGrEMYyXe0hPYWPVKWelliQvPubgltlcBIEB0Ymcs4lR4WWWgQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/segmented-control@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/segmented-control/-/segmented-control-8.7.7.tgz#0a8607a08b6eea0277afb393f93301c427e976b1" - integrity sha512-gRnYKSKd2eAtuyIBLyQRtu7Y+A7L4k1wx63rNVDn5NQ5Kb6ObjAGKDpFC7zBq+WFOTavR0CH2t48Nyhhqlz/pQ== +"@dhis2-ui/segmented-control@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/segmented-control/-/segmented-control-8.13.10.tgz#675acc2a3812429a9a147e49b7c54c73bed7712d" + integrity sha512-jh6iqfKIcHcDIMiG8/IjqIujkDNEWZFz3emsipPb7qDWyL0UZBJ4W72b9ybfFh0svj37caX3deZGSnnXM5Pweg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/select@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/select/-/select-8.7.7.tgz#3268624ea3f9bab29b3489143d615e31b1ce6d46" - integrity sha512-sP1xlhtUtlsbEZFV3BVipI6EDDEvIKbKB4LnXwfkhlI9aJM9n/f3gIpLVWENqdH4nh+tOSm2kBXLJIzfUDYoTw== - dependencies: - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/checkbox" "8.7.7" - "@dhis2-ui/chip" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/popper" "8.7.7" - "@dhis2-ui/status-icon" "8.7.7" +"@dhis2-ui/select@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/select/-/select-8.13.10.tgz#adbaf7c1c299b1b24c8f1e409a66962c806e7e29" + integrity sha512-Y5fDF2DBG3YMGhw7qSOFKWb41+f6SutQDP5cZckoNlNCBXTe/89O5PW96Yh/ruJ3bFg+l4HzafTa4f4Z2H7F/g== + dependencies: + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/checkbox" "8.13.10" + "@dhis2-ui/chip" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2-ui/status-icon" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/selector-bar@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/selector-bar/-/selector-bar-8.7.7.tgz#f18466831742d3a97e11c38b378c8014f680295c" - integrity sha512-fSNyrl0xijrIzCRKFHbHM1X2d5VkoOOEX2FhsXE0V4KY+nXGUzdF3Nxm84mrOjfVUjwZk09ilDY+stQhBCmbeg== - dependencies: - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/popper" "8.7.7" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" +"@dhis2-ui/selector-bar@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/selector-bar/-/selector-bar-8.13.10.tgz#5bfaa6f46397384c0161ba87603e2345e4852f96" + integrity sha512-Wa6vGpn4RfjgSC2+Fiw9xzi/rrNen3pUMhUg6KBRqtOtOUQkZzTa/5WEfWqw/IYUGiG1u9vD0BFzI9aiVsX1kA== + dependencies: + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" "@testing-library/react" "^12.1.2" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/sharing-dialog@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/sharing-dialog/-/sharing-dialog-8.7.7.tgz#19ae6f15985ca002782030833ddb659bb210513b" - integrity sha512-pxmzKODQ/AYSks7gYDKjmAq+Sr4TJfsYJksPpGViHc4Qoa/ym7Yez4vTaYyC3FlBRUEs0tj9qATXcD+kAT8f4w== - dependencies: - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/divider" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/menu" "8.7.7" - "@dhis2-ui/modal" "8.7.7" - "@dhis2-ui/notice-box" "8.7.7" - "@dhis2-ui/popper" "8.7.7" - "@dhis2-ui/select" "8.7.7" - "@dhis2-ui/tab" "8.7.7" - "@dhis2-ui/tooltip" "8.7.7" - "@dhis2-ui/user-avatar" "8.7.7" +"@dhis2-ui/sharing-dialog@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/sharing-dialog/-/sharing-dialog-8.13.10.tgz#5d2eb77bb17b31d247092a66106f1a5a92be30a7" + integrity sha512-Ne/e1n/dL3GKZYlaub9GZO/EoGXTvsCFy5wvHvbrfdG+zJ/nNqBDcr+yIGf+oMCOAaHFipCKphQPBDC9LYu5aw== + dependencies: + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/divider" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/menu" "8.13.10" + "@dhis2-ui/modal" "8.13.10" + "@dhis2-ui/notice-box" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2-ui/select" "8.13.10" + "@dhis2-ui/tab" "8.13.10" + "@dhis2-ui/tooltip" "8.13.10" + "@dhis2-ui/user-avatar" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" "@react-hook/size" "^2.1.2" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/status-icon@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/status-icon/-/status-icon-8.7.7.tgz#ab4f0094c6c5a287d54bed1a88416a2ca763c154" - integrity sha512-HMxry7oJWVnrgJsg7cCRkqy+46suRL1JlIGc7r4UNWCK5WMROsqrTAbbBJpI6r8fgGzKHVbLPjbP15WK7kMNIA== +"@dhis2-ui/status-icon@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/status-icon/-/status-icon-8.13.10.tgz#1260b0099bc70011c28fd81ff37bf54ef43f9743" + integrity sha512-W3+gPTTtlNkHoeWuSvVhOTuf+PtQBxWFBSDDuxN99AlJPk6O+XTbl6z/7CTcf6K2pKSAHbpc1sqbY6Eoiy/kog== dependencies: - "@dhis2-ui/loader" "8.7.7" + "@dhis2-ui/loader" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/switch@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/switch/-/switch-8.7.7.tgz#fadb692913aaeb1989696cbb439f44ddcac85a13" - integrity sha512-tb0J0XUusztt4vN8ZRs4EOyvuiAHwSLJvfRcZ0XUNAjfeJwhsZMGRL8hUM7rDE40LP5s2dF+SLeZ4oUv/ytF5Q== +"@dhis2-ui/switch@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/switch/-/switch-8.13.10.tgz#531eee0a74ad956004648b4508909889b59a2429" + integrity sha512-GatHs6xCor559JsVjfbEdmp7ZEdDUsN4my43kWBGYj611fgKgLqF3iqUOPom0eyn5acxRtd2gDAd6se1jJpMKQ== dependencies: - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/required" "8.7.7" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/required" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/tab@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/tab/-/tab-8.7.7.tgz#91168ba3510d7932e01098a5b487ad16920a3267" - integrity sha512-R6UwJa87a1KByz9MY9XzIdiVYCDICwlRHdgSgu/iBq+LNhbFnKHq7oQuExI24SzDicYYJwcdBkB5fR07AKz5mQ== +"@dhis2-ui/tab@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/tab/-/tab-8.13.10.tgz#aceda14aea299bcfe7c2cabdbdc70c057e2d834f" + integrity sha512-qnVwDwlZAimGLEFarOlLUc3oS8oFrRR4niwZk/iPprrTZV5snthnmkZFrITE0CvWvmoOmd2KwiKEdq47xEuELw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/table@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/table/-/table-8.7.7.tgz#77cab7d0357e92137c3dcc6ad38107ec5c3ee2a6" - integrity sha512-D9Le3DvsZd6D3P+rgmaom5pL1rhNKFH7iwhL/8k38ifsoI0xiTxrK9g9aD5W/0rxfSH0rjOcjb9U/fNg8rp10g== +"@dhis2-ui/table@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/table/-/table-8.13.10.tgz#853400e83eb166a1769a94c9035758663d31ebd1" + integrity sha512-q2zi4t3w+gkg7wnIAGXEE6C9SPt5kNFYqIcQ8nSeexU2c5i+MkzalH3WpEqS5rq6CF9gHkCQ2DZ5GI3Pi+AEFw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/tag@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/tag/-/tag-8.7.7.tgz#8f6b81ef2f54ed8e6749552871f559d2870397df" - integrity sha512-B7oU/wvZ/4TSxXVQr5OQWx4bB9dZ+TrTrjZigLbTRLf7L4Kl4wdyciCMBIIPHvvLaJPKGUItuKCJSDk4y7IuEQ== +"@dhis2-ui/tag@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/tag/-/tag-8.13.10.tgz#3a7dee10785692c8031bd5a2af6b0f5356e11723" + integrity sha512-bxygOblNPIu97k6yICFgFvqavqRsimwEbCQTpKJfhgsNcm8egGjgmt6lv2uWSgq3s5ypp5kpz6nvlYBe/sOM1w== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/text-area@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/text-area/-/text-area-8.7.7.tgz#031da32c454cffaa590dcc75377fae58026ddd3f" - integrity sha512-Ux36zKW+w0fgk1WANrtWldqfolnqP8/065dvyp2HG+Uvvr30oGIvCGWd8eZq8wpmGEYR4tBbVQl9kLqRdr/gpA== +"@dhis2-ui/text-area@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/text-area/-/text-area-8.13.10.tgz#aedfc1ec6275570e1931c7a39aa1eac14f83ef6a" + integrity sha512-al7FSd2Mb6uZtwauY4PA+jrgPMIjMpXgavEkcfzANoBa9GKflCYJobFNq24ZmJDRxl9UgbF/CdCzCfmHkHlokw== dependencies: - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/status-icon" "8.7.7" + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/status-icon" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-icons" "8.7.7" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-icons" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/tooltip@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/tooltip/-/tooltip-8.7.7.tgz#25b62ddb4ccb465435f036b49f8b1ac239b5bab3" - integrity sha512-n5dmLI+boMXH8O569SaYcf/F337TDtR0SnCXCC4aIkpb9wtFpxTtx7FeHERxkwiiC3Z3QyRx5oott2tcmIaxVw== +"@dhis2-ui/tooltip@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/tooltip/-/tooltip-8.13.10.tgz#3372a031c2ffbcd2c54a6fc4509bfc55c50e7d51" + integrity sha512-KtZvCju4CXjZ9vQxUOskSKLVVYwkfGUa6vVa5c64pKdNK4xOnpcbWp7CZpbsRrxphKr16/WmjpJCH/7tu/tO5A== dependencies: - "@dhis2-ui/popper" "8.7.7" - "@dhis2-ui/portal" "8.7.7" + "@dhis2-ui/popper" "8.13.10" + "@dhis2-ui/portal" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/transfer@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/transfer/-/transfer-8.7.7.tgz#05bcd0a86121296dced7d76a0dcea634bc3600d1" - integrity sha512-vEYXG2innjdBUa3tD+Rab83ZxYt9mOwnYKw94dUbdSdGQkdeXaJI4Iw8ept4AvBrwLtZej3wWdWsGGXGiEOMIA== +"@dhis2-ui/transfer@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/transfer/-/transfer-8.13.10.tgz#369adb4394955a185994537ca44db551bfb7d18a" + integrity sha512-jiHqwbaiNXU6UHrvbpWSQF+RXEMOFoSamOWGImvCMy2NZr/98Kq5pAELOQaL9SgW6PAGgqDaIcCE76ehgGIAOw== dependencies: - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/intersection-detector" "8.7.7" - "@dhis2-ui/loader" "8.7.7" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/intersection-detector" "8.13.10" + "@dhis2-ui/loader" "8.13.10" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/user-avatar@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2-ui/user-avatar/-/user-avatar-8.7.7.tgz#865e5d157b4292c70dd7d699c9f0bac7ee76c83f" - integrity sha512-NJM1gBMH2dh2HMmRkC0t4HKBjO5oyPW2GRRnae5VpSYxv+lKmak/0YF8Zw/1lgoJ+TN+dg3e4NFxJi2FzO4hlA== +"@dhis2-ui/user-avatar@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2-ui/user-avatar/-/user-avatar-8.13.10.tgz#25c20816fb333eca1229c63085f3f14a798cf142" + integrity sha512-IRWKDss24ntp1Pk6v1a26Mulo+bfv4Mtr1qpYJoDp9xyqmsUqguoA+F9yEN9yXGajGMoe2zTV7Kk2Taz8L1OcQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.7.7" + "@dhis2/ui-constants" "8.13.10" classnames "^2.3.1" prop-types "^15.7.2" @@ -1959,17 +1929,7 @@ "@dhis2/pwa" "10.2.0" moment "^2.24.0" -"@dhis2/app-runtime@^3.6.1": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.8.0.tgz#4ec7fc4ec6647dc8428e3c0d2e14b2d188a993b9" - integrity sha512-f5M1RfUJb4yZaPDywTfogVXjzWcuYGJ7JQzny6iWXrJu1+qrRKYbfFutYNhB+4bXD4bB59DelHWqVaHtrGvbVA== - dependencies: - "@dhis2/app-service-alerts" "3.8.0" - "@dhis2/app-service-config" "3.8.0" - "@dhis2/app-service-data" "3.8.0" - "@dhis2/app-service-offline" "3.8.0" - -"@dhis2/app-runtime@^3.9.3": +"@dhis2/app-runtime@^3.6.1", "@dhis2/app-runtime@^3.9.3": version "3.9.3" resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.9.3.tgz#6316aabf213be5ee33da36a015f85ef72bd54544" integrity sha512-Exfp8EFa81uobEpFFKwxWi6KEioZlGhaQwIBb/dV9sxSjMEg3wO1ULdSi2VBdf9/UQKb/0YM+Ki7d9/bCsRrFw== @@ -1979,33 +1939,16 @@ "@dhis2/app-service-data" "3.9.3" "@dhis2/app-service-offline" "3.9.3" -"@dhis2/app-service-alerts@3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.8.0.tgz#a899e3b392b6af49407be5251abe13eb0f2bdb60" - integrity sha512-Y2gDxSLS91YwOyvRQXpZMMOF4GZeoswPypDFy1U1OV8S1HdNa4J3dFtKqwRaiC8KP2o9bi+MsJjDA5fwl/jaOA== - "@dhis2/app-service-alerts@3.9.3": version "3.9.3" resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.9.3.tgz#bb7d6ab98b2c2b49f2c1f84750f4015ee1e51058" integrity sha512-Zp8J90c5Qbh9fqtDKT0QSI0jCOUst64cE0Y7rzCe7JUGUsD84FTYVHY6xPMnhDf65kQ2Amh4aiKVO45y40UjQA== -"@dhis2/app-service-config@3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.8.0.tgz#59a9a6c1edb1cc094544a6f90f8c9cbd7ec6d565" - integrity sha512-6xA065s85ry41OUuczKiqe4WD8wKqeDnuEBHApunJMWiLZ8o/3Y7KtmB2g5tcqIKnYSRBP3CY0naYPgdy25k6A== - "@dhis2/app-service-config@3.9.3": version "3.9.3" resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.9.3.tgz#f665c7858cea028866cc6ea6cea4119d000208b5" integrity sha512-W2SPjWeCXKbr97PHz1DAHuRF/OKQ4jNUUmL3u4lAuCxnNJwQDJdV71fteevlQmT/tnl6tLeoy19Tlt0nDFP2jg== -"@dhis2/app-service-data@3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.8.0.tgz#e666e71c8ac7921243b25495780502f49dcbbb30" - integrity sha512-cDbzb2MRY4Gp2ohqD/ORQ2Jd3R79SjFJAscQdKX0YTf9avBNoepgHHTk2tB2gYLpaJEWjRMRVtzcphzgeaJrqA== - dependencies: - react-query "^3.13.11" - "@dhis2/app-service-data@3.9.3": version "3.9.3" resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.9.3.tgz#46758bf3f7863972e9f7db93bb61f0262281f16d" @@ -2013,13 +1956,6 @@ dependencies: react-query "^3.13.11" -"@dhis2/app-service-offline@3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.8.0.tgz#5854342169c3214fccbbac0abbf6a8408aab3475" - integrity sha512-cimWQkFRHcfihTwZkHbwZJHp+jC6ifJ8oPafRjF8R9x/+Zopem8FyByk9GkiyRwrfgrLsjwZliDAcp/gWTrx4g== - dependencies: - lodash "^4.17.21" - "@dhis2/app-service-offline@3.9.3": version "3.9.3" resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.9.3.tgz#a4ec287e33f00768ff80dc5ceb59c674b50a80ab" @@ -2149,6 +2085,14 @@ i18next "^10.3" moment "^2.24.0" +"@dhis2/multi-calendar-dates@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@dhis2/multi-calendar-dates/-/multi-calendar-dates-1.0.2.tgz#e54dc85e512aba93fceef3004e67e199077f3ba8" + integrity sha512-oQZ7PFMwHFpt4ygDN9DmAeYO3g07L7AHJW6diZ37mzpkEF/DyMafhsZHnJWNlTH5HDp8nYuO3EjBiM7fZN6C0g== + dependencies: + "@js-temporal/polyfill" "^0.4.2" + classnames "^2.3.2" + "@dhis2/prop-types@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@dhis2/prop-types/-/prop-types-3.1.2.tgz#65b8ad2da8cd2f72bc8b951049a6c9d1b97af3e9" @@ -2165,90 +2109,91 @@ workbox-routing "^6.1.5" workbox-strategies "^6.1.5" -"@dhis2/ui-constants@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2/ui-constants/-/ui-constants-8.7.7.tgz#cbdc548ddc2dc0f98fe9d8541afe0efd247adf3e" - integrity sha512-SgvtBULPvu3/kZwrzKu55sLeWYnoWgLdiBLacScpXgGYXifHmsNtFVOB/31t2Gl01+JVO1MrQ1ANtGRNdzjF/w== +"@dhis2/ui-constants@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2/ui-constants/-/ui-constants-8.13.10.tgz#31ec927daa1638b130b523a27f1b3437e81538d4" + integrity sha512-b6pJd3jMkQo8lk87tvd/jlnfX3wa5ScVzo++HGTCO5hnGUizmcD5mOskZ6nYuFu2L2iJaywaTZMrz56SrerJcQ== dependencies: prop-types "^15.7.2" -"@dhis2/ui-forms@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2/ui-forms/-/ui-forms-8.7.7.tgz#b90c3253a69a169e1d5185c51783eb892f3ecb15" - integrity sha512-7ILC6CzKIM8F1bFdvUkBQ/GFwotA+f+jgoSRM68HPFYkPU5jVZUfZ/Dk9Z0WMy+E1t8cps7sLyjyFuhiR/T9Yw== - dependencies: - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/checkbox" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/file-input" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/radio" "8.7.7" - "@dhis2-ui/select" "8.7.7" - "@dhis2-ui/switch" "8.7.7" - "@dhis2-ui/text-area" "8.7.7" +"@dhis2/ui-forms@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2/ui-forms/-/ui-forms-8.13.10.tgz#d9c5bb472b12c4b1124669be6e03485d3a779bc1" + integrity sha512-bD5AV9XAdXg66axcON8ePqPpQ96DHzM0AbJ+kn8yW9KATiM8tC0RtO1KlKgdrYDO5mSHhNZQXBr2jtUJg7x1vg== + dependencies: + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/checkbox" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/file-input" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/radio" "8.13.10" + "@dhis2-ui/select" "8.13.10" + "@dhis2-ui/switch" "8.13.10" + "@dhis2-ui/text-area" "8.13.10" "@dhis2/prop-types" "^3.1.2" classnames "^2.3.1" final-form "^4.20.2" prop-types "^15.7.2" react-final-form "^6.5.3" -"@dhis2/ui-icons@8.7.7": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2/ui-icons/-/ui-icons-8.7.7.tgz#8138aebb46723cc82168663eb0f06a6659ab2a8c" - integrity sha512-appRE/9TmJ8oa0B+Zb4g2W5nD+KMmdmgIAktRDKa6zRwiUvEPHLRoydm92kn38oDIg7oS9wsAHGMN1eff67cNg== - -"@dhis2/ui@^8.6.2": - version "8.7.7" - resolved "https://registry.yarnpkg.com/@dhis2/ui/-/ui-8.7.7.tgz#07c29bbbf83bdeacc27660e4dbd70b917b30a2d6" - integrity sha512-e5laICpQD0MU4aUZzkGCGAvAbuigpCuqzQg4o8gOzR/tv1K78QnY/eMyF/vv7FBKFX9b0AyfKge0YTXPxXYPUQ== - dependencies: - "@dhis2-ui/alert" "8.7.7" - "@dhis2-ui/box" "8.7.7" - "@dhis2-ui/button" "8.7.7" - "@dhis2-ui/card" "8.7.7" - "@dhis2-ui/center" "8.7.7" - "@dhis2-ui/checkbox" "8.7.7" - "@dhis2-ui/chip" "8.7.7" - "@dhis2-ui/cover" "8.7.7" - "@dhis2-ui/css" "8.7.7" - "@dhis2-ui/divider" "8.7.7" - "@dhis2-ui/field" "8.7.7" - "@dhis2-ui/file-input" "8.7.7" - "@dhis2-ui/header-bar" "8.7.7" - "@dhis2-ui/help" "8.7.7" - "@dhis2-ui/input" "8.7.7" - "@dhis2-ui/intersection-detector" "8.7.7" - "@dhis2-ui/label" "8.7.7" - "@dhis2-ui/layer" "8.7.7" - "@dhis2-ui/legend" "8.7.7" - "@dhis2-ui/loader" "8.7.7" - "@dhis2-ui/logo" "8.7.7" - "@dhis2-ui/menu" "8.7.7" - "@dhis2-ui/modal" "8.7.7" - "@dhis2-ui/node" "8.7.7" - "@dhis2-ui/notice-box" "8.7.7" - "@dhis2-ui/organisation-unit-tree" "8.7.7" - "@dhis2-ui/pagination" "8.7.7" - "@dhis2-ui/popover" "8.7.7" - "@dhis2-ui/popper" "8.7.7" - "@dhis2-ui/portal" "8.7.7" - "@dhis2-ui/radio" "8.7.7" - "@dhis2-ui/required" "8.7.7" - "@dhis2-ui/segmented-control" "8.7.7" - "@dhis2-ui/select" "8.7.7" - "@dhis2-ui/selector-bar" "8.7.7" - "@dhis2-ui/sharing-dialog" "8.7.7" - "@dhis2-ui/switch" "8.7.7" - "@dhis2-ui/tab" "8.7.7" - "@dhis2-ui/table" "8.7.7" - "@dhis2-ui/tag" "8.7.7" - "@dhis2-ui/text-area" "8.7.7" - "@dhis2-ui/tooltip" "8.7.7" - "@dhis2-ui/transfer" "8.7.7" - "@dhis2-ui/user-avatar" "8.7.7" - "@dhis2/ui-constants" "8.7.7" - "@dhis2/ui-forms" "8.7.7" - "@dhis2/ui-icons" "8.7.7" +"@dhis2/ui-icons@8.13.10": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2/ui-icons/-/ui-icons-8.13.10.tgz#802f678bc09b157f775c0826564e23f48f1a72c8" + integrity sha512-NA9gm/qbsJD6JhDjQszQxKDfsQtV5SNiJA668rfJjEK51JC5L0TXeidnOnX2T4Tg4vHMIdOuyjRCTkm1z+C1JQ== + +"@dhis2/ui@^8.13.10", "@dhis2/ui@^8.6.2": + version "8.13.10" + resolved "https://registry.yarnpkg.com/@dhis2/ui/-/ui-8.13.10.tgz#dc70be2b02333ffea5a10110bd33c8aa0136131b" + integrity sha512-0iN0qoFG6km54v9xHng9BVfoCDmTc6knOG1n7HU3bkn/nv28Lj8LecMtcXpqiB7sdFLQHArMRPOE7Z+RICRwLQ== + dependencies: + "@dhis2-ui/alert" "8.13.10" + "@dhis2-ui/box" "8.13.10" + "@dhis2-ui/button" "8.13.10" + "@dhis2-ui/calendar" "8.13.10" + "@dhis2-ui/card" "8.13.10" + "@dhis2-ui/center" "8.13.10" + "@dhis2-ui/checkbox" "8.13.10" + "@dhis2-ui/chip" "8.13.10" + "@dhis2-ui/cover" "8.13.10" + "@dhis2-ui/css" "8.13.10" + "@dhis2-ui/divider" "8.13.10" + "@dhis2-ui/field" "8.13.10" + "@dhis2-ui/file-input" "8.13.10" + "@dhis2-ui/header-bar" "8.13.10" + "@dhis2-ui/help" "8.13.10" + "@dhis2-ui/input" "8.13.10" + "@dhis2-ui/intersection-detector" "8.13.10" + "@dhis2-ui/label" "8.13.10" + "@dhis2-ui/layer" "8.13.10" + "@dhis2-ui/legend" "8.13.10" + "@dhis2-ui/loader" "8.13.10" + "@dhis2-ui/logo" "8.13.10" + "@dhis2-ui/menu" "8.13.10" + "@dhis2-ui/modal" "8.13.10" + "@dhis2-ui/node" "8.13.10" + "@dhis2-ui/notice-box" "8.13.10" + "@dhis2-ui/organisation-unit-tree" "8.13.10" + "@dhis2-ui/pagination" "8.13.10" + "@dhis2-ui/popover" "8.13.10" + "@dhis2-ui/popper" "8.13.10" + "@dhis2-ui/portal" "8.13.10" + "@dhis2-ui/radio" "8.13.10" + "@dhis2-ui/required" "8.13.10" + "@dhis2-ui/segmented-control" "8.13.10" + "@dhis2-ui/select" "8.13.10" + "@dhis2-ui/selector-bar" "8.13.10" + "@dhis2-ui/sharing-dialog" "8.13.10" + "@dhis2-ui/switch" "8.13.10" + "@dhis2-ui/tab" "8.13.10" + "@dhis2-ui/table" "8.13.10" + "@dhis2-ui/tag" "8.13.10" + "@dhis2-ui/text-area" "8.13.10" + "@dhis2-ui/tooltip" "8.13.10" + "@dhis2-ui/transfer" "8.13.10" + "@dhis2-ui/user-avatar" "8.13.10" + "@dhis2/ui-constants" "8.13.10" + "@dhis2/ui-forms" "8.13.10" + "@dhis2/ui-icons" "8.13.10" prop-types "^15.7.2" "@eslint-community/eslint-utils@^4.2.0": @@ -2621,6 +2566,14 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@js-temporal/polyfill@^0.4.2": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@js-temporal/polyfill/-/polyfill-0.4.4.tgz#4c26b4a1a68c19155808363f520204712cfc2558" + integrity sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg== + dependencies: + jsbi "^4.3.0" + tslib "^2.4.1" + "@juggle/resize-observer@^3.3.1": version "3.4.0" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" @@ -3299,23 +3252,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.5.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz#fb48c31cadc853ffc1dc35373f56b5e2a8908fe9" - integrity sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ== - dependencies: - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/type-utils" "5.50.0" - "@typescript-eslint/utils" "5.50.0" - debug "^4.3.4" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/eslint-plugin@^5.55.0": +"@typescript-eslint/eslint-plugin@^5.5.0", "@typescript-eslint/eslint-plugin@^5.55.0": version "5.55.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz#bc2400c3a23305e8c9a9c04aa40933868aaaeb47" integrity sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg== @@ -3338,17 +3275,7 @@ dependencies: "@typescript-eslint/utils" "5.50.0" -"@typescript-eslint/parser@^5.5.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.50.0.tgz#a33f44b2cc83d1b7176ec854fbecd55605b0b032" - integrity sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ== - dependencies: - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/typescript-estree" "5.50.0" - debug "^4.3.4" - -"@typescript-eslint/parser@^5.55.0": +"@typescript-eslint/parser@^5.5.0", "@typescript-eslint/parser@^5.55.0": version "5.55.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.55.0.tgz#8c96a0b6529708ace1dcfa60f5e6aec0f5ed2262" integrity sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw== @@ -3374,16 +3301,6 @@ "@typescript-eslint/types" "5.55.0" "@typescript-eslint/visitor-keys" "5.55.0" -"@typescript-eslint/type-utils@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz#509d5cc9728d520008f7157b116a42c5460e7341" - integrity sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ== - dependencies: - "@typescript-eslint/typescript-estree" "5.50.0" - "@typescript-eslint/utils" "5.50.0" - debug "^4.3.4" - tsutils "^3.21.0" - "@typescript-eslint/type-utils@5.55.0": version "5.55.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz#74bf0233523f874738677bb73cb58094210e01e9" @@ -3430,7 +3347,7 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.50.0", "@typescript-eslint/utils@^5.43.0": +"@typescript-eslint/utils@5.50.0": version "5.50.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.50.0.tgz#807105f5ffb860644d30d201eefad7017b020816" integrity sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw== @@ -3444,7 +3361,7 @@ eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/utils@5.55.0": +"@typescript-eslint/utils@5.55.0", "@typescript-eslint/utils@^5.43.0": version "5.55.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.55.0.tgz#34e97322e7ae5b901e7a870aabb01dad90023341" integrity sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw== @@ -4657,7 +4574,7 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -classnames@^2.2.6, classnames@^2.3.1: +classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -8412,6 +8329,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbi@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741" + integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -8798,12 +8720,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lz-string@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" - integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== - -lz-string@^1.5.0: +lz-string@^1.4.4, lz-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== @@ -12060,10 +11977,10 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" - integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== +tslib@^2.0.3, tslib@^2.4.1: + version "2.5.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" + integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== tsutils@^3.21.0: version "3.21.0" From 4741bcc24fb2bc84d9156cbe94fbe3024d8c60a8 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 19 Jun 2023 23:14:02 +0200 Subject: [PATCH 05/25] fix: simplify result type --- src/lib/models/useModelGist.ts | 22 +++++++++++----------- src/types/generated/utility.ts | 13 +++++++------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index edb41824..5c5d08d5 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -86,20 +86,20 @@ type GistPaginator = { getPrevPage: () => boolean } -type BaseUseGistModelResult = Pick< +type BaseUseModelGistResult = Pick< QueryResponse, 'loading' | 'error' | 'called' | 'refetch' > & { data?: Response } -type UseGistModelResultPaginated = - BaseUseGistModelResult & { +type UseModelGistResultPaginated = + BaseUseModelGistResult & { pagination: GistPaginator } -type UseGistModelResult = - | BaseUseGistModelResult - | UseGistModelResultPaginated +type UseModelGistResult = + | BaseUseModelGistResult + | UseModelGistResultPaginated const isDataCollection = ( data: unknown @@ -111,17 +111,17 @@ const isDataCollection = ( export function useModelGist( gistResource: GistResourceString, params?: GistParams -): UseGistModelResultPaginated +): UseModelGistResultPaginated export function useModelGist( gistResource: GistResourceString, params?: GistParams -): UseGistModelResult +): UseModelGistResult export function useModelGist( gistResource: GistResourceString, params?: GistParams -): UseGistModelResult { +): UseModelGistResult { const [gistQuery] = useState( createGistQuery(gistResource, params) ) @@ -141,7 +141,7 @@ export function useModelGist( ) return React.useMemo(() => { - const baseResult: UseGistModelResult = { + const baseResult: UseModelGistResult = { loading: queryResponse.loading, called: queryResponse.called, error: queryResponse.error, @@ -149,7 +149,7 @@ export function useModelGist( refetch: queryResponse.refetch, } if (pagination) { - const result: UseGistModelResultPaginated = { + const result: UseModelGistResultPaginated = { ...baseResult, pagination, } diff --git a/src/types/generated/utility.ts b/src/types/generated/utility.ts index c7ab2826..0397103d 100644 --- a/src/types/generated/utility.ts +++ b/src/types/generated/utility.ts @@ -1,6 +1,6 @@ /* GENERATED BY https://github.com/Birkbjo/dhis2-open-api-ts */ -import { DataElement } from './' +// import { DataElement } from './' import { IdentifiableObject, GistPager } from './' // import { CategoryCombo, DataElement } from "../generated"; @@ -29,12 +29,12 @@ export type GistModel = BaseGist & { export type GistModelCollection = GistModel[] // a modelcollection with the keyprop -// need it's own type because the key is based on the resource +// need it's own type because the name of the list is based on resource or pagedListName query-param export type GistModelCollectionPart< T extends IdentifiableObject, - Resource extends string = string + PagedListName extends string = 'result' > = { - [K in Resource]: GistModel[] + [K in PagedListName]: GistModel[] } export type GistPagedResponse = { @@ -43,8 +43,8 @@ export type GistPagedResponse = { export type GistCollectionResponse< T extends IdentifiableObject = IdentifiableObject, - Resource extends string = string -> = GistPagedResponse & GistModelCollectionPart + PagedListName extends string = 'result' +> = GistPagedResponse & GistModelCollectionPart export type GistObjectResponse< T extends IdentifiableObject = IdentifiableObject @@ -105,6 +105,7 @@ export type GistParams = { order?: string page?: number pageSize?: number + pageListName?: string references?: boolean rootJunction?: 'AND' | 'OR' total?: boolean From 53671596abc6c99c11a8ae6ea322087a65ecac75 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 21 Jun 2023 14:15:09 +0200 Subject: [PATCH 06/25] fix(usemodelgist): add goToPage to paginator --- src/lib/models/useModelGist.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 5c5d08d5..0fc574aa 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -42,6 +42,7 @@ function createGistQuery( result: { resource: `${resource}`, params: ({ page, ...dynamicParams }) => ({ + total: true, ...params, ...dynamicParams, page, @@ -75,15 +76,28 @@ function usePagination( return true }, [refetch, pager]) + const goToPage = useCallback( + (page: number) => { + if (!pager?.pageCount || page > pager.pageCount) { + return false + } + refetch({ page: page }) + return true + }, + [refetch, pager] + ) + return { getNextPage, getPrevPage, + goToPage, } } type GistPaginator = { getNextPage: () => boolean getPrevPage: () => boolean + goToPage: (page: number) => boolean } type BaseUseModelGistResult = Pick< @@ -103,7 +117,7 @@ type UseModelGistResult = const isDataCollection = ( data: unknown -): data is GistCollectionResponse => { +): data is GistCollectionResponse => { // gist endpoints are always paged if they're collections return (data as GistCollectionResponse)?.pager !== undefined } From d59d5ae5a419e7c969f7ff39840e0d22c91be278 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 21 Jun 2023 16:09:34 +0200 Subject: [PATCH 07/25] feat: sectionlistwrapper --- src/components/sectionList/SectionList.tsx | 12 ++- src/components/sectionList/SectionListRow.tsx | 17 ++++- .../sectionList/SectionListWrapper.tsx | 75 +++++++++++++++++++ src/components/sectionList/index.ts | 1 + src/components/sectionList/types.ts | 5 ++ src/pages/dataElements/List.tsx | 27 ++++--- src/types/generated/utility.ts | 2 +- src/types/index.ts | 1 + 8 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 src/components/sectionList/SectionListWrapper.tsx diff --git a/src/components/sectionList/SectionList.tsx b/src/components/sectionList/SectionList.tsx index d0a77d71..2da39079 100644 --- a/src/components/sectionList/SectionList.tsx +++ b/src/components/sectionList/SectionList.tsx @@ -8,23 +8,33 @@ import { Checkbox, } from '@dhis2/ui' import React, { PropsWithChildren } from 'react' +import { CheckBoxOnChangeObject } from '../../types' import { IdentifiableObject } from '../../types/generated' import { SelectedColumns } from './types' type SectionListProps = { headerColumns: SelectedColumns + onSelectAll: (checked: boolean) => void + allSelected?: boolean } export const SectionList = ({ + allSelected, headerColumns, children, + onSelectAll, }: PropsWithChildren>) => { return ( - + + onSelectAll(checked) + } + /> {headerColumns.map((headerColumn) => ( = { - modelData: Model + modelData: Model | GistModel selectedColumns: SelectedColumns + onSelect: (modelId: string, checked: boolean) => void + selected: boolean } export function SectionListRow({ selectedColumns, modelData, + onSelect, + selected, }: SectionListRowProps) { return ( - + { + onSelect(modelData.id, checked) + }} + /> {selectedColumns.map(({ modelPropertyName }) => ( diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx new file mode 100644 index 00000000..5363f160 --- /dev/null +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -0,0 +1,75 @@ +import React, { useMemo, useState } from 'react' +import { + IdentifiableObject, + GistModel, + GistCollectionResponse, +} from '../../types/models' +import { SectionList } from './SectionList' +import { SectionListRow } from './SectionListRow' +import { SelectedColumns } from './types' + +type SectionListWrapperProps = { + availableColumns?: SelectedColumns + defaultColumns: SelectedColumns + data?: GistCollectionResponse +} +export const SectionListWrapper = ({ + availableColumns, + defaultColumns, + data, +}: SectionListWrapperProps) => { + const [selectedColumns, setSelectedColumns] = + useState>(defaultColumns) + const [selectedModels, setSelectedModels] = useState>(new Set()) + console.log({ selectedModels }) + const handleSelect = (id: string, checked: boolean) => { + console.log('select', id, checked) + if (checked) { + setSelectedModels(new Set(selectedModels).add(id)) + } else { + selectedModels.delete(id) + setSelectedModels(new Set(selectedModels)) + } + } + + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedModels( + new Set( + data?.result?.map((model) => { + return model.id + }) + ) + ) + } else { + setSelectedModels(new Set()) + } + } + + const allSelected = useMemo(() => { + return ( + data?.result.length !== 0 && + data?.result.length === selectedModels.size + ) + }, [data?.result, selectedModels.size]) + + return ( + + {!data?.result + ? 'Loading...' + : data?.result.map((model) => ( + + ))} + + ) +} diff --git a/src/components/sectionList/index.ts b/src/components/sectionList/index.ts index 1457a992..3b244c5d 100644 --- a/src/components/sectionList/index.ts +++ b/src/components/sectionList/index.ts @@ -1,3 +1,4 @@ export * from './SectionList' export * from './SectionListRow' +export * from './SectionListWrapper' export type * from './types' diff --git a/src/components/sectionList/types.ts b/src/components/sectionList/types.ts index 9081f3fc..046d16f9 100644 --- a/src/components/sectionList/types.ts +++ b/src/components/sectionList/types.ts @@ -7,3 +7,8 @@ export type SelectedColumn = { export type SelectedColumns = SelectedColumn[] +export type CheckBoxOnChangeObject = { + checked: boolean + name?: string + value?: string +} diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index 0a94ad43..f238c715 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -1,8 +1,18 @@ import i18n from '@dhis2/d2-i18n' import React, { useEffect } from 'react' -import { SectionList, SectionListRow, SelectedColumns } from '../../components' +import { + SectionList, + SectionListRow, + SectionListWrapper, + SelectedColumns, +} from '../../components' import { useModelGist } from '../../lib/' -import { DataElement, GistCollectionResponse } from '../../types/generated' +import { + DataElement, + GistCollectionResponse, + GistModel, + IdentifiableObject, +} from '../../types/models' const filterFields = [ 'access', @@ -58,15 +68,10 @@ export const Component = () => { type="text" onChange={(val) => setFilter(val.target.value)} > - - {data?.result.map((dataElement) => ( - - ))} - + ) diff --git a/src/types/generated/utility.ts b/src/types/generated/utility.ts index 0397103d..1273375b 100644 --- a/src/types/generated/utility.ts +++ b/src/types/generated/utility.ts @@ -7,7 +7,7 @@ import { IdentifiableObject, GistPager } from './' type ModelReferenceCollection = Array type ModelReference = IdentifiableObject | ModelReferenceCollection -type BaseGist = { +type BaseGist = IdentifiableObject & { apiEndpoints: GistApiEndpoints } export type GistApiEndpoints = { diff --git a/src/types/index.ts b/src/types/index.ts index 0bbfaa56..482b4165 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1 +1,2 @@ export type * from './query' +export * from './ui' From c689080388fbecaa4a27a613ba1f8c0044045fcf Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 21 Jun 2023 17:04:20 +0200 Subject: [PATCH 08/25] feat: add translations for model constants --- src/constants/translatedModelConstants.ts | 71 +++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/constants/translatedModelConstants.ts diff --git a/src/constants/translatedModelConstants.ts b/src/constants/translatedModelConstants.ts new file mode 100644 index 00000000..ba8dd8d1 --- /dev/null +++ b/src/constants/translatedModelConstants.ts @@ -0,0 +1,71 @@ +import i18n from '@dhis2/d2-i18n' + +// because of the way d2-i18n works, we cannot translate dynamic values +// so we have to list them manually +export const AGGREGATION_TYPE = { + SUM: i18n.t('Sum'), + AVERAGE: i18n.t('Average'), + AVERAGE_SUM_ORG_UNIT: i18n.t('Average (sum in org unit)'), + LAST: i18n.t('Last value (sum in org unit hierarchy)'), + LAST_AVERAGE_ORG_UNIT: i18n.t('Last value (average in org unit)'), + LAST_LAST_ORG_UNIT: i18n.t('Last value (last in org unit hierarchy)'), + LAST_IN_PERIOD: i18n.t('Last value in period (sum in org unit hierarchy)'), + LAST_IN_PERIOD_AVERAGE_ORG_UNIT: i18n.t('Lastinperiodaverageorgunit'), + FIRST: i18n.t('First value (sum in org unit hierarchy)'), + FIRST_AVERAGE_ORG_UNIT: i18n.t( + 'First value (average in org unit hierarchy)' + ), + FIRST_FIRST_ORG_UNIT: i18n.t('First value (first in org unit hierarchy)'), + COUNT: i18n.t('Count'), + STDDEV: i18n.t('Standard deviation'), + VARIANCE: i18n.t('Variance'), + MIN: i18n.t('Min'), + MAX: i18n.t('Max'), + MIN_SUM_ORG_UNIT: i18n.t('Min (sum in org unit)'), + MAX_SUM_ORG_UNIT: i18n.t('Max (sum in org unit)'), + NONE: i18n.t('None'), + CUSTOM: i18n.t('Custom'), + DEFAULT: i18n.t('Default'), +} + +export const DOMAIN_TYPE = { + AGGREGATE: i18n.t('Aggregate'), + TRACKER: i18n.t('Tracker'), +} + +export const VALUE_TYPE = { + TEXT: i18n.t('Text'), + LONG_TEXT: i18n.t('Long text'), + MULTI_TEXT: i18n.t('Text with multiple values'), + LETTER: i18n.t('Letter'), + PHONE_NUMBER: i18n.t('Phone number'), + EMAIL: i18n.t('Email'), + BOOLEAN: i18n.t('Yes/No'), + TRUE_ONLY: i18n.t('Yes only'), + DATE: i18n.t('Date'), + DATETIME: i18n.t('Date and time'), + TIME: i18n.t('Time'), + NUMBER: i18n.t('Number'), + UNIT_INTERVAL: i18n.t('Unit interval'), + PERCENTAGE: i18n.t('Percentage'), + INTEGER: i18n.t('Integer'), + INTEGER_POSITIVE: i18n.t('Positive integer'), + INTEGER_NEGATIVE: i18n.t('Negative integer'), + INTEGER_ZERO_OR_POSITIVE: i18n.t('Positive or Zero integer'), + TRACKER_ASSOCIATE: i18n.t('Tracker associate'), + USERNAME: i18n.t('Username'), + COORDINATE: i18n.t('Coordinate'), + ORGANISATION_UNIT: i18n.t('Organisation unit'), + REFERENCE: i18n.t('Reference'), + AGE: i18n.t('Age'), + URL: i18n.t('URL'), + FILE_RESOURCE: i18n.t('File'), + IMAGE: i18n.t('Image'), + GEOJSON: i18n.t('GeoJSON'), +} + +export const constantPropertyTranlsations = { + aggregationType: AGGREGATION_TYPE, + domainType: DOMAIN_TYPE, + valueType: VALUE_TYPE, +} From f585402189c407b48569fba7a5f5fd287bfaf990 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 21 Jun 2023 18:31:17 +0200 Subject: [PATCH 09/25] feat: sectionlistheader, wip filters --- .../sectionList/SectionList.module.css | 16 +++++++ src/components/sectionList/SectionListRow.tsx | 1 + .../sectionList/SectionListWrapper.tsx | 43 +++++++++++-------- .../sectionList/SelectionListHeaderNormal.tsx | 21 +++++++++ 4 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 src/components/sectionList/SelectionListHeaderNormal.tsx diff --git a/src/components/sectionList/SectionList.module.css b/src/components/sectionList/SectionList.module.css index 61bc13d8..01ca8158 100644 --- a/src/components/sectionList/SectionList.module.css +++ b/src/components/sectionList/SectionList.module.css @@ -1,3 +1,19 @@ +.listHeaderNormal { + background-color: #fff; + width: 100%; + height: 24px; + display: flex; + align-items: center; + height: 48px; + padding: var(--spacers-dp8); + margin-top: 8px; + gap: var(--spacers-dp8); +} + +.listHeaderNormal a { + line-height: 16px; +} + .listRow { } diff --git a/src/components/sectionList/SectionListRow.tsx b/src/components/sectionList/SectionListRow.tsx index a46a69cc..2e31f2d2 100644 --- a/src/components/sectionList/SectionListRow.tsx +++ b/src/components/sectionList/SectionListRow.tsx @@ -6,6 +6,7 @@ import { CheckBoxOnChangeObject } from '../../types' import { IdentifiableObject, GistModel } from '../../types/models' import css from './SectionList.module.css' import { SelectedColumns } from './types' + export type SectionListRowProps = { modelData: Model | GistModel selectedColumns: SelectedColumns diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 5363f160..bac490e5 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -4,8 +4,10 @@ import { GistModel, GistCollectionResponse, } from '../../types/models' +import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' import { SectionListRow } from './SectionListRow' +import { SelectionListHeader } from './SelectionListHeaderNormal' import { SelectedColumns } from './types' type SectionListWrapperProps = { @@ -21,9 +23,8 @@ export const SectionListWrapper = ({ const [selectedColumns, setSelectedColumns] = useState>(defaultColumns) const [selectedModels, setSelectedModels] = useState>(new Set()) - console.log({ selectedModels }) + const handleSelect = (id: string, checked: boolean) => { - console.log('select', id, checked) if (checked) { setSelectedModels(new Set(selectedModels).add(id)) } else { @@ -54,22 +55,26 @@ export const SectionListWrapper = ({ }, [data?.result, selectedModels.size]) return ( - - {!data?.result - ? 'Loading...' - : data?.result.map((model) => ( - - ))} - +
+ + + + {!data?.result + ? 'Loading...' + : data?.result.map((model) => ( + + ))} + +
) } diff --git a/src/components/sectionList/SelectionListHeaderNormal.tsx b/src/components/sectionList/SelectionListHeaderNormal.tsx new file mode 100644 index 00000000..4a0c26dc --- /dev/null +++ b/src/components/sectionList/SelectionListHeaderNormal.tsx @@ -0,0 +1,21 @@ +import i18n from '@dhis2/d2-i18n' +import { Button } from '@dhis2/ui' +import { IconAdd24 } from '@dhis2/ui-icons' +import React from 'react' +import { Link } from 'react-router-dom' +import { routePaths } from '../../app/routes/routePaths' +import css from './SectionList.module.css' + +export const SelectionListHeader = () => { + return ( +
+ + + + + +
+ ) +} From 342dbadbf37015ebeae7429ca3a9c2c38f54b251 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Tue, 4 Jul 2023 16:34:34 +0200 Subject: [PATCH 10/25] fix: wip filters --- package.json | 3 +- src/app/routes/Router.tsx | 13 +++++-- .../filters/ConstantSelectionFilter.tsx | 37 +++++++++++++++++++ .../sectionList/filters/FilterWrapper.tsx | 12 ++++++ .../sectionList/filters/Filters.module.css | 3 ++ .../filters/useSectionListFilter.ts | 5 +++ src/lib/models/useModelGist.ts | 1 + src/pages/dataElements/List.tsx | 1 - yarn.lock | 12 ++++++ 9 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 src/components/sectionList/filters/ConstantSelectionFilter.tsx create mode 100644 src/components/sectionList/filters/FilterWrapper.tsx create mode 100644 src/components/sectionList/filters/Filters.module.css create mode 100644 src/components/sectionList/filters/useSectionListFilter.ts diff --git a/package.json b/package.json index 81175fc7..ce4f1c7f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@dhis2/app-runtime": "^3.9.3", "@dhis2/ui": "^8.13.10", - "react-router-dom": "^6.11.2" + "react-router-dom": "^6.11.2", + "use-query-params": "^2.2.1" } } diff --git a/src/app/routes/Router.tsx b/src/app/routes/Router.tsx index 8c146531..112fcf69 100644 --- a/src/app/routes/Router.tsx +++ b/src/app/routes/Router.tsx @@ -10,6 +10,8 @@ import { RouteObject, useParams, } from 'react-router-dom' +import { QueryParamProvider } from 'use-query-params' +import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6' import { SECTIONS_MAP, Section } from '../../constants' import { isModuleNotFoundError } from '../../lib' import { isValidUid } from '../../models' @@ -17,7 +19,6 @@ import { Layout } from '../layout' import { DefaultErrorRoute } from './DefaultErrorRoute' import { LegacyAppRedirect } from './LegacyAppRedirect' import { getSectionPath, routePaths } from './routePaths' - // This loads all the overview routes in the same chunk since they resolve to the same promise // see https://reactrouter.com/en/main/route/lazy#multiple-routes-in-a-single-file // Overviews are small, and the AllOverview would load all the other overviews anyway, @@ -101,7 +102,14 @@ const sectionRoutes = Object.values(SECTIONS_MAP).map((section) => ( )) const routes = createRoutesFromElements( - } errorElement={}> + + + + } + errorElement={} + > } @@ -125,7 +133,6 @@ const routes = createRoutesFromElements( ) export const hashRouter = createHashRouter(routes) - export const ConfiguredRouter = () => { return } diff --git a/src/components/sectionList/filters/ConstantSelectionFilter.tsx b/src/components/sectionList/filters/ConstantSelectionFilter.tsx new file mode 100644 index 00000000..a3ef4914 --- /dev/null +++ b/src/components/sectionList/filters/ConstantSelectionFilter.tsx @@ -0,0 +1,37 @@ +import { SingleSelect, SingleSelectOption } from '@dhis2/ui' +import React from 'react' +import { useSearchParams } from 'react-router-dom' +import { useQueryParam, ObjectParam } from 'use-query-params' +import css from './Filters.module.css' + +type ConstantSelectionFilterProps = { + filterKey: string + label: string + options: { + label: string + value: string + } +} + +export const ConstantSelectionFilter = ({ + label, +}: ConstantSelectionFilterProps) => { + const [value, setValue] = React.useState('') + const [filter, setFilter] = useQueryParam('filter', ObjectParam) + + return ( + { + setFilter((prev) => ({ ...prev, domainType: selected })) + console.log('selected', selected) + }} + selected={filter?.domainType || ''} + placeholder={label} + > + + + + + ) +} diff --git a/src/components/sectionList/filters/FilterWrapper.tsx b/src/components/sectionList/filters/FilterWrapper.tsx new file mode 100644 index 00000000..0a6ef99e --- /dev/null +++ b/src/components/sectionList/filters/FilterWrapper.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { ConstantSelectionFilter } from './ConstantSelectionFilter' + +type FilterWrapperProps = React.PropsWithChildren<{}> + +export const FilterWrapper = () => { + return ( +
+ +
+ ) +} diff --git a/src/components/sectionList/filters/Filters.module.css b/src/components/sectionList/filters/Filters.module.css new file mode 100644 index 00000000..138d44f2 --- /dev/null +++ b/src/components/sectionList/filters/Filters.module.css @@ -0,0 +1,3 @@ +.constantSelectionFilter { + width: 320px; +} diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts new file mode 100644 index 00000000..8fec2d84 --- /dev/null +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -0,0 +1,5 @@ +import { useQueryParam, ObjectParam } from 'use-query-params' + +export const useSectionListFilter = () => { + const [filter, setFilter] = useQueryParam('filter', ObjectParam) +} diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 0fc574aa..3683e3ac 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -42,6 +42,7 @@ function createGistQuery( result: { resource: `${resource}`, params: ({ page, ...dynamicParams }) => ({ + pageListName: 'result', total: true, ...params, ...dynamicParams, diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index f238c715..f3d70ab6 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -44,7 +44,6 @@ export const Component = () => { 'dataElements/gist', { fields: filterFields.concat(), - pageListName: 'result', order: 'name:ASC', } ) diff --git a/yarn.lock b/yarn.lock index ddd3149b..1854016d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11068,6 +11068,11 @@ serialize-javascript@^6.0.0: dependencies: randombytes "^2.1.0" +serialize-query-params@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/serialize-query-params/-/serialize-query-params-2.0.2.tgz#598a3fb9e13f4ea1c1992fbd20231aa16b31db81" + integrity sha512-1chMo1dST4pFA9RDXAtF0Rbjaut4is7bzFbI1Z26IuMub68pNCILku85aYmeFhvnY//BXUPUhoRMjYcsT93J/Q== + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -12250,6 +12255,13 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-query-params@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/use-query-params/-/use-query-params-2.2.1.tgz#c558ab70706f319112fbccabf6867b9f904e947d" + integrity sha512-i6alcyLB8w9i3ZK3caNftdb+UnbfBRNPDnc89CNQWkGRmDrm/gfydHvMBfVsQJRq3NoHOM2dt/ceBWG2397v1Q== + dependencies: + serialize-query-params "^2.0.2" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From c431631a002b5fdd2df8ebf78b3b245e2be70930 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Tue, 4 Jul 2023 23:44:47 +0200 Subject: [PATCH 11/25] feat: useSectionListFilter --- .../filters/useSectionListFilter.ts | 141 +++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts index 8fec2d84..b4c6e843 100644 --- a/src/components/sectionList/filters/useSectionListFilter.ts +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -1,5 +1,142 @@ +import { useCallback, useMemo, useEffect } from 'react' import { useQueryParam, ObjectParam } from 'use-query-params' +import { Schema, useSchemaFromHandle } from '../../../lib' +import { QueryRefetchFunction } from '../../../types' +import { GistParams } from '../../../types/generated' -export const useSectionListFilter = () => { - const [filter, setFilter] = useQueryParam('filter', ObjectParam) +type ObjectParamType = typeof ObjectParam.default + +type Filters = Record + +// special key for handling search for identifiable objects +// eg. searches for name, code and shortname +// this would translate to "token" in the old API, but does not exist in GIST-API +const IDENTIFIABLE_KEY = 'identifiable' + +const IDENTIFIABLE_FIELDS = { + name: { + operator: 'ilike', + }, + code: { + operator: 'ilike', + }, + shortName: { + operator: 'ilike', + }, + id: { + operator: 'eq', + }, +} + +const getRelevantFiltersForSchema = ( + filters: ObjectParamType, + schema: Schema +): Filters => { + if (!filters) { + return {} + } + /* TODO: verify values for filters + Might want to do this when parsing to queryFilter. */ + const relevantFilters = Object.entries(filters).filter(([key]) => { + return key === IDENTIFIABLE_KEY || schema.properties[key] + }) + return Object.fromEntries(relevantFilters) +} + +const useFilterQueryParam = () => { + return useQueryParam('filter', ObjectParam) +} + +export const useSectionListFilters = (): ReturnType< + typeof useQueryParam +> => { + const [filter, setFilter] = useFilterQueryParam() + const schema = useSchemaFromHandle() + + return useMemo( + () => [getRelevantFiltersForSchema(filter, schema), setFilter], + [filter, schema, setFilter] + ) +} + +export const useSectionListFilter = ( + filterKey: string +): [string | undefined, (value: string | undefined) => void] => { + const [filters, setFilters] = useSectionListFilters() + + const boundSetFilter = useCallback( + (value: string | undefined) => { + if (!value) { + setFilters((filters) => { + if (!filters) { + return undefined + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [filterKey]: _, ...rest } = filters + return Object.keys(rest).length === 0 ? undefined : rest + }) + } else { + setFilters((filters) => ({ ...filters, [filterKey]: value })) + } + }, + [filterKey, setFilters] + ) + + return useMemo(() => { + return [filters?.[filterKey] ?? undefined, boundSetFilter] + }, [filters, filterKey, boundSetFilter]) +} + +export type ParseToQueryFilterResult = { + filter: string[] + rootJunction: GistParams['rootJunction'] +} + +const parseToQueryFilter = (filters: Filters): ParseToQueryFilterResult => { + const { [IDENTIFIABLE_KEY]: identifiableValue, ...restFilters } = filters + const queryFilters: string[] = [] + + const hasOtherFilters = Object.keys(restFilters).length > 0 + // Groups are a powerful way to combine filters, + // here we use them for identifiable filters, to group them with "OR" and + // rest of the filters with "AND". + // Unfortunately, it doesn't work to send groups without at least two, + // so we need to add them conditionally. + // see https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-239/metadata-gist.html#gist_parameters_filter + const identifiableFilterGroup = hasOtherFilters ? `0:` : '' + if (identifiableValue) { + Object.entries(IDENTIFIABLE_FIELDS).forEach(([key, { operator }]) => { + queryFilters.push( + `${identifiableFilterGroup}${key}:${operator}:${identifiableValue}` + ) + }) + } + const restFilterGroup = identifiableValue ? `1:` : '' + Object.entries(restFilters).forEach(([key, value]) => { + queryFilters.push(`${restFilterGroup}${key}:eq:${value}`) + }) + // when there are no other filters than identifiable, we can't group them + // and thus we need to set rootJunction to OR + const rootJunction = + identifiableValue && !hasOtherFilters ? 'OR' : undefined + return { filter: queryFilters, rootJunction } +} + +export const useSectionListQueryFilter = () => { + const [filters] = useSectionListFilters() + + return useMemo(() => { + return parseToQueryFilter(filters) + }, [filters]) +} + +export const useSectionListFilterRefetch = (refetch: QueryRefetchFunction) => { + const { filter, rootJunction } = useSectionListQueryFilter() + + useEffect(() => { + refetch({ + filter, + rootJunction, + }) + }, [refetch, filter, rootJunction]) } From 0eac9d17a4884da6ddf799fd40a3193a8c1264df Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 02:27:04 +0200 Subject: [PATCH 12/25] feat: working filters --- src/components/loading/Loader.tsx | 6 +- src/components/loading/LoadingSpinner.tsx | 16 ++++++ .../sectionList/SectionListLoader.tsx | 13 +++++ .../sectionList/SectionListWrapper.tsx | 35 ++++++------ .../filters/ConstantSelectionFilter.tsx | 56 ++++++++++++++----- .../sectionList/filters/FilterWrapper.tsx | 24 ++++++-- .../sectionList/filters/Filters.module.css | 20 +++++++ .../filters/IdentifiableFilter.tsx | 35 ++++++++++++ src/components/sectionList/filters/index.ts | 1 + .../filters/useSectionListFilter.ts | 17 +++--- src/components/sectionList/index.ts | 1 + src/constants/translatedModelConstants.ts | 2 +- src/lib/index.ts | 2 + src/lib/models/useModelGist.ts | 1 + src/lib/routeUtils/CustomQueryParam.ts | 9 +++ src/lib/routeUtils/index.ts | 1 + src/lib/schemas/index.tsx | 1 + src/lib/schemas/useSchemaFromHandle.tsx | 6 ++ src/lib/useSectionHandle.ts | 13 ++++- src/pages/dataElements/List.tsx | 42 +++++--------- src/types/query.ts | 2 + 21 files changed, 226 insertions(+), 77 deletions(-) create mode 100644 src/components/loading/LoadingSpinner.tsx create mode 100644 src/components/sectionList/SectionListLoader.tsx create mode 100644 src/components/sectionList/filters/IdentifiableFilter.tsx create mode 100644 src/components/sectionList/filters/index.ts create mode 100644 src/lib/routeUtils/CustomQueryParam.ts create mode 100644 src/lib/routeUtils/index.ts create mode 100644 src/lib/schemas/useSchemaFromHandle.tsx diff --git a/src/components/loading/Loader.tsx b/src/components/loading/Loader.tsx index 825da934..8e80530e 100644 --- a/src/components/loading/Loader.tsx +++ b/src/components/loading/Loader.tsx @@ -1,8 +1,8 @@ import i18n from '@dhis2/d2-i18n' -import { CircularLoader, NoticeBox } from '@dhis2/ui' +import { NoticeBox } from '@dhis2/ui' import React from 'react' import { QueryResponse } from '../../types' -import styles from './Loader.module.css' +import { LoadingSpinner } from './LoadingSpinner' interface LoaderProps { children: React.ReactNode @@ -11,7 +11,7 @@ interface LoaderProps { } export const Loader = ({ children, queryResponse, label }: LoaderProps) => { if (queryResponse.loading) { - return + return } if (queryResponse.error) { const message = queryResponse.error?.message diff --git a/src/components/loading/LoadingSpinner.tsx b/src/components/loading/LoadingSpinner.tsx new file mode 100644 index 00000000..039655e0 --- /dev/null +++ b/src/components/loading/LoadingSpinner.tsx @@ -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 +}) => ( + +) diff --git a/src/components/sectionList/SectionListLoader.tsx b/src/components/sectionList/SectionListLoader.tsx new file mode 100644 index 00000000..9839e46c --- /dev/null +++ b/src/components/sectionList/SectionListLoader.tsx @@ -0,0 +1,13 @@ +import { DataTableRow, DataTableCell } from '@dhis2/ui' +import React from 'react' +import { LoadingSpinner } from '../loading/LoadingSpinner' + +export const SectionListLoader = () => { + return ( + + + + + + ) +} diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index bac490e5..c49cef9e 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -1,11 +1,8 @@ import React, { useMemo, useState } from 'react' -import { - IdentifiableObject, - GistModel, - GistCollectionResponse, -} from '../../types/models' +import { IdentifiableObject, GistCollectionResponse } from '../../types/models' import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' +import { SectionListLoader } from './SectionListLoader' import { SectionListRow } from './SectionListRow' import { SelectionListHeader } from './SelectionListHeaderNormal' import { SelectedColumns } from './types' @@ -14,11 +11,13 @@ type SectionListWrapperProps = { availableColumns?: SelectedColumns defaultColumns: SelectedColumns data?: GistCollectionResponse + filterElement?: React.ReactElement } export const SectionListWrapper = ({ availableColumns, defaultColumns, data, + filterElement, }: SectionListWrapperProps) => { const [selectedColumns, setSelectedColumns] = useState>(defaultColumns) @@ -56,24 +55,26 @@ export const SectionListWrapper = ({ return (
- + {filterElement} - {!data?.result - ? 'Loading...' - : data?.result.map((model) => ( - - ))} + {!data?.result ? ( + + ) : ( + data?.result.map((model) => ( + + )) + )}
) diff --git a/src/components/sectionList/filters/ConstantSelectionFilter.tsx b/src/components/sectionList/filters/ConstantSelectionFilter.tsx index f48e8924..1e689f18 100644 --- a/src/components/sectionList/filters/ConstantSelectionFilter.tsx +++ b/src/components/sectionList/filters/ConstantSelectionFilter.tsx @@ -1,33 +1,63 @@ import { SingleSelect, SingleSelectOption } from '@dhis2/ui' import React from 'react' -import { useSearchParams } from 'react-router-dom' -import { useQueryParam, ObjectParam } from 'use-query-params' +import { + DOMAIN_TYPE, + VALUE_TYPE, +} from '../../../constants/translatedModelConstants' +import { SelectOnChangeObject } from '../../../types' import css from './Filters.module.css' +import { useSectionListFilter } from './useSectionListFilter' type ConstantSelectionFilterProps = { label: string + constants: Record + filterKey: string + filterable?: boolean } export const ConstantSelectionFilter = ({ + constants, + filterKey, label, + filterable, }: ConstantSelectionFilterProps) => { - const [value, setValue] = React.useState('') - const [filter, setFilter] = useQueryParam('filter', ObjectParam) - + const [filter, setFilter] = useSectionListFilter(filterKey) return ( { - setFilter((prev) => ({ ...prev, domainType: selected })) - console.log('selected', selected) + onChange={({ selected }: SelectOnChangeObject) => { + setFilter(selected) }} - selected={filter?.domainType || ''} + selected={filter} placeholder={label} + dense + filterable={filterable} > - - - + + {Object.entries(constants).map(([key, label]) => ( + + ))} ) } + +export const DomainTypeSelectionFilter = () => { + return ( + + ) +} + +export const ValueTypeSelectionFilter = () => { + return ( + + ) +} diff --git a/src/components/sectionList/filters/FilterWrapper.tsx b/src/components/sectionList/filters/FilterWrapper.tsx index 0a6ef99e..662ad197 100644 --- a/src/components/sectionList/filters/FilterWrapper.tsx +++ b/src/components/sectionList/filters/FilterWrapper.tsx @@ -1,12 +1,26 @@ +import i18n from '@dhis2/d2-i18n' +import { Button } from '@dhis2/ui' import React from 'react' -import { ConstantSelectionFilter } from './ConstantSelectionFilter' +import css from './Filters.module.css' +import { IdentifiableFilter } from './IdentifiableFilter' +import { useSectionListFilters } from './useSectionListFilter' -type FilterWrapperProps = React.PropsWithChildren<{}> +type FilterWrapperProps = React.PropsWithChildren + +export const FilterWrapper = ({ children }: FilterWrapperProps) => { + const [, setFilters] = useSectionListFilters() + + const handleClear = () => { + setFilters(undefined) + } -export const FilterWrapper = () => { return ( -
- +
+ + {children} +
) } diff --git a/src/components/sectionList/filters/Filters.module.css b/src/components/sectionList/filters/Filters.module.css index 138d44f2..6363b729 100644 --- a/src/components/sectionList/filters/Filters.module.css +++ b/src/components/sectionList/filters/Filters.module.css @@ -1,3 +1,23 @@ +.selectionFilter { + min-width: 190px; +} + .constantSelectionFilter { + composes: selectionFilter; +} + +/* make dense selection fields same height as text field*/ +.constantSelectionFilter [data-test='dhis2-uicore-select-input'] { + max-height: 32px; +} + +.identifiableSelectionFilter { width: 320px; } + +.filterWrapper { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} diff --git a/src/components/sectionList/filters/IdentifiableFilter.tsx b/src/components/sectionList/filters/IdentifiableFilter.tsx new file mode 100644 index 00000000..a5637d9a --- /dev/null +++ b/src/components/sectionList/filters/IdentifiableFilter.tsx @@ -0,0 +1,35 @@ +import i18n from '@dhis2/d2-i18n' +import { Input } from '@dhis2/ui' +import React, { useEffect, useState } from 'react' +import { useDebounce } from '../../../lib' +import { InputOnChangeObject } from '../../../types' +import css from './Filters.module.css' +import { IDENTIFIABLE_KEY, useSectionListFilter } from './useSectionListFilter' + +export const IdentifiableFilter = () => { + const [filter, setFilter] = useSectionListFilter(IDENTIFIABLE_KEY) + const [value, setValue] = useState(filter) + const debouncedValue = useDebounce(value, 200) + + useEffect(() => { + setFilter(debouncedValue || undefined) // convert empty string to undefined + }, [debouncedValue, setFilter]) + + useEffect(() => { + if (!filter) { + setValue('') + } + }, [filter]) + + return ( + <> + setValue(value.value)} + value={value} + dense + /> + + ) +} diff --git a/src/components/sectionList/filters/index.ts b/src/components/sectionList/filters/index.ts new file mode 100644 index 00000000..23d1457d --- /dev/null +++ b/src/components/sectionList/filters/index.ts @@ -0,0 +1 @@ +export * from './useSectionListFilter' diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts index b4c6e843..77c88f24 100644 --- a/src/components/sectionList/filters/useSectionListFilter.ts +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -1,6 +1,6 @@ import { useCallback, useMemo, useEffect } from 'react' import { useQueryParam, ObjectParam } from 'use-query-params' -import { Schema, useSchemaFromHandle } from '../../../lib' +import { Schema, useSchemaFromHandle, CustomObjectParam } from '../../../lib' import { QueryRefetchFunction } from '../../../types' import { GistParams } from '../../../types/generated' @@ -9,9 +9,9 @@ type ObjectParamType = typeof ObjectParam.default type Filters = Record // special key for handling search for identifiable objects -// eg. searches for name, code and shortname +// eg. searches for name, code, id and shortname // this would translate to "token" in the old API, but does not exist in GIST-API -const IDENTIFIABLE_KEY = 'identifiable' +export const IDENTIFIABLE_KEY = 'identifiable' const IDENTIFIABLE_FIELDS = { name: { @@ -35,8 +35,7 @@ const getRelevantFiltersForSchema = ( if (!filters) { return {} } - /* TODO: verify values for filters - Might want to do this when parsing to queryFilter. */ + /* TODO: verify values for filters */ const relevantFilters = Object.entries(filters).filter(([key]) => { return key === IDENTIFIABLE_KEY || schema.properties[key] }) @@ -44,7 +43,7 @@ const getRelevantFiltersForSchema = ( } const useFilterQueryParam = () => { - return useQueryParam('filter', ObjectParam) + return useQueryParam('filter', CustomObjectParam) } export const useSectionListFilters = (): ReturnType< @@ -82,9 +81,7 @@ export const useSectionListFilter = ( [filterKey, setFilters] ) - return useMemo(() => { - return [filters?.[filterKey] ?? undefined, boundSetFilter] - }, [filters, filterKey, boundSetFilter]) + return [filters?.[filterKey] ?? undefined, boundSetFilter] } export type ParseToQueryFilterResult = { @@ -100,7 +97,7 @@ const parseToQueryFilter = (filters: Filters): ParseToQueryFilterResult => { // Groups are a powerful way to combine filters, // here we use them for identifiable filters, to group them with "OR" and // rest of the filters with "AND". - // Unfortunately, it doesn't work to send groups without at least two, + // Unfortunately, it doesn't work to use groups without at least two, // so we need to add them conditionally. // see https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-239/metadata-gist.html#gist_parameters_filter const identifiableFilterGroup = hasOtherFilters ? `0:` : '' diff --git a/src/components/sectionList/index.ts b/src/components/sectionList/index.ts index 3b244c5d..112e0ba2 100644 --- a/src/components/sectionList/index.ts +++ b/src/components/sectionList/index.ts @@ -2,3 +2,4 @@ export * from './SectionList' export * from './SectionListRow' export * from './SectionListWrapper' export type * from './types' +export * from './filters' diff --git a/src/constants/translatedModelConstants.ts b/src/constants/translatedModelConstants.ts index ba8dd8d1..08dd790d 100644 --- a/src/constants/translatedModelConstants.ts +++ b/src/constants/translatedModelConstants.ts @@ -64,7 +64,7 @@ export const VALUE_TYPE = { GEOJSON: i18n.t('GeoJSON'), } -export const constantPropertyTranlsations = { +export const constantPropertyTranslations = { aggregationType: AGGREGATION_TYPE, domainType: DOMAIN_TYPE, valueType: VALUE_TYPE, diff --git a/src/lib/index.ts b/src/lib/index.ts index ef513787..b57c7132 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -6,3 +6,5 @@ export * from './errors' export * from './user' export * from './useSectionHandle' export * from './sections' +export * from './useDebounce' +export * from './routeUtils' diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 3683e3ac..2e823693 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -44,6 +44,7 @@ function createGistQuery( params: ({ page, ...dynamicParams }) => ({ pageListName: 'result', total: true, + order: 'name:ASC', ...params, ...dynamicParams, page, diff --git a/src/lib/routeUtils/CustomQueryParam.ts b/src/lib/routeUtils/CustomQueryParam.ts new file mode 100644 index 00000000..df64773a --- /dev/null +++ b/src/lib/routeUtils/CustomQueryParam.ts @@ -0,0 +1,9 @@ +import { encodeObject, decodeObject, ObjectParam } from 'use-query-params' + +const entrySeparator = '~' // default is "_" which breaks constants (delimited by _) + +export const CustomObjectParam: typeof ObjectParam = { + encode: (obj) => encodeObject(obj, undefined, entrySeparator), + + decode: (str) => decodeObject(str, undefined, entrySeparator), +} diff --git a/src/lib/routeUtils/index.ts b/src/lib/routeUtils/index.ts new file mode 100644 index 00000000..af4abb36 --- /dev/null +++ b/src/lib/routeUtils/index.ts @@ -0,0 +1 @@ +export * from './CustomQueryParam' diff --git a/src/lib/schemas/index.tsx b/src/lib/schemas/index.tsx index f2b0c0cd..adb49860 100644 --- a/src/lib/schemas/index.tsx +++ b/src/lib/schemas/index.tsx @@ -1,2 +1,3 @@ export * from '../../types/schemaBase' export { useSchemas, useSchema, useSetSchemas } from './schemaStore' +export * from './useSchemaFromHandle' diff --git a/src/lib/schemas/useSchemaFromHandle.tsx b/src/lib/schemas/useSchemaFromHandle.tsx new file mode 100644 index 00000000..c5bb1a6a --- /dev/null +++ b/src/lib/schemas/useSchemaFromHandle.tsx @@ -0,0 +1,6 @@ +import { useSchema, useSchemaSectionHandleOrThrow } from '../' +export const useSchemaFromHandle = () => { + const section = useSchemaSectionHandleOrThrow() + const schema = useSchema(section.name) + return schema +} diff --git a/src/lib/useSectionHandle.ts b/src/lib/useSectionHandle.ts index 9d8d1059..4a4306b8 100644 --- a/src/lib/useSectionHandle.ts +++ b/src/lib/useSectionHandle.ts @@ -1,5 +1,5 @@ import { useMatches } from 'react-router-dom' -import { Section } from '../types' +import { SchemaSection, Section } from '../types' import { MatchRouteHandle } from './../app/routes/types' export const useSectionHandle = (): Section | undefined => { @@ -8,3 +8,14 @@ export const useSectionHandle = (): Section | undefined => { return match?.handle?.section } + +export const useSchemaSectionHandleOrThrow = (): SchemaSection => { + const matches = useMatches() as MatchRouteHandle[] + const match = matches.find((routeMatch) => routeMatch.handle?.section) + + const section = match?.handle?.section + if (!section) { + throw new Error('Could not find schema section handle') + } + return section +} diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index f3d70ab6..fbc03068 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -1,23 +1,22 @@ import i18n from '@dhis2/d2-i18n' -import React, { useEffect } from 'react' +import React from 'react' import { - SectionList, - SectionListRow, SectionListWrapper, SelectedColumns, + useSectionListFilterRefetch, } from '../../components' -import { useModelGist } from '../../lib/' import { - DataElement, - GistCollectionResponse, - GistModel, - IdentifiableObject, -} from '../../types/models' + DomainTypeSelectionFilter, + ValueTypeSelectionFilter, +} from '../../components/sectionList/filters/ConstantSelectionFilter' +import { useModelGist } from '../../lib/' +import { DataElement, GistCollectionResponse } from '../../types/models' const filterFields = [ 'access', 'id', 'name', + 'code', 'domainType', 'valueType', 'lastUpdated', @@ -44,32 +43,21 @@ export const Component = () => { 'dataElements/gist', { fields: filterFields.concat(), - order: 'name:ASC', } ) - - const [filter, setFilter] = React.useState('') - - useEffect(() => { - if (filter) { - console.log('refetch', filter) - refetch({ - filter: [`name:ilike:${filter}`, 'domainType:eq:AGGREGATE'], - }) - } else { - refetch({ filter: undefined }) - } - }, [refetch, filter]) + useSectionListFilterRefetch(refetch) return (
- setFilter(val.target.value)} - > + + + + } />
diff --git a/src/types/query.ts b/src/types/query.ts index d4b9b55d..b4f9415f 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -3,3 +3,5 @@ import type { useDataQuery } from '@dhis2/app-runtime' export type QueryResponse = ReturnType export type Query = Parameters[0] + +export type QueryRefetchFunction = QueryResponse['refetch'] From 73ce7cff106b6ff8c92a92dda9cb0c34995b1322 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 02:33:04 +0200 Subject: [PATCH 13/25] refactor: move useSectionHandle to routeUtils --- src/lib/index.ts | 2 +- src/lib/routeUtils/index.ts | 1 + src/lib/{ => routeUtils}/useSectionHandle.ts | 4 ++-- src/lib/schemas/useSchemaFromHandle.tsx | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) rename src/lib/{ => routeUtils}/useSectionHandle.ts (85%) diff --git a/src/lib/index.ts b/src/lib/index.ts index b57c7132..9ea301c0 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -4,7 +4,7 @@ export { useLoadApp } from './useLoadApp' export type { Schema } from './useLoadApp' export * from './errors' export * from './user' -export * from './useSectionHandle' +export * from './routeUtils/useSectionHandle' export * from './sections' export * from './useDebounce' export * from './routeUtils' diff --git a/src/lib/routeUtils/index.ts b/src/lib/routeUtils/index.ts index af4abb36..974d45db 100644 --- a/src/lib/routeUtils/index.ts +++ b/src/lib/routeUtils/index.ts @@ -1 +1,2 @@ export * from './CustomQueryParam' +export * from './useSectionHandle' diff --git a/src/lib/useSectionHandle.ts b/src/lib/routeUtils/useSectionHandle.ts similarity index 85% rename from src/lib/useSectionHandle.ts rename to src/lib/routeUtils/useSectionHandle.ts index 4a4306b8..8d0f447b 100644 --- a/src/lib/useSectionHandle.ts +++ b/src/lib/routeUtils/useSectionHandle.ts @@ -1,6 +1,6 @@ import { useMatches } from 'react-router-dom' -import { SchemaSection, Section } from '../types' -import { MatchRouteHandle } from './../app/routes/types' +import { MatchRouteHandle } from '../../app/routes/types' +import { SchemaSection, Section } from '../../types' export const useSectionHandle = (): Section | undefined => { const matches = useMatches() as MatchRouteHandle[] diff --git a/src/lib/schemas/useSchemaFromHandle.tsx b/src/lib/schemas/useSchemaFromHandle.tsx index c5bb1a6a..241b5e78 100644 --- a/src/lib/schemas/useSchemaFromHandle.tsx +++ b/src/lib/schemas/useSchemaFromHandle.tsx @@ -1,4 +1,5 @@ import { useSchema, useSchemaSectionHandleOrThrow } from '../' + export const useSchemaFromHandle = () => { const section = useSchemaSectionHandleOrThrow() const schema = useSchema(section.name) From a4b22920311d8129eb86dffef61a14628009444a Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 02:33:24 +0200 Subject: [PATCH 14/25] fix: fix uncontrolled input in identifiablefilter --- src/components/sectionList/filters/ConstantSelectionFilter.tsx | 2 ++ src/components/sectionList/filters/IdentifiableFilter.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/sectionList/filters/ConstantSelectionFilter.tsx b/src/components/sectionList/filters/ConstantSelectionFilter.tsx index 1e689f18..194116dd 100644 --- a/src/components/sectionList/filters/ConstantSelectionFilter.tsx +++ b/src/components/sectionList/filters/ConstantSelectionFilter.tsx @@ -1,3 +1,4 @@ +import i18n from '@dhis2/d2-i18n' import { SingleSelect, SingleSelectOption } from '@dhis2/ui' import React from 'react' import { @@ -32,6 +33,7 @@ export const ConstantSelectionFilter = ({ placeholder={label} dense filterable={filterable} + noMatchText={i18n.t('No matches')} > {Object.entries(constants).map(([key, label]) => ( diff --git a/src/components/sectionList/filters/IdentifiableFilter.tsx b/src/components/sectionList/filters/IdentifiableFilter.tsx index a5637d9a..f4aa0aad 100644 --- a/src/components/sectionList/filters/IdentifiableFilter.tsx +++ b/src/components/sectionList/filters/IdentifiableFilter.tsx @@ -8,7 +8,7 @@ import { IDENTIFIABLE_KEY, useSectionListFilter } from './useSectionListFilter' export const IdentifiableFilter = () => { const [filter, setFilter] = useSectionListFilter(IDENTIFIABLE_KEY) - const [value, setValue] = useState(filter) + const [value, setValue] = useState(filter || '') const debouncedValue = useDebounce(value, 200) useEffect(() => { From cc294703cba68ce003d778d15353bbffaf0ac8da Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 16:29:16 +0200 Subject: [PATCH 15/25] feat(list): pagination --- .../sectionList/SectionListPagination.tsx | 73 +++++++++++++++++++ .../sectionList/SectionListWrapper.tsx | 5 ++ src/components/sectionList/filters/index.ts | 2 + .../filters/useSectionListFilter.ts | 33 +++++++-- src/components/sectionList/index.ts | 1 + src/lib/models/index.ts | 1 + src/lib/models/useModelGist.ts | 33 +++++---- src/lib/useDebounce.ts | 15 ++++ src/pages/dataElements/List.tsx | 9 ++- 9 files changed, 148 insertions(+), 24 deletions(-) create mode 100644 src/components/sectionList/SectionListPagination.tsx create mode 100644 src/lib/useDebounce.ts diff --git a/src/components/sectionList/SectionListPagination.tsx b/src/components/sectionList/SectionListPagination.tsx new file mode 100644 index 00000000..a21f022e --- /dev/null +++ b/src/components/sectionList/SectionListPagination.tsx @@ -0,0 +1,73 @@ +import { Pagination, DataTableRow, DataTableCell } from '@dhis2/ui' +import React, { useEffect } from 'react' +import { + useQueryParam, + NumericObjectParam, + withDefault, +} from 'use-query-params' +import { GistPaginator } from '../../lib/' + +type SectionListPaginationProps = { + pagination: GistPaginator +} + +const defaultPaginationQueryParams = { + page: 1, + pageSize: 10, +} + +const paginationQueryParams = withDefault( + NumericObjectParam, + defaultPaginationQueryParams +) + +export const usePaginiationQueryParams = () => { + // typeof paginationQueryParams.default => { + const [params, setParams] = useQueryParam('pager', paginationQueryParams, { + removeDefaultsFromUrl: true, + }) + + return [validatePagerParams(params), setParams] as const +} + +const validatePagerParams = (params: typeof paginationQueryParams.default) => { + if (!params) { + return defaultPaginationQueryParams + } + const isValid = Object.values(params).every( + (value) => value && !isNaN(value) + ) + + return isValid ? params : defaultPaginationQueryParams +} + +export const SectionListPagination = ({ + pagination, +}: SectionListPaginationProps) => { + const [paginationParams, setPaginationParams] = usePaginiationQueryParams() + + useEffect(() => { + if (!pagination.pager) { + return + } + setPaginationParams({ + page: pagination.pager?.page, + pageSize: pagination.pager?.pageSize, + }) + }, [pagination, setPaginationParams]) + + return ( + + + + + + ) +} diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index c49cef9e..6aa7ead4 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -1,8 +1,10 @@ import React, { useMemo, useState } from 'react' +import { GistPaginator } from '../../lib/models/useModelGist' import { IdentifiableObject, GistCollectionResponse } from '../../types/models' import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' import { SectionListLoader } from './SectionListLoader' +import { SectionListPagination } from './SectionListPagination' import { SectionListRow } from './SectionListRow' import { SelectionListHeader } from './SelectionListHeaderNormal' import { SelectedColumns } from './types' @@ -12,12 +14,14 @@ type SectionListWrapperProps = { defaultColumns: SelectedColumns data?: GistCollectionResponse filterElement?: React.ReactElement + pagination: GistPaginator } export const SectionListWrapper = ({ availableColumns, defaultColumns, data, filterElement, + pagination, }: SectionListWrapperProps) => { const [selectedColumns, setSelectedColumns] = useState>(defaultColumns) @@ -75,6 +79,7 @@ export const SectionListWrapper = ({ /> )) )} +
) diff --git a/src/components/sectionList/filters/index.ts b/src/components/sectionList/filters/index.ts index 23d1457d..c55b7179 100644 --- a/src/components/sectionList/filters/index.ts +++ b/src/components/sectionList/filters/index.ts @@ -1 +1,3 @@ export * from './useSectionListFilter' +export * from './ConstantSelectionFilter' +export * from './IdentifiableFilter' diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts index 77c88f24..8dbcbdc3 100644 --- a/src/components/sectionList/filters/useSectionListFilter.ts +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -1,8 +1,13 @@ -import { useCallback, useMemo, useEffect } from 'react' +import { useCallback, useMemo, useEffect, useRef } from 'react' import { useQueryParam, ObjectParam } from 'use-query-params' -import { Schema, useSchemaFromHandle, CustomObjectParam } from '../../../lib' +import { + Schema, + useSchemaFromHandle, + CustomObjectParam, + GistParams, +} from '../../../lib' import { QueryRefetchFunction } from '../../../types' -import { GistParams } from '../../../types/generated' +import { usePaginiationQueryParams } from '../SectionListPagination' type ObjectParamType = typeof ObjectParam.default @@ -28,7 +33,7 @@ const IDENTIFIABLE_FIELDS = { }, } -const getRelevantFiltersForSchema = ( +const getVerifiedFiltersForSchema = ( filters: ObjectParamType, schema: Schema ): Filters => { @@ -53,7 +58,7 @@ export const useSectionListFilters = (): ReturnType< const schema = useSchemaFromHandle() return useMemo( - () => [getRelevantFiltersForSchema(filter, schema), setFilter], + () => [getVerifiedFiltersForSchema(filter, schema), setFilter], [filter, schema, setFilter] ) } @@ -129,11 +134,29 @@ export const useSectionListQueryFilter = () => { export const useSectionListFilterRefetch = (refetch: QueryRefetchFunction) => { const { filter, rootJunction } = useSectionListQueryFilter() + const firstRender = useRef(true) useEffect(() => { + if (firstRender.current) { + firstRender.current = false + return + } refetch({ filter, rootJunction, + page: 1, // reset to first page when filter changes }) }, [refetch, filter, rootJunction]) } + +export const useQueryParamsForModelGist = (): GistParams => { + const [paginationParams] = usePaginiationQueryParams() + const filterParams = useSectionListQueryFilter() + + return useMemo(() => { + return { + ...paginationParams, + ...filterParams, + } + }, [paginationParams, filterParams]) +} diff --git a/src/components/sectionList/index.ts b/src/components/sectionList/index.ts index 112e0ba2..f930c6b6 100644 --- a/src/components/sectionList/index.ts +++ b/src/components/sectionList/index.ts @@ -3,3 +3,4 @@ export * from './SectionListRow' export * from './SectionListWrapper' export type * from './types' export * from './filters' +export * from './SectionListPagination' diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts index 3da2fe24..0d8f640f 100644 --- a/src/lib/models/index.ts +++ b/src/lib/models/index.ts @@ -1,2 +1,3 @@ export { useModelGist } from './useModelGist' +export type { GistPaginator, GistParams } from './useModelGist' export { isValidUid } from './uid' diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 2e823693..375ed068 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -12,7 +12,7 @@ import { import { QueryResponse, Query } from '../../types/query' // these normally are just strings, but useQuery supports arrays -type GistParams = Omit & { +export type GistParams = Omit & { fields?: string | string[] filter?: string | string[] } @@ -41,13 +41,12 @@ function createGistQuery( return { result: { resource: `${resource}`, - params: ({ page, ...dynamicParams }) => ({ + params: ({ ...dynamicParams }) => ({ pageListName: 'result', total: true, order: 'name:ASC', ...params, ...dynamicParams, - page, }), }, } @@ -89,17 +88,29 @@ function usePagination( [refetch, pager] ) + const changePageSize = useCallback( + (pageSize: number) => { + refetch({ pageSize: pageSize }) + return true + }, + [refetch] + ) + return { getNextPage, getPrevPage, goToPage, + changePageSize, + pager, } } -type GistPaginator = { +export type GistPaginator = { + changePageSize: (pageSize: number) => boolean getNextPage: () => boolean getPrevPage: () => boolean goToPage: (page: number) => boolean + pager?: GistPager } type BaseUseModelGistResult = Pick< @@ -117,7 +128,7 @@ type UseModelGistResult = | BaseUseModelGistResult | UseModelGistResultPaginated -const isDataCollection = ( +export const isDataCollection = ( data: unknown ): data is GistCollectionResponse => { // gist endpoints are always paged if they're collections @@ -141,16 +152,8 @@ export function useModelGist( const [gistQuery] = useState( createGistQuery(gistResource, params) ) - // workaround to keep previous data while fetching, to prevent flickering - // "mimicks" react-query's 'keepPreviousData' - const [previousData, setPreviousData] = - useState>() const queryResponse = useDataQuery>(gistQuery) - const stickyData = queryResponse.data || previousData - if (queryResponse.data && previousData !== queryResponse.data) { - setPreviousData(queryResponse.data) - } const pagination = usePagination( queryResponse.refetch, queryResponse.data?.result @@ -161,7 +164,7 @@ export function useModelGist( loading: queryResponse.loading, called: queryResponse.called, error: queryResponse.error, - data: stickyData?.result, + data: queryResponse.data?.result, refetch: queryResponse.refetch, } if (pagination) { @@ -172,5 +175,5 @@ export function useModelGist( return result } return baseResult - }, [queryResponse, stickyData, pagination]) + }, [queryResponse, pagination]) } diff --git a/src/lib/useDebounce.ts b/src/lib/useDebounce.ts new file mode 100644 index 00000000..d7fa2567 --- /dev/null +++ b/src/lib/useDebounce.ts @@ -0,0 +1,15 @@ +import { useEffect, useState } from 'react' + +export function useDebounce(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = useState(value) + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay || 200) + + return () => { + clearTimeout(timer) + } + }, [value, delay]) + + return debouncedValue +} diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index fbc03068..e31f7061 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -4,11 +4,10 @@ import { SectionListWrapper, SelectedColumns, useSectionListFilterRefetch, -} from '../../components' -import { DomainTypeSelectionFilter, ValueTypeSelectionFilter, -} from '../../components/sectionList/filters/ConstantSelectionFilter' + useQueryParamsForModelGist, +} from '../../components' import { useModelGist } from '../../lib/' import { DataElement, GistCollectionResponse } from '../../types/models' @@ -39,10 +38,12 @@ const defaulHeaderColumns: SelectedColumns = [ ] export const Component = () => { + const initialParams = useQueryParamsForModelGist() const { refetch, data, pagination } = useModelGist( 'dataElements/gist', { fields: filterFields.concat(), + ...initialParams, } ) useSectionListFilterRefetch(refetch) @@ -58,8 +59,8 @@ export const Component = () => { } + pagination={pagination} /> - ) } From 6718da9e2285f83cf2675aa7aba2bbb472793be9 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 17:03:51 +0200 Subject: [PATCH 16/25] fix: missing files --- src/lib/index.ts | 1 - src/types/ui.ts | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/types/ui.ts diff --git a/src/lib/index.ts b/src/lib/index.ts index 9ea301c0..02caed71 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -4,7 +4,6 @@ export { useLoadApp } from './useLoadApp' export type { Schema } from './useLoadApp' export * from './errors' export * from './user' -export * from './routeUtils/useSectionHandle' export * from './sections' export * from './useDebounce' export * from './routeUtils' diff --git a/src/types/ui.ts b/src/types/ui.ts new file mode 100644 index 00000000..0d4c43cf --- /dev/null +++ b/src/types/ui.ts @@ -0,0 +1,22 @@ +// @dhis2/ui is not typed, so we need to create our own types for it. + +export type CheckBoxOnChangeObject = { + checked: boolean + name?: string + value?: string +} + +export type InputOnChangeObject = { + value: string + name: string | undefined +} + +export type InputOnChange = ( + value: InputOnChangeObject, + event: React.ChangeEvent +) => void + +export type SelectOnChangeObject = { + selected: string | undefined +} +export type SelectOnChange = (value: SelectOnChangeObject) => void From 647713b5a8e4498eaddcc8cb62a6d5429d394351 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 18:20:16 +0200 Subject: [PATCH 17/25] fix: add empty list message --- .../sectionList/SectionList.module.css | 4 ++++ .../sectionList/SectionListEmpty.tsx | 18 ++++++++++++++++++ .../sectionList/SectionListPagination.tsx | 3 +++ .../sectionList/SectionListWrapper.tsx | 3 +++ .../filters/ConstantSelectionFilter.tsx | 1 + 5 files changed, 29 insertions(+) create mode 100644 src/components/sectionList/SectionListEmpty.tsx diff --git a/src/components/sectionList/SectionList.module.css b/src/components/sectionList/SectionList.module.css index 01ca8158..0d51467c 100644 --- a/src/components/sectionList/SectionList.module.css +++ b/src/components/sectionList/SectionList.module.css @@ -30,3 +30,7 @@ .listActions button { padding: 0 2px !important; } + +.listEmpty { + text-align: center; +} diff --git a/src/components/sectionList/SectionListEmpty.tsx b/src/components/sectionList/SectionListEmpty.tsx new file mode 100644 index 00000000..e087a1c7 --- /dev/null +++ b/src/components/sectionList/SectionListEmpty.tsx @@ -0,0 +1,18 @@ +import i18n from '@dhis2/d2-i18n' +import { DataTableRow, DataTableCell } from '@dhis2/ui' +import React from 'react' +import css from './SectionList.module.css' + +export const SectionListEmpty = () => { + return ( + + +

+ {i18n.t( + "There's no items that match your selected filter." + )} +

+
+
+ ) +} diff --git a/src/components/sectionList/SectionListPagination.tsx b/src/components/sectionList/SectionListPagination.tsx index a21f022e..f8e93fc3 100644 --- a/src/components/sectionList/SectionListPagination.tsx +++ b/src/components/sectionList/SectionListPagination.tsx @@ -56,6 +56,9 @@ export const SectionListPagination = ({ }) }, [pagination, setPaginationParams]) + if (!pagination.pager?.total) { + return null + } return ( diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 6aa7ead4..e9e7f9de 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -3,6 +3,7 @@ import { GistPaginator } from '../../lib/models/useModelGist' import { IdentifiableObject, GistCollectionResponse } from '../../types/models' import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' +import { SectionListEmpty } from './SectionListEmpty' import { SectionListLoader } from './SectionListLoader' import { SectionListPagination } from './SectionListPagination' import { SectionListRow } from './SectionListRow' @@ -68,6 +69,8 @@ export const SectionListWrapper = ({ > {!data?.result ? ( + ) : data.result.length < 1 ? ( + ) : ( data?.result.map((model) => ( From 4e6e0b117dc646e4097003911ca2d2bcc516250d Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 18:29:39 +0200 Subject: [PATCH 18/25] fix: update empty text --- src/components/sectionList/SectionListEmpty.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/sectionList/SectionListEmpty.tsx b/src/components/sectionList/SectionListEmpty.tsx index e087a1c7..9c37330b 100644 --- a/src/components/sectionList/SectionListEmpty.tsx +++ b/src/components/sectionList/SectionListEmpty.tsx @@ -8,9 +8,7 @@ export const SectionListEmpty = () => {

- {i18n.t( - "There's no items that match your selected filter." - )} + {i18n.t("There aren't any items that match your filter.")}

From ec160e71869536c76aaac269513f90ad82a2cd08 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Wed, 5 Jul 2023 22:29:04 +0200 Subject: [PATCH 19/25] fix: handle errors in list request --- .../sectionList/SectionListEmpty.tsx | 16 --------- .../sectionList/SectionListMessage.tsx | 32 +++++++++++++++++ .../sectionList/SectionListWrapper.tsx | 35 ++++++++++++------- src/lib/models/index.ts | 7 +++- src/lib/models/useModelGist.ts | 4 +-- src/pages/dataElements/List.tsx | 17 ++++----- 6 files changed, 69 insertions(+), 42 deletions(-) delete mode 100644 src/components/sectionList/SectionListEmpty.tsx create mode 100644 src/components/sectionList/SectionListMessage.tsx diff --git a/src/components/sectionList/SectionListEmpty.tsx b/src/components/sectionList/SectionListEmpty.tsx deleted file mode 100644 index 9c37330b..00000000 --- a/src/components/sectionList/SectionListEmpty.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import i18n from '@dhis2/d2-i18n' -import { DataTableRow, DataTableCell } from '@dhis2/ui' -import React from 'react' -import css from './SectionList.module.css' - -export const SectionListEmpty = () => { - return ( - - -

- {i18n.t("There aren't any items that match your filter.")} -

-
-
- ) -} diff --git a/src/components/sectionList/SectionListMessage.tsx b/src/components/sectionList/SectionListMessage.tsx new file mode 100644 index 00000000..e326cf76 --- /dev/null +++ b/src/components/sectionList/SectionListMessage.tsx @@ -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 ( + + {children} + + ) +} + +export const SectionListEmpty = () => { + return ( + +

+ {i18n.t("There aren't any items that match your filter.")} +

+
+ ) +} + +export const SectionListError = () => { + return ( + + + {i18n.t('An error occurred while loading the items.')} + + + ) +} diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index e9e7f9de..1ee4d5e3 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -1,10 +1,10 @@ import React, { useMemo, useState } from 'react' -import { GistPaginator } from '../../lib/models/useModelGist' +import { UseModelGistResultPaginated } from '../../lib/models/useModelGist' import { IdentifiableObject, GistCollectionResponse } from '../../types/models' import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' -import { SectionListEmpty } from './SectionListEmpty' import { SectionListLoader } from './SectionListLoader' +import { SectionListEmpty, SectionListError } from './SectionListMessage' import { SectionListPagination } from './SectionListPagination' import { SectionListRow } from './SectionListRow' import { SelectionListHeader } from './SelectionListHeaderNormal' @@ -13,21 +13,21 @@ import { SelectedColumns } from './types' type SectionListWrapperProps = { availableColumns?: SelectedColumns defaultColumns: SelectedColumns - data?: GistCollectionResponse filterElement?: React.ReactElement - pagination: GistPaginator + gistResponse: UseModelGistResultPaginated> } export const SectionListWrapper = ({ availableColumns, defaultColumns, - data, filterElement, - pagination, + gistResponse, }: SectionListWrapperProps) => { const [selectedColumns, setSelectedColumns] = useState>(defaultColumns) const [selectedModels, setSelectedModels] = useState>(new Set()) + const { pagination, error, data } = gistResponse + const handleSelect = (id: string, checked: boolean) => { if (checked) { setSelectedModels(new Set(selectedModels).add(id)) @@ -58,6 +58,19 @@ export const SectionListWrapper = ({ ) }, [data?.result, selectedModels.size]) + const SectionListMessage = () => { + if (error) { + console.log(error.details) + return + } + if (!data?.result) { + return + } + if (data?.result?.length < 1) { + return + } + return null + } return (
{filterElement} @@ -67,11 +80,8 @@ export const SectionListWrapper = ({ onSelectAll={handleSelectAll} allSelected={allSelected} > - {!data?.result ? ( - - ) : data.result.length < 1 ? ( - - ) : ( + + {data?.result && data?.result.map((model) => ( ({ onSelect={handleSelect} selected={selectedModels.has(model.id)} /> - )) - )} + ))}
diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts index 0d8f640f..7578ef97 100644 --- a/src/lib/models/index.ts +++ b/src/lib/models/index.ts @@ -1,3 +1,8 @@ export { useModelGist } from './useModelGist' -export type { GistPaginator, GistParams } from './useModelGist' +export type { + GistPaginator, + GistParams, + UseModelGistResultPaginated, + UseModelGistResult, +} from './useModelGist' export { isValidUid } from './uid' diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 375ed068..795cfca4 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -120,11 +120,11 @@ type BaseUseModelGistResult = Pick< data?: Response } -type UseModelGistResultPaginated = +export type UseModelGistResultPaginated = BaseUseModelGistResult & { pagination: GistPaginator } -type UseModelGistResult = +export type UseModelGistResult = | BaseUseModelGistResult | UseModelGistResultPaginated diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index e31f7061..d968f109 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -39,27 +39,24 @@ const defaulHeaderColumns: SelectedColumns = [ export const Component = () => { const initialParams = useQueryParamsForModelGist() - const { refetch, data, pagination } = useModelGist( - 'dataElements/gist', - { - fields: filterFields.concat(), - ...initialParams, - } - ) - useSectionListFilterRefetch(refetch) + const gistResponse = useModelGist('dataElements/gist', { + fields: filterFields.concat(), + ...initialParams, + }) + + useSectionListFilterRefetch(gistResponse.refetch) return (
} - pagination={pagination} + gistResponse={gistResponse} />
) From c38fff9b85924857eb550b1e46aa7f46dae85c56 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 7 Jul 2023 15:37:52 +0200 Subject: [PATCH 20/25] refactor(pagination): refetch when pagination changes --- .../sectionList/SectionListPagination.tsx | 133 +++++++++++++++--- .../sectionList/SectionListWrapper.tsx | 13 +- .../filters/useSectionListFilter.ts | 54 ++++--- .../filters/useSectionListPagination.ts | 112 +++++++++++++++ src/components/sectionList/index.ts | 1 + .../useSectionListParamsRefetch.tsx | 18 +++ src/lib/models/useModelGist.ts | 15 +- src/pages/dataElements/List.tsx | 20 ++- 8 files changed, 300 insertions(+), 66 deletions(-) create mode 100644 src/components/sectionList/filters/useSectionListPagination.ts create mode 100644 src/components/sectionList/useSectionListParamsRefetch.tsx diff --git a/src/components/sectionList/SectionListPagination.tsx b/src/components/sectionList/SectionListPagination.tsx index f8e93fc3..595bd177 100644 --- a/src/components/sectionList/SectionListPagination.tsx +++ b/src/components/sectionList/SectionListPagination.tsx @@ -1,69 +1,162 @@ import { Pagination, DataTableRow, DataTableCell } from '@dhis2/ui' -import React, { useEffect } from 'react' +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 = { - pagination: GistPaginator + data: GistCollectionResponse | undefined +} + +type PaginationQueryParams = { + page: number + pageSize: number } const defaultPaginationQueryParams = { page: 1, - pageSize: 10, + pageSize: 20, } +const PAGE_SIZES = [5, 10, 20, 30, 40, 50, 75, 100] + const paginationQueryParams = withDefault( NumericObjectParam, defaultPaginationQueryParams ) -export const usePaginiationQueryParams = () => { - // typeof paginationQueryParams.default => { +export const usePaginationQueryParams = () => { const [params, setParams] = useQueryParam('pager', paginationQueryParams, { removeDefaultsFromUrl: true, }) - return [validatePagerParams(params), setParams] as const + return useMemo( + () => [validatePagerParams(params), setParams] as const, + [params, setParams] + ) } -const validatePagerParams = (params: typeof paginationQueryParams.default) => { +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 + + const validatedPageSize = PAGE_SIZES.reduce((prev, curr) => + Math.abs(curr - pageSize) < Math.abs(prev - pageSize) ? curr : prev + ) - return isValid ? params : defaultPaginationQueryParams + return { + page, + pageSize: validatedPageSize, + } } -export const SectionListPagination = ({ - pagination, -}: SectionListPaginationProps) => { - const [paginationParams, setPaginationParams] = usePaginiationQueryParams() +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(() => { - if (!pagination.pager) { - return + // 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) } - setPaginationParams({ - page: pagination.pager?.page, - pageSize: pagination.pager?.pageSize, - }) - }, [pagination, setPaginationParams]) + }, [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 ( s.toString())} + page={page} pageSize={paginationParams.pageSize} pageCount={pagination.pager?.pageCount} total={pagination.pager?.total} diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 1ee4d5e3..65dca8d4 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -1,5 +1,5 @@ +import { FetchError } from '@dhis2/app-runtime' import React, { useMemo, useState } from 'react' -import { UseModelGistResultPaginated } from '../../lib/models/useModelGist' import { IdentifiableObject, GistCollectionResponse } from '../../types/models' import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' @@ -14,20 +14,20 @@ type SectionListWrapperProps = { availableColumns?: SelectedColumns defaultColumns: SelectedColumns filterElement?: React.ReactElement - gistResponse: UseModelGistResultPaginated> + data: GistCollectionResponse | undefined + error: FetchError | undefined } export const SectionListWrapper = ({ availableColumns, defaultColumns, filterElement, - gistResponse, + data, + error, }: SectionListWrapperProps) => { const [selectedColumns, setSelectedColumns] = useState>(defaultColumns) const [selectedModels, setSelectedModels] = useState>(new Set()) - const { pagination, error, data } = gistResponse - const handleSelect = (id: string, checked: boolean) => { if (checked) { setSelectedModels(new Set(selectedModels).add(id)) @@ -71,6 +71,7 @@ export const SectionListWrapper = ({ } return null } + return (
{filterElement} @@ -91,7 +92,7 @@ export const SectionListWrapper = ({ selected={selectedModels.has(model.id)} /> ))} - +
) diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts index 8dbcbdc3..85ced4e1 100644 --- a/src/components/sectionList/filters/useSectionListFilter.ts +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -1,13 +1,12 @@ -import { useCallback, useMemo, useEffect, useRef } from 'react' -import { useQueryParam, ObjectParam } from 'use-query-params' +import { useCallback, useMemo } from 'react' +import { useQueryParam, ObjectParam, UrlUpdateType } from 'use-query-params' import { Schema, useSchemaFromHandle, CustomObjectParam, GistParams, } from '../../../lib' -import { QueryRefetchFunction } from '../../../types' -import { usePaginiationQueryParams } from '../SectionListPagination' +import { usePaginationQueryParams } from '../SectionListPagination' type ObjectParamType = typeof ObjectParam.default @@ -51,14 +50,28 @@ const useFilterQueryParam = () => { return useQueryParam('filter', CustomObjectParam) } -export const useSectionListFilters = (): ReturnType< - typeof useQueryParam -> => { - const [filter, setFilter] = useFilterQueryParam() +export const useSectionListFilters = () => { + const [filter, setFilterParam] = useFilterQueryParam() + const [, setPagingParam] = usePaginationQueryParams() + const schema = useSchemaFromHandle() + // override setFilter to be able to reset Page when filter changes + const setFilter = useCallback( + ( + filter: Parameters[0], + updateType?: UrlUpdateType + ) => { + setFilterParam(filter, updateType) + // set page to 1 when filter changes + // do this here instead of useEffect to prevent unnecessary refetches + setPagingParam((pagingParams) => ({ ...pagingParams, page: 1 })) + }, + [setFilterParam, setPagingParam] + ) + return useMemo( - () => [getVerifiedFiltersForSchema(filter, schema), setFilter], + () => [getVerifiedFiltersForSchema(filter, schema), setFilter] as const, [filter, schema, setFilter] ) } @@ -94,7 +107,7 @@ export type ParseToQueryFilterResult = { rootJunction: GistParams['rootJunction'] } -const parseToQueryFilter = (filters: Filters): ParseToQueryFilterResult => { +const parseToGistQueryFilter = (filters: Filters): ParseToQueryFilterResult => { const { [IDENTIFIABLE_KEY]: identifiableValue, ...restFilters } = filters const queryFilters: string[] = [] @@ -128,29 +141,12 @@ export const useSectionListQueryFilter = () => { const [filters] = useSectionListFilters() return useMemo(() => { - return parseToQueryFilter(filters) + return parseToGistQueryFilter(filters) }, [filters]) } -export const useSectionListFilterRefetch = (refetch: QueryRefetchFunction) => { - const { filter, rootJunction } = useSectionListQueryFilter() - const firstRender = useRef(true) - - useEffect(() => { - if (firstRender.current) { - firstRender.current = false - return - } - refetch({ - filter, - rootJunction, - page: 1, // reset to first page when filter changes - }) - }, [refetch, filter, rootJunction]) -} - export const useQueryParamsForModelGist = (): GistParams => { - const [paginationParams] = usePaginiationQueryParams() + const [paginationParams] = usePaginationQueryParams() const filterParams = useSectionListQueryFilter() return useMemo(() => { diff --git a/src/components/sectionList/filters/useSectionListPagination.ts b/src/components/sectionList/filters/useSectionListPagination.ts new file mode 100644 index 00000000..2ec437ff --- /dev/null +++ b/src/components/sectionList/filters/useSectionListPagination.ts @@ -0,0 +1,112 @@ +import { useCallback, useMemo } from 'react' +import { + useQueryParam, + NumericObjectParam, + withDefault, +} from 'use-query-params' +import { GistPaginator } from '../../../lib/' +import { GistCollectionResponse } from '../../../types/generated' + +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 + + const validatedPageSize = PAGE_SIZES.reduce((prev, curr) => + Math.abs(curr - pageSize) < Math.abs(prev - pageSize) ? curr : prev + ) + + return { + page, + pageSize: validatedPageSize, + } +} + +export const 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, + } +} diff --git a/src/components/sectionList/index.ts b/src/components/sectionList/index.ts index f930c6b6..74644164 100644 --- a/src/components/sectionList/index.ts +++ b/src/components/sectionList/index.ts @@ -4,3 +4,4 @@ export * from './SectionListWrapper' export type * from './types' export * from './filters' export * from './SectionListPagination' +export * from './useSectionListParamsRefetch' diff --git a/src/components/sectionList/useSectionListParamsRefetch.tsx b/src/components/sectionList/useSectionListParamsRefetch.tsx new file mode 100644 index 00000000..84e26014 --- /dev/null +++ b/src/components/sectionList/useSectionListParamsRefetch.tsx @@ -0,0 +1,18 @@ +import { useEffect } from 'react' +import { QueryRefetchFunction } from '../../types' +import { useSectionListQueryFilter } from './filters' +import { usePaginationQueryParams } from './SectionListPagination' + +/** Refetches when filter and pagination params change */ +export const useSectionListParamsRefetch = (refetch: QueryRefetchFunction) => { + const { filter, rootJunction } = useSectionListQueryFilter() + const [paginationParams] = usePaginationQueryParams() + + useEffect(() => { + refetch({ + ...paginationParams, + filter, + rootJunction, + }) + }, [refetch, paginationParams, filter, rootJunction]) +} diff --git a/src/lib/models/useModelGist.ts b/src/lib/models/useModelGist.ts index 795cfca4..3916b264 100644 --- a/src/lib/models/useModelGist.ts +++ b/src/lib/models/useModelGist.ts @@ -135,24 +135,31 @@ export const isDataCollection = ( return (data as GistCollectionResponse)?.pager !== undefined } +type UseDataQueryOptions = Parameters[1] export function useModelGist( gistResource: GistResourceString, - params?: GistParams + params?: GistParams, + dataQueryOptions?: UseDataQueryOptions ): UseModelGistResultPaginated export function useModelGist( gistResource: GistResourceString, - params?: GistParams + params?: GistParams, + dataQueryOptions?: UseDataQueryOptions ): UseModelGistResult export function useModelGist( gistResource: GistResourceString, - params?: GistParams + params?: GistParams, + dataQueryOptions?: Parameters[1] ): UseModelGistResult { const [gistQuery] = useState( createGistQuery(gistResource, params) ) - const queryResponse = useDataQuery>(gistQuery) + const queryResponse = useDataQuery>( + gistQuery, + dataQueryOptions + ) const pagination = usePagination( queryResponse.refetch, diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index d968f109..4a70ac07 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -3,10 +3,10 @@ import React from 'react' import { SectionListWrapper, SelectedColumns, - useSectionListFilterRefetch, DomainTypeSelectionFilter, ValueTypeSelectionFilter, useQueryParamsForModelGist, + useSectionListParamsRefetch, } from '../../components' import { useModelGist } from '../../lib/' import { DataElement, GistCollectionResponse } from '../../types/models' @@ -39,12 +39,17 @@ const defaulHeaderColumns: SelectedColumns = [ export const Component = () => { const initialParams = useQueryParamsForModelGist() - const gistResponse = useModelGist('dataElements/gist', { - fields: filterFields.concat(), - ...initialParams, - }) + const { refetch, error, data } = useModelGist( + 'dataElements/gist', + { + fields: filterFields.concat(), + ...initialParams, + }, + // refetched on mount by useSectionListParamsRefetch below + { lazy: true } + ) - useSectionListFilterRefetch(gistResponse.refetch) + useSectionListParamsRefetch(refetch) return (
@@ -56,7 +61,8 @@ export const Component = () => { } - gistResponse={gistResponse} + error={error} + data={data} />
) From 92c1648d5253c741ce13c868bd269589d3077f4d Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 7 Jul 2023 15:52:43 +0200 Subject: [PATCH 21/25] fix: review --- ...istMessage.tsx => SectionListMessages.tsx} | 0 .../sectionList/SectionListWrapper.tsx | 2 +- .../sectionList/filters/ConstantFilters.tsx | 27 +++++++++++++++++++ .../filters/ConstantSelectionFilter.tsx | 25 ----------------- src/components/sectionList/filters/index.ts | 1 + src/components/sectionList/types.ts | 6 +++-- 6 files changed, 33 insertions(+), 28 deletions(-) rename src/components/sectionList/{SectionListMessage.tsx => SectionListMessages.tsx} (100%) create mode 100644 src/components/sectionList/filters/ConstantFilters.tsx diff --git a/src/components/sectionList/SectionListMessage.tsx b/src/components/sectionList/SectionListMessages.tsx similarity index 100% rename from src/components/sectionList/SectionListMessage.tsx rename to src/components/sectionList/SectionListMessages.tsx diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 65dca8d4..0bc1e4a2 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -4,7 +4,7 @@ import { IdentifiableObject, GistCollectionResponse } from '../../types/models' import { FilterWrapper } from './filters/FilterWrapper' import { SectionList } from './SectionList' import { SectionListLoader } from './SectionListLoader' -import { SectionListEmpty, SectionListError } from './SectionListMessage' +import { SectionListEmpty, SectionListError } from './SectionListMessages' import { SectionListPagination } from './SectionListPagination' import { SectionListRow } from './SectionListRow' import { SelectionListHeader } from './SelectionListHeaderNormal' diff --git a/src/components/sectionList/filters/ConstantFilters.tsx b/src/components/sectionList/filters/ConstantFilters.tsx new file mode 100644 index 00000000..4dacfef1 --- /dev/null +++ b/src/components/sectionList/filters/ConstantFilters.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { + DOMAIN_TYPE, + VALUE_TYPE, +} from '../../../constants/translatedModelConstants' +import { ConstantSelectionFilter } from './ConstantSelectionFilter' + +export const DomainTypeSelectionFilter = () => { + return ( + + ) +} + +export const ValueTypeSelectionFilter = () => { + return ( + + ) +} diff --git a/src/components/sectionList/filters/ConstantSelectionFilter.tsx b/src/components/sectionList/filters/ConstantSelectionFilter.tsx index 8ade0b6c..50b3bde1 100644 --- a/src/components/sectionList/filters/ConstantSelectionFilter.tsx +++ b/src/components/sectionList/filters/ConstantSelectionFilter.tsx @@ -1,10 +1,6 @@ import i18n from '@dhis2/d2-i18n' import { SingleSelect, SingleSelectOption } from '@dhis2/ui' import React from 'react' -import { - DOMAIN_TYPE, - VALUE_TYPE, -} from '../../../constants/translatedModelConstants' import { SelectOnChangeObject } from '../../../types' import css from './Filters.module.css' import { useSectionListFilter } from './useSectionListFilter' @@ -43,24 +39,3 @@ export const ConstantSelectionFilter = ({ ) } - -export const DomainTypeSelectionFilter = () => { - return ( - - ) -} - -export const ValueTypeSelectionFilter = () => { - return ( - - ) -} diff --git a/src/components/sectionList/filters/index.ts b/src/components/sectionList/filters/index.ts index c55b7179..01def766 100644 --- a/src/components/sectionList/filters/index.ts +++ b/src/components/sectionList/filters/index.ts @@ -1,3 +1,4 @@ export * from './useSectionListFilter' export * from './ConstantSelectionFilter' export * from './IdentifiableFilter' +export * from './ConstantFilters' diff --git a/src/components/sectionList/types.ts b/src/components/sectionList/types.ts index 046d16f9..d714f9b2 100644 --- a/src/components/sectionList/types.ts +++ b/src/components/sectionList/types.ts @@ -5,8 +5,10 @@ export type SelectedColumn = { modelPropertyName: keyof Model } -export type SelectedColumns = - SelectedColumn[] +export type SelectedColumns< + Model extends IdentifiableObject = IdentifiableObject +> = SelectedColumn[] + export type CheckBoxOnChangeObject = { checked: boolean name?: string From 3b9d811588bc70b9b1b765427758bdf86262f63c Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 7 Jul 2023 15:59:45 +0200 Subject: [PATCH 22/25] docs: add comment --- .../sectionList/SectionListPagination.tsx | 1 + .../filters/useSectionListFilter.ts | 3 + .../filters/useSectionListPagination.ts | 112 ------------------ 3 files changed, 4 insertions(+), 112 deletions(-) delete mode 100644 src/components/sectionList/filters/useSectionListPagination.ts diff --git a/src/components/sectionList/SectionListPagination.tsx b/src/components/sectionList/SectionListPagination.tsx index 595bd177..59a99ba3 100644 --- a/src/components/sectionList/SectionListPagination.tsx +++ b/src/components/sectionList/SectionListPagination.tsx @@ -56,6 +56,7 @@ const validatePagerParams = ( 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 ) diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts index 85ced4e1..3c48471c 100644 --- a/src/components/sectionList/filters/useSectionListFilter.ts +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -76,6 +76,9 @@ export const useSectionListFilters = () => { ) } +/** Helper-hook to select a single filter. + * eg. [domainType, setDomainType] = useSectionListFilter('domainType') + */ export const useSectionListFilter = ( filterKey: string ): [string | undefined, (value: string | undefined) => void] => { diff --git a/src/components/sectionList/filters/useSectionListPagination.ts b/src/components/sectionList/filters/useSectionListPagination.ts deleted file mode 100644 index 2ec437ff..00000000 --- a/src/components/sectionList/filters/useSectionListPagination.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { useCallback, useMemo } from 'react' -import { - useQueryParam, - NumericObjectParam, - withDefault, -} from 'use-query-params' -import { GistPaginator } from '../../../lib/' -import { GistCollectionResponse } from '../../../types/generated' - -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 - - const validatedPageSize = PAGE_SIZES.reduce((prev, curr) => - Math.abs(curr - pageSize) < Math.abs(prev - pageSize) ? curr : prev - ) - - return { - page, - pageSize: validatedPageSize, - } -} - -export const 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, - } -} From 0522f815c381cad9c3100d9393de51cb610e409e Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 7 Jul 2023 23:26:30 +0200 Subject: [PATCH 23/25] fix: use spacers for styling --- src/components/sectionList/SectionList.module.css | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/sectionList/SectionList.module.css b/src/components/sectionList/SectionList.module.css index 0d51467c..1bead8ec 100644 --- a/src/components/sectionList/SectionList.module.css +++ b/src/components/sectionList/SectionList.module.css @@ -6,25 +6,22 @@ align-items: center; height: 48px; padding: var(--spacers-dp8); - margin-top: 8px; + margin-top: var(--spacers-dp8); gap: var(--spacers-dp8); } .listHeaderNormal a { - line-height: 16px; -} - -.listRow { + line-height: var(--spacers-dp16); } .listRow td { - padding-top: 8px; - padding-bottom: 8px; + padding-top: var(--spacers-dp8); + padding-bottom: var(--spacers-dp8); } .listActions { display: flex; - gap: 8px; + gap: var(--spacers-dp8); } .listActions button { From ab00bfd112044a701eb0c3da18413d36a0b2d985 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Sun, 9 Jul 2023 16:39:20 +0200 Subject: [PATCH 24/25] test: fix test - change import --- src/lib/schemas/useSchemaFromHandle.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/schemas/useSchemaFromHandle.tsx b/src/lib/schemas/useSchemaFromHandle.tsx index 241b5e78..0010ba1b 100644 --- a/src/lib/schemas/useSchemaFromHandle.tsx +++ b/src/lib/schemas/useSchemaFromHandle.tsx @@ -1,4 +1,5 @@ -import { useSchema, useSchemaSectionHandleOrThrow } from '../' +import { useSchemaSectionHandleOrThrow } from '../routeUtils' +import { useSchema } from './schemaStore' export const useSchemaFromHandle = () => { const section = useSchemaSectionHandleOrThrow() From c4c507f46c9d1129a588ffca88c49876bda76ff8 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Sun, 9 Jul 2023 16:48:42 +0200 Subject: [PATCH 25/25] fix: update and simplify filter logic --- .../sectionList/SectionListWrapper.tsx | 2 +- .../filters/useSectionListFilter.ts | 28 +++++++------------ .../useSectionListParamsRefetch.tsx | 12 +++----- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 0bc1e4a2..a56aeb40 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -60,7 +60,7 @@ export const SectionListWrapper = ({ const SectionListMessage = () => { if (error) { - console.log(error.details) + console.log(error.details || error) return } if (!data?.result) { diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts index 3c48471c..535e9e98 100644 --- a/src/components/sectionList/filters/useSectionListFilter.ts +++ b/src/components/sectionList/filters/useSectionListFilter.ts @@ -105,39 +105,31 @@ export const useSectionListFilter = ( return [filters?.[filterKey] ?? undefined, boundSetFilter] } -export type ParseToQueryFilterResult = { - filter: string[] - rootJunction: GistParams['rootJunction'] -} - -const parseToGistQueryFilter = (filters: Filters): ParseToQueryFilterResult => { +const parseToGistQueryFilter = (filters: Filters): string[] => { const { [IDENTIFIABLE_KEY]: identifiableValue, ...restFilters } = filters const queryFilters: string[] = [] - const hasOtherFilters = Object.keys(restFilters).length > 0 // Groups are a powerful way to combine filters, // here we use them for identifiable filters, to group them with "OR" and // rest of the filters with "AND". - // Unfortunately, it doesn't work to use groups without at least two, - // so we need to add them conditionally. // see https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-239/metadata-gist.html#gist_parameters_filter - const identifiableFilterGroup = hasOtherFilters ? `0:` : '' if (identifiableValue) { + const identifiableFilterGroup = `0:` Object.entries(IDENTIFIABLE_FIELDS).forEach(([key, { operator }]) => { queryFilters.push( `${identifiableFilterGroup}${key}:${operator}:${identifiableValue}` ) }) } - const restFilterGroup = identifiableValue ? `1:` : '' + let restFilterGroup: number | undefined + if (identifiableValue) { + restFilterGroup = 1 + } Object.entries(restFilters).forEach(([key, value]) => { - queryFilters.push(`${restFilterGroup}${key}:eq:${value}`) + const group = restFilterGroup ? `${restFilterGroup++}:` : '' + queryFilters.push(`${group}${key}:eq:${value}`) }) - // when there are no other filters than identifiable, we can't group them - // and thus we need to set rootJunction to OR - const rootJunction = - identifiableValue && !hasOtherFilters ? 'OR' : undefined - return { filter: queryFilters, rootJunction } + return queryFilters } export const useSectionListQueryFilter = () => { @@ -155,7 +147,7 @@ export const useQueryParamsForModelGist = (): GistParams => { return useMemo(() => { return { ...paginationParams, - ...filterParams, + filter: filterParams, } }, [paginationParams, filterParams]) } diff --git a/src/components/sectionList/useSectionListParamsRefetch.tsx b/src/components/sectionList/useSectionListParamsRefetch.tsx index 84e26014..77bd5e00 100644 --- a/src/components/sectionList/useSectionListParamsRefetch.tsx +++ b/src/components/sectionList/useSectionListParamsRefetch.tsx @@ -1,18 +1,14 @@ import { useEffect } from 'react' import { QueryRefetchFunction } from '../../types' -import { useSectionListQueryFilter } from './filters' -import { usePaginationQueryParams } from './SectionListPagination' +import { useQueryParamsForModelGist } from './filters' /** Refetches when filter and pagination params change */ export const useSectionListParamsRefetch = (refetch: QueryRefetchFunction) => { - const { filter, rootJunction } = useSectionListQueryFilter() - const [paginationParams] = usePaginationQueryParams() + const params = useQueryParamsForModelGist() useEffect(() => { refetch({ - ...paginationParams, - filter, - rootJunction, + ...params, }) - }, [refetch, paginationParams, filter, rootJunction]) + }, [refetch, params]) }