Merge pull request #31 from Kitzanos/master-sync/14-01-2024

Master sync/14 01 2024
This commit is contained in:
Vitalii Kiiko
2025-01-14 10:13:56 +01:00
committed by GitHub
24 changed files with 1045 additions and 459 deletions

View File

@@ -4,53 +4,54 @@
"private": true,
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"@babel/preset-react": "7.24.7",
"@date-fns/tz": "1.1.2",
"@emailjs/browser": "^4.4.1",
"@emotion/styled": "11.13.0",
"@babel/preset-react": "7.25.9",
"@date-fns/tz": "1.2.0",
"@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",
"@wordpress/i18n": "5.8.0",
"@wordpress/react-i18n": "4.8.0",
"@xyflow/react": "12.3.1",
"@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",
"copy-to-clipboard": "^3.3.3",
"deep-object-diff": "^1.1.9",
"dompurify": "3.1.7",
"copy-to-clipboard": "3.3.3",
"deep-object-diff": "1.1.9",
"dompurify": "3.2.2",
"fast-deep-equal": "3.1.3",
"hotkeys-js": "^3.13.7",
"html-react-parser": "5.1.16",
"hotkeys-js": "3.13.7",
"html-react-parser": "5.1.18",
"jwt-decode": "4.0.0",
"klona": "2.0.6",
"leader-line-new": "1.1.9",
"luxon": "3.5.0",
"object-path-immutable": "4.1.2",
"primeicons": "7.0.0",
"primereact": "10.8.4",
"quill": "2.0.2",
"primereact": "10.8.5",
"quill": "2.0.3",
"ramda": "0.30.1",
"react": "18.3.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "18.3.1",
"react-hook-form": "7.53.0",
"react-router-dom": "6.26.2",
"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",
"zustand-x": "3.0.4"
},
"devDependencies": {
"@babel/cli": "7.25.6",
"@babel/core": "7.25.2",
"@babel/plugin-syntax-jsx": "7.24.7",
"@wordpress/babel-plugin-makepot": "6.8.0",
"@babel/cli": "7.25.9",
"@babel/core": "7.26.0",
"@babel/plugin-syntax-jsx": "7.25.9",
"@wordpress/babel-plugin-makepot": "6.13.0",
"babel-plugin-macros": "3.1.0",
"node-wp-i18n": "^1.2.7",
"sass": "1.79.3",
"sass-loader": "16.0.2"
"node-wp-i18n": "1.2.7",
"sass": "1.81.0",
"sass-loader": "16.0.3"
},
"scripts": {
"start": "GENERATE_SOURCEMAP=false react-scripts start",

View File

@@ -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 {
}

View File

@@ -33,3 +33,135 @@
text-align: center;
}
}
.flowContainer {
width: 100%;
overflow-x: auto;
margin-top: 30px;
}
.flowContainerInner {
position: relative;
display: flex;
flex-direction: column;
width: max-content;
margin: 0 auto;
}
.flowContainer__level {
display: flex;
justify-content: center;
gap: 20px;
/*min-height: 240px;*/
/*margin: 0 auto;*/
&.initialLevel {
padding: 0 0 30px;
}
&.intermediateLevel, &.finalLevel {
padding: 30px 0 30px;
}
&.intermediateLevel {
border-bottom: 1px solid var(--table-border-color);
border-top: 1px solid var(--table-border-color);
}
}
.flowContainer__flowItem {
position: relative;
display: flex;
flex-direction: column;
width: 280px;
min-width: 280px;
padding: 15px;
border: 1px solid var(--table-border-color);
z-index: 9;
.flowContainer__flowItemInner > label {
border-color: #757575;
background-color: #757575;
color: white;
}
&.initialForm, &.finalForm {
.flowContainer__flowItemInner > label {
border-color: var(--card-full-background-color-3);
background-color: var(--card-full-background-color-3);
color: white;
}
}
&.levelForms, &.initialForm {
&:after {
position: absolute;
bottom: -31px;
left: 50%;
content: '';
width: 1px;
height: 31px;
background-color: var(--table-border-color);
}
}
&.levelForms, &.finalForm {
&:before {
position: absolute;
top: -31px;
left: 50%;
content: '';
width: 1px;
height: 31px;
background-color: var(--table-border-color);
}
}
}
.flowContainer__levelMaskStart, .flowContainer__levelMaskEnd {
position: absolute;
width: 140px;
height: 100% ;
top: -1px;
background-color: white;
}
.flowContainer__levelMaskStart {
left: 0;
}
.flowContainer__levelMaskEnd {
right: 0;
}
.flowContainer__flowItemInner {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-width: 250px;
border: 1px solid var(--panel-content-borderColor);
height: 100%;
> label {
display: flex;
justify-content: center;
padding: 10px;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
text-align: center;
}
}
.flowContainer__flowItemContent {
padding: 5px;
display: flex;
flex-direction: column;
.appForm__field {
margin-top: 10px;
label {
text-align: center;
}
}
}

