- added Table field;

This commit is contained in:
Vitalii Kiiko
2024-10-02 16:06:21 +02:00
parent 0634cd2305
commit cb9327a740
14 changed files with 565 additions and 30 deletions

View File

@@ -45,6 +45,20 @@
input[disabled], div.p-disabled:not(.p-inputswitch) { input[disabled], div.p-disabled:not(.p-inputswitch) {
background-color: #e3e3e3; background-color: #e3e3e3;
} }
&.table {
div.addNewTableRow {
width: 100%;
text-align: center;
padding: 5px 0;
background: var(--table-border-color);
color: var(--primary-text);
&:hover {
cursor: pointer;
}
}
}
} }
.appForm__fieldItem { .appForm__fieldItem {

View File

@@ -61,7 +61,7 @@
.label { .label {
p { p {
margin-bottom: 10px; margin-bottom: 0;
&.ql-indent-1 { &.ql-indent-1 {
padding-left: 3em; padding-left: 3em;
@@ -179,4 +179,13 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
}
.formElementSettings__repeaterItem {
}
.formElementSettings__subRepeater {
padding: 10px 20px;
background-color: #f9f9f9;
} }

View File

@@ -24,6 +24,7 @@ body {
flex-direction: column; flex-direction: column;
flex-grow: 1; flex-grow: 1;
} }
.inner { .inner {
display: flex; display: flex;
height: 100%; height: 100%;
@@ -102,4 +103,48 @@ 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

@@ -106,6 +106,16 @@
background-color: rgba(255,255,255,0.3) background-color: rgba(255,255,255,0.3)
} }
.p-inputgroup {
align-items: center;
}
.flex-1 {
display: flex;
align-items: center;
gap: 0.5em;
}
.mb-2 { .mb-2 {
margin-bottom: 4px; margin-bottom: 4px;
} }

View File

@@ -15,9 +15,10 @@
--global-textColor: #4B5563; --global-textColor: #4B5563;
--theme-highlight-background: #BADEBE; --theme-highlight-background: #BADEBE;
--primary-text: #3B7C43; --primary-text: #3B7C43;
--table-border-color: #B7B7B7B2;
--message-error-background: #ffdbdb; --message-error-background: #ffdbdb;
--message-error-color: #C2504D; --message-error-color: #C2504D;
--message-info-background: rgba(219, 234, 254, 0.70); --message-info-background: rgba(183, 183, 183, 0.7);
--message-info-color: #3B82F6; --message-info-color: #3B82F6;
--card-full-background-color-2: #EEC137; --card-full-background-color-2: #EEC137;

View File

@@ -0,0 +1,150 @@
import React, { useEffect, useState } from 'react';
import { classNames } from 'primereact/utils';
import { __ } from '@wordpress/i18n';
import {
useReactTable,
getCoreRowModel,
flexRender,
} from '@tanstack/react-table';
import { pathOr, isEmpty } from 'ramda';
import { wrap } from 'object-path-immutable';
const Table = ({
fieldName,
label,
register,
errors,
config = {},
defaultValue = [],
tableColumns = []
}) => {
const [stateFieldData, setStateFieldData] = useState([]);
const [rowsData, setRowsData] = useState([]);
const [isDisabledNewRow, setIsDisabledNewRow] = useState(false);
const [columns, setColumns] = useState([]);
const table = useReactTable({
data: rowsData,
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}
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 obj = stateFieldData
.reduce((acc, cur) => {
acc[cur.name] = ''
return acc;
}, {});
setRowsData([...rowsData, obj]);
}
useEffect(() => {
let shouldDisableNewRows = false;
const columns = stateFieldData.map((o) => {
const item = {
accessorKey: o.name,
header: () => o.label,
footer: (props) => props.column.id
}
if (o.predefined) {
shouldDisableNewRows = true;
item.cell = (info) => {
return info.getValue();
}
}
return item;
});
setIsDisabledNewRow(shouldDisableNewRows);
setColumns(columns);
}, [stateFieldData]);
useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], tableColumns);
const rowsData = pathOr([], ['rowsData'], tableColumns);
setStateFieldData(stateFieldData);
setRowsData(rowsData);
}, [tableColumns]);
useEffect(() => {
register(fieldName, config)
}, []);
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired ? '*' : null}
</label>
<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>
{!isDisabledNewRow && !isEmpty(columns)
? <div className="addNewTableRow" onClick={addNewRow}>{__('Aggiungi una righa', 'gepafin')}</div>
: null}
</>)
}
export default Table;

