@hed-hog/core 0.0.349 → 0.0.351

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 (350) hide show
  1. package/dist/access-log/access-log.controller.d.ts +16 -0
  2. package/dist/access-log/access-log.controller.d.ts.map +1 -0
  3. package/dist/access-log/access-log.controller.js +44 -0
  4. package/dist/access-log/access-log.controller.js.map +1 -0
  5. package/dist/access-log/access-log.interceptor.d.ts +9 -0
  6. package/dist/access-log/access-log.interceptor.d.ts.map +1 -0
  7. package/dist/access-log/access-log.interceptor.js +46 -0
  8. package/dist/access-log/access-log.interceptor.js.map +1 -0
  9. package/dist/access-log/access-log.module.d.ts +3 -0
  10. package/dist/access-log/access-log.module.d.ts.map +1 -0
  11. package/dist/access-log/access-log.module.js +29 -0
  12. package/dist/access-log/access-log.module.js.map +1 -0
  13. package/dist/access-log/access-log.service.d.ts +47 -0
  14. package/dist/access-log/access-log.service.d.ts.map +1 -0
  15. package/dist/access-log/access-log.service.js +113 -0
  16. package/dist/access-log/access-log.service.js.map +1 -0
  17. package/dist/access-log/dto/query-access-log.dto.d.ts +8 -0
  18. package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -0
  19. package/dist/access-log/dto/query-access-log.dto.js +42 -0
  20. package/dist/access-log/dto/query-access-log.dto.js.map +1 -0
  21. package/dist/auth/auth.controller.d.ts +12 -12
  22. package/dist/auth/auth.service.d.ts +27 -27
  23. package/dist/auth/auth.service.d.ts.map +1 -1
  24. package/dist/auth/auth.service.js +0 -4
  25. package/dist/auth/auth.service.js.map +1 -1
  26. package/dist/challenge/challenge.service.d.ts +2 -2
  27. package/dist/challenge/challenge.service.d.ts.map +1 -1
  28. package/dist/challenge/challenge.service.js +0 -1
  29. package/dist/challenge/challenge.service.js.map +1 -1
  30. package/dist/core/system.module.d.ts.map +1 -1
  31. package/dist/core/system.module.js +1 -0
  32. package/dist/core/system.module.js.map +1 -1
  33. package/dist/core.module.d.ts.map +1 -1
  34. package/dist/core.module.js +15 -0
  35. package/dist/core.module.js.map +1 -1
  36. package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
  37. package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
  38. package/dist/dashboard/dashboard.module.d.ts.map +1 -1
  39. package/dist/dashboard/dashboard.module.js +4 -1
  40. package/dist/dashboard/dashboard.module.js.map +1 -1
  41. package/dist/file/file.controller.js +0 -1
  42. package/dist/file/file.controller.js.map +1 -1
  43. package/dist/file/file.service.d.ts +65 -0
  44. package/dist/file/file.service.d.ts.map +1 -1
  45. package/dist/file/file.service.js +151 -3
  46. package/dist/file/file.service.js.map +1 -1
  47. package/dist/file/provider/abstract.provider.d.ts +8 -0
  48. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  49. package/dist/file/provider/abstract.provider.js.map +1 -1
  50. package/dist/file/provider/azure.provider.d.ts +4 -0
  51. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  52. package/dist/file/provider/azure.provider.js +14 -0
  53. package/dist/file/provider/azure.provider.js.map +1 -1
  54. package/dist/file/provider/gcs.provider.d.ts +4 -0
  55. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  56. package/dist/file/provider/gcs.provider.js +14 -0
  57. package/dist/file/provider/gcs.provider.js.map +1 -1
  58. package/dist/file/provider/local.provider.d.ts +4 -0
  59. package/dist/file/provider/local.provider.d.ts.map +1 -1
  60. package/dist/file/provider/local.provider.js +19 -0
  61. package/dist/file/provider/local.provider.js.map +1 -1
  62. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  63. package/dist/file/provider/provider.factory.js +3 -0
  64. package/dist/file/provider/provider.factory.js.map +1 -1
  65. package/dist/file/provider/s3.provider.d.ts +4 -0
  66. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  67. package/dist/file/provider/s3.provider.js +17 -0
  68. package/dist/file/provider/s3.provider.js.map +1 -1
  69. package/dist/index.d.ts +3 -0
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +4 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/mcp/guards/mcp-auth.guard.d.ts +6 -1
  74. package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
  75. package/dist/mcp/guards/mcp-auth.guard.js +29 -2
  76. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  77. package/dist/mcp/mcp-api-key.controller.d.ts +32 -0
  78. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -0
  79. package/dist/mcp/mcp-api-key.controller.js +64 -0
  80. package/dist/mcp/mcp-api-key.controller.js.map +1 -0
  81. package/dist/mcp/mcp-api-key.service.d.ts +27 -0
  82. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -0
  83. package/dist/mcp/mcp-api-key.service.js +74 -0
  84. package/dist/mcp/mcp-api-key.service.js.map +1 -0
  85. package/dist/mcp/mcp-audit.service.d.ts +3 -1
  86. package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
  87. package/dist/mcp/mcp-audit.service.js +14 -2
  88. package/dist/mcp/mcp-audit.service.js.map +1 -1
  89. package/dist/mcp/mcp-registry.service.d.ts +1 -2
  90. package/dist/mcp/mcp-registry.service.d.ts.map +1 -1
  91. package/dist/mcp/mcp-registry.service.js +6 -2
  92. package/dist/mcp/mcp-registry.service.js.map +1 -1
  93. package/dist/mcp/mcp.controller.d.ts +3 -1
  94. package/dist/mcp/mcp.controller.d.ts.map +1 -1
  95. package/dist/mcp/mcp.controller.js +10 -2
  96. package/dist/mcp/mcp.controller.js.map +1 -1
  97. package/dist/mcp/mcp.module.d.ts.map +1 -1
  98. package/dist/mcp/mcp.module.js +80 -1
  99. package/dist/mcp/mcp.module.js.map +1 -1
  100. package/dist/mcp/tools/core-ai.mcp-tools.d.ts +45 -0
  101. package/dist/mcp/tools/core-ai.mcp-tools.d.ts.map +1 -0
  102. package/dist/mcp/tools/core-ai.mcp-tools.js +214 -0
  103. package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -0
  104. package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts +21 -0
  105. package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts.map +1 -0
  106. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +89 -0
  107. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -0
  108. package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts +29 -0
  109. package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts.map +1 -0
  110. package/dist/mcp/tools/core-dashboard.mcp-tools.js +149 -0
  111. package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -0
  112. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts +30 -0
  113. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -0
  114. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +185 -0
  115. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -0
  116. package/dist/mcp/tools/core-files.mcp-tools.d.ts +26 -0
  117. package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -0
  118. package/dist/mcp/tools/core-files.mcp-tools.js +140 -0
  119. package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -0
  120. package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts +26 -0
  121. package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts.map +1 -0
  122. package/dist/mcp/tools/core-integration-events.mcp-tools.js +168 -0
  123. package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -0
  124. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +32 -0
  125. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -0
  126. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +178 -0
  127. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -0
  128. package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts +14 -0
  129. package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts.map +1 -0
  130. package/dist/mcp/tools/core-mail-sent.mcp-tools.js +106 -0
  131. package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -0
  132. package/dist/mcp/tools/core-mail.mcp-tools.d.ts +36 -0
  133. package/dist/mcp/tools/core-mail.mcp-tools.d.ts.map +1 -0
  134. package/dist/mcp/tools/core-mail.mcp-tools.js +184 -0
  135. package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -0
  136. package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts +21 -0
  137. package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts.map +1 -0
  138. package/dist/mcp/tools/core-mcp-chat.mcp-tools.js +118 -0
  139. package/dist/mcp/tools/core-mcp-chat.mcp-tools.js.map +1 -0
  140. package/dist/mcp/tools/core-menus.mcp-tools.d.ts +40 -0
  141. package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -0
  142. package/dist/mcp/tools/core-menus.mcp-tools.js +268 -0
  143. package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -0
  144. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts +31 -0
  145. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -0
  146. package/dist/mcp/tools/core-notifications.mcp-tools.js +178 -0
  147. package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -0
  148. package/dist/mcp/tools/core-profile.mcp-tools.d.ts +20 -0
  149. package/dist/mcp/tools/core-profile.mcp-tools.d.ts.map +1 -0
  150. package/dist/mcp/tools/core-profile.mcp-tools.js +111 -0
  151. package/dist/mcp/tools/core-profile.mcp-tools.js.map +1 -0
  152. package/dist/mcp/tools/core-roles.mcp-tools.d.ts +70 -0
  153. package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -0
  154. package/dist/mcp/tools/core-roles.mcp-tools.js +355 -0
  155. package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -0
  156. package/dist/mcp/tools/core-routes.mcp-tools.d.ts +52 -0
  157. package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -0
  158. package/dist/mcp/tools/core-routes.mcp-tools.js +254 -0
  159. package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -0
  160. package/dist/mcp/tools/core-screens.mcp-tools.d.ts +47 -0
  161. package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -0
  162. package/dist/mcp/tools/core-screens.mcp-tools.js +250 -0
  163. package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -0
  164. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts +20 -0
  165. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -0
  166. package/dist/mcp/tools/core-sessions.mcp-tools.js +128 -0
  167. package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -0
  168. package/dist/mcp/tools/core-settings.mcp-tools.d.ts +25 -0
  169. package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
  170. package/dist/mcp/tools/core-settings.mcp-tools.js +138 -0
  171. package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
  172. package/dist/mcp/tools/core-system.mcp-tools.d.ts +8 -0
  173. package/dist/mcp/tools/core-system.mcp-tools.d.ts.map +1 -0
  174. package/dist/mcp/tools/core-system.mcp-tools.js +40 -0
  175. package/dist/mcp/tools/core-system.mcp-tools.js.map +1 -0
  176. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts +46 -0
  177. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -0
  178. package/dist/mcp/tools/core-webhooks.mcp-tools.js +266 -0
  179. package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -0
  180. package/dist/mcp-chat/mcp-chat.controller.d.ts +5 -1
  181. package/dist/mcp-chat/mcp-chat.controller.d.ts.map +1 -1
  182. package/dist/mcp-chat/mcp-chat.controller.js +46 -0
  183. package/dist/mcp-chat/mcp-chat.controller.js.map +1 -1
  184. package/dist/mcp-chat/mcp-chat.service.d.ts +31 -3
  185. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  186. package/dist/mcp-chat/mcp-chat.service.js +776 -38
  187. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  188. package/dist/notification/dto/create-notification.dto.d.ts +26 -0
  189. package/dist/notification/dto/create-notification.dto.d.ts.map +1 -0
  190. package/dist/notification/dto/create-notification.dto.js +86 -0
  191. package/dist/notification/dto/create-notification.dto.js.map +1 -0
  192. package/dist/notification/dto/update-progress.dto.d.ts +6 -0
  193. package/dist/notification/dto/update-progress.dto.d.ts.map +1 -0
  194. package/dist/notification/dto/update-progress.dto.js +33 -0
  195. package/dist/notification/dto/update-progress.dto.js.map +1 -0
  196. package/dist/notification/notification.controller.d.ts +112 -0
  197. package/dist/notification/notification.controller.d.ts.map +1 -0
  198. package/dist/notification/notification.controller.js +130 -0
  199. package/dist/notification/notification.controller.js.map +1 -0
  200. package/dist/notification/notification.module.d.ts +3 -0
  201. package/dist/notification/notification.module.d.ts.map +1 -0
  202. package/dist/notification/notification.module.js +29 -0
  203. package/dist/notification/notification.module.js.map +1 -0
  204. package/dist/notification/notification.service.d.ts +112 -0
  205. package/dist/notification/notification.service.d.ts.map +1 -0
  206. package/dist/notification/notification.service.js +269 -0
  207. package/dist/notification/notification.service.js.map +1 -0
  208. package/dist/pdf/pdf.service.js +1 -1
  209. package/dist/pdf/pdf.service.js.map +1 -1
  210. package/dist/profile/profile.controller.d.ts +4 -4
  211. package/dist/profile/profile.controller.js +0 -1
  212. package/dist/profile/profile.controller.js.map +1 -1
  213. package/dist/profile/profile.module.d.ts.map +1 -1
  214. package/dist/profile/profile.module.js +1 -0
  215. package/dist/profile/profile.module.js.map +1 -1
  216. package/dist/profile/profile.service.d.ts +4 -4
  217. package/dist/session/session.controller.d.ts +3 -3
  218. package/dist/session/session.service.d.ts +9 -9
  219. package/dist/setting/dto/update-setting-list.dto.d.ts +9 -0
  220. package/dist/setting/dto/update-setting-list.dto.d.ts.map +1 -0
  221. package/dist/setting/dto/update-setting-list.dto.js +44 -0
  222. package/dist/setting/dto/update-setting-list.dto.js.map +1 -0
  223. package/dist/setting/setting.controller.d.ts +20 -1
  224. package/dist/setting/setting.controller.d.ts.map +1 -1
  225. package/dist/setting/setting.controller.js +12 -0
  226. package/dist/setting/setting.controller.js.map +1 -1
  227. package/dist/setting/setting.service.d.ts +20 -1
  228. package/dist/setting/setting.service.d.ts.map +1 -1
  229. package/dist/setting/setting.service.js +80 -4
  230. package/dist/setting/setting.service.js.map +1 -1
  231. package/dist/task/task.service.d.ts +13 -0
  232. package/dist/task/task.service.d.ts.map +1 -1
  233. package/dist/task/task.service.js +85 -0
  234. package/dist/task/task.service.js.map +1 -1
  235. package/dist/token/token.service.d.ts.map +1 -1
  236. package/dist/token/token.service.js +0 -1
  237. package/dist/token/token.service.js.map +1 -1
  238. package/dist/user/user.controller.d.ts +11 -11
  239. package/dist/user/user.service.d.ts +27 -27
  240. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +22 -0
  241. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -0
  242. package/dist/webhook-integration/dto/webhook-action.dto.js +120 -0
  243. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -0
  244. package/dist/webhook-integration/webhook-integration.controller.d.ts +7 -5
  245. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  246. package/dist/webhook-integration/webhook-integration.controller.js +18 -5
  247. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  248. package/dist/webhook-integration/webhook-integration.service.d.ts +18 -5
  249. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  250. package/dist/webhook-integration/webhook-integration.service.js +342 -59
  251. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  252. package/dist/webhook-integration/webhook-utils.d.ts +1 -0
  253. package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
  254. package/dist/webhook-integration/webhook-utils.js +38 -0
  255. package/dist/webhook-integration/webhook-utils.js.map +1 -1
  256. package/hedhog/data/menu.yaml +63 -4
  257. package/hedhog/data/route.yaml +1437 -0
  258. package/hedhog/data/setting_group.yaml +112 -0
  259. package/hedhog/data/setting_subgroup.yaml +32 -0
  260. package/hedhog/frontend/app/access-log/page.tsx.ejs +772 -0
  261. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +285 -0
  262. package/hedhog/frontend/app/account/layout.tsx.ejs +32 -55
  263. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +5 -0
  264. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +10 -3
  265. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +33 -13
  266. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +12 -5
  267. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +12 -5
  268. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +3559 -1835
  269. package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +633 -0
  270. package/hedhog/frontend/app/mcp_chat/chat-types.ts.ejs +16 -0
  271. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +583 -384
  272. package/hedhog/frontend/app/menu/page.tsx.ejs +9 -3
  273. package/hedhog/frontend/app/roles/page.tsx.ejs +18 -6
  274. package/hedhog/frontend/app/routes/page.tsx.ejs +9 -3
  275. package/hedhog/frontend/app/setting-list/page.tsx.ejs +822 -0
  276. package/hedhog/frontend/app/users/page.tsx.ejs +445 -203
  277. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +310 -304
  278. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +41 -44
  279. package/hedhog/frontend/messages/en.json +343 -3
  280. package/hedhog/frontend/messages/pt.json +343 -3
  281. package/hedhog/table/access_log.yaml +31 -0
  282. package/hedhog/table/mcp_api_key.yaml +30 -0
  283. package/hedhog/table/notification.yaml +72 -0
  284. package/hedhog/table/webhook_action_log.yaml +49 -0
  285. package/package.json +5 -4
  286. package/src/access-log/access-log.controller.ts +29 -0
  287. package/src/access-log/access-log.interceptor.ts +38 -0
  288. package/src/access-log/access-log.module.ts +16 -0
  289. package/src/access-log/access-log.service.ts +129 -0
  290. package/src/access-log/dto/query-access-log.dto.ts +23 -0
  291. package/src/auth/auth.service.ts +0 -7
  292. package/src/challenge/challenge.service.ts +0 -2
  293. package/src/core/system.module.ts +1 -0
  294. package/src/core.module.ts +15 -0
  295. package/src/dashboard/dashboard.module.ts +4 -1
  296. package/src/file/file.controller.ts +14 -14
  297. package/src/file/file.service.ts +198 -4
  298. package/src/file/provider/abstract.provider.ts +10 -0
  299. package/src/file/provider/azure.provider.ts +24 -0
  300. package/src/file/provider/gcs.provider.ts +20 -0
  301. package/src/file/provider/local.provider.ts +30 -2
  302. package/src/file/provider/provider.factory.ts +3 -0
  303. package/src/file/provider/s3.provider.ts +22 -0
  304. package/src/index.ts +5 -0
  305. package/src/mcp/guards/mcp-auth.guard.ts +64 -32
  306. package/src/mcp/mcp-api-key.controller.ts +42 -0
  307. package/src/mcp/mcp-api-key.service.ts +66 -0
  308. package/src/mcp/mcp-audit.service.ts +47 -36
  309. package/src/mcp/mcp-registry.service.ts +75 -69
  310. package/src/mcp/mcp.controller.ts +40 -30
  311. package/src/mcp/mcp.module.ts +115 -36
  312. package/src/mcp/tools/core-ai.mcp-tools.ts +156 -0
  313. package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +63 -0
  314. package/src/mcp/tools/core-dashboard.mcp-tools.ts +103 -0
  315. package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +134 -0
  316. package/src/mcp/tools/core-files.mcp-tools.ts +105 -0
  317. package/src/mcp/tools/core-integration-events.mcp-tools.ts +118 -0
  318. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +128 -0
  319. package/src/mcp/tools/core-mail-sent.mcp-tools.ts +76 -0
  320. package/src/mcp/tools/core-mail.mcp-tools.ts +130 -0
  321. package/src/mcp/tools/core-mcp-chat.mcp-tools.ts +81 -0
  322. package/src/mcp/tools/core-menus.mcp-tools.ts +197 -0
  323. package/src/mcp/tools/core-notifications.mcp-tools.ts +128 -0
  324. package/src/mcp/tools/core-profile.mcp-tools.ts +74 -0
  325. package/src/mcp/tools/core-roles.mcp-tools.ts +288 -0
  326. package/src/mcp/tools/core-routes.mcp-tools.ts +196 -0
  327. package/src/mcp/tools/core-screens.mcp-tools.ts +186 -0
  328. package/src/mcp/tools/core-sessions.mcp-tools.ts +97 -0
  329. package/src/mcp/tools/core-settings.mcp-tools.ts +171 -53
  330. package/src/mcp/tools/core-system.mcp-tools.ts +19 -0
  331. package/src/mcp/tools/core-webhooks.mcp-tools.ts +199 -0
  332. package/src/mcp-chat/mcp-chat.controller.ts +113 -60
  333. package/src/mcp-chat/mcp-chat.service.ts +1343 -332
  334. package/src/notification/dto/create-notification.dto.ts +71 -0
  335. package/src/notification/dto/update-progress.dto.ts +16 -0
  336. package/src/notification/notification.controller.ts +83 -0
  337. package/src/notification/notification.module.ts +16 -0
  338. package/src/notification/notification.service.ts +324 -0
  339. package/src/pdf/pdf.service.ts +1 -1
  340. package/src/profile/profile.controller.ts +1 -1
  341. package/src/profile/profile.module.ts +1 -0
  342. package/src/setting/dto/update-setting-list.dto.ts +32 -0
  343. package/src/setting/setting.controller.ts +9 -0
  344. package/src/setting/setting.service.ts +99 -11
  345. package/src/task/task.service.ts +103 -0
  346. package/src/token/token.service.ts +0 -2
  347. package/src/webhook-integration/dto/webhook-action.dto.ts +96 -0
  348. package/src/webhook-integration/webhook-integration.controller.ts +124 -103
  349. package/src/webhook-integration/webhook-integration.service.ts +1137 -735
  350. package/src/webhook-integration/webhook-utils.ts +136 -79
