@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
@@ -78,27 +78,54 @@ function RelationItemList({
78
78
  ),
79
79
  error && /* @__PURE__ */ jsxs("div", { className: "px-3 py-2 text-sm text-red-600", children: [
80
80
  /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
81
- /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 mr-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
81
+ /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 mr-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
82
+ "path",
83
+ {
84
+ strokeLinecap: "round",
85
+ strokeLinejoin: "round",
86
+ strokeWidth: 2,
87
+ d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
88
+ }
89
+ ) }),
82
90
  "Failed to load options"
83
91
  ] }),
84
92
  /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500 mt-1", children: error.networkError ? "Network error" : "Please try again" })
85
93
  ] }),
86
94
  !error && loading && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-500", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
87
- /* @__PURE__ */ jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4 text-gray-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
88
- /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
89
- /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
90
- ] }),
95
+ /* @__PURE__ */ jsxs(
96
+ "svg",
97
+ {
98
+ className: "animate-spin -ml-1 mr-2 h-4 w-4 text-gray-500",
99
+ xmlns: "http://www.w3.org/2000/svg",
100
+ fill: "none",
101
+ viewBox: "0 0 24 24",
102
+ children: [
103
+ /* @__PURE__ */ jsx(
104
+ "circle",
105
+ {
106
+ className: "opacity-25",
107
+ cx: "12",
108
+ cy: "12",
109
+ r: "10",
110
+ stroke: "currentColor",
111
+ strokeWidth: "4"
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsx(
115
+ "path",
116
+ {
117
+ className: "opacity-75",
118
+ fill: "currentColor",
119
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
120
+ }
121
+ )
122
+ ]
123
+ }
124
+ ),
91
125
  "Loading..."
92
126
  ] }) }),
93
127
  !error && !loading && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-500", children: "No items found" }),
94
- !error && !loading && items.length > 0 && items.map((item) => /* @__PURE__ */ jsx(
95
- RelationItem,
96
- {
97
- item,
98
- onSelect
99
- },
100
- item.id
101
- ))
128
+ !error && !loading && items.length > 0 && items.map((item) => /* @__PURE__ */ jsx(RelationItem, { item, onSelect }, item.id))
102
129
  ] });
103
130
  }