View File

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

View File

@@ -9,6 +9,7 @@ import { MultiSelect } from 'primereact/multiselect';
import { Editor } from 'primereact/editor'; import { Editor } from 'primereact/editor';
import { mimeTypes } from '../../../../../../configData'; import { mimeTypes } from '../../../../../../configData';
import ElementSettingTableColumns from '../ElementSettingTableColumns';
const ElementSetting = ({ setting, changeFn, updateDataFn }) => { const ElementSetting = ({ setting, changeFn, updateDataFn }) => {
@@ -18,7 +19,8 @@ const ElementSetting = ({ setting, changeFn, updateDataFn }) => {
step: __('Numero Decimali', 'gepafin'), step: __('Numero Decimali', 'gepafin'),
options: __('Opzioni', 'gepafin'), options: __('Opzioni', 'gepafin'),
mime: __('Tipo di file', 'gepafin'), mime: __('Tipo di file', 'gepafin'),
text: __('Testo formattato', 'gepafin') text: __('Testo formattato', 'gepafin'),
table_columns: __('Colonne', 'gepafin'),
} }
const renderHeader = () => { const renderHeader = () => {
@@ -41,31 +43,42 @@ const ElementSetting = ({ setting, changeFn, updateDataFn }) => {
const header = renderHeader(); const header = renderHeader();
return <div className="formElementSettings__field" key={setting.name}> const getProperField = (setting) => {
<label htmlFor={setting.name}>{settingLabels[setting.name]}</label> if (setting.name === 'options') {
{setting.name === 'options' return <ElementSettingRepeater
? <ElementSettingRepeater
value={is(Array, setting.value) ? setting.value : []} value={is(Array, setting.value) ? setting.value : []}
name={setting.name} name={setting.name}
setDataFn={updateDataFn}/> setDataFn={updateDataFn}/>
: setting.name === 'mime' } else if (setting.name === 'mime') {
? <MultiSelect return <MultiSelect
value={is(Array, setting.value) ? setting.value : []} value={is(Array, setting.value) ? setting.value : []}
onChange={(e) => updateDataFn(setting.name, e.value)} onChange={(e) => updateDataFn(setting.name, e.value)}
options={mimeTypes} options={mimeTypes}
optionLabel="name" optionLabel="name"
display="chip" display="chip"
placeholder={__('Scegli', 'gepafin')} /> placeholder={__('Scegli', 'gepafin')} />
: setting.name === 'text' } else if (setting.name === 'text') {
? <Editor return <Editor
value={setting.value} value={setting.value}
headerTemplate={header} headerTemplate={header}
onTextChange={(e) => changeFn(e.htmlValue, setting.name)} onTextChange={(e) => changeFn(e.htmlValue, setting.name)}
style={{ height: 80 * 4 }} style={{ height: 80 * 4 }}
/> />
: <InputText id={setting.name} aria-describedby={`${setting.name}-help`} } else if (setting.name === 'table_columns') {
value={setting.value} return <ElementSettingTableColumns
onChange={(e) => changeFn(e.target.value, setting.name)}/>} value={is(Object, setting.value) ? setting.value : {}}
name={setting.name}
setDataFn={updateDataFn}/>
} else {
return <InputText id={setting.name} aria-describedby={`${setting.name}-help`}
value={setting.value}
onChange={(e) => changeFn(e.target.value, setting.name)}/>
}
}
return <div className="formElementSettings__field" key={setting.name}>
<label htmlFor={setting.name}>{settingLabels[setting.name]}</label>
{getProperField(setting)}
</div> </div>
} }

View File

@@ -45,10 +45,9 @@ const ElementSettingRepeater = ({
}, []); }, []);
useEffect(() => { useEffect(() => {
console.log('useEffect', [...stateFieldData])
setDataFn(name, [...stateFieldData]); setDataFn(name, [...stateFieldData]);
}, [stateFieldData]) }, [stateFieldData])
console.log('stateFieldData', stateFieldData, value)
return ( return (
<div className="formElementSettings__repeater"> <div className="formElementSettings__repeater">
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem"> {stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">

View File

@@ -0,0 +1,139 @@
import React, { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { wrap } from 'object-path-immutable';
import { findIndex, propEq } 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
}) => {
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 = (index) => {
const newStateFieldData = wrap(stateFieldData)
.insert([index, 'rows'], { label: '' }, stateFieldData[index].rows.length)
.value();
setStateFieldData(newStateFieldData);
}
const removeRow = (index, indexK) => {
const newStateFieldData = wrap(stateFieldData)
.del([index, 'rows', indexK])
.value();
setStateFieldData(newStateFieldData);
}
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, index, indexK) => {
const { value } = e.target;
const newStateFieldData = wrap(stateFieldData)
.set([index, 'rows', indexK, 'label'], value)
.value();
setStateFieldData(newStateFieldData);
}
const setChecked = (value, index) => {
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.predefined = value;
if (value === false) {
o.rows = [];
}
}
return o;
})
setStateFieldData(newData);
}
const properField = (item, i) => {
return <>
<InputText value={item.label} onInput={(e) => onInputChange(e, i)}/>
<div className="flex-1">
<span>{__('Predefinito?', 'gepafin')}</span>
<InputSwitch checked={item.predefined} onChange={(e) => setChecked(e.value, i)}/>
</div>
</>
}
const properSubField = (item, i, k) => {
return <InputText value={item.label} onInput={(e) => onSubInputChange(e, i, k)}/>
}
useEffect(() => {
const storeFieldData = value ?? [];
setStateFieldData(storeFieldData);
}, []);
useEffect(() => {
setDataFn(name, [...stateFieldData]);
}, [stateFieldData]);
stateFieldData.filter(o => o.predefined)
return (
<>
<div className="formElementSettings__repeater">
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
<div className="p-inputgroup flex-1">
{properField(o, i)}
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeItem(i)}/>
</div>
</div>)}
<Button type="button" outlined label={__('Aggiungi', 'gepafin')} onClick={addNewItem}/>
</div>
{stateFieldData
.filter(o => o.predefined)
.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
<div className="formElementSettings__repeater formElementSettings__subRepeater">
<label>{__('Righe per colonna:', 'gepafin')} <strong>{o.label}</strong></label>
<div className="formElementSettings__repeater">
{o.rows.map((c, k) => {
const properIndex = findIndex(propEq(o.name, 'name'))(stateFieldData);
return <div key={k} className="formElementSettings__repeaterItem">
<div className="p-inputgroup flex-1">
{properSubField(c, properIndex, k)}
<Button icon="pi pi-times" className="p-button-danger"
onClick={() => removeRow(properIndex, k)}/>
</div>
</div>
})}
<Button type="button" outlined
label={__('Aggiungi una righa', 'gepafin')}
onClick={() => addNewRow(findIndex(propEq(o.name, 'name'))(stateFieldData))}/>
</div>
</div>
</div>)}
</>
)
}
export default ElementSettingTableColumns;

