@soulbatical/tetra-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/components/AutoCard.d.ts +41 -0
  2. package/dist/components/AutoCard.d.ts.map +1 -0
  3. package/dist/components/AutoCard.js +73 -0
  4. package/dist/components/AutoCard.js.map +1 -0
  5. package/dist/components/CookieConsent.d.ts +15 -0
  6. package/dist/components/CookieConsent.d.ts.map +1 -0
  7. package/dist/components/CookieConsent.js +27 -0
  8. package/dist/components/CookieConsent.js.map +1 -0
  9. package/dist/components/GenericListLayout.d.ts +48 -0
  10. package/dist/components/GenericListLayout.d.ts.map +1 -0
  11. package/dist/components/GenericListLayout.js +18 -0
  12. package/dist/components/GenericListLayout.js.map +1 -0
  13. package/dist/components/ListGenerator.d.ts +41 -0
  14. package/dist/components/ListGenerator.d.ts.map +1 -0
  15. package/dist/components/ListGenerator.js +40 -0
  16. package/dist/components/ListGenerator.js.map +1 -0
  17. package/dist/components/LoadingStates.d.ts +38 -0
  18. package/dist/components/LoadingStates.d.ts.map +1 -0
  19. package/dist/components/LoadingStates.js +51 -0
  20. package/dist/components/LoadingStates.js.map +1 -0
  21. package/dist/components/OrgSwitcher.d.ts +15 -0
  22. package/dist/components/OrgSwitcher.d.ts.map +1 -0
  23. package/dist/components/OrgSwitcher.js +36 -0
  24. package/dist/components/OrgSwitcher.js.map +1 -0
  25. package/dist/components/PageHeader.d.ts +16 -0
  26. package/dist/components/PageHeader.d.ts.map +1 -0
  27. package/dist/components/PageHeader.js +10 -0
  28. package/dist/components/PageHeader.js.map +1 -0
  29. package/dist/components/ProtectedRoute.d.ts +9 -0
  30. package/dist/components/ProtectedRoute.d.ts.map +1 -0
  31. package/dist/components/ProtectedRoute.js +30 -0
  32. package/dist/components/ProtectedRoute.js.map +1 -0
  33. package/dist/components/StatusBadge.d.ts +15 -0
  34. package/dist/components/StatusBadge.d.ts.map +1 -0
  35. package/dist/components/StatusBadge.js +79 -0
  36. package/dist/components/StatusBadge.js.map +1 -0
  37. package/dist/components/ThemeToggle.d.ts +14 -0
  38. package/dist/components/ThemeToggle.d.ts.map +1 -0
  39. package/dist/components/ThemeToggle.js +20 -0
  40. package/dist/components/ThemeToggle.js.map +1 -0
  41. package/dist/hooks/use-mobile.d.ts +2 -0
  42. package/dist/hooks/use-mobile.d.ts.map +1 -0
  43. package/dist/hooks/use-mobile.js +17 -0
  44. package/dist/hooks/use-mobile.js.map +1 -0
  45. package/dist/hooks/useAuth.d.ts +34 -0
  46. package/dist/hooks/useAuth.d.ts.map +1 -0
  47. package/dist/hooks/useAuth.js +156 -0
  48. package/dist/hooks/useAuth.js.map +1 -0
  49. package/dist/hooks/useCacheInvalidation.d.ts +25 -0
  50. package/dist/hooks/useCacheInvalidation.d.ts.map +1 -0
  51. package/dist/hooks/useCacheInvalidation.js +57 -0
  52. package/dist/hooks/useCacheInvalidation.js.map +1 -0
  53. package/dist/hooks/useDebounce.d.ts +2 -0
  54. package/dist/hooks/useDebounce.d.ts.map +1 -0
  55. package/dist/hooks/useDebounce.js +15 -0
  56. package/dist/hooks/useDebounce.js.map +1 -0
  57. package/dist/hooks/useDialog.d.ts +26 -0
  58. package/dist/hooks/useDialog.d.ts.map +1 -0
  59. package/dist/hooks/useDialog.js +37 -0
  60. package/dist/hooks/useDialog.js.map +1 -0
  61. package/dist/hooks/useFilterConfigs.d.ts +81 -0
  62. package/dist/hooks/useFilterConfigs.d.ts.map +1 -0
  63. package/dist/hooks/useFilterConfigs.js +68 -0
  64. package/dist/hooks/useFilterConfigs.js.map +1 -0
  65. package/dist/hooks/useFormSubmit.d.ts +79 -0
  66. package/dist/hooks/useFormSubmit.d.ts.map +1 -0
  67. package/dist/hooks/useFormSubmit.js +57 -0
  68. package/dist/hooks/useFormSubmit.js.map +1 -0
  69. package/dist/hooks/useGenericList.d.ts +62 -0
  70. package/dist/hooks/useGenericList.d.ts.map +1 -0
  71. package/dist/hooks/useGenericList.js +75 -0
  72. package/dist/hooks/useGenericList.js.map +1 -0
  73. package/dist/hooks/useGenericListWithConfig.d.ts +65 -0
  74. package/dist/hooks/useGenericListWithConfig.d.ts.map +1 -0
  75. package/dist/hooks/useGenericListWithConfig.js +98 -0
  76. package/dist/hooks/useGenericListWithConfig.js.map +1 -0
  77. package/dist/hooks/useInfiniteScroll.d.ts +11 -0
  78. package/dist/hooks/useInfiniteScroll.d.ts.map +1 -0
  79. package/dist/hooks/useInfiniteScroll.js +63 -0
  80. package/dist/hooks/useInfiniteScroll.js.map +1 -0
  81. package/dist/hooks/useOrganizations.d.ts +27 -0
  82. package/dist/hooks/useOrganizations.d.ts.map +1 -0
  83. package/dist/hooks/useOrganizations.js +56 -0
  84. package/dist/hooks/useOrganizations.js.map +1 -0
  85. package/dist/hooks/usePageContext.d.ts +29 -0
  86. package/dist/hooks/usePageContext.d.ts.map +1 -0
  87. package/dist/hooks/usePageContext.js +32 -0
  88. package/dist/hooks/usePageContext.js.map +1 -0
  89. package/dist/hooks/usePersistedQueryState.d.ts +35 -0
  90. package/dist/hooks/usePersistedQueryState.d.ts.map +1 -0
  91. package/dist/hooks/usePersistedQueryState.js +187 -0
  92. package/dist/hooks/usePersistedQueryState.js.map +1 -0
  93. package/dist/hooks/useQueryState.d.ts +40 -0
  94. package/dist/hooks/useQueryState.d.ts.map +1 -0
  95. package/dist/hooks/useQueryState.js +246 -0
  96. package/dist/hooks/useQueryState.js.map +1 -0
  97. package/dist/index.d.ts +30 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +43 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/lib/utils.d.ts +3 -0
  102. package/dist/lib/utils.d.ts.map +1 -0
  103. package/dist/lib/utils.js +6 -0
  104. package/dist/lib/utils.js.map +1 -0
  105. package/dist/providers/QueryProvider.d.ts +9 -0
  106. package/dist/providers/QueryProvider.d.ts.map +1 -0
  107. package/dist/providers/QueryProvider.js +20 -0
  108. package/dist/providers/QueryProvider.js.map +1 -0
  109. package/dist/providers/ThemeProvider.d.ts +15 -0
  110. package/dist/providers/ThemeProvider.d.ts.map +1 -0
  111. package/dist/providers/ThemeProvider.js +13 -0
  112. package/dist/providers/ThemeProvider.js.map +1 -0
  113. package/dist/services/apiClient.d.ts +20 -0
  114. package/dist/services/apiClient.d.ts.map +1 -0
  115. package/dist/services/apiClient.js +85 -0
  116. package/dist/services/apiClient.js.map +1 -0
  117. package/dist/types/index.d.ts +119 -0
  118. package/dist/types/index.d.ts.map +1 -0
  119. package/dist/types/index.js +5 -0
  120. package/dist/types/index.js.map +1 -0
  121. package/package.json +68 -0
  122. package/src/styles/dark-mode.css +135 -0
  123. package/src/styles/theme.css +105 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Filter configuration from backend
