add bando page form;

This commit is contained in:
Vitalii Kiiko
2024-08-20 08:17:42 +02:00
parent 8616ae04b3
commit b4522f1580
14 changed files with 952 additions and 81 deletions

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { isNil } from 'ramda';
import { Calendar } from 'primereact/calendar';
const Datepicker = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null,
minDate = null,
maxDate = null
}) => {
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null}
</label>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<Calendar id={field.name}
value={field.value}
onChange={(e) => field.onChange(e.value)}
dateFormat="dd/mm/yy"
mask="99/99/9999"
showIcon
minDate={minDate} maxDate={maxDate}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
{infoText ? <small>{infoText}</small> : null}
</>)
}
export default Datepicker;

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { isNil } from 'ramda';
import { Calendar } from 'primereact/calendar';
const DatepickerRange = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null,
minDate = null,
maxDate = null
}) => {
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null}
</label>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<Calendar id={field.name}
value={field.value}
onChange={(e) => field.onChange(e.value)}
dateFormat="dd/mm/yy" mask="99/99/9999"
showIcon
minDate={minDate} maxDate={maxDate}
selectionMode="range" readOnlyInput hideOnRangeSelection
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
{infoText ? <small>{infoText}</small> : null}
</>)
}
export default DatepickerRange;

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { __ } from '@wordpress/i18n';
import { FileUpload } from 'primereact/fileupload';
const Fileupload = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null,
accept = 'image/*',
api = '/api/upload',
emptyText = __('Trascina qui il tuo file', 'gepafin'),
chooseLabel = __('Aggiungi immagine', 'gepafin')
}) => {
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null}
</label>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<FileUpload
id={field.name}
name={`${field.name}[]`}
url={api}
multiple
accept={accept}
maxFileSize={1000000}
emptyTemplate={<p>{emptyText}</p>}
chooseLabel={chooseLabel}
cancelLabel={__('Cancella', 'gepafin')}
uploadLabel={__('Carica', 'gepafin')}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
{infoText ? <small>{infoText}</small> : null}
{defaultValue ? <p>Uploaded:</p> : null}
{defaultValue}
</>)
}
export default Fileupload;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { InputNumber } from 'primereact/inputnumber';
const NumberInput = ({
fieldName,
label,
control,
errors,
defaultValue = 0,
config = {},
infoText = null,
inputgroup = false,
icon = null,
locale = 'it-IT',
minFractionDigits = 2,
step = 1,
min,
max
}) => {
const input = <Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<InputNumber inputId={field.name}
value={field.value}
onValueChange={(e) => field.onChange(e.value)}
min={min}
max={max}
locale={locale} minFractionDigits={minFractionDigits} step={step}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null}
</label>
{inputgroup
? <div className="p-inputgroup flex-1">
<span className="p-inputgroup-addon">
{icon}
</span>
{input}
</div>
: input}
{infoText ? <small>{infoText}</small> : null}
</>)
}
export default NumberInput;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { InputTextarea } from 'primereact/inputtextarea';
const TextArea = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null
}) => {
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null}
</label>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<InputTextarea id={field.name}
{...field}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
{infoText ? <small>{infoText}</small> : null}
</>)
}
export default TextArea;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { InputText } from 'primereact/inputtext';
const TextInput = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null,
inputgroup = false,
icon = null
}) => {
const input = <Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<InputText id={field.name}
{...field}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null}
</label>
{inputgroup
? <div className="p-inputgroup flex-1">
<span className="p-inputgroup-addon">
{icon}
</span>
{input}
</div>
: input}
{infoText ? <small>{infoText}</small> : null}
</>)
}
export default TextInput;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { isNil } from 'ramda';
import { classNames } from 'primereact/utils';
// components
import TextInput from './components/TextInput';
import TextArea from './components/TextArea';
import Datepicker from './components/Datepicker';
import DatepickerRange from './components/DatepickerRange';
import Fileupload from './components/Fileupload';
import NumberInput from './components/NumberInput';
const FormField = (props) => {
const fields = {
textinput: TextInput,
textarea: TextArea,
datepicker: Datepicker,
datepickerrange: DatepickerRange,
fileupload: Fileupload,
numberinput: NumberInput
}
const Comp = !isNil(fields[props.type]) ? fields[props.type] : null;
return (!isNil(Comp)
? <div className={classNames(['appForm__field', props.type])}>
<Comp {...props} />
</div>
: null
)
}
export default FormField;

View File

