From 73b319ea9cd471277cf0c5100e557042d70be2f0 Mon Sep 17 00:00:00 2001 From: Vitalii Kiiko Date: Mon, 23 Dec 2024 09:44:42 +0100 Subject: [PATCH 1/7] - save progress; --- .../components/AppTopbar/index.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/layouts/DefaultLayout/components/AppTopbar/index.js b/src/layouts/DefaultLayout/components/AppTopbar/index.js index af1e9a8..27c3a6b 100644 --- a/src/layouts/DefaultLayout/components/AppTopbar/index.js +++ b/src/layouts/DefaultLayout/components/AppTopbar/index.js @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { __ } from '@wordpress/i18n'; // components @@ -11,9 +11,11 @@ import { InputText } from 'primereact/inputtext'; import { Badge } from 'primereact/badge'; import { Button } from 'primereact/button'; import TopBarProfileMenu from '../../../../components/TopBarProfileMenu'; +import { Sidebar } from 'primereact/sidebar'; const AppTopbar = () => { const menuLeft = useRef(null); + const [notificationsVisible, setNotificationsVisible] = useState(false); const startContent = @@ -42,8 +44,19 @@ const AppTopbar = () => { return ( - + <> + + setNotificationsVisible(false)} fullScreen> +

Sidebar

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore + et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. +

+
+ ) } -export default AppTopbar; \ No newline at end of file +export default AppTopbar; From bb983feb1248bc1c5ff6d380ef77c7e38b6da9aa Mon Sep 17 00:00:00 2001 From: Vitalii Kiiko Date: Tue, 24 Dec 2024 14:50:20 +0100 Subject: [PATCH 2/7] - added sidebar container for notifications; - styles and interactions for notifications; --- .../scss/components/notificationsSidebar.scss | 55 ++++++++ src/assets/scss/theme.scss | 3 +- .../components/NotificationItem/index.js | 19 +++ .../NotificationItemChosen/index.js | 22 ++++ src/components/NotificationsSidebar/index.js | 121 ++++++++++++++++++ .../components/AppTopbar/index.js | 24 +--- 6 files changed, 224 insertions(+), 20 deletions(-) create mode 100644 src/assets/scss/components/notificationsSidebar.scss create mode 100644 src/components/NotificationsSidebar/components/NotificationItem/index.js create mode 100644 src/components/NotificationsSidebar/components/NotificationItemChosen/index.js create mode 100644 src/components/NotificationsSidebar/index.js 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..df13bc0 --- /dev/null +++ b/src/components/NotificationsSidebar/components/NotificationItem/index.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const NotificationItem = ({ item, clickFn }) => { + const handleClick = () => { + clickFn(item.id); + } + + return ( +
  • +
    + {item.title} + {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..4821df3 --- /dev/null +++ b/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { __ } from '@wordpress/i18n'; +import { Button } from 'primereact/button'; + +const NotificationItemChosen = ({ item, closeFn }) => { + return ( +
    +
    + ) +} + +export default NotificationItemChosen; diff --git a/src/components/NotificationsSidebar/index.js b/src/components/NotificationsSidebar/index.js new file mode 100644 index 0000000..55b08f5 --- /dev/null +++ b/src/components/NotificationsSidebar/index.js @@ -0,0 +1,121 @@ +import React, { useEffect, useState } from 'react'; +import { __ } from '@wordpress/i18n'; +import { head, isEmpty } from 'ramda'; + +// 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 NotificationsSidebar = () => { + 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({}); + + // Handle tab change + const handleTabChange = (e) => { + setActiveIndex(e.index); + fetchTabData(e.index); + }; + + const fetchTabData = (index) => { + setChosenMsg({}); + console.log('fetchTabData', index); + setLoading(true); + setTimeout(() => { + setLoading(false); + }, 7000) + } + + 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({}); + } + + useEffect(() => { + setNotifications(() => { + const msg = { + 'id': 35, + 'createdDate': '2024-12-23T14:55:27.278103', + 'updatedDate': '2024-12-23T14:55:27.278103', + 'userId': 30, + 'title': 'Il Risultato della Valutazione per la Richiesta È Disponibile', + 'message': 'Il risultato della valutazione per la richiesta ai sensi del protocollo n. 10000015 è ora disponibile.', + 'status': 'UNREAD', + 'companyId': 103, + 'redirectUrl': 'EVALUATION_RESULT', + 'notificationType': 'EVALUATION_RESULT' + }; + return Array.from({ length: 33 }, (_, index) => ({ + ...msg, + id: msg.id + index + })); + }) + }, []); + + 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/layouts/DefaultLayout/components/AppTopbar/index.js b/src/layouts/DefaultLayout/components/AppTopbar/index.js index 27c3a6b..8bb75b4 100644 --- a/src/layouts/DefaultLayout/components/AppTopbar/index.js +++ b/src/layouts/DefaultLayout/components/AppTopbar/index.js @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import { __ } from '@wordpress/i18n'; // components @@ -8,14 +8,12 @@ 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 { Sidebar } from 'primereact/sidebar'; +import NotificationsSidebar from '../../../../components/NotificationsSidebar'; const AppTopbar = () => { const menuLeft = useRef(null); - const [notificationsVisible, setNotificationsVisible] = useState(false); const startContent = @@ -26,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 =
      + {item.fieldValue + ? item.fieldValue.map(o =>
    • + {o.filePath ? {o.name} : null} +
    • ) + : null} +
    ; + 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/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 = '' }) => { ?