- added new settings for call;

- added duplciate form field functionality to form builder;
- added improvements to form builder;
- fixed issue with saving data in evaluation;
This commit is contained in:
Vitalii Kiiko
2025-02-06 17:25:46 +01:00
15 changed files with 944 additions and 337 deletions

View File

@@ -1,12 +1,16 @@
import React, { useEffect, useRef, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useDrag, useDrop } from 'react-dnd'
import { ItemTypes } from '../ItemTypes';
import { __ } from '@wordpress/i18n';
import { head, isEmpty } from 'ramda';
import { klona } from 'klona';
// store
import { storeSet, useStore } from '../../../../store';
// tools
import uniqid from '../../../../helpers/uniqid';
// components
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
@@ -19,6 +23,8 @@ const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
const element = head(elements.filter(o => o.id === id));
const [isVariable, setIsVariable] = useState('secondary');
const [isFormula, setIsFormula] = useState('secondary');
const [variableName, setVariableName] = useState('secondary');
const [formulaName, setFormulaName] = useState('secondary');
const [isRequestedAmount, setIsRequestedAmount] = useState(false);
const [isDelegation, setIsDelegation] = useState(false);
@@ -97,6 +103,18 @@ const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
storeSet.main.activeElement(id);
}
const duplicateElement = useCallback((id) => {
const duplicatedElement = head(elements.filter(o => o.id === id));
if (duplicatedElement) {
const copyElement = klona(duplicatedElement);
copyElement.id = uniqid();
const originalIndex = elements.map(o => o.id).indexOf(id);
const newElements = [...elements].toSpliced(originalIndex, 0, copyElement);
storeSet.main.formElements(newElements);
}
}, [elements]);
const remove = (id) => {
storeSet.main.removeElement(id);
}
@@ -112,10 +130,12 @@ const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
if (variable && !isEmpty(variable.value)) {
setIsVariable('warning');
setVariableName(variable.value)
}
if (formula && !isEmpty(formula.value)) {
setIsFormula('warning');
setFormulaName(formula.value)
}
if (isRequestedAmount && !isEmpty(isRequestedAmount.value) && isRequestedAmount.value) {
@@ -137,9 +157,9 @@ const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
<div className="tagHeader">
<Tag value={label} severity="info"/>
{['numberinput', 'criteria_table'].includes(name)
? <Tag value="var" severity={isVariable}/> : null}
? <Tag value="var" severity={isVariable} title={variableName}/> : null}
{name === 'numberinput'
? <Tag value="f(x)" severity={isFormula}/> : null}
? <Tag value="f(x)" severity={isFormula} title={formulaName}/> : null}
{isRequestedAmount
? <Tag value="importo" severity={isRequestedAmount}/> : null}
{isDelegation
@@ -148,6 +168,7 @@ const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
<BuilderElementProperLabel id={id} defaultLabel={label}/>
</div>
<div className="actions">
<Button icon="pi pi-clone" onClick={() => duplicateElement(id)} outlined severity="success"/>
<Button icon="pi pi-cog" onClick={() => openSettings(id)} outlined severity="info"/>
<Button icon="pi pi-trash" disabled={bandoStatus === 'PUBLISH'} onClick={() => remove(id)} outlined severity="danger"/>
</div>

View File

@@ -35,12 +35,13 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
text: __('Testo formattato', 'gepafin'),
table_columns: '',
criteria_table_columns: '',
variable: __('Variable (only letters and "_")', 'gepafin'),
formula: __('Auto calculation formula', 'gepafin')
variable: __('Variabile (lettere, cifre e "_"; il primo carattere deve essere una lettera!)', 'gepafin'),
formula: __('Formula di calcolo automatico', 'gepafin'),
isChecklistItem: __('Fa parte di "checklist"?', 'gepafin'),
}
const settingDescription = {
formula: __('Create formula using previously declared variables. Use these math operators: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>. Example of formula: <code>{entrate}+{assicurazione}</code>.', 'gepafin')
formula: __('Crea una formula usando variabili dichiarate in precedenza. Utilizza questi operatori matematici: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>. Esempio: <code>{entrate}+{assicurazione}</code>.', 'gepafin')
}
const renderHeader = () => {
@@ -104,7 +105,7 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
name={setting.name}
bandoStatus={bandoStatus}
setDataFn={updateDataFn}/>
} else if (['isRequestedAmount', 'isDelegation'].includes(setting.name)) {
} else if (['isRequestedAmount', 'isDelegation', 'isChecklistItem'].includes(setting.name)) {
return <InputSwitch
checked={setting.value}
onChange={(e) => changeFn(e.value, setting.name)}/>

View File

@@ -8,7 +8,7 @@ const ElementSettingChips = ({ restrictedValues = [], changeFn, value = [] }) =>
const [lastTyped, setLastTyped] = useState([])
const isValidValue = (newVal) => {
const validationRegex = /^[a-zA-Z_]+$/;
const validationRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
return validationRegex.test(newVal);
};

View File

@@ -19,7 +19,7 @@ import { MultiSelect } from 'primereact/multiselect';
import { dynamicDataOptions } from '../../../../configData';
const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
const BuilderElementSettings = ({ closeSettingsFn, callStatus, context }) => {
const elements = useStore().main.formElements();
const activeElement = useStore().main.activeElement();
const criteriaOptions = useStore().main.bandoCriteria();
@@ -123,6 +123,10 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
});
}
settings = settings.filter(o => context === 'call'
? !['isRequestedAmount', 'isDelegation'].includes(o.name)
: !['isChecklistItem'].includes(o.name));
if (chosen) {
setActiveElementData(klona(chosen));
setSettings(settings);
@@ -149,7 +153,7 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
.map((o) => <ElementSetting
key={o.name}
setting={o}
bandoStatus={bandoStatus}
callStatus={callStatus}
changeFn={onChange}
updateDataFn={onUpdateOptions}/>)
: null}
@@ -220,7 +224,8 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
</div> : null}
</div>) : null}
</TabPanel> : null}
<TabPanel header={__('Criteri', 'gepafin')}>
{context === 'application'
? <TabPanel header={__('Criteri', 'gepafin')}>
<div className="formElementSettings__field">
<label htmlFor="criteria">{__('Criteri di valutazione', 'gepafin')}</label>
<MultiSelect
@@ -233,25 +238,28 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
display="chip"
placeholder={__('Scegli', 'gepafin')}/>
</div>
</TabPanel>
</TabPanel> : null}
{settings
&& settings
.filter(o => ['variable', 'formula'].includes(o.name)).length > 0
? <TabPanel header={__('Calculation', 'gepafin')}>
? <TabPanel header={__('Calcolo', 'gepafin')}>
{settings
? settings
.filter(o => ['variable', 'formula'].includes(o.name))
.map((o) => <ElementSetting
key={o.name}
setting={o}
bandoStatus={bandoStatus}
callStatus={callStatus}
changeFn={onChange}
updateDataFn={onUpdateOptions}/>)
: null}
</TabPanel> : null}
</TabView>
<Button label={__('Salva', 'gepafin')} onClick={saveSettings}/>
<div style={{display: 'flex', gap: '0.5rem'}}>
<Button label={__('Annulla', 'gepafin')} onClick={closeSettingsFn} severity="danger"/>
<Button label={__('Salva', 'gepafin')} onClick={saveSettings}/>
</div>
</div>
: null
)

