Skip to content
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"classnames": "^2.3.1",
"expr-eval": "^2.0.2",
"final-form": "^4.20.6",
"final-form-set-field-data": "^1.0.2",
"history": "^5.1.0",
"html-react-parser": "^1.4.8",
"idb-keyval": "^6.1.0",
Expand Down
10 changes: 1 addition & 9 deletions src/data-workspace/data-entry-cell/data-entry-field.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import React from 'react'
import { useLockedContext } from '../../shared/index.js'
import { getFieldId } from '../get-field-id.js'
import { EntryFieldInput } from './entry-field-input.js'
Expand All @@ -15,12 +15,6 @@ export const DataEntryField = React.memo(function DataEntryField({
// { [deId]: { [cocId]: value } }
const fieldname = getFieldId(de.id, coc.id)

// todo: perhaps this could be refactored to use DV mutation state
// See https://dhis2.atlassian.net/browse/TECH-1316
const [syncStatus, setSyncStatus] = useState({
syncing: false,
synced: false,
})
const { locked } = useLockedContext()

return (
Expand All @@ -29,15 +23,13 @@ export const DataEntryField = React.memo(function DataEntryField({
fieldname={fieldname}
deId={de.id}
cocId={coc.id}
syncStatus={syncStatus}
disabled={disabled}
locked={locked}
>
<EntryFieldInput
fieldname={fieldname}
dataElement={de}
categoryOptionCombo={coc}
setSyncStatus={setSyncStatus}
disabled={disabled}
locked={locked}
/>
Expand Down
18 changes: 4 additions & 14 deletions src/data-workspace/data-entry-cell/entry-field-input.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'
import React, { useCallback, useMemo } from 'react'
import { useForm } from 'react-final-form'
import { useSetRightHandPanel } from '../../right-hand-panel/index.js'
import {
VALUE_TYPES,
Expand Down Expand Up @@ -59,7 +60,6 @@ export function EntryFieldInput({
fieldname,
dataElement: de,
categoryOptionCombo: coc,
setSyncStatus,
disabled,
locked,
}) {
Expand All @@ -68,7 +68,7 @@ export function EntryFieldInput({
// used so we don't consume the "id" which
// would cause this component to rerender
const setRightHandPanel = useSetRightHandPanel()

const form = useForm()
// todo: maybe move to InnerWrapper?
// See https://dhis2.atlassian.net/browse/TECH-1296
const onKeyDown = useCallback(
Expand Down Expand Up @@ -97,24 +97,15 @@ export function EntryFieldInput({
const sharedProps = useMemo(
() => ({
fieldname,
form,
deId: de.id,
cocId: coc.id,
disabled,
locked,
setSyncStatus,
onFocus,
onKeyDown,
}),
[
fieldname,
de,
coc,
disabled,
locked,
setSyncStatus,
onFocus,
onKeyDown,
]
[fieldname, form, de, coc, disabled, locked, onFocus, onKeyDown]
)

return <InputComponent sharedProps={sharedProps} de={de} />
Expand All @@ -135,5 +126,4 @@ EntryFieldInput.propTypes = {
disabled: PropTypes.bool,
fieldname: PropTypes.string,
locked: PropTypes.bool,
setSyncStatus: PropTypes.func,
}
42 changes: 31 additions & 11 deletions src/data-workspace/data-entry-cell/inner-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IconMore16, colors } from '@dhis2/ui'
import { useIsMutating } from '@tanstack/react-query'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'
import { useField } from 'react-final-form'
import React, { useEffect } from 'react'
import { useField, useForm } from 'react-final-form'
import {
mutationKeys as dataValueMutationKeys,
useDataValueParams,
Expand Down Expand Up @@ -50,7 +50,6 @@ export function InnerWrapper({
fieldname,
deId,
cocId,
syncStatus,
}) {
const hasComment = useValueStore((state) =>
state.hasComment({
Expand All @@ -61,9 +60,34 @@ export function InnerWrapper({
const { item } = useHighlightedFieldIdContext()
const highlighted = item && deId === item.de.id && cocId === item.coc.id
const {
meta: { invalid, active },
} = useField(fieldname, { subscription: { invalid: true, active: true } })
input: { value },
meta: { invalid, active, data, dirty },
} = useField(fieldname, {
// by default undefined is formatted to ''
// this preserves the format used in the input-component
format: (v) => v,
subscription: {
value: true,
invalid: true,
active: true,
data: true,
dirty: true,
},
})
const form = useForm()

// initalize lastSyncedValue
useEffect(
() => {
form.mutators.setFieldData(fieldname, {
lastSyncedValue: value,
})
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)

const synced = dirty && data.lastSyncedValue === value
// Detect if this field is sending data
const dataValueParams = useDataValueParams({ deId, cocId })
const activeMutations = useIsMutating({
Expand All @@ -74,7 +98,7 @@ export function InnerWrapper({
// see https://dhis2.atlassian.net/browse/TECH-1316
const cellStateClassName = invalid
? styles.invalid
: activeMutations === 0 && syncStatus.synced
: activeMutations === 0 && synced
? styles.synced
: null

Expand All @@ -90,7 +114,7 @@ export function InnerWrapper({
{children}
<SyncStatusIndicator
isLoading={activeMutations > 0}
isSynced={syncStatus.synced}
isSynced={synced}
/>
<CommentIndicator hasComment={hasComment} />
</div>
Expand All @@ -103,8 +127,4 @@ InnerWrapper.propTypes = {
disabled: PropTypes.bool,
fieldname: PropTypes.string,
locked: PropTypes.bool,
syncStatus: PropTypes.shape({
synced: PropTypes.bool,
syncing: PropTypes.bool,
}),
}
2 changes: 2 additions & 0 deletions src/data-workspace/final-form-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import setFieldData from 'final-form-set-field-data'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import { Form } from 'react-final-form'
Expand Down Expand Up @@ -37,6 +38,7 @@ export function FinalFormWrapper({ children, dataValueSet }) {
initialValues={initialValues}
subscriptions={subscriptions}
keepDirtyOnReinitialize
mutators={{ setFieldData }}
>
{() => children}
</Form>
Expand Down
19 changes: 10 additions & 9 deletions src/data-workspace/inputs/boolean-radios.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import i18n from '@dhis2/d2-i18n'
import { Button, Radio } from '@dhis2/ui'
import cx from 'classnames'
import React, { useState } from 'react'
import React from 'react'
import { useField } from 'react-final-form'
import { useSetDataValueMutation } from '../../shared/index.js'
import styles from './inputs.module.css'
Expand All @@ -15,11 +15,11 @@ import { convertCallbackSignatures, InputPropTypes } from './utils.js'
// does `isEqual` prop help make 1/true and 0/false/'' equal?
export const BooleanRadios = ({
fieldname,
form,
deId,
cocId,
disabled,
locked,
setSyncStatus,
onKeyDown,
onFocus,
}) => {
Expand Down Expand Up @@ -57,9 +57,10 @@ export const BooleanRadios = ({

const {
input: { value: fieldvalue },
meta,
} = useField(fieldname)
const [lastSyncedValue, setLastSyncedValue] = useState(fieldvalue)
meta: { valid, data },
} = useField(fieldname, {
subscription: { value: true, valid: true, data: true },
})

const { mutate } = useSetDataValueMutation({ deId, cocId })
const syncData = (value) => {
Expand All @@ -69,8 +70,9 @@ export const BooleanRadios = ({
{ value: value || '' },
{
onSuccess: () => {
setLastSyncedValue(value)
setSyncStatus({ syncing: false, synced: true })
form.mutators.setFieldData(fieldname, {
lastSyncedValue: value,
})
},
}
)
Expand All @@ -80,9 +82,8 @@ export const BooleanRadios = ({
delete clearButtonProps.type

const handleChange = (value) => {
const { valid } = meta
// If this value has changed, sync it to server if valid
if (valid && value !== lastSyncedValue) {
if (valid && value !== data.lastSyncedValue) {
syncData(value)
}
}
Expand Down
15 changes: 9 additions & 6 deletions src/data-workspace/inputs/file-inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ const formatFileSize = (size) => {

export const FileResourceInput = ({
fieldname,
form,
image,
deId,
cocId,
disabled,
locked,
setSyncStatus,
onKeyDown,
onFocus,
}) => {
Expand Down Expand Up @@ -65,15 +65,17 @@ export const FileResourceInput = ({

const handleChange = ({ files }) => {
const newFile = files[0]
input.onChange({ name: newFile.name, size: newFile.size })
const newFileValue = { name: newFile.name, size: newFile.size }
input.onChange(newFileValue)
input.onBlur()
if (newFile instanceof File) {
setSyncStatus({ syncing: true, synced: false })
uploadFile(
{ file: newFile },
{
onSuccess: () => {
setSyncStatus({ syncing: false, synced: true })
form.mutators.setFieldData(fieldname, {
lastSyncedValue: newFileValue,
})
},
}
)
Expand All @@ -83,10 +85,11 @@ export const FileResourceInput = ({
const handleDelete = () => {
input.onChange('')
input.onBlur()
setSyncStatus({ syncing: true, synced: false })
deleteFile(null, {
onSuccess: () => {
setSyncStatus({ syncing: false, synced: true })
form.mutators.setFieldData(fieldname, {
lastSyncedValue: null,
})
},
})
}
Expand Down
17 changes: 9 additions & 8 deletions src/data-workspace/inputs/generic-input.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import React from 'react'
import { useField } from 'react-final-form'
import {
NUMBER_TYPES,
Expand All @@ -23,9 +23,9 @@ const htmlTypeAttrsByValueType = {

export const GenericInput = ({
fieldname,
form,
deId,
cocId,
setSyncStatus,
valueType,
onKeyDown,
onFocus,
Expand All @@ -48,14 +48,14 @@ export const GenericInput = ({
}
const { input, meta } = useField(fieldname, {
validate: validateByValueTypeWithLimits(valueType, limits),
subscription: { value: true, dirty: true, valid: true },
subscription: { value: true, dirty: true, valid: true, data: true },
format: formatValue,
formatOnBlur: true,
// This is require to ensure form is validated on first page load
// This is required to ensure form is validated on first page load
// this is because the validate prop doesn't rerender when limits change
data: limits,
})
const [lastSyncedValue, setLastSyncedValue] = useState(input.value)

const { mutate } = useSetDataValueMutation({ deId, cocId })
const syncData = (value) => {
// todo: Here's where an error state could be set: ('onError')
Expand All @@ -64,8 +64,9 @@ export const GenericInput = ({
{ value: value || '' },
{
onSuccess: () => {
setLastSyncedValue(value)
setSyncStatus({ syncing: false, synced: true })
form.mutators.setFieldData(fieldname, {
lastSyncedValue: value,
})
},
}
)
Expand All @@ -75,7 +76,7 @@ export const GenericInput = ({
const { value } = input
const formattedValue = formatValue(value)
const { valid } = meta
if (valid && formattedValue !== lastSyncedValue) {
if (valid && formattedValue !== meta.data.lastSyncedValue) {
syncData(formattedValue)
}
}
Expand Down
Loading