@hed-hog/core 0.0.298 → 0.0.299

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 (139) hide show
  1. package/dist/dashboard/dashboard/dashboard.controller.d.ts +3 -0
  2. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  3. package/dist/dashboard/dashboard/dashboard.service.d.ts +3 -0
  4. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +12 -0
  6. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard-component/dashboard-component.controller.js +22 -0
  8. package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
  9. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +15 -0
  10. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-component/dashboard-component.service.js +110 -3
  12. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  13. package/dist/dashboard/dashboard-component/dto/create.dto.d.ts +1 -0
  14. package/dist/dashboard/dashboard-component/dto/create.dto.d.ts.map +1 -1
  15. package/dist/dashboard/dashboard-component/dto/create.dto.js +5 -0
  16. package/dist/dashboard/dashboard-component/dto/create.dto.js.map +1 -1
  17. package/dist/dashboard/dashboard-component/dto/update.dto.d.ts +1 -0
  18. package/dist/dashboard/dashboard-component/dto/update.dto.d.ts.map +1 -1
  19. package/dist/dashboard/dashboard-component/dto/update.dto.js +5 -0
  20. package/dist/dashboard/dashboard-component/dto/update.dto.js.map +1 -1
  21. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -0
  22. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
  23. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -0
  24. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
  25. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +7 -1
  26. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +7 -1
  28. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  29. package/dist/dashboard/dashboard-core/dashboard-core.service.js +88 -4
  30. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  31. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +1 -0
  32. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  33. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +1 -0
  34. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  35. package/hedhog/data/dashboard_item.yaml +1 -1
  36. package/hedhog/data/route.yaml +12 -0
  37. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +141 -24
  38. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
  39. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +136 -23
  40. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +266 -85
  41. package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +11 -0
  42. package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +192 -0
  43. package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +236 -0
  44. package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +108 -0
  45. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +66 -0
  46. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +122 -0
  47. package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +63 -0
  48. package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +73 -0
  49. package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +73 -0
  50. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +123 -0
  51. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +118 -0
  52. package/hedhog/frontend/messages/en.json +3 -0
  53. package/hedhog/frontend/messages/pt.json +3 -0
  54. package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
  55. package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
  56. package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
  57. package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
  58. package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
  59. package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
  60. package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
  61. package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
  62. package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
  63. package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
  64. package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
  65. package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
  66. package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
  67. package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
  68. package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
  69. package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
  70. package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
  71. package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
  72. package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
  73. package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
  74. package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
  75. package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
  76. package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
  77. package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
  78. package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
  79. package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
  80. package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
  81. package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
  82. package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
  83. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +33 -29
  84. package/hedhog/frontend/widgets/active-users-card.tsx.ejs +58 -0
  85. package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +223 -0
  86. package/hedhog/frontend/widgets/email-notifications.tsx.ejs +226 -0
  87. package/hedhog/frontend/widgets/locale-config.tsx.ejs +168 -0
  88. package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +115 -0
  89. package/hedhog/frontend/widgets/mail-config.tsx.ejs +199 -0
  90. package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +58 -0
  91. package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +149 -0
  92. package/hedhog/frontend/widgets/menus-card.tsx.ejs +58 -0
  93. package/hedhog/frontend/widgets/oauth-config.tsx.ejs +175 -0
  94. package/hedhog/frontend/widgets/permissions-card.tsx.ejs +61 -0
  95. package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +156 -0
  96. package/hedhog/frontend/widgets/profile-card.tsx.ejs +186 -0
  97. package/hedhog/frontend/widgets/routes-card.tsx.ejs +58 -0
  98. package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +183 -0
  99. package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +62 -0
  100. package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +57 -0
  101. package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +57 -0
  102. package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +57 -0
  103. package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +57 -0
  104. package/hedhog/frontend/widgets/storage-config.tsx.ejs +196 -0
  105. package/hedhog/frontend/widgets/theme-config.tsx.ejs +213 -0
  106. package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +210 -0
  107. package/hedhog/frontend/widgets/user-roles.tsx.ejs +132 -0
  108. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +1 -1
  109. package/hedhog/table/dashboard_component.yaml +7 -0
  110. package/package.json +4 -4
  111. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +36 -12
  112. package/src/dashboard/dashboard-component/dashboard-component.service.ts +150 -3
  113. package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
  114. package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
  115. package/src/dashboard/dashboard-core/dashboard-core.service.ts +108 -5
  116. /package/hedhog/frontend/app/dashboard/components/widgets/{active-users-card.tsx.ejs → core.active-users-card.tsx.ejs} +0 -0
  117. /package/hedhog/frontend/app/dashboard/components/widgets/{activity-timeline.tsx.ejs → core.activity-timeline.tsx.ejs} +0 -0
  118. /package/hedhog/frontend/app/dashboard/components/widgets/{email-notifications.tsx.ejs → core.email-notifications.tsx.ejs} +0 -0
  119. /package/hedhog/frontend/app/dashboard/components/widgets/{locale-config.tsx.ejs → core.locale-config.tsx.ejs} +0 -0
  120. /package/hedhog/frontend/app/dashboard/components/widgets/{login-history-chart.tsx.ejs → core.login-history-chart.tsx.ejs} +0 -0
  121. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-config.tsx.ejs → core.mail-config.tsx.ejs} +0 -0
  122. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-card.tsx.ejs → core.mail-sent-card.tsx.ejs} +0 -0
  123. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-chart.tsx.ejs → core.mail-sent-chart.tsx.ejs} +0 -0
  124. /package/hedhog/frontend/app/dashboard/components/widgets/{menus-card.tsx.ejs → core.menus-card.tsx.ejs} +0 -0
  125. /package/hedhog/frontend/app/dashboard/components/widgets/{oauth-config.tsx.ejs → core.oauth-config.tsx.ejs} +0 -0
  126. /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-card.tsx.ejs → core.permissions-card.tsx.ejs} +0 -0
  127. /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-chart.tsx.ejs → core.permissions-chart.tsx.ejs} +0 -0
  128. /package/hedhog/frontend/app/dashboard/components/widgets/{profile-card.tsx.ejs → core.profile-card.tsx.ejs} +0 -0
  129. /package/hedhog/frontend/app/dashboard/components/widgets/{routes-card.tsx.ejs → core.routes-card.tsx.ejs} +0 -0
  130. /package/hedhog/frontend/app/dashboard/components/widgets/{session-activity-chart.tsx.ejs → core.session-activity-chart.tsx.ejs} +0 -0
  131. /package/hedhog/frontend/app/dashboard/components/widgets/{sessions-today-card.tsx.ejs → core.sessions-today-card.tsx.ejs} +0 -0
  132. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-access-level.tsx.ejs → core.stat-access-level.tsx.ejs} +0 -0
  133. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-actions-today.tsx.ejs → core.stat-actions-today.tsx.ejs} +0 -0
  134. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-consecutive-days.tsx.ejs → core.stat-consecutive-days.tsx.ejs} +0 -0
  135. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-online-time.tsx.ejs → core.stat-online-time.tsx.ejs} +0 -0
  136. /package/hedhog/frontend/app/dashboard/components/widgets/{storage-config.tsx.ejs → core.storage-config.tsx.ejs} +0 -0
  137. /package/hedhog/frontend/app/dashboard/components/widgets/{theme-config.tsx.ejs → core.theme-config.tsx.ejs} +0 -0
  138. /package/hedhog/frontend/app/dashboard/components/widgets/{user-growth-chart.tsx.ejs → core.user-growth-chart.tsx.ejs} +0 -0
  139. /package/hedhog/frontend/app/dashboard/components/widgets/{user-roles.tsx.ejs → core.user-roles.tsx.ejs} +0 -0
