From 10331cf92a87a50a3348c5ad30b3339203c16af7 Mon Sep 17 00:00:00 2001 From: Vitalii Kiiko Date: Mon, 13 Jan 2025 15:38:23 +0100 Subject: [PATCH] - added codice ateco as dynamic data; - stat charts to admin dashboard page; --- package.json | 7 +- src/assets/scss/components/charts.scss | 39 ++++ src/assets/scss/theme.scss | 1 + src/components/ChartDomandePerBando/index.js | 60 ++++++ src/components/ChartStatoDomande/index.js | 58 ++++++ .../components/NodeInitialForm/index.js | 50 ----- .../components/NodeIntermediateForm/index.js | 78 -------- src/components/FlowBuilder/index.js | 132 ------------- src/configData.js | 1 + .../components/AppSidebar/index.js | 16 +- src/pages/BandoApplication/index.js | 3 +- src/pages/Dashboard/index.js | 42 ++-- .../AllDomandeArchiveTable/index.js | 182 ++++++++++++++++++ src/pages/DomandeArchive/index.js | 25 +++ src/routes.js | 13 ++ 15 files changed, 429 insertions(+), 278 deletions(-) create mode 100644 src/assets/scss/components/charts.scss create mode 100644 src/components/ChartDomandePerBando/index.js create mode 100644 src/components/ChartStatoDomande/index.js delete mode 100644 src/components/FlowBuilder/components/NodeInitialForm/index.js delete mode 100644 src/components/FlowBuilder/components/NodeIntermediateForm/index.js delete mode 100644 src/components/FlowBuilder/index.js create mode 100644 src/pages/DomandeArchive/components/AllDomandeArchiveTable/index.js create mode 100644 src/pages/DomandeArchive/index.js diff --git a/package.json b/package.json index 4061fa5..ccd2660 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "@emailjs/browser": "4.4.1", "@emotion/styled": "11.13.5", "@number-flow/react": "0.4.2", - "@sentry/browser": "^8.42.0", - "@stomp/stompjs": "^7.0.0", - "@tanstack/react-table": "^8.20.5", + "@sentry/browser": "8.42.0", + "@stomp/stompjs": "7.0.0", + "@tanstack/react-table": "8.20.5", "@wordpress/i18n": "5.13.0", "@wordpress/react-i18n": "4.13.0", "codice-fiscale-js": "2.3.22", @@ -37,6 +37,7 @@ "react-hook-form": "7.53.2", "react-router-dom": "7.0.1", "react-scripts": "5.0.1", + "recharts": "2.15.0", "sockjs-client": "^1.6.1", "validate.js": "0.13.1", "zustand": "4.5.4", diff --git a/src/assets/scss/components/charts.scss b/src/assets/scss/components/charts.scss new file mode 100644 index 0000000..15cff0e --- /dev/null +++ b/src/assets/scss/components/charts.scss @@ -0,0 +1,39 @@ +.chartCard { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + min-height: 220px; + padding: 20px 5px; + border-radius: 6px; + border: 1px solid #EAB308; + background: #FFF; +} + +.chartCard__title { + color: var(--global-textColor); + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: normal; + margin: 0 0 16px; +} + +.chartCard__chart { + width: 100%; + height: 24rem; +} + +.chartCard__tooltip { + padding: 5px 10px; + background-color: white; + border: 1px solid var(--global-textColor); +} + +.chartCard__tooltipTitle { + font-weight: bold; +} + +.chartCard__tooltipText { + +} \ No newline at end of file diff --git a/src/assets/scss/theme.scss b/src/assets/scss/theme.scss index 91a0727..53499eb 100644 --- a/src/assets/scss/theme.scss +++ b/src/assets/scss/theme.scss @@ -50,3 +50,4 @@ @import "./components/evaluation.scss"; @import "./components/fieldsRepeater.scss"; @import "./components/notificationsSidebar.scss"; +@import "./components/charts.scss"; diff --git a/src/components/ChartDomandePerBando/index.js b/src/components/ChartDomandePerBando/index.js new file mode 100644 index 0000000..700e6a7 --- /dev/null +++ b/src/components/ChartDomandePerBando/index.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { __ } from '@wordpress/i18n'; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { isEmpty } from 'ramda'; + +// components + + +const ChartDomandePerBando = ({ title, data = [] }) => { + const truncateText = (text) => { + const maxLength = 12; + if (typeof text === 'string' && text.length > maxLength) { + return `${text.slice(0, maxLength)}...`; + } + return text; + }; + + // Custom tooltip + const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+

+ {__('Domande', 'gepafin')}: {payload[0].value} +

+
+ ); + } + return null; + }; + + return (
+ {title ? {title} : null} + {data && !isEmpty(data) + ?
+ + + + + + }/> + + + + +
: null} +
) +} + +export default ChartDomandePerBando; \ No newline at end of file diff --git a/src/components/ChartStatoDomande/index.js b/src/components/ChartStatoDomande/index.js new file mode 100644 index 0000000..856fd6c --- /dev/null +++ b/src/components/ChartStatoDomande/index.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { __ } from '@wordpress/i18n'; +import { Tooltip, ResponsiveContainer, Cell, Pie, PieChart } from 'recharts'; +import { isEmpty } from 'ramda'; +import getBandoLabel from '../../helpers/getBandoLabel'; + +// components + + +const ChartStatoDomande = ({ title, data = [] }) => { + const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8', '#82ca9d']; + + const CustomTooltip = ({ active, payload }) => { + if (active && payload && payload.length) { + return ( +
+