View File

@@ -13,7 +13,7 @@ import BuilderElementSettings from '../BuilderElementSettings';
import BuilderDropzone from '../BuilderDropzone';
import BlockingOverlay from '../../../../components/BlockingOverlay';
const FormBuilder = ({ bandoStatus }) => {
const FormBuilder = ({ callStatus, context }) => {
const elements = useStore().main.formElements();
const elementItems = useStore().main.elementItems();
const activeElement = useStore().main.activeElement();
@@ -27,10 +27,10 @@ const FormBuilder = ({ bandoStatus }) => {
id={field.id}
label={field.label}
name={field.name}
bandoStatus={bandoStatus}
callStatus={callStatus}
/>
)
}, [bandoStatus]);
}, [callStatus]);
const renderItem = useCallback((item) => {
return (
@@ -56,9 +56,11 @@ const FormBuilder = ({ bandoStatus }) => {
return (
<>
<Sidebar visible={!isEmpty(activeElement)} onHide={closeSettings} className="formBuilder__elementSettings">
<Sidebar visible={!isEmpty(activeElement)} onHide={closeSettings} dismissable={false} className="formBuilder__elementSettings">
<h2>{__('Impostazioni del campo modulo', 'gepafin')}</h2>
{!isEmpty(activeElement) ? <BuilderElementSettings closeSettingsFn={closeSettings} bandoStatus={bandoStatus}/> : null}
{!isEmpty(activeElement)
? <BuilderElementSettings closeSettingsFn={closeSettings} callStatus={callStatus} context={context}/>
: null}
</Sidebar>
<div className="formBuilder">
<div className="formBuilder__main">

View File

@@ -24,7 +24,7 @@ import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import BandoService from '../../service/bando-service';
// TODO temp data
import { elementItems } from '../../tempData';
//import { elementItems } from '../../tempData';
const BandoFormsEdit = () => {
const { id, formId } = useParams();
@@ -215,8 +215,8 @@ const BandoFormsEdit = () => {
const getElementItemsCallback = (data) => {
if (data.status === 'SUCCESS') {
storeSet.main.elementItems(elementItems.sort((a, b) => a.sortOrder - b.sortOrder));
//storeSet.main.elementItems(data.data.sort((a, b) => a.sortOrder - b.sortOrder));
//storeSet.main.elementItems(elementItems.sort((a, b) => a.sortOrder - b.sortOrder));
storeSet.main.elementItems(data.data.sort((a, b) => a.sortOrder - b.sortOrder));
}
storeSet.main.unsetAsyncRequest();
}
@@ -317,7 +317,7 @@ const BandoFormsEdit = () => {
<div className="appPageSection">
<DndProvider backend={HTML5Backend}>
<FormBuilder bandoStatus={bandoStatus}/>
<FormBuilder callStatus={bandoStatus} context="application"/>
</DndProvider>
</div>