admin_lsac 2.0.23 → 2.0.24

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/admin_lsac.d.ts CHANGED
@@ -7,7 +7,7 @@ declare module 'admin_lsac' {
7
7
  name: string;
8
8
  label: string;
9
9
  type: 'string' | 'number' | 'file' | 'date' | 'datetime-local' | 'time';
10
- validationSchema: any;
10
+ validationSchema?: any;
11
11
  }
12
12
  export interface Option {
13
13
  value: string | number | boolean;
@@ -37,12 +37,12 @@ declare module 'admin_lsac' {
37
37
  export interface Action {
38
38
  name: string;
39
39
  fields?: FormField[];
40
- onSubmit: (obj1: any, obj2?: any, obj3?: any) => Promise<any>;
40
+ onSubmit: (obj1?: any, obj2?: any, obj3?: any) => Promise<any>;
41
41
  }
42
42
  export interface RowAction {
43
43
  name: string;
44
44
  fields?: FormField[];
45
- initialise?: (obj: any) => Promise<any>;
45
+ initialise?: (rowId: any) => Promise<any>;
46
46
  onSubmit: (obj1: any, obj2?: any, obj3?: any) => Promise<any>;
47
47
  }
48
48
  export interface PageSchema {
@@ -7,7 +7,7 @@ declare module 'admin_lsac' {
7
7
  name: string;
8
8
  label: string;
9
9
  type: 'string' | 'number' | 'file' | 'date' | 'datetime-local' | 'time';
10
- validationSchema: any;
10
+ validationSchema?: any;
11
11
  }
12
12
  export interface Option {
13
13
  value: string | number | boolean;
@@ -37,12 +37,12 @@ declare module 'admin_lsac' {
37
37
  export interface Action {
38
38
  name: string;
39
39
  fields?: FormField[];
40
- onSubmit: (obj1: any, obj2?: any, obj3?: any) => Promise<any>;
40
+ onSubmit: (obj1?: any, obj2?: any, obj3?: any) => Promise<any>;
41
41
  }
42
42
  export interface RowAction {
43
43
  name: string;
44
44
  fields?: FormField[];
45
- initialise?: (obj: any) => Promise<any>;
45
+ initialise?: (rowId: any) => Promise<any>;
46
46
  onSubmit: (obj1: any, obj2?: any, obj3?: any) => Promise<any>;
47
47
  }
48
48
  export interface PageSchema {
@@ -28,7 +28,20 @@ function exportToCSV(data, filename) {
28
28
  filename: filename,
29
29
  });
30
30
  if (data.length > 1) {
31
- const csv = generateCsv(csvConfig)(data);
31
+ const filteredData = data.map((row) => {
32
+ const cleaned = {};
33
+ for (const key in row) {
34
+ const value = row[key];
35
+ if (typeof value === 'string' ||
36
+ typeof value === 'number' ||
37
+ typeof value === 'boolean' ||
38
+ value === null) {
39
+ cleaned[key] = value;
40
+ }
41
+ }
42
+ return cleaned;
43
+ });
44
+ const csv = generateCsv(csvConfig)(filteredData);
32
45
  download(csvConfig)(csv);
33
46
  }
34
47
  else {
@@ -99,9 +112,12 @@ export function AdminPage({ pages, selectedPage, basePath, apiURL = '', }) {
99
112
  if (rowObject !== undefined) {
100
113
  selectedAction.onSubmit(rowId, rowObject);
101
114
  }
102
- else {
115
+ else if (rowId !== undefined) {
103
116
  selectedAction.onSubmit(rowId);
104
117
  }
118
+ else {
119
+ selectedAction.onSubmit();
120
+ }
105
121
  onClose();
106
122
  }, children: "Confirm Action" })), _jsx(Button, { colorScheme: "red", mr: 3, onClick: onClose, children: "Close" })] })] })] })] }));
107
123
  }
