dce-reactkit 3.4.0 → 3.5.1-beta.1

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.
Files changed (129) hide show
  1. package/.eslintrc.js +2 -0
  2. package/.vscode/settings.json +1 -0
  3. package/dist/cjs/index.js +36372 -191
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.d.ts +10 -0
  6. package/dist/cjs/types/components/DBEntryManagerPanel/AddOrEditDBEntry/index.d.ts +34 -0
  7. package/dist/cjs/types/components/DBEntryManagerPanel/helpers/generateEndpointPath.d.ts +9 -0
  8. package/dist/cjs/types/components/DBEntryManagerPanel/index.d.ts +25 -0
  9. package/dist/cjs/types/components/DBEntryManagerPanel/types/DBEntry.d.ts +8 -0
  10. package/dist/cjs/types/components/DBEntryManagerPanel/types/DBEntryField.d.ts +49 -0
  11. package/dist/cjs/types/components/DBEntryManagerPanel/types/DBEntryFieldType.d.ts +12 -0
  12. package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +40 -0
  13. package/dist/cjs/types/helpers/canReviewLogs.d.ts +0 -5
  14. package/dist/cjs/types/index.d.ts +6 -1
  15. package/dist/esm/index.js +36587 -427
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/types/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.d.ts +10 -0
  18. package/dist/esm/types/components/DBEntryManagerPanel/AddOrEditDBEntry/index.d.ts +34 -0
  19. package/dist/esm/types/components/DBEntryManagerPanel/helpers/generateEndpointPath.d.ts +9 -0
  20. package/dist/esm/types/components/DBEntryManagerPanel/index.d.ts +25 -0
  21. package/dist/esm/types/components/DBEntryManagerPanel/types/DBEntry.d.ts +8 -0
  22. package/dist/esm/types/components/DBEntryManagerPanel/types/DBEntryField.d.ts +49 -0
  23. package/dist/esm/types/components/DBEntryManagerPanel/types/DBEntryFieldType.d.ts +12 -0
  24. package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +40 -0
  25. package/dist/esm/types/helpers/canReviewLogs.d.ts +0 -5
  26. package/dist/esm/types/index.d.ts +6 -1
  27. package/dist/index.d.ts +166 -35
  28. package/package.json +16 -12
  29. package/src/client/initClient.tsx +23 -31
  30. package/src/components/AppWrapper.tsx +1 -1
  31. package/src/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.tsx +303 -0
  32. package/src/components/DBEntryManagerPanel/AddOrEditDBEntry/index.tsx +737 -0
  33. package/src/components/DBEntryManagerPanel/helpers/generateEndpointPath.ts +16 -0
  34. package/src/components/DBEntryManagerPanel/index.tsx +522 -0
  35. package/src/components/DBEntryManagerPanel/types/DBEntry.ts +7 -0
  36. package/src/components/DBEntryManagerPanel/types/DBEntryField.ts +94 -0
  37. package/src/components/DBEntryManagerPanel/types/DBEntryFieldType.ts +18 -0
  38. package/src/components/ItemPicker/index.test.tsx +10 -10
  39. package/src/helpers/addDBEditorEndpoints.ts +128 -0
  40. package/src/helpers/canReviewLogs.ts +0 -20
  41. package/src/helpers/genCSV.ts +2 -5
  42. package/src/helpers/{getOrdinal.ts → getOrdinal.tsx} +0 -1
  43. package/src/index.ts +11 -9
  44. package/tsconfig.json +2 -6
  45. package/dist/cjs/types/helpers/abbreviate.test.d.ts +0 -5
  46. package/dist/cjs/types/helpers/avg.test.d.ts +0 -5
  47. package/dist/cjs/types/helpers/canReviewLogs.test.d.ts +0 -5
  48. package/dist/cjs/types/helpers/ceilToNumDecimals.test.d.ts +0 -5
  49. package/dist/cjs/types/helpers/compareArraysByProp.test.d.ts +0 -5
  50. package/dist/cjs/types/helpers/extractProp.test.d.ts +0 -5
  51. package/dist/cjs/types/helpers/floorToNumDecimals.test.d.ts +0 -5
  52. package/dist/cjs/types/helpers/forceNumIntoBounds.test.d.ts +0 -5
  53. package/dist/cjs/types/helpers/genCSV.test.d.ts +0 -5
  54. package/dist/cjs/types/helpers/genCommaList.d.ts +0 -12
  55. package/dist/cjs/types/helpers/genCommaList.test.d.ts +0 -1
  56. package/dist/cjs/types/helpers/getHumanReadableDate.test.d.ts +0 -5
  57. package/dist/cjs/types/helpers/getLocalTimeInfo.test.d.ts +0 -6
  58. package/dist/cjs/types/helpers/getMonthName.test.d.ts +0 -5
  59. package/dist/cjs/types/helpers/getOrdinal.test.d.ts +0 -5
  60. package/dist/cjs/types/helpers/getPartOfDay.test.d.ts +0 -5
  61. package/dist/cjs/types/helpers/getTimeInfoInET.test.d.ts +0 -5
  62. package/dist/cjs/types/helpers/validators/shared/constants/ERROR_MESSAGES.d.ts +0 -10
  63. package/dist/cjs/types/helpers/validators/shared/helpers/validateRegex.d.ts +0 -19
  64. package/dist/cjs/types/helpers/validators/shared/types/ValidationResult.d.ts +0 -12
  65. package/dist/cjs/types/helpers/validators/validateEmail.d.ts +0 -11
  66. package/dist/cjs/types/helpers/validators/validateEmail.test.d.ts +0 -1
  67. package/dist/cjs/types/helpers/validators/validatePhoneNumber.d.ts +0 -11
  68. package/dist/cjs/types/helpers/validators/validatePhoneNumber.test.d.ts +0 -1
  69. package/dist/cjs/types/helpers/validators/validateString.d.ts +0 -22
  70. package/dist/cjs/types/helpers/validators/validateString.test.d.ts +0 -1
  71. package/dist/esm/types/helpers/abbreviate.test.d.ts +0 -5
  72. package/dist/esm/types/helpers/avg.test.d.ts +0 -5
  73. package/dist/esm/types/helpers/canReviewLogs.test.d.ts +0 -5
  74. package/dist/esm/types/helpers/ceilToNumDecimals.test.d.ts +0 -5
  75. package/dist/esm/types/helpers/compareArraysByProp.test.d.ts +0 -5
  76. package/dist/esm/types/helpers/extractProp.test.d.ts +0 -5
  77. package/dist/esm/types/helpers/floorToNumDecimals.test.d.ts +0 -5
  78. package/dist/esm/types/helpers/forceNumIntoBounds.test.d.ts +0 -5
  79. package/dist/esm/types/helpers/genCSV.test.d.ts +0 -5
  80. package/dist/esm/types/helpers/genCommaList.d.ts +0 -12
  81. package/dist/esm/types/helpers/genCommaList.test.d.ts +0 -1
  82. package/dist/esm/types/helpers/getHumanReadableDate.test.d.ts +0 -5
  83. package/dist/esm/types/helpers/getLocalTimeInfo.test.d.ts +0 -6
  84. package/dist/esm/types/helpers/getMonthName.test.d.ts +0 -5
  85. package/dist/esm/types/helpers/getOrdinal.test.d.ts +0 -5
  86. package/dist/esm/types/helpers/getPartOfDay.test.d.ts +0 -5
  87. package/dist/esm/types/helpers/getTimeInfoInET.test.d.ts +0 -5
  88. package/dist/esm/types/helpers/validators/shared/constants/ERROR_MESSAGES.d.ts +0 -10
  89. package/dist/esm/types/helpers/validators/shared/helpers/validateRegex.d.ts +0 -19
  90. package/dist/esm/types/helpers/validators/shared/types/ValidationResult.d.ts +0 -12
  91. package/dist/esm/types/helpers/validators/validateEmail.d.ts +0 -11
  92. package/dist/esm/types/helpers/validators/validateEmail.test.d.ts +0 -1
  93. package/dist/esm/types/helpers/validators/validatePhoneNumber.d.ts +0 -11
  94. package/dist/esm/types/helpers/validators/validatePhoneNumber.test.d.ts +0 -1
  95. package/dist/esm/types/helpers/validators/validateString.d.ts +0 -22
  96. package/dist/esm/types/helpers/validators/validateString.test.d.ts +0 -1
  97. package/src/helpers/abbreviate.test.ts +0 -26
  98. package/src/helpers/avg.test.ts +0 -24
  99. package/src/helpers/canReviewLogs.test.ts +0 -67
  100. package/src/helpers/ceilToNumDecimals.test.ts +0 -39
  101. package/src/helpers/compareArraysByProp.test.ts +0 -102
  102. package/src/helpers/extractProp.test.ts +0 -34
  103. package/src/helpers/floorToNumDecimals.test.ts +0 -39
  104. package/src/helpers/forceNumIntoBounds.test.ts +0 -34
  105. package/src/helpers/genCSV.test.ts +0 -61
  106. package/src/helpers/genCommaList.test.ts +0 -57
  107. package/src/helpers/genCommaList.ts +0 -21
  108. package/src/helpers/getHumanReadableDate.test.ts +0 -29
  109. package/src/helpers/getLocalTimeInfo.test.ts +0 -48
  110. package/src/helpers/getMonthName.test.ts +0 -79
  111. package/src/helpers/getOrdinal.test.ts +0 -84
  112. package/src/helpers/getPartOfDay.test.ts +0 -56
  113. package/src/helpers/getTimeInfoInET.test.ts +0 -72
  114. package/src/helpers/validators/shared/constants/ERROR_MESSAGES.ts +0 -18
  115. package/src/helpers/validators/shared/helpers/validateRegex.ts +0 -47
  116. package/src/helpers/validators/shared/types/ValidationResult.ts +0 -20
  117. package/src/helpers/validators/validateEmail.test.ts +0 -110
  118. package/src/helpers/validators/validateEmail.ts +0 -47
  119. package/src/helpers/validators/validatePhoneNumber.test.ts +0 -94
  120. package/src/helpers/validators/validatePhoneNumber.ts +0 -49
  121. package/src/helpers/validators/validateString.test.ts +0 -175
  122. package/src/helpers/validators/validateString.ts +0 -171
  123. /package/src/helpers/{abbreviate.ts → abbreviate.tsx} +0 -0
  124. /package/src/helpers/{avg.ts → avg.tsx} +0 -0
  125. /package/src/helpers/{ceilToNumDecimals.ts → ceilToNumDecimals.tsx} +0 -0
  126. /package/src/helpers/{floorToNumDecimals.ts → floorToNumDecimals.tsx} +0 -0
  127. /package/src/helpers/{forceNumIntoBounds.ts → forceNumIntoBounds.tsx} +0 -0
  128. /package/src/helpers/{getLocalTimeInfo.ts → getLocalTimeInfo.tsx} +0 -0
  129. /package/src/helpers/{getTimeInfoInET.ts → getTimeInfoInET.tsx} +0 -0
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Generates the endpoint path for the given collection name
3
+ * @author Yuen Ler Chow
4
+ * @param collectionName the name of the collection
5
+ * @param [adminsOnly] true if the endpoint is for admins only
6
+ * @returns the endpoint path
7
+ */
8
+ const generateEndpointPath = (collectionName: string, adminsOnly?: boolean) => {
9
+ // Determine prefix based on whether the endpoint is for admins only
10
+ const userPath = adminsOnly ? 'admin' : 'ttm';
11
+
12
+ // Return the endpoint path
13
+ return `/api/${userPath}/dce-reactkit/dbeditor/${collectionName}`;
14
+ };
15
+
16
+ export default generateEndpointPath;
@@ -0,0 +1,522 @@
1
+ /**
2
+ * DB Entry Manager Panel
3
+ * @author Yuen Ler Chow
4
+ * @author Gabe Abrams
5
+ */
6
+
7
+ // Import React
8
+ import React, { useReducer, useEffect } from 'react';
9
+
10
+ // Import FontAwesome
11
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
12
+ import { faPlus, faTrash, faCog } from '@fortawesome/free-solid-svg-icons';
13
+
14
+ // Import shared components
15
+ import TabBox from '../TabBox';
16
+ import visitServerEndpoint from '../../helpers/visitServerEndpoint';
17
+ import { showFatalError, confirm } from '../AppWrapper';
18
+ import LoadingSpinner from '../LoadingSpinner';
19
+
20
+ // Import types
21
+ import DBEntry from './types/DBEntry';
22
+ import DBEntryField from './types/DBEntryField';
23
+
24
+ // Import other components
25
+ import AddOrEditDBEntry from './AddOrEditDBEntry';
26
+ import generateEndpointPath from './helpers/generateEndpointPath';
27
+
28
+ /*------------------------------------------------------------------------*/
29
+ /* -------------------------------- Types ------------------------------- */
30
+ /*------------------------------------------------------------------------*/
31
+
32
+ // Props definition
33
+ type Props = {
34
+ // List of db entry fields
35
+ entryFields: DBEntryField[],
36
+ // a prop that is unique to each item
37
+ idPropName: string,
38
+ // the prop that you want to show up as the title of each item
39
+ titlePropName: string,
40
+ // The prop that you want in parentheses after the title
41
+ descriptionPropName: string,
42
+ // the title of the tabBox
43
+ itemListTitle: string,
44
+ // the phrase you want when you say "add a new [itemName]"
45
+ itemName: string,
46
+ /**
47
+ * Function to validate the db entry before sending to the server
48
+ * (throws an error if not validate)
49
+ * @param dbEntry the db entry to validate
50
+ */
51
+ validateEntry?: (dbEntry: DBEntry) => Promise<void>,
52
+ /**
53
+ * Function to modify the db entry before sending to the server
54
+ * @param dbEntry the db entry to modify
55
+ * @returns the modified db entry
56
+ */
57
+ modifyEntry?: (dbEntry: DBEntry) => Promise<DBEntry> | DBEntry,
58
+ // True if editing is disabled
59
+ disableEdit?: boolean,
60
+ // Name of the collection in the database
61
+ collectionName: string,
62
+ // True if only admins can access this page
63
+ adminsOnly?: boolean,
64
+ // the query to filter the db entries
65
+ filterQuery?: { [k: string]: any },
66
+ };
67
+
68
+ /*------------------------------------------------------------------------*/
69
+ /* -------------------------------- State ------------------------------- */
70
+ /*------------------------------------------------------------------------*/
71
+
72
+ /* -------- State Definition -------- */
73
+
74
+ type State = {
75
+ // List of db items
76
+ dbEntries: DBEntry[],
77
+ // True if adding a new db item
78
+ adding: boolean,
79
+ // Db item to edit
80
+ dbEntryToEdit?: DBEntry,
81
+ // True if loading
82
+ loading: boolean,
83
+ };
84
+
85
+ /* ------------- Actions ------------ */
86
+
87
+ // Types of actions
88
+ enum ActionType {
89
+ // Show adder
90
+ ShowAdder = 'ShowAdder',
91
+ // Show editor
92
+ ShowEditor = 'ShowEditor',
93
+ // Finish adding
94
+ FinishAdd = 'FinishAdd',
95
+ // Finish loading
96
+ FinishLoading = 'FinishLoading',
97
+ // Start deletion process
98
+ StartDelete = 'StartDelete',
99
+ // Finish deletion process
100
+ FinishDelete = 'FinishDelete',
101
+ }
102
+
103
+ // Action definitions
104
+ type Action = (
105
+ | {
106
+ // Action type
107
+ type: ActionType.FinishLoading,
108
+ // db entry list
109
+ dbEntries: DBEntry[],
110
+ }
111
+ | {
112
+ // Action type
113
+ type: ActionType.ShowEditor,
114
+ // db item to edit
115
+ dbEntry: DBEntry,
116
+ }
117
+ | {
118
+ // Action type
119
+ type: ActionType.FinishAdd,
120
+ // DB entry that was added
121
+ dbEntry?: DBEntry,
122
+ // unique id prop name
123
+ idPropName: string,
124
+ }
125
+ | {
126
+ // Action type
127
+ type: ActionType.StartDelete,
128
+ }
129
+ | {
130
+ // Action type
131
+ type: ActionType.FinishDelete,
132
+ // db entry that was deleted
133
+ dbEntry: DBEntry,
134
+ // unique id prop name
135
+ idPropName: string,
136
+ }
137
+ | {
138
+ // Action type
139
+ type: (
140
+ | ActionType.ShowAdder
141
+ ),
142
+ }
143
+ );
144
+
145
+ /**
146
+ * Reducer that executes actions
147
+ * @author Yuen Ler Chow
148
+ * @param state current state
149
+ * @param action action to execute
150
+ * @returns updated state
151
+ */
152
+ const reducer = (state: State, action: Action): State => {
153
+ switch (action.type) {
154
+ case ActionType.FinishLoading: {
155
+ return {
156
+ ...state,
157
+ loading: false,
158
+ dbEntries: action.dbEntries,
159
+ };
160
+ }
161
+ case ActionType.ShowAdder: {
162
+ return {
163
+ ...state,
164
+ adding: true,
165
+ dbEntryToEdit: undefined,
166
+ };
167
+ }
168
+ case ActionType.ShowEditor: {
169
+ return {
170
+ ...state,
171
+ adding: false,
172
+ dbEntryToEdit: action.dbEntry,
173
+ };
174
+ }
175
+ case ActionType.FinishAdd: {
176
+ // Handle cancel
177
+ const finishedEntry = action.dbEntry;
178
+ if (!finishedEntry) {
179
+ return {
180
+ ...state,
181
+ adding: false,
182
+ dbEntryToEdit: undefined,
183
+ };
184
+ }
185
+
186
+ // Create an updated list of DB entries
187
+ let updatedDbEntries: DBEntry[];
188
+ if (state.adding) {
189
+ updatedDbEntries = [...state.dbEntries, finishedEntry];
190
+ } else {
191
+ updatedDbEntries = state.dbEntries.map((existingDbEntry) => {
192
+ if (state.dbEntryToEdit && state.dbEntryToEdit[action.idPropName] === existingDbEntry[action.idPropName]) {
193
+ // This is the entry being edited! Replace
194
+ return finishedEntry;
195
+ }
196
+ // This is not the entry being edited
197
+ return existingDbEntry;
198
+ });
199
+ }
200
+
201
+ // Update the state
202
+ return {
203
+ ...state,
204
+ adding: false,
205
+ dbEntryToEdit: undefined,
206
+ dbEntries: updatedDbEntries,
207
+ };
208
+ }
209
+ case ActionType.StartDelete: {
210
+ return {
211
+ ...state,
212
+ loading: true,
213
+ };
214
+ }
215
+ case ActionType.FinishDelete: {
216
+ return {
217
+ ...state,
218
+ loading: false,
219
+ // Remove the deleted entry from the list
220
+ dbEntries: state.dbEntries.filter((entry: DBEntry) => {
221
+ return (
222
+ entry[action.idPropName] !== action.dbEntry[action.idPropName]
223
+ );
224
+ }),
225
+ };
226
+ }
227
+ default: {
228
+ return state;
229
+ }
230
+ }
231
+ };
232
+
233
+ /*------------------------------------------------------------------------*/
234
+ /* ------------------------------ Component ----------------------------- */
235
+ /*------------------------------------------------------------------------*/
236
+
237
+ const DBEntryManagerPanel: React.FC<Props> = (props) => {
238
+ // Destructure all props
239
+ const {
240
+ entryFields,
241
+ titlePropName,
242
+ descriptionPropName,
243
+ idPropName,
244
+ itemListTitle,
245
+ itemName,
246
+ validateEntry,
247
+ modifyEntry,
248
+ disableEdit,
249
+ collectionName,
250
+ adminsOnly,
251
+ filterQuery,
252
+ } = props;
253
+
254
+ /* -------------- State ------------- */
255
+
256
+ // Initial state
257
+ const initialState: State = {
258
+ dbEntries: [],
259
+ adding: false,
260
+ loading: true,
261
+ };
262
+
263
+ // Initialize state
264
+ const [state, dispatch] = useReducer(reducer, initialState);
265
+
266
+ // Destructure common state
267
+ const {
268
+ adding,
269
+ dbEntryToEdit,
270
+ dbEntries,
271
+ loading,
272
+ } = state;
273
+
274
+ /*------------------------------------------------------------------------*/
275
+ /* ------------------------- Component Functions ------------------------ */
276
+ /*------------------------------------------------------------------------*/
277
+
278
+ // Generate the endpoint path
279
+ const endpoint = generateEndpointPath(collectionName, adminsOnly);
280
+
281
+ /**
282
+ * Delete a database entry
283
+ * @author Yuen Ler Chow
284
+ * @param entry the database entry to delete
285
+ */
286
+ const deleteEntry = async (entry: DBEntry) => {
287
+ // Confirm
288
+ const confirmed = await confirm(
289
+ 'Remove?',
290
+ `Are you sure you want to remove this ${itemName}?`,
291
+ {
292
+ confirmButtonText: 'Remove Item',
293
+ },
294
+ );
295
+
296
+ // Skip if cancelled
297
+ if (!confirmed) {
298
+ return;
299
+ }
300
+
301
+ // Remove the entry
302
+ try {
303
+ // Start loader
304
+ dispatch({
305
+ type: ActionType.StartDelete,
306
+ });
307
+
308
+ // Perform deletion
309
+ await visitServerEndpoint({
310
+ path: `${endpoint}/${entry[idPropName]}`,
311
+ method: 'DELETE',
312
+ });
313
+
314
+ // Finish loader
315
+ dispatch({
316
+ type: ActionType.FinishDelete,
317
+ dbEntry: entry,
318
+ idPropName,
319
+ });
320
+ } catch (err) {
321
+ return showFatalError(err);
322
+ }
323
+ };
324
+
325
+ /*------------------------------------------------------------------------*/
326
+ /* ------------------------- Lifecycle Functions ------------------------ */
327
+ /*------------------------------------------------------------------------*/
328
+
329
+ /**
330
+ * Mount
331
+ * @author Yuen Ler Chow
332
+ */
333
+ useEffect(
334
+ () => {
335
+ (async () => {
336
+ // Load list of database entries
337
+ try {
338
+ const data = await visitServerEndpoint({
339
+ path: endpoint,
340
+ method: 'GET',
341
+ params: {
342
+ filterQuery: JSON.stringify(filterQuery),
343
+ },
344
+ });
345
+
346
+ // Save loaded data
347
+ dispatch({
348
+ type: ActionType.FinishLoading,
349
+ dbEntries: data,
350
+ });
351
+ } catch (err) {
352
+ return showFatalError(err);
353
+ }
354
+ })();
355
+ },
356
+ [],
357
+ );
358
+
359
+ /*------------------------------------------------------------------------*/
360
+ /* ------------------------------- Render ------------------------------- */
361
+ /*------------------------------------------------------------------------*/
362
+
363
+ /*----------------------------------------*/
364
+ /* ---------------- Views --------------- */
365
+ /*----------------------------------------*/
366
+
367
+ let body: React.ReactNode;
368
+
369
+ /* ------------- Loading ------------ */
370
+
371
+ if (loading) {
372
+ body = (
373
+ <LoadingSpinner />
374
+ );
375
+ }
376
+
377
+ /* ------------- List of entries ------------ */
378
+
379
+ if (!loading && !adding && !dbEntryToEdit) {
380
+ // Create body
381
+ body = (
382
+ <div>
383
+ <TabBox
384
+ title={itemListTitle}
385
+ >
386
+ {/* List of DB entries */}
387
+ {dbEntries.map((entry) => {
388
+ return (
389
+ <div
390
+ key={entry[idPropName]}
391
+ className="alert alert-secondary p-2 mb-2 d-flex align-items-center justify-content-center mb-1"
392
+ >
393
+ {/* Title */}
394
+ <div className="flex-grow-1">
395
+ <h4 className="m-0">
396
+ <span className="fw-bold">
397
+ {entry[titlePropName]}
398
+ </span>
399
+ <span className="small">
400
+ {' '}
401
+ (
402
+ {entry[descriptionPropName]}
403
+ )
404
+ </span>
405
+ </h4>
406
+ </div>
407
+
408
+ {/* Buttons */}
409
+ <div className="d-flex align-items-center">
410
+ {/* Remove Button */}
411
+ <button
412
+ type="button"
413
+ id={`DBEntryManagerPanel-remove-entry-with-id-${entry[idPropName]}`}
414
+ className="btn btn-secondary me-1"
415
+ aria-label={`remove database entry: ${entry[titlePropName]}`}
416
+ onClick={() => {
417
+ deleteEntry(entry);
418
+ }}
419
+ >
420
+ <FontAwesomeIcon
421
+ icon={faTrash}
422
+ />
423
+ <span className="d-none d-md-inline ms-1">
424
+ Remove
425
+ </span>
426
+ </button>
427
+
428
+ {/* Edit Button */}
429
+ {!disableEdit && (
430
+ <button
431
+ type="button"
432
+ id={`DBEntryManagerPanel-edit-with-id-${entry[idPropName]}`}
433
+ className="btn btn-primary"
434
+ aria-label={`edit db entry: ${entry[titlePropName]}`}
435
+ onClick={() => {
436
+ dispatch({
437
+ type: ActionType.ShowEditor,
438
+ dbEntry: entry,
439
+ });
440
+ }}
441
+ >
442
+ <FontAwesomeIcon
443
+ icon={faCog}
444
+ />
445
+ <span className="d-none d-md-inline ms-1">
446
+ Edit
447
+ </span>
448
+ </button>
449
+ )}
450
+ </div>
451
+ </div>
452
+ );
453
+ })}
454
+
455
+ {/* Add DB entry Button */}
456
+ <div className="d-grid">
457
+ <button
458
+ type="button"
459
+ id="DBEntryManagerPanel-add-entry"
460
+ className="btn btn-lg btn-primary"
461
+ aria-label={`add a new ${itemName} entry to the list of entries`}
462
+ onClick={() => {
463
+ dispatch({
464
+ type: ActionType.ShowAdder,
465
+ });
466
+ }}
467
+ >
468
+ <FontAwesomeIcon
469
+ icon={faPlus}
470
+ className="me-2"
471
+ />
472
+ Add
473
+ {' '}
474
+ {itemName}
475
+ </button>
476
+ </div>
477
+ </TabBox>
478
+ </div>
479
+ );
480
+ }
481
+
482
+ /* --------- Create or edit entry -------- */
483
+
484
+ if (!loading && (adding || dbEntryToEdit)) {
485
+ body = (
486
+ <AddOrEditDBEntry
487
+ saveEndpointPath={endpoint}
488
+ validateEntry={validateEntry}
489
+ modifyEntry={modifyEntry}
490
+ entryFields={entryFields}
491
+ dbEntryToEdit={dbEntryToEdit}
492
+ idPropName={idPropName}
493
+ entries={dbEntries}
494
+ itemName={itemName}
495
+ onFinished={(entry?: DBEntry) => {
496
+ dispatch({
497
+ type: ActionType.FinishAdd,
498
+ dbEntry: entry,
499
+ idPropName,
500
+ });
501
+ }}
502
+ />
503
+ );
504
+ }
505
+
506
+ /*----------------------------------------*/
507
+ /* --------------- Main UI -------------- */
508
+ /*----------------------------------------*/
509
+
510
+ return (
511
+ <div>
512
+ {body}
513
+ </div>
514
+ );
515
+ };
516
+
517
+ /*------------------------------------------------------------------------*/
518
+ /* ------------------------------- Wrap Up ------------------------------ */
519
+ /*------------------------------------------------------------------------*/
520
+
521
+ // Export component
522
+ export default DBEntryManagerPanel;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generic type for an object
3
+ * @author Yuen Ler Chow
4
+ */
5
+ type DBEntry = { [k: string]: any };
6
+
7
+ export default DBEntry;
@@ -0,0 +1,94 @@
1
+ import DBEntryFieldType from './DBEntryFieldType';
2
+
3
+ /**
4
+ * A database entry input field
5
+ * @author Yuen Ler Chow
6
+ */
7
+ type DBEntryField = (
8
+ {
9
+ // The label of the field
10
+ label: string,
11
+ // The key/prop this corresponds to in the DBEntry
12
+ objectKey: string,
13
+ // The placeholder text
14
+ placeholder: string,
15
+ // If true, only allow the user to edit when creating (not when editing)
16
+ lockAfterCreation?: boolean,
17
+ // If true, the field is required
18
+ required?: boolean,
19
+ } & (
20
+ // A string input field
21
+ | {
22
+ // The type of the field
23
+ type: DBEntryFieldType.String,
24
+ // The required minimum number of characters
25
+ minNumChars?: number,
26
+ // The required maximum number of characters
27
+ maxNumChars?: number,
28
+ // The default value for the field
29
+ defaultValue?: string,
30
+ // If defined, the choices the user can choose from
31
+ choices?: {
32
+ // The title of the choice (human-readable)
33
+ title: string,
34
+ // The value of the choice (what is stored in the DB)
35
+ value: string,
36
+ }[],
37
+ }
38
+ // A number input field
39
+ | {
40
+ // The type of the field
41
+ type: DBEntryFieldType.Number,
42
+ // The required minimum number
43
+ minNumber?: number,
44
+ // The required maximum number
45
+ maxNumber?: number,
46
+ // The default value for the field
47
+ defaultValue?: number,
48
+ }
49
+ // Checkbox input field
50
+ | {
51
+ // The type of the field
52
+ type: DBEntryFieldType.StringArray,
53
+ // The required minimum number of elements
54
+ minNumElements?: number,
55
+ // The required maximum number of elements
56
+ maxNumElements?: number,
57
+ // The default value for the field
58
+ defaultValue?: string[],
59
+ // If defined, the choices the user can choose from
60
+ choices?: {
61
+ // The title of the choice (human-readable)
62
+ title: string,
63
+ // The value of the choice (what is stored in the DB)
64
+ value: string,
65
+ }[],
66
+ }
67
+ // A number list input field
68
+ | {
69
+ // The type of the field
70
+ type: DBEntryFieldType.NumberArray,
71
+ // The required minimum number of elements
72
+ minNumElements?: number,
73
+ // The required maximum number of elements
74
+ maxNumElements?: number,
75
+ // The required minimum number
76
+ minNumber?: number,
77
+ // The required maximum number
78
+ maxNumber?: number,
79
+ // The default value for the field
80
+ defaultValue?: number[],
81
+ }
82
+ // Object input field
83
+ | {
84
+ // The type of the field
85
+ type: DBEntryFieldType.Object,
86
+ // The required minimum number of elements
87
+ defaultValue?: { [k: string]: any },
88
+ // The required minimum number of elements
89
+ subfields: DBEntryField[],
90
+ }
91
+ )
92
+ );
93
+
94
+ export default DBEntryField;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Options for field types
3
+ * @author Yuen Ler Chow
4
+ */
5
+ enum DBEntryFieldType {
6
+ // A string input field
7
+ String = 'String',
8
+ // A number input field
9
+ Number = 'Number',
10
+ // input field with subfields that are also DBEntryFields
11
+ Object = 'Object',
12
+ // list of strings input field
13
+ StringArray = 'StringArray',
14
+ // list of numbers input field
15
+ NumberArray = 'NumberArray',
16
+ }
17
+
18
+ export default DBEntryFieldType;