Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: data set required and setup fields #474

Merged
merged 6 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ function createSectionLazyRouteFunction(
return async () => {
try {
return await import(
`../../pages/${section.namePlural}/${componentFileName}`
`../../pages/${
section.routeName || section.namePlural
}/${componentFileName}`
)
} catch (e) {
// means the component is not implemented yet
Expand Down
33 changes: 20 additions & 13 deletions src/components/SearchableSingleSelect/SearchableSingleSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type OnChange = ({ selected }: { selected: string }) => void
type OnFilterChange = ({ value }: { value: string }) => void
export interface SearchableSingleSelectPropTypes {
onChange: OnChange
onFilterChange: OnFilterChange
onFilterChange?: OnFilterChange
onEndReached?: () => void
onRetryClick: () => void
dense?: boolean
Expand All @@ -65,6 +65,7 @@ export interface SearchableSingleSelectPropTypes {
showAllOption?: boolean
onBlur?: () => void
onFocus?: () => void
searchable?: boolean
}

export const SearchableSingleSelect = ({
Expand All @@ -85,13 +86,15 @@ export const SearchableSingleSelect = ({
selected,
showAllOption,
showEndLoader,
searchable = true,
}: SearchableSingleSelectPropTypes) => {
const [loadingSpinnerRef, setLoadingSpinnerRef] = useState<HTMLElement>()

const { liveValue: filter, setValue: setFilterValue } =
useDebouncedState<string>({
initialValue: '',
onSetDebouncedValue: (value: string) => onFilterChange({ value }),
onSetDebouncedValue: (value: string) =>
onFilterChange && onFilterChange({ value }),
})

useEffect(() => {
Expand Down Expand Up @@ -137,18 +140,22 @@ export const SearchableSingleSelect = ({
onFocus={onFocus}
dense={dense}
>
<div className={classes.searchField}>
<div className={classes.searchInput}>
<Input
dense
initialFocus
value={filter}
onChange={({ value }) => setFilterValue(value ?? '')}
placeholder={i18n.t('Filter options')}
type="search"
/>
{searchable && (
<div className={classes.searchField}>
<div className={classes.searchInput}>
<Input
dense
initialFocus
value={filter}
onChange={({ value }) =>
setFilterValue(value ?? '')
}
placeholder={i18n.t('Filter options')}
type="search"
/>
</div>
</div>
</div>
)}

{withAllOptions.map(({ value, label }) => (
<SingleSelectOption key={value} value={value} label={label} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import i18n from '@dhis2/d2-i18n'
import React, { forwardRef } from 'react'
import { ModelSingleSelectLegacy } from '../ModelSingleSelect'
import type { ModelSingleSelectLegacyProps } from '../ModelSingleSelect'
import { useInitialOptionQuery } from './useInitialOptionQuery'
import { useOptionsQuery } from './useOptionsQuery'
import { useCategoryCombosQuery } from './useCategoryCombosQuery'
import { useInitialCategoryComboQuery } from './useInitialCategoryComboQuery'

type CategoryComboSelectProps = Omit<
ModelSingleSelectLegacyProps,
Expand All @@ -30,8 +30,8 @@ export const CategoryComboSelect = forwardRef(function CategoryComboSelect(
required={required}
invalid={invalid}
disabled={disabled}
useInitialOptionQuery={useInitialOptionQuery}
useOptionsQuery={useOptionsQuery}
useInitialOptionQuery={useInitialCategoryComboQuery}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are just renaming

useOptionsQuery={useCategoryCombosQuery}
placeholder={placeholder}
showAllOption={showAllOption}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const DEFAULT_CATEGORY_SELECT_OPTION = {
label: DEFAULT_CATEGORY_COMBO.displayName,
}

export function useOptionsQuery() {
export function useCategoryCombosQuery() {
const [loadedOptions, setLoadedOptions] = useState<SelectOption[]>([])
// The gist doesn't include the `isDefault` value, need to use `useDataQuery`
const queryResult = useDataQuery<CategoryComboQueryResult>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type InitialCategoryComboQueryResult = {
categoryCombo: FilteredCategoryCombo
}

const INITIAL_OPTION_QUERY = {
const INITIAL_CATEGORY_COMBO_QUERY = {
categoryCombo: {
resource: 'categoryCombos',
id: (variables: Record<string, string>) => variables.id,
Expand All @@ -18,23 +18,26 @@ const INITIAL_OPTION_QUERY = {
},
}

export function useInitialOptionQuery({
export function useInitialCategoryComboQuery({
selected,
onComplete,
}: {
onComplete: (option: SelectOption) => void
selected?: string
}) {
const initialSelected = useRef(selected)
return useDataQuery<InitialCategoryComboQueryResult>(INITIAL_OPTION_QUERY, {
lazy:
!initialSelected.current ||
initialSelected.current === DEFAULT_CATEGORY_COMBO.id,
variables: { id: selected },
onComplete: (data) => {
const categoryCombo = data.categoryCombo
const { id: value, displayName: label } = categoryCombo
onComplete({ value, label })
},
})
return useDataQuery<InitialCategoryComboQueryResult>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again only renaming in this file

INITIAL_CATEGORY_COMBO_QUERY,
{
lazy:
!initialSelected.current ||
initialSelected.current === DEFAULT_CATEGORY_COMBO.id,
variables: { id: selected },
onComplete: (data) => {
const categoryCombo = data.categoryCombo
const { id: value, displayName: label } = categoryCombo
onComplete({ value, label })
},
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const BaseModelSingleSelect = <
const { allModelsMap, allSingleSelectOptions } = useMemo(() => {
const allModelsMap = new Map(available.map((o) => [o.id, o]))
// due to pagination, the selected model might not be in the available list, so add it
if (selected && !allModelsMap.get(selected.id)) {
if (selected && selected.id && !allModelsMap.get(selected.id)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can the selected be truthy but not contain an id with the types here? This is more protective and it can stay, but it shouldn't really happen with correct typing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i added it cause i messed everything up by sending selected=undefined. But you are right, prob if i fixed teh types before that it would not have happened

allModelsMap.set(selected.id, selected)
}
const allSingleSelectOptions = Array.from(allModelsMap).map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,14 @@ export const ModelSingleSelect = <
queryFn: queryFn<Response<TModel>>,
keepPreviousData: true,
getNextPageParam: (lastPage) =>
lastPage.pager.nextPage ? lastPage.pager.page + 1 : undefined,
lastPage.pager?.nextPage ? lastPage.pager.page + 1 : undefined,
getPreviousPageParam: (firstPage) =>
firstPage.pager.prevPage ? firstPage.pager.page - 1 : undefined,
firstPage.pager?.prevPage ? firstPage.pager.page - 1 : undefined,
staleTime: 60 * 1000,
})

const shouldFetchSelected = !!selected && selected.displayName === undefined
const shouldFetchSelected =
!!selected && selected.displayName === undefined && !!selected.id
// if we just have the ID - fetch the displayName
const selectedQuery = useQuery({
queryKey: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function ModelSingleSelectField<TModel extends DisplayableModel>({
input.onBlur()
onChange?.(selected)
}}
invalid={meta.touched && !!meta.error}
/>
</Field>
)
Expand Down
3 changes: 2 additions & 1 deletion src/lib/constants/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export const SCHEMA_SECTIONS = {
// @ts-expect-error temporary route for testing
dataSetWIP: {
name: SchemaName.dataSet,
namePlural: 'dataSetsWip',
routeName: 'dataSetsWip',
namePlural: 'dataSets',
title: i18n.t('Data set'),
titlePlural: i18n.t('Data sets'),
parentSectionKey: 'dataSet',
Expand Down
24 changes: 24 additions & 0 deletions src/lib/constants/translatedModelConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,37 @@ export const FORM_TYPE = {
SECTION_MULTIORG: i18n.t('Section Multi-org'),
}

const PERIOD_TYPE = {
BiMonthly: i18n.t('BiMonthly'),
BiWeekly: i18n.t('BiWeekly'),
Daily: i18n.t('Daily'),
FinancialApril: i18n.t('FinancialApril'),
FinancialJuly: i18n.t('FinancialJuly'),
FinancialNov: i18n.t('FinancialNov'),
FinancialOct: i18n.t('FinancialOct'),
Monthly: i18n.t('Monthly'),
Quarterly: i18n.t('Quarterly'),
QuarterlyNov: i18n.t('QuarterlyNov'),
SixMonthlyApril: i18n.t('SixMonthlyApril'),
SixMonthlyNov: i18n.t('SixMonthlyNov'),
SixMonthly: i18n.t('SixMonthly'),
TwoYearly: i18n.t('TwoYearly'),
Weekly: i18n.t('Weekly'),
WeeklySaturday: i18n.t('WeeklySaturday'),
WeeklySunday: i18n.t('WeeklySunday'),
WeeklyThursday: i18n.t('WeeklyThursday'),
WeeklyWednesday: i18n.t('WeeklyWednesday'),
Yearly: i18n.t('Yearly'),
}

const allConstantTranslations: Record<string, string> = {
...AGGREGATION_TYPE,
...DOMAIN_TYPE,
...VALUE_TYPE,
...DATA_DIMENSION_TYPE,
...GEOMETRY_TYPE,
...FORM_TYPE,
...PERIOD_TYPE,
}

export const getConstantTranslation = (constant: string): string => {
Expand Down
17 changes: 7 additions & 10 deletions src/lib/optionSet/useOptionSetsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type OptionSetQueryResult = {
}
}

const CATEGORY_COMBOS_QUERY = {
const OPTION_SETS_QUERY = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙃

optionSets: {
resource: 'optionSets',
params: (variables: Record<string, string>) => {
Expand All @@ -35,15 +35,12 @@ const CATEGORY_COMBOS_QUERY = {

export function useOptionSetsQuery() {
const [loadedOptions, setLoadedOptions] = useState<SelectOption[]>([])
const queryResult = useDataQuery<OptionSetQueryResult>(
CATEGORY_COMBOS_QUERY,
{
variables: {
page: 1,
filter: '',
},
}
)
const queryResult = useDataQuery<OptionSetQueryResult>(OPTION_SETS_QUERY, {
variables: {
page: 1,
filter: '',
},
})
const { data } = queryResult

// Must be done in `useEffect` and not in `onComplete`, as `onComplete`
Expand Down
2 changes: 1 addition & 1 deletion src/lib/routeUtils/routePaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getSectionPath = (section: Section | string) => {
if (typeof section === 'string') {
return section
}
return section.namePlural
return section.routeName || section.namePlural
}

export const getSectionNewPath = (section: Section | string) => {
Expand Down
33 changes: 33 additions & 0 deletions src/pages/dataSetsWip/form/CategoryComboField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import i18n from '@dhis2/d2-i18n'
import React from 'react'
import { ModelSingleSelectField } from '../../../components/metadataFormControls/ModelSingleSelect'
import { DEFAULT_CATEGORY_COMBO } from '../../../lib'

const CATEGORY_COMBOS_QUERY = {
resource: 'categoryCombos',
params: {
filter: ['dataDimensionType:eq:ATTRIBUTE'],
},
}

const DEFAULT_CATEGORY_SELECT_OPTION = {
id: DEFAULT_CATEGORY_COMBO.id,
displayName: DEFAULT_CATEGORY_COMBO.displayName,
}

export function CategoryComboField() {
return (
<ModelSingleSelectField
required
name="categoryCombo"
label={i18n.t('{{fieldLabel}} (required)', {
fieldLabel: i18n.t('Category combination'),
})}
query={CATEGORY_COMBOS_QUERY}
transform={(catCombos) => [
DEFAULT_CATEGORY_SELECT_OPTION,
...catCombos,
]}
/>
)
}
17 changes: 15 additions & 2 deletions src/pages/dataSetsWip/form/DataSetFormContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ import {
import {
SECTIONS_MAP,
useSectionedFormContext,
useSelectedSection,
useSyncSelectedSectionWithScroll,
} from '../../../lib'
import { ColorAndIconField } from '../../dataElements/fields'
import { CategoryComboField } from './CategoryComboField'
import { DataSetFormDescriptor } from './formDescriptor'
import { PeriodTypeField } from './PeriodTypeField'

const section = SECTIONS_MAP.dataSet

export const DataSetFormContents = () => {
const descriptor = useSectionedFormContext<typeof DataSetFormDescriptor>()
useSyncSelectedSectionWithScroll()
const [selectedSection] = useSelectedSection()
return (
<>
<SectionedFormSections>
Expand All @@ -42,6 +43,7 @@ export const DataSetFormContents = () => {
</StandardFormSectionDescription>
<DefaultIdentifiableFields />
<DescriptionField schemaSection={section} />
<ColorAndIconField />
</SectionedFormSection>
<SectionedFormSection name={descriptor.getSection('data').name}>
<StandardFormSectionTitle>
Expand All @@ -60,6 +62,16 @@ export const DataSetFormContents = () => {
}}
/>
</StandardFormField>
<div style={{ height: 24 }} />
<StandardFormSectionTitle>
{i18n.t('Data set disaggregation')}
</StandardFormSectionTitle>
<StandardFormSectionDescription>
{i18n.t(
'Choose an optional category combination to disaggregate the entire data set.'
)}
</StandardFormSectionDescription>
<CategoryComboField />
</SectionedFormSection>
<SectionedFormSection
name={descriptor.getSection('periods').name}
Expand All @@ -72,6 +84,7 @@ export const DataSetFormContents = () => {
'Choose for what time periods data can be entered for this data set'
)}
</StandardFormSectionDescription>
<PeriodTypeField />
</SectionedFormSection>
<SectionedFormSection
name={descriptor.getSection('validation').name}
Expand Down
Loading
Loading