@hed-hog/core 0.0.298 → 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 (123) hide show
  1. package/dist/dashboard/dashboard/dashboard.controller.d.ts +9 -0
  2. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  3. package/dist/dashboard/dashboard/dashboard.service.d.ts +9 -0
  4. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +14 -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 +28 -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 +22 -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 +185 -35
  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 +72 -1
  26. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +111 -0
  28. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  29. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +76 -1
  30. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  31. package/dist/dashboard/dashboard-core/dashboard-core.service.js +614 -23
  32. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  33. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +3 -0
  34. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  35. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +3 -0
  36. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  37. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
  38. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
  39. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
  40. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
  41. package/hedhog/data/dashboard.yaml +12 -6
  42. package/hedhog/data/dashboard_component_role.yaml +66 -0
  43. package/hedhog/data/dashboard_item.yaml +1 -1
  44. package/hedhog/data/dashboard_role.yaml +2 -8
  45. package/hedhog/data/route.yaml +84 -0
  46. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +457 -135
  47. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
  48. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +365 -28
  49. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +376 -247
  50. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
  51. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1389 -0
  52. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
  53. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
  54. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
  55. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
  56. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
  57. package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
  58. package/hedhog/frontend/messages/en.json +115 -2
  59. package/hedhog/frontend/messages/pt.json +114 -1
  60. package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
  61. package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
  62. package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
  63. package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
  64. package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
  65. package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
  66. package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
  67. package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
  68. package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
  69. package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
  70. package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
  71. package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
  72. package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
  73. package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
  74. package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
  75. package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
  76. package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
  77. package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
  78. package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
  79. package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
  80. package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
  81. package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
  82. package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
  83. package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
  84. package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
  85. package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
  86. package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
  87. package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
  88. package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
  89. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +34 -30
  90. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/active-users-card.tsx.ejs +2 -2
  91. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/activity-timeline.tsx.ejs +1 -1
  92. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/email-notifications.tsx.ejs +1 -1
  93. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/locale-config.tsx.ejs +1 -1
  94. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/login-history-chart.tsx.ejs +1 -1
  95. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/mail-config.tsx.ejs +1 -1
  96. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/mail-sent-card.tsx.ejs +2 -2
  97. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/mail-sent-chart.tsx.ejs +1 -1
  98. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/menus-card.tsx.ejs +2 -2
  99. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/oauth-config.tsx.ejs +1 -1
  100. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/permissions-card.tsx.ejs +2 -2
  101. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/permissions-chart.tsx.ejs +1 -1
  102. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/profile-card.tsx.ejs +1 -1
  103. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/routes-card.tsx.ejs +2 -2
  104. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/session-activity-chart.tsx.ejs +1 -1
  105. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/sessions-today-card.tsx.ejs +2 -2
  106. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-access-level.tsx.ejs +1 -1
  107. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-actions-today.tsx.ejs +1 -1
  108. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-consecutive-days.tsx.ejs +1 -1
  109. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-online-time.tsx.ejs +1 -1
  110. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/storage-config.tsx.ejs +1 -1
  111. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/theme-config.tsx.ejs +1 -1
  112. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-growth-chart.tsx.ejs +1 -1
  113. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-roles.tsx.ejs +1 -1
  114. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +2 -2
  115. package/hedhog/table/dashboard.yaml +6 -0
  116. package/hedhog/table/dashboard_component.yaml +7 -0
  117. package/package.json +5 -5
  118. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +51 -14
  119. package/src/dashboard/dashboard-component/dashboard-component.service.ts +254 -43
  120. package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
  121. package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
  122. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +112 -1
  123. package/src/dashboard/dashboard-core/dashboard-core.service.ts +782 -24
@@ -430,6 +430,115 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
430
430
  }
431
431
  return value.map((provider) => String(provider).toLowerCase());
432
432
  }
