antd-crud-table 0.0.12 → 0.0.13
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 +810 -52
- package/dist/CrudTableExperimental.cjs +1 -1
- package/dist/CrudTableExperimental.js +2 -2
- package/dist/CrudTableExperimentalLazy.d.ts +4 -0
- package/dist/hooks/useCrudTable.d.ts +19 -11
- package/dist/useCrudTable.cjs +1 -1
- package/dist/useCrudTable.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,24 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
# 🧩 `antd-crud-table` – A Dynamic React Table Generator with Forms 🚀
|
|
4
4
|
|
|
5
|
-
`
|
|
5
|
+
`antd-crud-table` is a highly flexible and powerful React library built using `antd` and `@ant-design/pro-components`. It provides both a declarative component-based approach and a modern hook-based architecture for creating editable, paginated tables with form support, data fetching, sorting, filtering, and custom rendering. Perfect for building admin dashboards and data management UIs with minimal boilerplate.
|
|
6
|
+
|
|
7
|
+
## 🆕 Enhanced 📋 API Reference
|
|
8
|
+
|
|
9
|
+
### CrudTableExperimental Props (Enhanced)
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Description |
|
|
12
|
+
|------|------|-------------|
|
|
13
|
+
| `title` | `string` | Table header title |
|
|
14
|
+
| `rowKey` | `keyof T` | Unique identifier for each row |
|
|
15
|
+
| `columns` | `CrudColumn<T>[]` | Column definitions with enhanced features |
|
|
16
|
+
| `hookConfig` | `UseCrudTableConfig<T>` | Hook configuration for data operations |
|
|
17
|
+
| `defaultPageSize?` | `number` | Initial page size (default: 10) |
|
|
18
|
+
| `enableBulkOperations?` | `boolean` | Enable bulk select/delete (default: false) |
|
|
19
|
+
| `customActions?` | `(record, actions) => ReactNode[]` | Custom row actions |ased Architecture**
|
|
20
|
+
|
|
21
|
+
The experimental version introduces a powerful hook-based architecture with multiple data source strategies:
|
|
22
|
+
- **Static Data**: Perfect for prototypes and small datasets
|
|
23
|
+
- **API Integration**: REST API support with automatic request handling
|
|
24
|
+
- **Custom Operations**: Full control with GraphQL, IndexedDB, or custom logic
|
|
25
|
+
- **Built-in State Management**: Loading states, error handling, optimistic updates
|
|
6
26
|
|
|
7
27
|
---
|
|
8
28
|
|
|
9
29
|
## 📦 Installation
|
|
10
30
|
|
|
11
|
-
Install dependencies with:
|
|
12
|
-
|
|
13
31
|
```bash
|
|
14
32
|
npm install antd-crud-table
|
|
15
33
|
```
|
|
16
34
|
|
|
35
|
+
**Peer Dependencies:**
|
|
36
|
+
```bash
|
|
37
|
+
npm install react react-dom antd @ant-design/pro-components
|
|
38
|
+
```
|
|
39
|
+
|
|
17
40
|
---
|
|
18
41
|
|
|
19
|
-
##
|
|
42
|
+
## 🚀 Quick Start
|
|
43
|
+
|
|
44
|
+
Choose your preferred approach:
|
|
45
|
+
|
|
46
|
+
### Modern Approach (Experimental) - Hook-Based
|
|
20
47
|
|
|
21
48
|
```tsx
|
|
22
|
-
import
|
|
49
|
+
import { CrudTableExperimental } from 'antd-crud-table';
|
|
50
|
+
|
|
51
|
+
// Static data example
|
|
52
|
+
const UserManagement = () => (
|
|
53
|
+
<CrudTableExperimental<User>
|
|
54
|
+
title="User Management"
|
|
55
|
+
rowKey="id"
|
|
56
|
+
hookConfig={{
|
|
57
|
+
staticData: users, // Your data array
|
|
58
|
+
optimisticUpdates: true,
|
|
59
|
+
}}
|
|
60
|
+
columns={[
|
|
61
|
+
{
|
|
62
|
+
title: 'Name',
|
|
63
|
+
dataIndex: 'name',
|
|
64
|
+
fieldType: 'string',
|
|
65
|
+
formConfig: { required: true },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
title: 'Status',
|
|
69
|
+
dataIndex: 'status',
|
|
70
|
+
fieldType: 'enum',
|
|
71
|
+
enumOptions: {
|
|
72
|
+
active: { text: 'Active', color: 'green' },
|
|
73
|
+
inactive: { text: 'Inactive', color: 'orange' },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
]}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Classic Approach (Original) - Service-Based
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { CrudTable } from 'antd-crud-table';
|
|
23
85
|
|
|
24
86
|
const userService = {
|
|
25
87
|
getList: async () => ({ data: [], total: 0 }),
|
|
@@ -28,73 +90,523 @@ const userService = {
|
|
|
28
90
|
delete: async (id) => {},
|
|
29
91
|
};
|
|
30
92
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
93
|
+
const UserTable = () => (
|
|
94
|
+
<CrudTable
|
|
95
|
+
title="User Management"
|
|
96
|
+
rowKey="id"
|
|
97
|
+
service={userService}
|
|
98
|
+
columns={[
|
|
99
|
+
{
|
|
100
|
+
title: 'Name',
|
|
101
|
+
dataIndex: 'name',
|
|
102
|
+
fieldType: 'string',
|
|
103
|
+
fieldEditable: true,
|
|
104
|
+
formConfig: { required: true },
|
|
105
|
+
},
|
|
106
|
+
]}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🎯 **Enhanced Features (Experimental)**
|
|
114
|
+
|
|
115
|
+
### 1. **Multiple Data Source Strategies**
|
|
116
|
+
|
|
117
|
+
#### Static Data (Perfect for Prototyping)
|
|
118
|
+
```tsx
|
|
119
|
+
<CrudTableExperimental
|
|
120
|
+
hookConfig={{
|
|
121
|
+
staticData: mockUsers,
|
|
122
|
+
optimisticUpdates: true,
|
|
123
|
+
}}
|
|
124
|
+
// ... other props
|
|
125
|
+
/>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### API Integration (Production Ready)
|
|
129
|
+
```tsx
|
|
130
|
+
<CrudTableExperimental
|
|
131
|
+
hookConfig={{
|
|
132
|
+
api: {
|
|
133
|
+
baseUrl: 'https://api.example.com',
|
|
134
|
+
endpoints: {
|
|
135
|
+
list: '/users',
|
|
136
|
+
create: '/users',
|
|
137
|
+
update: '/users',
|
|
138
|
+
delete: '/users',
|
|
139
|
+
},
|
|
140
|
+
headers: {
|
|
141
|
+
'Authorization': 'Bearer your-token',
|
|
142
|
+
},
|
|
143
|
+
transform: {
|
|
144
|
+
response: (data) => ({
|
|
145
|
+
data: data.users,
|
|
146
|
+
total: data.totalCount,
|
|
147
|
+
}),
|
|
148
|
+
request: (data) => ({
|
|
149
|
+
...data,
|
|
150
|
+
updatedAt: new Date().toISOString(),
|
|
151
|
+
}),
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
onSuccess: (operation, data) => {
|
|
155
|
+
console.log(`${operation} completed`, data);
|
|
156
|
+
},
|
|
157
|
+
onError: (operation, error) => {
|
|
158
|
+
console.error(`${operation} failed`, error);
|
|
159
|
+
},
|
|
160
|
+
}}
|
|
161
|
+
// ... other props
|
|
162
|
+
/>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Custom Operations (Maximum Flexibility)
|
|
166
|
+
```tsx
|
|
167
|
+
<CrudTableExperimental
|
|
168
|
+
hookConfig={{
|
|
169
|
+
operations: {
|
|
170
|
+
getList: async (params) => {
|
|
171
|
+
const result = await myGraphQLQuery(params);
|
|
172
|
+
return {
|
|
173
|
+
data: result.users,
|
|
174
|
+
total: result.totalCount,
|
|
175
|
+
success: true,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
create: async (data) => await myCreateMutation(data),
|
|
179
|
+
update: async (id, data) => await myUpdateMutation(id, data),
|
|
180
|
+
delete: async (id) => await myDeleteMutation(id),
|
|
181
|
+
},
|
|
182
|
+
}}
|
|
183
|
+
// ... other props
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 2. **Advanced Features**
|
|
188
|
+
|
|
189
|
+
#### Bulk Operations
|
|
190
|
+
```tsx
|
|
191
|
+
<CrudTableExperimental
|
|
192
|
+
enableBulkOperations={true}
|
|
193
|
+
// Automatically adds bulk select and delete functionality
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### Custom Actions
|
|
198
|
+
```tsx
|
|
199
|
+
<CrudTableExperimental
|
|
200
|
+
customActions={(record, actions) => [
|
|
201
|
+
<Button
|
|
202
|
+
key="export"
|
|
203
|
+
onClick={() => exportUser(record)}
|
|
204
|
+
>
|
|
205
|
+
Export
|
|
206
|
+
</Button>,
|
|
207
|
+
<Button
|
|
208
|
+
key="clone"
|
|
209
|
+
onClick={() => actions.create({...record, id: undefined})}
|
|
210
|
+
>
|
|
211
|
+
Clone
|
|
212
|
+
</Button>
|
|
213
|
+
]}
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Enhanced Validation
|
|
218
|
+
```tsx
|
|
219
|
+
columns={[
|
|
220
|
+
{
|
|
221
|
+
dataIndex: 'email',
|
|
222
|
+
title: 'Email',
|
|
223
|
+
fieldType: 'string',
|
|
224
|
+
formConfig: {
|
|
225
|
+
required: true,
|
|
226
|
+
rules: [
|
|
227
|
+
{ required: true, message: 'Email is required' },
|
|
228
|
+
{ type: 'email', message: 'Invalid email format' },
|
|
229
|
+
{
|
|
230
|
+
validator: async (_, value) => {
|
|
231
|
+
const exists = await checkEmailExists(value);
|
|
232
|
+
if (exists) throw new Error('Email already exists');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
]}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 3. **Custom Hooks**
|
|
242
|
+
|
|
243
|
+
Create your own specialized hooks for different use cases:
|
|
244
|
+
|
|
245
|
+
#### Example 1: useUserCrud - Specialized User Management
|
|
246
|
+
```tsx
|
|
247
|
+
import { useCrudTable, type UseCrudTableConfig } from 'antd-crud-table';
|
|
248
|
+
|
|
249
|
+
#### Example 1: useUserCrud - Specialized User Management
|
|
250
|
+
```tsx
|
|
251
|
+
import { useCrudTable, type UseCrudTableConfig } from 'antd-crud-table';
|
|
252
|
+
|
|
253
|
+
export const useUserCrud = (baseConfig?: Partial<UseCrudTableConfig<any>['api']>) => {
|
|
254
|
+
const config: UseCrudTableConfig<any> = {
|
|
255
|
+
api: {
|
|
256
|
+
baseUrl: '/api/users',
|
|
257
|
+
headers: {
|
|
258
|
+
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
|
259
|
+
},
|
|
260
|
+
transform: {
|
|
261
|
+
request: (data) => ({
|
|
262
|
+
...data,
|
|
263
|
+
// Add default values or transformations
|
|
264
|
+
updatedAt: new Date().toISOString(),
|
|
265
|
+
}),
|
|
266
|
+
response: (data) => ({
|
|
267
|
+
data: data.users || data.data || [],
|
|
268
|
+
total: data.total || data.count || 0,
|
|
269
|
+
success: true,
|
|
270
|
+
}),
|
|
271
|
+
},
|
|
272
|
+
...baseConfig,
|
|
42
273
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
274
|
+
defaultPageSize: 10,
|
|
275
|
+
optimisticUpdates: true,
|
|
276
|
+
onSuccess: (operation, data) => {
|
|
277
|
+
console.log(`User ${operation} completed:`, data);
|
|
278
|
+
},
|
|
279
|
+
onError: (operation, error) => {
|
|
280
|
+
console.error(`User ${operation} failed:`, error);
|
|
281
|
+
// Could add toast notifications, error reporting, etc.
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return useCrudTable('id', config);
|
|
286
|
+
};
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### Example 2: useLocalStorageCrud - Local Storage Persistence
|
|
290
|
+
```tsx
|
|
291
|
+
export const useLocalStorageCrud = <T extends Record<string, any>>(
|
|
292
|
+
storageKey: string,
|
|
293
|
+
rowKey: keyof T,
|
|
294
|
+
initialData: T[] = []
|
|
295
|
+
) => {
|
|
296
|
+
const getStoredData = (): T[] => {
|
|
297
|
+
try {
|
|
298
|
+
const stored = localStorage.getItem(storageKey);
|
|
299
|
+
return stored ? JSON.parse(stored) : initialData;
|
|
300
|
+
} catch {
|
|
301
|
+
return initialData;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const setStoredData = (data: T[]) => {
|
|
306
|
+
localStorage.setItem(storageKey, JSON.stringify(data));
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
return useCrudTable(rowKey, {
|
|
310
|
+
operations: {
|
|
311
|
+
getList: async (params) => {
|
|
312
|
+
const data = getStoredData();
|
|
313
|
+
const { current = 1, pageSize = 10, ...filters } = params;
|
|
314
|
+
|
|
315
|
+
// Apply filters
|
|
316
|
+
let filteredData = data;
|
|
317
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
318
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
319
|
+
filteredData = filteredData.filter(item =>
|
|
320
|
+
String(item[key]).toLowerCase().includes(String(value).toLowerCase())
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Apply pagination
|
|
326
|
+
const start = (current - 1) * pageSize;
|
|
327
|
+
const paginatedData = filteredData.slice(start, start + pageSize);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
data: paginatedData,
|
|
331
|
+
total: filteredData.length,
|
|
332
|
+
success: true,
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
create: async (newItem) => {
|
|
337
|
+
const data = getStoredData();
|
|
338
|
+
const maxId = Math.max(...data.map(item => Number(item[rowKey]) || 0), 0);
|
|
339
|
+
const created = {
|
|
340
|
+
[rowKey]: maxId + 1,
|
|
341
|
+
...newItem,
|
|
342
|
+
createdAt: new Date().toISOString(),
|
|
343
|
+
} as T;
|
|
344
|
+
|
|
345
|
+
data.push(created);
|
|
346
|
+
setStoredData(data);
|
|
347
|
+
return created;
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
update: async (id, updateData) => {
|
|
351
|
+
const data = getStoredData();
|
|
352
|
+
const index = data.findIndex(item => item[rowKey] === id);
|
|
353
|
+
if (index === -1) throw new Error('Item not found');
|
|
354
|
+
|
|
355
|
+
data[index] = {
|
|
356
|
+
...data[index],
|
|
357
|
+
...updateData,
|
|
358
|
+
updatedAt: new Date().toISOString(),
|
|
359
|
+
};
|
|
360
|
+
setStoredData(data);
|
|
361
|
+
return data[index];
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
delete: async (id) => {
|
|
365
|
+
const data = getStoredData();
|
|
366
|
+
const filtered = data.filter(item => item[rowKey] !== id);
|
|
367
|
+
setStoredData(filtered);
|
|
50
368
|
},
|
|
51
|
-
formConfig: { required: true },
|
|
52
369
|
},
|
|
53
|
-
|
|
370
|
+
optimisticUpdates: true,
|
|
371
|
+
});
|
|
54
372
|
};
|
|
373
|
+
```
|
|
55
374
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
375
|
+
#### Example 3: useRealtimeCrud - WebSocket Integration
|
|
376
|
+
```tsx
|
|
377
|
+
#### Example 3: useRealtimeCrud - WebSocket Integration
|
|
378
|
+
```tsx
|
|
379
|
+
export const useRealtimeCrud = <T extends Record<string, any>>(
|
|
380
|
+
rowKey: keyof T,
|
|
381
|
+
websocketUrl: string,
|
|
382
|
+
apiConfig: UseCrudTableConfig<T>['api']
|
|
383
|
+
) => {
|
|
384
|
+
const config: UseCrudTableConfig<T> = {
|
|
385
|
+
api: apiConfig,
|
|
386
|
+
optimisticUpdates: false, // Disable optimistic updates for realtime
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const crud = useCrudTable(rowKey, config);
|
|
390
|
+
|
|
391
|
+
// In a real implementation, you would set up WebSocket listeners here
|
|
392
|
+
// useEffect(() => {
|
|
393
|
+
// const ws = new WebSocket(websocketUrl);
|
|
394
|
+
//
|
|
395
|
+
// ws.onmessage = (event) => {
|
|
396
|
+
// const { type, data } = JSON.parse(event.data);
|
|
397
|
+
// switch (type) {
|
|
398
|
+
// case 'created':
|
|
399
|
+
// case 'updated':
|
|
400
|
+
// case 'deleted':
|
|
401
|
+
// crud.refresh(); // Refresh data when changes occur
|
|
402
|
+
// break;
|
|
403
|
+
// }
|
|
404
|
+
// };
|
|
405
|
+
//
|
|
406
|
+
// return () => ws.close();
|
|
407
|
+
// }, [websocketUrl]);
|
|
408
|
+
|
|
409
|
+
return crud;
|
|
410
|
+
};
|
|
59
411
|
```
|
|
60
412
|
|
|
61
|
-
|
|
413
|
+
#### Example 4: useInfiniteScrollCrud - Infinite Scrolling
|
|
414
|
+
```tsx
|
|
415
|
+
#### Example 4: useInfiniteScrollCrud - Infinite Scrolling
|
|
416
|
+
```tsx
|
|
417
|
+
export const useInfiniteScrollCrud = <T extends Record<string, any>>(
|
|
418
|
+
rowKey: keyof T,
|
|
419
|
+
baseConfig: UseCrudTableConfig<T>
|
|
420
|
+
) => {
|
|
421
|
+
// This would extend the base hook with infinite scroll capabilities
|
|
422
|
+
// Implementation would handle cursor-based pagination, data accumulation, etc.
|
|
423
|
+
|
|
424
|
+
const config: UseCrudTableConfig<T> = {
|
|
425
|
+
...baseConfig,
|
|
426
|
+
// Add infinite scroll specific configuration
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
return useCrudTable(rowKey, config);
|
|
430
|
+
};
|
|
431
|
+
```
|
|
62
432
|
|
|
63
|
-
|
|
433
|
+
#### Example 5: useCachedCrud - Advanced Caching
|
|
434
|
+
```tsx
|
|
435
|
+
#### Example 5: useCachedCrud - Advanced Caching
|
|
436
|
+
```tsx
|
|
437
|
+
export const useCachedCrud = <T extends Record<string, any>>(
|
|
438
|
+
rowKey: keyof T,
|
|
439
|
+
cacheKey: string,
|
|
440
|
+
baseConfig: UseCrudTableConfig<T>
|
|
441
|
+
) => {
|
|
442
|
+
const config: UseCrudTableConfig<T> = {
|
|
443
|
+
...baseConfig,
|
|
444
|
+
enableCache: true,
|
|
445
|
+
// In a real implementation, you might integrate with:
|
|
446
|
+
// - React Query
|
|
447
|
+
// - SWR
|
|
448
|
+
// - Redux Toolkit Query
|
|
449
|
+
// - Apollo Client
|
|
450
|
+
// etc.
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
return useCrudTable(rowKey, config);
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
```
|
|
64
457
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
458
|
+
#### Usage Examples
|
|
459
|
+
```tsx
|
|
460
|
+
// Using the specialized hooks
|
|
461
|
+
const UserTable = () => {
|
|
462
|
+
const userCrud = useUserCrud();
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<CrudTableExperimental
|
|
466
|
+
title="Users"
|
|
467
|
+
rowKey="id"
|
|
468
|
+
hookConfig={userCrud}
|
|
469
|
+
columns={userColumns}
|
|
470
|
+
/>
|
|
471
|
+
);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const OfflineTable = () => {
|
|
475
|
+
const offlineCrud = useLocalStorageCrud<User>('users-cache', 'id', mockUsers);
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
<CrudTableExperimental
|
|
479
|
+
title="Offline Users"
|
|
480
|
+
rowKey="id"
|
|
481
|
+
hookConfig={offlineCrud}
|
|
482
|
+
columns={userColumns}
|
|
483
|
+
/>
|
|
484
|
+
);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const RealtimeTable = () => {
|
|
488
|
+
const realtimeCrud = useRealtimeCrud<User>(
|
|
489
|
+
'id',
|
|
490
|
+
'wss://api.example.com/ws',
|
|
491
|
+
{ baseUrl: '/api/users' }
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<CrudTableExperimental
|
|
496
|
+
title="Realtime Users"
|
|
497
|
+
rowKey="id"
|
|
498
|
+
hookConfig={realtimeCrud}
|
|
499
|
+
columns={userColumns}
|
|
500
|
+
/>
|
|
501
|
+
);
|
|
502
|
+
};
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## 🏆 Complete Feature Set
|
|
508
|
+
|
|
509
|
+
### Core Features
|
|
510
|
+
- 🎨 **Multiple Column Types**: `string`, `number`, `boolean`, `date`, `enum`, `custom`
|
|
511
|
+
- ✅ **Integrated Create/Edit Modal Forms** with validation
|
|
512
|
+
- 🚀 **ProTable Integration**: Sorting, pagination & filtering built-in
|
|
513
|
+
- 🔁 **Real-time Data Operations** with loading states
|
|
514
|
+
- 🧠 **Custom Transform & Render Logic** per field
|
|
515
|
+
- 📆 **Smart Date/Time Handling** with `date-fns` + `dayjs`
|
|
516
|
+
- 🧰 **Full TypeScript Support** with generics
|
|
517
|
+
- 🔐 **Field-Level Edit Controls**
|
|
518
|
+
- 🧼 **Professional UI** with row differentiation
|
|
519
|
+
|
|
520
|
+
### Enhanced Features (Experimental)
|
|
521
|
+
- 🪝 **Hook-Based Architecture** with `useCrudTable`
|
|
522
|
+
- 🔌 **Multiple Data Sources**: Static, API, or custom operations
|
|
523
|
+
- ⚡ **Built-in State Management**: Loading, error states, optimistic updates
|
|
524
|
+
- 🔧 **Extensible Design**: Create custom hooks for your domain
|
|
525
|
+
- 📊 **Performance Optimized**: Caching, lazy loading, optimistic updates
|
|
526
|
+
- 🎛️ **Bulk Operations**: Select and delete multiple rows
|
|
527
|
+
- 🎯 **Custom Actions**: Add your own row-level actions
|
|
528
|
+
- 🔍 **Advanced Search**: Configurable column-level search
|
|
529
|
+
- ✨ **Enhanced Validation**: Complex form validation rules
|
|
74
530
|
|
|
75
531
|
---
|
|
76
532
|
|
|
77
|
-
##
|
|
533
|
+
## API Reference
|
|
534
|
+
|
|
535
|
+
### CrudTableV2 Props (Enhanced)
|
|
78
536
|
|
|
79
|
-
### `CrudTableConfig<T>`
|
|
80
537
|
| Prop | Type | Description |
|
|
81
538
|
|------|------|-------------|
|
|
82
|
-
| `columns` | `CrudColumn<T>[]` | Column definitions including types and rendering logic |
|
|
83
|
-
| `service` | `{ getList, create, update, delete }` | API service methods for data fetching and CRUD |
|
|
84
|
-
| `rowKey` | `keyof T` | Unique key for each row |
|
|
85
539
|
| `title` | `string` | Table header title |
|
|
86
|
-
| `
|
|
540
|
+
| `rowKey` | `keyof T` | Unique identifier for each row |
|
|
541
|
+
| `columns` | `CrudColumn<T>[]` | Column definitions with enhanced features |
|
|
542
|
+
| `hookConfig` | `UseCrudTableConfig<T>` | Hook configuration for data operations |
|
|
543
|
+
| `defaultPageSize?` | `number` | Initial page size (default: 10) |
|
|
544
|
+
| `enableBulkOperations?` | `boolean` | Enable bulk select/delete (default: false) |
|
|
545
|
+
| `customActions?` | `(record, actions) => ReactNode[]` | Custom row actions |
|
|
87
546
|
|
|
88
|
-
###
|
|
89
|
-
Extends `ProColumns<T>` with:
|
|
547
|
+
### CrudColumn<T> (Enhanced)
|
|
90
548
|
|
|
91
549
|
| Prop | Type | Description |
|
|
92
550
|
|------|------|-------------|
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `fieldEditable?` | `boolean` | Whether
|
|
97
|
-
| `
|
|
551
|
+
| `dataIndex` | `keyof T` | Field key in your data |
|
|
552
|
+
| `title` | `string` | Column header text |
|
|
553
|
+
| `fieldType` | `FieldType` | `"string" \| "number" \| "boolean" \| "date" \| "enum" \| "custom"` |
|
|
554
|
+
| `fieldEditable?` | `boolean` | Whether field can be edited (default: true) |
|
|
555
|
+
| `searchable?` | `boolean` | Whether field appears in search (default: true) |
|
|
556
|
+
| `enumOptions?` | `Record<string, {text: string, color?: string}>` | Options for enum fields |
|
|
557
|
+
| `customRender?` | `(value, record) => ReactNode` | Custom display renderer |
|
|
558
|
+
| `formConfig?` | `FormConfig` | Form field configuration |
|
|
559
|
+
|
|
560
|
+
### FormConfig
|
|
561
|
+
|
|
562
|
+
| Prop | Type | Description |
|
|
563
|
+
|------|------|-------------|
|
|
564
|
+
| `required?` | `boolean` | Whether field is required |
|
|
565
|
+
| `rules?` | `FormRule[]` | Ant Design validation rules |
|
|
566
|
+
| `component?` | `ReactNode` | Custom form component |
|
|
567
|
+
| `transform?` | `(value) => any` | Transform value before saving |
|
|
568
|
+
|
|
569
|
+
### UseCrudTableConfig<T>
|
|
570
|
+
|
|
571
|
+
Choose one approach:
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
// Static data approach
|
|
575
|
+
{
|
|
576
|
+
staticData: T[];
|
|
577
|
+
optimisticUpdates?: boolean;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// API approach
|
|
581
|
+
{
|
|
582
|
+
api: {
|
|
583
|
+
baseUrl: string;
|
|
584
|
+
endpoints?: {...};
|
|
585
|
+
headers?: Record<string, string>;
|
|
586
|
+
transform?: {...};
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Custom operations approach
|
|
591
|
+
{
|
|
592
|
+
operations: {
|
|
593
|
+
getList: (params) => Promise<{data: T[], total: number}>;
|
|
594
|
+
create: (data: Partial<T>) => Promise<T>;
|
|
595
|
+
update: (id, data: Partial<T>) => Promise<T>;
|
|
596
|
+
delete: (id) => Promise<void>;
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Legacy CrudTable Props (Original)
|
|
602
|
+
|
|
603
|
+
| Prop | Type | Description |
|
|
604
|
+
|------|------|-------------|
|
|
605
|
+
| `columns` | `CrudColumn<T>[]` | Column definitions |
|
|
606
|
+
| `service` | `CrudService<T>` | Service object with CRUD methods |
|
|
607
|
+
| `rowKey` | `keyof T` | Unique key for each row |
|
|
608
|
+
| `title` | `string` | Table header title |
|
|
609
|
+
| `defaultPageSize?` | `number` | Optional default page size (default: 5) |
|
|
98
610
|
|
|
99
611
|
---
|
|
100
612
|
|
|
@@ -118,6 +630,252 @@ Customize row striping using `.row-differentiator` in `CrudTable.css`:
|
|
|
118
630
|
|
|
119
631
|
---
|
|
120
632
|
|
|
633
|
+
## 🔄 Migration Guide
|
|
634
|
+
|
|
635
|
+
### Upgrading from Original to Experimental
|
|
636
|
+
|
|
637
|
+
**Original (Service-Based):**
|
|
638
|
+
```tsx
|
|
639
|
+
<CrudTable
|
|
640
|
+
title="Users"
|
|
641
|
+
rowKey="id"
|
|
642
|
+
service={UserService}
|
|
643
|
+
columns={columns}
|
|
644
|
+
/>
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**Experimental (Hook-Based):**
|
|
648
|
+
```tsx
|
|
649
|
+
<CrudTableExperimental
|
|
650
|
+
title="Users"
|
|
651
|
+
rowKey="id"
|
|
652
|
+
hookConfig={{
|
|
653
|
+
operations: UserService, // Reuse existing service
|
|
654
|
+
// Or choose new approaches:
|
|
655
|
+
// staticData: users,
|
|
656
|
+
// api: { baseUrl: '/api' },
|
|
657
|
+
}}
|
|
658
|
+
columns={columns}
|
|
659
|
+
/>
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Breaking Changes in Experimental
|
|
663
|
+
- ✅ **Fully backward compatible**: Original components still work
|
|
664
|
+
- 🔄 **New import**: `CrudTableExperimental` for enhanced version
|
|
665
|
+
- 🎛️ **Service → hookConfig**: More flexible configuration
|
|
666
|
+
- 📊 **Enhanced props**: Additional optional features
|
|
667
|
+
|
|
668
|
+
### Lazy Loading Options
|
|
669
|
+
|
|
670
|
+
For better performance with code splitting:
|
|
671
|
+
|
|
672
|
+
```tsx
|
|
673
|
+
// Standard lazy loading
|
|
674
|
+
import { CrudTableLazy } from 'antd-crud-table';
|
|
675
|
+
|
|
676
|
+
// Experimental lazy loading
|
|
677
|
+
import { CrudTableExperimentalLazy } from 'antd-crud-table';
|
|
678
|
+
|
|
679
|
+
<CrudTableExperimentalLazy
|
|
680
|
+
title="Users"
|
|
681
|
+
rowKey="id"
|
|
682
|
+
hookConfig={hookConfig}
|
|
683
|
+
columns={columns}
|
|
684
|
+
/>
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
---
|
|
688
|
+
|
|
689
|
+
## 🎨 Column Type Examples
|
|
690
|
+
|
|
691
|
+
### String Field
|
|
692
|
+
```tsx
|
|
693
|
+
{
|
|
694
|
+
dataIndex: 'name',
|
|
695
|
+
title: 'Full Name',
|
|
696
|
+
fieldType: 'string',
|
|
697
|
+
formConfig: {
|
|
698
|
+
required: true,
|
|
699
|
+
rules: [
|
|
700
|
+
{ min: 2, message: 'Name must be at least 2 characters' }
|
|
701
|
+
]
|
|
702
|
+
},
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Number Field
|
|
707
|
+
```tsx
|
|
708
|
+
{
|
|
709
|
+
dataIndex: 'age',
|
|
710
|
+
title: 'Age',
|
|
711
|
+
fieldType: 'number',
|
|
712
|
+
formConfig: {
|
|
713
|
+
rules: [
|
|
714
|
+
{ type: 'number', min: 0, max: 120, message: 'Invalid age' }
|
|
715
|
+
]
|
|
716
|
+
},
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Date Field
|
|
721
|
+
```tsx
|
|
722
|
+
{
|
|
723
|
+
dataIndex: 'createdAt',
|
|
724
|
+
title: 'Created Date',
|
|
725
|
+
fieldType: 'date',
|
|
726
|
+
searchable: false, // Exclude from search
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Boolean Field
|
|
731
|
+
```tsx
|
|
732
|
+
{
|
|
733
|
+
dataIndex: 'isActive',
|
|
734
|
+
title: 'Active Status',
|
|
735
|
+
fieldType: 'boolean',
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Enum Field
|
|
740
|
+
```tsx
|
|
741
|
+
{
|
|
742
|
+
dataIndex: 'status',
|
|
743
|
+
title: 'Status',
|
|
744
|
+
fieldType: 'enum',
|
|
745
|
+
enumOptions: {
|
|
746
|
+
active: { text: 'Active', color: 'green' },
|
|
747
|
+
pending: { text: 'Pending', color: 'orange' },
|
|
748
|
+
inactive: { text: 'Inactive', color: 'red' },
|
|
749
|
+
},
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Custom Field
|
|
754
|
+
```tsx
|
|
755
|
+
{
|
|
756
|
+
dataIndex: 'customField',
|
|
757
|
+
title: 'Custom Display',
|
|
758
|
+
fieldType: 'custom',
|
|
759
|
+
customRender: (value, record) => (
|
|
760
|
+
<div>
|
|
761
|
+
<Avatar src={record.avatar} />
|
|
762
|
+
<span>{record.name}</span>
|
|
763
|
+
</div>
|
|
764
|
+
),
|
|
765
|
+
formConfig: {
|
|
766
|
+
component: <MyCustomInput />,
|
|
767
|
+
},
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
## 🧪 Testing
|
|
774
|
+
|
|
775
|
+
The hook-based architecture enables easy testing:
|
|
776
|
+
|
|
777
|
+
```tsx
|
|
778
|
+
import { renderHook, act } from '@testing-library/react';
|
|
779
|
+
import { useCrudTable } from 'antd-crud-table';
|
|
780
|
+
|
|
781
|
+
test('should handle CRUD operations', async () => {
|
|
782
|
+
const mockData = [
|
|
783
|
+
{ id: 1, name: 'John', age: 30 }
|
|
784
|
+
];
|
|
785
|
+
|
|
786
|
+
const { result } = renderHook(() =>
|
|
787
|
+
useCrudTable('id', {
|
|
788
|
+
staticData: mockData,
|
|
789
|
+
})
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
await act(async () => {
|
|
793
|
+
const created = await result.current.create({
|
|
794
|
+
name: 'Jane',
|
|
795
|
+
age: 25
|
|
796
|
+
});
|
|
797
|
+
expect(created).toBeTruthy();
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
expect(result.current.state.data).toHaveLength(2);
|
|
801
|
+
});
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
## 🚀 Performance Tips
|
|
807
|
+
|
|
808
|
+
### 1. **Use Static Data for Prototyping**
|
|
809
|
+
```tsx
|
|
810
|
+
// Perfect for demos and development
|
|
811
|
+
hookConfig={{ staticData: mockData }}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### 2. **Enable Optimistic Updates**
|
|
815
|
+
```tsx
|
|
816
|
+
// For better UX with reliable backends
|
|
817
|
+
hookConfig={{
|
|
818
|
+
api: {...},
|
|
819
|
+
optimisticUpdates: true
|
|
820
|
+
}}
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### 3. **Implement Proper Caching**
|
|
824
|
+
```tsx
|
|
825
|
+
// Custom hook with caching
|
|
826
|
+
const useUserCrud = () => {
|
|
827
|
+
return useCrudTable('id', {
|
|
828
|
+
enableCache: true,
|
|
829
|
+
// ... other config
|
|
830
|
+
});
|
|
831
|
+
};
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### 4. **Lazy Load Components**
|
|
835
|
+
```tsx
|
|
836
|
+
import { CrudTableLazy } from 'antd-crud-table';
|
|
837
|
+
// Component will be loaded when needed
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
## 🔮 Roadmap
|
|
843
|
+
|
|
844
|
+
### Coming Soon
|
|
845
|
+
- 🌐 **WebSocket Integration**: Real-time updates
|
|
846
|
+
- 📊 **Virtual Scrolling**: Handle thousands of rows
|
|
847
|
+
- 📤 **Export Functionality**: CSV/Excel export
|
|
848
|
+
- 🎨 **Theme Support**: Multiple UI themes
|
|
849
|
+
- 🔍 **Advanced Filters**: Complex filtering UI
|
|
850
|
+
- 📱 **Mobile Optimization**: Better mobile experience
|
|
851
|
+
|
|
852
|
+
### Community Requests
|
|
853
|
+
- 🔧 **Plugin System**: Extensible architecture
|
|
854
|
+
- 📈 **Analytics Integration**: Built-in tracking
|
|
855
|
+
- 🌍 **i18n Support**: Multi-language support
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
## 🤝 Contributing
|
|
860
|
+
|
|
861
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md).
|
|
862
|
+
|
|
863
|
+
### Development Setup
|
|
864
|
+
```bash
|
|
865
|
+
git clone https://github.com/maifeeulasad/antd-crud-table
|
|
866
|
+
cd antd-crud-table
|
|
867
|
+
npm install
|
|
868
|
+
npm run dev
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## 📄 License
|
|
874
|
+
|
|
875
|
+
MIT License - feel free to use in personal and commercial projects.
|
|
876
|
+
|
|
877
|
+
---
|
|
878
|
+
|
|
121
879
|
🎉 Build elegant CRUD interfaces faster than ever with `antd-crud-table`!
|
|
122
880
|
|
|
123
881
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const o=require("./jsx-runtime-qcKc-DpB.cjs"),f=require("react"),n=require("./CrudTable-ck226KHr.cjs"),z=require("./useCrudTable.cjs"),T=require("./index-DygfPlBI.cjs"),_=w=>{const{columns:x,rowKey:d,title:I,defaultPageSize:y=10,hookConfig:k,enableBulkOperations:R=!1,customActions:h}=w,i=z.useCrudTable(d,{
|
|
1
|
+
"use strict";const o=require("./jsx-runtime-qcKc-DpB.cjs"),f=require("react"),n=require("./CrudTable-ck226KHr.cjs"),z=require("./useCrudTable.cjs"),T=require("./index-DygfPlBI.cjs"),_=w=>{const{columns:x,rowKey:d,title:I,defaultPageSize:y=10,hookConfig:k,enableBulkOperations:R=!1,customActions:h}=w,i=z.useCrudTable(d,{defaultPageSize:y,...k}),[v,j]=f.useState(!1),[m,S]=f.useState(null),[u,g]=f.useState([]),[p]=n.Form.useForm();f.useEffect(()=>{i.refresh()},[]);const E=x.map(e=>{const t={...e,dataIndex:e.dataIndex,title:e.title,search:e.searchable!==!1};switch(e.fieldType){case"date":return{...t,valueType:"dateTime",render:(r,s)=>{const a=s[e.dataIndex];if(!a)return"-";try{return o.jsxRuntimeExports.jsx("span",{children:n.format(n.parseISO(a),"yyyy-MM-dd HH:mm")})}catch{return o.jsxRuntimeExports.jsx("span",{children:a})}}};case"enum":return{...t,valueType:"select",valueEnum:e.enumOptions,render:(r,s)=>{var c;const a=s[e.dataIndex],l=(c=e.enumOptions)==null?void 0:c[a];return l?o.jsxRuntimeExports.jsx(n.Tag,{color:l.color,children:l.text}):a}};case"number":return{...t,valueType:"digit",render:(r,s)=>{const a=s[e.dataIndex];return typeof a=="number"?a.toLocaleString():a}};case"boolean":return{...t,valueType:"switch",render:(r,s)=>o.jsxRuntimeExports.jsx(n.Tag,{color:s[e.dataIndex]?"green":"red",children:s[e.dataIndex]?"Yes":"No"})};case"custom":return{...t,render:(r,s)=>{var a;return(a=e.customRender)==null?void 0:a.call(e,s[e.dataIndex],s)}};default:return t}});E.push({title:"Actions",valueType:"option",width:200,render:(e,t)=>{const r=[o.jsxRuntimeExports.jsx(n.Button,{type:"link",size:"small",onClick:()=>b(t),children:"Edit"},"edit"),o.jsxRuntimeExports.jsx(n.Button,{type:"link",size:"small",danger:!0,onClick:()=>q(t[d]),children:"Delete"},"delete")],s=(h==null?void 0:h(t,i))||[];return[...r,...s]}});const F=async(e,t,r)=>{try{const s={current:e.current,pageSize:e.pageSize,sortBy:Object.keys(t)[0],sortOrder:Object.values(t)[0],...r,...e},a=i.operations;if(a!=null&&a.getList){const l=await a.getList(s);return{data:l.data,success:!0,total:l.total}}return{data:i.state.data,success:!0,total:i.state.total}}catch{return T.staticMethods.error("Failed to fetch data"),{data:[],success:!1,total:0}}},b=e=>{if(S(e||null),e){const t={...e};x.forEach(r=>{const s=r.dataIndex;if(r.fieldType==="date"&&t[s])try{t[s]=n.dayjs(t[s])}catch{}}),p.setFieldsValue(t)}else p.resetFields();j(!0)},O=async()=>{var e;try{const t=await p.validateFields(),r={...t};x.forEach(s=>{var l;const a=s.dataIndex;if(s.fieldType==="date"&&t[a])try{r[a]=n.formatISO(t[a])}catch{}(l=s.formConfig)!=null&&l.transform&&(r[a]=s.formConfig.transform(t[a]))}),m&&m[d]?await i.update(m[d],r):await i.create(r),j(!1),(e=i.actionRef.current)==null||e.reload()}catch(t){console.error("Form validation failed:",t)}},q=async e=>{n.Modal.confirm({title:"Are you sure?",content:"This action cannot be undone.",okText:"Yes, Delete",okType:"danger",cancelText:"Cancel",onOk:async()=>{var r;await i.delete(e)&&((r=i.actionRef.current)==null||r.reload())}})},D=async()=>{if(u.length===0){T.staticMethods.warning("Please select items to delete");return}n.Modal.confirm({title:`Delete ${u.length} items?`,content:"This action cannot be undone.",okText:"Yes, Delete All",okType:"danger",cancelText:"Cancel",onOk:async()=>{var t;const e=u.map(r=>i.delete(r));await Promise.allSettled(e),g([]),(t=i.actionRef.current)==null||t.reload()}})},B=R?{selectedRowKeys:u,onChange:g}:void 0;return o.jsxRuntimeExports.jsxs(n.ProConfigProvider,{needDeps:!0,children:[o.jsxRuntimeExports.jsx(n.ProviderTableContainer,{headerTitle:I,rowKey:d,rowClassName:(e,t)=>t%2===0?"row-differentiator":"",actionRef:i.actionRef,columns:E,request:F,search:{labelWidth:"auto"},pagination:{pageSize:y,showSizeChanger:!0,showQuickJumper:!0},loading:i.state.loading,rowSelection:B,toolBarRender:()=>[o.jsxRuntimeExports.jsx(n.Button,{type:"primary",icon:o.jsxRuntimeExports.jsx(n.RefIcon,{}),onClick:()=>b(),children:"New"},"add"),...R&&u.length>0?[o.jsxRuntimeExports.jsxs(n.Button,{danger:!0,onClick:D,children:["Delete Selected (",u.length,")"]},"bulk-delete")]:[],o.jsxRuntimeExports.jsx(n.Dropdown,{menu:{items:[{key:"export",label:"Export",disabled:!0},{key:"refresh",label:"Refresh",onClick:()=>i.refresh()}]},children:o.jsxRuntimeExports.jsx(n.Button,{children:o.jsxRuntimeExports.jsx(n.RefIcon$1,{})})},"menu")],options:{setting:{listsHeight:400},reload:()=>i.refresh()},dateFormatter:"string"}),o.jsxRuntimeExports.jsx(n.Modal,{title:m?"Edit Item":"Create Item",open:v,onOk:O,onCancel:()=>j(!1),destroyOnClose:!0,width:600,children:o.jsxRuntimeExports.jsx(n.Form,{form:p,layout:"vertical",children:x.map(e=>{var l,c,C;if(!e.dataIndex)return null;const t=e.dataIndex,r=e.title,s=!(e.fieldEditable??!0),a=((l=e.formConfig)==null?void 0:l.rules)||((c=e.formConfig)!=null&&c.required?[{required:!0,message:`${r} is required`}]:[]);if((C=e.formConfig)!=null&&C.component)return o.jsxRuntimeExports.jsx(n.Form.Item,{name:t,label:r,rules:a,children:e.formConfig.component},t);switch(e.fieldType){case"string":return o.jsxRuntimeExports.jsx(n.Form.Item,{name:t,label:r,rules:a,children:o.jsxRuntimeExports.jsx(n.Input,{disabled:s})},t);case"number":return o.jsxRuntimeExports.jsx(n.Form.Item,{name:t,label:r,rules:a,children:o.jsxRuntimeExports.jsx(n.TypedInputNumber,{style:{width:"100%"},disabled:s})},t);case"date":return o.jsxRuntimeExports.jsx(n.Form.Item,{name:t,label:r,rules:a,children:o.jsxRuntimeExports.jsx(n.DatePicker,{style:{width:"100%"},showTime:!0,disabled:s})},t);case"boolean":return o.jsxRuntimeExports.jsx(n.Form.Item,{name:t,label:r,valuePropName:"checked",children:o.jsxRuntimeExports.jsx(n.Switch,{disabled:s})},t);case"enum":return o.jsxRuntimeExports.jsx(n.Form.Item,{name:t,label:r,rules:a,children:o.jsxRuntimeExports.jsx(n.Select,{disabled:s,placeholder:`Select ${r.toLowerCase()}`,options:Object.entries(e.enumOptions||{}).map(([M,P])=>({label:P.text,value:M}))})},t);default:return null}})})})]})};module.exports=_;
|
|
@@ -5,8 +5,8 @@ import { useCrudTable as te } from "./useCrudTable.js";
|
|
|
5
5
|
import { s as R } from "./index-DNXRWKhU.js";
|
|
6
6
|
const oe = (S) => {
|
|
7
7
|
const { columns: m, rowKey: c, title: O, defaultPageSize: j = 10, hookConfig: D, enableBulkOperations: C = !1, customActions: x } = S, i = te(c, {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
defaultPageSize: j,
|
|
9
|
+
...D
|
|
10
10
|
}), [E, y] = g(!1), [h, F] = g(null), [d, w] = g([]), [p] = l.useForm();
|
|
11
11
|
A(() => {
|
|
12
12
|
i.refresh();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { EnhancedCrudTableConfig, DataType } from './CrudTableExperimental';
|
|
2
|
+
declare const CrudTableExperimentalLazy: <T extends DataType>(props: EnhancedCrudTableConfig<T>) => import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export default CrudTableExperimentalLazy;
|
|
4
|
+
export type { EnhancedCrudTableConfig, DataType };
|
|
@@ -25,9 +25,18 @@ export type CrudState<T> = {
|
|
|
25
25
|
current: number;
|
|
26
26
|
pageSize: number;
|
|
27
27
|
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
type UseCrudTableConfigBase = {
|
|
29
|
+
defaultPageSize?: number;
|
|
30
|
+
enableCache?: boolean;
|
|
31
|
+
optimisticUpdates?: boolean;
|
|
32
|
+
onSuccess?: (operation: 'create' | 'update' | 'delete' | 'fetch', data: any) => void;
|
|
33
|
+
onError?: (operation: 'create' | 'update' | 'delete' | 'fetch', error: any) => void;
|
|
34
|
+
};
|
|
35
|
+
interface UseCrudTableConfigStatic<T> extends UseCrudTableConfigBase {
|
|
36
|
+
staticData: T[];
|
|
37
|
+
}
|
|
38
|
+
interface UseCrudTableConfigApi<T> extends UseCrudTableConfigBase {
|
|
39
|
+
api: {
|
|
31
40
|
baseUrl?: string;
|
|
32
41
|
endpoints?: {
|
|
33
42
|
list?: string;
|
|
@@ -41,13 +50,11 @@ export type UseCrudTableConfig<T> = {
|
|
|
41
50
|
response?: (data: any) => CrudResponse<T>;
|
|
42
51
|
};
|
|
43
52
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
onError?: (operation: 'create' | 'update' | 'delete' | 'fetch', error: any) => void;
|
|
50
|
-
};
|
|
53
|
+
}
|
|
54
|
+
interface UseCrudTableConfigCustom<T> extends UseCrudTableConfigBase {
|
|
55
|
+
operations: Partial<CrudOperation<T>>;
|
|
56
|
+
}
|
|
57
|
+
export type UseCrudTableConfig<T> = UseCrudTableConfigStatic<T> | UseCrudTableConfigApi<T> | UseCrudTableConfigCustom<T>;
|
|
51
58
|
export type CrudTableActions<T> = {
|
|
52
59
|
refresh: () => Promise<void>;
|
|
53
60
|
create: (data: Partial<T>) => Promise<T | null>;
|
|
@@ -58,4 +65,5 @@ export type CrudTableActions<T> = {
|
|
|
58
65
|
state: CrudState<T>;
|
|
59
66
|
actionRef: React.RefObject<ActionType | null>;
|
|
60
67
|
};
|
|
61
|
-
export declare const useCrudTable: <T extends Record<string, any>>(rowKey: keyof T, config
|
|
68
|
+
export declare const useCrudTable: <T extends Record<string, any>>(rowKey: keyof T, config: UseCrudTableConfig<T>) => CrudTableActions<T>;
|
|
69
|
+
export {};
|
package/dist/useCrudTable.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("react"),y=require("./index-DygfPlBI.cjs"),M=S=>{const{baseUrl:e="",endpoints:i={},headers:w={},transform:t}=S.api
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("react"),y=require("./index-DygfPlBI.cjs"),M=S=>{const{baseUrl:e="",endpoints:i={},headers:w={},transform:t}=S.api,r={list:"/list",create:"/create",update:"/update",delete:"/delete",...i},n=async(u,c={})=>{const h=await fetch(`${e}${u}`,{headers:{"Content-Type":"application/json",...w},...c});if(!h.ok)throw new Error(`HTTP error! status: ${h.status}`);return h.json()};return{getList:async u=>{const c=new URL(`${e}${r.list}`,window.location.origin);Object.entries(u).forEach(([b,E])=>{E!=null&&c.searchParams.append(b,String(E))});const h=await n(c.pathname+c.search);return t!=null&&t.response?t.response(h):h},create:async u=>{const c=t!=null&&t.request?t.request(u):u;return n(r.create,{method:"POST",body:JSON.stringify(c)})},update:async(u,c)=>{const h=t!=null&&t.request?t.request(c):c;return n(`${r.update}/${u}`,{method:"PUT",body:JSON.stringify(h)})},delete:async u=>{await n(`${r.delete}/${u}`,{method:"DELETE"})}}},D=(S,e)=>{let i=[...S],w=Math.max(...i.map(t=>Number(t[e])||0))+1;return{getList:async t=>{const{current:r=1,pageSize:n=10,sortBy:u,sortOrder:c,...h}=t;let b=[...i];Object.entries(h).forEach(([s,l])=>{l!=null&&l!==""&&(b=b.filter(a=>String(a[s]).toLowerCase().includes(String(l).toLowerCase())))}),u&&c&&b.sort((s,l)=>{const a=s[u],o=l[u],p=a>o?1:a<o?-1:0;return c==="ascend"?p:-p});const E=(r-1)*n;return{data:b.slice(E,E+n),total:b.length,success:!0}},create:async t=>{const r={[e]:w++,...t};return i.push(r),r},update:async(t,r)=>{const n=i.findIndex(u=>u[e]===t);if(n===-1)throw new Error("Item not found");return i[n]={...i[n],...r},i[n]},delete:async t=>{const r=i.findIndex(n=>n[e]===t);if(r===-1)throw new Error("Item not found");i.splice(r,1)}}},T=(S,e)=>{const i=m.useRef(null),[w,t]=m.useState({loading:!1,error:null,data:[],total:0,current:1,pageSize:e.defaultPageSize||10}),r=(()=>{if("operations"in e)return e.operations;if("staticData"in e)return D(e.staticData,S);if("api"in e)return M(e);throw new Error("useCrudTable: Must provide either staticData, api config, or custom operations")})(),n=m.useCallback(async()=>{var d;t(s=>({...s,loading:!0,error:null}));try{const s={current:w.current,pageSize:w.pageSize},l=await r.getList(s);t(a=>({...a,data:l.data,total:l.total,loading:!1}))}catch(s){const l=s instanceof Error?s.message:"Failed to fetch data";t(a=>({...a,loading:!1,error:l})),(d=e.onError)==null||d.call(e,"fetch",s),y.staticMethods.error(l)}},[w.current,w.pageSize,r,e]),u=m.useCallback(async d=>{var s,l;if(!r.create)return y.staticMethods.error("Create operation not supported"),null;try{t(o=>({...o,loading:!0}));const a=await r.create(d);return e.optimisticUpdates?t(o=>({...o,data:[...o.data,a],total:o.total+1,loading:!1})):await n(),(s=e.onSuccess)==null||s.call(e,"create",a),y.staticMethods.success("Created successfully"),a}catch(a){t(p=>({...p,loading:!1}));const o=a instanceof Error?a.message:"Create failed";return(l=e.onError)==null||l.call(e,"create",a),y.staticMethods.error(o),null}},[r,e,n]),c=m.useCallback(async(d,s)=>{var l,a;if(!r.update)return y.staticMethods.error("Update operation not supported"),null;try{t(p=>({...p,loading:!0}));const o=await r.update(d,s);return e.optimisticUpdates?t(p=>({...p,data:p.data.map(C=>C[S]===d?{...C,...o}:C),loading:!1})):await n(),(l=e.onSuccess)==null||l.call(e,"update",o),y.staticMethods.success("Updated successfully"),o}catch(o){t(C=>({...C,loading:!1}));const p=o instanceof Error?o.message:"Update failed";return(a=e.onError)==null||a.call(e,"update",o),y.staticMethods.error(p),null}},[r,e,S,n]),h=m.useCallback(async d=>{var s,l;if(!r.delete)return y.staticMethods.error("Delete operation not supported"),!1;try{return t(a=>({...a,loading:!0})),await r.delete(d),e.optimisticUpdates?t(a=>({...a,data:a.data.filter(o=>o[S]!==d),total:a.total-1,loading:!1})):await n(),(s=e.onSuccess)==null||s.call(e,"delete",d),y.staticMethods.success("Deleted successfully"),!0}catch(a){t(p=>({...p,loading:!1}));const o=a instanceof Error?a.message:"Delete failed";return(l=e.onError)==null||l.call(e,"delete",a),y.staticMethods.error(o),!1}},[r,e,S,n]),b=m.useCallback(d=>{t(s=>({...s,pageSize:d,current:1}))},[]),E=m.useCallback(d=>{t(s=>({...s,current:d}))},[]);return{refresh:n,create:u,update:c,delete:h,setPageSize:b,setCurrentPage:E,state:w,actionRef:i}};exports.useCrudTable=T;
|
package/dist/useCrudTable.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRef as D, useState as U, useCallback as C } from "react";
|
|
2
2
|
import { s as y } from "./index-DNXRWKhU.js";
|
|
3
3
|
const $ = (m) => {
|
|
4
|
-
const { baseUrl: e = "", endpoints: i = {}, headers: S = {}, transform: t } = m.api
|
|
4
|
+
const { baseUrl: e = "", endpoints: i = {}, headers: S = {}, transform: t } = m.api, a = {
|
|
5
5
|
list: "/list",
|
|
6
6
|
create: "/create",
|
|
7
7
|
update: "/update",
|
|
@@ -87,7 +87,7 @@ const $ = (m) => {
|
|
|
87
87
|
i.splice(a, 1);
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
|
-
}, T = (m, e
|
|
90
|
+
}, T = (m, e) => {
|
|
91
91
|
const i = D(null), [S, t] = U({
|
|
92
92
|
loading: !1,
|
|
93
93
|
error: null,
|
|
@@ -96,11 +96,11 @@ const $ = (m) => {
|
|
|
96
96
|
current: 1,
|
|
97
97
|
pageSize: e.defaultPageSize || 10
|
|
98
98
|
}), a = (() => {
|
|
99
|
-
if (e
|
|
99
|
+
if ("operations" in e)
|
|
100
100
|
return e.operations;
|
|
101
|
-
if (e
|
|
101
|
+
if ("staticData" in e)
|
|
102
102
|
return f(e.staticData, m);
|
|
103
|
-
if (e
|
|
103
|
+
if ("api" in e)
|
|
104
104
|
return $(e);
|
|
105
105
|
throw new Error("useCrudTable: Must provide either staticData, api config, or custom operations");
|
|
106
106
|
})(), n = C(async () => {
|