diff --git a/i18n/en.pot b/i18n/en.pot
index 07aee14b3..327c69ed9 100644
--- a/i18n/en.pot
+++ b/i18n/en.pot
@@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-"POT-Creation-Date: 2024-11-04T20:16:04.917Z\n"
-"PO-Revision-Date: 2024-11-04T20:16:04.918Z\n"
+"POT-Creation-Date: 2024-11-06T18:10:35.150Z\n"
+"PO-Revision-Date: 2024-11-06T18:10:35.150Z\n"
msgid "schemas"
msgstr "schemas"
@@ -411,6 +411,15 @@ msgstr "Translation updated successfully"
msgid "Save translations"
msgstr "Save translations"
+msgid "Go back"
+msgstr "Go back"
+
+msgid "Next section"
+msgstr "Next section"
+
+msgid "Save and exit"
+msgstr "Save and exit"
+
msgid "Can edit and capture"
msgstr "Can edit and capture"
@@ -1091,15 +1100,36 @@ msgstr ""
"included. PHU will still be available for the PHU level, but not included "
"in the aggregations to the levels above."
+msgid "Set up the basic information for this data set."
+msgstr "Set up the basic information for this data set."
+
+msgid "Configure data elements"
+msgstr "Configure data elements"
+
+msgid "Choose what data is collected for this data set."
+msgstr "Choose what data is collected for this data set."
+
+msgid "Configure data entry periods"
+msgstr "Configure data entry periods"
+
+msgid "Choose for what time periods data can be entered for this data set"
+msgstr "Choose for what time periods data can be entered for this data set"
+
msgid "Setup"
msgstr "Setup"
msgid "Data"
msgstr "Data"
+msgid "Data Elements"
+msgstr "Data Elements"
+
msgid "Periods"
msgstr "Periods"
+msgid "Period type"
+msgstr "Period type"
+
msgid "Organisation Units"
msgstr "Organisation Units"
diff --git a/src/lib/form/index.ts b/src/lib/form/index.ts
index 2f8ef9f18..0a796e6c5 100644
--- a/src/lib/form/index.ts
+++ b/src/lib/form/index.ts
@@ -6,4 +6,3 @@ export { validate, createFormValidate } from './validate'
export { useOnSubmitEdit, useOnSubmitNew } from './useOnSubmit'
export { modelFormSchemas } from './modelFormSchemas'
export * from './sectionedForm'
-export * from './sectionedForm/sectionedFormDescriptor'
diff --git a/src/lib/form/sectionedForm/SectionedFormBase.tsx b/src/lib/form/sectionedForm/SectionedFormBase.tsx
deleted file mode 100644
index fcfc0c016..000000000
--- a/src/lib/form/sectionedForm/SectionedFormBase.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-import { SectionedFormProvider } from './SectionedFormContext'
-
-type SectionedFormProps = {
- name: string
- children: React.ReactNode
-}
-export const SectionedFormBase = ({ name, children }: SectionedFormProps) => {
- return (
-
- {children}
-
- )
-}
diff --git a/src/lib/form/sectionedForm/SectionedFormContext.tsx b/src/lib/form/sectionedForm/SectionedFormContext.tsx
index a84e87b8d..e78764380 100644
--- a/src/lib/form/sectionedForm/SectionedFormContext.tsx
+++ b/src/lib/form/sectionedForm/SectionedFormContext.tsx
@@ -1,20 +1,89 @@
import React, { createContext, useState } from 'react'
-import { createFormStore, FormProps, FormStore } from './formStore'
+import { SectionDescriptor, SectionedFormDescriptor } from './formDescriptor'
-export const SectionedFormContext = createContext(null)
+/* Some of the types in this file may look complex.
+ However they are here to help type-safety and autocommpletion for consumers.
+
+ The only thing consumers need to do is pass the type of the formdescriptor to use the context.
+ useSectionedFormDescriptor()
+ This helps usage in specific form components.
+*/
-export const SectionedFormProvider = ({
+type AllFieldNames =
+ T['sections'][number]['fields'][number]['name']
+
+/* Helper to avoid returning undefined from a map when we know we have the value from the type.
+And conversely - add undefined to TType if we dont have a specifc type for T*/
+type EnforceIfInferrable<
+ T extends SectionedFormDescriptor,
+ TType
+> = T extends SectionedFormDescriptor
+ ? unknown extends U
+ ? TType | undefined
+ : TType
+ : never
+
+function createContextValue(
+ descriptor: T
+) {
+ const fieldLabels = Object.fromEntries(
+ descriptor.sections.flatMap((section) =>
+ section.fields.map((f) => [f.name, f.label] as const)
+ )
+ ) as Record, EnforceIfInferrable>
+
+ const sectionMap = Object.fromEntries(
+ descriptor.sections.map((s) => [s.name, s])
+ ) as Record<
+ T['sections'][number]['name'],
+ EnforceIfInferrable
+ >
+
+ const sections: T['sections'] = descriptor.sections
+ return {
+ formName: descriptor.name,
+ formLabel: descriptor.label,
+ sections,
+ getSection: (name: T['sections'][number]['name']) => sectionMap[name],
+ getFieldLabel: (field: AllFieldNames) => {
+ return fieldLabels[field]
+ },
+ }
+}
+type SectionFormContextValue = ReturnType<
+ typeof createContextValue
+>
+
+export const SectionedFormContext = createContext | null>(null)
+
+export const SectionedFormDescriptorProvider = <
+ T extends SectionedFormDescriptor
+>({
children,
initialValue,
}: {
- initialValue: Partial
+ initialValue: T
children: React.ReactNode
}) => {
- const [store] = useState(() => createFormStore(initialValue))
+ const [contextValue] = useState(() => createContextValue(initialValue))
return (
-
+
{children}
)
}
+
+export const useSectionedFormDescriptor = <
+ T extends SectionedFormDescriptor
+>() => {
+ const context = React.useContext(SectionedFormContext)
+ if (!context) {
+ throw new Error(
+ 'useSectionedFormDescriptor must be used within a SectionedFormDescriptorProvider'
+ )
+ }
+ return context as SectionFormContextValue
+}
diff --git a/src/lib/form/sectionedForm/SectionedFormDescriptorProvider.tsx b/src/lib/form/sectionedForm/SectionedFormDescriptorProvider.tsx
deleted file mode 100644
index b785cdf71..000000000
--- a/src/lib/form/sectionedForm/SectionedFormDescriptorProvider.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, { createContext, useState } from 'react'
-import {
- SectionDescriptor,
- SectionedFormDescriptor,
-} from './sectionedFormDescriptor'
-
-/* Some of the types in this file may look complex.
- However they are here to help type-safety and autocommpletion for consumers.
-
- The only thing consumers need to do is pass the type of the formdescriptor to use the context.
- useSectionedFormDescriptor()
- This helps usage in specific form components.
-*/
-
-type AllFieldNames =
- T['sections'][number]['fields'][number]['name']
-
-/* Helper to avoid returning undefined from a map when we know we have the value from the type.
-And conversely - add undefined to TType if we dont have a specifc type for T*/
-type EnforceIfInferrable<
- T extends SectionedFormDescriptor,
- TType
-> = T extends SectionedFormDescriptor
- ? unknown extends U
- ? TType | undefined
- : TType
- : never
-
-function createContextValue(descriptor: T) {
- const fieldLabels = Object.fromEntries(
- descriptor.sections.flatMap((section) =>
- section.fields.map((f) => [f.name, f.label] as const)
- )
- ) as Record, EnforceIfInferrable>
-
- const sectionMap = Object.fromEntries(
- descriptor.sections.map((s) => [s.name, s])
- ) as Record<
- T['sections'][number]['name'],
- EnforceIfInferrable
- >
-
- const sections: T['sections'] = descriptor.sections
- return {
- formName: descriptor.name,
- formLabel: descriptor.label,
- sections,
- getSection: (name: T['sections'][number]['name']) => sectionMap[name],
- getFieldLabel: (field: AllFieldNames) => {
- return fieldLabels[field]
- },
- }
-}
-type SectionFormContextValue = ReturnType<
- typeof createContextValue
->
-
-export const SectionedFormContext = createContext | null>(null)
-
-export const SectionedFormDescriptorProvider = <
- T extends SectionedFormDescriptor
->({
- children,
- initialValue,
-}: {
- initialValue: T
- children: React.ReactNode
-}) => {
- const [contextValue] = useState(() => createContextValue(initialValue))
-
- return (
-
- {children}
-
- )
-}
-
-export const useSectionedFormDescriptor = <
- T extends SectionedFormDescriptor
->() => {
- const context = React.useContext(SectionedFormContext)
- if (!context) {
- throw new Error(
- 'useSectionedFormDescriptor must be used within a SectionedFormDescriptorProvider'
- )
- }
- return context as SectionFormContextValue
-}
diff --git a/src/lib/form/sectionedForm/SectionedFormField.tsx b/src/lib/form/sectionedForm/SectionedFormField.tsx
deleted file mode 100644
index 1bf058cb1..000000000
--- a/src/lib/form/sectionedForm/SectionedFormField.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React, { useEffect } from 'react'
-import { useRegisterField } from './useRegister'
-
-type SectionedFormField = {
- label: string
- name: string
- children: React.ReactNode
-}
-export const SectionFormField = ({
- label,
- name,
- children,
-}: SectionedFormField) => {
- const register = useRegisterField()
-
- useEffect(() => {
- register({ name, label })
- }, [register, name, label])
-
- return children
-}
diff --git a/src/lib/form/sectionedForm/SectionedFormSection.tsx b/src/lib/form/sectionedForm/SectionedFormSection.tsx
deleted file mode 100644
index 79af2c3db..000000000
--- a/src/lib/form/sectionedForm/SectionedFormSection.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import { SectionedFormSectionProvider } from './SectionedFormSectionContext'
-import { createSectionStore } from './sectionStore'
-import { SectionedFormSectionContext } from './SectionedFormSectionContext'
-import { useRegisterFormSection } from './useRegister'
-
-type SectionFormSectionProps = {
- label: string
- name: string
- children: React.ReactNode
-}
-export const SectionedFormSection = ({
- label,
- name,
- children,
-}: SectionFormSectionProps) => {
- return (
-
- {children}
-
- )
-}
diff --git a/src/lib/form/sectionedForm/SectionedFormSectionContext.tsx b/src/lib/form/sectionedForm/SectionedFormSectionContext.tsx
deleted file mode 100644
index 7ee63ec10..000000000
--- a/src/lib/form/sectionedForm/SectionedFormSectionContext.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React, { createContext, useEffect, useState } from 'react'
-import { useStore } from 'zustand'
-import { SectionedFormContext } from './SectionedFormContext'
-import { createSectionStore, SectionProps, SectionStore } from './sectionStore'
-
-export const SectionedFormSectionContext = createContext(
- null
-)
-
-export const SectionedFormSectionProvider = ({
- children,
- initialValue,
-}: {
- initialValue: SectionProps
- children: React.ReactNode
-}) => {
- const [store] = useState(() => createSectionStore(initialValue))
- const formContext = React.useContext(SectionedFormContext)
- if (!formContext) {
- throw new Error(
- 'SectionedFormSectionProvider must be wrapped in a SectionFormSectionProvider'
- )
- }
-
- const addSectionToForm = useStore(formContext, (state) => state.addSection)
-
- useEffect(() => {
- addSectionToForm(store.getState().section)
- }, [store, addSectionToForm])
-
- return (
-
- {children}
-
- )
-}
diff --git a/src/lib/form/sectionedForm/formStore.ts b/src/lib/form/sectionedForm/formStore.ts
deleted file mode 100644
index 6bc7db713..000000000
--- a/src/lib/form/sectionedForm/formStore.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { createStore } from 'zustand'
-import { devtools } from 'zustand/middleware'
-import { uniqueBy } from '../../../lib/utils'
-import { SectionedFormSection, SectionFormField } from './types'
-
-type SectionIdentifier = string | SectionedFormSection
-
-export interface FormProps {
- name: string
- sections: SectionedFormSection[]
- sectionFields: Map
-}
-
-export interface FormState extends FormProps {
- addSection: (section: SectionedFormSection) => void
- addField: (
- section: string | SectionedFormSection,
- field: SectionFormField
- ) => void
- getFieldsForSection: (section: SectionIdentifier) => SectionFormField[]
- getSectionsForField: (
- field: string | SectionFormField
- ) => SectionedFormSection[] | undefined
-}
-
-export type FormStore = ReturnType
-
-export const createFormStore = (initialProps: Partial) =>
- createStore()(
- devtools((set, get) => ({
- name: '',
- sections: [],
- sectionFields: new Map(),
- ...initialProps,
- addSection: (section: SectionedFormSection) => {
- const prevSections = get().sections
- set({ sections: prevSections.concat(section) })
- },
- addField: (section, field) => {
- const sectionName = resolveSectionName(section)
- const prevFieldsMap = get().sectionFields
- const newFields = uniqueBy(
- prevFieldsMap.get(sectionName)?.concat(field) || [field],
- (field) => field.name
- )
-
- const sectionFields = new Map(prevFieldsMap).set(
- sectionName,
- newFields
- )
- set({ sectionFields })
- },
- getSections: () => get().sections,
- getFieldsForSection: (section: SectionIdentifier) => {
- const sectionName = resolveSectionName(section)
- return get().sectionFields.get(sectionName) || []
- },
- getSectionsForField: (field: string | SectionFormField) => {
- const fieldName = typeof field === 'string' ? field : field.name
- const fieldsBySection = get().sectionFields
- for (const [section, fields] of fieldsBySection.entries()) {
- if (fields.find((f) => f.name === fieldName)) {
- return get().sections.filter((s) => s.name === section)
- }
- }
- return undefined
- },
- }))
- )
-
-const resolveSectionName = (section: SectionIdentifier) => {
- return typeof section === 'string' ? section : section.name
-}
diff --git a/src/lib/form/sectionedForm/index.ts b/src/lib/form/sectionedForm/index.ts
index d22f6e6db..c851e06ed 100644
--- a/src/lib/form/sectionedForm/index.ts
+++ b/src/lib/form/sectionedForm/index.ts
@@ -1,6 +1,6 @@
-export { SectionFormField } from './SectionedFormField'
-export { SectionedFormSection } from './SectionedFormSection'
-export { SectionedFormBase } from './SectionedFormBase'
-export { useSectionedFormState } from './useSectionedFormState'
export * from './useSelectedSection'
-export * from './SectionedFormDescriptorProvider'
+export {
+ useSectionedFormDescriptor,
+ SectionedFormDescriptorProvider,
+} from './SectionedFormContext'
+export * from './types'
diff --git a/src/lib/form/sectionedForm/sectionStore.ts b/src/lib/form/sectionedForm/sectionStore.ts
deleted file mode 100644
index fbea0cd35..000000000
--- a/src/lib/form/sectionedForm/sectionStore.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { create, createStore } from 'zustand'
-import { devtools } from 'zustand/middleware'
-import { SectionedFormSection, SectionFormField } from './types'
-
-export interface SectionProps {
- section: SectionedFormSection
-}
-
-export interface SectionState extends SectionProps {
- setSection: (section: SectionedFormSection) => void
- getSection: () => SectionedFormSection
-}
-export type SectionStore = ReturnType
-
-export const createSectionStore = (initialProps: SectionProps) =>
- createStore()(
- devtools((set, get) => ({
- ...initialProps,
- setSection: (section: SectionedFormSection) => {
- set({ section })
- },
- getSection: () => {
- return get().section
- },
- }))
- )
diff --git a/src/lib/form/sectionedForm/types.ts b/src/lib/form/sectionedForm/types.ts
index 5dd3a6ca7..d57a62641 100644
--- a/src/lib/form/sectionedForm/types.ts
+++ b/src/lib/form/sectionedForm/types.ts
@@ -1,9 +1,20 @@
-export type SectionedFormSection = {
- name: string
+export type FieldDescriptor = {
label: string
+ // keyof T | (string & {}) allows auto-completion for keys of T, while also allowing
+ // any other string to be used as a key. This allows fields that not necessarily map to the model-property
+ name: keyof T | (string & {})
+}
+
+export type SectionDescriptor = {
+ label: string
+ name: string
+ // keyof T | (string & {}) allows auto-completion for fields, while also allowing
+ // any other string to be used as a key
+ fields: FieldDescriptor[]
}
-export type SectionFormField = {
+export type SectionedFormDescriptor = {
name: string
label: string
+ sections: SectionDescriptor[]
}
diff --git a/src/lib/form/sectionedForm/useRegister.ts b/src/lib/form/sectionedForm/useRegister.ts
deleted file mode 100644
index 31aa7efee..000000000
--- a/src/lib/form/sectionedForm/useRegister.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import React, { useCallback } from 'react'
-import { useStore } from 'zustand'
-import { SectionedFormContext } from './SectionedFormContext'
-import { SectionedFormSectionContext } from './SectionedFormSectionContext'
-import { SectionFormField } from './types'
-
-export const useRegisterField = () => {
- const sectionContext = React.useContext(SectionedFormSectionContext)!
- const formContext = React.useContext(SectionedFormContext)!
-
- const currentSection = useStore(sectionContext, (state) =>
- state.getSection()
- )
-
- const addFieldToForm = useStore(formContext, (state) => state.addField)
-
- return useCallback(
- (field: SectionFormField) => {
- if (currentSection) {
- addFieldToForm(currentSection, field)
- } else {
- console.error(`Tried to register field ${field.name} in section, but no section is set.
-Make sure to wrap fields in a SectionedFormSection component`)
- }
- },
- [addFieldToForm, currentSection]
- )
-}
diff --git a/src/lib/form/sectionedForm/useSectionedFormState.ts b/src/lib/form/sectionedForm/useSectionedFormState.ts
deleted file mode 100644
index 0987fa3ef..000000000
--- a/src/lib/form/sectionedForm/useSectionedFormState.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useContext } from 'react'
-import { useStore } from 'zustand'
-import { FormState } from './formStore'
-import { SectionedFormContext } from './SectionedFormContext'
-
-export function useSectionedFormState(): FormState
-export function useSectionedFormState(selector: (state: FormState) => T): T
-export function useSectionedFormState(selector?: (state: FormState) => T) {
- const formStore = useContext(SectionedFormContext)
- if (!formStore) {
- throw new Error(
- 'useFormState must be used within a SectionedFormProvider'
- )
- }
- return useStore(formStore, selector!)
-}
diff --git a/src/lib/form/sectionedForm/useSelectedSection.ts b/src/lib/form/sectionedForm/useSelectedSection.ts
index 96fe38660..75d2fbcd9 100644
--- a/src/lib/form/sectionedForm/useSelectedSection.ts
+++ b/src/lib/form/sectionedForm/useSelectedSection.ts
@@ -1,11 +1,6 @@
import { useMemo } from 'react'
-import {
- useQueryParam,
- StringParam,
- createEnumParam,
- withDefault,
-} from 'use-query-params'
-import { useSectionedFormDescriptor } from './SectionedFormDescriptorProvider'
+import { useQueryParam, createEnumParam, withDefault } from 'use-query-params'
+import { useSectionedFormDescriptor } from './SectionedFormContext'
export const FORM_SECTION_PARAM_KEY = 'section'
@@ -25,7 +20,6 @@ export const useSelectedSection = () => {
[sections]
)
- console.log({ paramConfig })
return useQueryParam(FORM_SECTION_PARAM_KEY, paramConfig, {
removeDefaultsFromUrl: true,
})