- added bando preview page;

- added bando form preview;
This commit is contained in:
Vitalii Kiiko
2024-08-27 17:02:16 +02:00
parent 5095ed7365
commit 87684bc76b
37 changed files with 1235 additions and 246 deletions

6
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@emotion/styled": "11.13.0",
"@wordpress/i18n": "^5.5.0",
"@wordpress/react-i18n": "^4.5.0",
"deep-object-diff": "^1.1.9",
"dompurify": "3.1.6",
"fast-deep-equal": "^3.1.3",
"html-react-parser": "5.1.12",
@@ -6765,6 +6766,11 @@
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
},
"node_modules/deep-object-diff": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz",
"integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA=="
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",

View File

@@ -9,6 +9,7 @@
"@emotion/styled": "11.13.0",
"@wordpress/i18n": "^5.5.0",
"@wordpress/react-i18n": "^4.5.0",
"deep-object-diff": "^1.1.9",
"dompurify": "3.1.6",
"fast-deep-equal": "^3.1.3",
"html-react-parser": "5.1.12",

View File

@@ -8,6 +8,7 @@
flex-direction: column;
gap: 14px;
padding: 5px 0;
width: 100%;
label {
font-size: 14px;

View File

@@ -12,30 +12,6 @@
}
}
.appPageSection {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
color: var(--Black);
font-size: 21px;
font-style: normal;
font-weight: 600;
line-height: normal;
margin: 0 0 24px;
}
}
.appPageSection__table {
width: 100%;
}
.appTableHeader {
display: flex;
justify-content: space-between;
}
.appPage__pageHeader {
display: flex;
flex-direction: column;
@@ -67,6 +43,157 @@
padding: 24px 0;
}
.appPageSection__hr {
position: relative;
width: 100%;
padding: 0 0 0 30px;
&:before {
position: absolute;
width: 100%;
height: 1px;
content: '';
top: 50%;
left: 0;
z-index: 1;
background-color: #E5E7EB;
}
span {
font-size: 14px;
font-style: normal;
font-weight: 700;
line-height: normal;
padding: 0 10px;
background-color: white;
z-index: 9;
position: relative;
}
}
.appPage__content {
display: flex;
flex-direction: column;
gap: 48px;
}
.appPageSection {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
color: var(--global-textColor);
font-size: 21px;
font-style: normal;
font-weight: 600;
line-height: normal;
margin: 0 0 24px;
}
}
.appPageSection__withBorder {
display: flex;
flex-direction: column;
gap: 7px;
width: 100%;
padding: 17px;
border-radius: 6px;
border: 1px solid var(--card-borderColor-color);
&.disabled {
filter: grayscale(1);
opacity: 0.8;
}
h2 {
color: var(--global-textColor);
font-size: 21px;
font-style: normal;
font-weight: 600;
line-height: normal;
margin: 0;
}
.row {
display: flex;
gap: 1rem;
align-items: center;
padding: 5px 0;
&.rowContent {
padding: 17px 0;
}
p {
margin: 0;
flex: 1 1 100%;
}
ul {
padding-left: 1rem;
}
button {
flex: 0 0 auto;
}
}
}
.appPageSection__hero {
display: flex;
height: 172px;
width: 100%;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.appPageSection__preview {
padding: 17px 0;
background: repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
#f8d282 10px,
#f8d282 20px
);
.p-button {
background: white;
}
}
.appPageSection__row {
display: flex;
gap: 1rem;
}
.appPageSection__pMeta {
display: flex;
gap: 0.5rem;
justify-content: space-between;
span {
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
}
.appPageSection__table {
width: 100%;
}
.appTableHeader {
display: flex;
justify-content: space-between;
}
.appPageSection__actions {
display: flex;
gap: 24px;

View File

@@ -27,7 +27,14 @@
padding: 20px;
border: 1px #DDD;
background: var(--surface-50);
box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.10);
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.10);
.dropzone {
width: 100%;
height: 100%;
border-radius: 1rem;
border: 3px dashed #bdbdbd;
}
}
.formBuilder__element {
@@ -36,11 +43,23 @@
align-items: center;
gap: 1rem;
padding: 10px 20px;
border: 1px dashed var(--button-secondary-borderColor);
border: 1px solid var(--button-secondary-borderColor);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 21px;
.meta {
display: flex;
flex-direction: column;
gap: 5px;
align-items: flex-start;
}
.actions {
display: flex;
gap: 1rem;
}
}
.formBuilder__aside {
@@ -82,3 +101,16 @@
.formBuilder__elementSettings {
width: 40rem;
}
.formElementSettings {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.formElementSettings__field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

View File

@@ -12,8 +12,7 @@ body {
margin: 0;
font-family: 'Montserrat';
p, span, input, label:not(.p-error), textarea, a, li, h1, h2, h3, h4, h5, h6, div, th, td, button {
font-family: 'Montserrat';
p, span:not(.p-button-label, .p-button-icon, .p-badge), input, label:not(.p-error), textarea, a, li, h1, h2, h3, h4, h5, h6, div, th, td {
color: var(--global-textColor);
}
}
@@ -84,6 +83,21 @@ body {
> main {
flex: 1 1 auto;
box-sizing: border-box;
padding: 0 24px 50px;
padding: 0 24px 20px;
display: flex;
flex-direction: column;
> footer {
margin-top: auto;
padding: 100px 0 0;
p {
text-align: center;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
}
}
}

View File

@@ -46,6 +46,14 @@
}
}
.p-accordion {
width: 100%;
}
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: var(--theme-highlight-background);
color: var(--primary-text);
}
.mb-2 {

View File

@@ -3,10 +3,6 @@
gap: 14px;
align-items: flex-end;
.appForm__field {
width: 100%;
}
button {
flex: 0 0 auto;
}

View File

@@ -13,6 +13,8 @@
--button-secondary-borderColor: #C79807;
--button-secondary-background: var(--menu-borderColor);
--global-textColor: #4B5563;
--theme-highlight-background: #BADEBE;
--primary-text: #3B7C43;
--card-full-background-color-2: #EEC137;
--card-full-background-color-3: #FA8E42;

View File

@@ -4,14 +4,15 @@ import { Controller } from 'react-hook-form';
import { InputTextarea } from 'primereact/inputtextarea';
const TextArea = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null
}) => {
fieldName,
label,
control,
rows = 4,
errors,
defaultValue,
config = {},
infoText = null
}) => {
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
@@ -24,6 +25,7 @@ const TextArea = ({
rules={config}
render={({ field, fieldState }) => (
<InputTextarea id={field.name}
rows={rows}
{...field}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>

View File

@@ -48,6 +48,7 @@ const FormFieldRepeater = ({
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.value = e.value;
o.id = e.id;
}
return o;
})

View File

@@ -53,6 +53,7 @@ const FormFieldRepeaterCriteria = ({
if (i === index) {
o.value = targetedOption.value;
o.score = targetedOption.score;
o.id = targetedOption.id;
}
return o;
});
@@ -91,7 +92,7 @@ const FormFieldRepeaterCriteria = ({
useEffect(() => {
const storeFieldData = data[fieldName] ?? [];
const newData = storeFieldData.map(o => ({ ...o, status: o.id ? 'existing' : 'new' }))
const newData = storeFieldData.map(o => ({ ...o, status: o.id ? 'existing' : 'new' }));
setStateFieldData(newData);
setThreshold(data['threshold'])
register(fieldName, config)

View File

@@ -40,17 +40,6 @@ const FormFieldRepeaterFaq = ({
setStateFieldData([...stateFieldData, chosen]);
}
const onInputChange = (e, index) => {
const { value } = e.target;
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.value = value;
}
return o;
})
setStateFieldData(newData);
}
const addNewItem = () => {
const newItem = { id: 0, status: 'new', question: '', answer: '', visible: true };
setStateFieldData([...stateFieldData, newItem]);

View File

@@ -8,7 +8,7 @@ const UnsavedChangesDetector = ({ initialData, getValuesFn }) => {
const [initial] = useState(initialData);
const warnIfUnsavedChanges = useCallback((event) => {
const updatedData = getValuesFn()
const updatedData = getValuesFn();
const isEqual = equal(initial, updatedData);
if (!isEqual) {

View File

@@ -0,0 +1,6 @@
const getDateFromISOstring = (value, options = {}) => {
const optionsMerged = options || { day: '2-digit', month: '2-digit', year: 'numeric', dateStyle: 'short' }
return Intl.DateTimeFormat('it-IT', optionsMerged).format(new Date(value))
}
export default getDateFromISOstring;

View File

@@ -0,0 +1,5 @@
const getNumberFormatted = (value) => {
return Intl.NumberFormat('it-IT', { minimumFractionDigits: 2 }).format(value);
}
export default getNumberFormatted;

View File

@@ -0,0 +1,11 @@
const getNumberWithCurrency = (value, currency = 'EUR') => {
const formatter = Intl.NumberFormat('it-IT', {
style: 'currency',
currencyDisplay: 'symbol',
currency
})
return formatter.format(value)
}
export default getNumberWithCurrency;

View File

@@ -48,6 +48,10 @@ const DefaultLayout = ({ children }) => {
<AppSidebar/>
<main>
{children}
<footer>
<p>{__('© 2024 Gepafin. Tutti i diritti riservati.', 'gepafin')}</p>
</footer>
</main>
</div>
</div>

View File

@@ -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"/>

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"/>

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

View File

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

View File

@@ -11,17 +11,19 @@ import BandoEdit from './pages/BandoEdit';
import BandoView from './pages/BandoView';
import BandoFormsEdit from './pages/BandoFormsEdit';
import BandoForms from './pages/BandoForms';
import BandoFormsPreview from './pages/BandoFormsPreview';
const routes = () => (
<Routes>
<Route element={<ProtectedRoute/>}>
<Route path="/" element={<DefaultLayout><Dashboard/></DefaultLayout>}/>
<Route path="/bandi" element={<DefaultLayout><Bandi/></DefaultLayout>}/>
<Route path="/bandi/preview/:id" element={<DefaultLayout><BandoView/></DefaultLayout>}/>
<Route path="/bandi/preview-evaluation/:id" element={<DefaultLayout><BandoView/></DefaultLayout>}/>
<Route path="/bandi/:id" element={<DefaultLayout><BandoEdit/></DefaultLayout>}/>
<Route path="/bandi/:id/preview" element={<DefaultLayout><BandoView/></DefaultLayout>}/>
<Route path="/bandi/:id/preview-evaluation" element={<DefaultLayout><BandoView/></DefaultLayout>}/>
<Route path="/bandi/:id/forms" element={<DefaultLayout><BandoForms/></DefaultLayout>}/>
<Route path="/bandi/:id/forms/:formId" element={<DefaultLayout><BandoFormsEdit/></DefaultLayout>}/>
<Route path="/bandi/:id/forms/:formId/preview" element={<DefaultLayout><BandoFormsPreview/></DefaultLayout>}/>
</Route>
<Route exact path="/login" element={<Login/>}/>
{/*<Route exact path="/forgot-password" element={<ForgotPassword/>}/>*/}

View File

@@ -1,4 +1,4 @@
import { head } from 'ramda';
export const actionsAlpha = (set, get, api) => ({
setAsyncRequest: () => {
@@ -19,5 +19,28 @@ export const actionsBeta = (set, get, api) => ({
doLogout: () => {
set.userData({});
set.token('');
},
removeElement: (id) => {
const elements = get.formElements();
const newElements = elements.filter(o => o.id !== id);
set.formElements(newElements);
},
moveElement: (dragIndex, hoverIndex, item) => {
const prevFields = get.formElements();
if (dragIndex === -1) {
const configs = get.elementItems();
const itemCfg = head(configs.filter(o => o.id === item.dbId));
const newItem = {
...itemCfg,
id: item.id,
dbId: item.dbId
}
const newElements = prevFields.toSpliced(hoverIndex, 0, newItem);
set.formElements(newElements);
} else {
let newFields = prevFields.toSpliced(dragIndex, 1);
const newElements = newFields.toSpliced(hoverIndex, 0, prevFields[dragIndex]);
set.formElements(newElements);
}
}
});

View File

@@ -7,7 +7,9 @@ const initialStore = {
// bando form
bandoFormErrors: {},
// form builder
elements: [],
formId: '',
formLabel: '',
formElements: [],
elementItems: [],
activeElement: ''
}

330
src/tempData.js Normal file
View File

@@ -0,0 +1,330 @@
export const bandoTest = {
"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": 10000,
"amountMax": 2500,
"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"
}
],
status: 'draft',
id: 11,
createdDate: "2024-08-07T08:14:07.849Z",
updatedDate: "2024-08-07T08:14:07.849Z",
"documentation":[
{
"id":18,
"name":"SCR-20240820-kiwn.pdf",
"filePath":"https://mementoresources.s3.eu-west-1.amazonaws.com/gepafin/SCR-20240820-kiwn.pdf",
"description":null,
"createdDate":"2024-08-26T06:51:11.800799252",
"updatedDate":"2024-08-26T06:51:11.800826092"
}
],
"criteria":[
{
"id":null,
"value":"Innovatività del progetto",
"status":"new",
"score":9
},
{
"id":null,
"value":"Impatto sulla competitività dell'azienda",
"status":"new",
"score":3
},
{
"id":null,
"value":"Sostenibilità economico-finanziaria",
"status":"new",
"score":5
}
],
"threshold":11,
"images":[
{
"id":19,
"name":"photo-1618245318763-a15156d6b23c.avif",
"filePath":"https://mementoresources.s3.eu-west-1.amazonaws.com/gepafin/photo-1618245318763-a15156d6b23c.avif",
"description":null,
"createdDate":"2024-08-26T07:28:16.954763338",
"updatedDate":"2024-08-26T07:28:16.954843237"
}
],
"checklist":[
{
"id":null,
"value":"Innovatività del progetto",
"status":"new"
},
{
"id":null,
"value":"Some new check",
"status":"new"
},
{
"id":null,
"value":"Check #2",
"status":"new"
}
]
}
export const formData = {
id: 15,
label: 'La forma per Innovazione digitale 2024',
content: [
{
"id": "aec5ee1885",
"name": "textinput",
"label": "Text Input",
"settings": [
{
"name": "label",
"value": "Text input"
},
{
"name": "placeholder",
"value": "Placeholder text"
}
],
"validators": {
"isRequired": false,
"minLength": null,
"maxLength": null,
"pattern": null,
"custom": null
},
"dbId": 1
},
{
"id": "a730f1f4d0",
"name": "textarea",
"label": "Text Area",
"settings": [
{
"name": "label",
"value": "Text area"
},
{
"name": "placeholder",
"value": "Placeholder text"
}
],
"validators": {
"isRequired": false,
"minLength": null,
"maxLength": null,
"pattern": null,
"custom": null
},
"dbId": 2
},
{
"id": "aa8746a7c3",
"name": "textinput",
"label": "P.IVA",
"settings": [
{
"name": "label",
"value": "P.IVA"
},
{
"name": "placeholder",
"value": "Insert p.iva number"
}
],
"validators": {
"isRequired": true,
"minLength": null,
"maxLength": null,
"pattern": null,
"custom": "isValidVAT"
},
"dbId": 4
},
{
"id": "ae3dde17cd",
"name": "radio",
"label": "Radio Input",
"settings": [
{
"name": "label",
"value": "Radio input"
},
{
"name": "options",
"value": [
{
"name": "opt1",
"label": "Opt1"
}
]
}
],
"validators": {
"isRequired": false,
"min": null,
"max": null,
"custom": null
},
"dbId": 5
},
{
"id": "abf838016f",
"name": "textinput",
"label": "Number Input",
"settings": [
{
"name": "label",
"value": "Number"
},
{
"name": "placeholder",
"value": ""
}
],
"validators": {
"isRequired": false,
"min": null,
"max": null,
"pattern": null,
"custom": null
},
"dbId": 3
}
]
};
export const elementItems = [
{
id: 1,
name: 'textinput',
label: 'Text Input',
settings: [
{
name: "label",
value: "Text input"
},
{
name: "placeholder",
value: "Placeholder text"
}
],
validators: {
isRequired: false,
minLength: null,
maxLength: null,
pattern: null,
custom: null
}
},
{
id: 2,
name: 'textarea',
label: 'Text Area',
settings: [
{
name: "label",
value: "Text area"
},
{
name: "placeholder",
value: "Placeholder text"
}
],
validators: {
isRequired: false,
minLength: null,
maxLength: null,
pattern: null,
custom: null
}
},
{
id: 3,
name: 'textinput',
label: 'Number Input',
settings: [
{
name: "label",
value: "Number"
},
{
name: "placeholder",
value: ""
}
],
validators: {
isRequired: false,
min: null,
max: null,
pattern: null,
custom: null
}
},
{
id: 4,
name: 'textinput',
label: 'P.IVA',
settings: [
{
name: "label",
value: "P.IVA"
},
{
name: "placeholder",
value: "Insert p.iva number"
}
],
validators: {
isRequired: true,
minLength: null,
maxLength: null,
pattern: null,
custom: 'isValidVAT'
}
},
{
id: 5,
name: 'radio',
label: 'Radio Input',
settings: [
{
name: "label",
value: "Radio input"
},
{
name: "options",
value: [
{ name: "opt1", label: "Opt1" }
]
}
],
validators: {
isRequired: false,
min: null,
max: null,
custom: null
}
}
]

View File

@@ -4000,6 +4000,11 @@ deep-is@^0.1.3, deep-is@~0.1.3:
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deep-object-diff@^1.1.9:
version "1.1.9"
resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz"
integrity sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==
deepmerge@^4.2.2, deepmerge@^4.3.0:
version "4.3.1"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"