@hed-hog/core 0.0.370 → 0.0.371

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 (248) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -0
  3. package/dist/ai/ai.service.d.ts.map +1 -1
  4. package/dist/ai/ai.service.js +10 -7
  5. package/dist/ai/ai.service.js.map +1 -1
  6. package/dist/app-command/app-command.module.d.ts +3 -0
  7. package/dist/app-command/app-command.module.d.ts.map +1 -0
  8. package/dist/app-command/app-command.module.js +22 -0
  9. package/dist/app-command/app-command.module.js.map +1 -0
  10. package/dist/app-command/app-command.registry.d.ts +22 -0
  11. package/dist/app-command/app-command.registry.d.ts.map +1 -0
  12. package/dist/{webhook-integration/webhook-command.registry.js → app-command/app-command.registry.js} +6 -6
  13. package/dist/app-command/app-command.registry.js.map +1 -0
  14. package/dist/auth/auth.controller.d.ts +2 -2
  15. package/dist/auth/auth.controller.d.ts.map +1 -1
  16. package/dist/auth/auth.controller.js +20 -7
  17. package/dist/auth/auth.controller.js.map +1 -1
  18. package/dist/auth/auth.service.d.ts +3 -3
  19. package/dist/auth/auth.service.d.ts.map +1 -1
  20. package/dist/auth/auth.service.js +10 -36
  21. package/dist/auth/auth.service.js.map +1 -1
  22. package/dist/auth/dto/forget.dto.d.ts +1 -0
  23. package/dist/auth/dto/forget.dto.d.ts.map +1 -1
  24. package/dist/auth/dto/forget.dto.js +5 -0
  25. package/dist/auth/dto/forget.dto.js.map +1 -1
  26. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  27. package/dist/auth/guards/auth.guard.js +4 -1
  28. package/dist/auth/guards/auth.guard.js.map +1 -1
  29. package/dist/challenge/challenge.service.d.ts +8 -7
  30. package/dist/challenge/challenge.service.d.ts.map +1 -1
  31. package/dist/challenge/challenge.service.js +27 -19
  32. package/dist/challenge/challenge.service.js.map +1 -1
  33. package/dist/file/dto/upload.dto.d.ts +3 -0
  34. package/dist/file/dto/upload.dto.d.ts.map +1 -1
  35. package/dist/file/dto/upload.dto.js +22 -0
  36. package/dist/file/dto/upload.dto.js.map +1 -1
  37. package/dist/file/file.controller.d.ts +2 -1
  38. package/dist/file/file.controller.d.ts.map +1 -1
  39. package/dist/file/file.controller.js +29 -16
  40. package/dist/file/file.controller.js.map +1 -1
  41. package/dist/file/file.service.d.ts +51 -1
  42. package/dist/file/file.service.d.ts.map +1 -1
  43. package/dist/file/file.service.js +250 -18
  44. package/dist/file/file.service.js.map +1 -1
  45. package/dist/file/image-optimization.service.d.ts +17 -4
  46. package/dist/file/image-optimization.service.d.ts.map +1 -1
  47. package/dist/file/image-optimization.service.js +101 -46
  48. package/dist/file/image-optimization.service.js.map +1 -1
  49. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  50. package/dist/file/provider/s3.provider.js +15 -7
  51. package/dist/file/provider/s3.provider.js.map +1 -1
  52. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  53. package/dist/file/provider/spaces.provider.js +18 -8
  54. package/dist/file/provider/spaces.provider.js.map +1 -1
  55. package/dist/index.d.ts +4 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration/services/event-subscriber.registry.d.ts +5 -0
  60. package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -1
  61. package/dist/integration/services/event-subscriber.registry.js +15 -0
  62. package/dist/integration/services/event-subscriber.registry.js.map +1 -1
  63. package/dist/integration/services/outbox.processor.d.ts.map +1 -1
  64. package/dist/integration/services/outbox.processor.js +2 -4
  65. package/dist/integration/services/outbox.processor.js.map +1 -1
  66. package/dist/integration/services/outbox.service.d.ts +4 -2
  67. package/dist/integration/services/outbox.service.d.ts.map +1 -1
  68. package/dist/integration/services/outbox.service.js +26 -15
  69. package/dist/integration/services/outbox.service.js.map +1 -1
  70. package/dist/integration-profile/integration-credential-crypto.service.d.ts +43 -0
  71. package/dist/integration-profile/integration-credential-crypto.service.d.ts.map +1 -0
  72. package/dist/integration-profile/integration-credential-crypto.service.js +142 -0
  73. package/dist/integration-profile/integration-credential-crypto.service.js.map +1 -0
  74. package/dist/integration-profile/integration-profile.controller.d.ts +6 -7
  75. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  76. package/dist/integration-profile/integration-profile.controller.js +4 -3
  77. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  78. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
  79. package/dist/integration-profile/integration-profile.module.js +5 -2
  80. package/dist/integration-profile/integration-profile.module.js.map +1 -1
  81. package/dist/integration-profile/integration-profile.secrets.d.ts +16 -0
  82. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -0
  83. package/dist/integration-profile/integration-profile.secrets.js +56 -0
  84. package/dist/integration-profile/integration-profile.secrets.js.map +1 -0
  85. package/dist/integration-profile/integration-profile.service.d.ts +125 -8
  86. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  87. package/dist/integration-profile/integration-profile.service.js +257 -68
  88. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  89. package/dist/integration-profile/integration-profile.utils.d.ts +17 -0
  90. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  91. package/dist/integration-profile/integration-profile.utils.js +31 -0
  92. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  93. package/dist/language/en.json +178 -0
  94. package/dist/language/pt.json +178 -0
  95. package/dist/mail/mail.controller.d.ts.map +1 -1
  96. package/dist/mail/mail.controller.js +5 -28
  97. package/dist/mail/mail.controller.js.map +1 -1
  98. package/dist/mail/mail.module.js +18 -18
  99. package/dist/mail/mail.module.js.map +1 -1
  100. package/dist/mail/mail.service.d.ts +13 -2
  101. package/dist/mail/mail.service.d.ts.map +1 -1
  102. package/dist/mail/mail.service.js +25 -15
  103. package/dist/mail/mail.service.js.map +1 -1
  104. package/dist/mcp-chat/mcp-chat.service.js +6 -5
  105. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  106. package/dist/oauth/providers/facebook.provider.js +5 -5
  107. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  108. package/dist/oauth/providers/github.provider.js +5 -5
  109. package/dist/oauth/providers/github.provider.js.map +1 -1
  110. package/dist/oauth/providers/google.provider.js +5 -5
  111. package/dist/oauth/providers/google.provider.js.map +1 -1
  112. package/dist/oauth/providers/microsoft-entra-id.provider.js +5 -5
  113. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  114. package/dist/oauth/providers/microsoft.provider.js +5 -5
  115. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  116. package/dist/pdf/pdf.service.d.ts +4 -0
  117. package/dist/pdf/pdf.service.d.ts.map +1 -1
  118. package/dist/pdf/pdf.service.js +16 -9
  119. package/dist/pdf/pdf.service.js.map +1 -1
  120. package/dist/profile/profile.controller.d.ts +7 -7
  121. package/dist/profile/profile.controller.d.ts.map +1 -1
  122. package/dist/profile/profile.controller.js +28 -21
  123. package/dist/profile/profile.controller.js.map +1 -1
  124. package/dist/profile/profile.service.d.ts +7 -7
  125. package/dist/profile/profile.service.d.ts.map +1 -1
  126. package/dist/profile/profile.service.js +21 -25
  127. package/dist/profile/profile.service.js.map +1 -1
  128. package/dist/setting/setting.service.d.ts +1 -0
  129. package/dist/setting/setting.service.d.ts.map +1 -1
  130. package/dist/setting/setting.service.js +15 -2
  131. package/dist/setting/setting.service.js.map +1 -1
  132. package/dist/token/token.service.d.ts.map +1 -1
  133. package/dist/token/token.service.js +2 -1
  134. package/dist/token/token.service.js.map +1 -1
  135. package/dist/user/user.controller.d.ts +9 -0
  136. package/dist/user/user.controller.d.ts.map +1 -1
  137. package/dist/user/user.controller.js +13 -0
  138. package/dist/user/user.controller.js.map +1 -1
  139. package/dist/user/user.service.d.ts +7 -0
  140. package/dist/user/user.service.d.ts.map +1 -1
  141. package/dist/user/user.service.js +19 -0
  142. package/dist/user/user.service.js.map +1 -1
  143. package/dist/utils/webauthn-origin.d.ts +11 -0
  144. package/dist/utils/webauthn-origin.d.ts.map +1 -0
  145. package/dist/utils/webauthn-origin.js +37 -0
  146. package/dist/utils/webauthn-origin.js.map +1 -0
  147. package/dist/webhook-integration/webhook-integration.controller.d.ts +13 -0
  148. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  149. package/dist/webhook-integration/webhook-integration.controller.js +29 -0
  150. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  151. package/dist/webhook-integration/webhook-integration.module.js +3 -3
  152. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  153. package/dist/webhook-integration/webhook-integration.service.d.ts +18 -2
  154. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  155. package/dist/webhook-integration/webhook-integration.service.js +110 -6
  156. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  157. package/hedhog/data/integration_provider.yaml +9 -0
  158. package/hedhog/data/integration_type.yaml +6 -0
  159. package/hedhog/data/menu.yaml +6 -0
  160. package/hedhog/data/role.yaml +7 -0
  161. package/hedhog/data/route.yaml +36 -0
  162. package/hedhog/data/setting_group.yaml +119 -13
  163. package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
  164. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
  165. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
  166. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
  167. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
  168. package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
  169. package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
  170. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
  171. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
  172. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
  173. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
  174. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
  175. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
  176. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
  177. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +141 -29
  178. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
  179. package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
  180. package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
  181. package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
  182. package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
  183. package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
  184. package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
  185. package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
  186. package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
  187. package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
  188. package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
  189. package/hedhog/frontend/messages/en.json +69 -4
  190. package/hedhog/frontend/messages/pt.json +69 -4
  191. package/hedhog/table/setting.yaml +2 -0
  192. package/hedhog/table/webhook_call_log.yaml +49 -46
  193. package/package.json +8 -3
  194. package/src/ai/ai.service.ts +9 -6
  195. package/src/app-command/app-command.module.ts +9 -0
  196. package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
  197. package/src/auth/auth.controller.ts +20 -4
  198. package/src/auth/auth.service.ts +10 -36
  199. package/src/auth/dto/forget.dto.ts +5 -1
  200. package/src/auth/guards/auth.guard.ts +4 -1
  201. package/src/challenge/challenge.service.ts +30 -19
  202. package/src/file/dto/upload.dto.ts +25 -1
  203. package/src/file/file.controller.ts +31 -18
  204. package/src/file/file.service.ts +341 -16
  205. package/src/file/image-optimization.service.ts +111 -46
  206. package/src/file/provider/s3.provider.ts +16 -7
  207. package/src/file/provider/spaces.provider.ts +15 -5
  208. package/src/index.ts +4 -1
  209. package/src/integration/services/event-subscriber.registry.ts +15 -0
  210. package/src/integration/services/outbox.processor.ts +2 -5
  211. package/src/integration/services/outbox.service.ts +35 -17
  212. package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
  213. package/src/integration-profile/integration-profile.controller.ts +2 -0
  214. package/src/integration-profile/integration-profile.module.ts +5 -2
  215. package/src/integration-profile/integration-profile.secrets.ts +51 -0
  216. package/src/integration-profile/integration-profile.service.ts +335 -54
  217. package/src/integration-profile/integration-profile.utils.ts +56 -0
  218. package/src/language/en.json +5 -1
  219. package/src/language/pt.json +5 -1
  220. package/src/mail/mail.controller.ts +5 -29
  221. package/src/mail/mail.module.ts +5 -5
  222. package/src/mail/mail.service.ts +37 -13
  223. package/src/mcp-chat/mcp-chat.service.ts +5 -5
  224. package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
  225. package/src/oauth/providers/facebook.provider.ts +3 -3
  226. package/src/oauth/providers/github.provider.ts +3 -3
  227. package/src/oauth/providers/google.provider.ts +3 -3
  228. package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
  229. package/src/oauth/providers/microsoft.provider.ts +3 -3
  230. package/src/pdf/pdf.service.ts +19 -6
  231. package/src/profile/profile.controller.ts +21 -14
  232. package/src/profile/profile.service.ts +23 -20
  233. package/src/security/security.service.spec.ts +222 -0
  234. package/src/setting/setting.service.ts +15 -2
  235. package/src/token/token.service.ts +2 -1
  236. package/src/user/user.controller.ts +26 -16
  237. package/src/user/user.service.ts +25 -0
  238. package/src/utils/webauthn-origin.ts +38 -0
  239. package/src/webhook-integration/webhook-integration.controller.ts +23 -0
  240. package/src/webhook-integration/webhook-integration.module.ts +3 -3
  241. package/src/webhook-integration/webhook-integration.service.ts +115 -5
  242. package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
  243. package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
  244. package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
  245. package/src/menu/menu.service.spec.ts +0 -247
  246. package/src/route/route.service.spec.ts +0 -300
  247. package/src/screen/screen.service.spec.ts +0 -298
  248. package/src/user/user.service.spec.ts +0 -294