{getBandoLabel(payload[0].name)}

+

+ {__('Domande', 'gepafin')}: {payload[0].value} +

+
+ ); + } + return null; + }; + + return (
+ {title ? {title} : null} + {data && !isEmpty(data) + ?
+ + + `${(percent * 100).toFixed(0)}%`} + outerRadius={120} + fill="#8884d8" + dataKey="numberOfApplications" + nameKey="status" + > + {data.map((entry, index) => ( + + ))} + + } /> + + +
: null} +
) +} + +export default ChartStatoDomande; \ No newline at end of file diff --git a/src/components/FlowBuilder/components/NodeInitialForm/index.js b/src/components/FlowBuilder/components/NodeInitialForm/index.js deleted file mode 100644 index 79fe833..0000000 --- a/src/components/FlowBuilder/components/NodeInitialForm/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { isEmpty, head } from 'ramda'; - -// store -import { storeGet, storeSet } from '../../../../store'; -import { __ } from '@wordpress/i18n'; - -const NodeInitialForm = ({ data: { id, label = '' } }) => { - const flowData = storeGet.main.flowData(); - const [value, setValue] = useState(''); - - useEffect(() => { - const flowForms = storeGet.main.flowForms(); - const form = head(flowForms.filter(o => String(o.id) === String(id))); - const flowDataItem = head(flowData.filter(o => String(o.formId) === String(id))); - - if (form && flowDataItem) { - const field = head(form.content.filter(o => o.id === flowDataItem.chosenField)); - - if (field) { - const label = head(field.settings.filter(o => o.name === 'label')); - setValue(label ? label.value : field.label); - } - } - }, [flowData]); - - return ( -
- - {value} - {/*{options && !isEmpty(options) - ? : null}*/} - -
- ); -} - -export default NodeInitialForm; \ No newline at end of file diff --git a/src/components/FlowBuilder/components/NodeIntermediateForm/index.js b/src/components/FlowBuilder/components/NodeIntermediateForm/index.js deleted file mode 100644 index 8cc3a0e..0000000 --- a/src/components/FlowBuilder/components/NodeIntermediateForm/index.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { head, isEmpty } from 'ramda'; -import { __ } from '@wordpress/i18n'; - -import { useStore, storeSet, storeGet } from '../../../../store'; - -const NodeIntermediateForm = ({ data: { id, label = '' } }) => { - const flowEdges = useStore().main.flowEdges(); - const flowData = useStore().main.flowData(); - const [options, setOptions] = useState([]); - const [value, setValue] = useState(''); - - const onChangeFn = (e) => { - const { value } = e.target; - const data = { - formId: String(id), - chosenField: '', - chosenValue: value - } - setValue(value); - storeSet.main.addFlowData(data); - } - - useEffect(() => { - const edge = head(flowEdges.filter(o => o.target === String(id))); - if (edge) { - const sourceForm = edge.source; - const sourceFormData = head(flowData.filter(o => String(o.formId) === sourceForm)); - const flowForms = storeGet.main.flowForms(); - const form = head(flowForms.filter(o => String(o.id) === String(sourceForm))); - - if (form && sourceFormData) { - const { chosenField } = sourceFormData; - const field = head(form.content.filter(o => o.id === chosenField)); - if (field) { - const options = head(field.settings.filter(o => o.name === 'options')); - if (options) { - setOptions(options.value); - } - } - } - } - - const flowDataForm = head(flowData.filter(o => String(o.formId) === String(id))); - - if (flowDataForm) { - setValue(flowDataForm.chosenValue); - } - }, [flowEdges, flowData]); - - return ( -
- - - {options && !isEmpty(options) - ? : null} - -
- ); -} - -export default NodeIntermediateForm; \ No newline at end of file diff --git a/src/components/FlowBuilder/index.js b/src/components/FlowBuilder/index.js deleted file mode 100644 index 3bf66de..0000000 --- a/src/components/FlowBuilder/index.js +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - ReactFlow, - Background -} from '@xyflow/react'; -import { isEmpty } from 'ramda'; - -import '@xyflow/react/dist/style.css'; - -// store -import { useStore, storeSet } from '../../store'; - -// nodes -import NodeInitialForm from './components/NodeInitialForm'; -import NodeIntermediateForm from './components/NodeIntermediateForm'; - -const nodeTypes = { - initialForm: NodeInitialForm, - intermediateForm: NodeIntermediateForm -}; - -const FlowBuilder = ({ initialForm = 0, finalForm = 0, mainField = '' }) => { - const flowForms = useStore().main.flowForms(); - const [nodes, setNodes] = useState([]); - const [edges, setEdges] = useState([]); - - const range = (start, stop, step) => { - return Array.from( - { length: (stop - start) / step + 1 }, - (_, i) => start + i * step - ); - } - - useEffect(() => { - if ( - (flowForms.length === 2 && initialForm) || - (flowForms.length > 2 && initialForm && finalForm) - ) { - const total = (flowForms.length - 2) * (200 - 90); - let coordinates = range(total * -1, total, 200); - - const initialNodes = flowForms.map(o => { - const formId = String(o.id); - let obj; - - if (formId === String(initialForm)) { - obj = { - id: formId, - type: 'initialForm', - data: { label: o.label, id: formId }, - position: { x: 0, y: 0 }, - } - } else if (formId === String(finalForm)) { - obj = { - id: formId, - type: 'output', - data: { label: o.label, id: formId }, - position: { x: 0, y: flowForms.length === 2 ? 150 : 300 }, - } - } else { - const x = coordinates.splice(0, 1); - obj = { - id: formId, - type: 'intermediateForm', - data: { label: o.label, id: formId }, - position: { x, y: 150 }, - } - } - return obj - }); - - let edges = []; - // eslint-disable-next-line - flowForms.map(o => { - const formId = String(o.id); - - if (formId !== String(initialForm) && formId !== String(finalForm)) { - edges.push({ - id: `${initialForm}->${formId}`, - source: String(initialForm), - target: formId, - type: 'smoothstep' - }); - } - if (formId !== String(initialForm) && formId !== String(finalForm) && String(finalForm) !== '0') { - edges.push({ - id: `${formId}->${finalForm}`, - source: formId, - target: String(finalForm), - type: 'smoothstep' - }); - } - }); - - if (flowForms.length === 2 && initialForm && finalForm) { - edges.push({ - id: `${initialForm}->${finalForm}`, - source: String(initialForm), - target: String(finalForm), - type: 'smoothstep' - }); - } - - setNodes(initialNodes); - setEdges(edges); - storeSet.main.flowEdges(edges); - } else { - setNodes([]); - setEdges([]); - } - }, [initialForm, finalForm, flowForms, mainField]); - - return ( - !isEmpty(nodes) && !isEmpty(edges) - ?
- - - -
- : null - ); -} - -export default FlowBuilder; \ No newline at end of file diff --git a/src/configData.js b/src/configData.js index ba5d649..5d3e7b8 100644 --- a/src/configData.js +++ b/src/configData.js @@ -19,6 +19,7 @@ const dynamicDataForTextinput = [ { label: 'ragione sociale', value: 'company.companyName' }, { label: 'partita IVA', value: 'company.vatNumber' }, { label: 'codice fiscale azienda', value: 'company.codiceFiscale' }, + { label: 'codice ateco', value: 'company.codiceAteco' }, { label: 'indirizzo', value: 'company.address' }, { label: 'numero di telefono azienda', value: 'company.phoneNumber' }, { label: 'città', value: 'company.city' }, diff --git a/src/layouts/DefaultLayout/components/AppSidebar/index.js b/src/layouts/DefaultLayout/components/AppSidebar/index.js index 37aa0eb..5c81b8f 100644 --- a/src/layouts/DefaultLayout/components/AppSidebar/index.js +++ b/src/layouts/DefaultLayout/components/AppSidebar/index.js @@ -64,11 +64,25 @@ const AppSidebar = () => { }, { label: __('Archivio domande', 'gepafin'), - icon: 'pi pi-file', + icon: 'pi pi-briefcase', href: '/domande', id: 7, enable: intersection(permissions, ['APPLY_CALLS']).length }, + { + label: __('Archivio domande', 'gepafin'), + icon: 'pi pi-briefcase', + href: '/domande-archivio', + id: 5, + enable: intersection(permissions, ['VIEW_USERS', 'MANAGE_USERS']).length + }, + { + label: __('Archivio domande', 'gepafin'), + icon: 'pi pi-briefcase', + href: '/domande-archivio', + id: 6, + enable: intersection(permissions, ['EVALUATE_APPLICATIONS']).length + }, { label: __('Soccorso istruttorio', 'gepafin'), icon: , diff --git a/src/pages/BandoApplication/index.js b/src/pages/BandoApplication/index.js index 6a33db6..d6d324b 100644 --- a/src/pages/BandoApplication/index.js +++ b/src/pages/BandoApplication/index.js @@ -312,7 +312,8 @@ const BandoApplication = () => { dynamicData = Object.keys(company).reduce((acc, cur) => { if ([ 'companyName', 'vatNumber', 'codiceFiscale', 'address', 'phoneNumber', - 'city', 'province', 'cap', 'country', 'pec', 'email', 'contactName', 'contactEmail' + 'city', 'province', 'cap', 'country', 'pec', 'email', 'contactName', 'contactEmail', + 'codiceAteco' ].includes(cur)) { acc.company[cur] = company[cur]; } diff --git a/src/pages/Dashboard/index.js b/src/pages/Dashboard/index.js index 71334b0..4bb003e 100644 --- a/src/pages/Dashboard/index.js +++ b/src/pages/Dashboard/index.js @@ -4,33 +4,31 @@ import { useNavigate } from 'react-router-dom'; import { pathOr } from 'ramda'; import NumberFlow from '@number-flow/react'; -// store -//import { storeSet } from '../../store'; - // api import DashboardService from '../../service/dashboard-service'; // components import LatestBandiTable from './components/LatestBandiTable'; -//import LatestUsersActivityTable from './components/LatestUsersActivityTable'; import { Button } from 'primereact/button'; -//import MyEvaluationsTable from '../DashboardPreInstructor/components/PreInstructorDomandeTable'; import AllDomandeTable from '../Domande/components/AllDomandeTable'; import DraftApplicationsTable from './components/DraftApplicationsTable'; +import ChartDomandePerBando from '../../components/ChartDomandePerBando'; +import ChartStatoDomande from '../../components/ChartStatoDomande'; const Dashboard = () => { const navigate = useNavigate(); const [mainStats, setMainStats] = useState({}); + const [chartStats, setChartStats] = useState({}); const onGoToCreateNewBando = () => { navigate('/bandi/new'); } - /*const onGoToUsers = () => { - console.log('onGoToUsers') + const onGoToUsers = () => { + navigate('/utenti'); } - const onGoToStats = () => { + /*const onGoToStats = () => { console.log('onGoToStats') } @@ -45,16 +43,18 @@ const Dashboard = () => { const getStats = (data) => { if (data.status === 'SUCCESS') { setMainStats(data.data.widget1); + setChartStats(data.data.widgetBars); } } - const errGetStats = () => {} + const errGetStats = () => { + } useEffect(() => { DashboardService.getAdminStats(getStats, errGetStats); }, []); - return( + return (

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

@@ -147,20 +147,36 @@ const Dashboard = () => {
+ {chartStats.applicationPerCall + ?
+

{__('Statistiche di sistema', 'gepafin')}

+
+ + +
+
: null} + +
+
{__('Azioni rapide', 'gepafin')}
+
+
+ ); + }; + + const dateAppliedBodyTemplate = (rowData) => { + return formatDate(rowData.submissionDate); + }; + + const statusFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} + itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel} className="p-column-filter" + showClear/>; + }; + + const dateFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} + dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999"/>; + }; + + const statusBodyTemplate = (rowData) => { + return ; + }; + + const statusItemTemplate = (option) => { + return ; + }; + + const actionsBodyTemplate = (rowData) => { + return
+ +
+ } + + const header = renderHeader(); + + return ( +
+ setFilters(e.filters)}> + + + + + + + + + +
+ ) +} + +export default AllDomandeArchiveTable; diff --git a/src/pages/DomandeArchive/index.js b/src/pages/DomandeArchive/index.js new file mode 100644 index 0000000..c6e7885 --- /dev/null +++ b/src/pages/DomandeArchive/index.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { __ } from '@wordpress/i18n'; + +// components +import AllDomandeArchiveTable from './components/AllDomandeArchiveTable'; + +const Domande = () => { + + return ( +
+
+

