- udpated spreadsheet form field functionality;

This commit is contained in:
Vitalii Kiiko
2026-04-08 16:08:29 +02:00
parent c52d0c8fd9
commit 3ea1dbe25e
6 changed files with 64 additions and 40 deletions

View File

@@ -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 (
<>

View File

@@ -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;

View File

@@ -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 (
<>

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -225,7 +225,17 @@ const BuilderElementSettings = ({ closeSettingsFn, callStatus, context }) => {
value={validators[k]}
onChange={(e) => onChangeValidator(e.target.value, k)}/>
</div> : null}
{numberBasedValidatorFields.includes(k) && !isNil(validators[k])
{['min', 'max'].includes(k) && !isNil(validators[k])
? <div className="formElementSettings__field">
<label htmlFor={k}>{k}</label>
<InputText id={k}
aria-describedby={`${k}-help`}
value={validators[k]}
keyfilter="num"
placeholder="0"
onChange={(e) => onChangeValidator(e.target.value, k)}/>
</div> : null}
{['minLength', 'maxLength'].includes(k) && !isNil(validators[k])
? <div className="formElementSettings__field">
<label htmlFor={k}>{k}</label>
<InputText id={k}