dce-reactkit 3.4.0-beta.5 → 3.4.0-beta.7

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.
@@ -26,6 +26,7 @@ declare type Props = {
26
26
  entryFields: DBEntryField[];
27
27
  dbEntryToEdit?: DBEntry;
28
28
  idPropName: string;
29
+ itemName: string;
29
30
  saveEndpointPath: string;
30
31
  entries: DBEntry[];
31
32
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.4.0-beta.5",
3
+ "version": "3.4.0-beta.7",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -434,4 +434,4 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
434
434
  /*------------------------------------------------------------------------*/
435
435
 
436
436
  // Export component
437
- export default AppWrapper;
437
+ export default AppWrapper;
@@ -8,7 +8,6 @@ import { MultiValue } from 'react-select';
8
8
  // Import other types
9
9
  import DBEntryFieldType from '../types/DBEntryFieldType';
10
10
 
11
-
12
11
  /*------------------------------------------------------------------------*/
13
12
  /* -------------------------------- Types ------------------------------- */
14
13
  /*------------------------------------------------------------------------*/
@@ -87,7 +86,6 @@ const reducer = (state: State, action: Action): State => {
87
86
  /*------------------------------------------------------------------------*/
88
87
 
89
88
  const CreatableMultiselect: React.FC<Props> = (props) => {
90
-
91
89
  // Destructure all props
92
90
  const {
93
91
  type,
@@ -109,7 +107,6 @@ const CreatableMultiselect: React.FC<Props> = (props) => {
109
107
  // Destructure common state
110
108
  const { inputValue } = state;
111
109
 
112
-
113
110
  /*------------------------------------------------------------------------*/
114
111
  /* ------------------------- Component Functions ------------------------ */
115
112
  /*------------------------------------------------------------------------*/
@@ -178,7 +175,7 @@ const CreatableMultiselect: React.FC<Props> = (props) => {
178
175
  .filter((val) => {
179
176
  return !values.some((value) => {
180
177
  return value === val;
181
- })
178
+ });
182
179
  })
183
180
  );
184
181
  onChange([...values, ...newValues] as string[]);
@@ -196,8 +193,6 @@ const CreatableMultiselect: React.FC<Props> = (props) => {
196
193
  * @param event keyboard event
197
194
  */
198
195
  const handleKeyDown: KeyboardEventHandler = (event) => {
199
- event.preventDefault();
200
-
201
196
  // Skip if no input value
202
197
  if (!inputValue) {
203
198
  return;
@@ -205,6 +200,7 @@ const CreatableMultiselect: React.FC<Props> = (props) => {
205
200
 
206
201
  // Add values if enter or tab is pressed
207
202
  if (['Enter', 'Tab'].includes(event.key)) {
203
+ event.preventDefault();
208
204
  addValues(inputValue);
209
205
  }
210
206
  };
@@ -28,6 +28,7 @@ import DBEntryFieldType from '../types/DBEntryFieldType';
28
28
  // Import other components
29
29
  import CreatableMultiselect from './CreatableMultiselect';
30
30
 
31
+
31
32
  /*------------------------------------------------------------------------*/
32
33
  /* -------------------------------- Types ------------------------------- */
33
34
  /*------------------------------------------------------------------------*/
@@ -42,12 +43,12 @@ type Props = {
42
43
  onFinished: (dbEntry?: DBEntry) => void,
43
44
  /**
44
45
  * Function to validate the DBEntry before saving
45
- * @param dbEntry
46
+ * @param dbEntry
46
47
  */
47
48
  validateEntry?: (dbEntry: DBEntry) => Promise<void>,
48
49
  /**
49
50
  * Function to modify the DBEntry before saving
50
- * @param dbEntry
51
+ * @param dbEntry
51
52
  * @returns the modified DBEntry
52
53
  */
53
54
  modifyEntry?: (dbEntry: DBEntry) => DBEntry,
@@ -57,6 +58,8 @@ type Props = {
57
58
  dbEntryToEdit?: DBEntry,
58
59
  // The unique object key of the DBEntry
59
60
  idPropName: string,
61
+ // item name
62
+ itemName: string,
60
63
  // Server endpoint path to save the DBEntry
61
64
  saveEndpointPath: string,
62
65
  // All entries in the database
@@ -159,6 +162,7 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
159
162
  idPropName,
160
163
  saveEndpointPath,
161
164
  entries,
165
+ itemName,
162
166
  } = props;
163
167
 
164
168
  /* -------------- State ------------- */
@@ -199,6 +203,9 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
199
203
 
200
204
  const modifiedEntry = modifyEntry ? modifyEntry(entry) : entry;
201
205
 
206
+ // add id key to entry so that when we delete the item, the key will always be "id"
207
+ modifiedEntry.id = modifiedEntry[idPropName];
208
+
202
209
  // Send to server
203
210
  try {
204
211
  await visitServerEndpoint({
@@ -224,53 +231,35 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
224
231
  onFinished(undefined);
225
232
  };
226
233
 
227
- /*------------------------------------------------------------------------*/
228
- /* ------------------------------- Render ------------------------------- */
229
- /*------------------------------------------------------------------------*/
230
-
231
- /*----------------------------------------*/
232
- /* ---------------- Views --------------- */
233
- /*----------------------------------------*/
234
-
235
- // Body
236
- let body: React.ReactNode;
237
-
238
- /* ------------- Loading ------------ */
239
-
240
- if (saving) {
241
- body = (
242
- <LoadingSpinner />
243
- );
244
- }
245
-
246
- /* -------------- Form -------------- */
247
-
248
- // If not saving, validate what the user has entered
249
- if (!saving) {
250
- // Validation error if there is one
251
- let validationError: string | undefined = undefined;
234
+ /**
235
+ * Create validation error message for the DBEntry
236
+ * @author Yuen Ler Chow
237
+ * @param fields the fields to validate
238
+ * @returns the validation error message, or an empty string if no error
239
+ */
252
240
 
253
- // Validate each field
254
- for (let i = 0; i < entryFields.length; i += 1) {
255
- const field = entryFields[i];
241
+ const validate = (fields: DBEntryField[]) => {
242
+ let validationError = '';
243
+ for (let i = 0; i < fields.length; i += 1) {
244
+ const field = fields[i];
256
245
  const value = entry[field.objectKey];
257
246
 
258
- // Check if required field is empty
259
- if (field.required && !value) {
247
+ // Check if required field is empty. Field is automatically required if it is the idPropName
248
+ if ((field.required || (field.objectKey === idPropName)) && !value) {
260
249
  validationError = `Please fill in the ${field.label} field`;
261
- break;
250
+ return validationError;
262
251
  }
263
252
 
264
253
  // Check if unique field is unique
265
254
  if (field.objectKey === idPropName) {
266
255
  if (entries.find((e) => { return e[idPropName] === value; })) {
267
256
  validationError = `An item with the ${field.label} ${value} already exists. ${field.label} must be unique.`;
268
- break;
257
+ return validationError;
269
258
  }
270
259
  }
271
260
 
272
261
  // If they have entered a value for the field, check if it is valid
273
- if (value) {
262
+ if (value || field.type === DBEntryFieldType.Object) {
274
263
  // String validation
275
264
  if (field.type === DBEntryFieldType.String) {
276
265
  if (
@@ -334,24 +323,54 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
334
323
  && value[j] < field.minNumber
335
324
  ) {
336
325
  validationError = `${field.label} values must be at least ${field.minNumber}`;
337
- break;
338
- } else if (
326
+ return validationError;
327
+ } if (
339
328
  // Maximum value requirement is defined
340
329
  field.maxNumber
341
330
  // Value is too large
342
331
  && value[j] > field.maxNumber
343
332
  ) {
344
333
  validationError = `${field.label} values must be at most ${field.maxNumber}`;
345
- break;
334
+ return validationError;
346
335
  }
347
336
  }
348
337
  }
338
+ } else if (field.type === DBEntryFieldType.Object) {
339
+ validationError = validate(field.subfields);
349
340
  }
350
341
  }
351
342
  if (validationError) {
352
- break;
343
+ return validationError;
353
344
  }
354
345
  }
346
+ return validationError;
347
+ };
348
+
349
+ /*------------------------------------------------------------------------*/
350
+ /* ------------------------------- Render ------------------------------- */
351
+ /*------------------------------------------------------------------------*/
352
+
353
+ /*----------------------------------------*/
354
+ /* ---------------- Views --------------- */
355
+ /*----------------------------------------*/
356
+
357
+ // Body
358
+ let body: React.ReactNode;
359
+
360
+ /* ------------- Loading ------------ */
361
+
362
+ if (saving) {
363
+ body = (
364
+ <LoadingSpinner />
365
+ );
366
+ }
367
+
368
+ /* -------------- Form -------------- */
369
+
370
+ // If not saving, validate what the user has entered
371
+ if (!saving) {
372
+ // Validation error if there is one
373
+ const validationError = validate(entryFields);
355
374
 
356
375
  /**
357
376
  * Render a single entry field
@@ -367,7 +386,8 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
367
386
  key={field.objectKey}
368
387
  className="mb-2"
369
388
  >
370
- <div className="input-group"
389
+ <div
390
+ className="input-group"
371
391
  style={{
372
392
  pointerEvents: (disabled ? 'none' : 'auto'),
373
393
  }}
@@ -475,7 +495,8 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
475
495
  key={field.objectKey}
476
496
  className="mb-2"
477
497
  >
478
- <div className="input-group"
498
+ <div
499
+ className="input-group"
479
500
  style={{
480
501
  pointerEvents: (disabled ? 'none' : 'auto'),
481
502
  }}
@@ -589,7 +610,7 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
589
610
  );
590
611
  }
591
612
 
592
- if (field.type == DBEntryFieldType.Object) {
613
+ if (field.type === DBEntryFieldType.Object) {
593
614
  return (
594
615
  <div
595
616
  key={field.objectKey}
@@ -603,11 +624,13 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
603
624
  {field.label}
604
625
  </span>
605
626
  {/* Add each subfield */}
606
- {
607
- field.subfields.map((subfield: DBEntryField) => {
608
- return renderEntryField(subfield, disabled);
609
- })
610
- }
627
+ <div className="flex-grow-1 p-2 form-control">
628
+ {
629
+ field.subfields.map((subfield: DBEntryField) => {
630
+ return <div>{renderEntryField(subfield, disabled)}</div>;
631
+ })
632
+ }
633
+ </div>
611
634
  </div>
612
635
  </div>
613
636
  );
@@ -615,11 +638,12 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
615
638
 
616
639
  // This should never happen
617
640
  return null;
618
- }
641
+ };
619
642
 
620
643
  // UI
621
644
  body = (
622
645
  <div>
646
+ <h1>{dbEntryToEdit ? `Edit ${itemName}` : `Create ${itemName}`}</h1>
623
647
  {/* Entry fields */}
624
648
  {
625
649
  entryFields.map((field: DBEntryField) => {
@@ -637,7 +661,7 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
637
661
  className="btn btn-primary btn-lg me-1"
638
662
  aria-label="Save changes"
639
663
  onClick={async () => {
640
- if (validationError) {
664
+ if (validationError && validationError.length > 0) {
641
665
  return alert(
642
666
  'Please fix the following error',
643
667
  validationError,
@@ -645,7 +669,7 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
645
669
  }
646
670
  if (validateEntry) {
647
671
  try {
648
- validateEntry(entry);
672
+ await validateEntry(entry);
649
673
  } catch (error) {
650
674
  return alert(
651
675
  'Please fix the following error',
@@ -672,7 +696,7 @@ const AddOrEditDBEntry: React.FC<Props> = (props) => {
672
696
  Cancel
673
697
  </button>
674
698
  </div>
675
- </div >
699
+ </div>
676
700
  );
677
701
  }
678
702
 
@@ -224,7 +224,6 @@ const reducer = (state: State, action: Action): State => {
224
224
  /*------------------------------------------------------------------------*/
225
225
 
226
226
  const DBEntryManagerPanel: React.FC<Props> = (props) => {
227
-
228
227
  // Destructure all props
229
228
  const {
230
229
  entryFields,
@@ -238,7 +237,7 @@ const DBEntryManagerPanel: React.FC<Props> = (props) => {
238
237
  disableEdit,
239
238
  collectionName,
240
239
  adminsOnly,
241
- filterQuery
240
+ filterQuery,
242
241
  } = props;
243
242
 
244
243
  /* -------------- State ------------- */
@@ -481,6 +480,7 @@ const DBEntryManagerPanel: React.FC<Props> = (props) => {
481
480
  dbEntryToEdit={dbEntryToEdit}
482
481
  idPropName={idPropName}
483
482
  entries={dbEntries}
483
+ itemName={itemName}
484
484
  onFinished={(entry?: DBEntry) => {
485
485
  dispatch({
486
486
  type: ActionType.FinishAdd,