@@ -16,6 +16,9 @@ import {
16
16
  SelectValue,
17
17
  } from '@/components/ui/select';
18
18
  import { Switch } from '@/components/ui/switch';
19
+ import { TagsInput } from '@/components/ui/tags-input';
20
+ import { ByteSizeInput } from './byte-size-input';
21
+ import { DurationInput } from './duration-input';
19
22
  import { revalidateSystemIcon } from '@/lib/revalidate-icon';
20
23
  import { cn } from '@/lib/utils';
21
24
  import {
@@ -88,6 +91,7 @@ function getLocalizedIntegrationName(
88
91
  }
89
92
 
90
93
  const MAIL_INTEGRATION_PROFILE_SETTING_SLUG = 'mail-integration-profile-id';
94
+ const SCALING_INFRA_PROFILE_SETTING_SLUG = 'scaling.video.integration_profile_id';
91
95
  const COMMERCE_PAYMENT_PROFILE_SETTING_SLUG =
92
96
  'commerce-default-payment-profile-id';
93
97
  const DAILY_REPORT_EMAIL_INTEGRATION_SETTING_SLUG =
@@ -106,9 +110,16 @@ const OAUTH_PROFILE_SLUG_TO_PROVIDER: Record<string, string> = {
106
110
  'oauth-microsoft-profile-id': 'microsoft-oauth',
107
111
  'oauth-microsoft-entra-id-profile-id': 'microsoft-entra-id-oauth',
108
112
  };
109
- const INTEGRATION_PROFILE_PICKER_SLUGS = [
110
- 'file-storage-profile-id',
113
+ // Settings cujo valor é o SLUG de um integration_profile. O picker continua sendo
114
+ // selecionado/exibido pelo nome; apenas o valor persistido é o slug.
115
+ const ALL_INTEGRATION_PROFILE_SETTING_SLUGS = [
116
+ MAIL_INTEGRATION_PROFILE_SETTING_SLUG,
117
+ SCALING_INFRA_PROFILE_SETTING_SLUG,
111
118
  COMMERCE_PAYMENT_PROFILE_SETTING_SLUG,
119
+ DAILY_REPORT_EMAIL_INTEGRATION_SETTING_SLUG,
120
+ DAILY_REPORT_WHATSAPP_INTEGRATION_SETTING_SLUG,
121
+ 'file-storage-profile-id',
122
+ 'lms-bulk-upload-storage-profile-id',
112
123
  ...AI_PROFILE_SETTING_SLUGS,
113
124
  ...Object.keys(OAUTH_PROFILE_SLUG_TO_PROVIDER),
114
125
  ];
@@ -199,6 +210,11 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
199
210
  const [storageProfileEditingId, setStorageProfileEditingId] = useState<
200
211
  number | null
201
212
  >(null);
213
+ // Id resolvido a partir do slug salvo (a setting guarda o slug; o botão "editar"
214
+ // e o sheet precisam do id numérico do perfil).
215
+ const [resolvedProfileId, setResolvedProfileId] = useState<number | null>(
216
+ null
217
+ );
202
218
  const [showSecretValue, setShowSecretValue] = useState(false);
203
219
  const [isUploading, setIsUploading] = useState(false);
204
220
  const debounceTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
@@ -261,21 +277,24 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
261
277
  }, [localValue]);