104
131
  function RelationDropdownContent({
@@ -4,5 +4,5 @@ interface RelationFilterFieldProps {
4
4
  currentValue: any;
5
5
  onChange: (value: any) => void;
6
6
  }
7
- export declare function RelationFilterField({ fieldName, relatedModelName, currentValue, onChange }: Readonly<RelationFilterFieldProps>): import("react/jsx-runtime").JSX.Element;
7
+ export declare function RelationFilterField({ fieldName, relatedModelName, currentValue, onChange, }: Readonly<RelationFilterFieldProps>): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -17,22 +17,30 @@ function RelationFilterField({
17
17
  setIsOpen(false);
18
18
  }, []);
19
19
  useClickOutside(dropdownRef, handleCloseDropdown, isOpen);
20
- const { relatedItems, loading, error: relationError, hasDocument } = useRelationData(
20
+ const {
21
+ relatedItems,
22
+ loading,
23
+ error: relationError,
24
+ hasDocument
25
+ } = useRelationData(
21
26
  relatedModelName,
22
27
  searchTerm,
23
28
  isOpen,
24
- currentValue == null ? void 0 : currentValue.id
29
+ currentValue?.id
25
30
  // Pass current value ID so it can be loaded even when dropdown is closed
26
31
  );
27
32
  const currentItem = useMemo(
28
- () => (currentValue == null ? void 0 : currentValue.id) ? relatedItems.find((item) => item.id === currentValue.id) : null,
29
- [currentValue == null ? void 0 : currentValue.id, relatedItems]
33
+ () => currentValue?.id ? relatedItems.find((item) => item.id === currentValue.id) : null,
34
+ [currentValue?.id, relatedItems]
35
+ );
36
+ const handleSelect = useCallback(
37
+ (item) => {
38
+ onChange({ id: item.id });
39
+ setIsOpen(false);
40
+ setSearchTerm("");
41
+ },
42
+ [onChange]
30
43
  );
31
- const handleSelect = useCallback((item) => {
32
- onChange({ id: item.id });
33
- setIsOpen(false);
34
- setSearchTerm("");
35
- }, [onChange]);
36
44
  const handleClear = useCallback(() => {
37
45
  onChange(void 0);
38
46
  setIsOpen(false);
@@ -54,7 +62,7 @@ function RelationFilterField({
54
62
  "input",
55
63
  {
56
64
  type: "text",
57
- value: (currentValue == null ? void 0 : currentValue.id) || "",
65
+ value: currentValue?.id || "",
58
66
  onChange: (e) => onChange(e.target.value ? { id: e.target.value } : void 0),
59
67
  placeholder: "Enter ID...",
60
68
  className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-web focus:border-green-web text-sm"
@@ -1,3 +1,4 @@
1
1
  export * from './filters';
2
2
  export * from './shared';
3
3
  export * from './RelationFieldWrapper';
4
+ export * from './ExportButton';
@@ -9,25 +9,10 @@ function AdminBreadcrumbs({
9
9
  }) {
10
10
  const renderHomeLink = () => {
11
11
  if (!showHome) return null;
12
- return /* @__PURE__ */ jsx("li", { className: "flex items-center", children: /* @__PURE__ */ jsx(
13
- Link,
14
- {
15
- to: homeHref,
16
- className: "text-gray-400 hover:text-gray-500",
17
- "aria-label": "Home",
18
- children: /* @__PURE__ */ jsx(HomeIcon, { className: "h-5 w-5 flex-shrink-0" })
19
- }
20
- ) }, "home");
12
+ return /* @__PURE__ */ jsx("li", { className: "flex items-center", children: /* @__PURE__ */ jsx(Link, { to: homeHref, className: "text-gray-400 hover:text-gray-500", "aria-label": "Home", children: /* @__PURE__ */ jsx(HomeIcon, { className: "h-5 w-5 flex-shrink-0" }) }) }, "home");
21
13
  };
22
14
  const renderBreadcrumbItem = (item, index, isLast) => {
23
- const content = item.href && !item.isActive ? /* @__PURE__ */ jsx(
24
- Link,
25
- {
26
- to: item.href,
27
- className: "text-sm font-medium text-gray-500 hover:text-gray-700",
28
- children: item.label
29
- }
30
- ) : /* @__PURE__ */ jsx(
15
+ const content = item.href && !item.isActive ? /* @__PURE__ */ jsx(Link, { to: item.href, className: "text-sm font-medium text-gray-500 hover:text-gray-700", children: item.label }) : /* @__PURE__ */ jsx(
31
16
  "span",
32
17
  {
33
18
  className: `text-sm font-medium ${item.isActive ? "text-gray-900" : "text-gray-500"}`,
@@ -109,7 +109,12 @@ function AdminLoadingState({
109
109
  return sizes[size];
110
110
  };
111
111
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center p-8 ${className}`, children: [
112
- /* @__PURE__ */ jsx("div", { className: `animate-spin rounded-full border-b-2 border-green-web ${getSizeClasses()}` }),
112
+ /* @__PURE__ */ jsx(
113
+ "div",
114
+ {
115
+ className: `animate-spin rounded-full border-b-2 border-green-web ${getSizeClasses()}`
116
+ }
117
+ ),
113
118
  /* @__PURE__ */ jsx("h3", { className: "mt-4 text-sm font-medium text-gray-900", children: title }),
114
119
  message && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500", children: message })
115
120
  ] });
@@ -151,7 +151,12 @@ function AdminStatusDisplay({
151
151
  const finalClasses = `${baseClasses} ${interactiveClasses} ${className}`.trim();
152
152
  const renderDot = () => {
153
153
  if (!shouldShowDot) return null;
154
- return /* @__PURE__ */ jsx("span", { className: `inline-block rounded-full mr-2 ${config.bgColor.replace("bg-", "bg-").replace("-100", "-400")} ${sizeClasses.dot}` });
154
+ return /* @__PURE__ */ jsx(
155
+ "span",
156
+ {
157
+ className: `inline-block rounded-full mr-2 ${config.bgColor.replace("bg-", "bg-").replace("-100", "-400")} ${sizeClasses.dot}`
158
+ }
159
+ );
155
160
  };
156
161
  const renderIcon = () => {
157
162
  if (!shouldShowIcon) return null;
@@ -168,16 +173,7 @@ function AdminStatusDisplay({
168
173
  }
169
174
  };
170
175
  if (isClickable) {
171
- return /* @__PURE__ */ jsx(
172
- "button",
173
- {
174
- type: "button",
175
- onClick: handleClick,
176
- className: finalClasses,
177
- title: tooltip,
178
- children: content
179
- }
180
- );
176
+ return /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClick, className: finalClasses, title: tooltip, children: content });
181
177
  }
182
178
  return /* @__PURE__ */ jsx("span", { className: finalClasses, title: tooltip, children: content });
183
179
  }
@@ -42,7 +42,7 @@ export interface AdminDataProviderProps {
42
42
  * ```tsx
43
43
  * import * as Sdk from '@your-project/shared/sdk'
44
44
  * import { DATABASE_MODELS } from '@your-project/shared/sdk'
45
- * import { AdminDataProvider } from '@nestledjs/admin-data'
45
+ * import { AdminDataProvider } from '@nestledjs/data-browser'
46
46
  *
47
47
  * <AdminDataProvider
48
48
  * sdk={Sdk}
@@ -54,7 +54,7 @@ export interface AdminDataProviderProps {
54
54
  * </AdminDataProvider>
55
55
  * ```
56
56
  */
57
- export declare function AdminDataProvider({ children, sdk, databaseModels, basePath, formTheme, displayFieldConfig }: Readonly<AdminDataProviderProps>): import("react/jsx-runtime").JSX.Element;
57
+ export declare function AdminDataProvider({ children, sdk, databaseModels, basePath, formTheme, displayFieldConfig, }: Readonly<AdminDataProviderProps>): import("react/jsx-runtime").JSX.Element;
58
58
  /**
59
59
  * Hook to access the admin data context
60
60
  * @throws Error if used outside of AdminDataProvider
@@ -9,7 +9,7 @@ function adminListReducer(state, action) {
9
9
  case "SET_SKIP":
10
10
  return { ...state, skip: action.payload };
11
11
  case "SET_PAGE_SIZE":
12
- return { ...state, pageSize: action.payload };
12
+ return { ...state, pageSize: action.payload, skip: 0 };
13
13
  case "SET_SORT":
14
14
  return { ...state, sort: action.payload };
15
15
  case "SET_VISIBLE_COLUMNS":
@@ -3,7 +3,7 @@ function useClickOutside(ref, handler, isActive = true) {
3
3
  useEffect(() => {
4
4
  if (!isActive) return;
5
5
  const handleClickOutside = (event) => {
6
- if (ref.current && !ref.current.contains(event.target)) {
6
+ if (ref.current && event.target instanceof Node && !ref.current.contains(event.target)) {
7
7
  handler();
8
8
  }
9
9
  };
@@ -16,7 +16,7 @@ function useRelationData(relatedModelName, searchTerm, isOpen, currentValueId) {
16
16
  [sdk, relatedModel]
17
17
  );
18
18
  const relatedDataPath = useMemo(
19
- () => (relatedModel == null ? void 0 : relatedModel.pluralModelPropertyName) || relatedModelName.charAt(0).toLowerCase() + relatedModelName.slice(1) + "s",
19
+ () => relatedModel?.pluralModelPropertyName || relatedModelName.charAt(0).toLowerCase() + relatedModelName.slice(1) + "s",
20
20
  [relatedModel, relatedModelName]
21
21
  );
22
22
  const searchableFields = useMemo(() => {
@@ -69,6 +69,7 @@ function useRelationData(relatedModelName, searchTerm, isOpen, currentValueId) {
69
69
  return true;
70
70
  });
71
71
  } catch (error) {
72
+ console.error("Unexpected error:", error);
72
73
  return [];
73
74
  }
74
75
  }, [relatedData, relatedDataPath, relationError]);
@@ -60,8 +60,7 @@ function AdminDataLayout() {
60
60
  showNotification("success", "Preferences exported successfully");
61
61
  };
62
62
  const handleImport = async (event) => {
63
- var _a;
64
- const file = (_a = event.target.files) == null ? void 0 : _a[0];
63
+ const file = event.target.files?.[0];
65
64
  if (!file) return;
66
65
  try {
67
66
  const content2 = await file.text();
@@ -85,24 +84,50 @@ function AdminDataLayout() {
85
84
  }
86
85
  };
87
86
  const triggerImport = () => {
88
- var _a;
89
- (_a = fileInputRef.current) == null ? void 0 : _a.click();
87
+ fileInputRef.current?.click();
90
88
  };
91
89
  const content = /* @__PURE__ */ jsxs("div", { className: "max-w-full mx-auto flex flex-col p-0.5", children: [
92
- notification && /* @__PURE__ */ jsx("div", { className: `mb-4 px-4 py-3 rounded-md border ${notification.type === "success" ? "bg-green-50 border-green-200 text-green-800 dark:bg-green-900/20 dark:border-green-800 dark:text-green-200" : "bg-red-50 border-red-200 text-red-800 dark:bg-red-900/20 dark:border-red-800 dark:text-red-200"}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
93
- notification.type === "success" ? /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 mr-2", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 mr-2", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clipRule: "evenodd" }) }),
94
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: notification.message })
95
- ] }) }),
90
+ notification && /* @__PURE__ */ jsx(
91
+ "div",
92
+ {
93
+ className: `mb-4 px-4 py-3 rounded-md border ${notification.type === "success" ? "bg-green-50 border-green-200 text-green-800 dark:bg-green-900/20 dark:border-green-800 dark:text-green-200" : "bg-red-50 border-red-200 text-red-800 dark:bg-red-900/20 dark:border-red-800 dark:text-red-200"}`,
94
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
95
+ notification.type === "success" ? /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 mr-2", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx(
96
+ "path",
97
+ {
98
+ fillRule: "evenodd",
99
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z",
100
+ clipRule: "evenodd"
101
+ }
102
+ ) }) : /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 mr-2", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx(
103
+ "path",
104
+ {
105
+ fillRule: "evenodd",
106
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",
107
+ clipRule: "evenodd"
108
+ }
109
+ ) }),
110
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: notification.message })
111
+ ] })
112
+ }
113
+ ),
96
114
  /* @__PURE__ */ jsxs("div", { className: "mb-6 space-y-3", children: [
97
115
  /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-3", children: [
98
116
  /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
99
- /* @__PURE__ */ jsx("label", { htmlFor: "model-selector", className: "block text-sm font-medium text-gray-900 dark:text-gray-100 mb-2", children: "Select Model" }),
117
+ /* @__PURE__ */ jsx(
118
+ "label",
119
+ {
120
+ htmlFor: "model-selector",
121
+ className: "block text-sm font-medium text-gray-900 dark:text-gray-100 mb-2",
122
+ children: "Select Model"
123
+ }
124
+ ),
100
125
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
101
126
  /* @__PURE__ */ jsxs(
102
127
  "select",
103
128
  {
104
129
  id: "model-selector",
105
- value: (currentModel == null ? void 0 : currentModel.name) || "",
130
+ value: currentModel?.name || "",
106
131
  onChange: handleModelChange,
107
132
  className: "w-full h-[50px] pl-4 pr-10 py-3 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-green-web focus:border-green-web text-base appearance-none cursor-pointer",
108
133
  children: [
@@ -111,7 +136,23 @@ function AdminDataLayout() {
111
136
  ]
112
137
  }
113
138
  ),
114
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-gray-400", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) }) })
139
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3", children: /* @__PURE__ */ jsx(
140
+ "svg",
141
+ {
142
+ className: "h-5 w-5 text-gray-400",
143
+ xmlns: "http://www.w3.org/2000/svg",
144
+ viewBox: "0 0 20 20",
145
+ fill: "currentColor",
146
+ children: /* @__PURE__ */ jsx(
147
+ "path",
148
+ {
149
+ fillRule: "evenodd",
150
+ d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z",
151
+ clipRule: "evenodd"
152
+ }
153
+ )
154
+ }
155
+ ) })
115
156
  ] })
