@hed-hog/core 0.0.299 → 0.0.300

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 (112) hide show
  1. package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -0
  2. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  3. package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -0
  4. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +2 -1
  6. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard-component/dashboard-component.controller.js +6 -3
  8. package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
  9. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +7 -1
  10. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-component/dashboard-component.service.js +76 -33
  12. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  13. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +65 -0
  14. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  15. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +111 -0
  16. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  17. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +69 -0
  18. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  19. package/dist/dashboard/dashboard-core/dashboard-core.service.js +526 -19
  20. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  21. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -0
  22. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  23. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -0
  24. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  25. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
  26. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
  28. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
  29. package/hedhog/data/dashboard.yaml +12 -6
  30. package/hedhog/data/dashboard_component_role.yaml +66 -0
  31. package/hedhog/data/dashboard_role.yaml +2 -8
  32. package/hedhog/data/route.yaml +72 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +333 -128
  34. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +277 -53
  35. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +179 -231
  36. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
  37. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1389 -0
  38. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
  39. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
  40. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
  41. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
  42. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
  43. package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
  44. package/hedhog/frontend/messages/en.json +112 -2
  45. package/hedhog/frontend/messages/pt.json +111 -1
  46. package/hedhog/frontend/widgets/account-security.tsx.ejs +1 -1
  47. package/hedhog/frontend/widgets/active-users-card.tsx.ejs +2 -2
  48. package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +1 -1
  49. package/hedhog/frontend/widgets/email-notifications.tsx.ejs +1 -1
  50. package/hedhog/frontend/widgets/locale-config.tsx.ejs +1 -1
  51. package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +1 -1
  52. package/hedhog/frontend/widgets/mail-config.tsx.ejs +1 -1
  53. package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +2 -2
  54. package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +1 -1
  55. package/hedhog/frontend/widgets/menus-card.tsx.ejs +2 -2
  56. package/hedhog/frontend/widgets/oauth-config.tsx.ejs +1 -1
  57. package/hedhog/frontend/widgets/permissions-card.tsx.ejs +2 -2
  58. package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +1 -1
  59. package/hedhog/frontend/widgets/profile-card.tsx.ejs +1 -1
  60. package/hedhog/frontend/widgets/routes-card.tsx.ejs +2 -2
  61. package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +1 -1
  62. package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +2 -2
  63. package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +1 -1
  64. package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +1 -1
  65. package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +1 -1
  66. package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +1 -1
  67. package/hedhog/frontend/widgets/storage-config.tsx.ejs +1 -1
  68. package/hedhog/frontend/widgets/theme-config.tsx.ejs +1 -1
  69. package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +1 -1
  70. package/hedhog/frontend/widgets/user-roles.tsx.ejs +1 -1
  71. package/hedhog/frontend/widgets/user-sessions.tsx.ejs +1 -1
  72. package/hedhog/table/dashboard.yaml +6 -0
  73. package/package.json +5 -5
  74. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +15 -2
  75. package/src/dashboard/dashboard-component/dashboard-component.service.ts +107 -43
  76. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +112 -1
  77. package/src/dashboard/dashboard-core/dashboard-core.service.ts +674 -19
  78. package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +0 -11
  79. package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +0 -192
  80. package/hedhog/frontend/app/dashboard/components/widgets/core.active-users-card.tsx.ejs +0 -58
  81. package/hedhog/frontend/app/dashboard/components/widgets/core.activity-timeline.tsx.ejs +0 -223
  82. package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +0 -226
  83. package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +0 -168
  84. package/hedhog/frontend/app/dashboard/components/widgets/core.login-history-chart.tsx.ejs +0 -115
  85. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +0 -199
  86. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-card.tsx.ejs +0 -58
  87. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-chart.tsx.ejs +0 -149
  88. package/hedhog/frontend/app/dashboard/components/widgets/core.menus-card.tsx.ejs +0 -58
  89. package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +0 -175
  90. package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-card.tsx.ejs +0 -61
  91. package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-chart.tsx.ejs +0 -156
  92. package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +0 -186
  93. package/hedhog/frontend/app/dashboard/components/widgets/core.routes-card.tsx.ejs +0 -58
  94. package/hedhog/frontend/app/dashboard/components/widgets/core.session-activity-chart.tsx.ejs +0 -183
  95. package/hedhog/frontend/app/dashboard/components/widgets/core.sessions-today-card.tsx.ejs +0 -62
  96. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-access-level.tsx.ejs +0 -57
  97. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-actions-today.tsx.ejs +0 -57
  98. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-consecutive-days.tsx.ejs +0 -57
  99. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-online-time.tsx.ejs +0 -57
  100. package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +0 -196
  101. package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +0 -213
  102. package/hedhog/frontend/app/dashboard/components/widgets/core.user-growth-chart.tsx.ejs +0 -210
  103. package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +0 -132
  104. package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +0 -236
  105. package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +0 -108
  106. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +0 -66
  107. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +0 -122
  108. package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +0 -63
  109. package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +0 -73
  110. package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +0 -73
  111. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +0 -123
  112. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +0 -118