262
278
 
263
279
  useEffect(() => {
264
- if (!INTEGRATION_PROFILE_PICKER_SLUGS.includes(setting.slug)) {
280
+ if (!ALL_INTEGRATION_PROFILE_SETTING_SLUGS.includes(setting.slug)) {
265
281
  setStorageProfileSelectedLabel('');
282
+ setResolvedProfileId(null);
266
283
  return;
267
284
  }
268
285
 
269
- const profileId = Number(localValue);
270
- if (!profileId) {
286
+ const profileSlug = String(localValue ?? '').trim();
287
+ if (!profileSlug) {
271
288
  setStorageProfileSelectedLabel('');
289
+ setResolvedProfileId(null);
272
290
  return;
273
291
  }
274
292
 
275
293
  let isCurrent = true;
276
294
 
277
- request<IntegrationProfileOption>({
278
- url: `/integration-profile/${profileId}`,
295
+ const qs = new URLSearchParams({ slug: profileSlug, pageSize: '1' });
296
+ request<ApiPaginated<IntegrationProfileOption>>({
297
+ url: `/integration-profile?${qs}`,
279
298
  method: 'GET',
280
299
  })
281
300
  .then((response) => {
@@ -283,11 +302,14 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
283
302
  return;
284
303
  }
285
304
 
286
- setStorageProfileSelectedLabel(response.data?.name ?? '');
305
+ const profile = response.data?.data?.[0] ?? null;
306
+ setStorageProfileSelectedLabel(profile?.name ?? '');
307
+ setResolvedProfileId(profile?.id ?? null);
287
308
  })
288
309
  .catch(() => {
289
310
  if (isCurrent) {
290
311
  setStorageProfileSelectedLabel('');
312
+ setResolvedProfileId(null);
291
313
  }
292
314
  });
293
315
 
@@ -375,6 +397,36 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
375
397
  />
376
398
  );
377
399
 
400
+ case SettingComponentEnum.INPUT_DURATION:
401
+ isTextInputRef.current = true; // reaproveita o debounce de 500ms existente
402
+ return (
403
+ <DurationInput
404
+ value={
405
+ typeof parsedValue === 'number'
406
+ ? parsedValue
407
+ : parseFloat(parsedValue) || 0
408
+ }
409
+ onChange={(seconds) => handleChangeValue(formatValue(seconds))}
410
+ persistKey={setting.slug}
411
+ className={cn(className, 'w-full min-w-0 bg-background md:min-w-50')}
412
+ />
413
+ );
414
+
415
+ case SettingComponentEnum.INPUT_BYTES:
416
+ isTextInputRef.current = true; // reaproveita o debounce de 500ms existente
417
+ return (
418
+ <ByteSizeInput
419
+ value={
420
+ typeof parsedValue === 'number'
421
+ ? parsedValue
422
+ : parseFloat(parsedValue) || 0
423
+ }
424
+ onChange={(bytes) => handleChangeValue(formatValue(bytes))}
425
+ persistKey={setting.slug}
426
+ className={cn(className, 'w-full min-w-0 bg-background md:min-w-50')}
427
+ />
428
+ );
429
+
378
430
  case SettingComponentEnum.INPUT_SECRET:
379
431
  isTextInputRef.current = true;
380
432
  return (
@@ -503,6 +555,17 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
503
555
  </div>
504
556
  );
505
557
 
558
+ case SettingComponentEnum.INPUT_TAGS: {
559
+ const tagsValue: string[] = Array.isArray(parsedValue) ? parsedValue : [];
560
+ return (
561
+ <TagsInput
562
+ value={tagsValue}
563
+ onChange={(val) => handleChangeValue(formatValue(val), true)}
564
+ className={cn(className, 'w-full min-w-0 bg-background md:min-w-50')}
565
+ />
566
+ );
567
+ }
568
+
506
569
  case SettingComponentEnum.SWITCH:
507
570
  return (
508
571
  <Switch
@@ -664,12 +727,15 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
664
727
  <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
665
728
  placeholder={t('selectStorageProfile')}
666
729
  emptyStateDescription={t('storageProfileEmptyState')}
667
- value={parsedValue ? Number(parsedValue) : null}
668
- onChange={(id, option) => {
730
+ value={parsedValue || null}
731
+ onChange={(slug, option) => {
669
732
  setStorageProfileSelectedLabel(option?.name ?? '');
670
- handleChangeValue(id != null ? String(id) : '', true);
733
+ setResolvedProfileId(
734
+ option ? Number((option as { id?: number }).id) || null : null
735
+ );
736
+ handleChangeValue(slug != null ? String(slug) : '', true);
671
737
  }}
672
- valueType="number"
738
+ valueType="string"
673
739
  initialSelectedLabel={storageProfileSelectedLabel}
674
740
  showCreateButton={false}
675
741
  clearable={false}
@@ -695,7 +761,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
695
761
  hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
696
762
  };
697
763
  }}
698
- getOptionValue={(opt) => opt.id}
764
+ getOptionValue={(opt) => opt.slug}
699
765
  getOptionLabel={(opt) => String(opt.name ?? '')}
700
766
  getOptionDescription={(opt) => {
701
767
  const providerSlug = String(
@@ -718,12 +784,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
718
784
  size="icon"
719
785
  className="h-10 w-10 shrink-0"
720
786
  onClick={() => {
721
- setStorageProfileEditingId(
722
- parsedValue ? Number(parsedValue) : null
723
- );
787
+ setStorageProfileEditingId(resolvedProfileId);
724
788
  setIsStorageProfileSheetOpen(true);
725
789
  }}
726
- disabled={!parsedValue}
790
+ disabled={!resolvedProfileId}
727
791
  aria-label="Editar perfil de armazenamento"
728
792
  title="Editar perfil de armazenamento"
729
793
  >
@@ -759,7 +823,8 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
759
823
  lockedTypeSlug="storage"
760
824
  onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
761
825
  setStorageProfileSelectedLabel(profile.name ?? '');
762
- handleChangeValue(String(profile.id), true);
826
+ setResolvedProfileId(profile.id ?? null);
827
+ handleChangeValue(String(profile.slug ?? ''), true);
763
828
  }}
764
829
  />
765
830
  </>
@@ -773,12 +838,15 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
773
838
  <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
774
839
  placeholder={t('selectAiProfile')}
775
840
  emptyStateDescription={t('aiProfileEmptyState')}
776
- value={parsedValue ? Number(parsedValue) : null}
777
- onChange={(id, option) => {
841
+ value={parsedValue || null}
842
+ onChange={(slug, option) => {
778
843
  setStorageProfileSelectedLabel(option?.name ?? '');
779
- handleChangeValue(id != null ? String(id) : '', true);
844
+ setResolvedProfileId(
845
+ option ? Number((option as { id?: number }).id) || null : null
846
+ );
847
+ handleChangeValue(slug != null ? String(slug) : '', true);
780
848
  }}
781
- valueType="number"
849
+ valueType="string"
782
850
  initialSelectedLabel={storageProfileSelectedLabel}
783
851
  showCreateButton={false}
784
852
  clearable={false}
@@ -803,7 +871,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
803
871
  hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
804
872
  };
805
873
  }}
806
- getOptionValue={(opt) => opt.id}
874
+ getOptionValue={(opt) => opt.slug}
807
875
  getOptionLabel={(opt) => String(opt.name ?? '')}
808
876
  getOptionDescription={(opt) => {
809
877
  const providerSlug = String(
@@ -826,12 +894,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
826
894
  size="icon"
827
895
  className="h-10 w-10 shrink-0"
828
896
  onClick={() => {
829
- setStorageProfileEditingId(
830
- parsedValue ? Number(parsedValue) : null
831
- );
897
+ setStorageProfileEditingId(resolvedProfileId);
832
898
  setIsStorageProfileSheetOpen(true);
833
899
  }}
834
- disabled={!parsedValue}
900
+ disabled={!resolvedProfileId}
835
901
  aria-label="Editar perfil de IA"
836
902
  title="Editar perfil de IA"
837
903
  >
@@ -865,7 +931,8 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
865
931
  lockedTypeSlug="ai"
866
932
  onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
867
933
  setStorageProfileSelectedLabel(profile.name ?? '');
868
- handleChangeValue(String(profile.id), true);
934
+ setResolvedProfileId(profile.id ?? null);
935
+ handleChangeValue(String(profile.slug ?? ''), true);
869
936
  }}
870
937
  />
871
938
  </>
@@ -880,12 +947,15 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
880
947
  <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
881
948
  placeholder={t('selectOAuthProfile')}
882
949
  emptyStateDescription={t('oauthProfileEmptyState')}
883
- value={parsedValue ? Number(parsedValue) : null}
884
- onChange={(id, option) => {
950
+ value={parsedValue || null}
951
+ onChange={(slug, option) => {
885
952
  setStorageProfileSelectedLabel(option?.name ?? '');
886
- handleChangeValue(id != null ? String(id) : '', true);
953
+ setResolvedProfileId(
954
+ option ? Number((option as { id?: number }).id) || null : null
955
+ );
956
+ handleChangeValue(slug != null ? String(slug) : '', true);
887
957
  }}
888
- valueType="number"
958
+ valueType="string"
889
959
  initialSelectedLabel={storageProfileSelectedLabel}
890
960
  showCreateButton={false}
891
961
  clearable={false}
@@ -910,7 +980,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
910
980
  hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
911
981
  };
912
982
  }}
913
- getOptionValue={(opt) => opt.id}
983
+ getOptionValue={(opt) => opt.slug}
914
984
  getOptionLabel={(opt) => String(opt.name ?? '')}
915
985
  getOptionDescription={(opt) => {
916
986
  const providerSlug = String(
@@ -933,12 +1003,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
933
1003
  size="icon"
934
1004
  className="h-10 w-10 shrink-0"
935
1005
  onClick={() => {
936
- setStorageProfileEditingId(
937
- parsedValue ? Number(parsedValue) : null
938
- );
1006
+ setStorageProfileEditingId(resolvedProfileId);
939
1007
  setIsStorageProfileSheetOpen(true);
940
1008
  }}
941
- disabled={!parsedValue}
1009
+ disabled={!resolvedProfileId}
942
1010
  aria-label="Editar perfil OAuth"
943
1011
  title="Editar perfil OAuth"
944
1012
  >
@@ -972,7 +1040,8 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
972
1040
  lockedTypeSlug="oauth"
973
1041
  onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
974
1042
  setStorageProfileSelectedLabel(profile.name ?? '');
975
- handleChangeValue(String(profile.id), true);
1043
+ setResolvedProfileId(profile.id ?? null);
1044
+ handleChangeValue(String(profile.slug ?? ''), true);
976
1045
  }}
977
1046
  />
978
1047
  </>
@@ -986,12 +1055,15 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
986
1055
  <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
987
1056
  placeholder={t('selectPaymentProfile')}
988
1057
  emptyStateDescription={t('paymentProfileEmptyState')}
989
- value={parsedValue ? Number(parsedValue) : null}
990
- onChange={(id, option) => {
1058
+ value={parsedValue || null}
1059
+ onChange={(slug, option) => {
991
1060
  setStorageProfileSelectedLabel(option?.name ?? '');
992
- handleChangeValue(id != null ? String(id) : '', true);
1061
+ setResolvedProfileId(
1062
+ option ? Number((option as { id?: number }).id) || null : null
1063
+ );
1064
+ handleChangeValue(slug != null ? String(slug) : '', true);
993
1065
  }}
994
- valueType="number"
1066
+ valueType="string"
995
1067
  initialSelectedLabel={storageProfileSelectedLabel}
996
1068
  showCreateButton={false}
997
1069
  clearable={false}
@@ -1016,7 +1088,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1016
1088
  hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
1017
1089
  };
1018
1090
  }}
1019
- getOptionValue={(opt) => opt.id}
1091
+ getOptionValue={(opt) => opt.slug}
1020
1092
  getOptionLabel={(opt) => String(opt.name ?? '')}
1021
1093
  getOptionDescription={(opt) => {
1022
1094
  const providerSlug = String(
@@ -1039,12 +1111,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1039
1111
  size="icon"
1040
1112
  className="h-10 w-10 shrink-0"
1041
1113
  onClick={() => {
1042
- setStorageProfileEditingId(
1043
- parsedValue ? Number(parsedValue) : null
1044
- );
1114
+ setStorageProfileEditingId(resolvedProfileId);
1045
1115
  setIsStorageProfileSheetOpen(true);
1046
1116
  }}
1047
- disabled={!parsedValue}
1117
+ disabled={!resolvedProfileId}
1048
1118
  aria-label="Editar perfil de pagamento"
1049
1119
  title="Editar perfil de pagamento"
1050
1120
  >
@@ -1078,7 +1148,116 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1078
1148
  lockedTypeSlug="payment"
1079
1149
  onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
1080
1150
  setStorageProfileSelectedLabel(profile.name ?? '');
1081
- handleChangeValue(String(profile.id), true);
1151
+ setResolvedProfileId(profile.id ?? null);
1152
+ handleChangeValue(String(profile.slug ?? ''), true);
1153
+ }}
1154
+ />
1155
+ </>
1156
+ );
1157
+ }
1158
+
1159
+ if (setting.slug === SCALING_INFRA_PROFILE_SETTING_SLUG) {
1160
+ return (
1161
+ <>
1162
+ <div className="flex w-full min-w-0 items-center gap-2 md:max-w-72">
1163
+ <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
1164
+ placeholder={t('selectInfraProfile')}
1165
+ emptyStateDescription={t('infraProfileEmptyState')}
1166
+ value={parsedValue || null}
1167
+ onChange={(slug, option) => {
1168
+ setStorageProfileSelectedLabel(option?.name ?? '');
1169
+ setResolvedProfileId(
1170
+ option ? Number((option as { id?: number }).id) || null : null
1171
+ );
1172
+ handleChangeValue(slug != null ? String(slug) : '', true);
1173
+ }}
1174
+ valueType="string"
1175
+ initialSelectedLabel={storageProfileSelectedLabel}
1176
+ showCreateButton={false}
1177
+ clearable
1178
+ loadOptions={async ({ page, pageSize, search }) => {
1179
+ const qs = new URLSearchParams({
1180
+ page: String(page),
1181
+ pageSize: String(pageSize),
1182
+ typeSlug: 'infrastructure',
1183
+ });
1184
+ if (search) qs.set('search', search);
1185
+ const response = await request<
1186
+ ApiPaginated<
1187
+ IntegrationProfileOption & Record<string, unknown>
1188
+ >
1189
+ >({
1190
+ url: `/integration-profile?${qs}`,
1191
+ method: 'GET',
1192
+ });
1193
+ const paged = response.data;
1194
+ return {
1195
+ items: paged?.data ?? [],
1196
+ hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
1197
+ };
1198
+ }}
1199
+ getOptionValue={(opt) => opt.slug}
1200
+ getOptionLabel={(opt) => String(opt.name ?? '')}
1201
+ getOptionDescription={(opt) => {
1202
+ const providerSlug = String(
1203
+ opt.integration_provider?.slug ?? ''
1204
+ );
1205
+ const providerName = getLocalizedIntegrationName(
1206
+ opt.integration_provider?.integration_provider_locale,
1207
+ currentLocaleCode
1208
+ );
1209
+ return [providerName || providerSlug, opt.slug]
1210
+ .filter(Boolean)
1211
+ .join(' • ');
1212
+ }}
1213
+ className={cn('flex-1 min-w-0', className)}
1214
+ />
1215
+
1216
+ <Button
1217
+ type="button"
1218
+ variant="outline"
1219
+ size="icon"
1220
+ className="h-10 w-10 shrink-0"
1221
+ onClick={() => {
1222
+ setStorageProfileEditingId(resolvedProfileId);
1223
+ setIsStorageProfileSheetOpen(true);
1224
+ }}
1225
+ disabled={!resolvedProfileId}
1226
+ aria-label="Editar perfil de infraestrutura"
1227
+ title="Editar perfil de infraestrutura"
1228
+ >
1229
+ <Pencil className="h-4 w-4" />
1230
+ </Button>
1231
+
1232
+ <Button
1233
+ type="button"
1234
+ variant="outline"
1235
+ size="icon"
1236
+ className="h-10 w-10 shrink-0"
1237
+ onClick={() => {
1238
+ setStorageProfileEditingId(null);
1239
+ setIsStorageProfileSheetOpen(true);
1240
+ }}
1241
+ aria-label="Criar perfil de infraestrutura"
1242
+ title="Criar perfil de infraestrutura"
1243
+ >
1244
+ <Plus className="h-4 w-4" />
1245
+ </Button>
1246
+ </div>
1247
+
1248
+ <IntegrationProfileSheet
1249
+ open={isStorageProfileSheetOpen}
1250
+ onOpenChange={(nextOpen) => {
1251
+ setIsStorageProfileSheetOpen(nextOpen);
1252
+ if (!nextOpen) setStorageProfileEditingId(null);
1253
+ }}
1254
+ profileId={storageProfileEditingId}
1255
+ initialTypeSlug="infrastructure"
1256
+ lockedTypeSlug="infrastructure"
1257
+ onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
1258
+ setStorageProfileSelectedLabel(profile.name ?? '');
1259
+ setResolvedProfileId(profile.id ?? null);
1260
+ handleChangeValue(String(profile.slug ?? ''), true);
1082
1261
  }}
1083
1262
  />
1084
1263
  </>
@@ -1092,12 +1271,15 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1092
1271
  <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
1093
1272
  placeholder={t('selectMailProfile')}
1094
1273
  emptyStateDescription={t('mailProfileEmptyState')}
1095
- value={parsedValue ? Number(parsedValue) : null}
1096
- onChange={(id, option) => {
1274
+ value={parsedValue || null}
1275
+ onChange={(slug, option) => {
1097
1276
  setStorageProfileSelectedLabel(option?.name ?? '');
1098
- handleChangeValue(id != null ? String(id) : '', true);
1277
+ setResolvedProfileId(
1278
+ option ? Number((option as { id?: number }).id) || null : null
1279
+ );
1280
+ handleChangeValue(slug != null ? String(slug) : '', true);
1099
1281
  }}
1100
- valueType="number"
1282
+ valueType="string"
1101
1283
  initialSelectedLabel={storageProfileSelectedLabel}
1102
1284
  showCreateButton={false}
1103
1285
  clearable={false}
@@ -1122,7 +1304,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1122
1304
  hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
1123
1305
  };
1124
1306
  }}
1125
- getOptionValue={(opt) => opt.id}
1307
+ getOptionValue={(opt) => opt.slug}
1126
1308
  getOptionLabel={(opt) => String(opt.name ?? '')}
1127
1309
  getOptionDescription={(opt) => {
1128
1310
  const providerSlug = String(
@@ -1145,12 +1327,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1145
1327
  size="icon"
1146
1328
  className="h-10 w-10 shrink-0"
1147
1329
  onClick={() => {
1148
- setStorageProfileEditingId(
1149
- parsedValue ? Number(parsedValue) : null
1150
- );
1330
+ setStorageProfileEditingId(resolvedProfileId);
1151
1331
  setIsStorageProfileSheetOpen(true);
1152
1332
  }}
1153
- disabled={!parsedValue}
1333
+ disabled={!resolvedProfileId}
1154
1334
  aria-label="Editar perfil de email"
1155
1335
  title="Editar perfil de email"
1156
1336
  >
@@ -1184,7 +1364,8 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1184
1364
  lockedTypeSlug="email"
1185
1365
  onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
1186
1366
  setStorageProfileSelectedLabel(profile.name ?? '');
1187
- handleChangeValue(String(profile.id), true);
1367
+ setResolvedProfileId(profile.id ?? null);
1368
+ handleChangeValue(String(profile.slug ?? ''), true);
1188
1369
  }}
1189
1370
  />
1190
1371
  </>
@@ -1225,11 +1406,11 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1225
1406
  ? t('selectMailProfile')
1226
1407
  : t('selectWhatsAppProfile')
1227
1408
  }
1228
- value={parsedValue ? Number(parsedValue) : null}
1229
- onChange={(id) =>
1230
- handleChangeValue(id != null ? String(id) : '', true)
1409
+ value={parsedValue || null}
1410
+ onChange={(slug) =>
1411
+ handleChangeValue(slug != null ? String(slug) : '', true)
1231
1412
  }
1232
- valueType="number"
1413
+ valueType="string"
1233
1414
  loadOptions={async ({ page, pageSize, search }) => {
1234
1415
  const qs = new URLSearchParams({
1235
1416
  page: String(page),
@@ -1249,7 +1430,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
1249
1430
  hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
1250
1431
  };
1251
1432
  }}
1252
- getOptionValue={(opt) => opt.id}
1433
+ getOptionValue={(opt) => opt.slug}
1253
1434
  getOptionLabel={(opt) => String(opt.name ?? '')}
1254
1435
  getOptionDescription={(opt) =>
1255
1436
  String(opt.integration_provider?.slug ?? opt.slug ?? '')
@@ -158,6 +158,8 @@ const normalizeUserDashboardLayout = (item: WidgetLayout): LayoutItem => {
158
158
  return layoutItem;
159
159
  };
160
160
 
161
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
162
+
161
163
  export const DashboardContent = ({
162
164
  dashboardSlug,
163
165
  showHeader = true,
@@ -183,11 +185,41 @@ export const DashboardContent = ({
183
185
  const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
184
186
  const [hasChanges, setHasChanges] = useState(false);
185
187
  const [isSaving, setIsSaving] = useState(false);
188
+ const { data: generalSettings } = useQuery<{
189
+ data: Array<{ slug: string; value: string }>;
190
+ }>({
191
+ queryKey: ['setting-group-general'],
192
+ queryFn: async () => {
193
+ const response = await request<{
194
+ data: Array<{ slug: string; value: string }>;
195
+ }>({ url: '/setting/group/general', method: 'GET' });
196
+ return response.data;
197
+ },
198
+ staleTime: 5 * 60 * 1000,
199
+ });
200
+
201
+ const pageSizeOptions = useMemo(() => {
202
+ const setting = generalSettings?.data?.find(
203
+ (s) => s.slug === 'pagination-page-sizes'
204
+ );
205
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
206
+ try {
207
+ const parsed = JSON.parse(setting.value) as string[];
208
+ const sizes = parsed
209
+ .map(Number)
210
+ .filter((n) => !isNaN(n) && n > 0)
211
+ .sort((a, b) => a - b);
212
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
213
+ } catch {
214
+ return DEFAULT_PAGE_SIZES;
215
+ }
216
+ }, [generalSettings]);
217
+
186
218
  const [componentsPage, setComponentsPage] = useState(1);
187
219
  const [componentsPageSize, setComponentsPageSize] = usePersistedPageSize({
188
220
  storageKey: 'pagination:global:pageSize',
189
221
  defaultValue: 12,
190
- allowedValues: [6, 12, 24, 48],
222
+ allowedValues: pageSizeOptions,
191
223
  });
192
224
  const [componentsSearchQuery, setComponentsSearchQuery] = useState('');
193
225
  const [componentsModuleFilter, setComponentsModuleFilter] = useState('all');
@@ -134,6 +134,8 @@ type RequestError = {
134
134
  };
135
135
  };
136
136
 
137
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
138
+
137
139
  export function ComponentRolesTab() {
138
140
  const t = useTranslations('core.DashboardManagement');
139
141
  const { request, currentLocaleCode, getSettingValue } = useApp();
@@ -147,10 +149,40 @@ export function ComponentRolesTab() {
147
149
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
148
150
  const [roleToDelete, setRoleToDelete] = useState<number | null>(null);
149
151
  const [page, setPage] = useState(1);
152
+ const { data: generalSettings } = useQuery<{
153
+ data: Array<{ slug: string; value: string }>;
154
+ }>({
155
+ queryKey: ['setting-group-general'],
156
+ queryFn: async () => {
157
+ const response = await request<{
158
+ data: Array<{ slug: string; value: string }>;
159
+ }>({ url: '/setting/group/general', method: 'GET' });
160
+ return response.data;
161
+ },
162
+ staleTime: 5 * 60 * 1000,
163
+ });
164
+
165
+ const pageSizeOptions = useMemo(() => {
166
+ const setting = generalSettings?.data?.find(
167
+ (s) => s.slug === 'pagination-page-sizes'
168
+ );
169
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
170
+ try {
171
+ const parsed = JSON.parse(setting.value) as string[];
172
+ const sizes = parsed
173
+ .map(Number)
174
+ .filter((n) => !isNaN(n) && n > 0)
175
+ .sort((a, b) => a - b);
176
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
177
+ } catch {
178
+ return DEFAULT_PAGE_SIZES;
179
+ }
180
+ }, [generalSettings]);
181
+
150
182
  const [pageSize, setPageSize] = usePersistedPageSize({
151
183
  storageKey: 'pagination:global:pageSize',
152
- defaultValue: 10,
153
- allowedValues: [10, 20, 30, 50],
184
+ defaultValue: 12,
185
+ allowedValues: pageSizeOptions,
154
186
  });
155
187
  const searchQuery = '';
156
188
  const debouncedSearch = useDebounce(searchQuery, 500);
@@ -540,7 +572,7 @@ export function ComponentRolesTab() {
540
572
  <SelectValue />
541
573
  </SelectTrigger>
542
574
  <SelectContent>
543
- {[10, 20, 30, 50].map((size) => (
575
+ {pageSizeOptions.map((size) => (
544
576
  <SelectItem key={size} value={String(size)}>
545
577
  {size}
546
578
  </SelectItem>