kmod-cli 1.0.11 → 1.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.
@@ -0,0 +1,931 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useState,
5
+ } from 'react';
6
+
7
+ import axios, {
8
+ AxiosError,
9
+ AxiosInstance,
10
+ AxiosRequestConfig,
11
+ } from 'axios';
12
+
13
+ // ============ TYPES ============
14
+
15
+ export interface Pagination {
16
+ current?: number;
17
+ pageSize?: number;
18
+ mode?: 'server' | 'client';
19
+ }
20
+
21
+ export interface Sorter {
22
+ field: string;
23
+ order: 'asc' | 'desc';
24
+ }
25
+
26
+ export type FilterOperator =
27
+ | 'eq'
28
+ | 'ne'
29
+ | 'lt'
30
+ | 'lte'
31
+ | 'gt'
32
+ | 'gte'
33
+ | 'in'
34
+ | 'contains';
35
+
36
+ export interface Filter {
37
+ field: string;
38
+ operator: FilterOperator;
39
+ value: any;
40
+ }
41
+
42
+ export interface GetListParams {
43
+ pagination?: Pagination;
44
+ sorters?: Sorter[];
45
+ filters?: Filter[];
46
+ meta?: AxiosRequestConfig;
47
+ }
48
+
49
+ export interface GetOneParams {
50
+ id: string | number;
51
+ meta?: AxiosRequestConfig;
52
+ }
53
+
54
+ export interface GetManyParams {
55
+ ids: (string | number)[];
56
+ meta?: AxiosRequestConfig;
57
+ }
58
+
59
+ export interface CreateParams<T = any> {
60
+ variables: T;
61
+ meta?: AxiosRequestConfig;
62
+ }
63
+
64
+ export interface CreateManyParams<T = any> {
65
+ variables: T[];
66
+ meta?: AxiosRequestConfig;
67
+ }
68
+
69
+ export interface UpdateParams<T = any> {
70
+ id: string | number;
71
+ variables: Partial<T>;
72
+ meta?: AxiosRequestConfig;
73
+ }
74
+
75
+ export interface UpdateManyParams<T = any> {
76
+ ids: (string | number)[];
77
+ variables: Partial<T>;
78
+ meta?: AxiosRequestConfig;
79
+ }
80
+
81
+ export interface DeleteOneParams {
82
+ id: string | number;
83
+ meta?: AxiosRequestConfig;
84
+ }
85
+
86
+ export interface DeleteManyParams {
87
+ ids: (string | number)[];
88
+ meta?: AxiosRequestConfig;
89
+ }
90
+
91
+ export interface CustomParams {
92
+ url: string;
93
+ method?: 'get' | 'post' | 'put' | 'patch' | 'delete';
94
+ payload?: any;
95
+ query?: Record<string, any>;
96
+ headers?: Record<string, string>;
97
+ }
98
+
99
+ export interface GetListResponse<T = any> {
100
+ data: T[];
101
+ total: number;
102
+ }
103
+
104
+ export interface GetOneResponse<T = any> {
105
+ data: T;
106
+ }
107
+
108
+ export interface GetManyResponse<T = any> {
109
+ data: T[];
110
+ }
111
+
112
+ export interface CreateResponse<T = any> {
113
+ data: T;
114
+ }
115
+
116
+ export interface UpdateResponse<T = any> {
117
+ data: T;
118
+ }
119
+
120
+ export interface DeleteResponse<T = any> {
121
+ data: T;
122
+ }
123
+
124
+ export interface CustomResponse<T = any> {
125
+ data: T;
126
+ }
127
+
128
+ export interface DataProviderError {
129
+ message: string;
130
+ statusCode: number;
131
+ errors?: any;
132
+ }
133
+
134
+ export interface DataProviderOptions {
135
+ cacheTime?: number;
136
+ retryCount?: number;
137
+ retryDelay?: number;
138
+ }
139
+
140
+ interface CacheItem<T = any> {
141
+ data: T;
142
+ timestamp: number;
143
+ }
144
+
145
+ export interface UseListOptions {
146
+ refetchInterval?: number;
147
+ enabled?: boolean;
148
+ }
149
+
150
+ export interface UseOneOptions {
151
+ enabled?: boolean;
152
+ }
153
+
154
+ export interface UseMutationOptions<TData = any> {
155
+ onSuccess?: (data: TData) => void;
156
+ onError?: (error: DataProviderError) => void;
157
+ }
158
+
159
+ // ============ DATA PROVIDER CLASS ============
160
+
161
+ class DataProvider {
162
+ private apiUrl: string;
163
+ private httpClient: AxiosInstance;
164
+ private cache: Map<string, CacheItem>;
165
+ private options: Required<DataProviderOptions>;
166
+
167
+ constructor(
168
+ apiUrl: string,
169
+ httpClient: AxiosInstance = axios,
170
+ options: DataProviderOptions = {}
171
+ ) {
172
+ this.apiUrl = apiUrl;
173
+ this.httpClient = httpClient;
174
+ this.cache = new Map();
175
+ this.options = {
176
+ cacheTime: 5 * 60 * 1000,
177
+ retryCount: 3,
178
+ retryDelay: 1000,
179
+ ...options
180
+ };
181
+ }
182
+
183
+ // Cache helpers
184
+ private getCacheKey(resource: string, params?: any): string {
185
+ return `${resource}:${JSON.stringify(params || {})}`;
186
+ }
187
+
188
+ private getCache<T = any>(key: string): T | null {
189
+ const cached = this.cache.get(key);
190
+ if (!cached) return null;
191
+
192
+ const now = Date.now();
193
+ if (now - cached.timestamp > this.options.cacheTime) {
194
+ this.cache.delete(key);
195
+ return null;
196
+ }
197
+
198
+ return cached.data as T;
199
+ }
200
+
201
+ private setCache<T = any>(key: string, data: T): void {
202
+ this.cache.set(key, {
203
+ data,
204
+ timestamp: Date.now()
205
+ });
206
+ }
207
+
208
+ public invalidateCache(resource: string): void {
209
+ const keys = Array.from(this.cache.keys());
210
+ keys.forEach(key => {
211
+ if (key.startsWith(`${resource}:`)) {
212
+ this.cache.delete(key);
213
+ }
214
+ });
215
+ }
216
+
217
+ public clearAllCache(): void {
218
+ this.cache.clear();
219
+ }
220
+
221
+ // Retry logic
222
+ private async retryRequest<T>(
223
+ fn: () => Promise<T>,
224
+ retries: number = this.options.retryCount
225
+ ): Promise<T> {
226
+ try {
227
+ return await fn();
228
+ } catch (error) {
229
+ if (retries <= 0) throw error;
230
+
231
+ // Không retry với lỗi 4xx (client errors)
232
+ const axiosError = error as AxiosError;
233
+ if (
234
+ axiosError.response &&
235
+ axiosError.response.status >= 400 &&
236
+ axiosError.response.status < 500
237
+ ) {
238
+ throw error;
239
+ }
240
+
241
+ await new Promise(resolve =>
242
+ setTimeout(resolve, this.options.retryDelay)
243
+ );
244
+
245
+ return this.retryRequest(fn, retries - 1);
246
+ }
247
+ }
248
+
249
+ // CRUD Methods
250
+ async getList<T = any>(
251
+ resource: string,
252
+ params: GetListParams = {},
253
+ useCache: boolean = true
254
+ ): Promise<GetListResponse<T>> {
255
+ const cacheKey = this.getCacheKey(resource, params);
256
+
257
+ if (useCache) {
258
+ const cached = this.getCache<GetListResponse<T>>(cacheKey);
259
+ if (cached) return cached;
260
+ }
261
+
262
+ const { pagination, filters, sorters, meta } = params;
263
+ const url = `${this.apiUrl}/${resource}`;
264
+ const query: Record<string, any> = {};
265
+
266
+ if (pagination) {
267
+ const { current = 1, pageSize = 10 } = pagination;
268
+ query._start = (current - 1) * pageSize;
269
+ query._limit = pageSize;
270
+ }
271
+
272
+ if (sorters && sorters.length > 0) {
273
+ query._sort = sorters.map(s => s.field).join(',');
274
+ query._order = sorters.map(s => s.order).join(',');
275
+ }
276
+
277
+ if (filters && filters.length > 0) {
278
+ filters.forEach(filter => {
279
+ const { field, operator, value } = filter;
280
+ switch (operator) {
281
+ case 'eq':
282
+ query[field] = value;
283
+ break;
284
+ case 'ne':
285
+ query[`${field}_ne`] = value;
286
+ break;
287
+ case 'lt':
288
+ query[`${field}_lt`] = value;
289
+ break;
290
+ case 'lte':
291
+ query[`${field}_lte`] = value;
292
+ break;
293
+ case 'gt':
294
+ query[`${field}_gt`] = value;
295
+ break;
296
+ case 'gte':
297
+ query[`${field}_gte`] = value;
298
+ break;
299
+ case 'in':
300
+ query[`${field}_in`] = Array.isArray(value) ? value.join(',') : value;
301
+ break;
302
+ case 'contains':
303
+ query[`${field}_like`] = value;
304
+ break;
305
+ }
306
+ });
307
+ }
308
+
309
+ try {
310
+ const result = await this.retryRequest(async () => {
311
+ const response = await this.httpClient.get<T[] | { data: T[], total: number }>(
312
+ url,
313
+ {
314
+ params: query,
315
+ ...meta
316
+ }
317
+ );
318
+
319
+ const isArrayResponse = Array.isArray(response.data);
320
+ const data = isArrayResponse ? response.data : (response.data as any).data || [];
321
+
322
+ const total = response.headers['x-total-count']
323
+ ? parseInt(response.headers['x-total-count'] as string)
324
+ : (isArrayResponse ? data.length : (response.data as any).total || 0);
325
+
326
+ return {
327
+ data: Array.isArray(data) ? data : [],
328
+ total
329
+ };
330
+ });
331
+
332
+ if (useCache) {
333
+ this.setCache(cacheKey, result);
334
+ }
335
+
336
+ return result;
337
+ } catch (error) {
338
+ throw this.handleError(error);
339
+ }
340
+ }
341
+
342
+ async getOne<T = any>(
343
+ resource: string,
344
+ params: GetOneParams,
345
+ useCache: boolean = true
346
+ ): Promise<GetOneResponse<T>> {
347
+ const { id, meta } = params;
348
+ const cacheKey = this.getCacheKey(`${resource}/${id}`, {});
349
+
350
+ if (useCache) {
351
+ const cached = this.getCache<GetOneResponse<T>>(cacheKey);
352
+ if (cached) return cached;
353
+ }
354
+
355
+ const url = `${this.apiUrl}/${resource}/${id}`;
356
+
357
+ try {
358
+ const result = await this.retryRequest(async () => {
359
+ const response = await this.httpClient.get<T>(url, meta);
360
+ return { data: response.data };
361
+ });
362
+
363
+ if (useCache) {
364
+ this.setCache(cacheKey, result);
365
+ }
366
+
367
+ return result;
368
+ } catch (error) {
369
+ throw this.handleError(error);
370
+ }
371
+ }
372
+
373
+ async getMany<T = any>(
374
+ resource: string,
375
+ params: GetManyParams,
376
+ useCache: boolean = true
377
+ ): Promise<GetManyResponse<T>> {
378
+ const { ids, meta } = params;
379
+ const url = `${this.apiUrl}/${resource}`;
380
+
381
+ try {
382
+ const result = await this.retryRequest(async () => {
383
+ const response = await this.httpClient.get<T[]>(url, {
384
+ params: { id: ids },
385
+ ...meta
386
+ });
387
+ return {
388
+ data: Array.isArray(response.data) ? response.data : []
389
+ };
390
+ });
391
+
392
+ return result;
393
+ } catch (error) {
394
+ throw this.handleError(error);
395
+ }
396
+ }
397
+
398
+ async create<T = any, V = any>(
399
+ resource: string,
400
+ params: CreateParams<V>
401
+ ): Promise<CreateResponse<T>> {
402
+ const { variables, meta } = params;
403
+ const url = `${this.apiUrl}/${resource}`;
404
+
405
+ try {
406
+ const result = await this.retryRequest(async () => {
407
+ const response = await this.httpClient.post<T>(url, variables, meta);
408
+ return { data: response.data };
409
+ });
410
+
411
+ this.invalidateCache(resource);
412
+ return result;
413
+ } catch (error) {
414
+ throw this.handleError(error);
415
+ }
416
+ }
417
+
418
+ async createMany<T = any, V = any>(
419
+ resource: string,
420
+ params: CreateManyParams<V>
421
+ ): Promise<GetManyResponse<T>> {
422
+ const { variables, meta } = params;
423
+
424
+ try {
425
+ const result = await this.retryRequest(async () => {
426
+ const responses = await Promise.all(
427
+ variables.map(variable =>
428
+ this.httpClient.post<T>(`${this.apiUrl}/${resource}`, variable, meta)
429
+ )
430
+ );
431
+ return { data: responses.map(r => r.data) };
432
+ });
433
+
434
+ this.invalidateCache(resource);
435
+ return result;
436
+ } catch (error) {
437
+ throw this.handleError(error);
438
+ }
439
+ }
440
+
441
+ async update<T = any, V = any>(
442
+ resource: string,
443
+ params: UpdateParams<V>
444
+ ): Promise<UpdateResponse<T>> {
445
+ const { id, variables, meta } = params;
446
+ const url = `${this.apiUrl}/${resource}/${id}`;
447
+
448
+ try {
449
+ const result = await this.retryRequest(async () => {
450
+ const response = await this.httpClient.patch<T>(url, variables, meta);
451
+ return { data: response.data };
452
+ });
453
+
454
+ this.invalidateCache(resource);
455
+ return result;
456
+ } catch (error) {
457
+ throw this.handleError(error);
458
+ }
459
+ }
460
+
461
+ async updateMany<T = any, V = any>(
462
+ resource: string,
463
+ params: UpdateManyParams<V>
464
+ ): Promise<GetManyResponse<T>> {
465
+ const { ids, variables, meta } = params;
466
+
467
+ try {
468
+ const result = await this.retryRequest(async () => {
469
+ const responses = await Promise.all(
470
+ ids.map(id =>
471
+ this.httpClient.patch<T>(
472
+ `${this.apiUrl}/${resource}/${id}`,
473
+ variables,
474
+ meta
475
+ )
476
+ )
477
+ );
478
+ return { data: responses.map(r => r.data) };
479
+ });
480
+
481
+ this.invalidateCache(resource);
482
+ return result;
483
+ } catch (error) {
484
+ throw this.handleError(error);
485
+ }
486
+ }
487
+
488
+ async deleteOne<T = any>(
489
+ resource: string,
490
+ params: DeleteOneParams
491
+ ): Promise<DeleteResponse<T>> {
492
+ const { id, meta } = params;
493
+ const url = `${this.apiUrl}/${resource}/${id}`;
494
+
495
+ try {
496
+ const result = await this.retryRequest(async () => {
497
+ const response = await this.httpClient.delete<T>(url, meta);
498
+ return { data: response.data };
499
+ });
500
+
501
+ this.invalidateCache(resource);
502
+ return result;
503
+ } catch (error) {
504
+ throw this.handleError(error);
505
+ }
506
+ }
507
+
508
+ async deleteMany<T = any>(
509
+ resource: string,
510
+ params: DeleteManyParams
511
+ ): Promise<GetManyResponse<T>> {
512
+ const { ids, meta } = params;
513
+
514
+ try {
515
+ const result = await this.retryRequest(async () => {
516
+ const responses = await Promise.all(
517
+ ids.map(id =>
518
+ this.httpClient.delete<T>(`${this.apiUrl}/${resource}/${id}`, meta)
519
+ )
520
+ );
521
+ return { data: responses.map(r => r.data) };
522
+ });
523
+
524
+ this.invalidateCache(resource);
525
+ return result;
526
+ } catch (error) {
527
+ throw this.handleError(error);
528
+ }
529
+ }
530
+
531
+ async custom<T = any>(params: CustomParams): Promise<CustomResponse<T>> {
532
+ const { url, method = 'get', payload, query, headers } = params;
533
+
534
+ try {
535
+ return await this.retryRequest(async () => {
536
+ const response = await this.httpClient<T>({
537
+ url: `${this.apiUrl}${url}`,
538
+ method,
539
+ data: payload,
540
+ params: query,
541
+ headers,
542
+ });
543
+ return { data: response.data };
544
+ });
545
+ } catch (error) {
546
+ throw this.handleError(error);
547
+ }
548
+ }
549
+
550
+ private handleError(error: unknown): DataProviderError {
551
+ const axiosError = error as AxiosError<any>;
552
+
553
+ if (axiosError.response) {
554
+ return {
555
+ message: axiosError.response.data?.message || axiosError.message || 'Request failed',
556
+ statusCode: axiosError.response.status,
557
+ errors: axiosError.response.data?.errors,
558
+ };
559
+ } else if (axiosError.request) {
560
+ return {
561
+ message: 'Network error',
562
+ statusCode: 0,
563
+ };
564
+ }
565
+
566
+ return {
567
+ message: (error as Error).message || 'Unknown error',
568
+ statusCode: 0,
569
+ };
570
+ }
571
+ }
572
+
573
+ // ============ REACT HOOKS ============
574
+
575
+ export function useList<T = any>(
576
+ dataProvider: DataProvider,
577
+ resource: string,
578
+ params: GetListParams = {},
579
+ options: UseListOptions = {}
580
+ ) {
581
+ const [data, setData] = useState<T[]>([]);
582
+ const [total, setTotal] = useState<number>(0);
583
+ const [loading, setLoading] = useState<boolean>(true);
584
+ const [error, setError] = useState<DataProviderError | null>(null);
585
+
586
+ const { refetchInterval, enabled = true } = options;
587
+ const paramsStr = JSON.stringify(params);
588
+
589
+ const refetch = useCallback(async () => {
590
+ if (!enabled) return;
591
+
592
+ try {
593
+ setLoading(true);
594
+ setError(null);
595
+ const result = await dataProvider.getList<T>(resource, JSON.parse(paramsStr));
596
+ setData(result.data || []);
597
+ setTotal(result.total || 0);
598
+ } catch (err) {
599
+ setError(err as DataProviderError);
600
+ setData([]);
601
+ setTotal(0);
602
+ } finally {
603
+ setLoading(false);
604
+ }
605
+ }, [dataProvider, resource, paramsStr, enabled]);
606
+
607
+ useEffect(() => {
608
+ refetch();
609
+ }, [refetch]);
610
+
611
+ useEffect(() => {
612
+ if (refetchInterval && enabled) {
613
+ const interval = setInterval(refetch, refetchInterval);
614
+ return () => clearInterval(interval);
615
+ }
616
+ }, [refetchInterval, refetch, enabled]);
617
+
618
+ return { data, total, loading, error, refetch };
619
+ }
620
+
621
+ export function useOne<T = any>(
622
+ dataProvider: DataProvider,
623
+ resource: string,
624
+ id: string | number | null | undefined,
625
+ options: UseOneOptions = {}
626
+ ) {
627
+ const [data, setData] = useState<T | null>(null);
628
+ const [loading, setLoading] = useState<boolean>(true);
629
+ const [error, setError] = useState<DataProviderError | null>(null);
630
+
631
+ const { enabled = true } = options;
632
+
633
+ const refetch = useCallback(async () => {
634
+ if (!enabled || !id) {
635
+ setLoading(false);
636
+ return;
637
+ }
638
+
639
+ try {
640
+ setLoading(true);
641
+ setError(null);
642
+ const result = await dataProvider.getOne<T>(resource, { id });
643
+ setData(result.data);
644
+ } catch (err) {
645
+ setError(err as DataProviderError);
646
+ setData(null);
647
+ } finally {
648
+ setLoading(false);
649
+ }
650
+ }, [dataProvider, resource, id, enabled]);
651
+
652
+ useEffect(() => {
653
+ refetch();
654
+ }, [refetch]);
655
+
656
+ return { data, loading, error, refetch };
657
+ }
658
+
659
+ export function useCreate<T = any, V = any>(
660
+ dataProvider: DataProvider,
661
+ resource: string,
662
+ options: UseMutationOptions<T> = {}
663
+ ) {
664
+ const [loading, setLoading] = useState<boolean>(false);
665
+ const [error, setError] = useState<DataProviderError | null>(null);
666
+
667
+ const { onSuccess, onError } = options;
668
+
669
+ const mutate = useCallback(async (variables: V): Promise<T> => {
670
+ setLoading(true);
671
+ setError(null);
672
+
673
+ try {
674
+ const result = await dataProvider.create<T, V>(resource, { variables });
675
+ if (onSuccess) {
676
+ onSuccess(result.data);
677
+ }
678
+ return result.data;
679
+ } catch (err) {
680
+ const errorObj = err as DataProviderError;
681
+ setError(errorObj);
682
+ if (onError) {
683
+ onError(errorObj);
684
+ }
685
+ throw errorObj;
686
+ } finally {
687
+ setLoading(false);
688
+ }
689
+ }, [dataProvider, resource, onSuccess, onError]);
690
+
691
+ return { mutate, loading, error };
692
+ }
693
+
694
+ export function useUpdate<T = any, V = any>(
695
+ dataProvider: DataProvider,
696
+ resource: string,
697
+ options: UseMutationOptions<T> = {}
698
+ ) {
699
+ const [loading, setLoading] = useState<boolean>(false);
700
+ const [error, setError] = useState<DataProviderError | null>(null);
701
+
702
+ const { onSuccess, onError } = options;
703
+
704
+ const mutate = useCallback(
705
+ async (id: string | number, variables: Partial<V>): Promise<T> => {
706
+ setLoading(true);
707
+ setError(null);
708
+
709
+ try {
710
+ const result = await dataProvider.update<T, V>(resource, { id, variables });
711
+ if (onSuccess) {
712
+ onSuccess(result.data);
713
+ }
714
+ return result.data;
715
+ } catch (err) {
716
+ const errorObj = err as DataProviderError;
717
+ setError(errorObj);
718
+ if (onError) {
719
+ onError(errorObj);
720
+ }
721
+ throw errorObj;
722
+ } finally {
723
+ setLoading(false);
724
+ }
725
+ },
726
+ [dataProvider, resource, onSuccess, onError]
727
+ );
728
+
729
+ return { mutate, loading, error };
730
+ }
731
+
732
+ export function useDelete<T = any>(
733
+ dataProvider: DataProvider,
734
+ resource: string,
735
+ options: UseMutationOptions<T> = {}
736
+ ) {
737
+ const [loading, setLoading] = useState<boolean>(false);
738
+ const [error, setError] = useState<DataProviderError | null>(null);
739
+
740
+ const { onSuccess, onError } = options;
741
+
742
+ const mutate = useCallback(
743
+ async (id: string | number): Promise<T> => {
744
+ setLoading(true);
745
+ setError(null);
746
+
747
+ try {
748
+ const result = await dataProvider.deleteOne<T>(resource, { id });
749
+ if (onSuccess) {
750
+ onSuccess(result.data);
751
+ }
752
+ return result.data;
753
+ } catch (err) {
754
+ const errorObj = err as DataProviderError;
755
+ setError(errorObj);
756
+ if (onError) {
757
+ onError(errorObj);
758
+ }
759
+ throw errorObj;
760
+ } finally {
761
+ setLoading(false);
762
+ }
763
+ },
764
+ [dataProvider, resource, onSuccess, onError]
765
+ );
766
+
767
+ return { mutate, loading, error };
768
+ }
769
+
770
+ export default DataProvider;
771
+
772
+ /* ============ VÍ DỤ SỬ DỤNG ============
773
+
774
+ // 1. Setup DataProvider
775
+ import DataProvider, { useList, useOne, useCreate, useUpdate, useDelete } from './DataProvider';
776
+ import axios from 'axios';
777
+
778
+ interface User {
779
+ id: number;
780
+ name: string;
781
+ email: string;
782
+ status: 'active' | 'inactive';
783
+ createdAt: string;
784
+ }
785
+
786
+ interface CreateUserInput {
787
+ name: string;
788
+ email: string;
789
+ status?: 'active' | 'inactive';
790
+ }
791
+
792
+ const axiosInstance = axios.create({
793
+ baseURL: 'https://api.example.com',
794
+ });
795
+
796
+ axiosInstance.interceptors.request.use(config => {
797
+ const token = localStorage.getItem('token');
798
+ if (token) {
799
+ config.headers.Authorization = `Bearer ${token}`;
800
+ }
801
+ return config;
802
+ });
803
+
804
+ const dataProvider = new DataProvider('', axiosInstance, {
805
+ cacheTime: 5 * 60 * 1000,
806
+ retryCount: 3,
807
+ retryDelay: 1000
808
+ });
809
+
810
+ // 2. Sử dụng trong component
811
+ function UsersList() {
812
+ const [page, setPage] = useState(1);
813
+
814
+ const { data, total, loading, error, refetch } = useList<User>(
815
+ dataProvider,
816
+ 'users',
817
+ {
818
+ pagination: { current: page, pageSize: 10 },
819
+ sorters: [{ field: 'createdAt', order: 'desc' }],
820
+ filters: [{ field: 'status', operator: 'eq', value: 'active' }]
821
+ },
822
+ { refetchInterval: 30000 }
823
+ );
824
+
825
+ const { mutate: createUser, loading: creating } = useCreate<User, CreateUserInput>(
826
+ dataProvider,
827
+ 'users',
828
+ {
829
+ onSuccess: (data) => {
830
+ console.log('Created:', data);
831
+ refetch();
832
+ },
833
+ onError: (err) => {
834
+ console.error('Error:', err);
835
+ }
836
+ }
837
+ );
838
+
839
+ const { mutate: updateUser } = useUpdate<User, Partial<User>>(
840
+ dataProvider,
841
+ 'users',
842
+ {
843
+ onSuccess: () => refetch()
844
+ }
845
+ );
846
+
847
+ const { mutate: deleteUser } = useDelete<User>(
848
+ dataProvider,
849
+ 'users',
850
+ {
851
+ onSuccess: () => refetch()
852
+ }
853
+ );
854
+
855
+ const handleCreate = async () => {
856
+ try {
857
+ await createUser({
858
+ name: 'John Doe',
859
+ email: 'john@example.com',
860
+ status: 'active'
861
+ });
862
+ } catch (err) {
863
+ // Error handled
864
+ }
865
+ };
866
+
867
+ const handleUpdate = async (id: number) => {
868
+ try {
869
+ await updateUser(id, { name: 'Jane Doe' });
870
+ } catch (err) {
871
+ // Error handled
872
+ }
873
+ };
874
+
875
+ const handleDelete = async (id: number) => {
876
+ try {
877
+ await deleteUser(id);
878
+ } catch (err) {
879
+ // Error handled
880
+ }
881
+ };
882
+
883
+ if (loading) return <div>Loading...</div>;
884
+ if (error) return <div>Error: {error.message}</div>;
885
+
886
+ return (
887
+ <div>
888
+ <button onClick={handleCreate} disabled={creating}>
889
+ Create User
890
+ </button>
891
+
892
+ {data.map(user => (
893
+ <div key={user.id}>
894
+ <span>{user.name}</span>
895
+ <button onClick={() => handleUpdate(user.id)}>Edit</button>
896
+ <button onClick={() => handleDelete(user.id)}>Delete</button>
897
+ </div>
898
+ ))}
899
+
900
+ <button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
901
+ Previous
902
+ </button>
903
+ <button onClick={() => setPage(p => p + 1)}>Next</button>
904
+
905
+ <div>Total: {total}</div>
906
+ </div>
907
+ );
908
+ }
909
+
910
+ // 3. Chi tiết user
911
+ function UserDetail({ userId }: { userId: number }) {
912
+ const { data, loading, error, refetch } = useOne<User>(
913
+ dataProvider,
914
+ 'users',
915
+ userId
916
+ );
917
+
918
+ if (loading) return <div>Loading...</div>;
919
+ if (error) return <div>Error: {error.message}</div>;
920
+ if (!data) return <div>No data</div>;
921
+
922
+ return (
923
+ <div>
924
+ <h1>{data.name}</h1>
925
+ <p>{data.email}</p>
926
+ <button onClick={refetch}>Refresh</button>
927
+ </div>
928
+ );
929
+ }
930
+
931
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kmod-cli",
3
- "version": "1.0.11",
3
+ "version": "1.1.0",
4
4
  "description": "Stack components utilities fast setup in projects",
5
5
  "author": "kumo_d",
6
6
  "license": "MIT",