- styled asteriks sign;

- fixed issue with validation on registration page;
- fixed issue with inputnumber;
- fixed issue with editor field;;
- added editors for new faq item form;
- fixed displaying html as simple text;
- fixed saving company data after saving;
- added toast for edit bando form;
- improved edit forms form;
- fixed styles for various elements;
This commit is contained in:
Vitalii Kiiko
2024-10-04 11:31:47 +02:00
parent af52610b30
commit 7804a67fd2
36 changed files with 520 additions and 637 deletions

View File

@@ -4,6 +4,7 @@
gap: 24px; gap: 24px;
} }
.appForm__field { .appForm__field {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 14px;
@@ -58,7 +59,56 @@
cursor: pointer; cursor: pointer;
} }
} }
:where(table) {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
text-indent: 0;
border-right: 1px solid var(--table-border-color);
} }
td,
th {
padding: 10px;
border-top: 1px solid var(--table-border-color);
border-bottom: 1px solid var(--table-border-color);
border-left: 1px solid var(--table-border-color);
background-color: white;
color: var(--global-textColor);
font-size: 15px;
text-align: left;
text-align: start;
}
th {
padding: 15px 10px;
font-weight: bold;
}
td {
input {
width: 100%;
padding: 3px 5px;
}
}
tfoot td,
tfoot th {
border-top: 1px solid var(--table-border-color);
border-bottom: 0
}
table.striped tbody tr:nth-child(odd) td,
table.striped tbody tr:nth-child(odd) th {
background-color: var(--table-border-color)
}
}
}
.appForm__field--required.appForm__field--required {
margin-left: 2px;
color: var(--message-error-color);
} }
.appForm__fieldItem { .appForm__fieldItem {

View File

@@ -218,6 +218,16 @@
width: 100%; width: 100%;
} }
.appPageSection__titleClickable {
width: 100%;
height: 100%;
display: block;
&:hover {
cursor: pointer;
}
}
.appTableHeader { .appTableHeader {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@@ -104,47 +104,3 @@ body {
} }
} }
} }
:where(table) {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
text-indent: 0;
border-right: 1px solid var(--table-border-color);
}
td,
th {
padding: 10px;
border-top: 1px solid var(--table-border-color);
border-bottom: 1px solid var(--table-border-color);
border-left: 1px solid var(--table-border-color);
background-color: white;
color: var(--global-textColor);
font-size: 15px;
text-align: left;
text-align: start;
}
th {
padding: 15px 10px;
font-weight: bold;
}
td {
input {
width: 100%;
padding: 3px 5px;
}
}
tfoot td,
tfoot th {
border-top: 1px solid var(--table-border-color);
border-bottom: 0
}
table.striped tbody tr:nth-child(odd) td,
table.striped tbody tr:nth-child(odd) th {
background-color: var(--table-border-color)
}

View File