116
157
  ] }),
117
158
  /* @__PURE__ */ jsx(
@@ -120,7 +161,23 @@ function AdminDataLayout() {
120
161
  onClick: toggleFullscreen,
121
162
  className: "h-[50px] px-4 py-3 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-green-web focus:border-green-web transition-colors",
122
163
  title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
123
- children: isFullscreen ? /* @__PURE__ */ jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25" }) }) : /* @__PURE__ */ jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) })
164
+ children: isFullscreen ? /* @__PURE__ */ jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
165
+ "path",
166
+ {
167
+ strokeLinecap: "round",
168
+ strokeLinejoin: "round",
169
+ strokeWidth: 2,
170
+ d: "M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"
171
+ }
172
+ ) }) : /* @__PURE__ */ jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
173
+ "path",
174
+ {
175
+ strokeLinecap: "round",
176
+ strokeLinejoin: "round",
177
+ strokeWidth: 2,
178
+ d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
179
+ }
180
+ ) })
124
181
  }
125
182
  )
126
183
  ] }),
@@ -133,7 +190,15 @@ function AdminDataLayout() {
133
190
  className: "inline-flex items-center px-3 py-1.5 text-gray-700 dark:text-gray-300 hover:text-green-web dark:hover:text-green-web transition-colors",
134
191
  title: "Export your preferences to a file",
135
192
  children: [
136
- /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" }) }),
193
+ /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
194
+ "path",
195
+ {
196
+ strokeLinecap: "round",
197
+ strokeLinejoin: "round",
198
+ strokeWidth: 2,
199
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
200
+ }
201
+ ) }),
137
202
  "Export"
