import React, { useState, useEffect, useRef, useMemo } from 'react'; import { __, sprintf } from '@wordpress/i18n'; import { useParams } from 'react-router-dom'; import { head, is, pluck, isEmpty, pathOr, isNil } from 'ramda'; import { useForm } from 'react-hook-form'; import 'quill/dist/quill.core.css'; import { wrap } from 'object-path-immutable'; import { evaluate } from 'mathjs'; import equal from 'fast-deep-equal'; import { klona } from 'klona'; // store import { storeSet, storeGet, useStore } from '../../store'; // api import ApplicationService from '../../service/application-service'; // tools import { isPIVA, isCodiceFiscale, isCAP, isIBAN, isEmail, isEmailPEC, isUrl, isMarcaDaBollo, minChecks, maxChecks, nonEmptyTables } from '../../helpers/validators'; import renderHtmlContent from '../../helpers/renderHtmlContent'; import set404FromErrorResponse from '../../helpers/set404FromErrorResponse'; import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText'; import renderWithDataVars from '../../helpers/renderWithDataVars'; import getTokens from '../../helpers/getTokens'; import formatDateString from '../../helpers/formatDateString'; import isDateTimeInPast from '../../helpers/isDateTimeInPast'; // components import { Skeleton } from 'primereact/skeleton'; import { Button } from 'primereact/button'; import FormField from '../../components/FormField'; import { Toast } from 'primereact/toast'; import { Messages } from 'primereact/messages'; import ApplicationSteps from './ApplicationSteps'; import BlockingOverlay from '../../components/BlockingOverlay'; import { Dialog } from 'primereact/dialog'; import FileuploadApplicationSignedPdf from '../../components/FileuploadApplicationSignedPdf'; import { defaultMaxFileSize } from '../../configData'; const BandoApplication = () => { const chosenCompanyId = useStore().main.chosenCompanyId(); const { id } = useParams(); const [isExpired, setIsExpired] = useState(false); const [formData, setFormData] = useState([]); const [formInitialData, setFormInitialData] = useState(null); const [fieldsWithVars, setFieldsWithVars] = useState({}); const [fieldsWithFormula, setFieldsWithFormula] = useState({}); const [bandoTitle, setBandoTitle] = useState(''); const [bandoId, setBandoId] = useState(0); const [formId, setFormId] = useState(''); const [totalSteps, setTotalSteps] = useState(0); const [visibleConfirmation, setVisibleConfirmation] = useState(false); const [applicationStatus, setApplicationStatus] = useState(''); const [activeStep, setActiveStep] = useState(1); const [signedPdfFile, setSignedPdfFile] = useState([]); const isAsyncRequest = useStore().main.isAsyncRequest(); const toast = useRef(null); const formMsgs = useRef(null); const { control, handleSubmit, formState: { errors }, setValue, trigger, register, getValues, watch, reset } = useForm({ defaultValues: useMemo(() => { return formInitialData ? formInitialData : {} }, [formInitialData]), mode: 'onChange' }); const validationFns = { isPIVA, isCodiceFiscale, isCAP, isIBAN, isEmail, isEmailPEC, isUrl, isMarcaDaBollo, minChecks, maxChecks, nonEmptyTables } const activeStepIndex = activeStep - 1; const values = getValues(); const formValues = watch(); const onValidate = () => { const applId = getApplicationId(); storeSet.main.setAsyncRequest(); formMsgs.current.clear(); ApplicationService.validateApplication(applId, {}, validateApplicationCallback, errValidateApplicationCallback); }; const onSubmit = () => { const applId = getApplicationId(); storeSet.main.setAsyncRequest(); formMsgs.current.clear(); ApplicationService.updateStatusApplication(applId, {}, submitApplicationCallback, errSubmitApplicationCallback, [ ['status', 'SUBMIT'] ]); }; const submitApplicationCallback = (data) => { if (data.status === 'SUCCESS') { if (data.data.status) { setApplicationStatus(data.data.status); // ask why not 'applicationStatus'? } } storeSet.main.unsetAsyncRequest(); } const errSubmitApplicationCallback = (data) => { storeSet.main.unsetAsyncRequest(); if (data.status === 'VALIDATION_ERROR') { if (formMsgs.current) { formMsgs.current.show([ { id: '99', sticky: true, severity: 'error', summary: '', detail: data.data.join(', '), closable: true } ]); } } else if (data.status === 'EXCEPTION_ERROR') { if (formMsgs.current) { formMsgs.current.show([ { id: '99', sticky: true, severity: 'error', summary: '', detail: data.message, closable: true } ]); } } else if (data.status === 'BAD_REQUEST') { if (formMsgs.current) { formMsgs.current.show([ { id: '99', sticky: true, severity: 'error', summary: '', detail: data.message, closable: true } ]); toast.current.show({ severity: 'error', summary: '', detail: data.message }); } } else { set404FromErrorResponse(data); } } const validateApplicationCallback = (data) => { if (data.status === 'SUCCESS') { if (data.data.status) { setApplicationStatus(data.data.status); // ask why not 'applicationStatus'? } } storeSet.main.unsetAsyncRequest(); } const errValidateApplicationCallback = (data) => { if (toast.current) { toast.current.show({ severity: 'error', summary: '', detail: data.message }); } storeSet.main.unsetAsyncRequest(); } const saveDraft = (saveAndMove = '') => { trigger(); const formValues = getValues(); const usedFieldsIds = pluck('id', formData); const newFormValues = Object.keys(formValues) .filter(v => usedFieldsIds.includes(v)) .reduce((acc, cur) => { const formField = head(formData.filter(o => o.id === cur)); let fieldVal = formValues[cur]; if (formValues[cur] && formValues[cur].toISOString) { fieldVal = formatDateString(formValues[cur]); } fieldVal = isEmpty(fieldVal) ? null : fieldVal; if (formField && formField.name === 'fileupload') { fieldVal = is(Array, fieldVal) ? fieldVal.map(o => o.id).join(',') : null; } acc.push({ 'fieldId': cur, 'fieldValue': fieldVal }); return acc; }, []); const submitData = { formFields: newFormValues } if (formId) { const applId = getApplicationId(); storeSet.main.setAsyncRequest(); if (formMsgs.current) { formMsgs.current.clear(); } ApplicationService.saveDraft(applId, submitData, (data) => saveDraftCallback(data, saveAndMove), errSaveDraftCallback, [ ['formId', formId] ]); } } const getApplicationId = () => { const parsed = parseInt(id) return !isNaN(parsed) ? parsed : 0; } const saveDraftCallback = (data, saveAndMove = '') => { storeSet.main.unsetAsyncRequest(); if (data.status === 'SUCCESS') { if (toast.current) { toast.current.show({ severity: 'success', summary: '', detail: __('Salvato!', 'gepafin') }); } if (!isEmpty(saveAndMove) && is(String, saveAndMove)) { storeSet.main.setAsyncRequest(); ApplicationService.getApplicationForm(data.data.id, getApplFormCallback, errGetApplFormCallbacks, [ ['formId', formId], ['action', saveAndMove] ]); } else { ApplicationService.getApplicationForm(data.data.id, getStatusCheckCallback, errGetStatusCheckCallbacks); } } } const errSaveDraftCallback = (data) => { storeSet.main.unsetAsyncRequest(); if (data.status === 'VALIDATION_ERROR') { if (formMsgs.current) { formMsgs.current.show([ { id: '99', sticky: true, severity: 'error', summary: '', detail: data.data.join(', '), closable: true } ]); } } else if (data.status === 'EXCEPTION_ERROR') { if (formMsgs.current) { formMsgs.current.show([ { id: '99', sticky: true, severity: 'error', summary: '', detail: data.message, closable: true } ]); } } else { set404FromErrorResponse(data); } } const goBackward = () => { saveDraft('PREVIOUS'); } const goForward = () => { saveDraft('NEXT'); } const getApplFormCallback = (data) => { if (data.status === 'SUCCESS') { setBandoTitle(data.data.callTitle); setBandoId(data.data.callId); setFormData(data.data.applicationFormResponse.content); setFormId(data.data.formId); setTotalSteps(data.data.totalFormSteps); //setCompletedSteps(data.data.completedSteps); setApplicationStatus(data.data.applicationStatus) setActiveStep(data.data.currentStep); const isCallExpired = isDateTimeInPast(data.data.callEndDate, data.data.callEndTime); setIsExpired(isCallExpired); const chosenCompanyId = data.data.companyId; const companies = storeGet.main.companies(); const company = head(companies.filter(o => o.id === chosenCompanyId)); let formDataInitial = {}; let dynamicData = { company: {}, user: {}, custom: {} }; if (company) { dynamicData = Object.keys(company).reduce((acc, cur) => { if ([ 'companyName', 'vatNumber', 'codiceFiscale', 'address', 'phoneNumber', 'city', 'province', 'cap', 'country', 'pec', 'email', 'contactName', 'contactEmail', 'codiceAteco' ].includes(cur)) { acc.company[cur] = company[cur]; } return acc; }, dynamicData); } const userData = storeGet.main.userData(); Object.keys(userData).reduce((acc, cur) => { if ([ 'email', 'firstName', 'lastName', 'phoneNumber', 'codiceFiscale' ].includes(cur)) { acc.user[cur] = userData[cur]; } if (['dateOfBirth'].includes(cur)) { acc.user[cur] = new Date(userData[cur]); } return acc; }, dynamicData); const userFullName = `${userData.firstName} ${userData.lastName}`; dynamicData = wrap(dynamicData).set(['custom', 'userFullName'], userFullName).value(); const legalRepresentantName = company.isLegalRepresentant ? userFullName : ''; dynamicData = wrap(dynamicData).set(['custom', 'legalRepresentant'], legalRepresentantName).value(); let allvars = {}; let allformulas = {}; if (data.data.applicationFormResponse.content) { // eslint-disable-next-line array-callback-return data.data.applicationFormResponse.content.map((o) => { if (o.dynamicData && !isEmpty(o.dynamicData)) { formDataInitial[o.id] = pathOr('', o.dynamicData.split('.'), dynamicData); } const variable = head(o.settings.filter(o => o.name === 'variable')); if (variable && !isEmpty(variable.value)) { allvars[o.id] = variable.value[0]; } const formula = head(o.settings.filter(o => o.name === 'formula')); if (formula && !isEmpty(formula.value)) { allformulas[o.id] = formula.value; } }); } if (data.data.applicationFormResponse.formFields) { const submitData = data.data.applicationFormResponse.formFields.map((o) => ({ fieldId: o.fieldId, fieldValue: o.fieldValue })); formDataInitial = submitData.reduce((acc, cur) => { if (cur.fieldValue) { acc[cur.fieldId] = cur.fieldValue; } return acc; }, formDataInitial); } reset(); setFieldsWithVars(allvars); setFieldsWithFormula(allformulas); setFormInitialData(formDataInitial); } storeSet.main.unsetAsyncRequest(); } const errGetApplFormCallbacks = (data) => { storeSet.main.unsetAsyncRequest(); if (data.status === 'VALIDATION_ERROR') { if (toast.current) { toast.current.show({ severity: 'error', summary: '', detail: data.message }); } } else { set404FromErrorResponse(data); } } const getStatusCheckCallback = (data) => { if (data.status === 'SUCCESS') { //setCompletedSteps(data.data.completedSteps); } } const errGetStatusCheckCallbacks = (data) => { set404FromErrorResponse(data); } const actionBtns =
{activeStep > 1 && activeStep <= totalSteps ?
const onDownloadApplicationPdf = () => { const applId = getApplicationId(); storeSet.main.setAsyncRequest(); ApplicationService.downloadApplicationPdf(applId, {}, getPdfCallback, errPdfCallback); } const getPdfCallback = (data) => { const applId = getApplicationId(); const pdfFile = new Blob([data], { type: 'application/octet-stream' }) const url = window.URL.createObjectURL(pdfFile); const link = document.createElement('a'); link.href = url; link.setAttribute('download', `application-${applId}.pdf`); document.body.appendChild(link); link.click(); link.remove(); storeSet.main.unsetAsyncRequest(); } const errPdfCallback = (data) => { set404FromErrorResponse(data); storeSet.main.unsetAsyncRequest(); } const getSignedPdfCallback = (data) => { if (data.status === 'SUCCESS') { setSignedPdfFile([data.data]); } storeSet.main.unsetAsyncRequest(); } const errSignedPdfCallbacks = () => { storeSet.main.unsetAsyncRequest(); } const handleSetSignedDocumentFromFileupload = (fieldName, stateFieldData) => { setSignedPdfFile(stateFieldData); if (!isEmpty(stateFieldData)) { setApplicationStatus('READY'); } } const doChangeToDraft = () => { const applId = getApplicationId(); storeSet.main.setAsyncRequest(); formMsgs.current.clear(); ApplicationService.updateStatusApplication(applId, {}, changeToDraftCallback, errChangeToDraftCallback, [ ['status', 'DRAFT'] ]); }; const changeToDraftCallback = (data) => { if (data.status === 'SUCCESS') { if (data.data.status) { setApplicationStatus(data.data.status); } } storeSet.main.unsetAsyncRequest(); } const errChangeToDraftCallback = (data) => { storeSet.main.unsetAsyncRequest(); toast.current.show({ severity: 'error', summary: '', detail: data.message }); } // TODO hardcoded for now const signedDocMime = bandoId === 10 ? ['.p7m,application/pkcs7-mime,application/x-pkcs7-mime', '.pdf,application/pdf'] : ['.p7m,application/pkcs7-mime,application/x-pkcs7-mime']; const signedDocValidationString = bandoId === 10 ? ['.p7m', '.pdf'] : ['.p7m']; useEffect(() => { if (!isEmpty(fieldsWithVars) && !isEmpty(fieldsWithFormula)) { const updatedFormValues = klona(formValues); let context = {}; // eslint-disable-next-line array-callback-return Object.keys(updatedFormValues).map(fieldId => { if (!isNil(fieldsWithFormula[fieldId])) { const formula = fieldsWithFormula[fieldId]; context = getTokens(formula) .filter(v => !['false', 'null', 'true'].includes(v)) .reduce((acc, cur) => { acc[cur] = isNil(context[cur]) ? 0 : context[cur]; return acc; }, {}); const mathFormula = renderWithDataVars(formula, context); try { updatedFormValues[fieldId] = evaluate(mathFormula); } catch (e) { console.log('Error in math formula: "', mathFormula, '"', e.message); updatedFormValues[fieldId] = 0; } } if (!isNil(fieldsWithVars[fieldId])) { context[fieldsWithVars[fieldId]] = updatedFormValues[fieldId] } }); if (!isEmpty(updatedFormValues) && !equal(updatedFormValues, formValues)) { reset(updatedFormValues); } } }, [formValues]); useEffect(() => { if ('SUBMIT' === applicationStatus) { setVisibleConfirmation(true); } if (['AWAITING', 'READY', 'SUBMIT'].includes(applicationStatus)) { const applId = getApplicationId(); if (applId) { storeSet.main.setAsyncRequest(); ApplicationService.getApplicationSignedPdf(applId, getSignedPdfCallback, errSignedPdfCallbacks); } } }, [applicationStatus]); useEffect(() => { if (formInitialData) { //reset(); Object.keys(formInitialData).map(k => setValue(k, formInitialData[k])); trigger(); } }, [formInitialData]); useEffect(() => { const applId = getApplicationId(); if (applId) { storeSet.main.setAsyncRequest(); ApplicationService.getApplicationForm(applId, getApplFormCallback, errGetApplFormCallbacks); } }, [id, chosenCompanyId]); return (
{!isAsyncRequest ?

{sprintf(__('Domanda per il Bando: %s', 'gepafin'), bandoTitle)}

: <> }
{'DRAFT' === applicationStatus ? : null}
{ if (!visibleConfirmation) return; setVisibleConfirmation(false); }}>

