@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.
- package/lib/api/TanstackQueryProvider.d.ts +146 -0
- package/lib/api/TanstackQueryProvider.js +140 -0
- package/lib/api/index.d.ts +2 -0
- package/lib/api/index.js +2 -0
- package/lib/api/tanstackQuery.d.ts +249 -0
- package/lib/api/tanstackQuery.js +299 -0
- package/lib/components/layout/Sidebar.js +50 -16
- package/lib/components/ui/Sheet.d.ts +1 -1
- package/lib/context/SidebarContext.d.ts +20 -0
- package/lib/dashboard-visuals-v2/DashboardWrapperV2.d.ts +81 -0
- package/lib/dashboard-visuals-v2/DashboardWrapperV2.js +217 -0
- package/lib/dashboard-visuals-v2/cards/CardGrid.d.ts +22 -0
- package/lib/dashboard-visuals-v2/cards/CardGrid.js +27 -0
- package/lib/dashboard-visuals-v2/cards/InfoCard.d.ts +7 -0
- package/lib/dashboard-visuals-v2/cards/InfoCard.js +26 -0
- package/lib/dashboard-visuals-v2/cards/MetricCard.d.ts +7 -0
- package/lib/dashboard-visuals-v2/cards/MetricCard.js +37 -0
- package/lib/dashboard-visuals-v2/cards/StatCard.d.ts +7 -0
- package/lib/dashboard-visuals-v2/cards/StatCard.js +92 -0
- package/lib/dashboard-visuals-v2/cards/TopItemsCard.d.ts +7 -0
- package/lib/dashboard-visuals-v2/cards/TopItemsCard.js +34 -0
- package/lib/dashboard-visuals-v2/cards/TransactionListCard.d.ts +7 -0
- package/lib/dashboard-visuals-v2/cards/TransactionListCard.js +24 -0
- package/lib/dashboard-visuals-v2/cards/index.d.ts +9 -0
- package/lib/dashboard-visuals-v2/cards/index.js +9 -0
- package/lib/dashboard-visuals-v2/charts/AreaChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/AreaChart.js +124 -0
- package/lib/dashboard-visuals-v2/charts/BarChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/BarChart.js +106 -0
- package/lib/dashboard-visuals-v2/charts/BaseChart.d.ts +61 -0
- package/lib/dashboard-visuals-v2/charts/BaseChart.js +173 -0
- package/lib/dashboard-visuals-v2/charts/DonutChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/DonutChart.js +108 -0
- package/lib/dashboard-visuals-v2/charts/HeatmapChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/HeatmapChart.js +101 -0
- package/lib/dashboard-visuals-v2/charts/LineChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/LineChart.js +109 -0
- package/lib/dashboard-visuals-v2/charts/MixedChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/MixedChart.js +106 -0
- package/lib/dashboard-visuals-v2/charts/PieChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/PieChart.js +10 -0
- package/lib/dashboard-visuals-v2/charts/RadialChart.d.ts +7 -0
- package/lib/dashboard-visuals-v2/charts/RadialChart.js +72 -0
- package/lib/dashboard-visuals-v2/charts/index.d.ts +12 -0
- package/lib/dashboard-visuals-v2/charts/index.js +12 -0
- package/lib/dashboard-visuals-v2/components/DataFetchingVisual.d.ts +0 -0
- package/lib/dashboard-visuals-v2/components/DataFetchingVisual.js +1 -0
- package/lib/dashboard-visuals-v2/components/index.d.ts +0 -0
- package/lib/dashboard-visuals-v2/components/index.js +1 -0
- package/lib/dashboard-visuals-v2/hooks/index.d.ts +4 -0
- package/lib/dashboard-visuals-v2/hooks/index.js +4 -0
- package/lib/dashboard-visuals-v2/hooks/useChartData.d.ts +72 -0
- package/lib/dashboard-visuals-v2/hooks/useChartData.js +122 -0
- package/lib/dashboard-visuals-v2/index.d.ts +10 -0
- package/lib/dashboard-visuals-v2/index.js +16 -0
- package/lib/dashboard-visuals-v2/types/card.types.d.ts +237 -0
- package/lib/dashboard-visuals-v2/types/card.types.js +30 -0
- package/lib/dashboard-visuals-v2/types/chart.types.d.ts +308 -0
- package/lib/dashboard-visuals-v2/types/chart.types.js +25 -0
- package/lib/dashboard-visuals-v2/types/index.d.ts +6 -0
- package/lib/dashboard-visuals-v2/types/index.js +6 -0
- package/lib/dashboard-visuals-v2/utils/index.d.ts +0 -0
- package/lib/dashboard-visuals-v2/utils/index.js +1 -0
- package/lib/dashboard-visuals-v2/utils/propsTransformer.d.ts +0 -0
- package/lib/dashboard-visuals-v2/utils/propsTransformer.js +1 -0
- package/lib/dashboard-visuals-v2/visualRegistry.d.ts +59 -0
- package/lib/dashboard-visuals-v2/visualRegistry.js +110 -0
- package/lib/data-table-v2/DataTable.d.ts +7 -0
- package/lib/data-table-v2/DataTable.js +206 -0
- package/lib/data-table-v2/components/DataTableBody.d.ts +19 -0
- package/lib/data-table-v2/components/DataTableBody.js +20 -0
- package/lib/data-table-v2/components/DataTableEmpty.d.ts +17 -0
- package/lib/data-table-v2/components/DataTableEmpty.js +13 -0
- package/lib/data-table-v2/components/DataTableError.d.ts +16 -0
- package/lib/data-table-v2/components/DataTableError.js +14 -0
- package/lib/data-table-v2/components/DataTableHeader.d.ts +15 -0
- package/lib/data-table-v2/components/DataTableHeader.js +31 -0
- package/lib/data-table-v2/components/DataTableLoading.d.ts +14 -0
- package/lib/data-table-v2/components/DataTableLoading.js +19 -0
- package/lib/data-table-v2/components/DataTablePagination.d.ts +36 -0
- package/lib/data-table-v2/components/DataTablePagination.js +20 -0
- package/lib/data-table-v2/components/DataTableRowActions.d.ts +13 -0
- package/lib/data-table-v2/components/DataTableRowActions.js +57 -0
- package/lib/data-table-v2/components/DataTableSearch.d.ts +19 -0
- package/lib/data-table-v2/components/DataTableSearch.js +33 -0
- package/lib/data-table-v2/components/DataTableToolbar.d.ts +54 -0
- package/lib/data-table-v2/components/DataTableToolbar.js +28 -0
- package/lib/data-table-v2/components/index.d.ts +12 -0
- package/lib/data-table-v2/components/index.js +12 -0
- package/lib/data-table-v2/hooks/index.d.ts +4 -0
- package/lib/data-table-v2/hooks/index.js +4 -0
- package/lib/data-table-v2/hooks/useTableData.d.ts +118 -0
- package/lib/data-table-v2/hooks/useTableData.js +210 -0
- package/lib/data-table-v2/index.d.ts +9 -0
- package/lib/data-table-v2/index.js +12 -0
- package/lib/data-table-v2/types/index.d.ts +296 -0
- package/lib/data-table-v2/types/index.js +1 -0
- package/lib/data-table-v2/utils/export.d.ts +26 -0
- package/lib/data-table-v2/utils/export.js +92 -0
- package/lib/data-table-v2/utils/index.d.ts +4 -0
- package/lib/data-table-v2/utils/index.js +4 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +23 -0
- package/lib/styles.css +219 -0
- 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 (
|
|
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 (
|
|
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:
|
|
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:
|
|
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.
|
|
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 (
|
|
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 (
|
|
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:
|
|
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?: "
|
|
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;
|