@@ -1,11 +1,27 @@
1
1
  'use client';
2
2
 
3
3
  import { Button } from '@/components/ui/button';
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '@/components/ui/dialog';
4
12
  import { Skeleton } from '@/components/ui/skeleton';
5
- import { IconCamera } from '@tabler/icons-react';
6
- import { useEffect, useState } from 'react';
13
+ import { IconCamera, IconScreenshot } from '@tabler/icons-react';
14
+ import { toBlob } from 'html-to-image';
15
+ import { useTranslations } from 'next-intl';
16
+ import {
17
+ type ComponentType,
18
+ type MouseEvent,
19
+ useEffect,
20
+ useState,
21
+ } from 'react';
22
+ import { toast } from 'sonner';
7
23
  import { DynamicWidget } from '../components';
8
- import { WidgetRendererProps } from './types';
24
+ import { WidgetLayout, WidgetRendererProps } from './types';
9
25
 
10
26
  const getWidgetBaseSlug = (slug: string): string => {
11
27
  const parts = slug.split('.');
@@ -43,13 +59,29 @@ const getWidgetImportCandidates = (
43
59
  );
44
60
  };
45
61
 
62
+ const importWidgetComponent = (candidate: string) =>
63
+ import(`@/components/widgets/${candidate}`);
64
+
65
+ type LoadedWidgetComponent = ComponentType<{
66
+ widget: WidgetLayout;
67
+ onRemove: () => void;
68
+ }>;
69
+
46
70
  export const WidgetRenderer = ({
47
71
  widget,
48
72
  onRemove,
49
73
  onCapture,
50
74
  }: WidgetRendererProps) => {
51
- const [Component, setComponent] = useState<any>(null);
75
+ const t = useTranslations('core.WidgetRenderer');
76
+ const [Component, setComponent] = useState<LoadedWidgetComponent | null>(
77
+ null
78
+ );
52
79
  const [useFallback, setUseFallback] = useState(false);
80
+ const [isScreenshotDialogOpen, setIsScreenshotDialogOpen] = useState(false);
81
+ const [isGeneratingScreenshot, setIsGeneratingScreenshot] = useState(false);
82
+ const [isSharingScreenshot, setIsSharingScreenshot] = useState(false);
83
+ const [screenshotUrl, setScreenshotUrl] = useState<string | null>(null);
84
+ const [screenshotBlob, setScreenshotBlob] = useState<Blob | null>(null);
53
85
 
54
86
  useEffect(() => {
55
87
  let cancelled = false;
@@ -65,7 +97,7 @@ export const WidgetRenderer = ({
65
97
 
66
98
  for (const candidate of candidates) {
67
99
  try {
68
- const mod = await import(`../components/widgets/${candidate}`);
100
+ const mod = await importWidgetComponent(candidate);
69
101
  if (!cancelled) {
70
102
  setComponent(() => mod.default);
71
103
  }
@@ -90,59 +122,148 @@ export const WidgetRenderer = ({
90
122
  };
91
123
  }, [widget.slug, widget.library_slug]);
92
124
 
93
- if (useFallback) {
94
- return (
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>
124
- );
125
- }
125
+ useEffect(() => {
126
+ return () => {
127
+ if (screenshotUrl) {
128
+ URL.revokeObjectURL(screenshotUrl);
129
+ }
130
+ };
131
+ }, [screenshotUrl]);
126
132
 
127
- if (!Component) {
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
- );
133
- }
133
+ const getScreenshotFileName = () => {
134
+ const baseSlug = getWidgetBaseSlug(widget.slug)
135
+ .replace(/[^a-z0-9-_]+/gi, '-')
136
+ .toLowerCase();
134
137
 
135
- return (
136
- <div
137
- className="group relative h-full w-full"
138
- data-widget-instance-id={widget.i}
139
- >
138
+ return `dashboard-widget-${baseSlug}-${Date.now()}.png`;
139
+ };
140
+
141
+ const generateScreenshotBlob = async () => {
142
+ const widgetElement = document.querySelector(
143
+ `[data-widget-instance-id="${widget.i}"]`
144
+ ) as HTMLElement | null;
145
+
146
+ if (!widgetElement) {
147
+ throw new Error('Widget element not found for screenshot');
148
+ }
149
+
150
+ const screenshot = await toBlob(widgetElement, {
151
+ cacheBust: true,
152
+ pixelRatio: 2,
153
+ backgroundColor: '#ffffff',
154
+ filter: (node) =>
155
+ !(node instanceof HTMLElement && node.dataset.widgetAction === 'true'),
156
+ });
157
+
158
+ if (!screenshot) {
159
+ throw new Error('Failed to generate screenshot blob');
160
+ }
161
+
162
+ return screenshot;
163
+ };
164
+
165
+ const handleOpenScreenshotDialog = async (
166
+ event: MouseEvent<HTMLButtonElement>
167
+ ) => {
168
+ event.stopPropagation();
169
+ event.preventDefault();
170
+
171
+ setIsGeneratingScreenshot(true);
172
+
173
+ try {
174
+ const screenshot = await generateScreenshotBlob();
175
+ const nextScreenshotUrl = URL.createObjectURL(screenshot);
176
+
177
+ setScreenshotBlob(screenshot);
178
+ setScreenshotUrl((currentUrl) => {
179
+ if (currentUrl) {
180
+ URL.revokeObjectURL(currentUrl);
181
+ }
182
+
183
+ return nextScreenshotUrl;
184
+ });
185
+ setIsScreenshotDialogOpen(true);
186
+ } catch (error) {
187
+ console.error('Erro ao gerar print do widget:', error);
188
+ toast.error(t('generateScreenshotError'));
189
+ } finally {
190
+ setIsGeneratingScreenshot(false);
191
+ }
192
+ };
193
+
194
+ const handleDownloadScreenshot = () => {
195
+ if (!screenshotBlob) {
196
+ toast.error(t('generateAgainToDownload'));
197
+ return;
198
+ }
199
+
200
+ const downloadUrl = URL.createObjectURL(screenshotBlob);
201
+ const link = document.createElement('a');
202
+ link.href = downloadUrl;
203
+ link.download = getScreenshotFileName();
204
+ document.body.appendChild(link);
205
+ link.click();
206
+ document.body.removeChild(link);
207
+ URL.revokeObjectURL(downloadUrl);
208
+ };
209
+
210
+ const handleShareScreenshot = async () => {
211
+ if (!screenshotBlob) {
212
+ toast.error(t('generateAgainToShare'));
213
+ return;
214
+ }
215
+
216
+ if (
217
+ typeof navigator === 'undefined' ||
218
+ typeof navigator.share !== 'function' ||
219
+ typeof File === 'undefined'
220
+ ) {
221
+ toast.error(t('shareNotSupported'));
222
+ return;
223
+ }
224
+
225
+ setIsSharingScreenshot(true);
226
+
227
+ try {
228
+ const file = new File([screenshotBlob], getScreenshotFileName(), {
229
+ type: screenshotBlob.type || 'image/png',
230
+ });
231
+
232
+ if (
233
+ typeof navigator.canShare === 'function' &&
234
+ !navigator.canShare({ files: [file] })
235
+ ) {
236
+ throw new Error('Browser does not support sharing this file');
237
+ }
238
+
239
+ await navigator.share({
240
+ title: widget.name,
241
+ text: t('shareText', { name: widget.name }),
242
+ files: [file],
243
+ });
244
+ } catch (error) {
245
+ if (error instanceof DOMException && error.name === 'AbortError') {
246
+ return;
247
+ }
248
+
249
+ console.error('Erro ao compartilhar print do widget:', error);
250
+ toast.error(t('shareImageError'));
251
+ } finally {
252
+ setIsSharingScreenshot(false);
253
+ }
254
+ };
255
+
256
+ const actionButtons = (
257
+ <>
140
258
  {onCapture ? (
141
259
  <Button
142
260
  type="button"
143
261
  variant="ghost"
144
262
  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"
263
+ className="no-drag absolute right-17 top-3 z-30 size-6 cursor-pointer shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
264
+ title={t('saveWidgetPreview')}
265
+ aria-label={t('saveWidgetPreview')}
266
+ data-widget-action="true"
146
267
  onClick={(event) => {
147
268
  event.stopPropagation();
148
269
  event.preventDefault();
@@ -152,7 +273,110 @@ export const WidgetRenderer = ({
152
273
  <IconCamera className="size-3" />
153
274
  </Button>
154
275
  ) : null}
155
- <Component widget={widget} onRemove={onRemove} />
156
- </div>
276
+ <Button
277
+ type="button"
278
+ variant="ghost"
279
+ size="icon"
280
+ className="no-drag absolute right-10 top-3 z-30 size-6 cursor-pointer shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
281
+ title={
282
+ isGeneratingScreenshot
283
+ ? t('generatingWidgetScreenshot')
284
+ : t('generateWidgetScreenshot')
285
+ }
286
+ aria-label={
287
+ isGeneratingScreenshot
288
+ ? t('generatingWidgetScreenshot')
289
+ : t('generateWidgetScreenshot')
290
+ }
291
+ data-widget-action="true"
292
+ onClick={(event) => {
293
+ void handleOpenScreenshotDialog(event);
294
+ }}
295
+ disabled={isGeneratingScreenshot}
296
+ >
297
+ <IconScreenshot className="size-3" />
298
+ </Button>
299
+ </>
300
+ );
301
+
302
+ if (!Component && !useFallback) {
303
+ return (
304
+ <div className="h-full w-full" data-widget-instance-id={widget.i}>
305
+ <Skeleton className="h-full w-full" />
306
+ </div>
307
+ );
308
+ }
309
+
310
+ return (
311
+ <>
312
+ <div
313
+ className="group relative h-full w-full"
314
+ data-widget-instance-id={widget.i}
315
+ >
316
+ {actionButtons}
317
+ {useFallback ? (
318
+ <DynamicWidget
319
+ title={widget.name}
320
+ value={0}
321
+ description={widget.description || ''}
322
+ iconName="settings"
323
+ color="#000000"
324
+ draggable
325
+ onRemove={onRemove}
326
+ />
327
+ ) : Component ? (
328
+ <Component widget={widget} onRemove={onRemove} />
329
+ ) : null}
330
+ </div>
331
+
332
+ <Dialog
333
+ open={isScreenshotDialogOpen}
334
+ onOpenChange={setIsScreenshotDialogOpen}
335
+ >
336
+ <DialogContent className="sm:max-w-3xl">
337
+ <DialogHeader>
338
+ <DialogTitle>{t('screenshotTitle')}</DialogTitle>
339
+ <DialogDescription>{t('screenshotDescription')}</DialogDescription>
340
+ </DialogHeader>
341
+
342
+ <div className="overflow-hidden rounded-lg border bg-muted/30">
343
+ {screenshotUrl ? (
344
+ <img
345
+ src={screenshotUrl}
346
+ alt={t('screenshotAlt', { name: widget.name })}
347
+ className="max-h-[70vh] w-full object-contain"
348
+ />
349
+ ) : (
350
+ <div className="flex h-60 items-center justify-center p-4">
351
+ <Skeleton className="h-full w-full" />
352
+ </div>
353
+ )}
354
+ </div>
355
+
356
+ <DialogFooter className="gap-2 sm:justify-between">
357
+ <p className="text-muted-foreground text-xs">{widget.name}</p>
358
+ <div className="flex flex-wrap gap-2">
359
+ <Button
360
+ type="button"
361
+ variant="outline"
362
+ onClick={() => {
363
+ void handleShareScreenshot();
364
+ }}
365
+ disabled={!screenshotBlob || isSharingScreenshot}
366
+ >
367
+ {isSharingScreenshot ? t('sharing') : t('share')}
368
+ </Button>
369
+ <Button
370
+ type="button"
371
+ onClick={handleDownloadScreenshot}
372
+ disabled={!screenshotBlob}
373
+ >
374
+ {t('downloadImage')}
375
+ </Button>
376
+ </div>
377
+ </DialogFooter>
378
+ </DialogContent>
379
+ </Dialog>
380
+ </>
157
381
  );
158
382
  };