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.
- package/.eslintrc.js +2 -0
- package/.vscode/settings.json +1 -0
- package/dist/cjs/index.js +36372 -191
- 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 +34 -0
- package/dist/cjs/types/components/DBEntryManagerPanel/helpers/generateEndpointPath.d.ts +9 -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/helpers/canReviewLogs.d.ts +0 -5
- package/dist/cjs/types/index.d.ts +6 -1
- package/dist/esm/index.js +36587 -427
- 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 +34 -0
- package/dist/esm/types/components/DBEntryManagerPanel/helpers/generateEndpointPath.d.ts +9 -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/helpers/canReviewLogs.d.ts +0 -5
- package/dist/esm/types/index.d.ts +6 -1
- package/dist/index.d.ts +166 -35
- package/package.json +16 -12
- package/src/client/initClient.tsx +23 -31
- package/src/components/AppWrapper.tsx +1 -1
- package/src/components/DBEntryManagerPanel/AddOrEditDBEntry/CreatableMultiselect.tsx +303 -0
- package/src/components/DBEntryManagerPanel/AddOrEditDBEntry/index.tsx +737 -0
- package/src/components/DBEntryManagerPanel/helpers/generateEndpointPath.ts +16 -0
- package/src/components/DBEntryManagerPanel/index.tsx +522 -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/components/ItemPicker/index.test.tsx +10 -10
- package/src/helpers/addDBEditorEndpoints.ts +128 -0
- package/src/helpers/canReviewLogs.ts +0 -20
- package/src/helpers/genCSV.ts +2 -5
- package/src/helpers/{getOrdinal.ts → getOrdinal.tsx} +0 -1
- package/src/index.ts +11 -9
- package/tsconfig.json +2 -6
- package/dist/cjs/types/helpers/abbreviate.test.d.ts +0 -5
- package/dist/cjs/types/helpers/avg.test.d.ts +0 -5
- package/dist/cjs/types/helpers/canReviewLogs.test.d.ts +0 -5
- package/dist/cjs/types/helpers/ceilToNumDecimals.test.d.ts +0 -5
- package/dist/cjs/types/helpers/compareArraysByProp.test.d.ts +0 -5
- package/dist/cjs/types/helpers/extractProp.test.d.ts +0 -5
- package/dist/cjs/types/helpers/floorToNumDecimals.test.d.ts +0 -5
- package/dist/cjs/types/helpers/forceNumIntoBounds.test.d.ts +0 -5
- package/dist/cjs/types/helpers/genCSV.test.d.ts +0 -5
- package/dist/cjs/types/helpers/genCommaList.d.ts +0 -12
- package/dist/cjs/types/helpers/genCommaList.test.d.ts +0 -1
- package/dist/cjs/types/helpers/getHumanReadableDate.test.d.ts +0 -5
- package/dist/cjs/types/helpers/getLocalTimeInfo.test.d.ts +0 -6
- package/dist/cjs/types/helpers/getMonthName.test.d.ts +0 -5
- package/dist/cjs/types/helpers/getOrdinal.test.d.ts +0 -5
- package/dist/cjs/types/helpers/getPartOfDay.test.d.ts +0 -5
- package/dist/cjs/types/helpers/getTimeInfoInET.test.d.ts +0 -5
- package/dist/cjs/types/helpers/validators/shared/constants/ERROR_MESSAGES.d.ts +0 -10
- package/dist/cjs/types/helpers/validators/shared/helpers/validateRegex.d.ts +0 -19
- package/dist/cjs/types/helpers/validators/shared/types/ValidationResult.d.ts +0 -12
- package/dist/cjs/types/helpers/validators/validateEmail.d.ts +0 -11
- package/dist/cjs/types/helpers/validators/validateEmail.test.d.ts +0 -1
- package/dist/cjs/types/helpers/validators/validatePhoneNumber.d.ts +0 -11
- package/dist/cjs/types/helpers/validators/validatePhoneNumber.test.d.ts +0 -1
- package/dist/cjs/types/helpers/validators/validateString.d.ts +0 -22
- package/dist/cjs/types/helpers/validators/validateString.test.d.ts +0 -1
- package/dist/esm/types/helpers/abbreviate.test.d.ts +0 -5
- package/dist/esm/types/helpers/avg.test.d.ts +0 -5
- package/dist/esm/types/helpers/canReviewLogs.test.d.ts +0 -5
- package/dist/esm/types/helpers/ceilToNumDecimals.test.d.ts +0 -5
- package/dist/esm/types/helpers/compareArraysByProp.test.d.ts +0 -5
- package/dist/esm/types/helpers/extractProp.test.d.ts +0 -5
- package/dist/esm/types/helpers/floorToNumDecimals.test.d.ts +0 -5
- package/dist/esm/types/helpers/forceNumIntoBounds.test.d.ts +0 -5
- package/dist/esm/types/helpers/genCSV.test.d.ts +0 -5
- package/dist/esm/types/helpers/genCommaList.d.ts +0 -12
- package/dist/esm/types/helpers/genCommaList.test.d.ts +0 -1
- package/dist/esm/types/helpers/getHumanReadableDate.test.d.ts +0 -5
- package/dist/esm/types/helpers/getLocalTimeInfo.test.d.ts +0 -6
- package/dist/esm/types/helpers/getMonthName.test.d.ts +0 -5
- package/dist/esm/types/helpers/getOrdinal.test.d.ts +0 -5
- package/dist/esm/types/helpers/getPartOfDay.test.d.ts +0 -5
- package/dist/esm/types/helpers/getTimeInfoInET.test.d.ts +0 -5
- package/dist/esm/types/helpers/validators/shared/constants/ERROR_MESSAGES.d.ts +0 -10
- package/dist/esm/types/helpers/validators/shared/helpers/validateRegex.d.ts +0 -19
- package/dist/esm/types/helpers/validators/shared/types/ValidationResult.d.ts +0 -12
- package/dist/esm/types/helpers/validators/validateEmail.d.ts +0 -11
- package/dist/esm/types/helpers/validators/validateEmail.test.d.ts +0 -1
- package/dist/esm/types/helpers/validators/validatePhoneNumber.d.ts +0 -11
- package/dist/esm/types/helpers/validators/validatePhoneNumber.test.d.ts +0 -1
- package/dist/esm/types/helpers/validators/validateString.d.ts +0 -22
- package/dist/esm/types/helpers/validators/validateString.test.d.ts +0 -1
- package/src/helpers/abbreviate.test.ts +0 -26
- package/src/helpers/avg.test.ts +0 -24
- package/src/helpers/canReviewLogs.test.ts +0 -67
- package/src/helpers/ceilToNumDecimals.test.ts +0 -39
- package/src/helpers/compareArraysByProp.test.ts +0 -102
- package/src/helpers/extractProp.test.ts +0 -34
- package/src/helpers/floorToNumDecimals.test.ts +0 -39
- package/src/helpers/forceNumIntoBounds.test.ts +0 -34
- package/src/helpers/genCSV.test.ts +0 -61
- package/src/helpers/genCommaList.test.ts +0 -57
- package/src/helpers/genCommaList.ts +0 -21
- package/src/helpers/getHumanReadableDate.test.ts +0 -29
- package/src/helpers/getLocalTimeInfo.test.ts +0 -48
- package/src/helpers/getMonthName.test.ts +0 -79
- package/src/helpers/getOrdinal.test.ts +0 -84
- package/src/helpers/getPartOfDay.test.ts +0 -56
- package/src/helpers/getTimeInfoInET.test.ts +0 -72
- package/src/helpers/validators/shared/constants/ERROR_MESSAGES.ts +0 -18
- package/src/helpers/validators/shared/helpers/validateRegex.ts +0 -47
- package/src/helpers/validators/shared/types/ValidationResult.ts +0 -20
- package/src/helpers/validators/validateEmail.test.ts +0 -110
- package/src/helpers/validators/validateEmail.ts +0 -47
- package/src/helpers/validators/validatePhoneNumber.test.ts +0 -94
- package/src/helpers/validators/validatePhoneNumber.ts +0 -49
- package/src/helpers/validators/validateString.test.ts +0 -175
- package/src/helpers/validators/validateString.ts +0 -171
- /package/src/helpers/{abbreviate.ts → abbreviate.tsx} +0 -0
- /package/src/helpers/{avg.ts → avg.tsx} +0 -0
- /package/src/helpers/{ceilToNumDecimals.ts → ceilToNumDecimals.tsx} +0 -0
- /package/src/helpers/{floorToNumDecimals.ts → floorToNumDecimals.tsx} +0 -0
- /package/src/helpers/{forceNumIntoBounds.ts → forceNumIntoBounds.tsx} +0 -0
- /package/src/helpers/{getLocalTimeInfo.ts → getLocalTimeInfo.tsx} +0 -0
- /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,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;
|