@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.
Files changed (34) hide show
  1. package/README.md +43 -39
  2. package/index.js +6 -1
  3. package/lib/components/ExportButton.d.ts +14 -0
  4. package/lib/components/ExportButton.js +281 -0
  5. package/lib/components/FilterField.js +1 -2
  6. package/lib/components/RelationFieldWrapper.d.ts +1 -1
  7. package/lib/components/RelationFieldWrapper.js +2 -2
  8. package/lib/components/filters/DateRangeFilter.d.ts +1 -1
  9. package/lib/components/filters/DateRangeFilter.js +2 -2
  10. package/lib/components/filters/EnumFilter.d.ts +1 -1
  11. package/lib/components/filters/NumberRangeFilter.d.ts +1 -1
  12. package/lib/components/filters/NumberRangeFilter.js +2 -2
  13. package/lib/components/filters/RelationComponents.d.ts +5 -5
  14. package/lib/components/filters/RelationComponents.js +40 -13
  15. package/lib/components/filters/RelationFilterField.d.ts +1 -1
  16. package/lib/components/filters/RelationFilterField.js +18 -10
  17. package/lib/components/index.d.ts +1 -0
  18. package/lib/components/shared/AdminBreadcrumbs.js +2 -17
  19. package/lib/components/shared/AdminErrorStates.js +6 -1
  20. package/lib/components/shared/AdminStatusDisplay.js +7 -11
  21. package/lib/context/AdminDataContext.d.ts +2 -2
  22. package/lib/hooks/useAdminList.js +1 -1
  23. package/lib/hooks/useClickOutside.js +1 -1
  24. package/lib/hooks/useRelationData.js +2 -1
  25. package/lib/layouts/AdminDataLayout.js +87 -14
  26. package/lib/pages/AdminDataCreatePage.d.ts +1 -1
  27. package/lib/pages/AdminDataCreatePage.js +68 -45
  28. package/lib/pages/AdminDataEditPage.js +71 -38
  29. package/lib/pages/AdminDataListPage.js +116 -85
  30. package/lib/utils/graphql-utils.d.ts +7 -1
  31. package/lib/utils/graphql-utils.js +343 -331
  32. package/lib/utils/secure-storage.js +26 -20
  33. package/lib/utils/string-utils.js +31 -8
  34. 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
- var _a;
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] = (field == null ? void 0 : field.type.toLowerCase()) === "date" ? value.toISOString().split("T")[0] : value.toISOString();
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 = ((_a = field.relationFromFields) == null ? void 0 : _a[0]) || `${field.name}Id`;
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 { databaseModels, basePath = "/admin/data", formTheme, displayFieldConfig } = useAdminDataContext();
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(AdminDataEditPageContent, { model, id: validatedId, basePath, formTheme, displayFieldConfig });
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({ show, modelName, isDeleting, onConfirm, onCancel }) {
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({ submissionStatus, deleteStatus, submissionMessage, deleteMessage, modelName }) {
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({ model, id, basePath, formTheme, displayFieldConfig }) {
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
- var _a, _b;
354
- if (!(documents == null ? void 0 : documents.query)) return null;
375
+ if (!documents?.query) return null;
355
376
  try {
356
- if (((_a = documents.query) == null ? void 0 : _a.kind) === "Document" && ((_b = documents.query) == null ? void 0 : _b.definitions)) {
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
- var _a, _b;
366
- if (!(documents == null ? void 0 : documents.update)) return null;
387
+ if (!documents?.update) return null;
367
388
  try {
368
- if (((_a = documents.update) == null ? void 0 : _a.kind) === "Document" && ((_b = documents.update) == null ? void 0 : _b.definitions)) {
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
- var _a, _b;
378
- if (!(documents == null ? void 0 : documents.delete)) return null;
399
+ if (!documents?.delete) return null;
379
400
  try {
380
- if (((_a = documents.delete) == null ? void 0 : _a.kind) === "Document" && ((_b = documents.delete) == null ? void 0 : _b.definitions)) {
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 == null ? void 0 : data[responseFieldName];
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.scrollTo({ top: 0, behavior: "smooth" });
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(Link, { to: basePath, className: "text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400", children: "Data Browser" }) }),
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((enumType) => {
18
- try {
19
- const enumObject = sdk[enumType];
20
- if (!enumObject || typeof enumObject !== "object") {
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
- const values = Object.values(enumObject).filter((value) => typeof value === "string");
24
- if (values.length === 0) {
25
- const keys = Object.keys(enumObject).filter((key) => isNaN(Number(key)));
26
- return keys.length > 0 ? keys : null;
27
- }
28
- return values;
29
- } catch (error2) {
30
- return null;
31
- }
32
- }, [sdk]);
33
- const updateRelatedEnumFilter = useCallback((filters2, relationName, enumFieldName, value) => {
34
- const newFilters = { ...filters2 };
35
- if (value === void 0 || value === null || value === "") {
36
- const relationFilter = newFilters[relationName];
37
- if (relationFilter && typeof relationFilter === "object") {
38
- delete relationFilter[enumFieldName];
39
- if (Object.keys(relationFilter).length === 0) {
40
- delete newFilters[relationName];
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
- } else {
44
- if (!newFilters[relationName]) {
45
- newFilters[relationName] = {};
46
- }
47
- const relationFilter = newFilters[relationName];
48
- if (relationFilter && typeof relationFilter === "object") {
49
- relationFilter[enumFieldName] = value;
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
- return newFilters;
53
- }, []);
54
- const updateSimpleFilter = useCallback((filters2, fieldName, value) => {
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 || ((params == null ? void 0 : params.dataTypePlural) ?? "");
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 (model == null ? void 0 : model.pluralModelPropertyName) || pluralParam.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
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((f) => searchableFieldNames.includes(f));
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
- }, [model == null ? void 0 : model.name, fieldNames, searchableFieldNames, getDefaultSearchFields, setVisibleColumns, setSearchFields, dispatch, urlFilters]);
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 { data, loading, error, networkStatus, refetch } = useQuery(query ?? sdk.__AdminUsersDocument, {
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
- if (!processedItems || processedItems.length === 0) {
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: filteredItems,
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: () => window.location.reload(),
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.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase())).join(", ") : getPluralName(model.name).toLowerCase()}...`,
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;