@pagamio/frontend-commons-lib 0.8.212 → 0.8.214

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 (105) hide show
  1. package/lib/api/TanstackQueryProvider.d.ts +146 -0
  2. package/lib/api/TanstackQueryProvider.js +140 -0
  3. package/lib/api/index.d.ts +2 -0
  4. package/lib/api/index.js +2 -0
  5. package/lib/api/tanstackQuery.d.ts +249 -0
  6. package/lib/api/tanstackQuery.js +299 -0
  7. package/lib/components/layout/Sidebar.js +50 -16
  8. package/lib/components/ui/Sheet.d.ts +1 -1
  9. package/lib/context/SidebarContext.d.ts +20 -0
  10. package/lib/dashboard-visuals-v2/DashboardWrapperV2.d.ts +81 -0
  11. package/lib/dashboard-visuals-v2/DashboardWrapperV2.js +217 -0
  12. package/lib/dashboard-visuals-v2/cards/CardGrid.d.ts +22 -0
  13. package/lib/dashboard-visuals-v2/cards/CardGrid.js +27 -0
  14. package/lib/dashboard-visuals-v2/cards/InfoCard.d.ts +7 -0
  15. package/lib/dashboard-visuals-v2/cards/InfoCard.js +26 -0
  16. package/lib/dashboard-visuals-v2/cards/MetricCard.d.ts +7 -0
  17. package/lib/dashboard-visuals-v2/cards/MetricCard.js +37 -0
  18. package/lib/dashboard-visuals-v2/cards/StatCard.d.ts +7 -0
  19. package/lib/dashboard-visuals-v2/cards/StatCard.js +92 -0
  20. package/lib/dashboard-visuals-v2/cards/TopItemsCard.d.ts +7 -0
  21. package/lib/dashboard-visuals-v2/cards/TopItemsCard.js +34 -0
  22. package/lib/dashboard-visuals-v2/cards/TransactionListCard.d.ts +7 -0
  23. package/lib/dashboard-visuals-v2/cards/TransactionListCard.js +24 -0
  24. package/lib/dashboard-visuals-v2/cards/index.d.ts +9 -0
  25. package/lib/dashboard-visuals-v2/cards/index.js +9 -0
  26. package/lib/dashboard-visuals-v2/charts/AreaChart.d.ts +7 -0
  27. package/lib/dashboard-visuals-v2/charts/AreaChart.js +124 -0
  28. package/lib/dashboard-visuals-v2/charts/BarChart.d.ts +7 -0
  29. package/lib/dashboard-visuals-v2/charts/BarChart.js +106 -0
  30. package/lib/dashboard-visuals-v2/charts/BaseChart.d.ts +61 -0
  31. package/lib/dashboard-visuals-v2/charts/BaseChart.js +173 -0
  32. package/lib/dashboard-visuals-v2/charts/DonutChart.d.ts +7 -0
  33. package/lib/dashboard-visuals-v2/charts/DonutChart.js +108 -0
  34. package/lib/dashboard-visuals-v2/charts/HeatmapChart.d.ts +7 -0
  35. package/lib/dashboard-visuals-v2/charts/HeatmapChart.js +101 -0
  36. package/lib/dashboard-visuals-v2/charts/LineChart.d.ts +7 -0
  37. package/lib/dashboard-visuals-v2/charts/LineChart.js +109 -0
  38. package/lib/dashboard-visuals-v2/charts/MixedChart.d.ts +7 -0
  39. package/lib/dashboard-visuals-v2/charts/MixedChart.js +106 -0
  40. package/lib/dashboard-visuals-v2/charts/PieChart.d.ts +7 -0
  41. package/lib/dashboard-visuals-v2/charts/PieChart.js +10 -0
  42. package/lib/dashboard-visuals-v2/charts/RadialChart.d.ts +7 -0
  43. package/lib/dashboard-visuals-v2/charts/RadialChart.js +72 -0
  44. package/lib/dashboard-visuals-v2/charts/index.d.ts +12 -0
  45. package/lib/dashboard-visuals-v2/charts/index.js +12 -0
  46. package/lib/dashboard-visuals-v2/components/DataFetchingVisual.d.ts +0 -0
  47. package/lib/dashboard-visuals-v2/components/DataFetchingVisual.js +1 -0
  48. package/lib/dashboard-visuals-v2/components/index.d.ts +0 -0
  49. package/lib/dashboard-visuals-v2/components/index.js +1 -0
  50. package/lib/dashboard-visuals-v2/hooks/index.d.ts +4 -0
  51. package/lib/dashboard-visuals-v2/hooks/index.js +4 -0
  52. package/lib/dashboard-visuals-v2/hooks/useChartData.d.ts +72 -0
  53. package/lib/dashboard-visuals-v2/hooks/useChartData.js +122 -0
  54. package/lib/dashboard-visuals-v2/index.d.ts +10 -0
  55. package/lib/dashboard-visuals-v2/index.js +16 -0
  56. package/lib/dashboard-visuals-v2/types/card.types.d.ts +237 -0
  57. package/lib/dashboard-visuals-v2/types/card.types.js +30 -0
  58. package/lib/dashboard-visuals-v2/types/chart.types.d.ts +308 -0
  59. package/lib/dashboard-visuals-v2/types/chart.types.js +25 -0
  60. package/lib/dashboard-visuals-v2/types/index.d.ts +6 -0
  61. package/lib/dashboard-visuals-v2/types/index.js +6 -0
  62. package/lib/dashboard-visuals-v2/utils/index.d.ts +0 -0
  63. package/lib/dashboard-visuals-v2/utils/index.js +1 -0
  64. package/lib/dashboard-visuals-v2/utils/propsTransformer.d.ts +0 -0
  65. package/lib/dashboard-visuals-v2/utils/propsTransformer.js +1 -0
  66. package/lib/dashboard-visuals-v2/visualRegistry.d.ts +59 -0
  67. package/lib/dashboard-visuals-v2/visualRegistry.js +110 -0
  68. package/lib/data-table-v2/DataTable.d.ts +7 -0
  69. package/lib/data-table-v2/DataTable.js +206 -0
  70. package/lib/data-table-v2/components/DataTableBody.d.ts +19 -0
  71. package/lib/data-table-v2/components/DataTableBody.js +20 -0
  72. package/lib/data-table-v2/components/DataTableEmpty.d.ts +17 -0
  73. package/lib/data-table-v2/components/DataTableEmpty.js +13 -0
  74. package/lib/data-table-v2/components/DataTableError.d.ts +16 -0
  75. package/lib/data-table-v2/components/DataTableError.js +14 -0
  76. package/lib/data-table-v2/components/DataTableHeader.d.ts +15 -0
  77. package/lib/data-table-v2/components/DataTableHeader.js +31 -0
  78. package/lib/data-table-v2/components/DataTableLoading.d.ts +14 -0
  79. package/lib/data-table-v2/components/DataTableLoading.js +19 -0
  80. package/lib/data-table-v2/components/DataTablePagination.d.ts +36 -0
  81. package/lib/data-table-v2/components/DataTablePagination.js +20 -0
  82. package/lib/data-table-v2/components/DataTableRowActions.d.ts +13 -0
  83. package/lib/data-table-v2/components/DataTableRowActions.js +57 -0
  84. package/lib/data-table-v2/components/DataTableSearch.d.ts +19 -0
  85. package/lib/data-table-v2/components/DataTableSearch.js +33 -0
  86. package/lib/data-table-v2/components/DataTableToolbar.d.ts +54 -0
  87. package/lib/data-table-v2/components/DataTableToolbar.js +28 -0
  88. package/lib/data-table-v2/components/index.d.ts +12 -0
  89. package/lib/data-table-v2/components/index.js +12 -0
  90. package/lib/data-table-v2/hooks/index.d.ts +4 -0
  91. package/lib/data-table-v2/hooks/index.js +4 -0
  92. package/lib/data-table-v2/hooks/useTableData.d.ts +118 -0
  93. package/lib/data-table-v2/hooks/useTableData.js +210 -0
  94. package/lib/data-table-v2/index.d.ts +9 -0
  95. package/lib/data-table-v2/index.js +12 -0
  96. package/lib/data-table-v2/types/index.d.ts +296 -0
  97. package/lib/data-table-v2/types/index.js +1 -0
  98. package/lib/data-table-v2/utils/export.d.ts +26 -0
  99. package/lib/data-table-v2/utils/export.js +92 -0
  100. package/lib/data-table-v2/utils/index.d.ts +4 -0
  101. package/lib/data-table-v2/utils/index.js +4 -0
  102. package/lib/index.d.ts +4 -0
  103. package/lib/index.js +23 -0
  104. package/lib/styles.css +219 -0
  105. package/package.json +7 -1
