- added bando preview page;
- added bando form preview;
This commit is contained in:
@@ -13,7 +13,7 @@ const BandoEditFormActions = ({ openPreview, openPreviewEvaluation }) => {
|
||||
label={__('Salva bozza', 'gepafin')} icon="pi pi-save" iconPos="right"/>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={true}
|
||||
disabled={false}
|
||||
outlined
|
||||
onClick={openPreview}
|
||||
label={__('Anteprima beneficiario', 'gepafin')} icon="pi pi-eye" iconPos="right"/>
|
||||
|
||||
@@ -41,11 +41,11 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors },
|
||||
// end of temp data
|
||||
|
||||
const openPreview = () => {
|
||||
navigate('/bandi/preview/11');
|
||||
navigate('/bandi/11/preview');
|
||||
}
|
||||
|
||||
const openPreviewEvaluation = () => {
|
||||
navigate('/bandi/preview-evaluation/11');
|
||||
navigate('/bandi/11/preview-evaluation');
|
||||
}
|
||||
|
||||
useImperativeHandle(
|
||||
@@ -57,6 +57,9 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors },
|
||||
},
|
||||
getErrors: () => {
|
||||
return errors;
|
||||
},
|
||||
getValues: () => {
|
||||
return getValues();
|
||||
}
|
||||
}
|
||||
}, [errors, isValid]);
|
||||
@@ -106,6 +109,7 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors },
|
||||
fieldName="descriptionLong"
|
||||
label={__('Descrizione completa', 'gepafin')}
|
||||
control={control}
|
||||
rows={7}
|
||||
errors={errors}
|
||||
defaultValue={values['descriptionLong']}
|
||||
config={{
|
||||
@@ -199,6 +203,12 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors },
|
||||
}}
|
||||
label={__('FAQ', 'gepafin')}/>
|
||||
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
<div className="appPageSection__hr">
|
||||
<span>{__('Azioni', 'gepafin')}</span>
|
||||
</div>
|
||||
|
||||
<BandoEditFormActions
|
||||
openPreview={openPreview}
|
||||
openPreviewEvaluation={openPreviewEvaluation}/>
|
||||
|
||||
@@ -60,9 +60,12 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors },
|
||||
},
|
||||
getErrors: () => {
|
||||
return errors;
|
||||
},
|
||||
getValues: () => {
|
||||
return getValues();
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
}, [errors, isValid]);
|
||||
|
||||
useEffect(() => {
|
||||
trigger().then(() => clearErrors());
|
||||
@@ -84,7 +87,7 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors },
|
||||
validate: {
|
||||
minOneItem: v => !isEmpty(v) || __('Almeno 1 criterio di valutazione', 'gepafin'),
|
||||
noEmptyValue: v => v
|
||||
.filter(o => isEmpty(o.value) || isEmpty(o.score)).length === 0
|
||||
.filter(o => isEmpty(o.value) || isEmpty(o.score)).length === 0
|
||||
|| __('Non lasciare il valore vuoto', 'gepafin')
|
||||
}
|
||||
}}/>
|
||||
@@ -135,7 +138,12 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors },
|
||||
}}
|
||||
/>
|
||||
|
||||
<button type="button" onClick={() => console.log(getValues())}>Check</button>
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
<div className="appPageSection__hr">
|
||||
<span>{__('Azioni', 'gepafin')}</span>
|
||||
</div>
|
||||
|
||||
<BandoEditFormActions
|
||||
openPreview={openPreview}
|
||||
openPreviewEvaluation={openPreviewEvaluation}/>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
//import equal from 'fast-deep-equal';
|
||||
import { is, isNil } from 'ramda';
|
||||
|
||||
// components
|
||||
import getBandoLabel from '../../helpers/getBandoLabel';
|
||||
@@ -10,7 +12,9 @@ import { Steps } from 'primereact/steps';
|
||||
import BandoEditFormStep1 from './components/BandoEditFormStep1';
|
||||
import BandoEditFormStep2 from './components/BandoEditFormStep2';
|
||||
import { Messages } from 'primereact/messages';
|
||||
import { is, isNil } from 'ramda';
|
||||
|
||||
// TODO temp
|
||||
import { bandoTest } from '../../tempData';
|
||||
|
||||
const BandoEdit = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -18,8 +22,8 @@ const BandoEdit = () => {
|
||||
const [activeStep, setActiveStep] = useState(0)
|
||||
const [data, setData] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
||||
const [templates, setTemplate] = useState(null);
|
||||
//const [selectedTemplate, setSelectedTemplate] = useState(null);
|
||||
//const [templates, setTemplate] = useState(null);
|
||||
const formRef = useRef(null);
|
||||
const stepErrorMsgs = useRef(null);
|
||||
|
||||
@@ -31,7 +35,11 @@ const BandoEdit = () => {
|
||||
return false
|
||||
}
|
||||
stepErrorMsgs.current.clear();
|
||||
if (formRef.current.isFormValid()) {
|
||||
const isFormValid = formRef.current.isFormValid();
|
||||
//const values = formRef.current.getValues();
|
||||
//const diffData = equal(values, data);
|
||||
// TODO warn about unsaved data
|
||||
if (isFormValid) {
|
||||
goToStep(0)
|
||||
} else {
|
||||
stepErrorMsgs.current.show([
|
||||
@@ -50,7 +58,9 @@ const BandoEdit = () => {
|
||||
}
|
||||
stepErrorMsgs.current.clear();
|
||||
const isFormValid = formRef.current.isFormValid();
|
||||
console.log('before go to step 1:', isFormValid)
|
||||
//const values = formRef.current.getValues();
|
||||
//const diffData = equal(values, data);
|
||||
// TODO warn about unsaved data
|
||||
if (isFormValid) {
|
||||
goToStep(1);
|
||||
} else {
|
||||
@@ -81,47 +91,7 @@ const BandoEdit = () => {
|
||||
status: 'draft',
|
||||
name: ''
|
||||
}
|
||||
: {
|
||||
"name": "Innovazione digitale 2024",
|
||||
"confidi": false,
|
||||
"descriptionShort": "Supporto alle PMI per progetti di digitalizzazione e innovazione tecnologica.",
|
||||
"descriptionLong": "Il bando \"Innovazione Digitale 2024\" mira a sostenere le PMI nell'adozione di tecnologie digitali innovative. I progetti finanziabili includono l'implementazione di soluzioni di intelligenza artificiale, blockchain, IoT, e altre tecnologie avanzate che possono migliorare la competitività delle imprese.",
|
||||
"documentationRequested": "Documentazione richiesta*",
|
||||
"dates": [
|
||||
"2024-08-27T22:00:00.000Z",
|
||||
"2024-10-29T23:00:00.000Z"
|
||||
],
|
||||
"amount": 0,
|
||||
"amountMax": 0,
|
||||
"aimedTo": [
|
||||
{
|
||||
"id": 3,
|
||||
"value": "PMI con sede in Umbria",
|
||||
"status": "existing"
|
||||
}
|
||||
],
|
||||
"faq": [
|
||||
{
|
||||
"id": 2,
|
||||
"question": "Question 1?",
|
||||
"answer": "Lorem ipsum dolor",
|
||||
"visible": true,
|
||||
"status": "existing"
|
||||
}
|
||||
],
|
||||
"documentation": [
|
||||
/*{
|
||||
createdDate: "2024-08-23T12:40:47.700350791",
|
||||
description: null,
|
||||
filePath: "https://mementoresources.s3.eu-west-1.amazonaws.com/gepafin/SCR-20240820-kiwn.pdf",
|
||||
id: 10,
|
||||
name: "SCR-20240820-kiwn.pdf",
|
||||
updatedDate: "2024-08-23T12:40:47.700373894"
|
||||
}*/
|
||||
],
|
||||
status: 'draft',
|
||||
id: 11
|
||||
}
|
||||
: bandoTest;
|
||||
|
||||
if (bandoId === 0) {
|
||||
setData(data);
|
||||
@@ -134,11 +104,11 @@ const BandoEdit = () => {
|
||||
|
||||
setData(data);
|
||||
|
||||
const templates = [
|
||||
/*const templates = [
|
||||
{ name: 'Il mio template', value: 22 },
|
||||
{ name: 'Template #11', value: 11 },
|
||||
];
|
||||
setTemplate(templates);
|
||||
setTemplate(templates);*/
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
@@ -4,21 +4,34 @@ import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
// components
|
||||
import { Button } from 'primereact/button';
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
|
||||
const BandoForms = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate()
|
||||
const [templates, setTemplate] = useState(null);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
||||
//const [data, setData] = useState({});
|
||||
//const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const doCreateNewForm = () => {
|
||||
navigate('/bandi/11/forms/new');
|
||||
navigate(`/bandi/${id}/forms/new`);
|
||||
}
|
||||
|
||||
const goToEditBando = () => {
|
||||
navigate(`/bandi/${id}`);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const parsed = parseInt(id)
|
||||
const bandoId = !isNaN(parsed) ? parsed : 0;
|
||||
|
||||
const templates = [
|
||||
{ name: 'Il mio template', value: 22 },
|
||||
{ name: 'Template #11', value: 11 },
|
||||
];
|
||||
setTemplate(templates);
|
||||
|
||||
// TODO
|
||||
}, [id]);
|
||||
|
||||
@@ -33,11 +46,53 @@ const BandoForms = () => {
|
||||
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
<div className="appPageSection">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={doCreateNewForm}
|
||||
label={__('Crea/modifica form', 'gepafin')}/>
|
||||
<div className="appPage__content">
|
||||
<div className="appPageSection">
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={goToEditBando}
|
||||
label={__('Modifica bando', 'gepafin')}
|
||||
icon="pi pi-arrow-left" iconPos="left"/>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder disabled">
|
||||
<h2>{__('Usa un template', 'gepafin')}</h2>
|
||||
<div className="row">
|
||||
<p>{__('Scegli tra i template predefiniti e personalizzali', 'gepafin')}</p>
|
||||
<Dropdown
|
||||
id="template"
|
||||
disabled={true}
|
||||
value={selectedTemplate}
|
||||
onChange={(e) => setSelectedTemplate(e.value)}
|
||||
options={templates}
|
||||
optionLabel="name"
|
||||
placeholder={__('Seleziona template', 'gepafin')}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Crea un nuovo Form da Zero', 'gepafin')}</h2>
|
||||
<div className="row">
|
||||
<p>{__('Inizia con un form completamente vuoto e personalizzabil', 'gepafin')}</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={doCreateNewForm}
|
||||
label={__('Crea form', 'gepafin')}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder disabled">
|
||||
<h2>{__('Modifica Form esistente', 'gepafin')}</h2>
|
||||
<div className="row">
|
||||
<p>{__('Continua a lavorare su un form precedentemente salvato', 'gepafin')}</p>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={true}
|
||||
onClick={doCreateNewForm}
|
||||
label={__('Modifica', 'gepafin')}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
26
src/pages/BandoFormsEdit/components/BuilderDropzone/index.js
Normal file
26
src/pages/BandoFormsEdit/components/BuilderDropzone/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { useDrop } from 'react-dnd';
|
||||
import { ItemTypes } from '../ItemTypes';
|
||||
|
||||
// store
|
||||
import { storeSet } from '../../../../store';
|
||||
|
||||
const BuilderDropzone = () => {
|
||||
const dropzoneRef = useRef();
|
||||
|
||||
const [, drop] = useDrop({
|
||||
accept: ItemTypes.FIELD,
|
||||
hover(item, monitor) {
|
||||
storeSet.main.moveElement(-1, 0, item);
|
||||
item.index = 0;
|
||||
},
|
||||
});
|
||||
|
||||
drop(dropzoneRef);
|
||||
|
||||
return (
|
||||
<div ref={dropzoneRef} className="dropzone"></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BuilderDropzone;
|
||||
@@ -7,8 +7,10 @@ import { storeSet } from '../../../../store';
|
||||
|
||||
// components
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import BuilderElementProperLabel from '../BuilderElementProperLabel';
|
||||
|
||||
const BuilderElement = ({ id, name, label, index, move }) => {
|
||||
const BuilderElement = ({ id, name, label, index }) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
const [{ handlerId }, drop] = useDrop({
|
||||
@@ -74,17 +76,31 @@ const BuilderElement = ({ id, name, label, index, move }) => {
|
||||
}),
|
||||
});
|
||||
|
||||
const move = (dragIndex, hoverIndex, item) => {
|
||||
storeSet.main.moveElement(dragIndex, hoverIndex, item);
|
||||
}
|
||||
|
||||
const openSettings = (id) => {
|
||||
storeSet.main.activeElement(id);
|
||||
}
|
||||
|
||||
const remove = (id) => {
|
||||
storeSet.main.removeElement(id);
|
||||
}
|
||||
|
||||
const opacity = isDragging ? 0 : 1;
|
||||
drag(drop(ref));
|
||||
|
||||
return (
|
||||
<div ref={ref} className="formBuilder__element" style={{ opacity }} data-handler-id={handlerId}>
|
||||
{label}
|
||||
<Button icon="pi pi-cog" onClick={() => openSettings(id)} outlined />
|
||||
<div className="meta">
|
||||
<Tag value={name} severity="info"/>
|
||||
<BuilderElementProperLabel id={id} defaultLabel={label}/>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<Button icon="pi pi-cog" onClick={() => openSettings(id)} outlined severity="info" />
|
||||
<Button icon="pi pi-trash" onClick={() => remove(id)} outlined severity="danger" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ItemTypes } from '../ItemTypes';
|
||||
import uniqid from '../../../../helpers/uniqid';
|
||||
|
||||
|
||||
const BuilderElementItem = ({ dbId, name, label, move }) => {
|
||||
const BuilderElementItem = ({ dbId, name, label }) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
const [{ isDragging }, drag] = useDrag(() => ({
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { head } from 'ramda';
|
||||
|
||||
// store
|
||||
import { useStore } from '../../../../store';
|
||||
|
||||
const BuilderElementProperLabel = ({ id, defaultLabel }) => {
|
||||
const elements = useStore().main.formElements();
|
||||
const [label, setLabel] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const element = head(elements.filter(o => o.id === id));
|
||||
const setting = head(element.settings.filter(o => o.name === 'label'));
|
||||
if (setting.value) {
|
||||
setLabel(setting.value);
|
||||
} else {
|
||||
setLabel(defaultLabel);
|
||||
}
|
||||
}, [elements]);
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
export default BuilderElementProperLabel;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { head } from 'ramda';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { klona } from 'klona';
|
||||
|
||||
// store
|
||||
import { storeSet, useStore } from '../../../../store';
|
||||
|
||||
// components
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tag } from 'primereact/tag';
|
||||
|
||||
const BuilderElementSettings = () => {
|
||||
const elements = useStore().main.elements();
|
||||
const activeElement = useStore().main.activeElement();
|
||||
const [activeElementData, setActiveElementData] = useState({});
|
||||
const [settings, setSettings] = useState([]);
|
||||
|
||||
const onChange = (value, name) => {
|
||||
const newSettings = settings
|
||||
.map(o => {
|
||||
if (o.name === name) {
|
||||
o.value = value;
|
||||
}
|
||||
|
||||
return o;
|
||||
});
|
||||
|
||||
setSettings(newSettings);
|
||||
}
|
||||
|
||||
const saveSettings = () => {
|
||||
activeElementData.settings = settings;
|
||||
const newElements = elements.map(o => o.id === activeElementData.id ? activeElementData : o);
|
||||
storeSet.main.elements(newElements);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const chosen = head(elements.filter(o => o.id === activeElement));
|
||||
if (chosen) {
|
||||
setActiveElementData(klona(chosen));
|
||||
setSettings(klona(chosen.settings));
|
||||
} else {
|
||||
setActiveElementData({});
|
||||
setSettings([]);
|
||||
}
|
||||
}, [activeElement])
|
||||
|
||||
return (activeElementData
|
||||
? <div className="formElementSettings">
|
||||
<Tag value={activeElementData.name} severity="info"/>
|
||||
{settings
|
||||
? settings.map((o) => <div className="formElementSettings__field" key={o.name}>
|
||||
<label htmlFor={o.name}>{o.name}</label>
|
||||
<InputText id={o.name} aria-describedby={`${o.name}-help`}
|
||||
value={o.value}
|
||||
onChange={(e) => onChange(e.target.value, o.name)}/>
|
||||
</div>) : null}
|
||||
|
||||
<Button label={__('Salva', 'gepafin')} onClick={saveSettings}/>
|
||||
</div>
|
||||
: null
|
||||
)
|
||||
}
|
||||
|
||||
export default BuilderElementSettings;
|
||||
@@ -1,38 +1,22 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { head, isEmpty } from 'ramda';
|
||||
import { isEmpty } from 'ramda';
|
||||
|
||||
// store
|
||||
import { storeGet, storeSet, useStore } from '../../../../store';
|
||||
import { storeSet, useStore } from '../../../../store';
|
||||
|
||||
// components
|
||||
import BuilderElement from '../BuilderElement';
|
||||
import BuilderElementItem from '../BuilderElementItem';
|
||||
import { Sidebar } from 'primereact/sidebar';
|
||||
import BuilderElementSettings from '../BuilderElementSettings';
|
||||
import BuilderDropzone from '../BuilderDropzone';
|
||||
|
||||
const FormBuilder = () => {
|
||||
const [fields, setFields] = useState([]);
|
||||
const [items, setItems] = useState([]);
|
||||
const elements = useStore().main.formElements();
|
||||
const elementItems = useStore().main.elementItems();
|
||||
const activeElement = useStore().main.activeElement();
|
||||
|
||||
const moveField = useCallback((dragIndex, hoverIndex, item) => {
|
||||
setFields((prevFields) => {
|
||||
if (dragIndex === -1) {
|
||||
const configs = storeGet.main.elementItems();
|
||||
const itemCfg = head(configs.filter(o => o.id === item.dbId));
|
||||
const newItem = {
|
||||
...itemCfg,
|
||||
id: item.id,
|
||||
dbId: item.dbId
|
||||
}
|
||||
return prevFields.toSpliced(hoverIndex, 0, newItem);
|
||||
} else {
|
||||
let newFields = prevFields.toSpliced(dragIndex, 1);
|
||||
return newFields.toSpliced(hoverIndex, 0, prevFields[dragIndex]);
|
||||
}
|
||||
})
|
||||
}, []);
|
||||
|
||||
const renderField = useCallback((field, index) => {
|
||||
return (
|
||||
<BuilderElement
|
||||
@@ -41,26 +25,17 @@ const FormBuilder = () => {
|
||||
id={field.id}
|
||||
label={field.label}
|
||||
name={field.name}
|
||||
move={moveField}
|
||||
/>
|
||||
)
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const elements = storeGet.main.elements();
|
||||
const elementItems = storeGet.main.elementItems();
|
||||
setFields(elements);
|
||||
setItems(elementItems);
|
||||
}, [])
|
||||
|
||||
const renderItem = useCallback((item, index) => {
|
||||
const renderItem = useCallback((item) => {
|
||||
return (
|
||||
<BuilderElementItem
|
||||
key={item.id}
|
||||
dbId={item.id}
|
||||
label={item.label}
|
||||
name={item.name}
|
||||
move={moveField}
|
||||
/>
|
||||
)
|
||||
}, []);
|
||||
@@ -73,21 +48,21 @@ const FormBuilder = () => {
|
||||
<>
|
||||
<Sidebar visible={!isEmpty(activeElement)} onHide={closeSettings} className="formBuilder__elementSettings">
|
||||
<h2>{__('Impostazioni del campo modulo', 'gepafin')}</h2>
|
||||
<p>
|
||||
Form fields here
|
||||
</p>
|
||||
{!isEmpty(activeElement) ? <BuilderElementSettings/> : null}
|
||||
</Sidebar>
|
||||
<div className="formBuilder">
|
||||
<div className="formBuilder__main">
|
||||
<h2>{__('Trascina qui gli elementi del Form', 'gepafin')}</h2>
|
||||
<div className="formBuilder__content">
|
||||
{fields.map((field, i) => renderField(field, i))}
|
||||
{!isEmpty(elements)
|
||||
? elements.map((field, i) => renderField(field, i))
|
||||
: <BuilderDropzone/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="formBuilder__aside">
|
||||
<h2>{__('Elementi del Form', 'gepafin')}</h2>
|
||||
<ul className="formBuilder__list">
|
||||
{items.map((item) => renderItem(item))}
|
||||
{elementItems.map((item) => renderItem(item))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { DndProvider } from 'react-dnd'
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { klona } from 'klona';
|
||||
|
||||
// store
|
||||
import { storeSet } from '../../store';
|
||||
import { storeSet, storeGet } from '../../store';
|
||||
|
||||
// components
|
||||
import FormBuilder from './components/FormBuilder';
|
||||
import { Button } from 'primereact/button';
|
||||
|
||||
// TODO temp
|
||||
import { formData, elementItems } from '../../tempData';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
|
||||
const BandoFormsEdit = () => {
|
||||
const { id, formId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [formName, setFormName] = useState('');
|
||||
|
||||
const goBack = () => {
|
||||
navigate('/bandi/11/forms');
|
||||
navigate(`/bandi/${id}/forms`);
|
||||
}
|
||||
|
||||
const doSave = () => {
|
||||
console.log('doSave');
|
||||
console.log('doSave', storeGet.main.formElements());
|
||||
}
|
||||
|
||||
const openPreview = () => {
|
||||
console.log('openPreview');
|
||||
navigate(`/bandi/${id}/forms/${formId}/preview`);
|
||||
}
|
||||
|
||||
const doPublish = () => {
|
||||
@@ -39,38 +45,9 @@ const BandoFormsEdit = () => {
|
||||
//const bandoFormId = !isNaN(parsedFormId) ? parsedFormId : 0;
|
||||
|
||||
// 1. TODO get builder content data from API
|
||||
const elements = [
|
||||
{
|
||||
id: 'a123456',
|
||||
name: 'textinput',
|
||||
dbId: 1,
|
||||
label: 'Full Name',
|
||||
},
|
||||
{
|
||||
id: 'a456789',
|
||||
name: 'textarea',
|
||||
dbId: 2,
|
||||
label: 'Bio',
|
||||
}
|
||||
]
|
||||
const elementItems = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'textinput',
|
||||
label: 'Text Input'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'textarea',
|
||||
label: 'Text Area'
|
||||
},
|
||||
{
|
||||
id: 3, // DB id
|
||||
name: 'piva',
|
||||
label: 'P.IVA'
|
||||
},
|
||||
]
|
||||
storeSet.main.elements(elements);
|
||||
storeSet.main.formLabel(formData.label);
|
||||
const elements = klona(formData.content);
|
||||
storeSet.main.formElements(elements);
|
||||
storeSet.main.elementItems(elementItems);
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -87,9 +64,21 @@ const BandoFormsEdit = () => {
|
||||
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
<div className="appForm__field">
|
||||
<label htmlFor="label">{__('Form label', 'gepafin')}</label>
|
||||
<InputText
|
||||
id="label"
|
||||
value={formName}
|
||||
placeholder={__('Nome della forma', 'gepafin')}
|
||||
onChange={(e) => setFormName(e.target.value)}
|
||||
aria-describedby="label-help"/>
|
||||
</div>
|
||||
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
<div className="appPageSection">
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
{!isLoading ? <FormBuilder/>: null}
|
||||
{!isLoading ? <FormBuilder/> : null}
|
||||
</DndProvider>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +95,6 @@ const BandoFormsEdit = () => {
|
||||
outlined
|
||||
label={__('Salva progressi', 'gepafin')} icon="pi pi-save" iconPos="right"/>
|
||||
<Button
|
||||
disabled={true}
|
||||
outlined
|
||||
onClick={openPreview}
|
||||
label={__('Visualizza Anteprima Beneficiario', 'gepafin')} icon="pi pi-image" iconPos="right"/>
|
||||
|
||||
121
src/pages/BandoFormsPreview/index.js
Normal file
121
src/pages/BandoFormsPreview/index.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { bandoTest, formData } from '../../tempData';
|
||||
|
||||
// tools
|
||||
import getNumberWithCurrency from '../../helpers/getNumberWithCurrency';
|
||||
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
|
||||
|
||||
// components
|
||||
import { Skeleton } from 'primereact/skeleton';
|
||||
import { Accordion } from 'primereact/accordion';
|
||||
import { AccordionTab } from 'primereact/accordion';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Button } from 'primereact/button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { is, isNil } from 'ramda';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
const BandoFormsPreview = () => {
|
||||
const { id, formId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [bando, setBando] = useState({});
|
||||
const [data, setData] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors, isValid },
|
||||
setValue,
|
||||
register,
|
||||
trigger,
|
||||
getValues,
|
||||
clearErrors
|
||||
} = useForm({defaultValues: {}, mode: 'onChange'});
|
||||
const values = getValues();
|
||||
|
||||
const onSubmit = (formData) => {
|
||||
console.log('onSubmit', formData);
|
||||
};
|
||||
|
||||
const closePreview = () => {
|
||||
navigate(`/bandi/${id}`);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
//const parsed = parseInt(id)
|
||||
//const bandoId = !isNaN(parsed) ? parsed : 0;
|
||||
|
||||
setTimeout(() => {
|
||||
const data = formData;
|
||||
setData(data);
|
||||
|
||||
const bandoData = bandoTest;
|
||||
setBando(bandoData);
|
||||
|
||||
setIsLoading(false)
|
||||
}, 3000);
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div className="appPage">
|
||||
{!isLoading
|
||||
? <div className="appPage__pageHeader">
|
||||
<h1>{sprintf(__('Domanda per il Bando: %s', 'gepafin'), bando.name)}</h1>
|
||||
</div>
|
||||
: <>
|
||||
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
|
||||
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
|
||||
</>}
|
||||
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
{!isLoading
|
||||
? <div className="appPage__content">
|
||||
<div className="appPageSection__preview">
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={closePreview}
|
||||
label={__('Chiudi Anteprima', 'gepafin')}
|
||||
icon="pi pi-arrow-left" iconPos="left"/>
|
||||
</div>
|
||||
|
||||
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
|
||||
{data.content.map(o => <FormField
|
||||
key={o.id}
|
||||
type={o.name}
|
||||
fieldName={`field_${o.id}`}
|
||||
label={o.label}
|
||||
control={control}
|
||||
errors={errors}
|
||||
defaultValue={values[`field_${o.id}`]}
|
||||
/>)}
|
||||
</form>
|
||||
|
||||
<div className="appPageSection__preview">
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={closePreview}
|
||||
label={__('Chiudi Anteprima', 'gepafin')}
|
||||
icon="pi pi-arrow-left" iconPos="left"/>
|
||||
</div>
|
||||
</div>
|
||||
: <>
|
||||
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
|
||||
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
|
||||
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
|
||||
<Skeleton width="100%" height="4rem" className="mb-8"></Skeleton>
|
||||
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
|
||||
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
|
||||
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
|
||||
<Skeleton width="100%" height="4rem"></Skeleton>
|
||||
</>}
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default BandoFormsPreview;
|
||||
@@ -1,56 +1,52 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Skeleton } from 'primereact/skeleton';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { bandoTest } from '../../tempData';
|
||||
|
||||
// tools
|
||||
import getNumberWithCurrency from '../../helpers/getNumberWithCurrency';
|
||||
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
|
||||
|
||||
// components
|
||||
import { Skeleton } from 'primereact/skeleton';
|
||||
import { Accordion } from 'primereact/accordion';
|
||||
import { AccordionTab } from 'primereact/accordion';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Button } from 'primereact/button';
|
||||
|
||||
const BandoView = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [data, setData] = useState({});
|
||||
const [newQuestion, setNewQuestion] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const closePreview = () => {
|
||||
navigate(`/bandi/${id}`);
|
||||
}
|
||||
|
||||
const scaricaBando = () => {
|
||||
|
||||
}
|
||||
|
||||
const scaricaModulistica = () => {
|
||||
|
||||
}
|
||||
|
||||
const submitQuestion = () => {
|
||||
|
||||
}
|
||||
|
||||
const saveToFavourites = () => {
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
//const parsed = parseInt(id)
|
||||
//const bandoId = !isNaN(parsed) ? parsed : 0;
|
||||
|
||||
setTimeout(() => {
|
||||
const data = {
|
||||
name: 'Bando Innovazione 2024',
|
||||
descriptionShort: 'Supporto alle PMI per progetti di digitalizzazione e innovazione tecnologica.',
|
||||
descriptionLong: 'Il bando "Innovazione Digitale 2024" mira a sostenere le PMI nell\'adozione di tecnologie digitali innovative. I progetti finanziabili includono l\'implementazione di soluzioni di intelligenza artificiale, blockchain, IoT, e altre tecnologie avanzate che possono migliorare la competitività delle imprese.',
|
||||
dates: [ "2024-08-20T22:00:00.000Z", "2024-08-28T22:00:00.000Z" ],
|
||||
amount: 10000000,
|
||||
amountMax: 2000,
|
||||
aimedTo: [
|
||||
{ id: 11, value: 'PMI con sede in Umbria' },
|
||||
{ id: 12, value: 'Almeno 2 anni di attività' },
|
||||
{ id: 15, value: 'Fatturato annuo non superiore a € 50 milioni' }
|
||||
],
|
||||
documentationRequested: 'Some text',
|
||||
threshold: 12,
|
||||
criteria: [
|
||||
{ id: 15, value: 'Innovatività del progetto', score: 9 },
|
||||
{ id: 16, value: 'Impatto sulla competitività dell\'azienda', score: 3 },
|
||||
{ id: 17, value: 'Sostenibilità economico-finanziaria', score: 5 }
|
||||
],
|
||||
faq: [
|
||||
{id: 2, question: 'È possibile presentare più di un progetto?', answer: 'No, ogni azienda può presentare un solo progetto per questo bando.'}
|
||||
],
|
||||
checklist: [
|
||||
{ id: 9, value: 'Requisiti di ammissibilità soddisfatti' },
|
||||
{ id: 21, value: 'Documentazione completa' }
|
||||
],
|
||||
docs: [
|
||||
{id: 12, url: '#', name: 'file_name'}
|
||||
],
|
||||
images: [
|
||||
{id: 15, url: '#', name: 'file_name'}
|
||||
],
|
||||
status: 'draft',
|
||||
id: 11,
|
||||
created: '2024-08-07T00:00:00+00:00'
|
||||
}
|
||||
const data = bandoTest;
|
||||
setData(data);
|
||||
setIsLoading(false)
|
||||
}, 3000);
|
||||
@@ -63,7 +59,7 @@ const BandoView = () => {
|
||||
<h1>{data.name}</h1>
|
||||
<p>
|
||||
{__('Data:', 'gepafin')}
|
||||
<span>{data.created}</span>
|
||||
<span>{getDateFromISOstring(data.createdDate)}</span>
|
||||
</p>
|
||||
</div>
|
||||
: <>
|
||||
@@ -74,11 +70,176 @@ const BandoView = () => {
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
{!isLoading
|
||||
? <>
|
||||
<div className="appPageSection">
|
||||
Preview beneficiario
|
||||
? <div className="appPage__content">
|
||||
<div className="appPageSection__preview">
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={closePreview}
|
||||
label={__('Chiudi Anteprima', 'gepafin')}
|
||||
icon="pi pi-arrow-left" iconPos="left"/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
<picture className="appPageSection__hero">
|
||||
<source srcSet={data.images[0] ? data.images[0].filePath : ''}/>
|
||||
<img src={data.images[0] ? data.images[0].filePath : ''} alt={data.name}/>
|
||||
</picture>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Descrizione breve', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<p>{data.descriptionShort}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__row">
|
||||
<div className="appPageSection__withBorder">
|
||||
<p className="appPageSection__pMeta">
|
||||
<span>{__('Importo totale', 'gepafin')}</span>
|
||||
<span>{getNumberWithCurrency(data.amount)}</span>
|
||||
</p>
|
||||
<p className="appPageSection__pMeta">
|
||||
<span>{__('Importo massimo per progetto', 'gepafin')}</span>
|
||||
<span>{getNumberWithCurrency(data.amountMax)}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<p className="appPageSection__pMeta">
|
||||
<span>{__('Data apertura', 'gepafin')}</span>
|
||||
<span></span>
|
||||
</p>
|
||||
<p className="appPageSection__pMeta">
|
||||
<span>{__('Data chiusura', 'gepafin')}</span>
|
||||
<span></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Descrizione dettagliata', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<p>{data.descriptionLong}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Requisiti di Partecipazione', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<ul>
|
||||
{data.aimedTo.map((o, i) => <li key={i}>
|
||||
{o.value}
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Documentazione Richiesta', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<p>{data.documentationRequested}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Criteri di Valutazione', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<ul>
|
||||
{data.criteria.map((o, i) => <li key={i}>
|
||||
{`${o.value} ${sprintf(__('(%d punti)'), o.score)}`}
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Allegati', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<ul>
|
||||
{data.documentation.map((o, i) => <li key={i}>
|
||||
<a href={o.filePath} target="_blank" rel="noreferrer">{o.name}</a>
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection">
|
||||
<h2>{__('FAQ', 'gepafin')}</h2>
|
||||
<Accordion>
|
||||
{data.faq.map((o, i) => <AccordionTab key={i} header={o.question}>
|
||||
<p>
|
||||
{o.answer}
|
||||
</p>
|
||||
</AccordionTab>)}
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection">
|
||||
<h2>{__('Non hai trovato la risposta che cercavi?', 'gepafin')}</h2>
|
||||
<div className="appForm__field">
|
||||
<label htmlFor="newQuestion">{__('Fai una domanda', 'gepafin')}</label>
|
||||
<InputTextarea
|
||||
id="newQuestion"
|
||||
rows={7}
|
||||
value={newQuestion}
|
||||
placeholder={__('Digita qui la tua domanda', 'gepafin')}
|
||||
onChange={(e) => setNewQuestion(e.target.value)}
|
||||
aria-describedby="newQuestion-help"/>
|
||||
<small id="newQuestion-help">
|
||||
{__('Riceverai una notifica quando ti risponderemo', 'gepafin')}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection">
|
||||
<h2>{__('Download Documenti', 'gepafin')}</h2>
|
||||
<div className="appPageSection__actions">
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={scaricaBando}
|
||||
label={__('Scarica Bando Completo', 'gepafin')}
|
||||
icon="pi pi-download" iconPos="right"/>
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={scaricaModulistica}
|
||||
label={__('Scarica Modulistica', 'gepafin')}
|
||||
icon="pi pi-download" iconPos="right"/>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={true}
|
||||
onClick={submitQuestion}
|
||||
label={__('Presenta Domanda', 'gepafin')}
|
||||
icon="pi pi-save" iconPos="right"/>
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
rounded
|
||||
disabled={true}
|
||||
onClick={saveToFavourites}
|
||||
label={__('Aggiungi a Preferiti', 'gepafin')}
|
||||
icon="pi pi-heart" iconPos="left"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__withBorder">
|
||||
<h2>{__('Contatti per Assistenza', 'gepafin')}</h2>
|
||||
<div className="row rowContent">
|
||||
<p>Email: bandi@gepafin.it</p>
|
||||
<p>Telefono: +39 075 123 4567</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appPageSection__preview">
|
||||
<Button
|
||||
type="button"
|
||||
outlined
|
||||
onClick={closePreview}
|
||||
label={__('Chiudi Anteprima', 'gepafin')}
|
||||
icon="pi pi-arrow-left" iconPos="left"/>
|
||||
</div>
|
||||
</div>
|
||||
: <>
|
||||
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
|
||||
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
|
||||
|
||||
Reference in New Issue
Block a user