@nestledjs/data-browser 1.0.15 → 1.0.16
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/README.md +43 -39
- package/index.js +4 -1
- package/lib/components/ExportButton.d.ts +7 -7
- package/lib/components/ExportButton.js +133 -36
- package/lib/components/RelationFieldWrapper.d.ts +1 -1
- package/lib/components/RelationFieldWrapper.js +1 -1
- package/lib/components/filters/DateRangeFilter.d.ts +1 -1
- package/lib/components/filters/EnumFilter.d.ts +1 -1
- package/lib/components/filters/NumberRangeFilter.d.ts +1 -1
- package/lib/components/filters/RelationComponents.d.ts +5 -5
- package/lib/components/filters/RelationComponents.js +40 -13
- package/lib/components/filters/RelationFilterField.d.ts +1 -1
- package/lib/components/filters/RelationFilterField.js +14 -6
- package/lib/components/shared/AdminBreadcrumbs.js +2 -17
- package/lib/components/shared/AdminErrorStates.js +6 -1
- package/lib/components/shared/AdminStatusDisplay.js +7 -11
- package/lib/context/AdminDataContext.d.ts +2 -2
- package/lib/hooks/useClickOutside.js +1 -1
- package/lib/hooks/useRelationData.js +1 -0
- package/lib/layouts/AdminDataLayout.js +84 -9
- package/lib/pages/AdminDataCreatePage.d.ts +1 -1
- package/lib/pages/AdminDataCreatePage.js +66 -46
- package/lib/pages/AdminDataEditPage.js +53 -19
- package/lib/pages/AdminDataListPage.js +87 -79
- package/lib/utils/graphql-utils.d.ts +7 -1
- package/lib/utils/graphql-utils.js +337 -320
- package/lib/utils/secure-storage.js +23 -14
- package/lib/utils/string-utils.js +31 -8
- package/package.json +3 -3
|
@@ -15,52 +15,62 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
15
15
|
const params = useParams();
|
|
16
16
|
const [searchParams] = useSearchParams();
|
|
17
17
|
const { sdk, databaseModels, basePath = "/admin/data" } = useAdminDataContext();
|
|
18
|
-
const getEnumValues = useCallback(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const getEnumValues = useCallback(
|
|
19
|
+
(enumType) => {
|
|
20
|
+
try {
|
|
21
|
+
const enumObject = sdk[enumType];
|
|
22
|
+
if (!enumObject || typeof enumObject !== "object") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const values = Object.values(enumObject).filter((value) => typeof value === "string");
|
|
26
|
+
if (values.length === 0) {
|
|
27
|
+
const keys = Object.keys(enumObject).filter((key) => Number.isNaN(Number(key)));
|
|
28
|
+
return keys.length > 0 ? keys : null;
|
|
29
|
+
}
|
|
30
|
+
return values.filter((v) => typeof v === "string");
|
|
31
|
+
} catch (error2) {
|
|
32
|
+
console.error("Unexpected error:", error2);
|
|
22
33
|
return null;
|
|
23
34
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
},
|
|
36
|
+
[sdk]
|
|
37
|
+
);
|
|
38
|
+
const updateRelatedEnumFilter = useCallback(
|
|
39
|
+
(filters2, relationName, enumFieldName, value) => {
|
|
40
|
+
const newFilters = { ...filters2 };
|
|
41
|
+
if (value === void 0 || value === null || value === "") {
|
|
42
|
+
const relationFilter = newFilters[relationName];
|
|
43
|
+
if (relationFilter && typeof relationFilter === "object") {
|
|
44
|
+
delete relationFilter[enumFieldName];
|
|
45
|
+
if (Object.keys(relationFilter).length === 0) {
|
|
46
|
+
delete newFilters[relationName];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
if (!newFilters[relationName]) {
|
|
51
|
+
newFilters[relationName] = {};
|
|
52
|
+
}
|
|
53
|
+
const relationFilter = newFilters[relationName];
|
|
54
|
+
if (relationFilter && typeof relationFilter === "object") {
|
|
55
|
+
relationFilter[enumFieldName] = value;
|
|
42
56
|
}
|
|
43
57
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
return newFilters;
|
|
59
|
+
},
|
|
60
|
+
[]
|
|
61
|
+
);
|
|
62
|
+
const updateSimpleFilter = useCallback(
|
|
63
|
+
(filters2, fieldName, value) => {
|
|
64
|
+
const newFilters = { ...filters2 };
|
|
65
|
+
if (value === void 0 || value === null || value === "") {
|
|
66
|
+
delete newFilters[fieldName];
|
|
67
|
+
} else {
|
|
68
|
+
newFilters[fieldName] = value;
|
|
51
69
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const newFilters = { ...filters2 };
|
|
57
|
-
if (value === void 0 || value === null || value === "") {
|
|
58
|
-
delete newFilters[fieldName];
|
|
59
|
-
} else {
|
|
60
|
-
newFilters[fieldName] = value;
|
|
61
|
-
}
|
|
62
|
-
return newFilters;
|
|
63
|
-
}, []);
|
|
70
|
+
return newFilters;
|
|
71
|
+
},
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
64
74
|
const { state, dispatch } = useAdminList();
|
|
65
75
|
const {
|
|
66
76
|
search,
|
|
@@ -75,10 +85,6 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
75
85
|
showFilters,
|
|
76
86
|
showSearchFieldSelector
|
|
77
87
|
} = state;
|
|
78
|
-
useCallback(
|
|
79
|
-
(skip2) => dispatch({ type: "SET_SKIP", payload: skip2 }),
|
|
80
|
-
[dispatch]
|
|
81
|
-
);
|
|
82
88
|
const setFilters = useCallback(
|
|
83
89
|
(filters2) => dispatch({ type: "SET_FILTERS", payload: filters2 }),
|
|
84
90
|
[dispatch]
|
|
@@ -160,7 +166,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
160
166
|
}, [sdk, model]);
|
|
161
167
|
const { listQuery: query } = documents;
|
|
162
168
|
const dataPath = useMemo(() => {
|
|
163
|
-
return model?.pluralModelPropertyName || pluralParam.
|
|
169
|
+
return model?.pluralModelPropertyName || pluralParam.replaceAll(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
164
170
|
}, [model, pluralParam]);
|
|
165
171
|
const paginationPath = useMemo(() => {
|
|
166
172
|
return "counters";
|
|
@@ -216,11 +222,13 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
216
222
|
setVisibleColumns(filteredCols);
|
|
217
223
|
const storedSort = AdminLocalStorage.getSortPreference(model.name);
|
|
218
224
|
const sortFieldValid = storedSort && (storedSort.orderBy === "id" || fieldNames.includes(storedSort.orderBy));
|
|
219
|
-
const nextSort = sortFieldValid ? storedSort : { orderBy: "id", orderDirection: "desc" };
|
|
225
|
+
const nextSort = sortFieldValid && storedSort ? storedSort : { orderBy: "id", orderDirection: "desc" };
|
|
220
226
|
dispatch({ type: "SET_SORT", payload: nextSort });
|
|
221
227
|
const storedSearch = AdminLocalStorage.getSearchFields(model.name);
|
|
222
228
|
const defaults = getDefaultSearchFields(searchableFieldNames);
|
|
223
|
-
const filteredSearch = (storedSearch || defaults).filter(
|
|
229
|
+
const filteredSearch = (storedSearch || defaults).filter(
|
|
230
|
+
(f) => searchableFieldNames.includes(f)
|
|
231
|
+
);
|
|
224
232
|
setSearchFields(filteredSearch);
|
|
225
233
|
dispatch({ type: "SET_SEARCH", payload: "" });
|
|
226
234
|
dispatch({ type: "SET_DEBOUNCED_SEARCH", payload: "" });
|
|
@@ -230,7 +238,16 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
230
238
|
dispatch({ type: "SET_FILTERS", payload: urlFilters });
|
|
231
239
|
dispatch({ type: "SET_SHOW_FILTERS", payload: true });
|
|
232
240
|
}
|
|
233
|
-
}, [
|
|
241
|
+
}, [
|
|
242
|
+
model?.name,
|
|
243
|
+
fieldNames,
|
|
244
|
+
searchableFieldNames,
|
|
245
|
+
getDefaultSearchFields,
|
|
246
|
+
setVisibleColumns,
|
|
247
|
+
setSearchFields,
|
|
248
|
+
dispatch,
|
|
249
|
+
urlFilters
|
|
250
|
+
]);
|
|
234
251
|
const setSortSafely = useCallback(
|
|
235
252
|
(newSort) => {
|
|
236
253
|
const resolvedSort = typeof newSort === "function" ? newSort(sort) : newSort;
|
|
@@ -285,7 +302,22 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
285
302
|
filters: Object.keys(filters).length > 0 ? filters : void 0
|
|
286
303
|
}
|
|
287
304
|
};
|
|
288
|
-
const
|
|
305
|
+
const extractItems = useCallback((anyData, dataPath2) => {
|
|
306
|
+
let processedItems = dataPath2 && anyData[dataPath2] ? anyData[dataPath2] : [];
|
|
307
|
+
if (!processedItems || processedItems.length === 0) {
|
|
308
|
+
for (const [, value] of Object.entries(anyData)) {
|
|
309
|
+
if (Array.isArray(value) && value.length > 0 && value[0]?.id) {
|
|
310
|
+
processedItems = value;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return processedItems;
|
|
316
|
+
}, []);
|
|
317
|
+
const filterValidItems = useCallback((items2) => {
|
|
318
|
+
return items2.filter((item) => item && typeof item === "object" && item.id);
|
|
319
|
+
}, []);
|
|
320
|
+
const { data, loading, error } = useQuery(query ?? sdk.__AdminUsersDocument, {
|
|
289
321
|
variables,
|
|
290
322
|
skip: !model || !query,
|
|
291
323
|
errorPolicy: "all",
|
|
@@ -299,11 +331,6 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
299
331
|
}
|
|
300
332
|
});
|
|
301
333
|
const { validatedItems, validatedPagination, dataError } = useMemo(() => {
|
|
302
|
-
if (error) {
|
|
303
|
-
const apolloError = error;
|
|
304
|
-
if (apolloError.networkError) ;
|
|
305
|
-
if (apolloError.graphQLErrors?.length > 0) ;
|
|
306
|
-
}
|
|
307
334
|
if (!data) {
|
|
308
335
|
return {
|
|
309
336
|
validatedItems: [],
|
|
@@ -313,18 +340,8 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
313
340
|
}
|
|
314
341
|
try {
|
|
315
342
|
const anyData = data;
|
|
316
|
-
let processedItems = dataPath && anyData[dataPath] ? anyData[dataPath] : [];
|
|
317
343
|
const processedPagination = paginationPath && anyData[paginationPath] ? anyData[paginationPath] : void 0;
|
|
318
|
-
|
|
319
|
-
for (const [key, value] of Object.entries(anyData)) {
|
|
320
|
-
if (Array.isArray(value)) {
|
|
321
|
-
if (value.length > 0 && value[0]?.id) {
|
|
322
|
-
processedItems = value;
|
|
323
|
-
break;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
344
|
+
const processedItems = extractItems(anyData, dataPath);
|
|
328
345
|
if (!Array.isArray(processedItems)) {
|
|
329
346
|
return {
|
|
330
347
|
validatedItems: [],
|
|
@@ -332,17 +349,8 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
332
349
|
dataError: new Error("Invalid data format: expected array")
|
|
333
350
|
};
|
|
334
351
|
}
|
|
335
|
-
const filteredItems = processedItems.filter((item, index) => {
|
|
336
|
-
if (!item || typeof item !== "object") {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
if (!item.id) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
return true;
|
|
343
|
-
});
|
|
344
352
|
return {
|
|
345
|
-
validatedItems:
|
|
353
|
+
validatedItems: filterValidItems(processedItems),
|
|
346
354
|
validatedPagination: processedPagination,
|
|
347
355
|
dataError: null
|
|
348
356
|
};
|
|
@@ -353,7 +361,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
353
361
|
dataError: err instanceof Error ? err : new Error("Unknown data processing error")
|
|
354
362
|
};
|
|
355
363
|
}
|
|
356
|
-
}, [data, dataPath, paginationPath, error]);
|
|
364
|
+
}, [data, dataPath, paginationPath, error, extractItems, filterValidItems]);
|
|
357
365
|
const items = validatedItems;
|
|
358
366
|
const pagination = validatedPagination;
|
|
359
367
|
if (!model) {
|
|
@@ -422,7 +430,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
422
430
|
/* @__PURE__ */ jsx(
|
|
423
431
|
"button",
|
|
424
432
|
{
|
|
425
|
-
onClick: () =>
|
|
433
|
+
onClick: () => globalThis.location.reload(),
|
|
426
434
|
className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-web hover:bg-green-web-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web",
|
|
427
435
|
children: "Reload Page"
|
|
428
436
|
}
|
|
@@ -710,7 +718,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
710
718
|
id: "search",
|
|
711
719
|
name: "search",
|
|
712
720
|
className: "block w-full pl-10 pr-12 py-2 border border-gray-300 dark:border-gray-600 rounded-md leading-5 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:placeholder-gray-400 dark:focus:placeholder-gray-500 focus:ring-1 focus:ring-green-web focus:border-green-web sm:text-sm",
|
|
713
|
-
placeholder: `Search ${searchFields.length > 0 ? searchFields.map((field) => field.
|
|
721
|
+
placeholder: `Search ${searchFields.length > 0 ? searchFields.map((field) => field.replaceAll(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase())).join(", ") : getPluralName(model.name).toLowerCase()}...`,
|
|
714
722
|
type: "search",
|
|
715
723
|
value: state.search || "",
|
|
716
724
|
onChange: (e) => dispatch({ type: "SET_SEARCH", payload: e.target.value })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FormField } from '@nestledjs/forms';
|
|
1
|
+
import { FormField } from '@nestledjs/forms-core';
|
|
2
2
|
import { DatabaseModel } from '../types';
|
|
3
3
|
import { DisplayFieldConfig } from '../context/AdminDataContext';
|
|
4
4
|
/**
|
|
@@ -15,6 +15,10 @@ export declare function getAdminDocuments(sdk: any, model: DatabaseModel): {
|
|
|
15
15
|
* Get the GraphQL mutation name for a given operation
|
|
16
16
|
*/
|
|
17
17
|
export declare function getMutationName(model: DatabaseModel, operation: 'create' | 'update' | 'delete'): string;
|
|
18
|
+
/**
|
|
19
|
+
* Convert model name to kebab-case for URLs
|
|
20
|
+
*/
|
|
21
|
+
export declare function toKebabCase(str: string): string;
|
|
18
22
|
/**
|
|
19
23
|
* Options for building form fields
|
|
20
24
|
*/
|
|
@@ -34,3 +38,5 @@ export declare function buildFormFields(sdk: any, model: DatabaseModel, operatio
|
|
|
34
38
|
* Removes Apollo metadata and system fields
|
|
35
39
|
*/
|
|
36
40
|
export declare function cleanFormInput(input: Record<string, unknown>, model?: DatabaseModel): Record<string, unknown>;
|
|
41
|
+
export declare function toReadableText(text: string): string;
|
|
42
|
+
export declare function sanitizeInput(input: string | undefined): string;
|