@@ -0,0 +1,299 @@
1
+ /**
2
+ * @fileoverview TanStack Query integration for the API client
3
+ * Provides hooks for data fetching, mutations, and cache management
4
+ */
5
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient, } from '@tanstack/react-query';
6
+ import { useApi } from './context';
7
+ /**
8
+ * Creates a fetcher function using the existing ApiClient
9
+ * @template ResponseData - Expected response type
10
+ */
11
+ function useApiFetcher() {
12
+ const api = useApi();
13
+ return async (endpoint, config) => {
14
+ switch (config?.method) {
15
+ case 'POST':
16
+ return api.post(endpoint, config.body ? JSON.parse(config.body.toString()) : undefined, config);
17
+ case 'PUT':
18
+ return api.put(endpoint, config.body ? JSON.parse(config.body.toString()) : undefined, config);
19
+ case 'PATCH':
20
+ return api.patch(endpoint, config.body ? JSON.parse(config.body.toString()) : undefined, config);
21
+ case 'DELETE':
22
+ return api.delete(endpoint, config);
23
+ default:
24
+ return api.get(endpoint, config);
25
+ }
26
+ };
27
+ }
28
+ /**
29
+ * Main TanStack Query hook that automatically inherits auth config
30
+ * @template TData - Type of the expected response data
31
+ * @template TError - Type of the error response (defaults to ApiErrorResponse)
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function UsersList() {
36
+ * const { data, isLoading, error } = useApiQuery<User[]>({
37
+ * queryKey: ['users'],
38
+ * endpoint: '/users',
39
+ * });
40
+ *
41
+ * if (isLoading) return <div>Loading...</div>;
42
+ * if (error) return <div>Error: {error.message}</div>;
43
+ *
44
+ * return (
45
+ * <ul>
46
+ * {data?.map(user => <li key={user.id}>{user.name}</li>)}
47
+ * </ul>
48
+ * );
49
+ * }
50
+ * ```
51
+ */
52
+ export function useApiQuery(options) {
53
+ const fetcher = useApiFetcher();
54
+ const { queryKey, endpoint, requestConfig, ...queryOptions } = options;
55
+ return useQuery({
56
+ queryKey,
57
+ queryFn: () => fetcher(endpoint, requestConfig),
58
+ ...queryOptions,
59
+ });
60
+ }
61
+ /**
62
+ * Paginated TanStack Query hook for Spring Boot style responses
63
+ * @template TItem - Type of items in the paginated response
64
+ * @template TError - Type of the error response
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * function PaginatedUsers() {
69
+ * const { data, isLoading } = usePaginatedApiQuery<User>({
70
+ * queryKey: ['users', { page: 0, size: 10 }],
71
+ * endpoint: '/users?page=0&size=10',
72
+ * });
73
+ *
74
+ * return (
75
+ * <div>
76
+ * {data?.content.map(user => <div key={user.id}>{user.name}</div>)}
77
+ * <p>Total: {data?.totalElements}</p>
78
+ * </div>
79
+ * );
80
+ * }
81
+ * ```
82
+ */
83
+ export function usePaginatedApiQuery(options) {
84
+ return useApiQuery(options);
85
+ }
86
+ /**
87
+ * Infinite query hook for infinite scrolling with Spring Boot pagination
88
+ * @template TItem - Type of items in the paginated response
89
+ * @template TError - Type of the error response
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * function InfiniteUsersList() {
94
+ * const {
95
+ * data,
96
+ * fetchNextPage,
97
+ * hasNextPage,
98
+ * isFetchingNextPage,
99
+ * } = useInfiniteApiQuery<User>({
100
+ * queryKey: ['users', 'infinite'],
101
+ * endpoint: '/users',
102
+ * pageSize: 20,
103
+ * });
104
+ *
105
+ * return (
106
+ * <div>
107
+ * {data?.pages.flatMap(page => page.content).map(user => (
108
+ * <div key={user.id}>{user.name}</div>
109
+ * ))}
110
+ * {hasNextPage && (
111
+ * <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
112
+ * {isFetchingNextPage ? 'Loading...' : 'Load More'}
113
+ * </button>
114
+ * )}
115
+ * </div>
116
+ * );
117
+ * }
118
+ * ```
119
+ */
120
+ export function useInfiniteApiQuery(options) {
121
+ const fetcher = useApiFetcher();
122
+ const { queryKey, endpoint, pageSize = 20, pageParam = 'page', sizeParam = 'size', requestConfig, ...queryOptions } = options;
123
+ // Build endpoint with pagination params
124
+ const buildEndpoint = (page) => {
125
+ const separator = endpoint.includes('?') ? '&' : '?';
126
+ return `${endpoint}${separator}${pageParam}=${page}&${sizeParam}=${pageSize}`;
127
+ };
128
+ return useInfiniteQuery({
129
+ queryKey,
130
+ queryFn: ({ pageParam: page }) => fetcher(buildEndpoint(page), requestConfig),
131
+ initialPageParam: 0,
132
+ getNextPageParam: (lastPage) => {
133
+ if (lastPage.last)
134
+ return undefined;
135
+ return lastPage.number + 1;
136
+ },
137
+ getPreviousPageParam: (firstPage) => {
138
+ if (firstPage.first)
139
+ return undefined;
140
+ return firstPage.number - 1;
141
+ },
142
+ ...queryOptions,
143
+ });
144
+ }
145
+ /**
146
+ * Mutation hook for POST, PUT, PATCH, DELETE operations
147
+ * @template TData - Type of the response data
148
+ * @template TVariables - Type of the mutation variables
149
+ * @template TError - Type of the error response
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * function CreateUserForm() {
154
+ * const queryClient = useQueryClient();
155
+ * const { mutate, isPending } = useApiQueryMutation<User, CreateUserDto>({
156
+ * method: 'POST',
157
+ * onSuccess: () => {
158
+ * queryClient.invalidateQueries({ queryKey: ['users'] });
159
+ * },
160
+ * });
161
+ *
162
+ * const handleSubmit = (data: CreateUserDto) => {
163
+ * mutate({ endpoint: '/users', data });
164
+ * };
165
+ *
166
+ * return (
167
+ * <form onSubmit={handleSubmit}>
168
+ * // ...form fields
169
+ * <button type="submit" disabled={isPending}>
170
+ * {isPending ? 'Creating...' : 'Create User'}
171
+ * </button>
172
+ * </form>
173
+ * );
174
+ * }
175
+ * ```
176
+ */
177
+ export function useApiQueryMutation(options) {
178
+ const api = useApi();
179
+ const queryClient = useQueryClient();
180
+ const { method, invalidateKeys, ...mutationOptions } = options;
181
+ return useMutation({
182
+ mutationFn: async ({ endpoint, data, config }) => {
183
+ switch (method) {
184
+ case 'POST':
185
+ return api.post(endpoint, data, config);
186
+ case 'PUT':
187
+ return api.put(endpoint, data, config);
188
+ case 'PATCH':
189
+ return api.patch(endpoint, data, config);
190
+ case 'DELETE':
191
+ return api.delete(endpoint, config);
192
+ default:
193
+ throw new Error(`Unsupported mutation method: ${method}`);
194
+ }
195
+ },
196
+ onSuccess: async () => {
197
+ // Invalidate specified query keys
198
+ if (invalidateKeys?.length) {
199
+ await Promise.all(invalidateKeys.map((key) => queryClient.invalidateQueries({ queryKey: key })));
200
+ }
201
+ },
202
+ ...mutationOptions,
203
+ });
204
+ }
205
+ /**
206
+ * Convenience hook for POST mutations
207
+ */
208
+ export function useApiPostMutation(options) {
209
+ return useApiQueryMutation({ method: 'POST', ...options });
210
+ }
211
+ /**
212
+ * Convenience hook for PUT mutations
213
+ */
214
+ export function useApiPutMutation(options) {
215
+ return useApiQueryMutation({ method: 'PUT', ...options });
216
+ }
217
+ /**
218
+ * Convenience hook for PATCH mutations
219
+ */
220
+ export function useApiPatchMutation(options) {
221
+ return useApiQueryMutation({ method: 'PATCH', ...options });
222
+ }
223
+ /**
224
+ * Convenience hook for DELETE mutations
225
+ */
226
+ export function useApiDeleteMutation(options) {
227
+ return useApiQueryMutation({ method: 'DELETE', ...options });
228
+ }
229
+ /**
230
+ * Hook for prefetching data
231
+ *
232
+ * @example
233
+ * ```tsx
234
+ * function UserLink({ userId }: { userId: string }) {
235
+ * const prefetch = useApiPrefetch();
236
+ *
237
+ * return (
238
+ * <Link
239
+ * to={`/users/${userId}`}
240
+ * onMouseEnter={() => prefetch({
241
+ * queryKey: ['user', userId],
242
+ * endpoint: `/users/${userId}`,
243
+ * })}
244
+ * >
245
+ * View User
246
+ * </Link>
247
+ * );
248
+ * }
249
+ * ```
250
+ */
251
+ export function useApiPrefetch() {
252
+ const queryClient = useQueryClient();
253
+ const fetcher = useApiFetcher();
254
+ return (options) => {
255
+ const { queryKey, endpoint, requestConfig, staleTime } = options;
256
+ return queryClient.prefetchQuery({
257
+ queryKey,
258
+ queryFn: () => fetcher(endpoint, requestConfig),
259
+ staleTime,
260
+ });
261
+ };
262
+ }
263
+ /**
264
+ * Hook for accessing the query client with typed helpers
265
+ *
266
+ * @example
267
+ * ```tsx
268
+ * function RefreshButton() {
269
+ * const { invalidate, setData, removeQueries } = useApiQueryClient();
270
+ *
271
+ * return (
272
+ * <button onClick={() => invalidate(['users'])}>
273
+ * Refresh Users
274
+ * </button>
275
+ * );
276
+ * }
277
+ * ```
278
+ */
279
+ export function useApiQueryClient() {
280
+ const queryClient = useQueryClient();
281
+ return {
282
+ /** The underlying query client */
283
+ queryClient,
284
+ /** Invalidate queries by key */
285
+ invalidate: (queryKey) => queryClient.invalidateQueries({ queryKey }),
286
+ /** Set query data directly */
287
+ setData: (queryKey, data) => queryClient.setQueryData(queryKey, data),
288
+ /** Get cached query data */
289
+ getData: (queryKey) => queryClient.getQueryData(queryKey),
290
+ /** Remove queries from cache */
291
+ removeQueries: (queryKey) => queryClient.removeQueries({ queryKey }),
292
+ /** Cancel ongoing queries */
293
+ cancelQueries: (queryKey) => queryClient.cancelQueries({ queryKey }),
294
+ /** Refetch queries */
295
+ refetch: (queryKey) => queryClient.refetchQueries({ queryKey }),
296
+ /** Reset queries to initial state */
297
+ resetQueries: (queryKey) => queryClient.resetQueries({ queryKey }),
298
+ };
299
+ }
@@ -13,9 +13,14 @@ const AppSidebarMenu = () => {
13
13
  // Default behavior all items in one ItemGroup
14
14
  return (_jsx(Sidebar.Items, { children: _jsx(Sidebar.ItemGroup, { children: pages.map((item) => (_jsx(AppSidebarItem, { ...item }, item.label))) }) }));
15
15
  };