{__('Archivio domande', 'gepafin')}

+
+ +
+ +
+

{__('Domande pubblicate', 'gepafin')}

+ +
+
+ ) +} + +export default Domande; \ No newline at end of file diff --git a/src/routes.js b/src/routes.js index d524bf5..c8c0302 100644 --- a/src/routes.js +++ b/src/routes.js @@ -40,6 +40,7 @@ import BandiPreferredBeneficiario from './pages/BandiPreferredBeneficiario'; import DomandeInstructorManager from './pages/DomandeInstructorManager'; import DomandaEditInstructorManager from './pages/DomandaEditInstructorManager'; import UserActivity from './pages/UserActivity'; +import DomandeArchive from './pages/DomandeArchive'; const routes = ({ role, chosenCompanyId }) => { @@ -124,6 +125,18 @@ const routes = ({ role, chosenCompanyId }) => { {'ROLE_PRE_INSTRUCTOR' === role ? : null} {'ROLE_INSTRUCTOR_MANAGER' === role ? : null} }/> + + {'ROLE_SUPER_ADMIN' === role ? : null} + {'ROLE_BENEFICIARY' === role ? : null} + {'ROLE_PRE_INSTRUCTOR' === role ? : null} + {'ROLE_INSTRUCTOR_MANAGER' === role ? : null} + }/> + + {'ROLE_SUPER_ADMIN' === role ? : null} + {'ROLE_BENEFICIARY' === role ? : null} + {'ROLE_PRE_INSTRUCTOR' === role ? : null} + {'ROLE_INSTRUCTOR_MANAGER' === role ? : null} + }/> {'ROLE_SUPER_ADMIN' === role ? : null} {'ROLE_BENEFICIARY' === role ? : null}