@hed-hog/core 0.0.354 → 0.0.356

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 (167) hide show
  1. package/dist/ai/ai.service.d.ts +3 -12
  2. package/dist/ai/ai.service.d.ts.map +1 -1
  3. package/dist/ai/ai.service.js +34 -125
  4. package/dist/ai/ai.service.js.map +1 -1
  5. package/dist/core.module.d.ts.map +1 -1
  6. package/dist/core.module.js +3 -3
  7. package/dist/core.module.js.map +1 -1
  8. package/dist/file/file.service.d.ts +1 -1
  9. package/dist/file/file.service.d.ts.map +1 -1
  10. package/dist/file/file.service.js +50 -64
  11. package/dist/file/file.service.js.map +1 -1
  12. package/dist/file/provider/provider.enum.d.ts +2 -1
  13. package/dist/file/provider/provider.enum.d.ts.map +1 -1
  14. package/dist/file/provider/provider.enum.js +1 -0
  15. package/dist/file/provider/provider.enum.js.map +1 -1
  16. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  17. package/dist/file/provider/provider.factory.js +3 -0
  18. package/dist/file/provider/provider.factory.js.map +1 -1
  19. package/dist/file/provider/spaces.provider.d.ts +27 -0
  20. package/dist/file/provider/spaces.provider.d.ts.map +1 -0
  21. package/dist/file/provider/spaces.provider.js +218 -0
  22. package/dist/file/provider/spaces.provider.js.map +1 -0
  23. package/dist/index.d.ts +4 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/integration-profile/dto/create.dto.d.ts +9 -0
  28. package/dist/integration-profile/dto/create.dto.d.ts.map +1 -0
  29. package/dist/integration-profile/dto/create.dto.js +54 -0
  30. package/dist/integration-profile/dto/create.dto.js.map +1 -0
  31. package/dist/integration-profile/dto/export.dto.d.ts +5 -0
  32. package/dist/integration-profile/dto/export.dto.d.ts.map +1 -0
  33. package/dist/integration-profile/dto/export.dto.js +29 -0
  34. package/dist/integration-profile/dto/export.dto.js.map +1 -0
  35. package/dist/integration-profile/dto/import-confirm.dto.d.ts +17 -0
  36. package/dist/integration-profile/dto/import-confirm.dto.d.ts.map +1 -0
  37. package/dist/integration-profile/dto/import-confirm.dto.js +77 -0
  38. package/dist/integration-profile/dto/import-confirm.dto.js.map +1 -0
  39. package/dist/integration-profile/dto/update.dto.d.ts +6 -0
  40. package/dist/integration-profile/dto/update.dto.d.ts.map +1 -0
  41. package/dist/integration-profile/dto/update.dto.js +9 -0
  42. package/dist/integration-profile/dto/update.dto.js.map +1 -0
  43. package/dist/integration-profile/integration-profile.controller.d.ts +142 -0
  44. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -0
  45. package/dist/integration-profile/integration-profile.controller.js +165 -0
  46. package/dist/integration-profile/integration-profile.controller.js.map +1 -0
  47. package/dist/integration-profile/integration-profile.module.d.ts +3 -0
  48. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -0
  49. package/dist/integration-profile/integration-profile.module.js +29 -0
  50. package/dist/integration-profile/integration-profile.module.js.map +1 -0
  51. package/dist/integration-profile/integration-profile.service.d.ts +160 -0
  52. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -0
  53. package/dist/integration-profile/integration-profile.service.js +606 -0
  54. package/dist/integration-profile/integration-profile.service.js.map +1 -0
  55. package/dist/integration-profile/integration-profile.utils.d.ts +24 -0
  56. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -0
  57. package/dist/integration-profile/integration-profile.utils.js +147 -0
  58. package/dist/integration-profile/integration-profile.utils.js.map +1 -0
  59. package/dist/mail/mail.module.d.ts.map +1 -1
  60. package/dist/mail/mail.module.js +27 -19
  61. package/dist/mail/mail.module.js.map +1 -1
  62. package/dist/mail/mail.service.d.ts +3 -2
  63. package/dist/mail/mail.service.d.ts.map +1 -1
  64. package/dist/mail/mail.service.js +25 -47
  65. package/dist/mail/mail.service.js.map +1 -1
  66. package/dist/mcp/mcp.module.js +2 -2
  67. package/dist/mcp/mcp.module.js.map +1 -1
  68. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +3 -15
  69. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -1
  70. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +12 -94
  71. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -1
  72. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  73. package/dist/mcp-chat/mcp-chat.service.js +17 -16
  74. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  75. package/dist/setting/setting.service.d.ts.map +1 -1
  76. package/dist/setting/setting.service.js +0 -2
  77. package/dist/setting/setting.service.js.map +1 -1
  78. package/dist/webhook-integration/event-webhook.controller.d.ts +1 -0
  79. package/dist/webhook-integration/event-webhook.controller.d.ts.map +1 -1
  80. package/dist/webhook-integration/event-webhook.controller.js +10 -0
  81. package/dist/webhook-integration/event-webhook.controller.js.map +1 -1
  82. package/dist/webhook-integration/event-webhook.service.d.ts +2 -0
  83. package/dist/webhook-integration/event-webhook.service.d.ts.map +1 -1
  84. package/dist/webhook-integration/event-webhook.service.js +30 -0
  85. package/dist/webhook-integration/event-webhook.service.js.map +1 -1
  86. package/dist/webhook-integration/webhook-integration.controller.d.ts +1 -0
  87. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  88. package/dist/webhook-integration/webhook-integration.controller.js +10 -0
  89. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  90. package/dist/webhook-integration/webhook-integration.service.d.ts +2 -1
  91. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  92. package/dist/webhook-integration/webhook-integration.service.js +62 -44
  93. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  94. package/dist/whatsapp-profile/dto/create.dto.d.ts +11 -0
  95. package/dist/whatsapp-profile/dto/create.dto.d.ts.map +1 -0
  96. package/dist/whatsapp-profile/dto/create.dto.js +55 -0
  97. package/dist/whatsapp-profile/dto/create.dto.js.map +1 -0
  98. package/dist/whatsapp-profile/dto/update.dto.d.ts +6 -0
  99. package/dist/whatsapp-profile/dto/update.dto.d.ts.map +1 -0
  100. package/dist/whatsapp-profile/dto/update.dto.js +9 -0
  101. package/dist/whatsapp-profile/dto/update.dto.js.map +1 -0
  102. package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts +54 -0
  103. package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts.map +1 -0
  104. package/dist/whatsapp-profile/whatsapp-profile.controller.js +85 -0
  105. package/dist/whatsapp-profile/whatsapp-profile.controller.js.map +1 -0
  106. package/dist/whatsapp-profile/whatsapp-profile.module.d.ts +3 -0
  107. package/dist/whatsapp-profile/whatsapp-profile.module.d.ts.map +1 -0
  108. package/dist/whatsapp-profile/whatsapp-profile.module.js +26 -0
  109. package/dist/whatsapp-profile/whatsapp-profile.module.js.map +1 -0
  110. package/dist/whatsapp-profile/whatsapp-profile.service.d.ts +63 -0
  111. package/dist/whatsapp-profile/whatsapp-profile.service.d.ts.map +1 -0
  112. package/dist/whatsapp-profile/whatsapp-profile.service.js +138 -0
  113. package/dist/whatsapp-profile/whatsapp-profile.service.js.map +1 -0
  114. package/hedhog/data/file_provider.yaml +9 -1
  115. package/hedhog/data/integration_provider.yaml +132 -0
  116. package/hedhog/data/integration_type.yaml +29 -0
  117. package/hedhog/data/menu.yaml +15 -14
  118. package/hedhog/data/role.yaml +8 -0
  119. package/hedhog/data/route.yaml +66 -15
  120. package/hedhog/data/setting_group.yaml +30 -191
  121. package/hedhog/data/setting_subgroup.yaml +5 -13
  122. package/hedhog/data/whatsapp_profile.yaml +6 -0
  123. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +25 -20
  124. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +579 -204
  125. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +1662 -0
  126. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +233 -108
  127. package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +3 -822
  128. package/hedhog/frontend/app/users/permissions.tsx.ejs +79 -2
  129. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +7 -2
  130. package/hedhog/frontend/messages/en.json +172 -0
  131. package/hedhog/frontend/messages/pt.json +172 -0
  132. package/hedhog/table/integration_profile.yaml +34 -0
  133. package/hedhog/table/integration_provider.yaml +23 -0
  134. package/hedhog/table/integration_type.yaml +19 -0
  135. package/hedhog/table/webhook_action.yaml +85 -85
  136. package/hedhog/table/whatsapp_profile.yaml +26 -0
  137. package/package.json +2 -2
  138. package/src/ai/ai.service.ts +35 -153
  139. package/src/core.module.ts +3 -3
  140. package/src/file/file.service.ts +60 -76
  141. package/src/file/provider/provider.enum.ts +1 -0
  142. package/src/file/provider/provider.factory.ts +3 -0
  143. package/src/file/provider/spaces.provider.ts +261 -0
  144. package/src/index.ts +6 -3
  145. package/src/integration-profile/dto/create.dto.ts +44 -0
  146. package/src/integration-profile/dto/export.dto.ts +15 -0
  147. package/src/integration-profile/dto/import-confirm.dto.ts +61 -0
  148. package/src/integration-profile/dto/update.dto.ts +4 -0
  149. package/src/integration-profile/integration-profile.controller.ts +129 -0
  150. package/src/integration-profile/integration-profile.module.ts +16 -0
  151. package/src/integration-profile/integration-profile.service.ts +788 -0
  152. package/src/integration-profile/integration-profile.utils.ts +156 -0
  153. package/src/mail/mail.module.ts +27 -18
  154. package/src/mail/mail.service.ts +31 -60
  155. package/src/mcp/mcp.module.ts +2 -2
  156. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +10 -69
  157. package/src/mcp-chat/mcp-chat.service.ts +23 -20
  158. package/src/setting/setting.service.ts +0 -2
  159. package/src/webhook-integration/event-webhook.controller.ts +55 -50
  160. package/src/webhook-integration/event-webhook.service.ts +422 -384
  161. package/src/webhook-integration/webhook-integration.controller.ts +5 -0
  162. package/src/webhook-integration/webhook-integration.service.ts +74 -46
  163. package/src/whatsapp-profile/dto/create.dto.ts +36 -0
  164. package/src/whatsapp-profile/dto/update.dto.ts +4 -0
  165. package/src/whatsapp-profile/whatsapp-profile.controller.ts +51 -0
  166. package/src/whatsapp-profile/whatsapp-profile.module.ts +13 -0
  167. package/src/whatsapp-profile/whatsapp-profile.service.ts +160 -0