@@ -0,0 +1,822 @@
1
+ 'use client';
2
+
3
+ import {
4
+ closestCenter,
5
+ DndContext,
6
+ type DragEndEvent,
7
+ PointerSensor,
8
+ useSensor,
9
+ useSensors,
10
+ } from '@dnd-kit/core';
11
+ import {
12
+ arrayMove,
13
+ SortableContext,
14
+ useSortable,
15
+ verticalListSortingStrategy,
16
+ } from '@dnd-kit/sortable';
17
+ import { CSS } from '@dnd-kit/utilities';
18
+ import {
19
+ EmptyState,
20
+ Page,
21
+ PageHeader,
22
+ SearchBar,
23
+ } from '@/components/entity-list';
24
+ import { Badge } from '@/components/ui/badge';
25
+ import { Button } from '@/components/ui/button';
26
+ import {
27
+ Card,
28
+ CardContent,
29
+ CardDescription,
30
+ CardHeader,
31
+ CardTitle,
32
+ } from '@/components/ui/card';
33
+ import { Input } from '@/components/ui/input';
34
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
35
+ import {
36
+ Sheet,
37
+ SheetDescription,
38
+ SheetHeader,
39
+ SheetTitle,
40
+ } from '@/components/ui/sheet';
41
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
42
+ import {
43
+ Tooltip,
44
+ TooltipContent,
45
+ TooltipTrigger,
46
+ } from '@/components/ui/tooltip';
47
+ import { PaginatedResult } from '@/types/pagination-result';
48
+ import {
49
+ Setting,
50
+ SettingComponentEnum,
51
+ SettingGroup,
52
+ SettingSubgroup,
53
+ } from '@hed-hog/api-types';
54
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
55
+ import {
56
+ GripVertical,
57
+ ListTree,
58
+ Pencil,
59
+ Plus,
60
+ Save,
61
+ Settings2,
62
+ SlidersHorizontal,
63
+ Trash2,
64
+ } from 'lucide-react';
65
+ import { useTranslations } from 'next-intl';
66
+ import { CSSProperties, useEffect, useMemo, useRef, useState } from 'react';
67
+
68
+ type SettingWithList = Setting & {
69
+ subgroup_id?: number | null;
70
+ setting_subgroup?: SettingSubgroup | null;
71
+ setting_list?: Array<{
72
+ id: number;
73
+ order?: number;
74
+ value: string;
75
+ }>;
76
+ };
77
+
78
+ type SubgroupSection = {
79
+ key: string;
80
+ slug: string;
81
+ name: string;
82
+ description?: string;
83
+ settings: SettingWithList[];
84
+ };
85
+
86
+ type DraftOption = {
87
+ id: string;
88
+ value: string;
89
+ };
90
+
91
+ const createEmptyPage = <T,>(pageSize: number): PaginatedResult<T> => ({
92
+ total: 0,
93
+ lastPage: 0,
94
+ page: 1,
95
+ pageSize,
96
+ prev: null,
97
+ next: null,
98
+ data: [],
99
+ });
100
+
101
+ export default function CoreSettingListPage() {
102
+ const t = useTranslations('core.SettingListPage');
103
+ const { request, currentLocaleCode, showToastHandler } = useApp();
104
+ const [search, setSearch] = useState('');
105
+ const [activeGroupSlug, setActiveGroupSlug] = useState<string>('all');
106
+ const [activeSubgroupSlug, setActiveSubgroupSlug] = useState<string>('all');
107
+ const [savingOptionBySettingId, setSavingOptionBySettingId] = useState<
108
+ Record<number, boolean>
109
+ >({});
110
+ const [editingSettingId, setEditingSettingId] = useState<number | null>(null);
111
+ const [sheetOpen, setSheetOpen] = useState(false);
112
+ const [sheetDraftOptions, setSheetDraftOptions] = useState<DraftOption[]>([]);
113
+ const draftIdRef = useRef(0);
114
+
115
+ const createDraftOption = (value = ''): DraftOption => {
116
+ draftIdRef.current += 1;
117
+ return { id: `draft-option-${draftIdRef.current}`, value };
118
+ };
119
+
120
+ const sensors = useSensors(
121
+ useSensor(PointerSensor, {
122
+ activationConstraint: { distance: 6 },
123
+ })
124
+ );
125
+
126
+ const { data: groupsPage } = useQuery<PaginatedResult<SettingGroup>>({
127
+ queryKey: ['core-setting-list-groups', currentLocaleCode],
128
+ queryFn: async () => {
129
+ const response = await request<PaginatedResult<SettingGroup>>({
130
+ url: '/setting/group?page=1&pageSize=100',
131
+ method: 'GET',
132
+ });
133
+ return response.data;
134
+ },
135
+ placeholderData: (previous) =>
136
+ previous ?? createEmptyPage<SettingGroup>(100),
137
+ });
138
+
139
+ const groups = useMemo<SettingGroup[]>(
140
+ () => groupsPage?.data ?? [],
141
+ [groupsPage]
142
+ );
143
+
144
+ useEffect(() => {
145
+ if (!groups.length) return;
146
+
147
+ if (activeGroupSlug === 'all') {
148
+ return;
149
+ }
150
+
151
+ const groupExists = groups.some(
152
+ (group: SettingGroup) => group.slug === activeGroupSlug
153
+ );
154
+ if (!groupExists) {
155
+ setActiveGroupSlug('all');
156
+ }
157
+ }, [activeGroupSlug, groups]);
158
+
159
+ useEffect(() => {
160
+ setActiveSubgroupSlug('all');
161
+ }, [activeGroupSlug]);
162
+
163
+ const {
164
+ data: settingsPage,
165
+ isLoading,
166
+ refetch,
167
+ } = useQuery<PaginatedResult<SettingWithList>>({
168
+ queryKey: ['core-setting-list-items', activeGroupSlug, currentLocaleCode],
169
+ queryFn: async () => {
170
+ const url =
171
+ activeGroupSlug === 'all'
172
+ ? '/setting?page=1&pageSize=500'
173
+ : `/setting/group/${activeGroupSlug}?page=1&pageSize=200`;
174
+
175
+ const response = await request<PaginatedResult<SettingWithList>>({
176
+ url,
177
+ method: 'GET',
178
+ });
179
+ return response.data;
180
+ },
181
+ placeholderData: (previous) =>
182
+ previous ?? createEmptyPage<SettingWithList>(200),
183
+ });
184
+
185
+ const allSettings = useMemo<SettingWithList[]>(
186
+ () => settingsPage?.data ?? [],
187
+ [settingsPage]
188
+ );
189
+
190
+ const listTypeSettings = useMemo<SettingWithList[]>(
191
+ () =>
192
+ allSettings.filter((setting) => {
193
+ const component = setting.component;
194
+ const isListComponent =
195
+ component === SettingComponentEnum.COMBOBOX ||
196
+ component === SettingComponentEnum.CHECKBOX;
197
+
198
+ return isListComponent;
199
+ }),
200
+ [allSettings]
201
+ );
202
+
203
+ const subgroupOptions = useMemo(() => {
204
+ const map = new Map<
205
+ string,
206
+ { value: string; label: string; description?: string }
207
+ >();
208
+
209
+ listTypeSettings.forEach((setting) => {
210
+ const subgroup = setting.setting_subgroup;
211
+ if (!subgroup?.slug) {
212
+ return;
213
+ }
214
+
215
+ map.set(subgroup.slug, {
216
+ value: subgroup.slug,
217
+ label: subgroup.name?.toString() || subgroup.slug,
218
+ description: subgroup.description?.toString() || undefined,
219
+ });
220
+ });
221
+
222
+ return Array.from(map.values()).sort((left, right) =>
223
+ left.label.localeCompare(right.label)
224
+ );
225
+ }, [listTypeSettings]);
226
+
227
+ const visibleSettings = useMemo(() => {
228
+ if (activeSubgroupSlug === 'all') {
229
+ return listTypeSettings;
230
+ }
231
+
232
+ return listTypeSettings.filter(
233
+ (setting) => setting.setting_subgroup?.slug === activeSubgroupSlug
234
+ );
235
+ }, [activeSubgroupSlug, listTypeSettings]);
236
+
237
+ const currentGroup = useMemo(
238
+ () => groups.find((group) => group.slug === activeGroupSlug) ?? null,
239
+ [activeGroupSlug, groups]
240
+ );
241
+
242
+ const filteredSettings = useMemo(() => {
243
+ const normalizedQuery = search.trim().toLowerCase();
244
+
245
+ if (!normalizedQuery) {
246
+ return visibleSettings;
247
+ }
248
+
249
+ return visibleSettings.filter((setting: SettingWithList) => {
250
+ const options = (setting.setting_list ?? []).map(
251
+ (option: { value: string }) => option.value
252
+ );
253
+ const text = [
254
+ setting.name,
255
+ setting.slug,
256
+ setting.description,
257
+ setting.setting_subgroup?.name,
258
+ setting.setting_subgroup?.slug,
259
+ options.join(' '),
260
+ ]
261
+ .filter(Boolean)
262
+ .join(' ')
263
+ .toLowerCase();
264
+
265
+ return text.includes(normalizedQuery);
266
+ });
267
+ }, [search, visibleSettings]);
268
+
269
+ const groupedSections = useMemo<SubgroupSection[]>(() => {
270
+ const sectionsMap = new Map<string, SubgroupSection>();
271
+
272
+ filteredSettings.forEach((setting) => {
273
+ const subgroup = setting.setting_subgroup ?? null;
274
+ const subgroupSlug = subgroup?.slug ?? '__ungrouped__';
275
+ const sectionKey = subgroupSlug;
276
+
277
+ if (!sectionsMap.has(sectionKey)) {
278
+ sectionsMap.set(sectionKey, {
279
+ key: sectionKey,
280
+ slug: subgroupSlug,
281
+ name:
282
+ subgroup?.name?.toString() ||
283
+ (subgroup ? subgroup.slug : t('sections.withoutSubgroup')),
284
+ description: subgroup?.description?.toString() || undefined,
285
+ settings: [],
286
+ });
287
+ }
288
+
289
+ sectionsMap.get(sectionKey)?.settings.push(setting);
290
+ });
291
+
292
+ const sections = Array.from(sectionsMap.values());
293
+ sections.forEach((section) => {
294
+ section.settings.sort((left, right) =>
295
+ left.slug.localeCompare(right.slug)
296
+ );
297
+ });
298
+
299
+ sections.sort((left, right) => {
300
+ if (left.slug === '__ungrouped__') return 1;
301
+ if (right.slug === '__ungrouped__') return -1;
302
+ return left.name.localeCompare(right.name);
303
+ });
304
+
305
+ return sections;
306
+ }, [filteredSettings, t]);
307
+
308
+ const settingsWithOptionsCount = useMemo(
309
+ () => listTypeSettings.length,
310
+ [listTypeSettings]
311
+ );
312
+
313
+ const totalOptions = useMemo(
314
+ () =>
315
+ listTypeSettings.reduce(
316
+ (acc: number, setting: SettingWithList) =>
317
+ acc + (setting.setting_list?.length ?? 0),
318
+ 0
319
+ ),
320
+ [listTypeSettings]
321
+ );
322
+
323
+ const controls = useMemo(
324
+ () => [
325
+ {
326
+ id: 'group',
327
+ type: 'select' as const,
328
+ value: activeGroupSlug,
329
+ onChange: (value: string) => setActiveGroupSlug(value),
330
+ placeholder: t('filters.groupPlaceholder'),
331
+ options: [
332
+ { value: 'all', label: t('filters.allGroups') },
333
+ ...groups.map((group: SettingGroup) => ({
334
+ value: group.slug,
335
+ label: group.name || group.slug,
336
+ })),
337
+ ],
338
+ },
339
+ {
340
+ id: 'subgroup',
341
+ type: 'select' as const,
342
+ value: activeSubgroupSlug,
343
+ onChange: (value: string) => setActiveSubgroupSlug(value),
344
+ placeholder: t('filters.subgroupPlaceholder'),
345
+ options: [
346
+ { value: 'all', label: t('filters.allSubgroups') },
347
+ ...subgroupOptions.map((subgroup) => ({
348
+ value: subgroup.value,
349
+ label: subgroup.label,
350
+ })),
351
+ ],
352
+ },
353
+ ],
354
+ [activeGroupSlug, activeSubgroupSlug, groups, subgroupOptions, t]
355
+ );
356
+
357
+ const editingSetting = useMemo(
358
+ () =>
359
+ editingSettingId == null
360
+ ? null
361
+ : (listTypeSettings.find(
362
+ (setting) => setting.id === editingSettingId
363
+ ) ?? null),
364
+ [editingSettingId, listTypeSettings]
365
+ );
366
+
367
+ const openSheetForSetting = (setting: SettingWithList) => {
368
+ if (setting.id == null) {
369
+ return;
370
+ }
371
+
372
+ const sortedOptions = [...(setting.setting_list ?? [])].sort(
373
+ (left, right) => (left.order ?? 0) - (right.order ?? 0)
374
+ );
375
+
376
+ setEditingSettingId(setting.id);
377
+ setSheetDraftOptions(
378
+ sortedOptions.map((option) => createDraftOption(option.value))
379
+ );
380
+ setSheetOpen(true);
381
+ };
382
+
383
+ const handleSheetDragEnd = ({ active, over }: DragEndEvent) => {
384
+ if (!over || active.id === over.id) {
385
+ return;
386
+ }
387
+
388
+ setSheetDraftOptions((previous) => {
389
+ const oldIndex = previous.findIndex((item) => item.id === active.id);
390
+ const newIndex = previous.findIndex((item) => item.id === over.id);
391
+
392
+ if (oldIndex < 0 || newIndex < 0) {
393
+ return previous;
394
+ }
395
+
396
+ return arrayMove(previous, oldIndex, newIndex);
397
+ });
398
+ };
399
+
400
+ const saveSettingOptions = async (
401
+ setting: SettingWithList,
402
+ overrideValues?: string[],
403
+ successMessageKey:
404
+ | 'optionsSavedSuccess'
405
+ | 'optionsClearedSuccess' = 'optionsSavedSuccess',
406
+ errorMessageKey:
407
+ | 'optionsSavedError'
408
+ | 'optionsClearedError' = 'optionsSavedError'
409
+ ) => {
410
+ const settingId = setting.id;
411
+ if (!settingId) {
412
+ return;
413
+ }
414
+
415
+ const values = (overrideValues ?? []).map((value) => value.trim());
416
+
417
+ setSavingOptionBySettingId((previous) => ({
418
+ ...previous,
419
+ [settingId]: true,
420
+ }));
421
+
422
+ try {
423
+ await request({
424
+ url: `/setting/${settingId}/options`,
425
+ method: 'PUT',
426
+ data: {
427
+ options: values.map((value, index) => ({
428
+ value,
429
+ order: index + 1,
430
+ })),
431
+ },
432
+ });
433
+
434
+ showToastHandler('success', t(successMessageKey));
435
+ await refetch();
436
+ } catch (_error) {
437
+ showToastHandler('error', t(errorMessageKey));
438
+ } finally {
439
+ setSavingOptionBySettingId((previous) => ({
440
+ ...previous,
441
+ [settingId]: false,
442
+ }));
443
+ }
444
+ };
445
+
446
+ const handleSaveSheet = async () => {
447
+ if (!editingSetting) {
448
+ return;
449
+ }
450
+
451
+ const values = sheetDraftOptions.map((option) => option.value);
452
+ await saveSettingOptions(editingSetting, values);
453
+ setSheetOpen(false);
454
+ setEditingSettingId(null);
455
+ };
456
+
457
+ const handleClearOptions = async (setting: SettingWithList) => {
458
+ await saveSettingOptions(
459
+ setting,
460
+ [],
461
+ 'optionsClearedSuccess',
462
+ 'optionsClearedError'
463
+ );
464
+ };
465
+
466
+ const addSheetOption = () => {
467
+ setSheetDraftOptions((previous) => [...previous, createDraftOption('')]);
468
+ };
469
+
470
+ const removeSheetOption = (draftId: string) => {
471
+ setSheetDraftOptions((previous) =>
472
+ previous.filter((option) => option.id !== draftId)
473
+ );
474
+ };
475
+
476
+ const updateSheetOption = (draftId: string, value: string) => {
477
+ setSheetDraftOptions((previous) =>
478
+ previous.map((option) =>
479
+ option.id === draftId ? { ...option, value } : option
480
+ )
481
+ );
482
+ };
483
+
484
+ return (
485
+ <Page>
486
+ <PageHeader
487
+ breadcrumbs={[
488
+ { label: t('breadcrumbs.home'), href: '/' },
489
+ { label: t('breadcrumbs.core') },
490
+ { label: t('title') },
491
+ ]}
492
+ title={t('title')}
493
+ description={t('description')}
494
+ />
495
+
496
+ <KpiCardsGrid
497
+ items={[
498
+ {
499
+ key: 'groups',
500
+ title: t('kpis.groups'),
501
+ value: groups.length,
502
+ icon: Settings2,
503
+ iconContainerClassName: 'bg-sky-50 text-sky-700',
504
+ accentClassName: 'from-sky-500/20 via-sky-500/10 to-transparent',
505
+ },
506
+ {
507
+ key: 'settings',
508
+ title: t('kpis.settingsTotal'),
509
+ value: settingsWithOptionsCount,
510
+ icon: ListTree,
511
+ iconContainerClassName: 'bg-emerald-50 text-emerald-700',
512
+ accentClassName:
513
+ 'from-emerald-500/20 via-emerald-500/10 to-transparent',
514
+ },
515
+ {
516
+ key: 'subgroups',
517
+ title: t('kpis.subgroups'),
518
+ value: subgroupOptions.length,
519
+ icon: SlidersHorizontal,
520
+ iconContainerClassName: 'bg-amber-50 text-amber-700',
521
+ accentClassName:
522
+ 'from-amber-500/20 via-amber-500/10 to-transparent',
523
+ },
524
+ {
525
+ key: 'options',
526
+ title: t('kpis.totalOptions'),
527
+ value: totalOptions,
528
+ icon: SlidersHorizontal,
529
+ iconContainerClassName: 'bg-violet-50 text-violet-700',
530
+ accentClassName:
531
+ 'from-violet-500/20 via-violet-500/10 to-transparent',
532
+ },
533
+ ]}
534
+ />
535
+
536
+ <Card>
537
+ <CardContent className="flex flex-wrap items-center justify-between gap-3 py-3">
538
+ <div className="space-y-0.5">
539
+ <p className="text-sm font-medium text-foreground">
540
+ {activeGroupSlug === 'all'
541
+ ? t('filters.allGroups')
542
+ : currentGroup?.name || activeGroupSlug}
543
+ </p>
544
+ {activeGroupSlug !== 'all' && currentGroup?.description ? (
545
+ <p className="text-xs text-muted-foreground">
546
+ {currentGroup.description}
547
+ </p>
548
+ ) : null}
549
+ </div>
550
+ <Badge variant="secondary">
551
+ {t('kpis.options')}: {totalOptions}
552
+ </Badge>
553
+ </CardContent>
554
+ </Card>
555
+
556
+ <SearchBar
557
+ searchQuery={search}
558
+ onSearchChange={setSearch}
559
+ onSearch={() => undefined}
560
+ placeholder={t('searchPlaceholder')}
561
+ controls={controls}
562
+ />
563
+
564
+ {isLoading ? (
565
+ <Card>
566
+ <CardContent className="py-8 text-sm text-muted-foreground">
567
+ {t('loading')}
568
+ </CardContent>
569
+ </Card>
570
+ ) : filteredSettings.length === 0 ? (
571
+ <EmptyState
572
+ icon={<ListTree className="h-10 w-10" />}
573
+ title={t('emptyTitle')}
574
+ description={
575
+ listTypeSettings.length === 0
576
+ ? t('emptyDescriptionNoSettings')
577
+ : t('emptyDescriptionFiltered')
578
+ }
579
+ />
580
+ ) : (
581
+ <div className="grid gap-3">
582
+ {groupedSections.map((section) => (
583
+ <Card key={section.key}>
584
+ <CardHeader className="pb-3">
585
+ <CardTitle className="text-base">{section.name}</CardTitle>
586
+ {section.description ? (
587
+ <CardDescription>{section.description}</CardDescription>
588
+ ) : null}
589
+ </CardHeader>
590
+
591
+ <CardContent className="space-y-4">
592
+ {section.settings.map((setting: SettingWithList) => {
593
+ const settingId = setting.id;
594
+ const isSaving =
595
+ settingId != null
596
+ ? savingOptionBySettingId[settingId] === true
597
+ : false;
598
+ const optionsCount = setting.setting_list?.length ?? 0;
599
+
600
+ return (
601
+ <div
602
+ key={setting.id}
603
+ className="flex items-center justify-between gap-3 border-b border-border/60 pb-3 last:border-0 last:pb-0"
604
+ >
605
+ <div className="min-w-0 flex-1">
606
+ <p className="truncate text-sm font-semibold text-foreground">
607
+ {setting.name}
608
+ </p>
609
+ </div>
610
+
611
+ <div className="flex items-center gap-2">
612
+ <Badge variant="secondary">
613
+ {t('itemCount', { count: optionsCount })}
614
+ </Badge>
615
+
616
+ <Tooltip>
617
+ <TooltipTrigger asChild>
618
+ <Button
619
+ type="button"
620
+ size="icon"
621
+ variant="ghost"
622
+ onClick={() => openSheetForSetting(setting)}
623
+ disabled={settingId == null || isSaving}
624
+ aria-label={t('actions.editOptions')}
625
+ >
626
+ <Pencil className="h-4 w-4" />
627
+ </Button>
628
+ </TooltipTrigger>
629
+ <TooltipContent>
630
+ <p>{t('actions.editOptions')}</p>
631
+ </TooltipContent>
632
+ </Tooltip>
633
+
634
+ <Tooltip>
635
+ <TooltipTrigger asChild>
636
+ <Button
637
+ type="button"
638
+ size="icon"
639
+ variant="ghost"
640
+ onClick={() => handleClearOptions(setting)}
641
+ disabled={settingId == null || isSaving}
642
+ aria-label={t('actions.clearOptions')}
643
+ >
644
+ <Trash2 className="h-4 w-4" />
645
+ </Button>
646
+ </TooltipTrigger>
647
+ <TooltipContent>
648
+ <p>{t('actions.clearOptions')}</p>
649
+ </TooltipContent>
650
+ </Tooltip>
651
+ </div>
652
+ </div>
653
+ );
654
+ })}
655
+ </CardContent>
656
+ </Card>
657
+ ))}
658
+ </div>
659
+ )}
660
+
661
+ <Sheet
662
+ open={sheetOpen}
663
+ onOpenChange={(open) => {
664
+ setSheetOpen(open);
665
+ if (!open) {
666
+ setEditingSettingId(null);
667
+ setSheetDraftOptions([]);
668
+ }
669
+ }}
670
+ >
671
+ <ResizableSheetContent
672
+ sheetId="core-setting-list-options-sheet"
673
+ defaultWidth={768}
674
+ minWidth={520}
675
+ maxWidth={1100}
676
+ className="w-full overflow-y-auto sm:max-w-2xl"
677
+ >
678
+ <SheetHeader>
679
+ <SheetTitle>{t('sheet.title')}</SheetTitle>
680
+ <SheetDescription>
681
+ {editingSetting
682
+ ? t('sheet.description', { name: editingSetting.name ?? '' })
683
+ : t('sheet.descriptionFallback')}
684
+ </SheetDescription>
685
+ </SheetHeader>
686
+
687
+ <div className="mt-6 space-y-4">
688
+ <div className="flex items-center justify-between gap-2">
689
+ <Badge variant="secondary">
690
+ {t('itemCount', { count: sheetDraftOptions.length })}
691
+ </Badge>
692
+ <Button
693
+ type="button"
694
+ size="sm"
695
+ variant="outline"
696
+ onClick={addSheetOption}
697
+ >
698
+ <Plus className="mr-1 h-3.5 w-3.5" />
699
+ {t('actions.addOption')}
700
+ </Button>
701
+ </div>
702
+
703
+ {sheetDraftOptions.length === 0 ? (
704
+ <p className="text-sm text-muted-foreground">
705
+ {t('emptyOptions')}
706
+ </p>
707
+ ) : (
708
+ <DndContext
709
+ sensors={sensors}
710
+ collisionDetection={closestCenter}
711
+ onDragEnd={handleSheetDragEnd}
712
+ >
713
+ <SortableContext
714
+ items={sheetDraftOptions.map((option) => option.id)}
715
+ strategy={verticalListSortingStrategy}
716
+ >
717
+ <div className="space-y-2">
718
+ {sheetDraftOptions.map((option, index) => (
719
+ <SortableOptionItem
720
+ key={option.id}
721
+ option={option}
722
+ index={index}
723
+ placeholder={t('optionPlaceholder')}
724
+ removeLabel={t('actions.removeOption')}
725
+ onChange={updateSheetOption}
726
+ onRemove={removeSheetOption}
727
+ />
728
+ ))}
729
+ </div>
730
+ </SortableContext>
731
+ </DndContext>
732
+ )}
733
+
734
+ <div className="flex justify-end">
735
+ <Button
736
+ type="button"
737
+ onClick={handleSaveSheet}
738
+ disabled={
739
+ editingSettingId == null ||
740
+ (editingSettingId != null &&
741
+ savingOptionBySettingId[editingSettingId] === true)
742
+ }
743
+ >
744
+ <Save className="mr-1 h-3.5 w-3.5" />
745
+ {editingSettingId != null &&
746
+ savingOptionBySettingId[editingSettingId]
747
+ ? t('actions.saving')
748
+ : t('actions.saveOptions')}
749
+ </Button>
750
+ </div>
751
+ </div>
752
+ </ResizableSheetContent>
753
+ </Sheet>
754
+ </Page>
755
+ );
756
+ }
757
+
758
+ function SortableOptionItem({
759
+ option,
760
+ index,
761
+ placeholder,
762
+ removeLabel,
763
+ onChange,
764
+ onRemove,
765
+ }: {
766
+ option: DraftOption;
767
+ index: number;
768
+ placeholder: string;
769
+ removeLabel: string;
770
+ onChange: (id: string, value: string) => void;
771
+ onRemove: (id: string) => void;
772
+ }) {
773
+ const {
774
+ attributes,
775
+ listeners,
776
+ setNodeRef,
777
+ transform,
778
+ transition,
779
+ isDragging,
780
+ } = useSortable({ id: option.id });
781
+
782
+ const style: CSSProperties = {
783
+ transform: CSS.Transform.toString(transform),
784
+ transition,
785
+ opacity: isDragging ? 0.6 : undefined,
786
+ };
787
+
788
+ return (
789
+ <div
790
+ ref={setNodeRef}
791
+ style={style}
792
+ className="flex items-center gap-2 rounded-md border p-2"
793
+ >
794
+ <button
795
+ type="button"
796
+ {...attributes}
797
+ {...listeners}
798
+ className="flex cursor-grab touch-none items-center justify-center rounded p-1 text-muted-foreground active:cursor-grabbing"
799
+ aria-label={`reorder-option-${index + 1}`}
800
+ >
801
+ <GripVertical className="h-4 w-4" />
802
+ </button>
803
+
804
+ <Input
805
+ value={option.value}
806
+ onChange={(event) => onChange(option.id, event.target.value)}
807
+ placeholder={placeholder}
808
+ className="bg-background"
809
+ />
810
+
811
+ <Button
812
+ type="button"
813
+ size="icon"
814
+ variant="ghost"
815
+ onClick={() => onRemove(option.id)}
816
+ aria-label={removeLabel}
817
+ >
818
+ <Trash2 className="h-4 w-4" />
819
+ </Button>
820
+ </div>
821
+ );
822
+ }