- updated call form, new fields;

- updated preview pages for bando;
- fixed bug with decimal number field;
This commit is contained in:
Vitalii Kiiko
2024-10-01 12:55:17 +02:00
parent 111b1b5620
commit 676d7621e7
17 changed files with 282 additions and 56 deletions

View File

@@ -8,6 +8,7 @@ import AuthenticationService from './service/authentication-service';
// store
import { useStore, storeSet } from './store';
import CompanyService from './service/company-service';
const i18n = createI18n({}, 'gepafin');
@@ -17,6 +18,8 @@ function App() {
const callback = (data) => {
if (data.status === 'SUCCESS') {
storeSet.main.userData(data.data);
storeSet.main.setAsyncRequest();
CompanyService.getCompanyForUser(data.data.id, companyCallback, errCompanyCallback)
} else {
storeSet.main.doLogout();
}
@@ -28,6 +31,18 @@ function App() {
storeSet.main.unsetAsyncRequest();
}
const companyCallback = (data) => {
if (data.status === 'SUCCESS') {
storeSet.main.companies(data.data);
}
storeSet.main.unsetAsyncRequest();
}
const errCompanyCallback = (data) => {
storeSet.main.doLogout();
storeSet.main.unsetAsyncRequest();
}
useEffect(() => {
storeSet.main.setAsyncRequest();
AuthenticationService.me(callback, errCallback);

View File

@@ -151,3 +151,7 @@
}
}
}
.appForm__content {
}

View File

@@ -12,8 +12,8 @@ body {
margin: 0;
font-family: "Montserrat", sans-serif;
p, span:not(.p-button-label, .p-button-icon, .p-badge, .p-message-detail, .p-highlight),
input, label:not(.p-error), textarea, a, li, h1, h2, h3, h4, h5, h6, div, th, td {
p, span:not(.p-button-label, .p-button-icon, .p-badge, .p-message-detail, .p-highlight, .p-inline-message-text),
input, label:not(.p-error), textarea, a, li, h1, h2, h3, h4, h5, h6, div:not(.p-inline-message, .p-toast-detail), th, td {
color: var(--global-textColor);
}
}

View File

@@ -24,12 +24,12 @@ const Datepicker = ({
</label>
<Controller
name={fieldName}
disabled={disabled}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<Calendar id={field.name}
disabled={disabled}
value={field.value ?? []}
onChange={(e) => field.onChange(e.value)}
dateFormat="dd/mm/yy"

View File

@@ -36,6 +36,7 @@ const NumberInput = ({
max={max}
locale={locale}
useGrouping={useGrouping}
maxFractionDigits={maxFractionDigits}
minFractionDigits={minFractionDigits}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>

View File

@@ -15,7 +15,8 @@ const TextInput = ({
icon = null,
placeholder = '',
inputtype = 'text',
disabled = false
disabled = false,
onBlurFn = () => {}
}) => {
const input = <Controller
name={fieldName}
@@ -26,6 +27,7 @@ const TextInput = ({
<InputText id={field.name}
disabled={disabled}
{...field}
onBlur={onBlurFn}
type={inputtype}
placeholder={placeholder}
className={classNames({ 'p-invalid': fieldState.invalid })}/>

View File

@@ -5,7 +5,7 @@ const getNumberWithCurrency = (value, currency = 'EUR') => {
currency
})
return formatter.format(value)
return value ? formatter.format(value) : formatter.format(0)
}
export default getNumberWithCurrency;

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useParams } from 'react-router-dom';
import { klona } from 'klona';
import { head, range, is, pluck } from 'ramda';
import { useForm } from 'react-hook-form';
import { TZDate } from '@date-fns/tz';
// store
import { storeSet, useStore } from '../../store';
@@ -22,15 +23,15 @@ import {
isUrl,
isMarcaDaBollo
} from '../../helpers/validators';
import renderHtmlContent from '../../helpers/renderHtmlContent';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
// components
import { Skeleton } from 'primereact/skeleton';
import { Button } from 'primereact/button';
import FormField from '../../components/FormField';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import { Steps } from 'primereact/steps';
import { Toast } from 'primereact/toast';
import { TZDate } from '@date-fns/tz';
import { Messages } from 'primereact/messages';
const BandoApplication = () => {
@@ -300,6 +301,7 @@ const BandoApplication = () => {
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
{formData.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
const text = head(o.settings.filter(o => o.name === 'text'));
const placeholder = head(o.settings.filter(o => o.name === 'placeholder'));
const options = head(o.settings.filter(o => o.name === 'options'));
const step = head(o.settings.filter(o => o.name === 'step'));
@@ -329,7 +331,9 @@ const BandoApplication = () => {
return acc;
}, {});
return <FormField
return ['paragraph'].includes(o.name) && text
? <div className="appForm__content">{renderHtmlContent(text.value)}</div>
: <FormField
key={o.id}
type={o.name}
fieldName={o.id}

View File

@@ -79,6 +79,19 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors, st
}
});
}
if (!isNil(formData.startTime)) {
if (!is(String, formData.startTime)) {
const tzAwareDate = new TZDate(formData.startTime, 'Europe/Berlin');
formData.startTime = tzAwareDate.toISOString().substring(11, 16);
}
}
if (!isNil(formData.endTime)) {
if (!is(String, formData.endTime)) {
const tzAwareDate = new TZDate(formData.endTime, 'Europe/Berlin');
formData.endTime = tzAwareDate.toISOString().substring(11, 16);
}
}
storeSet.main.setAsyncRequest();
if (!formData.id) {
@@ -384,9 +397,10 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors, st
errors={errors}
defaultValue={values['phoneNumber']}
config={{
required: __('È obbligatorio', 'gepafin'),
pattern: /^[0-9]/
}}
inputgroup={true}
icon="+39"
/>
</div>

View File

@@ -41,11 +41,11 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors, st
}, [formInitialData]), mode: 'onChange'
});
const values = getValues();
const step2Props = ['threshold', 'criteria', 'checkList', 'docs', 'images'];
//const step2Props = ['threshold', 'criteria', 'checkList', 'docs', 'images'];
const toast = useRef(null);
const onSubmit = (formData) => {
if (!isNil(formData.dates) && formData.dates.length) {
/*if (!isNil(formData.dates) && formData.dates.length) {
formData.dates = formData.dates.map(v => {
if (is(String, v)) {
return v;
@@ -64,10 +64,11 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors, st
}, {});
storeSet.main.setAsyncRequest();
BandoService.updateBandoStep2(formData.id, forSubmit, createCallback, errCreateCallback);
BandoService.updateBandoStep2(formData.id, forSubmit, createCallback, errCreateCallback);*/
};
const onSaveDraft = () => {
trigger();
const formData = getValues();
if (!isNil(formData.dates) && formData.dates.length) {
formData.dates = formData.dates.map(v => {
@@ -79,6 +80,19 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors, st
}
});
}
if (!isNil(formData.startTime)) {
if (!is(String, formData.startTime)) {
const tzAwareDate = new TZDate(formData.startTime, 'Europe/Berlin');
formData.startTime = tzAwareDate.toISOString().substring(11, 16);
}
}
if (!isNil(formData.endTime)) {
if (!is(String, formData.endTime)) {
const tzAwareDate = new TZDate(formData.endTime, 'Europe/Berlin');
formData.endTime = tzAwareDate.toISOString().substring(11, 16);
}
}
storeSet.main.setAsyncRequest();
if (!formData.id) {

View File

@@ -14,7 +14,7 @@ const ElementSetting = ({ setting, changeFn, updateDataFn }) => {
const settingLabels = {
label: __('Label', 'gepafin'),
placeholder: __('Segnaposto', 'gepafin'),
step: __('Precisione decimale', 'gepafin'),
step: __('Numero Decimali', 'gepafin'),
options: __('Opzioni', 'gepafin'),
mime: __('Tipo di file', 'gepafin'),
text: __('Testo formattato', 'gepafin')

View File

@@ -28,6 +28,7 @@ import {
isPIVA,
isUrl
} from '../../helpers/validators';
import renderHtmlContent from '../../helpers/renderHtmlContent';
const BandoFormsPreview = () => {
const { id, formId } = useParams();
@@ -55,7 +56,8 @@ const BandoFormsPreview = () => {
isMarcaDaBollo
}
const onSubmit = () => {}
const onSubmit = () => {
}
const closePreview = () => {
const parsedId = parseInt(id)
@@ -116,6 +118,7 @@ const BandoFormsPreview = () => {
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
{formData.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
const text = head(o.settings.filter(o => o.name === 'text'));
const placeholder = head(o.settings.filter(o => o.name === 'placeholder'));
const options = head(o.settings.filter(o => o.name === 'options'));
const step = head(o.settings.filter(o => o.name === 'step'));
@@ -145,7 +148,9 @@ const BandoFormsPreview = () => {
return acc;
}, {});
return <FormField
return ['paragraph'].includes(o.name) && text
? <div className="appForm__content">{renderHtmlContent(text.value)}</div>
: <FormField
key={o.id}
type={o.name}
fieldName={o.id}
@@ -155,7 +160,7 @@ const BandoFormsPreview = () => {
register={register}
errors={errors}
defaultValue={values[o.id]}
maxFractionDigits={step}
maxFractionDigits={step ? step.value : 0}
accept={mimeValue}
config={validations}
options={options ? options.value : []}

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { is, isEmpty } from 'ramda';
import { is, isEmpty, isNil } from 'ramda';
// store
import { storeSet, useStore } from '../../store';
@@ -113,6 +113,10 @@ const BandoView = () => {
<span>{__('Importo totale', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amount)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Importo minimo per progetto', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amountMin)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Importo massimo per progetto', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amountMax)}</span>
@@ -122,11 +126,11 @@ const BandoView = () => {
<div className="appPageSection__withBorder">
<p className="appPageSection__pMeta">
<span>{__('Data apertura', 'gepafin')}</span>
<span>{getDateFromISOstring(data.dates[0])}</span>
<span>{getDateFromISOstring(data.dates[0])} {data.startTime}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Data chiusura', 'gepafin')}</span>
<span>{getDateFromISOstring(data.dates[1])}</span>
<span>{getDateFromISOstring(data.dates[1])} {data.endTime}</span>
</p>
</div>
</div>
@@ -246,8 +250,8 @@ const BandoView = () => {
<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>
<p>Email: {data.email}</p>
{!isNil(data.phoneNumber) ? <p>{__('Telefono', 'gepafin')}: +39 {data.phoneNumber}</p> : null}
</div>
</div>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { is, isEmpty } from 'ramda';
import { is, isEmpty, pathOr, isNil } from 'ramda';
// store
import { storeSet, useStore } from '../../store';
@@ -9,6 +9,8 @@ import { storeSet, useStore } from '../../store';
// tools
import getNumberWithCurrency from '../../helpers/getNumberWithCurrency';
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import renderHtmlContent from '../../helpers/renderHtmlContent';
// components
import { Skeleton } from 'primereact/skeleton';
@@ -16,21 +18,24 @@ import { Accordion } from 'primereact/accordion';
import { AccordionTab } from 'primereact/accordion';
import { InputTextarea } from 'primereact/inputtextarea';
import { Button } from 'primereact/button';
import BandoService from '../../service/bando-service';
import { Messages } from 'primereact/messages';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import { Message } from 'primereact/message';
// api
import BandoService from '../../service/bando-service';
import FaqItemService from '../../service/faq-item-service';
import ApplicationService from '../../service/application-service';
import renderHtmlContent from '../../helpers/renderHtmlContent';
const BandoViewBeneficiario = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
const companies = useStore().main.companies();
const { id } = useParams();
const navigate = useNavigate();
const [data, setData] = useState({});
const [newQuestion, setNewQuestion] = useState('');
const [applicationObj, setApplicationObj] = useState(true);
const bandoMsgs = useRef(null);
const chosenCompanyId = pathOr(0, [0], companies);
const scaricaBando = () => {
@@ -212,6 +217,10 @@ const BandoViewBeneficiario = () => {
<span>{__('Importo totale', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amount)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Importo minimo per progetto', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amountMin)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Importo massimo per progetto', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amountMax)}</span>
@@ -221,11 +230,11 @@ const BandoViewBeneficiario = () => {
<div className="appPageSection__withBorder">
<p className="appPageSection__pMeta">
<span>{__('Data apertura', 'gepafin')}</span>
<span>{getDateFromISOstring(data.dates[0])}</span>
<span>{getDateFromISOstring(data.dates[0])} {data.startTime}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Data chiusura', 'gepafin')}</span>
<span>{getDateFromISOstring(data.dates[1])}</span>
<span>{getDateFromISOstring(data.dates[1])} {data.endTime}</span>
</p>
</div>
</div>
@@ -311,6 +320,12 @@ const BandoViewBeneficiario = () => {
label={__('Salva', 'gepafin')}/>
</div>
{chosenCompanyId === 0
? <>
<Message severity="error" text={__("Devi creare un'azienda prima di partecipare nei bandi. Vai nel profilo aziendale.", 'gepafin')} />
</>
: null}
<div className="appPageSection">
<h2>{__('Download Documenti', 'gepafin')}</h2>
<div className="appPageSection__actions">
@@ -330,7 +345,7 @@ const BandoViewBeneficiario = () => {
icon="pi pi-download" iconPos="right"/>
<Button
type="button"
disabled={isAsyncRequest}
disabled={isAsyncRequest || chosenCompanyId === 0}
onClick={submitApplication}
label={__('Presenta Domanda', 'gepafin')}
icon="pi pi-save" iconPos="right"/>
@@ -348,8 +363,8 @@ const BandoViewBeneficiario = () => {
<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>
<p>Email: {data.email}</p>
{!isNil(data.phoneNumber) ? <p>{__('Telefono', 'gepafin')}: +39 {data.phoneNumber}</p> : null}
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import React, { useMemo, useRef } from 'react';
import { __ } from '@wordpress/i18n';
import { isEmpty, isNil } from 'ramda';
// store
import { storeSet, useStore } from '../../store';
@@ -12,12 +13,15 @@ import { Button } from 'primereact/button';
import { useForm } from 'react-hook-form';
// api
import UserService from '../../service/user-service';
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
import CompanyService from '../../service/company-service';
// tools
import { isPIVA } from '../../helpers/validators';
import BlockingOverlay from '../../components/BlockingOverlay';
const ProfileCompany = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
const userData = useStore().main.userData();
const companies = useStore().main.companies();
const infoMsgs = useRef(null);
const {
@@ -27,8 +31,8 @@ const ProfileCompany = () => {
setValue
} = useForm({
defaultValues: useMemo(() => {
return userData;
}, [userData]),
return companies[0];
}, [companies]),
mode: 'onChange'
});
@@ -36,7 +40,11 @@ const ProfileCompany = () => {
infoMsgs.current.clear();
storeSet.main.setAsyncRequest();
UserService.updateUser(formData, updateCallback, updateError);
if (isNil(formData.id)) {
CompanyService.createCompany(formData, updateCallback, updateError);
} else {
CompanyService.updateCompany(formData.id, formData, updateCallback, updateError);
}
};
const updateCallback = (data) => {
@@ -51,6 +59,47 @@ const ProfileCompany = () => {
storeSet.main.unsetAsyncRequest();
}
const checkVatNumber = (e) => {
infoMsgs.current.clear();
const isValid = isPIVA(e.target.value);
if (isValid) {
storeSet.main.setAsyncRequest();
CompanyService.checkVat(checkVatCallback, errCheckVatCallback, [['vatNumber', e.target.value]])
}
}
const checkVatCallback = (data) => {
if (data.status === 'SUCCESS') {
const resp = data.data.data;
if (!isEmpty(resp)) {
const {
cap, cf, denominazione, piva, indirizzo, comune, dettaglio: { pec }
} = resp;
const formData = {
cap,
pec,
email: pec,
city: comune,
codiceFiscale: cf,
address: indirizzo,
vatNumber: piva,
companyName: denominazione
}
console.log('formData', formData);
Object.keys(formData).map(k => setValue(k, formData[k]));
}
//setData(getFormattedBandiData(data.data));
}
storeSet.main.unsetAsyncRequest();
}
const errCheckVatCallback = (data) => {
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
return (
<div className="appPage">
<div className="appPage__pageHeader">
@@ -61,25 +110,101 @@ const ProfileCompany = () => {
<Messages ref={infoMsgs}/>
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
<BlockingOverlay shouldDisplay={isAsyncRequest}/>
<div className="appPageSection">
<h2>{__('Informazioni aziendali', 'gepafin')}</h2>
<div className="appForm__cols">
<FormField
type="textinput"
fieldName="organization"
disabled={true}
fieldName="companyName"
label={__('Ragione Sociale', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
placeholder="Azienda"
placeholder="Nome di azienda"
/>
</div>
<div className="appForm__cols">
<FormField
type="textinput"
fieldName="vatNumber"
label={__('P.IVA', 'gepafin')}
onBlurFn={checkVatNumber}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
<FormField
type="textinput"
disabled={true}
fieldName="codiceFiscale"
label={__('Codice fiscale', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
</div>
<div className="appForm__cols">
<FormField
type="textinput"
disabled={true}
fieldName="pec"
label={__('Email PEC', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
<FormField
type="textinput"
disabled={true}
fieldName="email"
label={__('Email', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
{/*<FormField
type="textinput"
fieldName="phoneNumber"
label={__('Telefono', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>*/}
</div>
<div className="appForm__cols">
<FormField
type="textinput"
disabled={true}
fieldName="address"
label={__('Indirizzo Sede Legale', 'gepafin')}
label={__('Indirizzo', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
<FormField
type="textinput"
disabled={true}
fieldName="city"
label={__('Città', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
<FormField
type="textinput"
disabled={true}
fieldName="cap"
label={__('CAP', 'gepafin')}
control={control}
errors={errors}
config={{ required: __('È obbligatorio', 'gepafin') }}

View File

@@ -0,0 +1,22 @@
import { NetworkService } from './network-service';
const API_BASE_URL = process.env.REACT_APP_API_EXECUTION_ADDRESS;
export default class CompanyService {
static createCompany = (body, callback, errCallback) => {
NetworkService.post(`${API_BASE_URL}/company`, body, callback, errCallback);
};
static updateCompany = (id, body, callback, errCallback) => {
NetworkService.post(`${API_BASE_URL}/company/${id}`, body, callback, errCallback);
};
static checkVat = (callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/company/vatNumber`, callback, errCallback, queryParams);
};
static getCompanyForUser = (id, callback, errCallback) => {
NetworkService.get(`${API_BASE_URL}/company/user/${id}`, callback, errCallback);
};
}

View File

@@ -5,6 +5,7 @@ const initialStore = {
// user
userData: {},
token: '',
companies: [],
// bando form
formInitialData: {},
// form builder