@nestledjs/data-browser 1.0.14 → 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 +6 -1
- package/lib/components/ExportButton.d.ts +14 -0
- package/lib/components/ExportButton.js +281 -0
- package/lib/components/FilterField.js +1 -2
- package/lib/components/RelationFieldWrapper.d.ts +1 -1
- package/lib/components/RelationFieldWrapper.js +2 -2
- package/lib/components/filters/DateRangeFilter.d.ts +1 -1
- package/lib/components/filters/DateRangeFilter.js +2 -2
- package/lib/components/filters/EnumFilter.d.ts +1 -1
- package/lib/components/filters/NumberRangeFilter.d.ts +1 -1
- package/lib/components/filters/NumberRangeFilter.js +2 -2
- 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 +18 -10
- package/lib/components/index.d.ts +1 -0
- 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/useAdminList.js +1 -1
- package/lib/hooks/useClickOutside.js +1 -1
- package/lib/hooks/useRelationData.js +2 -1
- package/lib/layouts/AdminDataLayout.js +87 -14
- package/lib/pages/AdminDataCreatePage.d.ts +1 -1
- package/lib/pages/AdminDataCreatePage.js +68 -45
- package/lib/pages/AdminDataEditPage.js +71 -38
- package/lib/pages/AdminDataListPage.js +116 -85
- package/lib/utils/graphql-utils.d.ts +7 -1
- package/lib/utils/graphql-utils.js +343 -331
- package/lib/utils/secure-storage.js +26 -20
- package/lib/utils/string-utils.js +31 -8
- package/package.json +2 -2
|
@@ -10,7 +10,7 @@ import { Form } from "@nestledjs/forms";
|
|
|
10
10
|
import { useAdminDataContext } from "../context/AdminDataContext.js";
|
|
11
11
|
import { AdminDataStateMessage } from "../components/AdminDataStateMessage.js";
|
|
12
12
|
import { useParams, Link, useNavigate } from "react-router";
|
|
13
|
-
import { getAdminDocuments, buildFormFields, cleanFormInput } from "../utils/graphql-utils.js";
|
|
13
|
+
import { sanitizeInput, getAdminDocuments, toKebabCase, toReadableText, buildFormFields, cleanFormInput } from "../utils/graphql-utils.js";
|
|
14
14
|
function toLowerCamelCase(name) {
|
|
15
15
|
if (!name) return "";
|
|
16
16
|
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
@@ -21,23 +21,12 @@ function getModelIdVariableName(modelName) {
|
|
|
21
21
|
function getModelResponseFieldName(modelName) {
|
|
22
22
|
return toLowerCamelCase(modelName);
|
|
23
23
|
}
|
|
24
|
-
function toReadableText(text) {
|
|
25
|
-
return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
|
|
26
|
-
}
|
|
27
24
|
if (process.env.NODE_ENV !== "production") {
|
|
28
25
|
loadDevMessages();
|
|
29
26
|
loadErrorMessages();
|
|
30
27
|
}
|
|
31
|
-
const sanitizeInput = (input) => {
|
|
32
|
-
if (!input || typeof input !== "string") return "";
|
|
33
|
-
return input.replace(/[<>"'%;()&+]/g, "").replace(/javascript:/gi, "").replace(/on\w+\s*=/gi, "").trim().substring(0, 100);
|
|
34
|
-
};
|
|
35
|
-
const toKebabCase = (str) => {
|
|
36
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
37
|
-
};
|
|
38
28
|
function processRelationFieldValue(field, item) {
|
|
39
|
-
|
|
40
|
-
const relationFieldName = ((_a = field.relationFromFields) == null ? void 0 : _a[0]) || `${field.name}Id`;
|
|
29
|
+
const relationFieldName = field.relationFromFields?.[0] || `${field.name}Id`;
|
|
41
30
|
let value = item[relationFieldName];
|
|
42
31
|
if (value === void 0) {
|
|
43
32
|
const relationObject = item[field.name];
|
|
@@ -63,6 +52,7 @@ function processDateFieldValue(field, value) {
|
|
|
63
52
|
return dateValue.toISOString().substring(0, 16);
|
|
64
53
|
}
|
|
65
54
|
} catch (e) {
|
|
55
|
+
console.error("Unexpected error:", e);
|
|
66
56
|
return "";
|
|
67
57
|
}
|
|
68
58
|
}
|
|
@@ -95,7 +85,7 @@ function performFinalSafetyChecks(initialValues, model) {
|
|
|
95
85
|
initialValues[key] = value.id;
|
|
96
86
|
} else if (value instanceof Date) {
|
|
97
87
|
const field = model.fields.find((f) => f.name === key);
|
|
98
|
-
initialValues[key] =
|
|
88
|
+
initialValues[key] = field?.type.toLowerCase() === "date" ? value.toISOString().split("T")[0] : value.toISOString();
|
|
99
89
|
} else {
|
|
100
90
|
initialValues[key] = "";
|
|
101
91
|
}
|
|
@@ -117,10 +107,9 @@ function extractInitialValues(model, item) {
|
|
|
117
107
|
return true;
|
|
118
108
|
});
|
|
119
109
|
editableFields.forEach((field) => {
|
|
120
|
-
var _a;
|
|
121
110
|
let value = item[field.name];
|
|
122
111
|
if (field.relationName && !field.isList) {
|
|
123
|
-
const relationFieldName =
|
|
112
|
+
const relationFieldName = field.relationFromFields?.[0] || `${field.name}Id`;
|
|
124
113
|
initialValues[relationFieldName] = processRelationFieldValue(field, item);
|
|
125
114
|
} else {
|
|
126
115
|
const fieldTypeLower = field.type.toLowerCase();
|
|
@@ -151,7 +140,12 @@ const validateId = (id) => {
|
|
|
151
140
|
};
|
|
152
141
|
function AdminDataEditPage() {
|
|
153
142
|
const { dataType, id } = useParams();
|
|
154
|
-
const {
|
|
143
|
+
const {
|
|
144
|
+
databaseModels,
|
|
145
|
+
basePath = "/admin/data",
|
|
146
|
+
formTheme,
|
|
147
|
+
displayFieldConfig
|
|
148
|
+
} = useAdminDataContext();
|
|
155
149
|
const findModelByName = (name) => {
|
|
156
150
|
return databaseModels.find((model2) => model2.name === name);
|
|
157
151
|
};
|
|
@@ -205,9 +199,24 @@ function AdminDataEditPage() {
|
|
|
205
199
|
) })
|
|
206
200
|
] }) }) }) });
|
|
207
201
|
}
|
|
208
|
-
return /* @__PURE__ */ jsx(
|
|
202
|
+
return /* @__PURE__ */ jsx(
|
|
203
|
+
AdminDataEditPageContent,
|
|
204
|
+
{
|
|
205
|
+
model,
|
|
206
|
+
id: validatedId,
|
|
207
|
+
basePath,
|
|
208
|
+
formTheme,
|
|
209
|
+
displayFieldConfig
|
|
210
|
+
}
|
|
211
|
+
);
|
|
209
212
|
}
|
|
210
|
-
function DeleteConfirmModal({
|
|
213
|
+
function DeleteConfirmModal({
|
|
214
|
+
show,
|
|
215
|
+
modelName,
|
|
216
|
+
isDeleting,
|
|
217
|
+
onConfirm,
|
|
218
|
+
onCancel
|
|
219
|
+
}) {
|
|
211
220
|
if (!show) return null;
|
|
212
221
|
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-gray-600 dark:bg-gray-900 bg-opacity-50 dark:bg-opacity-75 overflow-y-auto h-full w-full z-50 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl max-w-md w-full mx-4", children: [
|
|
213
222
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
|
|
@@ -273,7 +282,13 @@ function getStatusIcon(isSuccess, isError) {
|
|
|
273
282
|
}
|
|
274
283
|
return /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" });
|
|
275
284
|
}
|
|
276
|
-
function StatusMessage({
|
|
285
|
+
function StatusMessage({
|
|
286
|
+
submissionStatus,
|
|
287
|
+
deleteStatus,
|
|
288
|
+
submissionMessage,
|
|
289
|
+
deleteMessage,
|
|
290
|
+
modelName
|
|
291
|
+
}) {
|
|
277
292
|
if (submissionStatus === "idle" && deleteStatus === "idle") return null;
|
|
278
293
|
const isSuccess = submissionStatus === "success" || deleteStatus === "success";
|
|
279
294
|
const isError = submissionStatus === "error" || deleteStatus === "error";
|
|
@@ -336,7 +351,13 @@ async function executeDeleteMutation(deleteMutation, id, idVariableName, model)
|
|
|
336
351
|
};
|
|
337
352
|
}
|
|
338
353
|
}
|
|
339
|
-
function AdminDataEditPageContent({
|
|
354
|
+
function AdminDataEditPageContent({
|
|
355
|
+
model,
|
|
356
|
+
id,
|
|
357
|
+
basePath,
|
|
358
|
+
formTheme,
|
|
359
|
+
displayFieldConfig
|
|
360
|
+
}) {
|
|
340
361
|
const navigate = useNavigate();
|
|
341
362
|
const { sdk, databaseModels } = useAdminDataContext();
|
|
342
363
|
const [submissionState, setSubmissionState] = useState({ status: "idle" });
|
|
@@ -346,42 +367,43 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme, displayField
|
|
|
346
367
|
try {
|
|
347
368
|
return getAdminDocuments(sdk, model);
|
|
348
369
|
} catch (error2) {
|
|
370
|
+
console.error("Unexpected error:", error2);
|
|
349
371
|
return null;
|
|
350
372
|
}
|
|
351
373
|
}, [sdk, model]);
|
|
352
374
|
const QUERY = useMemo(() => {
|
|
353
|
-
|
|
354
|
-
if (!(documents == null ? void 0 : documents.query)) return null;
|
|
375
|
+
if (!documents?.query) return null;
|
|
355
376
|
try {
|
|
356
|
-
if (
|
|
377
|
+
if (documents.query?.kind === "Document" && documents.query?.definitions) {
|
|
357
378
|
return documents.query;
|
|
358
379
|
}
|
|
359
380
|
return gql(documents.query);
|
|
360
381
|
} catch (error2) {
|
|
382
|
+
console.error("Unexpected error:", error2);
|
|
361
383
|
return null;
|
|
362
384
|
}
|
|
363
385
|
}, [documents]);
|
|
364
386
|
const UPDATE_MUTATION = useMemo(() => {
|
|
365
|
-
|
|
366
|
-
if (!(documents == null ? void 0 : documents.update)) return null;
|
|
387
|
+
if (!documents?.update) return null;
|
|
367
388
|
try {
|
|
368
|
-
if (
|
|
389
|
+
if (documents.update?.kind === "Document" && documents.update?.definitions) {
|
|
369
390
|
return documents.update;
|
|
370
391
|
}
|
|
371
392
|
return gql(documents.update);
|
|
372
393
|
} catch (error2) {
|
|
394
|
+
console.error("Unexpected error:", error2);
|
|
373
395
|
return null;
|
|
374
396
|
}
|
|
375
397
|
}, [documents]);
|
|
376
398
|
const DELETE_MUTATION = useMemo(() => {
|
|
377
|
-
|
|
378
|
-
if (!(documents == null ? void 0 : documents.delete)) return null;
|
|
399
|
+
if (!documents?.delete) return null;
|
|
379
400
|
try {
|
|
380
|
-
if (
|
|
401
|
+
if (documents.delete?.kind === "Document" && documents.delete?.definitions) {
|
|
381
402
|
return documents.delete;
|
|
382
403
|
}
|
|
383
404
|
return gql(documents.delete);
|
|
384
405
|
} catch (error2) {
|
|
406
|
+
console.error("Unexpected error:", error2);
|
|
385
407
|
return null;
|
|
386
408
|
}
|
|
387
409
|
}, [documents]);
|
|
@@ -413,6 +435,13 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme, displayField
|
|
|
413
435
|
}
|
|
414
436
|
`
|
|
415
437
|
);
|
|
438
|
+
useEffect(() => {
|
|
439
|
+
if (deleteState.status !== "success") return;
|
|
440
|
+
const timer = setTimeout(() => {
|
|
441
|
+
navigate(`${basePath}/${toKebabCase(model.pluralName)}`);
|
|
442
|
+
}, 1500);
|
|
443
|
+
return () => clearTimeout(timer);
|
|
444
|
+
}, [deleteState.status, navigate, basePath, model.pluralName]);
|
|
416
445
|
useEffect(() => {
|
|
417
446
|
if (submissionState.status === "error") {
|
|
418
447
|
const timer = setTimeout(() => {
|
|
@@ -465,7 +494,7 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme, displayField
|
|
|
465
494
|
}
|
|
466
495
|
);
|
|
467
496
|
}
|
|
468
|
-
const item = data
|
|
497
|
+
const item = data?.[responseFieldName];
|
|
469
498
|
if (!item) {
|
|
470
499
|
return /* @__PURE__ */ jsx(
|
|
471
500
|
AdminDataStateMessage,
|
|
@@ -494,7 +523,9 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme, displayField
|
|
|
494
523
|
status: result.success ? "success" : "error",
|
|
495
524
|
message: result.message
|
|
496
525
|
});
|
|
497
|
-
window
|
|
526
|
+
if (globalThis.window !== void 0) {
|
|
527
|
+
globalThis.scrollTo({ top: 0, behavior: "smooth" });
|
|
528
|
+
}
|
|
498
529
|
if (result.success) {
|
|
499
530
|
await refetch();
|
|
500
531
|
}
|
|
@@ -506,16 +537,18 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme, displayField
|
|
|
506
537
|
status: result.success ? "success" : "error",
|
|
507
538
|
message: result.message
|
|
508
539
|
});
|
|
509
|
-
if (result.success) {
|
|
510
|
-
setTimeout(() => {
|
|
511
|
-
navigate(`${basePath}/${toKebabCase(model.pluralName)}`);
|
|
512
|
-
}, 1500);
|
|
513
|
-
}
|
|
514
540
|
};
|
|
515
541
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
516
542
|
/* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
|
|
517
543
|
/* @__PURE__ */ jsx("nav", { className: "flex mb-6", "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsxs("ol", { className: "flex items-center space-x-4", children: [
|
|
518
|
-
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
544
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
545
|
+
Link,
|
|
546
|
+
{
|
|
547
|
+
to: basePath,
|
|
548
|
+
className: "text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400",
|
|
549
|
+
children: "Data Browser"
|
|
550
|
+
}
|
|
551
|
+
) }),
|
|
519
552
|
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
|
|
520
553
|
/* @__PURE__ */ jsx(
|
|
521
554
|
"svg",
|
|
@@ -7,6 +7,7 @@ import { AdminLocalStorage } from "../utils/secure-storage.js";
|
|
|
7
7
|
import { kebabCase, formatFieldName } from "../utils/string-utils.js";
|
|
8
8
|
import { useParams, useSearchParams, Link } from "react-router";
|
|
9
9
|
import { FilterField } from "../components/FilterField.js";
|
|
10
|
+
import { ExportButton } from "../components/ExportButton.js";
|
|
10
11
|
import { useAdminList } from "../hooks/useAdminList.js";
|
|
11
12
|
import { getAdminDocuments } from "../utils/graphql-utils.js";
|
|
12
13
|
import { useAdminDataContext } from "../context/AdminDataContext.js";
|
|
@@ -14,52 +15,62 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
14
15
|
const params = useParams();
|
|
15
16
|
const [searchParams] = useSearchParams();
|
|
16
17
|
const { sdk, databaseModels, basePath = "/admin/data" } = useAdminDataContext();
|
|
17
|
-
const getEnumValues = useCallback(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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);
|
|
21
33
|
return null;
|
|
22
34
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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;
|
|
41
56
|
}
|
|
42
57
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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;
|
|
50
69
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const newFilters = { ...filters2 };
|
|
56
|
-
if (value === void 0 || value === null || value === "") {
|
|
57
|
-
delete newFilters[fieldName];
|
|
58
|
-
} else {
|
|
59
|
-
newFilters[fieldName] = value;
|
|
60
|
-
}
|
|
61
|
-
return newFilters;
|
|
62
|
-
}, []);
|
|
70
|
+
return newFilters;
|
|
71
|
+
},
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
63
74
|
const { state, dispatch } = useAdminList();
|
|
64
75
|
const {
|
|
65
76
|
search,
|
|
@@ -74,10 +85,6 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
74
85
|
showFilters,
|
|
75
86
|
showSearchFieldSelector
|
|
76
87
|
} = state;
|
|
77
|
-
useCallback(
|
|
78
|
-
(skip2) => dispatch({ type: "SET_SKIP", payload: skip2 }),
|
|
79
|
-
[dispatch]
|
|
80
|
-
);
|
|
81
88
|
const setFilters = useCallback(
|
|
82
89
|
(filters2) => dispatch({ type: "SET_FILTERS", payload: filters2 }),
|
|
83
90
|
[dispatch]
|
|
@@ -114,7 +121,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
114
121
|
}, 500);
|
|
115
122
|
return () => clearTimeout(timer);
|
|
116
123
|
}, [state.search]);
|
|
117
|
-
const pluralParam = propModelName || (
|
|
124
|
+
const pluralParam = propModelName || (params?.dataTypePlural ?? "");
|
|
118
125
|
const model = useMemo(() => {
|
|
119
126
|
if (propModelName) {
|
|
120
127
|
return databaseModels.find((m) => m.name === propModelName);
|
|
@@ -131,10 +138,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
131
138
|
for (const [key, value] of searchParams.entries()) {
|
|
132
139
|
if (key !== "page" && key !== "search" && key !== "sort") {
|
|
133
140
|
const relationField = model.fields.find(
|
|
134
|
-
(f) =>
|
|
135
|
-
var _a;
|
|
136
|
-
return f.relationName && !f.isList && ((_a = f.relationFromFields) == null ? void 0 : _a[0]) === key;
|
|
137
|
-
}
|
|
141
|
+
(f) => f.relationName && !f.isList && f.relationFromFields?.[0] === key
|
|
138
142
|
);
|
|
139
143
|
if (relationField) {
|
|
140
144
|
filters2[relationField.name] = { id: value };
|
|
@@ -162,7 +166,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
162
166
|
}, [sdk, model]);
|
|
163
167
|
const { listQuery: query } = documents;
|
|
164
168
|
const dataPath = useMemo(() => {
|
|
165
|
-
return
|
|
169
|
+
return model?.pluralModelPropertyName || pluralParam.replaceAll(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
166
170
|
}, [model, pluralParam]);
|
|
167
171
|
const paginationPath = useMemo(() => {
|
|
168
172
|
return "counters";
|
|
@@ -218,11 +222,13 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
218
222
|
setVisibleColumns(filteredCols);
|
|
219
223
|
const storedSort = AdminLocalStorage.getSortPreference(model.name);
|
|
220
224
|
const sortFieldValid = storedSort && (storedSort.orderBy === "id" || fieldNames.includes(storedSort.orderBy));
|
|
221
|
-
const nextSort = sortFieldValid ? storedSort : { orderBy: "id", orderDirection: "desc" };
|
|
225
|
+
const nextSort = sortFieldValid && storedSort ? storedSort : { orderBy: "id", orderDirection: "desc" };
|
|
222
226
|
dispatch({ type: "SET_SORT", payload: nextSort });
|
|
223
227
|
const storedSearch = AdminLocalStorage.getSearchFields(model.name);
|
|
224
228
|
const defaults = getDefaultSearchFields(searchableFieldNames);
|
|
225
|
-
const filteredSearch = (storedSearch || defaults).filter(
|
|
229
|
+
const filteredSearch = (storedSearch || defaults).filter(
|
|
230
|
+
(f) => searchableFieldNames.includes(f)
|
|
231
|
+
);
|
|
226
232
|
setSearchFields(filteredSearch);
|
|
227
233
|
dispatch({ type: "SET_SEARCH", payload: "" });
|
|
228
234
|
dispatch({ type: "SET_DEBOUNCED_SEARCH", payload: "" });
|
|
@@ -232,7 +238,16 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
232
238
|
dispatch({ type: "SET_FILTERS", payload: urlFilters });
|
|
233
239
|
dispatch({ type: "SET_SHOW_FILTERS", payload: true });
|
|
234
240
|
}
|
|
235
|
-
}, [
|
|
241
|
+
}, [
|
|
242
|
+
model?.name,
|
|
243
|
+
fieldNames,
|
|
244
|
+
searchableFieldNames,
|
|
245
|
+
getDefaultSearchFields,
|
|
246
|
+
setVisibleColumns,
|
|
247
|
+
setSearchFields,
|
|
248
|
+
dispatch,
|
|
249
|
+
urlFilters
|
|
250
|
+
]);
|
|
236
251
|
const setSortSafely = useCallback(
|
|
237
252
|
(newSort) => {
|
|
238
253
|
const resolvedSort = typeof newSort === "function" ? newSort(sort) : newSort;
|
|
@@ -287,7 +302,22 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
287
302
|
filters: Object.keys(filters).length > 0 ? filters : void 0
|
|
288
303
|
}
|
|
289
304
|
};
|
|
290
|
-
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, {
|
|
291
321
|
variables,
|
|
292
322
|
skip: !model || !query,
|
|
293
323
|
errorPolicy: "all",
|
|
@@ -301,12 +331,6 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
301
331
|
}
|
|
302
332
|
});
|
|
303
333
|
const { validatedItems, validatedPagination, dataError } = useMemo(() => {
|
|
304
|
-
var _a, _b;
|
|
305
|
-
if (error) {
|
|
306
|
-
const apolloError = error;
|
|
307
|
-
if (apolloError.networkError) ;
|
|
308
|
-
if (((_a = apolloError.graphQLErrors) == null ? void 0 : _a.length) > 0) ;
|
|
309
|
-
}
|
|
310
334
|
if (!data) {
|
|
311
335
|
return {
|
|
312
336
|
validatedItems: [],
|
|
@@ -316,18 +340,8 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
316
340
|
}
|
|
317
341
|
try {
|
|
318
342
|
const anyData = data;
|
|
319
|
-
let processedItems = dataPath && anyData[dataPath] ? anyData[dataPath] : [];
|
|
320
343
|
const processedPagination = paginationPath && anyData[paginationPath] ? anyData[paginationPath] : void 0;
|
|
321
|
-
|
|
322
|
-
for (const [key, value] of Object.entries(anyData)) {
|
|
323
|
-
if (Array.isArray(value)) {
|
|
324
|
-
if (value.length > 0 && ((_b = value[0]) == null ? void 0 : _b.id)) {
|
|
325
|
-
processedItems = value;
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
344
|
+
const processedItems = extractItems(anyData, dataPath);
|
|
331
345
|
if (!Array.isArray(processedItems)) {
|
|
332
346
|
return {
|
|
333
347
|
validatedItems: [],
|
|
@@ -335,17 +349,8 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
335
349
|
dataError: new Error("Invalid data format: expected array")
|
|
336
350
|
};
|
|
337
351
|
}
|
|
338
|
-
const filteredItems = processedItems.filter((item, index) => {
|
|
339
|
-
if (!item || typeof item !== "object") {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
if (!item.id) {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
return true;
|
|
346
|
-
});
|
|
347
352
|
return {
|
|
348
|
-
validatedItems:
|
|
353
|
+
validatedItems: filterValidItems(processedItems),
|
|
349
354
|
validatedPagination: processedPagination,
|
|
350
355
|
dataError: null
|
|
351
356
|
};
|
|
@@ -356,7 +361,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
356
361
|
dataError: err instanceof Error ? err : new Error("Unknown data processing error")
|
|
357
362
|
};
|
|
358
363
|
}
|
|
359
|
-
}, [data, dataPath, paginationPath, error]);
|
|
364
|
+
}, [data, dataPath, paginationPath, error, extractItems, filterValidItems]);
|
|
360
365
|
const items = validatedItems;
|
|
361
366
|
const pagination = validatedPagination;
|
|
362
367
|
if (!model) {
|
|
@@ -425,7 +430,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
425
430
|
/* @__PURE__ */ jsx(
|
|
426
431
|
"button",
|
|
427
432
|
{
|
|
428
|
-
onClick: () =>
|
|
433
|
+
onClick: () => globalThis.location.reload(),
|
|
429
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",
|
|
430
435
|
children: "Reload Page"
|
|
431
436
|
}
|
|
@@ -713,7 +718,7 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
713
718
|
id: "search",
|
|
714
719
|
name: "search",
|
|
715
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",
|
|
716
|
-
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()}...`,
|
|
717
722
|
type: "search",
|
|
718
723
|
value: state.search || "",
|
|
719
724
|
onChange: (e) => dispatch({ type: "SET_SEARCH", payload: e.target.value })
|
|
@@ -744,6 +749,32 @@ function AdminDataListPage({ modelName: propModelName } = {}) {
|
|
|
744
749
|
}
|
|
745
750
|
),
|
|
746
751
|
columnSelector,
|
|
752
|
+
query && /* @__PURE__ */ jsx(
|
|
753
|
+
ExportButton,
|
|
754
|
+
{
|
|
755
|
+
query,
|
|
756
|
+
dataPath,
|
|
757
|
+
variables,
|
|
758
|
+
visibleColumns,
|
|
759
|
+
fieldNames,
|
|
760
|
+
modelName: model.name,
|
|
761
|
+
hasActiveFilters: Object.keys(filters).length > 0
|
|
762
|
+
}
|
|
763
|
+
),
|
|
764
|
+
/* @__PURE__ */ jsxs(
|
|
765
|
+
"select",
|
|
766
|
+
{
|
|
767
|
+
value: pageSize,
|
|
768
|
+
onChange: (e) => dispatch({ type: "SET_PAGE_SIZE", payload: Number(e.target.value) }),
|
|
769
|
+
className: "px-3 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web",
|
|
770
|
+
"aria-label": "Rows per page",
|
|
771
|
+
children: [
|
|
772
|
+
/* @__PURE__ */ jsx("option", { value: 20, children: "20 / page" }),
|
|
773
|
+
/* @__PURE__ */ jsx("option", { value: 50, children: "50 / page" }),
|
|
774
|
+
/* @__PURE__ */ jsx("option", { value: 100, children: "100 / page" })
|
|
775
|
+
]
|
|
776
|
+
}
|
|
777
|
+
),
|
|
747
778
|
/* @__PURE__ */ jsx(
|
|
748
779
|
Link,
|
|
749
780
|
{
|
|
@@ -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;
|