Merge pull request #35 from Kitzanos/master-sync/fields-calc-feature

Master sync/fields calc feature
This commit is contained in:
Vitalii Kiiko
2025-02-13 11:48:01 +01:00
committed by GitHub
110 changed files with 6682 additions and 1238 deletions

View File

@@ -4,38 +4,41 @@
"private": true,
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"@babel/preset-react": "7.25.9",
"@babel/preset-react": "7.26.3",
"@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",
"@emotion/styled": "11.14.0",
"@number-flow/react": "0.5.5",
"@sentry/browser": "8.51.0",
"@stomp/stompjs": "7.0.0",
"@tanstack/react-table": "8.20.5",
"@wordpress/i18n": "5.13.0",
"@wordpress/react-i18n": "4.13.0",
"@tanstack/react-table": "8.20.6",
"@wordpress/i18n": "5.16.0",
"@wordpress/react-i18n": "4.16.0",
"codice-fiscale-js": "2.3.22",
"copy-to-clipboard": "3.3.3",
"deep-object-diff": "1.1.9",
"dompurify": "3.2.2",
"dompurify": "3.2.3",
"expression-language": "^1.2.0",
"fast-deep-equal": "3.1.3",
"hotkeys-js": "3.13.7",
"html-react-parser": "5.1.18",
"hotkeys-js": "3.13.9",
"html-react-parser": "5.2.2",
"jwt-decode": "4.0.0",
"klona": "2.0.6",
"leader-line-new": "1.1.9",
"luxon": "3.5.0",
"mathjs": "^14.0.1",
"mustache": "^4.2.0",
"object-path-immutable": "4.1.2",
"primeicons": "7.0.0",
"primereact": "10.8.5",
"primereact": "10.9.2",
"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.2",
"react-router-dom": "7.0.1",
"react-hook-form": "7.54.2",
"react-router-dom": "7.1.3",
"react-scripts": "5.0.1",
"recharts": "2.15.0",
"sockjs-client": "^1.6.1",
@@ -44,14 +47,14 @@
"zustand-x": "3.0.4"
},
"devDependencies": {
"@babel/cli": "7.25.9",
"@babel/core": "7.26.0",
"@babel/cli": "7.26.4",
"@babel/core": "7.26.7",
"@babel/plugin-syntax-jsx": "7.25.9",
"@wordpress/babel-plugin-makepot": "6.13.0",
"@wordpress/babel-plugin-makepot": "6.16.0",
"babel-plugin-macros": "3.1.0",
"node-wp-i18n": "1.2.7",
"sass": "1.81.0",
"sass-loader": "16.0.3"
"sass": "1.83.4",
"sass-loader": "16.0.4"
},
"scripts": {
"start": "GENERATE_SOURCEMAP=false react-scripts start",

View File

@@ -57,13 +57,10 @@
background-color: #e3e3e3;
}
&.table {
&.table, &.criteria_table {
div.addNewTableRow {
width: 100%;
text-align: center;
padding: 5px 0;
background: var(--table-border-color);
color: var(--primary-text);
justify-content: center;
&:hover {
cursor: pointer;
@@ -88,7 +85,6 @@
color: var(--global-textColor);
font-size: 15px;
text-align: left;
text-align: start;
}
th {
@@ -97,16 +93,15 @@
}
td {
min-width: 120px;
input {
width: 100%;
padding: 3px 5px;
}
}
tfoot td,
tfoot th {
tfoot td {
border-top: 1px solid var(--table-border-color);
border-top: 1px solid var(--table-border-color);
border-bottom: 0
}
table.striped tbody tr:nth-child(odd) td,

View File

@@ -53,11 +53,24 @@
font-weight: 400;
line-height: 21px;
&:hover {
cursor: pointer;
}
/*&.selected {
border-color: var(--menuitem-active-background);
}*/
.meta {
display: flex;
flex-direction: column;
gap: 5px;
align-items: flex-start;
.tagHeader {
display: flex;
gap: 10px;
}
}
.label {
@@ -111,7 +124,7 @@
border: 1px solid var(--button-secondary-borderColor);
background-color: var(--button-secondary-borderColor);
width: 100%;
padding: 10px;
padding: 20px 10px;
opacity: 0.6;
color: white;
font-size: 11px;
@@ -176,6 +189,44 @@
gap: 0.5rem;
}
.formElementSettings__fieldDescription, .formElementSettings__fieldVarsList {
padding: 15px;
background-color: #ffe0c5;
border: 1px solid #e6a973;
p {
margin: 0;
color: #c68e5e;
font-size: 15px;
line-height: 1.5;
}
code {
font-size: 14px;
padding: 3px 6px;
border: 1px solid #e1b48b;
background-color: #fbeadb;
border-radius: 3px;
user-select: all;
}
}
.formElementSettings__fieldVarsList {
background-color: #e7fddd;
border: 1px solid #9de673;
p {
color: #5eae30;
}
code {
border: 1px solid #9de673;
background-color: #effaea;
margin-right: 4px;
user-select: all;
}
}
.formElementSettings__tabs {
width: 100%;
@@ -198,9 +249,13 @@
.formElementSettings__repeaterItem {
display: grid;
grid-template-columns: 4.5fr 2.4fr 1fr 1.4fr 0.7fr;
grid-template-columns: 1fr;
gap: 12px;
&.tableRow {
grid-template-columns: 4.5fr 2.4fr 1fr 1.4fr 0.7fr;
}
> div {
display: flex;
align-items: center;

View File

@@ -116,6 +116,20 @@ img {
}
}
}
li div.nonLink {
display: flex;
padding: 10.5px 17.5px;
align-items: center;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 100%;
text-decoration: none;
border-bottom: 1px solid var(--menu-borderColor);
background-color: var(--button-secondary-borderColor);
color: white;
}
}
}

View File

@@ -31,6 +31,14 @@
}
}
.p-tag {
&.p-tag-secondary {
background-color: var(--table-border-color);
}
&.p-tag-tertiary {
background-color: var(--card-full-background-color-6);
}
.p-tag-value {
color: var(--menuitem-active-color);
}
@@ -147,6 +155,10 @@
max-width: 100%;
}
.p-dropdown, .p-chips, .p-chips-multiple-container {
width: 100%;
}
.p-password.p-inputwrapper {
width: 100%;
@@ -155,6 +167,10 @@
}
}
.p-inputnumber-input[readonly] {
background-color: #e1e1e1;
}
.p-inputgroup.flex-1 {
align-items: center;
}

View File

@@ -43,6 +43,119 @@
}
}
.statsBigBadges__gridItemDoubleStats {
display: flex;
flex-direction: column;
padding: 16px;
border-radius: 6px;
border: 1px solid #858585;
background: #cecece;
align-items: center;
gap: 32px;
span {
color: #FFF;
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: normal;
text-align: center;
}
> span:first-of-type {
min-height: 50px;
}
span.number {
font-size: 22px;
}
.auxStats {
display: flex;
gap: 7px;
align-items: flex-start;
margin-top: auto;
span {
font-size: 15px;
font-weight: 400;
text-align: left;
}
span.badge {
display: inline-block;
padding: 5px 10px;
background-color: var(--card-full-background-color-4);
border-radius: 4px;
margin-right: 7px;
}
}
&:nth-of-type(1) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-2);
}
&:nth-of-type(2) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-3);
}
&:nth-of-type(3) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-5);
}
&:nth-of-type(4) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-7);
}
}
.statsBigBadges__gridItemDoubleStatsBeneficiary {
display: flex;
flex-direction: column;
padding: 16px;
border-radius: 6px;
border: 1px solid #858585;
background: #cecece;
align-items: center;
gap: 32px;
span {
color: #FFF;
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: normal;
text-align: center;
}
> span:first-of-type {
min-height: 50px;
}
&:nth-of-type(1) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-2);
}
&:nth-of-type(2) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-4);
}
&:nth-of-type(3) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-3);
}
&:nth-of-type(4) {
border: 1px solid var(--yellow-500);
background: var(--card-full-background-color-1);
}
}
.statsBigBadges__grid {
.statsBigBadges__gridItem {
&:nth-of-type(1) {
@@ -109,11 +222,27 @@
}
}
}
&.doubleStatsItems {
grid-template-columns: repeat(4, minmax(220px, 1fr));
}
}
@media (max-width: 1290px) {
.statsBigBadges__grid {
&.doubleStatsItems {
grid-template-columns: repeat(2, minmax(220px, 1fr));
}
}
}
@media (max-width: 820px) {
.statsBigBadges__grid {
grid-template-columns: 1fr;
&.doubleStatsItems {
grid-template-columns: minmax(220px, 1fr);
}
}
}

View File

@@ -22,7 +22,10 @@ const ChartDomandePerBando = ({ title, data = [] }) => {
<div className="chartCard__tooltip">
<p className="chartCard__tooltipTitle">{label}</p>
<p className="chartCard__tooltipText">
{__('Domande', 'gepafin')}: {payload[0].value}
{__('In bozza', 'gepafin')}: {payload[0].value}
</p>
<p className="chartCard__tooltipText">
{__('Inviate', 'gepafin')}: {payload[1].value}
</p>
</div>
);
@@ -50,7 +53,8 @@ const ChartDomandePerBando = ({ title, data = [] }) => {
<YAxis/>
<Tooltip content={<CustomTooltip/>}/>
<Legend/>
<Bar dataKey="numberOfApplications" fill="#EEC137" name={__('Quantità delle domande', 'gepafin')}/>
<Bar dataKey="numberOfDraftApplications" fill="#8884d8" name={__('Domande in bozza', 'gepafin')}/>
<Bar dataKey="numberOfSubmitedApplications" fill="#EEC137" name={__('Domande inviate', 'gepafin')}/>
</BarChart>
</ResponsiveContainer>
</div> : null}

View File

@@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { Tooltip, ResponsiveContainer, Cell, Pie, PieChart, Legend } from 'recharts';
import { isEmpty } from 'ramda';
// tools
import getBandoLabel from '../../helpers/getBandoLabel';
const ChartDomandePerStato = ({ title, data = [] }) => {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8', '#82ca9d'];
const [chartData, setChartData] = useState({});
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">
{payload[0].name}: {payload[0].value}
</p>
</div>
);
}
return null;
};
useEffect(() => {
const grouped = data.reduce((acc, cur) => {
if (cur.status === 'APPROVED') {
acc.approved.value = cur.numberOfApplication;
} else if (cur.status === 'REJECTED') {
acc.rejected.value = cur.numberOfApplication;
} else {
acc.inProgress.value += cur.numberOfApplication;
}
return acc;
}, {
inProgress: {value: 0, label: __('In corso', 'gepafin')},
approved: {value: 0, label: __('Approvato', 'gepafin')},
rejected: {value: 0, label: __('Respinto', 'gepafin')}
});
setChartData(grouped)
}, [data]);
return (<div className="chartCard">
{title ? <span className="chartCard__title">{title}</span> : null}
{chartData && !isEmpty(chartData)
? <div className="chartCard__chart">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={Object.values(chartData)}
cx="50%"
cy="50%"
labelLine={false}
label={({ percent }) => `${(percent * 100).toFixed(0)}%`}
outerRadius={120}
fill="#8884d8"
dataKey="value"
nameKey="label"
>
{Object.values(chartData).map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
<Legend verticalAlign="top" height={36}/>
</PieChart>
</ResponsiveContainer>
</div> : null}
</div>)
}
export default ChartDomandePerStato;

View File

@@ -0,0 +1,56 @@
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 ChartRichiesteVsApprovate = ({ title, data = [] }) => {
// 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">
{__('In bozza', 'gepafin')}: {payload[0].value}
</p>
<p className="chartCard__tooltipText">
{__('Inviate', 'gepafin')}: {payload[1].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="month"/>
<YAxis/>
<Tooltip content={<CustomTooltip/>}/>
<Legend/>
<Bar dataKey="totalRequested" fill="#8884d8" stackId="a" name={__('Richiesti', 'gepafin')}/>
<Bar dataKey="totalApproved" fill="#EEC137" stackId="a" name={__('Approvati', 'gepafin')}/>
</BarChart>
</ResponsiveContainer>
</div> : null}
</div>)
}
export default ChartRichiesteVsApprovate;

View File

@@ -38,7 +38,7 @@ const ChartStatoDomande = ({ title, data = [] }) => {
label={({ percent }) => `${(percent * 100).toFixed(0)}%`}
outerRadius={120}
fill="#8884d8"
dataKey="numberOfApplications"
dataKey="numberOfSubmitedApplications"
nameKey="status"
>
{data.map((entry, index) => (

View File

@@ -0,0 +1,20 @@
import { InputText } from 'primereact/inputtext';
const DefaultCell = ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
const disabled = table.options.meta?.disabled;
const onFocus = (e) => {
e.target.select();
}
return (
<InputText
value={initialValue ?? ''}
disabled={disabled}
onFocus={onFocus}
onChange={(e) => table.options.meta?.updateData(index, id, e.target.value)} />
);
};
export default DefaultCell;

View File

@@ -0,0 +1,15 @@
import { head, is, isNil, pathOr } from 'ramda';
import getNumberFormatted from '../../../../../../../helpers/getNumberFormatted';
const LastRowCell = ({columnId, lastRowCfg, columnMeta = {}, tableValue = []}) => {
const cellData = head(lastRowCfg.filter(o => !isNil(o[columnId])));
let cellValue = cellData[columnId];
if (columnMeta.enableFormula) {
cellValue = pathOr(0, ['total'], tableValue);
}
return <td>{is(Number, cellValue) ? getNumberFormatted(cellValue) : cellValue}</td>;
};
export default LastRowCell;

View File

@@ -0,0 +1,30 @@
import { InputNumber } from 'primereact/inputnumber';
const NumericFormulaCell = ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
const disabled = table.options.meta?.disabled;
const onFocus = (e) => {
e.target.select();
}
const onChange = (value) => {
table.options.meta?.updateData(index, id, value);
};
return (
<InputNumber
disabled={disabled}
value={initialValue ?? 0}
onValueChange={(e) => onChange(e.value)}
onFocus={onFocus}
minFractionDigits={0}
maxFractionDigits={2}
locale='it-IT'
useGrouping={false}
showButtons
/>
);
};
export default NumericFormulaCell;

View File

@@ -0,0 +1,100 @@
import React from 'react';
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { wrap } from 'object-path-immutable';
import { head, isEmpty, isNil, pathOr, sum } from 'ramda';
// components
import DefaultCell from './components/DefaultCell';
import LastRowCell from './components/LastRowCell';
const RenderTable = ({ tableValue = {}, columnsCfg, lastRowCfg, setTableValueFn, disabled }) => {
const rows = pathOr([], ['rows'], tableValue)
const table = useReactTable({
data: rows,
columns: columnsCfg,
defaultColumn: {
cell: DefaultCell
},
getCoreRowModel: getCoreRowModel(),
meta: {
disabled,
updateData: (rowIndex, columnId, value) => {
const columnCfgData = head(columnsCfg.filter(o => o.accessorKey === columnId));
const cellData = head(lastRowCfg.filter(o => !isNil(o[columnId])));
const cellValue = cellData[columnId];
let newRowsData = wrap(tableValue).set(['rows', rowIndex, columnId], value).value();
let total = pathOr(0, ['total'], tableValue);
if (columnCfgData.meta.enableFormula) {
const getAllRowsValues = newRowsData.rows
.map(row => row[columnId])
.map(v => isEmpty(v) || isNil(v) ? 0 : v);
if (cellValue === 'sum') {
total = sum(getAllRowsValues);
} else {
total = 0;
}
}
newRowsData = wrap(newRowsData).set(['total'], total).value();
setTableValueFn(newRowsData);
},
}
});
return (
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
);
})}
</tr>
);
})}
</tbody>
{!isEmpty(lastRowCfg)
? <tfoot><tr>
{columnsCfg.map((o) => <LastRowCell
key={o.accessorKey}
columnId={o.accessorKey}
columnMeta={o.meta}
lastRowCfg={lastRowCfg}
tableValue={tableValue}/>)}
</tr></tfoot>
: null}
</table>
)
}
export default RenderTable

View File

@@ -0,0 +1,132 @@
import React, { useEffect, useState, useCallback } from 'react';
import { classNames } from 'primereact/utils';
import { pathOr, isEmpty } from 'ramda';
import { wrap } from 'object-path-immutable';
import equal from 'fast-deep-equal';
import { klona } from 'klona';
// tools
import { nonEmptyTables } from '../../../../helpers/validators';
//components
import RenderTable from './RenderTable';
import NumericFormulaCell from './RenderTable/components/NumericFormulaCell';
const Table = ({
fieldName,
setDataFn,
label,
register,
errors,
disabled = false,
config = {},
defaultValue = [],
tableColumns = []
}) => {
const [columnsCfg, setColumnsCfg] = useState([]);
const [rowsCfg, setRowsCfg] = useState([]);
const [columns, setColumns] = useState([]);
const [lastRow, setLastRow] = useState([]);
const [tableValue, setTableValue] = useState(null);
const updateValue = useCallback((data) => {
setTableValue(data);
setDataFn(fieldName, data, { shouldValidate: true });
}, [tableValue, defaultValue]);
const properConfig = (config) => {
let newConfig = klona(config);
if (config.validate && config.validate.nonEmptyTables) {
newConfig = wrap(newConfig)
.set(['validate', 'nonEmptyTables'], (v) => nonEmptyTables(v, tableColumns))
.value();
}
return newConfig;
}
useEffect(() => {
let newColumns = columnsCfg.map((o) => {
const item = {
accessorKey: o.name,
header: () => o.label,
footer: (props) => props.column.id,
meta: {
predefined: o.predefined,
enableFormula: o.enableFormula,
fieldtype: o.fieldtype
}
}
if (o.enableFormula || o.fieldtype === 'numeric') {
item.cell = NumericFormulaCell;
} else {
item.cell = (info) => info.getValue();
}
return item;
});
setColumns(newColumns);
}, [columnsCfg, disabled]);
useEffect(() => {
setTableValue(rowsCfg);
}, [rowsCfg]);
useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], tableColumns);
const obj = stateFieldData
.reduce((acc, cur) => {
acc[cur.name] = ''
return acc;
}, {});
let rowsData = pathOr([obj], ['rowsData'], tableColumns);
rowsData = isEmpty(rowsData) ? [obj] : rowsData;
setColumnsCfg(stateFieldData);
setRowsCfg({ rows: rowsData, total: 0 });
let lastRowData = stateFieldData.reduce((acc, cur) => {
const value = cur.enableFormula ? cur.lastRowFormula : (cur.lastRowText ? cur.lastRowText : '');
acc.push({ [cur.name]: value });
return acc;
}, []);
const lastRowValues = lastRowData
.map(o => {
const values = Object.values(o);
return values[0];
})
.filter(v => !isEmpty(v));
setLastRow(!isEmpty(lastRowValues) ? lastRowData : []);
}, [tableColumns]);
useEffect(() => {
if (!equal(tableValue, defaultValue)) {
setTableValue(defaultValue);
}
}, [defaultValue]);
useEffect(() => {
setTableValue(defaultValue);
register(fieldName, properConfig(config));
}, []);
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired || (config.validate && config.validate.nonEmptyTables)
? <span className="appForm__field--required">*</span> : null}
</label>
{tableValue
? <RenderTable
columnsCfg={columns}
tableValue={tableValue}
lastRowCfg={lastRow}
setTableValueFn={updateValue}
disabled={disabled}/> : null}
</>)
}
export default Table;

View File