433
+ slugifyDashboardName(value) {
434
+ const normalized = value
435
+ .normalize('NFD')
436
+ .replace(/[\u0300-\u036f]/g, '')
437
+ .toLowerCase()
438
+ .replace(/[^a-z0-9]+/g, '-')
439
+ .replace(/^-+|-+$/g, '');
440
+ return normalized || `dashboard-${Date.now()}`;
441
+ }
442
+ getDashboardDisplayName(dashboard) {
443
+ var _a, _b;
444
+ return ((_b = (_a = dashboard.dashboard_locale) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.name) || dashboard.slug;
445
+ }
446
+ async buildUniqueDashboardSlug(baseSlug) {
447
+ let slug = baseSlug;
448
+ let suffix = 2;
449
+ while (await this.prismaService.dashboard.findFirst({
450
+ where: { slug },
451
+ select: { id: true },
452
+ })) {
453
+ slug = `${baseSlug}-${suffix}`;
454
+ suffix += 1;
455
+ }
456
+ return slug;
457
+ }
458
+ async getDashboardUserOrThrow(userId, slug, locale) {
459
+ const dashboardUser = await this.prismaService.dashboard_user.findFirst({
460
+ where: {
461
+ user_id: userId,
462
+ dashboard: { slug },
463
+ },
464
+ include: {
465
+ dashboard: {
466
+ include: {
467
+ dashboard_locale: {
468
+ where: {
469
+ locale: {
470
+ code: locale,
471
+ },
472
+ },
473
+ },
474
+ },
475
+ },
476
+ },
477
+ });
478
+ if (!dashboardUser) {
479
+ throw new common_1.ForbiddenException((0, api_locale_1.getLocaleText)('dashboardNotFound', locale, 'Dashboard not found.'));
480
+ }
481
+ return dashboardUser;
482
+ }
483
+ async getUserRoleIds(userId) {
484
+ const roleUsers = await this.prismaService.role_user.findMany({
485
+ where: { user_id: userId },
486
+ select: { role_id: true },
487
+ });
488
+ return roleUsers.map((roleUser) => roleUser.role_id);
489
+ }
490
+ async getAccessibleTemplateOrThrow(userId, templateSlug, locale) {
491
+ const userRoleIds = await this.getUserRoleIds(userId);
492
+ const templateAccessFilter = userRoleIds.length > 0
493
+ ? {
494
+ OR: [
495
+ {
496
+ dashboard_role: {
497
+ some: {
498
+ role_id: {
499
+ in: userRoleIds,
500
+ },
501
+ },
502
+ },
503
+ },
504
+ {
505
+ dashboard_role: {
506
+ none: {},
507
+ },
508
+ },
509
+ ],
510
+ }
511
+ : {
512
+ dashboard_role: {
513
+ none: {},
514
+ },
515
+ };
516
+ const template = await this.prismaService.dashboard.findFirst({
517
+ where: Object.assign({ slug: templateSlug, is_template: true }, templateAccessFilter),
518
+ include: {
519
+ dashboard_locale: {
520
+ where: {
521
+ locale: {
522
+ code: locale,
523
+ },
524
+ },
525
+ },
526
+ dashboard_item: {
527
+ select: {
528
+ component_id: true,
529
+ width: true,
530
+ height: true,
531
+ x_axis: true,
532
+ y_axis: true,
533
+ },
534
+ },
535
+ },
536
+ });
537
+ if (!template) {
538
+ throw new common_1.ForbiddenException((0, api_locale_1.getLocaleText)('dashboardNotFound', locale, 'Dashboard template not found.'));
539
+ }
540
+ return template;
541
+ }
433
542
  async getHome(userId, locale) {
434
543
  const user = await this.prismaService.user.findUnique({
435
544
  where: { id: userId },
@@ -739,6 +848,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
739
848
  i: `widget-${item.id}`,
740
849
  component_id: item.component_id,
741
850
  slug: component.slug,
851
+ library_slug: component.library_slug,
742
852
  name: (locale === null || locale === void 0 ? void 0 : locale.name) || component.slug,
743
853
  description: (locale === null || locale === void 0 ? void 0 : locale.description) || '',
744
854
  x: item.x_axis,
@@ -770,26 +880,29 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
770
880
  if (!canAccess) {
771
881
  throw new common_1.ForbiddenException('Access denied to this dashboard');
772
882
  }
773
- for (const item of layout) {
774
- const itemId = parseInt(item.i.replace('widget-', ''));
775
- const dashboardItem = await this.prismaService.dashboard_item.findFirst({
776
- where: {
777
- id: itemId,
778
- dashboard_id: dashboard.id,
779
- },
780
- });
781
- if (!dashboardItem) {
782
- continue;
883
+ const layoutUpdates = layout.flatMap((item) => {
884
+ const itemId = Number.parseInt(item.i.replace('widget-', ''), 10);
885
+ if (Number.isNaN(itemId)) {
886
+ this.logger.warn(`Skipping dashboard layout item with invalid id: ${item.i}`);
887
+ return [];
783
888
  }
784
- await this.prismaService.dashboard_item.update({
785
- where: { id: dashboardItem.id },
786
- data: {
787
- x_axis: item.x,
788
- y_axis: item.y,
789
- width: item.w,
790
- height: item.h,
791
- },
792
- });
889
+ return [
890
+ this.prismaService.dashboard_item.updateMany({
891
+ where: {
892
+ id: itemId,
893
+ dashboard_id: dashboard.id,
894
+ },
895
+ data: {
896
+ x_axis: item.x,
897
+ y_axis: item.y,
898
+ width: item.w,
899
+ height: item.h,
900
+ },
901
+ }),
902
+ ];
903
+ });
904
+ if (layoutUpdates.length > 0) {
905
+ await this.prismaService.$transaction(layoutUpdates);
793
906
  }
794
907
  return { success: true };
795
908
  }
@@ -811,8 +924,44 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
811
924
  if (!canAccess) {
812
925
  throw new common_1.ForbiddenException('Access denied to this dashboard');
813
926
  }
927
+ const userRoles = await this.prismaService.role_user.findMany({
928
+ where: { user_id: userId },
929
+ select: { role_id: true },
930
+ });
931
+ const userRoleIds = userRoles.map((item) => item.role_id);
932
+ if (userRoleIds.length === 0) {
933
+ throw new common_1.ForbiddenException('Access denied to this component');
934
+ }
935
+ const slugParts = componentSlug.split('.').filter(Boolean);
936
+ const requestedSlug = slugParts.length > 0 ? slugParts[slugParts.length - 1] : componentSlug;
937
+ const requestedLibrarySlug = slugParts.length > 1 ? slugParts[0] : undefined;
814
938
  const component = await this.prismaService.dashboard_component.findFirst({
815
- where: { slug: componentSlug },
939
+ where: {
940
+ AND: [
941
+ requestedLibrarySlug
942
+ ? {
943
+ OR: [
944
+ { slug: componentSlug },
945
+ {
946
+ slug: requestedSlug,
947
+ library_slug: requestedLibrarySlug,
948
+ },
949
+ ],
950
+ }
951
+ : {
952
+ OR: [{ slug: componentSlug }, { slug: requestedSlug }],
953
+ },
954
+ {
955
+ dashboard_component_role: {
956
+ some: {
957
+ role_id: {
958
+ in: userRoleIds,
959
+ },
960
+ },
961
+ },
962
+ },
963
+ ],
964
+ },
816
965
  include: {
817
966
  dashboard_component_locale: {
818
967
  where: {
@@ -824,7 +973,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
824
973
  },
825
974
  });
826
975
  if (!component) {
827
- throw new Error(`Component with slug '${componentSlug}' not found`);
976
+ throw new common_1.ForbiddenException('Access denied to this component');
828
977
  }
829
978
  let dashboardItem = await this.prismaService.dashboard_item.findFirst({
830
979
  where: {
@@ -833,6 +982,16 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
833
982
  },
834
983
  });
835
984
  if (!dashboardItem) {
985
+ const dashboardItems = await this.prismaService.dashboard_item.findMany({
986
+ where: {
987
+ dashboard_id: dashboard.id,
988
+ },
989
+ select: {
990
+ y_axis: true,
991
+ height: true,
992
+ },
993
+ });
994
+ const nextAvailableY = dashboardItems.reduce((maxY, item) => Math.max(maxY, item.y_axis + item.height), 0);
836
995
  dashboardItem = await this.prismaService.dashboard_item.create({
837
996
  data: {
838
997
  dashboard_id: dashboard.id,
@@ -840,7 +999,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
840
999
  width: component.width,
841
1000
  height: component.height,
842
1001
  x_axis: 0,
843
- y_axis: 0,
1002
+ y_axis: nextAvailableY,
844
1003
  },
845
1004
  });
846
1005
  }
@@ -849,6 +1008,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
849
1008
  i: `widget-${dashboardItem.id}`,
850
1009
  component_id: component.id,
851
1010
  slug: component.slug,
1011
+ library_slug: component.library_slug,
852
1012
  name: (locale === null || locale === void 0 ? void 0 : locale.name) || component.slug,
853
1013
  description: (locale === null || locale === void 0 ? void 0 : locale.description) || '',
854
1014
  x: dashboardItem.x_axis,
@@ -863,7 +1023,43 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
863
1023
  };
864
1024
  }
865
1025
  async removeWidgetFromUserDashboard(userId, slug, widgetId) {
866
- throw new Error('Not implemented yet');
1026
+ const dashboard = await this.prismaService.dashboard.findFirst({
1027
+ where: { slug },
1028
+ });
1029
+ if (!dashboard) {
1030
+ throw new Error(`Dashboard with slug '${slug}' not found`);
1031
+ }
1032
+ const canAccess = await this.prismaService.dashboard_user.findFirst({
1033
+ where: {
1034
+ dashboard_id: dashboard.id,
1035
+ user_id: userId,
1036
+ },
1037
+ select: { id: true },
1038
+ });
1039
+ if (!canAccess) {
1040
+ throw new common_1.ForbiddenException('Access denied to this dashboard');
1041
+ }
1042
+ const parsedWidgetId = Number(widgetId.replace(/^widget-/, ''));
1043
+ if (!Number.isInteger(parsedWidgetId) || parsedWidgetId <= 0) {
1044
+ throw new common_1.BadRequestException('Invalid widget id');
1045
+ }
1046
+ const dashboardItem = await this.prismaService.dashboard_item.findFirst({
1047
+ where: {
1048
+ id: parsedWidgetId,
1049
+ dashboard_id: dashboard.id,
1050
+ },
1051
+ select: { id: true },
1052
+ });
1053
+ if (!dashboardItem) {
1054
+ throw new common_1.BadRequestException('Widget not found in this dashboard');
1055
+ }
1056
+ await this.prismaService.dashboard_item.delete({
1057
+ where: { id: dashboardItem.id },
1058
+ });
1059
+ return {
1060
+ success: true,
1061
+ removedWidgetId: `widget-${dashboardItem.id}`,
1062
+ };
867
1063
  }
868
1064
  async checkDashboardAccess(userId, slug, locale) {
869
1065
  var _a;
@@ -904,6 +1100,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
904
1100
  };
905
1101
  }
906
1102
  async getUserDashboards(userId, locale) {
1103
+ await this.getHome(userId, locale);
907
1104
  const dashboardUsers = await this.prismaService.dashboard_user.findMany({
908
1105
  where: { user_id: userId },
909
1106
  include: {
@@ -919,6 +1116,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
919
1116
  },
920
1117
  },
921
1118
  },
1119
+ orderBy: [{ is_home: 'desc' }, { id: 'asc' }],
922
1120
  });
923
1121
  const uniqueByDashboardId = new Map();
924
1122
  for (const dashboardUser of dashboardUsers) {
@@ -929,9 +1127,402 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
929
1127
  return Array.from(uniqueByDashboardId.values()).map((dashboardUser) => ({
930
1128
  id: dashboardUser.dashboard.id,
931
1129
  slug: dashboardUser.dashboard.slug,
1130
+ name: this.getDashboardDisplayName(dashboardUser.dashboard),
1131
+ icon: dashboardUser.dashboard.icon,
1132
+ is_home: dashboardUser.is_home,
932
1133
  dashboard_locale: dashboardUser.dashboard.dashboard_locale,
933
1134
  }));
934
1135
  }
1136
+ async getAvailableTemplates(userId, locale) {
1137
+ const userRoleIds = await this.getUserRoleIds(userId);
1138
+ const templateAccessFilter = userRoleIds.length > 0
1139
+ ? {
1140
+ OR: [
1141
+ {
1142
+ dashboard_role: {
1143
+ some: {
1144
+ role_id: {
1145
+ in: userRoleIds,
1146
+ },
1147
+ },
1148
+ },
1149
+ },
1150
+ {
1151
+ dashboard_role: {
1152
+ none: {},
1153
+ },
1154
+ },
1155
+ ],
1156
+ }
1157
+ : {
1158
+ dashboard_role: {
1159
+ none: {},
1160
+ },
1161
+ };
1162
+ const templates = await this.prismaService.dashboard.findMany({
1163
+ where: Object.assign({ is_template: true }, templateAccessFilter),
1164
+ include: {
1165
+ dashboard_locale: {
1166
+ where: {
1167
+ locale: {
1168
+ code: locale,
1169
+ },
1170
+ },
1171
+ },
1172
+ dashboard_item: {
1173
+ select: { id: true },
1174
+ },
1175
+ },
1176
+ orderBy: [{ id: 'asc' }],
1177
+ });
1178
+ return templates.map((template) => ({
1179
+ id: template.id,
1180
+ slug: template.slug,
1181
+ name: this.getDashboardDisplayName(template),
1182
+ icon: template.icon,
1183
+ itemCount: template.dashboard_item.length,
1184
+ }));
1185
+ }
1186
+ async createUserDashboard(userId, data, locale) {
1187
+ var _a, _b, _c, _d;
1188
+ const templateSlug = this.toNullableString(data === null || data === void 0 ? void 0 : data.templateSlug);
1189
+ const template = templateSlug
1190
+ ? await this.getAccessibleTemplateOrThrow(userId, templateSlug, locale)
1191
+ : null;
1192
+ const templateName = template ? this.getDashboardDisplayName(template) : null;
1193
+ const name = this.toNullableString(data === null || data === void 0 ? void 0 : data.name) || templateName;
1194
+ if (!name) {
1195
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'Dashboard name is required.'));
1196
+ }
1197
+ const requestedSlug = this.toNullableString(data === null || data === void 0 ? void 0 : data.slug);
1198
+ const baseSlug = this.slugifyDashboardName(requestedSlug || name);
1199
+ const slug = await this.buildUniqueDashboardSlug(baseSlug);
1200
+ const icon = (data === null || data === void 0 ? void 0 : data.icon) === undefined
1201
+ ? (_a = template === null || template === void 0 ? void 0 : template.icon) !== null && _a !== void 0 ? _a : null
1202
+ : (_c = (_b = this.toNullableString(data === null || data === void 0 ? void 0 : data.icon)) !== null && _b !== void 0 ? _b : template === null || template === void 0 ? void 0 : template.icon) !== null && _c !== void 0 ? _c : null;
1203
+ const [localeRecord, existingCount] = await Promise.all([
1204
+ this.prismaService.locale.findFirst({
1205
+ where: { code: locale },
1206
+ select: { id: true },
1207
+ }),
1208
+ this.prismaService.dashboard_user.count({
1209
+ where: { user_id: userId },
1210
+ }),
1211
+ ]);
1212
+ const dashboard = await this.prismaService.dashboard.create({
1213
+ data: {
1214
+ slug,
1215
+ icon,
1216
+ },
1217
+ });
1218
+ if (localeRecord) {
1219
+ await this.prismaService.dashboard_locale.create({
1220
+ data: {
1221
+ dashboard_id: dashboard.id,
1222
+ locale_id: localeRecord.id,
1223
+ name,
1224
+ },
1225
+ });
1226
+ }
1227
+ await this.prismaService.dashboard_user.create({
1228
+ data: {
1229
+ dashboard_id: dashboard.id,
1230
+ user_id: userId,
1231
+ is_home: existingCount === 0,
1232
+ },
1233
+ });
1234
+ if ((_d = template === null || template === void 0 ? void 0 : template.dashboard_item) === null || _d === void 0 ? void 0 : _d.length) {
1235
+ await this.prismaService.dashboard_item.createMany({
1236
+ data: template.dashboard_item.map((item) => ({
1237
+ dashboard_id: dashboard.id,
1238
+ component_id: item.component_id,
1239
+ width: item.width,
1240
+ height: item.height,
1241
+ x_axis: item.x_axis,
1242
+ y_axis: item.y_axis,
1243
+ })),
1244
+ });
1245
+ }
1246
+ return {
1247
+ id: dashboard.id,
1248
+ slug,
1249
+ name,
1250
+ icon,
1251
+ is_home: existingCount === 0,
1252
+ };
1253
+ }
1254
+ async renameUserDashboard(userId, slug, data, locale) {
1255
+ var _a, _b;
1256
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1257
+ const name = this.toNullableString(data === null || data === void 0 ? void 0 : data.name);
1258
+ const normalizedIcon = (data === null || data === void 0 ? void 0 : data.icon) === undefined ? undefined : this.toNullableString(data === null || data === void 0 ? void 0 : data.icon);
1259
+ if (!name && (data === null || data === void 0 ? void 0 : data.icon) === undefined) {
1260
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'Dashboard name or icon is required.'));
1261
+ }
1262
+ if ((data === null || data === void 0 ? void 0 : data.icon) !== undefined) {
1263
+ await this.prismaService.dashboard.update({
1264
+ where: { id: dashboardUser.dashboard_id },
1265
+ data: {
1266
+ icon: normalizedIcon !== null && normalizedIcon !== void 0 ? normalizedIcon : null,
1267
+ },
1268
+ });
1269
+ }
1270
+ if (name) {
1271
+ const localeRecord = await this.prismaService.locale.findFirst({
1272
+ where: { code: locale },
1273
+ select: { id: true },
1274
+ });
1275
+ if (!localeRecord) {
1276
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('localeNotFound', locale, 'Locale not found.'));
1277
+ }
1278
+ const existingLocale = await this.prismaService.dashboard_locale.findFirst({
1279
+ where: {
1280
+ dashboard_id: dashboardUser.dashboard_id,
1281
+ locale_id: localeRecord.id,
1282
+ },
1283
+ select: { id: true },
1284
+ });
1285
+ if (existingLocale) {
1286
+ await this.prismaService.dashboard_locale.update({
1287
+ where: { id: existingLocale.id },
1288
+ data: { name },
1289
+ });
1290
+ }
1291
+ else {
1292
+ await this.prismaService.dashboard_locale.create({
1293
+ data: {
1294
+ dashboard_id: dashboardUser.dashboard_id,
1295
+ locale_id: localeRecord.id,
1296
+ name,
1297
+ },
1298
+ });
1299
+ }
1300
+ }
1301
+ const updatedDashboard = await this.prismaService.dashboard.findUnique({
1302
+ where: { id: dashboardUser.dashboard_id },
1303
+ include: {
1304
+ dashboard_locale: {
1305
+ where: {
1306
+ locale: {
1307
+ code: locale,
1308
+ },
1309
+ },
1310
+ },
1311
+ },
1312
+ });
1313
+ return {
1314
+ success: true,
1315
+ slug,
1316
+ name: name ||
1317
+ (updatedDashboard
1318
+ ? this.getDashboardDisplayName(updatedDashboard)
1319
+ : slug),
1320
+ icon: (_b = (_a = updatedDashboard === null || updatedDashboard === void 0 ? void 0 : updatedDashboard.icon) !== null && _a !== void 0 ? _a : normalizedIcon) !== null && _b !== void 0 ? _b : null,
1321
+ };
1322
+ }
1323
+ async setHomeDashboard(userId, slug, locale) {
1324
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1325
+ await this.prismaService.dashboard_user.updateMany({
1326
+ where: { user_id: userId },
1327
+ data: { is_home: false },
1328
+ });
1329
+ await this.prismaService.dashboard_user.update({
1330
+ where: { id: dashboardUser.id },
1331
+ data: { is_home: true },
1332
+ });
1333
+ return {
1334
+ success: true,
1335
+ slug,
1336
+ };
1337
+ }
1338
+ async getDashboardShares(userId, slug, locale) {
1339
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1340
+ const sharedUsers = await this.prismaService.dashboard_user.findMany({
1341
+ where: {
1342
+ dashboard_id: dashboardUser.dashboard_id,
1343
+ },
1344
+ include: {
1345
+ user: {
1346
+ select: {
1347
+ id: true,
1348
+ name: true,
1349
+ user_identifier: {
1350
+ where: {
1351
+ type: 'email',
1352
+ },
1353
+ select: {
1354
+ value: true,
1355
+ },
1356
+ take: 1,
1357
+ },
1358
+ },
1359
+ },
1360
+ },
1361
+ orderBy: {
1362
+ id: 'asc',
1363
+ },
1364
+ });
1365
+ return sharedUsers.map((sharedDashboardUser) => {
1366
+ var _a, _b;
1367
+ return ({
1368
+ id: sharedDashboardUser.user.id,
1369
+ name: sharedDashboardUser.user.name,
1370
+ email: (_b = (_a = sharedDashboardUser.user.user_identifier[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null,
1371
+ isCurrentUser: sharedDashboardUser.user.id === userId,
1372
+ isHome: sharedDashboardUser.is_home,
1373
+ });
1374
+ });
1375
+ }
1376
+ async getShareableUsers(userId, slug, search, locale) {
1377
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1378
+ const normalizedSearch = this.toNullableString(search);
1379
+ const existingUsers = await this.prismaService.dashboard_user.findMany({
1380
+ where: {
1381
+ dashboard_id: dashboardUser.dashboard_id,
1382
+ },
1383
+ select: {
1384
+ user_id: true,
1385
+ },
1386
+ });
1387
+ const users = await this.prismaService.user.findMany({
1388
+ where: Object.assign({ id: {
1389
+ notIn: existingUsers.map((item) => item.user_id),
1390
+ } }, (normalizedSearch
1391
+ ? {
1392
+ OR: [
1393
+ {
1394
+ name: {
1395
+ contains: normalizedSearch,
1396
+ mode: 'insensitive',
1397
+ },
1398
+ },
1399
+ {
1400
+ user_identifier: {
1401
+ some: {
1402
+ type: 'email',
1403
+ value: {
1404
+ contains: normalizedSearch,
1405
+ mode: 'insensitive',
1406
+ },
1407
+ },
1408
+ },
1409
+ },
1410
+ ],
1411
+ }
1412
+ : {})),
1413
+ select: {
1414
+ id: true,
1415
+ name: true,
1416
+ user_identifier: {
1417
+ where: {
1418
+ type: 'email',
1419
+ },
1420
+ select: {
1421
+ value: true,
1422
+ },
1423
+ take: 1,
1424
+ },
1425
+ },
1426
+ take: 20,
1427
+ orderBy: {
1428
+ id: 'desc',
1429
+ },
1430
+ });
1431
+ return users.map((candidateUser) => {
1432
+ var _a, _b;
1433
+ return ({
1434
+ id: candidateUser.id,
1435
+ name: candidateUser.name,
1436
+ email: (_b = (_a = candidateUser.user_identifier[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null,
1437
+ });
1438
+ });
1439
+ }
1440
+ async shareDashboard(userId, slug, sharedUserId, locale) {
1441
+ if (!sharedUserId || Number.isNaN(Number(sharedUserId))) {
1442
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'User is required.'));
1443
+ }
1444
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1445
+ if (sharedUserId === userId) {
1446
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.invalidValue', locale, 'You already have access to this dashboard.'));
1447
+ }
1448
+ const targetUser = await this.prismaService.user.findUnique({
1449
+ where: { id: sharedUserId },
1450
+ select: { id: true },
1451
+ });
1452
+ if (!targetUser) {
1453
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
1454
+ }
1455
+ const existingShare = await this.prismaService.dashboard_user.findFirst({
1456
+ where: {
1457
+ dashboard_id: dashboardUser.dashboard_id,
1458
+ user_id: sharedUserId,
1459
+ },
1460
+ select: { id: true },
1461
+ });
1462
+ if (existingShare) {
1463
+ return {
1464
+ success: true,
1465
+ alreadyShared: true,
1466
+ };
1467
+ }
1468
+ await this.prismaService.dashboard_user.create({
1469
+ data: {
1470
+ dashboard_id: dashboardUser.dashboard_id,
1471
+ user_id: sharedUserId,
1472
+ is_home: false,
1473
+ },
1474
+ });
1475
+ return {
1476
+ success: true,
1477
+ };
1478
+ }
1479
+ async revokeDashboardShare(userId, slug, sharedUserId, locale) {
1480
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1481
+ if (sharedUserId === userId) {
1482
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.invalidValue', locale, 'Use the remove dashboard action to leave this tab.'));
1483
+ }
1484
+ await this.prismaService.dashboard_user.deleteMany({
1485
+ where: {
1486
+ dashboard_id: dashboardUser.dashboard_id,
1487
+ user_id: sharedUserId,
1488
+ },
1489
+ });
1490
+ return {
1491
+ success: true,
1492
+ };
1493
+ }
1494
+ async removeUserDashboard(userId, slug, locale) {
1495
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1496
+ await this.prismaService.dashboard_user.delete({
1497
+ where: { id: dashboardUser.id },
1498
+ });
1499
+ if (dashboardUser.is_home) {
1500
+ const nextDashboard = await this.prismaService.dashboard_user.findFirst({
1501
+ where: { user_id: userId },
1502
+ orderBy: { id: 'asc' },
1503
+ });
1504
+ if (nextDashboard) {
1505
+ await this.prismaService.dashboard_user.update({
1506
+ where: { id: nextDashboard.id },
1507
+ data: { is_home: true },
1508
+ });
1509
+ }
1510
+ }
1511
+ const remainingShares = await this.prismaService.dashboard_user.count({
1512
+ where: {
1513
+ dashboard_id: dashboardUser.dashboard_id,
1514
+ },
1515
+ });
1516
+ if (remainingShares === 0) {
1517
+ await this.prismaService.dashboard.delete({
1518
+ where: { id: dashboardUser.dashboard_id },
1519
+ });
1520
+ }
1521
+ return {
1522
+ success: true,
1523
+ removedSlug: slug,
1524
+ };
1525
+ }
935
1526
  async getAccountSecurity(userId) {
936
1527
  const now = new Date();
937
1528
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);