@nestledjs/data-browser 0.1.0

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 (35) hide show
  1. package/.babelrc +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +320 -0
  4. package/eslint.config.mjs +12 -0
  5. package/package.json +44 -0
  6. package/project.json +39 -0
  7. package/src/index.ts +28 -0
  8. package/src/lib/admin-data.spec.tsx +10 -0
  9. package/src/lib/admin-data.tsx +9 -0
  10. package/src/lib/components/filters/DateRangeFilter.tsx +88 -0
  11. package/src/lib/components/filters/NumberRangeFilter.tsx +111 -0
  12. package/src/lib/components/filters/RelationComponents.tsx +176 -0
  13. package/src/lib/components/filters/RelationFilterField.tsx +106 -0
  14. package/src/lib/components/filters/index.ts +5 -0
  15. package/src/lib/components/index.ts +2 -0
  16. package/src/lib/components/shared/AdminBreadcrumbs.tsx +88 -0
  17. package/src/lib/components/shared/AdminErrorStates.tsx +180 -0
  18. package/src/lib/components/shared/AdminStatusDisplay.tsx +292 -0
  19. package/src/lib/components/shared/index.ts +3 -0
  20. package/src/lib/context/AdminDataContext.tsx +74 -0
  21. package/src/lib/hooks/useAdminList.ts +42 -0
  22. package/src/lib/hooks/useClickOutside.ts +21 -0
  23. package/src/lib/hooks/useDebounce.ts +16 -0
  24. package/src/lib/hooks/useRelationData.ts +114 -0
  25. package/src/lib/layouts/AdminDataLayout.tsx +251 -0
  26. package/src/lib/pages/AdminDataCreatePage.tsx +415 -0
  27. package/src/lib/pages/AdminDataEditPage.tsx +777 -0
  28. package/src/lib/pages/AdminDataIndexPage.tsx +50 -0
  29. package/src/lib/pages/AdminDataListPage.tsx +921 -0
  30. package/src/lib/types/index.ts +51 -0
  31. package/src/lib/utils/graphql-utils.ts +538 -0
  32. package/src/lib/utils/secure-storage.ts +305 -0
  33. package/src/lib/utils/string-utils.ts +53 -0
  34. package/tsconfig.json +17 -0
  35. package/tsconfig.lib.json +20 -0
