diff --git a/.env b/.env index fc25010..871e4c5 100644 --- a/.env +++ b/.env @@ -1,6 +1,7 @@ REACT_APP_TAB_TITLE=Gepafin REACT_APP_API_EXECUTION_ADDRESS=https://api-dev-gepafin.memento.credit/v1 REACT_APP_API_ADDRESS=https://api-dev-gepafin.memento.credit +REACT_APP_API_ADDRESS_WS=https://api-dev-gepafin.memento.credit/wss REACT_APP_LOGO_FILENAME=gepafin-logo.svg REACT_APP_FAVICON_FILENAME=gepafin-favicon.ico REACT_APP_HUB_ID=p4lk3bcx1RStqTaIVVbXs diff --git a/package.json b/package.json index e58a0a4..0783782 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@emotion/styled": "11.13.0", "@number-flow/react": "0.4.2", "@sentry/browser": "^8.42.0", + "@stomp/stompjs": "^7.0.0", "@tanstack/react-table": "^8.20.5", "@wordpress/i18n": "5.8.0", "@wordpress/react-i18n": "4.8.0", @@ -36,6 +37,7 @@ "react-hook-form": "7.53.0", "react-router-dom": "6.26.2", "react-scripts": "5.0.1", + "sockjs-client": "^1.6.1", "validate.js": "0.13.1", "zustand": "4.5.4", "zustand-x": "3.0.4" diff --git a/src/assets/scss/components/appPage.scss b/src/assets/scss/components/appPage.scss index 9d3de37..6c2e5e2 100644 --- a/src/assets/scss/components/appPage.scss +++ b/src/assets/scss/components/appPage.scss @@ -11,7 +11,7 @@ font-weight: 600; line-height: normal; } - + .appPageLogin__wrapper { h1 { text-align: center; @@ -89,6 +89,7 @@ } .appPageSection { + position: relative; display: flex; flex-direction: column; align-items: flex-start; @@ -99,7 +100,7 @@ gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); width: 100%; - + /*> div { max-width: 50%; }*/ @@ -130,7 +131,7 @@ padding: 5px 0; width: 100%; } - + .col { display: flex; flex-direction: column; @@ -188,7 +189,7 @@ ul, ol { padding-left: 1rem; - + li { color: var(--global-textColor); } @@ -217,7 +218,7 @@ .appPageSection__pMeta { margin-bottom: 1em; - + span:nth-of-type(1) { max-width: 30%; } @@ -263,7 +264,7 @@ display: flex; flex-direction: column; gap: 1.2rem; - + div { display: flex; gap: 0.5rem; @@ -294,11 +295,11 @@ color: var(--message-info-color); border-color: var(--message-info-color); } - + .summary { font-weight: bold; } - + a { color: inherit; } @@ -401,7 +402,7 @@ gap: 10px; align-items: center; flex-wrap: wrap; - + &.lessGap { gap: 12px; } @@ -418,7 +419,7 @@ background-color: transparent; color: var(--global-textColor); padding: 0; - + &:hover { cursor: pointer; color: var(--message-info-color); @@ -434,10 +435,19 @@ } } +.appPageSection__emailTemplate { + > div { + max-width: 100%!important; + > div { + max-width: 100%!important; + } + } +} + @media (max-width: 700px) { .appPageSection { &.columns { grid-template-columns: 1fr; } } -} \ No newline at end of file +} diff --git a/src/assets/scss/components/notificationsSidebar.scss b/src/assets/scss/components/notificationsSidebar.scss new file mode 100644 index 0000000..2bc2eb8 --- /dev/null +++ b/src/assets/scss/components/notificationsSidebar.scss @@ -0,0 +1,55 @@ +.notificationsIcon { + &:hover { + cursor: pointer; + } +} + +.notificationsSidebar { + max-width: 360px; + width: 100%; +} + +.notificationsSidebar__loading { + padding: 30px 0; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.notificationsSidebar__list { + display: flex; + flex-direction: column; + gap: 5px; + list-style: none; + padding: 0; +} + +.notificationsSidebar__listItem { + display: flex; + justify-content: space-between; + align-items: center; + gap: 5px; + padding: 15px 0; + border-bottom: 1px solid #e7e7e7; + + &:hover { + cursor: pointer; + color: var(--primary-text); + } +} + +.notificationsSidebar__listItemContent { + display: flex; + flex-direction: column; + gap: 5px; + font-size: 14px; +} + +.notificationsSidebar__listItemChosen { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 5px; +} diff --git a/src/assets/scss/theme.scss b/src/assets/scss/theme.scss index 4ad1ef5..2b48f72 100644 --- a/src/assets/scss/theme.scss +++ b/src/assets/scss/theme.scss @@ -44,4 +44,5 @@ @import "./components/error404.scss"; @import "./components/myTable.scss"; @import "./components/evaluation.scss"; -@import "./components/fieldsRepeater.scss"; \ No newline at end of file +@import "./components/fieldsRepeater.scss"; +@import "./components/notificationsSidebar.scss"; diff --git a/src/components/NotificationsSidebar/components/NotificationItem/index.js b/src/components/NotificationsSidebar/components/NotificationItem/index.js new file mode 100644 index 0000000..3d2efc5 --- /dev/null +++ b/src/components/NotificationsSidebar/components/NotificationItem/index.js @@ -0,0 +1,22 @@ +import React from 'react'; +import getDateFromISOstring from '../../../../helpers/getDateFromISOstring'; + +const NotificationItem = ({ item, clickFn }) => { + const handleClick = () => { + clickFn(item.id); + } + + return ( +
  • +
    + {item.status === 'READ' + ?

    {item.title}

    + : {item.title}} + {getDateFromISOstring(item.createdDate)} +
    + +
  • + ) +} + +export default NotificationItem; diff --git a/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js b/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js new file mode 100644 index 0000000..4588de6 --- /dev/null +++ b/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { __ } from '@wordpress/i18n'; +import { Button } from 'primereact/button'; +import getDateFromISOstring from '../../../../helpers/getDateFromISOstring'; + +const NotificationItemChosen = ({ item, closeFn, markReadFn }) => { + return ( +
    +
    + ) +} + +export default NotificationItemChosen; diff --git a/src/components/NotificationsSidebar/index.js b/src/components/NotificationsSidebar/index.js new file mode 100644 index 0000000..31b00e5 --- /dev/null +++ b/src/components/NotificationsSidebar/index.js @@ -0,0 +1,280 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { __ } from '@wordpress/i18n'; +import { head, isEmpty, pathOr } from 'ramda'; +import SockJS from 'sockjs-client'; +import { Stomp } from '@stomp/stompjs'; + +// store +import { storeGet, useStore } from '../../store'; + +// api +import NotificationService from '../../service/notification-service'; + +// tools +import set404FromErrorResponse from '../../helpers/set404FromErrorResponse'; + +// components +import { Badge } from 'primereact/badge'; +import { Sidebar } from 'primereact/sidebar'; +import { TabPanel, TabView } from 'primereact/tabview'; +import NotificationItem from './components/NotificationItem'; +import NotificationItemChosen from './components/NotificationItemChosen'; + +const socketUrl = process.env.REACT_APP_API_ADDRESS_WS; + +const NotificationsSidebar = () => { + const chosenCompanyId = useStore().main.chosenCompanyId(); + const userData = useStore().main.userData(); + const [activeIndex, setActiveIndex] = useState(0); + const [loading, setLoading] = useState(false); + const [notificationsVisible, setNotificationsVisible] = useState(false); + const [notifications, setNotifications] = useState([]); + const [notificationsRead, setNotificationsRead] = useState([]); + const [chosenMsg, setChosenMsg] = useState({}); + const socket = useRef(null); + const stomp = useRef(null); + const [currentSubscription, setCurrentSubscription] = useState(null); + const [isConnected, setIsConnected] = useState(false); + + // Handle tab change + const handleTabChange = (e) => { + setActiveIndex(e.index); + fetchTabData(e.index); + }; + + const fetchTabData = (index) => { + setChosenMsg({}); + + if (0 === index) { + fetchMessages(); + } else { + fetchMessages('READ'); + } + } + + const chooseNotification = (id) => { + const properItems = activeIndex === 0 ? notifications : notificationsRead; + const chosen = head(properItems.filter(o => o.id === id)); + if (chosen) { + setChosenMsg(chosen); + } + } + + const closeChosenMsg = () => { + setChosenMsg({}); + } + + const fetchMessages = (status = 'UNREAD') => { + const chosenCompanyId = storeGet.main.chosenCompanyId(); + const userData = storeGet.main.userData(); + const role = pathOr('', ['role', 'roleType'], userData); + + if (currentSubscription) { + console.log('UNsubscribed') + currentSubscription.unsubscribe(); + setCurrentSubscription(null); + } + + if (isConnected && userData.id && chosenCompanyId !== 0 && role === 'ROLE_BENEFICIARY') { + setLoading(true); + NotificationService.getNotifications( + userData.id, + status === 'UNREAD' ? getNotifications : getNotificationsRead, + errGetNotifications, + [ + ['status', status], + ['companyId', chosenCompanyId] + ] + ); + if (socket.current) { + subscribeTo(`/topic/notifications_user_${userData.id}_company_${chosenCompanyId}`) + } + } else if (isConnected && userData.id && role !== 'ROLE_BENEFICIARY') { + setLoading(true); + NotificationService.getNotifications( + userData.id, + status === 'UNREAD' ? getNotifications : getNotificationsRead, + errGetNotifications, + [ + ['status', status] + ] + ); + if (socket.current) { + subscribeTo(`/topic/notifications_user_${userData.id}`) + } + } + } + + const getNotifications = (resp) => { + if (resp.status === 'SUCCESS') { + setNotifications(resp.data); + } + set404FromErrorResponse(resp); + setLoading(false); + } + + const getNotificationsRead = (resp) => { + if (resp.status === 'SUCCESS') { + setNotificationsRead(resp.data); + } + set404FromErrorResponse(resp); + setLoading(false); + } + + const errGetNotifications = (resp) => { + set404FromErrorResponse(resp); + setLoading(false); + } + + const makeNotificationRead = (id) => { + NotificationService.notificationMakeRead(id, makeReadCallback, makeReadErrorCallback) + } + + const makeReadCallback = (resp) => { + if (resp.status === 'SUCCESS') { + if (0 === activeIndex) { + const msgs = notifications.map(o => o.id === resp.data.id ? resp.data : o); + setNotifications(msgs); + } else { + const msgs = notificationsRead.map(o => o.id === resp.data.id ? resp.data : o); + setNotificationsRead(msgs); + } + } + set404FromErrorResponse(resp); + } + + const makeReadErrorCallback = (resp) => { + set404FromErrorResponse(resp); + } + + const connectWebSocket = () => { + socket.current = new SockJS(socketUrl); + stomp.current = Stomp.over(socket.current); + + stomp.current.configure({ + debug: function(str) { + //console.log(str); + }, + reconnectDelay: 5000, + heartbeatIncoming: 20000, + heartbeatOutgoing: 20000 + }); + + stomp.current.connect( + {}, + () => { + // connected + console.log('Websocket connected'); + setIsConnected(true); + }, + (error) => { + console.error('WebSocket Connection Error:', error); + setIsConnected(false); + setTimeout(connectWebSocket, 5000); + } + ); + }; + + const subscribeTo = (topic) => { + console.log('subscribeTo', topic) + const subscription = stomp.current.subscribe( + topic, + (message) => { + try { + const notification = JSON.parse(message.body); + console.log('notification', notification) + //setNotifications(prev => [notification, ...prev]); + } catch (error) { + console.error('Error parsing notification:', error); + } + } + ); + + setCurrentSubscription(subscription); + } + + useEffect(() => { + fetchMessages(); + }, [chosenCompanyId, userData.id, isConnected]); + + useEffect(() => { + connectWebSocket(); + + return () => { + if (currentSubscription) { + currentSubscription.unsubscribe(); + setCurrentSubscription(null); + } + + if (stomp.current) { + stomp.current.disconnect(() => { + console.log('WebSocket Disconnected'); + }); + } + }; + }, []); + + return ( + <> + setNotificationsVisible(true)}> + + + setNotificationsVisible(false)}> + + + {loading + ?
    + +
    + : !isEmpty(chosenMsg) + ? + : (notifications.length > 0 + ?
      + {notifications.map(o => )} +
    + :
    + + {__('Vuoto', 'gepafin')} +
    )} +
    + + {loading + ?
    + +
    + : !isEmpty(chosenMsg) + ? + : (notificationsRead.length > 0 + ?
      + {notificationsRead.map(o => )} +
    + : +
    + + {__('Vuoto', 'gepafin')} +
    )} +
    +
    +
    + + ) +} + +export default NotificationsSidebar; diff --git a/src/helpers/getStrippedHtmlBodyTags.js b/src/helpers/getStrippedHtmlBodyTags.js new file mode 100644 index 0000000..59c4315 --- /dev/null +++ b/src/helpers/getStrippedHtmlBodyTags.js @@ -0,0 +1,28 @@ +import parse from 'html-react-parser'; +import DOMPurify from 'dompurify'; + +const getEmailTemplateForSoccorso = (content = '', fallback = '') => { + const config = { + FORBID_TAGS: ['html', 'body'], + WHOLE_DOCUMENT: false, + RETURN_DOM: false, + RETURN_DOM_FRAGMENT: false, + RETURN_DOM_IMPORT: false, + FORCE_BODY: false, + ADD_TAGS: ['*'], + ADD_ATTR: ['*'] + }; + try { + const wrappedHtml = `
    ${content}
    `; + const cleaned = DOMPurify.sanitize(wrappedHtml, config); + + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = cleaned; + return parse(tempDiv.innerHTML); + } catch (error) { + console.error('DOMPurify cleaning error:', error); + return fallback; + } +} + +export default getEmailTemplateForSoccorso; \ No newline at end of file diff --git a/src/layouts/DefaultLayout/components/AppTopbar/index.js b/src/layouts/DefaultLayout/components/AppTopbar/index.js index af1e9a8..8bb75b4 100644 --- a/src/layouts/DefaultLayout/components/AppTopbar/index.js +++ b/src/layouts/DefaultLayout/components/AppTopbar/index.js @@ -8,9 +8,9 @@ import LogoIcon from '../../../../icons/LogoIcon'; import { IconField } from 'primereact/iconfield'; import { InputIcon } from 'primereact/inputicon'; import { InputText } from 'primereact/inputtext'; -import { Badge } from 'primereact/badge'; import { Button } from 'primereact/button'; import TopBarProfileMenu from '../../../../components/TopBarProfileMenu'; +import NotificationsSidebar from '../../../../components/NotificationsSidebar'; const AppTopbar = () => { const menuLeft = useRef(null); @@ -24,14 +24,13 @@ const AppTopbar = () => { - - - + {/* */} + + + + + + + + + + + + ); + }; + + const header = renderHeader(); + + const updateEvaluationValue = (value, path, maxValue = null) => { + let finalValue = value; + + if (maxValue || maxValue === 0) { + finalValue = value > maxValue ? maxValue : value; + } + + const newData = wrap(data).set(path, finalValue).value(); + setData(newData); + updateFlagsForSoccorso(newData); + } + + const doSaveDraft = useCallback((doRedirect = '') => { + const formData = { + criteria: klona(data.criteria), + checklist: klona(data.checklist), + files: klona(data.files), + evaluationDocument: klona(data.evaluationDocument.map(o => ({ + ...o, + fileValue: o.fileValue[0] ? o.fileValue[0].id : '' + }) + )), + amendmentDetails: klona(data.amendmentDetails), + note: data.note + } + + ApplicationEvaluationService.updateEvaluation( + data.assignedApplicationId, + formData, + (data) => updateCallback(data, doRedirect), + errUpdateCallback + ); + }, [data]); + + const updateCallback = (data, doRedirect = '') => { + if (data.status === 'SUCCESS') { + setData(getFormattedData(data.data)); + if (toast.current) { + toast.current.show({ + severity: 'success', + summary: '', + detail: data.message + }); + } + if (!isEmpty(doRedirect)) { + navigate(doRedirect); + } + } + storeSet.main.unsetAsyncRequest(); + } + + const errUpdateCallback = (data) => { + if (toast.current && data.message) { + toast.current.show({ + severity: 'error', + summary: '', + detail: data.message + }); + } + set404FromErrorResponse(data); + storeSet.main.unsetAsyncRequest(); + } + + const doApprove = () => { + const formData = { + applicationStatus: 'APPROVED', + criteria: klona(data.criteria), + checklist: klona(data.checklist), + files: klona(data.files), + note: data.note, + motivation + } + + setIsVisibleCompleteDialog(false); + ApplicationEvaluationService.updateEvaluation(data.assignedApplicationId, formData, updateStatusCallback, errUpdateStatusCallback); + } + + const doReject = () => { + const formData = { + applicationStatus: 'REJECTED', + criteria: klona(data.criteria), + checklist: klona(data.checklist), + files: klona(data.files), + note: data.note, + motivation + } + + setIsVisibleCompleteDialog(false); + ApplicationEvaluationService.updateEvaluation(data.assignedApplicationId, formData, updateStatusCallback, errUpdateStatusCallback); + } + + const updateStatusCallback = (data) => { + if (data.status === 'SUCCESS') { + setData(getFormattedData(data.data)); + if (toast.current) { + toast.current.show({ + severity: 'success', + summary: '', + detail: data.message + }); + } + } + storeSet.main.unsetAsyncRequest(); + } + + const errUpdateStatusCallback = (data) => { + if (toast.current && data.message) { + toast.current.show({ + severity: 'error', + summary: '', + detail: data.message + }); + } + set404FromErrorResponse(data); + storeSet.main.unsetAsyncRequest(); + } + + const displayCriterionData = (id) => { + const criterion = head(data.criteria.filter(o => o.id === id)); + setCriterionDataTitle(criterion.label); + const content =
    +

    {__('I campi correlati')}

    + {criterion.criteriaMappedFields ? criterion.criteriaMappedFields.map(o => criteriaDataItem(o)) : null} +
    ; + setCriterionDataContent(content); + setIsVisibleCriterionData(id); + } + + const criteriaDataItem = (item) => { + let content = ''; + + switch (item.fieldName) { + case 'fileupload' : + content = ; + break; + case 'table' : + const th = Object.keys(item.fieldValue[0]); + content = + + + {th.map(v => )} + + + + {item.fieldValue + ? item.fieldValue.map((o, i) => + {Object.values(o).map(v => )} + ) + : null} + +
    {v}
    {v}
    ; + break; + default : + content = item.fieldValue; + break; + } + + return
    + {item.fieldLabel} + {content} +
    + } + + const hideCriterionData = () => { + setIsVisibleCriterionData(0); + setCriterionDataTitle(''); + setCriterionDataContent(''); + } + + const getAmendmentsCallback = (data) => { + if (data.status === 'SUCCESS') { + if (data.data.length) { + setConnectedSoccorsoId(data.data[0].id); + } + } + } + + const errGetAmendmentsCallback = () => { + if (toast.current && data.message) { + toast.current.show({ + severity: 'error', + summary: '', + detail: data.message + }); + } + set404FromErrorResponse(data); + } + + const shouldDisableField = (fieldName) => { + return !['EVALUATION'].includes(data.applicationStatus) + || (['ADMISSIBLE'].includes(data.applicationStatus) && fieldName !== 'criteria') + } + + const headerCompleteDialog = () => { + return 'approve' === operationType + ? {__('Confermare l\'approvazione', 'gepafin')} + : {__('Confermare il rifiuto', 'gepafin')}; + } + + const hideCompleteDialog = () => { + setIsVisibleCompleteDialog(false); + setOperationType(''); + setMotivation(''); + } + + const footerCompleteDialog = () => { + return
    +
    + } + + const initiateApproving = () => { + setOperationType('approve'); + setIsVisibleCompleteDialog(true); + + } + + const initiateRejecting = () => { + setOperationType('reject'); + setIsVisibleCompleteDialog(true); + } + + const doCheckNDG = () => { + storeSet.main.setAsyncRequest(); + doSaveDraft(); + setTimeout(() => { + AppointmentService.getNdg(id, getNdgCallback, errGetNdgCallback); + }, 100); + } + + const getNdgCallback = (data) => { + if (data.status === 'SUCCESS') { + if (toast.current && data.message) { + toast.current.show({ + severity: 'success', + summary: '', + detail: data.message + }); + } + } + storeSet.main.unsetAsyncRequest(); + } + + const errGetNdgCallback = (data) => { + if (toast.current && data.message) { + toast.current.show({ + severity: data.status === 'SUCCESS' ? 'info' : 'error', + summary: '', + detail: data.message + }); + } + set404FromErrorResponse(data); + storeSet.main.unsetAsyncRequest(); + } + + const doCreateAppointment = () => { + setAppointmentData({ + title: '', + text: '', + duration: 0, + amount: 0 + }); + setIsVisibleAppointmentDialog(true); + } + + const setValue = (name, value) => { + const newData = wrap(appointmentData).set(name, value).value(); + setAppointmentData(newData); + } + + const headerAppointmentDialog = () => { + return {__('Crea appuntamento', 'gepafin')}; + } + + const hideAppointmentDialog = () => { + setIsVisibleAppointmentDialog(false); + setAppointmentData({}); + } + + const footerAppointmentDialog = () => { + return
    +
    + } + + const doCreateAppointmentRequest = () => { + if ( + !isEmpty(appointmentData.title) && !isEmpty(appointmentData.text) && !isEmpty(appointmentData.amount) + && !isEmpty(appointmentData.duration) && appointmentData.duration !== 0 && appointmentData.amount !== 0 + ) { + storeSet.main.setAsyncRequest(); + const submitData = { + 'importoBreveTermine': appointmentData.amount, + 'durataMesiFinanziamento': appointmentData.duration, + 'nota': { + 'titolo': appointmentData.title, + 'testo': appointmentData.text + } + } + + AppointmentService.createAppointment(id, submitData, getAppointemntCallback, errGetAppointemntCallback); + } + } + + const getAppointemntCallback = (data) => { + if (data.status === 'SUCCESS') { + if (toast.current && data.message) { + toast.current.show({ + severity: 'success', + summary: '', + detail: data.message + }); + } + } + setIsVisibleAppointmentDialog(false); + storeSet.main.unsetAsyncRequest(); + } + + const errGetAppointemntCallback = (data) => { + if (toast.current && data.message) { + toast.current.show({ + severity: data.status === 'SUCCESS' ? 'info' : 'error', + summary: '', + detail: data.message + }); + } + setIsVisibleAppointmentDialog(false); + set404FromErrorResponse(data); + storeSet.main.unsetAsyncRequest(); + } + + const doMakeAdmisible = () => { + // TODO + } + + const evaluationShouldBeBlocked = (data = {}) => { + const userData = storeGet.main.userData() + return isAsyncRequest || userData.id !== data.assignedUserId; + } + + useEffect(() => { + const maxScore = pathOr(0, ['minScore'], data); + const criteria = pathOr([], ['criteria'], data); + const scoreSum = sum(criteria.map(o => o.score)); + + setIsAdmissible(scoreSum !== 0 && scoreSum >= maxScore); + }, [data]); + + useEffect(() => { + const parsed = parseInt(id) + const entityId = !isNaN(parsed) ? parsed : 0; + + storeSet.main.setAsyncRequest(); + ApplicationEvaluationService.getEvaluationByApplId(getCallback, errGetCallback, [ + ['applicationId', entityId] + ]); + AmendmentsService.getSoccorsoByApplId(entityId, getAmendmentsCallback, errGetAmendmentsCallback, [ + ['statuses', 'AWAITING'] + ]); + }, [id]); + + return ( +
    +
    +

    {__('Valuta domanda', 'gepafin')}

    +
    + +
    + + +
    +
    + +
    + + {!isAsyncRequest && !isEmpty(data) + ?
    +
    +

    + {__('ID domanda', 'gepafin')} + {data.applicationId} +

    +

    + {__('Protocollo', 'gepafin')} + {data.protocolNumber} +

    +

    + {__('NDG', 'gepafin')} + {data.ndg} +

    +

    + {__('Appuntamento', 'gepafin')} + {data.appointmentId} +

    +

    + {__('Bando', 'gepafin')} + {data.callName} +

    +

    + {__('Referente Aziendale', 'gepafin')} + {data.beneficiary} +

    +

    + {__('Azienda Beneficiaria', 'gepafin')} + {data.companyName} +

    +

    + {__('Data ricezione', 'gepafin')} + {getDateFromISOstring(data.submissionDate)} +

    +

    + {__('Data assegnazione', 'gepafin')} + {getDateFromISOstring(data.assignedAt)} +

    +

    + {__('Scadenza Valutazione', 'gepafin')} + {getDateFromISOstring(data.evaluationEndDate)} +

    +

    + {__('Stato', 'gepafin')} + {getBandoLabel(data.applicationStatus)} +

    +
    + +
    +

    {__('Scarica documenti della domanda', 'gepafin')}

    +
    + + + +
    +
    + +
    +

    {__('Documenti aggiuntivi', 'gepafin')}

    + updateEvaluationValue( + data, + ['evaluationDocument'] + )} + shouldDisable={['APPROVED', 'REJECTED'].includes(data.applicationStatus) || evaluationShouldBeBlocked(data)} + sourceId={data.assignedApplicationId} + sourceName="evaluation"/> +
    + +
    +

    {__('Checklist Valutazione', 'gepafin')}

    +
    +
    +

    {__('Lista', 'gepafin')}

    +
    +
    + {data.checklist.map((o, i) =>
    + updateEvaluationValue( + e.checked, + ['checklist', i, 'valid'] + )} + checked={o.valid}> + +
    )} +
    +
    + +

    {__('Note', 'gepafin')}

    +
    + updateEvaluationValue( + e.htmlValue, + ['note'] + )} + style={{ height: 80 * 3, width: '100%' }} + /> +
    +
    +
    +

    {__('Documenti allegati', 'gepafin')}

    + shouldDisableField(name) || evaluationShouldBeBlocked(data)} + name="files" + ndg={data.ndg} + applicationId={id}/> +
    +
    +
    + + {!isEmpty(data.amendmentDetails) + ?
    +

    {__('Documenti di soccorso', 'gepafin')}

    + shouldDisableField(name) || evaluationShouldBeBlocked(data)} + name="amendmentDetails" + ndg={data.ndg} + applicationId={id}/> +
    : null} + +
    +

    {__('Punteggi di valutazione', 'gepafin')}

    + {data.criteria + ? + + + + + + + + + {data.criteria.map((o, i) => + + + + )} + + + + + + + + + + + +
    {__('Parametro', 'gepafin')}{__('Punteggio', 'gepafin')}{__('Stato', 'gepafin')}
    {o.label} +
    + updateEvaluationValue( + e.value, + ['criteria', i, 'score'], + o.criteria + )}/> + + / {o.maxScore} + +
    +
    +
    + {!isEmpty(o.criteriaMappedFields) + ?
    +
    {__('Punteggio:', 'gepafin')}{sum(data.criteria.map(o => o.score))} + {isAdmissible + ? : null} + {!isAdmissible + ? : null} +
    {sprintf(__('Punteggio minimo per l\'ammissione: %d'), data.minScore)}
    : null} +
    + +
    + +
    + {__('Azioni rapide', 'gepafin')} +
    + +
    +
    + {['EVALUATION', 'SOCCORSO', 'CLOSE'].includes(data.applicationStatus) + ?
    +
    + + + {criterionDataContent} + + + +
    + + setMotivation(e.htmlValue)} + style={{ height: 80 * 3, width: '100%' }} + /> +
    +
    + + +
    + + setValue('amount', e.value)}/> +
    +
    + + setValue('duration', e.value)}/> +
    +
    + + setValue('title', e.target.value)}/> +
    +
    + + setValue('text', e.target.value)} + rows={3} + cols={30}/> +
    +
    + +
    + : <> + + + + + + + + + } +
    + ) + +} + +export default DomandaEditPreInstructor; diff --git a/src/pages/DomandaEditPreInstructor/components/RepeaterFields/index.js b/src/pages/DomandaEditPreInstructor/components/RepeaterFields/index.js index d6249d9..36564c5 100644 --- a/src/pages/DomandaEditPreInstructor/components/RepeaterFields/index.js +++ b/src/pages/DomandaEditPreInstructor/components/RepeaterFields/index.js @@ -138,7 +138,7 @@ const RepeaterFields = ({ className="fieldsRepeater__addNew" outlined type="button" - disabled={watchFields && watchFields.filter(o => isEmpty(o.nameValue) || isEmpty(o.fileValue)).length > 0 || shouldDisable} + disabled={(watchFields && watchFields.filter(o => isEmpty(o.nameValue) || isEmpty(o.fileValue)).length > 0) || shouldDisable} onClick={addNew} label={__('Aggiungi nuovo file', 'gepafin')} /> diff --git a/src/pages/DomandaEditPreInstructor/index.js b/src/pages/DomandaEditPreInstructor/index.js index df7742c..9c2c517 100644 --- a/src/pages/DomandaEditPreInstructor/index.js +++ b/src/pages/DomandaEditPreInstructor/index.js @@ -568,11 +568,11 @@ const DomandaEditPreInstructor = () => { {data.callName}

    - {__('Beneficiario', 'gepafin')} + {__('Referente Aziendale', 'gepafin')} {data.beneficiary}

    - {__('Azienda', 'gepafin')} + {__('Azienda Beneficiaria', 'gepafin')} {data.companyName}

    @@ -934,4 +934,4 @@ const DomandaEditPreInstructor = () => { } -export default DomandaEditPreInstructor; \ No newline at end of file +export default DomandaEditPreInstructor; diff --git a/src/pages/Domande/components/AllDomandeTable/index.js b/src/pages/Domande/components/AllDomandeTable/index.js index 8505ff8..6159a66 100644 --- a/src/pages/Domande/components/AllDomandeTable/index.js +++ b/src/pages/Domande/components/AllDomandeTable/index.js @@ -139,7 +139,7 @@ const AllDomandeTable = ({ openDialogFn, updaterString = '' }) => { ?