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