138
203
  ]
139
204
  }
@@ -146,7 +211,15 @@ function AdminDataLayout() {
146
211
  className: "inline-flex items-center px-3 py-1.5 text-gray-700 dark:text-gray-300 hover:text-green-web dark:hover:text-green-web transition-colors",
147
212
  title: "Import preferences from a file",
148
213
  children: [
149
- /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) }),
214
+ /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
215
+ "path",
216
+ {
217
+ strokeLinecap: "round",
218
+ strokeLinejoin: "round",
219
+ strokeWidth: 2,
220
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
221
+ }
222
+ ) }),
150
223
  "Import"
151
224
  ]
152
225
  }
@@ -1,4 +1,4 @@
1
- export declare function AdminDataCreatePage(): import("react/jsx-runtime").JSX.Element;
1
+ export declare function AdminDataCreatePage(): import("react/jsx-runtime").JSX.Element | null;
2
2
  export declare function AdminDataCreateErrorBoundary({ error }: Readonly<{
3
3
  error: Error;
4
4
  }>): import("react/jsx-runtime").JSX.Element;
@@ -7,20 +7,15 @@ import { ErrorBoundary } from "@nestledjs/shared-components";
7
7
  import { Form } from "@nestledjs/forms";
8
8
  import { useAdminDataContext } from "../context/AdminDataContext.js";