View File

@@ -0,0 +1,151 @@
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
}) => {
const [stateFieldData, setStateFieldData] = useState([]);
const [rowsData, setRowsData] = useState([]);
const removeItem = (index) => {
const newData = stateFieldData.toSpliced(index, 1);
setStateFieldData(newData);
}
const addNewItem = () => {
setStateFieldData([...stateFieldData, { name: uniqid('o'), label: '', predefined: false }]);
}
const addNewRow = () => {
const obj = stateFieldData
.filter(o => o.predefined)
.reduce((acc, cur) => {
acc[cur.name] = ''
return acc;
}, {});
setRowsData([...rowsData, obj]);
}
const removeRow = (index) => {
const newRowsData = wrap(rowsData).del([index]).value();
setRowsData(newRowsData);
}
const onInputChange = (e, index) => {
const { value } = e.target;
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.label = value;
}
return o;
})
setStateFieldData(newData);
}
const onSubInputChange = (e, name, index) => {
const { value } = e.target;
const newRowsData = wrap(rowsData).set([index, name], value).value();
setRowsData(newRowsData);
}
const setChecked = (value, index) => {
let name = '';
const newData = stateFieldData.map((o, i) => {
if (i === index) {
o.predefined = value;
name = o.name;
}
return o;
});
let newRowsData = [];
if (value === false) {
newRowsData = rowsData.map(o => wrap(o).set([name], '').value());
} else {
newRowsData = rowsData.map(o => wrap(o).set([name], '').value());
}
setRowsData(newRowsData);
setStateFieldData(newData);
}
const properField = (item, i) => {
return <>
<InputText value={item.label} onInput={(e) => onInputChange(e, i)}/>
<div className="flex-1">
<span>{__('Predefinito?', 'gepafin')}</span>
<InputSwitch checked={item.predefined} onChange={(e) => setChecked(e.value, i)}/>
</div>
</>
}
const properSubField = (item, i, name) => {
return <InputText value={item[name]} onInput={(e) => onSubInputChange(e, name, i)}/>
}
useEffect(() => {
const stateFieldData = pathOr([], ['stateFieldData'], value);
setStateFieldData(stateFieldData);
const rowsData = pathOr([], ['rowsData'], value);
setRowsData(rowsData);
}, []);
useEffect(() => {
setDataFn(name, {
stateFieldData,
rowsData
});
}, [stateFieldData, rowsData]);
stateFieldData.filter(o => o.predefined)
return (
<>
<div className="formElementSettings__repeater">
{stateFieldData.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
<div className="p-inputgroup flex-1">
{properField(o, i)}
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeItem(i)}/>
</div>
</div>)}
<Button type="button" outlined label={__('Aggiungi', 'gepafin')} onClick={addNewItem}/>
</div>
{stateFieldData
.filter(o => o.predefined)
.map((o, i) => <div key={i} className="formElementSettings__repeaterItem">
<div className="formElementSettings__repeater formElementSettings__subRepeater">
<label>{__('Righe per colonna:', 'gepafin')} <strong>{o.label}</strong></label>
<div className="formElementSettings__repeater">
{rowsData.map((c, k) => {
return <div key={k} className="formElementSettings__repeaterItem">
<div className="p-inputgroup flex-1">
{properSubField(c, k, o.name)}
<Button icon="pi pi-times" className="p-button-danger"
onClick={() => removeRow(k)}/>
</div>
</div>
})}
<Button type="button" outlined
label={__('Aggiungi una righa', 'gepafin')}
onClick={addNewRow}/>
</div>
</div>
</div>)}
</>
)
}
export default ElementSettingTableColumns;