@@ -0,0 +1,108 @@
import React, { useRef, useEffect, useState } from 'react';
import { classNames } from 'primereact/utils';
import { __ } from '@wordpress/i18n';
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { Menu } from 'primereact/menu';
import { Dropdown } from 'primereact/dropdown';
const FormFieldRepeater = ({
data,
setDataFn,
fieldName,
options = [],
errors,
register,
label,
infoText
}) => {
const forMenu = useRef(null);
const [stateFieldData, setStateFieldData] = useState([]);
const menuItems = [
{
type: 'existing',
label: __('Esistente', 'gepafin'),
command: (data) => {
setStateFieldData([...stateFieldData, {id: null, value: '', status: data.item.type}]);
}
},
{
type: 'new',
label: __('Nuovo', 'gepafin'),
command: (data) => {
setStateFieldData([...stateFieldData, {id: null, value: '', status: data.item.type}]);
}
}
]
const removeItem = (index) => {
const newData = stateFieldData.toSpliced(index, 1);
setStateFieldData(newData);
}
const selectItem = (e, index) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.value = e.value;
}
return o;
})
setStateFieldData(newData);
}
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 properField = (item, i) => {
return item.status === 'new'
? <InputText value={item.value} onInput={(e) => onInputChange(e, i)}/>
: <Dropdown value={item.value}
onChange={(e) => selectItem(e, i)}
optionDisabled={(opt) => usedExistingValues.includes(opt.value)}
options={options} optionLabel="value"/>
}
const usedExistingValues = stateFieldData
.filter(o => o.status === 'existing')
.map(o => o.value);
useEffect(() => {
const storeFieldData = data[fieldName] ?? [];
const newData = storeFieldData.map(o => ({...o, status: o.id ? 'existing' : 'new'}))
setStateFieldData(newData);
register(fieldName)
}, [])
useEffect(() => {
setDataFn(fieldName, [...stateFieldData]);
}, [stateFieldData])
return (
<div className={classNames(['appForm__field', 'formfieldrepeater'])}>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}
</label>
{stateFieldData.map((o, i) => <div key={i} className={classNames('appForm__repeaterItem')}>
<div className="p-inputgroup flex-1">
{properField(o, i)}
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeItem(i)}/>
</div>
{o.status === 'new' && infoText ? <small>{infoText}</small> : null}
</div>)}
<Menu model={menuItems} popup ref={forMenu} id="aimedForMenu"/>
<Button type="button" iconPos="right" label={__('Aggiungi', 'gepafin')}
icon="pi pi-chevron-down" onClick={(event) => forMenu.current.toggle(event)}
aria-controls="aimedForMenu" aria-haspopup/>
</div>
)
}
export default FormFieldRepeater;

View File

@@ -0,0 +1,143 @@
import React, { useRef, useEffect, useState } from 'react';
import { classNames } from 'primereact/utils';
import { __ } from '@wordpress/i18n';
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { Menu } from 'primereact/menu';
import { Dropdown } from 'primereact/dropdown';
import { InputNumber } from 'primereact/inputnumber';
const FormFieldRepeaterCriteria = ({
data,
setDataFn,
fieldName,
options = [],
errors,
register,
label,
infoText
}) => {
const forMenu = useRef(null);
const [stateFieldData, setStateFieldData] = useState([]);
const menuItems = [
{
type: 'existing',
label: __('Esistente', 'gepafin'),
command: (data) => {
setStateFieldData([...stateFieldData, { id: null, value: '', status: data.item.type }]);
}
},
{
type: 'new',
label: __('Nuovo', 'gepafin'),
command: (data) => {
setStateFieldData([...stateFieldData, { id: null, value: '', status: data.item.type }]);
}
}
]
const removeItem = (index) => {
const newData = stateFieldData.toSpliced(index, 1);
setStateFieldData(newData);
}
const selectItem = (e, index) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.value = e.value;
}
return o;
})
setStateFieldData(newData);
}
const onInputChange = (value, index, name) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o[name] = value;
}
return o;
})
setStateFieldData(newData);
}
const properField = (item, i) => {
return item.status === 'new'
? <InputText value={item.value} onInput={(e) => onInputChange(e.target.value, i, 'value')}/>
: <Dropdown value={item.value}
onChange={(e) => selectItem(e, i)}
optionDisabled={(opt) => usedExistingValues.includes(opt.value)}
options={options} optionLabel="value"/>
}
const usedExistingValues = stateFieldData
.filter(o => o.status === 'existing')
.map(o => o.value);
useEffect(() => {
const storeFieldData = data[fieldName] ?? [];
const newData = storeFieldData.map(o => ({ ...o, status: o.id ? 'existing' : 'new' }))
setStateFieldData(newData);
register(fieldName)
}, [])
useEffect(() => {
setDataFn(fieldName, [...stateFieldData]);
}, [stateFieldData])
return (
<div className={classNames(['appForm__field', 'formfieldrepeater'])}>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}
</label>
{stateFieldData.map((o, i) => <div key={i} className={classNames("appForm__field", 'appForm__repeaterItem')}>
<div className="appForm__twoCols">
<div>
<label htmlFor="criterionTotal">{__('Punteggio Totale', 'gepafin')}</label>
<InputNumber inputId="criterionTotal"
value={o.total}
showButtons
onValueChange={(e) => onInputChange(e.value, i, 'total')}/>
</div>
<div>
<label htmlFor="criterionThreshold">{__('Punteggio minimo per lammissione', 'gepafin')}</label>
<InputNumber inputId="criterionThreshold"
value={o.threshold}
showButtons
onValueChange={(e) => onInputChange(e.value, i, 'threshold')}/>
</div>
</div>
<div className="appForm__twoCols">
<div>
<label>{__('Nome criterio di valutazione', 'gepafin')}</label>
<div className="p-inputgroup flex-1">
{properField(o, i)}
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeItem(i)}/>
</div>
{o.status === 'new' && infoText ? <small>{infoText}</small> : null}
</div>
<div>
<label htmlFor="criterionMin">{__('Punteggio minimo', 'gepafin')}</label>
<InputNumber inputId="criterionMin"
value={o.min}
showButtons
onValueChange={(e) => onInputChange(e.value, i, 'min')}/>
</div>
<div>
<label htmlFor="criterionMax">{__('Punteggio massimo', 'gepafin')}</label>
<InputNumber inputId="criterionMax"
value={o.max}
showButtons
onValueChange={(e) => onInputChange(e.value, i, 'max')}/>
</div>
</div>
</div>)}
<Menu model={menuItems} popup ref={forMenu} id="aimedForMenu"/>
<Button type="button" iconPos="right" label={__('Aggiungi', 'gepafin')}
icon="pi pi-chevron-down" onClick={(event) => forMenu.current.toggle(event)}
aria-controls="aimedForMenu" aria-haspopup/>
</div>
)
}
export default FormFieldRepeaterCriteria;