9
9
  import { useParams, Link, useNavigate } from "react-router";
10
- import { getAdminDocuments, buildFormFields, cleanFormInput } from "../utils/graphql-utils.js";
11
- function toReadableText(text) {
12
- return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
13
- }
14
- function sanitizeInput(input) {
15
- if (!input || typeof input !== "string") return "";
16
- return input.replace(/[<>"'%;()&+]/g, "").replace(/javascript:/gi, "").replace(/on\w+\s*=/gi, "").trim().substring(0, 100);
17
- }
18
- const toKebabCase = (str) => {
19
- return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
20
- };
10
+ import { sanitizeInput, getAdminDocuments, toKebabCase, buildFormFields, toReadableText, cleanFormInput } from "../utils/graphql-utils.js";
21
11
  function AdminDataCreatePage() {
22
12
  const { dataType } = useParams();
23
- const { databaseModels, basePath = "/admin/data", formTheme, displayFieldConfig } = useAdminDataContext();
13
+ const {
14
+ databaseModels,
15
+ basePath = "/admin/data",
16
+ formTheme,
17
+ displayFieldConfig
18
+ } = useAdminDataContext();
24
19
  const findModelByName = (name) => {
25
20
  return databaseModels.find((model2) => model2.name === name);
26
21
  };
@@ -69,9 +64,23 @@ function AdminDataCreatePage() {
69
64
  ) })
70
65
  ] }) }) }) });
