@hed-hog/core 0.0.350 → 0.0.353

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 (284) 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 +9 -9
  22. package/dist/auth/auth.service.d.ts +19 -19
  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 +12 -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/mcp/mcp-audit.service.d.ts +3 -1
  70. package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
  71. package/dist/mcp/mcp-audit.service.js +14 -2
  72. package/dist/mcp/mcp-audit.service.js.map +1 -1
  73. package/dist/mcp/mcp.controller.d.ts +3 -1
  74. package/dist/mcp/mcp.controller.d.ts.map +1 -1
  75. package/dist/mcp/mcp.controller.js +10 -2
  76. package/dist/mcp/mcp.controller.js.map +1 -1
  77. package/dist/mcp/mcp.module.d.ts.map +1 -1
  78. package/dist/mcp/mcp.module.js +72 -0
  79. package/dist/mcp/mcp.module.js.map +1 -1
  80. package/dist/mcp/tools/core-ai.mcp-tools.d.ts +45 -0
  81. package/dist/mcp/tools/core-ai.mcp-tools.d.ts.map +1 -0
  82. package/dist/mcp/tools/core-ai.mcp-tools.js +214 -0
  83. package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -0
  84. package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts +21 -0
  85. package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts.map +1 -0
  86. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +89 -0
  87. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -0
  88. package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts +29 -0
  89. package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts.map +1 -0
  90. package/dist/mcp/tools/core-dashboard.mcp-tools.js +149 -0
  91. package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -0
  92. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts +30 -0
  93. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -0
  94. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +185 -0
  95. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -0
  96. package/dist/mcp/tools/core-files.mcp-tools.d.ts +26 -0
  97. package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -0
  98. package/dist/mcp/tools/core-files.mcp-tools.js +140 -0
  99. package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -0
  100. package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts +26 -0
  101. package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts.map +1 -0
  102. package/dist/mcp/tools/core-integration-events.mcp-tools.js +168 -0
  103. package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -0
  104. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +32 -0
  105. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -0
  106. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +178 -0
  107. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -0
  108. package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts +14 -0
  109. package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts.map +1 -0
  110. package/dist/mcp/tools/core-mail-sent.mcp-tools.js +106 -0
  111. package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -0
  112. package/dist/mcp/tools/core-mail.mcp-tools.d.ts +36 -0
  113. package/dist/mcp/tools/core-mail.mcp-tools.d.ts.map +1 -0
  114. package/dist/mcp/tools/core-mail.mcp-tools.js +184 -0
  115. package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -0
  116. package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts +21 -0
  117. package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts.map +1 -0
  118. package/dist/mcp/tools/core-mcp-chat.mcp-tools.js +118 -0
  119. package/dist/mcp/tools/core-mcp-chat.mcp-tools.js.map +1 -0
  120. package/dist/mcp/tools/core-menus.mcp-tools.d.ts +40 -0
  121. package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -0
  122. package/dist/mcp/tools/core-menus.mcp-tools.js +268 -0
  123. package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -0
  124. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts +31 -0
  125. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -0
  126. package/dist/mcp/tools/core-notifications.mcp-tools.js +178 -0
  127. package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -0
  128. package/dist/mcp/tools/core-profile.mcp-tools.d.ts +20 -0
  129. package/dist/mcp/tools/core-profile.mcp-tools.d.ts.map +1 -0
  130. package/dist/mcp/tools/core-profile.mcp-tools.js +111 -0
  131. package/dist/mcp/tools/core-profile.mcp-tools.js.map +1 -0
  132. package/dist/mcp/tools/core-roles.mcp-tools.d.ts +70 -0
  133. package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -0
  134. package/dist/mcp/tools/core-roles.mcp-tools.js +355 -0
  135. package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -0
  136. package/dist/mcp/tools/core-routes.mcp-tools.d.ts +52 -0
  137. package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -0
  138. package/dist/mcp/tools/core-routes.mcp-tools.js +254 -0
  139. package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -0
  140. package/dist/mcp/tools/core-screens.mcp-tools.d.ts +47 -0
  141. package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -0
  142. package/dist/mcp/tools/core-screens.mcp-tools.js +250 -0
  143. package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -0
  144. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts +20 -0
  145. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -0
  146. package/dist/mcp/tools/core-sessions.mcp-tools.js +128 -0
  147. package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -0
  148. package/dist/mcp/tools/core-settings.mcp-tools.d.ts +25 -0
  149. package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
  150. package/dist/mcp/tools/core-settings.mcp-tools.js +138 -0
  151. package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
  152. package/dist/mcp/tools/core-system.mcp-tools.d.ts +8 -0
  153. package/dist/mcp/tools/core-system.mcp-tools.d.ts.map +1 -0
  154. package/dist/mcp/tools/core-system.mcp-tools.js +40 -0
  155. package/dist/mcp/tools/core-system.mcp-tools.js.map +1 -0
  156. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts +46 -0
  157. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -0
  158. package/dist/mcp/tools/core-webhooks.mcp-tools.js +266 -0
  159. package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -0
  160. package/dist/mcp-chat/mcp-chat.controller.d.ts +1 -1
  161. package/dist/mcp-chat/mcp-chat.service.d.ts +25 -2
  162. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  163. package/dist/mcp-chat/mcp-chat.service.js +666 -36
  164. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  165. package/dist/notification/notification.controller.d.ts +8 -4
  166. package/dist/notification/notification.controller.d.ts.map +1 -1
  167. package/dist/notification/notification.controller.js +12 -0
  168. package/dist/notification/notification.controller.js.map +1 -1
  169. package/dist/notification/notification.service.d.ts +19 -5
  170. package/dist/notification/notification.service.d.ts.map +1 -1
  171. package/dist/notification/notification.service.js +155 -9
  172. package/dist/notification/notification.service.js.map +1 -1
  173. package/dist/pdf/pdf.service.js +1 -1
  174. package/dist/pdf/pdf.service.js.map +1 -1
  175. package/dist/profile/profile.controller.d.ts +3 -3
  176. package/dist/profile/profile.controller.js +0 -1
  177. package/dist/profile/profile.controller.js.map +1 -1
  178. package/dist/profile/profile.module.d.ts.map +1 -1
  179. package/dist/profile/profile.module.js +1 -0
  180. package/dist/profile/profile.module.js.map +1 -1
  181. package/dist/profile/profile.service.d.ts +3 -3
  182. package/dist/session/session.controller.d.ts +2 -2
  183. package/dist/session/session.service.d.ts +6 -6
  184. package/dist/setting/setting.controller.d.ts +1 -1
  185. package/dist/setting/setting.service.d.ts +1 -1
  186. package/dist/setting/setting.service.d.ts.map +1 -1
  187. package/dist/setting/setting.service.js +1 -4
  188. package/dist/setting/setting.service.js.map +1 -1
  189. package/dist/task/task.service.d.ts +1 -0
  190. package/dist/task/task.service.d.ts.map +1 -1
  191. package/dist/task/task.service.js +25 -0
  192. package/dist/task/task.service.js.map +1 -1
  193. package/dist/token/token.service.d.ts.map +1 -1
  194. package/dist/token/token.service.js +0 -1
  195. package/dist/token/token.service.js.map +1 -1
  196. package/dist/user/user.controller.d.ts +9 -9
  197. package/dist/user/user.service.d.ts +21 -21
  198. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +22 -0
  199. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -0
  200. package/dist/webhook-integration/dto/webhook-action.dto.js +120 -0
  201. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -0
  202. package/dist/webhook-integration/webhook-integration.controller.d.ts +7 -5
  203. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  204. package/dist/webhook-integration/webhook-integration.controller.js +18 -5
  205. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  206. package/dist/webhook-integration/webhook-integration.service.d.ts +14 -5
  207. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  208. package/dist/webhook-integration/webhook-integration.service.js +174 -41
  209. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  210. package/hedhog/data/menu.yaml +15 -0
  211. package/hedhog/data/route.yaml +1304 -0
  212. package/hedhog/data/setting_group.yaml +67 -0
  213. package/hedhog/data/setting_subgroup.yaml +16 -0
  214. package/hedhog/frontend/app/access-log/page.tsx.ejs +772 -0
  215. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +10 -3
  216. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +33 -13
  217. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +12 -5
  218. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +12 -5
  219. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +1817 -356
  220. package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +43 -5
  221. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +35 -7
  222. package/hedhog/frontend/app/menu/page.tsx.ejs +9 -3
  223. package/hedhog/frontend/app/roles/page.tsx.ejs +18 -6
  224. package/hedhog/frontend/app/routes/page.tsx.ejs +9 -3
  225. package/hedhog/frontend/app/setting-list/page.tsx.ejs +344 -171
  226. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +310 -304
  227. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +25 -7
  228. package/hedhog/frontend/messages/en.json +104 -3
  229. package/hedhog/frontend/messages/pt.json +104 -3
  230. package/hedhog/table/access_log.yaml +31 -0
  231. package/package.json +4 -3
  232. package/src/access-log/access-log.controller.ts +29 -0
  233. package/src/access-log/access-log.interceptor.ts +38 -0
  234. package/src/access-log/access-log.module.ts +16 -0
  235. package/src/access-log/access-log.service.ts +129 -0
  236. package/src/access-log/dto/query-access-log.dto.ts +23 -0
  237. package/src/auth/auth.service.ts +0 -7
  238. package/src/challenge/challenge.service.ts +0 -2
  239. package/src/core/system.module.ts +1 -0
  240. package/src/core.module.ts +12 -0
  241. package/src/dashboard/dashboard.module.ts +4 -1
  242. package/src/file/file.controller.ts +14 -14
  243. package/src/file/file.service.ts +198 -4
  244. package/src/file/provider/abstract.provider.ts +10 -0
  245. package/src/file/provider/azure.provider.ts +24 -0
  246. package/src/file/provider/gcs.provider.ts +20 -0
  247. package/src/file/provider/local.provider.ts +30 -2
  248. package/src/file/provider/provider.factory.ts +3 -0
  249. package/src/file/provider/s3.provider.ts +22 -0
  250. package/src/mcp/mcp-audit.service.ts +47 -36
  251. package/src/mcp/mcp.controller.ts +40 -30
  252. package/src/mcp/mcp.module.ts +72 -0
  253. package/src/mcp/tools/core-ai.mcp-tools.ts +156 -0
  254. package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +63 -0
  255. package/src/mcp/tools/core-dashboard.mcp-tools.ts +103 -0
  256. package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +134 -0
  257. package/src/mcp/tools/core-files.mcp-tools.ts +105 -0
  258. package/src/mcp/tools/core-integration-events.mcp-tools.ts +118 -0
  259. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +128 -0
  260. package/src/mcp/tools/core-mail-sent.mcp-tools.ts +76 -0
  261. package/src/mcp/tools/core-mail.mcp-tools.ts +130 -0
  262. package/src/mcp/tools/core-mcp-chat.mcp-tools.ts +81 -0
  263. package/src/mcp/tools/core-menus.mcp-tools.ts +197 -0
  264. package/src/mcp/tools/core-notifications.mcp-tools.ts +128 -0
  265. package/src/mcp/tools/core-profile.mcp-tools.ts +74 -0
  266. package/src/mcp/tools/core-roles.mcp-tools.ts +288 -0
  267. package/src/mcp/tools/core-routes.mcp-tools.ts +196 -0
  268. package/src/mcp/tools/core-screens.mcp-tools.ts +186 -0
  269. package/src/mcp/tools/core-sessions.mcp-tools.ts +97 -0
  270. package/src/mcp/tools/core-settings.mcp-tools.ts +171 -53
  271. package/src/mcp/tools/core-system.mcp-tools.ts +19 -0
  272. package/src/mcp/tools/core-webhooks.mcp-tools.ts +199 -0
  273. package/src/mcp-chat/mcp-chat.service.ts +887 -38
  274. package/src/notification/notification.controller.ts +20 -8
  275. package/src/notification/notification.service.ts +209 -9
  276. package/src/pdf/pdf.service.ts +1 -1
  277. package/src/profile/profile.controller.ts +1 -1
  278. package/src/profile/profile.module.ts +1 -0
  279. package/src/setting/setting.service.ts +8 -11
  280. package/src/task/task.service.ts +31 -0
  281. package/src/token/token.service.ts +0 -2
  282. package/src/webhook-integration/dto/webhook-action.dto.ts +96 -0
  283. package/src/webhook-integration/webhook-integration.controller.ts +124 -103
  284. package/src/webhook-integration/webhook-integration.service.ts +267 -64
