@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
|
@@ -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(
|
|
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(
|
|
88
|
-
|
|
89
|
-
|
|
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 {
|
|
20
|
+
const {
|
|
21
|
+
relatedItems,
|
|
22
|
+
loading,
|
|
23
|
+
error: relationError,
|
|
24
|
+
hasDocument
|
|
25
|
+
} = useRelationData(
|
|
21
26
|
relatedModelName,
|
|
22
27
|
searchTerm,
|
|
23
28
|
isOpen,
|
|
24
|
-
currentValue
|
|
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
|
-
() =>
|
|
29
|
-
[currentValue
|
|
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:
|
|
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"
|
|
@@ -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(
|
|
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(
|
|
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/
|
|
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
|
-
() =>
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
93
|
-
|
|
94
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
87
|
-
if (!(documents == null ? void 0 : documents.create)) return null;
|
|
96
|
+
if (!documents?.create) return null;
|
|
88
97
|
try {
|
|
89
|
-
if (
|
|
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(
|
|
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" &&
|
|
219
|
-
"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
{
|