{__('Grazie, la tua domanda è stata inviata correttamente. Entro 24 ore riceverai una pec con data, ora e numero di protocollo.', 'gepafin')}

{isExpired ?
{__('Il bando è scaduto!', 'gepafin')}
: null} {'DRAFT' === applicationStatus ?
{actionBtns}
: null} {'DRAFT' === applicationStatus /*|| 'AWAITING' === applicationStatus*/ ? 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 tableColumns = head(o.settings.filter(o => o.name === 'table_columns')); const step = head(o.settings.filter(o => o.name === 'step')); const mime = head(o.settings.filter(o => o.name === 'mime')); const formula = head(o.settings.filter(o => o.name === 'formula')); let mimeValue = ''; if (mime) { mimeValue = mime.value.map(o => o.code ? o.code : o.ext); } const validations = Object.keys(o.validators).reduce((acc, cur) => { if (o.validators[cur]) { if (['min', 'max', 'minLength', 'maxLength', 'maxSize'].includes(cur)) { acc[cur] = parseInt(o.validators[cur]); } else if ('pattern' === cur) { acc[cur] = new RegExp(o.validators[cur]); } else if ('isRequired' === cur) { //acc[cur] = o.validators[cur]; acc['required'] = true; } else if ('custom' === cur && validationFns[o.validators[cur]]) { if (!acc.validate) { acc.validate = {}; } acc.validate[o.validators[cur]] = validationFns[o.validators[cur]]; } } return acc; }, {}); //console.log('validations', validations, o.name) return ['paragraph'].includes(o.name) && text ?
{renderHtmlContent(text.value)}
: }) : null} {['AWAITING'].includes(applicationStatus) ?
: null} {['AWAITING', 'READY', 'SUBMIT', 'EVALUATION'].includes(applicationStatus) ?
: null} {'DRAFT' !== applicationStatus ?
: null} {'DRAFT' !== applicationStatus ?
: null}
{'DRAFT' === applicationStatus ?
{__('Azioni rapide', 'gepafin')}
: null} {isExpired ?
{__('Il bando è scaduto!', 'gepafin')}
: null} {'DRAFT' === applicationStatus ?
{actionBtns}
: null}
) } export default BandoApplication;