package/.babelrc ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "presets": [
3
+ [
4
+ "@nx/react/babel",
5
+ {
6
+ "runtime": "automatic",
7
+ "useBuiltIns": "usage"
8
+ }
9
+ ]
10
+ ],
11
+ "plugins": []
12
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
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/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # @nestledjs/data-browser
2
+
3
+ Universal admin data browser for Nestled framework projects with full CRUD operations, advanced filtering, and customizable views.
4
+
5
+ ## Features
6
+
7
+ - 🔍 **Auto-generated CRUD Interface** - Automatically generates admin UI for all Prisma models
8
+ - 📊 **Advanced Data Table** - Sorting, filtering, pagination, column selection
9
+ - 🔎 **Smart Search** - Multi-field text search with debouncing
10
+ - 📝 **Dynamic Forms** - Auto-generated create/edit forms from model schema
11
+ - 🎨 **Dark Mode Support** - Full dark mode theming
12
+ - 💾 **Persistent Preferences** - Saves column visibility, sort order, and search preferences per model
13
+ - 🔐 **Type-Safe** - Full TypeScript support with GraphQL code generation
14
+ - 📱 **Responsive** - Mobile-friendly with fullscreen mode
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @nestledjs/data-browser
20
+ # or
21
+ pnpm add @nestledjs/data-browser
22
+ # or
23
+ yarn add @nestledjs/data-browser
24
+ ```
25
+
26
+ ## Prerequisites
27
+
28
+ This package requires a Nestled framework project with:
29
+
30
+ - **Apollo Client v4+** for GraphQL operations
31
+ - **React Router v7+** for routing
32
+ - **@nestledjs/forms** for form generation
33
+ - **@nestledjs/helpers** for utility functions
34
+ - **Prisma** for database models
35
+ - **Generated GraphQL SDK** with admin CRUD operations
36
+
37
+ ### Peer Dependencies
38
+
39
+ ```json
40
+ {
41
+ "@apollo/client": "^4.0.0",
42
+ "@nestledjs/forms": "^0.5.0",
43
+ "@nestledjs/helpers": "^0.1.0",
44
+ "react": "^19.0.0",
45
+ "react-router": "^7.0.0"
46
+ }
47
+ ```
48
+
49
+ ### Required Project Components
50
+
51
+ Your Nestled project must also export:
52
+
53
+ 1. **Web UI Components** from `@your-project/web-ui`:
54
+ - `WebUiDataTable`
55
+ - `WebUiErrorBoundary`
56
+
57
+ 2. **Form Theme** from `@your-project/shared/styles`:
58
+ - `formTheme`
59
+
60
+ 3. **GraphQL SDK** from `@your-project/shared/sdk`:
61
+ - `DATABASE_MODELS` (auto-generated model metadata)
62
+ - GraphQL documents with `__Admin*Document` naming
63
+
64
+ ## Quick Start
65
+
66
+ ### Step 1: Install
67
+
68
+ ```bash
69
+ pnpm add @nestledjs/data-browser
70
+ ```
71
+
72
+ ### Step 2: Update Import Paths
73
+
74
+ Since this package imports from your project's namespaced packages, find and replace in the source:
75
+
76
+ ```
77
+ @nestled-template → @your-project-name
78
+ ```
79
+
80
+ This affects 3 files in `libs/admin-data/src/lib/pages/`.
81
+
82
+ ### Step 3: Create Route Wrapper
83
+
84
+ Create `apps/web/app/routes/admin/data/_layout.tsx`:
85
+
86
+ ```typescript
87
+ import * as Sdk from '@your-project/shared/sdk'
88
+ import { DATABASE_MODELS } from '@your-project/shared/sdk'
89
+ import { AdminDataProvider, AdminDataLayout } from '@nestledjs/data-browser'
90
+
91
+ export default function DataLayoutRoute() {
92
+ return (
93
+ <AdminDataProvider
94
+ sdk={Sdk}
95
+ databaseModels={DATABASE_MODELS}
96
+ basePath="/admin/data"
97
+ >
98
+ <AdminDataLayout />
99
+ </AdminDataProvider>
100
+ )
101
+ }
102
+ ```
103
+
104
+ ### Step 4: Create Page Routes
105
+
106
+ Create these minimal route files:
107
+
108
+ **`apps/web/app/routes/admin/data/index.tsx`**:
109
+ ```typescript
110
+ import { AdminDataIndexPage } from '@nestledjs/data-browser'
111
+ export default AdminDataIndexPage
112
+ ```
113
+
114
+ **`apps/web/app/routes/admin/data/$dataTypePlural.tsx`**:
115
+ ```typescript
116
+ import { AdminDataListPage, AdminDataErrorBoundary } from '@nestledjs/data-browser'
117
+
118
+ export default function DataListRoute() {
119
+ return <AdminDataListPage />
120
+ }
121
+
122
+ export function ErrorBoundary({ error }: Readonly<{ error: Error }>) {
123
+ return <AdminDataErrorBoundary error={error} />
124
+ }
125
+ ```
126
+
127
+ **`apps/web/app/routes/admin/data/$dataType.create.tsx`**:
128
+ ```typescript
129
+ import { AdminDataCreatePage, AdminDataCreateErrorBoundary } from '@nestledjs/data-browser'
130
+
131
+ export default function CreateDataRoute() {
132
+ return <AdminDataCreatePage />
133
+ }
134
+
135
+ export function ErrorBoundary({ error }: Readonly<{ error: Error }>) {
136
+ return <AdminDataCreateErrorBoundary error={error} />
137
+ }
138
+ ```
139
+
140
+ **`apps/web/app/routes/admin/data/$dataType.$id.tsx`**:
141
+ ```typescript
142
+ import { AdminDataEditPage, AdminDataEditErrorBoundary } from '@nestledjs/data-browser'
143
+
144
+ export default function EditDataRoute() {
145
+ return <AdminDataEditPage />
146
+ }
147
+
148
+ export function ErrorBoundary({ error }: Readonly<{ error: Error }>) {
149
+ return <AdminDataEditErrorBoundary error={error} />
150
+ }
151
+ ```
152
+
153
+ ### Step 5: Register Routes
154
+
155
+ In `apps/web/app/routes.tsx`:
156
+
157
+ ```typescript
158
+ import { index, route, type RouteConfig } from '@react-router/dev/routes'
159
+
160
+ export default [
161
+ route('admin', './routes/admin/_layout.tsx', [
162
+ route('data', './routes/admin/data/_layout.tsx', [
163
+ index('./routes/admin/data/index.tsx'),
164
+ route(':dataTypePlural', './routes/admin/data/$dataTypePlural.tsx'),
165
+ route(':dataType/create', './routes/admin/data/$dataType.create.tsx'),
166
+ route(':dataType/:id', './routes/admin/data/$dataType.$id.tsx'),
167
+ ]),
168
+ ]),
169
+ ] satisfies RouteConfig
170
+ ```
171
+
172
+ ### Step 6: Access the Data Browser
173
+
174
+ Navigate to `/admin/data` in your application!
175
+
176
+ ## Usage
177
+
178
+ ### Landing Page (`/admin/data`)
179
+
180
+ Shows all available database models with a searchable list.
181
+
182
+ ### List View (`/admin/data/users`)
183
+
184
+ - **Search**: Multi-field text search across string fields
185
+ - **Filter**: Advanced filters for dates, numbers, enums, and relations
186
+ - **Sort**: Click column headers to sort
187
+ - **Columns**: Show/hide columns via column selector
188
+ - **Pagination**: Navigate through large datasets
189
+
190
+ ### Create (`/admin/data/user/create`)
191
+
192
+ Auto-generated form based on your Prisma model schema with:
193
+ - Type-aware inputs (text, number, date, enum, relation dropdowns)
194
+ - Validation based on model requirements
195
+ - Real-time error handling
196
+
197
+ ### Edit (`/admin/data/user/123`)
198
+
199
+ Pre-filled form with current values, plus delete functionality.
200
+
201
+ ## API
202
+
203
+ ### AdminDataProvider
204
+
205
+ The context provider that makes SDK and models available to all components.
206
+
207
+ ```typescript
208
+ <AdminDataProvider
209
+ sdk={Sdk} // Your GraphQL SDK namespace
210
+ databaseModels={DATABASE_MODELS} // Array of model metadata
211
+ basePath="/admin/data" // Optional: Custom route prefix
212
+ >
213
+ {children}
214
+ </AdminDataProvider>
215
+ ```
216
+
217
+ ### useAdminDataContext
218
+
219
+ Hook to access the admin data context:
220
+
221
+ ```typescript
222
+ const { sdk, databaseModels, basePath } = useAdminDataContext()
223
+ ```
224
+
225
+ ## GraphQL Schema Requirements
226
+
227
+ The data browser expects these operations for each model:
228
+
229
+ ```graphql
230
+ # Queries
231
+ query __AdminUser($input: AdminUserInput!)
232
+ query __AdminUsers($input: AdminUsersInput!)
233
+
234
+ # Mutations
235
+ mutation __AdminCreateUser($input: CreateUserInput!)
236
+ mutation __AdminUpdateUser($input: UpdateUserInput!)
237
+ mutation __AdminDeleteUser($input: DeleteUserInput!)
238
+ ```
239
+
240
+ These are auto-generated by the Nestled CRUD generator when you run:
241
+
242
+ ```bash
243
+ pnpm db-update
244
+ ```
245
+
246
+ ## Preferences Storage
247
+
248
+ User preferences are saved to `localStorage` with key `mi-admin-config`:
249
+
250
+ - Column visibility per model
251
+ - Sort preference per model
252
+ - Search fields per model
253
+
254
+ **Export/Import**: Use the header buttons to backup and restore preferences across devices.
255
+
256
+ ## Customization
257
+
258
+ ### Custom Base Path
259
+
260
+ ```typescript
261
+ <AdminDataProvider basePath="/admin/database">
262
+ ```
263
+
264
+ Changes all routes to use `/admin/database/*` instead of `/admin/data/*`.
265
+
266
+ ### Custom Styling
267
+
268
+ The data browser uses Tailwind CSS classes. Customize via your project's Tailwind config and the `formTheme` export.
269
+
270
+ ## Troubleshooting
271
+
272
+ ### "Missing GraphQL documents for model X"
273
+
274
+ **Solution**: Run GraphQL code generation:
275
+ ```bash
276
+ pnpm sdk
277
+ ```
278
+
279
+ ### "useAdminDataContext must be used within AdminDataProvider"
280
+
281
+ **Solution**: Ensure all data browser components are children of `<AdminDataProvider>` in `_layout.tsx`.
282
+
283
+ ### Import errors for WebUiDataTable or formTheme
284
+
285
+ **Solution**: Ensure your project exports these from:
286
+ - `@your-project/web-ui`
287
+ - `@your-project/shared/styles`
288
+
289
+ ### DATABASE_MODELS is undefined
290
+
291
+ **Solution**: Run the model generator:
292
+ ```bash
293
+ pnpm generate:models
294
+ ```
295
+
296
+ This creates the `DATABASE_MODELS` export from your Prisma schema.
297
+
298
+ ## Development
299
+
300
+ ### Build
301
+
302
+ ```bash
303
+ nx build admin-data
304
+ ```
305
+
306
+ Output: `dist/libs/admin-data/`
307
+
308
+ ### Publish
309
+
310
+ ```bash
311
+ nx publish admin-data
312
+ ```
313
+
314
+ ## License
315
+
316
+ MIT
317
+
318
+ ## Support
319
+
320
+ For issues and questions, please visit the [Nestled framework repository](https://github.com/nestledjs/nestled).
@@ -0,0 +1,12 @@
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
+ ]
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@nestledjs/data-browser",
3
+ "version": "0.1.0",
4
+ "description": "Universal admin data browser for Nestled framework projects with full CRUD operations",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "keywords": [
8
+ "nestled",
9
+ "admin",
10
+ "data-browser",
11
+ "crud",
12
+ "react",
13
+ "graphql",
14
+ "apollo"
15
+ ],
16
+ "author": "Nestled Team",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/nestledjs/nestled_template.git",
21
+ "directory": "libs/admin-data"
22
+ },
23
+ "peerDependencies": {
24
+ "@apollo/client": "^4.0.0",
25
+ "@heroicons/react": "^2.0.0",
26
+ "@nestledjs/forms": "^0.5.0",
27
+ "@nestledjs/helpers": "^0.1.0",
28
+ "@nestledjs/shared-components": "^0.1.0",
29
+ "react": "^19.0.0",
30
+ "react-router": "^7.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "@heroicons/react": {
34
+ "optional": false
35
+ }
36
+ },
37
+ "dependencies": {},
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ },
42
+ "module": "./src/index.js",
43
+ "type": "module"
44
+ }
package/project.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "admin-data",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/admin-data/src",
5
+ "projectType": "library",
6
+ "tags": ["publishable"],
7
+ "targets": {
8
+ "build": {
9
+ "executor": "@nx/js:tsc",
10
+ "outputs": ["{options.outputPath}"],
11
+ "options": {
12
+ "outputPath": "dist/libs/admin-data",
13
+ "main": "libs/admin-data/src/index.ts",
14
+ "tsConfig": "libs/admin-data/tsconfig.lib.json",
15
+ "assets": [
16
+ "libs/admin-data/*.md",
17
+ {
18
+ "input": "libs/admin-data",
19
+ "glob": "package.json",
20
+ "output": "."
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ "publish": {
26
+ "executor": "nx:run-commands",
27
+ "dependsOn": ["build"],
28
+ "options": {
29
+ "command": "npm publish dist/libs/admin-data --access public"
30
+ }
31
+ },
32
+ "version": {
33
+ "executor": "@jscutlery/semver:version",
34
+ "options": {
35
+ "preset": "conventional"
36
+ }
37
+ }
38
+ }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ // Context
2
+ export * from './lib/context/AdminDataContext'
3
+
4
+ // Main components
5
+ export * from './lib/pages/AdminDataCreatePage'
6
+ export * from './lib/pages/AdminDataEditPage'
7
+ export * from './lib/pages/AdminDataListPage'
8
+ export * from './lib/pages/AdminDataIndexPage'
9
+
10
+ // Layouts
11
+ export * from './lib/layouts/AdminDataLayout'
12
+
13
+ // Types
14
+ export * from './lib/types'
15
+
16
+ // Hooks
17
+ export * from './lib/hooks/useAdminList'
18
+ export * from './lib/hooks/useClickOutside'
19
+ export * from './lib/hooks/useDebounce'
20
+ export * from './lib/hooks/useRelationData'
21
+
22
+ // Components
23
+ export * from './lib/components'
24
+
25
+ // Utilities
26
+ export * from './lib/utils/graphql-utils'
27
+ export * from './lib/utils/secure-storage'
28
+ export * from './lib/utils/string-utils'
@@ -0,0 +1,10 @@
1
+ import { render } from '@testing-library/react'
2
+
3
+ import AdminData from './admin-data'
4
+
5
+ describe('AdminData', () => {
6
+ it('should render successfully', () => {
7
+ const { baseElement } = render(<AdminData />)
8
+ expect(baseElement).toBeTruthy()
9
+ })
10
+ })
@@ -0,0 +1,9 @@
1
+ export function AdminData() {
2
+ return (
3
+ <div>
4
+ <h1>Welcome to AdminData!</h1>
5
+ </div>
6
+ )
7
+ }
8
+
9
+ export default AdminData
@@ -0,0 +1,88 @@
1
+ import { formatFieldName } from '../../utils/string-utils'
2
+
3
+ interface DateRangeFilterProps {
4
+ fieldName: string
5
+ currentValue: any
6
+ onChange: (value: any) => void
7
+ }
8
+
9
+ // Component for date range filtering
10
+ export function DateRangeFilter({
11
+ fieldName,
12
+ currentValue,
13
+ onChange
14
+ }: DateRangeFilterProps) {
15
+ // Parse current value if it's a range object
16
+ const fromDate = currentValue?.gte ? new Date(currentValue.gte).toISOString().split('T')[0] : ''
17
+ const toDate = currentValue?.lte ? new Date(currentValue.lte).toISOString().split('T')[0] : ''
18
+
19
+ const handleFromChange = (date: string) => {
20
+ const newValue = { ...currentValue }
21
+ if (date) {
22
+ newValue.gte = new Date(date).toISOString()
23
+ } else {
24
+ delete newValue.gte
25
+ }
26
+
27
+ // If no from or to date, clear the filter entirely
28
+ if (!newValue.gte && !newValue.lte) {
29
+ onChange(undefined)
30
+ } else {
31
+ onChange(newValue)
32
+ }
33
+ }
34
+
35
+ const handleToChange = (date: string) => {
36
+ const newValue = { ...currentValue }
37
+ if (date) {
38
+ // Set to end of day for "to" date
39
+ const endOfDay = new Date(date)
40
+ endOfDay.setHours(23, 59, 59, 999)
41
+ newValue.lte = endOfDay.toISOString()
42
+ } else {
43
+ delete newValue.lte
44
+ }
45
+
46
+ // If no from or to date, clear the filter entirely
47
+ if (!newValue.gte && !newValue.lte) {
48
+ onChange(undefined)
49
+ } else {
50
+ onChange(newValue)
51
+ }
52
+ }
53
+
54
+ return (
55
+ <div className="space-y-1">
56
+ <label className="block text-sm font-medium text-gray-700">
57
+ {formatFieldName(fieldName)}
58
+ </label>
59
+ <div className="grid grid-cols-2 gap-2">
60
+ <div>
61
+ <label className="block text-xs text-gray-500 mb-1">From</label>
62
+ <input
63
+ type="date"
64
+ value={fromDate}
65
+ onChange={(e) => handleFromChange(e.target.value)}
66
+ className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-green-web focus:border-green-web"
67
+ />
68
+ </div>
69
+ <div>
70
+ <label className="block text-xs text-gray-500 mb-1">To</label>
71
+ <input
72
+ type="date"
73
+ value={toDate}
74
+ onChange={(e) => handleToChange(e.target.value)}
75
+ className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-green-web focus:border-green-web"
76
+ />
77
+ </div>
78
+ </div>
79
+ {(fromDate || toDate) && (
80
+ <div className="text-xs text-gray-500">
81
+ {fromDate && toDate && `${fromDate} to ${toDate}`}
82
+ {fromDate && !toDate && `From ${fromDate}`}
83
+ {!fromDate && toDate && `Until ${toDate}`}
84
+ </div>
85
+ )}
86
+ </div>
87
+ )
88
+ }