@@ -110,6 +110,12 @@
align-items: center; align-items: center;
} }
.p-accordion-header-text, .p-accordion-content {
p {
margin: 0;
}
}
.flex-1 { .flex-1 {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -65,7 +65,7 @@ const Checkboxes = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
{input} {input}
{infoText ? <small>{infoText}</small> : null} {infoText ? <small>{infoText}</small> : null}

View File

@@ -20,7 +20,7 @@ const Datepicker = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
<Controller <Controller
name={fieldName} name={fieldName}

View File

@@ -1,13 +1,16 @@
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { classNames } from 'primereact/utils'; import { classNames } from 'primereact/utils';
import { head } from 'ramda'
import { __ } from '@wordpress/i18n';
import FileUploadService from '../../../../service/file-upload-service'; import FileUploadService from '../../../../service/file-upload-service';
import { FileUpload } from 'primereact/fileupload'; import { FileUpload } from 'primereact/fileupload';
import { __ } from '@wordpress/i18n';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { mimeTypes } from '../../../../configData';
const Fileupload = ({ const Fileupload = ({
fieldName, fieldName,
setDataFn, setDataFn,
@@ -17,7 +20,7 @@ const Fileupload = ({
defaultValue = [], defaultValue = [],
config = {}, config = {},
infoText = null, infoText = null,
accept = 'image/*', accept = ['image/*'],
doctype = 'images', doctype = 'images',
maxSize = 100000000, maxSize = 100000000,
emptyText = __('Trascina qui il tuo file', 'gepafin'), emptyText = __('Trascina qui il tuo file', 'gepafin'),
@@ -119,6 +122,21 @@ const Fileupload = ({
}); });
} }
const getPropeMimeLabels = (acceptFormats) => {
return acceptFormats
.map(v => {
const found = head(mimeTypes.filter(o => o.code === v));
let res = v;
if (found) {
res = found.name;
}
return res;
})
.join(', ');
}
useEffect(() => { useEffect(() => {
setStateFieldData(defaultValue); setStateFieldData(defaultValue);
register(fieldName, config) register(fieldName, config)
@@ -126,7 +144,7 @@ const Fileupload = ({
useEffect(() => { useEffect(() => {
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
setAcceptFormats(accept.replace(/\*/g, '.\*').replace(/,/g, '|')); setAcceptFormats(accept.join(',').replace(/\*/g, '.\*').replace(/,/g, '|'));
}, [accept]); }, [accept]);
useEffect(() => { useEffect(() => {
@@ -140,8 +158,8 @@ const Fileupload = ({
sourceId || sourceId === 0 sourceId || sourceId === 0
? <> ? <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
{acceptFormats ? ' (' + acceptFormats.split('|').join(', ') + ')' : null} {acceptFormats ? ' (' + getPropeMimeLabels(accept) + ')' : null}
</label> </label>
<FileUpload <FileUpload
ref={inputRef} ref={inputRef}

View File

@@ -7,6 +7,8 @@ import FileUploadService from '../../../../service/file-upload-service';
import { FileUpload } from 'primereact/fileupload'; import { FileUpload } from 'primereact/fileupload';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { head } from 'ramda';
import { mimeTypes } from '../../../../configData';
const FileuploadAsync = ({ const FileuploadAsync = ({
fieldName, fieldName,
@@ -17,7 +19,7 @@ const FileuploadAsync = ({
defaultValue = [], defaultValue = [],
config = {}, config = {},
infoText = null, infoText = null,
accept = 'image/*', accept = ['image/*'],
doctype = 'images', doctype = 'images',
maxSize = 100000000, maxSize = 100000000,
emptyText = __('Trascina qui il tuo file', 'gepafin'), emptyText = __('Trascina qui il tuo file', 'gepafin'),
@@ -121,6 +123,21 @@ const FileuploadAsync = ({
}); });
} }
const getPropeMimeLabels = (acceptFormats) => {
return acceptFormats
.map(v => {
const found = head(mimeTypes.filter(o => o.code === v));
let res = v;
if (found) {
res = found.name;
}
return res;
})
.join(', ');
}
useEffect(() => { useEffect(() => {
setStateFieldData(defaultValue); setStateFieldData(defaultValue);
register(fieldName, config) register(fieldName, config)
@@ -128,7 +145,7 @@ const FileuploadAsync = ({
useEffect(() => { useEffect(() => {
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
setAcceptFormats(accept.replace(/\*/g, '.\*').replace(/,/g, '|')); setAcceptFormats(accept.join(',').replace(/\*/g, '.\*').replace(/,/g, '|'));
}, [accept]); }, [accept]);
useEffect(() => { useEffect(() => {
@@ -143,7 +160,7 @@ const FileuploadAsync = ({
? <> ? <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required ? '*' : null} {label}{config.required ? '*' : null}
{acceptFormats ? ' (' + acceptFormats.split('|').join(', ') + ')' : null} {acceptFormats ? ' (' + getPropeMimeLabels(accept) + ')' : null}
</label> </label>
<FileUpload <FileUpload
ref={inputRef} ref={inputRef}

View File

@@ -20,8 +20,9 @@ const NumberInput = ({
min, min,
max, max,
disabled = false, disabled = false,
useGrouping = true useGrouping = false
}) => { }) => {
console.log('defaultValue', defaultValue);
const input = <Controller const input = <Controller
name={fieldName} name={fieldName}
control={control} control={control}
@@ -43,10 +44,10 @@ const NumberInput = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
{inputgroup {inputgroup
? <div className="p-inputgroup flex-1"> ? <div className="p-inputgroup">
<span className="p-inputgroup-addon"> <span className="p-inputgroup-addon">
{icon} {icon}
</span> </span>

View File

@@ -34,7 +34,7 @@ const Radio = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
{input} {input}
{infoText ? <small>{infoText}</small> : null} {infoText ? <small>{infoText}</small> : null}

View File

@@ -36,10 +36,10 @@ const Select = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
{inputgroup {inputgroup
? <div className="p-inputgroup flex-1"> ? <div className="p-inputgroup">
<span className="p-inputgroup-addon"> <span className="p-inputgroup-addon">
{icon} {icon}
</span> </span>

View File

@@ -36,7 +36,7 @@ const Switch = ({
<> <>
<div className="appForm__row"> <div className="appForm__row">
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] }, 'mr-8')}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] }, 'mr-8')}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
<div className="appForm__row"> <div className="appForm__row">
{offLabel ? <span>{offLabel}</span> : null} {offLabel ? <span>{offLabel}</span> : null}

View File

@@ -0,0 +1,79 @@
import React from 'react';
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { wrap } from 'object-path-immutable';
const RenderTable = ({ data, columns, setRowsFn }) => {
const table = useReactTable({
data,
columns,
defaultColumn: {
cell: ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
const onBlur = (e) => {
table.options.meta?.updateData(index, id, e.target.value);
};
return (
<input
value={initialValue ? initialValue : ''}
onChange={(e) => table.options.meta?.updateData(index, id, e.target.value)}
onBlur={onBlur}
/>
);
},
},
getCoreRowModel: getCoreRowModel(),
meta: {
updateData: (rowIndex, columnId, value) => {
const newRowsData = wrap(data).set([rowIndex, columnId], value).value();
setRowsFn(newRowsData);
},
}
});
return (
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
)
}
export default RenderTable

View File

@@ -1,17 +1,16 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { classNames } from 'primereact/utils'; import { classNames } from 'primereact/utils';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import {
useReactTable,
getCoreRowModel,
flexRender,
} from '@tanstack/react-table';
import { pathOr, isEmpty, isNil } from 'ramda'; import { pathOr, isEmpty, isNil } from 'ramda';
import { wrap } from 'object-path-immutable'; import { wrap } from 'object-path-immutable';
//components
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import RenderTable from './RenderTable';
const Table = ({ const Table = ({
fieldName, fieldName,
setDataFn,
label, label,
register, register,
errors, errors,
@@ -19,59 +18,46 @@ const Table = ({
defaultValue = [], defaultValue = [],
tableColumns = [] tableColumns = []
}) => { }) => {
const [stateFieldData, setStateFieldData] = useState([]); const [columnsCfg, setColumnsCfg] = useState([]);
const [rowsData, setRowsData] = useState([]); const [rowsCfg, setRowsCfg] = useState([]);
const [rowsDataLength, setRowsDataLength] = useState(0);
const [rowIndexToDelete, rowRowIndexToDelete] = useState(null);
const [isDisabledNewRow, setIsDisabledNewRow] = useState(false);
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
const table = useReactTable({ const [rows, setRows] = useState([]);
data: rowsData, const [shouldDisableNewRows, setShouldDisableNewRows] = useState(false);
columns, const [rowIndexToDelete, rowRowIndexToDelete] = useState(null);
defaultColumn: {
cell: ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
const onBlur = (e) => {
table.options.meta?.updateData(index, id, e.target.value);
};
return (
<input
value={initialValue}
onChange={(e) => table.options.meta?.updateData(index, id, e.target.value)}
onBlur={onBlur}
/>
);
},
},
getCoreRowModel: getCoreRowModel(),
meta: {
updateData: (rowIndex, columnId, value) => {
const newRowsData = wrap(rowsData).set([rowIndex, columnId], value).value();
setRowsData(newRowsData);
},
},
debugTable: true,
});
const addNewRow = () => { const addNewRow = () => {
const obj = stateFieldData const obj = columnsCfg
.reduce((acc, cur) => { .reduce((acc, cur) => {
acc[cur.name] = '' acc[cur.name] = ''
return acc; return acc;
}, {}); }, {});
setRowsData([...rowsData, obj]); const newRowsData = [...rows, obj];
setRows(newRowsData);
setDataFn(fieldName, newRowsData, { shouldValidate: true });
} }
const removeRow = (index) => { const removeRow = (index) => {
rowRowIndexToDelete(index) rowRowIndexToDelete(index);
}
useEffect(() => {
if (!isNil(rowIndexToDelete)) {
const newRowsData = wrap(rows).del([rowIndexToDelete]).value();
setRows(newRowsData);
setDataFn(fieldName, [...newRowsData], { shouldValidate: true });
}
rowRowIndexToDelete(null);
}, [rowIndexToDelete]);
const updateRows = (data) => {
setRows(data);
setDataFn(fieldName, data, { shouldValidate: true });
} }
useEffect(() => { useEffect(() => {
let shouldDisableNewRows = false; let shouldDisableNewRows = false;
let columns = stateFieldData.map((o) => { let newColumns = columnsCfg.map((o) => {
const item = { const item = {
accessorKey: o.name, accessorKey: o.name,
header: () => o.label, header: () => o.label,
@@ -88,10 +74,10 @@ const Table = ({
return item; return item;
}); });
setIsDisabledNewRow(shouldDisableNewRows); setShouldDisableNewRows(shouldDisableNewRows);
if (!shouldDisableNewRows && !isEmpty(columns)) { if (!shouldDisableNewRows && !isEmpty(newColumns)) {
columns.push({ newColumns.push({
accessorKey: 'actions', accessorKey: 'actions',
header: () => '', header: () => '',
footer: (props) => props.column.id, footer: (props) => props.column.id,
@@ -104,20 +90,12 @@ const Table = ({
}) })
} }
setColumns(columns); setColumns(newColumns);
}, [stateFieldData, rowsDataLength]); }, [columnsCfg]);
useEffect(() => { useEffect(() => {
setRowsDataLength(rowsData.length); setRows(rowsCfg);
}, [rowsData]); }, [rowsCfg]);
useEffect(() => {
if (!isNil(rowIndexToDelete)) {
const newRowsData = wrap(rowsData).del([rowIndexToDelete]).value();
setRowsData(newRowsData);
}
rowRowIndexToDelete(null);
}, [rowIndexToDelete]);
useEffect(() => { useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], tableColumns); const stateFieldData = pathOr([], ['stateFieldData'], tableColumns);
@@ -128,60 +106,21 @@ const Table = ({
}, {}); }, {});
let rowsData = pathOr([obj], ['rowsData'], tableColumns); let rowsData = pathOr([obj], ['rowsData'], tableColumns);
rowsData = isEmpty(rowsData) ? [obj] : rowsData; rowsData = isEmpty(rowsData) ? [obj] : rowsData;
setStateFieldData(stateFieldData); setColumnsCfg(stateFieldData);
setRowsData(rowsData); setRowsCfg(rowsData);
}, [tableColumns]); }, [tableColumns]);
useEffect(() => { useEffect(() => {
register(fieldName, config) register(fieldName, config);
}, []); }, []);
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
<table> <RenderTable columns={columns} data={rows} setRowsFn={updateRows}/>
<thead> {!isEmpty(columns) && !shouldDisableNewRows
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
{!isDisabledNewRow && !isEmpty(columns)
? <div className="addNewTableRow" onClick={addNewRow}>{__('Aggiungi una righa', 'gepafin')}</div> ? <div className="addNewTableRow" onClick={addNewRow}>{__('Aggiungi una righa', 'gepafin')}</div>
: null} : null}
</>) </>)

View File

@@ -17,7 +17,7 @@ const TextArea = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
<Controller <Controller
name={fieldName} name={fieldName}

View File

@@ -37,10 +37,10 @@ const TextInput = ({
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
{inputgroup {inputgroup
? <div className="p-inputgroup flex-1"> ? <div className="p-inputgroup">
<span className="p-inputgroup-addon"> <span className="p-inputgroup-addon">
{icon} {icon}
</span> </span>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { classNames } from 'primereact/utils'; import { classNames } from 'primereact/utils';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import { Editor } from 'primereact/editor'; import { Editor } from 'primereact/editor';
import BlockingOverlay from '../../../BlockingOverlay';
const Wysiwyg = ({ const Wysiwyg = ({
fieldName, fieldName,
@@ -12,6 +13,7 @@ const Wysiwyg = ({
defaultValue, defaultValue,
config = {}, config = {},
infoText = null, infoText = null,
placeholder={placeholder},
disabled = false disabled = false
}) => { }) => {
@@ -36,8 +38,9 @@ const Wysiwyg = ({
return ( return (
<> <>
<BlockingOverlay shouldDisplay={disabled}/>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null} {label}{config.required || config.isRequired ? <span className="appForm__field--required">*</span> : null}
</label> </label>
<Controller <Controller
name={fieldName} name={fieldName}
@@ -50,6 +53,7 @@ const Wysiwyg = ({
readOnly={disabled} readOnly={disabled}
{...field} {...field}
headerTemplate={header} headerTemplate={header}
placeholder={placeholder}
onTextChange={(e) => field.onChange(e.htmlValue)} onTextChange={(e) => field.onChange(e.htmlValue)}
style={{ height: 80 * rows }} style={{ height: 80 * rows }}
className={classNames({ 'p-invalid': fieldState.invalid })} className={classNames({ 'p-invalid': fieldState.invalid })}

View File

@@ -9,9 +9,9 @@ import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown'; import { Dropdown } from 'primereact/dropdown';
import { Accordion, AccordionTab } from 'primereact/accordion'; import { Accordion, AccordionTab } from 'primereact/accordion';
import { Dialog } from 'primereact/dialog'; import { Dialog } from 'primereact/dialog';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputSwitch } from 'primereact/inputswitch'; import { InputSwitch } from 'primereact/inputswitch';
import renderHtmlContent from '../../helpers/renderHtmlContent';
import { Editor } from 'primereact/editor';
const FormFieldRepeaterFaq = ({ const FormFieldRepeaterFaq = ({
data, data,
@@ -108,7 +108,8 @@ const FormFieldRepeaterFaq = ({
const footerEditDialog = () => { const footerEditDialog = () => {
return <div> return <div>
<Button type="button" disabled={disabled} label={__('Anulla', 'gepafin')} onClick={hideEditDialog} outlined/> <Button type="button" disabled={disabled} label={__('Anulla', 'gepafin')} onClick={hideEditDialog}
outlined/>
<Button <Button
type="button" type="button"
disabled={isEmpty(title) || isEmpty(question) || isEmpty(answer) || disabled} disabled={isEmpty(title) || isEmpty(question) || isEmpty(answer) || disabled}
@@ -122,6 +123,26 @@ const FormFieldRepeaterFaq = ({
.map(o => o.title) .map(o => o.title)
}, [stateFieldData]); }, [stateFieldData]);
const renderHeader = () => {
return (
<span className="ql-formats">
<button className="ql-bold" aria-label="Bold"></button>
<button className="ql-italic" aria-label="Italic"></button>
<button className="ql-underline" aria-label="Underline"></button>
<button className="ql-link" aria-label="Link"></button>
<button className="ql-list" value="ordered"></button>
<button className="ql-header" value="2"></button>
<button className="ql-header" value="3"></button>
<button className="ql-blockquote"></button>
<button className="ql-list" value="bullet"></button>
<button className="ql-indent" value="-1"></button>
<button className="ql-indent" value="+1"></button>
</span>
);
};
const header = renderHeader();
useEffect(() => { useEffect(() => {
const storeFieldData = data ?? []; const storeFieldData = data ?? [];
setStateFieldData(storeFieldData); setStateFieldData(storeFieldData);
@@ -163,12 +184,13 @@ const FormFieldRepeaterFaq = ({
optionLabel="title"/> optionLabel="title"/>
</div> </div>
<Accordion activeIndex={0}> <Accordion activeIndex={0}>
{stateFieldData.map((o, i) => <AccordionTab key={i} tabIndex={i} {stateFieldData.map((o, i) =>
<AccordionTab key={i} tabIndex={i}
header={ header={
<div className="appForm__faqTab"> <div className="appForm__faqTab">
<div className="appForm__faqTabItem"> <div className="appForm__faqTabItem">
<span> <span>
{o.value} {renderHtmlContent(o.value)}
</span> </span>
</div> </div>
<div className="appForm__faqTabItem"> <div className="appForm__faqTabItem">
@@ -195,7 +217,7 @@ const FormFieldRepeaterFaq = ({
} }
> >
<p className="m-0"> <p className="m-0">
{o.response} {renderHtmlContent(o.response)}
</p> </p>
</AccordionTab>)} </AccordionTab>)}
</Accordion> </Accordion>
@@ -205,26 +227,41 @@ const FormFieldRepeaterFaq = ({
footer={footerEditDialog} footer={footerEditDialog}
style={{ maxWidth: '600px', width: '100%' }} style={{ maxWidth: '600px', width: '100%' }}
onHide={hideEditDialog}> onHide={hideEditDialog}>
<div className="appPage__spacer"></div>
<div className="appForm__field"> <div className="appForm__field">
<label>{__('Titolo FAQ', 'gepafin')}</label> <label for="faqTitle">{__('Titolo FAQ', 'gepafin')}</label>
<InputText value={title} onChange={(e) => onChangeEditItem(e.target.value, 'title')}/> <Editor
id="faqTitle"
value={title}
headerTemplate={header}
onTextChange={(e) => onChangeEditItem(e.htmlValue, 'title')}
style={{ height: 80 * 1 }}
/>
</div> </div>
<div className="appForm__field"> <div className="appForm__field">
<label>{__('Domanda', 'gepafin')}</label> <label for="faqValue">{__('Domanda', 'gepafin')}</label>
<InputText value={question} onChange={(e) => onChangeEditItem(e.target.value, 'value')}/> <Editor
id="faqValue"
value={question}
headerTemplate={header}
onTextChange={(e) => onChangeEditItem(e.htmlValue, 'value')}
style={{ height: 80 * 1 }}
/>
</div> </div>
<div className="appForm__field"> <div className="appForm__field">
<label>{__('Risposta', 'gepafin')}</label> <label for="faqResponse">{__('Risposta', 'gepafin')}</label>
<InputTextarea value={answer} onChange={(e) => onChangeEditItem(e.target.value, 'response')} <Editor
rows={5} id="faqResponse"
cols={30}/> value={answer}
headerTemplate={header}
onTextChange={(e) => onChangeEditItem(e.htmlValue, 'response')}
style={{ height: 80 * 2 }}
/>
</div> </div>
<div className="appForm__field"> <div className="appForm__field">
<label>{__('Pubblicato?', 'gepafin')}</label> <label>{__('Pubblicato?', 'gepafin')}</label>
<InputSwitch checked={isVisible} onChange={(e) => onChangeEditItem(e.value, 'isVisible')}/> <InputSwitch checked={isVisible}
onChange={(e) => onChangeEditItem(e.value, 'isVisible')}/>
</div> </div>
<div className="appPage__spacer"></div>
</Dialog> </Dialog>
</div> </div>
) )

View File

@@ -1,6 +1,10 @@
export const mimeTypes = [ export const mimeTypes = [
{ name: 'PDF file', code: 'application/pdf' }, { name: 'PDF', code: 'application/pdf' },
{ name: 'ZIP file', code: 'application/zip' }, { name: 'ZIP', code: 'application/zip' },
{ name: 'Immagine', code: 'image/*' }, { name: 'Immagine', code: 'image/*' },
{ name: 'Word doc', code: 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document' } {
name: 'Word doc',
code: 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
},
{ name: 'Excel doc', code: 'application/vnd.ms-excel' }
]; ];

View File

@@ -98,6 +98,10 @@ const AllBandiTable = () => {
); );
}; };
const nameBodyTemplate = (rowData) => {
return <span>{rowData.name}</span>
}
const dateStartBodyTemplate = (rowData) => { const dateStartBodyTemplate = (rowData) => {
return getDateFromISOstring(rowData.dates[0]); return getDateFromISOstring(rowData.dates[0]);
}; };
@@ -137,7 +141,8 @@ const AllBandiTable = () => {
globalFilterFields={['name', 'status']} globalFilterFields={['name', 'status']}
header={header} header={header}
emptyMessage="Nothing found." onFilter={(e) => setFilters(e.filters)}> emptyMessage="Nothing found." onFilter={(e) => setFilters(e.filters)}>
<Column field="name" header={__('Nome Bando', 'gepafin')} filter filterPlaceholder="Search by name" <Column field="name" header={__('Nome Bando', 'gepafin')}
filter filterPlaceholder={__('Cerca', 'gepafin')}
style={{ minWidth: '12rem' }}/> style={{ minWidth: '12rem' }}/>
<Column header={__('Data Pubblicazione', 'gepafin')} filterField="start_date" dataType="date" <Column header={__('Data Pubblicazione', 'gepafin')} filterField="start_date" dataType="date"
style={{ minWidth: '10rem' }} style={{ minWidth: '10rem' }}

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect} from 'react'; import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda'; import { is, uniq, isNil } from 'ramda';
import { wrap } from 'object-path-immutable';
// store // store
import { storeSet, storeGet } from '../../../../store'; import { storeSet, useStore } from '../../../../store';
// tools // tools
import getBandoSeverity from '../../../../helpers/getBandoSeverity'; import getBandoSeverity from '../../../../helpers/getBandoSeverity';
@@ -23,12 +24,13 @@ import getNumberWithCurrency from '../../../../helpers/getNumberWithCurrency';
import renderHtmlContent from '../../../../helpers/renderHtmlContent'; import renderHtmlContent from '../../../../helpers/renderHtmlContent';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import set404FromErrorResponse from '../../../../helpers/set404FromErrorResponse';
const AllBandiAccordion = () => { const AllBandiAccordion = () => {
const isAsyncRequest = useStore().main.isAsyncRequest();
const [items, setItems] = useState(null); const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null); const [filters, setFilters] = useState(null);
const [loading, setLoading] = useState(false);
const [expandedRows, setExpandedRows] = useState(null); const [expandedRows, setExpandedRows] = useState(null);
const [statuses, setStatuses] = useState([]); const [statuses, setStatuses] = useState([]);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -47,7 +49,7 @@ const AllBandiAccordion = () => {
} }
const errGetCallbacks = (data) => { const errGetCallbacks = (data) => {
console.log('errGetCallbacks', data) set404FromErrorResponse(data);
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
} }
@@ -58,6 +60,22 @@ const AllBandiAccordion = () => {
}); });
}; };
const nameBodyTemplate = (rowData) => {
return <span
className="appPageSection__titleClickable"
onClick={() => {
let newExpandedRows;
if (isNil(expandedRows) || isNil(expandedRows[rowData.id])) {
newExpandedRows = isNil(expandedRows)
? wrap({}).set([rowData.id], true).value()
: wrap(expandedRows).set([rowData.id], true).value();
} else {
newExpandedRows = wrap(expandedRows).del([rowData.id]).value();
}
setExpandedRows(newExpandedRows);
}}>{rowData.name}</span>
}
const amountBodyTemplate = (rowData) => { const amountBodyTemplate = (rowData) => {
return getNumberWithCurrency(rowData.amount); return getNumberWithCurrency(rowData.amount);
}; };
@@ -108,13 +126,16 @@ const AllBandiAccordion = () => {
return( return(
<div className="appPageSection__table"> <div className="appPageSection__table">
<DataTable value={items} paginator rows={10} loading={loading} dataKey="id" <DataTable value={items} paginator rows={10} loading={isAsyncRequest} dataKey="id"
filters={filters} emptyMessage="Nothing found." filters={filters} emptyMessage="Nothing found."
expandedRows={expandedRows} onRowToggle={(e) => setExpandedRows(e.data)} expandedRows={expandedRows}
onRowToggle={(e) => setExpandedRows(e.data)}
rowExpansionTemplate={rowExpansionTemplate} rowExpansionTemplate={rowExpansionTemplate}
onFilter={(e) => setFilters(e.filters)}> onFilter={(e) => setFilters(e.filters)}>
<Column expander={allowExpansion} style={{ width: '5rem' }} /> <Column expander={allowExpansion} style={{ width: '5rem' }} />
<Column field="name" header={__('Bando', 'gepafin')} style={{ minWidth: '12rem' }}/> <Column field="name" header={__('Bando', 'gepafin')}
body={nameBodyTemplate}
style={{ minWidth: '12rem' }}/>
<Column header={__('Importo totale', 'gepafin')} filterField="amount" <Column header={__('Importo totale', 'gepafin')} filterField="amount"
style={{ minWidth: '10rem' }} body={amountBodyTemplate} sortable/> style={{ minWidth: '10rem' }} body={amountBodyTemplate} sortable/>
<Column field="status" header={__('Stato', 'gepafin')} filterMenuStyle={{ width: '14rem' }} <Column field="status" header={__('Stato', 'gepafin')} filterMenuStyle={{ width: '14rem' }}

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { range } from 'ramda';
import { __ } from '@wordpress/i18n';
// components
import { Steps } from 'primereact/steps';
const ApplicationSteps = ({ totalSteps = 0, activeStepIndex }) => {
const rangeArr = range(1, totalSteps + 1);
const items = rangeArr.map(() => ({ label: 'Passo' }));
// TODO update to using Steps after primereact is updated
return(
0 !== totalSteps
? <span>{__('Passo', 'gepafin')}: {activeStepIndex + 1}</span>
: null
)
/*return(
0 !== totalSteps
? <Steps model={items} activeIndex={activeStepIndex} readOnly/>
: null
)*/
}
export default ApplicationSteps

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { klona } from 'klona'; import { klona } from 'klona';
import { head, range, is, pluck } from 'ramda'; import { head, is, pluck, isEmpty } from 'ramda';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { TZDate } from '@date-fns/tz'; import { TZDate } from '@date-fns/tz';
@@ -30,9 +30,9 @@ import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import { Skeleton } from 'primereact/skeleton'; import { Skeleton } from 'primereact/skeleton';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import FormField from '../../components/FormField'; import FormField from '../../components/FormField';
import { Steps } from 'primereact/steps';
import { Toast } from 'primereact/toast'; import { Toast } from 'primereact/toast';
import { Messages } from 'primereact/messages'; import { Messages } from 'primereact/messages';
import ApplicationSteps from './ApplicationSteps';
const BandoApplication = () => { const BandoApplication = () => {
const { id } = useParams(); const { id } = useParams();
@@ -43,7 +43,6 @@ const BandoApplication = () => {
const [totalSteps, setTotalSteps] = useState(0); const [totalSteps, setTotalSteps] = useState(0);
const [completedSteps, setCompletedSteps] = useState(0); const [completedSteps, setCompletedSteps] = useState(0);
const [activeStep, setActiveStep] = useState(1); const [activeStep, setActiveStep] = useState(1);
const [stepItems, setStepItems] = useState([{ label: 'Passo' }]);
const isAsyncRequest = useStore().main.isAsyncRequest(); const isAsyncRequest = useStore().main.isAsyncRequest();
const toast = useRef(null); const toast = useRef(null);
const formMsgs = useRef(null); const formMsgs = useRef(null);
@@ -54,7 +53,7 @@ const BandoApplication = () => {
setValue, setValue,
trigger, trigger,
register, register,
getValues, getValues
} = useForm({ defaultValues: {}, mode: 'onChange' }); } = useForm({ defaultValues: {}, mode: 'onChange' });
const values = getValues(); const values = getValues();
const validationFns = { const validationFns = {
@@ -67,6 +66,7 @@ const BandoApplication = () => {
isUrl, isUrl,
isMarcaDaBollo isMarcaDaBollo
} }
const activeStepIndex = activeStep - 1;
const onSubmit = () => { const onSubmit = () => {
const applId = getApplicationId(); const applId = getApplicationId();
@@ -91,7 +91,6 @@ const BandoApplication = () => {
} }
const errSubmitApplicationCallback = (data) => { const errSubmitApplicationCallback = (data) => {
console.log(data)
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
if (data.status === 'VALIDATION_ERROR') { if (data.status === 'VALIDATION_ERROR') {
if (formMsgs.current) { if (formMsgs.current) {
@@ -120,7 +119,7 @@ const BandoApplication = () => {
} }
} }
const saveDraft = () => { const saveDraft = (saveAndMove = '') => {
trigger(); trigger();
const formValues = getValues(); const formValues = getValues();
const usedFieldsIds = pluck('id', formData); const usedFieldsIds = pluck('id', formData);
@@ -157,7 +156,7 @@ const BandoApplication = () => {
formMsgs.current.clear(); formMsgs.current.clear();
} }
ApplicationService.saveDraft(applId, submitData, saveDraftCallback, errSaveDraftCallback, [ ApplicationService.saveDraft(applId, submitData, (data) => saveDraftCallback(data, saveAndMove), errSaveDraftCallback, [
['formId', formId] ['formId', formId]
]); ]);
} }
@@ -168,7 +167,7 @@ const BandoApplication = () => {
return !isNaN(parsed) ? parsed : 0; return !isNaN(parsed) ? parsed : 0;
} }
const saveDraftCallback = (data) => { const saveDraftCallback = (data, saveAndMove) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
if (toast.current) { if (toast.current) {
toast.current.show({ toast.current.show({
@@ -177,9 +176,17 @@ const BandoApplication = () => {
detail: __('Salvato!', 'gepafin') detail: __('Salvato!', 'gepafin')
}); });
} }
if (!isEmpty(saveAndMove)) {
storeSet.main.setAsyncRequest();
ApplicationService.getApplicationForm(data.data.id, getApplFormCallback, errGetApplFormCallbacks, [
['formId', formId],
['action', saveAndMove]
]);
} else {
// update info about application completeness // update info about application completeness
ApplicationService.getApplicationForm(data.data.id, getStatusCheckCallback, errGetStatusCheckCallbacks); ApplicationService.getApplicationForm(data.data.id, getStatusCheckCallback, errGetStatusCheckCallbacks);
} }
}
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
} }
@@ -213,31 +220,37 @@ const BandoApplication = () => {
} }
const goBackward = () => { const goBackward = () => {
if (formId) { saveDraft('PREVIOUS');
/*if (formId) {
const applId = getApplicationId(); const applId = getApplicationId();
storeSet.main.setAsyncRequest(); storeSet.main.setAsyncRequest();
ApplicationService.getApplicationForm(applId, getApplFormCallback, errGetApplFormCallbacks, [ ApplicationService.getApplicationForm(applId, getApplFormCallback, errGetApplFormCallbacks, [
['formId', formId], ['formId', formId],
['action', 'PREVIOUS'] ['action', 'PREVIOUS']
]); ]);
} }*/
} }
const goForward = () => { const goForward = () => {
if (formId) { saveDraft('NEXT');
/*if (formId) {
const applId = getApplicationId(); const applId = getApplicationId();
storeSet.main.setAsyncRequest(); storeSet.main.setAsyncRequest();
ApplicationService.getApplicationForm(applId, getApplFormCallback, errGetApplFormCallbacks, [ ApplicationService.getApplicationForm(applId, getApplFormCallback, errGetApplFormCallbacks, [
['formId', formId], ['formId', formId],
['action', 'NEXT'] ['action', 'NEXT']
]); ]);
} }*/
} }
const getApplFormCallback = (data) => { const getApplFormCallback = (data) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
setBandoTitle(data.data.callTitle); setBandoTitle(data.data.callTitle);
setFormData(data.data.applicationFormResponse.content); setFormData(data.data.applicationFormResponse.content);
setFormId(data.data.formId);
setTotalSteps(data.data.totalFormSteps);
setCompletedSteps(data.data.completedSteps);
setActiveStep(data.data.currentStep);
if (data.data.applicationFormResponse.formFields) { if (data.data.applicationFormResponse.formFields) {
const submitData = data.data.applicationFormResponse.formFields.map((o) => ({ const submitData = data.data.applicationFormResponse.formFields.map((o) => ({
@@ -246,11 +259,6 @@ const BandoApplication = () => {
})); }));
setFormInitialData(submitData) setFormInitialData(submitData)
} }
setFormId(data.data.formId);
setTotalSteps(data.data.totalFormSteps);
setCompletedSteps(data.data.completedSteps);
setActiveStep(data.data.currentStep);
} }
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
} }
@@ -285,11 +293,6 @@ const BandoApplication = () => {
newFormData.map(o => setValue(o.fieldId, o.fieldValue)); newFormData.map(o => setValue(o.fieldId, o.fieldValue));
}, [formInitialData]); }, [formInitialData]);
useEffect(() => {
const rangeArr = range(1, totalSteps + 1);
setStepItems(rangeArr.map(() => ({ label: 'Passo' })));
}, [totalSteps])
useEffect(() => { useEffect(() => {
const applId = getApplicationId(); const applId = getApplicationId();
@@ -312,8 +315,7 @@ const BandoApplication = () => {
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
{!isAsyncRequest <ApplicationSteps totalSteps={totalSteps} activeStepIndex={activeStepIndex}/>
? <Steps model={stepItems} activeIndex={activeStep - 1}/> : null}
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
@@ -327,12 +329,13 @@ const BandoApplication = () => {
const text = head(o.settings.filter(o => o.name === 'text')); const text = head(o.settings.filter(o => o.name === 'text'));
const placeholder = head(o.settings.filter(o => o.name === 'placeholder')); const placeholder = head(o.settings.filter(o => o.name === 'placeholder'));
const options = head(o.settings.filter(o => o.name === 'options')); const options = head(o.settings.filter(o => o.name === 'options'));
const tableColumns = head(o.settings.filter(o => o.name === 'table_columns'));
const step = head(o.settings.filter(o => o.name === 'step')); const step = head(o.settings.filter(o => o.name === 'step'));
const mime = head(o.settings.filter(o => o.name === 'mime')); const mime = head(o.settings.filter(o => o.name === 'mime'));
let mimeValue = ''; let mimeValue = '';
if (mime) { if (mime) {
mimeValue = mime.value.map(o => o.code).join(','); mimeValue = mime.value.map(o => o.code);
} }
const validations = Object.keys(o.validators).reduce((acc, cur) => { const validations = Object.keys(o.validators).reduce((acc, cur) => {
@@ -367,13 +370,14 @@ const BandoApplication = () => {
register={register} register={register}
errors={errors} errors={errors}
defaultValue={values[o.id]} defaultValue={values[o.id]}
maxFractionDigits={step} maxFractionDigits={step ? step.value : 0}
accept={mimeValue} accept={mimeValue}
config={validations} config={validations}
options={options ? options.value : []} options={options ? options.value : []}
setDataFn={setValue} setDataFn={setValue}
sourceId={getApplicationId()} sourceId={getApplicationId()}
useGrouping={false} useGrouping={false}
tableColumns={tableColumns ? tableColumns.value : {}}
/> />
})} })}

View File

@@ -162,7 +162,6 @@ const BandoEditFormStep1 = forwardRef(function ({ initialData, getFormErrors, st
} }
const errLookupdataCallback = (data) => { const errLookupdataCallback = (data) => {
console.log('errLookupdataCallback', data);
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
} }

View File

@@ -242,7 +242,7 @@ const BandoEditFormStep2 = forwardRef(function ({ initialData, getFormErrors, st
errors={errors} errors={errors}
defaultValue={values['docs']} defaultValue={values['docs']}
config={{ required: __('È obbligatorio', 'gepafin') }} config={{ required: __('È obbligatorio', 'gepafin') }}
accept="application/pdf,application/vnd.ms-excel" accept={["application/pdf", "application/vnd.ms-excel"]}
chooseLabel={__('Aggiungi documento', 'gepafin')} chooseLabel={__('Aggiungi documento', 'gepafin')}
multiple={true} multiple={true}
doctype='document' doctype='document'

View File

@@ -22,6 +22,7 @@ import BandoEditFormStep2 from './components/BandoEditFormStep2';
import { Messages } from 'primereact/messages'; import { Messages } from 'primereact/messages';
import FormsService from '../../service/forms-service'; import FormsService from '../../service/forms-service';
import BlockingOverlay from '../../components/BlockingOverlay'; import BlockingOverlay from '../../components/BlockingOverlay';
import { Toast } from 'primereact/toast';
const BandoEdit = () => { const BandoEdit = () => {
const isAsyncRequest = useStore().main.isAsyncRequest(); const isAsyncRequest = useStore().main.isAsyncRequest();
@@ -32,6 +33,7 @@ const BandoEdit = () => {
const [forms, setForms] = useState([]); const [forms, setForms] = useState([]);
const formRef = useRef(null); const formRef = useRef(null);
const bandoMsgs = useRef(null); const bandoMsgs = useRef(null);
const toast = useRef(null);
const stepItems = [ const stepItems = [
{ {
@@ -81,11 +83,18 @@ const BandoEdit = () => {
bandoMsgs.current.show([ bandoMsgs.current.show([
{ {
id: '99', id: '99',
sticky: true, severity: 'success', summary: '', sticky: true, severity: 'info', summary: '',
detail: __('Potrai pubblicare il tuo Bando.', 'gepafin'), detail: __('Potrai pubblicare il tuo Bando.', 'gepafin'),
closable: false closable: false
} }
]); ]);
if (toast.current) {
toast.current.show({
severity: 'info',
summary: '',
detail: __('Potrai pubblicare il tuo Bando.', 'gepafin')
});
}
} }
} }
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
@@ -125,6 +134,13 @@ const BandoEdit = () => {
} }
]); ]);
} }
if (toast.current) {
toast.current.show({
severity: 'success',
summary: '',
detail: __('Pubblicato!', 'gepafin')
});
}
setData(data.data); setData(data.data);
} }
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
@@ -247,6 +263,7 @@ const BandoEdit = () => {
<div className="appPage__spacer"></div> <div className="appPage__spacer"></div>
<Messages ref={bandoMsgs}/> <Messages ref={bandoMsgs}/>
<Toast ref={toast} />
{!isEmpty(data) {!isEmpty(data)
? <> ? <>

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { head } from 'ramda'; import { head, pathOr } from 'ramda';
// store // store
import { useStore } from '../../../../store'; import { useStore } from '../../../../store';
@@ -7,10 +7,11 @@ import renderHtmlContent from '../../../../helpers/renderHtmlContent';
const BuilderElementProperLabel = ({ id, defaultLabel }) => { const BuilderElementProperLabel = ({ id, defaultLabel }) => {
const elements = useStore().main.formElements(); const elements = useStore().main.formElements();
const element = head(elements.filter(o => o.id === id));
const [label, setLabel] = useState(''); const [label, setLabel] = useState('');
const isRequired = pathOr(false, ['validators', 'isRequired'], element)
useEffect(() => { useEffect(() => {
const element = head(elements.filter(o => o.id === id));
const label = head(element.settings.filter(o => o.name === 'label')); const label = head(element.settings.filter(o => o.name === 'label'));
const text = head(element.settings.filter(o => o.name === 'text')); const text = head(element.settings.filter(o => o.name === 'text'));
@@ -23,7 +24,10 @@ const BuilderElementProperLabel = ({ id, defaultLabel }) => {
} }
}, [elements]); }, [elements]);
return <div className="label">{renderHtmlContent(label)}</div> return <div className="label">
{renderHtmlContent(label)}
{isRequired ? <span className="appForm__field--required">*</span> : null}
</div>
} }
export default BuilderElementProperLabel; export default BuilderElementProperLabel;

View File

@@ -23,7 +23,7 @@ import FormsService from '../../service/forms-service';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse'; import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
// TODO temp data // TODO temp data
import { elementItems } from '../../tempData'; //import { elementItems } from '../../tempData';
const BandoFormsEdit = () => { const BandoFormsEdit = () => {
const { id, formId } = useParams(); const { id, formId } = useParams();
@@ -168,7 +168,14 @@ const BandoFormsEdit = () => {
} }
const openPreview = () => { const openPreview = () => {
if ('PUBLISH' !== bandoStatus) {
doSave(true); doSave(true);
} else {
const bandoId = getBandoId();
const parsedFormId = parseInt(formId)
const bandoFormId = !isNaN(parsedFormId) ? parsedFormId : 0;
navigate(`/bandi/${bandoId}/forms/${bandoFormId}/preview`);
}
} }
const confirmDelete = (event) => { const confirmDelete = (event) => {
@@ -208,8 +215,8 @@ const BandoFormsEdit = () => {
const getElementItemsCallback = (data) => { const getElementItemsCallback = (data) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
storeSet.main.elementItems(elementItems.sort((a, b) => a.sortOrder - b.sortOrder)); //storeSet.main.elementItems(elementItems.sort((a, b) => a.sortOrder - b.sortOrder));
//storeSet.main.elementItems(data.data.sort((a, b) => a.sortOrder - b.sortOrder)); storeSet.main.elementItems(data.data.sort((a, b) => a.sortOrder - b.sortOrder));
} }
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
} }

View File

@@ -150,7 +150,7 @@ const BandoFormsPreview = () => {
}, {}); }, {});
return ['paragraph'].includes(o.name) && text return ['paragraph'].includes(o.name) && text
? <div className="appForm__content">{renderHtmlContent(text.value)}</div> ? <div className="appForm__content" key={o.id}>{renderHtmlContent(text.value)}</div>
: <FormField : <FormField
key={o.id} key={o.id}
type={o.name} type={o.name}
@@ -167,6 +167,7 @@ const BandoFormsPreview = () => {
options={options ? options.value : []} options={options ? options.value : []}
setDataFn={setValue} setDataFn={setValue}
sourceId={0} sourceId={0}
useGrouping={false}
tableColumns={tableColumns ? tableColumns.value : {}} tableColumns={tableColumns ? tableColumns.value : {}}
/> />
})} })}

View File

@@ -95,10 +95,11 @@ const BandoView = () => {
icon="pi pi-arrow-left" iconPos="left"/> icon="pi pi-arrow-left" iconPos="left"/>
</div> </div>
<picture className="appPageSection__hero"> {!isEmpty(data.images)
? <picture className="appPageSection__hero">
<source srcSet={data.images[0] ? data.images[0].filePath : ''}/> <source srcSet={data.images[0] ? data.images[0].filePath : ''}/>
<img src={data.images[0] ? data.images[0].filePath : ''} alt={data.name}/> <img src={data.images[0] ? data.images[0].filePath : ''} alt={data.name}/>
</picture> </picture> : null}
<div className="appPageSection__withBorder"> <div className="appPageSection__withBorder">
<h2>{__('Descrizione breve', 'gepafin')}</h2> <h2>{__('Descrizione breve', 'gepafin')}</h2>
@@ -187,9 +188,9 @@ const BandoView = () => {
<Accordion> <Accordion>
{data.faq {data.faq
.filter(o => o.isVisible) .filter(o => o.isVisible)
.map((o, i) => <AccordionTab key={i} header={o.value}> .map((o, i) => <AccordionTab key={i} header={renderHtmlContent(o.value)}>
<p> <p>
{o.response} {renderHtmlContent(o.response)}
</p> </p>
</AccordionTab>)} </AccordionTab>)}
</Accordion> </Accordion>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { is, isEmpty, pathOr, isNil } from 'ramda'; import { is, isEmpty, isNil } from 'ramda';
// store // store
import { storeSet, useStore } from '../../store'; import { storeSet, useStore } from '../../store';
@@ -16,7 +16,6 @@ import renderHtmlContent from '../../helpers/renderHtmlContent';
import { Skeleton } from 'primereact/skeleton'; import { Skeleton } from 'primereact/skeleton';
import { Accordion } from 'primereact/accordion'; import { Accordion } from 'primereact/accordion';
import { AccordionTab } from 'primereact/accordion'; import { AccordionTab } from 'primereact/accordion';
import { InputTextarea } from 'primereact/inputtextarea';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { Messages } from 'primereact/messages'; import { Messages } from 'primereact/messages';
import { Message } from 'primereact/message'; import { Message } from 'primereact/message';
@@ -29,14 +28,13 @@ import { Editor } from 'primereact/editor';
const BandoViewBeneficiario = () => { const BandoViewBeneficiario = () => {
const isAsyncRequest = useStore().main.isAsyncRequest(); const isAsyncRequest = useStore().main.isAsyncRequest();
const companies = useStore().main.companies(); const chosenCompanyId = useStore().main.chosenCompanyId();
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [data, setData] = useState({}); const [data, setData] = useState({});
const [newQuestion, setNewQuestion] = useState(''); const [newQuestion, setNewQuestion] = useState('');
const [applicationObj, setApplicationObj] = useState(true); const [applicationObj, setApplicationObj] = useState(true);
const bandoMsgs = useRef(null); const bandoMsgs = useRef(null);
const chosenCompanyId = pathOr(0, [0, 'id'], companies);
const scaricaBando = () => { const scaricaBando = () => {
@@ -90,12 +88,12 @@ const BandoViewBeneficiario = () => {
bandoMsgs.current.clear(); bandoMsgs.current.clear();
} }
const obj = { const obj = {
"id": null, 'id': null,
"lookUpDataId": null, 'lookUpDataId': null,
"title": newQuestion, 'title': newQuestion,
"value": newQuestion, 'value': newQuestion,
"response": "", 'response': '',
"isVisible": false 'isVisible': false
} }
storeSet.main.setAsyncRequest(); storeSet.main.setAsyncRequest();
FaqItemService.addQuestion(id, obj, createCallBack, errCreateCallback, [['companyId', chosenCompanyId]]) FaqItemService.addQuestion(id, obj, createCallBack, errCreateCallback, [['companyId', chosenCompanyId]])
@@ -161,7 +159,7 @@ const BandoViewBeneficiario = () => {
const getApplCallback = (data) => { const getApplCallback = (data) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
if(data.data.length) { if (data.data.length) {
setApplicationObj(data.data[0]); setApplicationObj(data.data[0]);
} }
} }
@@ -220,10 +218,11 @@ const BandoViewBeneficiario = () => {
{!isAsyncRequest && !isEmpty(data) {!isAsyncRequest && !isEmpty(data)
? <div className="appPage__content"> ? <div className="appPage__content">
<picture className="appPageSection__hero"> {!isEmpty(data.images)
? <picture className="appPageSection__hero">
<source srcSet={data.images[0] ? data.images[0].filePath : ''}/> <source srcSet={data.images[0] ? data.images[0].filePath : ''}/>
<img src={data.images[0] ? data.images[0].filePath : ''} alt={data.name}/> <img src={data.images[0] ? data.images[0].filePath : ''} alt={data.name}/>
</picture> </picture> : null}
<div className="appPageSection__withBorder"> <div className="appPageSection__withBorder">
<h2>{__('Descrizione breve', 'gepafin')}</h2> <h2>{__('Descrizione breve', 'gepafin')}</h2>
@@ -312,9 +311,9 @@ const BandoViewBeneficiario = () => {
<Accordion> <Accordion>
{data.faq {data.faq
.filter(o => o.isVisible) .filter(o => o.isVisible)
.map((o, i) => <AccordionTab key={i} header={o.value}> .map((o, i) => <AccordionTab key={i} header={renderHtmlContent(o.value)}>
<p> <p>
{o.response} {renderHtmlContent(o.response)}
</p> </p>
</AccordionTab>)} </AccordionTab>)}
</Accordion> </Accordion>
@@ -345,7 +344,8 @@ const BandoViewBeneficiario = () => {
{chosenCompanyId === 0 {chosenCompanyId === 0
? <> ? <>
<Message severity="error" text={__("Devi creare un'azienda prima di partecipare nei bandi. Vai nel profilo aziendale.", 'gepafin')} /> <Message severity="error"
text={__('Devi creare un\'azienda prima di partecipare nei bandi. Vai nel profilo aziendale.', 'gepafin')}/>
</> </>
: null} : null}
@@ -387,7 +387,8 @@ const BandoViewBeneficiario = () => {
<h2>{__('Contatti per Assistenza', 'gepafin')}</h2> <h2>{__('Contatti per Assistenza', 'gepafin')}</h2>
<div className="row rowContent"> <div className="row rowContent">
<p>Email: {data.email}</p> <p>Email: {data.email}</p>
{!isNil(data.phoneNumber) ? <p>{__('Telefono', 'gepafin')}: +39 {data.phoneNumber}</p> : null} {!isNil(data.phoneNumber) ?
<p>{__('Telefono', 'gepafin')}: +39 {data.phoneNumber}</p> : null}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,10 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { isEmpty, isNil, pathOr } from 'ramda'; import { isEmpty, isNil, pathOr, head } from 'ramda';
import { klona } from 'klona';
// store // store
import { storeSet, useStore } from '../../store'; import { storeSet, useStore, storeGet } from '../../store';
// components // components
import { Messages } from 'primereact/messages'; import { Messages } from 'primereact/messages';
@@ -18,7 +19,6 @@ import CompanyService from '../../service/company-service';
// tools // tools
import { isPIVA } from '../../helpers/validators'; import { isPIVA } from '../../helpers/validators';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse'; import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
import { klona } from 'klona';
const ProfileCompany = () => { const ProfileCompany = () => {
const isAsyncRequest = useStore().main.isAsyncRequest(); const isAsyncRequest = useStore().main.isAsyncRequest();
@@ -67,7 +67,19 @@ const ProfileCompany = () => {
const updateCallback = (data) => { const updateCallback = (data) => {
if (data.status === 'SUCCESS') { if (data.status === 'SUCCESS') {
//setData(getFormattedBandiData(data.data)); const company = klona(data.data);
const companies = storeGet.main.companies();
const existingCompany = head(companies.filter(o => o.id === company.id));
let newCompanies = [];
if (existingCompany) {
newCompanies = companies.map(o => o.id === company.id ? company : o)
} else {
newCompanies = [...companies, company];
storeSet.main.chosenCompanyId(company.id);
}
storeSet.main.companies(newCompanies);
} }
storeSet.main.unsetAsyncRequest(); storeSet.main.unsetAsyncRequest();
} }
@@ -129,7 +141,6 @@ const ProfileCompany = () => {
useEffect(() => { useEffect(() => {
const chosenCompany = pathOr({}, [0], companies); const chosenCompany = pathOr({}, [0], companies);
console.log('chosenCompany', chosenCompany, companies)
setFormInitialData(chosenCompany); setFormInitialData(chosenCompany);
}, [companies]); }, [companies]);

View File

@@ -161,10 +161,7 @@ const Registration = () => {
control={control} control={control}
errors={errors} errors={errors}
config={{ config={{
required: __('È obbligatorio', 'gepafin'), required: __('È obbligatorio', 'gepafin')
validate: {
isCodiceFiscale
}
}} }}
placeholder="ABC1234" placeholder="ABC1234"
/> />

View File

@@ -6,6 +6,7 @@ const initialStore = {
userData: {}, userData: {},
token: '', token: '',
companies: [], companies: [],
chosenCompanyId: 0,
// bando form // bando form
formInitialData: {}, formInitialData: {},
// form builder // form builder

View File

@@ -6,7 +6,8 @@ const zustandXOpts = {
enabled: true, enabled: true,
partialize: (state) => ({ partialize: (state) => ({
//userData: state.userData, //userData: state.userData,
token: state.token token: state.token,
chosenCompanyId: state.chosenCompanyId,
}), }),
} }
} }

View File

@@ -1,337 +1,3 @@
export const bandoTest = {
"name": "Innovazione digitale 2024",
"confidi": false,
"descriptionShort": "Supporto alle PMI per progetti di digitalizzazione e innovazione tecnologica.",
"descriptionLong": "Il bando \"Innovazione Digitale 2024\" mira a sostenere le PMI nell'adozione di tecnologie digitali innovative. I progetti finanziabili includono l'implementazione di soluzioni di intelligenza artificiale, blockchain, IoT, e altre tecnologie avanzate che possono migliorare la competitività delle imprese.",
"documentationRequested": "Documentazione richiesta*",
"dates": [
"2024-08-27T22:00:00.000Z",
"2024-10-29T23:00:00.000Z"
],
"amount": 10000,
"amountMax": 2500,
"aimedTo": [
{
"id": 3,
"value": "PMI con sede in Umbria",
"status": "existing"
}
],
"faq": [
{
"id": 2,
"question": "Question 1?",
"answer": "Lorem ipsum dolor",
"visible": true,
"status": "existing"
}
],
status: 'draft',
id: 11,
createdDate: "2024-08-07T08:14:07.849Z",
updatedDate: "2024-08-07T08:14:07.849Z",
"documentation":[
{
"id":18,
"name":"SCR-20240820-kiwn.pdf",
"filePath":"https://mementoresources.s3.eu-west-1.amazonaws.com/gepafin/SCR-20240820-kiwn.pdf",
"description":null,
"createdDate":"2024-08-26T06:51:11.800799252",
"updatedDate":"2024-08-26T06:51:11.800826092"
}
],
"criteria":[
{
"id":null,
"value":"Innovatività del progetto",
"status":"new",
"score":9
},
{
"id":null,
"value":"Impatto sulla competitività dell'azienda",
"status":"new",
"score":3
},
{
"id":null,
"value":"Sostenibilità economico-finanziaria",
"status":"new",
"score":5
}
],
"threshold":11,
"images":[
{
"id":19,
"name":"photo-1618245318763-a15156d6b23c.avif",
"filePath":"https://mementoresources.s3.eu-west-1.amazonaws.com/gepafin/photo-1618245318763-a15156d6b23c.avif",
"description":null,
"createdDate":"2024-08-26T07:28:16.954763338",
"updatedDate":"2024-08-26T07:28:16.954843237"
}
],
"checklist":[
{
"id":null,
"value":"Innovatività del progetto",
"status":"new"
},
{
"id":null,
"value":"Some new check",
"status":"new"
},
{
"id":null,
"value":"Check #2",
"status":"new"
}
]
}
export const formData = {
id: 15,
label: 'La forma per Innovazione digitale 2024',
content: [
{
"id": "a9a8aeb479",
"name": "textinput",
"label": "Testo Breve",
"settings": [
{
"name": "label",
"value": "Testo Breve"
},
{
"name": "placeholder",
"value": ""
}
],
"validators": {
"isRequired": true,
"minLength": "3",
"maxLength": null,
"pattern": null,
"custom": null
},
"dbId": 1
},
{
"id": "a20469fc97",
"name": "textarea",
"label": "Testo Lungo",
"settings": [
{
"name": "label",
"value": "Testo Lungo"
},
{
"name": "placeholder",
"value": ""
}
],
"validators": {
"isRequired": false,
"minLength": null,
"maxLength": null,
"pattern": null,
"custom": null
},
"dbId": 2
},
{
"id": "a21dc560f6",
"name": "wysiwyg",
"label": "Campo di Testo Formattato",
"settings": [
{
"name": "label",
"value": "Testo Formattato"
},
{
"name": "placeholder",
"value": ""
}
],
"validators": {
"isRequired": false,
"minLength": null,
"maxLength": null,
"pattern": null,
"custom": null
},
"dbId": 3
},
{
"id": "a5c3860c1a",
"name": "numberinput",
"label": "Campo Numerico",
"settings": [
{
"name": "label",
"value": "Numero"
},
{
"name": "placeholder",
"value": 0
},
{
"name": "step",
"value": 0
}
],
"validators": {
"isRequired": false,
"min": null,
"max": null,
"pattern": null,
"custom": null
},
"dbId": 4
},
{
"id": "a7252ecc8d",
"name": "radio",
"label": "Scelta Singola",
"settings": [
{
"name": "label",
"value": "Scelta Singola"
},
{
"name": "options",
"value": [
{
"name": "o8df4ffa62",
"label": "Radio opzione A"
},
{
"name": "o3ed6fb4d8",
"label": "Radio opzione B"
}
]
}
],
"validators": {
"isRequired": true,
"custom": null
},
"dbId": 5
},
{
"id": "a778783c9d",
"name": "select",
"label": "Menu a Tendina",
"settings": [
{
"name": "label",
"value": "Menu a Tendina"
},
{
"name": "options",
"value": [
{
"name": "od9f50d8a8",
"label": "Opzione A"
},
{
"name": "o8cb208732",
"label": "Opzione B"
}
]
}
],
"validators": {
"isRequired": false,
"custom": null
},
"dbId": 6
},
{
"id": "afee29df1a",
"name": "switch",
"label": "Casella di Spunta",
"settings": [
{
"name": "label",
"value": "Casella di Spunta"
}
],
"validators": {
"isRequired": false
},
"dbId": 8
},
{
"id": "a5fdbd77df",
"name": "checkboxes",
"label": "Scelta Multipla",
"settings": [
{
"name": "label",
"value": "Scelta Multipla"
},
{
"name": "options",
"value": [
{
"name": "o55ea20665",
"label": "Opz checkbox A"
},
{
"name": "oc10db3d79",
"label": "Opz checkbox B"
}
]
}
],
"validators": {
"isRequired": true,
"custom": null
},
"dbId": 7
},
{
"id": "a2810fd8a1",
"name": "fileupload",
"label": "Caricamento File",
"settings": [
{
"name": "label",
"value": "Caricamento File"
},
{
"name": "mime",
"value": ['image/jpeg', 'image/png']
}
],
"validators": {
"isRequired": true,
"maxSize": 100000,
"custom": null
},
"dbId": 10
},
{
"id": "ae14c94da7",
"name": "datepicker",
"label": "Data",
"settings": [
{
"name": "label",
"value": "Data"
}
],
"validators": {
"isRequired": true,
"custom": null
},
"dbId": 9
}
]
};
export const elementItems = [ export const elementItems = [
{ {
id: 1, id: 1,