View File

@@ -147,6 +147,14 @@
max-width: 100%;
}
.p-password.p-inputwrapper {
width: 100%;
> div, input {
width: 100%;
}
}
.p-inputgroup.flex-1 {
align-items: center;
}

View File

@@ -73,3 +73,9 @@
margin-right: 7px;
}
}
@media (max-width: 500px) {
.topBar__endContent {
flex-wrap: wrap;
}
}

View File

@@ -21,6 +21,7 @@
--message-warning-color: #cc8925;
--message-info-background: rgba(183, 183, 183, 0.7);
--message-info-color: #3B82F6;
--panel-content-borderColor: #E5E7EB;
--card-full-background-color-2: #EEC137;
--card-full-background-color-3: #FA8E42;
@@ -49,3 +50,4 @@
@import "./components/evaluation.scss";
@import "./components/fieldsRepeater.scss";
@import "./components/notificationsSidebar.scss";
@import "./components/charts.scss";

View File

@@ -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 (
<div className="chartCard__tooltip">
<p className="chartCard__tooltipTitle">{label}</p>
<p className="chartCard__tooltipText">
{__('Domande', 'gepafin')}: {payload[0].value}
</p>
</div>
);
}
return null;
};
return (<div className="chartCard">
{title ? <span className="chartCard__title">{title}</span> : null}
{data && !isEmpty(data)
? <div className="chartCard__chart">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={data}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 60,
}}
>
<CartesianGrid strokeDasharray="3 3"/>
<XAxis dataKey="callName" angle={-45} textAnchor="end" height={120}
tickFormatter={truncateText}/>
<YAxis/>
<Tooltip content={<CustomTooltip/>}/>
<Legend/>
<Bar dataKey="numberOfApplications" fill="#EEC137" name={__('Quantità delle domande', 'gepafin')}/>
</BarChart>
</ResponsiveContainer>
</div> : null}
</div>)
}
export default ChartDomandePerBando;

View File

@@ -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 (
<div className="chartCard__tooltip">
<p className="chartCard__tooltipTitle">{getBandoLabel(payload[0].name)}</p>
<p className="chartCard__tooltipText">
{__('Domande', 'gepafin')}: {payload[0].value}
</p>
</div>
);
}
return null;
};
return (<div className="chartCard">
{title ? <span className="chartCard__title">{title}</span> : null}
{data && !isEmpty(data)
? <div className="chartCard__chart">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
labelLine={false}
label={({ percent }) => `${(percent * 100).toFixed(0)}%`}
outerRadius={120}
fill="#8884d8"
dataKey="numberOfApplications"
nameKey="status"
>
{data.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
</PieChart>
</ResponsiveContainer>
</div> : null}
</div>)
}
export default ChartStatoDomande;

View File

@@ -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 (
<div className="nodeInitialForm">
<label>
{label}
</label>
<span>{value}</span>
{/*{options && !isEmpty(options)
? <select onChange={onChangeFn} value={value}>
<option value="">{__('Scegli il campo', 'gepafin')}</option>
{options.map(o => <option key={o.name} value={o.name}>
{o.label}
</option>)}
</select> : null}*/}
<Handle
type="source"
position={Position.Bottom}
isConnectable={true}
/>
</div>
);
}
export default NodeInitialForm;

View File

@@ -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 (
<div className="nodeIntermediateForm">
<Handle
type="target"
position={Position.Top}
isConnectable={true}
/>
<label>
{label}
</label>
{options && !isEmpty(options)
? <select onChange={onChangeFn} value={value}>
<option value="">{__('Scegli il valore', 'gepafin')}</option>
{options.map(o => <option key={o.name} value={o.name}>
{o.label}
</option>)}
</select> : null}
<Handle
type="source"
position={Position.Bottom}
isConnectable={true}
/>
</div>
);
}
export default NodeIntermediateForm;

View File

@@ -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)
? <div className="flowBuilder__wrapper">
<ReactFlow
nodes={nodes}
edges={edges}
nodesDraggable={false}
nodesConnectable={false}
fitView
nodeTypes={nodeTypes}
attributionPosition="top-right"
>
<Background variant="dots" gap={12} size={1}/>
</ReactFlow>
</div>
: null
);
}
export default FlowBuilder;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form';
import { Password } from 'primereact/password';
const PasswordField = ({
fieldName,
label,
control,
errors,
defaultValue,
config = {},
infoText = null,
inputgroup = false,
icon = null,
placeholder = '',
disabled = false,
onBlurFn = () => {
}
}) => {
const input = <Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
rules={config}
render={({ field, fieldState }) => (
<Password
id={field.name}
disabled={disabled}
{...field}
value={field.value ? field.value : ''}
onBlur={onBlurFn}
placeholder={placeholder}
className={classNames({ 'p-invalid': fieldState.invalid })}
toggleMask />
)}/>
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label>
{inputgroup
? <div className="p-inputgroup">
<span className="p-inputgroup-addon">
{icon}
</span>
{input}
</div>
: input}
{infoText ? <small>{infoText}</small> : null}
</>)
}
export default PasswordField;

