Files
bflows-bandi-fe/src/pages/BandoFlowEdit/index.js
Vitalii Kiiko 7dcf932819 - updated zustand and react libraries;
- added 'put in draft' btn;
2025-04-10 12:36:19 +02:00

592 lines
24 KiB
JavaScript

import React, { useEffect, useState, useCallback, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { isEmpty, head, pathOr } from 'ramda';
// store
import { storeSet } from '../../store';
// api
import FormsService from '../../service/forms-service';
import FlowService from '../../service/flow-service';
// tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
// components
import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown';
import { Messages } from 'primereact/messages';
import { confirmPopup, ConfirmPopup } from 'primereact/confirmpopup';
import { Toast } from 'primereact/toast';
const BandoFlowEdit = () => {
const { id } = useParams();
const navigate = useNavigate();
const [flowStructure, setFlowStructure] = useState({
initialForm: 0,
finalForm: 0,
flowData: [],
flowEdges: [],
chosenField: ''
});
const [forms, setForms] = useState([]);
const [formOptions, setFormOptions] = useState([]);
const [chosenMainFieldOptions, setChosenMainFieldOptions] = useState([]);
const [mainFieldSuboptions, setMainFieldSubOptions] = useState([]);
const [bandoStatus, setBandoStatus] = useState('');
const [isFlowAllowed, setIsFlowAllowed] = useState(true);
const flowMsgs = useRef(null);
const toast = useRef(null);
const itemRefs = useRef({});
const itemContainerRef = useRef(null);
const getBandoId = () => {
const parsed = parseInt(id)
return !isNaN(parsed) ? parsed : 0;
}
const goBack = () => {
const bandoId = getBandoId();
navigate(`/bandi/${bandoId}/forms`);
}
const confirmDelete = (event) => {
confirmPopup({
target: event.currentTarget,
message: __('Sei sicuro di reset questo flow?', 'gepafin'),
acceptLabel: __('Si', 'gepafin'),
icon: 'pi pi-info-circle',
defaultFocus: 'reject',
acceptClassName: 'p-button-danger',
accept: doDelete,
reject: () => {
}
});
};
const doDelete = () => {
if (flowMsgs.current) {
flowMsgs.current.clear();
}
setFlowStructure({
initialForm: 0,
finalForm: 0,
flowData: [],
flowEdges: [],
chosenField: ''
})
setIsFlowAllowed(false);
setChosenMainFieldOptions([]);
}
const updateInitialForm = useCallback((value) => {
const finalFormObj = head(forms.filter(o => o.id !== value));
if (forms.length === 2 && finalFormObj) {
setFlowStructure({
...flowStructure,
initialForm: value,
finalForm: finalFormObj.id
});
} else {
setFlowStructure({
...flowStructure,
initialForm: value
});
}
}, [flowStructure])
const updateFinalForm = useCallback((value) => {
const filtered = flowStructure.flowData.filter(o => o.formId === flowStructure.initialForm);
const flowEdges = buildFlowEdges(flowStructure.initialForm, value);
setFlowStructure({
...flowStructure,
flowEdges,
flowData: filtered,
finalForm: value
});
}, [flowStructure]);
const updateChosenField = useCallback((value) => {
setFlowStructure({
...flowStructure,
chosenField: value
});
}, [flowStructure]);
const addFlowData = useCallback((data) => {
const initial = flowStructure.flowData;
const exists = initial ? initial.filter(o => parseInt(o.formId) === parseInt(data.formId)) : [];
let final = [];
if (exists.length) {
final = initial.map(o => parseInt(o.formId) === parseInt(data.formId) ? data : o);
} else {
final = [...initial, data];
}
setFlowStructure({
...flowStructure,
flowData: final
});
}, [flowStructure]);
const updateItermediateForm = (value, formId) => {
const isUsed = flowStructure.flowData.map(o => o.chosenValue).filter(v => !isEmpty(v)).includes(value);
if (!isUsed) {
const data = {
formId: parseInt(formId),
chosenField: '',
chosenValue: value
}
addFlowData(data);
}
}
const displayChosenOptionValue = (id) => {
const suboptionId = pathOr('', ['chosenValue'], head(flowStructure.flowData.filter(f => parseInt(f.formId) === parseInt(id))));
return pathOr('', ['label'], head(mainFieldSuboptions.filter(o => o.name === suboptionId)));
}
const disabledOptionForIntermediateForm = (opt) => {
return flowStructure.flowData.map(o => o.chosenValue).filter(v => !isEmpty(v)).includes(opt.name);
}
const shoudDisableSaving = useCallback(() => {
const nonEmptyFlowItems = flowStructure.flowData.filter(o => isEmpty(o.chosenField)).filter(o => !isEmpty(o.chosenValue));
/*if (forms.length > 2) {
console.log('disable BTN:', nonEmptyFlowItems.length !== forms.length - 2, isEmpty(flowStructure.flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(flowStructure.initialForm), isEmpty(flowStructure.finalForm));
} else {
console.log('disable BTN (2 forms):', isEmpty(flowStructure.flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(flowStructure.initialForm), isEmpty(flowStructure.finalForm));
}*/
return forms.length > 2
? nonEmptyFlowItems.length !== forms.length - 2 || isEmpty(flowStructure.flowEdges) || 'PUBLISH' === bandoStatus
|| isEmpty(flowStructure.initialForm) || isEmpty(flowStructure.finalForm)
: isEmpty(flowStructure.flowEdges) || 'PUBLISH' === bandoStatus
|| isEmpty(flowStructure.initialForm) || isEmpty(flowStructure.finalForm);
}, [flowStructure, forms]);
const doSave = () => {
storeSet('setAsyncRequest');
const bandoId = getBandoId();
if (flowMsgs.current) {
flowMsgs.current.clear();
}
FlowService.createFlow(bandoId, flowStructure, getFlowCreateCallback, errGetFlowCreateCallback);
}
const getFlowCreateCallback = (data) => {
if (data.status === 'SUCCESS') {
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: __('Il flusso è stato aggiornato correttamente!', 'gepafin')
});
}
}
storeSet('unsetAsyncRequest');
}
const errGetFlowCreateCallback = (data) => {
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
const getFormsCallback = (data) => {
if (data.status === 'SUCCESS') {
setForms(data.data);
const formOptions = data.data.map(o => ({ label: o.label, value: o.id }))
setFormOptions([{ label: '', value: '' }, ...formOptions]);
const bandoId = getBandoId();
storeSet('setAsyncRequest');
FlowService.getFlow(bandoId, (resp) => getFlowCallback(resp, data.data), errGetFlowCallback);
}
storeSet('unsetAsyncRequest');
}
const errGetFormsCallback = (data) => {
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
const getFlowCallback = (data, forms) => {
if (data.status === 'SUCCESS' && data.data) {
const chosenFieldItem = head(data.data.flowData.filter(o => !isEmpty(o.chosenField)));
setBandoStatus(data.data.callStatus);
if (chosenFieldItem) {
setFlowStructure({
initialForm: data.data.initialForm,
finalForm: data.data.finalForm,
flowData: data.data.flowData,
flowEdges: data.data.flowEdges,
chosenField: chosenFieldItem.chosenField
});
const form = head(forms.filter(o => o.id === data.data.initialForm));
const relevantFields = form
? form.content
.filter(o => ['radio', 'select'].includes(o.name))
.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
return { value: o.id, label: label ? label.value : o.label };
})
: [];
setChosenMainFieldOptions(relevantFields);
const field = form ? head(form.content.filter(o => o.id === chosenFieldItem.chosenField)) : null;
if (field) {
const options = head(field.settings.filter(o => o.name === 'options'));
setMainFieldSubOptions(options.value);
}
} else {
setFlowStructure({
initialForm: data.data.initialForm,
finalForm: data.data.finalForm,
flowData: data.data.flowData,
flowEdges: data.data.flowEdges,
chosenField: ''
});
}
}
storeSet('unsetAsyncRequest');
}
const errGetFlowCallback = (data) => {
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
const setItemRef = (id, element) => {
itemRefs.current[id] = element;
};
const buildFlowEdges = (initialForm, finalForm) => {
let edges = [];
if (!isEmpty(initialForm) && !isEmpty(finalForm)) {
// eslint-disable-next-line
forms.map(o => {
const formId = String(o.id);
if (formId !== String(initialForm) && formId !== String(finalForm)) {
edges.push({
id: `${initialForm}->${formId}`,
source: String(initialForm),
target: formId,
type: 'smoothstep'
});
}
if (formId !== String(initialForm) && formId !== String(finalForm) && String(finalForm) !== '0') {
edges.push({
id: `${formId}->${finalForm}`,
source: formId,
target: String(finalForm),
type: 'smoothstep'
});
}
});
if (forms.length === 2 && initialForm && finalForm) {
edges.push({
id: `${initialForm}->${finalForm}`,
source: String(initialForm),
target: String(finalForm),
type: 'smoothstep'
});
}
}
return edges;
};
useEffect(() => {
const bandoId = getBandoId();
storeSet('setAsyncRequest');
FormsService.getFormsForCall(bandoId, getFormsCallback, errGetFormsCallback);
}, [id]);
useEffect(() => {
if (flowMsgs.current && forms.length < 2) {
flowMsgs.current.clear();
flowMsgs.current.show([
{
id: '1',
sticky: true, severity: 'info', summary: '',
detail: __('Devi creare almeno 2 form.', 'gepafin'),
closable: false
}
]);
} else {
flowMsgs.current.clear();
if (itemContainerRef.current) {
itemContainerRef.current.dispatchEvent(new Event('scroll'));
}
}
}, [forms]);
useEffect(() => {
const initialForm = flowStructure.initialForm;
const finalForm = flowStructure.finalForm;
const chosenField = flowStructure.chosenField;
if (!isEmpty(initialForm) && !isEmpty(finalForm)) {
const form = head(forms.filter(o => String(o.id) === String(initialForm)))
const relevantFields = form
? form.content
.filter(o => ['radio', 'select'].includes(o.name))
.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
return { value: o.id, label: label ? label.value : o.label };
})
: [];
setChosenMainFieldOptions([
{ label: isEmpty(relevantFields) ? __('Nessun scelta', 'gepafin') : '', value: '' },
...relevantFields]
);
if (forms.length === 2) {
setIsFlowAllowed(true);
}
//const flowEdges = buildFlowEdges(initialForm, finalForm);
if (!isEmpty(chosenField)) {
const field = form ? head(form.content.filter(o => o.id === chosenField)) : null;
let options = [];
if (field) {
options = head(field.settings.filter(o => o.name === 'options'));
}
if (field && options.value && options.value.length === forms.length - 2) {
setIsFlowAllowed(true);
const suboptions = [
{ label: __('Nessun scelta', 'gepafin'), name: '' },
...options.value
]
setMainFieldSubOptions(suboptions);
const data = {
formId: parseInt(initialForm),
chosenField: chosenField,
chosenValue: ''
}
addFlowData(data);
if (flowMsgs.current && !isEmpty(chosenField)) {
flowMsgs.current.clear();
}
} else {
setIsFlowAllowed(false);
let msg = 'Non è possibile creare il flusso. Il campo principale deve avere esattamente %s opzioni.';
if (forms.length - 2 === 1) {
msg = 'Non è possibile creare il flusso. Il campo principale deve avere esattamente %s opzioni.';
}
if (flowMsgs.current && !isEmpty(chosenField)) {
flowMsgs.current.clear();
flowMsgs.current.show([
{
id: '1',
sticky: true, severity: 'error', summary: '',
detail: sprintf(
__(msg, 'gepafin'),
forms.length - 2
),
closable: false
}
]);
}
}
}
}
}, [flowStructure.initialForm, flowStructure.finalForm, flowStructure.chosenField]);
const { initialForm = 0, finalForm = 0, flowData = [], chosenField = '' } = flowStructure;
const initialFormData = head(forms.filter(o => o.id === initialForm));
const finalFormData = head(forms.filter(o => o.id === finalForm));
const levelForms = forms.filter(o => o.id !== initialForm && o.id !== finalForm);
return (
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Gestisci flusso dei form', 'gepafin')}</h1>
<p>
{__('Scegli un form iniziale e li form finale e aggiungi i form intermedi per questo bando', 'gepafin')}
</p>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<div className="appForm__cols">
<div className="appForm__field">
<label htmlFor="initialForm">{__('Scegli form iniziale', 'gepafin')}</label>
<Dropdown
id="initialForm"
disabled={'PUBLISH' === bandoStatus}
value={initialForm}
onChange={(e) => updateInitialForm(e.value)}
optionDisabled={(opt) => finalForm === opt.value || isEmpty(opt.value)}
options={formOptions}
optionLabel="label"
optionValue="value"
placeholder={__('Scegli il form', 'gepafin')}/>
</div>
{forms.length > 2 && initialForm && chosenMainFieldOptions
? <div className="appForm__field">
<label htmlFor="chosenMainField">{__('Scegli il campo principale', 'gepafin')}</label>
<Dropdown
id="chosenMainField"
disabled={'PUBLISH' === bandoStatus}
value={chosenField}
onChange={(e) => updateChosenField(e.value)}
optionDisabled={(opt) => isEmpty(opt.value)}
options={chosenMainFieldOptions}
optionLabel="label"
optionValue="value"
placeholder={__('Scegli il campo', 'gepafin')}/>
</div> : null}
{(forms.length > 2 && chosenField && isFlowAllowed) || (forms.length === 2 && isFlowAllowed)
? <div className="appForm__field">
<label htmlFor="finalForm">{__('Scegli form finale', 'gepafin')}</label>
<Dropdown
id="finalForm"
disabled={'PUBLISH' === bandoStatus}
value={finalForm}
onChange={(e) => updateFinalForm(e.value)}
optionDisabled={(opt) => initialForm === opt.value || isEmpty(opt.value)}
options={formOptions}
optionLabel="label"
optionValue="value"
placeholder={__('Scegli il form', 'gepafin')}/>
</div> : null}
</div>
</div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
onClick={goBack}
outlined
label={__('Indietro', 'gepafin')} icon="pi pi-arrow-left" iconPos="left"/>
<Button
onClick={doSave}
disabled={shoudDisableSaving()}
label={__('Salva', 'gepafin')} icon="pi pi-save" iconPos="right"/>
</div>
</div>
<div className="appPageSection">
<Messages ref={flowMsgs}/>
{forms.length >= 2 && initialForm && finalForm && isFlowAllowed
? <div className="flowContainer" ref={itemContainerRef}>
<div className="flowContainerInner">
<div className="flowContainer__level initialLevel">
<div className="flowContainer__flowItem initialForm"
ref={(el) => initialForm ? setItemRef(initialForm, el) : null}>
<div className="flowContainer__flowItemInner">
<label htmlFor="chosenMainField">
{initialFormData.label}
</label>
</div>
</div>
</div>
{levelForms.length && initialForm && finalForm
? <div className="flowContainer__level intermediateLevel">
{levelForms.map((o, i) => <div key={o.id}
ref={(el) => setItemRef(o.id, el)}
className="flowContainer__flowItem levelForms">
<div className="flowContainer__flowItemInner">
<label htmlFor="chosenMainField">{o.label}</label>
<div className="flowContainer__flowItemContent">
{mainFieldSuboptions && !isEmpty(mainFieldSuboptions)
? 'PUBLISH' !== bandoStatus
? <Dropdown
id="initialForm"
value={pathOr('', ['chosenValue'], head(flowData.filter(f => f.formId === parseInt(o.id))))}
onChange={(e) => updateItermediateForm(e.value, o.id)}
options={mainFieldSuboptions}
optionDisabled={disabledOptionForIntermediateForm}
optionLabel="label"
optionValue="name"
placeholder={__('Scegli il valore', 'gepafin')}/>
:
<label>{displayChosenOptionValue(o.id)}</label>
: null}
</div>
</div>
</div>)}
{levelForms.length > 1
? <>
<div className="flowContainer__levelMaskEnd"></div>
<div className="flowContainer__levelMaskStart"></div>
</> : null}
</div> : null}
{forms.length >= 2 && initialForm && finalForm
? <div className="flowContainer__level finalLevel">
<div className="flowContainer__flowItem finalForm"
ref={(el) => finalForm ? setItemRef(finalForm, el) : null}>
<div className="flowContainer__flowItemInner">
<label htmlFor="chosenMainField">
{finalFormData.label}
</label>
</div>
</div>
</div>
: null}
</div>
</div> : null}
</div>
<div className="appPage__spacer"></div>
<Toast ref={toast}/>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
onClick={goBack}
outlined
label={__('Indietro', 'gepafin')} icon="pi pi-arrow-left" iconPos="left"/>
<Button
onClick={doSave}
disabled={shoudDisableSaving()}
label={__('Salva', 'gepafin')} icon="pi pi-save" iconPos="right"/>
</div>
<div className="appPageSection__actions">
<ConfirmPopup/>
<Button
onClick={confirmDelete}
disabled={'PUBLISH' === bandoStatus}
severity="warning"
label={__('Reset', 'gepafin')} icon="pi pi-refresh" iconPos="right"/>
</div>
</div>
</div>
)
}
export default BandoFlowEdit;