71
66
  }
72
- return /* @__PURE__ */ jsx(AdminDataCreatePageContent, { model, basePath, formTheme, displayFieldConfig });
67
+ if (!model) return null;
68
+ return /* @__PURE__ */ jsx(
69
+ AdminDataCreatePageContent,
70
+ {
71
+ model,
72
+ basePath,
73
+ formTheme,
74
+ displayFieldConfig
75
+ }
76
+ );
73
77
  }
74
- function AdminDataCreatePageContent({ model, basePath, formTheme, displayFieldConfig }) {
78
+ function AdminDataCreatePageContent({
79
+ model,
80
+ basePath,
81
+ formTheme,
82
+ displayFieldConfig
83
+ }) {
75
84
  const navigate = useNavigate();
76
85
  const { sdk, databaseModels } = useAdminDataContext();
77
86
  const [submissionState, setSubmissionState] = useState({ status: "idle" });
@@ -79,18 +88,19 @@ function AdminDataCreatePageContent({ model, basePath, formTheme, displayFieldCo
79
88
  try {
80
89
  return getAdminDocuments(sdk, model);
81
90
  } catch (error) {
91
+ console.error("Unexpected error:", error);
82
92
  return null;
83
93
  }
84
94
  }, [sdk, model]);
85
95
  const CREATE_MUTATION = useMemo(() => {
86
- var _a, _b;
87
- if (!(documents == null ? void 0 : documents.create)) return null;
96
+ if (!documents?.create) return null;
88
97
  try {
89
- if (((_a = documents.create) == null ? void 0 : _a.kind) === "Document" && ((_b = documents.create) == null ? void 0 : _b.definitions)) {
98
+ if (documents.create?.kind === "Document" && documents.create?.definitions) {
90
99
  return documents.create;
91
100
  }
92
101
  return gql(documents.create);
93
102
  } catch (error) {
103
+ console.error("Unexpected error:", error);
94
104
  return null;
95
105
  }
96
106
  }, [documents]);
@@ -101,6 +111,21 @@ function AdminDataCreatePageContent({ model, basePath, formTheme, displayFieldCo
101
111
  }
102
112
  `
103
113
  );
114
+ useEffect(() => {
115
+ if (submissionState.status !== "success") return;
116
+ const timer = setTimeout(() => {
117
+ navigate(`${basePath}/${toKebabCase(model.pluralName)}`);
118
+ }, 1500);
119
+ return () => clearTimeout(timer);
120
+ }, [submissionState.status, navigate, basePath, model.pluralName]);
121
+ useEffect(() => {
122
+ if (submissionState.status === "error") {
123
+ const timer = setTimeout(() => {
124
+ setSubmissionState({ status: "idle" });
125
+ }, 5e3);
126
+ return () => clearTimeout(timer);
127
+ }
128
+ }, [submissionState.status]);
104
129
  if (!documents || !CREATE_MUTATION) {
105
130
  return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsx("div", { className: "mt-8 sm:mx-auto sm:w-full sm:max-w-2xl", children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 py-8 px-4 shadow sm:rounded-lg sm:px-10", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
106
131
  /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "mx-auto h-12 w-12 text-red-400" }),
@@ -142,9 +167,6 @@ function AdminDataCreatePageContent({ model, basePath, formTheme, displayFieldCo
142
167
  status: "success",
143
168
  message: `${toReadableText(model.name)} created successfully!`
144
169
  });
145
- setTimeout(() => {
146
- navigate(`${basePath}/${toKebabCase(model.pluralName)}`);
147
- }, 1500);
148
170
  } catch (error) {
149
171
  setSubmissionState({
150
172
  status: "error",
@@ -152,18 +174,17 @@ function AdminDataCreatePageContent({ model, basePath, formTheme, displayFieldCo
152
174
  });
153
175
  }
154
176
  };
155
- useEffect(() => {
156
- if (submissionState.status === "error") {
157
- const timer = setTimeout(() => {
158
- setSubmissionState({ status: "idle" });
159
- }, 5e3);
160
- return () => clearTimeout(timer);
161
- }
162
- }, [submissionState.status]);
163
177
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
164
178
  /* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
165
179
  /* @__PURE__ */ jsx("nav", { className: "flex mb-6", "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsxs("ol", { className: "flex items-center space-x-4", children: [
166
- /* @__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" }) }),
180
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
181
+ Link,
182
+ {
183
+ to: basePath,
184
+ className: "text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400",
185
+ children: "Data Browser"
186
+ }
187
+ ) }),
167
188
  /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
168
189
  /* @__PURE__ */ jsx(
169
190
  "svg",
@@ -215,22 +236,24 @@ function AdminDataCreatePageContent({ model, basePath, formTheme, displayFieldCo
215
236
  toReadableText(model.name)
216
237
  ] })
217
238
  ] }),
218
- submissionState.status !== "idle" && /* @__PURE__ */ jsx(
219
- "div",
220
- {
221
- className: `mb-6 rounded-md p-4 ${submissionState.status === "success" ? "bg-green-50 border border-green-200" : submissionState.status === "error" ? "bg-red-50 border border-red-200" : "bg-blue-50 border border-blue-200"}`,
222
- children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
223
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: submissionState.status === "success" ? /* @__PURE__ */ jsx(CheckCircleIcon, { className: "h-5 w-5 text-green-400" }) : submissionState.status === "error" ? /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "h-5 w-5 text-red-400" }) : /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" }) }),
224
- /* @__PURE__ */ jsx("div", { className: "ml-3", children: /* @__PURE__ */ jsx(
225
- "p",
226
- {
227
- className: `text-sm font-medium ${submissionState.status === "success" ? "text-green-800" : submissionState.status === "error" ? "text-red-800" : "text-blue-800"}`,
228
- children: submissionState.status === "loading" ? `Creating ${toReadableText(model.name)}...` : submissionState.message
229
- }
230
- ) })
231
- ] })
232
- }
233
- ),
239
+ submissionState.status !== "idle" && (() => {
240
+ const isSuccess = submissionState.status === "success";
241
+ const isError = submissionState.status === "error";
242
+ let bgClass = "bg-blue-50 border border-blue-200";
243
+ if (isSuccess) bgClass = "bg-green-50 border border-green-200";
244
+ else if (isError) bgClass = "bg-red-50 border border-red-200";
245
+ let textClass = "text-blue-800";
246
+ if (isSuccess) textClass = "text-green-800";
247
+ else if (isError) textClass = "text-red-800";
248
+ let icon = /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" });
249
+ if (isSuccess) icon = /* @__PURE__ */ jsx(CheckCircleIcon, { className: "h-5 w-5 text-green-400" });
250
+ else if (isError) icon = /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "h-5 w-5 text-red-400" });
251
+ const message = submissionState.status === "loading" ? `Creating ${toReadableText(model.name)}...` : submissionState.message;
252
+ return /* @__PURE__ */ jsx("div", { className: `mb-6 rounded-md p-4 ${bgClass}`, children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
253
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: icon }),
254
+ /* @__PURE__ */ jsx("div", { className: "ml-3", children: /* @__PURE__ */ jsx("p", { className: `text-sm font-medium ${textClass}`, children: message }) })
255
+ ] }) });
256
+ })(),
234
257
  /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 shadow-sm rounded-lg", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(
235
258
  Form,
236
259
  {