@hed-hog/core 0.0.297 → 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 (183) hide show
  1. package/dist/auth/auth.controller.d.ts +10 -10
  2. package/dist/auth/auth.service.d.ts +10 -10
  3. package/dist/dashboard/dashboard/dashboard.controller.d.ts +3 -0
  4. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard/dashboard.service.d.ts +3 -0
  6. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +12 -0
  8. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
  9. package/dist/dashboard/dashboard-component/dashboard-component.controller.js +22 -0
  10. package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
  11. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +15 -0
  12. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  13. package/dist/dashboard/dashboard-component/dashboard-component.service.js +110 -3
  14. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  15. package/dist/dashboard/dashboard-component/dto/create.dto.d.ts +1 -0
  16. package/dist/dashboard/dashboard-component/dto/create.dto.d.ts.map +1 -1
  17. package/dist/dashboard/dashboard-component/dto/create.dto.js +5 -0
  18. package/dist/dashboard/dashboard-component/dto/create.dto.js.map +1 -1
  19. package/dist/dashboard/dashboard-component/dto/update.dto.d.ts +1 -0
  20. package/dist/dashboard/dashboard-component/dto/update.dto.d.ts.map +1 -1
  21. package/dist/dashboard/dashboard-component/dto/update.dto.js +5 -0
  22. package/dist/dashboard/dashboard-component/dto/update.dto.js.map +1 -1
  23. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -0
  24. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
  25. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -0
  26. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +21 -1
  28. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  29. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
  30. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  31. package/dist/dashboard/dashboard-core/dashboard-core.module.d.ts.map +1 -1
  32. package/dist/dashboard/dashboard-core/dashboard-core.module.js +6 -1
  33. package/dist/dashboard/dashboard-core/dashboard-core.module.js.map +1 -1
  34. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +180 -2
  35. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  36. package/dist/dashboard/dashboard-core/dashboard-core.service.js +619 -9
  37. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  38. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +1 -0
  39. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  40. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +1 -0
  41. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  42. package/dist/file/file.controller.d.ts.map +1 -1
  43. package/dist/file/file.controller.js +16 -0
  44. package/dist/file/file.controller.js.map +1 -1
  45. package/dist/file/file.service.d.ts +7 -1
  46. package/dist/file/file.service.d.ts.map +1 -1
  47. package/dist/file/file.service.js +38 -1
  48. package/dist/file/file.service.js.map +1 -1
  49. package/dist/file/provider/s3.provider.d.ts +1 -0
  50. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  51. package/dist/file/provider/s3.provider.js +38 -29
  52. package/dist/file/provider/s3.provider.js.map +1 -1
  53. package/dist/oauth/oauth.service.d.ts.map +1 -1
  54. package/dist/oauth/oauth.service.js +2 -1
  55. package/dist/oauth/oauth.service.js.map +1 -1
  56. package/dist/user/constants/user.constants.d.ts +1 -0
  57. package/dist/user/constants/user.constants.d.ts.map +1 -1
  58. package/dist/user/constants/user.constants.js +2 -1
  59. package/dist/user/constants/user.constants.js.map +1 -1
  60. package/dist/user/user.controller.d.ts +10 -10
  61. package/dist/user/user.service.d.ts +30 -30
  62. package/dist/user/user.service.d.ts.map +1 -1
  63. package/dist/user/user.service.js +2 -1
  64. package/dist/user/user.service.js.map +1 -1
  65. package/hedhog/data/dashboard_item.yaml +10 -10
  66. package/hedhog/data/route.yaml +20 -0
  67. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +212 -34
  68. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
  69. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +136 -23
  70. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +266 -85
  71. package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +11 -0
  72. package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +192 -0
  73. package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +226 -0
  74. package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +168 -0
  75. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +199 -0
  76. package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +175 -0
  77. package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +186 -0
  78. package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +196 -0
  79. package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +213 -0
  80. package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +132 -0
  81. package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +236 -0
  82. package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +108 -0
  83. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +66 -0
  84. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +122 -0
  85. package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +63 -0
  86. package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +73 -0
  87. package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +73 -0
  88. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +123 -0
  89. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +118 -0
  90. package/hedhog/frontend/messages/en.json +93 -0
  91. package/hedhog/frontend/messages/pt.json +93 -0
  92. package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
  93. package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
  94. package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
  95. package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
  96. package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
  97. package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
  98. package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
  99. package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
  100. package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
  101. package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
  102. package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
  103. package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
  104. package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
  105. package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
  106. package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
  107. package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
  108. package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
  109. package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
  110. package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
  111. package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
  112. package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
  113. package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
  114. package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
  115. package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
  116. package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
  117. package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
  118. package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
  119. package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
  120. package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
  121. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +33 -29
  122. package/hedhog/frontend/widgets/active-users-card.tsx.ejs +58 -0
  123. package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +223 -0
  124. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/email-notifications.tsx.ejs +85 -61
  125. package/hedhog/frontend/widgets/locale-config.tsx.ejs +168 -0
  126. package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +115 -0
  127. package/hedhog/frontend/widgets/mail-config.tsx.ejs +199 -0
  128. package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +58 -0
  129. package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +149 -0
  130. package/hedhog/frontend/widgets/menus-card.tsx.ejs +58 -0
  131. package/hedhog/frontend/widgets/oauth-config.tsx.ejs +175 -0
  132. package/hedhog/frontend/widgets/permissions-card.tsx.ejs +61 -0
  133. package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +156 -0
  134. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/profile-card.tsx.ejs +3 -3
  135. package/hedhog/frontend/widgets/routes-card.tsx.ejs +58 -0
  136. package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +183 -0
  137. package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +62 -0
  138. package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +57 -0
  139. package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +57 -0
  140. package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +57 -0
  141. package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +57 -0
  142. package/hedhog/frontend/widgets/storage-config.tsx.ejs +196 -0
  143. package/hedhog/frontend/widgets/theme-config.tsx.ejs +213 -0
  144. package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +210 -0
  145. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-roles.tsx.ejs +12 -14
  146. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +1 -1
  147. package/hedhog/table/dashboard_component.yaml +7 -0
  148. package/hedhog/table/mail_sent_user.yaml +75 -0
  149. package/package.json +4 -4
  150. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +36 -12
  151. package/src/dashboard/dashboard-component/dashboard-component.service.ts +150 -3
  152. package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
  153. package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
  154. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
  155. package/src/dashboard/dashboard-core/dashboard-core.module.ts +6 -1
  156. package/src/dashboard/dashboard-core/dashboard-core.service.ts +874 -8
  157. package/src/file/file.controller.ts +37 -13
  158. package/src/file/file.service.ts +47 -5
  159. package/src/file/provider/s3.provider.ts +39 -29
  160. package/src/oauth/oauth.service.ts +8 -7
  161. package/src/user/constants/user.constants.ts +1 -0
  162. package/src/user/user.service.ts +2 -1
  163. package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +0 -309
  164. package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +0 -445
  165. package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +0 -296
  166. package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +0 -340
  167. package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +0 -275
  168. /package/hedhog/frontend/app/dashboard/components/widgets/{active-users-card.tsx.ejs → core.active-users-card.tsx.ejs} +0 -0
  169. /package/hedhog/frontend/app/dashboard/components/widgets/{activity-timeline.tsx.ejs → core.activity-timeline.tsx.ejs} +0 -0
  170. /package/hedhog/frontend/app/dashboard/components/widgets/{login-history-chart.tsx.ejs → core.login-history-chart.tsx.ejs} +0 -0
  171. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-card.tsx.ejs → core.mail-sent-card.tsx.ejs} +0 -0
  172. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-chart.tsx.ejs → core.mail-sent-chart.tsx.ejs} +0 -0
  173. /package/hedhog/frontend/app/dashboard/components/widgets/{menus-card.tsx.ejs → core.menus-card.tsx.ejs} +0 -0
  174. /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-card.tsx.ejs → core.permissions-card.tsx.ejs} +0 -0
  175. /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-chart.tsx.ejs → core.permissions-chart.tsx.ejs} +0 -0
  176. /package/hedhog/frontend/app/dashboard/components/widgets/{routes-card.tsx.ejs → core.routes-card.tsx.ejs} +0 -0
  177. /package/hedhog/frontend/app/dashboard/components/widgets/{session-activity-chart.tsx.ejs → core.session-activity-chart.tsx.ejs} +0 -0
  178. /package/hedhog/frontend/app/dashboard/components/widgets/{sessions-today-card.tsx.ejs → core.sessions-today-card.tsx.ejs} +0 -0
  179. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-access-level.tsx.ejs → core.stat-access-level.tsx.ejs} +0 -0
  180. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-actions-today.tsx.ejs → core.stat-actions-today.tsx.ejs} +0 -0
  181. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-consecutive-days.tsx.ejs → core.stat-consecutive-days.tsx.ejs} +0 -0
  182. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-online-time.tsx.ejs → core.stat-online-time.tsx.ejs} +0 -0
  183. /package/hedhog/frontend/app/dashboard/components/widgets/{user-growth-chart.tsx.ejs → core.user-growth-chart.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,16 +36,112 @@ 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
+
49
+ const USER_STATS_WIDGETS = new Set([
50
+ 'stat-online-time',
51
+ 'stat-actions-today',
52
+ 'stat-consecutive-days',
53
+ 'stat-access-level',
54
+ ]);
55
+
56
+ const USER_POST_HISTORY_WIDGETS = new Set([
57
+ 'account-security',
58
+ 'email-notifications',
59
+ ]);
60
+
61
+ const USER_BOTTOM_WIDGETS = new Set(['user-roles', 'activity-timeline']);
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
+
84
+ const normalizeUserDashboardLayout = (item: WidgetLayout): LayoutItem => {
85
+ const baseSlug = getWidgetBaseSlug(item.slug);
86
+
87
+ const layoutItem: LayoutItem = {
88
+ i: item.i,
89
+ x: item.x,
90
+ y: item.y,
91
+ w: item.w,
92
+ h: item.h,
93
+ minW: item.minW || 1,
94
+ maxW: item.maxW || 12,
95
+ minH: item.minH || 1,
96
+ maxH: item.maxH || 10,
97
+ static: false,
98
+ };
99
+
100
+ if (baseSlug === 'profile-card') {
101
+ layoutItem.h = Math.max(item.h, 4);
102
+ }
103
+
104
+ if (USER_STATS_WIDGETS.has(baseSlug)) {
105
+ layoutItem.y = Math.max(item.y, 4);
106
+ return layoutItem;
107
+ }
108
+
109
+ if (baseSlug === 'login-history-chart') {
110
+ layoutItem.y = Math.max(item.y, 5);
111
+ return layoutItem;
112
+ }
113
+
114
+ if (USER_POST_HISTORY_WIDGETS.has(baseSlug)) {
115
+ layoutItem.y = Math.max(item.y, 9);
116
+ return layoutItem;
117
+ }
118
+
119
+ if (USER_BOTTOM_WIDGETS.has(baseSlug)) {
120
+ layoutItem.y = Math.max(item.y, 14);
121
+ layoutItem.h = item.h + 1;
122
+ return layoutItem;
123
+ }
124
+
125
+ if (layoutItem.y >= 4) {
126
+ layoutItem.y += 1;
127
+ }
128
+
129
+ return layoutItem;
130
+ };
131
+
38
132
  export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
39
133
  const t = useTranslations('core.DashboardPage');
40
134
  const { request } = useApp();
41
135
  const router = useRouter();
42
136
  const isMobile = useIsMobile();
137
+ const isDevelopment = process.env.NODE_ENV === 'development';
43
138
 
44
139
  const [layout, setLayout] = useState<LayoutItem[]>([]);
45
140
  const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
46
141
  const [hasChanges, setHasChanges] = useState(false);
47
142
  const [isSaving, setIsSaving] = useState(false);
143
+ const [componentsPage, setComponentsPage] = useState(1);
144
+ const [componentsPageSize, setComponentsPageSize] = useState(12);
48
145
 
49
146
  const { data: dashboardAccess, isLoading: isCheckingAccess } =
50
147
  useQuery<DashboardAccessResponse>({
@@ -69,17 +166,26 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
69
166
  }, [dashboardAccess, dashboardSlug, router]);
70
167
 
71
168
  const {
72
- data: availableComponents,
169
+ data: availableComponentsResponse,
73
170
  isLoading: isLoadingComponents,
74
171
  refetch: refetchComponents,
75
- } = useQuery<any>({
76
- queryKey: ['dashboard-components'],
172
+ } = useQuery<DashboardComponentsPage>({
173
+ queryKey: [
174
+ 'dashboard-components',
175
+ dashboardSlug,
176
+ componentsPage,
177
+ componentsPageSize,
178
+ ],
77
179
  queryFn: async () => {
78
- const { data } = await request<any>({
180
+ const { data } = await request<DashboardComponentsPage>({
79
181
  url: '/dashboard-component/user',
80
182
  method: 'GET',
183
+ params: {
184
+ page: componentsPage,
185
+ pageSize: componentsPageSize,
186
+ },
81
187
  });
82
- return data.data;
188
+ return data;
83
189
  },
84
190
  enabled: dashboardAccess?.hasAccess ?? false,
85
191
  });
@@ -103,21 +209,22 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
103
209
  useEffect(() => {
104
210
  if (userLayout) {
105
211
  if (userLayout.length > 0) {
106
- const gridLayout = userLayout.map((item) => ({
107
- i: item.i,
108
- x: item.x,
109
- y: item.y,
110
- w: item.w,
111
- h:
112
- dashboardSlug === 'user' && item.slug === 'profile-card'
113
- ? Math.max(item.h, 3)
114
- : item.h,
115
- minW: item.minW || 1,
116
- maxW: item.maxW || 12,
117
- minH: item.minH || 1,
118
- maxH: item.maxH || 10,
119
- static: false,
120
- }));
212
+ const gridLayout = userLayout.map((item) =>
213
+ dashboardSlug === 'user'
214
+ ? normalizeUserDashboardLayout(item)
215
+ : {
216
+ i: item.i,
217
+ x: item.x,
218
+ y: item.y,
219
+ w: item.w,
220
+ h: item.h,
221
+ minW: item.minW || 1,
222
+ maxW: item.maxW || 12,
223
+ minH: item.minH || 1,
224
+ maxH: item.maxH || 10,
225
+ static: false,
226
+ }
227
+ );
121
228
 
122
229
  setLayout(gridLayout);
123
230
  setWidgets(userLayout);
@@ -127,16 +234,19 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
127
234
  }
128
235
  setHasChanges(false);
129
236
  }
130
- }, [userLayout]);
237
+ }, [userLayout, dashboardSlug]);
131
238
 
132
- const componentsToFilter = availableComponents?.data?.length
133
- ? availableComponents.data
134
- : availableComponents || [];
239
+ const componentsToFilter = availableComponentsResponse?.data || [];
240
+ const totalAvailableComponents =
241
+ availableComponentsResponse?.total ?? componentsToFilter.length;
135
242
 
136
243
  const filteredComponents =
137
244
  componentsToFilter.filter(
138
245
  (component: DashboardComponent) =>
139
- !widgets.some((widget) => widget.slug === component.slug)
246
+ !widgets.some(
247
+ (widget) =>
248
+ getWidgetIdentityKey(widget) === getWidgetIdentityKey(component)
249
+ )
140
250
  ) || [];
141
251
 
142
252
  const handleLayoutChange = useCallback((newLayout: LayoutItem[]) => {
@@ -167,19 +277,23 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
167
277
  }
168
278
  };
169
279
 
170
- const handleAddWidget = async (slug: string) => {
280
+ const handleAddWidget = async (slugs: string[]) => {
281
+ if (!slugs.length) return;
282
+
171
283
  try {
172
- await request({
173
- url: `/dashboard-core/widget/${dashboardSlug}`,
174
- method: 'POST',
175
- data: { componentSlug: slug },
176
- });
284
+ for (const slug of slugs) {
285
+ await request({
286
+ url: `/dashboard-core/widget/${dashboardSlug}`,
287
+ method: 'POST',
288
+ data: { componentSlug: slug },
289
+ });
290
+ }
177
291
 
178
292
  await new Promise((resolve) => setTimeout(resolve, 300));
179
293
  await Promise.all([refetchLayout(), refetchComponents()]);
180
294
  setHasChanges(false);
181
295
  } catch (error) {
182
- console.error('Erro ao adicionar widget:', error);
296
+ console.error('Erro ao adicionar widgets:', error);
183
297
  }
184
298
  };
185
299
 
@@ -196,11 +310,65 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
196
310
  }
197
311
  };
198
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
+
199
362
  const renderWidget = (widget: WidgetLayout) => {
200
363
  return (
201
364
  <WidgetRenderer
202
365
  widget={widget}
203
366
  onRemove={() => handleRemoveWidget(widget.i)}
367
+ onCapture={
368
+ isDevelopment
369
+ ? () => handleCaptureWidgetPreview(widget.i, widget.component_id)
370
+ : undefined
371
+ }
204
372
  />
205
373
  );
206
374
  };
@@ -320,7 +488,17 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
320
488
  )}