@@ -15,6 +15,7 @@ import { Skeleton } from '@/components/ui/skeleton';
15
15
  import { useIsMobile } from '@/components/ui/use-mobile';
16
16
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
17
17
  import { IconDeviceFloppy } from '@tabler/icons-react';
18
+ import { toBlob } from 'html-to-image';
18
19
  import { useTranslations } from 'next-intl';
19
20
  import { useRouter } from 'next/navigation';
20
21
  import { useCallback, useEffect, useState } from 'react';
@@ -35,6 +36,16 @@ interface DashboardContentProps {
35
36
  dashboardSlug: string;
36
37
  }
37
38
 
39
+ interface DashboardComponentsPage {
40
+ data: DashboardComponent[];
41
+ total: number;
42
+ lastPage: number;
43
+ page: number;
44
+ pageSize: number;
45
+ prev: number | null;
46
+ next: number | null;
47
+ }
48
+
38
49
  const USER_STATS_WIDGETS = new Set([
39
50
  'stat-online-time',
40
51
  'stat-actions-today',
@@ -49,7 +60,30 @@ const USER_POST_HISTORY_WIDGETS = new Set([
49
60
 
50
61
  const USER_BOTTOM_WIDGETS = new Set(['user-roles', 'activity-timeline']);
51
62
 
63
+ const getWidgetBaseSlug = (slug: string): string => {
64
+ const parts = slug.split('.');
65
+ return parts[parts.length - 1] || slug;
66
+ };
67
+
68
+ const getWidgetIdentityKey = ({
69
+ slug,
70
+ library_slug,
71
+ }: {
72
+ slug: string;
73
+ library_slug?: string;
74
+ }) => {
75
+ const baseSlug = getWidgetBaseSlug(slug);
76
+
77
+ if (library_slug) {
78
+ return `${library_slug}.${baseSlug}`;
79
+ }
80
+
81
+ return slug.includes('.') ? slug : baseSlug;
82
+ };
83
+
52
84
  const normalizeUserDashboardLayout = (item: WidgetLayout): LayoutItem => {
85
+ const baseSlug = getWidgetBaseSlug(item.slug);
86
+
53
87
  const layoutItem: LayoutItem = {
54
88
  i: item.i,
55
89
  x: item.x,
@@ -63,26 +97,26 @@ const normalizeUserDashboardLayout = (item: WidgetLayout): LayoutItem => {
63
97
  static: false,
64
98
  };
65
99
 
66
- if (item.slug === 'profile-card') {
100
+ if (baseSlug === 'profile-card') {
67
101
  layoutItem.h = Math.max(item.h, 4);
68
102
  }
69
103
 
70
- if (USER_STATS_WIDGETS.has(item.slug)) {
104
+ if (USER_STATS_WIDGETS.has(baseSlug)) {
71
105
  layoutItem.y = Math.max(item.y, 4);
72
106
  return layoutItem;
73
107
  }
74
108
 
75
- if (item.slug === 'login-history-chart') {
109
+ if (baseSlug === 'login-history-chart') {
76
110
  layoutItem.y = Math.max(item.y, 5);
77
111
  return layoutItem;
78
112
  }
79
113
 
80
- if (USER_POST_HISTORY_WIDGETS.has(item.slug)) {
114
+ if (USER_POST_HISTORY_WIDGETS.has(baseSlug)) {
81
115
  layoutItem.y = Math.max(item.y, 9);
82
116
  return layoutItem;
83
117
  }
84
118
 
85
- if (USER_BOTTOM_WIDGETS.has(item.slug)) {
119
+ if (USER_BOTTOM_WIDGETS.has(baseSlug)) {
86
120
  layoutItem.y = Math.max(item.y, 14);
87
121
  layoutItem.h = item.h + 1;
88
122
  return layoutItem;
@@ -100,11 +134,14 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
100
134
  const { request } = useApp();
101
135
  const router = useRouter();
102
136
  const isMobile = useIsMobile();
137
+ const isDevelopment = process.env.NODE_ENV === 'development';
103
138
 
104
139
  const [layout, setLayout] = useState<LayoutItem[]>([]);
105
140
  const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
106
141
  const [hasChanges, setHasChanges] = useState(false);
107
142
  const [isSaving, setIsSaving] = useState(false);
143
+ const [componentsPage, setComponentsPage] = useState(1);
144
+ const [componentsPageSize, setComponentsPageSize] = useState(12);
108
145
 
109
146
  const { data: dashboardAccess, isLoading: isCheckingAccess } =
110
147
  useQuery<DashboardAccessResponse>({
@@ -129,17 +166,26 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
129
166
  }, [dashboardAccess, dashboardSlug, router]);
130
167
 
131
168
  const {
132
- data: availableComponents,
169
+ data: availableComponentsResponse,
133
170
  isLoading: isLoadingComponents,
134
171
  refetch: refetchComponents,
135
- } = useQuery<any>({
136
- queryKey: ['dashboard-components'],
172
+ } = useQuery<DashboardComponentsPage>({
173
+ queryKey: [
174
+ 'dashboard-components',
175
+ dashboardSlug,
176
+ componentsPage,
177
+ componentsPageSize,
178
+ ],
137
179
  queryFn: async () => {
138
- const { data } = await request<any>({
180
+ const { data } = await request<DashboardComponentsPage>({
139
181
  url: '/dashboard-component/user',
140
182
  method: 'GET',
183
+ params: {
184
+ page: componentsPage,
185
+ pageSize: componentsPageSize,
186
+ },
141
187
  });
142
- return data.data;
188
+ return data;
143
189
  },
144
190
  enabled: dashboardAccess?.hasAccess ?? false,
145
191
  });
@@ -188,16 +234,19 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
188
234
  }
189
235
  setHasChanges(false);
190
236
  }
191
- }, [userLayout]);
237
+ }, [userLayout, dashboardSlug]);
192
238
 
193
- const componentsToFilter = availableComponents?.data?.length
194
- ? availableComponents.data
195
- : availableComponents || [];
239
+ const componentsToFilter = availableComponentsResponse?.data || [];
240
+ const totalAvailableComponents =
241
+ availableComponentsResponse?.total ?? componentsToFilter.length;
196
242
 
197
243
  const filteredComponents =
198
244
  componentsToFilter.filter(
199
245
  (component: DashboardComponent) =>
200
- !widgets.some((widget) => widget.slug === component.slug)
246
+ !widgets.some(
247
+ (widget) =>
248
+ getWidgetIdentityKey(widget) === getWidgetIdentityKey(component)
249
+ )
201
250
  ) || [];
202
251
 
203
252
  const handleLayoutChange = useCallback((newLayout: LayoutItem[]) => {
@@ -228,19 +277,23 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
228
277
  }
229
278
  };
230
279
 
231
- const handleAddWidget = async (slug: string) => {
280
+ const handleAddWidget = async (slugs: string[]) => {
281
+ if (!slugs.length) return;
282
+
232
283
  try {
233
- await request({
234
- url: `/dashboard-core/widget/${dashboardSlug}`,
235
- method: 'POST',
236
- data: { componentSlug: slug },
237
- });
284
+ for (const slug of slugs) {
285
+ await request({
286
+ url: `/dashboard-core/widget/${dashboardSlug}`,
287
+ method: 'POST',
288
+ data: { componentSlug: slug },
289
+ });
290
+ }
238
291
 
239
292
  await new Promise((resolve) => setTimeout(resolve, 300));
240
293
  await Promise.all([refetchLayout(), refetchComponents()]);
241
294
  setHasChanges(false);
242
295
  } catch (error) {
243
- console.error('Erro ao adicionar widget:', error);
296
+ console.error('Erro ao adicionar widgets:', error);
244
297
  }
245
298
  };
246
299
 
@@ -257,11 +310,65 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
257
310
  }
258
311
  };
259
312
 
313
+ const handleCaptureWidgetPreview = async (
314
+ widgetInstanceId: string,
315
+ componentId: number
316
+ ) => {
317
+ if (!isDevelopment) {
318
+ return;
319
+ }
320
+
321
+ try {
322
+ const widgetElement = document.querySelector(
323
+ `[data-widget-instance-id="${widgetInstanceId}"]`
324
+ ) as HTMLElement | null;
325
+
326
+ if (!widgetElement) {
327
+ throw new Error('Widget element not found for screenshot');
328
+ }
329
+
330
+ const screenshot = await toBlob(widgetElement, {
331
+ cacheBust: true,
332
+ pixelRatio: 2,
333
+ backgroundColor: '#ffffff',
334
+ });
335
+
336
+ if (!screenshot) {
337
+ throw new Error('Failed to generate screenshot blob');
338
+ }
339
+
340
+ const formData = new FormData();
341
+ formData.append(
342
+ 'file',
343
+ screenshot,
344
+ `dashboard-widget-${componentId}-${Date.now()}.png`
345
+ );
346
+
347
+ await request({
348
+ url: `/dashboard-component/${componentId}/preview`,
349
+ method: 'POST',
350
+ data: formData,
351
+ headers: {
352
+ 'Content-Type': 'multipart/form-data',
353
+ },
354
+ });
355
+
356
+ await refetchComponents();
357
+ } catch (error) {
358
+ console.error('Erro ao capturar preview do widget:', error);
359
+ }
360
+ };
361
+
260
362
  const renderWidget = (widget: WidgetLayout) => {
261
363
  return (
262
364
  <WidgetRenderer
263
365
  widget={widget}
264
366
  onRemove={() => handleRemoveWidget(widget.i)}
367
+ onCapture={
368
+ isDevelopment
369
+ ? () => handleCaptureWidgetPreview(widget.i, widget.component_id)
370
+ : undefined
371
+ }
265
372
  />
266
373
  );
267
374
  };
@@ -381,7 +488,17 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
381
488
  )}
382
489
  <AddWidgetSelectorDialog
383
490
  availableComponents={filteredComponents}
491
+ totalItems={totalAvailableComponents}
492
+ currentPage={availableComponentsResponse?.page ?? componentsPage}
493
+ pageSize={
494
+ availableComponentsResponse?.pageSize ?? componentsPageSize
495
+ }
384
496
  isLoading={isLoadingComponents}
497
+ onPageChange={setComponentsPage}
498
+ onPageSizeChange={(nextPageSize) => {
499
+ setComponentsPageSize(nextPageSize);
500
+ setComponentsPage(1);
501
+ }}
385
502
  onAdd={handleAddWidget}
386
503
  currentSlug={dashboardSlug}
387
504
  />
@@ -390,7 +507,7 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
390
507
  </header>
391
508
  <div className="flex flex-1 flex-col gap-4 overflow-auto p-4 pt-0">
392
509
  {widgets.length > 0 ? (
393
- <div className="min-h-[420px] sm:min-h-[520px] lg:min-h-[600px]">
510
+ <div className="min-h-105 sm:min-h-130 lg:min-h-150">
394
511
  <DraggableGrid
395
512
  className="dashboard-grid"
396
513
  layout={layout}
@@ -408,7 +525,7 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
408
525
  </DraggableGrid>
409
526
  </div>
410
527
  ) : (
411
- <div className="flex min-h-[400px] flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
528
+ <div className="flex min-h-100 flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
412
529
  <h3 className="text-lg font-semibold">{t('noWidgetAdded')}</h3>
413
530
  <p className="text-muted-foreground mt-2 text-sm">
414
531
  {t('startAddingWidgets')}
@@ -2,6 +2,7 @@ export interface WidgetLayout {
2
2
  i: string;
3
3
  component_id: number;
4
4
  slug: string;
5
+ library_slug?: string;
5
6
  name: string;
6
7
  description: string;
7
8
  x: number;
@@ -18,6 +19,7 @@ export interface WidgetLayout {
18
19
  export interface DashboardComponent {
19
20
  id: number;
20
21
  slug: string;
22
+ library_slug?: string;
21
23
  path: string;
22
24
  min_width: number;
23
25
  max_width?: number;
@@ -59,4 +61,5 @@ export interface LayoutItem {
59
61
  export interface WidgetRendererProps {
60
62
  widget: WidgetLayout;
61
63
  onRemove: () => void;
64
+ onCapture?: () => void;
62
65
  }
@@ -1,45 +1,158 @@
1
1
  'use client';
2
2
 
3
+ import { Button } from '@/components/ui/button';
3
4
  import { Skeleton } from '@/components/ui/skeleton';
5
+ import { IconCamera } from '@tabler/icons-react';
4
6
  import { useEffect, useState } from 'react';
5
7
  import { DynamicWidget } from '../components';
6
8
  import { WidgetRendererProps } from './types';
7
9
 
8
- export const WidgetRenderer = ({ widget, onRemove }: WidgetRendererProps) => {
10
+ const getWidgetBaseSlug = (slug: string): string => {
11
+ const parts = slug.split('.');
12
+ return parts[parts.length - 1] || slug;
13
+ };
14
+
15
+ const getWidgetLibrarySlug = (
16
+ slug: string,
17
+ librarySlug?: string
18
+ ): string | undefined => {
19
+ if (librarySlug) {
20
+ return librarySlug;
21
+ }
22
+
23
+ const parts = slug.split('.');
24
+ return parts.length > 1 ? parts[0] : 'core';
25
+ };
26
+
27
+ const getWidgetImportCandidates = (
28
+ slug: string,
29
+ librarySlug?: string
30
+ ): string[] => {
31
+ const baseSlug = getWidgetBaseSlug(slug);
32
+ const effectiveLibrarySlug = getWidgetLibrarySlug(slug, librarySlug);
33
+ const libraryPrefixedSlug = effectiveLibrarySlug
34
+ ? `${effectiveLibrarySlug}.${baseSlug}`
35
+ : null;
36
+
37
+ return Array.from(
38
+ new Set(
39
+ [libraryPrefixedSlug, slug, baseSlug].filter(
40
+ (candidate): candidate is string => Boolean(candidate)
41
+ )
42
+ )
43
+ );
44
+ };
45
+
46
+ export const WidgetRenderer = ({
47
+ widget,
48
+ onRemove,
49
+ onCapture,
50
+ }: WidgetRendererProps) => {
9
51
  const [Component, setComponent] = useState<any>(null);
10
52
  const [useFallback, setUseFallback] = useState(false);
11
53
 
12
54
  useEffect(() => {
13
- import(`../components/widgets/${widget.slug}`)
14
- .then((mod) => {
15
- setComponent(() => mod.default);
16
- })
17
- .catch((error) => {
18
- console.warn(
19
- `Widget component not found for slug: ${widget.slug}`,
20
- error
21
- );
55
+ let cancelled = false;
56
+
57
+ setComponent(null);
58
+ setUseFallback(false);
59
+
60
+ const loadWidgetComponent = async () => {
61
+ const candidates = getWidgetImportCandidates(
62
+ widget.slug,
63
+ widget.library_slug
64
+ );
65
+
66
+ for (const candidate of candidates) {
67
+ try {
68
+ const mod = await import(`../components/widgets/${candidate}`);
69
+ if (!cancelled) {
70
+ setComponent(() => mod.default);
71
+ }
72
+ return;
73
+ } catch {
74
+ // Try next candidate.
75
+ }
76
+ }
77
+
78
+ if (!cancelled) {
79
+ console.warn(`Widget component not found for slug: ${widget.slug}`, {
80
+ candidates,
81
+ });
22
82
  setUseFallback(true);
23
- });
24
- }, [widget.slug]);
83
+ }
84
+ };
85
+
86
+ void loadWidgetComponent();
87
+
88
+ return () => {
89
+ cancelled = true;
90
+ };
91
+ }, [widget.slug, widget.library_slug]);
25
92
 
26
93
  if (useFallback) {
27
94
  return (
28
- <DynamicWidget
29
- title={widget.name}
30
- value={0}
31
- description={widget.description || ''}
32
- iconName="settings"
33
- color="#000000"
34
- draggable
35
- onRemove={onRemove}
36
- />
95
+ <div
96
+ className="group relative h-full w-full"
97
+ data-widget-instance-id={widget.i}
98
+ >
99
+ {onCapture ? (
100
+ <Button
101
+ type="button"
102
+ variant="ghost"
103
+ size="icon"
104
+ className="no-drag absolute right-10 top-3 z-20 size-6 cursor-pointer shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
105
+ onClick={(event) => {
106
+ event.stopPropagation();
107
+ event.preventDefault();
108
+ onCapture();
109
+ }}
110
+ >
111
+ <IconCamera className="size-3" />
112
+ </Button>
113
+ ) : null}
114
+ <DynamicWidget
115
+ title={widget.name}
116
+ value={0}
117
+ description={widget.description || ''}
118
+ iconName="settings"
119
+ color="#000000"
120
+ draggable
121
+ onRemove={onRemove}
122
+ />
123
+ </div>
37
124
  );
38
125
  }
39
126
 
40
127
  if (!Component) {
41
- return <Skeleton className="h-full w-full" />;
128
+ return (
129
+ <div className="h-full w-full" data-widget-instance-id={widget.i}>
130
+ <Skeleton className="h-full w-full" />
131
+ </div>
132
+ );
42
133
  }
43
134
 
44
- return <Component widget={widget} onRemove={onRemove} />;
135
+ return (
136
+ <div
137
+ className="group relative h-full w-full"
138
+ data-widget-instance-id={widget.i}
139
+ >
140
+ {onCapture ? (
141
+ <Button
142
+ type="button"
143
+ variant="ghost"
144
+ size="icon"
145
+ className="no-drag absolute right-10 top-3 z-20 size-6 cursor-pointer shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
146
+ onClick={(event) => {
147
+ event.stopPropagation();
148
+ event.preventDefault();
149
+ onCapture();
150
+ }}
151
+ >
152
+ <IconCamera className="size-3" />
153
+ </Button>
154
+ ) : null}
155
+ <Component widget={widget} onRemove={onRemove} />
156
+ </div>
157
+ );
45
158
  };