admin_lsac 1.4.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +3 -1
- package/README.md +177 -19
- package/dist/esm/src/components/AdminPage.d.ts +2 -2
- package/dist/esm/src/components/AdminPage.js +27 -27
- package/dist/esm/src/components/GenericForm.d.ts +1 -1
- package/dist/esm/src/components/GenericForm.js +28 -30
- package/dist/esm/src/main.js +1 -1
- package/dist/esm/src/schema.d.ts +4 -4
- package/dist/esm/src/utils/websockets.js +3 -3
- package/package.json +1 -1
- package/vite.config.ts +3 -3
package/.eslintrc.cjs
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = {
|
|
|
6
6
|
'plugin:@typescript-eslint/recommended',
|
|
7
7
|
'plugin:react-hooks/recommended',
|
|
8
8
|
],
|
|
9
|
-
ignorePatterns: ['dist', '.eslintrc.cjs', '
|
|
9
|
+
ignorePatterns: ['dist', '.eslintrc.cjs', 'server'],
|
|
10
10
|
parser: '@typescript-eslint/parser',
|
|
11
11
|
plugins: ['react-refresh'],
|
|
12
12
|
rules: {
|
|
@@ -14,5 +14,7 @@ module.exports = {
|
|
|
14
14
|
'warn',
|
|
15
15
|
{ allowConstantExport: true },
|
|
16
16
|
],
|
|
17
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
18
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
17
19
|
},
|
|
18
20
|
}
|
package/README.md
CHANGED
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
For configuring your own admin panel, follow the next instructions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## How to install it?
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### `npm install export-to-csv@1.2.1`
|
|
7
|
+
- `npm install admin_lsac`
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
This command will download and install the specified package and its dependencies into your project's node_modules directory, making it available for use in your project.
|
|
12
10
|
|
|
13
11
|
## What do you need to import?
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
- `import { ChakraProvider } from '@chakra-ui/react'`
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
- `import { pageToUrl } from 'admin_lsac'`
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
- `import { AdminPage } from 'admin_lsac'`
|
|
20
18
|
|
|
21
|
-
You can import these in App.js / App.ts
|
|
19
|
+
You can import these in App.js / App.ts or wherever your routes to the pages are located.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
- `import { PageSchema } from 'admin_lsac'`
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
If your project is TypeScript-based, you need to use the line above as well.
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
Import the component from above where you want to write your schemas! (See below for details about schemas)
|
|
26
|
+
|
|
27
|
+
## How to add routes to the admin panel's pages?
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
{your_array.map((page) => (
|
|
@@ -39,7 +39,7 @@ You need to create an array of schemas for your own admin panel. You can write t
|
|
|
39
39
|
}
|
|
40
40
|
/>
|
|
41
41
|
))}
|
|
42
|
-
{your_array[0] ? (
|
|
42
|
+
{your_array[0] ? ( // recommended default route when accessing admin panel
|
|
43
43
|
<Route
|
|
44
44
|
path={`${your_url_to_admin_panel}/*`}
|
|
45
45
|
element={
|
|
@@ -53,7 +53,9 @@ You need to create an array of schemas for your own admin panel. You can write t
|
|
|
53
53
|
) : null}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
You will need to create an array of schemas for your own admin panel.
|
|
57
|
+
|
|
58
|
+
#### ⚠️ Don't forget to wrap the Router component into a ChakraProvider component. Otherwise, you will not like the way it looks. :)
|
|
57
59
|
|
|
58
60
|
## What is a schema?
|
|
59
61
|
|
|
@@ -121,9 +123,165 @@ export interface RowAction {
|
|
|
121
123
|
}
|
|
122
124
|
```
|
|
123
125
|
|
|
124
|
-
A page schema is based on these interfaces,
|
|
126
|
+
A page schema is based on these interfaces, and it describes how one table of your admin panel would need to be structured to accomplish what the user needs to do regarding an entity. (i.e. users, teams, companies, quizzes)
|
|
127
|
+
|
|
128
|
+
The most important interface is `PageSchema`, as it is at the top of the relations between all these interfaces.
|
|
129
|
+
|
|
130
|
+
What do the fields of `PageSchema` refer to?
|
|
131
|
+
|
|
132
|
+
- name = name of the table
|
|
133
|
+
- actions = these are operations that can affect all entries in the table (like updating all or some of the entries in the table) or operations that envolve creating new entries (one by one)
|
|
134
|
+
- rowActions = these are operations that can affect just one entry/row (like updating or deleting an entry)
|
|
135
|
+
- getRequest = this is a field containg the function that will retrieve all the entries from one table in the database
|
|
136
|
+
- tableFields = these are the 'definitions' of the displayed table's columns
|
|
137
|
+
|
|
138
|
+
The operations for the rowActions and actions will generally have a form to complete before executing it. There are 4 types of inputs for the form part:
|
|
139
|
+
|
|
140
|
+
- simpleField = simple data like numbers, strings or files
|
|
141
|
+
- dropdownField = enable the user to choose an option from a predefined list
|
|
142
|
+
- arrayField = arrays of data
|
|
143
|
+
- objectField = an object containing more fields/inputs, or even other objects (some cool recursion)
|
|
144
|
+
|
|
145
|
+
#### Schema example
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
const domain = "http://localhost:3001"; // dev mode
|
|
149
|
+
|
|
150
|
+
// helper function
|
|
151
|
+
const getAuth = () => {
|
|
152
|
+
// return a token or whatever :)
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const companies: PageSchema = {
|
|
156
|
+
name: "Companies",
|
|
157
|
+
|
|
158
|
+
getRequest: async () => { // retrieve all entries from the database
|
|
159
|
+
const res = await axios.get(domain + "/something", {
|
|
160
|
+
headers: getAuth(),
|
|
161
|
+
});
|
|
162
|
+
return res.data;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
tableFields: [ // the columns displayed on the page
|
|
166
|
+
{
|
|
167
|
+
name: "Company ID", // the ID must be displayed (and as first column),
|
|
168
|
+
// if you would like to use rowActions ⚠️
|
|
169
|
+
access: (obj: any) => obj.id, // obj.field_name
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "Name",
|
|
173
|
+
access: (obj: any) => obj.name,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "Logo",
|
|
177
|
+
access: (obj: any) => // you can have even more complex functions
|
|
178
|
+
obj.logoUrl
|
|
179
|
+
? React.createElement("img", {
|
|
180
|
+
src: obj.logoUrl,
|
|
181
|
+
alt: "ERROR",
|
|
182
|
+
style: { width: "100px" },
|
|
183
|
+
})
|
|
184
|
+
: "No Image Available",
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
|
|
188
|
+
actions: [
|
|
189
|
+
{
|
|
190
|
+
name: "Create New Quiz",
|
|
191
|
+
fields: [
|
|
192
|
+
{
|
|
193
|
+
name: "name",
|
|
194
|
+
label: "Quiz Name",
|
|
195
|
+
type: "string", // for every FormField, type must be specified
|
|
196
|
+
validationSchema: Yup.string().required(),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "questions",
|
|
200
|
+
label: "Questions",
|
|
201
|
+
type: "array",
|
|
202
|
+
element: {
|
|
203
|
+
name: "",
|
|
204
|
+
label: "",
|
|
205
|
+
type: "object",
|
|
206
|
+
fields: [
|
|
207
|
+
{
|
|
208
|
+
name: "text",
|
|
209
|
+
label: "Question Text",
|
|
210
|
+
type: "string",
|
|
211
|
+
validationSchema: Yup.string().required(),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "type",
|
|
215
|
+
label: "Question Type",
|
|
216
|
+
type: "dropdown",
|
|
217
|
+
getOptions: async () => [
|
|
218
|
+
{
|
|
219
|
+
value: "OPEN",
|
|
220
|
+
display: "Open",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
value: "SINGLE",
|
|
224
|
+
display: "Single",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
value: "MULTIPLE",
|
|
228
|
+
display: "Multiple",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
onSubmit: async (formValues: any) => { // onSubmit function can also be complex
|
|
237
|
+
// for actions, onSubmit will always have one param (formValues - you can change the name though)
|
|
238
|
+
// process formVaues as you wish
|
|
239
|
+
|
|
240
|
+
// Nevertheless, submitting the actual data is the most important thing
|
|
241
|
+
const result = await axios.post(domain + "/something", formValues, {
|
|
242
|
+
headers: getAuth(),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return result.data;
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
|
|
250
|
+
rowActions: [
|
|
251
|
+
{
|
|
252
|
+
name: "Delete Event - Variant 1", // without form
|
|
253
|
+
onSubmit: async (eventId: number) => { // onSubmit has only param, the row's ID
|
|
254
|
+
const res = await axios.delete(
|
|
255
|
+
domain + "/something/" + eventId
|
|
256
|
+
);
|
|
257
|
+
return res.data;
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "Delete Event - Variant 2", // with form
|
|
262
|
+
fields: [
|
|
263
|
+
{
|
|
264
|
+
name: "input",
|
|
265
|
+
label: "Mock Field",
|
|
266
|
+
type: "string",
|
|
267
|
+
validationSchema: "",
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
onSubmit: async (formValues: any, eventId: number) => { // onSubmit has two params
|
|
271
|
+
// the first param - formValues, while the second is the row's ID
|
|
272
|
+
const res = await axios.delete(
|
|
273
|
+
domain + "/something/" + eventId
|
|
274
|
+
);
|
|
275
|
+
return res.data;
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Please note that the schema above is not related to any entity, and the fields of PageSchema do not necessarily have any relation between them. These are just random examples to teach you how to create your own schemas.
|
|
125
283
|
|
|
126
|
-
#### An array of schemas
|
|
284
|
+
#### An array of schemas must be defined as below:
|
|
127
285
|
|
|
128
286
|
```
|
|
129
287
|
const your_array = [
|
|
@@ -137,7 +295,7 @@ const your_array = [
|
|
|
137
295
|
];
|
|
138
296
|
```
|
|
139
297
|
|
|
140
|
-
|
|
298
|
+
- In TypeScript, it would slightly change:
|
|
141
299
|
|
|
142
300
|
```
|
|
143
301
|
const your_array: PageSchema[] = [
|
|
@@ -153,9 +311,9 @@ const your_array: PageSchema[] = [
|
|
|
153
311
|
|
|
154
312
|
## Other Specifications
|
|
155
313
|
|
|
156
|
-
Due to some corrupt dependencies, you need a Node version >= 20.5.0 to use this package in a
|
|
314
|
+
Due to some corrupt dependencies, you will need a Node version >= 20.5.0 to use this NPM package (it may work in some cases, but we still recommend updating Node to a newer version).
|
|
157
315
|
|
|
158
|
-
For every entity you describe through a page schema, getRequest and onSubmit functions
|
|
316
|
+
For every entity you describe through a page schema, getRequest and onSubmit functions use routes created for the entity. `getRequest` function makes a GET request, while `onSumbit` function generally makes a POST, PATCH or DELETE request.
|
|
159
317
|
|
|
160
318
|
## Learn More
|
|
161
319
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { PageSchema } from
|
|
1
|
+
import { PageSchema } from '../schema';
|
|
2
2
|
export declare function pageToUrl(page: PageSchema): string;
|
|
3
|
-
export declare function AdminPage({ pages, selectedPage, basePath, apiURL }: {
|
|
3
|
+
export declare function AdminPage({ pages, selectedPage, basePath, apiURL, }: {
|
|
4
4
|
pages: PageSchema[];
|
|
5
5
|
selectedPage: PageSchema;
|
|
6
6
|
basePath: string;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from
|
|
3
|
-
import { ChakraProvider } from
|
|
4
|
-
import { Button, Modal, ModalHeader, ModalBody, useDisclosure, ModalOverlay, ModalContent, ModalCloseButton, Box, Stack, } from
|
|
5
|
-
import { GenericForm } from
|
|
6
|
-
import { ThemeProvider } from
|
|
7
|
-
import { createTheme } from
|
|
8
|
-
import { MaterialReactTable } from
|
|
9
|
-
import { mkConfig, generateCsv, download } from
|
|
10
|
-
import Swal from
|
|
11
|
-
import MenuItem from
|
|
12
|
-
import { io } from
|
|
13
|
-
import { newSocket } from
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { ChakraProvider } from '@chakra-ui/react';
|
|
4
|
+
import { Button, Modal, ModalHeader, ModalBody, useDisclosure, ModalOverlay, ModalContent, ModalCloseButton, Box, Stack, } from '@chakra-ui/react';
|
|
5
|
+
import { GenericForm } from './GenericForm';
|
|
6
|
+
import { ThemeProvider } from '@emotion/react';
|
|
7
|
+
import { createTheme } from '@mui/material/styles';
|
|
8
|
+
import { MaterialReactTable } from 'material-react-table';
|
|
9
|
+
import { mkConfig, generateCsv, download } from 'export-to-csv';
|
|
10
|
+
import Swal from 'sweetalert2';
|
|
11
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
12
|
+
import { io } from 'socket.io-client';
|
|
13
|
+
import { newSocket } from '../utils/websockets';
|
|
14
14
|
export function pageToUrl(page) {
|
|
15
|
-
return `${page.name.toLowerCase().replace(
|
|
15
|
+
return `${page.name.toLowerCase().replace(' ', '_')}`;
|
|
16
16
|
}
|
|
17
|
-
function Header({ pages, data, basePath
|
|
17
|
+
function Header({ pages, data, basePath }) {
|
|
18
18
|
return (_jsx(Box, { background: "blue.50", padding: 1, children: _jsxs(Stack, { margin: 3, direction: "row", justifyContent: "space-between", alignItems: "center", children: [_jsx(Stack, { margin: 4, gap: 10, direction: "row", alignItems: "center", children: pages.map((p) => (_jsx(Box, { as: "a", href: `${basePath}/${pageToUrl(p)}`, children: p.name }))) }), _jsx(Stack, { direction: "row", children: _jsx(Button, { colorScheme: "red", onClick: () => {
|
|
19
|
-
const filename = prompt(
|
|
19
|
+
const filename = prompt('Enter filename:');
|
|
20
20
|
if (filename)
|
|
21
21
|
exportToCSV(data, filename);
|
|
22
22
|
}, children: "Export Data" }) })] }) }));
|
|
@@ -25,10 +25,10 @@ function exportToCSV(data, filename) {
|
|
|
25
25
|
const formatImageData = (obj) => {
|
|
26
26
|
const formattedObj = {};
|
|
27
27
|
for (const key in obj) {
|
|
28
|
-
if (typeof obj[key] !==
|
|
28
|
+
if (typeof obj[key] !== 'object') {
|
|
29
29
|
formattedObj[key] = obj[key];
|
|
30
30
|
}
|
|
31
|
-
else if (typeof obj[key] ===
|
|
31
|
+
else if (typeof obj[key] === 'object' && obj[key] != null) {
|
|
32
32
|
Object.keys(obj[key]).forEach((k) => {
|
|
33
33
|
formattedObj[`${key}.${k}`] = obj[key][k];
|
|
34
34
|
});
|
|
@@ -47,12 +47,12 @@ function exportToCSV(data, filename) {
|
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
49
|
Swal.fire({
|
|
50
|
-
title:
|
|
51
|
-
text:
|
|
50
|
+
title: 'Error while exporting CSV!',
|
|
51
|
+
text: 'There is no data to export.',
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
export function AdminPage({ pages, selectedPage, basePath, apiURL = '' }) {
|
|
55
|
+
export function AdminPage({ pages, selectedPage, basePath, apiURL = '', }) {
|
|
56
56
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
57
57
|
const [selectedAction, setSelectedAction] = useState({});
|
|
58
58
|
const [data, setData] = useState([]);
|
|
@@ -68,10 +68,10 @@ export function AdminPage({ pages, selectedPage, basePath, apiURL = '' }) {
|
|
|
68
68
|
});
|
|
69
69
|
}, [refetch]); // Do not remove 'refetch' from here !!!
|
|
70
70
|
useEffect(() => {
|
|
71
|
-
socket.on(
|
|
72
|
-
console.log(
|
|
71
|
+
socket.on('updateData', (message) => {
|
|
72
|
+
console.log('Message emitted from socket: ' + message);
|
|
73
73
|
setRefetch(true);
|
|
74
|
-
const rowActionsMenu = document.querySelector(
|
|
74
|
+
const rowActionsMenu = document.querySelector('.MuiPaper-root.MuiPopover-paper.MuiMenu-paper');
|
|
75
75
|
if (rowActionsMenu) {
|
|
76
76
|
rowActionsMenu.remove();
|
|
77
77
|
}
|
|
@@ -83,7 +83,7 @@ export function AdminPage({ pages, selectedPage, basePath, apiURL = '' }) {
|
|
|
83
83
|
return (_jsxs(_Fragment, { children: [_jsx(Header, { pages: pages, data: data, basePath: basePath }), _jsx(ThemeProvider, { theme: createTheme(), children: _jsx(MaterialReactTable, { columns: selectedPage.tableFields.map((field, index) => ({
|
|
84
84
|
accessorFn: (row) => row[index],
|
|
85
85
|
header: field.name,
|
|
86
|
-
})), data: displayData, muiTableContainerProps: { sx: { maxHeight:
|
|
86
|
+
})), data: displayData, muiTableContainerProps: { sx: { maxHeight: '70vh' } }, enableColumnResizing: true, enableColumnPinning: true, enableGlobalFilterModes: true, enablePagination: true, paginationDisplayMode: "pages", enableRowVirtualization: true, rowVirtualizerOptions: { overscan: 20 }, renderTopToolbarCustomActions: () => (_jsx(ChakraProvider, { children: _jsx(Stack, { margin: 0, padding: 3, direction: "row", children: selectedPage.actions.map((action) => (_jsx(Button, { colorScheme: "blue", onClick: () => {
|
|
87
87
|
setSelectedAction(action);
|
|
88
88
|
onOpen();
|
|
89
89
|
}, children: action.name }))) }) })), enableRowActions: true, renderRowActionMenuItems: (obj) => {
|
|
@@ -91,12 +91,12 @@ export function AdminPage({ pages, selectedPage, basePath, apiURL = '' }) {
|
|
|
91
91
|
setSelectedAction(rowAction);
|
|
92
92
|
setRowId(obj.row.original[0]);
|
|
93
93
|
onOpen();
|
|
94
|
-
const rowActionsMenu = document.querySelector(
|
|
94
|
+
const rowActionsMenu = document.querySelector('.MuiPaper-root.MuiPopover-paper.MuiMenu-paper');
|
|
95
95
|
if (rowActionsMenu) {
|
|
96
96
|
rowActionsMenu.remove();
|
|
97
97
|
}
|
|
98
|
-
}, children: rowAction.name +
|
|
99
|
-
} }) }), _jsxs(Modal, { size:
|
|
98
|
+
}, children: rowAction.name + ' ' + obj.row.original[0] })));
|
|
99
|
+
} }) }), _jsxs(Modal, { size: '6xl', isOpen: isOpen, onClose: onClose, children: [_jsx(ModalOverlay, {}), _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: selectedAction.name }), _jsx(ModalCloseButton, {}), _jsxs(ModalBody, { children: [selectedAction.fields && selectedAction.fields?.length > 0 ? (_jsx(GenericForm, { action: selectedAction, onClose: onClose, rowId: rowId })) : (_jsx(Button, { colorScheme: "blue", mr: 3, onClick: () => {
|
|
100
100
|
selectedAction.onSubmit(rowId);
|
|
101
101
|
onClose();
|
|
102
102
|
}, children: "Confirm Action" })), _jsx(Button, { colorScheme: "red", mr: 3, onClick: onClose, children: "Close" })] })] })] })] }));
|
|
@@ -1,41 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input, Select, } from
|
|
3
|
-
import { Field, Form, Formik, FieldArray, getIn } from
|
|
4
|
-
import * as Yup from
|
|
5
|
-
import Swal from
|
|
6
|
-
import { useEffect, useState } from
|
|
2
|
+
import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input, Select, } from '@chakra-ui/react';
|
|
3
|
+
import { Field, Form, Formik, FieldArray, getIn } from 'formik';
|
|
4
|
+
import * as Yup from 'yup';
|
|
5
|
+
import Swal from 'sweetalert2';
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
7
7
|
function parseValidation(fields) {
|
|
8
8
|
if (!fields) {
|
|
9
9
|
return {};
|
|
10
10
|
}
|
|
11
11
|
return fields.reduce((acc, field) => {
|
|
12
|
-
if (field.type !==
|
|
13
|
-
field.type !== "object" &&
|
|
14
|
-
field.type !== "array") {
|
|
12
|
+
if (field.type !== 'dropdown' && field.type !== 'object' && field.type !== 'array') {
|
|
15
13
|
acc[field.name] = field.validationSchema;
|
|
16
14
|
}
|
|
17
|
-
else if (field.type ===
|
|
18
|
-
if (field.element.type ===
|
|
15
|
+
else if (field.type === 'array') {
|
|
16
|
+
if (field.element.type === 'dropdown') {
|
|
19
17
|
acc[field.name] = Yup.array().of(Yup.string().required());
|
|
20
18
|
}
|
|
21
|
-
else if (field.element.type ===
|
|
19
|
+
else if (field.element.type === 'object') {
|
|
22
20
|
acc[field.name] = Yup.array().of(Yup.object(parseValidation(field.element.fields)));
|
|
23
21
|
}
|
|
24
22
|
else {
|
|
25
23
|
acc[field.name] = Yup.array().of(field.element.validationSchema);
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
else if (field.type ===
|
|
26
|
+
else if (field.type === 'object') {
|
|
29
27
|
acc[field.name] = Yup.object(parseValidation(field.fields));
|
|
30
28
|
}
|
|
31
|
-
if (field.type ===
|
|
29
|
+
if (field.type === 'dropdown') {
|
|
32
30
|
acc[field.name] = Yup.string().required();
|
|
33
31
|
}
|
|
34
32
|
return acc;
|
|
35
33
|
}, {});
|
|
36
34
|
}
|
|
37
35
|
function generateSingleField(formfield, fieldName, key) {
|
|
38
|
-
return (_jsx(Field, { name: fieldName, children: ({ field, form }) => (_jsxs(FormControl, { isInvalid: getIn(form.touched, fieldName) && getIn(form.errors, fieldName), children: [_jsx(FormLabel, { marginTop: 2, children: formfield.label }), formfield.type ===
|
|
36
|
+
return (_jsx(Field, { name: fieldName, children: ({ field, form }) => (_jsxs(FormControl, { isInvalid: getIn(form.touched, fieldName) && getIn(form.errors, fieldName), children: [_jsx(FormLabel, { marginTop: 2, children: formfield.label }), formfield.type === 'string' || formfield.type === 'number' ? (_jsx(Input, { ...field, type: formfield.type === 'number' ? 'number' : 'text', placeholder: "" })) : formfield.type === 'file' ? (_jsx(Input, { type: "file", name: fieldName, onChange: (event) => {
|
|
39
37
|
if (event.currentTarget.files != null) {
|
|
40
38
|
form.setFieldValue(fieldName, event.currentTarget.files[0]);
|
|
41
39
|
}
|
|
@@ -46,18 +44,18 @@ function parseFields(fields, previous) {
|
|
|
46
44
|
return [];
|
|
47
45
|
}
|
|
48
46
|
const computedFields = fields.map((formfield, key) => {
|
|
49
|
-
|
|
50
|
-
if (formfield.type ===
|
|
47
|
+
const fieldName = previous !== undefined ? previous + '.' + formfield.name : formfield.name;
|
|
48
|
+
if (formfield.type === 'object') {
|
|
51
49
|
const objectFields = parseFields(formfield.fields, fieldName);
|
|
52
50
|
return _jsx(FormControl, { children: objectFields }, fieldName);
|
|
53
51
|
}
|
|
54
|
-
else if (formfield.type ===
|
|
52
|
+
else if (formfield.type === 'array') {
|
|
55
53
|
return (_jsx(FieldArray, { name: fieldName, children: ({ remove, push, form }) => (_jsxs("div", { children: [_jsx(FormLabel, { marginTop: 2, children: formfield.label }), getIn(form.values, fieldName).length > 0 &&
|
|
56
|
-
getIn(form.values, fieldName).map((_, index) => (_jsxs(Box, { paddingLeft: 10, children: [_jsx("h4", { children: _jsx("b", { children: `${fieldName}.${index}` }) }), _jsx(Box, { paddingLeft: 10, children: formfield.element.type ===
|
|
54
|
+
getIn(form.values, fieldName).map((_, index) => (_jsxs(Box, { paddingLeft: 10, children: [_jsx("h4", { children: _jsx("b", { children: `${fieldName}.${index}` }) }), _jsx(Box, { paddingLeft: 10, children: formfield.element.type === 'object'
|
|
57
55
|
? parseFields(formfield.element.fields, `${fieldName}.${index}`)
|
|
58
56
|
: generateSingleField(formfield.element, `${fieldName}.${index}`, key) }), _jsxs(Button, { type: "button", colorScheme: "red", marginY: 2, onClick: () => remove(index), children: ["Remove ", `${fieldName}.${index}`] })] }))), _jsxs(Button, { type: "button", marginY: 2, colorScheme: "blue", onClick: () => {
|
|
59
57
|
let value = undefined;
|
|
60
|
-
if (formfield.element.type ===
|
|
58
|
+
if (formfield.element.type === 'object') {
|
|
61
59
|
value = getCustomInitialValues(formfield.element.fields);
|
|
62
60
|
}
|
|
63
61
|
else {
|
|
@@ -73,18 +71,18 @@ function parseFields(fields, previous) {
|
|
|
73
71
|
return computedFields;
|
|
74
72
|
}
|
|
75
73
|
function getCustomInitialValues(fields) {
|
|
76
|
-
|
|
74
|
+
const obj = {};
|
|
77
75
|
if (!fields) {
|
|
78
76
|
return obj;
|
|
79
77
|
}
|
|
80
78
|
fields.forEach((field) => {
|
|
81
|
-
if (field.type ===
|
|
79
|
+
if (field.type === 'array')
|
|
82
80
|
obj[field.name] = [];
|
|
83
|
-
else if (field.type ===
|
|
81
|
+
else if (field.type === 'object')
|
|
84
82
|
obj[field.name] = getCustomInitialValues(field.fields);
|
|
85
|
-
else if (field.type ===
|
|
86
|
-
obj[field.name] =
|
|
87
|
-
else if (field.type ===
|
|
83
|
+
else if (field.type === 'string')
|
|
84
|
+
obj[field.name] = '';
|
|
85
|
+
else if (field.type === 'number')
|
|
88
86
|
obj[field.name] = 0;
|
|
89
87
|
else
|
|
90
88
|
obj[field.name] = null;
|
|
@@ -104,11 +102,11 @@ async function setOptions(fields) {
|
|
|
104
102
|
return;
|
|
105
103
|
}
|
|
106
104
|
const promises = fields.map(async (field) => {
|
|
107
|
-
if (field.type ===
|
|
105
|
+
if (field.type === 'dropdown')
|
|
108
106
|
field.options = await field.getOptions();
|
|
109
|
-
else if (field.type ===
|
|
107
|
+
else if (field.type === 'array')
|
|
110
108
|
await setOptions([field.element]);
|
|
111
|
-
else if (field.type ===
|
|
109
|
+
else if (field.type === 'object')
|
|
112
110
|
await setOptions(field.fields);
|
|
113
111
|
});
|
|
114
112
|
await Promise.all(promises);
|
|
@@ -137,14 +135,14 @@ export function GenericForm({ action, onClose, rowId }) {
|
|
|
137
135
|
await action.onSubmit(values);
|
|
138
136
|
if (onClose)
|
|
139
137
|
onClose();
|
|
140
|
-
Swal.fire(
|
|
138
|
+
Swal.fire('Ok');
|
|
141
139
|
}
|
|
142
140
|
catch (e) {
|
|
143
141
|
if (onClose)
|
|
144
142
|
onClose();
|
|
145
143
|
const err = e.request.response;
|
|
146
144
|
Swal.fire({
|
|
147
|
-
text:
|
|
145
|
+
text: 'Error: ' + err,
|
|
148
146
|
});
|
|
149
147
|
}
|
|
150
148
|
}, children: ({ isSubmitting }) => (_jsxs(Form, { children: [formFields, _jsx(Button, { mt: 4, colorScheme: "teal", isLoading: isSubmitting, type: "submit", children: "Submit" }), _jsx(Button, { mt: 4, colorScheme: "teal", type: "reset", children: "Initialise Form" })] })) }) }));
|
package/dist/esm/src/main.js
CHANGED
|
@@ -4,4 +4,4 @@ import ReactDOM from 'react-dom/client';
|
|
|
4
4
|
import App from './App';
|
|
5
5
|
import { event } from './testSchemas/event';
|
|
6
6
|
import './index.css';
|
|
7
|
-
ReactDOM.createRoot(document.getElementById('root')).render(_jsx(React.StrictMode, { children: _jsx(App, { pages: [event], basePath:
|
|
7
|
+
ReactDOM.createRoot(document.getElementById('root')).render(_jsx(React.StrictMode, { children: _jsx(App, { pages: [event], basePath: "" }) }));
|
package/dist/esm/src/schema.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface TableField {
|
|
|
6
6
|
export interface SimpleField {
|
|
7
7
|
name: string;
|
|
8
8
|
label: string;
|
|
9
|
-
type:
|
|
9
|
+
type: 'string' | 'number' | 'file';
|
|
10
10
|
validationSchema: any;
|
|
11
11
|
}
|
|
12
12
|
export interface Option {
|
|
@@ -16,20 +16,20 @@ export interface Option {
|
|
|
16
16
|
export interface DropdownField {
|
|
17
17
|
name: string;
|
|
18
18
|
label: string;
|
|
19
|
-
type:
|
|
19
|
+
type: 'dropdown';
|
|
20
20
|
options?: Option[] | undefined;
|
|
21
21
|
getOptions: () => Promise<Option[]>;
|
|
22
22
|
}
|
|
23
23
|
export interface ObjectField {
|
|
24
24
|
name: string;
|
|
25
25
|
label: string;
|
|
26
|
-
type:
|
|
26
|
+
type: 'object';
|
|
27
27
|
fields: FormField[];
|
|
28
28
|
}
|
|
29
29
|
export interface ArrayField {
|
|
30
30
|
name: string;
|
|
31
31
|
label: string;
|
|
32
|
-
type:
|
|
32
|
+
type: 'array';
|
|
33
33
|
element: Exclude<FormField, ArrayField>;
|
|
34
34
|
}
|
|
35
35
|
export type FormField = SimpleField | DropdownField | ObjectField | ArrayField;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import io from
|
|
2
|
-
import { useEffect } from
|
|
1
|
+
import io from 'socket.io-client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
3
|
const Socket = (setSocket, apiURL) => {
|
|
4
4
|
useEffect(() => {
|
|
5
5
|
const socketRef = io(apiURL, {
|
|
6
|
-
transports: [
|
|
6
|
+
transports: ['websocket'],
|
|
7
7
|
});
|
|
8
8
|
setSocket(socketRef);
|
|
9
9
|
return () => {
|
package/package.json
CHANGED
package/vite.config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import react from '@vitejs/plugin-react-swc'
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react-swc';
|
|
3
3
|
|
|
4
4
|
// https://vitejs.dev/config/
|
|
5
5
|
export default defineConfig({
|
|
6
6
|
plugins: [react()],
|
|
7
|
-
})
|
|
7
|
+
});
|