321
489
  <AddWidgetSelectorDialog
322
490
  availableComponents={filteredComponents}
491
+ totalItems={totalAvailableComponents}
492
+ currentPage={availableComponentsResponse?.page ?? componentsPage}
493
+ pageSize={
494
+ availableComponentsResponse?.pageSize ?? componentsPageSize
495
+ }
323
496
  isLoading={isLoadingComponents}
497
+ onPageChange={setComponentsPage}
498
+ onPageSizeChange={(nextPageSize) => {
499
+ setComponentsPageSize(nextPageSize);
500
+ setComponentsPage(1);
501
+ }}
324
502
  onAdd={handleAddWidget}
325
503
  currentSlug={dashboardSlug}
326
504
  />
@@ -329,7 +507,7 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
329
507
  </header>
330
508
  <div className="flex flex-1 flex-col gap-4 overflow-auto p-4 pt-0">
331
509
  {widgets.length > 0 ? (
332
- <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">
333
511
  <DraggableGrid
334
512
  className="dashboard-grid"
335
513
  layout={layout}
@@ -347,7 +525,7 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
347
525
  </DraggableGrid>
348
526
  </div>
349
527
  ) : (
350
- <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">
351
529
  <h3 className="text-lg font-semibold">{t('noWidgetAdded')}</h3>
352
530
  <p className="text-muted-foreground mt-2 text-sm">
353
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
  };