View File

@@ -16,6 +16,7 @@ import Wysiwyg from './components/Wysiwyg';
import Checkboxes from './components/Checkboxes';
import Fileupload from './components/Fileupload';
import Table from './components/Table';
import PasswordField from './components/PasswordField';
const FormField = (props) => {
const fields = {
@@ -31,7 +32,8 @@ const FormField = (props) => {
radio: Radio,
wysiwyg: Wysiwyg,
checkboxes: Checkboxes,
table: Table
table: Table,
password: PasswordField
}
const Comp = !isNil(fields[props.type]) ? fields[props.type] : null;

View File

@@ -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' },

View File

@@ -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: <HelpIcon/>,

View File

@@ -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];
}

View File

@@ -1,13 +1,14 @@
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { isEmpty, head } from 'ramda';
import { isEmpty, head, pathOr } from 'ramda';
// store
import { storeGet, storeSet, useStore } from '../../store';
import { storeSet } from '../../store';
// api
import FormsService from '../../service/forms-service';
import FlowService from '../../service/flow-service';
// tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
@@ -15,27 +16,33 @@ import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
// components
import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown';
import FlowBuilder from '../../components/FlowBuilder';
import { Messages } from 'primereact/messages';
import FlowService from '../../service/flow-service';
import { confirmPopup, ConfirmPopup } from 'primereact/confirmpopup';
import { Toast } from 'primereact/toast';
const BandoFlowEdit = () => {
const { id } = useParams();
const navigate = useNavigate();
const forms = useStore().main.flowForms();
const flowData = useStore().main.flowData();
const flowEdges = useStore().main.flowEdges();
const [flowStructure, setFlowStructure] = useState({
initialForm: 0,
finalForm: 0,
flowData: [],
flowEdges: [],
chosenField: ''
});
const [forms, setForms] = useState([]);
const [formOptions, setFormOptions] = useState([]);
const [initialForm, setInitialForm] = useState(0);
const [mainFieldOptions, setMainFieldOptions] = useState([]);
const [mainField, setMainField] = useState('');
const [chosenMainFieldOptions, setChosenMainFieldOptions] = useState([]);
//const [chosenMainField, setChosenMainField] = useState('');
const [mainFieldSuboptions, setMainFieldSubOptions] = useState([]);
const [bandoStatus, setBandoStatus] = useState('');
const [isFlowAllowed, setIsFlowAllowed] = useState(false);
const [finalForm, setFinalForm] = useState(0);
const [isFlowAllowed, setIsFlowAllowed] = useState(true);
const flowMsgs = useRef(null);
const toast = useRef(null);
const itemRefs = useRef({});
const itemContainerRef = useRef(null);
const getBandoId = () => {
const parsed = parseInt(id)
@@ -56,7 +63,8 @@ const BandoFlowEdit = () => {
defaultFocus: 'reject',
acceptClassName: 'p-button-danger',
accept: doDelete,
reject: () => {}
reject: () => {
}
});
};
@@ -64,46 +72,120 @@ const BandoFlowEdit = () => {
if (flowMsgs.current) {
flowMsgs.current.clear();
}
storeSet.main.flowData([]);
storeSet.main.flowEdges([]);
setInitialForm(0);
setMainFieldOptions([]);
setMainField('');
setFlowStructure({
initialForm: 0,
finalForm: 0,
flowData: [],
flowEdges: [],
chosenField: ''
})
setIsFlowAllowed(false);
setFinalForm(0);
setChosenMainFieldOptions([]);
}
const updateInitialForm = (value) => {
setInitialForm(value);
if (forms.length === 2) {
const finalForm = head(forms.filter(o => o.id !== value));
if (finalForm) {
setFinalForm(finalForm.id);
const updateInitialForm = useCallback((value) => {
const finalFormObj = head(forms.filter(o => o.id !== value));
if (forms.length === 2 && finalFormObj) {
setFlowStructure({
...flowStructure,
initialForm: value,
finalForm: finalFormObj.id
});
} else {
setFlowStructure({
...flowStructure,
initialForm: value
});
}
}, [flowStructure])
const updateFinalForm = useCallback((value) => {
const filtered = flowStructure.flowData.filter(o => o.formId === flowStructure.initialForm);
const flowEdges = buildFlowEdges(flowStructure.initialForm, value);
setFlowStructure({
...flowStructure,
flowEdges,
flowData: filtered,
finalForm: value
});
}, [flowStructure]);
const updateChosenField = useCallback((value) => {
setFlowStructure({
...flowStructure,
chosenField: value
});
}, [flowStructure]);
const addFlowData = useCallback((data) => {
const initial = flowStructure.flowData;
const exists = initial ? initial.filter(o => parseInt(o.formId) === parseInt(data.formId)) : [];
let final = [];
if (exists.length) {
final = initial.map(o => parseInt(o.formId) === parseInt(data.formId) ? data : o);
} else {
final = [...initial, data];
}
setFlowStructure({
...flowStructure,
flowData: final
});
}, [flowStructure]);
const updateItermediateForm = (value, formId) => {
const isUsed = flowStructure.flowData.map(o => o.chosenValue).filter(v => !isEmpty(v)).includes(value);
if (!isUsed) {
const data = {
formId: parseInt(formId),
chosenField: '',
chosenValue: value
}
addFlowData(data);
}
}
const displayChosenOptionValue = (id) => {
const suboptionId = pathOr('', ['chosenValue'], head(flowStructure.flowData.filter(f => parseInt(f.formId) === parseInt(id))));
return pathOr('', ['label'], head(mainFieldSuboptions.filter(o => o.name === suboptionId)));
}
const disabledOptionForIntermediateForm = (opt) => {
return flowStructure.flowData.map(o => o.chosenValue).filter(v => !isEmpty(v)).includes(opt.name);
}
const shoudDisableSaving = useCallback(() => {
const nonEmptyFlowItems = flowStructure.flowData.filter(o => isEmpty(o.chosenField)).filter(o => !isEmpty(o.chosenValue));
/*if (flowForms.length > 2) {
console.log('disable BTN:', nonEmptyFlowItems.length !== flowForms.length - 2, isEmpty(flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(initialForm), isEmpty(finalForm));
} else {
console.log('disable BTN:', nonEmptyFlowItems.length !== 1, isEmpty(flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(initialForm), isEmpty(finalForm));
}*/
return forms.length > 2
? isEmpty(flowData) || isEmpty(flowEdges) || isEmpty(initialForm) || isEmpty(finalForm)
|| flowData.length < forms.length - 1 || 'PUBLISH' === bandoStatus
: isEmpty(flowEdges) || isEmpty(initialForm) || 'PUBLISH' === bandoStatus;
}, [flowData, flowEdges]);
? nonEmptyFlowItems.length !== forms.length - 2 || isEmpty(flowStructure.flowEdges) || 'PUBLISH' === bandoStatus
|| isEmpty(flowStructure.initialForm) || isEmpty(flowStructure.finalForm)
: nonEmptyFlowItems.length !== 1 || isEmpty(flowStructure.flowEdges) || 'PUBLISH' === bandoStatus
|| isEmpty(flowStructure.initialForm) || isEmpty(flowStructure.finalForm);
}, [flowStructure, forms]);
const doSave = () => {
storeSet.main.setAsyncRequest();
const bandoId = getBandoId();
const body = {
initialForm,
finalForm,
flowData,
flowEdges
};
if (flowMsgs.current) {
flowMsgs.current.clear();
}
FlowService.createFlow(bandoId, body, getFlowCreateCallback, errGetFlowCreateCallback);
FlowService.createFlow(bandoId, flowStructure, getFlowCreateCallback, errGetFlowCreateCallback);
}
const getFlowCreateCallback = (data) => {
@@ -126,12 +208,13 @@ const BandoFlowEdit = () => {
const getFormsCallback = (data) => {
if (data.status === 'SUCCESS') {
setForms(data.data);
const formOptions = data.data.map(o => ({ label: o.label, value: o.id }))
storeSet.main.flowForms(data.data);
setFormOptions([{ label: '', value: '' }, ...formOptions]);
const bandoId = getBandoId();
storeSet.main.setAsyncRequest();
FlowService.getFlow(bandoId, getFlowCallback, errGetFlowCallback);
FlowService.getFlow(bandoId, (resp) => getFlowCallback(resp, data.data), errGetFlowCallback);
}
storeSet.main.unsetAsyncRequest();
}
@@ -141,21 +224,42 @@ const BandoFlowEdit = () => {
storeSet.main.unsetAsyncRequest();
}
const getFlowCallback = (data) => {
const getFlowCallback = (data, forms) => {
if (data.status === 'SUCCESS' && data.data) {
storeSet.main.flowData(data.data.flowData);
storeSet.main.flowEdges(data.data.flowEdges);
setInitialForm(data.data.initialForm);
setFinalForm(data.data.finalForm);
setBandoStatus(data.data.callStatus);
const chosenFieldItem = head(data.data.flowData.filter(o => !isEmpty(o.chosenField)));
if (chosenFieldItem) {
setMainField(chosenFieldItem.chosenField);
}
const flowDataItem = head(data.data.flowData.filter(o => !isEmpty(o.chosenField)));
setBandoStatus(data.data.callStatus);
if (flowDataItem) {
setMainField(flowDataItem.chosenField);
if (chosenFieldItem) {
setFlowStructure({
initialForm: data.data.initialForm,
finalForm: data.data.finalForm,
flowData: data.data.flowData,
flowEdges: data.data.flowEdges,
chosenField: chosenFieldItem.chosenField
});
const form = head(forms.filter(o => o.id === data.data.initialForm));
const relevantFields = form
? form.content
.filter(o => ['radio', 'select'].includes(o.name))
.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
return { value: o.id, label: label ? label.value : o.label };
})
: [];
setChosenMainFieldOptions(relevantFields);
const field = form ? head(form.content.filter(o => o.id === chosenFieldItem.chosenField)) : null;
if (field) {
const options = head(field.settings.filter(o => o.name === 'options'));
setMainFieldSubOptions(options.value);
}
} else {
setFlowStructure({
initialForm: data.data.initialForm,
finalForm: data.data.finalForm,
flowData: data.data.flowData,
flowEdges: data.data.flowEdges,
chosenField: ''
});
}
}
storeSet.main.unsetAsyncRequest();
@@ -166,71 +270,48 @@ const BandoFlowEdit = () => {
storeSet.main.unsetAsyncRequest();
}
useEffect(() => {
const flowForms = storeGet.main.flowForms();
const form = head(flowForms.filter(o => String(o.id) === String(initialForm)))
const field = form ? head(form.content.filter(o => o.id === mainField)) : null;
let options = [];
const setItemRef = (id, element) => {
itemRefs.current[id] = element;
};
if (field) {
options = head(field.settings.filter(o => o.name === 'options'));
const buildFlowEdges = (initialForm, finalForm) => {
let edges = [];
if (!isEmpty(initialForm) && !isEmpty(finalForm)) {
// eslint-disable-next-line
forms.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 (forms.length === 2 && initialForm && finalForm) {
edges.push({
id: `${initialForm}->${finalForm}`,
source: String(initialForm),
target: String(finalForm),
type: 'smoothstep'
});
}
}
if (field && options.value && options.value.length === flowForms.length - 2) {
setIsFlowAllowed(true);
const data = {
formId: String(initialForm),
chosenField: mainField,
chosenValue: ''
}
storeSet.main.addFlowData(data);
} else {
setIsFlowAllowed(false);
let msg = 'Non è possibile creare il flusso. Il campo principale deve avere esattamente %s opzioni.';
if (flowForms.length - 2 === 1) {
msg = 'Non è possibile creare il flusso. Il campo principale deve avere esattamente %s opzione.';
}
if (flowMsgs.current && !isEmpty(mainField)) {
flowMsgs.current.clear();
flowMsgs.current.show([
{
id: '1',
sticky: true, severity: 'error', summary: '',
detail: sprintf(
__(msg, 'gepafin'),
flowForms.length - 2
),
closable: false
}
]);
}
}
}, [mainField]);
useEffect(() => {
setMainField('');
setMainFieldOptions([]);
const flowForms = storeGet.main.flowForms();
const form = head(flowForms.filter(o => String(o.id) === String(initialForm)))
const relevantFields = form
? form.content
.filter(o => ['radio', 'select'].includes(o.name))
.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
return { value: o.id, label: label ? label.value : o.label };
})
: [];
setMainFieldOptions([
{label: isEmpty(relevantFields) ? __('Nessun scelta', 'gepafin') : '', value: ''},
...relevantFields]
);
if (flowForms.length === 2) {
setIsFlowAllowed(true)
}
}, [initialForm]);
return edges;
};
useEffect(() => {
const bandoId = getBandoId();
@@ -251,23 +332,98 @@ const BandoFlowEdit = () => {
]);
} else {
flowMsgs.current.clear();
if (itemContainerRef.current) {
itemContainerRef.current.dispatchEvent(new Event('scroll'));
}
}
}, [forms]);
useEffect(() => {
const chosenFieldItem = head(flowData.filter(o => !isEmpty(o.chosenField)));
if (chosenFieldItem) {
setMainField(chosenFieldItem.chosenField);
}
}, [flowData])
const initialForm = flowStructure.initialForm;
const finalForm = flowStructure.finalForm;
const chosenField = flowStructure.chosenField;
useEffect(() => {
return () => {
storeSet.main.flowForms([]);
storeSet.main.flowData([]);
storeSet.main.flowEdges([]);
if (!isEmpty(initialForm) && !isEmpty(finalForm)) {
const form = head(forms.filter(o => String(o.id) === String(initialForm)))
const relevantFields = form
? form.content
.filter(o => ['radio', 'select'].includes(o.name))
.map(o => {
const label = head(o.settings.filter(o => o.name === 'label'));
return { value: o.id, label: label ? label.value : o.label };
})
: [];
setChosenMainFieldOptions([
{ label: isEmpty(relevantFields) ? __('Nessun scelta', 'gepafin') : '', value: '' },
...relevantFields]
);
if (forms.length === 2) {
setIsFlowAllowed(true);
}
}, []);
//const flowEdges = buildFlowEdges(initialForm, finalForm);
if (!isEmpty(chosenField)) {
const field = form ? head(form.content.filter(o => o.id === chosenField)) : null;
let options = [];
if (field) {
options = head(field.settings.filter(o => o.name === 'options'));
}
if (field && options.value && options.value.length === forms.length - 2) {
setIsFlowAllowed(true);
const suboptions = [
{ label: __('Nessun scelta', 'gepafin'), name: '' },
...options.value
]
setMainFieldSubOptions(suboptions);
const data = {
formId: parseInt(initialForm),
chosenField: chosenField,
chosenValue: ''
}
addFlowData(data);
if (flowMsgs.current && !isEmpty(chosenField)) {
flowMsgs.current.clear();
}
} else {
setIsFlowAllowed(false);
let msg = 'Non è possibile creare il flusso. Il campo principale deve avere esattamente %s opzioni.';
if (forms.length - 2 === 1) {
msg = 'Non è possibile creare il flusso. Il campo principale deve avere esattamente %s opzioni.';
}
if (flowMsgs.current && !isEmpty(chosenField)) {
flowMsgs.current.clear();
flowMsgs.current.show([
{
id: '1',
sticky: true, severity: 'error', summary: '',
detail: sprintf(
__(msg, 'gepafin'),
forms.length - 2
),
closable: false
}
]);
}
}
}
}
}, [flowStructure.initialForm, flowStructure.finalForm, flowStructure.chosenField]);
const { initialForm = 0, finalForm = 0, flowData = [], chosenField = '' } = flowStructure;
const initialFormData = head(forms.filter(o => o.id === initialForm));
const finalFormData = head(forms.filter(o => o.id === finalForm));
const levelForms = forms.filter(o => o.id !== initialForm && o.id !== finalForm);
return (
<div className="appPage">
@@ -296,29 +452,29 @@ const BandoFlowEdit = () => {
placeholder={__('Scegli il form', 'gepafin')}/>
</div>
{forms.length > 2 && initialForm && mainFieldOptions
{forms.length > 2 && initialForm && chosenMainFieldOptions
? <div className="appForm__field">
<label htmlFor="mainField">{__('Scegli il campo principale', 'gepafin')}</label>
<label htmlFor="chosenMainField">{__('Scegli il campo principale', 'gepafin')}</label>
<Dropdown
id="mainField"
id="chosenMainField"
disabled={'PUBLISH' === bandoStatus}
value={mainField}
onChange={(e) => setMainField(e.value)}
value={chosenField}
onChange={(e) => updateChosenField(e.value)}
optionDisabled={(opt) => isEmpty(opt.value)}
options={mainFieldOptions}
options={chosenMainFieldOptions}
optionLabel="label"
optionValue="value"
placeholder={__('Scegli il campo', 'gepafin')}/>
</div> : null}
{(forms.length > 2 && mainField && isFlowAllowed) || (forms.length === 2 && isFlowAllowed)
{(forms.length > 2 && chosenField && isFlowAllowed) || (forms.length === 2 && isFlowAllowed)
? <div className="appForm__field">
<label htmlFor="finalForm">{__('Scegli form finale', 'gepafin')}</label>
<Dropdown
id="finalForm"
disabled={'PUBLISH' === bandoStatus}
value={finalForm}
onChange={(e) => setFinalForm(e.value)}
onChange={(e) => updateFinalForm(e.value)}
optionDisabled={(opt) => initialForm === opt.value || isEmpty(opt.value)}
options={formOptions}
optionLabel="label"
@@ -343,11 +499,67 @@ const BandoFlowEdit = () => {
<div className="appPageSection">
<Messages ref={flowMsgs}/>
{forms.length >= 2 && isFlowAllowed
? <FlowBuilder
initialForm={initialForm}
finalForm={finalForm}
mainField={mainField}/> : null}
{forms.length >= 2 && initialForm && finalForm && isFlowAllowed
? <div className="flowContainer" ref={itemContainerRef}>
<div className="flowContainerInner">
<div className="flowContainer__level initialLevel">
<div className="flowContainer__flowItem initialForm"
ref={(el) => initialForm ? setItemRef(initialForm, el) : null}>
<div className="flowContainer__flowItemInner">
<label htmlFor="chosenMainField">
{initialFormData.label}
</label>
</div>
</div>
</div>
{levelForms.length && initialForm && finalForm
? <div className="flowContainer__level intermediateLevel">
{levelForms.map((o, i) => <div key={o.id}
ref={(el) => setItemRef(o.id, el)}
className="flowContainer__flowItem levelForms">
<div className="flowContainer__flowItemInner">
<label htmlFor="chosenMainField">{o.label}</label>
<div className="flowContainer__flowItemContent">
{mainFieldSuboptions && !isEmpty(mainFieldSuboptions)
? 'PUBLISH' !== bandoStatus
? <Dropdown
id="initialForm"
value={pathOr('', ['chosenValue'], head(flowData.filter(f => f.formId === parseInt(o.id))))}
onChange={(e) => updateItermediateForm(e.value, o.id)}
options={mainFieldSuboptions}
optionDisabled={disabledOptionForIntermediateForm}
optionLabel="label"
optionValue="name"
placeholder={__('Scegli il valore', 'gepafin')}/>
:
<label>{displayChosenOptionValue(o.id)}</label>
: null}
</div>
</div>
</div>)}
{levelForms.length > 1
? <>
<div className="flowContainer__levelMaskEnd"></div>
<div className="flowContainer__levelMaskStart"></div>
</> : null}
</div> : null}
{forms.length >= 2 && initialForm && finalForm
? <div className="flowContainer__level finalLevel">
<div className="flowContainer__flowItem finalForm"
ref={(el) => finalForm ? setItemRef(finalForm, el) : null}>
<div className="flowContainer__flowItemInner">
<label htmlFor="chosenMainField">
{finalFormData.label}
</label>
</div>
</div>
</div>
: null}
</div>
</div> : null}
</div>
<div className="appPage__spacer"></div>

View File

@@ -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,10 +43,12 @@ 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);
@@ -147,20 +147,36 @@ const Dashboard = () => {
<div className="appPage__spacer"></div>
{chartStats.applicationPerCall
? <div className="appPageSection">
<h2>{__('Statistiche di sistema', 'gepafin')}</h2>
<div className="appPageSection columns">
<ChartDomandePerBando
title={__('Domande per bando', 'gepafin')}
data={chartStats.applicationPerCall}/>
<ChartStatoDomande
title={__('Stato domande', 'gepafin')}
data={chartStats.applicationPerStatus}/>
</div>
</div> : null}
<div className="appPage__spacer"></div>
<div className="appPageSection__hr">
<span>{__('Azioni rapide', 'gepafin')}</span>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
onClick={onGoToCreateNewBando}
label={__('Crea nuovo bando', 'gepafin')} icon="pi pi-plus" iconPos="right"/>
{/*<Button
disabled={true}
<Button
onClick={onGoToUsers}
label={__('Gestione utenti', 'gepafin')} icon="pi pi-users" iconPos="right"/>
<Button
{/*<Button
disabled={true}
onClick={onGoToStats}
label={__('Report mensile', 'gepafin')} icon="pi pi-chart-bar" iconPos="right"/>

View File

@@ -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 (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined
onClick={clearFilter}/>
</div>
);
};
const dateAppliedBodyTemplate = (rowData) => {
return formatDate(rowData.submissionDate);
};
const statusFilterTemplate = (options) => {
return <Dropdown value={options.value} options={statuses}
onChange={(e) => options.filterCallback(e.value, options.index)}
itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel} className="p-column-filter"
showClear/>;
};
const dateFilterTemplate = (options) => {
return <Calendar value={options.value} onChange={(e) => options.filterCallback(e.value, options.index)}
dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999"/>;
};
const statusBodyTemplate = (rowData) => {
return <ProperBandoLabel status={rowData.status}/>;
};
const statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)}/>;
};
const actionsBodyTemplate = (rowData) => {
return <div className="appPageSection__tableActions lessGap">
<Link to={`/domande-archivio/${rowData.id}/preview`}>
<Button severity="info" label={__('Anteprima', 'gepafin')} icon="pi pi-eye" size="small"
iconPos="right"/>
</Link>
</div>
}
const header = renderHeader();
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}
onFilter={(e) => setFilters(e.filters)}>
<Column field="id" header={__('ID domanda', 'gepafin')}
sortable filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '6rem' }}/>
<Column field="protocolNumber" header={__('Protocollo', 'gepafin')}
sortable filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '6rem' }}/>
<Column field="callTitle" header={__('Bando', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '10rem' }}/>
<Column field="companyName" header={__('Azienda Beneficiaria', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca il nome', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column header={__('Data Ricezione', 'gepafin')}
filterField="submissionDate" dataType="date"
style={{ minWidth: '8rem' }}
body={dateAppliedBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="assignedUserName" header={__('Assegnato', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca il nome', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column field="status" header={__('Stato', 'gepafin')}
style={{ minWidth: '8rem' }} body={statusBodyTemplate}
filter
filterElement={statusFilterTemplate}/>
<Column header={__('Azioni', 'gepafin')}
body={actionsBodyTemplate}/>
</DataTable>
</div>
)
}
export default AllDomandeArchiveTable;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
// components
import AllDomandeArchiveTable from './components/AllDomandeArchiveTable';
const Domande = () => {
return (
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Archivio domande', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Domande pubblicate', 'gepafin')}</h2>
<AllDomandeArchiveTable/>
</div>
</div>
)
}
export default Domande;

View File

@@ -1,9 +1,9 @@
import React, { useRef, useState, useEffect } from 'react';
import React, { useRef, useState, useEffect, useMemo } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useForm } from 'react-hook-form';
import { classNames } from 'primereact/utils';
import { isEmpty } from 'ramda';
import { useNavigate } from 'react-router-dom';
import { isEmpty, isNil } from 'ramda';
import { useNavigate, useSearchParams } from 'react-router-dom';
// tools
import AuthenticationService from '../../service/authentication-service';
@@ -24,7 +24,9 @@ const ResetPassword = () => {
const token = useStore().main.token();
const [loading, setLoading] = useState(false);
const [resetPassToken, setResetPassToken] = useState('');
const [resetPassEmail, setResetPassEmail] = useState('');
const errorMsgs = useRef(null);
let [searchParams] = useSearchParams();
const {
control,
handleSubmit,
@@ -47,12 +49,33 @@ const ResetPassword = () => {
}
if (request.token && !isEmpty(request.token)) {
AuthenticationService.resetPassword(request, getCallback, errCallback);
AuthenticationService.resetPassword(request, getCallbackReset, errCallback);
} else {
AuthenticationService.forgotPassword(request, getCallback, errCallback);
}
};
const getCallbackReset = (data) => {
if (data.status === 'SUCCESS') {
errorMsgs.current.show([
{
sticky: true, severity: 'success', summary: '',
detail: data.message,
closable: true
}
]);
} else {
errorMsgs.current.show([
{
sticky: true, severity: 'error', summary: '',
detail: data.message,
closable: true
}
]);
}
setLoading(false);
}
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setResetPassToken(data.data)
@@ -87,9 +110,18 @@ const ResetPassword = () => {
}, [token]);
useEffect(() => {
setValue('token', resetPassToken);
console.log(resetPassToken, resetPassEmail);
reset();
}, [resetPassToken])
setValue('token', resetPassToken);
setValue('email', resetPassEmail);
}, [resetPassToken, resetPassEmail]);
useEffect(() => {
const token = searchParams.get('token');
const email = searchParams.get('email');
setResetPassToken(token);
setResetPassEmail(email);
}, [searchParams]);
return (
<div className={classNames(['appPage', 'appPageLogin'])}>
@@ -113,7 +145,7 @@ const ResetPassword = () => {
placeholder="sample@example.com"
/>
{!isEmpty(resetPassToken)
{resetPassToken && !isEmpty(resetPassToken)
? <input
type="hidden"
name="token"
@@ -122,10 +154,9 @@ const ResetPassword = () => {
})}
/> : null}
{!isEmpty(resetPassToken)
{resetPassToken && !isEmpty(resetPassToken)
? <FormField
type="textinput"
inputtype="password"
type="password"
fieldName="newPassword"
label={__('Password', 'gepafin')}
control={control}
@@ -138,9 +169,9 @@ const ResetPassword = () => {
}}
/> : null}
{!isEmpty(resetPassToken)
{resetPassToken && !isEmpty(resetPassToken)
? <FormField
type="textinput"
type="password"
inputtype="password"
fieldName="confirmPassword"
label={__('Conferma password', 'gepafin')}

View File

@@ -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 ? <BandoApplicationPreview/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <BandoApplicationPreview/> : null}
</DefaultLayout>}/>
<Route path="/domande-archivio" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <DomandeArchive/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <DomandeArchive/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DomandeArchive/> : null}
</DefaultLayout>}/>
<Route path="/domande-archivio/:id/preview" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <BandoApplicationPreview/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <BandoApplicationPreview/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <BandoApplicationPreview/> : null}
</DefaultLayout>}/>
<Route path="/domande/:id/aggiungi-soccorso" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}

View File

@@ -43,16 +43,5 @@ export const actionsBeta = (set, get, api) => ({
const newElements = newFields.toSpliced(hoverIndex, 0, prevFields[dragIndex]);
set.formElements(newElements);
}
},
addFlowData: (data) => {
const initial = get.flowData();
const exists = initial ? initial.filter(o => parseInt(o.formId) === parseInt(data.formId)) : [];
if (exists.length) {
const newData = initial.map(o => parseInt(o.formId) === parseInt(data.formId) ? data : o);
set.flowData(newData);
} else {
set.flowData([...initial, data]);
}
}
});