diff --git a/src/components/FormField/components/CriteriaTable/index.js b/src/components/FormField/components/CriteriaTable/index.js index 0b34a78..a10610d 100644 --- a/src/components/FormField/components/CriteriaTable/index.js +++ b/src/components/FormField/components/CriteriaTable/index.js @@ -70,9 +70,6 @@ const Table = ({ setColumns(newColumns); }, [columnsCfg, disabled]); - useEffect(() => { - setTableValue(rowsCfg); - }, [rowsCfg]); useEffect(() => { const stateFieldData = pathOr([], ['stateFieldData'], tableColumns); @@ -102,16 +99,19 @@ const Table = ({ setLastRow(!isEmpty(lastRowValues) ? lastRowData : []); }, [tableColumns]); + // Saved data (defaultValue) takes priority over template defaults (rowsCfg). + // Using a single effect for both so the second render caused by rowsCfg changing + // doesn't overwrite the saved data that was just loaded from the API. useEffect(() => { - if (!equal(tableValue, defaultValue)) { - setTableValue(defaultValue); + const next = (defaultValue && !isEmpty(defaultValue)) ? defaultValue : rowsCfg; + if (!equal(tableValue, next)) { + setTableValue(next); } - }, [defaultValue]); + }, [rowsCfg, defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { - setTableValue(defaultValue); register(fieldName, properConfig(config)); - }, []); + }, []); // eslint-disable-line react-hooks/exhaustive-deps return ( <> diff --git a/src/components/FormField/components/Spreadsheet/index.js b/src/components/FormField/components/Spreadsheet/index.js index 72ec039..f0db9a0 100644 --- a/src/components/FormField/components/Spreadsheet/index.js +++ b/src/components/FormField/components/Spreadsheet/index.js @@ -61,6 +61,7 @@ const Spreadsheet = ({ fieldName, label, errors = {}, defaultValue, setDataFn, t const isRestoringRef = useRef(false); const tagCellMapRef = useRef({}); const reinitializeRef = useRef(null); + const initializedFromSavedRef = useRef(false); const fileInputRef = useRef(null); const addSheetsFileInputRef = useRef(null); @@ -173,6 +174,7 @@ const Spreadsheet = ({ fieldName, label, errors = {}, defaultValue, setDataFn, t }; const initialData = parseWorkbook(defaultValue) || templateData || { name: 'Sheet' }; + initializedFromSavedRef.current = !!parseWorkbook(defaultValue); doInit(initialData); reinitializeRef.current = doInit; @@ -186,6 +188,17 @@ const Spreadsheet = ({ fieldName, label, errors = {}, defaultValue, setDataFn, t }; }, []); // eslint-disable-line react-hooks/exhaustive-deps + // If the component mounted before saved data was available (reset() cleared form values), + // reinitialize univerjs once when the valid saved workbook arrives via defaultValue. + useEffect(() => { + if (initializedFromSavedRef.current) return; // already showing saved data + if (!reinitializeRef.current) return; // init useEffect hasn't run yet + const parsedDefault = parseWorkbook(defaultValue); + if (!parsedDefault) return; // still no valid workbook + initializedFromSavedRef.current = true; // prevent re-triggering on user edits + reinitializeRef.current(parsedDefault); + }, [defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps + useEffect(() => { const el = containerRef.current; if (!el) return; diff --git a/src/components/FormField/components/Table/index.js b/src/components/FormField/components/Table/index.js index 0edcb06..0164287 100644 --- a/src/components/FormField/components/Table/index.js +++ b/src/components/FormField/components/Table/index.js @@ -116,10 +116,6 @@ const Table = ({ setColumns(newColumns); }, [columnsCfg, disabled]); - useEffect(() => { - setRows(rowsCfg); - }, [rowsCfg]); - useEffect(() => { const stateFieldData = pathOr([], ['stateFieldData'], tableColumns); let rowsData = pathOr([], ['rowsData'], tableColumns); @@ -147,16 +143,19 @@ const Table = ({ setLastRow(!isEmpty(lastRowValues) ? lastRowData : []); }, [tableColumns, shouldDisableNewRows]); + // Saved data (defaultValue) takes priority over template defaults (rowsCfg). + // Using a single effect for both so the second render caused by rowsCfg changing + // doesn't overwrite the saved data that was just loaded from the API. useEffect(() => { - if (!equal(rows, defaultValue)) { - setRows(defaultValue); + const next = (defaultValue && !isEmpty(defaultValue)) ? defaultValue : rowsCfg; + if (!equal(rows, next)) { + setRows(next); } - }, [defaultValue]); + }, [rowsCfg, defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { - setRows(defaultValue); register(fieldName, properConfig(config)); - }, []); + }, []); // eslint-disable-line react-hooks/exhaustive-deps return ( <> diff --git a/src/helpers/validators.js b/src/helpers/validators.js index f891ee4..d5eb5db 100644 --- a/src/helpers/validators.js +++ b/src/helpers/validators.js @@ -89,31 +89,31 @@ export const createSpreadsheetValidator = (gepafin) => (workbookValue) => { }; export const nonEmptyTables = (v = [], tableCfg = []) => { + // table stores a plain array; criteria_table stores { rows: [...], total: N } + const rows = is(Array, v) ? v : (v && is(Array, v.rows) ? v.rows : []); const colsCfg = pathOr([], ['stateFieldData'], tableCfg); const nonPredefinedCells = colsCfg .filter(o => !o.predefined) .map(o => o.name); let isTableValid = true; - let atLeastOneCellFilled = false + let atLeastOneCellFilled = false; - if (is(Array, v)) { - // eslint-disable-next-line array-callback-return - v.map((row) => { - if (isEmpty(row)) { - isTableValid = false; - } else { - // eslint-disable-next-line array-callback-return - nonPredefinedCells.map((k) => { - if (isNil(row[k]) || isEmpty(row[k])) { - isTableValid = atLeastOneCellFilled; - } else { - atLeastOneCellFilled = true; - isTableValid = true; - } - }); - } - }); - } + // eslint-disable-next-line array-callback-return + rows.map((row) => { + if (isEmpty(row)) { + isTableValid = false; + } else { + // eslint-disable-next-line array-callback-return + nonPredefinedCells.map((k) => { + if (isNil(row[k]) || isEmpty(row[k])) { + isTableValid = atLeastOneCellFilled; + } else { + atLeastOneCellFilled = true; + isTableValid = true; + } + }); + } + }); - return is(Array, v) ? v.length >= 1 && isTableValid : false; + return rows.length >= 1 && isTableValid; } \ No newline at end of file diff --git a/src/pages/BandoApplication/index.js b/src/pages/BandoApplication/index.js index b1f0518..96d5bb3 100644 --- a/src/pages/BandoApplication/index.js +++ b/src/pages/BandoApplication/index.js @@ -401,7 +401,7 @@ const BandoApplication = () => { }, formDataInitial); } - reset(); + reset(formDataInitial); setFormInitialData(formDataInitial); } storeSet('unsetAsyncRequest'); @@ -743,8 +743,10 @@ const BandoApplication = () => { const validations = Object.keys(o.validators).reduce((acc, cur) => { if (o.validators[cur]) { - if (['min', 'max', 'minLength', 'maxLength', 'maxSize'].includes(cur)) { + if (['minLength', 'maxLength', 'maxSize'].includes(cur)) { acc[cur] = parseInt(o.validators[cur]); + } else if (['min', 'max'].includes(cur)) { + acc[cur] = parseFloat(o.validators[cur]); } else if ('pattern' === cur) { acc[cur] = new RegExp(o.validators[cur]); } else if ('isRequired' === cur) { diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js index 61a80cb..46456db 100644 --- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js +++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js @@ -225,7 +225,17 @@ const BuilderElementSettings = ({ closeSettingsFn, callStatus, context }) => { value={validators[k]} onChange={(e) => onChangeValidator(e.target.value, k)}/> : null} - {numberBasedValidatorFields.includes(k) && !isNil(validators[k]) + {['min', 'max'].includes(k) && !isNil(validators[k]) + ?
+ + onChangeValidator(e.target.value, k)}/> +
: null} + {['minLength', 'maxLength'].includes(k) && !isNil(validators[k]) ?