View File

@@ -0,0 +1,122 @@
import React, { useRef, useEffect, useState } from 'react';
import { classNames } from 'primereact/utils';
import { __ } from '@wordpress/i18n';
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown';
import { Accordion, AccordionTab } from 'primereact/accordion';
import { ToggleButton } from 'primereact/togglebutton';
const FormFieldRepeaterFaq = ({
data,
setDataFn,
fieldName,
options = [],
errors,
register,
label,
infoText
}) => {
const [stateFieldData, setStateFieldData] = useState([]);
const removeItem = (index) => {
const newData = stateFieldData.toSpliced(index, 1);
setStateFieldData(newData);
}
const selectItem = () => {
setStateFieldData([...stateFieldData, { id: 0, status: 'new', question: '', answer: '', visible: true }]);
}
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 setChecked = (e, index) => {
e.preventDefault();
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.visible = e.value;
}
return o;
});
setStateFieldData(newData);
}
const editItem = (e, index) => {
e.stopPropagation();
console.log('editItem')
}
const usedExistingValues = stateFieldData
.filter(o => o.status === 'existing')
.map(o => o.question);
useEffect(() => {
const storeFieldData = data[fieldName] ?? [];
const newData = storeFieldData.map(o => ({ ...o, status: o.id ? 'existing' : 'new' }))
setStateFieldData(newData);
register(fieldName)
}, [])
useEffect(() => {
setDataFn(fieldName, [...stateFieldData]);
}, [stateFieldData])
return (
<div className={classNames(['appForm__field', 'formfieldrepeater'])}>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}
</label>
<div className="appForm__faqHeaderControls">
<Button type="button" iconPos="left" label={__('Aggiungi', 'gepafin')}
icon="pi pi-plus" onClick={addNewItem}/>
<Dropdown onChange={(e) => selectItem(e)}
optionDisabled={(opt) => usedExistingValues.includes(opt.value)}
options={options} optionLabel="question"/>
</div>
<Accordion activeIndex={0}>
{stateFieldData.map((o, i) => <AccordionTab key={i}
header={
<div className="appForm__faqTab">
<div className="appForm__faqTabItem">
<ToggleButton
onIcon="pi pi-eye"
offIcon="pi pi-eye-slash"
onLabel=""
offLabel=""
checked={o.visible}
onChange={(e) => setChecked(e, i)}/>
{o.question}
</div>
<div className="appForm__faqTabItem">
<Button icon="pi pi-pencil" severity="success"
aria-label="Edit" onClick={(e) => editItem(e, i)}/>
<Button icon="pi pi-times" severity="danger"
aria-label="Cancel"
onClick={() => removeItem(i)}/>
</div>
</div>
}
>
<p className="m-0">
{o.answer}
</p>
</AccordionTab>)}
</Accordion>
</div>
)
}
export default FormFieldRepeaterFaq;