diff --git a/package.json b/package.json index ccd2660..7edb643 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/assets/scss/components/appForm.scss b/src/assets/scss/components/appForm.scss index bb532bd..dad056e 100644 --- a/src/assets/scss/components/appForm.scss +++ b/src/assets/scss/components/appForm.scss @@ -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, diff --git a/src/assets/scss/components/formBuilder.scss b/src/assets/scss/components/formBuilder.scss index b07f322..00db384 100644 --- a/src/assets/scss/components/formBuilder.scss +++ b/src/assets/scss/components/formBuilder.scss @@ -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; diff --git a/src/assets/scss/components/layout.scss b/src/assets/scss/components/layout.scss index e350503..5b617be 100644 --- a/src/assets/scss/components/layout.scss +++ b/src/assets/scss/components/layout.scss @@ -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; + } } } diff --git a/src/assets/scss/components/misc.scss b/src/assets/scss/components/misc.scss index 162068e..030a350 100644 --- a/src/assets/scss/components/misc.scss +++ b/src/assets/scss/components/misc.scss @@ -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; } diff --git a/src/assets/scss/components/statsBigBadges.scss b/src/assets/scss/components/statsBigBadges.scss index 6cb3c0a..6c08cb7 100644 --- a/src/assets/scss/components/statsBigBadges.scss +++ b/src/assets/scss/components/statsBigBadges.scss @@ -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); + } } } diff --git a/src/components/ChartDomandePerBando/index.js b/src/components/ChartDomandePerBando/index.js index 700e6a7..b13d369 100644 --- a/src/components/ChartDomandePerBando/index.js +++ b/src/components/ChartDomandePerBando/index.js @@ -22,7 +22,10 @@ const ChartDomandePerBando = ({ title, data = [] }) => {

{label}

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

+

+ {__('Inviate', 'gepafin')}: {payload[1].value}

); @@ -50,7 +53,8 @@ const ChartDomandePerBando = ({ title, data = [] }) => { }/> - + + : null} diff --git a/src/components/ChartDomandePerStato/index.js b/src/components/ChartDomandePerStato/index.js new file mode 100644 index 0000000..a27c0e5 --- /dev/null +++ b/src/components/ChartDomandePerStato/index.js @@ -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 ( +
+

{getBandoLabel(payload[0].name)}

+

+ {payload[0].name}: {payload[0].value} +

+
+ ); + } + 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 (
+ {title ? {title} : null} + {chartData && !isEmpty(chartData) + ?
+ + + `${(percent * 100).toFixed(0)}%`} + outerRadius={120} + fill="#8884d8" + dataKey="value" + nameKey="label" + > + {Object.values(chartData).map((entry, index) => ( + + ))} + + } /> + + + +
: null} +
) +} + +export default ChartDomandePerStato; \ No newline at end of file diff --git a/src/components/ChartRichiesteVsApprovate/index.js b/src/components/ChartRichiesteVsApprovate/index.js new file mode 100644 index 0000000..e693b45 --- /dev/null +++ b/src/components/ChartRichiesteVsApprovate/index.js @@ -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 ( +
+

{label}

+

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

+

+ {__('Inviate', 'gepafin')}: {payload[1].value} +

+
+ ); + } + return null; + }; + + return (
+ {title ? {title} : null} + {data && !isEmpty(data) + ?
+ + + + + + }/> + + + + + +
: null} +
) +} + +export default ChartRichiesteVsApprovate; \ No newline at end of file diff --git a/src/components/ChartStatoDomande/index.js b/src/components/ChartStatoDomande/index.js index 856fd6c..e5a9ddf 100644 --- a/src/components/ChartStatoDomande/index.js +++ b/src/components/ChartStatoDomande/index.js @@ -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) => ( diff --git a/src/components/FormField/components/CriteriaTable/RenderTable/components/DefaultCell/index.js b/src/components/FormField/components/CriteriaTable/RenderTable/components/DefaultCell/index.js new file mode 100644 index 0000000..0df7324 --- /dev/null +++ b/src/components/FormField/components/CriteriaTable/RenderTable/components/DefaultCell/index.js @@ -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 ( + table.options.meta?.updateData(index, id, e.target.value)} /> + ); +}; + +export default DefaultCell; \ No newline at end of file diff --git a/src/components/FormField/components/CriteriaTable/RenderTable/components/LastRowCell/index.js b/src/components/FormField/components/CriteriaTable/RenderTable/components/LastRowCell/index.js new file mode 100644 index 0000000..73d987c --- /dev/null +++ b/src/components/FormField/components/CriteriaTable/RenderTable/components/LastRowCell/index.js @@ -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 {is(Number, cellValue) ? getNumberFormatted(cellValue) : cellValue}; +}; + +export default LastRowCell; \ No newline at end of file diff --git a/src/components/FormField/components/CriteriaTable/RenderTable/components/NumericFormulaCell/index.js b/src/components/FormField/components/CriteriaTable/RenderTable/components/NumericFormulaCell/index.js new file mode 100644 index 0000000..75f3b3a --- /dev/null +++ b/src/components/FormField/components/CriteriaTable/RenderTable/components/NumericFormulaCell/index.js @@ -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 ( + onChange(e.value)} + onFocus={onFocus} + minFractionDigits={0} + maxFractionDigits={2} + locale='it-IT' + useGrouping={false} + showButtons + /> + ); +}; + +export default NumericFormulaCell; \ No newline at end of file diff --git a/src/components/FormField/components/CriteriaTable/RenderTable/index.js b/src/components/FormField/components/CriteriaTable/RenderTable/index.js new file mode 100644 index 0000000..e888433 --- /dev/null +++ b/src/components/FormField/components/CriteriaTable/RenderTable/index.js @@ -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.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + ); + })} + + {!isEmpty(lastRowCfg) + ? + {columnsCfg.map((o) => )} + + : null} +
+ {header.isPlaceholder ? null : ( + <> + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+ ) +} + +export default RenderTable \ No newline at end of file diff --git a/src/components/FormField/components/CriteriaTable/index.js b/src/components/FormField/components/CriteriaTable/index.js new file mode 100644 index 0000000..f965321 --- /dev/null +++ b/src/components/FormField/components/CriteriaTable/index.js @@ -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 ( + <> + + {tableValue + ? : null} + ) +} + +export default Table; \ No newline at end of file diff --git a/src/components/FormField/components/NumberInput/index.js b/src/components/FormField/components/NumberInput/index.js index 19fc28c..0fec4ce 100644 --- a/src/components/FormField/components/NumberInput/index.js +++ b/src/components/FormField/components/NumberInput/index.js @@ -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 }) => ( 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} diff --git a/src/components/FormField/components/Table/RenderTable/components/DefaultCell/index.js b/src/components/FormField/components/Table/RenderTable/components/DefaultCell/index.js index 6351f92..0df7324 100644 --- a/src/components/FormField/components/Table/RenderTable/components/DefaultCell/index.js +++ b/src/components/FormField/components/Table/RenderTable/components/DefaultCell/index.js @@ -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 ( - 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)} /> ); }; diff --git a/src/components/FormField/components/Table/RenderTable/components/LastRowCell/index.js b/src/components/FormField/components/Table/RenderTable/components/LastRowCell/index.js index 3a35e77..4a62335 100644 --- a/src/components/FormField/components/Table/RenderTable/components/LastRowCell/index.js +++ b/src/components/FormField/components/Table/RenderTable/components/LastRowCell/index.js @@ -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; } diff --git a/src/components/FormField/components/Table/RenderTable/components/NumericFormulaCell/index.js b/src/components/FormField/components/Table/RenderTable/components/NumericFormulaCell/index.js index 58a6f25..75f3b3a 100644 --- a/src/components/FormField/components/Table/RenderTable/components/NumericFormulaCell/index.js +++ b/src/components/FormField/components/Table/RenderTable/components/NumericFormulaCell/index.js @@ -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 ( - 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 /> ); }; diff --git a/src/components/FormField/components/Table/RenderTable/index.js b/src/components/FormField/components/Table/RenderTable/index.js index 604a453..f376913 100644 --- a/src/components/FormField/components/Table/RenderTable/index.js +++ b/src/components/FormField/components/Table/RenderTable/index.js @@ -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 }) => { ); })} - {!isEmpty(lastRow) - ? - {columns.map((o) => + {!isEmpty(lastRowCfg) + ? + {columnsCfg.map((o) => )} - + : null} - ) } diff --git a/src/components/FormField/components/Table/index.js b/src/components/FormField/components/Table/index.js index 8ebd19b..62d71de 100644 --- a/src/components/FormField/components/Table/index.js +++ b/src/components/FormField/components/Table/index.js @@ -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) ? * : null} - {rows ? : null} {!isEmpty(columns) && !shouldDisableNewRows - ?
+ ?
{__('Aggiungi una riga', 'gepafin')}
: null} diff --git a/src/components/FormField/index.js b/src/components/FormField/index.js index c1559a7..6a60658 100644 --- a/src/components/FormField/index.js +++ b/src/components/FormField/index.js @@ -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; diff --git a/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js b/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js index 4588de6..24ecc1e 100644 --- a/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js +++ b/src/components/NotificationsSidebar/components/NotificationItemChosen/index.js @@ -17,12 +17,13 @@ const NotificationItemChosen = ({ item, closeFn, markReadFn }) => { {getDateFromISOstring(item.createdDate)} {item.message} -
) } diff --git a/src/components/NotificationsSidebar/index.js b/src/components/NotificationsSidebar/index.js index f44a5e5..91555de 100644 --- a/src/components/NotificationsSidebar/index.js +++ b/src/components/NotificationsSidebar/index.js @@ -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 = () => { <> setNotificationsVisible(true)}> - o.status === 'UNREAD').length}> + { closeFn={closeChosenMsg} markReadFn={makeNotificationRead}/> : (notifications.length > 0 - ?
    - {notifications.map(o => )} -
+ ? <> +
    + {notifications.map(o => )} +
+ + :
{__('Vuoto', 'gepafin')} @@ -256,17 +330,24 @@ const NotificationsSidebar = () => { closeFn={closeChosenMsg} markReadFn={makeNotificationRead}/> : (notificationsRead.length > 0 - ?
    - {notificationsRead.map(o => )} -
- : -
- - {__('Vuoto', 'gepafin')} -
)} + ? <> +
    + {notificationsRead.map(o => )} +
+ + + : +
+ + {__('Vuoto', 'gepafin')} +
)} diff --git a/src/components/PaginatorBasic/index.js b/src/components/PaginatorBasic/index.js new file mode 100644 index 0000000..9c0c833 --- /dev/null +++ b/src/components/PaginatorBasic/index.js @@ -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 + ?
+ + + ({currentPage} {__('di', 'gepafin')} {totalPages}) + + +
: null + ) +} + +export default PaginatorBasic; \ No newline at end of file diff --git a/src/helpers/getBandoLabel.js b/src/helpers/getBandoLabel.js index 1816a42..7cbf3bf 100644 --- a/src/helpers/getBandoLabel.js +++ b/src/helpers/getBandoLabel.js @@ -59,6 +59,9 @@ const getBandoLabel = (status) => { case 'CLOSE': return __('Chiuso', 'gepafin'); + case 'REJECTED': + return __('Respinto', 'gepafin'); + default: return ''; } diff --git a/src/helpers/getBandoSeverity.js b/src/helpers/getBandoSeverity.js index d6c4dbf..03b8704 100644 --- a/src/helpers/getBandoSeverity.js +++ b/src/helpers/getBandoSeverity.js @@ -57,6 +57,9 @@ const getBandoSeverity = (status) => { case 'CLOSE': return 'closed'; + case 'REJECTED': + return 'danger'; + default: return 'info'; } diff --git a/src/helpers/getTokens.js b/src/helpers/getTokens.js new file mode 100644 index 0000000..81d40c3 --- /dev/null +++ b/src/helpers/getTokens.js @@ -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; \ No newline at end of file diff --git a/src/helpers/isDateTimeInFuture.js b/src/helpers/isDateTimeInFuture.js new file mode 100644 index 0000000..4c24c09 --- /dev/null +++ b/src/helpers/isDateTimeInFuture.js @@ -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; \ No newline at end of file diff --git a/src/helpers/keepKeys.js b/src/helpers/keepKeys.js new file mode 100644 index 0000000..170bbe6 --- /dev/null +++ b/src/helpers/keepKeys.js @@ -0,0 +1,3 @@ +const keepKeys = (arr, keys) => arr.map(obj => Object.fromEntries(keys.map(k => [k, obj[k]]))); + +export default keepKeys; \ No newline at end of file diff --git a/src/helpers/parseCommaDecimal.js b/src/helpers/parseCommaDecimal.js new file mode 100644 index 0000000..fe1c8d8 --- /dev/null +++ b/src/helpers/parseCommaDecimal.js @@ -0,0 +1,3 @@ +const parseCommaDecimal = (value = '') => parseFloat(String(value).replace(',', '.')); + +export default parseCommaDecimal; \ No newline at end of file diff --git a/src/helpers/removeKey.js b/src/helpers/removeKey.js new file mode 100644 index 0000000..fc591a9 --- /dev/null +++ b/src/helpers/removeKey.js @@ -0,0 +1,3 @@ +const removeKey = (arr, key) => arr.map(({[key]: _, ...rest}) => rest); + +export default removeKey; \ No newline at end of file diff --git a/src/helpers/renderWithDataVars.js b/src/helpers/renderWithDataVars.js new file mode 100644 index 0000000..ffeb4dc --- /dev/null +++ b/src/helpers/renderWithDataVars.js @@ -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; diff --git a/src/layouts/DefaultLayout/components/AppSidebar/index.js b/src/layouts/DefaultLayout/components/AppSidebar/index.js index 5c81b8f..c449022 100644 --- a/src/layouts/DefaultLayout/components/AppSidebar/index.js +++ b/src/layouts/DefaultLayout/components/AppSidebar/index.js @@ -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: , + 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: , - 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 =>
  • - {o.href - ? - {is(String, o.icon) - ? - : o.icon} - {o.label} - - : } -
  • )} + {o.href + ? + {is(String, o.icon) + ? + : o.icon} + {o.label} + + : (o.clickFn ? + + :
    + {o.label} +
    )} + )} } diff --git a/src/pages/Bandi/components/AllBandiTable/index.js b/src/pages/Bandi/components/AllBandiTable/index.js index 9bbc70f..14e457b 100644 --- a/src/pages/Bandi/components/AllBandiTable/index.js +++ b/src/pages/Bandi/components/AllBandiTable/index.js @@ -121,7 +121,7 @@ const AllBandiTable = () => { return(
    - { const rowExpansionTemplate = (data) => { const isCallExpired = isDateTimeInPast(data.dates[1], data.endTime); + const isCallScheduled = isDateTimeInFuture(data.dates[0], data.startTime); + return (
    {renderHtmlContent(data.descriptionShort)}

    {__('Scadenza', 'gepafin')}: {getDateFromISOstring(data.dates[1])}

    - {!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')) ? : null} {isCallExpired - ?

    {__('È scaduto', 'gepafin')}

    : null} - {isCallExpired || (!isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && data.confidi + ?

    : null} + {isCallScheduled + ?

    : null} + {isCallExpired || isCallScheduled || (!isEmpty(chosenCompanyId) && chosenCompanyId !== 0 && data.confidi && (data.id !== 6 || (data.id === 6 && REACT_APP_HUB_ID !== 'p4lk3bcx1RStqTaIVVbXs'))) ?
    + ); + }; + + /*const nameBodyTemplate = (rowData) => { + return {rowData.name} + }*/ + + const dateStartBodyTemplate = (rowData) => { + return getDateFromISOstring(rowData.dates[0]); + }; + + const dateEndBodyTemplate = (rowData) => { + return getDateFromISOstring(rowData.dates[1]); + }; + + const dateFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />; + }; + + const statusBodyTemplate = (rowData) => { + return ; + }; + + const statusFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} + itemTemplate={statusItemTemplate} + placeholder={translationStrings.selectOneLabel} + className="p-column-filter" + showClear />; + }; + + const statusItemTemplate = (option) => { + return ; + }; + + const actionsBodyTemplate = (rowData) => { + return +
    : null} - {'DRAFT' !== applicationStatus + {'AWAITING' === applicationStatus ?
    : null} - {'DRAFT' !== applicationStatus + {['AWAITING', 'READY'].includes(applicationStatus) ?
    + 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 = () => {
    : {}; + 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 />
    +
    + + + {APP_HUB_ID !== 't7jh5wfg9QXylNaTZkPoE' + ? : null} +
    + { @@ -221,7 +225,8 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, setInitialData, g return (
    - isEmpty(o.value) || isEmpty(o.score)).length === 0 || __('Non lasciare il valore vuoto', 'gepafin') } - }}/> + }}/> : null} - isEmpty(o.value)).length === 0 || __('Non lasciare il valore vuoto', 'gepafin') } }} - /> + /> : null}
    diff --git a/src/pages/BandoEdit/components/BandoEditFormStep3/index.js b/src/pages/BandoEdit/components/BandoEditFormStep3/index.js new file mode 100644 index 0000000..b67b93f --- /dev/null +++ b/src/pages/BandoEdit/components/BandoEditFormStep3/index.js @@ -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 ( +
    +
    + + + +
    + +
    + +
    + {__('Azioni', 'gepafin')} +
    + + + +
    + ) +}) + +export default BandoEditFormStep3; \ No newline at end of file diff --git a/src/pages/BandoEdit/index.js b/src/pages/BandoEdit/index.js index f9b0951..275fd61 100644 --- a/src/pages/BandoEdit/index.js +++ b/src/pages/BandoEdit/index.js @@ -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) ? : null} @@ -293,6 +312,9 @@ const BandoEdit = () => { {activeStep === 1 ? : null} + {activeStep === 2 && data.evaluationVersion === 'V2' + ? + : null}

    {__('Crea o modifica il Form compilabile dal Beneficiario', 'gepafin')}

    diff --git a/src/pages/BandoFlowEdit/index.js b/src/pages/BandoFlowEdit/index.js index af64d22..eee89f4 100644 --- a/src/pages/BandoFlowEdit/index.js +++ b/src/pages/BandoFlowEdit/index.js @@ -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]); diff --git a/src/pages/BandoFormsEdit/components/BuilderElement/index.js b/src/pages/BandoFormsEdit/components/BuilderElement/index.js index 8a7b86e..d0acc67 100644 --- a/src/pages/BandoFormsEdit/components/BuilderElement/index.js +++ b/src/pages/BandoFormsEdit/components/BuilderElement/index.js @@ -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 ?
    {__('lascia qui', 'gepafin')}
    - :
    + :
    - +
    + + {['numberinput', 'criteria_table'].includes(name) + ? : null} + {name === 'numberinput' + ? : null} + {isRequestedAmount + ? : null} + {isDelegation + ? : null} +
    -
    ) diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js index 67c1bf0..0795c5a 100644 --- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js +++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js @@ -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: +, -, *, /. Esempio: {entrate}+{assicurazione}.', '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 { name={setting.name} bandoStatus={bandoStatus} setDataFn={updateDataFn}/> - } else if (['isRequestedAmount', 'isDelegation'].includes(setting.name)) { + } else if (setting.name === 'criteria_table_columns') { + return + } else if (['isRequestedAmount', 'isDelegation', 'isChecklistItem'].includes(setting.name)) { return changeFn(e.value, setting.name)}/> + } else if (['variable'].includes(setting.name)) { + return changeFn(value, setting.name)} + value={setting.value}/> } else { return { } } + 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
    {getProperField(setting)} + {setting.name === 'formula' && !isEmpty(existingVars) + ?
    +

    Existing variables: {existingVars.map(v => {`{${v}}`})}

    +
    : null} + {settingDescription[setting.name] + ?
    +

    {renderHtmlContent(settingDescription[setting.name])}

    +
    : null}
    } diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingChips/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingChips/index.js new file mode 100644 index 0000000..4ea1149 --- /dev/null +++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingChips/index.js @@ -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 ( +
    + +
    + ); +} + +export default ElementSettingChips; \ No newline at end of file diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingCriteriaTableColumns/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingCriteriaTableColumns/index.js new file mode 100644 index 0000000..333d8b9 --- /dev/null +++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingCriteriaTableColumns/index.js @@ -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 <> +
    + onInputChange(e, i)}/> +
    +
    + onTypeChange(e.value, i)} + options={[ + { value: 'text', label: __('Testo', 'gepafin') }, + { value: 'numeric', label: __('Numerico', 'gepafin') } + ]}/> +
    +
    + +
    +
    + setChecked(e.value, i)}/> +
    +
    +
    + + } + + const properSubField = (item, i, name) => { + return <> +
    + onSubInputChange(e, name, i)}/> +
    +
    +
    + + } + + const properFieldsLastRow = useCallback((item, i) => { + return <> +
    + {sprintf(__('Colonna %d'), i + 1)} +
    + {item.enableFormula + ?
    + onLastRowFormulaChange(e.value, i)} + options={[ + { value: 'sum', label: __('Somma automatica', 'gepafin') } + ]}/> +
    + :
    + onLastRowInputChange(e, i)}/> +
    } + + }, [stateFieldData]); + + const lastRow =
    +
    +
    + {__('Definisci ultima righa', 'gepafin')} +
    +
    + {stateFieldData.map((o, i) =>
    + {properFieldsLastRow(o, i)} +
    )} +
    ; + + useEffect(() => { + const stateFieldData = pathOr([], ['stateFieldData'], value); + setStateFieldData(stateFieldData); + const rowsData = pathOr([], ['rowsData'], value); + setRowsData(rowsData); + }, []); + + useEffect(() => { + setDataFn(name, { + stateFieldData, + rowsData + }); + }, [stateFieldData, rowsData]); + + return ( + <> +
    + {stateFieldData.length > 0 + ?
    +
    {__('Colonne', 'gepafin')}
    +
    {__('Tipo', 'gepafin')}
    +
    {__('Calcola', 'gepafin')}
    +
    {__('Predefinito?', 'gepafin')}
    +
    +
    : null} + {stateFieldData.map((o, i) =>
    + {properFields(o, i)} +
    )} +
    + {stateFieldData + .filter(o => o.predefined).length > 0 + ?
    + + {stateFieldData + //.filter(o => o.predefined) + .map((o, i) => + o.predefined + ? +
    + {rowsData.map((c, k) => { + return
    + {properSubField(c, k, o.name)} +
    + })} +
    +
    : null)} +
    +
    + : null} + {lastRow} + + ) +} + +export default ElementSettingCriteriaTableColumns; \ No newline at end of file diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns-old/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns-old/index.js deleted file mode 100644 index ee5d9aa..0000000 --- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns-old/index.js +++ /dev/null @@ -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 <> - onInputChange(e, i)}/> -
    - {__('Predefinito?', 'gepafin')} - setChecked(e.value, i)}/> -
    - - } - - const properSubField = (item, i, name) => { - return 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 ( - <> -
    - {stateFieldData.map((o, i) =>
    -
    - {properField(o, i)} -
    -
    )} -
    - {stateFieldData - .filter(o => o.predefined) - .map((o, i) =>
    -
    - -
    - {rowsData.map((c, k) => { - return
    -
    - {properSubField(c, k, o.name)} -
    -
    - })} -
    -
    -
    )} - - ) -} - -export default ElementSettingTableColumns; \ No newline at end of file diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns/index.js index 1806e6d..8ecb228 100644 --- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns/index.js +++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingTableColumns/index.js @@ -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 = ({
    onTypeChange(e.value, i)} options={[ @@ -168,6 +171,7 @@ const ElementSettingTableColumns = ({
    @@ -212,6 +217,7 @@ const ElementSettingTableColumns = ({ {item.enableFormula ?
    onLastRowFormulaChange(e.value, i)} options={[ @@ -232,6 +238,7 @@ const ElementSettingTableColumns = ({ {__('Definisci ultima righa', 'gepafin')}
    {stateFieldData .filter(o => o.predefined).length > 0 diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js index 3e4f66a..d7fc321 100644 --- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js +++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/index.js @@ -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 }) => { {settings - ? settings.map((o) => ) + ? settings + .filter(o => !['variable', 'formula'].includes(o.name)) + .map((o) => ) : null} - {!isNil(dynamicDataOptions[activeElementData.name]) + {!isNil(dynamicDataOptions[activeElementData.name]) && context === 'application' ?
    {
    : null} ) : null}
    : null} - + {context === 'application' + ?
    { display="chip" placeholder={__('Scegli', 'gepafin')}/>
    -
    +
    : null} + {settings + && settings + .filter(o => ['variable', 'formula'].includes(o.name)).length > 0 + ? + {settings + ? settings + .filter(o => ['variable', 'formula'].includes(o.name)) + .map((o) => ) + : null} + : null}
    - + + + + + + + + + + + + ); + }; + + 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 {__('Richiesta di conferma', 'gepafin')}; + } + + const hideConfirmDialog = () => { + setIsVisibleConfirmDialog(false); + } + + const footerConfirmDialog = () => { + return
    +
    + } + + const doConfirm = () => { + setIsVisibleConfirmDialog(false); + doCreate(); + } + + return ( +
    +
    +

    {__('Richiesta Integrazione Documentale', 'gepafin')}

    +
    + +
    + + + +
    +
    + +
    + + {!isAsyncRequest && !isEmpty(data) + ?
    +
    +

    + {__('ID domanda', 'gepafin')} + {data.applicationId} +

    +

    + {__('Bando', 'gepafin')} + {data.callName} +

    +

    + {__('Referente Aziendale', 'gepafin')} + {data.beneficiaryName} +

    +
    + +
    +
    +
    +

    {__('Pec/Email', 'gepafin')}

    +
    + updateEvaluationValue( + e.htmlValue, + 'note' + )} + style={{ height: 80 * 3, width: '100%' }} + /> +
    + +

    {__('Tempo per la Risposta (giorni)', 'gepafin')}

    +
    + updateEvaluationValue( + e.value, + 'responseDays', + 9999 + )}/> +
    + +

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

    +
    +
    + updateEvaluationValue( + e.value, + 'isSendEmail' + )}/> + +
    +
    + updateEvaluationValue( + e.value, + 'isSendNotification' + )}/> + +
    +
    +
    + {formData.formFields + ?
    +

    {__('Documenti da Integrare', 'gepafin')}

    +
    +
    + {formData.formFields.map((o, i) =>
    + updateEvaluationValue( + e.checked, + `formFields.${i}.selected` + )} + checked={o.selected}> + +
    )} +
    +
    +
    + : null} +
    +
    + +
    + + {__('Attenzione', 'gepafin')} + {__("L'invio della richiesta di integrazione sospenderà il termine di valutazione della domanda.", 'gepafin')} +
    + +
    + {__('Azioni', 'gepafin')} +
    + +
    +
    +
    +
    + + +
    +

    {__('Soccorso istruttorio autorizzato dal direttore e autorizzazione caricata su portale a seguito del quale parte l\'email?', 'gepafin')}

    +
    +
    + +
    + : <> + + + + + + + + + } +
    + ) + +} + +export default SoccorsoAddInstructorManager; diff --git a/src/pages/SoccorsoEditInstructorManager/index.js b/src/pages/SoccorsoEditInstructorManager/index.js new file mode 100644 index 0000000..6726001 --- /dev/null +++ b/src/pages/SoccorsoEditInstructorManager/index.js @@ -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 ( + + + + + + + + + + + + + + ); + }; + + 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 {__('Chiudi Soccorso Istruttorio', 'gepafin')} + } + + const hideCloseAmendDialog = () => { + setIsVisibleCloseAmendDialog(false); + } + + const footerCloseAmendDialog = () => { + return
    +
    + } + + 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 {__('Estendi scadenza', 'gepafin')} + } + + const hideExtendRespDialog = () => { + setIsVisibleExtendTimeDialog(false); + } + + const footerExtendRespDialog = () => { + return
    +
    + } + + 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 ( +
    +
    +

    {__('Soccorso Istruttorio - Dettagli', 'gepafin')}

    +
    + +
    + + + +
    +
    + +
    + +
    +
    +

    + {__('ID domanda', 'gepafin')} + {data.applicationId} +

    +

    + {__('Bando', 'gepafin')} + {data.callName} +

    +

    + {__('Referente Aziendale', 'gepafin')} + {data.beneficiaryName} +

    +

    + {__('Inizio', 'gepafin')} + {getDateFromISOstring(data.startDate)} +

    +

    + {__('Scadenza', 'gepafin')} + {getDateFromISOstring(data.expirationDate)} +

    +

    + {__('Stato', 'gepafin')} + {getBandoLabel(data.status)} +

    +
    + +
    +

    {__('Dettagli richiesta', 'gepafin')}

    +

    {__('Note e spiegazioni', 'gepafin')}

    +
    {getEmailTemplateForSoccorso(data.emailTemplate, data.note)}
    +
    +
    +

    {__('Documenti richiesti', 'gepafin')}

    +
      + {data.formFields + ? data.formFields.map((o, i) =>
    1. + {o.label} +
    2. ) : null} +
    +
    + +
    +

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

    + +
    + + {data.formFields && !isEmpty(data.formFields) + ?
    +

    {__('Documenti Ricevuti', 'gepafin')}

    + +
    + {data.formFields.map((o, i) => { + return + })} + +
    : null} + +
    +

    {__('Documenti aggiuntivi', 'gepafin')}

    +
    +

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

    +
    + + updateNewAmendmentData( + e.htmlValue, + 'amendmentNotes' + )} + style={{ height: 80 * 3, width: '100%' }} + /> +
    + +
    +
    + +
    + +
    + {__('Azioni', 'gepafin')} +
    + +
    +
    +
    +
    + +
    + + +
    + + setExtendedTime(e.value)}/> +
    +
    + + +
    + +
    + + setInternalNote(e.htmlValue)} + style={{ height: 80 * 3, width: '100%' }} + /> +
    +
    +
    +
    + ) + +} + +export default SoccorsoEditInstructorManager; diff --git a/src/pages/SoccorsoIstruttorioInstructorManager/index.js b/src/pages/SoccorsoIstruttorioInstructorManager/index.js new file mode 100644 index 0000000..c4ef886 --- /dev/null +++ b/src/pages/SoccorsoIstruttorioInstructorManager/index.js @@ -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( +
    +
    +

    {__('Soccorso istruttorio panoramica', 'gepafin')}

    +
    + +
    + +
    +

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

    +
    +
    + {__('Totale richieste', 'gepafin')} + +
    +
    + {__('In attesa risposta', 'gepafin')} + +
    +
    + {__('Risposte ricevute', 'gepafin')} + +
    +
    + {__('Tempo medio di risposta', 'gepafin')} + +
    +
    + {__('Scadute', 'gepafin')} + +
    +
    + {__('Richieste in scadenza (48h)', 'gepafin')} + +
    +
    +
    + +
    + +
    + +
    +
    + ) +} + +export default SoccorsoIstruttorioInstructorManager; \ No newline at end of file diff --git a/src/pages/SoccorsoIstruttorioMioInstructorManager/components/InstructorManagerSoccorsiTable/index.js b/src/pages/SoccorsoIstruttorioMioInstructorManager/components/InstructorManagerSoccorsiTable/index.js new file mode 100644 index 0000000..5bbb927 --- /dev/null +++ b/src/pages/SoccorsoIstruttorioMioInstructorManager/components/InstructorManagerSoccorsiTable/index.js @@ -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 ( +
    +
    + ); + }; + + const dateStartBodyTemplate = (rowData) => { + return formatDate(rowData.startDate); + }; + + const dateExpirationBodyTemplate = (rowData) => { + return rowData.evaluationEndDate ? formatDate(rowData.evaluationEndDate) : ''; + }; + + const dateFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />; + }; + + const statusBodyTemplate = (rowData) => { + return ; + }; + + const statusFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} itemTemplate={statusItemTemplate} placeholder={translationStrings.selectOneLabel} className="p-column-filter" showClear />; + }; + + const statusItemTemplate = (option) => { + return ; + }; + + const actionsBodyTemplate = (rowData) => { + return +