Skip to content

Commit 6eae547

Browse files
committed
make it functional
1 parent ee9e4ab commit 6eae547

File tree

3 files changed

+497
-289
lines changed

3 files changed

+497
-289
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
import {
2+
ClearIndicator,
3+
DropdownIndicator,
4+
MenuList,
5+
Option,
6+
customSelectStyles,
7+
selectTheme,
8+
} from '@plone/volto/components/manage/Widgets/SelectStyling';
9+
import { Input, Pagination, Table } from 'semantic-ui-react';
10+
import { usePagination, useTable } from 'react-table';
11+
12+
import { Icon } from '@plone/volto/components';
13+
import React from 'react';
14+
import { compose } from 'redux';
15+
import deleteSVG from '@plone/volto/icons/delete.svg';
16+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
17+
import { map } from 'lodash';
18+
import { normalizeValue } from '@plone/volto/components/manage/Widgets/SelectUtils';
19+
import paginationLeftSVG from '@plone/volto/icons/left-key.svg';
20+
import paginationRightSVG from '@plone/volto/icons/right-key.svg';
21+
import plusSVG from '@plone/volto/icons/circle-plus.svg';
22+
import { useIntl } from 'react-intl';
23+
24+
const FieldEditor = (props) => {
25+
const {
26+
fieldSchema,
27+
value,
28+
onChange,
29+
onChangeSelect,
30+
onBlur,
31+
reactSelect,
32+
} = props;
33+
const Select = reactSelect.default;
34+
35+
const { type, choices, id, minimum, maximum, defaultValue } = fieldSchema;
36+
const intl = useIntl();
37+
const normalizedValue = normalizeValue(choices, value, intl);
38+
if (choices) {
39+
const options = [
40+
...map(choices, (option) => ({
41+
value: option[0],
42+
label:
43+
// Fix "None" on the serializer, to remove when fixed in p.restapi
44+
option[1] !== 'None' && option[1] ? option[1] : option[0],
45+
})),
46+
];
47+
return (
48+
<Select
49+
id={`field-${id}`}
50+
value={normalizedValue}
51+
className="react-table-select-field"
52+
theme={selectTheme}
53+
components={{
54+
...(choices?.length > 25 && {
55+
MenuList,
56+
}),
57+
DropdownIndicator,
58+
ClearIndicator,
59+
Option: Option,
60+
}}
61+
options={options}
62+
styles={customSelectStyles}
63+
onChange={onChangeSelect}
64+
onBlur={onBlur}
65+
/>
66+
);
67+
}
68+
if (type === 'number') {
69+
return (
70+
<Input
71+
id={`field-${id}`}
72+
className="react-table-integer-field"
73+
type="number"
74+
min={minimum || null}
75+
max={maximum || null}
76+
value={value || defaultValue}
77+
onChange={onChange}
78+
onBlur={onBlur}
79+
/>
80+
);
81+
}
82+
return (
83+
<Input
84+
id={`field-${id}`}
85+
type="text"
86+
className="react-table-text-field"
87+
fluid
88+
value={value}
89+
onChange={onChange}
90+
onBlur={onBlur}
91+
/>
92+
);
93+
};
94+
// Create an editable cell renderer
95+
const EditableCell = ({
96+
value: initialValue,
97+
row: { index },
98+
column: { id },
99+
updateCell, // This is a custom function that we supplied to our table instance
100+
selectedRow,
101+
setSelectedRow,
102+
schema,
103+
reactSelect,
104+
}) => {
105+
const fieldSchema = { ...schema?.properties?.[id], id: id };
106+
const [value, setValue] = React.useState(initialValue);
107+
const onChange = (e) => {
108+
setValue(e.target.value);
109+
};
110+
111+
const onChangeSelect = (e) => {
112+
setValue(e.value);
113+
};
114+
115+
const onBlur = () => {
116+
updateCell(index, id, value);
117+
};
118+
119+
React.useEffect(() => {
120+
setValue(initialValue);
121+
}, [initialValue]);
122+
return selectedRow === index ? (
123+
// eslint-disable-next-line jsx-a11y/no-autofocus
124+
<FieldEditor
125+
fieldSchema={fieldSchema}
126+
value={value}
127+
onChange={onChange}
128+
onChangeSelect={onChangeSelect}
129+
onBlur={onBlur}
130+
reactSelect={reactSelect}
131+
/>
132+
) : (
133+
<span
134+
role="button"
135+
className="editable-cell"
136+
tabIndex={0}
137+
onClick={() => {
138+
setSelectedRow(index);
139+
}}
140+
onKeyDown={() => {
141+
setSelectedRow(index);
142+
}}
143+
onFocus={() => {
144+
setSelectedRow(index);
145+
}}
146+
>
147+
{value || <>&nbsp;</>}
148+
</span>
149+
);
150+
};
151+
152+
const defaultColumn = {
153+
Cell: EditableCell,
154+
};
155+
156+
function EditableTable(props) {
157+
const {
158+
columns,
159+
data,
160+
updateCell,
161+
removeRow,
162+
addRowAfter,
163+
selectedRow,
164+
setSelectedRow,
165+
schema,
166+
reactSelect,
167+
} = props;
168+
if (data.length === 0) {
169+
addRowAfter({ key: 'Enter' }, 0);
170+
}
171+
const {
172+
getTableProps,
173+
getTableBodyProps,
174+
headerGroups,
175+
prepareRow,
176+
page,
177+
pageCount,
178+
gotoPage,
179+
setPageSize,
180+
state: { pageIndex, pageSize },
181+
} = useTable(
182+
{
183+
columns,
184+
data,
185+
defaultColumn,
186+
updateCell,
187+
selectedRow,
188+
setSelectedRow,
189+
schema,
190+
reactSelect,
191+
},
192+
usePagination,
193+
);
194+
195+
// Render the UI for your table
196+
return (
197+
<>
198+
<Table celled {...getTableProps()}>
199+
<Table.Header>
200+
{headerGroups.map((headerGroup, key) => (
201+
<Table.Row key={key} {...headerGroup.getHeaderGroupProps()}>
202+
{headerGroup.headers.map((column) => (
203+
<Table.HeaderCell {...column.getHeaderProps()}>
204+
{column.render('Header')}
205+
</Table.HeaderCell>
206+
))}
207+
<Table.HeaderCell>{'Actions'}</Table.HeaderCell>
208+
</Table.Row>
209+
))}
210+
</Table.Header>
211+
<Table.Body {...getTableBodyProps()}>
212+
{page.map((row, i) => {
213+
prepareRow(row);
214+
return (
215+
<Table.Row
216+
className={selectedRow === i ? 'selected-row' : ''}
217+
{...row.getRowProps()}
218+
>
219+
{row.cells.map((cell) => {
220+
return (
221+
<Table.Cell {...cell.getCellProps()}>
222+
{cell.render('Cell')}
223+
</Table.Cell>
224+
);
225+
})}
226+
<Table.Cell>
227+
<div className={'row-actions'}>
228+
<span
229+
onClick={(e) => addRowAfter(e, i)}
230+
onKeyDown={(e) => addRowAfter(e, i)}
231+
tabIndex={0}
232+
role="button"
233+
className="row-action"
234+
>
235+
<Icon name={plusSVG} size="23px" />
236+
</span>
237+
<span
238+
onClick={(e) => removeRow(e, i)}
239+
onKeyDown={(e) => removeRow(e, i)}
240+
tabIndex={0}
241+
role="button"
242+
className="row-action"
243+
>
244+
<Icon name={deleteSVG} size="23px" color="red" />
245+
</span>
246+
</div>
247+
</Table.Cell>
248+
</Table.Row>
249+
);
250+
})}
251+
</Table.Body>
252+
</Table>
253+
<div className="pagination-wrapper react-table-pagination">
254+
<Pagination
255+
activePage={pageIndex + 1}
256+
totalPages={pageCount}
257+
onPageChange={(e, { activePage }) => {
258+
gotoPage(activePage - 1);
259+
}}
260+
firstItem={null}
261+
lastItem={null}
262+
prevItem={{
263+
content: <Icon name={paginationLeftSVG} size="18px" />,
264+
icon: true,
265+
'aria-disabled': pageIndex + 1 === 1,
266+
className: pageIndex + 1 === 1 ? 'disabled' : null,
267+
}}
268+
nextItem={{
269+
content: <Icon name={paginationRightSVG} size="18px" />,
270+
icon: true,
271+
'aria-disabled': pageIndex + 1 === pageCount,
272+
className: pageIndex + 1 === pageCount ? 'disabled' : null,
273+
}}
274+
></Pagination>
275+
{/* eslint-disable-next-line jsx-a11y/no-onchange */}
276+
<select
277+
style={{ maxWidth: '7rem' }}
278+
value={pageSize}
279+
onChange={(e) => {
280+
setPageSize(Number(e.target.value));
281+
}}
282+
>
283+
{[10, 25, 50, 100].map((pageSize) => (
284+
<option key={pageSize} value={pageSize}>
285+
Show {pageSize}
286+
</option>
287+
))}
288+
</select>
289+
</div>
290+
</>
291+
);
292+
}
293+
294+
export default compose(injectLazyLibs(['reactSelect']))(EditableTable);

0 commit comments

Comments
 (0)