Merge branch 'develop' into new-role-director

This commit is contained in:
Vitalii Kiiko
2025-11-07 16:21:08 +01:00
18 changed files with 1341 additions and 65 deletions

View File

@@ -320,6 +320,10 @@
a { a {
color: inherit; color: inherit;
} }
ul li {
color: inherit;
}
} }
@container section_with_border (max-width: 600px) { @container section_with_border (max-width: 600px) {

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { classNames } from 'primereact/utils'; import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import { is, isEmpty } from 'ramda'; import { is, isEmpty, isNil } from 'ramda';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { isNaN } from 'mathjs'; import { isNaN } from 'mathjs';
@@ -18,7 +18,7 @@ const NumberInput = ({
icon = null, icon = null,
locale = 'it-IT', locale = 'it-IT',
minFractionDigits = 0, minFractionDigits = 0,
maxFractionDigits = 1, maxFractionDigits = 0,
min, min,
max, max,
disabled = false, disabled = false,
@@ -27,6 +27,7 @@ const NumberInput = ({
}) => { }) => {
const minAttr = config.min ? config.min : min; const minAttr = config.min ? config.min : min;
const maxAttr = config.max ? config.max : max; const maxAttr = config.max ? config.max : max;
const input = <Controller const input = <Controller
name={fieldName} name={fieldName}
control={control} control={control}
@@ -40,9 +41,10 @@ const NumberInput = ({
onValueChange={(e) => field.onChange(e.value)} onValueChange={(e) => field.onChange(e.value)}
min={minAttr} min={minAttr}
max={maxAttr} max={maxAttr}
mode="decimal"
locale={locale} locale={locale}
showButtons showButtons
useGrouping={useGrouping} useGrouping={!isNil(maxFractionDigits) && parseInt(maxFractionDigits) !== 0}
maxFractionDigits={!isNaN(parseInt(maxFractionDigits)) ? parseInt(maxFractionDigits) : 0} maxFractionDigits={!isNaN(parseInt(maxFractionDigits)) ? parseInt(maxFractionDigits) : 0}
minFractionDigits={!isNaN(parseInt(minFractionDigits)) ? parseInt(minFractionDigits) : 0} minFractionDigits={!isNaN(parseInt(minFractionDigits)) ? parseInt(minFractionDigits) : 0}
className={classNames({ 'p-invalid': fieldState.invalid })}/> className={classNames({ 'p-invalid': fieldState.invalid })}/>

View File

@@ -237,6 +237,11 @@ export const classificationType = [
'name': 'FATTURA', 'name': 'FATTURA',
'idTipoprotocollo': 2 'idTipoprotocollo': 2
}, },
{
'idClassificazione': 200,
'name': 'LETTERA ACCETTAZIONE DELIBERA',
'idTipoprotocollo': 1
},
{ {
'idClassificazione': 201, 'idClassificazione': 201,
'name': 'LETTERA ESITO DELIBERA', 'name': 'LETTERA ESITO DELIBERA',
@@ -247,6 +252,11 @@ export const classificationType = [
'name': 'LETTERA TRASPARENZA', 'name': 'LETTERA TRASPARENZA',
'idTipoprotocollo': 2 'idTipoprotocollo': 2
}, },
{
'idClassificazione': 212,
'name': 'LETTERA ACCETTAZIONE DELIBERA PROTOCOLLO',
'idTipoprotocollo': 2
},
{ {
'idClassificazione': 205, 'idClassificazione': 205,
'name': 'CONTRATTO', 'name': 'CONTRATTO',
@@ -288,21 +298,25 @@ export const rejectionReasons = [
export const amendmentRequestedDocs = { export const amendmentRequestedDocs = {
NESSUNA_GARANZIA: [ NESSUNA_GARANZIA: [
'Lettera di accettazione firmata' 'Lettera di accettazione firmata',
'Visura Centrale Rischi'
], ],
GARANZIA_MCC: [ GARANZIA_MCC: [
'Lettera di accettazione firmata', 'Lettera di accettazione firmata',
'Modulo di domanda della agevolazione (ex allegato 4)' 'Modulo di domanda della agevolazione (ex allegato 4)',
'Visura Centrale Rischi'
], ],
MCC_START_UP: [ MCC_START_UP: [
'Lettera di accettazione firmata', 'Lettera di accettazione firmata',
'Modulo di domanda della agevolazione (ex allegato 4)', 'Modulo di domanda della agevolazione (ex allegato 4)',
'Modello di valutazione bilanci previsionali', 'Modello di valutazione bilanci previsionali',
'Modello valutazione start up' 'Modello valutazione start up',
'Visura Centrale Rischi'
], ],
ALTRE_GARANZIE: [ ALTRE_GARANZIE: [
'Lettera di accettazione firmata', 'Lettera di accettazione firmata',
'Modello privacy', 'Modello privacy',
'Autocertificazione e altri eventuali in zip/p7m' 'Autocertificazione e altri eventuali in zip/p7m',
'Visura Centrale Rischi'
], ],
} }

View File

@@ -33,10 +33,10 @@ const getBandoLabel = (status) => {
return __('Ammisibile', 'gepafin'); return __('Ammisibile', 'gepafin');
case 'TECHNICAL_EVALUATION': case 'TECHNICAL_EVALUATION':
return __('Valutazione technico-finanziaria', 'gepafin'); return __('Valutazione tecnico-finanziaria', 'gepafin');
case 'AWAITING_TECHNICAL_EVALUATION': case 'AWAITING_TECHNICAL_EVALUATION':
return __('Pre valutazione technico-finanziaria', 'gepafin'); return __('Post valutazione tecnico-finanziaria', 'gepafin');
case 'DUE': case 'DUE':
return __('In scadenza', 'gepafin'); return __('In scadenza', 'gepafin');
@@ -76,6 +76,12 @@ const getBandoLabel = (status) => {
case 'TECHNICAL_EVALUATION_REJECTED': case 'TECHNICAL_EVALUATION_REJECTED':
return __('Respinto Tec-Fin', 'gepafin'); return __('Respinto Tec-Fin', 'gepafin');
case 'AWAITING_CONTRACT':
return __('In attesa di contratto', 'gepafin');
case 'CONTRACT_SIGNED':
return __('Contratto firmato', 'gepafin');
default: default:
return ''; return '';

View File

@@ -75,6 +75,12 @@ const getBandoSeverity = (status) => {
case 'TECHNICAL_EVALUATION_REJECTED': case 'TECHNICAL_EVALUATION_REJECTED':
return 'danger'; return 'danger';
case 'AWAITING_CONTRACT':
return 'warning';
case 'CONTRACT_SIGNED':
return 'success';
default: default:
return 'info'; return 'info';
} }

View File

@@ -1,29 +1,49 @@
import React, { useEffect, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { head, isEmpty, pathOr } from 'ramda'; import { head, isEmpty, pathOr } from 'ramda';
import NumberFlow from '@number-flow/react'; import NumberFlow from '@number-flow/react';
import { wrap } from 'object-path-immutable';
// store // store
import { useStoreValue } from '../../store'; import { storeGet, storeSet, useStoreValue } from '../../store';
// api // api
import DashboardService from '../../service/dashboard-service'; import DashboardService from '../../service/dashboard-service';
import ApplicationContractService from '../../service/application-contract-service';
// tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText';
// components // components
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import ErrorBoundary from '../../components/ErrorBoundary'; import ErrorBoundary from '../../components/ErrorBoundary';
import LatestBandiBeneficiarioTableAsync from './components/LatestBandiBeneficiarioTableAsync'; import LatestBandiBeneficiarioTableAsync from './components/LatestBandiBeneficiarioTableAsync';
import MyLatestSubmissionsTableAsync from './components/MyLatestSubmissionsTableAsync'; import MyLatestSubmissionsTableAsync from './components/MyLatestSubmissionsTableAsync';
import { classNames } from 'primereact/utils';
import { FileUpload } from 'primereact/fileupload';
import { defaultMaxFileSize, mimeTypes } from '../../configData';
import { Dialog } from 'primereact/dialog';
import { Toast } from 'primereact/toast';
const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID; const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const DashboardBeneficiario = () => { const DashboardBeneficiario = () => {
const isAsyncRequest = useStoreValue('isAsyncRequest');
const navigate = useNavigate(); const navigate = useNavigate();
const [mainStats, setMainStats] = useState({}); const [mainStats, setMainStats] = useState({});
const [contractsData, setContractsData] = useState([]);
const companies = useStoreValue('companies'); const companies = useStoreValue('companies');
const chosenCompanyId = useStoreValue('chosenCompanyId'); const chosenCompanyId = useStoreValue('chosenCompanyId');
const company = head(companies.filter(o => o.id === chosenCompanyId)); const company = head(companies.filter(o => o.id === chosenCompanyId));
const [isVisibleContractForm, setIsVisibleContractForm] = useState(false);
const [contractFormData, setContractFormData] = useState({
subject: '',
text: ''
});
const contractFormFilesRef = useRef(null);
const toast = useRef(null);
const goToAllSubmissions = () => { const goToAllSubmissions = () => {
navigate('/bandi'); navigate('/bandi');
@@ -42,11 +62,112 @@ const DashboardBeneficiario = () => {
const errGetStats = () => { const errGetStats = () => {
} }
const getContracts = (data) => {
if (data.status === 'SUCCESS') {
setContractsData(data.data);
}
}
const errGetContracts = () => {
}
const openSendContractForm = useCallback((id) => {
const contract = head(contractsData.filter(o => o.id === id));
if (contract) {
setContractFormData(contract)
setIsVisibleContractForm(true);
}
}, [contractsData]);
const headerContractDialog = () => {
return <span>{__('Invia il contratto', 'gepafin')}</span>;
}
const hideContractDialog = () => {
setIsVisibleContractForm(false);
setContractFormData({
subject: '',
text: ''
});
}
const footerContractDialog = useCallback(() => {
let isDisabled = !contractFormData.files || isEmpty(contractFormData.files) || isAsyncRequest;
return <div>
<Button type="button" label={__('Annulla', 'gepafin')} onClick={hideContractDialog} outlined/>
<Button
type="button"
disabled={isDisabled}
label={__('Invia', 'gepafin')} onClick={doSendContract}/>
</div>
}, [contractFormData]);
const updateContractFormData = (value, path) => {
const newData = wrap(contractFormData).set(path.split('.'), value).value();
setContractFormData(newData);
};
const doSendContract = useCallback(() => {
if (contractFormData.files && !isEmpty(contractFormData.files) && !isAsyncRequest) {
const formDataToSend = new FormData();
if (contractFormData.files && contractFormData.files.length > 0) {
contractFormData.files.forEach((file) => {
formDataToSend.append('beneficiaryContractDocuments', file);
});
}
storeSet('setAsyncRequest');
ApplicationContractService.updateApplicationContract(
contractFormData.id,
formDataToSend,
getUploadApplicationContractCallback,
errGetUploadApplicationContractCallback
);
}
}, [contractFormData]);
const getUploadApplicationContractCallback = (data) => {
if (data.status === 'SUCCESS') {
setContractsData(null);
if (toast.current && data.message) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
}
hideContractDialog();
storeSet('unsetAsyncRequest');
}
const errGetUploadApplicationContractCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: data.status === 'SUCCESS' ? 'info' : 'error',
summary: '',
detail: data.message
});
}
hideContractDialog();
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
useEffect(() => { useEffect(() => {
const existingCompany = head(companies.filter(o => o.id === chosenCompanyId)); const existingCompany = head(companies.filter(o => o.id === chosenCompanyId));
if (existingCompany) { if (existingCompany) {
DashboardService.getBeneficiaryStatsForCompany(existingCompany.id, getStats, errGetStats); DashboardService.getBeneficiaryStatsForCompany(existingCompany.id, getStats, errGetStats);
const userData = storeGet('userData');
ApplicationContractService.getContractByUserId(getContracts, errGetContracts, [
['userId', userData.id]
]);
} }
}, [companies, chosenCompanyId]); }, [companies, chosenCompanyId]);
@@ -57,7 +178,28 @@ const DashboardBeneficiario = () => {
{company ? <span className="companyName">{company.companyName}</span> : null} {company ? <span className="companyName">{company.companyName}</span> : null}
</div> </div>
{contractsData && !isEmpty(contractsData)
? <>
<div className="appPage__spacer"></div>
<div className="appPageSection__message warning">
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: '100%' }}>
<div style={{ display: 'flex', gap: 10 }}>
<i className="pi pi-info-circle"></i>
<span className="summary">{__('Contratti in attesa:', 'gepafin')}</span>
</div>
<ul>
{contractsData.map(o => <li key={o.id}>
<a href="#" onClick={() => openSendContractForm(o.id)}>
{o.callName} - Domanda #{o.applicationId}
</a>
</li>)}
</ul>
</div>
</div>
</> : null}
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
<Toast ref={toast}/>
<div className="appPageSection statsBigBadges"> <div className="appPageSection statsBigBadges">
<h2>{__('Panoramica di Sistema', 'gepafin')}</h2> <h2>{__('Panoramica di Sistema', 'gepafin')}</h2>
@@ -162,6 +304,88 @@ const DashboardBeneficiario = () => {
label={__('Contatta assistenza', 'gepafin')} icon="pi pi-envelope" iconPos="right"/>*/} label={__('Contatta assistenza', 'gepafin')} icon="pi pi-envelope" iconPos="right"/>*/}
</div> </div>
</div> </div>
<Dialog
visible={isVisibleContractForm}
modal
header={headerContractDialog}
footer={footerContractDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideContractDialog}>
<div className="appForm__field">
<p>Scarica il contratto:</p>
<ul>
{contractFormData?.instructorDocuments
? contractFormData.instructorDocuments.map(o => <li key={o.id}>
<a href={o.filePath} target="_blank" rel="noreferrer">{o.name}</a>
</li>)
: null}
</ul>
<p>Firmalo digitalmente e ricaricalo</p>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.files || isEmpty(contractFormData.files) })}>
{__('Files', 'gepafin')}* (p7m)
</label>
<FileUpload
ref={contractFormFilesRef}
name="files[]"
multiple
accept='.p7m,application/pkcs7-mime,application/x-pkcs7-mime'
maxFileSize={defaultMaxFileSize}
auto={false}
customUpload={true}
className={classNames({ 'p-invalid': !contractFormData.files || isEmpty(contractFormData.files) })}
onSelect={(e) => {
updateContractFormData(e.files, 'files');
}}
onRemove={(e) => {
const updatedFiles = contractFormFilesRef.current.getFiles();
updateContractFormData(updatedFiles, 'files');
}}
headerTemplate={(options) => {
const { chooseButton } = options;
return (
<div className="p-fileupload-buttonbar" data-pc-section="buttonbar">
{chooseButton}
</div>
);
}}
chooseOptions={{
label: __('Aggiungi i file', 'gepafin'),
icon: 'pi pi-plus'
}}
itemTemplate={(file, props) => {
return (
<div className="p-fileupload-row" data-pc-section="file">
<div data-pc-section="details" style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
textAlign: 'left'
}}>
<div className="p-fileupload-filename" data-pc-section="filename">
{file.name}
</div>
<span
data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span>
</div>
<div data-pc-section="actions">
<Button
type="button"
icon="pi pi-times"
className="p-button-rounded p-button-danger p-button-text"
onClick={() => props.onRemove()}
/>
</div>
</div>
)
}}
emptyTemplate={<p className="m-0">{__('Trascina i file qua')}</p>}
/>
</div>
</Dialog>
</div> </div>
) )
} }

View File

@@ -1,29 +1,49 @@
import React, { useEffect, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { head, isEmpty, pathOr } from 'ramda'; import { head, isEmpty, pathOr } from 'ramda';
import NumberFlow from '@number-flow/react'; import NumberFlow from '@number-flow/react';
import { wrap } from 'object-path-immutable';
// store // store
import { useStoreValue } from '../../store'; import { storeGet, storeSet, useStoreValue } from '../../store';
// api // api
import DashboardService from '../../service/dashboard-service'; import DashboardService from '../../service/dashboard-service';
import ApplicationContractService from '../../service/application-contract-service';
// tools
import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
// components // components
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import ErrorBoundary from '../../components/ErrorBoundary'; import ErrorBoundary from '../../components/ErrorBoundary';
import MyLatestSubmissionsTableAsync from '../DashboardBeneficiario/components/MyLatestSubmissionsTableAsync'; import MyLatestSubmissionsTableAsync from '../DashboardBeneficiario/components/MyLatestSubmissionsTableAsync';
import LatestBandiBeneficiarioTableAsync from '../DashboardBeneficiario/components/LatestBandiBeneficiarioTableAsync'; import LatestBandiBeneficiarioTableAsync from '../DashboardBeneficiario/components/LatestBandiBeneficiarioTableAsync';
import { classNames } from 'primereact/utils';
import { FileUpload } from 'primereact/fileupload';
import { defaultMaxFileSize } from '../../configData';
import { Dialog } from 'primereact/dialog';
import { Toast } from 'primereact/toast';
const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID; const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const DashboardBeneficiarioConfidi = () => { const DashboardBeneficiarioConfidi = () => {
const isAsyncRequest = useStoreValue('isAsyncRequest');
const navigate = useNavigate(); const navigate = useNavigate();
const [mainStats, setMainStats] = useState({}); const [mainStats, setMainStats] = useState({});
const [contractsData, setContractsData] = useState([]);
const companies = useStoreValue('companies'); const companies = useStoreValue('companies');
const chosenCompanyId = useStoreValue('chosenCompanyId'); const chosenCompanyId = useStoreValue('chosenCompanyId');
const company = head(companies.filter(o => o.id === chosenCompanyId)); const company = head(companies.filter(o => o.id === chosenCompanyId));
const [isVisibleContractForm, setIsVisibleContractForm] = useState(false);
const [contractFormData, setContractFormData] = useState({
subject: '',
text: ''
});
const contractFormFilesRef = useRef(null);
const toast = useRef(null);
const goToAllSubmissions = () => { const goToAllSubmissions = () => {
navigate('/bandi'); navigate('/bandi');
@@ -42,11 +62,112 @@ const DashboardBeneficiarioConfidi = () => {
const errGetStats = () => { const errGetStats = () => {
} }
const getContracts = (data) => {
if (data.status === 'SUCCESS') {
setContractsData(data.data);
}
}
const errGetContracts = () => {
}
const openSendContractForm = useCallback((id) => {
const contract = head(contractsData.filter(o => o.id === id));
if (contract) {
setContractFormData(contract)
setIsVisibleContractForm(true);
}
}, [contractsData]);
const headerContractDialog = () => {
return <span>{__('Invia il contratto', 'gepafin')}</span>;
}
const hideContractDialog = () => {
setIsVisibleContractForm(false);
setContractFormData({
subject: '',
text: ''
});
}
const footerContractDialog = useCallback(() => {
let isDisabled = !contractFormData.files || isEmpty(contractFormData.files) || isAsyncRequest;
return <div>
<Button type="button" label={__('Annulla', 'gepafin')} onClick={hideContractDialog} outlined/>
<Button
type="button"
disabled={isDisabled}
label={__('Invia', 'gepafin')} onClick={doSendContract}/>
</div>
}, [contractFormData]);
const updateContractFormData = (value, path) => {
const newData = wrap(contractFormData).set(path.split('.'), value).value();
setContractFormData(newData);
};
const doSendContract = useCallback(() => {
if (contractFormData.files && !isEmpty(contractFormData.files) && !isAsyncRequest) {
const formDataToSend = new FormData();
if (contractFormData.files && contractFormData.files.length > 0) {
contractFormData.files.forEach((file) => {
formDataToSend.append('beneficiaryContractDocuments', file);
});
}
storeSet('setAsyncRequest');
ApplicationContractService.updateApplicationContract(
contractFormData.id,
formDataToSend,
getUploadApplicationContractCallback,
errGetUploadApplicationContractCallback
);
}
}, [contractFormData]);
const getUploadApplicationContractCallback = (data) => {
if (data.status === 'SUCCESS') {
//setData(getFormattedData(data.data));
if (toast.current && data.message) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
}
hideContractDialog();
storeSet('unsetAsyncRequest');
}
const errGetUploadApplicationContractCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: data.status === 'SUCCESS' ? 'info' : 'error',
summary: '',
detail: data.message
});
}
hideContractDialog();
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
useEffect(() => { useEffect(() => {
const existingCompany = head(companies.filter(o => o.id === chosenCompanyId)); const existingCompany = head(companies.filter(o => o.id === chosenCompanyId));
if (existingCompany) { if (existingCompany) {
DashboardService.getBeneficiaryStatsForCompany(existingCompany.id, getStats, errGetStats); DashboardService.getBeneficiaryStatsForCompany(existingCompany.id, getStats, errGetStats);
const userData = storeGet('userData');
ApplicationContractService.getContractByUserId(getContracts, errGetContracts, [
['userId', userData.id]
]);
} }
}, [companies, chosenCompanyId]); }, [companies, chosenCompanyId]);
@@ -57,7 +178,28 @@ const DashboardBeneficiarioConfidi = () => {
{company ? <span className="companyName">{company.companyName}</span> : null} {company ? <span className="companyName">{company.companyName}</span> : null}
</div> </div>
{contractsData && !isEmpty(contractsData)
? <>
<div className="appPage__spacer"></div>
<div className="appPageSection__message warning">
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: '100%' }}>
<div style={{ display: 'flex', gap: 10 }}>
<i className="pi pi-info-circle"></i>
<span className="summary">{__('Contratti in attesa:', 'gepafin')}</span>
</div>
<ul>
{contractsData.map(o => <li key={o.id}>
<a href="#" onClick={() => openSendContractForm(o.id)}>
{o.callName} - Domanda #{o.applicationId}
</a>
</li>)}
</ul>
</div>
</div>
</> : null}
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
<Toast ref={toast}/>
<div className="appPageSection statsBigBadges"> <div className="appPageSection statsBigBadges">
<h2>{__('Panoramica di Sistema', 'gepafin')}</h2> <h2>{__('Panoramica di Sistema', 'gepafin')}</h2>
@@ -102,7 +244,7 @@ const DashboardBeneficiarioConfidi = () => {
<span className="summary">{__('Attenzione', 'gepafin')}</span> <span className="summary">{__('Attenzione', 'gepafin')}</span>
<span> <span>
{__('Per applicare ai bandi devi Registare un Azienda clicca', 'gepafin')} {__('Per applicare ai bandi devi Registare un Azienda clicca', 'gepafin')}
<Link to={`/agguingi-azienda`} style={{marginLeft: '0.5ch'}}>{__('qua', 'gepafin')}</Link> <Link to={`/agguingi-azienda`} style={{ marginLeft: '0.5ch' }}>{__('qua', 'gepafin')}</Link>
</span> </span>
</div> </div>
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
@@ -148,8 +290,102 @@ const DashboardBeneficiarioConfidi = () => {
<Button <Button
onClick={goToAllSubmissions} onClick={goToAllSubmissions}
label={__('Nuova domanda', 'gepafin')} icon="pi pi-plus" iconPos="right"/> label={__('Nuova domanda', 'gepafin')} icon="pi pi-plus" iconPos="right"/>
{/*<Button
disabled={true}
outlined
onClick={() => {
}}
label={__('Carica documento', 'gepafin')} icon="pi pi-upload" iconPos="right"/>
<Button
disabled={true}
outlined
onClick={() => {
}}
label={__('Contatta assistenza', 'gepafin')} icon="pi pi-envelope" iconPos="right"/>*/}
</div> </div>
</div> </div>
<Dialog
visible={isVisibleContractForm}
modal
header={headerContractDialog}
footer={footerContractDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideContractDialog}>
<div className="appForm__field">
<p>Scarica il contratto:</p>
<ul>
{contractFormData?.instructorDocuments
? contractFormData.instructorDocuments.map(o => <li key={o.id}>
<a href={o.filePath} target="_blank" rel="noreferrer">{o.name}</a>
</li>)
: null}
</ul>
<p>Firmalo digitalmente e ricaricalo</p>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.files || isEmpty(contractFormData.files) })}>
{__('Files', 'gepafin')}* (p7m)
</label>
<FileUpload
ref={contractFormFilesRef}
name="files[]"
multiple
accept='.p7m,application/pkcs7-mime,application/x-pkcs7-mime'
maxFileSize={defaultMaxFileSize}
auto={false}
customUpload={true}
className={classNames({ 'p-invalid': !contractFormData.files || isEmpty(contractFormData.files) })}
onSelect={(e) => {
updateContractFormData(e.files, 'files');
}}
onRemove={(e) => {
const updatedFiles = contractFormFilesRef.current.getFiles();
updateContractFormData(updatedFiles, 'files');
}}
headerTemplate={(options) => {
const { chooseButton } = options;
return (
<div className="p-fileupload-buttonbar" data-pc-section="buttonbar">
{chooseButton}
</div>
);
}}
chooseOptions={{
label: __('Aggiungi i file', 'gepafin'),
icon: 'pi pi-plus'
}}
itemTemplate={(file, props) => {
return (
<div className="p-fileupload-row" data-pc-section="file">
<div data-pc-section="details" style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
textAlign: 'left'
}}>
<div className="p-fileupload-filename" data-pc-section="filename">
{file.name}
</div>
<span
data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span>
</div>
<div data-pc-section="actions">
<Button
type="button"
icon="pi pi-times"
className="p-button-rounded p-button-danger p-button-text"
onClick={() => props.onRemove()}
/>
</div>
</div>
)
}}
emptyTemplate={<p className="m-0">{__('Trascina i file qua')}</p>}
/>
</div>
</Dialog>
</div> </div>
) )
} }