View File

@@ -121,6 +121,7 @@ const BandoFormsPreview = () => {
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 = '';
@@ -166,6 +167,7 @@ const BandoFormsPreview = () => {
options={options ? options.value : []} options={options ? options.value : []}
setDataFn={setValue} setDataFn={setValue}
sourceId={0} sourceId={0}
tableColumns={tableColumns.value ? tableColumns.value : {}}
/> />
})} })}
</form> </form>

View File

@@ -73,7 +73,7 @@ const routes = ({ role }) => {
{'ROLE_BENEFICIARY' === role ? <BandoApplication/> : null} {'ROLE_BENEFICIARY' === role ? <BandoApplication/> : null}
</DefaultLayout>}/> </DefaultLayout>}/>
<Route path="/profilo" element={<DefaultLayout> <Route path="/profilo" element={<DefaultLayout>
{'ROLE_SUPER_ADMIN' === role ? <PageNotFound/> : null} {'ROLE_SUPER_ADMIN' === role ? <Profile/> : null}
{'ROLE_BENEFICIARY' === role ? <Profile/> : null} {'ROLE_BENEFICIARY' === role ? <Profile/> : null}
</DefaultLayout>}/> </DefaultLayout>}/>
<Route path="/profilo-aziendale" element={<DefaultLayout> <Route path="/profilo-aziendale" element={<DefaultLayout>

View File

@@ -747,7 +747,7 @@ export const elementItems = [
}, },
{ {
name: "table_columns", name: "table_columns",
value: "Tabella" value: {}
} }
], ],
validators: {} validators: {}