admin_lsac 1.4.7 → 1.4.8

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.
@@ -0,0 +1,7 @@
1
+ import { PageSchema } from './schema';
2
+ import './App.css';
3
+ declare function App({ pages, basePath }: {
4
+ pages: PageSchema[];
5
+ basePath: string;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export default App;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChakraProvider } from '@chakra-ui/react';
3
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
4
+ import { AdminPage } from './components/AdminPage';
5
+ import { pageToUrl } from './components/AdminPage';
6
+ import './App.css';
7
+ function App({ pages, basePath }) {
8
+ return (_jsx(ChakraProvider, { children: _jsx(BrowserRouter, { children: _jsxs(Routes, { children: [pages.map((page) => (_jsx(Route, { path: `${basePath}/${pageToUrl(page)}`, element: _jsx(AdminPage, { pages: pages, selectedPage: page, basePath: basePath, apiURL: "http://localhost:3001/" }) }))), pages[0] ? (_jsx(Route, { path: `${basePath}/*`, element: _jsx(AdminPage, { pages: pages, selectedPage: pages[0], basePath: basePath, apiURL: "http://localhost:3001/" }) })) : null] }) }) }));
9
+ }
10
+ export default App;
@@ -0,0 +1,8 @@
1
+ import { PageSchema } from "../schema";
2
+ export declare function pageToUrl(page: PageSchema): string;
3
+ export declare function AdminPage({ pages, selectedPage, basePath, apiURL }: {
4
+ pages: PageSchema[];
5
+ selectedPage: PageSchema;
6
+ basePath: string;
7
+ apiURL?: string;
8
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
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
+ export function pageToUrl(page) {
15
+ return `${page.name.toLowerCase().replace(" ", "_")}`;
16
+ }
17
+ function Header({ pages, data, basePath, }) {
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("Enter filename:");
20
+ if (filename)
21
+ exportToCSV(data, filename);
22
+ }, children: "Export Data" }) })] }) }));
23
+ }
24
+ function exportToCSV(data, filename) {
25
+ const formatImageData = (obj) => {
26
+ const formattedObj = {};
27
+ for (const key in obj) {
28
+ if (typeof obj[key] !== "object") {
29
+ formattedObj[key] = obj[key];
30
+ }
31
+ else if (typeof obj[key] === "object" && obj[key] != null) {
32
+ Object.keys(obj[key]).forEach((k) => {
33
+ formattedObj[`${key}.${k}`] = obj[key][k];
34
+ });
35
+ }
36
+ }
37
+ return formattedObj;
38
+ };
39
+ const formattedData = data.map(formatImageData);
40
+ const csvConfig = mkConfig({
41
+ useKeysAsHeaders: true,
42
+ filename: filename,
43
+ });
44
+ if (formattedData.length > 0) {
45
+ const csv = generateCsv(csvConfig)(formattedData);
46
+ download(csvConfig)(csv);
47
+ }
48
+ else {
49
+ Swal.fire({
50
+ title: "Error while exporting CSV!",
51
+ text: "There is no data to export.",
52
+ });
53
+ }
54
+ }
55
+ export function AdminPage({ pages, selectedPage, basePath, apiURL = '' }) {
56
+ const { isOpen, onOpen, onClose } = useDisclosure();
57
+ const [selectedAction, setSelectedAction] = useState({});
58
+ const [data, setData] = useState([]);
59
+ const [displayData, setDisplayData] = useState([]);
60
+ const [rowId, setRowId] = useState();
61
+ const [refetch, setRefetch] = useState(false);
62
+ const [socket, setSocket] = useState(io);
63
+ newSocket(setSocket, apiURL);
64
+ useEffect(() => {
65
+ selectedPage.getRequest().then((resArray) => {
66
+ setData(resArray);
67
+ setRefetch(false);
68
+ });
69
+ }, [refetch]); // Do not remove 'refetch' from here !!!
70
+ useEffect(() => {
71
+ socket.on("updateData", (message) => {
72
+ console.log("Message emitted from socket: " + message);
73
+ setRefetch(true);
74
+ const rowActionsMenu = document.querySelector(".MuiPaper-root.MuiPopover-paper.MuiMenu-paper");
75
+ if (rowActionsMenu) {
76
+ rowActionsMenu.remove();
77
+ }
78
+ });
79
+ }, [socket, selectedPage]);
80
+ useEffect(() => {
81
+ setDisplayData(data.map((table_data) => selectedPage.tableFields.map((field) => field.access(table_data))));
82
+ }, [data]);
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
+ accessorFn: (row) => row[index],
85
+ header: field.name,
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
+ setSelectedAction(action);
88
+ onOpen();
89
+ }, children: action.name }))) }) })), enableRowActions: true, renderRowActionMenuItems: (obj) => {
90
+ return selectedPage.rowActions.map((rowAction) => (_jsx(MenuItem, { onClick: () => {
91
+ setSelectedAction(rowAction);
92
+ setRowId(obj.row.original[0]);
93
+ onOpen();
94
+ const rowActionsMenu = document.querySelector(".MuiPaper-root.MuiPopover-paper.MuiMenu-paper");
95
+ if (rowActionsMenu) {
96
+ rowActionsMenu.remove();
97
+ }
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
+ selectedAction.onSubmit(rowId);
101
+ onClose();
102
+ }, children: "Confirm Action" })), _jsx(Button, { colorScheme: "red", mr: 3, onClick: onClose, children: "Close" })] })] })] })] }));
103
+ }
@@ -0,0 +1,8 @@
1
+ import { Action, RowAction } from "../schema";
2
+ interface GenericFormProps {
3
+ action: Action | RowAction;
4
+ onClose?: () => void;
5
+ rowId?: number;
6
+ }
7
+ export declare function GenericForm({ action, onClose, rowId }: GenericFormProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,151 @@
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 "@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
+ function parseValidation(fields) {
8
+ if (!fields) {
9
+ return {};
10
+ }
11
+ return fields.reduce((acc, field) => {
12
+ if (field.type !== "dropdown" &&
13
+ field.type !== "object" &&
14
+ field.type !== "array") {
15
+ acc[field.name] = field.validationSchema;
16
+ }
17
+ else if (field.type === "array") {
18
+ if (field.element.type === "dropdown") {
19
+ acc[field.name] = Yup.array().of(Yup.string().required());
20
+ }
21
+ else if (field.element.type === "object") {
22
+ acc[field.name] = Yup.array().of(Yup.object(parseValidation(field.element.fields)));
23
+ }
24
+ else {
25
+ acc[field.name] = Yup.array().of(field.element.validationSchema);
26
+ }
27
+ }
28
+ else if (field.type === "object") {
29
+ acc[field.name] = Yup.object(parseValidation(field.fields));
30
+ }
31
+ if (field.type === "dropdown") {
32
+ acc[field.name] = Yup.string().required();
33
+ }
34
+ return acc;
35
+ }, {});
36
+ }
37
+ 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 === "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
+ if (event.currentTarget.files != null) {
40
+ form.setFieldValue(fieldName, event.currentTarget.files[0]);
41
+ }
42
+ } })) : (_jsxs(Select, { ...field, defaultValue: "", children: [_jsx("option", { disabled: true, value: "", children: "Select an option!" }), formfield.options?.map((option, key) => (_jsx("option", { value: `${option.value}`, children: option.display }, key)))] })), _jsx(FormErrorMessage, { children: getIn(form.errors, fieldName) })] })) }, key));
43
+ }
44
+ function parseFields(fields, previous) {
45
+ if (!fields) {
46
+ return [];
47
+ }
48
+ const computedFields = fields.map((formfield, key) => {
49
+ let fieldName = previous !== undefined ? previous + "." + formfield.name : formfield.name;
50
+ if (formfield.type === "object") {
51
+ const objectFields = parseFields(formfield.fields, fieldName);
52
+ return _jsx(FormControl, { children: objectFields }, fieldName);
53
+ }
54
+ else if (formfield.type === "array") {
55
+ 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 === "object"
57
+ ? parseFields(formfield.element.fields, `${fieldName}.${index}`)
58
+ : 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
+ let value = undefined;
60
+ if (formfield.element.type === "object") {
61
+ value = getCustomInitialValues(formfield.element.fields);
62
+ }
63
+ else {
64
+ value = getCustomInitialValues([formfield.element])[0];
65
+ }
66
+ push(value);
67
+ }, children: ["Add ", fieldName] })] })) }));
68
+ }
69
+ else {
70
+ return generateSingleField(formfield, fieldName, key);
71
+ }
72
+ });
73
+ return computedFields;
74
+ }
75
+ function getCustomInitialValues(fields) {
76
+ let obj = {};
77
+ if (!fields) {
78
+ return obj;
79
+ }
80
+ fields.forEach((field) => {
81
+ if (field.type === "array")
82
+ obj[field.name] = [];
83
+ else if (field.type === "object")
84
+ obj[field.name] = getCustomInitialValues(field.fields);
85
+ else if (field.type === "string")
86
+ obj[field.name] = "";
87
+ else if (field.type === "number")
88
+ obj[field.name] = 0;
89
+ else
90
+ obj[field.name] = null;
91
+ });
92
+ return obj;
93
+ }
94
+ async function getInitialValues(action, rowId) {
95
+ let obj = {};
96
+ if (action.initialise) {
97
+ obj = await action.initialise(rowId);
98
+ }
99
+ const customInitialValues = getCustomInitialValues(action.fields);
100
+ return Object.assign({}, customInitialValues, obj);
101
+ }
102
+ async function setOptions(fields) {
103
+ if (!fields) {
104
+ return;
105
+ }
106
+ const promises = fields.map(async (field) => {
107
+ if (field.type === "dropdown")
108
+ field.options = await field.getOptions();
109
+ else if (field.type === "array")
110
+ await setOptions([field.element]);
111
+ else if (field.type === "object")
112
+ await setOptions(field.fields);
113
+ });
114
+ await Promise.all(promises);
115
+ }
116
+ export function GenericForm({ action, onClose, rowId }) {
117
+ const [formFields, setFormFields] = useState(parseFields(action.fields));
118
+ const [initialValues, setInitialValues] = useState(getCustomInitialValues(action.fields));
119
+ const [validationSchema, _] = useState(parseValidation(action.fields));
120
+ useEffect(() => {
121
+ setOptions(action.fields).then(() => {
122
+ setFormFields(parseFields(action.fields));
123
+ });
124
+ }, []);
125
+ const handleFormReset = async () => {
126
+ const newInitialValues = await getInitialValues(action, rowId);
127
+ console.log(newInitialValues);
128
+ setInitialValues(newInitialValues);
129
+ };
130
+ if (!action.fields)
131
+ return _jsx(_Fragment, {});
132
+ return (_jsx(_Fragment, { children: _jsx(Formik, { enableReinitialize: true, initialValues: initialValues, validationSchema: Yup.object(validationSchema), onReset: handleFormReset, onSubmit: async (values) => {
133
+ try {
134
+ if (rowId)
135
+ await action.onSubmit(values, rowId);
136
+ else
137
+ await action.onSubmit(values);
138
+ if (onClose)
139
+ onClose();
140
+ Swal.fire("Ok");
141
+ }
142
+ catch (e) {
143
+ if (onClose)
144
+ onClose();
145
+ const err = e.request.response;
146
+ Swal.fire({
147
+ text: "Error: " + err,
148
+ });
149
+ }
150
+ }, 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" })] })) }) }));
151
+ }
@@ -0,0 +1 @@
1
+ import './index.css';
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom/client';
4
+ import App from './App';
5
+ import { event } from './testSchemas/event';
6
+ import './index.css';
7
+ ReactDOM.createRoot(document.getElementById('root')).render(_jsx(React.StrictMode, { children: _jsx(App, { pages: [event], basePath: '' }) }));
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ export interface TableField {
3
+ name: string;
4
+ access: (obj: any) => string | number | React.ReactElement;
5
+ }
6
+ export interface SimpleField {
7
+ name: string;
8
+ label: string;
9
+ type: "string" | "number" | "file";
10
+ validationSchema: any;
11
+ }
12
+ export interface Option {
13
+ value: string | number | boolean;
14
+ display: string;
15
+ }
16
+ export interface DropdownField {
17
+ name: string;
18
+ label: string;
19
+ type: "dropdown";
20
+ options?: Option[] | undefined;
21
+ getOptions: () => Promise<Option[]>;
22
+ }
23
+ export interface ObjectField {
24
+ name: string;
25
+ label: string;
26
+ type: "object";
27
+ fields: FormField[];
28
+ }
29
+ export interface ArrayField {
30
+ name: string;
31
+ label: string;
32
+ type: "array";
33
+ element: Exclude<FormField, ArrayField>;
34
+ }
35
+ export type FormField = SimpleField | DropdownField | ObjectField | ArrayField;
36
+ export interface Action {
37
+ name: string;
38
+ fields?: FormField[];
39
+ onSubmit: (obj1: any, obj2?: any) => Promise<any>;
40
+ }
41
+ export interface RowAction {
42
+ name: string;
43
+ fields?: FormField[];
44
+ initialise?: (obj: any) => any;
45
+ onSubmit: (obj1: any, obj2?: any) => Promise<any>;
46
+ }
47
+ export interface PageSchema {
48
+ name: string;
49
+ actions: Action[];
50
+ rowActions: RowAction[];
51
+ getRequest: () => Promise<any[]>;
52
+ tableFields: TableField[];
53
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { PageSchema } from '../schema';
2
+ export declare const event: PageSchema;
@@ -0,0 +1,89 @@
1
+ // import axios from "axios";
2
+ import React from 'react';
3
+ // const domain = "http://localhost:3001";
4
+ export const event = {
5
+ name: 'Events',
6
+ actions: [],
7
+ rowActions: [
8
+ // {
9
+ // name: "Delete Event",
10
+ // fields: [
11
+ // {
12
+ // name: "input",
13
+ // label: "Mock Field",
14
+ // type: "string",
15
+ // validationSchema: "",
16
+ // },
17
+ // ],
18
+ // initialise: async (eventId: number) => {
19
+ // const res = await axios.get(domain + "/events/" + eventId);
20
+ // let obj: any = {};
21
+ // obj.input = res.data.name;
22
+ // return obj;
23
+ // },
24
+ // onSubmit: async (_: any, eventId: number) => {
25
+ // const res = await axios.delete(
26
+ // domain + "/events/" + eventId
27
+ // );
28
+ // return res.data;
29
+ // },
30
+ // }
31
+ ],
32
+ getRequest: async () => {
33
+ // const res = await axios.get(domain + "/events");
34
+ // return res.data;
35
+ return [];
36
+ },
37
+ tableFields: [
38
+ {
39
+ name: 'Id',
40
+ access: (obj) => obj.id,
41
+ },
42
+ {
43
+ name: 'Event Name',
44
+ access: (obj) => obj.name,
45
+ },
46
+ {
47
+ name: 'Description',
48
+ access: (obj) => obj.description,
49
+ },
50
+ {
51
+ name: 'Category',
52
+ access: (obj) => obj.category,
53
+ },
54
+ {
55
+ name: 'Ticket Type',
56
+ access: (obj) => obj.ticketType,
57
+ },
58
+ {
59
+ name: 'Date',
60
+ access: (obj) => obj.date,
61
+ },
62
+ {
63
+ name: 'Release',
64
+ access: (obj) => obj.release,
65
+ },
66
+ {
67
+ name: 'Is Released',
68
+ access: (obj) => (obj.isReleased ? 'Is Released' : 'Not Released'),
69
+ },
70
+ {
71
+ name: 'Available Tickets',
72
+ access: (obj) => obj.availableTickets,
73
+ },
74
+ {
75
+ name: 'Max Tickets',
76
+ access: (obj) => obj.maxTickets,
77
+ },
78
+ {
79
+ name: 'Image',
80
+ access: (obj) => obj.imgUrl
81
+ ? React.createElement('img', {
82
+ src: obj.imgUrl,
83
+ alt: 'ERROR',
84
+ style: { width: '100px' },
85
+ })
86
+ : 'No Image Available',
87
+ },
88
+ ],
89
+ };
@@ -0,0 +1 @@
1
+ export declare const newSocket: (setSocket: any, apiURL: string) => void;
@@ -0,0 +1,15 @@
1
+ import io from "socket.io-client";
2
+ import { useEffect } from "react";
3
+ const Socket = (setSocket, apiURL) => {
4
+ useEffect(() => {
5
+ const socketRef = io(apiURL, {
6
+ transports: ["websocket"],
7
+ });
8
+ setSocket(socketRef);
9
+ return () => {
10
+ socketRef.removeAllListeners();
11
+ socketRef.disconnect();
12
+ };
13
+ }, []);
14
+ };
15
+ export const newSocket = (setSocket, apiURL) => Socket(setSocket, apiURL);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "admin_lsac",
3
3
  "private": false,
4
- "version": "1.4.7",
4
+ "version": "1.4.8",
5
5
  "description": "Generic Admin Panel created for storing events' data",
6
6
  "scripts": {
7
7
  "dev": "vite",