View File

@@ -31,9 +31,15 @@ import SoccorsoResendEmails from '../../../SoccorsoEditPreInstructor/components/
const APP_HUB_ID = process.env.REACT_APP_HUB_ID; const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const DomandeTablePreInstructorAsync = ({ userId = null, statuses = [], const DomandeTablePreInstructorAsync = ({
applicationStatuses = ['EVALUATION', 'SOCCORSO', 'NDG', 'APPOINTMENT', 'ADMISSIBLE', userId = null,
'AWAITING_TECHNICAL_EVALUATION', 'TECHNICAL_EVALUATION']}) => { statuses = [],
applicationStatuses = [
'EVALUATION', 'SOCCORSO', 'NDG', 'APPOINTMENT', 'ADMISSIBLE',
'AWAITING_TECHNICAL_EVALUATION', 'TECHNICAL_EVALUATION',
'AWAITING_CONTRACT', 'CONTRACT_SIGNED'
]
}) => {
const navigate = useNavigate(); const navigate = useNavigate();
const userData = useStoreValue('userData'); const userData = useStoreValue('userData');
const [localAsyncRequest, setLocalAsyncRequest] = useState(false); const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
@@ -244,7 +250,7 @@ const DomandeTablePreInstructorAsync = ({ userId = null, statuses = [],
AssignedApplicationService.assignApplicationPaginated(paginationQuery, getCallback, errGetCallbacks); AssignedApplicationService.assignApplicationPaginated(paginationQuery, getCallback, errGetCallbacks);
} }
}, [lazyState]); }, [lazyState]);
return ( return (
<div className="appPageSection__table"> <div className="appPageSection__table">
<DataTable <DataTable
@@ -300,7 +306,7 @@ const DomandeTablePreInstructorAsync = ({ userId = null, statuses = [],
<Column field="assignedUserName" header={__('Assegnato', 'gepafin')} <Column field="assignedUserName" header={__('Assegnato', 'gepafin')}
filterField="assignedUserName" filter filterField="assignedUserName" filter
filterMatchModeOptions={translationStrings.textFilterOptions} filterMatchModeOptions={translationStrings.textFilterOptions}
style={{ minWidth: '8rem' }}/> style={{ minWidth: '8rem' }}/>
<Column field="applicationStatus" header={__('Stato', 'gepafin')} <Column field="applicationStatus" header={__('Stato', 'gepafin')}
style={{ minWidth: '7rem' }} body={statusBodyTemplate} style={{ minWidth: '7rem' }} body={statusBodyTemplate}
filter filter

View File

@@ -110,7 +110,9 @@ const DashboardPreInstructor = () => {
<div className="appPageSection"> <div className="appPageSection">
<h2>{__('Coda di lavoro', 'gepafin')}</h2> <h2>{__('Coda di lavoro', 'gepafin')}</h2>
<DomandeTablePreInstructorAsync statuses={['OPEN', 'SOCCORSO']} userId={userData.id}/> <DomandeTablePreInstructorAsync
statuses={['OPEN', 'SOCCORSO', 'AWAITING_CONTRACT', 'CONTRACT_SIGNED']}
userId={userData.id}/>
</div> </div>
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>

View File

@@ -15,6 +15,7 @@ import { storeGet, storeSet, useStoreValue } from '../../store';
import ApplicationEvaluationService from '../../service/application-evaluation-service'; import ApplicationEvaluationService from '../../service/application-evaluation-service';
import AmendmentsService from '../../service/amendments-service'; import AmendmentsService from '../../service/amendments-service';
import AppointmentService from '../../service/appointment-service'; import AppointmentService from '../../service/appointment-service';
import ApplicationContractService from '../../service/application-contract-service';
// tools // tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse'; import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
@@ -60,6 +61,7 @@ import { SplitButton } from 'primereact/splitbutton';
import { FileUpload } from 'primereact/fileupload'; import { FileUpload } from 'primereact/fileupload';
import { defaultMaxFileSize, mimeTypes, rejectionReasons } from '../../configData'; import { defaultMaxFileSize, mimeTypes, rejectionReasons } from '../../configData';
import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText'; import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText';
import ArchiveDocument from '../DomandaEditPreInstructor/components/ArchiveDocument';
const APP_EVALUATION_FLOW_ID = process.env.REACT_APP_EVALUATION_FLOW_ID; const APP_EVALUATION_FLOW_ID = process.env.REACT_APP_EVALUATION_FLOW_ID;
const APP_HUB_ID = process.env.REACT_APP_HUB_ID; const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
@@ -98,6 +100,12 @@ const DomandaEditInstructorManager = () => {
duration: 0, duration: 0,
amount: 0 amount: 0
}); });
const [isVisibleContractForm, setIsVisibleContractForm] = useState(false);
const [contractFormData, setContractFormData] = useState({
subject: '',
text: ''
});
const contractFormFilesRef = useRef(null);
const [formData, setFormData] = useState([]); const [formData, setFormData] = useState([]);
const [formId, setFormId] = useState(0); const [formId, setFormId] = useState(0);
const [formInitialData, setFormInitialData] = useState(null); const [formInitialData, setFormInitialData] = useState(null);
@@ -225,7 +233,7 @@ const DomandaEditInstructorManager = () => {
const getCallback = (resp) => { const getCallback = (resp) => {
if (resp.status === 'SUCCESS') { if (resp.status === 'SUCCESS') {
setData(getFormattedData(resp.data)); setData(getFormattedData(resp.data));
setFinalDialogData((prev) => ({...prev, motivation: resp.data.motivation})); setFinalDialogData((prev) => ({ ...prev, motivation: resp.data.motivation }));
updateFlagsForSoccorso(resp.data); updateFlagsForSoccorso(resp.data);
if (resp.data.evaluationVersion === 'V2') { if (resp.data.evaluationVersion === 'V2') {
@@ -290,7 +298,7 @@ const DomandaEditInstructorManager = () => {
}; };
const header = renderHeader(); const header = renderHeader();
const technicalEvalItems = [ const tecnicalEvalItems = [
{ {
label: __('Nessuna garanzia', 'gepafin'), label: __('Nessuna garanzia', 'gepafin'),
icon: 'pi pi-pen-to-square', icon: 'pi pi-pen-to-square',
@@ -697,9 +705,9 @@ const DomandaEditInstructorManager = () => {
} }
const shouldDisableField = useCallback((fieldName) => { const shouldDisableField = useCallback((fieldName) => {
return !['EVALUATION', 'ADMISSIBLE'].includes(data.applicationStatus) return ['EXPIRED', 'CLOSE'].includes(data.status)
|| (['ADMISSIBLE', 'TECHNICAL_EVALUATION'].includes(data.applicationStatus) && !['criteria', 'note'].includes(fieldName)) || (['ADMISSIBLE', 'TECHNICAL_EVALUATION', 'TECHNICAL_EVALUATION', 'AWAITING_TECHNICAL_EVALUATION'].includes(data.applicationStatus)
|| (fieldName === 'files' && !isEmpty(data.amendmentDetails)) && !['criteria', 'note', 'files', 'amendmentDetails'].includes(fieldName))
}, [data.applicationStatus]); }, [data.applicationStatus]);
const headerCompleteDialog = () => { const headerCompleteDialog = () => {
@@ -932,6 +940,7 @@ const DomandaEditInstructorManager = () => {
const getAmendmentSpecialCallback = (data) => { const getAmendmentSpecialCallback = (data) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
setData(getFormattedData(data.data));
if (toast.current && data.message) { if (toast.current && data.message) {
toast.current.show({ toast.current.show({
severity: 'success', severity: 'success',
@@ -999,6 +1008,106 @@ const DomandaEditInstructorManager = () => {
setData(newData); setData(newData);
}, [data]); }, [data]);
const openSendContractForm = () => {
setIsVisibleContractForm(true);
}
const headerContractDialog = () => {
return <span>{__('Invia il contratto', 'gepafin')}</span>;
}
const hideContractDialog = () => {
setIsVisibleContractForm(false);
setContractFormData({
subject: '',
text: ''
});
}
const footerContractDialog = useCallback(() => {
let isDisabled = !contractFormData.subject || isEmpty(contractFormData.subject)
|| !contractFormData.text || isEmpty(contractFormData.text)
|| !contractFormData.files || isEmpty(contractFormData.files) || isAsyncRequest;
return <div>
<Button type="button" label={__('Annulla', 'gepafin')} onClick={hideContractDialog} outlined/>
<Button
type="button"
disabled={isDisabled}
label={__('Invia', 'gepafin')} onClick={doSendContract}/>
</div>
}, [contractFormData]);
const updateContractFormData = (value, path) => {
const newData = wrap(contractFormData).set(path.split('.'), value).value();
setContractFormData(newData);
};
const doSendContract = useCallback(() => {
if (
contractFormData.subject && !isEmpty(contractFormData.subject)
&& contractFormData.text && !isEmpty(contractFormData.text)
&& contractFormData.files && !isEmpty(contractFormData.files)
) {
const formDataToSend = new FormData();
const contractFormDataTemp = {
...contractFormData
};
delete contractFormDataTemp.files;
const jsonBlob = new Blob([JSON.stringify(contractFormDataTemp)], {
type: 'application/json'
});
formDataToSend.append('applicationContractRequest', jsonBlob);
if (contractFormData.files && contractFormData.files.length > 0) {
contractFormData.files.forEach((file) => {
formDataToSend.append('contractDocuments', file);
});
}
storeSet('setAsyncRequest');
ApplicationContractService.uploadApplicationContract(
data.applicationId,
formDataToSend,
getUploadApplicationContractCallback,
errGetUploadApplicationContractCallback
);
}
}, [contractFormData]);
const getUploadApplicationContractCallback = (data) => {
if (data.status === 'SUCCESS') {
setData((prev) => ({
...prev,
applicationStatus: 'AWAITING_CONTRACT',
contract: data.data
}));
if (toast.current && data.message) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
}
hideContractDialog();
storeSet('unsetAsyncRequest');
}
const errGetUploadApplicationContractCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: data.status === 'SUCCESS' ? 'info' : 'error',
summary: '',
detail: data.message
});
}
hideContractDialog();
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
const actionBtns = () => { const actionBtns = () => {
return <div className="appPageSection__actions"> return <div className="appPageSection__actions">
{(['SOCCORSO', 'CLOSE', 'EVALUATION', 'NDG', 'APPOINTMENT', 'ADMISSIBLE', {(['SOCCORSO', 'CLOSE', 'EVALUATION', 'NDG', 'APPOINTMENT', 'ADMISSIBLE',
@@ -1035,8 +1144,7 @@ const DomandaEditInstructorManager = () => {
onClick={() => doSaveDraft()} onClick={() => doSaveDraft()}
label={__('Crea valutazione', 'gepafin')} label={__('Crea valutazione', 'gepafin')}
icon="pi pi-save" iconPos="right"/>} icon="pi pi-save" iconPos="right"/>}
{APP_EVALUATION_FLOW_ID === '1' && ['EVALUATION'].includes(data.applicationStatus) {APP_EVALUATION_FLOW_ID === '1' && APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
&& APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <Button ? <Button
type="button" type="button"
disabled={!data.id disabled={!data.id
@@ -1103,7 +1211,7 @@ const DomandaEditInstructorManager = () => {
? __('Punteggio sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin') ? __('Punteggio sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin')
: __('Punteggio non sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin')} : __('Punteggio non sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin')}
severity={isAdmissible ? 'success' : 'warning'} severity={isAdmissible ? 'success' : 'warning'}
model={technicalEvalItems}/> : null} model={tecnicalEvalItems}/> : null}
<Button <Button
type="button" type="button"
disabled={!data.id || !['TECHNICAL_EVALUATION'].includes(data.applicationStatus) || evaluationBlockedForUser(data)} disabled={!data.id || !['TECHNICAL_EVALUATION'].includes(data.applicationStatus) || evaluationBlockedForUser(data)}
@@ -1135,6 +1243,13 @@ const DomandaEditInstructorManager = () => {
onClick={initiateRejecting} onClick={initiateRejecting}
label={__('Respingi domanda', 'gepafin')} label={__('Respingi domanda', 'gepafin')}
icon="pi pi-times" iconPos="right"/> : null} icon="pi pi-times" iconPos="right"/> : null}
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE' && data.applicationStatus === 'APPROVED'
? <Button
type="button"
disabled={!data.id}
onClick={openSendContractForm}
label={__('Invia il contratto', 'gepafin')}
/> : null}
</div> </div>
} }
@@ -1167,7 +1282,7 @@ const DomandaEditInstructorManager = () => {
? <div className="appPageSection__message info"> ? <div className="appPageSection__message info">
<i className="pi pi-info-circle"></i> <i className="pi pi-info-circle"></i>
<span className="summary">{__('Motivazione:', 'gepafin')}</span> <span className="summary">{__('Motivazione:', 'gepafin')}</span>
<div style={{display: 'flex', flexDirection: 'column', gap: 10}}> <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<span>{renderHtmlContent(data.motivation)}</span> <span>{renderHtmlContent(data.motivation)}</span>
{rejectionFiles()} {rejectionFiles()}
</div> </div>
@@ -1329,6 +1444,76 @@ const DomandaEditInstructorManager = () => {
{data.evaluationVersion === 'V2' {data.evaluationVersion === 'V2'
? <div className="appPageSection"> ? <div className="appPageSection">
<h3>{__('Contratto', 'gepafin')}</h3>
{data.contract && data.contract?.beneficiaryDocuments && !isEmpty(data.contract?.beneficiaryDocuments)
? <ol className="appPageSection__list">
{data.contract?.beneficiaryDocuments.map(o => <li key={o.id} className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(o.name)}</div>
<div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(o.fileDetail[0].filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(o.name)}
fileId={o.id}
updateFn={(attachId) => {
setData((prev) => {
const newDocuments = data.contract.beneficiaryDocuments
.map(ob => {
return ob.id === o.id
? {...o, documentAttachmentId: attachId}
: o;
});
return {
...prev,
contract: {
...prev.contract,
beneficiaryDocuments: newDocuments
}
}
});
}}
isSignedDocument={true}
docAttachmentId={o.documentAttachmentId}/>
</div>
</div>
</li>)}
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h2>{__('Domanda PDF firmato', 'gepafin')}</h2>
{data.signedDocument && !isEmpty(data.signedDocument)
? <ol className="appPageSection__list">
<li className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(data.signedDocument.fileName)}</div>
<div className="appPageSection__iconActions">
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(data.signedDocument.fileName)}
fileId={data.signedDocument.id}
updateFn={(attachId) => {
setData((prev) => ({
...prev,
signedDocument: {
...prev.signedDocument,
documentAttachmentId: attachId
}
}));
}}
isSignedDocument={true}
docAttachmentId={data.signedDocument.documentAttachmentId}/>
</div>
</div>
</li>
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h2>{__('Documenti allegati', 'gepafin')}</h2> <h2>{__('Documenti allegati', 'gepafin')}</h2>
{!isEmpty(data.files) {!isEmpty(data.files)
? <ListOfFiles ? <ListOfFiles
@@ -1441,6 +1626,82 @@ const DomandaEditInstructorManager = () => {
</div> </div>
</div> </div>
<div> <div>
<h3>{__('Contratto', 'gepafin')}</h3>
{data.contract && data.contract?.beneficiaryDocuments && !isEmpty(data.contract?.beneficiaryDocuments)
? <ol className="appPageSection__list">
{data.contract?.beneficiaryDocuments.map(o => <li key={o.id} className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(o.name)}</div>
<div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(o.fileDetail[0].filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(o.name)}
fileId={o.id}
updateFn={(attachId) => {
setData((prev) => {
const newDocuments = data.contract.beneficiaryDocuments
.map(ob => {
return ob.id === o.id
? {...o, documentAttachmentId: attachId}
: o;
});
return {
...prev,
contract: {
...prev.contract,
beneficiaryDocuments: newDocuments
}
}
});
}}
isSignedDocument={true}
docAttachmentId={o.documentAttachmentId}/>
</div>
</div>
</li>)}
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h3>{__('Domanda PDF firmato', 'gepafin')}</h3>
{data.signedDocument && !isEmpty(data.signedDocument)
? <ol className="appPageSection__list">
<li className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(data.signedDocument.fileName)}</div>
<div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(data.signedDocument.filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(data.signedDocument.fileName)}
fileId={data.signedDocument.id}
updateFn={(attachId) => {
setData((prev) => ({
...prev,
signedDocument: {
...prev.signedDocument,
documentAttachmentId: attachId
}
}));
}}
isSignedDocument={true}
docAttachmentId={data.signedDocument.documentAttachmentId}/>
</div>
</div>
</li>
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h3>{__('Documenti allegati', 'gepafin')}</h3> <h3>{__('Documenti allegati', 'gepafin')}</h3>
{!isEmpty(data.files) {!isEmpty(data.files)
? <ListOfFiles ? <ListOfFiles
@@ -1592,7 +1853,8 @@ const DomandaEditInstructorManager = () => {
</div> : null} </div> : null}
{operationType === 'reject' && APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE' {operationType === 'reject' && APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <div className="appForm__field"> ? <div className="appForm__field">
<label className={classNames({ 'p-error': !finalDialogData.reason || isEmpty(finalDialogData.reason) })}> <label
className={classNames({ 'p-error': !finalDialogData.reason || isEmpty(finalDialogData.reason) })}>
{__('Oggetto', 'gepafin')} {__('Oggetto', 'gepafin')}
</label> </label>
<Dropdown <Dropdown
@@ -1619,7 +1881,8 @@ const DomandaEditInstructorManager = () => {
style={{ width: '100%' }}/> style={{ width: '100%' }}/>
</div> : null} </div> : null}
<div className="appForm__field"> <div className="appForm__field">
<label className={classNames({ 'p-error': !finalDialogData.motivation || isEmpty(finalDialogData.motivation) })}> <label
className={classNames({ 'p-error': !finalDialogData.motivation || isEmpty(finalDialogData.motivation) })}>
{__('Motivazione', 'gepafin')} {__('Motivazione', 'gepafin')}
</label> </label>
<div translate="no"> <div translate="no">
@@ -1666,13 +1929,19 @@ const DomandaEditInstructorManager = () => {
icon: 'pi pi-plus' icon: 'pi pi-plus'
}} }}
itemTemplate={(file, props) => { itemTemplate={(file, props) => {
return( return (
<div className="p-fileupload-row" data-pc-section="file"> <div className="p-fileupload-row" data-pc-section="file">
<div data-pc-section="details" style={{display: 'flex', flexDirection: 'column', gap: '10px', textAlign: 'left'}}> <div data-pc-section="details" style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
textAlign: 'left'
}}>
<div className="p-fileupload-filename" data-pc-section="filename"> <div className="p-fileupload-filename" data-pc-section="filename">
{file.name} {file.name}
</div> </div>
<span data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span> <span
data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span>
</div> </div>
<div data-pc-section="actions"> <div data-pc-section="actions">
<Button <Button
@@ -1751,8 +2020,10 @@ const DomandaEditInstructorManager = () => {
onHide={hidePreTecEvalDialog}> onHide={hidePreTecEvalDialog}>
<div className="appForm__field"> <div className="appForm__field">
<label <label
className={classNames({ 'p-error': isEmpty(preTecEvalData.amount) className={classNames({
|| isNaN(parseFloat(preTecEvalData.amount)) || parseFloat(preTecEvalData.amount) <= 0 })}> 'p-error': isEmpty(preTecEvalData.amount)
|| isNaN(parseFloat(preTecEvalData.amount)) || parseFloat(preTecEvalData.amount) <= 0
})}>
{__('Importo', 'gepafin')} {__('Importo', 'gepafin')}
</label> </label>
<InputNumber <InputNumber
@@ -1764,6 +2035,102 @@ const DomandaEditInstructorManager = () => {
</div> </div>
</Dialog> </Dialog>
<Dialog
visible={isVisibleContractForm}
modal
header={headerContractDialog}
footer={footerContractDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideContractDialog}>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.subject || isEmpty(contractFormData.subject) })}>
{__('Oggetto', 'gepafin')}*
</label>
<InputText
value={contractFormData.subject}
invalid={isEmpty(contractFormData.subject)}
onChange={(e) => updateContractFormData(e.target.value, 'subject')}/>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.text || isEmpty(contractFormData.text) })}>
{__('Testo', 'gepafin')}*
</label>
<div translate="no">
<Editor
value={contractFormData.text}
readOnly={loading}
placeholder={__('Digita qui il messagio', 'gepafin')}
headerTemplate={header}
onTextChange={(e) => updateContractFormData(e.htmlValue, 'text')}
style={{ height: 80 * 3, width: '100%' }}
/>
</div>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.files || isEmpty(contractFormData.files) })}>
{__('Files', 'gepafin')}*
</label>
<FileUpload
ref={contractFormFilesRef}
name="files[]"
multiple
accept={mimeTypes.map(o => o.code).join(',')}
maxFileSize={defaultMaxFileSize}
auto={false}
customUpload={true}
className={classNames({ 'p-invalid': !contractFormData.files || isEmpty(contractFormData.files) })}
onSelect={(e) => {
updateContractFormData(e.files, 'files');
}}
onRemove={(e) => {
const updatedFiles = contractFormFilesRef.current.getFiles();
updateContractFormData(updatedFiles, 'files');
}}
headerTemplate={(options) => {
const { chooseButton } = options;
return (
<div className="p-fileupload-buttonbar" data-pc-section="buttonbar">
{chooseButton}
</div>
);
}}
chooseOptions={{
label: __('Aggiungi i file', 'gepafin'),
icon: 'pi pi-plus'
}}
itemTemplate={(file, props) => {
return (
<div className="p-fileupload-row" data-pc-section="file">
<div data-pc-section="details" style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
textAlign: 'left'
}}>
<div className="p-fileupload-filename" data-pc-section="filename">
{file.name}
</div>
<span
data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span>
</div>
<div data-pc-section="actions">
<Button
type="button"
icon="pi pi-times"
className="p-button-rounded p-button-danger p-button-text"
onClick={() => props.onRemove()}
/>
</div>
</div>
)
}}
emptyTemplate={<p className="m-0">{__('Trascina i file qua')}</p>}
/>
</div>
</Dialog>
</div> </div>
: <> : <>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton> <Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>

View File

@@ -28,7 +28,8 @@ const ArchiveDocument = ({
fileId = 0, fileId = 0,
fileDescr = '', fileDescr = '',
docAttachmentId = null, docAttachmentId = null,
updateFn = () => {} updateFn = () => {},
isSignedDocument = false
}) => { }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isVisibleDialog, setIsVisibleDialog] = useState(false); const [isVisibleDialog, setIsVisibleDialog] = useState(false);
@@ -93,7 +94,9 @@ const ArchiveDocument = ({
} }
} }
AppointmentService.archiveDocument(applicationId, fileId, submitData, submitCallback, errSubmitCallback) AppointmentService.archiveDocument(applicationId, fileId, submitData, submitCallback, errSubmitCallback, [
['isSignedDocument', isSignedDocument]
]);
} }
} }

View File

@@ -15,6 +15,7 @@ import { storeGet, storeSet, useStoreValue } from '../../store';
import ApplicationEvaluationService from '../../service/application-evaluation-service'; import ApplicationEvaluationService from '../../service/application-evaluation-service';
import AmendmentsService from '../../service/amendments-service'; import AmendmentsService from '../../service/amendments-service';
import AppointmentService from '../../service/appointment-service'; import AppointmentService from '../../service/appointment-service';
import ApplicationContractService from '../../service/application-contract-service';
// tools // tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse'; import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
@@ -60,6 +61,7 @@ import { SplitButton } from 'primereact/splitbutton';
import { FileUpload } from 'primereact/fileupload'; import { FileUpload } from 'primereact/fileupload';
import { defaultMaxFileSize, mimeTypes, rejectionReasons } from '../../configData'; import { defaultMaxFileSize, mimeTypes, rejectionReasons } from '../../configData';
import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText'; import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText';
import ArchiveDocument from './components/ArchiveDocument';
const APP_EVALUATION_FLOW_ID = process.env.REACT_APP_EVALUATION_FLOW_ID; const APP_EVALUATION_FLOW_ID = process.env.REACT_APP_EVALUATION_FLOW_ID;
const APP_HUB_ID = process.env.REACT_APP_HUB_ID; const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
@@ -98,6 +100,12 @@ const DomandaEditPreInstructor = () => {
duration: 0, duration: 0,
amount: 0 amount: 0
}); });
const [isVisibleContractForm, setIsVisibleContractForm] = useState(false);
const [contractFormData, setContractFormData] = useState({
subject: '',
text: ''
});
const contractFormFilesRef = useRef(null);
const [formData, setFormData] = useState([]); const [formData, setFormData] = useState([]);
const [formId, setFormId] = useState(0); const [formId, setFormId] = useState(0);
const [formInitialData, setFormInitialData] = useState(null); const [formInitialData, setFormInitialData] = useState(null);
@@ -225,7 +233,7 @@ const DomandaEditPreInstructor = () => {
const getCallback = (resp) => { const getCallback = (resp) => {
if (resp.status === 'SUCCESS') { if (resp.status === 'SUCCESS') {
setData(getFormattedData(resp.data)); setData(getFormattedData(resp.data));
setFinalDialogData((prev) => ({...prev, motivation: resp.data.motivation})); setFinalDialogData((prev) => ({ ...prev, motivation: resp.data.motivation }));
updateFlagsForSoccorso(resp.data); updateFlagsForSoccorso(resp.data);
if (resp.data.evaluationVersion === 'V2') { if (resp.data.evaluationVersion === 'V2') {
@@ -290,7 +298,7 @@ const DomandaEditPreInstructor = () => {
}; };
const header = renderHeader(); const header = renderHeader();
const technicalEvalItems = [ const tecnicalEvalItems = [
{ {
label: __('Nessuna garanzia', 'gepafin'), label: __('Nessuna garanzia', 'gepafin'),
icon: 'pi pi-pen-to-square', icon: 'pi pi-pen-to-square',
@@ -697,9 +705,9 @@ const DomandaEditPreInstructor = () => {
} }
const shouldDisableField = useCallback((fieldName) => { const shouldDisableField = useCallback((fieldName) => {
return !['EVALUATION', 'ADMISSIBLE'].includes(data.applicationStatus) return ['EXPIRED', 'CLOSE'].includes(data.status)
|| (['ADMISSIBLE', 'TECHNICAL_EVALUATION'].includes(data.applicationStatus) && !['criteria', 'note'].includes(fieldName)) || (['ADMISSIBLE', 'TECHNICAL_EVALUATION', 'TECHNICAL_EVALUATION', 'AWAITING_TECHNICAL_EVALUATION'].includes(data.applicationStatus)
|| (fieldName === 'files' && !isEmpty(data.amendmentDetails)) && !['criteria', 'note', 'files', 'amendmentDetails'].includes(fieldName))
}, [data.applicationStatus]); }, [data.applicationStatus]);
const headerCompleteDialog = () => { const headerCompleteDialog = () => {
@@ -932,6 +940,7 @@ const DomandaEditPreInstructor = () => {
const getAmendmentSpecialCallback = (data) => { const getAmendmentSpecialCallback = (data) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
setData(getFormattedData(data.data));
if (toast.current && data.message) { if (toast.current && data.message) {
toast.current.show({ toast.current.show({
severity: 'success', severity: 'success',
@@ -999,6 +1008,106 @@ const DomandaEditPreInstructor = () => {
setData(newData); setData(newData);
}, [data]); }, [data]);
const openSendContractForm = () => {
setIsVisibleContractForm(true);
}
const headerContractDialog = () => {
return <span>{__('Invia il contratto', 'gepafin')}</span>;
}
const hideContractDialog = () => {
setIsVisibleContractForm(false);
setContractFormData({
subject: '',
text: ''
});
}
const footerContractDialog = useCallback(() => {
let isDisabled = !contractFormData.subject || isEmpty(contractFormData.subject)
|| !contractFormData.text || isEmpty(contractFormData.text)
|| !contractFormData.files || isEmpty(contractFormData.files) || isAsyncRequest;
return <div>
<Button type="button" label={__('Annulla', 'gepafin')} onClick={hideContractDialog} outlined/>
<Button
type="button"
disabled={isDisabled}
label={__('Invia', 'gepafin')} onClick={doSendContract}/>
</div>
}, [contractFormData]);
const updateContractFormData = (value, path) => {
const newData = wrap(contractFormData).set(path.split('.'), value).value();
setContractFormData(newData);
};
const doSendContract = useCallback(() => {
if (
contractFormData.subject && !isEmpty(contractFormData.subject)
&& contractFormData.text && !isEmpty(contractFormData.text)
&& contractFormData.files && !isEmpty(contractFormData.files)
) {
const formDataToSend = new FormData();
const contractFormDataTemp = {
...contractFormData
};
delete contractFormDataTemp.files;
const jsonBlob = new Blob([JSON.stringify(contractFormDataTemp)], {
type: 'application/json'
});
formDataToSend.append('applicationContractRequest', jsonBlob);
if (contractFormData.files && contractFormData.files.length > 0) {
contractFormData.files.forEach((file) => {
formDataToSend.append('contractDocuments', file);
});
}
storeSet('setAsyncRequest');
ApplicationContractService.uploadApplicationContract(
data.applicationId,
formDataToSend,
getUploadApplicationContractCallback,
errGetUploadApplicationContractCallback
);
}
}, [contractFormData]);
const getUploadApplicationContractCallback = (data) => {
if (data.status === 'SUCCESS') {
setData((prev) => ({
...prev,
applicationStatus: 'AWAITING_CONTRACT',
contract: data.data
}));
if (toast.current && data.message) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
}
hideContractDialog();
storeSet('unsetAsyncRequest');
}
const errGetUploadApplicationContractCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: data.status === 'SUCCESS' ? 'info' : 'error',
summary: '',
detail: data.message
});
}
hideContractDialog();
set404FromErrorResponse(data);
storeSet('unsetAsyncRequest');
}
const actionBtns = () => { const actionBtns = () => {
return <div className="appPageSection__actions"> return <div className="appPageSection__actions">
{(['SOCCORSO', 'CLOSE', 'EVALUATION', 'NDG', 'APPOINTMENT', 'ADMISSIBLE', {(['SOCCORSO', 'CLOSE', 'EVALUATION', 'NDG', 'APPOINTMENT', 'ADMISSIBLE',
@@ -1035,8 +1144,7 @@ const DomandaEditPreInstructor = () => {
onClick={() => doSaveDraft()} onClick={() => doSaveDraft()}
label={__('Crea valutazione', 'gepafin')} label={__('Crea valutazione', 'gepafin')}
icon="pi pi-save" iconPos="right"/>} icon="pi pi-save" iconPos="right"/>}
{APP_EVALUATION_FLOW_ID === '1' && ['EVALUATION'].includes(data.applicationStatus) {APP_EVALUATION_FLOW_ID === '1' && APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
&& APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <Button ? <Button
type="button" type="button"
disabled={!data.id disabled={!data.id
@@ -1103,7 +1211,7 @@ const DomandaEditPreInstructor = () => {
? __('Punteggio sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin') ? __('Punteggio sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin')
: __('Punteggio non sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin')} : __('Punteggio non sufficiente per passaggio alla valutazione tecnica ed economico finanziaria', 'gepafin')}
severity={isAdmissible ? 'success' : 'warning'} severity={isAdmissible ? 'success' : 'warning'}
model={technicalEvalItems}/> : null} model={tecnicalEvalItems}/> : null}
<Button <Button
type="button" type="button"
disabled={!data.id || !['TECHNICAL_EVALUATION'].includes(data.applicationStatus) || evaluationBlockedForUser(data)} disabled={!data.id || !['TECHNICAL_EVALUATION'].includes(data.applicationStatus) || evaluationBlockedForUser(data)}
@@ -1135,6 +1243,13 @@ const DomandaEditPreInstructor = () => {
onClick={initiateRejecting} onClick={initiateRejecting}
label={__('Respingi domanda', 'gepafin')} label={__('Respingi domanda', 'gepafin')}
icon="pi pi-times" iconPos="right"/> : null} icon="pi pi-times" iconPos="right"/> : null}
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE' && data.applicationStatus === 'APPROVED'
? <Button
type="button"
disabled={!data.id}
onClick={openSendContractForm}
label={__('Invia il contratto', 'gepafin')}
/> : null}
</div> </div>
} }
@@ -1167,7 +1282,7 @@ const DomandaEditPreInstructor = () => {
? <div className="appPageSection__message info"> ? <div className="appPageSection__message info">
<i className="pi pi-info-circle"></i> <i className="pi pi-info-circle"></i>
<span className="summary">{__('Motivazione:', 'gepafin')}</span> <span className="summary">{__('Motivazione:', 'gepafin')}</span>
<div style={{display: 'flex', flexDirection: 'column', gap: 10}}> <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<span>{renderHtmlContent(data.motivation)}</span> <span>{renderHtmlContent(data.motivation)}</span>
{rejectionFiles()} {rejectionFiles()}
</div> </div>
@@ -1329,6 +1444,76 @@ const DomandaEditPreInstructor = () => {
{data.evaluationVersion === 'V2' {data.evaluationVersion === 'V2'
? <div className="appPageSection"> ? <div className="appPageSection">
<h3>{__('Contratto', 'gepafin')}</h3>
{data.contract && data.contract?.beneficiaryDocuments && !isEmpty(data.contract?.beneficiaryDocuments)
? <ol className="appPageSection__list">
{data.contract?.beneficiaryDocuments.map(o => <li key={o.id} className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(o.name)}</div>
<div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(o.fileDetail[0].filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(o.name)}
fileId={o.id}
updateFn={(attachId) => {
setData((prev) => {
const newDocuments = data.contract.beneficiaryDocuments
.map(ob => {
return ob.id === o.id
? {...o, documentAttachmentId: attachId}
: o;
});
return {
...prev,
contract: {
...prev.contract,
beneficiaryDocuments: newDocuments
}
}
});
}}
isSignedDocument={true}
docAttachmentId={o.documentAttachmentId}/>
</div>
</div>
</li>)}
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h2>{__('Domanda PDF firmato', 'gepafin')}</h2>
{data.signedDocument && !isEmpty(data.signedDocument)
? <ol className="appPageSection__list">
<li className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(data.signedDocument.fileName)}</div>
<div className="appPageSection__iconActions">
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(data.signedDocument.fileName)}
fileId={data.signedDocument.id}
updateFn={(attachId) => {
setData((prev) => ({
...prev,
signedDocument: {
...prev.signedDocument,
documentAttachmentId: attachId
}
}));
}}
isSignedDocument={true}
docAttachmentId={data.signedDocument.documentAttachmentId}/>
</div>
</div>
</li>
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h2>{__('Documenti allegati', 'gepafin')}</h2> <h2>{__('Documenti allegati', 'gepafin')}</h2>
{!isEmpty(data.files) {!isEmpty(data.files)
? <ListOfFiles ? <ListOfFiles
@@ -1441,6 +1626,82 @@ const DomandaEditPreInstructor = () => {
</div> </div>
</div> </div>
<div> <div>
<h3>{__('Contratto', 'gepafin')}</h3>
{data.contract && data.contract?.beneficiaryDocuments && !isEmpty(data.contract?.beneficiaryDocuments)
? <ol className="appPageSection__list">
{data.contract?.beneficiaryDocuments.map(o => <li key={o.id} className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(o.name)}</div>
<div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(o.fileDetail[0].filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(o.name)}
fileId={o.id}
updateFn={(attachId) => {
setData((prev) => {
const newDocuments = data.contract.beneficiaryDocuments
.map(ob => {
return ob.id === o.id
? {...o, documentAttachmentId: attachId}
: o;
});
return {
...prev,
contract: {
...prev.contract,
beneficiaryDocuments: newDocuments
}
}
});
}}
isSignedDocument={true}
docAttachmentId={o.documentAttachmentId}/>
</div>
</div>
</li>)}
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h3>{__('Domanda PDF firmato', 'gepafin')}</h3>
{data.signedDocument && !isEmpty(data.signedDocument)
? <ol className="appPageSection__list">
<li className="appPageSection__listItem">
<div className="appPageSection__listItemRow">
<div>{renderHtmlContent(data.signedDocument.fileName)}</div>
<div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(data.signedDocument.filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
<ArchiveDocument
ndg={data.ndg}
applicationId={id}
fileDescr={renderHtmlContent(data.signedDocument.fileName)}
fileId={data.signedDocument.id}
updateFn={(attachId) => {
setData((prev) => ({
...prev,
signedDocument: {
...prev.signedDocument,
documentAttachmentId: attachId
}
}));
}}
isSignedDocument={true}
docAttachmentId={data.signedDocument.documentAttachmentId}/>
</div>
</div>
</li>
</ol>
: <p>{__('Nessun documento firmato', 'gepafin')}</p>}
<h3>{__('Documenti allegati', 'gepafin')}</h3> <h3>{__('Documenti allegati', 'gepafin')}</h3>
{!isEmpty(data.files) {!isEmpty(data.files)
? <ListOfFiles ? <ListOfFiles
@@ -1592,7 +1853,8 @@ const DomandaEditPreInstructor = () => {
</div> : null} </div> : null}
{operationType === 'reject' && APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE' {operationType === 'reject' && APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <div className="appForm__field"> ? <div className="appForm__field">
<label className={classNames({ 'p-error': !finalDialogData.reason || isEmpty(finalDialogData.reason) })}> <label
className={classNames({ 'p-error': !finalDialogData.reason || isEmpty(finalDialogData.reason) })}>
{__('Oggetto', 'gepafin')} {__('Oggetto', 'gepafin')}
</label> </label>
<Dropdown <Dropdown
@@ -1619,7 +1881,8 @@ const DomandaEditPreInstructor = () => {
style={{ width: '100%' }}/> style={{ width: '100%' }}/>
</div> : null} </div> : null}
<div className="appForm__field"> <div className="appForm__field">
<label className={classNames({ 'p-error': !finalDialogData.motivation || isEmpty(finalDialogData.motivation) })}> <label
className={classNames({ 'p-error': !finalDialogData.motivation || isEmpty(finalDialogData.motivation) })}>
{__('Motivazione', 'gepafin')} {__('Motivazione', 'gepafin')}
</label> </label>
<div translate="no"> <div translate="no">
@@ -1666,13 +1929,19 @@ const DomandaEditPreInstructor = () => {
icon: 'pi pi-plus' icon: 'pi pi-plus'
}} }}
itemTemplate={(file, props) => { itemTemplate={(file, props) => {
return( return (
<div className="p-fileupload-row" data-pc-section="file"> <div className="p-fileupload-row" data-pc-section="file">
<div data-pc-section="details" style={{display: 'flex', flexDirection: 'column', gap: '10px', textAlign: 'left'}}> <div data-pc-section="details" style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
textAlign: 'left'
}}>
<div className="p-fileupload-filename" data-pc-section="filename"> <div className="p-fileupload-filename" data-pc-section="filename">
{file.name} {file.name}
</div> </div>
<span data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span> <span
data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span>
</div> </div>
<div data-pc-section="actions"> <div data-pc-section="actions">
<Button <Button
@@ -1751,8 +2020,10 @@ const DomandaEditPreInstructor = () => {
onHide={hidePreTecEvalDialog}> onHide={hidePreTecEvalDialog}>
<div className="appForm__field"> <div className="appForm__field">
<label <label
className={classNames({ 'p-error': isEmpty(preTecEvalData.amount) className={classNames({
|| isNaN(parseFloat(preTecEvalData.amount)) || parseFloat(preTecEvalData.amount) <= 0 })}> 'p-error': isEmpty(preTecEvalData.amount)
|| isNaN(parseFloat(preTecEvalData.amount)) || parseFloat(preTecEvalData.amount) <= 0
})}>
{__('Importo', 'gepafin')} {__('Importo', 'gepafin')}
</label> </label>
<InputNumber <InputNumber
@@ -1764,6 +2035,102 @@ const DomandaEditPreInstructor = () => {
</div> </div>
</Dialog> </Dialog>
<Dialog
visible={isVisibleContractForm}
modal
header={headerContractDialog}
footer={footerContractDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideContractDialog}>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.subject || isEmpty(contractFormData.subject) })}>
{__('Oggetto', 'gepafin')}*
</label>
<InputText
value={contractFormData.subject}
invalid={isEmpty(contractFormData.subject)}
onChange={(e) => updateContractFormData(e.target.value, 'subject')}/>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.text || isEmpty(contractFormData.text) })}>
{__('Testo', 'gepafin')}*
</label>
<div translate="no">
<Editor
value={contractFormData.text}
readOnly={loading}
placeholder={__('Digita qui il messagio', 'gepafin')}
headerTemplate={header}
onTextChange={(e) => updateContractFormData(e.htmlValue, 'text')}
style={{ height: 80 * 3, width: '100%' }}
/>
</div>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': !contractFormData.files || isEmpty(contractFormData.files) })}>
{__('Files', 'gepafin')}*
</label>
<FileUpload
ref={contractFormFilesRef}
name="files[]"
multiple
accept={mimeTypes.map(o => o.code).join(',')}
maxFileSize={defaultMaxFileSize}
auto={false}
customUpload={true}
className={classNames({ 'p-invalid': !contractFormData.files || isEmpty(contractFormData.files) })}
onSelect={(e) => {
updateContractFormData(e.files, 'files');
}}
onRemove={(e) => {
const updatedFiles = contractFormFilesRef.current.getFiles();
updateContractFormData(updatedFiles, 'files');
}}
headerTemplate={(options) => {
const { chooseButton } = options;
return (
<div className="p-fileupload-buttonbar" data-pc-section="buttonbar">
{chooseButton}
</div>
);
}}
chooseOptions={{
label: __('Aggiungi i file', 'gepafin'),
icon: 'pi pi-plus'
}}
itemTemplate={(file, props) => {
return (
<div className="p-fileupload-row" data-pc-section="file">
<div data-pc-section="details" style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
textAlign: 'left'
}}>
<div className="p-fileupload-filename" data-pc-section="filename">
{file.name}
</div>
<span
data-pc-section="filesize">{getFormatedFileSizeText(file.size)}</span>
</div>
<div data-pc-section="actions">
<Button
type="button"
icon="pi pi-times"
className="p-button-rounded p-button-danger p-button-text"
onClick={() => props.onRemove()}
/>
</div>
</div>
)
}}
emptyTemplate={<p className="m-0">{__('Trascina i file qua')}</p>}
/>
</div>
</Dialog>
</div> </div>
: <> : <>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton> <Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>

View File

@@ -15,7 +15,10 @@ const DomandeArchive = () => {
<div className="appPageSection"> <div className="appPageSection">
<h2>{__('Domande completate', 'gepafin')}</h2> <h2>{__('Domande completate', 'gepafin')}</h2>
<DomandeTablePreInstructorAsync statuses={['CLOSE']} applicationStatuses={['APPROVED', 'REJECTED', 'TECHNICAL_EVALUATION_REJECTED']} userId={0}/> <DomandeTablePreInstructorAsync
statuses={['CLOSE', 'AWAITING_CONTRACT', 'CONTRACT_SIGNED']}
applicationStatuses={['APPROVED', 'REJECTED', 'TECHNICAL_EVALUATION_REJECTED', 'AWAITING_CONTRACT', 'CONTRACT_SIGNED']}
userId={0}/>
</div> </div>
</div> </div>
) )

View File

@@ -20,7 +20,10 @@ const DomandeArchivePreInstructor = () => {
<div className="appPageSection"> <div className="appPageSection">
<h2>{__('Domande completate', 'gepafin')}</h2> <h2>{__('Domande completate', 'gepafin')}</h2>
<DomandeTablePreInstructorAsync statuses={['CLOSE']} applicationStatuses={['APPROVED', 'REJECTED', 'TECHNICAL_EVALUATION_REJECTED']} userId={userData.id}/> <DomandeTablePreInstructorAsync
statuses={['CLOSE', 'AWAITING_CONTRACT', 'CONTRACT_SIGNED']}
applicationStatuses={['APPROVED', 'REJECTED', 'TECHNICAL_EVALUATION_REJECTED', 'AWAITING_CONTRACT', 'CONTRACT_SIGNED']}
userId={userData.id}/>
</div> </div>
</div> </div>
) )

View File

@@ -44,7 +44,7 @@ const AllDomandeBeneficiarioTableAsync = ({ statuses }) => {
companyName: { value: null, matchMode: 'contains' }, companyName: { value: null, matchMode: 'contains' },
submissionDate: { value: null, matchMode: 'dateIs' }, submissionDate: { value: null, matchMode: 'dateIs' },
assignedUserName: { value: null, matchMode: 'equals' }, assignedUserName: { value: null, matchMode: 'equals' },
status: { value: null, matchMode: 'equals' } applicationStatus: { value: null, matchMode: 'equals' }
} }
}); });
@@ -122,13 +122,14 @@ const AllDomandeBeneficiarioTableAsync = ({ statuses }) => {
options.filterCallback(e.value, options.index) options.filterCallback(e.value, options.index)
const filters = { ...lazyState.filters }; const filters = { ...lazyState.filters };
if (e.value) { if (e.value) {
filters['status'] = { value: e.value, matchMode: 'equals' }; filters['applicationStatus'] = { value: e.value, matchMode: 'equals' };
} else { } else {
delete filters['status']; delete filters['applicationStatus'];
} }
setLazyState({ ...lazyState, filters, first: 0 }); setLazyState({ ...lazyState, filters, first: 0 });
}} }}
itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel} className="p-column-filter"/>; itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel}
className="p-column-filter"/>;
}; };
const dateFilterTemplate = (options) => { const dateFilterTemplate = (options) => {
@@ -182,7 +183,7 @@ const AllDomandeBeneficiarioTableAsync = ({ statuses }) => {
filterMatchModeOptions={translationStrings.dateFilterOptions} filterMatchModeOptions={translationStrings.dateFilterOptions}
style={{ minWidth: '8rem' }} style={{ minWidth: '8rem' }}
body={dateAppliedBodyTemplate}/> body={dateAppliedBodyTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')} <Column field="applicationStatus" header={__('Stato', 'gepafin')}
filterElement={statusFilterTemplate} filter filterElement={statusFilterTemplate} filter
filterMatchModeOptions={translationStrings.statusFilterOptions} filterMatchModeOptions={translationStrings.statusFilterOptions}
style={{ minWidth: '8rem' }} style={{ minWidth: '8rem' }}

View File

@@ -23,8 +23,10 @@ const DomandeBeneficiario = () => {
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
<div className="appPageSection"> <div className="appPageSection">
<AllDomandeBeneficiarioTableAsync statuses={['SOCCORSO', 'APPROVED', 'REJECTED', 'EVALUATION', 'SUBMIT', <AllDomandeBeneficiarioTableAsync
'APPOINTMENT', 'NDG', 'ADMISSIBLE', 'AWAITING_TECHNICAL_EVALUATION', 'TECHNICAL_EVALUATION']}/> statuses={['SOCCORSO', 'APPROVED', 'REJECTED', 'EVALUATION', 'SUBMIT',
'APPOINTMENT', 'NDG', 'ADMISSIBLE', 'AWAITING_TECHNICAL_EVALUATION', 'TECHNICAL_EVALUATION',
'AWAITING_CONTRACT', 'CONTRACT_SIGNED']}/>
</div> </div>
</div> </div>
) )

View File

@@ -56,7 +56,7 @@ const SoccorsoEditBeneficiario = () => {
const goToArchivePage = () => { const goToArchivePage = () => {
navigate(`/domande`); navigate(`/domande`);
} }
console.log('data', data.amendmentType)
useEffect(() => { useEffect(() => {
const parsedId = parseInt(id); const parsedId = parseInt(id);
const entityId = !isNaN(parsedId) ? parsedId : 0; const entityId = !isNaN(parsedId) ? parsedId : 0;
@@ -115,6 +115,15 @@ const SoccorsoEditBeneficiario = () => {
return data; return data;
}; };
const isSpecialAmendmentValidationFailed = () => {
if (data.amendmentType === 'SPECIAL') {
const formValues = getValues();
const amendmentDocuments = formValues.amendmentDocuments;
return !amendmentDocuments || amendmentDocuments.length === 0;
}
return false;
};
const onSubmit = () => { const onSubmit = () => {
}; };
@@ -413,6 +422,9 @@ const SoccorsoEditBeneficiario = () => {
errors={errors} errors={errors}
defaultValue={formInitialData.amendmentDocuments ? formInitialData.amendmentDocuments : []} defaultValue={formInitialData.amendmentDocuments ? formInitialData.amendmentDocuments : []}
accept={[]} accept={[]}
config={{
required: true
}}
source="amendment" source="amendment"
sourceId={data.id} sourceId={data.id}
multiple={true} multiple={true}
@@ -432,7 +444,7 @@ const SoccorsoEditBeneficiario = () => {
{data.id {data.id
? <Button ? <Button
type="button" type="button"
disabled={isAsyncRequest || data.status !== 'AWAITING'} disabled={isAsyncRequest || data.status !== 'AWAITING' || isSpecialAmendmentValidationFailed()}
onClick={doUpdateAmendmentAndCompleteTask} onClick={doUpdateAmendmentAndCompleteTask}
label={__('Invia documenti', 'gepafin')} label={__('Invia documenti', 'gepafin')}
icon="pi pi-save" iconPos="right"/> : null} icon="pi pi-save" iconPos="right"/> : null}

View File

@@ -0,0 +1,18 @@
import { NetworkService } from './network-service';
const API_BASE_URL = process.env.REACT_APP_API_EXECUTION_ADDRESS;
export default class ApplicationContractService {
static getContractByUserId = (callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/applicationContract/user`, callback, errCallback, queryParams);
};
static uploadApplicationContract = (applicationId, body, callback, errCallback, queryParams) => {
NetworkService.postMultiPart(`${API_BASE_URL}/applicationContract/application/${applicationId}`, body, callback, errCallback, queryParams);
};
static updateApplicationContract = (contractId, body, callback, errCallback, queryParams) => {
NetworkService.putMultiPart(`${API_BASE_URL}/applicationContract/${contractId}`, body, callback, errCallback, queryParams);
};
}