@@ -21,6 +21,7 @@ const NumberInput = ({
min,
max,
disabled = false,
readOnly = false,
useGrouping = true
}) => {
const minAttr = config.min ? config.min : min;
@@ -33,11 +34,13 @@ const NumberInput = ({
render={({ field, fieldState }) => (
<InputNumber inputId={field.name}
disabled={disabled}
readOnly={readOnly}
value={field.value}
onValueChange={(e) => field.onChange(e.value)}
min={minAttr}
max={maxAttr}
locale={locale}
showButtons
useGrouping={useGrouping}
maxFractionDigits={!isNaN(parseInt(maxFractionDigits)) ? parseInt(maxFractionDigits) : 0}
minFractionDigits={!isNaN(parseInt(minFractionDigits)) ? parseInt(minFractionDigits) : 0}

View File

@@ -1,24 +1,19 @@
import { InputText } from 'primereact/inputtext';
const DefaultCell = ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
const disabled = table.options.meta?.disabled;
const onBlur = (e) => {
table.options.meta?.updateData(index, id, e.target.value);
};
const onFocus = (e) => {
e.target.select();
}
return (
<input
disabled={disabled}
<InputText
value={initialValue ?? ''}
onChange={(e) => table.options.meta?.updateData(index, id, e.target.value)}
onBlur={onBlur}
disabled={disabled}
onFocus={onFocus}
className="w-full px-2 py-1 border rounded"
/>
onChange={(e) => table.options.meta?.updateData(index, id, e.target.value)} />
);
};

View File

@@ -1,7 +1,8 @@
import { head, isEmpty, isNil, sum } from 'ramda';
import getNumberFormatted from '../../../../../../../helpers/getNumberFormatted';
const LastRowCell = ({columnId, lastRows, columnMeta = {}, getColumnDataFn}) => {
const cellData = head(lastRows.filter(o => !isNil(o[columnId])));
const LastRowCell = ({columnId, lastRowCfg, columnMeta = {}, getColumnDataFn}) => {
const cellData = head(lastRowCfg.filter(o => !isNil(o[columnId])));
let cellValue = cellData[columnId];
if (columnMeta.enableFormula) {
@@ -9,7 +10,7 @@ const LastRowCell = ({columnId, lastRows, columnMeta = {}, getColumnDataFn}) =>
.map(v => isEmpty(v) || isNil(v) ? 0 : v);
if (cellValue === 'sum') {
cellValue = sum(getAllRowsValues);
cellValue = getNumberFormatted(sum(getAllRowsValues));
} else {
cellValue = 0;
}

View File

@@ -1,32 +1,28 @@
import { InputNumber } from 'primereact/inputnumber';
const NumericFormulaCell = ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
const disabled = table.options.meta?.disabled;
const onBlur = (e) => {
const numValue = e.target.value === 0 ? null : Number(e.target.value);
table.options.meta?.updateData(index, id, numValue);
};
const onFocus = (e) => {
e.target.select();
}
const onChange = (e) => {
if (e.target.value === 0 || !isNaN(e.target.value)) {
table.options.meta?.updateData(index, id, e.target.value);
}
const onChange = (value) => {
table.options.meta?.updateData(index, id, value);
};
return (
<input
type="number"
<InputNumber
disabled={disabled}
value={initialValue ?? 0}
onChange={onChange}
onValueChange={(e) => onChange(e.value)}
onFocus={onFocus}
onBlur={onBlur}
step="any"
className="w-full px-2 py-1 border rounded"
minFractionDigits={0}
maxFractionDigits={2}
locale='it-IT'
useGrouping={false}
showButtons
/>
);
};

View File

@@ -7,10 +7,10 @@ import { isEmpty } from 'ramda';
import DefaultCell from './components/DefaultCell';
import LastRowCell from './components/LastRowCell';
const RenderTable = ({ data, columns, lastRow, setRowsFn, disabled }) => {
const RenderTable = ({ rowsData = [], columnsCfg, lastRowCfg, setRowsFn, disabled }) => {
const table = useReactTable({
data,
columns,
data: rowsData,
columns: columnsCfg,
defaultColumn: {
cell: DefaultCell
},
@@ -18,7 +18,7 @@ const RenderTable = ({ data, columns, lastRow, setRowsFn, disabled }) => {
meta: {
disabled,
updateData: (rowIndex, columnId, value) => {
const newRowsData = wrap(data).set([rowIndex, columnId], value).value();
const newRowsData = wrap(rowsData).set([rowIndex, columnId], value).value();
setRowsFn(newRowsData);
},
}
@@ -68,17 +68,17 @@ const RenderTable = ({ data, columns, lastRow, setRowsFn, disabled }) => {
</tr>
);
})}
{!isEmpty(lastRow)
? <tr>
{columns.map((o) => <LastRowCell
</tbody>
{!isEmpty(lastRowCfg)
? <tfoot><tr>
{columnsCfg.map((o) => <LastRowCell
key={o.accessorKey}
columnId={o.accessorKey}
columnMeta={o.meta}
lastRows={lastRow}
lastRowCfg={lastRowCfg}
getColumnDataFn={getColumnData}/>)}
</tr>
</tr></tfoot>
: null}
</tbody>
</table>
)
}

View File

@@ -122,13 +122,13 @@ const Table = ({
useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], tableColumns);
const obj = stateFieldData
/*const obj = stateFieldData
.reduce((acc, cur) => {
acc[cur.name] = ''
return acc;
}, {});
let rowsData = pathOr([obj], ['rowsData'], tableColumns);
rowsData = isEmpty(rowsData) ? [obj] : rowsData;
}, {});*/
let rowsData = pathOr([], ['rowsData'], tableColumns);
//rowsData = isEmpty(rowsData) ? [obj] : rowsData;
setColumnsCfg(stateFieldData);
setRowsCfg(rowsData);
@@ -169,14 +169,15 @@ const Table = ({
{label}{config.required || config.isRequired || (config.validate && config.validate.nonEmptyTables)
? <span className="appForm__field--required">*</span> : null}
</label>
{rows ? <RenderTable
columns={columns}
data={rows}
lastRow={lastRow}
{rows
? <RenderTable
columnsCfg={columns}
rowsData={rows}
lastRowCfg={lastRow}
setRowsFn={updateRows}
disabled={disabled}/> : null}
{!isEmpty(columns) && !shouldDisableNewRows
? <div className="addNewTableRow" onClick={addNewRow}>
? <div className="addNewTableRow p-button p-component" onClick={addNewRow}>
{__('Aggiungi una riga', 'gepafin')}
</div>
: null}

View File

@@ -17,6 +17,7 @@ import Checkboxes from './components/Checkboxes';
import Fileupload from './components/Fileupload';
import Table from './components/Table';
import PasswordField from './components/PasswordField';
import CriteriaTable from './components/CriteriaTable';
const FormField = (props) => {
const fields = {
@@ -33,6 +34,7 @@ const FormField = (props) => {
wysiwyg: Wysiwyg,
checkboxes: Checkboxes,
table: Table,
criteria_table: CriteriaTable,
password: PasswordField
}
const Comp = !isNil(fields[props.type]) ? fields[props.type] : null;

View File

@@ -17,12 +17,13 @@ const NotificationItemChosen = ({ item, closeFn, markReadFn }) => {
<span>{getDateFromISOstring(item.createdDate)}</span>
{item.message}
<Button
{item.status === 'UNREAD'
? <Button
style={{marginTop: '20px'}}
type="button"
outlined
onClick={() => markReadFn(item.id)}
label={__('Letto', 'gepafin')}/>
label={__('Letto', 'gepafin')}/> : null}
</div>
)
}

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { head, isEmpty, pathOr } from 'ramda';
import SockJS from 'sockjs-client';
@@ -19,6 +19,7 @@ import { Sidebar } from 'primereact/sidebar';
import { TabPanel, TabView } from 'primereact/tabview';
import NotificationItem from './components/NotificationItem';
import NotificationItemChosen from './components/NotificationItemChosen';
import PaginatorBasic from '../PaginatorBasic';
const socketUrl = process.env.REACT_APP_API_ADDRESS_WS;
@@ -35,22 +36,22 @@ const NotificationsSidebar = () => {
const stomp = useRef(null);
const [currentSubscription, setCurrentSubscription] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [totalRecordsNum, setTotalRecordsNum] = useState(0);
const [totalPagesNum, setTotalPagesNum] = useState(0);
const perPage = 10;
// Handle tab change
const handleTabChange = (e) => {
setActiveIndex(e.index);
fetchTabData(e.index);
};
const fetchTabData = (index) => {
setChosenMsg({});
if (0 === index) {
fetchMessages();
} else {
fetchMessages('READ');
if (e.index === activeIndex) {
return
}
}
setTotalRecordsNum(0);
setTotalPagesNum(0);
setChosenMsg({});
setActiveIndex(e.index);
setCurrentPage(1);
};
const chooseNotification = (id) => {
const properItems = activeIndex === 0 ? notifications : notificationsRead;
@@ -64,10 +65,27 @@ const NotificationsSidebar = () => {
setChosenMsg({});
}
const fetchMessages = (status = 'UNREAD') => {
const getPaginationQuery = (status = 'UNREAD', curPage = 1) => {
return {
'globalFilters': {
'page': curPage,
'limit': perPage,
'sortBy': {
'columnName': 'id',
'sortDesc': true
}
},
'status': [
status
]
}
}
const fetchMessages = useCallback((status = 'UNREAD') => {
const chosenCompanyId = storeGet.main.chosenCompanyId();
const userData = storeGet.main.userData();
const role = pathOr('', ['role', 'roleType'], userData);
const bodyParams = getPaginationQuery(status, currentPage);
if (currentSubscription) {
//console.log('UNsubscribed')
@@ -77,37 +95,72 @@ const NotificationsSidebar = () => {
if (userData.id && chosenCompanyId !== 0 && role === 'ROLE_BENEFICIARY') {
setLoading(true);
NotificationService.getNotifications(
userData.id,
status === 'UNREAD' ? getNotifications : getNotificationsRead,
errGetNotifications,
[
['status', status],
['companyId', chosenCompanyId]
]
);
if (isConnected && socket.current) {
subscribeTo(`/topic/notifications_user_${userData.id}_company_${chosenCompanyId}`)
}
} else if (userData.id && role !== 'ROLE_BENEFICIARY') {
setLoading(true);
NotificationService.getNotifications(
NotificationService.getNotificationsByCompanyId(
userData.id,
chosenCompanyId,
status === 'UNREAD' ? getNotifications : getNotificationsRead,
errGetNotifications,
[
['status', status]
]
);
if (isConnected && socket.current) {
subscribeTo(`/topic/notifications_user_${userData.id}_company_${chosenCompanyId}`)
}
} else if (userData.id && role !== 'ROLE_BENEFICIARY') {
setLoading(true);
/*NotificationService.getNotifications(
userData.id,
status === 'UNREAD' ? getNotifications : getNotificationsRead,
errGetNotifications,
[
['status', status]
]
);*/
NotificationService.getNotificationsPagination(
userData.id,
bodyParams,
status === 'UNREAD' ? getNotificationsPagi : getNotificationsReadPagi,
errGetNotifications
);
if (isConnected && socket.current) {
subscribeTo(`/topic/notifications_user_${userData.id}`)
}
}
}, [currentPage]);
const getNotificationsPagi = (resp) => {
if (resp.status === 'SUCCESS') {
const { body, totalRecords, currentPage, totalPages } = resp.data;
setNotifications(body);
setTotalRecordsNum(totalRecords);
setTotalPagesNum(totalPages);
if (currentPage > totalPages) {
setCurrentPage(totalPages);
}
}
set404FromErrorResponse(resp);
setLoading(false);
}
const getNotificationsReadPagi = (resp) => {
if (resp.status === 'SUCCESS') {
const { body, totalRecords, currentPage, totalPages } = resp.data;
setNotificationsRead(body);
setTotalRecordsNum(totalRecords);
setTotalPagesNum(totalPages);
if (currentPage > totalPages) {
setCurrentPage(totalPages);
}
}
set404FromErrorResponse(resp);
setLoading(false);
}
const getNotifications = (resp) => {
if (resp.status === 'SUCCESS') {
setNotifications(resp.data);
setTotalRecordsNum(resp.data.length);
}
set404FromErrorResponse(resp);
setLoading(false);
@@ -116,6 +169,7 @@ const NotificationsSidebar = () => {
const getNotificationsRead = (resp) => {
if (resp.status === 'SUCCESS') {
setNotificationsRead(resp.data);
setTotalRecordsNum(resp.data.length);
}
set404FromErrorResponse(resp);
setLoading(false);
@@ -139,6 +193,7 @@ const NotificationsSidebar = () => {
const msgs = notificationsRead.map(o => o.id === resp.data.id ? resp.data : o);
setNotificationsRead(msgs);
}
setTotalRecordsNum(totalRecordsNum - 1);
}
set404FromErrorResponse(resp);
}
@@ -152,7 +207,7 @@ const NotificationsSidebar = () => {
stomp.current = Stomp.over(socket.current);
stomp.current.configure({
debug: function(str) {
debug: function (str) {
//console.log(str);
},
reconnectDelay: 5000,
@@ -190,10 +245,22 @@ const NotificationsSidebar = () => {
setCurrentSubscription(subscription);
}
const onPageChange = (num) => {
setCurrentPage(num);
};
useEffect(() => {
fetchMessages();
}, [chosenCompanyId, userData.id, isConnected]);
useEffect(() => {
if (0 === activeIndex) {
fetchMessages();
} else {
fetchMessages('READ');
}
}, [currentPage, activeIndex]);
useEffect(() => {
connectWebSocket();
@@ -215,7 +282,7 @@ const NotificationsSidebar = () => {
<>
<i className="pi pi-bell p-overlay-badge topBar__icon notificationsIcon"
onClick={() => setNotificationsVisible(true)}>
<Badge value={notifications.filter(o => o.status === 'UNREAD').length}></Badge>
<Badge value={totalRecordsNum}></Badge>
</i>
<Sidebar
className="notificationsSidebar"
@@ -234,12 +301,19 @@ const NotificationsSidebar = () => {
closeFn={closeChosenMsg}
markReadFn={makeNotificationRead}/>
: (notifications.length > 0
? <ul className="notificationsSidebar__list">
{notifications.map(o => <NotificationItem
key={o.id}
item={o}
clickFn={chooseNotification}/>)}
</ul>
? <>
<ul className="notificationsSidebar__list">
{notifications.map(o => <NotificationItem
key={o.id}
item={o}
clickFn={chooseNotification}/>)}
</ul>
<PaginatorBasic
totalPages={totalPagesNum}
currentPage={currentPage}
clickFn={onPageChange}
/>
</>
: <div className="notificationsSidebar__loading">
<i className="pi pi-megaphone" style={{ fontSize: '2rem' }}></i>
{__('Vuoto', 'gepafin')}
@@ -256,17 +330,24 @@ const NotificationsSidebar = () => {
closeFn={closeChosenMsg}
markReadFn={makeNotificationRead}/>
: (notificationsRead.length > 0
? <ul className="notificationsSidebar__list">
{notificationsRead.map(o => <NotificationItem
key={o.id}
item={o}
clickFn={chooseNotification}/>)}
</ul>
:
<div className="notificationsSidebar__loading">
<i className="pi pi-megaphone" style={{ fontSize: '2rem' }}></i>
{__('Vuoto', 'gepafin')}
</div>)}
? <>
<ul className="notificationsSidebar__list">
{notificationsRead.map(o => <NotificationItem
key={o.id}
item={o}
clickFn={chooseNotification}/>)}
</ul>
<PaginatorBasic
totalPages={totalPagesNum}
currentPage={currentPage}
clickFn={onPageChange}
/>
</>
:
<div className="notificationsSidebar__loading">
<i className="pi pi-megaphone" style={{ fontSize: '2rem' }}></i>
{__('Vuoto', 'gepafin')}
</div>)}
</TabPanel>
</TabView>
</Sidebar>

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
const PaginatorBasic = ({
currentPage = 0,
totalPages = 0,
clickFn = () => {
}
}) => {
const handleClick = (num) => {
const newNum = num < 0
? 0
: num > totalPages ? totalPages : num;
clickFn(newNum);
}
const prevDisabled = currentPage <= 1;
const nextDisabled = currentPage >= totalPages
return (
totalPages !== 0
? <div className="p-paginator p-component" data-pc-name="paginator" data-pc-section="root">
<button
type="button"
className={`p-paginator-prev p-paginator-element p-link${prevDisabled ? ' p-disabled' : ''}`}
disabled={prevDisabled}
onClick={prevDisabled ? () => {
} : () => handleClick(currentPage - 1)}
aria-label={__('Pagina precedente', 'gepafin')}
data-pc-section="prevpagebutton">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"
className="p-icon p-paginator-icon" aria-hidden="true" data-pc-section="prevpageicon">
<path
d="M8.75 11.185C8.65146 11.1854 8.55381 11.1662 8.4628 11.1284C8.37179 11.0906 8.28924 11.0351 8.22 10.965L4.72 7.46496C4.57955 7.32433 4.50066 7.13371 4.50066 6.93496C4.50066 6.73621 4.57955 6.54558 4.72 6.40496L8.22 2.93496C8.36095 2.84357 8.52851 2.80215 8.69582 2.81733C8.86312 2.83252 9.02048 2.90344 9.14268 3.01872C9.26487 3.134 9.34483 3.28696 9.36973 3.4531C9.39463 3.61924 9.36303 3.78892 9.28 3.93496L6.28 6.93496L9.28 9.93496C9.42045 10.0756 9.49934 10.2662 9.49934 10.465C9.49934 10.6637 9.42045 10.8543 9.28 10.995C9.13526 11.1257 8.9448 11.1939 8.75 11.185Z"
fill="currentColor"></path>
</svg>
</button>
<span aria-live="polite" className="p-paginator-current" data-pc-section="current">
({currentPage} {__('di', 'gepafin')} {totalPages})
</span>
<button
type="button"
disabled={nextDisabled}
onClick={nextDisabled ? () => {
} : () => handleClick(currentPage + 1)}
className={`p-paginator-next p-paginator-element p-link${nextDisabled ? ' p-disabled' : ''}`}
aria-label={__('Pagina successiva', 'gepafin')}
data-pc-section="nextpagebutton">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"
className="p-icon p-paginator-icon" aria-hidden="true" data-pc-section="nextpageicon">
<path
d="M5.25 11.1728C5.14929 11.1694 5.05033 11.1455 4.9592 11.1025C4.86806 11.0595 4.78666 10.9984 4.72 10.9228C4.57955 10.7822 4.50066 10.5916 4.50066 10.3928C4.50066 10.1941 4.57955 10.0035 4.72 9.86283L7.72 6.86283L4.72 3.86283C4.66067 3.71882 4.64765 3.55991 4.68275 3.40816C4.71785 3.25642 4.79932 3.11936 4.91585 3.01602C5.03238 2.91268 5.17819 2.84819 5.33305 2.83149C5.4879 2.81479 5.64411 2.84671 5.78 2.92283L9.28 6.42283C9.42045 6.56346 9.49934 6.75408 9.49934 6.95283C9.49934 7.15158 9.42045 7.34221 9.28 7.48283L5.78 10.9228C5.71333 10.9984 5.63193 11.0595 5.5408 11.1025C5.44966 11.1455 5.35071 11.1694 5.25 11.1728Z"
fill="currentColor"></path>
</svg>
</button>
</div> : null
)
}
export default PaginatorBasic;

View File

@@ -59,6 +59,9 @@ const getBandoLabel = (status) => {
case 'CLOSE':
return __('Chiuso', 'gepafin');
case 'REJECTED':
return __('Respinto', 'gepafin');
default:
return '';
}

View File

@@ -57,6 +57,9 @@ const getBandoSeverity = (status) => {
case 'CLOSE':
return 'closed';
case 'REJECTED':
return 'danger';
default:
return 'info';
}

11
src/helpers/getTokens.js Normal file
View File

@@ -0,0 +1,11 @@
import { tokenize } from 'expression-language';
const getTokens = expr => tokenize(expr).tokens
.filter((o, i, arr) => o.type === 'name'
&& (typeof arr[i+1] === 'undefined'
|| (typeof arr[i+1] !== 'undefined' && arr[i+1].value !== '('))
)
.map(o => o.value)
.filter((v, i, arr) => arr.indexOf(v) === i);
export default getTokens;

View File

@@ -0,0 +1,10 @@
const isDateTimeInFuture = (dateStr, timeStr) => {
const [hours, minutes, seconds = 0] = timeStr.split(':').map(Number);
const dateTime = new Date(dateStr);
dateTime.setHours(hours, minutes, seconds);
const now = new Date();
return dateTime > now;
}
export default isDateTimeInFuture;

3
src/helpers/keepKeys.js Normal file
View File

@@ -0,0 +1,3 @@
const keepKeys = (arr, keys) => arr.map(obj => Object.fromEntries(keys.map(k => [k, obj[k]])));
export default keepKeys;

View File

@@ -0,0 +1,3 @@
const parseCommaDecimal = (value = '') => parseFloat(String(value).replace(',', '.'));
export default parseCommaDecimal;

3
src/helpers/removeKey.js Normal file
View File

@@ -0,0 +1,3 @@
const removeKey = (arr, key) => arr.map(({[key]: _, ...rest}) => rest);
export default removeKey;

View File

@@ -0,0 +1,23 @@
import mustache from 'mustache';
import { replace } from 'ramda';
/**
* Use mustachejs to parse content and replace variables with their values
* Each variable (everything in brackets) is expression for EE
*
* @param {string} content
* @param {object} context
*
* @returns {string}
*/
const renderWithDataVars = (content = '', context = {}) => {
try {
let newContent = replace(/{/g, '{{&', content);
newContent = replace(/}/g, '}}', newContent);
return mustache.render(newContent, context);
} catch {
return content;
}
};
export default renderWithDataVars;

View File

@@ -45,77 +45,105 @@ const AppSidebar = () => {
label: __('Bandi osservati', 'gepafin'),
icon: 'pi pi-star',
href: '/bandi-osservati',
id: 13,
id: 5,
enable: intersection(permissions, ['VIEW_CALLS']).length
},
{
label: __('Gestione domande', 'gepafin'),
icon: 'pi pi-file',
href: '/domande',
id: 5,
enable: intersection(permissions, ['VIEW_USERS', 'MANAGE_USERS']).length
id: 6,
enable: intersection(permissions, ['VIEW_USERS', 'MANAGE_USERS', 'ASSIGED_APPLICATION']).length
},
{
label: __('Domande da valutare', 'gepafin'),
icon: 'pi pi-calendar-clock',
href: '/domande',
id: 6,
id: 7,
enable: intersection(permissions, ['EVALUATE_APPLICATIONS']).length && !intersection(permissions, ['ASSIGED_APPLICATION']).length
},
{
label: __('Bandi attivi', 'gepafin'),
icon: 'pi pi-file',
href: '/bandi',
id: 8,
enable: intersection(permissions, ['EVALUATE_APPLICATIONS']).length
},
{
label: __('Soccorso', 'gepafin'),
icon: <HelpIcon/>,
href: '/soccorso-istruttorio',
id: 9,
enable: intersection(permissions, ['EVALUATE_APPLICATIONS']).length
},
{
label: __('Archivio domande', 'gepafin'),
icon: 'pi pi-briefcase',
href: '/domande',
id: 7,
id: 10,
enable: intersection(permissions, ['APPLY_CALLS']).length
},
{
label: __('Archivio domande', 'gepafin'),
icon: 'pi pi-briefcase',
href: '/domande-archivio',
id: 5,
id: 11,
enable: intersection(permissions, ['VIEW_USERS', 'MANAGE_USERS']).length
},
{
label: __('Archivio domande', 'gepafin'),
icon: 'pi pi-briefcase',
href: '/domande-archivio',
id: 6,
id: 12,
enable: intersection(permissions, ['EVALUATE_APPLICATIONS']).length
},
{
label: __('Area personale', 'gepafin'),
icon: 'pi pi-calendar-clock',
id: 17,
enable: intersection(permissions, ['ASSIGED_APPLICATION']).length
},
{
label: __('Domande da valutare', 'gepafin'),
icon: 'pi pi-calendar-clock',
href: '/mie-domande',
id: 18,
enable: intersection(permissions, ['ASSIGED_APPLICATION']).length
},
{
label: __('Soccorso istruttorio', 'gepafin'),
icon: <HelpIcon/>,
href: '/soccorso-istruttorio',
id: 8,
enable: intersection(permissions, ['EVALUATE_APPLICATIONS']).length
href: '/mio-soccorso-istruttorio',
id: 19,
enable: intersection(permissions, ['ASSIGED_APPLICATION']).length
},
{
label: __('Gestione utenti', 'gepafin'),
icon: 'pi pi-users',
href: '/utenti',
id: 9,
id: 13,
enable: intersection(permissions, ['VIEW_USERS', 'MANAGE_USERS']).length
},
{
label: __('Configurazione', 'gepafin'),
icon: 'pi pi-cog',
//href: '/configurazione',
id: 10,
id: 14,
enable: false
},
{
label: __('Report e Analisi', 'gepafin'),
label: __('Statistiche', 'gepafin'),
icon: 'pi pi-chart-bar',
//href: '/stats',
id: 11,
enable: false
href: '/stats',
id: 15,
enable: intersection(permissions, ['APPLY_CALLS']).length
},
{
label: __('Log di Sistema', 'gepafin'),
icon: 'pi pi-receipt',
clickFn: () => {},
id: 12,
clickFn: () => {
},
id: 16,
enable: false
}
]
@@ -125,20 +153,25 @@ const AppSidebar = () => {
{items
.filter(o => o.enable)
.map(o => <li key={o.id}>
{o.href
? <NavLink to={o.href}>
{is(String, o.icon)
? <i className={o.icon}></i>
: o.icon}
<span>{o.label}</span>
</NavLink>
: <button onClick={() => {}}>
{is(String, o.icon)
? <i className={o.icon}></i>
: o.icon}
<span>{o.label}</span>
</button>}
</li>)}
{o.href
? <NavLink to={o.href}>
{is(String, o.icon)
? <i className={o.icon}></i>
: o.icon}
<span>{o.label}</span>
</NavLink>
: (o.clickFn ?
<button onClick={() => {
}}>
{is(String, o.icon)
? <i className={o.icon}></i>
: o.icon}
<span>{o.label}</span>
</button>
: <div className="nonLink">
<span>{o.label}</span>
</div>)}
</li>)}
</ul>
</aside>
}

View File

@@ -121,7 +121,7 @@ const AllBandiTable = () => {
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import { is, uniq, isNil, isEmpty } from 'ramda';
import { wrap } from 'object-path-immutable';
import { useNavigate } from 'react-router-dom';
@@ -30,6 +30,8 @@ import { Button } from 'primereact/button';
// i18n
import translationStrings from '../../../../translationStringsForComponents';
import isDateTimeInPast from '../../../../helpers/isDateTimeInPast';
import isDateTimeInFuture from '../../../../helpers/isDateTimeInFuture';
import { Badge } from 'primereact/badge';
const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID;
@@ -182,18 +184,22 @@ const AllBandiAccordion = ({ showOnlyPreferred = false }) => {
const rowExpansionTemplate = (data) => {
const isCallExpired = isDateTimeInPast(data.dates[1], data.endTime);
const isCallScheduled = isDateTimeInFuture(data.dates[0], data.startTime);
return (
<div className="p-3">
{renderHtmlContent(data.descriptionShort)}
<p>{__('Scadenza', 'gepafin')}: {getDateFromISOstring(data.dates[1])}</p>
{!isCallExpired && !isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && (!data.confidi
{!isCallExpired && !isCallScheduled && !isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && (!data.confidi
|| (data.confidi && data.id === 6 && REACT_APP_HUB_ID === 'p4lk3bcx1RStqTaIVVbXs'))
? <Button onClick={() => goToBandoPage(data.id)} severity="info">
{__('Partecipa', 'gepafin')}
</Button> : null}
{isCallExpired
? <p>{__('È scaduto', 'gepafin')}</p> : null}
{isCallExpired || (!isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && data.confidi
? <p><Badge value={__('È scaduto', 'gepafin')} severity="danger"></Badge></p> : null}
{isCallScheduled
? <p><Badge value={sprintf(__('È programmato. Inizia: %s %s', 'gepafin'), getDateFromISOstring(data.dates[0]), data.startTime)}></Badge></p> : null}
{isCallExpired || isCallScheduled || (!isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && data.confidi
&& (data.id !== 6 || (data.id === 6 && REACT_APP_HUB_ID !== 'p4lk3bcx1RStqTaIVVbXs')))
? <Button onClick={() => goToBandoPage(data.id)} severity="info">
{__('Mostra', 'gepafin')}
@@ -210,7 +216,7 @@ const AllBandiAccordion = ({ showOnlyPreferred = false }) => {
<div className="appPageSection__table">
<DataTable value={items}
paginator
rows={10}
rows={5}
loading={isAsyncRequest}
dataKey="id"
filters={filters}

View File

@@ -0,0 +1,149 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda';
// tools
import getBandoSeverity from '../../../../helpers/getBandoSeverity';
import getBandoLabel from '../../../../helpers/getBandoLabel';
import getDateFromISOstring from '../../../../helpers/getDateFromISOstring';
// api
import BandoService from '../../../../service/bando-service';
// components
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Dropdown } from 'primereact/dropdown';
import { Button } from 'primereact/button';
import { Calendar } from 'primereact/calendar';
import { Tag } from 'primereact/tag';
import ProperBandoLabel from '../../../../components/ProperBandoLabel';
import { Link } from 'react-router-dom';
import translationStrings from '../../../../translationStringsForComponents';
const AllBandiTable = () => {
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [statuses, setStatuses] = useState([]);
useEffect(() => {
setLocalAsyncRequest(true);
BandoService.getBandi(getCallback, errGetCallbacks);
}, []);
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setItems(getFormattedBandiData(data.data));
setStatuses(uniq(data.data.map(o => o.status)))
initFilters();
}
setLocalAsyncRequest(false);
}
const errGetCallbacks = (data) => {
setLocalAsyncRequest(false);
}
const getFormattedBandiData = (data) => {
return data.map((d) => {
d.dates = d.dates.map(v => is(String, v) ? new Date(v) : (v ? v : ''));
return d;
});
};
const clearFilter = () => {
initFilters();
};
const initFilters = () => {
setFilters({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
start_date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
end_date: { 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 nameBodyTemplate = (rowData) => {
return <span>{rowData.name}</span>
}*/
const dateStartBodyTemplate = (rowData) => {
return getDateFromISOstring(rowData.dates[0]);
};
const dateEndBodyTemplate = (rowData) => {
return getDateFromISOstring(rowData.dates[1]);
};
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 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 statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)} />;
};
const actionsBodyTemplate = (rowData) => {
return <Link to={`/bandi/${rowData.id}`}>
<Button severity="info" label={__('Mostra', 'gepafin')} icon="pi pi-eye" size="small" iconPos="right" />
</Link>
}
const header = renderHeader();
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}
onFilter={(e) => setFilters(e.filters)}>
<Column field="name" header={__('Nome Bando', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column header={__('Data Pubblicazione', 'gepafin')} filterField="start_date" dataType="date"
style={{ minWidth: '8rem' }}
body={dateStartBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column header={__('Data Scadenza', 'gepafin')} filterField="end_date" dataType="date"
style={{ minWidth: '8rem' }}
body={dateEndBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')} filterMenuStyle={{ width: '14rem' }}
style={{ minWidth: '7rem' }} body={statusBodyTemplate} filter
filterElement={statusFilterTemplate}/>
<Column header={__('Azioni', 'gepafin')}
body={actionsBodyTemplate}/>
</DataTable>
</div>
)
}
export default AllBandiTable;

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
// components
import AllBandiTable from './components/AllBandiTable';
const BandiPreInstructor = () => {
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Bandi attivi', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<AllBandiTable/>
</div>
</div>
)
}
export default BandiPreInstructor;

View File

@@ -186,7 +186,7 @@ const AllBandiPreferredAccordion = () => {
<div className="appPageSection__table">
<DataTable value={items}
paginator
rows={10}
rows={5}
loading={isAsyncRequest}
dataKey="id"
filters={filters}

View File

@@ -1,10 +1,13 @@
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useParams } from 'react-router-dom';
import { head, is, pluck, isEmpty, pathOr } from 'ramda';
import { head, is, pluck, isEmpty, pathOr, isNil } from 'ramda';
import { useForm } from 'react-hook-form';
import 'quill/dist/quill.core.css';
import { wrap } from 'object-path-immutable';
import { evaluate } from 'mathjs';
import equal from 'fast-deep-equal';
import { klona } from 'klona';
// store
import { storeSet, storeGet, useStore } from '../../store';
@@ -26,6 +29,10 @@ import {
import renderHtmlContent from '../../helpers/renderHtmlContent';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import getFormatedFileSizeText from '../../helpers/getFormatedFileSizeText';
import renderWithDataVars from '../../helpers/renderWithDataVars';
import getTokens from '../../helpers/getTokens';
import formatDateString from '../../helpers/formatDateString';
import isDateTimeInPast from '../../helpers/isDateTimeInPast';
// components
import { Skeleton } from 'primereact/skeleton';
@@ -39,8 +46,7 @@ import { Dialog } from 'primereact/dialog';
import FileuploadApplicationSignedPdf from '../../components/FileuploadApplicationSignedPdf';
import { defaultMaxFileSize } from '../../configData';
import formatDateString from '../../helpers/formatDateString';
import isDateTimeInPast from '../../helpers/isDateTimeInPast';
import parseCommaDecimal from '../../helpers/parseCommaDecimal';
const BandoApplication = () => {
const chosenCompanyId = useStore().main.chosenCompanyId();
@@ -67,6 +73,7 @@ const BandoApplication = () => {
trigger,
register,
getValues,
watch,
reset
} = useForm({
defaultValues: useMemo(() => {
@@ -89,6 +96,7 @@ const BandoApplication = () => {
}
const activeStepIndex = activeStep - 1;
const values = getValues();
const formValues = watch();
const onValidate = () => {
const applId = getApplicationId();
@@ -165,19 +173,32 @@ const BandoApplication = () => {
const validateApplicationCallback = (data) => {
if (data.status === 'SUCCESS') {
if (data.data.status) {
setApplicationStatus(data.data.status); // ask why not 'applicationStatus'?
setApplicationStatus(data.data.status);
}
}
storeSet.main.unsetAsyncRequest();
}
const errValidateApplicationCallback = (data) => {
if (toast.current) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
if (data.status === 'VALIDATION_ERROR') {
if (formMsgs.current) {
formMsgs.current.show([
{
id: '99',
sticky: true, severity: 'error', summary: '',
detail: data.data.join(' '),
closable: true
}
]);
}
} else {
if (toast.current) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
}
storeSet.main.unsetAsyncRequest();
}
@@ -260,7 +281,7 @@ const BandoApplication = () => {
{
id: '99',
sticky: true, severity: 'error', summary: '',
detail: data.data.join(', '),
detail: data.data.join(' '),
closable: true
}
]);
@@ -301,9 +322,6 @@ const BandoApplication = () => {
setActiveStep(data.data.currentStep);
const isCallExpired = isDateTimeInPast(data.data.callEndDate, data.data.callEndTime);
setIsExpired(isCallExpired);
if (data.data.callId === 13) {
setIsExpired(true);
}
const chosenCompanyId = data.data.companyId;
const companies = storeGet.main.companies();
@@ -352,7 +370,7 @@ const BandoApplication = () => {
if (o.dynamicData && !isEmpty(o.dynamicData)) {
formDataInitial[o.id] = pathOr('', o.dynamicData.split('.'), dynamicData);
}
})
});
}
if (data.data.applicationFormResponse.formFields) {
@@ -423,7 +441,9 @@ const BandoApplication = () => {
icon="pi pi-arrow-right"
iconPos="right"/> : null}
<Button
type="button"
disabled={'SUBMIT' === applicationStatus || isExpired}
onClick={onValidate}
label={__('Convalidare', 'gepafin')}
icon="pi pi-check"
iconPos="right"/>
@@ -504,10 +524,48 @@ const BandoApplication = () => {
// TODO hardcoded for now
const signedDocMime = bandoId === 10
? ['.p7m,application/pkcs7-mime,application/x-pkcs7-mime', '.pdf,application/pdf']
: ['.p7m,application/pkcs7-mime,application/x-pkcs7-mime']
: ['.p7m,application/pkcs7-mime,application/x-pkcs7-mime'];
const signedDocValidationString = bandoId === 10
? ['.p7m', '.pdf']
: ['.p7m']
: ['.p7m'];
useEffect(() => {
let updatedFormValues = klona(formValues);
let context = {};
// eslint-disable-next-line array-callback-return
formData.map((o) => {
const variable = head(o.settings.filter(o => o.name === 'variable'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
if (formula && !isEmpty(formula.value)) {
context = getTokens(formula.value)
.filter(v => !['false', 'null', 'true'].includes(v))
.reduce((acc, cur) => {
acc[cur] = isNil(context[cur]) ? 0 : parseCommaDecimal(context[cur]);
return acc;
}, context);
const mathFormula = renderWithDataVars(formula.value, context);
try {
updatedFormValues[o.id] = evaluate(mathFormula);
} catch (e) {
console.log('Error in math formula: "', mathFormula, '"', e.message);
updatedFormValues[o.id] = 0;
}
}
if (variable && !isEmpty(variable.value)) {
context[variable.value[0]] = 'criteria_table' === o.name
? pathOr(0, [o.id, 'total'], updatedFormValues)
: pathOr(0, [o.id], updatedFormValues);
}
});
if (!isEmpty(updatedFormValues) && !equal(updatedFormValues, formValues)) {
reset(updatedFormValues);
}
}, [formValues]);
useEffect(() => {
if ('SUBMIT' === applicationStatus) {
@@ -595,9 +653,13 @@ const BandoApplication = () => {
const text = head(o.settings.filter(o => o.name === 'text'));
const placeholder = head(o.settings.filter(o => o.name === 'placeholder'));
const options = head(o.settings.filter(o => o.name === 'options'));
const tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
let tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
if (!tableColumns) {
tableColumns = head(o.settings.filter(o => o.name === 'criteria_table_columns'));
}
const step = head(o.settings.filter(o => o.name === 'step'));
const mime = head(o.settings.filter(o => o.name === 'mime'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
let mimeValue = '';
if (mime) {
@@ -623,7 +685,10 @@ const BandoApplication = () => {
return acc;
}, {});
//console.log('validations', validations, o.name)
/*if (o.name === 'table') {
console.log('value:', values[o.id] ? values[o.id] : '')
}*/
return ['paragraph'].includes(o.name) && text
? <div key={o.id}>
@@ -633,6 +698,8 @@ const BandoApplication = () => {
</div>
: <FormField
key={o.id}
disabled={isExpired}
readOnly={formula && !isEmpty(formula.value)}
type={o.name}
fieldName={o.id}
label={label ? label.value : ''}
@@ -681,7 +748,7 @@ const BandoApplication = () => {
iconPos="right"/>
</div> : null}
{'DRAFT' !== applicationStatus
{'AWAITING' === applicationStatus
? <div className="appPageSection">
<div className="appForm__field">
<label htmlFor="signedPdfFile">
@@ -704,7 +771,7 @@ const BandoApplication = () => {
</div>
: null}
{'DRAFT' !== applicationStatus
{['AWAITING', 'READY'].includes(applicationStatus)
? <div className="appPageSection">
<Button
type="button"

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useParams } from 'react-router-dom';
import { head, isEmpty, pathOr } from 'ramda';
import { head, isEmpty, isNil, pathOr } from 'ramda';
import { useForm } from 'react-hook-form';
import 'quill/dist/quill.core.css';
@@ -33,6 +33,12 @@ import { Toast } from 'primereact/toast';
import { Messages } from 'primereact/messages';
import ApplicationSteps from '../BandoApplication/ApplicationSteps';
import BlockingOverlay from '../../components/BlockingOverlay';
import { klona } from 'klona';
import getTokens from '../../helpers/getTokens';
import renderWithDataVars from '../../helpers/renderWithDataVars';
import { evaluate } from 'mathjs';
import equal from 'fast-deep-equal';
import parseCommaDecimal from '../../helpers/parseCommaDecimal';
const BandoApplicationPreview = () => {
const { id } = useParams();
@@ -55,7 +61,8 @@ const BandoApplicationPreview = () => {
trigger,
register,
getValues,
reset
reset,
watch
} = useForm({
defaultValues: useMemo(() => {
return formInitialData ? formInitialData : {}
@@ -77,6 +84,7 @@ const BandoApplicationPreview = () => {
}
const activeStepIndex = activeStep - 1;
const values = getValues();
const formValues = watch();
const onValidate = () => {
const applId = getApplicationId();
@@ -252,6 +260,43 @@ const BandoApplicationPreview = () => {
iconPos="right"/>
</div>
useEffect(() => {
let updatedFormValues = klona(formValues);
let context = {};
// eslint-disable-next-line array-callback-return
formData.map((o) => {
const variable = head(o.settings.filter(o => o.name === 'variable'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
if (formula && !isEmpty(formula.value)) {
context = getTokens(formula.value)
.filter(v => !['false', 'null', 'true'].includes(v))
.reduce((acc, cur) => {
acc[cur] = isNil(context[cur]) ? 0 : parseCommaDecimal(context[cur]);
return acc;
}, context);
const mathFormula = renderWithDataVars(formula.value, context);
try {
updatedFormValues[o.id] = evaluate(mathFormula);
} catch (e) {
console.log('Error in math formula: "', mathFormula, '"', e.message);
updatedFormValues[o.id] = 0;
}
}
if (variable && !isEmpty(variable.value)) {
context[variable.value[0]] = 'criteria_table' === o.name
? pathOr(0, [o.id, 'total'], updatedFormValues)
: pathOr(0, [o.id], updatedFormValues);
}
});
if (!isEmpty(updatedFormValues) && !equal(updatedFormValues, formValues)) {
reset(updatedFormValues);
}
}, [formValues]);
useEffect(() => {
if (formInitialData) {
//reset();
@@ -301,9 +346,13 @@ const BandoApplicationPreview = () => {
const text = head(o.settings.filter(o => o.name === 'text'));
const placeholder = head(o.settings.filter(o => o.name === 'placeholder'));
const options = head(o.settings.filter(o => o.name === 'options'));
const tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
let tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
if (!tableColumns) {
tableColumns = head(o.settings.filter(o => o.name === 'criteria_table_columns'));
}
const step = head(o.settings.filter(o => o.name === 'step'));
const mime = head(o.settings.filter(o => o.name === 'mime'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
let mimeValue = '';
if (mime) {
@@ -339,6 +388,7 @@ const BandoApplicationPreview = () => {
</div>
: <FormField
key={o.id}
readOnly={formula && !isEmpty(formula.value)}
type={o.name}
disabled={o.name === 'fileupload' || 'DRAFT' !== applicationStatus}
fieldName={o.id}

View File

@@ -27,6 +27,9 @@ import { storeSet } from '../../../../store';
import set404FromErrorResponse from '../../../../helpers/set404FromErrorResponse';
import getTimeParsedFromString from '../../../../helpers/getTimeParsedFromString';
import formatDateString from '../../../../helpers/formatDateString';
import EvaluationFormsService from '../../../../service/evaluation-forms-service';
const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const BandoEditFormStep1 = forwardRef(function ({ initialData, setInitialData, getFormErrors, status }, ref) {
const navigate = useNavigate();
@@ -50,7 +53,8 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, setInitialData, g
const values = getValues();
const toast = useRef(null);
const onSubmit = () => {};
const onSubmit = () => {
};
const onSaveDraft = () => {
trigger();
@@ -115,7 +119,12 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, setInitialData, g
}
const values = getValues();
if (!values.id && data.data.id) {
navigate(`/bandi/${data.data.id}`);
storeSet.main.setAsyncRequest();
const sampleFormData = {
label: `Evaluation form for call #${data.data.id}`,
content: []
}
EvaluationFormsService.createFormForCall(data.data.id, sampleFormData, createFormCallback, errCreateFormCallback)
} else {
setFormInitialData(data.data);
setInitialData(data.data);
@@ -135,6 +144,18 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, setInitialData, g
storeSet.main.unsetAsyncRequest();
}
const createFormCallback = (resp) => {
if (resp.status === 'SUCCESS') {
navigate(`/bandi/${resp.data.callId}`);
}
storeSet.main.unsetAsyncRequest();
}
const errCreateFormCallback = (resp) => {
set404FromErrorResponse(resp);
storeSet.main.unsetAsyncRequest();
}
const openPreview = () => {
navigate(`/bandi/${values.id}/preview`);
}
@@ -180,7 +201,7 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, setInitialData, g
![
'descriptionShort', 'descriptionLong', 'documentationRequested', 'threshold',
'aimedTo', 'criteria', 'docs', 'checklist', 'faq', 'amount', 'amountMin', 'amountMax',
'email', 'phoneNumber', 'checkList', 'images'
'email', 'phoneNumber', 'checkList', 'images', 'numberOfCheck', 'productId'
].includes(fieldName)
}
@@ -459,6 +480,35 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, setInitialData, g
/>
</div>
<div className="appForm__cols">
<FormField
type="numberinput"
disabled={shouldDisableField('numberOfCheck')}
fieldName="numberOfCheck"
label={__('Quantità dei checklist per creare un soccorso', 'gepafin')}
control={control}
errors={errors}
defaultValue={values['numberOfCheck']}
config={{
required: __('È obbligatorio', 'gepafin'),
}}
/>
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <FormField
type="numberinput"
disabled={shouldDisableField('productId')}
fieldName="productId"
label={__('Prodotto ID dentro Odessa', 'gepafin')}
control={control}
errors={errors}
defaultValue={values['productId']}
config={{
required: __('È obbligatorio', 'gepafin'),
}}
/> : null}
</div>
<FormFieldRepeaterFaq
data={values['faq']}
disabled={shouldDisableField('faq')}

View File

@@ -94,7 +94,11 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, setInitialData, g
delete formData.endDate;
storeSet.main.setAsyncRequest();
BandoService.updateBandoStep2(formData.id, formData, createCallback, errCreateCallback);
if (values.evaluationVersion === 'V1') {
BandoService.updateBandoStep2(formData.id, formData, createCallback, errCreateCallback);
} else if (values.evaluationVersion === 'V2') {
BandoService.updateBandoStep2V2(formData.id, formData, createCallback, errCreateCallback);
}
}
const createCallback = (data) => {
@@ -221,7 +225,8 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, setInitialData, g
return (
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
<UnsavedChangesDetector getValuesFn={getValues}/>
<FormFieldRepeaterCriteria
{values.evaluationVersion === 'V1'
? <FormFieldRepeaterCriteria
data={values}
disabled={shouldDisableField('criteria')}
setDataFn={setValue}
@@ -238,7 +243,7 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, setInitialData, g
.filter(o => isEmpty(o.value) || isEmpty(o.score)).length === 0
|| __('Non lasciare il valore vuoto', 'gepafin')
}
}}/>
}}/> : null}
<FormField
type="fileuploadasync"
@@ -275,7 +280,8 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, setInitialData, g
multiple={false}
/>
<FormFieldRepeater
{values.evaluationVersion === 'V1'
? <FormFieldRepeater
data={values['checkList']}
disabled={shouldDisableField('checkList')}
setDataFn={setValue}
@@ -292,7 +298,7 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, setInitialData, g
.filter(o => isEmpty(o.value)).length === 0 || __('Non lasciare il valore vuoto', 'gepafin')
}
}}
/>
/> : null}
<div className="appPage__spacer"></div>

View File

@@ -0,0 +1,149 @@
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { klona } from 'klona';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
// api
import EvaluationFormsService from '../../../../service/evaluation-forms-service';
import FormsService from '../../../../service/forms-service';
// store
import { storeGet, storeSet } from '../../../../store';
// tools
import set404FromErrorResponse from '../../../../helpers/set404FromErrorResponse';
// components
import BandoEditFormActions from '../BandoEditFormActions';
import { Toast } from 'primereact/toast';
import FormBuilder from '../../../BandoFormsEdit/components/FormBuilder';
//import { elementItems } from '../../../../tempData';
const BandoEditFormStep3 = forwardRef(function () {
const navigate = useNavigate();
const { id } = useParams();
const [formName, setFormName] = useState('');
const [bandoStatus, setBandoStatus] = useState('');
const toast = useRef(null);
const getBandoId = () => {
const parsed = parseInt(id)
return !isNaN(parsed) ? parsed : 0;
}
const onSaveDraft = () => {
const content = storeGet.main.formElements();
const formId = storeGet.main.formId();
const formData = {
label: formName,
content
}
storeSet.main.setAsyncRequest();
EvaluationFormsService.updateForm(formId, formData, updateFormCallback, errUpdateFormCallback)
}
const updateFormCallback = (resp) => {
if (resp.status === 'SUCCESS') {
setBandoStatus(resp.data.callStatus);
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: __('Il bando è stato aggiornato correttamente!', 'gepafin')
});
}
}
storeSet.main.unsetAsyncRequest();
}
const errUpdateFormCallback = (resp) => {
set404FromErrorResponse(resp);
storeSet.main.unsetAsyncRequest();
}
const openPreview = () => {
const bandoId = getBandoId();
navigate(`/bandi/${bandoId}/preview`);
}
const openPreviewEvaluation = () => {
const bandoId = getBandoId();
navigate(`/bandi/${bandoId}/preview-evaluation`);
}
const getElementItemsCallback = (data) => {
if (data.status === 'SUCCESS') {
//storeSet.main.elementItems(elementItems.sort((a, b) => a.sortOrder - b.sortOrder));
storeSet.main.elementItems(data.data.sort((a, b) => a.sortOrder - b.sortOrder));
}
storeSet.main.unsetAsyncRequest();
}
const errGetElementItemsCallbacks = (data) => {
storeSet.main.unsetAsyncRequest();
}
const getFormCallback = (resp) => {
if (resp.status === 'SUCCESS') {
storeSet.main.formId(resp.data.id);
storeSet.main.formLabel(resp.data.label);
setFormName(resp.data.label);
setBandoStatus(resp.data.callStatus);
const elements = klona(resp.data.content);
storeSet.main.formElements(elements);
}
storeSet.main.unsetAsyncRequest();
}
const errGetFormCallback = (resp) => {
set404FromErrorResponse(resp);
storeSet.main.unsetAsyncRequest();
}
useEffect(() => {
storeSet.main.setAsyncRequest();
EvaluationFormsService.getFormForCall(id, getFormCallback, errGetFormCallback)
}, [id]);
useEffect(() => {
storeSet.main.setAsyncRequest();
FormsService.getElementItems(getElementItemsCallback, errGetElementItemsCallbacks);
return () => {
storeSet.main.formId(0);
storeSet.main.formElements([]);
storeSet.main.activeElement('');
storeSet.main.selectedElement('');
}
}, []);
return (
<div className="appForm">
<div className="appPageSection">
<DndProvider backend={HTML5Backend}>
<FormBuilder callStatus={bandoStatus} context="call"/>
</DndProvider>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection__hr">
<span>{__('Azioni', 'gepafin')}</span>
</div>
<Toast ref={toast} />
<BandoEditFormActions
id={id}
status={bandoStatus}
submitFn={onSaveDraft}
openPreview={openPreview}
openPreviewEvaluation={openPreviewEvaluation}/>
</div>
)
})
export default BandoEditFormStep3;

View File

@@ -23,6 +23,7 @@ import { Messages } from 'primereact/messages';
import FormsService from '../../service/forms-service';
import BlockingOverlay from '../../components/BlockingOverlay';
import { Toast } from 'primereact/toast';
import BandoEditFormStep3 from './components/BandoEditFormStep3';
const BandoEdit = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
@@ -35,28 +36,45 @@ const BandoEdit = () => {
const bandoMsgs = useRef(null);
const toast = useRef(null);
const stepItems = [
{
label: __('Testi', 'gepafin'),
command: () => {
if (activeStep === 0) {
return false
const stepItems = (evalProcessVer) => {
let steps = [
{
label: __('Testi', 'gepafin'),
command: () => {
if (activeStep === 0) {
return false
}
bandoMsgs.current.clear();
goToStep(0);
}
bandoMsgs.current.clear();
goToStep(0);
}
},
{
label: __('Gestione', 'gepafin'),
command: () => {
if (activeStep === 1) {
return false
},
{
label: __('Gestione', 'gepafin'),
command: () => {
if (activeStep === 1) {
return false
}
bandoMsgs.current.clear();
goToStep(1);
}
bandoMsgs.current.clear();
goToStep(1);
}
];
if (evalProcessVer === 'V2') {
steps.push({
label: __('Valutazione', 'gepafin'),
command: () => {
if (activeStep === 2) {
return false
}
bandoMsgs.current.clear();
goToStep(2);
}
})
}
];
return steps;
}
const goToStep = (step) => {
setActiveStep(step);
@@ -238,7 +256,8 @@ const BandoEdit = () => {
if (bandoId === 0) {
setData({
status: null
status: null,
evaluationVersion: 'V2'
});
storeSet.main.unsetAsyncRequest();
@@ -274,7 +293,7 @@ const BandoEdit = () => {
{!isEmpty(data)
? <Steps
model={stepItems}
model={stepItems(data.evaluationVersion)}
activeIndex={activeStep}
readOnly={isNil(data.status)}/>
: null}
@@ -293,6 +312,9 @@ const BandoEdit = () => {
{activeStep === 1
? <BandoEditFormStep2 initialData={data} setInitialData={setData} ref={formRef} status={data.status}/>
: null}
{activeStep === 2 && data.evaluationVersion === 'V2'
? <BandoEditFormStep3/>
: null}
<div className="appPageSection">
<h2>{__('Crea o modifica il Form compilabile dal Beneficiario', 'gepafin')}</h2>

View File

@@ -35,7 +35,6 @@ const BandoFlowEdit = () => {
const [forms, setForms] = useState([]);
const [formOptions, setFormOptions] = useState([]);
const [chosenMainFieldOptions, setChosenMainFieldOptions] = useState([]);
//const [chosenMainField, setChosenMainField] = useState('');
const [mainFieldSuboptions, setMainFieldSubOptions] = useState([]);
const [bandoStatus, setBandoStatus] = useState('');
const [isFlowAllowed, setIsFlowAllowed] = useState(true);
@@ -162,18 +161,18 @@ const BandoFlowEdit = () => {
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));
/*if (forms.length > 2) {
console.log('disable BTN:', nonEmptyFlowItems.length !== forms.length - 2, isEmpty(flowStructure.flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(flowStructure.initialForm), isEmpty(flowStructure.finalForm));
} else {
console.log('disable BTN:', nonEmptyFlowItems.length !== 1, isEmpty(flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(initialForm), isEmpty(finalForm));
console.log('disable BTN (2 forms):', isEmpty(flowStructure.flowEdges), 'PUBLISH' === bandoStatus,
isEmpty(flowStructure.initialForm), isEmpty(flowStructure.finalForm));
}*/
return forms.length > 2
? 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.flowEdges) || 'PUBLISH' === bandoStatus
|| isEmpty(flowStructure.initialForm) || isEmpty(flowStructure.finalForm);
}, [flowStructure, forms]);

View File

@@ -1,11 +1,16 @@
import React, { useRef } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useDrag, useDrop } from 'react-dnd'
import { ItemTypes } from '../ItemTypes';
import { __ } from '@wordpress/i18n';
import { head, isEmpty } from 'ramda';
import { klona } from 'klona';
// store
import { storeSet, useStore } from '../../../../store';
// tools
import uniqid from '../../../../helpers/uniqid';
// components
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
@@ -13,7 +18,16 @@ import BuilderElementProperLabel from '../BuilderElementProperLabel';
const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
const draggingElementId = useStore().main.draggingElementId();
const selectedElement = useStore().main.selectedElement();
const ref = useRef(null);
const elements = useStore().main.formElements();
const element = head(elements.filter(o => o.id === id));
const [isVariable, setIsVariable] = useState('secondary');
const [isFormula, setIsFormula] = useState('secondary');
const [variableName, setVariableName] = useState('secondary');
const [formulaName, setFormulaName] = useState('secondary');
const [isRequestedAmount, setIsRequestedAmount] = useState(false);
const [isDelegation, setIsDelegation] = useState(false);
const [{ handlerId }, drop] = useDrop({
accept: ItemTypes.FIELD,
@@ -86,30 +100,92 @@ const BuilderElement = ({ id, name, label, index, bandoStatus }) => {
storeSet.main.moveElement(dragIndex, hoverIndex, item);
}
const openSettings = (id) => {
const openSettings = () => {
storeSet.main.activeElement(id);
}
const remove = (id) => {
const selectElement = () => {
storeSet.main.selectedElement(id);
}
const duplicateElement = useCallback(() => {
const duplicatedElement = head(elements.filter(o => o.id === id));
if (duplicatedElement) {
const copyElement = klona(duplicatedElement);
copyElement.settings = copyElement.settings.map((o) => {
if (o.name === 'label') {
o.value = `Copy - ${o.value}`
}
return o;
})
copyElement.id = uniqid();
const originalIndex = elements.map(o => o.id).indexOf(id);
const newElements = [...elements].toSpliced(originalIndex + 1, 0, copyElement);
storeSet.main.formElements(newElements);
}
}, [elements]);
const remove = () => {
storeSet.main.removeElement(id);
}
const opacity = isDragging ? 0 : 1;
drag(drop(ref));
useEffect(() => {
const variable = head(element.settings.filter(o => o.name === 'variable'));
const formula = head(element.settings.filter(o => o.name === 'formula'));
const isRequestedAmount = head(element.settings.filter(o => o.name === 'isRequestedAmount'));
const isDelegation = head(element.settings.filter(o => o.name === 'isDelegation'));
if (variable && !isEmpty(variable.value)) {
setIsVariable('warning');
setVariableName(variable.value)
}
if (formula && !isEmpty(formula.value)) {
setIsFormula('warning');
setFormulaName(formula.value)
}
if (isRequestedAmount && !isEmpty(isRequestedAmount.value) && isRequestedAmount.value) {
setIsRequestedAmount('tertiary');
}
if (isDelegation && !isEmpty(isDelegation.value) && isDelegation.value) {
setIsDelegation('tertiary');
}
}, [elements]);
return (
draggingElementId === id
? <div ref={ref} className="formBuilder__elementNew">
{__('lascia qui', 'gepafin')}
</div>
: <div ref={ref} className="formBuilder__element" style={{ opacity }} data-handler-id={handlerId}>
: <div ref={ref}
className={`formBuilder__element${selectedElement === id ? ' selected' : ''}`}
style={{ opacity }}
onClick={selectElement}
data-handler-id={handlerId}>
<div className="meta">
<Tag value={label} severity="info"/>
<div className="tagHeader">
<Tag value={label} severity="info"/>
{['numberinput', 'criteria_table'].includes(name)
? <Tag value="var" severity={isVariable} title={variableName}/> : null}
{name === 'numberinput'
? <Tag value="f(x)" severity={isFormula} title={formulaName}/> : null}
{isRequestedAmount
? <Tag value="importo" severity={isRequestedAmount}/> : null}
{isDelegation
? <Tag value="delega" severity={isDelegation}/> : null}
</div>
<BuilderElementProperLabel id={id} defaultLabel={label}/>
</div>
<div className="actions">
<Button icon="pi pi-cog" onClick={() => openSettings(id)} outlined severity="info"/>
<Button icon="pi pi-trash" disabled={bandoStatus === 'PUBLISH'} onClick={() => remove(id)} outlined severity="danger"/>
<Button icon="pi pi-clone" onClick={duplicateElement} outlined severity="success"/>
<Button icon="pi pi-cog" onClick={openSettings} outlined severity="info"/>
<Button icon="pi pi-trash" disabled={bandoStatus === 'PUBLISH'} onClick={remove} outlined severity="danger"/>
</div>
</div>
)

View File

@@ -1,18 +1,28 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { is } from 'ramda';
import { head, is, isEmpty, isNil, uniq } from 'ramda';
// store
import { storeGet } from '../../../../../../store';
// tools
import renderHtmlContent from '../../../../../../helpers/renderHtmlContent';
// components
import ElementSettingRepeater from '../ElementSettingRepeater';
import { InputText } from 'primereact/inputtext';
import { MultiSelect } from 'primereact/multiselect';
import { Editor } from 'primereact/editor';
import { mimeTypes } from '../../../../../../configData';
import ElementSettingTableColumns from '../ElementSettingTableColumns';
import { InputSwitch } from 'primereact/inputswitch';
import ElementSettingChips from '../ElementSettingChips';
import ElementSettingCriteriaTableColumns from '../ElementSettingCriteriaTableColumns';
import { mimeTypes } from '../../../../../../configData';
const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
const [existingVars, setExistingVars] = useState([]);
const settingLabels = {
label: __('Label', 'gepafin'),
@@ -24,6 +34,14 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
mime: __('Tipo di file', 'gepafin'),
text: __('Testo formattato', 'gepafin'),
table_columns: '',
criteria_table_columns: '',
variable: __('Variabile (lettere, cifre e "_"; il primo carattere deve essere una lettera!)', 'gepafin'),
formula: __('Formula di calcolo automatico', 'gepafin'),
isChecklistItem: __('Fa parte di "checklist"?', 'gepafin'),
}
const settingDescription = {
formula: __('Crea una formula usando variabili dichiarate in precedenza. Utilizza questi operatori matematici: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>. Esempio: <code>{entrate}+{assicurazione}</code>.', 'gepafin')
}
const renderHeader = () => {
@@ -60,7 +78,7 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
options={mimeTypes}
optionLabel="name"
display="chip"
placeholder={__('Scegli', 'gepafin')} />
placeholder={__('Scegli', 'gepafin')}/>
} else if (setting.name === 'text') {
return <Editor
value={setting.value}
@@ -81,10 +99,21 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
name={setting.name}
bandoStatus={bandoStatus}
setDataFn={updateDataFn}/>
} else if (['isRequestedAmount', 'isDelegation'].includes(setting.name)) {
} else if (setting.name === 'criteria_table_columns') {
return <ElementSettingCriteriaTableColumns
value={is(Object, setting.value) ? setting.value : {}}
name={setting.name}
bandoStatus={bandoStatus}
setDataFn={updateDataFn}/>
} else if (['isRequestedAmount', 'isDelegation', 'isChecklistItem'].includes(setting.name)) {
return <InputSwitch
checked={setting.value}
onChange={(e) => changeFn(e.value, setting.name)}/>
} else if (['variable'].includes(setting.name)) {
return <ElementSettingChips
restrictedValues={[]}
changeFn={(value) => changeFn(value, setting.name)}
value={setting.value}/>
} else {
return <InputText id={setting.name} aria-describedby={`${setting.name}-help`}
value={setting.value}
@@ -92,9 +121,34 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
}
}
useEffect(() => {
const elements = storeGet.main.formElements();
const activeElement = storeGet.main.activeElement();
const vars = elements
.filter(o => o.id !== activeElement)
// eslint-disable-next-line
.map((o) => {
const variableSetting = head(o.settings.filter(s => s.name === 'variable'));
if (variableSetting) {
return variableSetting.value[0];
}
})
.filter(v => !isNil(v));
setExistingVars(uniq(vars));
}, []);
return <div className="formElementSettings__field" key={setting.name}>
<label htmlFor={setting.name}>{settingLabels[setting.name]}</label>
{getProperField(setting)}
{setting.name === 'formula' && !isEmpty(existingVars)
? <div className="formElementSettings__fieldVarsList">
<p>Existing variables: {existingVars.map(v => <code key={v}>{`{${v}}`}</code>)}</p>
</div> : null}
{settingDescription[setting.name]
? <div className="formElementSettings__fieldDescription">
<p>{renderHtmlContent(settingDescription[setting.name])}</p>
</div> : null}
</div>
}

View File

@@ -0,0 +1,44 @@
import React, { useState } from 'react';
// components
import { Chips } from 'primereact/chips';
import { isEmpty } from 'ramda';
const ElementSettingChips = ({ restrictedValues = [], changeFn, value = [] }) => {
const [lastTyped, setLastTyped] = useState([])
const isValidValue = (newVal) => {
const validationRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
return validationRegex.test(newVal);
};
const handleAdd = (e) => {
const newValue = isEmpty(e.value) ? '' : e.value[e.value.length - 1];
if (restrictedValues.includes(newValue)) {
changeFn([]);
return;
}
if (!isValidValue(newValue)) {
changeFn(lastTyped);
return;
}
setLastTyped(e.value)
changeFn(e.value);
};
return (
<div>
<Chips
value={value}
addOnBlur={true}
onChange={handleAdd}
max={1}
/>
</div>
);
}
export default ElementSettingChips;

View File

@@ -0,0 +1,329 @@
import React, { useCallback, useEffect, useState } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { wrap } from 'object-path-immutable';
import { isEmpty, last, pathOr } from 'ramda';
// components
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { InputSwitch } from 'primereact/inputswitch';
import { Dropdown } from 'primereact/dropdown';
import { Accordion, AccordionTab } from 'primereact/accordion';
// tools
import uniqid from '../../../../../../helpers/uniqid';
import removeKey from '../../../../../../helpers/removeKey';
const ElementSettingCriteriaTableColumns = ({
value,
name,
setDataFn,
bandoStatus
}) => {
const [stateFieldData, setStateFieldData] = useState([]);
const [rowsData, setRowsData] = useState([]);
const removeItem = (index) => {
let newData = stateFieldData
.toSpliced(index, 1);
newData = newData.map((o, i) => {
return i === newData.length - 1
? {...o, fieldtype: 'numeric', predefined: false, enableFormula: true}
: {...o, fieldtype: 'text', predefined: true, enableFormula: false}
});
setStateFieldData(newData);
const newRowsData = removeKey(rowsData, last(newData.map(o => o.name)));
setRowsData(newRowsData);
}
const addNewItem = () => {
setStateFieldData([
...stateFieldData.map(o => ({...o, fieldtype: 'text', predefined: true, enableFormula: false})),
{ name: uniqid('o'), label: '', fieldtype: 'numeric', predefined: false, enableFormula: true }
]);
}
const addNewRow = () => {
const obj = stateFieldData
.filter(o => o.predefined)
.reduce((acc, cur) => {
acc[cur.name] = ''
return acc;
}, {});
setRowsData([...rowsData, obj]);
}
const removeRow = (index) => {
const newRowsData = wrap(rowsData).del([index]).value();
setRowsData(newRowsData);
}
const onInputChange = (e, index) => {
const { value } = e.target;
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.label = value;
}
return o;
})
setStateFieldData(newData);
}
const onLastRowInputChange = (e, index) => {
const { value } = e.target;
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.lastRowText = value;
}
return o;
})
setStateFieldData(newData);
}
const onTypeChange = (value, index) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.fieldtype = value;
}
return o;
})
setStateFieldData(newData);
}
const onLastRowFormulaChange = (value, index) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.lastRowFormula = value;
}
return o;
})
setStateFieldData(newData);
}
const onSubInputChange = (e, name, index) => {
const { value } = e.target;
const newRowsData = wrap(rowsData).set([index, name], value).value();
setRowsData(newRowsData);
}
const setChecked = (value, index) => {
let name = '';
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.predefined = value;
name = o.name;
}
return o;
});
let newRowsData = [];
if (value === false) {
newRowsData = rowsData.map(o => wrap(o).set([name], '').value());
} else {
newRowsData = rowsData.map(o => wrap(o).set([name], '').value());
}
setRowsData(newRowsData);
setStateFieldData(newData);
}
const setColFormulaChecked = (index) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
const newVal = o.enableFormula !== true;
o.enableFormula = newVal;
if (newVal) {
o.lastRowFormula = 'sum';
o.fieldtype = 'numeric';
delete o.lastRowText;
} else {
delete o.lastRowFormula;
delete o.fieldtype;
o.lastRowText = '';
}
}
return o;
});
setStateFieldData(newData);
}
const handleClearLastRowData = useCallback(() => {
const newData = stateFieldData.map((o) => {
delete o.lastRowFormula;
o.lastRowText = '';
delete o.enableFormula;
return o;
});
setStateFieldData(newData);
}, [stateFieldData]);
const properFields = (item, i) => {
return <>
<div>
<InputText
value={item.label}
placeholder={sprintf(__('Colonna %d', 'gepafin'), i + 1)}
onInput={(e) => onInputChange(e, i)}/>
</div>
<div>
<Dropdown
disabled={true}
value={item.fieldtype ? item.fieldtype : 'text'}
onChange={(e) => onTypeChange(e.value, i)}
options={[
{ value: 'text', label: __('Testo', 'gepafin') },
{ value: 'numeric', label: __('Numerico', 'gepafin') }
]}/>
</div>
<div>
<button
disabled={true}
className="formElementSettings__repeaterItemIconBtn"
onClick={() => setColFormulaChecked(i)}
data-active={item.enableFormula ? item.enableFormula : false}
type="button">
<i className="pi pi-calculator"></i>
</button>
</div>
<div>
<InputSwitch
checked={item.predefined}
disabled={true}
onChange={(e) => setChecked(e.value, i)}/>
</div>
<div>
<Button icon="pi pi-times"
disabled={bandoStatus === 'PUBLISH'}
className="p-button-danger"
onClick={() => removeItem(i)}/>
</div>
</>
}
const properSubField = (item, i, name) => {
return <>
<div>
<InputText
value={item[name]}
onInput={(e) => onSubInputChange(e, name, i)}/>
</div>
<div>
<Button icon="pi pi-times"
className="p-button-danger"
onClick={() => removeRow(i)}/>
</div>
</>
}
const properFieldsLastRow = useCallback((item, i) => {
return <>
<div>
<span>{sprintf(__('Colonna %d'), i + 1)}</span>
</div>
{item.enableFormula
? <div>
<Dropdown
disabled={bandoStatus === 'PUBLISH'}
value={item.lastRowFormula}
onChange={(e) => onLastRowFormulaChange(e.value, i)}
options={[
{ value: 'sum', label: __('Somma automatica', 'gepafin') }
]}/>
</div>
: <div>
<InputText
value={item.lastRowText ? item.lastRowText : ''}
onInput={(e) => onLastRowInputChange(e, i)}/>
</div>}
</>
}, [stateFieldData]);
const lastRow = <div className="formElementSettings__repeater">
<div className="formElementSettings__lastRowHeader">
<div className="formElementSettings__lastRowHeaderTitle">
{__('Definisci ultima righa', 'gepafin')}
</div>
<Button type="button"
disabled={bandoStatus === 'PUBLISH'}
outlined
label={__('Pulisci', 'gepafin')}
iconPos="right"
icon="pi pi-refresh"
onClick={handleClearLastRowData}/>
</div>
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__lastRowItem">
{properFieldsLastRow(o, i)}
</div>)}
</div>;
useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], value);
setStateFieldData(stateFieldData);
const rowsData = pathOr([], ['rowsData'], value);
setRowsData(rowsData);
}, []);
useEffect(() => {
setDataFn(name, {
stateFieldData,
rowsData
});
}, [stateFieldData, rowsData]);
return (
<>
<div className="formElementSettings__repeater">
{stateFieldData.length > 0
? <div className="formElementSettings__repeaterItem tableRow">
<div>{__('Colonne', 'gepafin')}</div>
<div>{__('Tipo', 'gepafin')}</div>
<div>{__('Calcola', 'gepafin')}</div>
<div>{__('Predefinito?', 'gepafin')}</div>
<div></div>
</div> : null}
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem tableRow">
{properFields(o, i)}
</div>)}
<Button
type="button"
disabled={bandoStatus === 'PUBLISH'}
outlined
label={__('Aggiungi colonna', 'gepafin')}
onClick={addNewItem}/>
</div>
{stateFieldData
.filter(o => o.predefined).length > 0
? <div className="formElementSettings__subRepeaterWrapper">
<Accordion activeIndex={0}>
{stateFieldData
//.filter(o => o.predefined)
.map((o, i) =>
o.predefined
? <AccordionTab
key={i}
header={sprintf(__('Righe per colonna: %s'), !isEmpty(o.label) ? o.label : i + 1)}>
<div className="formElementSettings__subRepeaterWrapper">
{rowsData.map((c, k) => {
return <div key={k} className="formElementSettings__subRepeaterItem">
{properSubField(c, k, o.name)}
</div>
})}
<Button type="button"
outlined
disabled={bandoStatus === 'PUBLISH'}
label={__('Aggiungi una riga', 'gepafin')}
onClick={addNewRow}/>
</div>
</AccordionTab> : null)}
</Accordion>
</div>
: null}
{lastRow}
</>
)
}
export default ElementSettingCriteriaTableColumns;

View File

@@ -1,159 +0,0 @@
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { wrap } from 'object-path-immutable';
import { pathOr } from 'ramda';
// components
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { InputSwitch } from 'primereact/inputswitch';
// tools
import uniqid from '../../../../../../helpers/uniqid';
const ElementSettingTableColumns = ({
value,
name,
setDataFn,
bandoStatus
}) => {
const [stateFieldData, setStateFieldData] = useState([]);
const [rowsData, setRowsData] = useState([]);
const removeItem = (index) => {
const newData = stateFieldData.toSpliced(index, 1);
setStateFieldData(newData);
}
const addNewItem = () => {
setStateFieldData([...stateFieldData, { name: uniqid('o'), label: '', predefined: false }]);
}
const addNewRow = () => {
const obj = stateFieldData
.filter(o => o.predefined)
.reduce((acc, cur) => {
acc[cur.name] = ''
return acc;
}, {});
setRowsData([...rowsData, obj]);
}
const removeRow = (index) => {
const newRowsData = wrap(rowsData).del([index]).value();
setRowsData(newRowsData);
}
const onInputChange = (e, index) => {
const { value } = e.target;
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.label = value;
}
return o;
})
setStateFieldData(newData);
}
const onSubInputChange = (e, name, index) => {
const { value } = e.target;
const newRowsData = wrap(rowsData).set([index, name], value).value();
setRowsData(newRowsData);
}
const setChecked = (value, index) => {
let name = '';
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.predefined = value;
name = o.name;
}
return o;
});
let newRowsData = [];
if (value === false) {
newRowsData = rowsData.map(o => wrap(o).set([name], '').value());
} else {
newRowsData = rowsData.map(o => wrap(o).set([name], '').value());
}
setRowsData(newRowsData);
setStateFieldData(newData);
}
const properField = (item, i) => {
return <>
<InputText value={item.label} onInput={(e) => onInputChange(e, i)}/>
<div className="flex-1">
<span>{__('Predefinito?', 'gepafin')}</span>
<InputSwitch
checked={item.predefined}
disabled={bandoStatus === 'PUBLISH'}
onChange={(e) => setChecked(e.value, i)}/>
</div>
</>
}
const properSubField = (item, i, name) => {
return <InputText value={item[name]} onInput={(e) => onSubInputChange(e, name, i)}/>
}
useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], value);
setStateFieldData(stateFieldData);
const rowsData = pathOr([], ['rowsData'], value);
setRowsData(rowsData);
}, []);
useEffect(() => {
setDataFn(name, {
stateFieldData,
rowsData
});
}, [stateFieldData, rowsData]);
stateFieldData.filter(o => o.predefined)
return (
<>
<div className="formElementSettings__repeater">
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
<div className="p-inputgroup flex-1">
{properField(o, i)}
<Button icon="pi pi-times" disabled={bandoStatus === 'PUBLISH'} className="p-button-danger" onClick={() => removeItem(i)}/>
</div>
</div>)}
<Button type="button" disabled={bandoStatus === 'PUBLISH'} outlined label={__('Aggiungi', 'gepafin')} onClick={addNewItem}/>
</div>
{stateFieldData
.filter(o => o.predefined)
.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
<div className="formElementSettings__repeater formElementSettings__subRepeater">
<label>{__('Righe per colonna:', 'gepafin')} <strong>{o.label}</strong></label>
<div className="formElementSettings__repeater">
{rowsData.map((c, k) => {
return <div key={k} className="formElementSettings__repeaterItem">
<div className="p-inputgroup flex-1">
{properSubField(c, k, o.name)}
<Button icon="pi pi-times"
disabled={bandoStatus === 'PUBLISH'}
className="p-button-danger"
onClick={() => removeRow(k)}/>
</div>
</div>
})}
<Button type="button"
outlined
disabled={bandoStatus === 'PUBLISH'}
label={__('Aggiungi una riga', 'gepafin')}
onClick={addNewRow}/>
</div>
</div>
</div>)}
</>
)
}
export default ElementSettingTableColumns;

View File

@@ -1,17 +1,18 @@
import React, { useCallback, useEffect, useState } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { wrap } from 'object-path-immutable';
import { isEmpty, pathOr } from 'ramda';
import { isEmpty, last, pathOr } from 'ramda';
// components
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { InputSwitch } from 'primereact/inputswitch';
import { Dropdown } from 'primereact/dropdown';
import { Accordion, AccordionTab } from 'primereact/accordion';
// tools
import uniqid from '../../../../../../helpers/uniqid';
import { Dropdown } from 'primereact/dropdown';
import { Accordion, AccordionTab } from 'primereact/accordion';
import removeKey from '../../../../../../helpers/removeKey';
const ElementSettingTableColumns = ({
value,
@@ -25,6 +26,8 @@ const ElementSettingTableColumns = ({
const removeItem = (index) => {
const newData = stateFieldData.toSpliced(index, 1);
setStateFieldData(newData);
const newRowsData = removeKey(rowsData, last(newData.map(o => o.name)));
setRowsData(newRowsData);
}
const addNewItem = () => {
@@ -158,7 +161,7 @@ const ElementSettingTableColumns = ({
</div>
<div>
<Dropdown
disabled={item.enableFormula}
disabled={item.enableFormula || bandoStatus === 'PUBLISH'}
value={item.fieldtype ? item.fieldtype : 'text'}
onChange={(e) => onTypeChange(e.value, i)}
options={[
@@ -168,6 +171,7 @@ const ElementSettingTableColumns = ({
</div>
<div>
<button
disabled={bandoStatus === 'PUBLISH'}
className="formElementSettings__repeaterItemIconBtn"
onClick={() => setColFormulaChecked(i)}
data-active={item.enableFormula ? item.enableFormula : false}
@@ -193,11 +197,12 @@ const ElementSettingTableColumns = ({
const properSubField = (item, i, name) => {
return <>
<div>
<InputText value={item[name]} onInput={(e) => onSubInputChange(e, name, i)}/>
<InputText
value={item[name]}
onInput={(e) => onSubInputChange(e, name, i)}/>
</div>
<div>
<Button icon="pi pi-times"
disabled={bandoStatus === 'PUBLISH'}
className="p-button-danger"
onClick={() => removeRow(i)}/>
</div>
@@ -212,6 +217,7 @@ const ElementSettingTableColumns = ({
{item.enableFormula
? <div>
<Dropdown
disabled={bandoStatus === 'PUBLISH'}
value={item.lastRowFormula}
onChange={(e) => onLastRowFormulaChange(e.value, i)}
options={[
@@ -232,6 +238,7 @@ const ElementSettingTableColumns = ({
{__('Definisci ultima righa', 'gepafin')}
</div>
<Button type="button"
disabled={bandoStatus === 'PUBLISH'}
outlined
label={__('Pulisci', 'gepafin')}
iconPos="right"
@@ -261,18 +268,22 @@ const ElementSettingTableColumns = ({
<>
<div className="formElementSettings__repeater">
{stateFieldData.length > 0
? <div className="formElementSettings__repeaterItem">
? <div className="formElementSettings__repeaterItem tableRow">
<div>{__('Colonne', 'gepafin')}</div>
<div>{__('Tipo', 'gepafin')}</div>
<div>{__('Calcola', 'gepafin')}</div>
<div>{__('Predefinito?', 'gepafin')}</div>
<div></div>
</div> : null}
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem tableRow">
{properFields(o, i)}
</div>)}
<Button type="button" disabled={bandoStatus === 'PUBLISH'} outlined
label={__('Aggiungi colonna', 'gepafin')} onClick={addNewItem}/>
<Button
type="button"
disabled={bandoStatus === 'PUBLISH'}
outlined
label={__('Aggiungi colonna', 'gepafin')}
onClick={addNewItem}/>
</div>
{stateFieldData
.filter(o => o.predefined).length > 0

View File

@@ -19,7 +19,7 @@ import { MultiSelect } from 'primereact/multiselect';
import { dynamicDataOptions } from '../../../../configData';
const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
const BuilderElementSettings = ({ closeSettingsFn, callStatus, context }) => {
const elements = useStore().main.formElements();
const activeElement = useStore().main.activeElement();
const criteriaOptions = useStore().main.bandoCriteria();
@@ -110,22 +110,6 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
return dynamicDataOptions[type] ?? [];
}
/*const searchDynamicTags = (event) => {
const type = activeElementData.name;
const available = dynamicDataOptions[type];
let filtered;
if (!event.query.trim().length) {
filtered = [...available];
} else {
filtered = available.filter((tag) => {
return tag.label.toLowerCase().startsWith(event.query.toLowerCase());
});
}
setFilteredDynamicDataOptions(filtered);
}*/
useEffect(() => {
const chosen = head(elements.filter(o => o.id === activeElement));
const elementItems = storeGet.main.elementItems();
@@ -139,6 +123,10 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
});
}
settings = settings.filter(o => context === 'call'
? !['isRequestedAmount', 'isDelegation', ''].includes(o.name)
: !['isChecklistItem'].includes(o.name));
if (chosen) {
setActiveElementData(klona(chosen));
setSettings(settings);
@@ -160,14 +148,16 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
<TabView className="formElementSettings__tabs">
<TabPanel header={__('Presentation', 'gepafin')}>
{settings
? settings.map((o) => <ElementSetting
key={o.name}
setting={o}
bandoStatus={bandoStatus}
changeFn={onChange}
updateDataFn={onUpdateOptions}/>)
? settings
.filter(o => !['variable', 'formula'].includes(o.name))
.map((o) => <ElementSetting
key={o.name}
setting={o}
callStatus={callStatus}
changeFn={onChange}
updateDataFn={onUpdateOptions}/>)
: null}
{!isNil(dynamicDataOptions[activeElementData.name])
{!isNil(dynamicDataOptions[activeElementData.name]) && context === 'application'
? <div className="formElementSettings__field">
<label htmlFor="dynamicData">{__('Dati dinamici', 'gepafin')}</label>
<Dropdown
@@ -234,7 +224,8 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
</div> : null}
</div>) : null}
</TabPanel> : null}
<TabPanel header={__('Criteri', 'gepafin')}>
{context === 'application'
? <TabPanel header={__('Criteri', 'gepafin')}>
<div className="formElementSettings__field">
<label htmlFor="criteria">{__('Criteri di valutazione', 'gepafin')}</label>
<MultiSelect
@@ -247,10 +238,28 @@ const BuilderElementSettings = ({ closeSettingsFn, bandoStatus }) => {
display="chip"
placeholder={__('Scegli', 'gepafin')}/>
</div>
</TabPanel>
</TabPanel> : null}
{settings
&& settings
.filter(o => ['variable', 'formula'].includes(o.name)).length > 0
? <TabPanel header={__('Calcolo', 'gepafin')}>
{settings
? settings
.filter(o => ['variable', 'formula'].includes(o.name))
.map((o) => <ElementSetting
key={o.name}
setting={o}
callStatus={callStatus}
changeFn={onChange}
updateDataFn={onUpdateOptions}/>)
: null}
</TabPanel> : null}
</TabView>
<Button label={__('Salva', 'gepafin')} onClick={saveSettings}/>
<div style={{display: 'flex', gap: '0.5rem'}}>
<Button label={__('Annulla', 'gepafin')} onClick={closeSettingsFn} severity="danger"/>
<Button label={__('Salva', 'gepafin')} onClick={saveSettings}/>
</div>
</div>
: null
)

View File

@@ -13,7 +13,7 @@ import BuilderElementSettings from '../BuilderElementSettings';
import BuilderDropzone from '../BuilderDropzone';
import BlockingOverlay from '../../../../components/BlockingOverlay';
const FormBuilder = ({ bandoStatus }) => {
const FormBuilder = ({ callStatus, context }) => {
const elements = useStore().main.formElements();
const elementItems = useStore().main.elementItems();
const activeElement = useStore().main.activeElement();
@@ -27,10 +27,10 @@ const FormBuilder = ({ bandoStatus }) => {
id={field.id}
label={field.label}
name={field.name}
bandoStatus={bandoStatus}
callStatus={callStatus}
/>
)
}, [bandoStatus]);
}, [callStatus]);
const renderItem = useCallback((item) => {
return (
@@ -56,9 +56,11 @@ const FormBuilder = ({ bandoStatus }) => {
return (
<>
<Sidebar visible={!isEmpty(activeElement)} onHide={closeSettings} className="formBuilder__elementSettings">
<Sidebar visible={!isEmpty(activeElement)} onHide={closeSettings} dismissable={false} className="formBuilder__elementSettings">
<h2>{__('Impostazioni del campo modulo', 'gepafin')}</h2>
{!isEmpty(activeElement) ? <BuilderElementSettings closeSettingsFn={closeSettings} bandoStatus={bandoStatus}/> : null}
{!isEmpty(activeElement)
? <BuilderElementSettings closeSettingsFn={closeSettings} callStatus={callStatus} context={context}/>
: null}
</Sidebar>
<div className="formBuilder">
<div className="formBuilder__main">

View File

@@ -110,7 +110,6 @@ const BandoFormsEdit = () => {
const formCreateCallback = (data, shouldRedirect) => {
if (data.status === 'SUCCESS') {
storeSet.main.unsetAsyncRequest();
const bandoId = getBandoId();
if (shouldRedirect) {
navigate(`/bandi/${bandoId}/forms/${data.data.id}/preview`);
@@ -127,6 +126,7 @@ const BandoFormsEdit = () => {
});
}
}
storeSet.main.unsetAsyncRequest();
}
const errFormCreateCallback = (data) => {
@@ -216,12 +216,14 @@ const BandoFormsEdit = () => {
const getElementItemsCallback = (data) => {
if (data.status === 'SUCCESS') {
//storeSet.main.elementItems(elementItems.sort((a, b) => a.sortOrder - b.sortOrder));
storeSet.main.elementItems(data.data.sort((a, b) => a.sortOrder - b.sortOrder));
storeSet.main.elementItems(data.data
.filter(o => o.id !== 22)
.sort((a, b) => a.sortOrder - b.sortOrder));
}
storeSet.main.unsetAsyncRequest();
}
const errGetElementItemsCallbacks = (data) => {
const errGetElementItemsCallback = () => {
storeSet.main.unsetAsyncRequest();
}
@@ -261,7 +263,7 @@ const BandoFormsEdit = () => {
const bandoFormId = !isNaN(parsedFormId) ? parsedFormId : 0;
storeSet.main.setAsyncRequest();
FormsService.getElementItems(getElementItemsCallback, errGetElementItemsCallbacks);
FormsService.getElementItems(getElementItemsCallback, errGetElementItemsCallback);
if (bandoFormId) {
storeSet.main.setAsyncRequest();
@@ -276,6 +278,8 @@ const BandoFormsEdit = () => {
storeSet.main.formLabel('');
storeSet.main.formElements([]);
storeSet.main.bandoCriteria([]);
storeSet.main.activeElement('');
storeSet.main.selectedElement('');
}
}, [id, formId]);
@@ -317,7 +321,7 @@ const BandoFormsEdit = () => {
<div className="appPageSection">
<DndProvider backend={HTML5Backend}>
<FormBuilder bandoStatus={bandoStatus}/>
<FormBuilder callStatus={bandoStatus} context="application"/>
</DndProvider>
</div>

View File

@@ -2,9 +2,11 @@ import React, { useState, useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { klona } from 'klona';
import { head, isNil } from 'ramda';
import { head, isNil, isEmpty, pathOr } from 'ramda';
import { useForm } from 'react-hook-form';
import 'quill/dist/quill.core.css';
import { evaluate } from 'mathjs';
import equal from 'fast-deep-equal';
// store
import { storeSet, useStore } from '../../store';
@@ -30,6 +32,9 @@ import {
isUrl, minChecks, maxChecks, nonEmptyTables
} from '../../helpers/validators';
import renderHtmlContent from '../../helpers/renderHtmlContent';
import renderWithDataVars from '../../helpers/renderWithDataVars';
import getTokens from '../../helpers/getTokens';
import parseCommaDecimal from '../../helpers/parseCommaDecimal';
const BandoFormsPreview = () => {
const { id, formId } = useParams();
@@ -43,9 +48,12 @@ const BandoFormsPreview = () => {
formState: { errors },
getValues,
register,
setValue
setValue,
watch,
reset
} = useForm({ defaultValues: {}, mode: 'onChange' });
const values = getValues();
const formValues = watch();
const validationFns = {
isPIVA,
isCodiceFiscale,
@@ -85,6 +93,43 @@ const BandoFormsPreview = () => {
storeSet.main.unsetAsyncRequest();
}
useEffect(() => {
let updatedFormValues = klona(formValues);
let context = {};
// eslint-disable-next-line array-callback-return
formData.map((o) => {
const variable = head(o.settings.filter(o => o.name === 'variable'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
if (formula && !isEmpty(formula.value)) {
context = getTokens(formula.value)
.filter(v => !['false', 'null', 'true'].includes(v))
.reduce((acc, cur) => {
acc[cur] = isNil(context[cur]) ? 0 : parseCommaDecimal(context[cur]);
return acc;
}, context);
const mathFormula = renderWithDataVars(formula.value, context);
try {
updatedFormValues[o.id] = evaluate(mathFormula);
} catch (e) {
console.log('Error in math formula: "', mathFormula, '"', e.message);
updatedFormValues[o.id] = 0;
}
}
if (variable && !isEmpty(variable.value)) {
context[variable.value[0]] = 'criteria_table' === o.name
? pathOr(0, [o.id, 'total'], updatedFormValues)
: pathOr(0, [o.id], updatedFormValues);
}
});
if (!isEmpty(updatedFormValues) && !equal(updatedFormValues, formValues)) {
reset(updatedFormValues);
}
}, [formValues]);
useEffect(() => {
const parsedFormId = parseInt(formId)
const bandoFormId = !isNaN(parsedFormId) ? parsedFormId : 0;
@@ -126,9 +171,13 @@ const BandoFormsPreview = () => {
const text = head(o.settings.filter(o => o.name === 'text'));
const placeholder = head(o.settings.filter(o => o.name === 'placeholder'));
const options = head(o.settings.filter(o => o.name === 'options'));
const tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
let tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
if (!tableColumns) {
tableColumns = head(o.settings.filter(o => o.name === 'criteria_table_columns'));
}
const step = head(o.settings.filter(o => o.name === 'step'));
const mime = head(o.settings.filter(o => o.name === 'mime'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
let mimeValue = '';
if (mime) {
@@ -162,6 +211,7 @@ const BandoFormsPreview = () => {
</div>
: <FormField
key={o.id}
readOnly={formula && !isEmpty(formula.value)}
type={o.name}
fieldName={o.id}
label={label ? label.value : ''}

View File

@@ -12,11 +12,13 @@ import getNumberWithCurrency from '../../helpers/getNumberWithCurrency';
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import renderHtmlContent from '../../helpers/renderHtmlContent';
import isDateTimeInPast from '../../helpers/isDateTimeInPast';
// api
import BandoService from '../../service/bando-service';
import FaqItemService from '../../service/faq-item-service';
import ApplicationService from '../../service/application-service';
import PreferredBandoService from '../../service/preferred-bando-service';
// components
import { Skeleton } from 'primereact/skeleton';
@@ -28,8 +30,6 @@ import { Message } from 'primereact/message';
import { Toast } from 'primereact/toast';
import { Editor } from 'primereact/editor';
import { Dialog } from 'primereact/dialog';
import PreferredBandoService from '../../service/preferred-bando-service';
import isDateTimeInPast from '../../helpers/isDateTimeInPast';
const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID;

View File

@@ -0,0 +1,269 @@
import React, { useState, useEffect, useRef } from 'react';
import { __ } from '@wordpress/i18n';
import { useParams } from 'react-router-dom';
import { is, isEmpty, isNil } from 'ramda';
import 'quill/dist/quill.core.css';
// store
import { storeSet, useStore } from '../../store';
// tools
import getNumberWithCurrency from '../../helpers/getNumberWithCurrency';
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
// components
import { Skeleton } from 'primereact/skeleton';
import { Accordion } from 'primereact/accordion';
import { AccordionTab } from 'primereact/accordion';
import { InputTextarea } from 'primereact/inputtextarea';
import { Button } from 'primereact/button';
import BandoService from '../../service/bando-service';
import { Messages } from 'primereact/messages';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import renderHtmlContent from '../../helpers/renderHtmlContent';
const REACT_APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const BandoViewPreInstructor = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
const { id } = useParams();
const [data, setData] = useState({});
const [newQuestion, setNewQuestion] = useState('');
const bandoMsgs = useRef(null);
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setData(getFormattedBandiData(data.data));
}
storeSet.main.unsetAsyncRequest();
}
const errGetCallback = (data) => {
if (bandoMsgs.current && data.message) {
bandoMsgs.current.show([
{
sticky: true, severity: 'error', summary: '',
detail: data.message,
closable: true
}
]);
}
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
const getFormattedBandiData = (data) => {
data.dates = data.dates.map(v => is(String, v) ? new Date(v) : (v ? v : ''));
return data;
};
useEffect(() => {
const parsed = parseInt(id)
const bandoId = !isNaN(parsed) ? parsed : 0;
BandoService.getBando(bandoId, getCallback, errGetCallback);
}, [id]);
return (
<div className="appPage">
{!isAsyncRequest && !isEmpty(data)
? <div className="appPage__pageHeader">
<h1>{data.name}</h1>
<p>
{__('Data:', 'gepafin')}
<span>{getDateFromISOstring(data.createdDate)}</span>
</p>
</div>
: <>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
</>}
<div className="appPage__spacer"></div>
<Messages ref={bandoMsgs}/>
{!isAsyncRequest && !isEmpty(data)
? <div className="appPage__content">
{!isEmpty(data.images)
? <picture className="appPageSection__hero">
<source srcSet={data.images[0] ? data.images[0].filePath : ''}/>
<img src={data.images[0] ? data.images[0].filePath : ''} alt={data.name}/>
</picture> : null}
<div className="appPageSection__withBorder">
<h2>{__('Descrizione breve', 'gepafin')}</h2>
<div className="ql-editor">
{renderHtmlContent(data.descriptionShort)}
</div>
</div>
<div className="appPageSection__row">
<div className="appPageSection__withBorder">
<p className="appPageSection__pMeta">
<span>{__('Importo totale', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amount)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Importo minimo per progetto', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amountMin)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Importo massimo per progetto', 'gepafin')}</span>
<span>{getNumberWithCurrency(data.amountMax)}</span>
</p>
</div>
<div className="appPageSection__withBorder">
<p className="appPageSection__pMeta">
<span>{__('Data apertura', 'gepafin')}</span>
<span>{getDateFromISOstring(data.dates[0])} {data.startTime}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Data chiusura', 'gepafin')}</span>
<span>{getDateFromISOstring(data.dates[1])} {data.endTime}</span>
</p>
</div>
</div>
<div className="appPageSection__withBorder">
<h2>{__('Descrizione dettagliata', 'gepafin')}</h2>
<div className="ql-editor">
{renderHtmlContent(data.descriptionLong)}
</div>
</div>
<div className="appPageSection__withBorder">
<h2>{__('Requisiti di Partecipazione', 'gepafin')}</h2>
<div className="row rowContent">
<ul>
{data.aimedTo.map((o, i) => <li key={i}>
{o.value}
</li>)}
</ul>
</div>
</div>
<div className="appPageSection__withBorder">
<h2>{__('Documentazione richiesta', 'gepafin')}</h2>
<div className="ql-editor">
{renderHtmlContent(data.documentationRequested)}
</div>
</div>
{/*<div className="appPageSection__withBorder">
<h2>{__('Criteri di Valutazione', 'gepafin')}</h2>
<div className="row rowContent">
<ul>
{data.criteria.map((o, i) => <li key={i}>
{o.value} {o.score > 0 ? sprintf(__(' (%d punti)'), o.score) : null}
</li>)}
</ul>
</div>
</div>*/}
<div className="appPageSection__withBorder">
<h2>{__('Allegati', 'gepafin')}</h2>
<div className="row rowContent">
<ul>
{data.docs
.filter(o => o.source === 'CALL' && o.type === 'DOCUMENT')
.map((o, i) => <li key={i}>
<a href={o.filePath} target="_blank" rel="noreferrer">{o.name}</a>
</li>)}
</ul>
</div>
</div>
<div className="appPageSection">
<h2>{__('FAQ', 'gepafin')}</h2>
<Accordion>
{data.faq
.filter(o => o.isVisible)
.map((o, i) => <AccordionTab key={i} header={renderHtmlContent(o.value)}>
<div className="ql-editor">
{renderHtmlContent(o.response)}
</div>
</AccordionTab>)}
</Accordion>
</div>
{REACT_APP_HUB_ID === 't7jh5wfg9QXylNaTZkPoE'
? null
: <div className="appPageSection">
<h2>{__('Non hai trovato la risposta che cercavi?', 'gepafin')}</h2>
<div className="appForm__field">
<label htmlFor="newQuestion">{__('Fai una domanda', 'gepafin')}</label>
<InputTextarea
id="newQuestion"
disabled
rows={7}
value={newQuestion}
placeholder={__('Digita qui la tua domanda', 'gepafin')}
onChange={(e) => setNewQuestion(e.target.value)}
aria-describedby="newQuestion-help"/>
<small id="newQuestion-help">
{__('Riceverai una notifica quando ti risponderemo', 'gepafin')}
</small>
</div>
</div>}
<div className="appPageSection">
<h2>{__('Download Documenti', 'gepafin')}</h2>
<div className="appPageSection__actions">
<Button
type="button"
outlined
onClick={() => {
}}
label={__('Scarica Bando Completo', 'gepafin')}
icon="pi pi-download" iconPos="right"/>
<Button
type="button"
outlined
onClick={() => {
}}
label={__('Scarica Modulistica', 'gepafin')}
icon="pi pi-download" iconPos="right"/>
<Button
type="button"
disabled={true}
onClick={() => {
}}
label={__('Presenta Domanda', 'gepafin')}
icon="pi pi-save" iconPos="right"/>
{/*<Button
type="button"
outlined
rounded
disabled={true}
onClick={() => {}}
label={__('Aggiungi a preferiti', 'gepafin')}
icon="pi pi-heart" iconPos="left"/>*/}
</div>
</div>
<div className="appPageSection__withBorder">
<h2>{__('Contatti per Assistenza', 'gepafin')}</h2>
<div className="row rowContent">
<p>Email: {data.email}</p>
{!isNil(data.phoneNumber) ?
<p>{__('Telefono', 'gepafin')}: +39 {data.phoneNumber}</p> : null}
</div>
</div>
</div>
: <>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="4rem" className="mb-8"></Skeleton>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="4rem"></Skeleton>
</>}
</div>
)
}
export default BandoViewPreInstructor;

View File

@@ -136,7 +136,7 @@ const DraftApplicationsTable = () => {
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -21,17 +21,43 @@ const LatestBandiTable = () => {
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [, setStatuses] = useState([]);
/*const [totalRecordsNum, setTotalRecordsNum] = useState(0);
const [perPageNum, setPerPageNum] = useState(0);
const getPaginationQuery = () => {
return {
"globalFilters": {
"page": 1,
"limit": 5,
"sortBy": {
"columnName": "ID",
"sortDesc": true
}
}
}
}
const onPageChange = (e) => {
console.log('onPageChange', e)
}*/
useEffect(() => {
setLocalAsyncRequest(true);
BandoService.getBandi(getCallback, errGetCallbacks);
//const paginationQuery = getPaginationQuery();
//BandoService.getBandiPaginated(paginationQuery, getCallback, errGetCallbacks);
}, []);
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
/*const { body, totalRecords, currentPage, totalPages, pageSize } = data.data;
setTotalRecordsNum(totalRecords);
setPerPageNum(pageSize);
const newItems = body.filter(o => o.status === 'PUBLISH');
setItems(getFormattedBandiData(newItems));
setStatuses(uniq(body.map(o => o.status)));*/
const newItems = data.data.filter(o => o.status === 'PUBLISH');
setItems(getFormattedBandiData(newItems));
setStatuses(uniq(data.data.map(o => o.status)))
setStatuses(uniq(data.data.map(o => o.status)));
initFilters();
}
setLocalAsyncRequest(false);
@@ -106,7 +132,10 @@ const LatestBandiTable = () => {
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items}
paginator showGridlines
/*lazy totalRecords={totalRecordsNum} onPage={onPageChange}*/
rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -103,7 +103,7 @@ const LatestUsersActivityTable = () => {
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={loading} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={loading} dataKey="id"
filters={filters}
globalFilterFields={['name', 'status']}
header={header}

View File

@@ -112,7 +112,7 @@ const Dashboard = () => {
currency: 'EUR',
currencyDisplay: 'symbol'
}}
locales="en-US"/></span>
locales="it-IT"/></span>
</div>
</div>
</div>

View File

@@ -189,7 +189,7 @@ const LatestBandiTable = () => {
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={loading} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={loading} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -204,7 +204,7 @@ const MyLatestSubmissionsTable = () => {
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -0,0 +1,228 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq, isNil, head } from 'ramda';
import { Link, useNavigate } from 'react-router-dom';
// api
import AssignedApplicationService from '../../../../service/assigned-application-service';
// tools
import getBandoLabel from '../../../../helpers/getBandoLabel';
import getBandoSeverity from '../../../../helpers/getBandoSeverity';
// 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';
import translationStrings from '../../../../translationStringsForComponents';
import { useStore } from '../../../../store';
import { klona } from 'klona';
const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const InstructorManagerMieDomandeTable = ({ userId = null, statuses = [] }) => {
const navigate = useNavigate();
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [statusesForFilter, setStatusesForFilter] = useState([]);
const userData = useStore().main.userData();
useEffect(() => {
if (!isNil(userId)) {
setLocalAsyncRequest(true);
if (userId === 0) {
AssignedApplicationService.getAssignedApplications(getCallback, errGetCallbacks, [
['statuses', statuses]
]);
} else {
AssignedApplicationService.getAssignedApplications(getCallback, errGetCallbacks, [
['userId', userId],
['statuses', statuses]
]);
}
}
}, [userId]);
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setItems(getFormattedData(data.data));
setStatusesForFilter(uniq(data.data.map(o => o.status)))
initFilters();
}
setLocalAsyncRequest(false);
}
const errGetCallbacks = (data) => {
setLocalAsyncRequest(false);
}
const getFormattedData = (data) => {
return data.map((d) => {
d.evaluationEndDate = is(String, d.evaluationEndDate) ? new Date(d.evaluationEndDate) : (d.evaluationEndDate ? d.evaluationEndDate : '');
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 },
callName: {
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 }]
},
evaluationEndDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
}
});
};
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 dateEndBodyTemplate = (rowData) => {
return formatDate(rowData.evaluationEndDate);
};
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 statusFilterTemplate = (options) => {
return <Dropdown value={options.value} options={statusesForFilter} onChange={(e) => options.filterCallback(e.value, options.index)} itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel} className="p-column-filter" showClear />;
};
const statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)} />;
};
const handleInitiateEvaluation = (id) => {
setLocalAsyncRequest(true);
AssignedApplicationService.updateStatusAssignedApplication(id, getInitEvalCallback, errInitEvalCallbacks, [
['status', 'OPEN']
])
}
const getInitEvalCallback = (resp) => {
if (resp.status === 'SUCCESS') {
const evaluation = klona(head(getFormattedData([resp.data])));
const newItems = items.map(o => o.id === evaluation.id ? evaluation : o);
setItems(newItems);
navigate(`/mie-domande/${evaluation.applicationId}`);
}
setLocalAsyncRequest(false);
}
const errInitEvalCallbacks = (resp) => {
setLocalAsyncRequest(false);
}
const actionsBodyTemplate = (rowData) => {
if (rowData.status === 'AWAITING') {
return <Button
severity="info"
onClick={() => handleInitiateEvaluation(rowData.id)}
label={__('Valuta', 'gepafin')}
icon="pi pi-eye"
size="small"
iconPos="right"/>
} else {
const label = ['OPEN', 'SOCCORSO'].includes(rowData.status) && userData.id === rowData.userId
? __('Valuta', 'gepafin')
: __('Mostra', 'gepafin');
return <Link to={`/mie-domande/${rowData.applicationId}`}>
<Button severity="info" label={label} icon="pi pi-eye" size="small" iconPos="right"/>
</Link>
}
}
const header = renderHeader();
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}
onFilter={(e) => setFilters(e.filters)}>
<Column field="applicationId" header={__('ID domanda', 'gepafin')}
sortable filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '6rem' }}/>
<Column field="protocolNumber" header={__('Protocollo', 'gepafin')}
sortable filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '6rem' }}/>
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <Column field="ndg" header={__('NDG', 'gepafin')}
sortable filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '6rem' }}/> : null}
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <Column field="appointmentId" header={__('ID appuntamento', 'gepafin')}
sortable filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '6rem' }}/> : null}
<Column field="callName" header={__('Bando', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column field="companyName" header={__('Azienda', '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 header={__('Scadenza', 'gepafin')} filterField="evaluationEndDate" dataType="date"
style={{ minWidth: '8rem' }}
body={dateEndBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')}
style={{ minWidth: '7rem' }} body={statusBodyTemplate} filter
filterElement={statusFilterTemplate} />
<Column header={__('Azioni', 'gepafin')}
body={actionsBodyTemplate}/>
</DataTable>
</div>
)
}
export default InstructorManagerMieDomandeTable;

View File

@@ -0,0 +1,119 @@
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate } from 'react-router-dom';
import NumberFlow from '@number-flow/react';
import { pathOr } from 'ramda';
// service
import DashboardService from '../../service/dashboard-service';
// components
import { Button } from 'primereact/button';
import PreInstructorDomandeTable from '../DashboardPreInstructor/components/PreInstructorDomandeTable';
const DashboardInstructorManager = () => {
const navigate = useNavigate();
const [mainStats, setMainStats] = useState({});
const goToAllEvaluations = () => {
navigate('/domande');
}
const getStats = (data) => {
if (data.status === 'SUCCESS') {
setMainStats(data.data);
}
}
const errGetStats = () => {}
const getStatValue = (key, fallback = '') => {
return pathOr(fallback, [key], mainStats);
}
useEffect(() => {
DashboardService.getEvaluationsStats(getStats, errGetStats);
}, []);
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Dashboard', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Riepilogo', 'gepafin')}</h2>
<div className="statsBigBadges__grid applStats">
<div className="statsBigBadges__gridItem">
<span>{__('Totale domande', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfAssignedApplication', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('In soccorso', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationInAmendmentState', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('In valutazione', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationInOpenState', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Completate', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationInCloseState', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Tempo medio di valutazione', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('averageEvaluationDays', 0)}
format={{ notation: 'compact' }}
suffix={` ${__('giorni', 'gepafin')}`}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Domande in scadenza (48h)', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationExpiringIn48Hours', 0)}
format={{ notation: 'compact' }}
locales="en-US"/></span>
</div>
</div>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Panoramica delle domande da valutare', 'gepafin')}</h2>
<PreInstructorDomandeTable statuses={['OPEN', 'SOCCORSO']} userId={0}/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection__hr">
<span>{__('Azioni rapide', 'gepafin')}</span>
</div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
onClick={goToAllEvaluations}
label={__('Tutte le domande', 'gepafin')} icon="pi pi-arrow-right" iconPos="right"/>
</div>
</div>
</div>
)
}
export default DashboardInstructorManager;

View File

@@ -1,10 +1,7 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda';
import { Link } from 'react-router-dom';
// store
import { useStore } from '../../../../store';
import { is, uniq, isNil, head } from 'ramda';
import { Link, useNavigate } from 'react-router-dom';
// api
import AssignedApplicationService from '../../../../service/assigned-application-service';
@@ -24,27 +21,40 @@ import { Dropdown } from 'primereact/dropdown';
import { Tag } from 'primereact/tag';
import translationStrings from '../../../../translationStringsForComponents';
import { useStore } from '../../../../store';
import { klona } from 'klona';
const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const PreInstructorDomandeTable = () => {
const userData = useStore().main.userData();
const PreInstructorDomandeTable = ({ userId = null, statuses = [] }) => {
const navigate = useNavigate();
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [statuses, setStatuses] = useState([]);
const [statusesForFilter, setStatusesForFilter] = useState([]);
const userData = useStore().main.userData();
useEffect(() => {
setLocalAsyncRequest(true);
AssignedApplicationService.getAssignedApplications(getCallback, errGetCallbacks, [
['userId', userData.id]
]);
}, []);
if (!isNil(userId)) {
setLocalAsyncRequest(true);
if (userId === 0) {
AssignedApplicationService.getAssignedApplications(getCallback, errGetCallbacks, [
['statuses', statuses]
]);
} else {
AssignedApplicationService.getAssignedApplications(getCallback, errGetCallbacks, [
['userId', userId],
['statuses', statuses]
]);
}
}
}, [userId]);
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setItems(getFormattedData(data.data));
setStatuses(uniq(data.data.map(o => o.status)))
setStatusesForFilter(uniq(data.data.map(o => o.status)))
initFilters();
}
setLocalAsyncRequest(false);
@@ -121,24 +131,58 @@ const PreInstructorDomandeTable = () => {
};
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 />;
return <Dropdown value={options.value} options={statusesForFilter} onChange={(e) => options.filterCallback(e.value, options.index)} itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel} className="p-column-filter" showClear />;
};
const statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)} />;
};
const handleInitiateEvaluation = (id) => {
setLocalAsyncRequest(true);
AssignedApplicationService.updateStatusAssignedApplication(id, getInitEvalCallback, errInitEvalCallbacks, [
['status', 'OPEN']
])
}
const getInitEvalCallback = (resp) => {
if (resp.status === 'SUCCESS') {
const evaluation = klona(head(getFormattedData([resp.data])));
const newItems = items.map(o => o.id === evaluation.id ? evaluation : o);
setItems(newItems);
navigate(`/domande/${evaluation.applicationId}`);
}
setLocalAsyncRequest(false);
}
const errInitEvalCallbacks = (resp) => {
setLocalAsyncRequest(false);
}
const actionsBodyTemplate = (rowData) => {
return <Link to={`/domande/${rowData.applicationId}`}>
<Button severity="info" label={__('Valuta', 'gepafin')} icon="pi pi-eye" size="small" iconPos="right"/>
</Link>
if (rowData.status === 'AWAITING') {
return <Button
severity="info"
onClick={() => handleInitiateEvaluation(rowData.id)}
label={__('Valuta', 'gepafin')}
icon="pi pi-eye"
size="small"
iconPos="right"/>
} else {
const label = ['OPEN', 'SOCCORSO'].includes(rowData.status) && userData.id === rowData.userId
? __('Valuta', 'gepafin')
: __('Mostra', 'gepafin');
return <Link to={`/domande/${rowData.applicationId}`}>
<Button severity="info" label={label} icon="pi pi-eye" size="small" iconPos="right"/>
</Link>
}
}
const header = renderHeader();
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -1,58 +1,116 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate } from 'react-router-dom';
import NumberFlow from '@number-flow/react';
import { pathOr } from 'ramda';
// store
//import { useStore } from '../../store';
import { useStore } from '../../store';
// api
//import DashboardService from '../../service/dashboard-service';
// service
import DashboardService from '../../service/dashboard-service';
// components
//import LatestBandiTable from './components/LatestBandiTable';
//import MyLatestSubmissionsTable from './components/MyLatestSubmissionsTable';
import { Button } from 'primereact/button';
import PreInstructorDomandeTable from './components/PreInstructorDomandeTable';
const DashboardPreInstructor = () => {
const navigate = useNavigate();
//const [mainStats, setMainStats] = useState({});
const [mainStats, setMainStats] = useState({});
const userData = useStore().main.userData();
const goToAllEvaluations = () => {
navigate('/domande');
}
const getStats = (data) => {
if (data.status === 'SUCCESS') {
setMainStats(data.data);
}
}
const errGetStats = () => {}
const getStatValue = (keys = [], fallback = '') => {
return pathOr(fallback, keys, mainStats);
}
useEffect(() => {
DashboardService.getInstructorAmendmentsStats(getStats, errGetStats, [
['userId', userData.id]
]);
}, []);
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Dashboard', 'gepafin')}</h1>
</div>
{/*<div className="appPage__spacer"></div>
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Panoramica di Sistema', 'gepafin')}</h2>
<div className="statsBigBadges__grid">
<div className="statsBigBadges__gridItem">
<span>{__('Domande attive', 'gepafin')}</span>
<span>{getStatValue('numberOfApplications', 0)}</span>
<h2>{__('Riepilogo', 'gepafin')}</h2>
<div className="statsBigBadges__grid doubleStatsItems">
<div className="statsBigBadges__gridItemDoubleStats">
<span>{__('Domande da valutare', 'gepafin')}</span>
<span className="number"><NumberFlow
value={getStatValue(['assignedApplication', 'totalAssignedApplication'], 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
{/*<div className="auxStats">
<span>
<span
className="badge">{getStatValue(['assignedApplication', 'additionalApplicationPercentage'], 0)}%</span>
{__('da ieri', 'gepafin')}</span>
</div>*/}
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Bandi osservati', 'gepafin')}</span>
<span>{getStatValue('numberOfCalls', 0)}</span>
<div className="statsBigBadges__gridItemDoubleStats">
<span>{__('Domande valutate', 'gepafin')}</span>
<span className="number"><NumberFlow
value={getStatValue(['evaluatedApplication', 'evaluatedApplication'], 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
{/*<div className="auxStats">
<span>
<span
className="badge">{getStatValue(['evaluatedApplication', 'dailyAverage'], 0)}</span>
{__('media giornaliera', 'gepafin')}</span>
</div>*/}
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Documenti da integrare', 'gepafin')}</span>
<span>{getStatValue('numberOfIntegratedDocuments', 0)}</span>
<div className="statsBigBadges__gridItemDoubleStats">
<span>{__('Tempo medio valutazione', 'gepafin')}</span>
<span className="number"><NumberFlow
value={getStatValue(['averageEvaluationDays', 'averageEvlauationDaysRating'], 0)}
format={{ notation: 'compact' }}
suffix={` ${__('giorni', 'gepafin')}`}
locales="it-IT"/></span>
{/*<div className="auxStats">
<span>
<span className="badge">{getStatValue(['averageEvaluationDays', 'timeDifferenceFromAverage'], 0)}</span>
{__('rispetto alla media', 'gepafin')}</span>
</div>*/}
</div>
<div className="statsBigBadges__gridItemDoubleStats">
<span>{__('Soccorsi istruttori in corso', 'gepafin')}</span>
<span className="number"><NumberFlow
value={getStatValue(['amendmentInProgress', 'totalAmendmentInProgress'], 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
{/*<div className="auxStats">
<span>
<span className="badge">{getStatValue(['amendmentInProgress', 'expiringToday'], 0)}</span>
{__('in scadenza oggi', 'gepafin')}</span>
</div>*/}
</div>
</div>
</div>*/}
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Coda di lavoro', 'gepafin')}</h2>
<PreInstructorDomandeTable/>
<PreInstructorDomandeTable statuses={['OPEN', 'SOCCORSO']} userId={userData.id}/>
</div>
<div className="appPage__spacer"></div>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
import { isNil } from 'ramda';
// components
import DownloadApplicationArchive from '../DownloadApplicationArchive';
import DownloadSignedApplication from '../DownloadSignedApplication';
import DownloadCompanyDelegation from '../DownloadCompanyDelegation';
const ApplicationDownloadFiles = ({ id }) => {
return (
!isNil(id)
? <div className="appPageSection">
<h2>{__('Scarica documenti della domanda', 'gepafin')}</h2>
<div className="appPageSection__row autoFlow">
<DownloadApplicationArchive applicationId={id}/>
<DownloadSignedApplication applicationId={id}/>
<DownloadCompanyDelegation applicationId={id}/>
</div>
</div> : null
)
}
export default ApplicationDownloadFiles;

View File

@@ -0,0 +1,70 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
import { isNil } from 'ramda';
// tools
import getDateTimeFromISOstring from '../../../../helpers/getDateTimeFromISOstring';
import getDateFromISOstring from '../../../../helpers/getDateFromISOstring';
import getBandoLabel from '../../../../helpers/getBandoLabel';
const APP_HUB_ID = process.env.REACT_APP_HUB_ID;
const ApplicationInfo = ({ data }) => {
return (
!isNil(data)
? <div className="appPageSection__withBorder columns">
<p className="appPageSection__pMeta">
<span>{__('ID domanda', 'gepafin')}</span>
<span>{data.applicationId}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Protocollo', 'gepafin')}</span>
<span>{data.protocolNumber}</span>
</p>
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <p className="appPageSection__pMeta">
<span>{__('NDG', 'gepafin')}</span>
<span>{data.ndg}</span>
</p> : null}
{APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE'
? <p className="appPageSection__pMeta">
<span>{__('Appuntamento', 'gepafin')}</span>
<span>{data.appointmentId}</span>
</p> : null}
<p className="appPageSection__pMeta">
<span>{__('Bando', 'gepafin')}</span>
<span>{data.callName}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Referente Aziendale', 'gepafin')}</span>
<span>{data.beneficiary}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Azienda Beneficiaria', 'gepafin')}</span>
<span>{data.companyName}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Data ricezione', 'gepafin')}</span>
<span>{getDateTimeFromISOstring(data.submissionDate)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Data assegnazione', 'gepafin')}</span>
<span>{getDateTimeFromISOstring(data.assignedAt)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Aassegnato a', 'gepafin')}</span>
<span>{data.assignedUserName}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Scadenza Valutazione', 'gepafin')}</span>
<span>{getDateFromISOstring(data.evaluationEndDate)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Stato', 'gepafin')}</span>
<span>{getBandoLabel(data.applicationStatus)}</span>
</p>
</div> : null
)
}
export default ApplicationInfo;

File diff suppressed because it is too large Load Diff

View File

@@ -150,7 +150,7 @@ const AllDomandeTable = ({ openDialogFn, updaterString = '' }) => {
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -200,7 +200,7 @@ const Domande = () => {
<span><NumberFlow
value={getStatValue('evaluationAverageTime', 0)}
format={{ notation: 'compact' }}
suffix={` ${__('minuti', 'gepafin')}`}
suffix={` ${__('giorni', 'gepafin')}`}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda';
import { Link, useLocation } from 'react-router-dom';
import { Link } from 'react-router-dom';
// api
import ApplicationService from '../../../../service/application-service';
@@ -29,7 +29,6 @@ const AllDomandeArchiveTable = ({ updaterString = '' }) => {
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [statuses, setStatuses] = useState([]);
const location = useLocation();
useEffect(() => {
setLocalAsyncRequest(true);
@@ -141,7 +140,7 @@ const AllDomandeArchiveTable = ({ updaterString = '' }) => {
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -2,10 +2,10 @@ import React from 'react';
import { __ } from '@wordpress/i18n';
// components
import AllDomandeArchiveTable from './components/AllDomandeArchiveTable';
const Domande = () => {
//import AllDomandeArchiveTable from './components/AllDomandeArchiveTable';
import PreInstructorDomandeTable from '../DashboardPreInstructor/components/PreInstructorDomandeTable';
const DomandeArchive = () => {
return (
<div className="appPage">
<div className="appPage__pageHeader">
@@ -15,11 +15,11 @@ const Domande = () => {
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Domande pubblicate', 'gepafin')}</h2>
<AllDomandeArchiveTable/>
<h2>{__('Domande completate', 'gepafin')}</h2>
<PreInstructorDomandeTable statuses={['CLOSE']} userId={0}/>
</div>
</div>
)
}
export default Domande;
export default DomandeArchive;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
// store
import { useStore } from '../../store';
// components
import PreInstructorDomandeTable from '../DashboardPreInstructor/components/PreInstructorDomandeTable';
const DomandeArchivePreInstructor = () => {
const userData = useStore().main.userData();
return (
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Archivio domande', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Domande completate', 'gepafin')}</h2>
<PreInstructorDomandeTable statuses={['CLOSE']} userId={userData.id}/>
</div>
</div>
)
}
export default DomandeArchivePreInstructor;

View File

@@ -157,7 +157,7 @@ const BeneficiarioDomandeTable = () => {
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -8,7 +8,7 @@ import { useStore } from '../../store';
// components
import BeneficiarioDomandeTable from './components/BeneficiarioDomandeTable';
const DomandePreInstructor = () => {
const DomandeBeneficiario = () => {
const chosenCompanyId = useStore().main.chosenCompanyId();
const companies = useStore().main.companies();
const company = head(companies.filter(o => o.id === chosenCompanyId));
@@ -29,4 +29,4 @@ const DomandePreInstructor = () => {
)
}
export default DomandePreInstructor;
export default DomandeBeneficiario;

View File

@@ -20,6 +20,7 @@ import { classNames } from 'primereact/utils';
import { Dropdown } from 'primereact/dropdown';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import DashboardService from '../../service/dashboard-service';
const DomandeInstructorManager = () => {
const [loading, setLoading] = useState(false);
@@ -30,6 +31,7 @@ const DomandeInstructorManager = () => {
const [chosenApplication, setChosenApplication] = useState(0);
const [updaterString, setUpdaterString] = useState('');
const toast = useRef(null);
const [mainStats, setMainStats] = useState({});
const getRolesCallback = (data) => {
if (data.status === 'SUCCESS') {
@@ -123,6 +125,18 @@ const DomandeInstructorManager = () => {
storeSet.main.unsetAsyncRequest();
}
const getStats = (data) => {
if (data.status === 'SUCCESS') {
setMainStats(data.data);
}
}
const errGetStats = () => {}
useEffect(() => {
DashboardService.getEvaluationsStats(getStats, errGetStats);
}, []);
useEffect(() => {
if (roleIds.length > 0) {
setLoading(true);
@@ -140,22 +154,74 @@ const DomandeInstructorManager = () => {
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Domande da valutare', 'gepafin')}</h1>
<h1>{__('Gestione domande', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<PreInstructorDomandeTable/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Domande pubblicate', 'gepafin')}</h2>
<h2>{__('Da assegnare', 'gepafin')}</h2>
<AllDomandeTable openDialogFn={openAssignDialog} updaterString={updaterString}/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('In lavorazione', 'gepafin')}</h2>
<PreInstructorDomandeTable statuses={['OPEN', 'SOCCORSO']} userId={0}/>
</div>
{/*<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Riepilogo', 'gepafin')}</h2>
<div className="statsBigBadges__grid applStats">
<div className="statsBigBadges__gridItem">
<span>{__('Totale domande', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfAssignedApplication', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('In soccorso', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationInAmendmentState', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('In valutazione', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationInOpenState', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Completate', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationInCloseState', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Tempo medio di valutazione', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('averageEvaluationDays', 0)}
format={{ notation: 'compact' }}
suffix={` ${__('giorni', 'gepafin')}`}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Domande in scadenza (48h)', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('numberOfApplicationExpiringIn48Hours', 0)}
format={{ notation: 'compact' }}
locales="en-US"/></span>
</div>
</div>
</div>*/}
<Dialog
visible={isVisibleEditDialog}
modal

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
// store
import { useStore } from '../../store';
// components
import InstructorManagerMieDomandeTable
from '../DashboardInstructorManager/components/InstructorManagerMieDomandeTable';
const DomandeMieInstructorManager = () => {
const userData = useStore().main.userData();
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Domande da valutare', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Nuove domande da valutare', 'gepafin')}</h2>
<InstructorManagerMieDomandeTable statuses={['AWAITING']} userId={userData.id}/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Coda di lavoro', 'gepafin')}</h2>
<InstructorManagerMieDomandeTable statuses={['OPEN', 'SOCCORSO']} userId={userData.id}/>
</div>
</div>
)
}
export default DomandeMieInstructorManager;

View File

@@ -1,10 +1,15 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
// store
import { useStore } from '../../store';
// components
import PreInstructorDomandeTable from '../DashboardPreInstructor/components/PreInstructorDomandeTable';
const DomandePreInstructor = () => {
const userData = useStore().main.userData();
return(
<div className="appPage">
<div className="appPage__pageHeader">
@@ -14,7 +19,8 @@ const DomandePreInstructor = () => {
<div className="appPage__spacer"></div>
<div className="appPageSection">
<PreInstructorDomandeTable/>
<h2>{__('Nuove domande da valutare', 'gepafin')}</h2>
<PreInstructorDomandeTable statuses={['AWAITING']} userId={userData.id}/>
</div>
</div>
)

View File

@@ -36,7 +36,7 @@ const Profile = () => {
const onSubmit = (formData) => {
storeSet.main.setAsyncRequest();
UserService.updateUser(userData.id, formData, updateCallback, updateError);
UserService.updateUserSelf(userData.id, formData, updateCallback, updateError);
};
const updateCallback = (data) => {
@@ -109,6 +109,7 @@ const Profile = () => {
<FormField
type="textinput"
fieldName="email"
disabled={true}
label={__('Email', 'gepafin')}
control={control}
errors={errors}

View File

@@ -36,7 +36,7 @@ const ProfileBeneficiario = () => {
const onSubmit = (formData) => {
storeSet.main.setAsyncRequest();
UserService.updateUser(userData.id, formData, updateCallback, updateError);
UserService.updateUserSelf(userData.id, formData, updateCallback, updateError);
};
const updateCallback = (data) => {

View File

@@ -1,8 +1,8 @@
import React, { useRef, useState, useEffect, useMemo } from 'react';
import React, { useRef, useState, useEffect } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { useForm } from 'react-hook-form';
import { classNames } from 'primereact/utils';
import { isEmpty, isNil } from 'ramda';
import { isEmpty } from 'ramda';
import { useNavigate, useSearchParams } from 'react-router-dom';
// tools

View File

@@ -0,0 +1,351 @@
import React, { useState, useEffect, useRef } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { isEmpty } from 'ramda';
import { wrap } from 'object-path-immutable';
// store
import { storeSet, useStore } from '../../store';
// api
import AmendmentsService from '../../service/amendments-service';
// tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
// components
import { Skeleton } from 'primereact/skeleton';
import { Button } from 'primereact/button';
import { Checkbox } from 'primereact/checkbox';
import { Editor } from 'primereact/editor';
import { InputNumber } from 'primereact/inputnumber';
import BlockingOverlay from '../../components/BlockingOverlay';
import { Toast } from 'primereact/toast';
import { InputSwitch } from 'primereact/inputswitch';
import ApplicationEvaluationService from '../../service/application-evaluation-service';
import { Dialog } from 'primereact/dialog';
const SoccorsoAddInstructorManager = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
const { id } = useParams();
const navigate = useNavigate();
const [data, setData] = useState({});
const [evaluationId, setEvaluationId] = useState(0);
const [formData, setFormData] = useState({});
const [isVisibleConfirmDialog, setIsVisibleConfirmDialog] = useState(false)
const toast = useRef(null);
const goToEvaluationPage = () => {
navigate(`/mie-domande/${id}`);
}
useEffect(() => {
const parsed = parseInt(id)
const entityId = !isNaN(parsed) ? parsed : 0;
storeSet.main.setAsyncRequest();
ApplicationEvaluationService.getEvaluationByApplId(getCallbackEvaluation, errGetCallback, [
['applicationId', entityId]
]);
}, [id]);
const getCallbackEvaluation = (data) => {
if (data.status === 'SUCCESS') {
setEvaluationId(data.data.id);
AmendmentsService.getSoccorsoByApplEvalId(data.data.id, getCallback, errGetCallback)
}
}
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setData(data.data);
setFormData(getFormattedFormData(data.data));
}
storeSet.main.unsetAsyncRequest();
}
const errGetCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
const getFormattedFormData = (data) => {
let newData = {};
newData.formFields = data.formFields;
newData.responseDays = 10;
newData.note = '';
newData.isSendNotification = true;
newData.isSendEmail = true;
return newData;
};
const renderHeader = () => {
return (
<span className="ql-formats">
<button className="ql-bold" aria-label="Bold"></button>
<button className="ql-italic" aria-label="Italic"></button>
<button className="ql-underline" aria-label="Underline"></button>
<button className="ql-link" aria-label="Link"></button>
<button className="ql-list" value="ordered"></button>
<button className="ql-header" value="2"></button>
<button className="ql-header" value="3"></button>
<button className="ql-blockquote"></button>
<button className="ql-list" value="bullet"></button>
<button className="ql-indent" value="-1"></button>
<button className="ql-indent" value="+1"></button>
</span>
);
};
const header = renderHeader();
const updateEvaluationValue = (value, path, maxValue) => {
let finalValue = value;
if (maxValue) {
finalValue = value > maxValue ? maxValue : value;
}
const newData = wrap(formData).set(path.split('.'), finalValue).value();
setFormData(newData);
}
const doCreate = () => {
storeSet.main.setAsyncRequest();
AmendmentsService.createSoccorso(formData, createCallback, errCreateCallback, [
['applicationEvaluationId', evaluationId]
]);
}
const createCallback = (data) => {
if (data.status === 'SUCCESS') {
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
setTimeout(() => {
navigate(`/mie-domande/${id}/soccorso/${data.data.id}`);
}, 1000)
}
storeSet.main.unsetAsyncRequest();
}
const errCreateCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
const initCreationProcess = () => {
setIsVisibleConfirmDialog(true);
}
const headerConfirmDialog = () => {
return <span>{__('Richiesta di conferma', 'gepafin')}</span>;
}
const hideConfirmDialog = () => {
setIsVisibleConfirmDialog(false);
}
const footerConfirmDialog = () => {
return <div>
<Button type="button" label={__('No', 'gepafin')} onClick={goToEvaluationPage} outlined/>
<Button
type="button"
label={__('Si', 'gepafin')} onClick={doConfirm}/>
</div>
}
const doConfirm = () => {
setIsVisibleConfirmDialog(false);
doCreate();
}
return (
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Richiesta Integrazione Documentale', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<Toast ref={toast}/>
<BlockingOverlay shouldDisplay={isAsyncRequest}/>
<div className="appPageSection__row">
<Button
type="button"
outlined
onClick={goToEvaluationPage}
label={__('Indietro', 'gepafin')}
icon="pi pi-arrow-left" iconPos="left"/>
</div>
<div className="appPage__spacer"></div>
{!isAsyncRequest && !isEmpty(data)
? <div className="appPage__content">
<div className="appPageSection__withBorder column">
<p className="appPageSection__pMeta">
<span>{__('ID domanda', 'gepafin')}</span>
<span>{data.applicationId}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Bando', 'gepafin')}</span>
<span>{data.callName}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Referente Aziendale', 'gepafin')}</span>
<span>{data.beneficiaryName}</span>
</p>
</div>
<div className="appPageSection">
<div className="appPageSection columns">
<div>
<h3>{__('Pec/Email', 'gepafin')}</h3>
<div style={{marginBottom: '30px'}}>
<Editor
value={formData.note}
placeholder={__('Digita qui il messagio', 'gepafin')}
headerTemplate={header}
onTextChange={(e) => updateEvaluationValue(
e.htmlValue,
'note'
)}
style={{ height: 80 * 3, width: '100%' }}
/>
</div>
<h3>{__('Tempo per la Risposta (giorni)', 'gepafin')}</h3>
<div style={{marginBottom: '30px'}}>
<InputNumber
keyfilter="int"
value={formData.responseDays}
showButtons
onChange={(e) => updateEvaluationValue(
e.value,
'responseDays',
9999
)}/>
</div>
<h3>{__('Notifica', 'gepafin')}</h3>
<div className="appPageSection__withBorder grey">
<div className="appForm__field row">
<InputSwitch
inputId="notify_email"
checked={formData.isSendEmail}
onChange={(e) => updateEvaluationValue(
e.value,
'isSendEmail'
)}/>
<label htmlFor="notify_email">{__('Notifiche Email', 'gepafin')}</label>
</div>
<div className="appForm__field row">
<InputSwitch
inputId="notify_push"
checked={formData.isSendNotification}
onChange={(e) => updateEvaluationValue(
e.value,
'isSendNotification'
)}/>
<label htmlFor="notify_push">{__('Notifiche Push', 'gepafin')}</label>
</div>
</div>
</div>
{formData.formFields
? <div>
<h3>{__('Documenti da Integrare', 'gepafin')}</h3>
<div className="appPageSection__withBorder grey">
<div className="appPageSection__checklist">
{formData.formFields.map((o, i) => <div key={o.fieldId}>
<Checkbox
inputId={`checklist_${o.fieldId}`}
onChange={(e) => updateEvaluationValue(
e.checked,
`formFields.${i}.selected`
)}
checked={o.selected}></Checkbox>
<label htmlFor={`checklist_${o.fieldId}`}>{o.label}</label>
</div>)}
</div>
</div>
</div>
: null}
</div>
</div>
<div className="appPageSection__message warning">
<i className="pi pi-exclamation-triangle"></i>
<span className="summary">{__('Attenzione', 'gepafin')}</span>
<span>{__("L'invio della richiesta di integrazione sospenderà il termine di valutazione della domanda.", 'gepafin')}</span>
</div>
<div className="appPageSection__hr">
<span>{__('Azioni', 'gepafin')}</span>
</div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
type="button"
outlined
onClick={goToEvaluationPage}
label={__('Anulla', 'gepafin')}
icon="pi pi-times" iconPos="right"/>
<Button
type="button"
onClick={initCreationProcess}
label={__('Invia richiesta', 'gepafin')}
icon="pi pi-check" iconPos="right"/>
</div>
</div>
<Dialog
visible={isVisibleConfirmDialog}
modal
header={headerConfirmDialog}
footer={footerConfirmDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideConfirmDialog}>
<div className="appForm__field">
<p>{__('Soccorso istruttorio autorizzato dal direttore e autorizzazione caricata su portale a seguito del quale parte l\'email?', 'gepafin')}</p>
</div>
</Dialog>
</div>
: <>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="4rem" className="mb-8"></Skeleton>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="2rem" className="mb-8"></Skeleton>
<Skeleton width="20%" height="1rem" className="mb-2"></Skeleton>
<Skeleton width="100%" height="4rem"></Skeleton>
</>}
</div>
)
}
export default SoccorsoAddInstructorManager;

View File

@@ -0,0 +1,587 @@
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom';
import { is, isEmpty } from 'ramda';
import { wrap } from 'object-path-immutable';
import { klona } from 'klona';
import { useForm } from 'react-hook-form';
// store
import { storeSet, useStore } from '../../store';
// api
import AmendmentsService from '../../service/amendments-service';
// tools
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import getBandoLabel from '../../helpers/getBandoLabel';
import getDateFromISOstring from '../../helpers/getDateFromISOstring';
import getEmailTemplateForSoccorso from '../../helpers/getStrippedHtmlBodyTags';
// components
import { Button } from 'primereact/button';
import BlockingOverlay from '../../components/BlockingOverlay';
import { Toast } from 'primereact/toast';
import { classNames } from 'primereact/utils';
import { Dialog } from 'primereact/dialog';
import FormField from '../../components/FormField';
import { Editor } from 'primereact/editor';
import { InputNumber } from 'primereact/inputnumber';
import SoccorsoComunications from '../SoccorsoEditPreInstructor/components/SoccorsoComunications';
const SoccorsoEditInstructorManager = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
const { id, amendmentId } = useParams();
const navigate = useNavigate();
const [data, setData] = useState({});
const [isVisibleCloseAmendDialog, setIsVisibleCloseAmendDialog] = useState(false);
const [isVisibleExtendTimeDialog, setIsVisibleExtendTimeDialog] = useState(false);
const [extendedTime, setExtendedTime] = useState(3);
const [isLoadingExtendingTime, setIsLoadingExtendingTime] = useState(false);
const [isLoadingReminding, setIsLoadingReminding] = useState(false);
const [internalNote, setInternalNote] = useState('');
const toast = useRef(null);
const [formInitialData, setFormInitialData] = useState({});
const {
control,
handleSubmit,
formState: { errors },
setValue,
register,
trigger,
getValues
} = useForm({
defaultValues: useMemo(() => {
return formInitialData;
}, [formInitialData]), mode: 'onChange'
});
const goToEvaluationPage = () => {
navigate(`/mie-domande/${id}`);
}
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
setData(getFormattedData(data.data));
let formDataInitial = data.data.applicationFormFields.reduce((acc, cur) => {
if (cur.fieldValue) {
acc[cur.fieldId] = cur.fieldValue;
}
return acc;
}, {});
formDataInitial = {
...formDataInitial,
amendmentDocuments: data.data.amendmentDocuments
}
setFormInitialData(formDataInitial);
}
storeSet.main.unsetAsyncRequest();
}
const errGetCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
const getFormattedData = (data) => {
data.startDate = is(String, data.startDate) ? new Date(data.startDate) : (data.startDate ? data.startDate : '');
data.expirationDate = is(String, data.expirationDate) ? new Date(data.expirationDate) : (data.expirationDate ? data.expirationDate : '');
return data;
};
const renderHeader = () => {
return (
<span className="ql-formats">
<button className="ql-bold" aria-label="Bold"></button>
<button className="ql-italic" aria-label="Italic"></button>
<button className="ql-underline" aria-label="Underline"></button>
<button className="ql-link" aria-label="Link"></button>
<button className="ql-list" value="ordered"></button>
<button className="ql-header" value="2"></button>
<button className="ql-header" value="3"></button>
<button className="ql-blockquote"></button>
<button className="ql-list" value="bullet"></button>
<button className="ql-indent" value="-1"></button>
<button className="ql-indent" value="+1"></button>
</span>
);
};
const header = renderHeader();
const updateNewAmendmentData = (value, path) => {
const newData = wrap(data).set(path, value).value();
setData(newData);
}
const onSubmit = () => {
};
const doUpdateAmendment = (doClose = false) => {
trigger();
let formValues = klona(getValues());
const newFormValues = Object.keys(formValues)
.filter(v => v !== 'amendmentDocuments')
.reduce((acc, cur) => {
let fieldVal = formValues[cur];
fieldVal = isEmpty(fieldVal) ? null : fieldVal;
fieldVal = is(Array, fieldVal) ? fieldVal.map(o => o.id).join(',') : null;
acc.push({
'fieldId': cur,
'fieldValue': fieldVal
});
return acc;
}, []);
const newAmendDocs = formValues.amendmentDocuments
? formValues.amendmentDocuments.map(o => o.id).join(',')
: '';
const submitData = {
applicationFormFields: newFormValues,
amendmentDocuments: newAmendDocs,
amendmentNotes: data.amendmentNotes
}
storeSet.main.setAsyncRequest();
AmendmentsService.updateSoccorso(
amendmentId,
submitData,
(resp) => updateAmendmentCallback(resp, doClose),
errUpdateAmendmentCallback
);
}
const updateAmendmentCallback = (data, doClose = false) => {
if (data.status === 'SUCCESS') {
setData(getFormattedData(data.data));
if (doClose) {
const submitData = {
internalNote
}
storeSet.main.setAsyncRequest();
AmendmentsService.closeSoccorso(amendmentId, submitData, closeAmendmentCallback, errCloseAmendmentCallback);
} else {
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
let formDataInitial = data.data.applicationFormFields.reduce((acc, cur) => {
if (cur.fieldValue) {
acc[cur.fieldId] = cur.fieldValue;
}
return acc;
}, formInitialData);
formDataInitial = {
...formDataInitial,
amendmentDocuments: data.data.amendmentDocuments
}
setFormInitialData(formDataInitial);
}
}
storeSet.main.unsetAsyncRequest();
}
const errUpdateAmendmentCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
const openCloseAmendmentDialog = () => {
setIsVisibleCloseAmendDialog(true);
}
const headerCloseAmendDialog = () => {
return <span>{__('Chiudi Soccorso Istruttorio', 'gepafin')}</span>
}
const hideCloseAmendDialog = () => {
setIsVisibleCloseAmendDialog(false);
}
const footerCloseAmendDialog = () => {
return <div>
<Button type="button" label={__('Anulla', 'gepafin')} onClick={hideCloseAmendDialog} outlined/>
<Button
type="button"
disabled={isAsyncRequest || isEmpty(data.internalNotes)}
label={__('Invia', 'gepafin')} onClick={doCloseAmendment}/>
</div>
}
const doCloseAmendment = () => {
doUpdateAmendment(true);
}
const closeAmendmentCallback = (data) => {
if (data.status === 'SUCCESS') {
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
if (data.data.status) {
updateNewAmendmentData(data.data.status, ['status']);
setIsVisibleCloseAmendDialog(false);
}
}
storeSet.main.unsetAsyncRequest();
}
const errCloseAmendmentCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest();
}
const headerExtendRespDialog = () => {
return <span>{__('Estendi scadenza', 'gepafin')}</span>
}
const hideExtendRespDialog = () => {
setIsVisibleExtendTimeDialog(false);
}
const footerExtendRespDialog = () => {
return <div>
<Button type="button" label={__('Anulla', 'gepafin')} onClick={hideExtendRespDialog} outlined/>
<Button
type="button"
disabled={isLoadingExtendingTime || isEmpty(extendedTime)}
label={__('Invia', 'gepafin')} onClick={doExtendTimeResponse}/>
</div>
}
const openExtendResponseTimeDialog = () => {
setIsVisibleExtendTimeDialog(true);
setExtendedTime(3);
}
const doExtendTimeResponse = () => {
setIsLoadingExtendingTime(true);
AmendmentsService.extendSoccorso(amendmentId, extendedTime, extendCallback, errExtendCallback);
}
const extendCallback = (data) => {
if (data.status === 'SUCCESS') {
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
setIsVisibleExtendTimeDialog(false);
}
setIsLoadingExtendingTime(false);
}
const errExtendCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
setIsLoadingExtendingTime(false);
}
const sendReminder = () => {
setIsLoadingReminding(true);
AmendmentsService.sendReminderForSoccorso(amendmentId, reminderCallback, errReminderCallback)
}
const reminderCallback = (data) => {
if (data.status === 'SUCCESS') {
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: data.message
});
}
}
setIsLoadingReminding(false);
}
const errReminderCallback = (data) => {
if (toast.current && data.message) {
toast.current.show({
severity: 'error',
summary: '',
detail: data.message
});
}
set404FromErrorResponse(data);
setIsLoadingReminding(false);
}
useEffect(() => {
if (formInitialData) {
Object.keys(formInitialData).map(k => setValue(k, formInitialData[k]));
trigger();
}
}, [formInitialData]);
useEffect(() => {
const parsedSoccorsoId = parseInt(amendmentId);
const soccorsoEntityId = !isNaN(parsedSoccorsoId) ? parsedSoccorsoId : 0;
AmendmentsService.getSoccorsoById(getCallback, errGetCallback, [['id', soccorsoEntityId]]);
}, [amendmentId]);
return (
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Soccorso Istruttorio - Dettagli', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<Toast ref={toast}/>
<BlockingOverlay shouldDisplay={isAsyncRequest}/>
<div className="appPageSection__row">
<Button
type="button"
outlined
onClick={goToEvaluationPage}
label={__('Indietro', 'gepafin')}
icon="pi pi-arrow-left" iconPos="left"/>
</div>
<div className="appPage__spacer"></div>
<div className="appPage__content">
<div className="appPageSection__withBorder columns">
<p className="appPageSection__pMeta">
<span>{__('ID domanda', 'gepafin')}</span>
<span>{data.applicationId}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Bando', 'gepafin')}</span>
<span>{data.callName}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Referente Aziendale', 'gepafin')}</span>
<span>{data.beneficiaryName}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Inizio', 'gepafin')}</span>
<span>{getDateFromISOstring(data.startDate)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Scadenza', 'gepafin')}</span>
<span>{getDateFromISOstring(data.expirationDate)}</span>
</p>
<p className="appPageSection__pMeta">
<span>{__('Stato', 'gepafin')}</span>
<span>{getBandoLabel(data.status)}</span>
</p>
</div>
<div className="appPageSection">
<h2>{__('Dettagli richiesta', 'gepafin')}</h2>
<h3>{__('Note e spiegazioni', 'gepafin')}</h3>
<div
className="appPageSection__emailTemplate">{getEmailTemplateForSoccorso(data.emailTemplate, data.note)}</div>
</div>
<div className="appPageSection">
<h3>{__('Documenti richiesti', 'gepafin')}</h3>
<ol className="appPageSection__list">
{data.formFields
? data.formFields.map((o, i) => <li key={o.fieldId}
style={{ flexDirection: 'row' }}>
<span>{o.label}</span>
</li>) : null}
</ol>
</div>
<div className="appPageSection">
<h2>{__('Comunicazioni', 'gepafin')}</h2>
<SoccorsoComunications amendmentId={amendmentId} soccorsoStatus={data.status}/>
</div>
{data.formFields && !isEmpty(data.formFields)
? <div className="appPageSection">
<h2>{__('Documenti Ricevuti', 'gepafin')}</h2>
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
{data.formFields.map((o, i) => {
return <FormField
key={o.fieldId}
disabled={['CLOSE', 'AWAITING', 'EXPIRED'].includes(data.status)}
type="fileupload"
setDataFn={setValue}
saveFormCallback={doUpdateAmendment}
fieldName={o.fieldId}
label={o.label}
control={control}
register={register}
errors={errors}
defaultValue={formInitialData[o.fieldId] ? formInitialData[o.fieldId] : []}
accept={[]}
source="AMENDMENT"
sourceId={amendmentId}
multiple={true}
/>
})}
</form>
</div> : null}
<div className="appPageSection">
<h2>{__('Documenti aggiuntivi', 'gepafin')}</h2>
<div className="appPageSection">
<h3>{__('Notes', 'gepafin')}</h3>
<div style={{ marginBottom: '30px', width: '100%', position: 'relative' }}>
<BlockingOverlay shouldDisplay={['CLOSE', 'AWAITING', 'EXPIRED'].includes(data.status)}/>
<Editor
value={data.amendmentNotes}
readOnly={['CLOSE', 'AWAITING', 'EXPIRED'].includes(data.status)}
placeholder={__('Digita qui il messagio', 'gepafin')}
headerTemplate={header}
onTextChange={(e) => updateNewAmendmentData(
e.htmlValue,
'amendmentNotes'
)}
style={{ height: 80 * 3, width: '100%' }}
/>
</div>
<FormField
type="fileupload"
disabled={['CLOSE', 'AWAITING', 'EXPIRED'].includes(data.status)}
setDataFn={setValue}
saveFormCallback={doUpdateAmendment}
fieldName="amendmentDocuments"
label={__('I file', 'gepafin')}
control={control}
register={register}
errors={errors}
defaultValue={formInitialData.amendmentDocuments ? formInitialData.amendmentDocuments : []}
accept={[]}
source="amendment"
sourceId={amendmentId}
multiple={true}
/>
</div>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection__hr">
<span>{__('Azioni', 'gepafin')}</span>
</div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
type="button"
onClick={sendReminder}
disabled={isLoadingReminding || ['CLOSE', 'EXPIRED'].includes(data.status)}
outlined
label={__('Invia Sollecito', 'gepafin')}
icon="pi pi-send"
/>
<Button
type="button"
onClick={openExtendResponseTimeDialog}
disabled={isLoadingExtendingTime || ['CLOSE', 'EXPIRED'].includes(data.status)}
outlined
label={__('Estendi Scadenza', 'gepafin')}
icon="pi pi-stopwatch"
/>
<Button
type="button"
onClick={() => doUpdateAmendment()}
disabled={isAsyncRequest || ['CLOSE', 'AWAITING', 'EXPIRED'].includes(data.status)}
label={__('Salva bozza', 'gepafin')}
icon="pi pi-save" iconPos="right"/>
<Button
type="button"
onClick={openCloseAmendmentDialog}
disabled={isAsyncRequest || ['CLOSE', 'AWAITING'].includes(data.status)}
label={__('Chiudi Soccorso Istruttorio', 'gepafin')}
icon="pi pi-times" iconPos="right"/>
</div>
</div>
</div>
<Dialog
visible={isVisibleExtendTimeDialog}
modal
header={headerExtendRespDialog}
footer={footerExtendRespDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideExtendRespDialog}>
<div className="appForm__field">
<label
className={classNames({ 'p-error': isEmpty(extendedTime) })}>
{__('Giorni', 'gepafin')}*
</label>
<InputNumber
keyfilter="int"
disabled={['CLOSE', 'EXPIRED'].includes(data.status)}
value={extendedTime}
showButtons
onChange={(e) => setExtendedTime(e.value)}/>
</div>
</Dialog>
<Dialog
visible={isVisibleCloseAmendDialog}
modal
header={headerCloseAmendDialog}
footer={footerCloseAmendDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideCloseAmendDialog}>
<div className="appForm__field">
<label>{__('Motivazioni', 'gepafin')}</label>
<div style={{ position: 'relative' }}>
<BlockingOverlay shouldDisplay={data.status === 'CLOSE'}/>
<Editor
value={internalNote}
readOnly={['CLOSE', 'EXPIRED'].includes(data.status)}
placeholder={__('Digita qui il messagio', 'gepafin')}
headerTemplate={header}
onTextChange={(e) => setInternalNote(e.htmlValue)}
style={{ height: 80 * 3, width: '100%' }}
/>
</div>
</div>
</Dialog>
</div>
)
}
export default SoccorsoEditInstructorManager;

View File

@@ -0,0 +1,95 @@
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
// components
import DashboardService from '../../service/dashboard-service';
import { pathOr } from 'ramda';
import NumberFlow from '@number-flow/react';
import PreInstructorSoccorsiTable from '../SoccorsoIstruttorioPreInstructor/components/PreInstructorSoccorsiTable';
const SoccorsoIstruttorioInstructorManager = () => {
const [mainStats, setMainStats] = useState({});
const getStats = (data) => {
if (data.status === 'SUCCESS') {
setMainStats(data.data);
}
}
const errGetStats = () => {}
const getStatValue = (key, fallback = '') => {
return pathOr(fallback, [key], mainStats);
}
useEffect(() => {
DashboardService.getAmendmentsStats(getStats, errGetStats);
}, []);
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Soccorso istruttorio panoramica', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Riepilogo', 'gepafin')}</h2>
<div className="statsBigBadges__grid applStats">
<div className="statsBigBadges__gridItem">
<span>{__('Totale richieste', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('totalAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('In attesa risposta', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('waitingForResponseAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Risposte ricevute', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('responseReceivedAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Tempo medio di risposta', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('averageResponseDays', 0)}
format={{ notation: 'compact' }}
suffix={` ${__('giorni', 'gepafin')}`}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Scadute', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('expiredAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Richieste in scadenza (48h)', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('expiringRequestsIn48Hours', 0)}
format={{ notation: 'compact' }}
locales="en-US"/></span>
</div>
</div>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<PreInstructorSoccorsiTable userId={0}/>
</div>
</div>
)
}
export default SoccorsoIstruttorioInstructorManager;

View File

@@ -0,0 +1,175 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda';
import { Link } from 'react-router-dom';
// store
import { storeGet } from '../../../../store';
// api
import AmendmentsService from '../../../../service/amendments-service';
// tools
import getBandoLabel from '../../../../helpers/getBandoLabel';
import getBandoSeverity from '../../../../helpers/getBandoSeverity';
// 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';
import translationStrings from '../../../../translationStringsForComponents';
const InstructorManagerSoccorsiTable = () => {
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [statuses, setStatuses] = useState([]);
useEffect(() => {
const userData = storeGet.main.userData();
setLocalAsyncRequest(true);
AmendmentsService.getSoccorsi(getCallback, errGetCallbacks, [
['userId', userData.id]
]);
}, []);
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.startDate = is(String, d.startDate) ? new Date(d.startDate) : (d.startDate ? d.startDate : '');
d.evaluationEndDate = is(String, d.evaluationEndDate) ? new Date(d.evaluationEndDate) : (d.evaluationEndDate ? d.evaluationEndDate : '');
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 },
callName: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
},
companyName: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
},
startDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
},
evaluationEndDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
}
});
};
const renderHeader = () => {
return (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined onClick={clearFilter} />
</div>
);
};
const dateStartBodyTemplate = (rowData) => {
return formatDate(rowData.startDate);
};
const dateExpirationBodyTemplate = (rowData) => {
return rowData.evaluationEndDate ? formatDate(rowData.evaluationEndDate) : '';
};
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 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 statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)} />;
};
const actionsBodyTemplate = (rowData) => {
return <Link to={`/mie-domande/${rowData.applicationId}/soccorso/${rowData.id}`}>
<Button severity="info" label={__('Dettagli', 'gepafin')} size="small" />
</Link>
}
const header = renderHeader();
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}
onFilter={(e) => setFilters(e.filters)}>
<Column field="applicationId" 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="callName" header={__('Bando', 'gepafin')}
filter filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column field="companyName" header={__('Azienda Beneficiaria', 'gepafin')}
filter filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column header={__('Data richiesta', 'gepafin')}
filterField="startDate" dataType="date"
style={{ minWidth: '8rem' }}
body={dateStartBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column header={__('Scadenza', 'gepafin')}
filterField="evaluationEndDate" dataType="date"
style={{ minWidth: '8rem' }}
body={dateExpirationBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')}
style={{ minWidth: '7rem' }} body={statusBodyTemplate} filter
filterElement={statusFilterTemplate} />
<Column header={__('Azioni', 'gepafin')}
body={actionsBodyTemplate}/>
</DataTable>
</div>
)
}
export default InstructorManagerSoccorsiTable;

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
// components
import InstructorManagerSoccorsiTable from './components/InstructorManagerSoccorsiTable';
const SoccorsoIstruttorioMioInstructorManager = () => {
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Soccorso istruttorio', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<InstructorManagerSoccorsiTable/>
</div>
</div>
)
}
export default SoccorsoIstruttorioMioInstructorManager;

View File

@@ -1,11 +1,8 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda';
import { is, isNil, uniq } from 'ramda';
import { Link } from 'react-router-dom';
// store
import { storeGet } from '../../../../store';
// api
import AmendmentsService from '../../../../service/amendments-service';
@@ -26,17 +23,25 @@ import { Tag } from 'primereact/tag';
import translationStrings from '../../../../translationStringsForComponents';
const PreInstructorSoccorsiTable = ({ openDialogFn }) => {
const PreInstructorSoccorsiTable = ({ userId = null }) => {
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
const [statuses, setStatuses] = useState([]);
useEffect(() => {
const userData = storeGet.main.userData()
setLocalAsyncRequest(true);
AmendmentsService.getSoccorsoByPreInstructorId(userData.id, getCallback, errGetCallbacks);
}, []);
if (!isNil(userId)) {
setLocalAsyncRequest(true);
if (userId === 0) {
AmendmentsService.getSoccorsi(getCallback, errGetCallbacks);
} else {
AmendmentsService.getSoccorsi(getCallback, errGetCallbacks, [
['userId', userId]
]);
}
}
}, [userId]);
const getCallback = (data) => {
if (data.status === 'SUCCESS') {
@@ -135,7 +140,7 @@ const PreInstructorSoccorsiTable = ({ openDialogFn }) => {
return(
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} loading={localAsyncRequest} dataKey="id"
<DataTable value={items} paginator showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -1,20 +1,96 @@
import React from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import { __ } from '@wordpress/i18n';
import { pathOr } from 'ramda';
import NumberFlow from '@number-flow/react';
// store
import { useStore } from '../../store';
// components
import PreInstructorSoccorsiTable from './components/PreInstructorSoccorsiTable';
import DashboardService from '../../service/dashboard-service';
const SoccorsoIstruttorioPreInstructor = () => {
const [mainStats, setMainStats] = useState({});
const userData = useStore().main.userData()
const getStats = (data) => {
if (data.status === 'SUCCESS') {
setMainStats(data.data);
}
}
const errGetStats = () => {}
const getStatValue = useCallback((key, fallback = '') => {
return pathOr(fallback, [key], mainStats);
}, [mainStats]);
useEffect(() => {
DashboardService.getAmendmentsStats(getStats, errGetStats);
}, []);
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Soccorso Istruttorio', 'gepafin')}</h1>
<h1>{__('Soccorso istruttorio', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Riepilogo', 'gepafin')}</h2>
<div className="statsBigBadges__grid applStats">
<div className="statsBigBadges__gridItem">
<span>{__('Totale richieste', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('totalAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('In attesa risposta', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('waitingForResponseAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Risposte ricevute', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('responseReceivedAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Tempo medio di risposta', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('averageResponseDays', 0)}
format={{ notation: 'compact' }}
suffix={` ${__('giorni', 'gepafin')}`}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Scadute', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('expiredAmendments', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Richieste in scadenza (48h)', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('expiringRequestsIn48Hours', 0)}
format={{ notation: 'compact' }}
locales="en-US"/></span>
</div>
</div>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<PreInstructorSoccorsiTable/>
<PreInstructorSoccorsiTable userId={userData.id}/>
</div>
</div>
)

View File

@@ -0,0 +1,146 @@
import React, { useState, useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { is, isEmpty, uniq } from 'ramda';
// store
import { useStore } from '../../../../store';
// api
import ApplicationService from '../../../../service/application-service';
// tools
import getNumberWithCurrency from '../../../../helpers/getNumberWithCurrency';
// components
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import ProperBandoLabel from '../../../../components/ProperBandoLabel';
import translationStrings from '../../../../translationStringsForComponents';
const BeneficiarioUltimeDomandeTable = () => {
const chosenCompanyId = useStore().main.chosenCompanyId();
const [items, setItems] = useState(null);
// eslint-disable-next-line
const [filters, setFilters] = useState(null);
const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
// eslint-disable-next-line
const [statuses, setStatuses] = useState([]);
const perPage = 5;
const getPaginationQuery = (status = 'DRAFT', curPage = 1) => {
return {
"globalFilters": {
//"year": 0,
"page": curPage,
//"search": "",
"limit": perPage,
},
//"daysRange": 0,
"status": status
}
}
useEffect(() => {
if (!isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && !localAsyncRequest) {
const bodyParams = getPaginationQuery(
['SOCCORSO', 'APPROVED', 'REJECTED', 'EVALUATION', 'SUBMIT'],
1
);
setLocalAsyncRequest(true);
ApplicationService.getApplicationsPaginated(bodyParams, getApplCallback, errGetApplCallback, [
['companyId', chosenCompanyId],
['statuses', ['SOCCORSO', 'APPROVED', 'REJECTED', 'EVALUATION', 'SUBMIT']] // 'NDG', 'ADMISSIBLE', 'APPOINTMENT'
]);
}
}, [chosenCompanyId]);
const getApplCallback = (resp) => {
if (resp.status === 'SUCCESS') {
if (resp.data && is(Array, resp.data.body)) {
setItems(getFormattedBandiData(resp.data.body));
setStatuses(uniq(items.map(o => o.status)))
initFilters();
}
}
setLocalAsyncRequest(false);
}
const errGetApplCallback = () => {
setLocalAsyncRequest(false);
}
const getFormattedBandiData = (data) => {
return [...(data || [])].map((d) => {
d.callEndDate = new Date(d.callEndDate);
d.modifiedDate = new Date(d.modifiedDate);
d.submissionDate = new Date(d.submissionDate);
return d;
});
};
const formatDate = (value) => {
return value.toLocaleDateString('it-IT', {
year: 'numeric'
});
};
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 }]
},
modifiedDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
},
callEndDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
}
});
};
const dateSubmissionBodyTemplate = (rowData) => {
return formatDate(rowData.submissionDate);
};
const importoBodyTemplate = (rowData) => {
return getNumberWithCurrency(rowData.amountRequested);
};
const statusBodyTemplate = (rowData) => {
return <ProperBandoLabel status={rowData.status}/>;
};
return (
<div className="appPageSection__table">
<DataTable value={items} showGridlines rows={5} loading={localAsyncRequest} dataKey="id"
stripedRows
emptyMessage={translationStrings.emptyMessage}>
<Column field="callTitle" header={__('Bando', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column header={__('Anno', 'gepafin')} dataType="date"
style={{ minWidth: '8rem' }}
body={dateSubmissionBodyTemplate}/>
<Column field="amountRequested"
header={__('Importo finanziato', 'gepafin')}
style={{ minWidth: '7rem' }}
body={importoBodyTemplate}/>
<Column field="status" header={__('Stato progetto', 'gepafin')}
style={{ minWidth: '7rem' }} body={statusBodyTemplate}/>
</DataTable>
</div>
)
}
export default BeneficiarioUltimeDomandeTable;

View File

@@ -0,0 +1,112 @@
import React, { useEffect, useState, useCallback } from 'react';
import { __ } from '@wordpress/i18n';
import { isEmpty, pathOr } from 'ramda';
import NumberFlow from '@number-flow/react';
// store
import { useStore } from '../../store';
// components
import DashboardService from '../../service/dashboard-service';
import ChartDomandePerStato from '../../components/ChartDomandePerStato';
import ChartRichiesteVsApprovate from '../../components/ChartRichiesteVsApprovate';
import BeneficiarioUltimeDomandeTable from './components/BeneficiarioUltimeDomandeTable';
const StatsBeneficiary = () => {
const [mainStats, setMainStats] = useState({});
const [chartStats, setChartStats] = useState({});
const chosenCompanyId = useStore().main.chosenCompanyId();
const getStats = (resp) => {
if (resp.status === 'SUCCESS') {
setMainStats(resp.data.applicationWidget);
setChartStats(resp.data.applicationWidgetBars);
}
}
const errGetStats = () => {}
const getStatValue = useCallback((key, fallback = '') => {
return pathOr(fallback, [key], mainStats);
}, [mainStats]);
useEffect(() => {
DashboardService.getBeneficiaryStatsPage(chosenCompanyId, getStats, errGetStats);
}, [chosenCompanyId]);
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Statistiche', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Riepilogo', 'gepafin')}</h2>
<div className="statsBigBadges__grid doubleStatsItems">
<div className="statsBigBadges__gridItemDoubleStatsBeneficiary">
<span>{__('Domande presentate', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('submittedApplication', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItemDoubleStatsBeneficiary">
<span>{__('Tasso di successo', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('waitingForResponseAmendments', 0)}
format={{ notation: 'compact' }}
suffix={` ${__('%', 'gepafin')}`}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItemDoubleStatsBeneficiary">
<span>{__('Domande approvate', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('approvedApplication', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/></span>
</div>
<div className="statsBigBadges__gridItemDoubleStatsBeneficiary">
<span>{__('Importo totale finanziato', 'gepafin')}</span>
<span><NumberFlow
value={getStatValue('averageResponseDays', 0)}
format={{
notation: 'compact',
compactDisplay: 'short',
roundingMode: 'trunc',
style: 'currency',
currency: 'EUR',
currencyDisplay: 'symbol'
}}
locales="it-IT"/></span>
</div>
</div>
</div>
<div className="appPage__spacer"></div>
{chartStats && !isEmpty(chartStats)
? <div className="appPageSection">
<h2>{__('Statistiche di sistema', 'gepafin')}</h2>
<div className="appPageSection columns">
<ChartRichiesteVsApprovate
title={__('Importi Richiesti VS Approvati', 'gepafin')}
data={chartStats.requestedVsApprovedAmount}/>
<ChartDomandePerStato
title={__('Domande per stato', 'gepafin')}
data={chartStats.applicationPerStatus}/>
</div>
</div> : null}
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Ultime richieste di finanziamento', 'gepafin')}</h2>
<BeneficiarioUltimeDomandeTable/>
</div>
</div>
)
}
export default StatsBeneficiary;

View File

@@ -0,0 +1,105 @@
import React, { useState, useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { is } from 'ramda';
// 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';
const UserActivityTable = ({ data = [] }) => {
const [items, setItems] = useState([]);
const [filters, setFilters] = useState(null);
useEffect(() => {
if (data) {
setItems(getFormattedData(data));
initFilters();
}
}, [data]);
const getFormattedData = (data) => {
return data.map((d) => {
d.createdDate = is(String, d.createdDate) ? new Date(d.createdDate) : (d.createdDate ? d.createdDate : '');
d.updatedDate = is(String, d.updatedDate) ? new Date(d.updatedDate) : (d.updatedDate ? d.updatedDate : '');
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 },
actionType: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
},
ipAddress: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
},
createdDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
}
});
};
const renderHeader = () => {
return (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined
onClick={clearFilter}/>
</div>
);
};
const dateBodyTemplate = (rowData) => {
return formatDate(rowData.createdDate);
};
const header = renderHeader();
return (
<div className="appPageSection__table">
<DataTable value={items} paginator showGridlines rows={10} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}
onFilter={(e) => setFilters(e.filters)}>
<Column header={__('Timestamp', 'gepafin')}
filterField="createdDate" dataType="date"
style={{ minWidth: '8rem' }}
body={dateBodyTemplate}/>
<Column field="actionType" header={__('Tipo di attività', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column field="actionContext" header={__('Azione', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column field="ipAddress" header={__('IP', 'gepafin')}
filter sortable
filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '8rem' }}/>
</DataTable>
</div>
)
}
export default UserActivityTable;

View File

@@ -15,6 +15,8 @@ import getDateFromISOstring from '../../helpers/getDateFromISOstring';
import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';
import { Dropdown } from 'primereact/dropdown';
import UserActionService from '../../service/user-action-service';
import UserActivityTable from './components/UserActivityTable';
const UserActivity = () => {
@@ -25,6 +27,11 @@ const UserActivity = () => {
const [user, setUser] = useState({});
const [roles, setRoles] = useState([]);
const [chosenRole, setChosenRole] = useState(0);
const [actionsContext, setActionsContext] = useState([]);
const [userActions, setUserActions] = useState({});
const [chosenPeriod, setChosenPeriod] = useState('');
const [chosenActivity, setChosenActivity] = useState('');
const goBack = () => {
navigate(`/utenti`);
@@ -56,13 +63,13 @@ const UserActivity = () => {
}
const getStatValue = (key, fallback = 0) => {
return pathOr(fallback, [key], {});
return pathOr(fallback, [key], userActions);
}
const handleRoleUpdate = () => {
if (user.role?.id !== chosenRole) {
setLoading(true);
UserService.updateUser(user.id, {roleId: chosenRole}, updateRoleCallback, errUpdateRoleCallback)
UserService.updateUser(user.id, { roleId: chosenRole }, updateRoleCallback, errUpdateRoleCallback)
}
}
@@ -78,11 +85,50 @@ const UserActivity = () => {
setLoading(false);
}
const getActionsContextCallback = (resp) => {
if (resp.status === 'SUCCESS') {
setActionsContext(resp.data)
}
setLoading(false);
}
const errGetActionsContextCallback = (resp) => {
set404FromErrorResponse(resp);
setLoading(false);
}
const getUserActionsCallback = (resp) => {
if (resp.status === 'SUCCESS') {
setUserActions(resp.data)
}
setLoading(false);
}
const errGetUserActionsCallback = (resp) => {
set404FromErrorResponse(resp);
setLoading(false);
}
const doFilterUserActivity = () => {
let queryParams = [];
if (!isEmpty(chosenPeriod)) {
queryParams.push(['timeFilter', chosenPeriod])
}
if (!isEmpty(chosenActivity)) {
queryParams.push(['actionContext', chosenActivity])
}
UserActionService.getUserActions(id, getUserActionsCallback, errGetUserActionsCallback, queryParams);
}
useEffect(() => {
if (id && !isEmpty(id)) {
setLoading(true);
UserService.getUser(id, getUserCallback, errGetUserCallback);
UserService.getRoles(getRolesCallback, errGetRolesCallback);
UserActionService.getActionContext(id, getActionsContextCallback, errGetActionsContextCallback);
UserActionService.getUserActions(id, getUserActionsCallback, errGetUserActionsCallback);
}
}, [id])
@@ -143,7 +189,10 @@ const UserActivity = () => {
disabled={isEmpty(roles) || loading}
value={chosenRole}
onChange={(e) => setChosenRole(e.value)}
options={roles.filter(o => [3, 5].includes(o.id)).map(o => ({ label: o.roleName, value: o.id }))}
options={roles.filter(o => [3, 5].includes(o.id)).map(o => ({
label: o.roleName,
value: o.id
}))}
optionLabel="label"
placeholder={__('Seleziona ruolo', 'gepafin')}/>
<Button
@@ -159,32 +208,82 @@ const UserActivity = () => {
<div className="appPage__spacer"></div>
{/*<div className="appPageSection">
<h2>{__('Statistiche attività', 'gepafin')}</h2>
<div className="statsBigBadges__grid grid-small">
<div className="statsBigBadges__gridItem">
<span>{__('Login totali', 'gepafin')}</span>
<span>{<NumberFlow
value={getStatValue('numberOfActiveCalls', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/>}</span>
{!isEmpty(userActions)
? <div className="appPageSection">
<h2>{__('Statistiche attività', 'gepafin')}</h2>
<div className="statsBigBadges__grid grid-small">
<div className="statsBigBadges__gridItem">
<span>{__('Login totali', 'gepafin')}</span>
<span>{<NumberFlow
value={getStatValue('numberOfLoginAttempts', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/>}</span>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Bandi gestiti', 'gepafin')}</span>
<span>{<NumberFlow
value={getStatValue('applicationsProcessed', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/>}</span>
</div>
{/*<div className="statsBigBadges__gridItem">
<span>{__('Domande processate', 'gepafin')}</span>
<span>{<NumberFlow
value={getStatValue('numberOfActiveCalls', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/>}</span>
</div>*/}
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Bandi gestiti', 'gepafin')}</span>
<span>{<NumberFlow
value={getStatValue('numberOfActiveCalls', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/>}</span>
</div> : null}
<div className="appPage__spacer"></div>
{!isEmpty(userActions)
? <div className="appPageSection">
<h2>{__('Filtri attività', 'gepafin')}</h2>
<div className="appPageSection columns">
<div className="row">
<label>{__('Periodo', 'gepafin')}</label>
<Dropdown
value={chosenPeriod}
onChange={(e) => setChosenPeriod(e.value)}
options={[
{ value: 'LAST_WEEK', label: __('Ultima settimana', 'gepafin') },
{ value: 'LAST_QUARTER', label: __('Ultimo trimestre', 'gepafin') },
{ value: 'LAST_SEMESTER', label: __('Ultimo semestre', 'gepafin') },
{ value: 'LAST_YEAR', label: __('Ultimo anno', 'gepafin') }
]}
optionLabel="label"/>
</div>
{!isEmpty(actionsContext)
? <div className="row">
<label>{__('Tipo di attività', 'gepafin')}</label>
<Dropdown
className="fullWidth"
value={chosenActivity}
onChange={(e) => setChosenActivity(e.value)}
options={actionsContext.map(o => ({
value: o.actionContext,
label: o.description
}))}
optionLabel="label"/>
</div> : null}
<div className="row">
<Button
type="button"
onClick={doFilterUserActivity}
label={__('Applica', 'gepafin')}/>
</div>
</div>
<div className="statsBigBadges__gridItem">
<span>{__('Domande processate', 'gepafin')}</span>
<span>{<NumberFlow
value={getStatValue('numberOfActiveCalls', 0)}
format={{ notation: 'compact' }}
locales="it-IT"/>}</span>
</div>
</div>
</div>*/}
</div> : null}
<div className="appPage__spacer"></div>
{!isEmpty(userActions)
? <div className="appPageSection">
<h2>{__('Attività dettagliate', 'gepafin')}</h2>
<UserActivityTable data={userActions.userActions}/>
</div> : null}
</div>
)
}

View File

@@ -141,7 +141,7 @@ const AllUsersTable = () => {
return (
<div className="appPageSection__table">
<DataTable value={users} paginator showGridlines rows={10} loading={loading} dataKey="id"
<DataTable value={users} paginator showGridlines rows={5} loading={loading} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
emptyMessage={translationStrings.emptyMessage}

View File

@@ -41,6 +41,16 @@ import DomandeInstructorManager from './pages/DomandeInstructorManager';
import DomandaEditInstructorManager from './pages/DomandaEditInstructorManager';
import UserActivity from './pages/UserActivity';
import DomandeArchive from './pages/DomandeArchive';
import BandiPreInstructor from './pages/BandiPreInstructor';
import BandoViewPreInstructor from './pages/BandoViewPreInstructor';
import DomandeArchivePreInstructor from './pages/DomandeArchivePreInstructor';
import DashboardInstructorManager from './pages/DashboardInstructorManager';
import DomandeMieInstructorManager from './pages/DomandeMieInstructorManager';
import SoccorsoAddInstructorManager from './pages/SoccorsoAddInstructorManager';
import SoccorsoEditInstructorManager from './pages/SoccorsoEditInstructorManager';
import SoccorsoIstruttorioInstructorManager from './pages/SoccorsoIstruttorioInstructorManager';
import SoccorsoIstruttorioMioInstructorManager from './pages/SoccorsoIstruttorioMioInstructorManager';
import StatsBeneficiary from './pages/StatsBeneficiary';
const routes = ({ role, chosenCompanyId }) => {
@@ -51,19 +61,19 @@ const routes = ({ role, chosenCompanyId }) => {
{'ROLE_SUPER_ADMIN' === role ? <Dashboard/> : null}
{'ROLE_BENEFICIARY' === role ? <DashboardBeneficiario/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <DashboardPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DashboardPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DashboardInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/bandi" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <Bandi/> : null}
{'ROLE_BENEFICIARY' === role ? <BandiBeneficiario/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <BandiPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <BandiPreInstructor/> : null}
</DefaultLayout>}/>
<Route path="/bandi/:id" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <BandoEdit/> : null}
{'ROLE_BENEFICIARY' === role ? <BandoViewBeneficiario/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <BandoViewPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <BandoViewPreInstructor/> : null}
</DefaultLayout>}/>
<Route path="/bandi/:id/preview" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <BandoView/> : null}
@@ -117,7 +127,7 @@ const routes = ({ role, chosenCompanyId }) => {
{'ROLE_SUPER_ADMIN' === role ? <BandoApplicationPreview/> : null}
{'ROLE_BENEFICIARY' === role ? <SoccorsoEditBeneficiario/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <DomandaEditPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DomandaEditInstructorManager/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DomandaEditPreInstructor/> : null}
</DefaultLayout>}/>
<Route path="/domande/:id/preview" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <BandoApplicationPreview/> : null}
@@ -128,7 +138,7 @@ const routes = ({ role, chosenCompanyId }) => {
<Route path="/domande-archivio" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <DomandeArchive/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <DomandeArchive/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <DomandeArchivePreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DomandeArchive/> : null}
</DefaultLayout>}/>
<Route path="/domande-archivio/:id/preview" element={<DefaultLayout>
@@ -141,7 +151,7 @@ const routes = ({ role, chosenCompanyId }) => {
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <SoccorsoAddPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <SoccorsoAddPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <PageNotFound/> : null}
</DefaultLayout>}/>
<Route path="/domande/:id/soccorso/:amendmentId" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
@@ -153,7 +163,37 @@ const routes = ({ role, chosenCompanyId }) => {
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <SoccorsoIstruttorioPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <SoccorsoIstruttorioPreInstructor/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <SoccorsoIstruttorioInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/mie-domande" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DomandeMieInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/mie-domande/:id/" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <DomandaEditInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/mie-domande/:id/aggiungi-soccorso" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <SoccorsoAddInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/mie-domande/:id/soccorso/:amendmentId" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <SoccorsoEditInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/mio-soccorso-istruttorio/" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <PageNotFound/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <SoccorsoIstruttorioMioInstructorManager/> : null}
</DefaultLayout>}/>
<Route path="/imieibandi" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
@@ -197,6 +237,12 @@ const routes = ({ role, chosenCompanyId }) => {
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <PageNotFound/> : null}
</DefaultLayout>}/>
<Route path="/stats" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null}
{'ROLE_BENEFICIARY' === role ? <StatsBeneficiary/> : null}
{'ROLE_PRE_INSTRUCTOR' === role ? <PageNotFound/> : null}
{'ROLE_INSTRUCTOR_MANAGER' === role ? <PageNotFound/> : null}
</DefaultLayout>}/>
</Route>
<Route exact path="/reset-password" element={<ResetPassword/>}/>
<Route exact path="/login" element={<Login/>}/>

View File

@@ -16,8 +16,8 @@ export default class AmendmentsService {
NetworkService.get(`${API_BASE_URL}/amendments`, callback, errCallback, queryParams);
};
static getSoccorsoByPreInstructorId = (id, callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/amendments/user/${id}`, callback, errCallback, queryParams);
static getSoccorsi = (callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/amendments/user`, callback, errCallback, queryParams);
};
static createSoccorso = (body, callback, errCallback, queryParams) => {

View File

@@ -8,7 +8,21 @@ export default class ApplicationEvaluationService {
NetworkService.get(`${API_BASE_URL}/applicationEvaluation/application`, callback, errCallback, queryParams);
};
static getEvaluationV2ByApplId = (callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/applicationEvaluation/v2`, callback, errCallback, queryParams);
};
static getEvaluationVersionByApplId = (id, callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/applicationEvaluation/application/${id}/version`, callback, errCallback, queryParams);
};
static updateEvaluation = (assignedApplicationId, body, callback, errCallback, queryParams) => {
NetworkService.put(`${API_BASE_URL}/applicationEvaluation/${assignedApplicationId}`, body, callback, errCallback, queryParams);
};
static updateEvaluationV2 = (assignedApplicationId, formId, body, callback, errCallback, queryParams = []) => {
NetworkService.put(`${API_BASE_URL}/applicationEvaluation/v2/assignedApplication/${assignedApplicationId}`, body, callback, errCallback, [
['evaluationFormId', formId]
]);
};
}

Some files were not shown because too many files have changed in this diff Show More