3
+ */
4
+ export interface FilterConfig {
5
+ name: string;
6
+ type: 'column' | 'enum' | 'multiselect' | 'daterange' | 'search' | 'time' | 'related' | 'nullable' | 'boolean';
7
+ column?: string;
8
+ ui: {
9
+ label: string;
10
+ placeholder?: string;
11
+ description?: string;
12
+ icon?: string;
13
+ options?: Array<{
14
+ value: string;
15
+ label: string;
16
+ icon?: string;
17
+ }>;
18
+ order?: number;
19
+ defaultVisible?: boolean;
20
+ };
21
+ }
22
+ /**
23
+ * Sort field configuration from backend (Config-Driven v2.0)
24
+ */
25
+ export interface SortFieldConfig {
26
+ field: string;
27
+ alias?: string;
28
+ label: string;
29
+ defaultDirection?: 'asc' | 'desc';
30
+ ui?: {
31
+ ascLabel?: string;
32
+ descLabel?: string;
33
+ };
34
+ }
35
+ /**
36
+ * Pagination configuration from backend (Config-Driven v2.0)
37
+ */
38
+ export interface PaginationConfig {
39
+ defaultLimit?: number;
40
+ maxLimit?: number;
41
+ minLimit?: number;
42
+ }
43
+ export interface FilterConfigsResponse {
44
+ filters: FilterConfig[];
45
+ sortOptions?: SortFieldConfig[];
46
+ pagination?: PaginationConfig;
47
+ display?: any;
48
+ meta: {
49
+ totalFilters: number;
50
+ feature: string;
51
+ autoRendered: boolean;
52
+ };
53
+ }
54
+ /**
55
+ * Helper: Generate sort options for GenericListLayout from backend config
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const { data } = useFilterConfigs('orders');
60
+ * const sortOptions = generateSortOptions(data?.sortOptions);
61
+ * // Returns: [
62
+ * // { value: 'order_date-desc', label: 'Date (Newest first)' },
63
+ * // { value: 'order_date-asc', label: 'Date (Oldest first)' },
64
+ * // ...
65
+ * // ]
66
+ * ```
67
+ */
68
+ export declare function generateSortOptions(sortFields?: SortFieldConfig[]): Array<{
69
+ value: string;
70
+ label: string;
71
+ }>;
72
+ /**
73
+ * Hook to fetch filter configurations from backend
74
+ *
75
+ * @param feature - Feature name (e.g., 'orders', 'products')
76
+ * @returns Query result with filter configs
77
+ */
78
+ export declare const useFilterConfigs: (feature: string, options?: {
79
+ apiPrefix?: string;
80
+ }) => import("@tanstack/react-query").UseQueryResult<FilterConfigsResponse, Error>;
81
+ //# sourceMappingURL=useFilterConfigs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFilterConfigs.d.ts","sourceRoot":"","sources":["../../src/hooks/useFilterConfigs.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE;QACF,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACjE,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,OAAO,CAAC;KAC1B,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAClC,EAAE,CAAC,EAAE;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,IAAI,EAAE;QACJ,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA6B3G;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,EAAE,UAAU;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,iFA4BjF,CAAC"}
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import { apiClient } from '../services/apiClient.js';
4
+ /**
5
+ * Helper: Generate sort options for GenericListLayout from backend config
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const { data } = useFilterConfigs('orders');
10
+ * const sortOptions = generateSortOptions(data?.sortOptions);
11
+ * // Returns: [
12
+ * // { value: 'order_date-desc', label: 'Date (Newest first)' },
13
+ * // { value: 'order_date-asc', label: 'Date (Oldest first)' },
14
+ * // ...
15
+ * // ]
16
+ * ```
17
+ */
18
+ export function generateSortOptions(sortFields) {
19
+ if (!sortFields || sortFields.length === 0)
20
+ return [];
21
+ const options = [];
22
+ for (const field of sortFields) {
23
+ // Use alias if available (for SQL RPC), otherwise use field
24
+ const sortKey = field.alias || field.field;
25
+ // Determine default direction
26
+ const defaultDir = field.defaultDirection || 'desc';
27
+ const otherDir = defaultDir === 'desc' ? 'asc' : 'desc';
28
+ // Add default direction first
29
+ const defaultLabel = field.ui?.[`${defaultDir}Label`] || `${field.label} (${defaultDir})`;
30
+ options.push({
31
+ value: `${sortKey}-${defaultDir}`,
32
+ label: defaultLabel
33
+ });
34
+ // Add other direction
35
+ const otherLabel = field.ui?.[`${otherDir}Label`] || `${field.label} (${otherDir})`;
36
+ options.push({
37
+ value: `${sortKey}-${otherDir}`,
38
+ label: otherLabel
39
+ });
40
+ }
41
+ return options;
42
+ }
43
+ /**
44
+ * Hook to fetch filter configurations from backend
45
+ *
46
+ * @param feature - Feature name (e.g., 'orders', 'products')
47
+ * @returns Query result with filter configs
48
+ */
49
+ export const useFilterConfigs = (feature, options) => {
50
+ const apiPrefix = options?.apiPrefix || 'admin';
51
+ return useQuery({
52
+ queryKey: ['filter-configs', feature, apiPrefix],
53
+ queryFn: async () => {
54
+ // apiClient.get returns parsed JSON directly and throws on error
55
+ const result = await apiClient.get(`/api/${apiPrefix}/${feature}/filters`);
56
+ if (!result.success) {
57
+ throw new Error(`Invalid response from /api/${apiPrefix}/${feature}/filters: missing 'success' property.`);
58
+ }
59
+ if (!result.data) {
60
+ throw new Error(`Invalid response from /api/${apiPrefix}/${feature}/filters: missing 'data' property.`);
61
+ }
62
+ return result.data;
63
+ },
64
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes (configs rarely change)
65
+ retry: 1, // Retry once on failure
66
+ });
67
+ };
68
+ //# sourceMappingURL=useFilterConfigs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFilterConfigs.js","sourceRoot":"","sources":["../../src/hooks/useFilterConfigs.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAuDrD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAA8B;IAChE,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtD,MAAM,OAAO,GAA4C,EAAE,CAAC;IAE5D,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,4DAA4D;QAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC;QAE3C,8BAA8B;QAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,gBAAgB,IAAI,MAAM,CAAC;QACpD,MAAM,QAAQ,GAAG,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QAExD,8BAA8B;QAC9B,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,UAAU,OAAgC,CAAW,IAAI,GAAG,KAAK,CAAC,KAAK,KAAK,UAAU,GAAG,CAAC;QAC7H,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,GAAG,OAAO,IAAI,UAAU,EAAE;YACjC,KAAK,EAAE,YAAY;SACpB,CAAC,CAAC;QAEH,sBAAsB;QACtB,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,QAAQ,OAAgC,CAAW,IAAI,GAAG,KAAK,CAAC,KAAK,KAAK,QAAQ,GAAG,CAAC;QACvH,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,GAAG,OAAO,IAAI,QAAQ,EAAE;YAC/B,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAE,OAAgC,EAAE,EAAE;IACpF,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,OAAO,CAAC;IAEhD,OAAO,QAAQ,CAAwB;QACrC,QAAQ,EAAE,CAAC,gBAAgB,EAAE,OAAO,EAAE,SAAS,CAAC;QAChD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,iEAAiE;YACjE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAChC,QAAQ,SAAS,IAAI,OAAO,UAAU,CACvC,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CACb,8BAA8B,SAAS,IAAI,OAAO,uCAAuC,CAC1F,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CACb,8BAA8B,SAAS,IAAI,OAAO,oCAAoC,CACvF,CAAC;YACJ,CAAC;YAED,OAAO,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC;QACD,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,8CAA8C;QACxE,KAAK,EAAE,CAAC,EAAE,wBAAwB;KACnC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Hook for handling form submissions with standard patterns
3
+ *
4
+ * Handles loading state, error handling, success messages, cache invalidation, and navigation.
5
+ * Toast and navigation are provided via callbacks -- the consumer project supplies these.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { handleSubmit, isSubmitting } = useFormSubmit({
10
+ * onSubmit: async (data) => createItem(data),
11
+ * successMessage: 'Item created successfully!',
12
+ * invalidateQueries: ['items', 'item-counts'],
13
+ * onSuccess: () => router.push('/items'),
14
+ * toast: { success: toast.success, error: toast.error },
15
+ * });
16
+ *
17
+ * return (
18
+ * <form onSubmit={(e) => handleSubmit(e, formData)}>
19
+ * <Button type="submit" disabled={isSubmitting}>
20
+ * {isSubmitting ? 'Creating...' : 'Create'}
21
+ * </Button>
22
+ * </form>
23
+ * );
24
+ * ```
25
+ */
26
+ interface UseFormSubmitOptions<TData = any, TResult = any> {
27
+ /**
28
+ * The async function to execute on form submit.
29
+ * Receives the form data and should return the result.
30
+ */
31
+ onSubmit: (data: TData) => Promise<TResult>;
32
+ /**
33
+ * Success message to show via toast (optional).
34
+ * If not provided, no success toast is shown.
35
+ */
36
+ successMessage?: string;
37
+ /**
38
+ * Error message to show via toast (optional).
39
+ * If not provided, uses error.message from caught error.
40
+ */
41
+ errorMessage?: string;
42
+ /**
43
+ * Array of query keys to invalidate after successful submission.
44
+ */
45
+ invalidateQueries?: string[];
46
+ /**
47
+ * Optional callback after successful submission.
48
+ * Receives the result from onSubmit. Use for navigation, closing dialogs, etc.
49
+ */
50
+ onSuccess?: (result: TResult) => void;
51
+ /**
52
+ * Optional callback after failed submission.
53
+ * Receives the error.
54
+ */
55
+ onError?: (error: Error) => void;
56
+ /**
57
+ * Optional callback that always runs after submission (success or error).
58
+ */
59
+ onSettled?: () => void;
60
+ /**
61
+ * Whether to prevent default form submission behavior.
62
+ * Default: true
63
+ */
64
+ preventDefault?: boolean;
65
+ /**
66
+ * Toast functions for displaying messages.
67
+ * Consumer project provides these (e.g., from sonner, react-hot-toast, etc.)
68
+ */
69
+ toast?: {
70
+ success?: (message: string) => void;
71
+ error?: (message: string) => void;
72
+ };
73
+ }
74
+ export declare const useFormSubmit: <TData = any, TResult = any>(options: UseFormSubmitOptions<TData, TResult>) => {
75
+ handleSubmit: (e: React.FormEvent, data?: TData) => Promise<TResult | undefined>;
76
+ isSubmitting: boolean;
77
+ };
78
+ export {};
79
+ //# sourceMappingURL=useFormSubmit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormSubmit.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormSubmit.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,UAAU,oBAAoB,CAAC,KAAK,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG;IACvD;;;OAGG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5C;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QACpC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KACnC,CAAC;CACH;AAED,eAAO,MAAM,aAAa,GAAI,KAAK,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,EACtD,SAAS,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC;sBAMxC,KAAK,CAAC,SAAS,SACX,KAAK,KACX,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;;CA4DhC,CAAC"}
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { useState, useCallback } from 'react';
3
+ import { useCacheInvalidation } from './useCacheInvalidation.js';
4
+ export const useFormSubmit = (options) => {
5
+ const [isSubmitting, setIsSubmitting] = useState(false);
6
+ const { invalidate } = useCacheInvalidation();
7
+ const handleSubmit = useCallback(async (e, data) => {
8
+ // Prevent default form submission if enabled
9
+ if (options.preventDefault !== false) {
10
+ e.preventDefault();
11
+ }
12
+ setIsSubmitting(true);
13
+ try {
14
+ // Execute the submit function
15
+ const result = await options.onSubmit(data);
16
+ // Show success message if provided
17
+ if (options.successMessage && options.toast?.success) {
18
+ options.toast.success(options.successMessage);
19
+ }
20
+ // Invalidate queries if provided
21
+ if (options.invalidateQueries && options.invalidateQueries.length > 0) {
22
+ invalidate(...options.invalidateQueries);
23
+ }
24
+ // Call onSuccess callback if provided
25
+ if (options.onSuccess) {
26
+ options.onSuccess(result);
27
+ }
28
+ return result;
29
+ }
30
+ catch (error) {
31
+ console.error('Form submission error:', error);
32
+ // Show error message
33
+ const errorMsg = options.errorMessage ||
34
+ (error instanceof Error ? error.message : 'An error occurred during submission');
35
+ if (options.toast?.error) {
36
+ options.toast.error(errorMsg);
37
+ }
38
+ // Call onError callback if provided
39
+ if (options.onError && error instanceof Error) {
40
+ options.onError(error);
41
+ }
42
+ return undefined;
43
+ }
44
+ finally {
45
+ setIsSubmitting(false);
46
+ // Call onSettled callback if provided
47
+ if (options.onSettled) {
48
+ options.onSettled();
49
+ }
50
+ }
51
+ }, [options, invalidate]);
52
+ return {
53
+ handleSubmit,
54
+ isSubmitting,
55
+ };
56
+ };
57
+ //# sourceMappingURL=useFormSubmit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormSubmit.js","sourceRoot":"","sources":["../../src/hooks/useFormSubmit.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAqFjE,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,OAA6C,EAC7C,EAAE;IACF,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,EAAE,UAAU,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAE9C,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,EACpC,CAAkB,EAClB,IAAY,EACkB,EAAE;QAChC,6CAA6C;QAC7C,IAAI,OAAO,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;YACrC,CAAC,CAAC,cAAc,EAAE,CAAC;QACrB,CAAC;QAED,eAAe,CAAC,IAAI,CAAC,CAAC;QAEtB,IAAI,CAAC;YACH,8BAA8B;YAC9B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,IAAa,CAAC,CAAC;YAErD,mCAAmC;YACnC,IAAI,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;gBACrD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YAChD,CAAC;YAED,iCAAiC;YACjC,IAAI,OAAO,CAAC,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtE,UAAU,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;YAC3C,CAAC;YAED,sCAAsC;YACtC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;YAE/C,qBAAqB;YACrB,MAAM,QAAQ,GACZ,OAAO,CAAC,YAAY;gBACpB,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC;YAEnF,IAAI,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YAED,oCAAoC;YACpC,IAAI,OAAO,CAAC,OAAO,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC9C,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,eAAe,CAAC,KAAK,CAAC,CAAC;YAEvB,sCAAsC;YACtC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IAE1B,OAAO;QACL,YAAY;QACZ,YAAY;KACb,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * useGenericList - Universal hook for list pages with filtering, infinite scroll, and counts
3
+ *
4
+ * Combines:
5
+ * - Filter state management with persistence (usePersistedQueryState)
6
+ * - Data fetching (custom list hook)
7
+ * - Counts fetching (custom counts hook)
8
+ * - Infinite scroll setup (useInfiniteScroll)
9
+ * - Data flattening
10
+ *
11
+ * Filter persistence per feature:
12
+ * - Filters are saved to sessionStorage per entity
13
+ * - Restored when navigating back (breadcrumbs, back button, sidebar)
14
+ * - Each feature remembers its own filters independently
15
+ *
16
+ * @example
17
+ * const { items, counts, filters, setFilter, isLoading, sentinelRef } = useGenericList({
18
+ * entity: 'orders',
19
+ * dataKey: 'orders',
20
+ * defaultFilters: { status: 'all', search: '', sortBy: 'date-desc' },
21
+ * useListHook: useOrdersList,
22
+ * useCountsHook: useOrdersCounts
23
+ * });
24
+ */
25
+ export declare function useGenericList<TItem, TFilters extends Record<string, any>, TCounts>({ entity, dataKey, defaultFilters, useListHook, useCountsHook, enabled, }: {
26
+ entity: string;
27
+ dataKey: string;
28
+ defaultFilters: TFilters;
29
+ useListHook: (filters: TFilters, options?: {
30
+ enabled?: boolean;
31
+ }) => {
32
+ data: any;
33
+ isLoading: boolean;
34
+ fetchNextPage: () => void;
35
+ hasNextPage?: boolean;
36
+ hasMoreItems?: boolean;
37
+ loadMore?: () => void;
38
+ isFetchingNextPage: boolean;
39
+ refetch?: () => void;
40
+ };
41
+ useCountsHook: (filters: Partial<TFilters>, options?: {
42
+ enabled?: boolean;
43
+ }) => {
44
+ data: TCounts | undefined;
45
+ isLoading: boolean;
46
+ };
47
+ enabled?: boolean;
48
+ }): {
49
+ items: TItem[];
50
+ counts: TCounts | undefined;
51
+ isLoading: boolean;
52
+ isLoadingCounts: boolean;
53
+ isFetchingNextPage: boolean;
54
+ filters: TFilters;
55
+ setFilter: <K extends keyof TFilters>(key: K, value: TFilters[K]) => void;
56
+ resetFilters: () => void;
57
+ sentinelRef: (node: HTMLDivElement | null) => void;
58
+ hasMore: boolean;
59
+ fetchNextPage: () => void;
60
+ refetch: (() => void) | undefined;
61
+ };
62
+ //# sourceMappingURL=useGenericList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGenericList.d.ts","sourceRoot":"","sources":["../../src/hooks/useGenericList.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,EACnF,MAAM,EACN,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,OAAc,GACf,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,QAAQ,CAAC;IACzB,WAAW,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK;QACnE,IAAI,EAAE,GAAG,CAAC;QACV,SAAS,EAAE,OAAO,CAAC;QACnB,aAAa,EAAE,MAAM,IAAI,CAAC;QAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;QACtB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC;IACF,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK;QAC9E,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;QAC1B,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;;;;;;;;;;;yBAZwB,IAAI;oBAKT,IAAI;EA6EvB"}
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { usePersistedQueryState } from './usePersistedQueryState.js';
3
+ import { useInfiniteScroll } from './useInfiniteScroll.js';
4
+ /**
5
+ * useGenericList - Universal hook for list pages with filtering, infinite scroll, and counts
6
+ *
7
+ * Combines:
8
+ * - Filter state management with persistence (usePersistedQueryState)
9
+ * - Data fetching (custom list hook)
10
+ * - Counts fetching (custom counts hook)
11
+ * - Infinite scroll setup (useInfiniteScroll)
12
+ * - Data flattening
13
+ *
14
+ * Filter persistence per feature:
15
+ * - Filters are saved to sessionStorage per entity
16
+ * - Restored when navigating back (breadcrumbs, back button, sidebar)
17
+ * - Each feature remembers its own filters independently
18
+ *
19
+ * @example
20
+ * const { items, counts, filters, setFilter, isLoading, sentinelRef } = useGenericList({
21
+ * entity: 'orders',
22
+ * dataKey: 'orders',
23
+ * defaultFilters: { status: 'all', search: '', sortBy: 'date-desc' },
24
+ * useListHook: useOrdersList,
25
+ * useCountsHook: useOrdersCounts
26
+ * });
27
+ */
28
+ export function useGenericList({ entity, dataKey, defaultFilters, useListHook, useCountsHook, enabled = true, }) {
29
+ // 1. Filter state management with persistence
30
+ const { filters, setFilter, resetFilters } = usePersistedQueryState(entity, defaultFilters);
31
+ // 2. Data fetching
32
+ const { data, isLoading, fetchNextPage, hasNextPage, hasMoreItems, loadMore, isFetchingNextPage, refetch } = useListHook(filters, { enabled });
33
+ // 3. Counts fetching (parallel request)
34
+ const { data: counts, isLoading: isLoadingCounts } = useCountsHook(filters, { enabled });
35
+ // 4. Data flattening with deduplication
36
+ // Defensive: filter out pages with missing/invalid data
37
+ const flatItems = (data?.pages?.flatMap((page) => page[dataKey] || []) || []);
38
+ // Deduplicate by ID (prevents React duplicate key warnings)
39
+ // Filter out undefined/null items before accessing properties
40
+ const items = Array.from(new Map(flatItems
41
+ .filter((item) => item != null) // Remove undefined/null items
42
+ .map((item) => {
43
+ // Support both new (id) and legacy (entityid) formats
44
+ const itemId = item.id || item[`${entity.replace(/s$/, '')}id`] || item[`${entity}id`];
45
+ return [itemId, item];
46
+ })).values());
47
+ // 5. Infinite scroll setup
48
+ const { sentinelRef } = useInfiniteScroll({
49
+ onLoadMore: loadMore || fetchNextPage,
50
+ hasMore: hasMoreItems ?? hasNextPage ?? false,
51
+ threshold: 300,
52
+ enabled: true
53
+ });
54
+ return {
55
+ // Data
56
+ items,
57
+ counts,
58
+ // Loading states
59
+ isLoading,
60
+ isLoadingCounts,
61
+ isFetchingNextPage,
62
+ // Filters
63
+ filters,
64
+ setFilter,
65
+ resetFilters,
66
+ // Infinite scroll
67
+ sentinelRef,
68
+ hasMore: hasMoreItems ?? hasNextPage ?? false,
69
+ // Pagination
70
+ fetchNextPage: loadMore || fetchNextPage,
71
+ // Refetch
72
+ refetch,
73
+ };
74
+ }
75
+ //# sourceMappingURL=useGenericList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGenericList.js","sourceRoot":"","sources":["../../src/hooks/useGenericList.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,cAAc,CAAuD,EACnF,MAAM,EACN,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,OAAO,GAAG,IAAI,GAoBf;IACC,8CAA8C;IAC9C,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,sBAAsB,CAAW,MAAM,EAAE,cAAc,CAAC,CAAC;IAEtG,mBAAmB;IACnB,MAAM,EACJ,IAAI,EACJ,SAAS,EACT,aAAa,EACb,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,kBAAkB,EAClB,OAAO,EACR,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAEtC,wCAAwC;IACxC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAEzF,wCAAwC;IACxC,wDAAwD;IACxD,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAY,CAAC;IAE9F,4DAA4D;IAC5D,8DAA8D;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CACtB,IAAI,GAAG,CACL,SAAS;SACN,MAAM,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,8BAA8B;SAClE,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE;QACjB,sDAAsD;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC;QACvF,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC,CAAC,CACL,CAAC,MAAM,EAAE,CACA,CAAC;IAEb,2BAA2B;IAC3B,MAAM,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC;QACxC,UAAU,EAAE,QAAQ,IAAI,aAAa;QACrC,OAAO,EAAE,YAAY,IAAI,WAAW,IAAI,KAAK;QAC7C,SAAS,EAAE,GAAG;QACd,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;IAEH,OAAO;QACL,OAAO;QACP,KAAK;QACL,MAAM;QAEN,iBAAiB;QACjB,SAAS;QACT,eAAe;QACf,kBAAkB;QAElB,UAAU;QACV,OAAO;QACP,SAAS;QACT,YAAY;QAEZ,kBAAkB;QAClB,WAAW;QACX,OAAO,EAAE,YAAY,IAAI,WAAW,IAAI,KAAK;QAE7C,aAAa;QACb,aAAa,EAAE,QAAQ,IAAI,aAAa;QAExC,UAAU;QACV,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * useGenericListWithConfig - Config-Driven List Hook
3
+ *
4
+ * Zero double configuration - defaults auto-generated from backend config!
5
+ *
6
+ * BEFORE (useGenericList):
7
+ * - Manual defaultFilters in every page
8
+ * - Double configuration (backend config + frontend defaults)
9
+ * - Easy to forget filters
10
+ *
11
+ * AFTER (useGenericListWithConfig):
12
+ * - Defaults auto-generated from backend
13
+ * - Single source of truth
14
+ * - All filters automatically included
15
+ *
16
+ * @example
17
+ * const { items, counts, filters, setFilter } = useGenericListWithConfig({
18
+ * featureName: 'orders',
19
+ * entity: 'orders',
20
+ * dataKey: 'orders',
21
+ * useListHook: useOrdersList,
22
+ * useCountsHook: useOrdersCounts,
23
+ * });
24
+ */
25
+ export declare function useGenericListWithConfig<TItem, TFilters extends Record<string, any>, TCounts>({ featureName, entity, dataKey, useListHook, useCountsHook, additionalDefaults, apiPrefix, }: {
26
+ featureName: string;
27
+ entity: string;
28
+ dataKey: string;
29
+ useListHook: (filters: TFilters, options?: {
30
+ enabled?: boolean;
31
+ }) => {
32
+ data: any;
33
+ isLoading: boolean;
34
+ fetchNextPage: () => void;
35
+ hasNextPage?: boolean;
36
+ hasMoreItems?: boolean;
37
+ loadMore?: () => void;
38
+ isFetchingNextPage: boolean;
39
+ refetch?: () => void;
40
+ };
41
+ useCountsHook: (filters: Partial<TFilters>, options?: {
42
+ enabled?: boolean;
43
+ }) => {
44
+ data: TCounts | undefined;
45
+ isLoading: boolean;
46
+ };
47
+ additionalDefaults?: Partial<TFilters>;
48
+ apiPrefix?: 'admin' | 'superadmin';
49
+ }): {
50
+ filterConfigs: import("./useFilterConfigs.js").FilterConfigsResponse | undefined;
51
+ isLoadingConfigs: boolean;
52
+ items: TItem[];
53
+ counts: TCounts | undefined;
54
+ isLoading: boolean;
55
+ isLoadingCounts: boolean;
56
+ isFetchingNextPage: boolean;
57
+ filters: TFilters;
58
+ setFilter: <K extends keyof TFilters>(key: K, value: TFilters[K]) => void;
59
+ resetFilters: () => void;
60
+ sentinelRef: (node: HTMLDivElement | null) => void;
61
+ hasMore: boolean;
62
+ fetchNextPage: () => void;
63
+ refetch: (() => void) | undefined;
64
+ };
65
+ //# sourceMappingURL=useGenericListWithConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGenericListWithConfig.d.ts","sourceRoot":"","sources":["../../src/hooks/useGenericListWithConfig.ts"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,EAC7F,WAAW,EACX,MAAM,EACN,OAAO,EACP,WAAW,EACX,aAAa,EACb,kBAAuB,EACvB,SAAmB,GACpB,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK;QACnE,IAAI,EAAE,GAAG,CAAC;QACV,SAAS,EAAE,OAAO,CAAC;QACnB,aAAa,EAAE,MAAM,IAAI,CAAC;QAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;QACtB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC;IACF,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK;QAC9E,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;QAC1B,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,kBAAkB,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,OAAO,GAAG,YAAY,CAAC;CACpC;;;;;;;;;;;;;;;EAiCA"}
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ import { useMemo } from 'react';
3
+ import { useGenericList } from './useGenericList.js';
4
+ import { useFilterConfigs } from './useFilterConfigs.js';
5
+ /**
6
+ * Generate default filter values from backend filter configs.
7
+ * Inlined utility (no external dependency needed).
8
+ */
9
+ function generateDefaultFilters(filters) {
10
+ if (!filters || filters.length === 0)
11
+ return {};
12
+ const defaults = {};
13
+ for (const filter of filters) {
14
+ switch (filter.type) {
15
+ case 'search':
16
+ defaults[filter.name] = '';
17
+ break;
18
+ case 'enum':
19
+ case 'column':
20
+ case 'nullable':
21
+ defaults[filter.name] = 'all';
22
+ break;
23
+ case 'multiselect':
24
+ defaults[filter.name] = [];
25
+ break;
26
+ case 'boolean':
27
+ defaults[filter.name] = '';
28
+ break;
29
+ case 'daterange':
30
+ defaults[filter.name] = '';
31
+ break;
32
+ case 'time':
33
+ defaults[filter.name] = '';
34
+ break;
35
+ case 'related':
36
+ defaults[filter.name] = '';
37
+ break;
38
+ default:
39
+ defaults[filter.name] = '';
40
+ }
41
+ }
42
+ return defaults;
43
+ }
44
+ /**
45
+ * useGenericListWithConfig - Config-Driven List Hook
46
+ *
47
+ * Zero double configuration - defaults auto-generated from backend config!
48
+ *
49
+ * BEFORE (useGenericList):
50
+ * - Manual defaultFilters in every page
51
+ * - Double configuration (backend config + frontend defaults)
52
+ * - Easy to forget filters
53
+ *
54
+ * AFTER (useGenericListWithConfig):
55
+ * - Defaults auto-generated from backend
56
+ * - Single source of truth
57
+ * - All filters automatically included
58
+ *
59
+ * @example
60
+ * const { items, counts, filters, setFilter } = useGenericListWithConfig({
61
+ * featureName: 'orders',
62
+ * entity: 'orders',
63
+ * dataKey: 'orders',
64
+ * useListHook: useOrdersList,
65
+ * useCountsHook: useOrdersCounts,
66
+ * });
67
+ */
68
+ export function useGenericListWithConfig({ featureName, entity, dataKey, useListHook, useCountsHook, additionalDefaults = {}, apiPrefix = 'admin', }) {
69
+ // Fetch filter configs from backend
70
+ const { data: filterConfigsData, isLoading: isLoadingConfigs } = useFilterConfigs(featureName, { apiPrefix });
71
+ // Auto-generate defaultFilters from config
72
+ const defaultFilters = useMemo(() => {
73
+ const configDefaults = generateDefaultFilters(filterConfigsData?.filters);
74
+ return {
75
+ ...configDefaults,
76
+ ...additionalDefaults,
77
+ };
78
+ }, [filterConfigsData, additionalDefaults]);
79
+ // Wait for configs to load before fetching data
80
+ // This prevents race condition where first fetch uses empty filters
81
+ const shouldEnableQuery = !isLoadingConfigs && filterConfigsData !== undefined;
82
+ // Use standard useGenericList with auto-generated defaults
83
+ const listResult = useGenericList({
84
+ entity,
85
+ dataKey,
86
+ defaultFilters,
87
+ useListHook,
88
+ useCountsHook,
89
+ enabled: shouldEnableQuery,
90
+ });
91
+ // Return combined result
92
+ return {
93
+ ...listResult,
94
+ filterConfigs: filterConfigsData,
95
+ isLoadingConfigs,
96
+ };
97
+ }
98
+ //# sourceMappingURL=useGenericListWithConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGenericListWithConfig.js","sourceRoot":"","sources":["../../src/hooks/useGenericListWithConfig.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD;;;GAGG;AACH,SAAS,sBAAsB,CAAC,OAAwB;IACtD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEhD,MAAM,QAAQ,GAAwB,EAAE,CAAC;IAEzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,QAAQ;gBACX,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM;YACR,KAAK,MAAM,CAAC;YACZ,KAAK,QAAQ,CAAC;YACd,KAAK,UAAU;gBACb,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;gBAC9B,MAAM;YACR,KAAK,aAAa;gBAChB,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM;YACR,KAAK,SAAS;gBACZ,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM;YACR,KAAK,WAAW;gBACd,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM;YACR,KAAK,MAAM;gBACT,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM;YACR,KAAK,SAAS;gBACZ,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM;YACR;gBACE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,wBAAwB,CAAuD,EAC7F,WAAW,EACX,MAAM,EACN,OAAO,EACP,WAAW,EACX,aAAa,EACb,kBAAkB,GAAG,EAAE,EACvB,SAAS,GAAG,OAAO,GAqBpB;IACC,oCAAoC;IACpC,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAE9G,2CAA2C;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE;QAClC,MAAM,cAAc,GAAG,sBAAsB,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;QAC1E,OAAO;YACL,GAAG,cAAc;YACjB,GAAG,kBAAkB;SACV,CAAC;IAChB,CAAC,EAAE,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAE5C,gDAAgD;IAChD,oEAAoE;IACpE,MAAM,iBAAiB,GAAG,CAAC,gBAAgB,IAAI,iBAAiB,KAAK,SAAS,CAAC;IAE/E,2DAA2D;IAC3D,MAAM,UAAU,GAAG,cAAc,CAA2B;QAC1D,MAAM;QACN,OAAO;QACP,cAAc;QACd,WAAW;QACX,aAAa;QACb,OAAO,EAAE,iBAAiB;KAC3B,CAAC,CAAC;IAEH,yBAAyB;IACzB,OAAO;QACL,GAAG,UAAU;QACb,aAAa,EAAE,iBAAiB;QAChC,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ interface UseInfiniteScrollOptions {
2
+ onLoadMore: () => void;
3
+ hasMore: boolean;
4
+ threshold?: number;
5
+ enabled?: boolean;
6
+ }
7
+ export declare const useInfiniteScroll: ({ onLoadMore, hasMore, threshold, enabled }: UseInfiniteScrollOptions) => {
8
+ sentinelRef: (node: HTMLDivElement | null) => void;
9
+ };
10
+ export {};
11
+ //# sourceMappingURL=useInfiniteScroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInfiniteScroll.d.ts","sourceRoot":"","sources":["../../src/hooks/useInfiniteScroll.ts"],"names":[],"mappings":"AAIA,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,iBAAiB,GAAI,6CAK/B,wBAAwB;wBAsCc,cAAc,GAAG,IAAI;CAoC7D,CAAC"}