dce-reactkit 3.3.6 → 3.4.0-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.
- package/dist/cjs/index.js +37520 -379
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.d.ts +10 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/AddOrEditDBEntry/index.d.ts +33 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/helpers/generateEndpointPath.d.ts +8 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/index.d.ts +25 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/types/DBEntry.d.ts +8 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/types/DBEntryField.d.ts +49 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/types/DBEntryFieldType.d.ts +12 -0
- package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +40 -0
- package/dist/cjs/types/index.d.ts +6 -1
- package/dist/esm/index.js +37694 -574
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.d.ts +10 -0
- package/dist/esm/types/components/DBEntryManagerPanel/AddOrEditDBEntry/index.d.ts +33 -0
- package/dist/esm/types/components/DBEntryManagerPanel/helpers/generateEndpointPath.d.ts +8 -0
- package/dist/esm/types/components/DBEntryManagerPanel/index.d.ts +25 -0
- package/dist/esm/types/components/DBEntryManagerPanel/types/DBEntry.d.ts +8 -0
- package/dist/esm/types/components/DBEntryManagerPanel/types/DBEntryField.d.ts +49 -0
- package/dist/esm/types/components/DBEntryManagerPanel/types/DBEntryFieldType.d.ts +12 -0
- package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +40 -0
- package/dist/esm/types/index.d.ts +6 -1
- package/dist/index.d.ts +166 -35
- package/package.json +8 -3
- package/src/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.tsx +290 -0
- package/src/components/DBEntryManagerPanel/AddOrEditDBEntry/index.tsx +699 -0
- package/src/components/DBEntryManagerPanel/helpers/generateEndpointPath.ts +15 -0
- package/src/components/DBEntryManagerPanel/index.tsx +511 -0
- package/src/components/DBEntryManagerPanel/types/DBEntry.ts +7 -0
- package/src/components/DBEntryManagerPanel/types/DBEntryField.ts +94 -0
- package/src/components/DBEntryManagerPanel/types/DBEntryFieldType.ts +18 -0
- package/src/helpers/addDBEditorEndpoints.ts +121 -0
- package/src/index.ts +11 -1
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel for adding a DBEntry to the database
|
|
3
|
+
* @author Yuen Ler Chow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React, { useReducer } from 'react';
|
|
8
|
+
|
|
9
|
+
// Import FontAwesome
|
|
10
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
11
|
+
import { faSave } from '@fortawesome/free-solid-svg-icons';
|
|
12
|
+
|
|
13
|
+
// Import dce-reactkit
|
|
14
|
+
import {
|
|
15
|
+
CheckboxButton,
|
|
16
|
+
LoadingSpinner,
|
|
17
|
+
RadioButton,
|
|
18
|
+
ButtonInputGroup,
|
|
19
|
+
alert,
|
|
20
|
+
showFatalError,
|
|
21
|
+
visitServerEndpoint,
|
|
22
|
+
} from 'dce-reactkit';
|
|
23
|
+
|
|
24
|
+
// Import other types
|
|
25
|
+
import DBEntry from '../types/DBEntry';
|
|
26
|
+
import DBEntryField from '../types/DBEntryField';
|
|
27
|
+
import DBEntryFieldType from '../types/DBEntryFieldType';
|
|
28
|
+
|
|
29
|
+
// Import other components
|
|
30
|
+
import CreatableMultiselect from './CreatableMultiselect';
|
|
31
|
+
|
|
32
|
+
/*------------------------------------------------------------------------*/
|
|
33
|
+
/* -------------------------------- Types ------------------------------- */
|
|
34
|
+
/*------------------------------------------------------------------------*/
|
|
35
|
+
|
|
36
|
+
// Props definition
|
|
37
|
+
type Props = {
|
|
38
|
+
/**
|
|
39
|
+
* Handler for when the user finishes adding the DBEntry
|
|
40
|
+
* (if no DBEntry is returned, process was cancelled)
|
|
41
|
+
* @param DBEntry the DBEntry that was just created
|
|
42
|
+
*/
|
|
43
|
+
onFinished: (dbEntry?: DBEntry) => void,
|
|
44
|
+
/**
|
|
45
|
+
* Function to validate the DBEntry before saving
|
|
46
|
+
* @param dbEntry
|
|
47
|
+
*/
|
|
48
|
+
validateEntry?: (dbEntry: DBEntry) => Promise<void>,
|
|
49
|
+
/**
|
|
50
|
+
* Function to modify the DBEntry before saving
|
|
51
|
+
* @param dbEntry
|
|
52
|
+
* @returns the modified DBEntry
|
|
53
|
+
*/
|
|
54
|
+
modifyEntry?: (dbEntry: DBEntry) => DBEntry,
|
|
55
|
+
// The fields needed to create a new DBEntry
|
|
56
|
+
entryFields: DBEntryField[],
|
|
57
|
+
// The DBEntry to edit (if any)
|
|
58
|
+
dbEntryToEdit?: DBEntry,
|
|
59
|
+
// The unique object key of the DBEntry
|
|
60
|
+
idPropName: string,
|
|
61
|
+
// Server endpoint path to save the DBEntry
|
|
62
|
+
saveEndpointPath: string,
|
|
63
|
+
// All entries in the database
|
|
64
|
+
entries: DBEntry[],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/*------------------------------------------------------------------------*/
|
|
68
|
+
/* -------------------------------- Style ------------------------------- */
|
|
69
|
+
/*------------------------------------------------------------------------*/
|
|
70
|
+
|
|
71
|
+
const style = `
|
|
72
|
+
.AddOrEditDBEntry-input-label {
|
|
73
|
+
min-width: 7rem;
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
/*------------------------------------------------------------------------*/
|
|
78
|
+
/* -------------------------------- State ------------------------------- */
|
|
79
|
+
/*------------------------------------------------------------------------*/
|
|
80
|
+
|
|
81
|
+
/* -------- State Definition -------- */
|
|
82
|
+
|
|
83
|
+
type State = {
|
|
84
|
+
// The DBEntry's current state
|
|
85
|
+
entry: DBEntry,
|
|
86
|
+
// True if currently saving
|
|
87
|
+
saving: boolean,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/* ------------- Actions ------------ */
|
|
91
|
+
|
|
92
|
+
// Types of actions
|
|
93
|
+
enum ActionType {
|
|
94
|
+
// Update the DBEntry
|
|
95
|
+
UpdateDBEntry = 'UpdateDBEntry',
|
|
96
|
+
// Start the save spinner
|
|
97
|
+
StartSave = 'StartSave',
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Action definitions
|
|
101
|
+
type Action = (
|
|
102
|
+
| {
|
|
103
|
+
// Action type
|
|
104
|
+
type: ActionType.UpdateDBEntry,
|
|
105
|
+
// New state of the DBEntry
|
|
106
|
+
dbEntry: DBEntry,
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
// Action type
|
|
110
|
+
type: (
|
|
111
|
+
| ActionType.StartSave
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reducer that executes actions
|
|
118
|
+
* @author Yuen Ler Chow
|
|
119
|
+
* @param state current state
|
|
120
|
+
* @param action action to execute
|
|
121
|
+
*/
|
|
122
|
+
const reducer = (state: State, action: Action): State => {
|
|
123
|
+
switch (action.type) {
|
|
124
|
+
case ActionType.UpdateDBEntry: {
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
entry: action.dbEntry,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
case ActionType.StartSave: {
|
|
131
|
+
return {
|
|
132
|
+
...state,
|
|
133
|
+
saving: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
default: {
|
|
137
|
+
return state;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/*------------------------------------------------------------------------*/
|
|
143
|
+
/* ------------------------------ Component ----------------------------- */
|
|
144
|
+
/*------------------------------------------------------------------------*/
|
|
145
|
+
|
|
146
|
+
const AddOrEditDBEntry: React.FC<Props> = (props) => {
|
|
147
|
+
/*------------------------------------------------------------------------*/
|
|
148
|
+
/* -------------------------------- Setup ------------------------------- */
|
|
149
|
+
/*------------------------------------------------------------------------*/
|
|
150
|
+
|
|
151
|
+
/* -------------- Props ------------- */
|
|
152
|
+
|
|
153
|
+
// Destructure all props
|
|
154
|
+
const {
|
|
155
|
+
onFinished,
|
|
156
|
+
entryFields,
|
|
157
|
+
dbEntryToEdit,
|
|
158
|
+
validateEntry,
|
|
159
|
+
modifyEntry,
|
|
160
|
+
idPropName,
|
|
161
|
+
saveEndpointPath,
|
|
162
|
+
entries,
|
|
163
|
+
} = props;
|
|
164
|
+
|
|
165
|
+
/* -------------- State ------------- */
|
|
166
|
+
|
|
167
|
+
// Initial state
|
|
168
|
+
const initialState: State = {
|
|
169
|
+
entry: dbEntryToEdit || {},
|
|
170
|
+
saving: false,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Initialize state
|
|
174
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
175
|
+
|
|
176
|
+
// Destructure common state
|
|
177
|
+
const {
|
|
178
|
+
entry,
|
|
179
|
+
saving,
|
|
180
|
+
} = state;
|
|
181
|
+
|
|
182
|
+
/*------------------------------------------------------------------------*/
|
|
183
|
+
/* ------------------------- Component Functions ------------------------ */
|
|
184
|
+
/*------------------------------------------------------------------------*/
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Save changes to the DBEntry and then finish
|
|
188
|
+
* @author Yuen Ler Chow
|
|
189
|
+
*/
|
|
190
|
+
const save = async () => {
|
|
191
|
+
// Start the save loading indicator
|
|
192
|
+
dispatch({ type: ActionType.StartSave });
|
|
193
|
+
|
|
194
|
+
// add all default values to the entry
|
|
195
|
+
entryFields.forEach((field) => {
|
|
196
|
+
if (field.defaultValue && !entry[field.objectKey]) {
|
|
197
|
+
entry[field.objectKey] = field.defaultValue;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const modifiedEntry = modifyEntry ? modifyEntry(entry) : entry;
|
|
202
|
+
|
|
203
|
+
// Send to server
|
|
204
|
+
try {
|
|
205
|
+
await visitServerEndpoint({
|
|
206
|
+
path: saveEndpointPath,
|
|
207
|
+
method: 'POST',
|
|
208
|
+
params: {
|
|
209
|
+
item: JSON.stringify(modifiedEntry),
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Finish
|
|
214
|
+
onFinished(entry);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
return showFatalError(err);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Cancel and return without saving
|
|
222
|
+
* @author Gabe Abrams
|
|
223
|
+
*/
|
|
224
|
+
const cancel = () => {
|
|
225
|
+
onFinished(undefined);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/*------------------------------------------------------------------------*/
|
|
229
|
+
/* ------------------------------- Render ------------------------------- */
|
|
230
|
+
/*------------------------------------------------------------------------*/
|
|
231
|
+
|
|
232
|
+
/*----------------------------------------*/
|
|
233
|
+
/* ---------------- Views --------------- */
|
|
234
|
+
/*----------------------------------------*/
|
|
235
|
+
|
|
236
|
+
// Body
|
|
237
|
+
let body: React.ReactNode;
|
|
238
|
+
|
|
239
|
+
/* ------------- Loading ------------ */
|
|
240
|
+
|
|
241
|
+
if (saving) {
|
|
242
|
+
body = (
|
|
243
|
+
<LoadingSpinner />
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* -------------- Form -------------- */
|
|
248
|
+
|
|
249
|
+
// If not saving, validate what the user has entered
|
|
250
|
+
if (!saving) {
|
|
251
|
+
// Validation error if there is one
|
|
252
|
+
let validationError: string | undefined = undefined;
|
|
253
|
+
|
|
254
|
+
// Validate each field
|
|
255
|
+
for (let i = 0; i < entryFields.length; i += 1) {
|
|
256
|
+
const field = entryFields[i];
|
|
257
|
+
const value = entry[field.objectKey];
|
|
258
|
+
|
|
259
|
+
// Check if required field is empty
|
|
260
|
+
if (field.required && !value) {
|
|
261
|
+
validationError = `Please fill in the ${field.label} field`;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check if unique field is unique
|
|
266
|
+
if (field.objectKey === idPropName) {
|
|
267
|
+
if (entries.find((e) => { return e[idPropName] === value; })) {
|
|
268
|
+
validationError = `An item with the ${field.label} ${value} already exists. ${field.label} must be unique.`;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// If they have entered a value for the field, check if it is valid
|
|
274
|
+
if (value) {
|
|
275
|
+
// String validation
|
|
276
|
+
if (field.type === DBEntryFieldType.String) {
|
|
277
|
+
if (
|
|
278
|
+
// Minimum length requirement is defined
|
|
279
|
+
field.minNumChars
|
|
280
|
+
// Value is too short
|
|
281
|
+
&& value.length < field.minNumChars
|
|
282
|
+
) {
|
|
283
|
+
validationError = `${field.label} must be at least ${field.minNumChars} character${field.minNumChars === 1 ? '' : 's'} long`;
|
|
284
|
+
} else if (
|
|
285
|
+
// Maximum length requirement is defined
|
|
286
|
+
field.maxNumChars
|
|
287
|
+
// Value is too long
|
|
288
|
+
&& value.length > field.maxNumChars
|
|
289
|
+
) {
|
|
290
|
+
validationError = `${field.label} must be at most ${field.maxNumChars} character${field.maxNumChars === 1 ? '' : 's'} long`;
|
|
291
|
+
}
|
|
292
|
+
// Number validation
|
|
293
|
+
} else if (field.type === DBEntryFieldType.Number) {
|
|
294
|
+
if (
|
|
295
|
+
// Minimum value requirement is defined
|
|
296
|
+
field.minNumber
|
|
297
|
+
// Value is too small
|
|
298
|
+
&& value < field.minNumber
|
|
299
|
+
) {
|
|
300
|
+
validationError = `${field.label} must be at least ${field.minNumber}`;
|
|
301
|
+
} else if (
|
|
302
|
+
// Maximum value requirement is defined
|
|
303
|
+
field.maxNumber
|
|
304
|
+
// Value is too large
|
|
305
|
+
&& value > field.maxNumber
|
|
306
|
+
) {
|
|
307
|
+
validationError = `${field.label} must be at most ${field.maxNumber}`;
|
|
308
|
+
}
|
|
309
|
+
} else if (
|
|
310
|
+
field.type === DBEntryFieldType.StringArray
|
|
311
|
+
|| field.type === DBEntryFieldType.NumberArray
|
|
312
|
+
) {
|
|
313
|
+
// String and Number Array validation
|
|
314
|
+
if (
|
|
315
|
+
// Minimum number of elements requirement is defined
|
|
316
|
+
field.minNumElements
|
|
317
|
+
// Value has too few elements
|
|
318
|
+
&& value.length < field.minNumElements
|
|
319
|
+
) {
|
|
320
|
+
validationError = `${field.label} must have at least ${field.minNumElements} value${field.minNumElements === 1 ? '' : 's'}`;
|
|
321
|
+
} else if (
|
|
322
|
+
// Maximum number of elements requirement is defined
|
|
323
|
+
field.maxNumElements
|
|
324
|
+
// Value has too many elements
|
|
325
|
+
&& value.length > field.maxNumElements
|
|
326
|
+
) {
|
|
327
|
+
validationError = `${field.label} must have at most ${field.maxNumElements} value${field.maxNumElements === 1 ? '' : 's'}`;
|
|
328
|
+
} else if (field.type === DBEntryFieldType.NumberArray) {
|
|
329
|
+
// Number Array validation
|
|
330
|
+
for (let j = 0; j < value.length; j += 1) {
|
|
331
|
+
if (
|
|
332
|
+
// Minimum value requirement is defined
|
|
333
|
+
field.minNumber
|
|
334
|
+
// Value is too small
|
|
335
|
+
&& value[j] < field.minNumber
|
|
336
|
+
) {
|
|
337
|
+
validationError = `${field.label} values must be at least ${field.minNumber}`;
|
|
338
|
+
break;
|
|
339
|
+
} else if (
|
|
340
|
+
// Maximum value requirement is defined
|
|
341
|
+
field.maxNumber
|
|
342
|
+
// Value is too large
|
|
343
|
+
&& value[j] > field.maxNumber
|
|
344
|
+
) {
|
|
345
|
+
validationError = `${field.label} values must be at most ${field.maxNumber}`;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (validationError) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Render a single entry field
|
|
359
|
+
* @author Yuen Ler Chow
|
|
360
|
+
* @param field the entry field to render
|
|
361
|
+
* @param disabled true if the field should be disabled
|
|
362
|
+
*/
|
|
363
|
+
const renderEntryField = (field: DBEntryField, disabled: boolean) => {
|
|
364
|
+
if (field.type === DBEntryFieldType.String) {
|
|
365
|
+
if (field.choices) {
|
|
366
|
+
return (
|
|
367
|
+
<div
|
|
368
|
+
key={field.objectKey}
|
|
369
|
+
className="mb-2"
|
|
370
|
+
>
|
|
371
|
+
<div className="input-group"
|
|
372
|
+
style={{
|
|
373
|
+
pointerEvents: (disabled ? 'none' : 'auto'),
|
|
374
|
+
}}
|
|
375
|
+
>
|
|
376
|
+
<ButtonInputGroup
|
|
377
|
+
label={field.label}
|
|
378
|
+
>
|
|
379
|
+
{
|
|
380
|
+
field.choices.map((choice) => {
|
|
381
|
+
return (
|
|
382
|
+
<RadioButton
|
|
383
|
+
key={choice.value}
|
|
384
|
+
text={choice.title}
|
|
385
|
+
selected={entry[field.objectKey] === choice.value}
|
|
386
|
+
onSelected={() => {
|
|
387
|
+
entry[field.objectKey] = choice.value;
|
|
388
|
+
dispatch({
|
|
389
|
+
type: ActionType.UpdateDBEntry,
|
|
390
|
+
dbEntry: entry,
|
|
391
|
+
});
|
|
392
|
+
}}
|
|
393
|
+
ariaLabel={choice.title}
|
|
394
|
+
/>
|
|
395
|
+
);
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
</ButtonInputGroup>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<div
|
|
406
|
+
className="mb-2"
|
|
407
|
+
key={field.objectKey}
|
|
408
|
+
>
|
|
409
|
+
<div className="input-group">
|
|
410
|
+
<span
|
|
411
|
+
className="AddOrEditDBEntry-input-label input-group-text"
|
|
412
|
+
>
|
|
413
|
+
{field.label}
|
|
414
|
+
</span>
|
|
415
|
+
<input
|
|
416
|
+
disabled={disabled}
|
|
417
|
+
type="text"
|
|
418
|
+
className="form-control"
|
|
419
|
+
placeholder={field.placeholder}
|
|
420
|
+
aria-describedby="AddOrEditDBEntry-form-name-label"
|
|
421
|
+
value={entry[field.objectKey] || ''}
|
|
422
|
+
onChange={(e) => {
|
|
423
|
+
entry[field.objectKey] = (
|
|
424
|
+
e.target.value
|
|
425
|
+
);
|
|
426
|
+
dispatch({
|
|
427
|
+
type: ActionType.UpdateDBEntry,
|
|
428
|
+
dbEntry: entry,
|
|
429
|
+
});
|
|
430
|
+
}}
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (field.type === DBEntryFieldType.Number) {
|
|
438
|
+
return (
|
|
439
|
+
<div
|
|
440
|
+
key={field.objectKey}
|
|
441
|
+
className="mb-2"
|
|
442
|
+
>
|
|
443
|
+
<div className="input-group">
|
|
444
|
+
<span
|
|
445
|
+
className="AddOrEditDBEntry-input-label input-group-text"
|
|
446
|
+
>
|
|
447
|
+
{field.label}
|
|
448
|
+
</span>
|
|
449
|
+
<input
|
|
450
|
+
type="text"
|
|
451
|
+
className="form-control"
|
|
452
|
+
placeholder={field.placeholder}
|
|
453
|
+
aria-describedby="AddOrEditDBEntry-form-name-label"
|
|
454
|
+
value={entry[field.objectKey] || ''}
|
|
455
|
+
disabled={disabled}
|
|
456
|
+
onChange={(e) => {
|
|
457
|
+
entry[field.objectKey] = (
|
|
458
|
+
e.target.value
|
|
459
|
+
.replace(/[^0-9]/g, '')
|
|
460
|
+
);
|
|
461
|
+
dispatch({
|
|
462
|
+
type: ActionType.UpdateDBEntry,
|
|
463
|
+
dbEntry: entry,
|
|
464
|
+
});
|
|
465
|
+
}}
|
|
466
|
+
/>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (field.type === DBEntryFieldType.StringArray) {
|
|
473
|
+
if (field.choices) {
|
|
474
|
+
return (
|
|
475
|
+
<div
|
|
476
|
+
key={field.objectKey}
|
|
477
|
+
className="mb-2"
|
|
478
|
+
>
|
|
479
|
+
<div className="input-group"
|
|
480
|
+
style={{
|
|
481
|
+
pointerEvents: (disabled ? 'none' : 'auto'),
|
|
482
|
+
}}
|
|
483
|
+
>
|
|
484
|
+
<ButtonInputGroup
|
|
485
|
+
label={field.label}
|
|
486
|
+
>
|
|
487
|
+
{
|
|
488
|
+
field.choices.map((choice) => {
|
|
489
|
+
return (
|
|
490
|
+
<CheckboxButton
|
|
491
|
+
key={choice.value}
|
|
492
|
+
text={choice.title}
|
|
493
|
+
checked={entry[field.objectKey] && entry[field.objectKey].includes(choice.value)}
|
|
494
|
+
onChanged={(checked) => {
|
|
495
|
+
if (checked) {
|
|
496
|
+
// Initialize array if it doesn't exist
|
|
497
|
+
if (!entry[field.objectKey]) {
|
|
498
|
+
entry[field.objectKey] = [];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Add value to array
|
|
502
|
+
entry[field.objectKey].push(choice.value);
|
|
503
|
+
} else {
|
|
504
|
+
// Remove value from array
|
|
505
|
+
entry[field.objectKey] = (
|
|
506
|
+
entry[field.objectKey]
|
|
507
|
+
.filter((val: string) => {
|
|
508
|
+
return val !== choice.value;
|
|
509
|
+
})
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Save
|
|
514
|
+
dispatch({
|
|
515
|
+
type: ActionType.UpdateDBEntry,
|
|
516
|
+
dbEntry: entry,
|
|
517
|
+
});
|
|
518
|
+
}}
|
|
519
|
+
ariaLabel={choice.title}
|
|
520
|
+
/>
|
|
521
|
+
);
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
</ButtonInputGroup>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Flexible (no specific choices for this field)
|
|
531
|
+
return (
|
|
532
|
+
<div
|
|
533
|
+
key={field.objectKey}
|
|
534
|
+
className="mb-2"
|
|
535
|
+
>
|
|
536
|
+
<div className="input-group">
|
|
537
|
+
<span
|
|
538
|
+
className="AddOrEditDBEntry-input-label input-group-text"
|
|
539
|
+
>
|
|
540
|
+
{field.label}
|
|
541
|
+
</span>
|
|
542
|
+
<div className="flex-grow-1">
|
|
543
|
+
<CreatableMultiselect
|
|
544
|
+
disabled={disabled}
|
|
545
|
+
type={DBEntryFieldType.StringArray}
|
|
546
|
+
values={entry[field.objectKey] || []}
|
|
547
|
+
onChange={(values) => {
|
|
548
|
+
// Update entry and save
|
|
549
|
+
entry[field.objectKey] = values;
|
|
550
|
+
dispatch({
|
|
551
|
+
type: ActionType.UpdateDBEntry,
|
|
552
|
+
dbEntry: entry,
|
|
553
|
+
});
|
|
554
|
+
}}
|
|
555
|
+
/>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (field.type === DBEntryFieldType.NumberArray) {
|
|
563
|
+
return (
|
|
564
|
+
<div
|
|
565
|
+
key={field.objectKey}
|
|
566
|
+
className="mb-2"
|
|
567
|
+
>
|
|
568
|
+
<div className="input-group">
|
|
569
|
+
<span
|
|
570
|
+
className="AddOrEditDBEntry-input-label input-group-text"
|
|
571
|
+
>
|
|
572
|
+
{field.label}
|
|
573
|
+
</span>
|
|
574
|
+
<div className="flex-grow-1">
|
|
575
|
+
<CreatableMultiselect
|
|
576
|
+
disabled={disabled}
|
|
577
|
+
type={DBEntryFieldType.NumberArray}
|
|
578
|
+
values={entry[field.objectKey] || []}
|
|
579
|
+
onChange={(values) => {
|
|
580
|
+
entry[field.objectKey] = values;
|
|
581
|
+
dispatch({
|
|
582
|
+
type: ActionType.UpdateDBEntry,
|
|
583
|
+
dbEntry: entry,
|
|
584
|
+
});
|
|
585
|
+
}}
|
|
586
|
+
/>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (field.type == DBEntryFieldType.Object) {
|
|
594
|
+
return (
|
|
595
|
+
<div
|
|
596
|
+
key={field.objectKey}
|
|
597
|
+
className="mb-2"
|
|
598
|
+
>
|
|
599
|
+
<div className="input-group">
|
|
600
|
+
{/* Label */}
|
|
601
|
+
<span
|
|
602
|
+
className="AddOrEditDBEntry-input-label input-group-text"
|
|
603
|
+
>
|
|
604
|
+
{field.label}
|
|
605
|
+
</span>
|
|
606
|
+
{/* Add each subfield */}
|
|
607
|
+
{
|
|
608
|
+
field.subfields.map((subfield: DBEntryField) => {
|
|
609
|
+
return renderEntryField(subfield, disabled);
|
|
610
|
+
})
|
|
611
|
+
}
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// This should never happen
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// UI
|
|
622
|
+
body = (
|
|
623
|
+
<div>
|
|
624
|
+
{/* Entry fields */}
|
|
625
|
+
{
|
|
626
|
+
entryFields.map((field: DBEntryField) => {
|
|
627
|
+
const disabled = (idPropName === field.objectKey && dbEntryToEdit !== undefined)
|
|
628
|
+
|| (field.lockAfterCreation !== undefined && dbEntryToEdit !== undefined);
|
|
629
|
+
return renderEntryField(field, disabled);
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
{/* Buttons */}
|
|
634
|
+
<div className="text-center mt-2">
|
|
635
|
+
<button
|
|
636
|
+
type="button"
|
|
637
|
+
id="AddOrEditDBEntry-save-changes-button"
|
|
638
|
+
className="btn btn-primary btn-lg me-1"
|
|
639
|
+
aria-label="Save changes"
|
|
640
|
+
onClick={async () => {
|
|
641
|
+
if (validationError) {
|
|
642
|
+
return alert(
|
|
643
|
+
'Please fix the following error',
|
|
644
|
+
validationError,
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
if (validateEntry) {
|
|
648
|
+
try {
|
|
649
|
+
validateEntry(entry);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
return alert(
|
|
652
|
+
'Please fix the following error',
|
|
653
|
+
String(error),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
save();
|
|
658
|
+
}}
|
|
659
|
+
>
|
|
660
|
+
<FontAwesomeIcon
|
|
661
|
+
icon={faSave}
|
|
662
|
+
className="me-1"
|
|
663
|
+
/>
|
|
664
|
+
Save
|
|
665
|
+
</button>
|
|
666
|
+
<button
|
|
667
|
+
type="button"
|
|
668
|
+
id="AddOrEditDBEntry-cancel-button"
|
|
669
|
+
className="btn btn-secondary btn-lg me-1"
|
|
670
|
+
aria-label="save changes"
|
|
671
|
+
onClick={cancel}
|
|
672
|
+
>
|
|
673
|
+
Cancel
|
|
674
|
+
</button>
|
|
675
|
+
</div>
|
|
676
|
+
</div >
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/*----------------------------------------*/
|
|
681
|
+
/* --------------- Main UI -------------- */
|
|
682
|
+
/*----------------------------------------*/
|
|
683
|
+
|
|
684
|
+
return (
|
|
685
|
+
<div className="alert alert-light text-black">
|
|
686
|
+
<style>
|
|
687
|
+
{style}
|
|
688
|
+
</style>
|
|
689
|
+
{body}
|
|
690
|
+
</div>
|
|
691
|
+
);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
/*------------------------------------------------------------------------*/
|
|
695
|
+
/* ------------------------------- Wrap Up ------------------------------ */
|
|
696
|
+
/*------------------------------------------------------------------------*/
|
|
697
|
+
|
|
698
|
+
// Export component
|
|
699
|
+
export default AddOrEditDBEntry;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Generates the endpoint path for the given collection name
|
|
4
|
+
* @author Yuen Ler Chow
|
|
5
|
+
* @param collectionName the name of the collection
|
|
6
|
+
* @param [adminsOnly] true if the endpoint is for admins only
|
|
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
|
+
export default generateEndpointPath;
|