@@ -9,7 +9,15 @@ function generateSingleField(formfield, fieldName, key) {
9
9
  formfield.type === 'number' ||
10
10
  formfield.type === 'date' ||
11
11
  formfield.type === 'datetime-local' ||
12
- formfield.type === 'time' ? (_jsx(Input, { ...field, value: field.value ?? '', type: formfield.type !== 'string' ? formfield.type : 'text' })) : formfield.type === 'file' ? (_jsx(Input, { type: "file", name: fieldName, onChange: (event) => {
12
+ formfield.type === 'time' ? (_jsx(Input, { ...field, value: field.value
13
+ ? formfield.type === 'date'
14
+ ? field.value.substring(0, 10) // "YYYY-MM-DD"
15
+ : formfield.type === 'time'
16
+ ? new Date(field.value).toISOString().substring(11, 16) // "HH:mm"
17
+ : formfield.type === 'datetime-local'
18
+ ? new Date(field.value).toISOString().substring(0, 16) // "YYYY-MM-DDTHH:mm"
19
+ : field.value
20
+ : '', type: formfield.type !== 'string' ? formfield.type : 'text' })) : formfield.type === 'file' ? (_jsx(Input, { type: "file", name: fieldName, onChange: (event) => {
13
21
  if (event.currentTarget.files != null) {
14
22
  form.setFieldValue(fieldName, event.currentTarget.files[0]);
15
23
  }
@@ -2,6 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import ReactDOM from 'react-dom/client';
4
4
  import App from './App';
5
- import { event } from './testSchemas/event';
5
+ import { coursesPage } from './testSchemas/course';
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: [coursesPage], basePath: "" }) }));
@@ -36,18 +36,18 @@ export type FormField = SimpleField | DropdownField | ObjectField | ArrayField;
36
36
  export interface Action {
37
37
  name: string;
38
38
  fields?: FormField[];
39
- onSubmit: (obj1: any, obj2?: any, obj3?: any) => Promise<any>;
39
+ onSubmit: (obj1?: any, obj2?: any, obj3?: any) => Promise<any>;
40
40
  }
41
41
  export interface RowAction {
42
42
  name: string;
43
43
  fields?: FormField[];
44
- initialise?: (obj: any) => Promise<any>;
44
+ initialise?: (rowId: any) => Promise<any>;
45
45
  onSubmit: (obj1: any, obj2?: any, obj3?: any) => Promise<any>;
46
46
  }
47
47
  export interface PageSchema {
48
48
  name: string;
49
49
  actions: Action[];
50
50
  rowActions: RowAction[];
51
- getRequest: (filter?: any) => Promise<any[]>;
51
+ getRequest: () => Promise<any[]>;
52
52
  tableFields: TableField[];
53
53
  }
@@ -0,0 +1,2 @@
1
+ import { PageSchema } from '../schema';
2
+ export declare const coursesPage: PageSchema;
@@ -0,0 +1,342 @@
1
+ import React from 'react';
2
+ import axios from 'axios';
3
+ import * as Yup from 'yup';
4
+ const domain = 'http://localhost:3001';
5
+ // Please try to modularize the code for a schema though
6
+ export const coursesPage = {
7
+ name: 'Courses',
8
+ // 1. Data retrieval: fetch all courses from backend
9
+ getRequest: async () => {
10
+ const res = await axios.get(`${domain}/courses`);
11
+ return res.data; // returns an array of course objects
12
+ },
13
+ // 2. Table fields: define columns for the courses table
14
+ tableFields: [
15
+ {
16
+ name: 'Course ID',
17
+ // The ID should be first column to allow rowActions to reference it (if rowActions needed)
18
+ access: (obj) => obj.id,
19
+ },
20
+ {
21
+ name: 'Name',
22
+ access: (obj) => obj.name,
23
+ },
24
+ {
25
+ name: 'Level',
26
+ access: (obj) => obj.level,
27
+ },
28
+ {
29
+ name: 'Credits',
30
+ access: (obj) => obj.credits,
31
+ },
32
+ {
33
+ name: 'Start Date',
34
+ access: (obj) => (obj.startDate ? new Date(obj.startDate).toLocaleDateString() : 'N/A'),
35
+ },
36
+ {
37
+ name: 'Instructor',
38
+ // Display instructor name (assuming obj.instructor has { name, email })
39
+ access: (obj) => (obj.instructor ? obj.instructor.name : 'N/A'),
40
+ },
41
+ {
42
+ name: 'Course Image',
43
+ // Use React.createElement to render an image
44
+ access: (obj) => obj.coverImage
45
+ ? React.createElement('img', {
46
+ src: `${domain}${obj.coverImage}`,
47
+ alt: 'Cover Image',
48
+ style: { width: '100px' },
49
+ })
50
+ : 'No Image',
51
+ },
52
+ ],
53
+ // 3. Global actions (top-level page actions)
54
+ actions: [
55
+ {
56
+ name: 'Ping Server',
57
+ // No form, no arguments: simple request to '/ping'
58
+ onSubmit: async () => {
59
+ const res = await axios.get(`${domain}/courses/ping`);
60
+ return res.data;
61
+ },
62
+ },
63
+ {
64
+ name: 'Create Course',
65
+ // Form with one argument (formValues) on submit
66
+ fields: [
67
+ {
68
+ // Course Name
69
+ name: 'name',
70
+ label: 'Course Name',
71
+ type: 'string',
72
+ validationSchema: Yup.string().required('Name is required'),
73
+ },
74
+ {
75
+ // Level (Undergraduate/Graduate dropdown)
76
+ name: 'level',
77
+ label: 'Level',
78
+ type: 'dropdown',
79
+ validationSchema: Yup.string().required('Level is required'),
80
+ getOptions: async () => [
81
+ { value: 'Undergraduate', display: 'Undergraduate' },
82
+ { value: 'Graduate', display: 'Graduate' },
83
+ ],
84
+ },
85
+ {
86
+ // Credits
87
+ name: 'credits',
88
+ label: 'Credits',
89
+ type: 'number',
90
+ validationSchema: Yup.number().required('Credits are required'),
91
+ },
92
+ {
93
+ // Start Date
94
+ name: 'startDate',
95
+ label: 'Start Date',
96
+ type: 'date',
97
+ validationSchema: Yup.date().required('Start date is required'),
98
+ },
99
+ {
100
+ // Instructor (object with name and email)
101
+ name: 'instructor',
102
+ label: 'Instructor',
103
+ type: 'object',
104
+ fields: [
105
+ {
106
+ name: 'name',
107
+ label: 'Instructor Name',
108
+ type: 'string',
109
+ validationSchema: Yup.string().required('Instructor name is required'),
110
+ },
111
+ {
112
+ name: 'email',
113
+ label: 'Instructor Email',
114
+ type: 'string',
115
+ validationSchema: Yup.string()
116
+ .email('Invalid email')
117
+ .required('Instructor email is required'),
118
+ },
119
+ ],
120
+ },
121
+ {
122
+ // Schedule (array of sessions with date and topic)
123
+ name: 'schedule',
124
+ label: 'Schedule',
125
+ type: 'array',
126
+ element: {
127
+ name: '', // no specific name for array elements
128
+ label: '', // (fields below define each session)
129
+ type: 'object',
130
+ fields: [
131
+ {
132
+ name: 'date',
133
+ label: 'Date',
134
+ type: 'date',
135
+ validationSchema: Yup.date().required('Session date is required'),
136
+ },
137
+ {
138
+ name: 'topic',
139
+ label: 'Topic',
140
+ type: 'string',
141
+ validationSchema: Yup.string().required('Session topic is required'),
142
+ },
143
+ ],
144
+ },
145
+ },
146
+ {
147
+ // Cover Image (file upload)
148
+ name: 'coverImage',
149
+ label: 'Cover Image',
150
+ type: 'file',
151
+ validationSchema: Yup.mixed().required('Cover image is required'),
152
+ },
153
+ ],
154
+ onSubmit: async (formValues) => {
155
+ // Create a new course
156
+ const data = new FormData();
157
+ Object.entries(formValues).forEach(([k, v]) => {
158
+ if (k === 'coverImage') {
159
+ if (v instanceof File)
160
+ data.append('coverImage', v);
161
+ }
162
+ else if (k === 'schedule') {
163
+ data.append('schedule', JSON.stringify(v));
164
+ }
165
+ else if (k === 'instructor') {
166
+ const instructor = v;
167
+ data.append('instructor[name]', instructor.name);
168
+ data.append('instructor[email]', instructor.email);
169
+ }
170
+ else {
171
+ data.append(k, v);
172
+ }
173
+ });
174
+ await axios.post(`${domain}/courses`, data, {
175
+ headers: { 'Content-Type': 'multipart/form-data' },
176
+ });
177
+ },
178
+ },
179
+ ],
180
+ // 4. Row-specific actions (actions for each course row)
181
+ rowActions: [
182
+ {
183
+ name: 'Delete Course',
184
+ // No form; onSubmit uses one argument (rowId) to delete that course
185
+ onSubmit: async (courseId) => {
186
+ const res = await axios.delete(`${domain}/courses/${courseId}`);
187
+ return res.data;
188
+ },
189
+ },
190
+ {
191
+ name: 'Archive Course',
192
+ // No form; onSubmit uses two arguments (rowId and rowObject) to archive
193
+ onSubmit: async (courseId, course) => {
194
+ console.log(course); // you can do whatever you want with the course information
195
+ const res = await axios.patch(`${domain}/courses/${courseId}/archive`);
196
+ return res.data;
197
+ },
198
+ },
199
+ {
200
+ name: 'Edit Basic Info',
201
+ // Form (name, level, credits) with two arguments onSubmit (formValues, rowId)
202
+ fields: [
203
+ {
204
+ name: 'name',
205
+ label: 'Course Name',
206
+ type: 'string',
207
+ validationSchema: Yup.string().required('Name is required'),
208
+ },
209
+ {
210
+ name: 'level',
211
+ label: 'Level',
212
+ type: 'dropdown',
213
+ validationSchema: Yup.string().required('Level is required'),
214
+ getOptions: async () => [
215
+ { value: 'Undergraduate', display: 'Undergraduate' },
216
+ { value: 'Graduate', display: 'Graduate' },
217
+ ],
218
+ },
219
+ {
220
+ name: 'credits',
221
+ label: 'Credits',
222
+ type: 'number',
223
+ validationSchema: Yup.number().required('Credits are required'),
224
+ },
225
+ ],
226
+ onSubmit: async (formValues, courseId) => {
227
+ // Update course basic info
228
+ const res = await axios.patch(`${domain}/courses/${courseId}`, formValues);
229
+ return res.data;
230
+ },
231
+ },
232
+ {
233
+ name: 'Edit Full Course',
234
+ // Form (all course fields) with three arguments onSubmit (formValues, rowId, rowObject)
235
+ fields: [
236
+ {
237
+ name: 'name',
238
+ label: 'Course Name',
239
+ type: 'string',
240
+ validationSchema: Yup.string().required('Name is required'),
241
+ },
242
+ {
243
+ name: 'level',
244
+ label: 'Level',
245
+ type: 'dropdown',
246
+ validationSchema: Yup.string().required('Level is required'),
247
+ getOptions: async () => [
248
+ { value: 'Undergraduate', display: 'Undergraduate' },
249
+ { value: 'Graduate', display: 'Graduate' },
250
+ ],
251
+ },
252
+ {
253
+ name: 'credits',
254
+ label: 'Credits',
255
+ type: 'number',
256
+ validationSchema: Yup.number().required('Credits are required'),
257
+ },
258
+ {
259
+ name: 'startDate',
260
+ label: 'Start Date',
261
+ type: 'date',
262
+ validationSchema: Yup.date().required('Start date is required'),
263
+ },
264
+ {
265
+ name: 'instructor',
266
+ label: 'Instructor',
267
+ type: 'object',
268
+ fields: [
269
+ {
270
+ name: 'name',
271
+ label: 'Instructor Name',
272
+ type: 'string',
273
+ validationSchema: Yup.string().required('Instructor name is required'),
274
+ },
275
+ {
276
+ name: 'email',
277
+ label: 'Instructor Email',
278
+ type: 'string',
279
+ validationSchema: Yup.string()
280
+ .email('Invalid email')
281
+ .required('Instructor email is required'),
282
+ },
283
+ ],
284
+ },
285
+ {
286
+ name: 'schedule',
287
+ label: 'Schedule',
288
+ type: 'array',
289
+ element: {
290
+ name: '',
291
+ label: '',
292
+ type: 'object',
293
+ fields: [
294
+ {
295
+ name: 'date',
296
+ label: 'Date',
297
+ type: 'date',
298
+ validationSchema: Yup.date().required('Session date is required'),
299
+ },
300
+ {
301
+ name: 'topic',
302
+ label: 'Topic',
303
+ type: 'string',
304
+ validationSchema: Yup.string().required('Session topic is required'),
305
+ },
306
+ ],
307
+ },
308
+ },
309
+ {
310
+ name: 'coverImage',
311
+ label: 'Cover Image',
312
+ type: 'file',
313
+ validationSchema: Yup.mixed().required('Cover image is required'),
314
+ },
315
+ ],
316
+ // Use initialise to fetch current course data and pre-fill the form
317
+ initialise: async (courseId) => {
318
+ const res = await axios.get(`${domain}/courses/${courseId}`);
319
+ return res.data; // return course object to populate the form
320
+ },
321
+ onSubmit: async (formValues, courseId, course) => {
322
+ // Update full course details
323
+ // Please note that you can include "initialise" and not the "rowObject" (or vice versa)
324
+ console.log(course); // you can do whatever you want with the course information
325
+ const data = new FormData();
326
+ data.append('name', formValues.name);
327
+ data.append('level', formValues.level);
328
+ data.append('credits', formValues.credits);
329
+ data.append('startDate', formValues.startDate);
330
+ data.append('instructor[name]', formValues.instructor.name);
331
+ data.append('instructor[email]', formValues.instructor.email);
332
+ data.append('schedule', JSON.stringify(formValues.schedule));
333
+ if (formValues.coverImage) {
334
+ data.append('coverImage', formValues.coverImage);
335
+ }
336
+ await axios.patch(`${domain}/courses/${courseId}`, data, {
337
+ headers: { 'Content-Type': 'multipart/form-data' },
338
+ });
339
+ },
340
+ },
341
+ ],
342
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "admin_lsac",
3
3
  "private": false,
4
- "version": "2.0.23",
4
+ "version": "2.0.24",
5
5
  "description": "Generic Admin Panel created for storing events' data",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -1,2 +0,0 @@
1
- import { PageSchema } from '../schema';
2
- export declare const event: PageSchema;
@@ -1,117 +0,0 @@
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 - Variant 1', // without form
10
- onSubmit: async (eventId) => {
11
- // onSubmit has only param, the row's ID
12
- const res = await axios.delete(domain + '/events/' + eventId);
13
- return res.data;
14
- },
15
- },
16
- {
17
- name: 'Delete Event - Variant 2', // with form
18
- fields: [
19
- {
20
- name: 'input',
21
- label: 'Mock Field',
22
- type: 'string',
23
- validationSchema: '',
24
- },
25
- {
26
- name: 'type',
27
- label: 'Question Type',
28
- type: 'dropdown',
29
- // validationSchema is missing here (optional), so it will not be a required field
30
- getOptions: async () => [
31
- {
32
- value: 'OPEN',
33
- display: 'Open',
34
- },
35
- {
36
- value: 'SINGLE',
37
- display: 'Single',
38
- },
39
- {
40
- value: 'MULTIPLE',
41
- display: 'Multiple',
42
- },
43
- ],
44
- },
45
- ],
46
- initialise: async (eventId) => {
47
- const res = await axios.get(domain + '/events/' + eventId);
48
- const obj = {};
49
- obj.input = res.data.name;
50
- return obj;
51
- },
52
- onSubmit: async (formValues, eventId) => {
53
- // onSubmit has two params
54
- // the first param - formValues, while the second is the row's ID
55
- console.log(formValues);
56
- const res = await axios.delete(domain + '/events/' + eventId);
57
- return res.data;
58
- },
59
- },
60
- ],
61
- getRequest: async () => {
62
- const res = await axios.get(domain + '/events');
63
- return res.data;
64
- },
65
- tableFields: [
66
- {
67
- name: 'Id',
68
- access: (obj) => obj.id,
69
- },
70
- {
71
- name: 'Event Name',
72
- access: (obj) => obj.name,
73
- },
74
- {
75
- name: 'Description',
76
- access: (obj) => obj.description,
77
- },
78
- {
79
- name: 'Category',
80
- access: (obj) => obj.category,
81
- },
82
- {
83
- name: 'Ticket Type',
84
- access: (obj) => obj.ticketType,
85
- },
86
- {
87
- name: 'Date',
88
- access: (obj) => obj.date,
89
- },
90
- {
91
- name: 'Release',
92
- access: (obj) => obj.release,
93
- },
94
- {
95
- name: 'Is Released',
96
- access: (obj) => (obj.isReleased ? 'Is Released' : 'Not Released'),
97
- },
98
- {
99
- name: 'Available Tickets',
100
- access: (obj) => obj.availableTickets,
101
- },
102
- {
103
- name: 'Max Tickets',
104
- access: (obj) => obj.maxTickets,
105
- },
106
- {
107
- name: 'Image',
108
- access: (obj) => obj.imgUrl
109
- ? React.createElement('img', {
110
- src: obj.imgUrl,
111
- alt: 'ERROR',
112
- style: { width: '100px' },
113
- })
114
- : 'No Image Available',
115
- },
116
- ],
117
- };