diff --git a/package.json b/package.json index fd39be92b6..b85a8448a2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/data-workspace/data-entry-cell/data-entry-field.js b/src/data-workspace/data-entry-cell/data-entry-field.js index 7ed6bc5cfd..a510736069 100644 --- a/src/data-workspace/data-entry-cell/data-entry-field.js +++ b/src/data-workspace/data-entry-cell/data-entry-field.js @@ -20,6 +20,7 @@ export const DataEntryField = React.memo(function DataEntryField({ const [syncStatus, setSyncStatus] = useState({ syncing: false, synced: false, + lastSyncedValue: undefined, }) const { locked } = useLockedContext() @@ -38,6 +39,7 @@ export const DataEntryField = React.memo(function DataEntryField({ dataElement={de} categoryOptionCombo={coc} setSyncStatus={setSyncStatus} + syncStatus={syncStatus} disabled={disabled} locked={locked} /> diff --git a/src/data-workspace/data-entry-cell/entry-field-input.js b/src/data-workspace/data-entry-cell/entry-field-input.js index 9169813c4b..cca43baec2 100644 --- a/src/data-workspace/data-entry-cell/entry-field-input.js +++ b/src/data-workspace/data-entry-cell/entry-field-input.js @@ -60,6 +60,7 @@ export function EntryFieldInput({ dataElement: de, categoryOptionCombo: coc, setSyncStatus, + syncStatus, disabled, locked, }) { @@ -102,6 +103,7 @@ export function EntryFieldInput({ disabled, locked, setSyncStatus, + syncStatus, onFocus, onKeyDown, }), @@ -112,6 +114,7 @@ export function EntryFieldInput({ disabled, locked, setSyncStatus, + syncStatus, onFocus, onKeyDown, ] @@ -136,4 +139,5 @@ EntryFieldInput.propTypes = { fieldname: PropTypes.string, locked: PropTypes.bool, setSyncStatus: PropTypes.func, + syncStatus: PropTypes.object, } diff --git a/src/data-workspace/data-entry-cell/inner-wrapper.js b/src/data-workspace/data-entry-cell/inner-wrapper.js index ab97d4f0d5..a88dfbd941 100644 --- a/src/data-workspace/data-entry-cell/inner-wrapper.js +++ b/src/data-workspace/data-entry-cell/inner-wrapper.js @@ -50,7 +50,10 @@ export function InnerWrapper({ fieldname, deId, cocId, +<<<<<<< HEAD syncStatus, +======= +>>>>>>> refactor: move lastSyncedValue to mutatior }) { const hasComment = useValueStore((state) => state.hasComment({ @@ -61,9 +64,17 @@ 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 }, + } = useField(fieldname, { + subscription: { + value: true, + invalid: true, + active: true, + data: true, + }, + }) + const synced = data.lastSyncedValue === value // Detect if this field is sending data const dataValueParams = useDataValueParams({ deId, cocId }) const activeMutations = useIsMutating({ @@ -74,7 +85,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 @@ -90,7 +101,7 @@ export function InnerWrapper({ {children} 0} - isSynced={syncStatus.synced} + isSynced={synced} /> @@ -103,8 +114,11 @@ InnerWrapper.propTypes = { disabled: PropTypes.bool, fieldname: PropTypes.string, locked: PropTypes.bool, +<<<<<<< HEAD syncStatus: PropTypes.shape({ synced: PropTypes.bool, syncing: PropTypes.bool, }), +======= +>>>>>>> refactor: move lastSyncedValue to mutatior } diff --git a/src/data-workspace/final-form-wrapper.js b/src/data-workspace/final-form-wrapper.js index c078ffb44e..7d3c549abf 100644 --- a/src/data-workspace/final-form-wrapper.js +++ b/src/data-workspace/final-form-wrapper.js @@ -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' @@ -37,6 +38,7 @@ export function FinalFormWrapper({ children, dataValueSet }) { initialValues={initialValues} subscriptions={subscriptions} keepDirtyOnReinitialize + mutators={{ setFieldData }} > {() => children} diff --git a/src/data-workspace/inputs/boolean-radios.js b/src/data-workspace/inputs/boolean-radios.js index 8b7e767d15..5e9a75f7ba 100644 --- a/src/data-workspace/inputs/boolean-radios.js +++ b/src/data-workspace/inputs/boolean-radios.js @@ -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' @@ -20,6 +20,7 @@ export const BooleanRadios = ({ disabled, locked, setSyncStatus, + syncStatus, onKeyDown, onFocus, }) => { @@ -59,7 +60,6 @@ export const BooleanRadios = ({ input: { value: fieldvalue }, meta, } = useField(fieldname) - const [lastSyncedValue, setLastSyncedValue] = useState(fieldvalue) const { mutate } = useSetDataValueMutation({ deId, cocId }) const syncData = (value) => { @@ -69,8 +69,7 @@ export const BooleanRadios = ({ { value: value || '' }, { onSuccess: () => { - setLastSyncedValue(value) - setSyncStatus({ syncing: false, synced: true }) + setSyncStatus({ synced: true, lastSyncedValue: value }) }, } ) @@ -82,7 +81,7 @@ export const BooleanRadios = ({ const handleChange = (value) => { const { valid } = meta // If this value has changed, sync it to server if valid - if (valid && value !== lastSyncedValue) { + if (valid && value !== syncStatus.lastSyncedValue) { syncData(value) } } diff --git a/src/data-workspace/inputs/file-inputs.js b/src/data-workspace/inputs/file-inputs.js index dd215358e3..bfb9d11fc9 100644 --- a/src/data-workspace/inputs/file-inputs.js +++ b/src/data-workspace/inputs/file-inputs.js @@ -68,12 +68,11 @@ export const FileResourceInput = ({ input.onChange({ name: newFile.name, size: newFile.size }) input.onBlur() if (newFile instanceof File) { - setSyncStatus({ syncing: true, synced: false }) uploadFile( { file: newFile }, { onSuccess: () => { - setSyncStatus({ syncing: false, synced: true }) + setSyncStatus({ synced: true }) }, } ) @@ -83,10 +82,9 @@ export const FileResourceInput = ({ const handleDelete = () => { input.onChange('') input.onBlur() - setSyncStatus({ syncing: true, synced: false }) deleteFile(null, { onSuccess: () => { - setSyncStatus({ syncing: false, synced: true }) + setSyncStatus({ synced: true }) }, }) } diff --git a/src/data-workspace/inputs/generic-input.js b/src/data-workspace/inputs/generic-input.js index 20ecced823..af9c847e20 100644 --- a/src/data-workspace/inputs/generic-input.js +++ b/src/data-workspace/inputs/generic-input.js @@ -1,7 +1,7 @@ import cx from 'classnames' import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { useField } from 'react-final-form' +import React from 'react' +import { useField, useForm } from 'react-final-form' import { NUMBER_TYPES, VALUE_TYPES, @@ -25,7 +25,6 @@ export const GenericInput = ({ fieldname, deId, cocId, - setSyncStatus, valueType, onKeyDown, onFocus, @@ -46,16 +45,17 @@ export const GenericInput = ({ return value.trim() } } + const form = useForm() 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 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') @@ -64,8 +64,9 @@ export const GenericInput = ({ { value: value || '' }, { onSuccess: () => { - setLastSyncedValue(value) - setSyncStatus({ syncing: false, synced: true }) + form.mutators.setFieldData(fieldname, { + lastSyncedValue: value, + }) }, } ) @@ -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) } } diff --git a/yarn.lock b/yarn.lock index bd3f4693ae..b89fc05729 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8446,6 +8446,11 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== +final-form-set-field-data@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/final-form-set-field-data/-/final-form-set-field-data-1.0.2.tgz#ce19095af5d607148c1e6ce3403d75d40223d848" + integrity sha512-gAnENimyQ5GW3OEGca5pbwm4lYshW2orzfBlPUYqzcm7ZxkQrVO8FqCAgEcCM+Rq9U1OU0q+D+UkqETvvDY6jw== + final-form@^4.20.2, final-form@^4.20.6: version "4.20.7" resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.20.7.tgz#e7e2eb5fd952951d4fe6153d46043da2d68b207e"