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')}
+
+
- {/*
-
diff --git a/src/pages/DomandeArchive/components/AllDomandeArchiveTable/index.js b/src/pages/DomandeArchive/components/AllDomandeArchiveTable/index.js
new file mode 100644
index 0000000..3b0e13a
--- /dev/null
+++ b/src/pages/DomandeArchive/components/AllDomandeArchiveTable/index.js
@@ -0,0 +1,182 @@
+import React, { useState, useEffect } from 'react';
+import { __ } from '@wordpress/i18n';
+import { is, uniq } from 'ramda';
+import { Link, useLocation } from 'react-router-dom';
+
+// api
+import ApplicationService from '../../../../service/application-service';
+
+// tools
+import getBandoLabel from '../../../../helpers/getBandoLabel';
+import getBandoSeverity from '../../../../helpers/getBandoSeverity';
+
+// translation
+import translationStrings from '../../../../translationStringsForComponents';
+
+// components
+import { FilterMatchMode, FilterOperator } from 'primereact/api';
+import { DataTable } from 'primereact/datatable';
+import { Column } from 'primereact/column';
+import { Button } from 'primereact/button';
+import { Calendar } from 'primereact/calendar';
+import ProperBandoLabel from '../../../../components/ProperBandoLabel';
+import { Dropdown } from 'primereact/dropdown';
+import { Tag } from 'primereact/tag';
+
+
+const AllDomandeArchiveTable = ({ updaterString = '' }) => {
+ const [items, setItems] = useState(null);
+ const [filters, setFilters] = useState(null);
+ const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
+ const [statuses, setStatuses] = useState([]);
+ const location = useLocation();
+
+ useEffect(() => {
+ setLocalAsyncRequest(true);
+ ApplicationService.getApplications(getCallback, errGetCallbacks, [
+ ['statuses', ['APPROVED', 'REJECTED']]
+ ]);
+ }, [updaterString]);
+
+ const getCallback = (data) => {
+ if (data.status === 'SUCCESS') {
+ setItems(getFormattedData(data.data));
+ setStatuses(uniq(data.data.map(o => o.status)))
+ initFilters();
+ }
+ setLocalAsyncRequest(false);
+ }
+
+ const errGetCallbacks = (data) => {
+ setLocalAsyncRequest(false);
+ }
+
+ const getFormattedData = (data) => {
+ return data.map((d) => {
+ d.callEndDate = is(String, d.callEndDate) ? new Date(d.callEndDate) : (d.callEndDate ? d.callEndDate : '');
+ d.modifiedDate = is(String, d.modifiedDate) ? new Date(d.modifiedDate) : (d.modifiedDate ? d.modifiedDate : '');
+ d.submissionDate = is(String, d.submissionDate) ? new Date(d.submissionDate) : (d.submissionDate ? d.submissionDate : '');
+ return d;
+ });
+ };
+
+ const formatDate = (value) => {
+ return value.toLocaleDateString('it-IT', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric'
+ });
+ };
+
+ const clearFilter = () => {
+ initFilters();
+ };
+
+ const initFilters = () => {
+ setFilters({
+ global: { value: null, matchMode: FilterMatchMode.CONTAINS },
+ callTitle: {
+ operator: FilterOperator.AND,
+ constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
+ },
+ companyName: {
+ operator: FilterOperator.AND,
+ constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
+ },
+ submissionDate: {
+ operator: FilterOperator.AND,
+ constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
+ },
+ callEndDate: {
+ operator: FilterOperator.AND,
+ constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
+ },
+ status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
+ });
+ };
+
+ const renderHeader = () => {
+ return (
+
+
+ );
+ };
+
+ 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}