- saving progress;

This commit is contained in:
Vitalii Kiiko
2025-01-21 11:08:37 +01:00
parent 61763a961b
commit 07cecda529
23 changed files with 915 additions and 62 deletions

View File

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

View File

@@ -0,0 +1,21 @@
import { head, isEmpty, isNil, sum } from 'ramda';
const LastRowCell = ({columnId, lastRows, columnMeta = {}, getColumnDataFn}) => {
const cellData = head(lastRows.filter(o => !isNil(o[columnId])));
let cellValue = cellData[columnId];
if (columnMeta.enableFormula) {
const getAllRowsValues = getColumnDataFn(columnId)
.map(v => isEmpty(v) || isNil(v) ? 0 : v);
if (cellValue === 'sum') {
cellValue = sum(getAllRowsValues);
} else {
cellValue = 0;
}
}
return <td>{cellValue}</td>;
};
export default LastRowCell;

View File

@@ -0,0 +1,34 @@
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);
}
};
return (
<input
type="number"
disabled={disabled}
value={initialValue ?? 0}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
step="any"
className="w-full px-2 py-1 border rounded"
/>
);
};
export default NumericFormulaCell;

View File

@@ -0,0 +1,86 @@
import React from 'react';
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { wrap } from 'object-path-immutable';
import { isEmpty } from 'ramda';
// components
import DefaultCell from './components/DefaultCell';
import LastRowCell from './components/LastRowCell';
const RenderTable = ({ data, columns, lastRow, setRowsFn, disabled }) => {
const table = useReactTable({
data,
columns,
defaultColumn: {
cell: DefaultCell
},
getCoreRowModel: getCoreRowModel(),
meta: {
disabled,
updateData: (rowIndex, columnId, value) => {
const newRowsData = wrap(data).set([rowIndex, columnId], value).value();
setRowsFn(newRowsData);
},
}
});
const getColumnData = (columnId) => {
const rows = table.getRowModel().rows;
return rows.map(row => row.getValue(columnId));
};
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>
);
})}
{!isEmpty(lastRow)
? <tr>
{columns.map((o) => <LastRowCell
key={o.accessorKey}
columnId={o.accessorKey}
columnMeta={o.meta}
lastRows={lastRow}
getColumnDataFn={getColumnData}/>)}
</tr>
: null}
</tbody>
</table>
)
}
export default RenderTable

View File

@@ -0,0 +1,132 @@
import React, { useEffect, useState, useCallback } from 'react';
import { classNames } from 'primereact/utils';
import { pathOr, isEmpty } from 'ramda';
import { wrap } from 'object-path-immutable';
import equal from 'fast-deep-equal';
import { klona } from 'klona';
// tools
import { nonEmptyTables } from '../../../../helpers/validators';
//components
import RenderTable from './RenderTable';
import NumericFormulaCell from './RenderTable/components/NumericFormulaCell';
const Table = ({
fieldName,
setDataFn,
label,
register,
errors,
disabled = false,
config = {},
defaultValue = [],
tableColumns = []
}) => {
const [columnsCfg, setColumnsCfg] = useState([]);
const [rowsCfg, setRowsCfg] = useState([]);
const [columns, setColumns] = useState([]);
const [lastRow, setLastRow] = useState([]);
const [rows, setRows] = useState(null);
const updateRows = useCallback((data) => {
setRows(data);
setDataFn(fieldName, data, { shouldValidate: true });
}, [rows, 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.predefined) {
item.cell = (info) => info.getValue();
} else if (o.enableFormula || o.fieldtype === 'numeric') {
item.cell = NumericFormulaCell;
}
return item;
});
setColumns(newColumns);
}, [columnsCfg, disabled]);
useEffect(() => {
setRows(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(rowsData);
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(rows, defaultValue)) {
setRows(defaultValue);
}
}, [defaultValue]);
useEffect(() => {
setRows(defaultValue);
register(fieldName, properConfig(config));
}, []);
console.log('rows', rows, lastRow, tableColumns)
return (
<>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
{label}{config.required || config.isRequired || (config.validate && config.validate.nonEmptyTables)
? <span className="appForm__field--required">*</span> : null}
</label>
{rows ? <RenderTable
columns={columns}
data={rows}
lastRow={lastRow}
setRowsFn={updateRows}
disabled={disabled}/> : null}
</>)
}
export default Table;

View File

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

View File

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