@nestledjs/data-browser 0.1.1 → 0.1.4
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/index.d.ts +15 -0
- package/index.js +64 -0
- package/lib/admin-data.d.ts +2 -0
- package/lib/components/filters/DateRangeFilter.d.ts +7 -0
- package/lib/components/filters/DateRangeFilter.js +105 -0
- package/lib/components/filters/NumberRangeFilter.d.ts +8 -0
- package/lib/components/filters/NumberRangeFilter.js +115 -0
- package/lib/components/filters/RelationComponents.d.ts +32 -0
- package/lib/components/filters/RelationComponents.js +265 -0
- package/lib/components/filters/RelationFilterField.d.ts +8 -0
- package/lib/components/filters/RelationFilterField.js +116 -0
- package/lib/components/filters/index.d.ts +4 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/shared/AdminBreadcrumbs.d.ts +13 -0
- package/lib/components/shared/AdminBreadcrumbs.js +103 -0
- package/lib/components/shared/AdminErrorStates.d.ts +30 -0
- package/lib/components/shared/AdminErrorStates.js +214 -0
- package/lib/components/shared/AdminStatusDisplay.d.ts +21 -0
- package/lib/components/shared/AdminStatusDisplay.js +205 -0
- package/lib/components/shared/index.d.ts +3 -0
- package/lib/context/AdminDataContext.d.ts +47 -0
- package/lib/context/AdminDataContext.js +28 -0
- package/lib/hooks/useAdminList.d.ts +5 -0
- package/lib/hooks/useAdminList.js +41 -0
- package/lib/hooks/useClickOutside.d.ts +2 -0
- package/lib/hooks/useClickOutside.js +13 -0
- package/lib/hooks/useDebounce.d.ts +1 -0
- package/lib/hooks/useDebounce.js +13 -0
- package/lib/hooks/useRelationData.d.ts +7 -0
- package/lib/hooks/useRelationData.js +64 -0
- package/lib/layouts/AdminDataLayout.d.ts +5 -0
- package/lib/layouts/AdminDataLayout.js +315 -0
- package/lib/pages/AdminDataCreatePage.d.ts +4 -0
- package/lib/pages/AdminDataCreatePage.js +487 -0
- package/lib/pages/AdminDataEditPage.d.ts +4 -0
- package/lib/pages/AdminDataEditPage.js +903 -0
- package/lib/pages/AdminDataIndexPage.d.ts +4 -0
- package/lib/pages/AdminDataIndexPage.js +91 -0
- package/lib/pages/AdminDataListPage.d.ts +9 -0
- package/lib/pages/AdminDataListPage.js +1218 -0
- package/lib/types/index.d.ts +60 -0
- package/lib/types/index.js +16 -0
- package/lib/utils/graphql-utils.d.ts +25 -0
- package/lib/utils/graphql-utils.js +265 -0
- package/lib/utils/secure-storage.d.ts +50 -0
- package/lib/utils/secure-storage.js +145 -0
- package/lib/utils/string-utils.d.ts +6 -0
- package/lib/utils/string-utils.js +27 -0
- package/package.json +16 -3
- package/.babelrc +0 -12
- package/LICENSE +0 -21
- package/eslint.config.mjs +0 -12
- package/project.json +0 -35
- package/src/index.ts +0 -28
- package/src/lib/admin-data.spec.tsx +0 -10
- package/src/lib/admin-data.tsx +0 -9
- package/src/lib/components/filters/DateRangeFilter.tsx +0 -88
- package/src/lib/components/filters/NumberRangeFilter.tsx +0 -111
- package/src/lib/components/filters/RelationComponents.tsx +0 -176
- package/src/lib/components/filters/RelationFilterField.tsx +0 -106
- package/src/lib/components/filters/index.ts +0 -5
- package/src/lib/components/index.ts +0 -2
- package/src/lib/components/shared/AdminBreadcrumbs.tsx +0 -88
- package/src/lib/components/shared/AdminErrorStates.tsx +0 -180
- package/src/lib/components/shared/AdminStatusDisplay.tsx +0 -292
- package/src/lib/components/shared/index.ts +0 -3
- package/src/lib/context/AdminDataContext.tsx +0 -74
- package/src/lib/hooks/useAdminList.ts +0 -42
- package/src/lib/hooks/useClickOutside.ts +0 -21
- package/src/lib/hooks/useDebounce.ts +0 -16
- package/src/lib/hooks/useRelationData.ts +0 -114
- package/src/lib/layouts/AdminDataLayout.tsx +0 -251
- package/src/lib/pages/AdminDataCreatePage.tsx +0 -415
- package/src/lib/pages/AdminDataEditPage.tsx +0 -777
- package/src/lib/pages/AdminDataIndexPage.tsx +0 -50
- package/src/lib/pages/AdminDataListPage.tsx +0 -921
- package/src/lib/types/index.ts +0 -51
- package/src/lib/utils/graphql-utils.ts +0 -538
- package/src/lib/utils/secure-storage.ts +0 -305
- package/src/lib/utils/string-utils.ts +0 -53
- package/tsconfig.json +0 -17
- package/tsconfig.lib.json +0 -20
- package/vite.config.ts +0 -35
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface AdminListState {
|
|
2
|
+
search: string | null;
|
|
3
|
+
debouncedSearch: string | null;
|
|
4
|
+
skip: number;
|
|
5
|
+
pageSize: number;
|
|
6
|
+
sort: {
|
|
7
|
+
orderBy: string;
|
|
8
|
+
orderDirection: string;
|
|
9
|
+
};
|
|
10
|
+
visibleColumns: string[];
|
|
11
|
+
showColumnSelector: boolean;
|
|
12
|
+
searchFields: string[];
|
|
13
|
+
showSearchFieldSelector: boolean;
|
|
14
|
+
filters: Record<string, unknown>;
|
|
15
|
+
showFilters: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type AdminListAction = {
|
|
18
|
+
type: 'SET_SEARCH';
|
|
19
|
+
payload: string | null;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'SET_DEBOUNCED_SEARCH';
|
|
22
|
+
payload: string | null;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'SET_SKIP';
|
|
25
|
+
payload: number;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'SET_PAGE_SIZE';
|
|
28
|
+
payload: number;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'SET_SORT';
|
|
31
|
+
payload: {
|
|
32
|
+
orderBy: string;
|
|
33
|
+
orderDirection: string;
|
|
34
|
+
};
|
|
35
|
+
} | {
|
|
36
|
+
type: 'SET_VISIBLE_COLUMNS';
|
|
37
|
+
payload: string[];
|
|
38
|
+
} | {
|
|
39
|
+
type: 'TOGGLE_COLUMN_SELECTOR';
|
|
40
|
+
} | {
|
|
41
|
+
type: 'SET_SEARCH_FIELDS';
|
|
42
|
+
payload: string[];
|
|
43
|
+
} | {
|
|
44
|
+
type: 'TOGGLE_SEARCH_FIELD_SELECTOR';
|
|
45
|
+
} | {
|
|
46
|
+
type: 'SET_FILTERS';
|
|
47
|
+
payload: Record<string, unknown>;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'TOGGLE_FILTERS';
|
|
50
|
+
} | {
|
|
51
|
+
type: 'RESET_PAGINATION';
|
|
52
|
+
} | {
|
|
53
|
+
type: 'RESET_FILTERS';
|
|
54
|
+
};
|
|
55
|
+
export declare const initialState: AdminListState;
|
|
56
|
+
export interface DatabaseModel {
|
|
57
|
+
name: string;
|
|
58
|
+
fields: any[];
|
|
59
|
+
pluralModelPropertyName?: string;
|
|
60
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const e = {
|
|
2
|
+
search: "",
|
|
3
|
+
debouncedSearch: "",
|
|
4
|
+
skip: 0,
|
|
5
|
+
pageSize: 20,
|
|
6
|
+
sort: { orderBy: "id", orderDirection: "desc" },
|
|
7
|
+
visibleColumns: [],
|
|
8
|
+
showColumnSelector: !1,
|
|
9
|
+
searchFields: [],
|
|
10
|
+
showSearchFieldSelector: !1,
|
|
11
|
+
filters: {},
|
|
12
|
+
showFilters: !1
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
e as initialState
|
|
16
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FormField } from '@nestledjs/forms';
|
|
2
|
+
import { DatabaseModel } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Get GraphQL documents for admin CRUD operations
|
|
5
|
+
*/
|
|
6
|
+
export declare function getAdminDocuments(sdk: any, model: DatabaseModel): {
|
|
7
|
+
query: any;
|
|
8
|
+
listQuery: any;
|
|
9
|
+
update: any;
|
|
10
|
+
delete: any;
|
|
11
|
+
create: any;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Get the GraphQL mutation name for a given operation
|
|
15
|
+
*/
|
|
16
|
+
export declare function getMutationName(model: DatabaseModel, operation: 'create' | 'update' | 'delete'): string;
|
|
17
|
+
/**
|
|
18
|
+
* Build form fields for a model and operation
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildFormFields(sdk: any, model: DatabaseModel, operation: 'create' | 'update', currentItem?: any, isSubmitting?: boolean): FormField[];
|
|
21
|
+
/**
|
|
22
|
+
* Clean form input data for GraphQL mutations
|
|
23
|
+
* Removes Apollo metadata and system fields
|
|
24
|
+
*/
|
|
25
|
+
export declare function cleanFormInput(input: Record<string, unknown>, model?: DatabaseModel): Record<string, unknown>;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { FormFieldClass as u } from "@nestledjs/forms";
|
|
2
|
+
import { getPluralName as j } from "@nestledjs/helpers";
|
|
3
|
+
function S(t, a) {
|
|
4
|
+
try {
|
|
5
|
+
const n = t[a];
|
|
6
|
+
if (!n || typeof n != "object")
|
|
7
|
+
return null;
|
|
8
|
+
const r = Object.values(n).filter((l) => typeof l == "string");
|
|
9
|
+
if (r.length === 0) {
|
|
10
|
+
const l = Object.keys(n).filter((b) => isNaN(Number(b)));
|
|
11
|
+
return l.length > 0 ? l : null;
|
|
12
|
+
}
|
|
13
|
+
return r;
|
|
14
|
+
} catch (n) {
|
|
15
|
+
return console.warn(`Failed to get enum values for type ${a}:`, n), null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function O(t) {
|
|
19
|
+
return t === t.toUpperCase() && t.length > 1 ? t.charAt(0).toUpperCase() + t.slice(1).toLowerCase() : t;
|
|
20
|
+
}
|
|
21
|
+
function G(t, a) {
|
|
22
|
+
if (!a || !a.name)
|
|
23
|
+
throw new Error("Invalid model provided to getAdminDocuments");
|
|
24
|
+
const n = O(a.name), r = O(j(a.name)), l = `__Admin${n}Document`, b = `__Admin${r}Document`, g = `__AdminUpdate${n}Document`, i = `__AdminDelete${n}Document`, m = `__AdminCreate${n}Document`, c = {
|
|
25
|
+
query: t[l],
|
|
26
|
+
// For single item
|
|
27
|
+
listQuery: t[b],
|
|
28
|
+
// For lists
|
|
29
|
+
update: t[g],
|
|
30
|
+
delete: t[i],
|
|
31
|
+
create: t[m]
|
|
32
|
+
}, e = [];
|
|
33
|
+
if (c.query || e.push(l), c.listQuery || e.push(b), c.create || e.push(m), c.update || e.push(g), c.delete || e.push(i), e.length > 0)
|
|
34
|
+
throw console.error(`[GraphQL Documents] Missing documents for model "${a.name}":`, {
|
|
35
|
+
model: a.name,
|
|
36
|
+
normalizedModelName: n,
|
|
37
|
+
normalizedPluralName: r,
|
|
38
|
+
missingDocuments: e,
|
|
39
|
+
availableDocuments: Object.keys(t).filter(
|
|
40
|
+
(h) => h.includes("Admin") && h.includes("Document")
|
|
41
|
+
)
|
|
42
|
+
}), new Error(
|
|
43
|
+
`Missing GraphQL documents for model "${a.name}": ${e.join(", ")}. Please ensure the API server is running and the GraphQL schema is up to date.`
|
|
44
|
+
);
|
|
45
|
+
return c;
|
|
46
|
+
}
|
|
47
|
+
function z(t, a) {
|
|
48
|
+
const n = t.name.charAt(0).toLowerCase() + t.name.slice(1);
|
|
49
|
+
switch (a) {
|
|
50
|
+
case "create":
|
|
51
|
+
return `create${t.name}`;
|
|
52
|
+
case "update":
|
|
53
|
+
return `update${t.name}`;
|
|
54
|
+
case "delete":
|
|
55
|
+
return `delete${t.name}`;
|
|
56
|
+
default:
|
|
57
|
+
return n;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function I(t, a, n, r, l) {
|
|
61
|
+
const g = a.fields.filter((e) => !(n === "create" && e.isId || e.isReadOnly || e.isGenerated || e.isUpdatedAt || e.name === "createdAt" || e.relationName && e.isList)).map((e) => {
|
|
62
|
+
var $;
|
|
63
|
+
const h = e.name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (d) => d.toUpperCase());
|
|
64
|
+
let o = r && n === "update" ? r[e.name] : void 0;
|
|
65
|
+
if ((e.type.toLowerCase() === "datetime" || e.type.toLowerCase() === "date") && (o instanceof Date || o && typeof o == "string"))
|
|
66
|
+
try {
|
|
67
|
+
const d = o instanceof Date ? o : new Date(o);
|
|
68
|
+
e.type.toLowerCase() === "date" ? o = d.toISOString().split("T")[0] : o = d.toISOString().substring(0, 16);
|
|
69
|
+
} catch (d) {
|
|
70
|
+
console.warn(`Failed to convert date value for field ${e.name}:`, d), o = "";
|
|
71
|
+
}
|
|
72
|
+
if (o && typeof o == "object" && !Array.isArray(o) && e.relationName) {
|
|
73
|
+
const d = o;
|
|
74
|
+
d.id && typeof d.id == "string" ? o = d.id : o = "";
|
|
75
|
+
}
|
|
76
|
+
o === null && e.type.toLowerCase() !== "boolean" && (o = "");
|
|
77
|
+
const _ = !!e.isList ? !1 : !e.isOptional, p = {
|
|
78
|
+
label: h,
|
|
79
|
+
required: _,
|
|
80
|
+
...o !== void 0 && { value: o }
|
|
81
|
+
};
|
|
82
|
+
switch (e.type.toLowerCase()) {
|
|
83
|
+
case "string":
|
|
84
|
+
return e.name.toLowerCase().includes("email") ? u.email(e.name, p) : e.name.toLowerCase().includes("description") || e.name.toLowerCase().includes("content") || e.name.toLowerCase().includes("notes") ? u.textArea(e.name, p) : u.text(e.name, p);
|
|
85
|
+
case "int":
|
|
86
|
+
case "bigint":
|
|
87
|
+
return u.text(e.name, p);
|
|
88
|
+
case "float":
|
|
89
|
+
case "decimal":
|
|
90
|
+
return u.text(e.name, p);
|
|
91
|
+
case "boolean": {
|
|
92
|
+
const f = r && n === "update" ? !!r[e.name] : !1;
|
|
93
|
+
return u.checkbox(e.name, {
|
|
94
|
+
...p,
|
|
95
|
+
required: !1,
|
|
96
|
+
...n === "update" && { value: f }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
case "datetime":
|
|
100
|
+
return u.dateTimePicker(e.name, p);
|
|
101
|
+
case "date":
|
|
102
|
+
return u.datePicker(e.name, p);
|
|
103
|
+
default:
|
|
104
|
+
const d = S(t, e.type);
|
|
105
|
+
if (d) {
|
|
106
|
+
const f = d.map((s) => ({
|
|
107
|
+
value: s,
|
|
108
|
+
label: s.replace(/_/g, " ").toLowerCase().replace(/^./, (C) => C.toUpperCase())
|
|
109
|
+
}));
|
|
110
|
+
return u.select(e.name, {
|
|
111
|
+
...p,
|
|
112
|
+
options: f
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (e.relationName && !e.isList) {
|
|
116
|
+
const f = (($ = e.relationFromFields) == null ? void 0 : $[0]) || e.name;
|
|
117
|
+
let s = r && n === "update" ? r[f] : void 0;
|
|
118
|
+
s && typeof s == "object" && s.id && (s = s.id), s === null && (s = "");
|
|
119
|
+
const C = `__Admin${e.type}sDocument`, F = `${e.type}sDocument`, N = t[C] || t[F];
|
|
120
|
+
if (N) {
|
|
121
|
+
let w = "id";
|
|
122
|
+
e.type === "Course" ? w = "title" : e.type === "User" || e.type === "Program" ? w = "name" : w = "title";
|
|
123
|
+
let A = [];
|
|
124
|
+
if (r && n === "update") {
|
|
125
|
+
const y = r[e.name];
|
|
126
|
+
if (y && typeof y == "object" && y.id) {
|
|
127
|
+
const L = y[w] || y.title || y.name || y.id;
|
|
128
|
+
A = [
|
|
129
|
+
{
|
|
130
|
+
value: y.id,
|
|
131
|
+
label: L
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return u.searchSelectApollo(f, {
|
|
137
|
+
label: h,
|
|
138
|
+
// Remove "ID" suffix - just use the field name
|
|
139
|
+
required: p.required,
|
|
140
|
+
dataType: e.type.toLowerCase() + "s",
|
|
141
|
+
// e.g., Course → courses
|
|
142
|
+
document: N,
|
|
143
|
+
searchFields: [w],
|
|
144
|
+
// For searching
|
|
145
|
+
selectOptionsFunction: (y) => {
|
|
146
|
+
const L = y.map((D) => ({
|
|
147
|
+
value: D.id,
|
|
148
|
+
label: D[w] || D.title || D.name || D.id
|
|
149
|
+
})), v = [...A];
|
|
150
|
+
return L.forEach((D) => {
|
|
151
|
+
v.find((x) => x.value === D.value) || v.push(D);
|
|
152
|
+
}), v;
|
|
153
|
+
},
|
|
154
|
+
...A.length > 0 && { initialOptions: A },
|
|
155
|
+
// Provide initial options if available
|
|
156
|
+
...s !== void 0 && { value: s }
|
|
157
|
+
});
|
|
158
|
+
} else
|
|
159
|
+
return console.warn(
|
|
160
|
+
`GraphQL document ${C} or ${F} not found for relation ${e.type}. Using text input instead.`
|
|
161
|
+
), u.text(f, {
|
|
162
|
+
label: `${h} ID`,
|
|
163
|
+
required: p.required,
|
|
164
|
+
helpText: "Enter the ID of the related record",
|
|
165
|
+
...s !== void 0 && { value: s }
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (e.kind === "enum" && e.enumValues) {
|
|
169
|
+
const f = e.enumValues.map((s) => ({
|
|
170
|
+
value: s,
|
|
171
|
+
label: s.replace(/_/g, " ").toLowerCase().replace(/^./, (C) => C.toUpperCase())
|
|
172
|
+
}));
|
|
173
|
+
return u.select(e.name, {
|
|
174
|
+
...p,
|
|
175
|
+
options: f
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return u.text(e.name, p);
|
|
179
|
+
}
|
|
180
|
+
}), c = l ? n === "create" ? "Creating..." : "Updating..." : n === "create" ? "Create" : "Update";
|
|
181
|
+
return g.push(
|
|
182
|
+
u.button("submit", {
|
|
183
|
+
text: c,
|
|
184
|
+
type: "submit",
|
|
185
|
+
variant: "primary",
|
|
186
|
+
disabled: l
|
|
187
|
+
})
|
|
188
|
+
), g;
|
|
189
|
+
}
|
|
190
|
+
const V = /* @__PURE__ */ new Set([
|
|
191
|
+
"__typename",
|
|
192
|
+
// Apollo type annotation
|
|
193
|
+
"id",
|
|
194
|
+
// Usually passed as separate variable
|
|
195
|
+
"createdAt",
|
|
196
|
+
// Auto-managed timestamp
|
|
197
|
+
"updatedAt",
|
|
198
|
+
// Auto-managed timestamp
|
|
199
|
+
"_count",
|
|
200
|
+
// Prisma count metadata
|
|
201
|
+
"_meta"
|
|
202
|
+
// Prisma metadata
|
|
203
|
+
]);
|
|
204
|
+
function U(t, a) {
|
|
205
|
+
return V.has(t) || a === void 0;
|
|
206
|
+
}
|
|
207
|
+
function q(t, a) {
|
|
208
|
+
if (t === "")
|
|
209
|
+
return null;
|
|
210
|
+
if ((a == null ? void 0 : a.type.toLowerCase()) === "datetime" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(t))
|
|
211
|
+
try {
|
|
212
|
+
return new Date(t).toISOString();
|
|
213
|
+
} catch {
|
|
214
|
+
return console.warn(`Failed to convert datetime-local value: ${t}`), t;
|
|
215
|
+
}
|
|
216
|
+
if (t === "true") return !0;
|
|
217
|
+
if (t === "false") return !1;
|
|
218
|
+
if (/^\d+(\.\d+)?$/.test(t)) {
|
|
219
|
+
const r = Number(t);
|
|
220
|
+
if (!isNaN(r))
|
|
221
|
+
return r;
|
|
222
|
+
}
|
|
223
|
+
return t;
|
|
224
|
+
}
|
|
225
|
+
function T(t, a) {
|
|
226
|
+
const n = P(t, a);
|
|
227
|
+
return Object.keys(n).length > 0 ? n : null;
|
|
228
|
+
}
|
|
229
|
+
function P(t, a) {
|
|
230
|
+
var l, b, g;
|
|
231
|
+
const n = {}, r = new Set(
|
|
232
|
+
((b = (l = a == null ? void 0 : a.fields) == null ? void 0 : l.filter((i) => i.type.toLowerCase() === "boolean")) == null ? void 0 : b.map((i) => i.name)) || []
|
|
233
|
+
);
|
|
234
|
+
for (const [i, m] of Object.entries(t)) {
|
|
235
|
+
if (r.has(i) && m === void 0) {
|
|
236
|
+
n[i] = !1;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (!U(i, m)) {
|
|
240
|
+
if (typeof m == "string") {
|
|
241
|
+
const c = (g = a == null ? void 0 : a.fields) == null ? void 0 : g.find((h) => h.name === i), e = q(m, c);
|
|
242
|
+
e !== null && (n[i] = e);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (typeof m == "object" && m !== null && !Array.isArray(m)) {
|
|
246
|
+
const c = m;
|
|
247
|
+
if (c.value !== void 0 && c.label !== void 0 && typeof c.value == "string") {
|
|
248
|
+
n[i] = c.value;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const e = T(c, a);
|
|
252
|
+
e !== null && (n[i] = e);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
n[i] = m;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return n;
|
|
259
|
+
}
|
|
260
|
+
export {
|
|
261
|
+
I as buildFormFields,
|
|
262
|
+
P as cleanFormInput,
|
|
263
|
+
G as getAdminDocuments,
|
|
264
|
+
z as getMutationName
|
|
265
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface AdminConfig {
|
|
2
|
+
version: string;
|
|
3
|
+
models: Record<string, {
|
|
4
|
+
visibleColumns?: string[];
|
|
5
|
+
searchFields?: string[];
|
|
6
|
+
sortPreference?: {
|
|
7
|
+
orderBy: string;
|
|
8
|
+
orderDirection: string;
|
|
9
|
+
};
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare const SecureAdminLocalStorage: {
|
|
13
|
+
getConfig: () => AdminConfig;
|
|
14
|
+
setConfig: (config: AdminConfig) => boolean;
|
|
15
|
+
getColumnVisibility: (modelName: string) => string[] | null;
|
|
16
|
+
setColumnVisibility: (modelName: string, visibleColumns: string[]) => boolean;
|
|
17
|
+
getSortPreference: (modelName: string) => {
|
|
18
|
+
orderBy: string;
|
|
19
|
+
orderDirection: string;
|
|
20
|
+
} | null;
|
|
21
|
+
setSortPreference: (modelName: string, sortPreference: {
|
|
22
|
+
orderBy: string;
|
|
23
|
+
orderDirection: string;
|
|
24
|
+
}) => boolean;
|
|
25
|
+
getSearchFields: (modelName: string) => string[] | null;
|
|
26
|
+
setSearchFields: (modelName: string, searchFields: string[]) => boolean;
|
|
27
|
+
exportConfig: () => string | null;
|
|
28
|
+
importConfig: (configJson: string) => boolean;
|
|
29
|
+
clearConfig: () => boolean;
|
|
30
|
+
};
|
|
31
|
+
export declare const AdminLocalStorage: {
|
|
32
|
+
getConfig: () => AdminConfig;
|
|
33
|
+
setConfig: (config: AdminConfig) => boolean;
|
|
34
|
+
getColumnVisibility: (modelName: string) => string[] | null;
|
|
35
|
+
setColumnVisibility: (modelName: string, visibleColumns: string[]) => boolean;
|
|
36
|
+
getSortPreference: (modelName: string) => {
|
|
37
|
+
orderBy: string;
|
|
38
|
+
orderDirection: string;
|
|
39
|
+
} | null;
|
|
40
|
+
setSortPreference: (modelName: string, sortPreference: {
|
|
41
|
+
orderBy: string;
|
|
42
|
+
orderDirection: string;
|
|
43
|
+
}) => boolean;
|
|
44
|
+
getSearchFields: (modelName: string) => string[] | null;
|
|
45
|
+
setSearchFields: (modelName: string, searchFields: string[]) => boolean;
|
|
46
|
+
exportConfig: () => string | null;
|
|
47
|
+
importConfig: (configJson: string) => boolean;
|
|
48
|
+
clearConfig: () => boolean;
|
|
49
|
+
};
|
|
50
|
+
export type { AdminConfig };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const c = "mi-admin-config", a = "1.0";
|
|
2
|
+
const s = (e) => typeof e != "string" ? "" : e.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/javascript:/gi, "").replace(/on\w+\s*=/gi, "").trim().substring(0, 1e3), f = (e) => Array.isArray(e) ? e.slice(0, 100).map((o) => s(o)).filter((o) => o.length > 0) : [], g = (e) => {
|
|
3
|
+
if (!e || typeof e != "object") return null;
|
|
4
|
+
const o = e, n = s(o.orderBy), t = s(o.orderDirection);
|
|
5
|
+
return !["asc", "desc"].includes(t) || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(n) ? null : { orderBy: n, orderDirection: t };
|
|
6
|
+
}, d = (e) => {
|
|
7
|
+
if (!e || typeof e != "object") return !1;
|
|
8
|
+
const o = e;
|
|
9
|
+
if (o.version !== a || !o.models || typeof o.models != "object") return !1;
|
|
10
|
+
const n = o.models;
|
|
11
|
+
for (const [t, r] of Object.entries(n)) {
|
|
12
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(t)) return !1;
|
|
13
|
+
if (r && typeof r == "object") {
|
|
14
|
+
const l = r;
|
|
15
|
+
if (l.visibleColumns !== void 0 && !Array.isArray(l.visibleColumns)) return !1;
|
|
16
|
+
if (l.sortPreference !== void 0) {
|
|
17
|
+
const u = g(l.sortPreference);
|
|
18
|
+
if (l.sortPreference !== null && u === null) return !1;
|
|
19
|
+
}
|
|
20
|
+
if (l.searchFields !== void 0 && !Array.isArray(l.searchFields)) return !1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return !0;
|
|
24
|
+
}, i = {
|
|
25
|
+
// Get the full admin config with validation
|
|
26
|
+
getConfig: () => {
|
|
27
|
+
try {
|
|
28
|
+
const e = localStorage.getItem(c);
|
|
29
|
+
if (!e)
|
|
30
|
+
return { version: a, models: {} };
|
|
31
|
+
if (e.length > 5e4)
|
|
32
|
+
return console.warn("[AdminLocalStorage] Config exceeds size limit, resetting"), localStorage.removeItem(c), { version: a, models: {} };
|
|
33
|
+
const o = JSON.parse(e);
|
|
34
|
+
return d(o) ? o : (console.warn("[AdminLocalStorage] Invalid config detected, resetting"), localStorage.removeItem(c), { version: a, models: {} });
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.warn("[AdminLocalStorage] Failed to load config:", e);
|
|
37
|
+
try {
|
|
38
|
+
localStorage.removeItem(c);
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
return { version: a, models: {} };
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
// Save the full admin config with validation
|
|
45
|
+
setConfig: (e) => {
|
|
46
|
+
try {
|
|
47
|
+
if (!d(e))
|
|
48
|
+
return console.warn("[AdminLocalStorage] Invalid config provided"), !1;
|
|
49
|
+
const o = JSON.stringify(e);
|
|
50
|
+
return o.length > 5e4 ? (console.warn("[AdminLocalStorage] Config too large to store"), !1) : (localStorage.setItem(c, o), !0);
|
|
51
|
+
} catch (o) {
|
|
52
|
+
return console.warn("[AdminLocalStorage] Failed to save config:", o), !1;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
// Get visible columns for a specific model with sanitization
|
|
56
|
+
getColumnVisibility: (e) => {
|
|
57
|
+
var r;
|
|
58
|
+
const o = s(e);
|
|
59
|
+
if (!o) return null;
|
|
60
|
+
const t = (r = i.getConfig().models[o]) == null ? void 0 : r.visibleColumns;
|
|
61
|
+
return t ? f(t) : null;
|
|
62
|
+
},
|
|
63
|
+
// Set visible columns for a specific model with validation
|
|
64
|
+
setColumnVisibility: (e, o) => {
|
|
65
|
+
const n = s(e), t = f(o);
|
|
66
|
+
if (!n) return !1;
|
|
67
|
+
const r = i.getConfig();
|
|
68
|
+
return r.models[n] || (r.models[n] = {}), r.models[n].visibleColumns = t, i.setConfig(r);
|
|
69
|
+
},
|
|
70
|
+
// Get sort preference for a specific model with validation
|
|
71
|
+
getSortPreference: (e) => {
|
|
72
|
+
var r;
|
|
73
|
+
const o = s(e);
|
|
74
|
+
if (!o) return null;
|
|
75
|
+
const t = (r = i.getConfig().models[o]) == null ? void 0 : r.sortPreference;
|
|
76
|
+
return t ? g(t) : null;
|
|
77
|
+
},
|
|
78
|
+
// Set sort preference for a specific model with validation
|
|
79
|
+
setSortPreference: (e, o) => {
|
|
80
|
+
const n = s(e), t = g(o);
|
|
81
|
+
if (!n || !t) return !1;
|
|
82
|
+
const r = i.getConfig();
|
|
83
|
+
return r.models[n] || (r.models[n] = {}), r.models[n].sortPreference = t, i.setConfig(r);
|
|
84
|
+
},
|
|
85
|
+
// Get search fields for a specific model with sanitization
|
|
86
|
+
getSearchFields: (e) => {
|
|
87
|
+
var r;
|
|
88
|
+
const o = s(e);
|
|
89
|
+
if (!o) return null;
|
|
90
|
+
const t = (r = i.getConfig().models[o]) == null ? void 0 : r.searchFields;
|
|
91
|
+
return t ? f(t) : null;
|
|
92
|
+
},
|
|
93
|
+
// Set search fields for a specific model with validation
|
|
94
|
+
setSearchFields: (e, o) => {
|
|
95
|
+
const n = s(e), t = f(o);
|
|
96
|
+
if (!n) return !1;
|
|
97
|
+
const r = i.getConfig();
|
|
98
|
+
return r.models[n] || (r.models[n] = {}), r.models[n].searchFields = t, i.setConfig(r);
|
|
99
|
+
},
|
|
100
|
+
// Export config as JSON string with validation
|
|
101
|
+
exportConfig: () => {
|
|
102
|
+
try {
|
|
103
|
+
const e = i.getConfig();
|
|
104
|
+
return d(e) ? JSON.stringify(e, null, 2) : (console.warn("[AdminLocalStorage] Cannot export invalid config"), null);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return console.warn("[AdminLocalStorage] Failed to export config:", e), null;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
// Import config from JSON string with comprehensive security validation
|
|
110
|
+
importConfig: (e) => {
|
|
111
|
+
try {
|
|
112
|
+
const o = s(e);
|
|
113
|
+
if (!o || o.length > 5e4)
|
|
114
|
+
return console.warn("[AdminLocalStorage] Invalid or oversized config JSON"), !1;
|
|
115
|
+
const n = JSON.parse(o);
|
|
116
|
+
if (!d(n))
|
|
117
|
+
return console.warn("[AdminLocalStorage] Failed config validation during import"), !1;
|
|
118
|
+
const t = JSON.stringify(n), r = [
|
|
119
|
+
/<script/i,
|
|
120
|
+
/javascript:/i,
|
|
121
|
+
/on\w+\s*=/i,
|
|
122
|
+
/eval\s*\(/i,
|
|
123
|
+
/function\s*\(/i
|
|
124
|
+
];
|
|
125
|
+
for (const l of r)
|
|
126
|
+
if (l.test(t))
|
|
127
|
+
return console.warn("[AdminLocalStorage] Suspicious content detected in config"), !1;
|
|
128
|
+
return i.setConfig(n);
|
|
129
|
+
} catch (o) {
|
|
130
|
+
return console.warn("[AdminLocalStorage] Failed to import config:", o), !1;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
// Clear all stored data (for security/privacy)
|
|
134
|
+
clearConfig: () => {
|
|
135
|
+
try {
|
|
136
|
+
return localStorage.removeItem(c), !0;
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return console.warn("[AdminLocalStorage] Failed to clear config:", e), !1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, m = i;
|
|
142
|
+
export {
|
|
143
|
+
m as AdminLocalStorage,
|
|
144
|
+
i as SecureAdminLocalStorage
|
|
145
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function kebabCase(name: string): string;
|
|
2
|
+
export declare function spacedWords(name: string): string;
|
|
3
|
+
export declare function formatFieldName(fieldName: string): string;
|
|
4
|
+
export declare function normalizeModelNameForDocument(modelName: string): string;
|
|
5
|
+
export declare function getItemDisplayName(item: any): string;
|
|
6
|
+
export declare function getSmartSearchFields(availableFields: string[]): string[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function n(r) {
|
|
2
|
+
return r.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
function s(r) {
|
|
5
|
+
return r.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2");
|
|
6
|
+
}
|
|
7
|
+
function c(r) {
|
|
8
|
+
return r.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (e) => e.toUpperCase());
|
|
9
|
+
}
|
|
10
|
+
function i(r) {
|
|
11
|
+
return r === r.toUpperCase() && r.length > 1 ? r.charAt(0).toUpperCase() + r.slice(1).toLowerCase() : r;
|
|
12
|
+
}
|
|
13
|
+
function u(r) {
|
|
14
|
+
return r.name ? r.name : r.title ? r.title : r.firstName && r.lastName ? `${r.firstName} ${r.lastName}` : r.firstName ? r.firstName : r.email ? r.email : r.id;
|
|
15
|
+
}
|
|
16
|
+
function f(r) {
|
|
17
|
+
const t = ["name", "title", "email", "firstName", "lastName", "subject"].filter((a) => r.includes(a));
|
|
18
|
+
return t.length >= 1 ? t.slice(0, 2) : r.slice(0, Math.min(2, r.length));
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
c as formatFieldName,
|
|
22
|
+
u as getItemDisplayName,
|
|
23
|
+
f as getSmartSearchFields,
|
|
24
|
+
n as kebabCase,
|
|
25
|
+
i as normalizeModelNameForDocument,
|
|
26
|
+
s as spacedWords
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestledjs/data-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Universal admin data browser for Nestled framework projects with full CRUD operations",
|
|
5
5
|
"main": "./index.js",
|
|
6
|
+
"module": "./index.js",
|
|
6
7
|
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./index.js",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"index.d.ts",
|
|
18
|
+
"lib/**/*",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
7
21
|
"keywords": [
|
|
8
22
|
"nestled",
|
|
9
23
|
"admin",
|
|
@@ -25,7 +39,7 @@
|
|
|
25
39
|
"@heroicons/react": "^2.0.0",
|
|
26
40
|
"@nestledjs/forms": "^0.5.0",
|
|
27
41
|
"@nestledjs/helpers": "^0.1.0",
|
|
28
|
-
"@nestledjs/shared-components": "^0.1.
|
|
42
|
+
"@nestledjs/shared-components": "^0.1.4",
|
|
29
43
|
"react": "^19.0.0",
|
|
30
44
|
"react-router": "^7.0.0"
|
|
31
45
|
},
|
|
@@ -39,6 +53,5 @@
|
|
|
39
53
|
"access": "public",
|
|
40
54
|
"registry": "https://registry.npmjs.org/"
|
|
41
55
|
},
|
|
42
|
-
"module": "./index.js",
|
|
43
56
|
"type": "module"
|
|
44
57
|
}
|
package/.babelrc
DELETED
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Nestled Team
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/eslint.config.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import nx from '@nx/eslint-plugin'
|
|
2
|
-
import baseConfig from '../../eslint.config.mjs'
|
|
3
|
-
|
|
4
|
-
export default [
|
|
5
|
-
...baseConfig,
|
|
6
|
-
...nx.configs['flat/react'],
|
|
7
|
-
{
|
|
8
|
-
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
|
9
|
-
// Override or add rules here
|
|
10
|
-
rules: {},
|
|
11
|
-
},
|
|
12
|
-
]
|