@openway/ui 1.0.1 → 1.0.2
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/dist/chunk-X4LIYOS5.js +3 -0
- package/dist/chunk-X4LIYOS5.js.map +1 -0
- package/dist/index.d.cts +4 -276
- package/dist/index.d.ts +4 -276
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +125 -3
- package/dist/query.d.ts +125 -3
- package/dist/query.js +1 -1
- package/dist/query.js.map +1 -1
- package/dist/{useInfiniteScroll-DAlgjUN4.d.cts → useInfiniteScroll-D9AW6cQV.d.cts} +274 -1
- package/dist/{useInfiniteScroll-DAlgjUN4.d.ts → useInfiniteScroll-D9AW6cQV.d.ts} +274 -1
- package/docs/hooks/useMutationApp.md +242 -0
- package/package.json +1 -1
- package/dist/chunk-CJQNI3QX.js +0 -3
- package/dist/chunk-CJQNI3QX.js.map +0 -1
package/dist/query.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { QueryFunctionContext, UseQueryOptions, UseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult } from '@tanstack/react-query';
|
|
1
|
+
import { QueryFunctionContext, UseQueryOptions, UseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, QueryKey, InvalidateQueryFilters, DefaultError, UseMutationOptions, InvalidateOptions, UseMutationResult } from '@tanstack/react-query';
|
|
2
2
|
import { RowData, SortingState, ColumnFiltersState, PaginationState, OnChangeFn } from '@tanstack/react-table';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { d as SelectOptionItem, U as UseInfiniteScrollOptions, a as ToastOptions, o as AlertVariant } from './useInfiniteScroll-D9AW6cQV.cjs';
|
|
5
|
+
import 'sonner';
|
|
5
6
|
import '@floating-ui/react';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -306,4 +307,125 @@ interface UseSelectInfiniteQueryReturn<TData = unknown, TResponse = unknown, TFi
|
|
|
306
307
|
*/
|
|
307
308
|
declare function useSelectInfiniteQuery<TData = unknown, TResponse = unknown, TPageParam = number, TFilters extends Record<string, unknown> = Record<string, unknown>, TError = Error>({ queryKey, queryFn, initialPageParam, getNextPageParam, getPreviousPageParam, selectOptions, mapOption, debounceMs, initialSearch, initialFilters, initialOptions, skeletonLines, skeletonHeight, renderLoadingMore, endMessage, scrollOptions, queryOptions, }: UseSelectInfiniteQueryOptions<TData, TResponse, TPageParam, TFilters, TError>): UseSelectInfiniteQueryReturn<TData, TResponse, TFilters, TError>;
|
|
308
309
|
|
|
309
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Mục tiêu invalidate cache: có thể là QueryKey (readonly unknown[]) hoặc InvalidateQueryFilters.
|
|
312
|
+
*/
|
|
313
|
+
type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;
|
|
314
|
+
/**
|
|
315
|
+
* Tùy chọn cấu hình Toast cho hook `useMutationApp`.
|
|
316
|
+
*/
|
|
317
|
+
interface UseMutationAppToastOptions<TData, TError, TVariables> {
|
|
318
|
+
/**
|
|
319
|
+
* Nội dung thông báo khi đang thực thi mutation (loading spinner, không tự đóng).
|
|
320
|
+
* Có thể là ReactNode hoặc một hàm nhận vào `variables`.
|
|
321
|
+
*/
|
|
322
|
+
loading?: ReactNode | ((variables: TVariables) => ReactNode);
|
|
323
|
+
/**
|
|
324
|
+
* Nội dung thông báo khi mutation hoàn tất thành công.
|
|
325
|
+
* Có thể là ReactNode hoặc một hàm nhận vào `(data, variables)`.
|
|
326
|
+
*/
|
|
327
|
+
success?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);
|
|
328
|
+
/**
|
|
329
|
+
* Nội dung thông báo khi mutation thất bại.
|
|
330
|
+
* - `true`: Tự động trích xuất thông điệp lỗi từ API/Error qua `extractErrorMessage`.
|
|
331
|
+
* - `false`: Không hiển thị toast lỗi.
|
|
332
|
+
* - `ReactNode`: Thông điệp lỗi cố định.
|
|
333
|
+
* - `(error, variables) => ReactNode`: Hàm tạo thông điệp lỗi linh hoạt.
|
|
334
|
+
* @default true
|
|
335
|
+
*/
|
|
336
|
+
error?: boolean | ReactNode | ((error: TError, variables: TVariables) => ReactNode);
|
|
337
|
+
/**
|
|
338
|
+
* Nội dung mô tả chi tiết (description) cho thông báo khi đang loading.
|
|
339
|
+
*/
|
|
340
|
+
loadingDescription?: ReactNode | ((variables: TVariables) => ReactNode);
|
|
341
|
+
/**
|
|
342
|
+
* Nội dung mô tả chi tiết (description) cho thông báo thành công.
|
|
343
|
+
*/
|
|
344
|
+
successDescription?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);
|
|
345
|
+
/**
|
|
346
|
+
* Nội dung mô tả chi tiết (description) cho thông báo lỗi.
|
|
347
|
+
*/
|
|
348
|
+
errorDescription?: ReactNode | ((error: TError, variables: TVariables) => ReactNode);
|
|
349
|
+
/**
|
|
350
|
+
* Tùy chọn cấu hình bổ sung cho Toast (duration, position, size, radius, closable...).
|
|
351
|
+
*/
|
|
352
|
+
options?: ToastOptions;
|
|
353
|
+
/**
|
|
354
|
+
* Kiểu hiển thị Toast: "soft" | "solid" | "outline".
|
|
355
|
+
* @default "soft"
|
|
356
|
+
*/
|
|
357
|
+
variant?: AlertVariant;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Các tùy chọn cấu hình cho hook `useMutationApp`.
|
|
361
|
+
*/
|
|
362
|
+
interface UseMutationAppOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> extends Omit<UseMutationOptions<TData, TError, TVariables, TContext>, "mutationFn"> {
|
|
363
|
+
/**
|
|
364
|
+
* Hàm gọi API hoặc thực hiện tác vụ bất đồng bộ.
|
|
365
|
+
*/
|
|
366
|
+
mutationFn?: (variables: TVariables) => Promise<TData>;
|
|
367
|
+
/**
|
|
368
|
+
* Khóa truy vấn hoặc danh sách khóa truy vấn cần tự động làm mới (invalidate) khi mutation thành công.
|
|
369
|
+
* Hỗ trợ:
|
|
370
|
+
* - Một QueryKey đơn: `["teachers"]` hoặc `["users", 1]`
|
|
371
|
+
* - Danh sách nhiều QueryKey: `[["teachers"], ["classes"]]`
|
|
372
|
+
* - Bộ lọc nâng cao InvalidateQueryFilters: `{ queryKey: ["teachers"], exact: true }`
|
|
373
|
+
* - Danh sách bộ lọc: `[{ queryKey: ["teachers"] }, { queryKey: ["classes"] }]`
|
|
374
|
+
* - Hàm trả về động: `(data, variables) => [["teachers"], ["classes", variables.classId]]`
|
|
375
|
+
*/
|
|
376
|
+
invalidateQueries?: InvalidateQueryTarget | InvalidateQueryTarget[] | ((data: TData, variables: TVariables) => InvalidateQueryTarget | InvalidateQueryTarget[] | void | null | undefined);
|
|
377
|
+
/**
|
|
378
|
+
* Tùy chọn InvalidateOptions bổ sung truyền cho `queryClient.invalidateQueries`
|
|
379
|
+
* (ví dụ: `throwOnError`, `cancelRefetch`).
|
|
380
|
+
*/
|
|
381
|
+
invalidateOptions?: InvalidateOptions;
|
|
382
|
+
/**
|
|
383
|
+
* Cấu hình hiển thị thông báo Toast.
|
|
384
|
+
* - `false`: Tắt toàn bộ thông báo Toast.
|
|
385
|
+
* - `true`: Bật Toast với cấu hình mặc định (tự động báo lỗi nếu có).
|
|
386
|
+
* - `object`: Cấu hình chi tiết `loading`, `success`, `error`.
|
|
387
|
+
*/
|
|
388
|
+
toast?: boolean | UseMutationAppToastOptions<TData, TError, TVariables>;
|
|
389
|
+
/**
|
|
390
|
+
* Shortcut cấu hình nhanh thông báo thành công.
|
|
391
|
+
*/
|
|
392
|
+
successMessage?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);
|
|
393
|
+
/**
|
|
394
|
+
* Shortcut cấu hình nhanh thông báo lỗi.
|
|
395
|
+
*/
|
|
396
|
+
errorMessage?: ReactNode | ((error: TError, variables: TVariables) => ReactNode);
|
|
397
|
+
/**
|
|
398
|
+
* Shortcut cấu hình nhanh thông báo loading.
|
|
399
|
+
*/
|
|
400
|
+
loadingMessage?: ReactNode | ((variables: TVariables) => ReactNode);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Kết quả trả về từ hook `useMutationApp`.
|
|
404
|
+
*/
|
|
405
|
+
type UseMutationAppReturn<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> = UseMutationResult<TData, TError, TVariables, TContext> & {
|
|
406
|
+
/**
|
|
407
|
+
* Bí danh (alias) của `isPending`, tương thích ngược với thói quen sử dụng `isLoading` của TanStack Query v4.
|
|
408
|
+
*/
|
|
409
|
+
isLoading: boolean;
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Hàm hỗ trợ tự động trích xuất thông điệp lỗi (error message) từ nhiều định dạng lỗi phổ biến:
|
|
413
|
+
* - Axios Error response (`error.response?.data?.message`, `error.response?.data?.error`...)
|
|
414
|
+
* - Fetch API response / HTTP status
|
|
415
|
+
* - Standard JS Error (`error.message`)
|
|
416
|
+
* - String error
|
|
417
|
+
*/
|
|
418
|
+
declare function extractErrorMessage(error: unknown, fallback?: string): string;
|
|
419
|
+
/**
|
|
420
|
+
* Hook `useMutationApp` - Tối ưu hóa tác vụ Mutation cho ứng dụng với TanStack Query v5 & `@openway/ui`.
|
|
421
|
+
*
|
|
422
|
+
* Tính năng chính:
|
|
423
|
+
* - **Tự động hóa Toast**: Hiển thị Toast loading khi xử lý, tự động cập nhật Toast success/error mượt mà.
|
|
424
|
+
* - **Tự động bóc tách lỗi**: Bóc tách thông điệp lỗi chính xác từ Axios / API response qua `extractErrorMessage`.
|
|
425
|
+
* - **Tự động Invalidate Cache**: Refresh tự động các query liên quan (như DataTable, Select) khi hoàn tất.
|
|
426
|
+
* - **Bổ sung `isLoading`**: Cung cấp alias `isLoading` cho `isPending`, thuận tiện khi code giao diện.
|
|
427
|
+
* - **Giữ trọn vẹn sức mạnh của TanStack Query**: Hỗ trợ đầy đủ `onMutate`, `onSuccess`, `onError`, `onSettled`.
|
|
428
|
+
*/
|
|
429
|
+
declare function useMutationApp<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown>(options: UseMutationAppOptions<TData, TError, TVariables, TContext>): UseMutationAppReturn<TData, TError, TVariables, TContext>;
|
|
430
|
+
|
|
431
|
+
export { type InvalidateQueryTarget, type SelectQueryParams, type TableQueryParams, type UseMutationAppOptions, type UseMutationAppReturn, type UseMutationAppToastOptions, type UseSelectInfiniteQueryOptions, type UseSelectInfiniteQueryReturn, type UseTableQueryOptions, type UseTableQueryReturn, extractErrorMessage, useMutationApp, useSelectInfiniteQuery, useTableQuery };
|
package/dist/query.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { QueryFunctionContext, UseQueryOptions, UseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult } from '@tanstack/react-query';
|
|
1
|
+
import { QueryFunctionContext, UseQueryOptions, UseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, QueryKey, InvalidateQueryFilters, DefaultError, UseMutationOptions, InvalidateOptions, UseMutationResult } from '@tanstack/react-query';
|
|
2
2
|
import { RowData, SortingState, ColumnFiltersState, PaginationState, OnChangeFn } from '@tanstack/react-table';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { d as SelectOptionItem, U as UseInfiniteScrollOptions, a as ToastOptions, o as AlertVariant } from './useInfiniteScroll-D9AW6cQV.js';
|
|
5
|
+
import 'sonner';
|
|
5
6
|
import '@floating-ui/react';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -306,4 +307,125 @@ interface UseSelectInfiniteQueryReturn<TData = unknown, TResponse = unknown, TFi
|
|
|
306
307
|
*/
|
|
307
308
|
declare function useSelectInfiniteQuery<TData = unknown, TResponse = unknown, TPageParam = number, TFilters extends Record<string, unknown> = Record<string, unknown>, TError = Error>({ queryKey, queryFn, initialPageParam, getNextPageParam, getPreviousPageParam, selectOptions, mapOption, debounceMs, initialSearch, initialFilters, initialOptions, skeletonLines, skeletonHeight, renderLoadingMore, endMessage, scrollOptions, queryOptions, }: UseSelectInfiniteQueryOptions<TData, TResponse, TPageParam, TFilters, TError>): UseSelectInfiniteQueryReturn<TData, TResponse, TFilters, TError>;
|
|
308
309
|
|
|
309
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Mục tiêu invalidate cache: có thể là QueryKey (readonly unknown[]) hoặc InvalidateQueryFilters.
|
|
312
|
+
*/
|
|
313
|
+
type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;
|
|
314
|
+
/**
|
|
315
|
+
* Tùy chọn cấu hình Toast cho hook `useMutationApp`.
|
|
316
|
+
*/
|
|
317
|
+
interface UseMutationAppToastOptions<TData, TError, TVariables> {
|
|
318
|
+
/**
|
|
319
|
+
* Nội dung thông báo khi đang thực thi mutation (loading spinner, không tự đóng).
|
|
320
|
+
* Có thể là ReactNode hoặc một hàm nhận vào `variables`.
|
|
321
|
+
*/
|
|
322
|
+
loading?: ReactNode | ((variables: TVariables) => ReactNode);
|
|
323
|
+
/**
|
|
324
|
+
* Nội dung thông báo khi mutation hoàn tất thành công.
|
|
325
|
+
* Có thể là ReactNode hoặc một hàm nhận vào `(data, variables)`.
|
|
326
|
+
*/
|
|
327
|
+
success?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);
|
|
328
|
+
/**
|
|
329
|
+
* Nội dung thông báo khi mutation thất bại.
|
|
330
|
+
* - `true`: Tự động trích xuất thông điệp lỗi từ API/Error qua `extractErrorMessage`.
|
|
331
|
+
* - `false`: Không hiển thị toast lỗi.
|
|
332
|
+
* - `ReactNode`: Thông điệp lỗi cố định.
|
|
333
|
+
* - `(error, variables) => ReactNode`: Hàm tạo thông điệp lỗi linh hoạt.
|
|
334
|
+
* @default true
|
|
335
|
+
*/
|
|
336
|
+
error?: boolean | ReactNode | ((error: TError, variables: TVariables) => ReactNode);
|
|
337
|
+
/**
|
|
338
|
+
* Nội dung mô tả chi tiết (description) cho thông báo khi đang loading.
|
|
339
|
+
*/
|
|
340
|
+
loadingDescription?: ReactNode | ((variables: TVariables) => ReactNode);
|
|
341
|
+
/**
|
|
342
|
+
* Nội dung mô tả chi tiết (description) cho thông báo thành công.
|
|
343
|
+
*/
|
|
344
|
+
successDescription?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);
|
|
345
|
+
/**
|
|
346
|
+
* Nội dung mô tả chi tiết (description) cho thông báo lỗi.
|
|
347
|
+
*/
|
|
348
|
+
errorDescription?: ReactNode | ((error: TError, variables: TVariables) => ReactNode);
|
|
349
|
+
/**
|
|
350
|
+
* Tùy chọn cấu hình bổ sung cho Toast (duration, position, size, radius, closable...).
|
|
351
|
+
*/
|
|
352
|
+
options?: ToastOptions;
|
|
353
|
+
/**
|
|
354
|
+
* Kiểu hiển thị Toast: "soft" | "solid" | "outline".
|
|
355
|
+
* @default "soft"
|
|
356
|
+
*/
|
|
357
|
+
variant?: AlertVariant;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Các tùy chọn cấu hình cho hook `useMutationApp`.
|
|
361
|
+
*/
|
|
362
|
+
interface UseMutationAppOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> extends Omit<UseMutationOptions<TData, TError, TVariables, TContext>, "mutationFn"> {
|
|
363
|
+
/**
|
|
364
|
+
* Hàm gọi API hoặc thực hiện tác vụ bất đồng bộ.
|
|
365
|
+
*/
|
|
366
|
+
mutationFn?: (variables: TVariables) => Promise<TData>;
|
|
367
|
+
/**
|
|
368
|
+
* Khóa truy vấn hoặc danh sách khóa truy vấn cần tự động làm mới (invalidate) khi mutation thành công.
|
|
369
|
+
* Hỗ trợ:
|
|
370
|
+
* - Một QueryKey đơn: `["teachers"]` hoặc `["users", 1]`
|
|
371
|
+
* - Danh sách nhiều QueryKey: `[["teachers"], ["classes"]]`
|
|
372
|
+
* - Bộ lọc nâng cao InvalidateQueryFilters: `{ queryKey: ["teachers"], exact: true }`
|
|
373
|
+
* - Danh sách bộ lọc: `[{ queryKey: ["teachers"] }, { queryKey: ["classes"] }]`
|
|
374
|
+
* - Hàm trả về động: `(data, variables) => [["teachers"], ["classes", variables.classId]]`
|
|
375
|
+
*/
|
|
376
|
+
invalidateQueries?: InvalidateQueryTarget | InvalidateQueryTarget[] | ((data: TData, variables: TVariables) => InvalidateQueryTarget | InvalidateQueryTarget[] | void | null | undefined);
|
|
377
|
+
/**
|
|
378
|
+
* Tùy chọn InvalidateOptions bổ sung truyền cho `queryClient.invalidateQueries`
|
|
379
|
+
* (ví dụ: `throwOnError`, `cancelRefetch`).
|
|
380
|
+
*/
|
|
381
|
+
invalidateOptions?: InvalidateOptions;
|
|
382
|
+
/**
|
|
383
|
+
* Cấu hình hiển thị thông báo Toast.
|
|
384
|
+
* - `false`: Tắt toàn bộ thông báo Toast.
|
|
385
|
+
* - `true`: Bật Toast với cấu hình mặc định (tự động báo lỗi nếu có).
|
|
386
|
+
* - `object`: Cấu hình chi tiết `loading`, `success`, `error`.
|
|
387
|
+
*/
|
|
388
|
+
toast?: boolean | UseMutationAppToastOptions<TData, TError, TVariables>;
|
|
389
|
+
/**
|
|
390
|
+
* Shortcut cấu hình nhanh thông báo thành công.
|
|
391
|
+
*/
|
|
392
|
+
successMessage?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);
|
|
393
|
+
/**
|
|
394
|
+
* Shortcut cấu hình nhanh thông báo lỗi.
|
|
395
|
+
*/
|
|
396
|
+
errorMessage?: ReactNode | ((error: TError, variables: TVariables) => ReactNode);
|
|
397
|
+
/**
|
|
398
|
+
* Shortcut cấu hình nhanh thông báo loading.
|
|
399
|
+
*/
|
|
400
|
+
loadingMessage?: ReactNode | ((variables: TVariables) => ReactNode);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Kết quả trả về từ hook `useMutationApp`.
|
|
404
|
+
*/
|
|
405
|
+
type UseMutationAppReturn<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> = UseMutationResult<TData, TError, TVariables, TContext> & {
|
|
406
|
+
/**
|
|
407
|
+
* Bí danh (alias) của `isPending`, tương thích ngược với thói quen sử dụng `isLoading` của TanStack Query v4.
|
|
408
|
+
*/
|
|
409
|
+
isLoading: boolean;
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Hàm hỗ trợ tự động trích xuất thông điệp lỗi (error message) từ nhiều định dạng lỗi phổ biến:
|
|
413
|
+
* - Axios Error response (`error.response?.data?.message`, `error.response?.data?.error`...)
|
|
414
|
+
* - Fetch API response / HTTP status
|
|
415
|
+
* - Standard JS Error (`error.message`)
|
|
416
|
+
* - String error
|
|
417
|
+
*/
|
|
418
|
+
declare function extractErrorMessage(error: unknown, fallback?: string): string;
|
|
419
|
+
/**
|
|
420
|
+
* Hook `useMutationApp` - Tối ưu hóa tác vụ Mutation cho ứng dụng với TanStack Query v5 & `@openway/ui`.
|
|
421
|
+
*
|
|
422
|
+
* Tính năng chính:
|
|
423
|
+
* - **Tự động hóa Toast**: Hiển thị Toast loading khi xử lý, tự động cập nhật Toast success/error mượt mà.
|
|
424
|
+
* - **Tự động bóc tách lỗi**: Bóc tách thông điệp lỗi chính xác từ Axios / API response qua `extractErrorMessage`.
|
|
425
|
+
* - **Tự động Invalidate Cache**: Refresh tự động các query liên quan (như DataTable, Select) khi hoàn tất.
|
|
426
|
+
* - **Bổ sung `isLoading`**: Cung cấp alias `isLoading` cho `isPending`, thuận tiện khi code giao diện.
|
|
427
|
+
* - **Giữ trọn vẹn sức mạnh của TanStack Query**: Hỗ trợ đầy đủ `onMutate`, `onSuccess`, `onError`, `onSettled`.
|
|
428
|
+
*/
|
|
429
|
+
declare function useMutationApp<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown>(options: UseMutationAppOptions<TData, TError, TVariables, TContext>): UseMutationAppReturn<TData, TError, TVariables, TContext>;
|
|
430
|
+
|
|
431
|
+
export { type InvalidateQueryTarget, type SelectQueryParams, type TableQueryParams, type UseMutationAppOptions, type UseMutationAppReturn, type UseMutationAppToastOptions, type UseSelectInfiniteQueryOptions, type UseSelectInfiniteQueryReturn, type UseTableQueryOptions, type UseTableQueryReturn, extractErrorMessage, useMutationApp, useSelectInfiniteQuery, useTableQuery };
|
package/dist/query.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import{
|
|
2
|
+
import{l as V,m as W,q as X,v as B}from"./chunk-X4LIYOS5.js";import{useCallback as U,useMemo as L,useState as j}from"react";import{useQuery as te,keepPreviousData as ne}from"@tanstack/react-query";function re(e){if(!e)return[];if(Array.isArray(e))return e;if(typeof e=="object"){let t=e;if(Array.isArray(t.data))return t.data;if(Array.isArray(t.items))return t.items;if(Array.isArray(t.rows))return t.rows;if(Array.isArray(t.results))return t.results}return[]}function ae(e){if(!e)return 0;if(Array.isArray(e))return e.length;if(typeof e=="object"){let t=e,a=t.total??t.rowCount??t.totalCount??t.count;if(typeof a=="number")return a;if(typeof a=="string"){let s=Number(a);if(!isNaN(s))return s}}return 0}function H(e,t){return typeof e=="function"?e(t):e}function oe(e){let{queryKey:t,queryFn:a,selectData:s=re,selectRowCount:h=ae,initialPagination:r,initialSorting:l,initialColumnFilters:S,initialGlobalFilter:O,autoResetPageIndex:x=!0,queryOptions:A}=e,[w,P]=j({pageIndex:r?.pageIndex??0,pageSize:r?.pageSize??B}),[C,E]=j(l??[]),[R,p]=j(S??[]),[D,k]=j(O??""),y=L(()=>{let u={page:w.pageIndex+1,pageSize:w.pageSize},i=C[0];if(i&&(u.sortBy=i.id,u.sortOrder=i.desc?"desc":"asc"),R.length>0){let z={};R.forEach(J=>{z[J.id]=J.value}),u.filters=z}return u},[w.pageIndex,w.pageSize,C,R]),N=L(()=>[...t,y],[t,y]),m=te({queryKey:N,queryFn:u=>a(y,u),placeholderData:ne,...A}),q=L(()=>m.data?s(m.data):[],[m.data,s]),n=L(()=>m.data?h(m.data):0,[m.data,h]),c=U(u=>{P(i=>H(u,i))},[]),g=U(u=>{E(i=>H(u,i)),x&&P(i=>i.pageIndex===0?i:{...i,pageIndex:0})},[x]),T=U(u=>{p(i=>H(u,i)),x&&P(i=>i.pageIndex===0?i:{...i,pageIndex:0})},[x]),f=U(u=>{k(u),x&&P(i=>i.pageIndex===0?i:{...i,pageIndex:0})},[x]),b=U(()=>{P({pageIndex:r?.pageIndex??0,pageSize:r?.pageSize??B}),E(l??[]),p(S??[]),k(O??"")},[r,l,S,O]),M=U(u=>{P(i=>({...i,pageIndex:Math.max(0,u-1)}))},[]),d=U(u=>{P({pageIndex:0,pageSize:u})},[]),F=U(u=>{f(u)},[f]),{isLoading:I,isRefetching:Q,refetch:o}=m,v=U(()=>{o()},[o]);return{tableProps:L(()=>({data:q,rowCount:n,isLoading:I,isRefresh:Q&&!I,onRefresh:v,manualPagination:!0,manualSorting:!0,manualFiltering:!0,pagination:w,onPaginationChange:c,sorting:C,onSortingChange:g,columnFilters:R,onColumnFiltersChange:T,globalFilter:D,onGlobalFilterChange:f}),[q,n,I,Q,v,w,c,C,g,R,T,D,f]),query:m,queryParams:y,resetAll:b,setPage:M,setPageSize:d,setGlobalFilter:F,setSorting:E,setColumnFilters:p}}import{useCallback as Y,useMemo as G,useState as $}from"react";import{useInfiniteQuery as se}from"@tanstack/react-query";import{jsx as _}from"react/jsx-runtime";function Z(e){if(!e)return[];if(Array.isArray(e))return e;if(typeof e!="object")return[];let t=e;return Array.isArray(t.data)?t.data:Array.isArray(t.items)?t.items:Array.isArray(t.options)?t.options:Array.isArray(t.results)?t.results:Array.isArray(t.rows)?t.rows:[]}var ie={};function ue({queryKey:e,queryFn:t,initialPageParam:a=1,getNextPageParam:s,getPreviousPageParam:h,selectOptions:r,mapOption:l,debounceMs:S=300,initialSearch:O="",initialFilters:x=ie,initialOptions:A,skeletonLines:w=2,skeletonHeight:P="1.75rem",renderLoadingMore:C,endMessage:E,scrollOptions:R,queryOptions:p}){let[D,k]=$(O),[y,N]=$(x),m=G(()=>[...e,{search:D,filters:y}],[e,D,y]),q=Y((d,F,I,Q)=>{if(s)return s(d,F,I,Q);if(!d||typeof d!="object")return;let o=d;if(typeof o.nextPage=="number"||typeof o.nextPage=="string")return o.nextPage;if(typeof o.next_page=="number"||typeof o.next_page=="string")return o.next_page;if(typeof o.nextCursor=="string"||typeof o.nextCursor=="number")return o.nextCursor;if(typeof o.next_cursor=="string"||typeof o.next_cursor=="number")return o.next_cursor;if(!(o.hasMore===!1||o.has_more===!1||o.hasNextPage===!1)&&typeof I=="number"){if(Z(d).length===0)return;let K=typeof o.total=="number"?o.total:typeof o.totalCount=="number"?o.totalCount:typeof o.count=="number"?o.count:void 0;return K!==void 0&&F.reduce((i,z)=>i+Z(z).length,0)>=K?void 0:I+1}},[s]),n=se({queryKey:m,queryFn:d=>t({pageParam:d.pageParam,search:D,filters:y},d),initialPageParam:a,getNextPageParam:q,getPreviousPageParam:h,...p}),c=G(()=>{let d=n.data?.pages??[],F=[],I=new Set;if(A&&A.length>0)for(let Q of A)I.has(Q.value)||(I.add(Q.value),F.push(Q));for(let Q of d){let o=[];if(r)o=r(Q);else{let v=Z(Q);l?o=v.map((K,u)=>l(K,u)):o=v}for(let v of o)v&&v.value!==void 0&&!I.has(v.value)&&(I.add(v.value),F.push(v))}return F},[n.data?.pages,r,l,A]),{sentinelRef:g}=W({onLoadMore:()=>{n.hasNextPage&&!n.isFetchingNextPage&&n.fetchNextPage()},hasMore:!!n.hasNextPage,isLoading:n.isFetchingNextPage||n.isLoading,disabled:n.isError,rootMargin:R?.rootMargin??"80px",threshold:R?.threshold??0,root:R?.root}),T=Y((d,F)=>{k(d),N(F)},[]),f=Y(()=>{k(O),N(x)},[O,x]),b=G(()=>c.length===0?null:n.isFetchingNextPage?_("div",{ref:g,className:"p-1 space-y-1 w-full",children:C?C():_(X,{lines:w,height:P,gap:"0.25rem",radius:"sm",className:"p-0.5"})}):n.hasNextPage?_("div",{ref:g,className:"h-1 w-full","aria-hidden":"true"}):E?_("div",{className:"py-2 px-1 text-center text-xs text-neutral-400 dark:text-neutral-500",children:E}):null,[c.length,n.isFetchingNextPage,n.hasNextPage,g,C,w,P,E]);return{selectProps:G(()=>({options:c,isLoading:n.isLoading,searchMode:"server",debounceMs:S,onSearch:T,listFooter:b}),[c,n.isLoading,S,T,b]),query:n,options:c,search:D,setSearch:k,filters:y,setFilters:N,sentinelRef:g,reset:f}}import{useRef as le}from"react";import{useMutation as ce,useQueryClient as pe}from"@tanstack/react-query";function ee(e,t="\u0110\xE3 x\u1EA3y ra l\u1ED7i. Vui l\xF2ng th\u1EED l\u1EA1i sau."){if(!e)return t;if(typeof e=="string"&&e.trim())return e;if(typeof e=="object"&&e!==null){let a=e;if(a.response&&typeof a.response=="object"&&a.response!==null){let s=a.response,h=typeof s.status=="number"?s.status:void 0;if(s.data&&typeof s.data=="object"&&s.data!==null){let r=s.data;if(typeof r.message=="string"&&r.message.trim())return r.message;if(typeof r.error=="string"&&r.error.trim())return r.error;if(typeof r.detail=="string"&&r.detail.trim())return r.detail;if(typeof r.title=="string"&&r.title.trim())return r.title;if(Array.isArray(r.message)&&r.message.length>0){let l=r.message[0];if(typeof l=="string")return l}if(Array.isArray(r.errors)&&r.errors.length>0){let l=r.errors[0];if(typeof l=="string")return l;if(typeof l=="object"&&l!==null){let S=l;if(typeof S.message=="string"&&S.message.trim())return S.message}}}if(h)switch(h){case 400:return"D\u1EEF li\u1EC7u y\xEAu c\u1EA7u kh\xF4ng h\u1EE3p l\u1EC7 (400).";case 401:return"Phi\xEAn l\xE0m vi\u1EC7c \u0111\xE3 h\u1EBFt h\u1EA1n ho\u1EB7c ch\u01B0a \u0111\u0103ng nh\u1EADp (401).";case 403:return"B\u1EA1n kh\xF4ng c\xF3 quy\u1EC1n th\u1EF1c hi\u1EC7n thao t\xE1c n\xE0y (403).";case 404:return"Kh\xF4ng t\xECm th\u1EA5y d\u1EEF li\u1EC7u y\xEAu c\u1EA7u (404).";case 409:return"D\u1EEF li\u1EC7u b\u1ECB tr\xF9ng l\u1EB7p ho\u1EB7c xung \u0111\u1ED9t (409).";case 422:return"D\u1EEF li\u1EC7u g\u1EEDi l\xEAn kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng (422).";case 500:return"L\u1ED7i m\xE1y ch\u1EE7 n\u1ED9i b\u1ED9. Vui l\xF2ng th\u1EED l\u1EA1i sau (500).";case 502:case 503:case 504:return"Kh\xF4ng th\u1EC3 k\u1EBFt n\u1ED1i \u0111\u1EBFn m\xE1y ch\u1EE7. Vui l\xF2ng th\u1EED l\u1EA1i sau."}}if(typeof a.message=="string"&&a.message.trim())return a.message;if(typeof a.error=="string"&&a.error.trim())return a.error;if(typeof a.detail=="string"&&a.detail.trim())return a.detail}return t}function ge(e,t){return e?Array.isArray(e)?e.length===0?[]:e.every(s=>Array.isArray(s)||typeof s=="object"&&s!==null&&"queryKey"in s)?e.map(s=>Array.isArray(s)?{filters:{queryKey:s},options:t}:{filters:s,options:t}):[{filters:{queryKey:e},options:t}]:typeof e=="object"&&e!==null&&"queryKey"in e?[{filters:e,options:t}]:[]:[]}function fe(e){let t=pe(),a=le([]),{mutationFn:s,invalidateQueries:h,invalidateOptions:r,toast:l=!0,successMessage:S,errorMessage:O,loadingMessage:x,onMutate:A,onSuccess:w,onError:P,onSettled:C,...E}=e,R=l!==!1,p=typeof l=="object"&&l!==null?{...l}:{},D=x??p.loading,k=S??p.success,y=O??p.error??!0,N=p.variant??"soft",m=p.options,q=ce({...E,mutationFn:s,onMutate:async(n,c)=>{let g;if(R&&D){let T=typeof D=="function"?D(n):D,f=typeof p.loadingDescription=="function"?p.loadingDescription(n):p.loadingDescription;g=V.loading(T,f,{...m,variant:N}),a.current.push(g)}try{if(A)return await A(n,c)}catch(T){throw g&&(V.dismiss(g),a.current=a.current.filter(f=>f!==g)),T}},onSuccess:async(n,c,g,T)=>{let f=a.current.shift();if(f&&V.dismiss(f),R&&k){let b=typeof k=="function"?k(n,c):k,M=typeof p.successDescription=="function"?p.successDescription(n,c):p.successDescription;V.success(b,M,{...m,variant:N})}if(h){let b=typeof h=="function"?h(n,c):h,M=ge(b,r);M.length>0&&await Promise.all(M.map(({filters:d,options:F})=>t.invalidateQueries(d,F)))}w&&await w(n,c,g,T)},onError:async(n,c,g,T)=>{let f=a.current.shift();if(f&&V.dismiss(f),R&&y!==!1){let b;typeof y=="function"?b=y(n,c):y===!0?b=ee(n):b=y;let M=typeof p.errorDescription=="function"?p.errorDescription(n,c):p.errorDescription;V.error(b,M,{...m,variant:N})}P&&await P(n,c,g,T)},onSettled:async(n,c,g,T,f)=>{C&&await C(n,c,g,T,f)}});return{...q,isLoading:q.isPending}}export{ee as extractErrorMessage,fe as useMutationApp,ue as useSelectInfiniteQuery,oe as useTableQuery};
|
|
3
3
|
//# sourceMappingURL=query.js.map
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/table/hooks/useTableQuery.ts","../src/components/select/hooks/useSelectInfiniteQuery.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\r\nimport {\r\n useQuery,\r\n keepPreviousData,\r\n type QueryFunctionContext,\r\n type UseQueryOptions,\r\n type UseQueryResult,\r\n} from \"@tanstack/react-query\";\r\nimport type {\r\n ColumnFiltersState,\r\n OnChangeFn,\r\n PaginationState,\r\n RowData,\r\n SortingState,\r\n Updater,\r\n} from \"@tanstack/react-table\";\r\nimport { DEFAULT_PAGE_SIZE } from \"../constants\";\r\n\r\n/**\r\n * Các tham số truy vấn chuẩn hóa gửi lên API máy chủ.\r\n */\r\nexport interface TableQueryParams {\r\n /**\r\n * Số thứ tự trang (1-indexed: 1, 2, 3...)\r\n */\r\n page: number;\r\n\r\n /**\r\n * Kích thước trang (số dòng trên 1 trang)\r\n */\r\n pageSize: number;\r\n\r\n /**\r\n * ID cột được sắp xếp\r\n */\r\n sortBy?: string;\r\n\r\n /**\r\n * Chiều sắp xếp: \"asc\" (tăng dần) hoặc \"desc\" (giảm dần)\r\n */\r\n sortOrder?: \"asc\" | \"desc\";\r\n\r\n /**\r\n * Danh sách bộ lọc cột dạng từ điển phẳng Record<cột, giá trị>\r\n */\r\n filters?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Các tùy chọn cấu hình cho hook `useTableQuery`.\r\n */\r\nexport interface UseTableQueryOptions<TData extends RowData = RowData, TResponse = unknown, TError = Error> {\r\n /**\r\n * Base query key cho TanStack Query.\r\n * `queryParams` sẽ tự động được thêm vào cuối queryKey: `[...queryKey, queryParams]`.\r\n */\r\n queryKey: readonly unknown[];\r\n\r\n /**\r\n * Hàm gọi API truy vấn dữ liệu từ máy chủ.\r\n * Nhận vào `queryParams` đã chuẩn hóa và `context` của TanStack Query.\r\n */\r\n queryFn: (params: TableQueryParams, context: QueryFunctionContext<readonly unknown[]>) => Promise<TResponse>;\r\n\r\n /**\r\n * Hàm trích xuất mảng dữ liệu các dòng (`TData[]`) từ kết quả API trả về.\r\n * Mặc định tự động kiểm tra `response.data`, `response.items`, `response.rows`, `response.results` hoặc bản thân mảng response.\r\n */\r\n selectData?: (response: TResponse) => TData[];\r\n\r\n /**\r\n * Hàm trích xuất tổng số lượng bản ghi (`rowCount` / `total`) từ kết quả API trả về.\r\n * Mặc định tự động kiểm tra `response.total`, `response.rowCount`, `response.totalCount`, `response.count`.\r\n */\r\n selectRowCount?: (response: TResponse) => number;\r\n\r\n /**\r\n * Cấu hình phân trang ban đầu\r\n */\r\n initialPagination?: {\r\n pageIndex?: number;\r\n pageSize?: number;\r\n };\r\n\r\n /**\r\n * Cấu hình sắp xếp ban đầu\r\n */\r\n initialSorting?: SortingState;\r\n\r\n /**\r\n * Cấu hình bộ lọc cột ban đầu\r\n */\r\n initialColumnFilters?: ColumnFiltersState;\r\n\r\n /**\r\n * Cấu hình từ khóa tìm kiếm ban đầu\r\n */\r\n initialGlobalFilter?: string;\r\n\r\n /**\r\n * Tự động đưa về trang đầu (pageIndex = 0) khi thay đổi tìm kiếm, bộ lọc hoặc sắp xếp.\r\n * @default true\r\n */\r\n autoResetPageIndex?: boolean;\r\n\r\n /**\r\n * Các tùy chọn nâng cao truyền trực tiếp cho hook `useQuery` của TanStack Query\r\n * (ví dụ: `staleTime`, `gcTime`, `refetchOnWindowFocus`, `enabled`...).\r\n */\r\n queryOptions?: Omit<UseQueryOptions<TResponse, TError, TResponse, readonly unknown[]>, \"queryKey\" | \"queryFn\">;\r\n}\r\n\r\n/**\r\n * Kết quả trả về từ hook `useTableQuery`.\r\n */\r\nexport interface UseTableQueryReturn<TData extends RowData = RowData, TResponse = unknown, TError = Error> {\r\n /**\r\n * Tập props trọn gói sẵn sàng truyền thẳng vào `<DataTable {...tableProps} />`\r\n */\r\n tableProps: {\r\n data: TData[];\r\n rowCount: number;\r\n isLoading: boolean;\r\n isRefresh: boolean;\r\n onRefresh: () => void;\r\n manualPagination: true;\r\n manualSorting: true;\r\n manualFiltering: true;\r\n pagination: PaginationState;\r\n onPaginationChange: OnChangeFn<PaginationState>;\r\n sorting: SortingState;\r\n onSortingChange: OnChangeFn<SortingState>;\r\n columnFilters: ColumnFiltersState;\r\n onColumnFiltersChange: OnChangeFn<ColumnFiltersState>;\r\n globalFilter: string;\r\n onGlobalFilterChange: (filter: string) => void;\r\n };\r\n\r\n /**\r\n * Instance query đầy đủ từ TanStack Query\r\n */\r\n query: UseQueryResult<TResponse, TError>;\r\n\r\n /**\r\n * Tham số truy vấn hiện tại (dùng để debug, ghi log, đồng bộ URL query string, hoặc xuất Excel)\r\n */\r\n queryParams: TableQueryParams;\r\n\r\n /**\r\n * Đặt lại toàn bộ phân trang, sắp xếp, tìm kiếm và bộ lọc về giá trị mặc định ban đầu\r\n */\r\n resetAll: () => void;\r\n\r\n /**\r\n * Đổi trang thủ công (1-indexed: 1, 2, 3...)\r\n */\r\n setPage: (page: number) => void;\r\n\r\n /**\r\n * Đổi số dòng trên mỗi trang thủ công\r\n */\r\n setPageSize: (pageSize: number) => void;\r\n\r\n /**\r\n * Đổi từ khóa tìm kiếm toàn bảng (lọc client-side)\r\n */\r\n setGlobalFilter: (filter: string) => void;\r\n\r\n /**\r\n * Cập nhật trạng thái sắp xếp\r\n */\r\n setSorting: (sorting: SortingState | ((prev: SortingState) => SortingState)) => void;\r\n\r\n /**\r\n * Cập nhật trạng thái bộ lọc cột\r\n */\r\n setColumnFilters: (filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)) => void;\r\n}\r\n\r\n/**\r\n * Trích xuất mặc định danh sách dòng dữ liệu từ API response.\r\n */\r\nfunction defaultSelectData<TData>(response: unknown): TData[] {\r\n if (!response) return [];\r\n if (Array.isArray(response)) return response as TData[];\r\n if (typeof response === \"object\") {\r\n const obj = response as Record<string, unknown>;\r\n if (Array.isArray(obj.data)) return obj.data as TData[];\r\n if (Array.isArray(obj.items)) return obj.items as TData[];\r\n if (Array.isArray(obj.rows)) return obj.rows as TData[];\r\n if (Array.isArray(obj.results)) return obj.results as TData[];\r\n }\r\n return [];\r\n}\r\n\r\n/**\r\n * Trích xuất mặc định tổng số lượng bản ghi từ API response.\r\n */\r\nfunction defaultSelectRowCount(response: unknown): number {\r\n if (!response) return 0;\r\n if (Array.isArray(response)) return response.length;\r\n if (typeof response === \"object\") {\r\n const obj = response as Record<string, unknown>;\r\n const total = obj.total ?? obj.rowCount ?? obj.totalCount ?? obj.count;\r\n if (typeof total === \"number\") return total;\r\n if (typeof total === \"string\") {\r\n const parsed = Number(total);\r\n if (!isNaN(parsed)) return parsed;\r\n }\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * Helper giải quyết Updater của TanStack Table\r\n */\r\nfunction resolveUpdater<T>(updater: Updater<T>, prev: T): T {\r\n return typeof updater === \"function\" ? (updater as (old: T) => T)(prev) : updater;\r\n}\r\n\r\n/**\r\n * Hook Adapter `useTableQuery` tích hợp TanStack Table v9 và TanStack Query v5.\r\n *\r\n * Tính năng:\r\n * - Đồng bộ hai chiều trạng thái bảng (Pagination, Sorting, Filtering, Search) với server API.\r\n * - Tự động định dạng tham số `TableQueryParams`: `page` (1-indexed), `pageSize`, `search`, `sortBy`, `sortOrder`, `filters`.\r\n * - Tự động gắn `queryParams` vào `queryKey` của TanStack Query để kích hoạt cache/refetch thông minh.\r\n * - Hỗ trợ phân trang mượt mà bằng `placeholderData: keepPreviousData` (không bị nhấp nháy Skeleton khi chuyển trang).\r\n * - Cung cấp `tableProps` dựng sẵn truyền trực tiếp vào `<DataTable {...tableProps} />`.\r\n */\r\nexport function useTableQuery<TData extends RowData = RowData, TResponse = unknown, TError = Error>(\r\n options: UseTableQueryOptions<TData, TResponse, TError>\r\n): UseTableQueryReturn<TData, TResponse, TError> {\r\n const {\r\n queryKey,\r\n queryFn,\r\n selectData = defaultSelectData,\r\n selectRowCount = defaultSelectRowCount,\r\n initialPagination,\r\n initialSorting,\r\n initialColumnFilters,\r\n initialGlobalFilter,\r\n autoResetPageIndex = true,\r\n queryOptions,\r\n } = options;\r\n\r\n // 1. Quản lý trạng thái bảng (Table states)\r\n const [pagination, setPagination] = useState<PaginationState>({\r\n pageIndex: initialPagination?.pageIndex ?? 0,\r\n pageSize: initialPagination?.pageSize ?? DEFAULT_PAGE_SIZE,\r\n });\r\n\r\n const [sorting, setSorting] = useState<SortingState>(initialSorting ?? []);\r\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(initialColumnFilters ?? []);\r\n const [globalFilter, setGlobalFilter] = useState<string>(initialGlobalFilter ?? \"\");\r\n\r\n // 2. Chuẩn hóa tham số query gửi lên server\r\n const queryParams = useMemo<TableQueryParams>(() => {\r\n const params: TableQueryParams = {\r\n page: pagination.pageIndex + 1,\r\n pageSize: pagination.pageSize,\r\n };\r\n\r\n const firstSort = sorting[0];\r\n if (firstSort) {\r\n params.sortBy = firstSort.id;\r\n params.sortOrder = firstSort.desc ? \"desc\" : \"asc\";\r\n }\r\n\r\n if (columnFilters.length > 0) {\r\n const filtersDict: Record<string, unknown> = {};\r\n columnFilters.forEach((cf) => {\r\n filtersDict[cf.id] = cf.value;\r\n });\r\n params.filters = filtersDict;\r\n }\r\n\r\n return params;\r\n }, [pagination.pageIndex, pagination.pageSize, sorting, columnFilters]);\r\n\r\n // 3. Ghép queryKey với queryParams\r\n const fullQueryKey = useMemo(() => [...queryKey, queryParams] as const, [queryKey, queryParams]);\r\n\r\n // 4. Gọi useQuery của TanStack Query v5\r\n const query = useQuery<TResponse, TError, TResponse, readonly unknown[]>({\r\n queryKey: fullQueryKey,\r\n queryFn: (ctx) => queryFn(queryParams, ctx),\r\n placeholderData: keepPreviousData,\r\n ...queryOptions,\r\n });\r\n\r\n // 5. Trích xuất dữ liệu hàng và tổng số lượng\r\n const data = useMemo<TData[]>(() => {\r\n return query.data ? selectData(query.data) : [];\r\n }, [query.data, selectData]);\r\n\r\n const rowCount = useMemo<number>(() => {\r\n return query.data ? selectRowCount(query.data) : 0;\r\n }, [query.data, selectRowCount]);\r\n\r\n // 6. Xử lý các sự kiện thay đổi trạng thái kèm autoResetPageIndex\r\n const handlePaginationChange: OnChangeFn<PaginationState> = useCallback((updater) => {\r\n setPagination((prev) => resolveUpdater(updater, prev));\r\n }, []);\r\n\r\n const handleSortingChange: OnChangeFn<SortingState> = useCallback(\r\n (updater) => {\r\n setSorting((prev) => resolveUpdater(updater, prev));\r\n if (autoResetPageIndex) {\r\n setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));\r\n }\r\n },\r\n [autoResetPageIndex]\r\n );\r\n\r\n const handleColumnFiltersChange: OnChangeFn<ColumnFiltersState> = useCallback(\r\n (updater) => {\r\n setColumnFilters((prev) => resolveUpdater(updater, prev));\r\n if (autoResetPageIndex) {\r\n setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));\r\n }\r\n },\r\n [autoResetPageIndex]\r\n );\r\n\r\n const handleGlobalFilterChange = useCallback(\r\n (filter: string) => {\r\n setGlobalFilter(filter);\r\n if (autoResetPageIndex) {\r\n setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));\r\n }\r\n },\r\n [autoResetPageIndex]\r\n );\r\n\r\n // 7. Các hàm tiện ích bổ sung\r\n const resetAll = useCallback(() => {\r\n setPagination({\r\n pageIndex: initialPagination?.pageIndex ?? 0,\r\n pageSize: initialPagination?.pageSize ?? DEFAULT_PAGE_SIZE,\r\n });\r\n setSorting(initialSorting ?? []);\r\n setColumnFilters(initialColumnFilters ?? []);\r\n setGlobalFilter(initialGlobalFilter ?? \"\");\r\n }, [initialPagination, initialSorting, initialColumnFilters, initialGlobalFilter]);\r\n\r\n const setPage = useCallback((page: number) => {\r\n setPagination((prev) => ({\r\n ...prev,\r\n pageIndex: Math.max(0, page - 1),\r\n }));\r\n }, []);\r\n\r\n const setPageSize = useCallback((pageSize: number) => {\r\n setPagination({\r\n pageIndex: 0,\r\n pageSize,\r\n });\r\n }, []);\r\n\r\n const setGlobalFilterValue = useCallback(\r\n (filter: string) => {\r\n handleGlobalFilterChange(filter);\r\n },\r\n [handleGlobalFilterChange]\r\n );\r\n\r\n // 8. Đóng gói tableProps truyền cho DataTable\r\n const { isLoading, isRefetching, refetch } = query;\r\n\r\n const handleRefresh = useCallback(() => {\r\n void refetch();\r\n }, [refetch]);\r\n\r\n const tableProps = useMemo(\r\n () => ({\r\n data,\r\n rowCount,\r\n isLoading,\r\n isRefresh: isRefetching && !isLoading,\r\n onRefresh: handleRefresh,\r\n manualPagination: true as const,\r\n manualSorting: true as const,\r\n manualFiltering: true as const,\r\n pagination,\r\n onPaginationChange: handlePaginationChange,\r\n sorting,\r\n onSortingChange: handleSortingChange,\r\n columnFilters,\r\n onColumnFiltersChange: handleColumnFiltersChange,\r\n globalFilter,\r\n onGlobalFilterChange: handleGlobalFilterChange,\r\n }),\r\n [\r\n data,\r\n rowCount,\r\n isLoading,\r\n isRefetching,\r\n handleRefresh,\r\n pagination,\r\n handlePaginationChange,\r\n sorting,\r\n handleSortingChange,\r\n columnFilters,\r\n handleColumnFiltersChange,\r\n globalFilter,\r\n handleGlobalFilterChange,\r\n ]\r\n );\r\n\r\n return {\r\n tableProps,\r\n query,\r\n queryParams,\r\n resetAll,\r\n setPage,\r\n setPageSize,\r\n setGlobalFilter: setGlobalFilterValue,\r\n setSorting,\r\n setColumnFilters,\r\n };\r\n}\r\n","import React, {\r\n useCallback,\r\n useMemo,\r\n useState,\r\n type ReactNode,\r\n} from \"react\";\r\nimport {\r\n useInfiniteQuery,\r\n type InfiniteData,\r\n type QueryFunctionContext,\r\n type UseInfiniteQueryOptions,\r\n type UseInfiniteQueryResult,\r\n} from \"@tanstack/react-query\";\r\nimport type { SelectOptionItem } from \"../types\";\r\nimport { useInfiniteScroll, type UseInfiniteScrollOptions } from \"@/hooks/useInfiniteScroll\";\r\nimport Skeleton from \"@/components/skeleton/Skeleton\";\r\n\r\n/**\r\n * Các tham số truy vấn chuẩn hóa được chuyển tới hàm `queryFn`.\r\n */\r\nexport interface SelectQueryParams<\r\n TPageParam = number,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n> {\r\n /**\r\n * Tham số phân trang hiện tại (trang hiện tại hoặc cursor).\r\n */\r\n pageParam: TPageParam;\r\n\r\n /**\r\n * Từ khóa tìm kiếm từ ô Select.\r\n */\r\n search: string;\r\n\r\n /**\r\n * Bộ lọc bổ sung từ menu filter của Select.\r\n */\r\n filters: TFilters;\r\n}\r\n\r\n/**\r\n * Hàm hỗ trợ tự động trích xuất mảng dữ liệu từ các cấu trúc response phổ biến.\r\n */\r\nfunction extractRawOptions(response: unknown): unknown[] {\r\n if (!response) return [];\r\n if (Array.isArray(response)) return response;\r\n if (typeof response !== \"object\") return [];\r\n\r\n const record = response as Record<string, unknown>;\r\n if (Array.isArray(record.data)) return record.data;\r\n if (Array.isArray(record.items)) return record.items;\r\n if (Array.isArray(record.options)) return record.options;\r\n if (Array.isArray(record.results)) return record.results;\r\n if (Array.isArray(record.rows)) return record.rows;\r\n\r\n return [];\r\n}\r\n\r\n/**\r\n * Các tùy chọn cấu hình cho hook `useSelectInfiniteQuery`.\r\n */\r\nexport interface UseSelectInfiniteQueryOptions<\r\n TData = unknown,\r\n TResponse = unknown,\r\n TPageParam = number,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n TError = Error,\r\n> {\r\n /**\r\n * Base query key cho TanStack Query.\r\n * `[{ search, filters }]` sẽ tự động được thêm vào cuối queryKey:\r\n * `[...queryKey, { search, filters }]`.\r\n */\r\n queryKey: readonly unknown[];\r\n\r\n /**\r\n * Hàm gọi API truy vấn dữ liệu từ máy chủ theo từng trang.\r\n * Nhận vào `SelectQueryParams` đã chuẩn hóa và `context` của TanStack Query.\r\n */\r\n queryFn: (\r\n params: SelectQueryParams<TPageParam, TFilters>,\r\n context: QueryFunctionContext<readonly unknown[], TPageParam>\r\n ) => Promise<TResponse>;\r\n\r\n /**\r\n * Giá trị tham số trang đầu tiên (initialPageParam).\r\n * @default 1\r\n */\r\n initialPageParam?: TPageParam;\r\n\r\n /**\r\n * Hàm xác định `pageParam` cho trang tiếp theo.\r\n * Trả về `undefined` hoặc `null` khi không còn dữ liệu tiếp theo.\r\n */\r\n getNextPageParam?: (\r\n lastPage: TResponse,\r\n allPages: TResponse[],\r\n lastPageParam: TPageParam,\r\n allPageParams: TPageParam[]\r\n ) => TPageParam | undefined | null;\r\n\r\n /**\r\n * Hàm xác định `pageParam` cho trang trước đó (nếu có).\r\n */\r\n getPreviousPageParam?: (\r\n firstPage: TResponse,\r\n allPages: TResponse[],\r\n firstPageParam: TPageParam,\r\n allPageParams: TPageParam[]\r\n ) => TPageParam | undefined | null;\r\n\r\n /**\r\n * Hàm trích xuất danh sách options (`SelectOptionItem<TData>[]`) từ mỗi `response` trang.\r\n * Nếu không truyền, mặc định sẽ kiểm tra `data`, `items`, `options`, `results`, `rows` hoặc chính `response`.\r\n */\r\n selectOptions?: (response: TResponse) => SelectOptionItem<TData>[];\r\n\r\n /**\r\n * Hàm biến đổi từng phần tử thô (raw entity) thành `SelectOptionItem<TData>`.\r\n */\r\n mapOption?: (item: unknown, index: number) => SelectOptionItem<TData>;\r\n\r\n /**\r\n * Thời gian debounce từ khóa tìm kiếm (tính theo ms).\r\n * @default 300\r\n */\r\n debounceMs?: number;\r\n\r\n /**\r\n * Từ khóa tìm kiếm ban đầu.\r\n */\r\n initialSearch?: string;\r\n\r\n /**\r\n * Giá trị bộ lọc menu ban đầu.\r\n */\r\n initialFilters?: TFilters;\r\n\r\n /**\r\n * Danh sách options ban đầu ghim sẵn (ví dụ các options đã chọn trước đó cần bảo lưu).\r\n */\r\n initialOptions?: SelectOptionItem<TData>[];\r\n\r\n /**\r\n * Số dòng Skeleton hiển thị khi đang tải thêm trang tiếp theo.\r\n * @default 2\r\n */\r\n skeletonLines?: number;\r\n\r\n /**\r\n * Chiều cao của mỗi dòng Skeleton khi đang tải thêm trang.\r\n * @default \"1.75rem\"\r\n */\r\n skeletonHeight?: number | string;\r\n\r\n /**\r\n * Tùy biến render khi đang tải thêm trang tiếp theo (thay thế Skeleton mặc định).\r\n */\r\n renderLoadingMore?: () => ReactNode;\r\n\r\n /**\r\n * Thông báo hiển thị khi đã tải hết toàn bộ dữ liệu ở đáy danh sách (tùy chọn).\r\n */\r\n endMessage?: ReactNode;\r\n\r\n /**\r\n * Tùy chọn truyền trực tiếp cho hook `useInfiniteScroll`.\r\n */\r\n scrollOptions?: Partial<Omit<UseInfiniteScrollOptions, \"onLoadMore\" | \"hasMore\" | \"isLoading\">>;\r\n\r\n /**\r\n * Các tùy chọn nâng cao truyền trực tiếp cho hook `useInfiniteQuery` của TanStack Query.\r\n */\r\n queryOptions?: Omit<\r\n UseInfiniteQueryOptions<TResponse, TError, InfiniteData<TResponse>, readonly unknown[], TPageParam>,\r\n \"queryKey\" | \"queryFn\" | \"initialPageParam\" | \"getNextPageParam\" | \"getPreviousPageParam\"\r\n >;\r\n}\r\n\r\n/**\r\n * Kết quả trả về từ hook `useSelectInfiniteQuery`.\r\n */\r\nexport interface UseSelectInfiniteQueryReturn<\r\n TData = unknown,\r\n TResponse = unknown,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n TError = Error,\r\n> {\r\n /**\r\n * Bộ props trọn gói sẵn sàng truyền thẳng vào `<Select {...selectProps} />` hoặc `<MultiSelect {...selectProps} />`\r\n */\r\n selectProps: {\r\n options: SelectOptionItem<TData>[];\r\n isLoading: boolean;\r\n searchMode: \"server\";\r\n debounceMs: number;\r\n onSearch: (query: string, filters: TFilters) => void;\r\n listFooter: ReactNode;\r\n };\r\n\r\n /**\r\n * Instance query đầy đủ từ TanStack Query\r\n */\r\n query: UseInfiniteQueryResult<InfiniteData<TResponse>, TError>;\r\n\r\n /**\r\n * Toàn bộ danh sách options đã gom tụ từ tất cả các trang\r\n */\r\n options: SelectOptionItem<TData>[];\r\n\r\n /**\r\n * Từ khóa tìm kiếm hiện tại (đã được Select debounce qua onSearch)\r\n */\r\n search: string;\r\n\r\n /**\r\n * Phương thức cập nhật từ khóa tìm kiếm\r\n */\r\n setSearch: (search: string) => void;\r\n\r\n /**\r\n * Bộ lọc menu hiện tại\r\n */\r\n filters: TFilters;\r\n\r\n /**\r\n * Phương thức cập nhật bộ lọc menu\r\n */\r\n setFilters: (filters: TFilters | ((prev: TFilters) => TFilters)) => void;\r\n\r\n /**\r\n * Ref gắn vào phần tử sentinel (IntersectionObserver)\r\n */\r\n sentinelRef: (node: HTMLElement | null) => void;\r\n\r\n /**\r\n * Đặt lại toàn bộ tìm kiếm, bộ lọc về giá trị ban đầu\r\n */\r\n reset: () => void;\r\n}\r\n\r\nconst EMPTY_FILTERS: Record<string, unknown> = {};\r\n\r\n/**\r\n * Hook `useSelectInfiniteQuery` kết hợp TanStack Query `useInfiniteQuery` với `useInfiniteScroll`\r\n * dành riêng cho `<Select />` và `<MultiSelect />` ở chế độ máy chủ (`searchMode=\"server\"`).\r\n */\r\nexport function useSelectInfiniteQuery<\r\n TData = unknown,\r\n TResponse = unknown,\r\n TPageParam = number,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n TError = Error,\r\n>({\r\n queryKey,\r\n queryFn,\r\n initialPageParam = 1 as unknown as TPageParam,\r\n getNextPageParam,\r\n getPreviousPageParam,\r\n selectOptions,\r\n mapOption,\r\n debounceMs = 300,\r\n initialSearch = \"\",\r\n initialFilters = EMPTY_FILTERS as TFilters,\r\n initialOptions,\r\n skeletonLines = 2,\r\n skeletonHeight = \"1.75rem\",\r\n renderLoadingMore,\r\n endMessage,\r\n scrollOptions,\r\n queryOptions,\r\n}: UseSelectInfiniteQueryOptions<TData, TResponse, TPageParam, TFilters, TError>): UseSelectInfiniteQueryReturn<TData, TResponse, TFilters, TError> {\r\n // 1. Quản lý từ khóa tìm kiếm & bộ lọc menu\r\n const [search, setSearch] = useState(initialSearch);\r\n const [filters, setFilters] = useState<TFilters>(initialFilters);\r\n\r\n // 2. Xây dựng full queryKey kết hợp search & filters\r\n const fullQueryKey = useMemo(\r\n () => [...queryKey, { search, filters }] as const,\r\n [queryKey, search, filters]\r\n );\r\n\r\n // 4. Default getNextPageParam thông minh\r\n const resolvedGetNextPageParam = useCallback(\r\n (\r\n lastPage: TResponse,\r\n allPages: TResponse[],\r\n lastPageParam: TPageParam,\r\n allPageParams: TPageParam[]\r\n ): TPageParam | undefined | null => {\r\n if (getNextPageParam) {\r\n return getNextPageParam(lastPage, allPages, lastPageParam, allPageParams);\r\n }\r\n\r\n if (!lastPage || typeof lastPage !== \"object\") return undefined;\r\n const record = lastPage as Record<string, unknown>;\r\n\r\n if (typeof record.nextPage === \"number\" || typeof record.nextPage === \"string\") {\r\n return record.nextPage as unknown as TPageParam;\r\n }\r\n if (typeof record.next_page === \"number\" || typeof record.next_page === \"string\") {\r\n return record.next_page as unknown as TPageParam;\r\n }\r\n if (typeof record.nextCursor === \"string\" || typeof record.nextCursor === \"number\") {\r\n return record.nextCursor as unknown as TPageParam;\r\n }\r\n if (typeof record.next_cursor === \"string\" || typeof record.next_cursor === \"number\") {\r\n return record.next_cursor as unknown as TPageParam;\r\n }\r\n\r\n if (record.hasMore === false || record.has_more === false || record.hasNextPage === false) {\r\n return undefined;\r\n }\r\n\r\n if (typeof lastPageParam === \"number\") {\r\n const rawItems = extractRawOptions(lastPage);\r\n if (rawItems.length === 0) return undefined;\r\n\r\n const total =\r\n typeof record.total === \"number\"\r\n ? record.total\r\n : typeof record.totalCount === \"number\"\r\n ? record.totalCount\r\n : typeof record.count === \"number\"\r\n ? record.count\r\n : undefined;\r\n\r\n if (total !== undefined) {\r\n const currentCount = allPages.reduce((acc, p) => acc + extractRawOptions(p).length, 0);\r\n if (currentCount >= total) return undefined;\r\n }\r\n\r\n return (lastPageParam + 1) as unknown as TPageParam;\r\n }\r\n\r\n return undefined;\r\n },\r\n [getNextPageParam]\r\n );\r\n\r\n // 5. TanStack Query useInfiniteQuery\r\n const query = useInfiniteQuery<TResponse, TError, InfiniteData<TResponse>, readonly unknown[], TPageParam>({\r\n queryKey: fullQueryKey,\r\n queryFn: (context) => {\r\n return queryFn(\r\n {\r\n pageParam: context.pageParam as TPageParam,\r\n search,\r\n filters,\r\n },\r\n context\r\n );\r\n },\r\n initialPageParam,\r\n getNextPageParam: resolvedGetNextPageParam,\r\n getPreviousPageParam,\r\n ...queryOptions,\r\n });\r\n\r\n // 6. Trích xuất và gom tụ options từ tất cả các trang\r\n const options = useMemo<SelectOptionItem<TData>[]>(() => {\r\n const pages = query.data?.pages ?? [];\r\n const allItems: SelectOptionItem<TData>[] = [];\r\n const seen = new Set<string | number>();\r\n\r\n // Nếu có initialOptions, đưa vào danh sách trước\r\n if (initialOptions && initialOptions.length > 0) {\r\n for (const opt of initialOptions) {\r\n if (!seen.has(opt.value)) {\r\n seen.add(opt.value);\r\n allItems.push(opt);\r\n }\r\n }\r\n }\r\n\r\n for (const page of pages) {\r\n let pageOptions: SelectOptionItem<TData>[] = [];\r\n\r\n if (selectOptions) {\r\n pageOptions = selectOptions(page);\r\n } else {\r\n const raw = extractRawOptions(page);\r\n if (mapOption) {\r\n pageOptions = raw.map((item, index) => mapOption(item, index));\r\n } else {\r\n pageOptions = raw as SelectOptionItem<TData>[];\r\n }\r\n }\r\n\r\n for (const opt of pageOptions) {\r\n if (opt && opt.value !== undefined && !seen.has(opt.value)) {\r\n seen.add(opt.value);\r\n allItems.push(opt);\r\n }\r\n }\r\n }\r\n\r\n return allItems;\r\n }, [query.data?.pages, selectOptions, mapOption, initialOptions]);\r\n\r\n // 7. useInfiniteScroll để tải trang tiếp theo khi người dùng cuộn đến cuối\r\n const { sentinelRef } = useInfiniteScroll({\r\n onLoadMore: () => {\r\n if (query.hasNextPage && !query.isFetchingNextPage) {\r\n query.fetchNextPage();\r\n }\r\n },\r\n hasMore: Boolean(query.hasNextPage),\r\n isLoading: query.isFetchingNextPage || query.isLoading,\r\n disabled: query.isError,\r\n rootMargin: scrollOptions?.rootMargin ?? \"80px\",\r\n threshold: scrollOptions?.threshold ?? 0,\r\n root: scrollOptions?.root,\r\n });\r\n\r\n // 8. Handler nhận search từ Select component\r\n const handleSearch = useCallback((newSearch: string, newFilters: TFilters) => {\r\n setSearch(newSearch);\r\n setFilters(newFilters);\r\n }, []);\r\n\r\n // 9. Reset state\r\n const reset = useCallback(() => {\r\n setSearch(initialSearch);\r\n setFilters(initialFilters);\r\n }, [initialSearch, initialFilters]);\r\n\r\n // 10. Giao diện Skeleton / Sentinel dưới đáy danh sách (listFooter)\r\n const listFooter = useMemo<ReactNode>(() => {\r\n // Nếu chưa có option nào thì để màn hình loading chính của SelectMenu hiển thị\r\n if (options.length === 0) return null;\r\n\r\n if (query.isFetchingNextPage) {\r\n return (\r\n <div ref={sentinelRef} className=\"p-1 space-y-1 w-full\">\r\n {renderLoadingMore ? (\r\n renderLoadingMore()\r\n ) : (\r\n <Skeleton\r\n lines={skeletonLines}\r\n height={skeletonHeight}\r\n gap=\"0.25rem\"\r\n radius=\"sm\"\r\n className=\"p-0.5\"\r\n />\r\n )}\r\n </div>\r\n );\r\n }\r\n\r\n if (query.hasNextPage) {\r\n return <div ref={sentinelRef} className=\"h-1 w-full\" aria-hidden=\"true\" />;\r\n }\r\n\r\n if (endMessage) {\r\n return (\r\n <div className=\"py-2 px-1 text-center text-xs text-neutral-400 dark:text-neutral-500\">\r\n {endMessage}\r\n </div>\r\n );\r\n }\r\n\r\n return null;\r\n }, [\r\n options.length,\r\n query.isFetchingNextPage,\r\n query.hasNextPage,\r\n sentinelRef,\r\n renderLoadingMore,\r\n skeletonLines,\r\n skeletonHeight,\r\n endMessage,\r\n ]);\r\n\r\n // 11. Đóng gói selectProps trọn gói\r\n const selectProps = useMemo(\r\n () => ({\r\n options,\r\n isLoading: query.isLoading,\r\n searchMode: \"server\" as const,\r\n debounceMs,\r\n onSearch: handleSearch,\r\n listFooter,\r\n }),\r\n [options, query.isLoading, debounceMs, handleSearch, listFooter]\r\n );\r\n\r\n return {\r\n selectProps,\r\n query,\r\n options,\r\n search,\r\n setSearch,\r\n filters,\r\n setFilters,\r\n sentinelRef,\r\n reset,\r\n };\r\n}\r\n\r\nexport default useSelectInfiniteQuery;\r\n"],"mappings":"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","names":["useCallback","useMemo","useState","useQuery","keepPreviousData","defaultSelectData","response","obj","defaultSelectRowCount","total","parsed","resolveUpdater","updater","prev","useTableQuery","options","queryKey","queryFn","selectData","selectRowCount","initialPagination","initialSorting","initialColumnFilters","initialGlobalFilter","autoResetPageIndex","queryOptions","pagination","setPagination","useState","DEFAULT_PAGE_SIZE","sorting","setSorting","columnFilters","setColumnFilters","globalFilter","setGlobalFilter","queryParams","useMemo","params","firstSort","filtersDict","cf","fullQueryKey","query","useQuery","ctx","keepPreviousData","data","rowCount","handlePaginationChange","useCallback","handleSortingChange","handleColumnFiltersChange","handleGlobalFilterChange","filter","resetAll","setPage","page","setPageSize","pageSize","setGlobalFilterValue","isLoading","isRefetching","refetch","handleRefresh","useCallback","useMemo","useState","useInfiniteQuery","jsx","extractRawOptions","response","record","EMPTY_FILTERS","useSelectInfiniteQuery","queryKey","queryFn","initialPageParam","getNextPageParam","getPreviousPageParam","selectOptions","mapOption","debounceMs","initialSearch","initialFilters","initialOptions","skeletonLines","skeletonHeight","renderLoadingMore","endMessage","scrollOptions","queryOptions","search","setSearch","useState","filters","setFilters","fullQueryKey","useMemo","resolvedGetNextPageParam","useCallback","lastPage","allPages","lastPageParam","allPageParams","total","acc","p","query","useInfiniteQuery","context","options","pages","allItems","seen","opt","page","pageOptions","raw","item","index","sentinelRef","useInfiniteScroll","handleSearch","newSearch","newFilters","reset","listFooter","Skeleton"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/table/hooks/useTableQuery.ts","../src/components/select/hooks/useSelectInfiniteQuery.tsx","../src/hooks/useMutationApp.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\r\nimport {\r\n useQuery,\r\n keepPreviousData,\r\n type QueryFunctionContext,\r\n type UseQueryOptions,\r\n type UseQueryResult,\r\n} from \"@tanstack/react-query\";\r\nimport type {\r\n ColumnFiltersState,\r\n OnChangeFn,\r\n PaginationState,\r\n RowData,\r\n SortingState,\r\n Updater,\r\n} from \"@tanstack/react-table\";\r\nimport { DEFAULT_PAGE_SIZE } from \"../constants\";\r\n\r\n/**\r\n * Các tham số truy vấn chuẩn hóa gửi lên API máy chủ.\r\n */\r\nexport interface TableQueryParams {\r\n /**\r\n * Số thứ tự trang (1-indexed: 1, 2, 3...)\r\n */\r\n page: number;\r\n\r\n /**\r\n * Kích thước trang (số dòng trên 1 trang)\r\n */\r\n pageSize: number;\r\n\r\n /**\r\n * ID cột được sắp xếp\r\n */\r\n sortBy?: string;\r\n\r\n /**\r\n * Chiều sắp xếp: \"asc\" (tăng dần) hoặc \"desc\" (giảm dần)\r\n */\r\n sortOrder?: \"asc\" | \"desc\";\r\n\r\n /**\r\n * Danh sách bộ lọc cột dạng từ điển phẳng Record<cột, giá trị>\r\n */\r\n filters?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Các tùy chọn cấu hình cho hook `useTableQuery`.\r\n */\r\nexport interface UseTableQueryOptions<TData extends RowData = RowData, TResponse = unknown, TError = Error> {\r\n /**\r\n * Base query key cho TanStack Query.\r\n * `queryParams` sẽ tự động được thêm vào cuối queryKey: `[...queryKey, queryParams]`.\r\n */\r\n queryKey: readonly unknown[];\r\n\r\n /**\r\n * Hàm gọi API truy vấn dữ liệu từ máy chủ.\r\n * Nhận vào `queryParams` đã chuẩn hóa và `context` của TanStack Query.\r\n */\r\n queryFn: (params: TableQueryParams, context: QueryFunctionContext<readonly unknown[]>) => Promise<TResponse>;\r\n\r\n /**\r\n * Hàm trích xuất mảng dữ liệu các dòng (`TData[]`) từ kết quả API trả về.\r\n * Mặc định tự động kiểm tra `response.data`, `response.items`, `response.rows`, `response.results` hoặc bản thân mảng response.\r\n */\r\n selectData?: (response: TResponse) => TData[];\r\n\r\n /**\r\n * Hàm trích xuất tổng số lượng bản ghi (`rowCount` / `total`) từ kết quả API trả về.\r\n * Mặc định tự động kiểm tra `response.total`, `response.rowCount`, `response.totalCount`, `response.count`.\r\n */\r\n selectRowCount?: (response: TResponse) => number;\r\n\r\n /**\r\n * Cấu hình phân trang ban đầu\r\n */\r\n initialPagination?: {\r\n pageIndex?: number;\r\n pageSize?: number;\r\n };\r\n\r\n /**\r\n * Cấu hình sắp xếp ban đầu\r\n */\r\n initialSorting?: SortingState;\r\n\r\n /**\r\n * Cấu hình bộ lọc cột ban đầu\r\n */\r\n initialColumnFilters?: ColumnFiltersState;\r\n\r\n /**\r\n * Cấu hình từ khóa tìm kiếm ban đầu\r\n */\r\n initialGlobalFilter?: string;\r\n\r\n /**\r\n * Tự động đưa về trang đầu (pageIndex = 0) khi thay đổi tìm kiếm, bộ lọc hoặc sắp xếp.\r\n * @default true\r\n */\r\n autoResetPageIndex?: boolean;\r\n\r\n /**\r\n * Các tùy chọn nâng cao truyền trực tiếp cho hook `useQuery` của TanStack Query\r\n * (ví dụ: `staleTime`, `gcTime`, `refetchOnWindowFocus`, `enabled`...).\r\n */\r\n queryOptions?: Omit<UseQueryOptions<TResponse, TError, TResponse, readonly unknown[]>, \"queryKey\" | \"queryFn\">;\r\n}\r\n\r\n/**\r\n * Kết quả trả về từ hook `useTableQuery`.\r\n */\r\nexport interface UseTableQueryReturn<TData extends RowData = RowData, TResponse = unknown, TError = Error> {\r\n /**\r\n * Tập props trọn gói sẵn sàng truyền thẳng vào `<DataTable {...tableProps} />`\r\n */\r\n tableProps: {\r\n data: TData[];\r\n rowCount: number;\r\n isLoading: boolean;\r\n isRefresh: boolean;\r\n onRefresh: () => void;\r\n manualPagination: true;\r\n manualSorting: true;\r\n manualFiltering: true;\r\n pagination: PaginationState;\r\n onPaginationChange: OnChangeFn<PaginationState>;\r\n sorting: SortingState;\r\n onSortingChange: OnChangeFn<SortingState>;\r\n columnFilters: ColumnFiltersState;\r\n onColumnFiltersChange: OnChangeFn<ColumnFiltersState>;\r\n globalFilter: string;\r\n onGlobalFilterChange: (filter: string) => void;\r\n };\r\n\r\n /**\r\n * Instance query đầy đủ từ TanStack Query\r\n */\r\n query: UseQueryResult<TResponse, TError>;\r\n\r\n /**\r\n * Tham số truy vấn hiện tại (dùng để debug, ghi log, đồng bộ URL query string, hoặc xuất Excel)\r\n */\r\n queryParams: TableQueryParams;\r\n\r\n /**\r\n * Đặt lại toàn bộ phân trang, sắp xếp, tìm kiếm và bộ lọc về giá trị mặc định ban đầu\r\n */\r\n resetAll: () => void;\r\n\r\n /**\r\n * Đổi trang thủ công (1-indexed: 1, 2, 3...)\r\n */\r\n setPage: (page: number) => void;\r\n\r\n /**\r\n * Đổi số dòng trên mỗi trang thủ công\r\n */\r\n setPageSize: (pageSize: number) => void;\r\n\r\n /**\r\n * Đổi từ khóa tìm kiếm toàn bảng (lọc client-side)\r\n */\r\n setGlobalFilter: (filter: string) => void;\r\n\r\n /**\r\n * Cập nhật trạng thái sắp xếp\r\n */\r\n setSorting: (sorting: SortingState | ((prev: SortingState) => SortingState)) => void;\r\n\r\n /**\r\n * Cập nhật trạng thái bộ lọc cột\r\n */\r\n setColumnFilters: (filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)) => void;\r\n}\r\n\r\n/**\r\n * Trích xuất mặc định danh sách dòng dữ liệu từ API response.\r\n */\r\nfunction defaultSelectData<TData>(response: unknown): TData[] {\r\n if (!response) return [];\r\n if (Array.isArray(response)) return response as TData[];\r\n if (typeof response === \"object\") {\r\n const obj = response as Record<string, unknown>;\r\n if (Array.isArray(obj.data)) return obj.data as TData[];\r\n if (Array.isArray(obj.items)) return obj.items as TData[];\r\n if (Array.isArray(obj.rows)) return obj.rows as TData[];\r\n if (Array.isArray(obj.results)) return obj.results as TData[];\r\n }\r\n return [];\r\n}\r\n\r\n/**\r\n * Trích xuất mặc định tổng số lượng bản ghi từ API response.\r\n */\r\nfunction defaultSelectRowCount(response: unknown): number {\r\n if (!response) return 0;\r\n if (Array.isArray(response)) return response.length;\r\n if (typeof response === \"object\") {\r\n const obj = response as Record<string, unknown>;\r\n const total = obj.total ?? obj.rowCount ?? obj.totalCount ?? obj.count;\r\n if (typeof total === \"number\") return total;\r\n if (typeof total === \"string\") {\r\n const parsed = Number(total);\r\n if (!isNaN(parsed)) return parsed;\r\n }\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * Helper giải quyết Updater của TanStack Table\r\n */\r\nfunction resolveUpdater<T>(updater: Updater<T>, prev: T): T {\r\n return typeof updater === \"function\" ? (updater as (old: T) => T)(prev) : updater;\r\n}\r\n\r\n/**\r\n * Hook Adapter `useTableQuery` tích hợp TanStack Table v9 và TanStack Query v5.\r\n *\r\n * Tính năng:\r\n * - Đồng bộ hai chiều trạng thái bảng (Pagination, Sorting, Filtering, Search) với server API.\r\n * - Tự động định dạng tham số `TableQueryParams`: `page` (1-indexed), `pageSize`, `search`, `sortBy`, `sortOrder`, `filters`.\r\n * - Tự động gắn `queryParams` vào `queryKey` của TanStack Query để kích hoạt cache/refetch thông minh.\r\n * - Hỗ trợ phân trang mượt mà bằng `placeholderData: keepPreviousData` (không bị nhấp nháy Skeleton khi chuyển trang).\r\n * - Cung cấp `tableProps` dựng sẵn truyền trực tiếp vào `<DataTable {...tableProps} />`.\r\n */\r\nexport function useTableQuery<TData extends RowData = RowData, TResponse = unknown, TError = Error>(\r\n options: UseTableQueryOptions<TData, TResponse, TError>\r\n): UseTableQueryReturn<TData, TResponse, TError> {\r\n const {\r\n queryKey,\r\n queryFn,\r\n selectData = defaultSelectData,\r\n selectRowCount = defaultSelectRowCount,\r\n initialPagination,\r\n initialSorting,\r\n initialColumnFilters,\r\n initialGlobalFilter,\r\n autoResetPageIndex = true,\r\n queryOptions,\r\n } = options;\r\n\r\n // 1. Quản lý trạng thái bảng (Table states)\r\n const [pagination, setPagination] = useState<PaginationState>({\r\n pageIndex: initialPagination?.pageIndex ?? 0,\r\n pageSize: initialPagination?.pageSize ?? DEFAULT_PAGE_SIZE,\r\n });\r\n\r\n const [sorting, setSorting] = useState<SortingState>(initialSorting ?? []);\r\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(initialColumnFilters ?? []);\r\n const [globalFilter, setGlobalFilter] = useState<string>(initialGlobalFilter ?? \"\");\r\n\r\n // 2. Chuẩn hóa tham số query gửi lên server\r\n const queryParams = useMemo<TableQueryParams>(() => {\r\n const params: TableQueryParams = {\r\n page: pagination.pageIndex + 1,\r\n pageSize: pagination.pageSize,\r\n };\r\n\r\n const firstSort = sorting[0];\r\n if (firstSort) {\r\n params.sortBy = firstSort.id;\r\n params.sortOrder = firstSort.desc ? \"desc\" : \"asc\";\r\n }\r\n\r\n if (columnFilters.length > 0) {\r\n const filtersDict: Record<string, unknown> = {};\r\n columnFilters.forEach((cf) => {\r\n filtersDict[cf.id] = cf.value;\r\n });\r\n params.filters = filtersDict;\r\n }\r\n\r\n return params;\r\n }, [pagination.pageIndex, pagination.pageSize, sorting, columnFilters]);\r\n\r\n // 3. Ghép queryKey với queryParams\r\n const fullQueryKey = useMemo(() => [...queryKey, queryParams] as const, [queryKey, queryParams]);\r\n\r\n // 4. Gọi useQuery của TanStack Query v5\r\n const query = useQuery<TResponse, TError, TResponse, readonly unknown[]>({\r\n queryKey: fullQueryKey,\r\n queryFn: (ctx) => queryFn(queryParams, ctx),\r\n placeholderData: keepPreviousData,\r\n ...queryOptions,\r\n });\r\n\r\n // 5. Trích xuất dữ liệu hàng và tổng số lượng\r\n const data = useMemo<TData[]>(() => {\r\n return query.data ? selectData(query.data) : [];\r\n }, [query.data, selectData]);\r\n\r\n const rowCount = useMemo<number>(() => {\r\n return query.data ? selectRowCount(query.data) : 0;\r\n }, [query.data, selectRowCount]);\r\n\r\n // 6. Xử lý các sự kiện thay đổi trạng thái kèm autoResetPageIndex\r\n const handlePaginationChange: OnChangeFn<PaginationState> = useCallback((updater) => {\r\n setPagination((prev) => resolveUpdater(updater, prev));\r\n }, []);\r\n\r\n const handleSortingChange: OnChangeFn<SortingState> = useCallback(\r\n (updater) => {\r\n setSorting((prev) => resolveUpdater(updater, prev));\r\n if (autoResetPageIndex) {\r\n setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));\r\n }\r\n },\r\n [autoResetPageIndex]\r\n );\r\n\r\n const handleColumnFiltersChange: OnChangeFn<ColumnFiltersState> = useCallback(\r\n (updater) => {\r\n setColumnFilters((prev) => resolveUpdater(updater, prev));\r\n if (autoResetPageIndex) {\r\n setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));\r\n }\r\n },\r\n [autoResetPageIndex]\r\n );\r\n\r\n const handleGlobalFilterChange = useCallback(\r\n (filter: string) => {\r\n setGlobalFilter(filter);\r\n if (autoResetPageIndex) {\r\n setPagination((prev) => (prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 }));\r\n }\r\n },\r\n [autoResetPageIndex]\r\n );\r\n\r\n // 7. Các hàm tiện ích bổ sung\r\n const resetAll = useCallback(() => {\r\n setPagination({\r\n pageIndex: initialPagination?.pageIndex ?? 0,\r\n pageSize: initialPagination?.pageSize ?? DEFAULT_PAGE_SIZE,\r\n });\r\n setSorting(initialSorting ?? []);\r\n setColumnFilters(initialColumnFilters ?? []);\r\n setGlobalFilter(initialGlobalFilter ?? \"\");\r\n }, [initialPagination, initialSorting, initialColumnFilters, initialGlobalFilter]);\r\n\r\n const setPage = useCallback((page: number) => {\r\n setPagination((prev) => ({\r\n ...prev,\r\n pageIndex: Math.max(0, page - 1),\r\n }));\r\n }, []);\r\n\r\n const setPageSize = useCallback((pageSize: number) => {\r\n setPagination({\r\n pageIndex: 0,\r\n pageSize,\r\n });\r\n }, []);\r\n\r\n const setGlobalFilterValue = useCallback(\r\n (filter: string) => {\r\n handleGlobalFilterChange(filter);\r\n },\r\n [handleGlobalFilterChange]\r\n );\r\n\r\n // 8. Đóng gói tableProps truyền cho DataTable\r\n const { isLoading, isRefetching, refetch } = query;\r\n\r\n const handleRefresh = useCallback(() => {\r\n void refetch();\r\n }, [refetch]);\r\n\r\n const tableProps = useMemo(\r\n () => ({\r\n data,\r\n rowCount,\r\n isLoading,\r\n isRefresh: isRefetching && !isLoading,\r\n onRefresh: handleRefresh,\r\n manualPagination: true as const,\r\n manualSorting: true as const,\r\n manualFiltering: true as const,\r\n pagination,\r\n onPaginationChange: handlePaginationChange,\r\n sorting,\r\n onSortingChange: handleSortingChange,\r\n columnFilters,\r\n onColumnFiltersChange: handleColumnFiltersChange,\r\n globalFilter,\r\n onGlobalFilterChange: handleGlobalFilterChange,\r\n }),\r\n [\r\n data,\r\n rowCount,\r\n isLoading,\r\n isRefetching,\r\n handleRefresh,\r\n pagination,\r\n handlePaginationChange,\r\n sorting,\r\n handleSortingChange,\r\n columnFilters,\r\n handleColumnFiltersChange,\r\n globalFilter,\r\n handleGlobalFilterChange,\r\n ]\r\n );\r\n\r\n return {\r\n tableProps,\r\n query,\r\n queryParams,\r\n resetAll,\r\n setPage,\r\n setPageSize,\r\n setGlobalFilter: setGlobalFilterValue,\r\n setSorting,\r\n setColumnFilters,\r\n };\r\n}\r\n","import React, {\r\n useCallback,\r\n useMemo,\r\n useState,\r\n type ReactNode,\r\n} from \"react\";\r\nimport {\r\n useInfiniteQuery,\r\n type InfiniteData,\r\n type QueryFunctionContext,\r\n type UseInfiniteQueryOptions,\r\n type UseInfiniteQueryResult,\r\n} from \"@tanstack/react-query\";\r\nimport type { SelectOptionItem } from \"../types\";\r\nimport { useInfiniteScroll, type UseInfiniteScrollOptions } from \"@/hooks/useInfiniteScroll\";\r\nimport Skeleton from \"@/components/skeleton/Skeleton\";\r\n\r\n/**\r\n * Các tham số truy vấn chuẩn hóa được chuyển tới hàm `queryFn`.\r\n */\r\nexport interface SelectQueryParams<\r\n TPageParam = number,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n> {\r\n /**\r\n * Tham số phân trang hiện tại (trang hiện tại hoặc cursor).\r\n */\r\n pageParam: TPageParam;\r\n\r\n /**\r\n * Từ khóa tìm kiếm từ ô Select.\r\n */\r\n search: string;\r\n\r\n /**\r\n * Bộ lọc bổ sung từ menu filter của Select.\r\n */\r\n filters: TFilters;\r\n}\r\n\r\n/**\r\n * Hàm hỗ trợ tự động trích xuất mảng dữ liệu từ các cấu trúc response phổ biến.\r\n */\r\nfunction extractRawOptions(response: unknown): unknown[] {\r\n if (!response) return [];\r\n if (Array.isArray(response)) return response;\r\n if (typeof response !== \"object\") return [];\r\n\r\n const record = response as Record<string, unknown>;\r\n if (Array.isArray(record.data)) return record.data;\r\n if (Array.isArray(record.items)) return record.items;\r\n if (Array.isArray(record.options)) return record.options;\r\n if (Array.isArray(record.results)) return record.results;\r\n if (Array.isArray(record.rows)) return record.rows;\r\n\r\n return [];\r\n}\r\n\r\n/**\r\n * Các tùy chọn cấu hình cho hook `useSelectInfiniteQuery`.\r\n */\r\nexport interface UseSelectInfiniteQueryOptions<\r\n TData = unknown,\r\n TResponse = unknown,\r\n TPageParam = number,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n TError = Error,\r\n> {\r\n /**\r\n * Base query key cho TanStack Query.\r\n * `[{ search, filters }]` sẽ tự động được thêm vào cuối queryKey:\r\n * `[...queryKey, { search, filters }]`.\r\n */\r\n queryKey: readonly unknown[];\r\n\r\n /**\r\n * Hàm gọi API truy vấn dữ liệu từ máy chủ theo từng trang.\r\n * Nhận vào `SelectQueryParams` đã chuẩn hóa và `context` của TanStack Query.\r\n */\r\n queryFn: (\r\n params: SelectQueryParams<TPageParam, TFilters>,\r\n context: QueryFunctionContext<readonly unknown[], TPageParam>\r\n ) => Promise<TResponse>;\r\n\r\n /**\r\n * Giá trị tham số trang đầu tiên (initialPageParam).\r\n * @default 1\r\n */\r\n initialPageParam?: TPageParam;\r\n\r\n /**\r\n * Hàm xác định `pageParam` cho trang tiếp theo.\r\n * Trả về `undefined` hoặc `null` khi không còn dữ liệu tiếp theo.\r\n */\r\n getNextPageParam?: (\r\n lastPage: TResponse,\r\n allPages: TResponse[],\r\n lastPageParam: TPageParam,\r\n allPageParams: TPageParam[]\r\n ) => TPageParam | undefined | null;\r\n\r\n /**\r\n * Hàm xác định `pageParam` cho trang trước đó (nếu có).\r\n */\r\n getPreviousPageParam?: (\r\n firstPage: TResponse,\r\n allPages: TResponse[],\r\n firstPageParam: TPageParam,\r\n allPageParams: TPageParam[]\r\n ) => TPageParam | undefined | null;\r\n\r\n /**\r\n * Hàm trích xuất danh sách options (`SelectOptionItem<TData>[]`) từ mỗi `response` trang.\r\n * Nếu không truyền, mặc định sẽ kiểm tra `data`, `items`, `options`, `results`, `rows` hoặc chính `response`.\r\n */\r\n selectOptions?: (response: TResponse) => SelectOptionItem<TData>[];\r\n\r\n /**\r\n * Hàm biến đổi từng phần tử thô (raw entity) thành `SelectOptionItem<TData>`.\r\n */\r\n mapOption?: (item: unknown, index: number) => SelectOptionItem<TData>;\r\n\r\n /**\r\n * Thời gian debounce từ khóa tìm kiếm (tính theo ms).\r\n * @default 300\r\n */\r\n debounceMs?: number;\r\n\r\n /**\r\n * Từ khóa tìm kiếm ban đầu.\r\n */\r\n initialSearch?: string;\r\n\r\n /**\r\n * Giá trị bộ lọc menu ban đầu.\r\n */\r\n initialFilters?: TFilters;\r\n\r\n /**\r\n * Danh sách options ban đầu ghim sẵn (ví dụ các options đã chọn trước đó cần bảo lưu).\r\n */\r\n initialOptions?: SelectOptionItem<TData>[];\r\n\r\n /**\r\n * Số dòng Skeleton hiển thị khi đang tải thêm trang tiếp theo.\r\n * @default 2\r\n */\r\n skeletonLines?: number;\r\n\r\n /**\r\n * Chiều cao của mỗi dòng Skeleton khi đang tải thêm trang.\r\n * @default \"1.75rem\"\r\n */\r\n skeletonHeight?: number | string;\r\n\r\n /**\r\n * Tùy biến render khi đang tải thêm trang tiếp theo (thay thế Skeleton mặc định).\r\n */\r\n renderLoadingMore?: () => ReactNode;\r\n\r\n /**\r\n * Thông báo hiển thị khi đã tải hết toàn bộ dữ liệu ở đáy danh sách (tùy chọn).\r\n */\r\n endMessage?: ReactNode;\r\n\r\n /**\r\n * Tùy chọn truyền trực tiếp cho hook `useInfiniteScroll`.\r\n */\r\n scrollOptions?: Partial<Omit<UseInfiniteScrollOptions, \"onLoadMore\" | \"hasMore\" | \"isLoading\">>;\r\n\r\n /**\r\n * Các tùy chọn nâng cao truyền trực tiếp cho hook `useInfiniteQuery` của TanStack Query.\r\n */\r\n queryOptions?: Omit<\r\n UseInfiniteQueryOptions<TResponse, TError, InfiniteData<TResponse>, readonly unknown[], TPageParam>,\r\n \"queryKey\" | \"queryFn\" | \"initialPageParam\" | \"getNextPageParam\" | \"getPreviousPageParam\"\r\n >;\r\n}\r\n\r\n/**\r\n * Kết quả trả về từ hook `useSelectInfiniteQuery`.\r\n */\r\nexport interface UseSelectInfiniteQueryReturn<\r\n TData = unknown,\r\n TResponse = unknown,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n TError = Error,\r\n> {\r\n /**\r\n * Bộ props trọn gói sẵn sàng truyền thẳng vào `<Select {...selectProps} />` hoặc `<MultiSelect {...selectProps} />`\r\n */\r\n selectProps: {\r\n options: SelectOptionItem<TData>[];\r\n isLoading: boolean;\r\n searchMode: \"server\";\r\n debounceMs: number;\r\n onSearch: (query: string, filters: TFilters) => void;\r\n listFooter: ReactNode;\r\n };\r\n\r\n /**\r\n * Instance query đầy đủ từ TanStack Query\r\n */\r\n query: UseInfiniteQueryResult<InfiniteData<TResponse>, TError>;\r\n\r\n /**\r\n * Toàn bộ danh sách options đã gom tụ từ tất cả các trang\r\n */\r\n options: SelectOptionItem<TData>[];\r\n\r\n /**\r\n * Từ khóa tìm kiếm hiện tại (đã được Select debounce qua onSearch)\r\n */\r\n search: string;\r\n\r\n /**\r\n * Phương thức cập nhật từ khóa tìm kiếm\r\n */\r\n setSearch: (search: string) => void;\r\n\r\n /**\r\n * Bộ lọc menu hiện tại\r\n */\r\n filters: TFilters;\r\n\r\n /**\r\n * Phương thức cập nhật bộ lọc menu\r\n */\r\n setFilters: (filters: TFilters | ((prev: TFilters) => TFilters)) => void;\r\n\r\n /**\r\n * Ref gắn vào phần tử sentinel (IntersectionObserver)\r\n */\r\n sentinelRef: (node: HTMLElement | null) => void;\r\n\r\n /**\r\n * Đặt lại toàn bộ tìm kiếm, bộ lọc về giá trị ban đầu\r\n */\r\n reset: () => void;\r\n}\r\n\r\nconst EMPTY_FILTERS: Record<string, unknown> = {};\r\n\r\n/**\r\n * Hook `useSelectInfiniteQuery` kết hợp TanStack Query `useInfiniteQuery` với `useInfiniteScroll`\r\n * dành riêng cho `<Select />` và `<MultiSelect />` ở chế độ máy chủ (`searchMode=\"server\"`).\r\n */\r\nexport function useSelectInfiniteQuery<\r\n TData = unknown,\r\n TResponse = unknown,\r\n TPageParam = number,\r\n TFilters extends Record<string, unknown> = Record<string, unknown>,\r\n TError = Error,\r\n>({\r\n queryKey,\r\n queryFn,\r\n initialPageParam = 1 as unknown as TPageParam,\r\n getNextPageParam,\r\n getPreviousPageParam,\r\n selectOptions,\r\n mapOption,\r\n debounceMs = 300,\r\n initialSearch = \"\",\r\n initialFilters = EMPTY_FILTERS as TFilters,\r\n initialOptions,\r\n skeletonLines = 2,\r\n skeletonHeight = \"1.75rem\",\r\n renderLoadingMore,\r\n endMessage,\r\n scrollOptions,\r\n queryOptions,\r\n}: UseSelectInfiniteQueryOptions<TData, TResponse, TPageParam, TFilters, TError>): UseSelectInfiniteQueryReturn<TData, TResponse, TFilters, TError> {\r\n // 1. Quản lý từ khóa tìm kiếm & bộ lọc menu\r\n const [search, setSearch] = useState(initialSearch);\r\n const [filters, setFilters] = useState<TFilters>(initialFilters);\r\n\r\n // 2. Xây dựng full queryKey kết hợp search & filters\r\n const fullQueryKey = useMemo(\r\n () => [...queryKey, { search, filters }] as const,\r\n [queryKey, search, filters]\r\n );\r\n\r\n // 4. Default getNextPageParam thông minh\r\n const resolvedGetNextPageParam = useCallback(\r\n (\r\n lastPage: TResponse,\r\n allPages: TResponse[],\r\n lastPageParam: TPageParam,\r\n allPageParams: TPageParam[]\r\n ): TPageParam | undefined | null => {\r\n if (getNextPageParam) {\r\n return getNextPageParam(lastPage, allPages, lastPageParam, allPageParams);\r\n }\r\n\r\n if (!lastPage || typeof lastPage !== \"object\") return undefined;\r\n const record = lastPage as Record<string, unknown>;\r\n\r\n if (typeof record.nextPage === \"number\" || typeof record.nextPage === \"string\") {\r\n return record.nextPage as unknown as TPageParam;\r\n }\r\n if (typeof record.next_page === \"number\" || typeof record.next_page === \"string\") {\r\n return record.next_page as unknown as TPageParam;\r\n }\r\n if (typeof record.nextCursor === \"string\" || typeof record.nextCursor === \"number\") {\r\n return record.nextCursor as unknown as TPageParam;\r\n }\r\n if (typeof record.next_cursor === \"string\" || typeof record.next_cursor === \"number\") {\r\n return record.next_cursor as unknown as TPageParam;\r\n }\r\n\r\n if (record.hasMore === false || record.has_more === false || record.hasNextPage === false) {\r\n return undefined;\r\n }\r\n\r\n if (typeof lastPageParam === \"number\") {\r\n const rawItems = extractRawOptions(lastPage);\r\n if (rawItems.length === 0) return undefined;\r\n\r\n const total =\r\n typeof record.total === \"number\"\r\n ? record.total\r\n : typeof record.totalCount === \"number\"\r\n ? record.totalCount\r\n : typeof record.count === \"number\"\r\n ? record.count\r\n : undefined;\r\n\r\n if (total !== undefined) {\r\n const currentCount = allPages.reduce((acc, p) => acc + extractRawOptions(p).length, 0);\r\n if (currentCount >= total) return undefined;\r\n }\r\n\r\n return (lastPageParam + 1) as unknown as TPageParam;\r\n }\r\n\r\n return undefined;\r\n },\r\n [getNextPageParam]\r\n );\r\n\r\n // 5. TanStack Query useInfiniteQuery\r\n const query = useInfiniteQuery<TResponse, TError, InfiniteData<TResponse>, readonly unknown[], TPageParam>({\r\n queryKey: fullQueryKey,\r\n queryFn: (context) => {\r\n return queryFn(\r\n {\r\n pageParam: context.pageParam as TPageParam,\r\n search,\r\n filters,\r\n },\r\n context\r\n );\r\n },\r\n initialPageParam,\r\n getNextPageParam: resolvedGetNextPageParam,\r\n getPreviousPageParam,\r\n ...queryOptions,\r\n });\r\n\r\n // 6. Trích xuất và gom tụ options từ tất cả các trang\r\n const options = useMemo<SelectOptionItem<TData>[]>(() => {\r\n const pages = query.data?.pages ?? [];\r\n const allItems: SelectOptionItem<TData>[] = [];\r\n const seen = new Set<string | number>();\r\n\r\n // Nếu có initialOptions, đưa vào danh sách trước\r\n if (initialOptions && initialOptions.length > 0) {\r\n for (const opt of initialOptions) {\r\n if (!seen.has(opt.value)) {\r\n seen.add(opt.value);\r\n allItems.push(opt);\r\n }\r\n }\r\n }\r\n\r\n for (const page of pages) {\r\n let pageOptions: SelectOptionItem<TData>[] = [];\r\n\r\n if (selectOptions) {\r\n pageOptions = selectOptions(page);\r\n } else {\r\n const raw = extractRawOptions(page);\r\n if (mapOption) {\r\n pageOptions = raw.map((item, index) => mapOption(item, index));\r\n } else {\r\n pageOptions = raw as SelectOptionItem<TData>[];\r\n }\r\n }\r\n\r\n for (const opt of pageOptions) {\r\n if (opt && opt.value !== undefined && !seen.has(opt.value)) {\r\n seen.add(opt.value);\r\n allItems.push(opt);\r\n }\r\n }\r\n }\r\n\r\n return allItems;\r\n }, [query.data?.pages, selectOptions, mapOption, initialOptions]);\r\n\r\n // 7. useInfiniteScroll để tải trang tiếp theo khi người dùng cuộn đến cuối\r\n const { sentinelRef } = useInfiniteScroll({\r\n onLoadMore: () => {\r\n if (query.hasNextPage && !query.isFetchingNextPage) {\r\n query.fetchNextPage();\r\n }\r\n },\r\n hasMore: Boolean(query.hasNextPage),\r\n isLoading: query.isFetchingNextPage || query.isLoading,\r\n disabled: query.isError,\r\n rootMargin: scrollOptions?.rootMargin ?? \"80px\",\r\n threshold: scrollOptions?.threshold ?? 0,\r\n root: scrollOptions?.root,\r\n });\r\n\r\n // 8. Handler nhận search từ Select component\r\n const handleSearch = useCallback((newSearch: string, newFilters: TFilters) => {\r\n setSearch(newSearch);\r\n setFilters(newFilters);\r\n }, []);\r\n\r\n // 9. Reset state\r\n const reset = useCallback(() => {\r\n setSearch(initialSearch);\r\n setFilters(initialFilters);\r\n }, [initialSearch, initialFilters]);\r\n\r\n // 10. Giao diện Skeleton / Sentinel dưới đáy danh sách (listFooter)\r\n const listFooter = useMemo<ReactNode>(() => {\r\n // Nếu chưa có option nào thì để màn hình loading chính của SelectMenu hiển thị\r\n if (options.length === 0) return null;\r\n\r\n if (query.isFetchingNextPage) {\r\n return (\r\n <div ref={sentinelRef} className=\"p-1 space-y-1 w-full\">\r\n {renderLoadingMore ? (\r\n renderLoadingMore()\r\n ) : (\r\n <Skeleton\r\n lines={skeletonLines}\r\n height={skeletonHeight}\r\n gap=\"0.25rem\"\r\n radius=\"sm\"\r\n className=\"p-0.5\"\r\n />\r\n )}\r\n </div>\r\n );\r\n }\r\n\r\n if (query.hasNextPage) {\r\n return <div ref={sentinelRef} className=\"h-1 w-full\" aria-hidden=\"true\" />;\r\n }\r\n\r\n if (endMessage) {\r\n return (\r\n <div className=\"py-2 px-1 text-center text-xs text-neutral-400 dark:text-neutral-500\">\r\n {endMessage}\r\n </div>\r\n );\r\n }\r\n\r\n return null;\r\n }, [\r\n options.length,\r\n query.isFetchingNextPage,\r\n query.hasNextPage,\r\n sentinelRef,\r\n renderLoadingMore,\r\n skeletonLines,\r\n skeletonHeight,\r\n endMessage,\r\n ]);\r\n\r\n // 11. Đóng gói selectProps trọn gói\r\n const selectProps = useMemo(\r\n () => ({\r\n options,\r\n isLoading: query.isLoading,\r\n searchMode: \"server\" as const,\r\n debounceMs,\r\n onSearch: handleSearch,\r\n listFooter,\r\n }),\r\n [options, query.isLoading, debounceMs, handleSearch, listFooter]\r\n );\r\n\r\n return {\r\n selectProps,\r\n query,\r\n options,\r\n search,\r\n setSearch,\r\n filters,\r\n setFilters,\r\n sentinelRef,\r\n reset,\r\n };\r\n}\r\n\r\nexport default useSelectInfiniteQuery;\r\n","import { useRef, type ReactNode } from \"react\";\nimport {\n useMutation,\n useQueryClient,\n type DefaultError,\n type InvalidateOptions,\n type InvalidateQueryFilters,\n type QueryKey,\n type UseMutationOptions,\n type UseMutationResult,\n} from \"@tanstack/react-query\";\nimport { toast } from \"../components/toast\";\nimport type { ToastOptions } from \"../components/toast/types\";\nimport type { AlertVariant } from \"../components/alert/types\";\n\n/**\n * Mục tiêu invalidate cache: có thể là QueryKey (readonly unknown[]) hoặc InvalidateQueryFilters.\n */\nexport type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;\n\n/**\n * Tùy chọn cấu hình Toast cho hook `useMutationApp`.\n */\nexport interface UseMutationAppToastOptions<TData, TError, TVariables> {\n /**\n * Nội dung thông báo khi đang thực thi mutation (loading spinner, không tự đóng).\n * Có thể là ReactNode hoặc một hàm nhận vào `variables`.\n */\n loading?: ReactNode | ((variables: TVariables) => ReactNode);\n\n /**\n * Nội dung thông báo khi mutation hoàn tất thành công.\n * Có thể là ReactNode hoặc một hàm nhận vào `(data, variables)`.\n */\n success?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);\n\n /**\n * Nội dung thông báo khi mutation thất bại.\n * - `true`: Tự động trích xuất thông điệp lỗi từ API/Error qua `extractErrorMessage`.\n * - `false`: Không hiển thị toast lỗi.\n * - `ReactNode`: Thông điệp lỗi cố định.\n * - `(error, variables) => ReactNode`: Hàm tạo thông điệp lỗi linh hoạt.\n * @default true\n */\n error?: boolean | ReactNode | ((error: TError, variables: TVariables) => ReactNode);\n\n /**\n * Nội dung mô tả chi tiết (description) cho thông báo khi đang loading.\n */\n loadingDescription?: ReactNode | ((variables: TVariables) => ReactNode);\n\n /**\n * Nội dung mô tả chi tiết (description) cho thông báo thành công.\n */\n successDescription?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);\n\n /**\n * Nội dung mô tả chi tiết (description) cho thông báo lỗi.\n */\n errorDescription?: ReactNode | ((error: TError, variables: TVariables) => ReactNode);\n\n /**\n * Tùy chọn cấu hình bổ sung cho Toast (duration, position, size, radius, closable...).\n */\n options?: ToastOptions;\n\n /**\n * Kiểu hiển thị Toast: \"soft\" | \"solid\" | \"outline\".\n * @default \"soft\"\n */\n variant?: AlertVariant;\n}\n\n/**\n * Các tùy chọn cấu hình cho hook `useMutationApp`.\n */\nexport interface UseMutationAppOptions<\n TData = unknown,\n TError = DefaultError,\n TVariables = void,\n TContext = unknown,\n> extends Omit<UseMutationOptions<TData, TError, TVariables, TContext>, \"mutationFn\"> {\n /**\n * Hàm gọi API hoặc thực hiện tác vụ bất đồng bộ.\n */\n mutationFn?: (variables: TVariables) => Promise<TData>;\n\n /**\n * Khóa truy vấn hoặc danh sách khóa truy vấn cần tự động làm mới (invalidate) khi mutation thành công.\n * Hỗ trợ:\n * - Một QueryKey đơn: `[\"teachers\"]` hoặc `[\"users\", 1]`\n * - Danh sách nhiều QueryKey: `[[\"teachers\"], [\"classes\"]]`\n * - Bộ lọc nâng cao InvalidateQueryFilters: `{ queryKey: [\"teachers\"], exact: true }`\n * - Danh sách bộ lọc: `[{ queryKey: [\"teachers\"] }, { queryKey: [\"classes\"] }]`\n * - Hàm trả về động: `(data, variables) => [[\"teachers\"], [\"classes\", variables.classId]]`\n */\n invalidateQueries?:\n | InvalidateQueryTarget\n | InvalidateQueryTarget[]\n | ((\n data: TData,\n variables: TVariables\n ) =>\n | InvalidateQueryTarget\n | InvalidateQueryTarget[]\n | void\n | null\n | undefined);\n\n /**\n * Tùy chọn InvalidateOptions bổ sung truyền cho `queryClient.invalidateQueries`\n * (ví dụ: `throwOnError`, `cancelRefetch`).\n */\n invalidateOptions?: InvalidateOptions;\n\n /**\n * Cấu hình hiển thị thông báo Toast.\n * - `false`: Tắt toàn bộ thông báo Toast.\n * - `true`: Bật Toast với cấu hình mặc định (tự động báo lỗi nếu có).\n * - `object`: Cấu hình chi tiết `loading`, `success`, `error`.\n */\n toast?: boolean | UseMutationAppToastOptions<TData, TError, TVariables>;\n\n /**\n * Shortcut cấu hình nhanh thông báo thành công.\n */\n successMessage?: ReactNode | ((data: TData, variables: TVariables) => ReactNode);\n\n /**\n * Shortcut cấu hình nhanh thông báo lỗi.\n */\n errorMessage?: ReactNode | ((error: TError, variables: TVariables) => ReactNode);\n\n /**\n * Shortcut cấu hình nhanh thông báo loading.\n */\n loadingMessage?: ReactNode | ((variables: TVariables) => ReactNode);\n}\n\n/**\n * Kết quả trả về từ hook `useMutationApp`.\n */\nexport type UseMutationAppReturn<\n TData = unknown,\n TError = DefaultError,\n TVariables = void,\n TContext = unknown,\n> = UseMutationResult<TData, TError, TVariables, TContext> & {\n /**\n * Bí danh (alias) của `isPending`, tương thích ngược với thói quen sử dụng `isLoading` của TanStack Query v4.\n */\n isLoading: boolean;\n};\n\n/**\n * Hàm hỗ trợ tự động trích xuất thông điệp lỗi (error message) từ nhiều định dạng lỗi phổ biến:\n * - Axios Error response (`error.response?.data?.message`, `error.response?.data?.error`...)\n * - Fetch API response / HTTP status\n * - Standard JS Error (`error.message`)\n * - String error\n */\nexport function extractErrorMessage(error: unknown, fallback = \"Đã xảy ra lỗi. Vui lòng thử lại sau.\"): string {\n if (!error) return fallback;\n\n if (typeof error === \"string\" && error.trim()) {\n return error;\n }\n\n if (typeof error === \"object\" && error !== null) {\n const errObj = error as Record<string, unknown>;\n\n // 1. Axios error structure: error.response?.data\n if (errObj.response && typeof errObj.response === \"object\" && errObj.response !== null) {\n const resp = errObj.response as Record<string, unknown>;\n const status = typeof resp.status === \"number\" ? resp.status : undefined;\n\n if (resp.data && typeof resp.data === \"object\" && resp.data !== null) {\n const data = resp.data as Record<string, unknown>;\n\n if (typeof data.message === \"string\" && data.message.trim()) {\n return data.message;\n }\n if (typeof data.error === \"string\" && data.error.trim()) {\n return data.error;\n }\n if (typeof data.detail === \"string\" && data.detail.trim()) {\n return data.detail;\n }\n if (typeof data.title === \"string\" && data.title.trim()) {\n return data.title;\n }\n\n // NestJS / class-validator / Laravel array errors: { message: [\"Field a is required\", ...] }\n if (Array.isArray(data.message) && data.message.length > 0) {\n const first = data.message[0];\n if (typeof first === \"string\") return first;\n }\n\n // Standard errors array: { errors: [\"...\", \"...\"] } or { errors: [{ message: \"...\" }] }\n if (Array.isArray(data.errors) && data.errors.length > 0) {\n const first = data.errors[0];\n if (typeof first === \"string\") return first;\n if (typeof first === \"object\" && first !== null) {\n const firstRecord = first as Record<string, unknown>;\n if (typeof firstRecord.message === \"string\" && firstRecord.message.trim()) {\n return firstRecord.message;\n }\n }\n }\n }\n\n // HTTP Status code fallback nếu body không có message rõ ràng\n if (status) {\n switch (status) {\n case 400:\n return \"Dữ liệu yêu cầu không hợp lệ (400).\";\n case 401:\n return \"Phiên làm việc đã hết hạn hoặc chưa đăng nhập (401).\";\n case 403:\n return \"Bạn không có quyền thực hiện thao tác này (403).\";\n case 404:\n return \"Không tìm thấy dữ liệu yêu cầu (404).\";\n case 409:\n return \"Dữ liệu bị trùng lặp hoặc xung đột (409).\";\n case 422:\n return \"Dữ liệu gửi lên không đúng định dạng (422).\";\n case 500:\n return \"Lỗi máy chủ nội bộ. Vui lòng thử lại sau (500).\";\n case 502:\n case 503:\n case 504:\n return \"Không thể kết nối đến máy chủ. Vui lòng thử lại sau.\";\n }\n }\n }\n\n // 2. Standard JS Error: error.message\n if (typeof errObj.message === \"string\" && errObj.message.trim()) {\n return errObj.message;\n }\n\n // 3. Simple fields: error.error, error.detail, error.title\n if (typeof errObj.error === \"string\" && errObj.error.trim()) {\n return errObj.error;\n }\n if (typeof errObj.detail === \"string\" && errObj.detail.trim()) {\n return errObj.detail;\n }\n }\n\n return fallback;\n}\n\n/**\n * Chuẩn hóa các mục tiêu invalidation thành danh sách `{ filters, options }`\n */\nfunction normalizeInvalidateTargets(\n targets: unknown,\n baseOptions?: InvalidateOptions\n): Array<{ filters: InvalidateQueryFilters; options?: InvalidateOptions }> {\n if (!targets) return [];\n\n if (Array.isArray(targets)) {\n if (targets.length === 0) return [];\n\n // Kiểm tra xem targets là danh sách các target hay bản thân nó là 1 QueryKey\n // Nếu mọi phần tử đều là Array hoặc Object có chứa 'queryKey', thì đó là mảng các target\n // Ví dụ: [ ['teachers'], ['classes'] ] hoặc [ { queryKey: ['teachers'] } ]\n const isArrayOfTargets = targets.every(\n (item) => Array.isArray(item) || (typeof item === \"object\" && item !== null && \"queryKey\" in item)\n );\n\n if (isArrayOfTargets) {\n return targets.map((item) => {\n if (Array.isArray(item)) {\n return { filters: { queryKey: item as QueryKey }, options: baseOptions };\n }\n return { filters: item as InvalidateQueryFilters, options: baseOptions };\n });\n }\n\n // Trường hợp là 1 QueryKey đơn lẻ dạng mảng: ['teachers'] hoặc ['teachers', 1]\n return [{ filters: { queryKey: targets as QueryKey }, options: baseOptions }];\n }\n\n // Trường hợp là 1 InvalidateQueryFilters object: { queryKey: ['teachers'], exact: true }\n if (typeof targets === \"object\" && targets !== null && \"queryKey\" in targets) {\n return [{ filters: targets as InvalidateQueryFilters, options: baseOptions }];\n }\n\n return [];\n}\n\n/**\n * Hook `useMutationApp` - Tối ưu hóa tác vụ Mutation cho ứng dụng với TanStack Query v5 & `@openway/ui`.\n *\n * Tính năng chính:\n * - **Tự động hóa Toast**: Hiển thị Toast loading khi xử lý, tự động cập nhật Toast success/error mượt mà.\n * - **Tự động bóc tách lỗi**: Bóc tách thông điệp lỗi chính xác từ Axios / API response qua `extractErrorMessage`.\n * - **Tự động Invalidate Cache**: Refresh tự động các query liên quan (như DataTable, Select) khi hoàn tất.\n * - **Bổ sung `isLoading`**: Cung cấp alias `isLoading` cho `isPending`, thuận tiện khi code giao diện.\n * - **Giữ trọn vẹn sức mạnh của TanStack Query**: Hỗ trợ đầy đủ `onMutate`, `onSuccess`, `onError`, `onSettled`.\n */\nexport function useMutationApp<\n TData = unknown,\n TError = DefaultError,\n TVariables = void,\n TContext = unknown,\n>(\n options: UseMutationAppOptions<TData, TError, TVariables, TContext>\n): UseMutationAppReturn<TData, TError, TVariables, TContext> {\n const queryClient = useQueryClient();\n const activeToastIdsRef = useRef<Array<string | number>>([]);\n\n const {\n mutationFn,\n invalidateQueries,\n invalidateOptions,\n toast: toastConfig = true,\n successMessage,\n errorMessage,\n loadingMessage,\n onMutate: userOnMutate,\n onSuccess: userOnSuccess,\n onError: userOnError,\n onSettled: userOnSettled,\n ...mutationOptions\n } = options;\n\n // Hợp nhất cấu hình toast từ object `toast` và các shortcut message\n const isToastEnabled = toastConfig !== false;\n const toastDetails: UseMutationAppToastOptions<TData, TError, TVariables> =\n typeof toastConfig === \"object\" && toastConfig !== null ? { ...toastConfig } : {};\n\n const effectiveLoading = loadingMessage ?? toastDetails.loading;\n const effectiveSuccess = successMessage ?? toastDetails.success;\n const effectiveError = errorMessage ?? toastDetails.error ?? true;\n const effectiveVariant = toastDetails.variant ?? \"soft\";\n const customToastOptions = toastDetails.options;\n\n const mutation = useMutation<TData, TError, TVariables, TContext>({\n ...mutationOptions,\n mutationFn,\n\n onMutate: async (variables, mutationContext) => {\n let toastId: string | number | undefined;\n\n if (isToastEnabled && effectiveLoading) {\n const loadingTitle =\n typeof effectiveLoading === \"function\"\n ? effectiveLoading(variables)\n : effectiveLoading;\n\n const loadingDesc =\n typeof toastDetails.loadingDescription === \"function\"\n ? toastDetails.loadingDescription(variables)\n : toastDetails.loadingDescription;\n\n toastId = toast.loading(loadingTitle, loadingDesc, {\n ...customToastOptions,\n variant: effectiveVariant,\n });\n\n activeToastIdsRef.current.push(toastId);\n }\n\n try {\n if (userOnMutate) {\n return await userOnMutate(variables, mutationContext);\n }\n } catch (err) {\n if (toastId) {\n toast.dismiss(toastId);\n activeToastIdsRef.current = activeToastIdsRef.current.filter((id) => id !== toastId);\n }\n throw err;\n }\n\n return undefined as TContext;\n },\n\n onSuccess: async (data, variables, context, mutationContext) => {\n const toastId = activeToastIdsRef.current.shift();\n\n // Đóng toast loading nếu có\n if (toastId) {\n toast.dismiss(toastId);\n }\n\n // Hiển thị toast thành công nếu có cấu hình\n if (isToastEnabled && effectiveSuccess) {\n const successTitle =\n typeof effectiveSuccess === \"function\"\n ? effectiveSuccess(data, variables)\n : effectiveSuccess;\n\n const successDesc =\n typeof toastDetails.successDescription === \"function\"\n ? toastDetails.successDescription(data, variables)\n : toastDetails.successDescription;\n\n toast.success(successTitle, successDesc, {\n ...customToastOptions,\n variant: effectiveVariant,\n });\n }\n\n // Tự động invalidate các query được chỉ định\n if (invalidateQueries) {\n const resolvedTargets =\n typeof invalidateQueries === \"function\"\n ? invalidateQueries(data, variables)\n : invalidateQueries;\n\n const normalized = normalizeInvalidateTargets(resolvedTargets, invalidateOptions);\n if (normalized.length > 0) {\n await Promise.all(\n normalized.map(({ filters, options: opts }) =>\n queryClient.invalidateQueries(filters, opts)\n )\n );\n }\n }\n\n // Thực thi callback onSuccess của người dùng\n if (userOnSuccess) {\n await userOnSuccess(data, variables, context, mutationContext);\n }\n },\n\n onError: async (error, variables, context, mutationContext) => {\n const toastId = activeToastIdsRef.current.shift();\n\n // Đóng toast loading nếu có\n if (toastId) {\n toast.dismiss(toastId);\n }\n\n // Hiển thị toast lỗi nếu được bật\n if (isToastEnabled && effectiveError !== false) {\n let errorTitle: ReactNode;\n\n if (typeof effectiveError === \"function\") {\n errorTitle = effectiveError(error, variables);\n } else if (effectiveError === true) {\n errorTitle = extractErrorMessage(error);\n } else {\n errorTitle = effectiveError;\n }\n\n const errorDesc =\n typeof toastDetails.errorDescription === \"function\"\n ? toastDetails.errorDescription(error, variables)\n : toastDetails.errorDescription;\n\n toast.error(errorTitle, errorDesc, {\n ...customToastOptions,\n variant: effectiveVariant,\n });\n }\n\n // Thực thi callback onError của người dùng\n if (userOnError) {\n await userOnError(error, variables, context, mutationContext);\n }\n },\n\n onSettled: async (data, error, variables, context, mutationContext) => {\n if (userOnSettled) {\n await userOnSettled(data, error, variables, context, mutationContext);\n }\n },\n });\n\n return {\n ...mutation,\n isLoading: mutation.isPending,\n };\n}\n"],"mappings":"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","names":["useCallback","useMemo","useState","useQuery","keepPreviousData","defaultSelectData","response","obj","defaultSelectRowCount","total","parsed","resolveUpdater","updater","prev","useTableQuery","options","queryKey","queryFn","selectData","selectRowCount","initialPagination","initialSorting","initialColumnFilters","initialGlobalFilter","autoResetPageIndex","queryOptions","pagination","setPagination","useState","DEFAULT_PAGE_SIZE","sorting","setSorting","columnFilters","setColumnFilters","globalFilter","setGlobalFilter","queryParams","useMemo","params","firstSort","filtersDict","cf","fullQueryKey","query","useQuery","ctx","keepPreviousData","data","rowCount","handlePaginationChange","useCallback","handleSortingChange","handleColumnFiltersChange","handleGlobalFilterChange","filter","resetAll","setPage","page","setPageSize","pageSize","setGlobalFilterValue","isLoading","isRefetching","refetch","handleRefresh","useCallback","useMemo","useState","useInfiniteQuery","jsx","extractRawOptions","response","record","EMPTY_FILTERS","useSelectInfiniteQuery","queryKey","queryFn","initialPageParam","getNextPageParam","getPreviousPageParam","selectOptions","mapOption","debounceMs","initialSearch","initialFilters","initialOptions","skeletonLines","skeletonHeight","renderLoadingMore","endMessage","scrollOptions","queryOptions","search","setSearch","useState","filters","setFilters","fullQueryKey","useMemo","resolvedGetNextPageParam","useCallback","lastPage","allPages","lastPageParam","allPageParams","total","acc","p","query","useInfiniteQuery","context","options","pages","allItems","seen","opt","page","pageOptions","raw","item","index","sentinelRef","useInfiniteScroll","handleSearch","newSearch","newFilters","reset","listFooter","Skeleton","useRef","useMutation","useQueryClient","extractErrorMessage","error","fallback","errObj","resp","status","data","first","firstRecord","normalizeInvalidateTargets","targets","baseOptions","item","useMutationApp","options","queryClient","useQueryClient","activeToastIdsRef","useRef","mutationFn","invalidateQueries","invalidateOptions","toastConfig","successMessage","errorMessage","loadingMessage","userOnMutate","userOnSuccess","userOnError","userOnSettled","mutationOptions","isToastEnabled","toastDetails","effectiveLoading","effectiveSuccess","effectiveError","effectiveVariant","customToastOptions","mutation","useMutation","variables","mutationContext","toastId","loadingTitle","loadingDesc","toast","err","id","context","successTitle","successDesc","resolvedTargets","normalized","filters","opts","errorTitle","errorDesc"]}
|