@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.
- package/.babelrc +12 -0
- package/LICENSE +21 -0
- package/README.md +320 -0
- package/eslint.config.mjs +12 -0
- package/package.json +44 -0
- package/project.json +39 -0
- package/src/index.ts +28 -0
- package/src/lib/admin-data.spec.tsx +10 -0
- package/src/lib/admin-data.tsx +9 -0
- package/src/lib/components/filters/DateRangeFilter.tsx +88 -0
- package/src/lib/components/filters/NumberRangeFilter.tsx +111 -0
- package/src/lib/components/filters/RelationComponents.tsx +176 -0
- package/src/lib/components/filters/RelationFilterField.tsx +106 -0
- package/src/lib/components/filters/index.ts +5 -0
- package/src/lib/components/index.ts +2 -0
- package/src/lib/components/shared/AdminBreadcrumbs.tsx +88 -0
- package/src/lib/components/shared/AdminErrorStates.tsx +180 -0
- package/src/lib/components/shared/AdminStatusDisplay.tsx +292 -0
- package/src/lib/components/shared/index.ts +3 -0
- package/src/lib/context/AdminDataContext.tsx +74 -0
- package/src/lib/hooks/useAdminList.ts +42 -0
- package/src/lib/hooks/useClickOutside.ts +21 -0
- package/src/lib/hooks/useDebounce.ts +16 -0
- package/src/lib/hooks/useRelationData.ts +114 -0
- package/src/lib/layouts/AdminDataLayout.tsx +251 -0
- package/src/lib/pages/AdminDataCreatePage.tsx +415 -0
- package/src/lib/pages/AdminDataEditPage.tsx +777 -0
- package/src/lib/pages/AdminDataIndexPage.tsx +50 -0
- package/src/lib/pages/AdminDataListPage.tsx +921 -0
- package/src/lib/types/index.ts +51 -0
- package/src/lib/utils/graphql-utils.ts +538 -0
- package/src/lib/utils/secure-storage.ts +305 -0
- package/src/lib/utils/string-utils.ts +53 -0
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +20 -0
package/.babelrc
ADDED
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,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
|
+
}
|