@@ -24,20 +24,151 @@ import { Eye, EyeOff, Upload } from 'lucide-react';
24
24
  import { useTranslations } from 'next-intl';
25
25
  import { useEffect, useRef, useState } from 'react';
26
26
 
27
- type SettingFieldProps = {
28
- setting: Setting;
29
- className?: string;
30
- };
31
-
32
- export const SettingField = ({ setting, className }: SettingFieldProps) => {
27
+ type SettingFieldProps = {
28
+ setting: Setting;
29
+ className?: string;
30
+ };
31
+
32
+ type ApiPaginated<T> = {
33
+ data: T[];
34
+ page: number;
35
+ lastPage: number;
36
+ };
37
+
38
+ type IntegrationTypeOption = {
39
+ id: number;
40
+ slug: string;
41
+ };
42
+
43
+ type IntegrationProviderOption = {
44
+ id: number;
45
+ slug: string;
46
+ type_id: number;
47
+ integration_provider_locale?: {
48
+ name: string;
49
+ locale: { code: string };
50
+ }[];
51
+ };
52
+
53
+ type IntegrationProfileOption = {
54
+ id: number;
55
+ slug: string;
56
+ name: string;
57
+ integration_provider?: {
58
+ slug: string;
59
+ integration_provider_locale?: {
60
+ name: string;
61
+ locale: { code: string };
62
+ }[];
63
+ };
64
+ };
65
+
66
+ function getLocalizedIntegrationName(
67
+ locales:
68
+ | {
69
+ name: string;
70
+ locale: { code: string };
71
+ }[]
72
+ | undefined,
73
+ localeCode: string
74
+ ) {
75
+ if (!locales?.length) {
76
+ return '';
77
+ }
78
+
79
+ return (
80
+ locales.find((locale) => locale.locale.code === localeCode)?.name ??
81
+ locales[0]?.name ??
82
+ ''
83
+ );
84
+ }
85
+
86
+ const DAILY_REPORT_EMAIL_PROFILE_SLUG =
87
+ 'operations.daily-report.email-profile-id';
88
+ const DAILY_REPORT_WHATSAPP_PROFILE_SLUG =
89
+ 'operations.daily-report.whatsapp-profile-id';
90
+
91
+ function toSlug(value: string) {
92
+ return (value || 'profile')
93
+ .toLowerCase()
94
+ .normalize('NFD')
95
+ .replace(/[\u0300-\u036f]/g, '')
96
+ .replace(/[^a-z0-9]+/g, '-')
97
+ .replace(/^-+|-+$/g, '');
98
+ }
99
+
100
+ function getDailyReportIntegrationTypeSlug(settingSlug: string) {
101
+ if (settingSlug === DAILY_REPORT_EMAIL_PROFILE_SLUG) {
102
+ return 'email';
103
+ }
104
+
105
+ if (settingSlug === DAILY_REPORT_WHATSAPP_PROFILE_SLUG) {
106
+ return 'whatsapp';
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ function buildIntegrationProfileConfig(
113
+ providerSlug: string,
114
+ values: Record<string, string>
115
+ ) {
116
+ const trim = (key: string) => String(values[key] ?? '').trim();
117
+
118
+ if (providerSlug === 'smtp') {
119
+ return {
120
+ host: trim('host'),
121
+ port: Number(trim('port') || 587),
122
+ username: trim('username'),
123
+ password: trim('password'),
124
+ secure: values.secure === 'true',
125
+ from_email: trim('from_email'),
126
+ from_name: trim('from_name'),
127
+ reply_to_email: trim('reply_to_email'),
128
+ };
129
+ }
130
+
131
+ if (providerSlug === 'gmail') {
132
+ return {
133
+ client_id: trim('client_id'),
134
+ client_secret: trim('client_secret'),
135
+ refresh_token: trim('refresh_token'),
136
+ from_email: trim('from_email'),
137
+ from_name: trim('from_name'),
138
+ };
139
+ }
140
+
141
+ if (providerSlug === 'ses') {
142
+ return {
143
+ access_key_id: trim('access_key_id'),
144
+ secret_access_key: trim('secret_access_key'),
145
+ region: trim('region'),
146
+ from_email: trim('from_email'),
147
+ from_name: trim('from_name'),
148
+ };
149
+ }
150
+
151
+ return {
152
+ host: trim('host'),
153
+ token: trim('token'),
154
+ instance_name: trim('instance_name'),
155
+ };
156
+ }
157
+
158
+ export const SettingField = ({ setting, className }: SettingFieldProps) => {
33
159
  const t = useTranslations('core.Configurations');
34
- const { showToastHandler, setSettingValue, request } = useApp();
160
+ const { showToastHandler, setSettingValue, request, currentLocaleCode } =
161
+ useApp();
35
162
  const component =
36
163
  setting.component?.replaceAll('_', '-') || SettingComponentEnum.INPUT_TEXT;
37
164
  const [localValue, setLocalValue] = useState(setting.value);
165
+ const [storageProfileSelectedLabel, setStorageProfileSelectedLabel] =
166
+ useState('');
38
167
  const [showSecretValue, setShowSecretValue] = useState(false);
168
+ const [isUploading, setIsUploading] = useState(false);
39
169
  const debounceTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
40
170
  const isTextInputRef = useRef<boolean>(false);
171
+ const fileInputRef = useRef<HTMLInputElement>(null);
41
172
 
42
173
  const isThemeSetting = (slug: string) => {
43
174
  return slug.startsWith('theme-') || slug === 'menu-width';
@@ -45,7 +176,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
45
176
 
46
177
  const applyThemeSettings = async () => {
47
178
  try {
48
- const response: any = await request({
179
+ const response = await request<{ setting?: Record<string, unknown> }>({
49
180
  url: '/setting/initial',
50
181
  method: 'GET',
51
182
  });
@@ -67,11 +198,11 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
67
198
 
68
199
  debounceTimerRef.current = setTimeout(() => {
69
200
  if (localValue !== setting.value) {
70
- setSettingValue(setting.slug, localValue)
71
- .then(async () => {
72
- showToastHandler('success', t('settingUpdated'));
73
-
74
- if (['icon-url'].includes(setting.slug)) {
201
+ setSettingValue(setting.slug, localValue)
202
+ .then(async () => {
203
+ showToastHandler('success', t('settingUpdated'));
204
+
205
+ if (['icon-url'].includes(setting.slug)) {
75
206
  await revalidateSystemIcon();
76
207
  showToastHandler('success', t('iconRevalidated'));
77
208
  window.location.reload();
@@ -94,13 +225,49 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
94
225
  };
95
226
  }, [localValue]);
96
227
 
97
- const handleChangeValue = (newValue: any, immediate = false) => {
228
+ useEffect(() => {
229
+ if (setting.slug !== 'file-storage-profile-id') {
230
+ setStorageProfileSelectedLabel('');
231
+ return;
232
+ }
233
+
234
+ const profileId = Number(localValue);
235
+ if (!profileId) {
236
+ setStorageProfileSelectedLabel('');
237
+ return;
238
+ }
239
+
240
+ let isCurrent = true;
241
+
242
+ request<IntegrationProfileOption>({
243
+ url: `/integration-profile/${profileId}`,
244
+ method: 'GET',
245
+ })
246
+ .then((response) => {
247
+ if (!isCurrent) {
248
+ return;
249
+ }
250
+
251
+ setStorageProfileSelectedLabel(response.data?.name ?? '');
252
+ })
253
+ .catch(() => {
254
+ if (isCurrent) {
255
+ setStorageProfileSelectedLabel('');
256
+ }
257
+ });
258
+
259
+ return () => {
260
+ isCurrent = false;
261
+ };
262
+ }, [localValue, request, setting.slug]);
263
+
264
+ const handleChangeValue = (newValue: string, immediate = false) => {
98
265
  setLocalValue(newValue);
99
266
  if (immediate) {
100
- setSettingValue(setting.slug, newValue)
101
- .then(async () => {
102
- showToastHandler('success', t('settingUpdated'));
103
- if (isThemeSetting(setting.slug)) {
267
+ setSettingValue(setting.slug, newValue)
268
+ .then(async () => {
269
+ showToastHandler('success', t('settingUpdated'));
270
+ if (isThemeSetting(setting.slug)) {
104
271
  await applyThemeSettings();
105
272
  }
106
273
  })
@@ -133,7 +300,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
133
300
  }
134
301
  };
135
302
 
136
- const formatValue = (val: any) => {
303
+ const formatValue = (val: unknown): string => {
137
304
  switch (setting.type) {
138
305
  case SettingTypeEnum.ARRAY:
139
306
  case SettingTypeEnum.JSON:
@@ -143,7 +310,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
143
310
  case SettingTypeEnum.NUMBER:
144
311
  return val?.toString() || '0';
145
312
  default:
146
- return val;
313
+ return String(val ?? '');
147
314
  }
148
315
  };
149
316
 
@@ -189,13 +356,11 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
189
356
  <Button
190
357
  type="button"
191
358
  variant="ghost"
192
- size="icon"
193
- className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
194
- onClick={() => setShowSecretValue((prev) => !prev)}
195
- aria-label={
196
- showSecretValue ? t('hidePassword') : t('showPassword')
197
- }
198
- >
359
+ size="icon"
360
+ className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
361
+ onClick={() => setShowSecretValue((prev) => !prev)}
362
+ aria-label={showSecretValue ? t('hidePassword') : t('showPassword')}
363
+ >
199
364
  {showSecretValue ? (
200
365
  <EyeOff className="h-4 w-4" />
201
366
  ) : (
@@ -207,8 +372,6 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
207
372
 
208
373
  case SettingComponentEnum.INPUT_FILE:
209
374
  isTextInputRef.current = true;
210
- const fileInputRef = useRef<HTMLInputElement>(null);
211
- const [isUploading, setIsUploading] = useState(false);
212
375
 
213
376
  const handleFileUpload = async (
214
377
  e: React.ChangeEvent<HTMLInputElement>
@@ -221,7 +384,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
221
384
  const formData = new FormData();
222
385
  formData.append('file', file);
223
386
 
224
- const response: any = await request({
387
+ const response = await request<{ id?: number | string }>({
225
388
  url: '/file',
226
389
  method: 'POST',
227
390
  data: formData,
@@ -236,7 +399,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
236
399
  throw new Error(t('fileIdNotReturned'));
237
400
  }
238
401
 
239
- const openResponse: any = await request({
402
+ const openResponse = await request<{ url?: string }>({
240
403
  url: `/file/open/${fileId}`,
241
404
  method: 'PUT',
242
405
  });
@@ -312,7 +475,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
312
475
  onCheckedChange={(checked) =>
313
476
  handleChangeValue(formatValue(checked), true)
314
477
  }
315
- className={cn(className)}
478
+ className={cn('w-8! min-w-8 max-w-8', className)}
316
479
  />
317
480
  );
318
481
 
@@ -434,7 +597,79 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
434
597
  );
435
598
 
436
599
  case SettingComponentEnum.ENTITY_PICKER:
437
- if (setting.slug !== 'mail-profile-id') {
600
+ const dailyReportIntegrationTypeSlug = getDailyReportIntegrationTypeSlug(
601
+ setting.slug
602
+ );
603
+
604
+ if (setting.slug === 'file-storage-profile-id') {
605
+ const loadStorageTypeId = async () => {
606
+ const typesResponse = await request<IntegrationTypeOption[]>({
607
+ url: '/integration-profile/types',
608
+ method: 'GET',
609
+ });
610
+ const types = (typesResponse.data ?? []) as IntegrationTypeOption[];
611
+ const typeId = types.find((type) => type.slug === 'storage')?.id;
612
+
613
+ if (!typeId) {
614
+ throw new Error('Integration type "storage" not found.');
615
+ }
616
+
617
+ return typeId;
618
+ };
619
+
620
+ return (
621
+ <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
622
+ placeholder={t('selectStorageProfile')}
623
+ emptyStateDescription={t('storageProfileEmptyState')}
624
+ value={parsedValue ? Number(parsedValue) : null}
625
+ onChange={(id, option) => {
626
+ setStorageProfileSelectedLabel(option?.name ?? '');
627
+ handleChangeValue(id != null ? String(id) : '', true);
628
+ }}
629
+ valueType="number"
630
+ initialSelectedLabel={storageProfileSelectedLabel}
631
+ showCreateButton={false}
632
+ loadOptions={async ({ page, pageSize, search }) => {
633
+ const typeId = await loadStorageTypeId();
634
+ const qs = new URLSearchParams({
635
+ page: String(page),
636
+ pageSize: String(pageSize),
637
+ typeId: String(typeId),
638
+ });
639
+ if (search) qs.set('search', search);
640
+ const response = await request<
641
+ ApiPaginated<IntegrationProfileOption & Record<string, unknown>>
642
+ >({
643
+ url: `/integration-profile?${qs}`,
644
+ method: 'GET',
645
+ });
646
+ const paged = response.data;
647
+ return {
648
+ items: paged?.data ?? [],
649
+ hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
650
+ };
651
+ }}
652
+ getOptionValue={(opt) => opt.id}
653
+ getOptionLabel={(opt) => String(opt.name ?? '')}
654
+ getOptionDescription={(opt) => {
655
+ const providerSlug = String(opt.integration_provider?.slug ?? '');
656
+ const providerName = getLocalizedIntegrationName(
657
+ opt.integration_provider?.integration_provider_locale,
658
+ currentLocaleCode
659
+ );
660
+ return [providerName || providerSlug, opt.slug]
661
+ .filter(Boolean)
662
+ .join(' • ');
663
+ }}
664
+ className={cn(className, 'w-full min-w-0 md:min-w-50')}
665
+ />
666
+ );
667
+ }
668
+
669
+ if (
670
+ setting.slug !== 'mail-profile-id' &&
671
+ !dailyReportIntegrationTypeSlug
672
+ ) {
438
673
  return (
439
674
  <Input
440
675
  type="text"
@@ -444,198 +679,338 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
444
679
  />
445
680
  );
446
681
  }
447
- return (
448
- <EntityPicker
449
- placeholder={t('selectMailProfile')}
450
- value={parsedValue ? Number(parsedValue) : null}
451
- onChange={(id) =>
452
- handleChangeValue(id != null ? String(id) : '', true)
453
- }
454
- valueType="number"
455
- loadOptions={async ({ page, pageSize, search }) => {
456
- const qs = new URLSearchParams({
457
- page: String(page),
458
- pageSize: String(pageSize),
459
- });
460
- if (search) qs.set('search', search);
461
- const response: any = await request({
462
- url: `/mail-profile?${qs}`,
463
- });
464
- const paged = response.data;
465
- return {
466
- items: paged?.data ?? [],
467
- hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
468
- };
469
- }}
470
- getOptionValue={(opt) => opt.id as number}
471
- getOptionLabel={(opt) => String(opt.name ?? '')}
472
- getOptionDescription={(opt) => String((opt as any).from_email ?? '')}
473
- mapSearchToCreateValues={(search) => ({
474
- name: search,
475
- provider: 'SMTP',
476
- })}
477
- showCreateButton
478
- onCreate={async (values) => {
479
- const slug = (values.name || 'profile')
480
- .toLowerCase()
481
- .normalize('NFD')
482
- .replace(/[\u0300-\u036f]/g, '')
483
- .replace(/[^a-z0-9]+/g, '-')
484
- .replace(/^-+|-+$/g, '');
485
- const response: any = await request({
486
- url: '/mail-profile',
487
- method: 'POST',
488
- data: { ...values, slug },
489
- });
490
- return response.data;
491
- }}
492
- renderCreateContent={({ values, setValue }) => {
493
- const provider = values.provider || 'SMTP';
494
- return (
495
- <div className="space-y-4">
496
- <div className="space-y-1">
497
- <Label>{t('mailProfile.nameLabel')}</Label>
498
- <Input
499
- value={values.name || ''}
500
- onChange={(e) => setValue('name', e.target.value)}
501
- placeholder={t('mailProfile.namePlaceholder')}
502
- />
503
- </div>
504
- <div className="space-y-1">
505
- <Label>{t('mailProfile.fromEmailLabel')}</Label>
506
- <Input
507
- type="email"
508
- value={values.from_email || ''}
509
- onChange={(e) => setValue('from_email', e.target.value)}
510
- placeholder={t('mailProfile.fromEmailPlaceholder')}
511
- />
512
- </div>
513
- <div className="space-y-1">
514
- <Label>{t('mailProfile.providerLabel')}</Label>
515
- <Select
516
- value={provider}
517
- onValueChange={(v) => setValue('provider', v)}
518
- >
519
- <SelectTrigger className="w-full">
520
- <SelectValue />
521
- </SelectTrigger>
522
- <SelectContent>
523
- <SelectItem value="SMTP">SMTP</SelectItem>
524
- <SelectItem value="GMAIL">Gmail</SelectItem>
525
- <SelectItem value="SES">
526
- {t('mailProfile.providers.ses')}
527
- </SelectItem>
528
- </SelectContent>
529
- </Select>
530
- </div>
531
- {provider === 'SMTP' && (
532
- <>
533
- <div className="space-y-1">
534
- <Label>{t('mailProfile.smtpHostLabel')}</Label>
535
- <Input
536
- value={values.smtp_host || ''}
537
- onChange={(e) => setValue('smtp_host', e.target.value)}
538
- placeholder={t('mailProfile.smtpHostPlaceholder')}
539
- />
540
- </div>
541
- <div className="space-y-1">
542
- <Label>{t('mailProfile.smtpPortLabel')}</Label>
543
- <Input
544
- type="number"
545
- value={values.smtp_port || '587'}
546
- onChange={(e) => setValue('smtp_port', e.target.value)}
547
- placeholder={t('mailProfile.smtpPortPlaceholder')}
548
- />
549
- </div>
550
- <div className="space-y-1">
551
- <Label>{t('mailProfile.smtpUsernameLabel')}</Label>
552
- <Input
553
- value={values.smtp_username || ''}
554
- onChange={(e) =>
555
- setValue('smtp_username', e.target.value)
556
- }
557
- placeholder={t('mailProfile.smtpUsernamePlaceholder')}
558
- />
559
- </div>
560
- <div className="space-y-1">
561
- <Label>{t('mailProfile.smtpPasswordLabel')}</Label>
562
- <Input
563
- type="password"
564
- value={values.smtp_password || ''}
565
- onChange={(e) =>
566
- setValue('smtp_password', e.target.value)
567
- }
568
- />
569
- </div>
570
- </>
571
- )}
572
- {provider === 'GMAIL' && (
573
- <>
682
+
683
+ if (dailyReportIntegrationTypeSlug) {
684
+ const loadIntegrationCatalog = async () => {
685
+ const qs = new URLSearchParams({
686
+ typeSlug: dailyReportIntegrationTypeSlug,
687
+ });
688
+ const providersResponse = await request<IntegrationProviderOption[]>({
689
+ url: `/integration-profile/providers?${qs}`,
690
+ method: 'GET',
691
+ });
692
+
693
+ return {
694
+ providers: (providersResponse.data ??
695
+ []) as IntegrationProviderOption[],
696
+ };
697
+ };
698
+
699
+ return (
700
+ <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
701
+ placeholder={
702
+ dailyReportIntegrationTypeSlug === 'email'
703
+ ? t('selectMailProfile')
704
+ : t('selectWhatsAppProfile')
705
+ }
706
+ value={parsedValue ? Number(parsedValue) : null}
707
+ onChange={(id) =>
708
+ handleChangeValue(id != null ? String(id) : '', true)
709
+ }
710
+ valueType="number"
711
+ loadOptions={async ({ page, pageSize, search }) => {
712
+ const qs = new URLSearchParams({
713
+ page: String(page),
714
+ pageSize: String(pageSize),
715
+ typeSlug: dailyReportIntegrationTypeSlug,
716
+ });
717
+ if (search) qs.set('search', search);
718
+ const response = await request<
719
+ ApiPaginated<IntegrationProfileOption & Record<string, unknown>>
720
+ >({
721
+ url: `/integration-profile?${qs}`,
722
+ method: 'GET',
723
+ });
724
+ const paged = response.data;
725
+ return {
726
+ items: paged?.data ?? [],
727
+ hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
728
+ };
729
+ }}
730
+ getOptionValue={(opt) => opt.id}
731
+ getOptionLabel={(opt) => String(opt.name ?? '')}
732
+ getOptionDescription={(opt) =>
733
+ String(opt.integration_provider?.slug ?? opt.slug ?? '')
734
+ }
735
+ mapSearchToCreateValues={(search) =>
736
+ dailyReportIntegrationTypeSlug === 'email'
737
+ ? {
738
+ name: search,
739
+ provider: 'smtp',
740
+ port: '587',
741
+ }
742
+ : {
743
+ name: search,
744
+ provider: 'evolution-api',
745
+ port: '',
746
+ }
747
+ }
748
+ showCreateButton
749
+ onCreate={async (values) => {
750
+ const providersCatalog = await loadIntegrationCatalog();
751
+ const providerSlug =
752
+ dailyReportIntegrationTypeSlug === 'email'
753
+ ? values.provider || 'smtp'
754
+ : 'evolution-api';
755
+ const provider = providersCatalog.providers.find(
756
+ (item) => item.slug === providerSlug
757
+ );
758
+
759
+ if (!provider) {
760
+ throw new Error(
761
+ `Integration provider "${providerSlug}" not found.`
762
+ );
763
+ }
764
+
765
+ const name = String(values.name || 'Profile').trim();
766
+ const response = await request<
767
+ IntegrationProfileOption & Record<string, unknown>
768
+ >({
769
+ url: '/integration-profile',
770
+ method: 'POST',
771
+ data: {
772
+ name,
773
+ slug: toSlug(name),
774
+ type_id: provider.type_id,
775
+ provider_id: provider.id,
776
+ config: buildIntegrationProfileConfig(providerSlug, values),
777
+ is_active: true,
778
+ },
779
+ });
780
+ return response.data;
781
+ }}
782
+ renderCreateContent={({ values, setValue }) => {
783
+ if (dailyReportIntegrationTypeSlug === 'whatsapp') {
784
+ return (
785
+ <div className="space-y-4">
574
786
  <div className="space-y-1">
575
- <Label>{t('mailProfile.clientIdLabel')}</Label>
787
+ <Label>{t('whatsappProfile.nameLabel')}</Label>
576
788
  <Input
577
- value={values.gmail_client_id || ''}
578
- onChange={(e) =>
579
- setValue('gmail_client_id', e.target.value)
580
- }
789
+ value={values.name || ''}
790
+ onChange={(e) => setValue('name', e.target.value)}
791
+ placeholder={t('whatsappProfile.namePlaceholder')}
581
792
  />
582
793
  </div>
583
794
  <div className="space-y-1">
584
- <Label>{t('mailProfile.clientSecretLabel')}</Label>
585
- <Input
586
- type="password"
587
- value={values.gmail_client_secret || ''}
588
- onChange={(e) =>
589
- setValue('gmail_client_secret', e.target.value)
590
- }
591
- />
795
+ <Label>{t('whatsappProfile.providerLabel')}</Label>
796
+ <Select
797
+ value={values.provider || 'evolution-api'}
798
+ onValueChange={(v) => setValue('provider', v)}
799
+ >
800
+ <SelectTrigger className="w-full">
801
+ <SelectValue />
802
+ </SelectTrigger>
803
+ <SelectContent>
804
+ <SelectItem value="evolution-api">
805
+ Evolution API
806
+ </SelectItem>
807
+ </SelectContent>
808
+ </Select>
592
809
  </div>
593
810
  <div className="space-y-1">
594
- <Label>{t('mailProfile.refreshTokenLabel')}</Label>
811
+ <Label>{t('whatsappProfile.hostLabel')}</Label>
595
812
  <Input
596
- value={values.gmail_refresh_token || ''}
597
- onChange={(e) =>
598
- setValue('gmail_refresh_token', e.target.value)
599
- }
813
+ value={values.host || ''}
814
+ onChange={(e) => setValue('host', e.target.value)}
815
+ placeholder={t('whatsappProfile.hostPlaceholder')}
600
816
  />
601
817
  </div>
602
- </>
603
- )}
604
- {provider === 'SES' && (
605
- <>
606
818
  <div className="space-y-1">
607
- <Label>{t('mailProfile.accessKeyIdLabel')}</Label>
819
+ <Label>{t('whatsappProfile.instanceNameLabel')}</Label>
608
820
  <Input
609
- value={values.ses_access_key_id || ''}
821
+ value={values.instance_name || ''}
610
822
  onChange={(e) =>
611
- setValue('ses_access_key_id', e.target.value)
823
+ setValue('instance_name', e.target.value)
612
824
  }
825
+ placeholder={t(
826
+ 'whatsappProfile.instanceNamePlaceholder'
827
+ )}
613
828
  />
614
829
  </div>
615
830
  <div className="space-y-1">
616
- <Label>{t('mailProfile.secretAccessKeyLabel')}</Label>
831
+ <Label>{t('whatsappProfile.tokenLabel')}</Label>
617
832
  <Input
618
833
  type="password"
619
- value={values.ses_secret_access_key || ''}
620
- onChange={(e) =>
621
- setValue('ses_secret_access_key', e.target.value)
622
- }
623
- />
624
- </div>
625
- <div className="space-y-1">
626
- <Label>{t('mailProfile.regionLabel')}</Label>
627
- <Input
628
- value={values.ses_region || ''}
629
- onChange={(e) => setValue('ses_region', e.target.value)}
630
- placeholder={t('mailProfile.regionPlaceholder')}
834
+ value={values.token || ''}
835
+ onChange={(e) => setValue('token', e.target.value)}
836
+ placeholder={t('whatsappProfile.tokenPlaceholder')}
631
837
  />
632
838
  </div>
633
- </>
634
- )}
635
- </div>
636
- );
637
- }}
638
- className={cn(className, 'w-full min-w-0 md:min-w-50')}
839
+ </div>
840
+ );
841
+ }
842
+
843
+ const provider = values.provider || 'smtp';
844
+ return (
845
+ <div className="space-y-4">
846
+ <div className="space-y-1">
847
+ <Label>{t('mailProfile.nameLabel')}</Label>
848
+ <Input
849
+ value={values.name || ''}
850
+ onChange={(e) => setValue('name', e.target.value)}
851
+ placeholder={t('mailProfile.namePlaceholder')}
852
+ />
853
+ </div>
854
+ <div className="space-y-1">
855
+ <Label>{t('mailProfile.providerLabel')}</Label>
856
+ <Select
857
+ value={provider}
858
+ onValueChange={(v) => setValue('provider', v)}
859
+ >
860
+ <SelectTrigger className="w-full">
861
+ <SelectValue />
862
+ </SelectTrigger>
863
+ <SelectContent>
864
+ <SelectItem value="smtp">SMTP</SelectItem>
865
+ <SelectItem value="gmail">Gmail</SelectItem>
866
+ <SelectItem value="ses">
867
+ {t('mailProfile.providers.ses')}
868
+ </SelectItem>
869
+ </SelectContent>
870
+ </Select>
871
+ </div>
872
+ <div className="space-y-1">
873
+ <Label>{t('mailProfile.fromEmailLabel')}</Label>
874
+ <Input
875
+ type="email"
876
+ value={values.from_email || ''}
877
+ onChange={(e) => setValue('from_email', e.target.value)}
878
+ placeholder={t('mailProfile.fromEmailPlaceholder')}
879
+ />
880
+ </div>
881
+ <div className="space-y-1">
882
+ <Label>{t('mailProfile.fromNameLabel')}</Label>
883
+ <Input
884
+ value={values.from_name || ''}
885
+ onChange={(e) => setValue('from_name', e.target.value)}
886
+ placeholder={t('mailProfile.fromNamePlaceholder')}
887
+ />
888
+ </div>
889
+ {provider === 'smtp' && (
890
+ <>
891
+ <div className="space-y-1">
892
+ <Label>{t('mailProfile.smtpHostLabel')}</Label>
893
+ <Input
894
+ value={values.host || ''}
895
+ onChange={(e) => setValue('host', e.target.value)}
896
+ placeholder={t('mailProfile.smtpHostPlaceholder')}
897
+ />
898
+ </div>
899
+ <div className="space-y-1">
900
+ <Label>{t('mailProfile.smtpPortLabel')}</Label>
901
+ <Input
902
+ type="number"
903
+ value={values.port || '587'}
904
+ onChange={(e) => setValue('port', e.target.value)}
905
+ placeholder={t('mailProfile.smtpPortPlaceholder')}
906
+ />
907
+ </div>
908
+ <div className="space-y-1">
909
+ <Label>{t('mailProfile.smtpUsernameLabel')}</Label>
910
+ <Input
911
+ value={values.username || ''}
912
+ onChange={(e) => setValue('username', e.target.value)}
913
+ placeholder={t('mailProfile.smtpUsernamePlaceholder')}
914
+ />
915
+ </div>
916
+ <div className="space-y-1">
917
+ <Label>{t('mailProfile.smtpPasswordLabel')}</Label>
918
+ <Input
919
+ type="password"
920
+ value={values.password || ''}
921
+ onChange={(e) => setValue('password', e.target.value)}
922
+ />
923
+ </div>
924
+ <div className="space-y-1">
925
+ <Label>{t('mailProfile.replyToEmailLabel')}</Label>
926
+ <Input
927
+ type="email"
928
+ value={values.reply_to_email || ''}
929
+ onChange={(e) =>
930
+ setValue('reply_to_email', e.target.value)
931
+ }
932
+ placeholder={t('mailProfile.replyToEmailPlaceholder')}
933
+ />
934
+ </div>
935
+ </>
936
+ )}
937
+ {provider === 'gmail' && (
938
+ <>
939
+ <div className="space-y-1">
940
+ <Label>{t('mailProfile.clientIdLabel')}</Label>
941
+ <Input
942
+ value={values.client_id || ''}
943
+ onChange={(e) =>
944
+ setValue('client_id', e.target.value)
945
+ }
946
+ />
947
+ </div>
948
+ <div className="space-y-1">
949
+ <Label>{t('mailProfile.clientSecretLabel')}</Label>
950
+ <Input
951
+ type="password"
952
+ value={values.client_secret || ''}
953
+ onChange={(e) =>
954
+ setValue('client_secret', e.target.value)
955
+ }
956
+ />
957
+ </div>
958
+ <div className="space-y-1">
959
+ <Label>{t('mailProfile.refreshTokenLabel')}</Label>
960
+ <Input
961
+ value={values.refresh_token || ''}
962
+ onChange={(e) =>
963
+ setValue('refresh_token', e.target.value)
964
+ }
965
+ />
966
+ </div>
967
+ </>
968
+ )}
969
+ {provider === 'ses' && (
970
+ <>
971
+ <div className="space-y-1">
972
+ <Label>{t('mailProfile.accessKeyIdLabel')}</Label>
973
+ <Input
974
+ value={values.access_key_id || ''}
975
+ onChange={(e) =>
976
+ setValue('access_key_id', e.target.value)
977
+ }
978
+ />
979
+ </div>
980
+ <div className="space-y-1">
981
+ <Label>{t('mailProfile.secretAccessKeyLabel')}</Label>
982
+ <Input
983
+ type="password"
984
+ value={values.secret_access_key || ''}
985
+ onChange={(e) =>
986
+ setValue('secret_access_key', e.target.value)
987
+ }
988
+ />
989
+ </div>
990
+ <div className="space-y-1">
991
+ <Label>{t('mailProfile.regionLabel')}</Label>
992
+ <Input
993
+ value={values.region || ''}
994
+ onChange={(e) => setValue('region', e.target.value)}
995
+ placeholder={t('mailProfile.regionPlaceholder')}
996
+ />
997
+ </div>
998
+ </>
999
+ )}
1000
+ </div>
1001
+ );
1002
+ }}
1003
+ className={cn(className, 'w-full min-w-0 md:min-w-50')}
1004
+ />
1005
+ );
1006
+ }
1007
+
1008
+ return (
1009
+ <Input
1010
+ type="text"
1011
+ value={parsedValue || ''}
1012
+ onChange={(e) => handleChangeValue(formatValue(e.target.value))}
1013
+ className={cn(className, 'bg-background')}
639
1014
  />
640
1015
  );
641
1016