@@ -1,5 +1,20 @@
1
1
  'use client';
2
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';
3
18
  import {
4
19
  EmptyState,
5
20
  Page,
@@ -17,6 +32,18 @@ import {
17
32
  } from '@/components/ui/card';
18
33
  import { Input } from '@/components/ui/input';
19
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';
20
47
  import { PaginatedResult } from '@/types/pagination-result';
21
48
  import {
22
49
  Setting,
@@ -26,7 +53,9 @@ import {
26
53
  } from '@hed-hog/api-types';
27
54
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
28
55
  import {
56
+ GripVertical,
29
57
  ListTree,
58
+ Pencil,
30
59
  Plus,
31
60
  Save,
32
61
  Settings2,
@@ -34,8 +63,7 @@ import {
34
63
  Trash2,
35
64
  } from 'lucide-react';
36
65
  import { useTranslations } from 'next-intl';
37
- import { useEffect, useMemo, useState } from 'react';
38
- import { SettingField } from '../configurations/[slug]/components/setting-field';
66
+ import { CSSProperties, useEffect, useMemo, useRef, useState } from 'react';
39
67
 
40
68
  type SettingWithList = Setting & {
41
69
  subgroup_id?: number | null;
@@ -55,6 +83,11 @@ type SubgroupSection = {
55
83
  settings: SettingWithList[];
56
84
  };
57
85
 
86
+ type DraftOption = {
87
+ id: string;
88
+ value: string;
89
+ };
90
+
58
91
  const createEmptyPage = <T,>(pageSize: number): PaginatedResult<T> => ({
59
92
  total: 0,
60
93
  lastPage: 0,
@@ -71,12 +104,24 @@ export default function CoreSettingListPage() {
71
104
  const [search, setSearch] = useState('');
72
105
  const [activeGroupSlug, setActiveGroupSlug] = useState<string>('all');
73
106
  const [activeSubgroupSlug, setActiveSubgroupSlug] = useState<string>('all');
74
- const [optionsDraftBySettingId, setOptionsDraftBySettingId] = useState<
75
- Record<number, string[]>
76
- >({});
77
107
  const [savingOptionBySettingId, setSavingOptionBySettingId] = useState<
78
108
  Record<number, boolean>
79
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
+ );
80
125
 
81
126
  const { data: groupsPage } = useQuery<PaginatedResult<SettingGroup>>({
82
127
  queryKey: ['core-setting-list-groups', currentLocaleCode],
@@ -155,25 +200,6 @@ export default function CoreSettingListPage() {
155
200
  [allSettings]
156
201
  );
157
202
 
158
- useEffect(() => {
159
- setOptionsDraftBySettingId((previous) => {
160
- const next = { ...previous };
161
- for (const setting of listTypeSettings) {
162
- if (!setting.id) {
163
- continue;
164
- }
165
-
166
- if (next[setting.id] === undefined) {
167
- next[setting.id] = [...(setting.setting_list ?? [])]
168
- .sort((left, right) => (left.order ?? 0) - (right.order ?? 0))
169
- .map((option) => option.value);
170
- }
171
- }
172
-
173
- return next;
174
- });
175
- }, [listTypeSettings]);
176
-
177
203
  const subgroupOptions = useMemo(() => {
178
204
  const map = new Map<
179
205
  string,
@@ -328,38 +354,65 @@ export default function CoreSettingListPage() {
328
354
  [activeGroupSlug, activeSubgroupSlug, groups, subgroupOptions, t]
329
355
  );
330
356
 
331
- const updateOptionDraft = (
332
- settingId: number,
333
- nextValues: string[] | ((previous: string[]) => string[])
334
- ) => {
335
- setOptionsDraftBySettingId((previous) => {
336
- const currentValues = previous[settingId] ?? [];
337
- const values =
338
- typeof nextValues === 'function'
339
- ? nextValues(currentValues)
340
- : nextValues;
341
-
342
- return {
343
- ...previous,
344
- [settingId]: values,
345
- };
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);
346
397
  });
347
398
  };
348
399
 
349
400
  const saveSettingOptions = async (
350
401
  setting: SettingWithList,
351
- overrideValues?: string[]
402
+ overrideValues?: string[],
403
+ successMessageKey:
404
+ | 'optionsSavedSuccess'
405
+ | 'optionsClearedSuccess' = 'optionsSavedSuccess',
406
+ errorMessageKey:
407
+ | 'optionsSavedError'
408
+ | 'optionsClearedError' = 'optionsSavedError'
352
409
  ) => {
353
410
  const settingId = setting.id;
354
411
  if (!settingId) {
355
412
  return;
356
413
  }
357
414
 
358
- const values = (
359
- overrideValues ??
360
- optionsDraftBySettingId[settingId] ??
361
- []
362
- ).map((value) => value.trim());
415
+ const values = (overrideValues ?? []).map((value) => value.trim());
363
416
 
364
417
  setSavingOptionBySettingId((previous) => ({
365
418
  ...previous,
@@ -378,18 +431,10 @@ export default function CoreSettingListPage() {
378
431
  },
379
432
  });
380
433
 
381
- showToastHandler('success', t('optionsSavedSuccess'));
434
+ showToastHandler('success', t(successMessageKey));
382
435
  await refetch();
383
-
384
- setOptionsDraftBySettingId((previous) => {
385
- const updatedValues = values.filter((value) => value.length > 0);
386
- return {
387
- ...previous,
388
- [settingId]: updatedValues,
389
- };
390
- });
391
436
  } catch (_error) {
392
- showToastHandler('error', t('optionsSavedError'));
437
+ showToastHandler('error', t(errorMessageKey));
393
438
  } finally {
394
439
  setSavingOptionBySettingId((previous) => ({
395
440
  ...previous,
@@ -398,6 +443,44 @@ export default function CoreSettingListPage() {
398
443
  }
399
444
  };
400
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
+
401
484
  return (
402
485
  <Page>
403
486
  <PageHeader
@@ -508,133 +591,63 @@ export default function CoreSettingListPage() {
508
591
  <CardContent className="space-y-4">
509
592
  {section.settings.map((setting: SettingWithList) => {
510
593
  const settingId = setting.id;
511
- const options = [...(setting.setting_list ?? [])].sort(
512
- (left, right) => (left.order ?? 0) - (right.order ?? 0)
513
- );
514
- const optionDraft =
515
- settingId != null
516
- ? (optionsDraftBySettingId[settingId] ??
517
- options.map((option) => option.value))
518
- : options.map((option) => option.value);
519
594
  const isSaving =
520
595
  settingId != null
521
596
  ? savingOptionBySettingId[settingId] === true
522
597
  : false;
598
+ const optionsCount = setting.setting_list?.length ?? 0;
523
599
 
524
600
  return (
525
601
  <div
526
602
  key={setting.id}
527
- className="space-y-2 border-b border-border/60 pb-4 last:border-0 last:pb-0"
603
+ className="flex items-center justify-between gap-3 border-b border-border/60 pb-3 last:border-0 last:pb-0"
528
604
  >
529
- <div className="flex flex-wrap items-start justify-between gap-3">
530
- <div className="min-w-0 flex-1 space-y-1">
531
- <p className="text-sm font-semibold text-foreground">
532
- {setting.name}
533
- </p>
534
- <p className="text-sm text-muted-foreground">
535
- {setting.description}
536
- </p>
537
- <Badge
538
- variant="outline"
539
- className="font-mono text-xs"
540
- >
541
- {setting.slug}
542
- </Badge>
543
- </div>
544
-
545
- <div className="w-full sm:w-auto sm:min-w-80">
546
- <SettingField setting={setting} className="w-full" />
547
- </div>
605
+ <div className="min-w-0 flex-1">
606
+ <p className="truncate text-sm font-semibold text-foreground">
607
+ {setting.name}
608
+ </p>
548
609
  </div>
549
610
 
550
- <div className="space-y-2">
551
- <div className="flex items-center justify-between gap-2">
552
- <p className="text-xs font-medium text-muted-foreground">
553
- {t('optionsSectionTitle')}
554
- </p>
555
- <Button
556
- type="button"
557
- size="sm"
558
- variant="outline"
559
- onClick={() => {
560
- if (settingId == null) return;
561
- updateOptionDraft(settingId, (previous) => [
562
- ...previous,
563
- '',
564
- ]);
565
- }}
566
- >
567
- <Plus className="mr-1 h-3.5 w-3.5" />
568
- {t('actions.addOption')}
569
- </Button>
570
- </div>
571
-
572
- {optionDraft.length === 0 ? (
573
- <p className="text-xs text-muted-foreground">
574
- {t('emptyOptions')}
575
- </p>
576
- ) : (
577
- <div className="space-y-2">
578
- {optionDraft.map((optionValue, index) => (
579
- <div
580
- key={`${settingId ?? 'setting'}-option-${index}`}
581
- className="flex items-center gap-2"
582
- >
583
- <Input
584
- value={optionValue}
585
- onChange={(event) => {
586
- if (settingId == null) return;
587
- const nextValue = event.target.value;
588
- updateOptionDraft(settingId, (previous) =>
589
- previous.map((value, currentIndex) =>
590
- currentIndex === index
591
- ? nextValue
592
- : value
593
- )
594
- );
595
- }}
596
- placeholder={t('optionPlaceholder')}
597
- className="bg-background"
598
- />
599
- <Button
600
- type="button"
601
- size="icon"
602
- variant="ghost"
603
- onClick={async () => {
604
- if (settingId == null) return;
605
- const nextValues = optionDraft.filter(
606
- (_, currentIndex) =>
607
- currentIndex !== index
608
- );
609
- updateOptionDraft(settingId, nextValues);
610
- await saveSettingOptions(
611
- setting,
612
- nextValues
613
- );
614
- }}
615
- disabled={isSaving}
616
- aria-label={t('actions.removeOption')}
617
- >
618
- <Trash2 className="h-4 w-4" />
619
- </Button>
620
- </div>
621
- ))}
622
- </div>
623
- )}
624
-
625
- <div className="flex justify-end">
626
- <Button
627
- type="button"
628
- size="sm"
629
- onClick={() => saveSettingOptions(setting)}
630
- disabled={isSaving || settingId == null}
631
- >
632
- <Save className="mr-1 h-3.5 w-3.5" />
633
- {isSaving
634
- ? t('actions.saving')
635
- : t('actions.saveOptions')}
636
- </Button>
637
- </div>
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>
638
651
  </div>
639
652
  </div>
640
653
  );
@@ -644,6 +657,166 @@ export default function CoreSettingListPage() {
644
657
  ))}
645
658
  </div>
646
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>
647
754
  </Page>
648
755
  );
649
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
+ }