16
- const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown }) => {
16
+ const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown, collapsible = true, showSeparator = false, comingSoon = false, isSectionHeader = false, }) => {
17
17
  const { pathname, linkComponent: Link } = useAppSidebarContext();
18
18
  const { t } = useTranslation();
19
+ const { tLib } = useLibTranslations();
20
+ // Ensure label is always a string to prevent .charAt() errors in flowbite-react
21
+ const safeLabel = String(label || '');
22
+ const translatedLabel = String(t(safeLabel) || safeLabel || '');
23
+ const translatedBadge = badge ? String(t(badge) || badge || '') : undefined;
19
24
  // Check if current path matches this item or any of its descendants
20
25
  const isParentActive = href ? pathname === href || pathname.startsWith(`${href}/`) : false;
21
26
  // Recursive function to check if any nested child is active
@@ -28,40 +33,69 @@ const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown
28
33
  return false;
29
34
  });
30
35
  };
36
+ // Helper to render a single child item (used in multiple places)
37
+ const renderChildItem = (child) => {
38
+ const childSafeLabel = String(child.label || '');
39
+ const childTranslatedLabel = String(t(childSafeLabel) || childSafeLabel || '');
40
+ // Inline section header - renders as a small label separator (flat, no nesting)
41
+ if (child.isSectionHeader) {
42
+ return (_jsxs("div", { className: "pt-3", children: [_jsx("hr", { className: "mb-2 border-t border-gray-200 dark:border-gray-700" }), _jsx("div", { className: "pb-1 px-3 text-xs font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-300", children: childTranslatedLabel })] }, child.label));
43
+ }
44
+ // Nested items with children - recurse
45
+ if (child.items?.length) {
46
+ return (_jsx("div", { className: "pl-3", children: _jsx(AppSidebarItem, { ...child }) }, child.label));
47
+ }
48
+ // Coming soon item - disabled, no navigation
49
+ if (child.comingSoon) {
50
+ return (_jsxs(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal cursor-not-allowed opacity-60', 'text-gray-400 dark:text-gray-500'), disabled: true, children: [childTranslatedLabel, _jsx("span", { className: "rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-medium text-amber-600 dark:bg-amber-900/30 dark:text-amber-400", children: tLib('sidebar.comingSoon', 'Soon') })] }, child.label));
51
+ }
52
+ // Regular item with link
53
+ if (child.href) {
54
+ return (_jsx(Sidebar.Item, { as: Link, href: child.href, target: child.target, icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === child.href &&
55
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: childTranslatedLabel }, child.label));
56
+ }
57
+ // Item without link (static text)
58
+ return (_jsx(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 cursor-default', 'dark:text-gray-400'), children: childTranslatedLabel }, child.label));
59
+ };
60
+ // Inline section header at top level - renders as a small label separator
61
+ if (isSectionHeader) {
62
+ return (_jsxs("div", { className: "pt-3", children: [_jsx("hr", { className: "mb-2 border-t border-gray-200 dark:border-gray-700" }), _jsx("div", { className: "pb-1 px-3 text-xs font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-300", children: translatedLabel })] }));
63
+ }
31
64
  // If there's exactly one item, render it as a direct link instead of a dropdown
32
65
  if (items?.length === 1 && !forceDropdown) {
33
66
  const singleItem = items[0];
34
67
  if (!singleItem.href) {
35
68
  // If single item has no href, render without link
36
- return (_jsx(Sidebar.Item, { icon: icon, label: badge && t(badge), className: twMerge('text-gray-600 cursor-default', 'dark:text-gray-400'), children: t(label) }));
69
+ return (_jsxs(Sidebar.Item, { icon: icon, className: twMerge('text-gray-600 cursor-default', 'dark:text-gray-400'), children: [translatedLabel, translatedBadge && _jsx("span", { className: "text-xs font-medium", children: translatedBadge })] }));
37
70
  }
38
- return (_jsx(Sidebar.Item, { as: Link, href: singleItem.href, target: target, icon: icon, label: badge && t(badge), className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === singleItem.href &&
39
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(label) }));
71
+ return (_jsxs(Sidebar.Item, { as: Link, href: singleItem.href, target: target, icon: icon, className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === singleItem.href &&
72
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: [translatedLabel, translatedBadge && _jsx("span", { className: "text-xs font-medium", children: translatedBadge })] }));
73
+ }
74
+ // Handle non-collapsible sections - render as section header with always-visible links
75
+ if (items?.length && collapsible === false) {
76
+ return (_jsxs("div", { className: "space-y-1", children: [showSeparator && _jsx("hr", { className: "my-3 border-t border-gray-200 dark:border-gray-700" }), _jsxs("div", { className: twMerge('flex items-center gap-3 px-3 py-2 text-sm font-semibold uppercase tracking-wider', 'text-gray-500 dark:text-gray-400'), children: [icon && React.createElement(icon, { className: 'h-5 w-5' }), _jsx("span", { children: translatedLabel })] }), _jsx("div", { className: "space-y-1 pl-2", children: items.map((child) => renderChildItem(child)) })] }));
40
77
  }
41
78
  if (items?.length) {
42
79
  const isOpen = isParentActive || hasActiveChild(items);
43
- return (_jsx(Sidebar.Collapse, { icon: icon, label: t(label), open: isOpen, className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30',
80
+ return (_jsx(Sidebar.Collapse, { icon: icon, label: translatedLabel, open: isOpen, className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30',
44
81
  // Parent styling when its child is active
45
82
  hasActiveChild(items) && 'text-primary-700 dark:text-primary-300',
46
83
  // Parent styling when it's directly active
47
84
  isParentActive &&
48
85
  !hasActiveChild(items) &&
49
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), theme: { list: 'space-y-2 py-2 [&>li>div]:w-full' }, children: items.map((child) => (_jsx(React.Fragment, { children: child.items?.length ? (
50
- // Recursively render nested items
51
- _jsx("div", { className: "pl-3", children: _jsx(AppSidebarItem, { ...child }) })) : child.href ? (
52
- // Render leaf item with link
53
- _jsx(Sidebar.Item, { as: Link, href: child.href, target: child.target, icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', pathname === child.href &&
54
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(child.label) })) : (
55
- // Render leaf item without link
56
- _jsx(Sidebar.Item, { icon: child.icon, className: twMerge('justify-center [&>*]:font-normal', 'text-gray-600 cursor-default', 'dark:text-gray-400'), children: t(child.label) })) }, child.label))) }));
86
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), theme: { list: 'space-y-2 py-2 [&>li>div]:w-full' }, children: items.map((child) => (_jsx(React.Fragment, { children: renderChildItem(child) }, child.label))) }));
57
87
  }
58
88
  // Render leaf item
89
+ // Handle comingSoon leaf item
90
+ if (comingSoon) {
91
+ return (_jsx(Sidebar.Item, { icon: icon, className: twMerge('cursor-not-allowed opacity-60', 'text-gray-400 dark:text-gray-500'), children: _jsxs("span", { className: "flex items-center justify-between w-full", children: [t(label), _jsx("span", { className: "rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-medium text-amber-600 dark:bg-amber-900/30 dark:text-amber-400", children: tLib('sidebar.comingSoon', 'Soon') })] }) }));
92
+ }
59
93
  // If no href, render as a non-link item
60
94
  if (!href) {
61
- return (_jsx(Sidebar.Item, { icon: icon, label: badge && t(badge), className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', 'cursor-default'), children: t(label) }));
95
+ return (_jsxs(Sidebar.Item, { icon: icon, className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', 'cursor-default'), children: [translatedLabel, translatedBadge && _jsx("span", { className: "text-xs font-medium", children: translatedBadge })] }));
62
96
  }
63
- return (_jsx(Sidebar.Item, { as: Link, href: href, target: target, icon: icon, label: badge && t(badge), className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', isParentActive &&
64
- 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(label) }));
97
+ return (_jsxs(Sidebar.Item, { as: Link, href: href, target: target, icon: icon, className: twMerge('text-gray-600 hover:text-primary-700 hover:bg-primary-50/70', 'dark:text-gray-400 dark:hover:text-primary-300 dark:hover:bg-primary-900/30', isParentActive &&
98
+ 'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: [translatedLabel, translatedBadge && _jsx("span", { className: "text-xs font-medium", children: translatedBadge })] }));
65
99
  };
66
100
  const AppMobileSidebar = () => {
67
101
  const { mobile: { isOpen, close }, sidebarHeader, sidebarFooter, } = useAppSidebarContext();
@@ -8,7 +8,7 @@ declare const SheetClose: React.ForwardRefExoticComponent<SheetPrimitive.DialogC
8
8
  declare const SheetPortal: React.FC<SheetPrimitive.DialogPortalProps>;
9
9
  declare const SheetOverlay: React.ForwardRefExoticComponent<Omit<SheetPrimitive.DialogOverlayProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
10
10
  declare const sheetVariants: (props?: ({
11
- side?: "bottom" | "left" | "right" | "top" | null | undefined;
11
+ side?: "left" | "right" | "top" | "bottom" | null | undefined;
12
12
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
13
13
  interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, VariantProps<typeof sheetVariants> {
14
14
  }
@@ -9,6 +9,26 @@ interface AppSidebarPageItem {
9
9
  badge?: string;
10
10
  roles?: string[];
11
11
  forceDropdown?: boolean;
12
+ /**
13
+ * When false, the section will not be collapsible - it will display as a
14
+ * section header with always-visible links below it. Defaults to true.
15
+ */
16
+ collapsible?: boolean;
17
+ /**
18
+ * When true, displays a horizontal separator line above the section.
19
+ * Useful for visually separating non-collapsible sections. Defaults to false.
20
+ */
21
+ showSeparator?: boolean;
22
+ /**
23
+ * When true, the item is shown but disabled with a "Coming Soon" badge.
24
+ * The link will not be navigable. Defaults to false.
25
+ */
26
+ comingSoon?: boolean;
27
+ /**
28
+ * When true, renders as an inline section header (small label with separator).
29
+ * Used for flat organization without nesting. Defaults to false.
30
+ */
31
+ isSectionHeader?: boolean;
12
32
  }
13
33
  /**
14
34
  * Props for the AppSidebarContext
@@ -0,0 +1,81 @@
1
+ type FilterValue = string | string[] | Date | null | undefined;
2
+ interface OptionsLabelProps {
3
+ label: string;
4
+ value: string;
5
+ }
6
+ interface FilterConfig {
7
+ name: string;
8
+ label?: string;
9
+ placeholder?: string;
10
+ type?: 'select' | 'multi-select' | 'date' | 'date-range';
11
+ options?: OptionsLabelProps[];
12
+ multi?: boolean;
13
+ query?: string;
14
+ url?: string;
15
+ tourUrl?: string;
16
+ valueKey?: string;
17
+ rangeKeys?: {
18
+ start: string;
19
+ end: string;
20
+ };
21
+ }
22
+ interface Config {
23
+ title: string;
24
+ summary: string;
25
+ filters: FilterConfig[];
26
+ tourFilters?: FilterConfig[];
27
+ themeColor?: string;
28
+ }
29
+ export interface MetricsDataProps {
30
+ id: number;
31
+ gridColSpan?: number;
32
+ metricData: any;
33
+ }
34
+ export interface VisualDataProps {
35
+ id: number;
36
+ sectionTitle: string;
37
+ data: MetricsDataProps[];
38
+ }
39
+ export interface DashboardApiPaths {
40
+ query: string;
41
+ metrics: string;
42
+ }
43
+ export interface DashboardWrapperV2Props {
44
+ data: {
45
+ config: Config;
46
+ visualData: VisualDataProps[];
47
+ eventsVisualData?: VisualDataProps[];
48
+ };
49
+ showVisualHeader?: boolean;
50
+ showEventsTabbedLayout?: boolean;
51
+ handleFilterChange?: (name: string, value: FilterValue) => void;
52
+ handleTourFilterChange?: (name: string, value: FilterValue) => void;
53
+ selectedFilters?: Record<string, FilterValue>;
54
+ tourSelectedFilters?: Record<string, FilterValue>;
55
+ resetFilters?: () => void;
56
+ handleApplyFilters?: () => void;
57
+ handleApplyTourFilters?: () => void;
58
+ apiPaths?: DashboardApiPaths;
59
+ /** Custom className for wrapper */
60
+ className?: string;
61
+ /** Custom classNames for sub-elements */
62
+ classNames?: {
63
+ header?: string;
64
+ title?: string;
65
+ summary?: string;
66
+ section?: string;
67
+ sectionTitle?: string;
68
+ grid?: string;
69
+ visual?: string;
70
+ };
71
+ /** Gap between visuals in grid (Tailwind gap class number) */
72
+ gridGap?: number;
73
+ /** Whether to use full-width layout */
74
+ fullWidth?: boolean;
75
+ }
76
+ /**
77
+ * DashboardWrapperV2 Component
78
+ * Drop-in replacement for DashboardWrapper using ApexCharts
79
+ */
80
+ export declare function DashboardWrapperV2({ data, showVisualHeader, showEventsTabbedLayout, handleFilterChange: externalHandleFilterChange, selectedFilters, tourSelectedFilters, resetFilters, handleApplyFilters, handleApplyTourFilters, handleTourFilterChange, apiPaths, className, classNames, gridGap, fullWidth, }: DashboardWrapperV2Props): import("react/jsx-runtime").JSX.Element;
81
+ export default DashboardWrapperV2;