@freighttech/terminal-tracking 0.7.0

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 (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/dist/generated/entities/terminal_config/index.js +53 -0
  4. package/dist/generated/entities/terminal_config/index.js.map +7 -0
  5. package/dist/generated/entities/terminal_event/index.js +59 -0
  6. package/dist/generated/entities/terminal_event/index.js.map +7 -0
  7. package/dist/generated/entities/terminal_tracking_job/index.js +37 -0
  8. package/dist/generated/entities/terminal_tracking_job/index.js.map +7 -0
  9. package/dist/generated/entities/terminal_vessel_visit/index.js +41 -0
  10. package/dist/generated/entities/terminal_vessel_visit/index.js.map +7 -0
  11. package/dist/generated/entities.ids.generated.js +26 -0
  12. package/dist/generated/entities.ids.generated.js.map +7 -0
  13. package/dist/index.js +20 -0
  14. package/dist/index.js.map +7 -0
  15. package/dist/modules/terminal_tracking/acl.js +14 -0
  16. package/dist/modules/terminal_tracking/acl.js.map +7 -0
  17. package/dist/modules/terminal_tracking/api/openapi.js +27 -0
  18. package/dist/modules/terminal_tracking/api/openapi.js.map +7 -0
  19. package/dist/modules/terminal_tracking/api/terminal-configs/route.js +140 -0
  20. package/dist/modules/terminal_tracking/api/terminal-configs/route.js.map +7 -0
  21. package/dist/modules/terminal_tracking/api/terminal-configs/test/route.js +58 -0
  22. package/dist/modules/terminal_tracking/api/terminal-configs/test/route.js.map +7 -0
  23. package/dist/modules/terminal_tracking/api/tracking-jobs/details/route.js +109 -0
  24. package/dist/modules/terminal_tracking/api/tracking-jobs/details/route.js.map +7 -0
  25. package/dist/modules/terminal_tracking/api/tracking-jobs/poll/route.js +58 -0
  26. package/dist/modules/terminal_tracking/api/tracking-jobs/poll/route.js.map +7 -0
  27. package/dist/modules/terminal_tracking/api/tracking-jobs/route.js +189 -0
  28. package/dist/modules/terminal_tracking/api/tracking-jobs/route.js.map +7 -0
  29. package/dist/modules/terminal_tracking/api/utils.js +12 -0
  30. package/dist/modules/terminal_tracking/api/utils.js.map +7 -0
  31. package/dist/modules/terminal_tracking/api/vessel-visits/route.js +83 -0
  32. package/dist/modules/terminal_tracking/api/vessel-visits/route.js.map +7 -0
  33. package/dist/modules/terminal_tracking/backend/terminal-configs/page.js +669 -0
  34. package/dist/modules/terminal_tracking/backend/terminal-configs/page.js.map +7 -0
  35. package/dist/modules/terminal_tracking/backend/terminal-configs/page.meta.js +19 -0
  36. package/dist/modules/terminal_tracking/backend/terminal-configs/page.meta.js.map +7 -0
  37. package/dist/modules/terminal_tracking/backend/terminal-containers/page.js +275 -0
  38. package/dist/modules/terminal_tracking/backend/terminal-containers/page.js.map +7 -0
  39. package/dist/modules/terminal_tracking/backend/terminal-containers/page.meta.js +19 -0
  40. package/dist/modules/terminal_tracking/backend/terminal-containers/page.meta.js.map +7 -0
  41. package/dist/modules/terminal_tracking/ce.js +35 -0
  42. package/dist/modules/terminal_tracking/ce.js.map +7 -0
  43. package/dist/modules/terminal_tracking/commands/index.js +4 -0
  44. package/dist/modules/terminal_tracking/commands/index.js.map +7 -0
  45. package/dist/modules/terminal_tracking/commands/terminal-configs.js +107 -0
  46. package/dist/modules/terminal_tracking/commands/terminal-configs.js.map +7 -0
  47. package/dist/modules/terminal_tracking/commands/terminal-tracking.js +27 -0
  48. package/dist/modules/terminal_tracking/commands/terminal-tracking.js.map +7 -0
  49. package/dist/modules/terminal_tracking/commands/tracking-jobs.js +56 -0
  50. package/dist/modules/terminal_tracking/commands/tracking-jobs.js.map +7 -0
  51. package/dist/modules/terminal_tracking/components/ContainerDetailsDrawer.js +371 -0
  52. package/dist/modules/terminal_tracking/components/ContainerDetailsDrawer.js.map +7 -0
  53. package/dist/modules/terminal_tracking/data/entities.js +337 -0
  54. package/dist/modules/terminal_tracking/data/entities.js.map +7 -0
  55. package/dist/modules/terminal_tracking/data/validators.js +98 -0
  56. package/dist/modules/terminal_tracking/data/validators.js.map +7 -0
  57. package/dist/modules/terminal_tracking/di.js +39 -0
  58. package/dist/modules/terminal_tracking/di.js.map +7 -0
  59. package/dist/modules/terminal_tracking/encryption.js +14 -0
  60. package/dist/modules/terminal_tracking/encryption.js.map +7 -0
  61. package/dist/modules/terminal_tracking/events.js +45 -0
  62. package/dist/modules/terminal_tracking/events.js.map +7 -0
  63. package/dist/modules/terminal_tracking/i18n/de.json +175 -0
  64. package/dist/modules/terminal_tracking/i18n/en.json +178 -0
  65. package/dist/modules/terminal_tracking/i18n/es.json +175 -0
  66. package/dist/modules/terminal_tracking/i18n/pl.json +206 -0
  67. package/dist/modules/terminal_tracking/index.js +15 -0
  68. package/dist/modules/terminal_tracking/index.js.map +7 -0
  69. package/dist/modules/terminal_tracking/lib/adapters/bct/adapter.js +164 -0
  70. package/dist/modules/terminal_tracking/lib/adapters/bct/adapter.js.map +7 -0
  71. package/dist/modules/terminal_tracking/lib/adapters/bct/auth/basic.js +23 -0
  72. package/dist/modules/terminal_tracking/lib/adapters/bct/auth/basic.js.map +7 -0
  73. package/dist/modules/terminal_tracking/lib/adapters/bct/incos-semantics.js +146 -0
  74. package/dist/modules/terminal_tracking/lib/adapters/bct/incos-semantics.js.map +7 -0
  75. package/dist/modules/terminal_tracking/lib/adapters/bct/types.js +1 -0
  76. package/dist/modules/terminal_tracking/lib/adapters/bct/types.js.map +7 -0
  77. package/dist/modules/terminal_tracking/lib/adapters/gct/adapter.js +106 -0
  78. package/dist/modules/terminal_tracking/lib/adapters/gct/adapter.js.map +7 -0
  79. package/dist/modules/terminal_tracking/lib/adapters/gct/auth/token.js +120 -0
  80. package/dist/modules/terminal_tracking/lib/adapters/gct/auth/token.js.map +7 -0
  81. package/dist/modules/terminal_tracking/lib/adapters/gct/gct-semantics.js +77 -0
  82. package/dist/modules/terminal_tracking/lib/adapters/gct/gct-semantics.js.map +7 -0
  83. package/dist/modules/terminal_tracking/lib/adapters/gct/types.js +1 -0
  84. package/dist/modules/terminal_tracking/lib/adapters/gct/types.js.map +7 -0
  85. package/dist/modules/terminal_tracking/lib/adapters/index.js +12 -0
  86. package/dist/modules/terminal_tracking/lib/adapters/index.js.map +7 -0
  87. package/dist/modules/terminal_tracking/lib/adapters/n4/adapter.js +121 -0
  88. package/dist/modules/terminal_tracking/lib/adapters/n4/adapter.js.map +7 -0
  89. package/dist/modules/terminal_tracking/lib/adapters/n4/auth/ropc.js +81 -0
  90. package/dist/modules/terminal_tracking/lib/adapters/n4/auth/ropc.js.map +7 -0
  91. package/dist/modules/terminal_tracking/lib/adapters/n4/http.js +66 -0
  92. package/dist/modules/terminal_tracking/lib/adapters/n4/http.js.map +7 -0
  93. package/dist/modules/terminal_tracking/lib/adapters/n4/n4-semantics.js +95 -0
  94. package/dist/modules/terminal_tracking/lib/adapters/n4/n4-semantics.js.map +7 -0
  95. package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/data-table.js +70 -0
  96. package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/data-table.js.map +7 -0
  97. package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/vessel-data-table.js +44 -0
  98. package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/vessel-data-table.js.map +7 -0
  99. package/dist/modules/terminal_tracking/lib/adapters/n4/proxy.js +15 -0
  100. package/dist/modules/terminal_tracking/lib/adapters/n4/proxy.js.map +7 -0
  101. package/dist/modules/terminal_tracking/lib/adapters/n4/types.js +1 -0
  102. package/dist/modules/terminal_tracking/lib/adapters/n4/types.js.map +7 -0
  103. package/dist/modules/terminal_tracking/lib/adapters/zoned-time.js +27 -0
  104. package/dist/modules/terminal_tracking/lib/adapters/zoned-time.js.map +7 -0
  105. package/dist/modules/terminal_tracking/lib/availability.js +40 -0
  106. package/dist/modules/terminal_tracking/lib/availability.js.map +7 -0
  107. package/dist/modules/terminal_tracking/lib/ensure-schedule.js +51 -0
  108. package/dist/modules/terminal_tracking/lib/ensure-schedule.js.map +7 -0
  109. package/dist/modules/terminal_tracking/lib/holds.js +240 -0
  110. package/dist/modules/terminal_tracking/lib/holds.js.map +7 -0
  111. package/dist/modules/terminal_tracking/lib/logger.js +19 -0
  112. package/dist/modules/terminal_tracking/lib/logger.js.map +7 -0
  113. package/dist/modules/terminal_tracking/lib/rate-limiter.js +30 -0
  114. package/dist/modules/terminal_tracking/lib/rate-limiter.js.map +7 -0
  115. package/dist/modules/terminal_tracking/lib/stops.js +48 -0
  116. package/dist/modules/terminal_tracking/lib/stops.js.map +7 -0
  117. package/dist/modules/terminal_tracking/lib/terminal-adapter.js +1 -0
  118. package/dist/modules/terminal_tracking/lib/terminal-adapter.js.map +7 -0
  119. package/dist/modules/terminal_tracking/lib/totp.js +41 -0
  120. package/dist/modules/terminal_tracking/lib/totp.js.map +7 -0
  121. package/dist/modules/terminal_tracking/migrations/Migration20260609120000_terminal_tracking.js +101 -0
  122. package/dist/modules/terminal_tracking/migrations/Migration20260609120000_terminal_tracking.js.map +7 -0
  123. package/dist/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.js +39 -0
  124. package/dist/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.js.map +7 -0
  125. package/dist/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.js +13 -0
  126. package/dist/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.js.map +7 -0
  127. package/dist/modules/terminal_tracking/migrations/Migration20260707120000_terminal_job_availability_markers.js +15 -0
  128. package/dist/modules/terminal_tracking/migrations/Migration20260707120000_terminal_job_availability_markers.js.map +7 -0
  129. package/dist/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.js +13 -0
  130. package/dist/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.js.map +7 -0
  131. package/dist/modules/terminal_tracking/services/terminalMatcherService.js +61 -0
  132. package/dist/modules/terminal_tracking/services/terminalMatcherService.js.map +7 -0
  133. package/dist/modules/terminal_tracking/services/terminalRegistry.js +24 -0
  134. package/dist/modules/terminal_tracking/services/terminalRegistry.js.map +7 -0
  135. package/dist/modules/terminal_tracking/services/terminalTrackingService.js +687 -0
  136. package/dist/modules/terminal_tracking/services/terminalTrackingService.js.map +7 -0
  137. package/dist/modules/terminal_tracking/setup.js +23 -0
  138. package/dist/modules/terminal_tracking/setup.js.map +7 -0
  139. package/dist/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.js +22 -0
  140. package/dist/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.js.map +7 -0
  141. package/generated/entities/terminal_config/index.ts +36 -0
  142. package/generated/entities/terminal_event/index.ts +39 -0
  143. package/generated/entities/terminal_tracking_job/index.ts +28 -0
  144. package/generated/entities/terminal_vessel_visit/index.ts +30 -0
  145. package/generated/entities.ids.generated.ts +52 -0
  146. package/package.json +80 -0
  147. package/src/index.ts +44 -0
  148. package/src/modules/terminal_tracking/__integration__/TC-TRACK-301-gct-config-test-connection.spec.ts +66 -0
  149. package/src/modules/terminal_tracking/__integration__/TC-TRACK-317-bct-config-test-connection.spec.ts +68 -0
  150. package/src/modules/terminal_tracking/__integration__/helpers.ts +148 -0
  151. package/src/modules/terminal_tracking/__integration__/meta.ts +4 -0
  152. package/src/modules/terminal_tracking/acl.ts +10 -0
  153. package/src/modules/terminal_tracking/api/openapi.ts +28 -0
  154. package/src/modules/terminal_tracking/api/terminal-configs/route.ts +140 -0
  155. package/src/modules/terminal_tracking/api/terminal-configs/test/route.ts +64 -0
  156. package/src/modules/terminal_tracking/api/tracking-jobs/details/route.ts +116 -0
  157. package/src/modules/terminal_tracking/api/tracking-jobs/poll/route.ts +64 -0
  158. package/src/modules/terminal_tracking/api/tracking-jobs/route.ts +243 -0
  159. package/src/modules/terminal_tracking/api/utils.ts +10 -0
  160. package/src/modules/terminal_tracking/api/vessel-visits/route.ts +95 -0
  161. package/src/modules/terminal_tracking/backend/terminal-configs/page.meta.ts +16 -0
  162. package/src/modules/terminal_tracking/backend/terminal-configs/page.tsx +728 -0
  163. package/src/modules/terminal_tracking/backend/terminal-containers/page.meta.ts +16 -0
  164. package/src/modules/terminal_tracking/backend/terminal-containers/page.tsx +328 -0
  165. package/src/modules/terminal_tracking/ce.ts +31 -0
  166. package/src/modules/terminal_tracking/commands/index.ts +3 -0
  167. package/src/modules/terminal_tracking/commands/terminal-configs.ts +132 -0
  168. package/src/modules/terminal_tracking/commands/terminal-tracking.ts +34 -0
  169. package/src/modules/terminal_tracking/commands/tracking-jobs.ts +69 -0
  170. package/src/modules/terminal_tracking/components/ContainerDetailsDrawer.tsx +540 -0
  171. package/src/modules/terminal_tracking/data/entities.ts +386 -0
  172. package/src/modules/terminal_tracking/data/validators.ts +113 -0
  173. package/src/modules/terminal_tracking/di.ts +46 -0
  174. package/src/modules/terminal_tracking/encryption.ts +12 -0
  175. package/src/modules/terminal_tracking/events.ts +48 -0
  176. package/src/modules/terminal_tracking/i18n/de.json +175 -0
  177. package/src/modules/terminal_tracking/i18n/en.json +178 -0
  178. package/src/modules/terminal_tracking/i18n/es.json +175 -0
  179. package/src/modules/terminal_tracking/i18n/pl.json +206 -0
  180. package/src/modules/terminal_tracking/index.ts +13 -0
  181. package/src/modules/terminal_tracking/lib/__tests__/availability.test.ts +136 -0
  182. package/src/modules/terminal_tracking/lib/__tests__/ensure-schedule.test.ts +67 -0
  183. package/src/modules/terminal_tracking/lib/__tests__/holds.test.ts +73 -0
  184. package/src/modules/terminal_tracking/lib/__tests__/stops.test.ts +80 -0
  185. package/src/modules/terminal_tracking/lib/__tests__/totp.test.ts +38 -0
  186. package/src/modules/terminal_tracking/lib/adapters/bct/__tests__/adapter.test.ts +196 -0
  187. package/src/modules/terminal_tracking/lib/adapters/bct/__tests__/incos-semantics.test.ts +303 -0
  188. package/src/modules/terminal_tracking/lib/adapters/bct/adapter.ts +219 -0
  189. package/src/modules/terminal_tracking/lib/adapters/bct/auth/basic.ts +34 -0
  190. package/src/modules/terminal_tracking/lib/adapters/bct/incos-semantics.ts +291 -0
  191. package/src/modules/terminal_tracking/lib/adapters/bct/types.ts +113 -0
  192. package/src/modules/terminal_tracking/lib/adapters/gct/__tests__/adapter.test.ts +238 -0
  193. package/src/modules/terminal_tracking/lib/adapters/gct/__tests__/gct-semantics.test.ts +133 -0
  194. package/src/modules/terminal_tracking/lib/adapters/gct/adapter.ts +152 -0
  195. package/src/modules/terminal_tracking/lib/adapters/gct/auth/token.ts +219 -0
  196. package/src/modules/terminal_tracking/lib/adapters/gct/gct-semantics.ts +136 -0
  197. package/src/modules/terminal_tracking/lib/adapters/gct/types.ts +58 -0
  198. package/src/modules/terminal_tracking/lib/adapters/index.ts +16 -0
  199. package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/adapter-proxy.test.ts +69 -0
  200. package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/data-table.test.ts +179 -0
  201. package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/http.test.ts +98 -0
  202. package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/n4-semantics.test.ts +140 -0
  203. package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/proxy.test.ts +19 -0
  204. package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/vessel-data-table.test.ts +67 -0
  205. package/src/modules/terminal_tracking/lib/adapters/n4/adapter.ts +163 -0
  206. package/src/modules/terminal_tracking/lib/adapters/n4/auth/ropc.ts +104 -0
  207. package/src/modules/terminal_tracking/lib/adapters/n4/http.ts +127 -0
  208. package/src/modules/terminal_tracking/lib/adapters/n4/n4-semantics.ts +156 -0
  209. package/src/modules/terminal_tracking/lib/adapters/n4/parsers/data-table.ts +102 -0
  210. package/src/modules/terminal_tracking/lib/adapters/n4/parsers/vessel-data-table.ts +60 -0
  211. package/src/modules/terminal_tracking/lib/adapters/n4/proxy.ts +24 -0
  212. package/src/modules/terminal_tracking/lib/adapters/n4/types.ts +56 -0
  213. package/src/modules/terminal_tracking/lib/adapters/zoned-time.ts +65 -0
  214. package/src/modules/terminal_tracking/lib/availability.ts +119 -0
  215. package/src/modules/terminal_tracking/lib/ensure-schedule.ts +83 -0
  216. package/src/modules/terminal_tracking/lib/holds.ts +344 -0
  217. package/src/modules/terminal_tracking/lib/logger.ts +21 -0
  218. package/src/modules/terminal_tracking/lib/rate-limiter.ts +52 -0
  219. package/src/modules/terminal_tracking/lib/stops.ts +124 -0
  220. package/src/modules/terminal_tracking/lib/terminal-adapter.ts +136 -0
  221. package/src/modules/terminal_tracking/lib/totp.ts +63 -0
  222. package/src/modules/terminal_tracking/migrations/Migration20260609120000_terminal_tracking.ts +104 -0
  223. package/src/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.ts +40 -0
  224. package/src/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.ts +11 -0
  225. package/src/modules/terminal_tracking/migrations/Migration20260707120000_terminal_job_availability_markers.ts +13 -0
  226. package/src/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.ts +16 -0
  227. package/src/modules/terminal_tracking/services/__tests__/availabilityEmit.test.ts +277 -0
  228. package/src/modules/terminal_tracking/services/__tests__/createJob.test.ts +59 -0
  229. package/src/modules/terminal_tracking/services/__tests__/pollAllBatch.test.ts +147 -0
  230. package/src/modules/terminal_tracking/services/__tests__/terminalMatcher.test.ts +70 -0
  231. package/src/modules/terminal_tracking/services/__tests__/vesselResolution.test.ts +453 -0
  232. package/src/modules/terminal_tracking/services/terminalMatcherService.ts +97 -0
  233. package/src/modules/terminal_tracking/services/terminalRegistry.ts +30 -0
  234. package/src/modules/terminal_tracking/services/terminalTrackingService.ts +912 -0
  235. package/src/modules/terminal_tracking/setup.ts +24 -0
  236. package/src/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.ts +32 -0
@@ -0,0 +1,669 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import { Page, PageBody } from "@freighttech/ui/backend/Page";
5
+ import { BooleanIcon } from "@freighttech/ui/backend/ValueIcons";
6
+ import { DynamicTable, TableSkeleton, useDynamicTablePage } from "@freighttech/ui/backend/dynamic-table";
7
+ import { RowActions } from "@freighttech/ui/backend/RowActions";
8
+ import { apiCallOrThrow } from "@freighttech/ui/backend/utils/apiCall";
9
+ import { flash } from "@freighttech/ui/backend/FlashMessages";
10
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
11
+ import {
12
+ Dialog,
13
+ DialogContent,
14
+ DialogHeader,
15
+ DialogTitle,
16
+ DialogFooter
17
+ } from "@freighttech/ui/primitives/dialog";
18
+ import { Button } from "@freighttech/ui/primitives/button";
19
+ import { Input } from "@freighttech/ui/primitives/input";
20
+ import { Label } from "@freighttech/ui/primitives/label";
21
+ import { Checkbox } from "@freighttech/ui/primitives/checkbox";
22
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@freighttech/ui/primitives/card";
23
+ import { Loader2, CheckCircle2, XCircle } from "lucide-react";
24
+ function mapItem(item) {
25
+ const id = typeof item.id === "string" ? item.id : null;
26
+ if (!id) return null;
27
+ return {
28
+ id,
29
+ terminalCode: item.terminalCode ?? "",
30
+ adapterType: item.adapterType ?? "",
31
+ displayName: item.displayName ?? "",
32
+ baseUrl: item.baseUrl ?? "",
33
+ authType: item.authType ?? "",
34
+ unlocode: item.unlocode ?? null,
35
+ isActive: item.isActive === true
36
+ };
37
+ }
38
+ const ADAPTER_SPECS = {
39
+ n4: {
40
+ value: "n4",
41
+ label: "DCT (Gda\u0144sk)",
42
+ authType: "oauth2_password",
43
+ showOAuth: true,
44
+ defaults: {
45
+ terminalCode: "dct",
46
+ displayName: "DCT Gda\u0144sk (Baltic Hub)",
47
+ baseUrl: "https://api2.baltichub.com/V1/",
48
+ tokenUrl: "https://bhctapicustomers.b2clogin.com/bhctapicustomers.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_ropc_login",
49
+ // Integrator-specific — configured per deployment, not published here.
50
+ clientId: "",
51
+ scope: "",
52
+ unlocode: "PLGDN",
53
+ smdgCodes: "PLGDNDCT",
54
+ bicCodes: "PLGDNSKZB",
55
+ nameAliases: "Baltic Hub, DCT Gda\u0144sk",
56
+ rateLimitRequests: 200,
57
+ rateLimitWindowSeconds: 60
58
+ }
59
+ },
60
+ bct: {
61
+ value: "bct",
62
+ label: "BCT / INCOS (Gdynia)",
63
+ authType: "basic",
64
+ showOAuth: false,
65
+ defaults: {
66
+ terminalCode: "bct",
67
+ displayName: "BCT Gdynia (INCOS)",
68
+ baseUrl: "https://incos.pl",
69
+ tokenUrl: "",
70
+ clientId: "",
71
+ scope: "",
72
+ unlocode: "PLGDY",
73
+ smdgCodes: "PLGDYPLBCT",
74
+ bicCodes: "PLGDYBCTA",
75
+ nameAliases: "BCT, Ba\u0142tycki Terminal Kontenerowy",
76
+ // INCOS caps at 10 000 requests/day.
77
+ rateLimitRequests: 1e4,
78
+ rateLimitWindowSeconds: 86400
79
+ }
80
+ },
81
+ gct: {
82
+ value: "gct",
83
+ label: "GCT (Gdynia)",
84
+ authType: "gct_token",
85
+ showOAuth: false,
86
+ defaults: {
87
+ terminalCode: "gct",
88
+ displayName: "GCT Gdynia",
89
+ baseUrl: "",
90
+ tokenUrl: "",
91
+ clientId: "",
92
+ scope: "",
93
+ unlocode: "PLGDY",
94
+ smdgCodes: "PLGDYPLGCT",
95
+ bicCodes: "PLGDYEOOJ",
96
+ nameAliases: "GCT",
97
+ rateLimitRequests: 200,
98
+ rateLimitWindowSeconds: 60
99
+ }
100
+ }
101
+ };
102
+ const ADAPTER_ORDER = ["n4", "bct", "gct"];
103
+ function credentialFieldsFor(adapterType, authType) {
104
+ if (adapterType === "gct") {
105
+ return [
106
+ { key: "companyCode", label: "Company code", placeholder: "ACME" },
107
+ { key: "loginName", label: "Login" },
108
+ { key: "totpSecret", label: "Tw\xF3j sekret 2FA", secret: true, placeholder: "JBSWY3DPEHPK3PXP" }
109
+ ];
110
+ }
111
+ if (adapterType === "n4" && authType === "oauth2_client_credentials") {
112
+ return [{ key: "clientSecret", label: "Client secret", secret: true }];
113
+ }
114
+ return [
115
+ { key: "username", label: "Username" },
116
+ { key: "password", label: "Password", secret: true }
117
+ ];
118
+ }
119
+ const baseForm = {
120
+ terminalCode: "",
121
+ adapterType: "n4",
122
+ displayName: "",
123
+ baseUrl: "",
124
+ proxyUrl: "",
125
+ authType: "oauth2_password",
126
+ tokenUrl: "",
127
+ scope: "",
128
+ clientId: "",
129
+ credentials: {},
130
+ unlocode: "",
131
+ bicCodes: "",
132
+ smdgCodes: "",
133
+ nameAliases: "",
134
+ rateLimitRequests: 200,
135
+ rateLimitWindowSeconds: 60,
136
+ isActive: true
137
+ };
138
+ function applyAdapterDefaults(adapterType, prev, opts) {
139
+ const spec = ADAPTER_SPECS[adapterType] ?? ADAPTER_SPECS.n4;
140
+ const d = spec.defaults;
141
+ return {
142
+ ...prev,
143
+ adapterType,
144
+ authType: spec.authType,
145
+ displayName: d.displayName ?? "",
146
+ baseUrl: d.baseUrl ?? "",
147
+ tokenUrl: d.tokenUrl ?? "",
148
+ scope: d.scope ?? "",
149
+ clientId: d.clientId ?? "",
150
+ unlocode: d.unlocode ?? "",
151
+ smdgCodes: d.smdgCodes ?? "",
152
+ bicCodes: d.bicCodes ?? "",
153
+ nameAliases: d.nameAliases ?? "",
154
+ rateLimitRequests: d.rateLimitRequests ?? 200,
155
+ rateLimitWindowSeconds: d.rateLimitWindowSeconds ?? 60,
156
+ credentials: {},
157
+ ...opts.includeTerminalCode ? { terminalCode: d.terminalCode ?? "" } : {}
158
+ };
159
+ }
160
+ const emptyForm = applyAdapterDefaults("n4", baseForm, { includeTerminalCode: true });
161
+ const API = "/api/terminal_tracking/terminal-configs";
162
+ function csv(value) {
163
+ const arr = value.split(",").map((s) => s.trim()).filter(Boolean);
164
+ return arr.length ? arr : void 0;
165
+ }
166
+ function TerminalConfigsPage() {
167
+ const t = useT();
168
+ const [dialogOpen, setDialogOpen] = useState(false);
169
+ const [editingId, setEditingId] = useState(null);
170
+ const [form, setForm] = useState(emptyForm);
171
+ const [submitting, setSubmitting] = useState(false);
172
+ const [testResult, setTestResult] = useState(null);
173
+ const isEdit = editingId !== null;
174
+ const spec = ADAPTER_SPECS[form.adapterType] ?? ADAPTER_SPECS.n4;
175
+ const credFields = credentialFieldsFor(form.adapterType, form.authType);
176
+ const setField = useCallback(
177
+ (key, value) => setForm((p) => ({ ...p, [key]: value })),
178
+ []
179
+ );
180
+ const setCredential = useCallback(
181
+ (key, value) => setForm((p) => ({ ...p, credentials: { ...p.credentials, [key]: value } })),
182
+ []
183
+ );
184
+ const changeAdapter = useCallback(
185
+ (adapterType) => setForm((p) => applyAdapterDefaults(adapterType, p, { includeTerminalCode: !isEdit })),
186
+ [isEdit]
187
+ );
188
+ const columns = useMemo(
189
+ () => [
190
+ {
191
+ data: "displayName",
192
+ title: t("terminal_tracking.terminal_config.displayName", "Name"),
193
+ width: 180,
194
+ readOnly: true,
195
+ renderer: (v) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: String(v ?? "") })
196
+ },
197
+ {
198
+ data: "terminalCode",
199
+ title: t("terminal_tracking.terminal_config.terminalCode", "Terminal code"),
200
+ width: 120,
201
+ readOnly: true
202
+ },
203
+ {
204
+ data: "adapterType",
205
+ title: t("terminal_tracking.terminal_config.adapterType", "Adapter"),
206
+ width: 130,
207
+ readOnly: true,
208
+ renderer: (v) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: ADAPTER_SPECS[String(v)]?.label ?? String(v ?? "") })
209
+ },
210
+ {
211
+ data: "unlocode",
212
+ title: t("terminal_tracking.terminal_config.unlocode", "UN/LOCODE"),
213
+ width: 110,
214
+ readOnly: true,
215
+ renderer: (v) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: String(v || "-") })
216
+ },
217
+ {
218
+ data: "isActive",
219
+ title: t("terminal_tracking.terminal_config.isActive", "Active"),
220
+ width: 80,
221
+ readOnly: true,
222
+ renderer: (v) => /* @__PURE__ */ jsx(BooleanIcon, { value: v === true || v === "true" })
223
+ }
224
+ ],
225
+ [t]
226
+ );
227
+ const openCreate = useCallback(() => {
228
+ setEditingId(null);
229
+ setForm(emptyForm);
230
+ setTestResult(null);
231
+ setDialogOpen(true);
232
+ }, []);
233
+ const addButton = useMemo(
234
+ () => /* @__PURE__ */ jsx(Button, { size: "sm", onClick: openCreate, children: t("terminal_tracking.terminal_config.create", "Add Terminal") }),
235
+ [openCreate, t]
236
+ );
237
+ const table = useDynamicTablePage({
238
+ source: API,
239
+ columns,
240
+ tableName: t("terminal_tracking.terminal_config.title", "Terminals"),
241
+ defaultPageSize: 20,
242
+ mapApiItem: mapItem,
243
+ cellEdit: false,
244
+ tableProps: {
245
+ height: "100%",
246
+ uiConfig: {
247
+ readOnlyStyle: "normal",
248
+ hideAddRowButton: true,
249
+ topBarEnd: addButton
250
+ }
251
+ }
252
+ });
253
+ const openEdit = useCallback(
254
+ (id) => {
255
+ const items = table.query.data?.items ?? [];
256
+ const raw = items.find((r) => r.id === id);
257
+ if (!raw) return;
258
+ const adapterType = raw.adapterType ?? "n4";
259
+ const rowAuthType = raw.authType ?? ADAPTER_SPECS[adapterType]?.authType ?? "oauth2_password";
260
+ setEditingId(id);
261
+ setTestResult(null);
262
+ setForm({
263
+ ...emptyForm,
264
+ terminalCode: raw.terminalCode ?? "",
265
+ adapterType,
266
+ // Preserve the row's stored auth type (e.g. a legacy n4 client-credentials config).
267
+ authType: rowAuthType,
268
+ displayName: raw.displayName ?? "",
269
+ baseUrl: raw.baseUrl ?? "",
270
+ proxyUrl: raw.proxyUrl ?? "",
271
+ tokenUrl: raw.tokenUrl ?? "",
272
+ scope: raw.scope ?? "",
273
+ clientId: raw.clientId ?? "",
274
+ credentials: {},
275
+ // secrets are never returned; blank = keep unchanged
276
+ unlocode: raw.unlocode ?? "",
277
+ bicCodes: Array.isArray(raw.bicCodes) ? raw.bicCodes.join(", ") : "",
278
+ smdgCodes: Array.isArray(raw.smdgCodes) ? raw.smdgCodes.join(", ") : "",
279
+ nameAliases: Array.isArray(raw.nameAliases) ? raw.nameAliases.join(", ") : "",
280
+ rateLimitRequests: typeof raw.rateLimitRequests === "number" ? raw.rateLimitRequests : 200,
281
+ rateLimitWindowSeconds: typeof raw.rateLimitWindowSeconds === "number" ? raw.rateLimitWindowSeconds : 60,
282
+ isActive: raw.isActive === true
283
+ });
284
+ setDialogOpen(true);
285
+ },
286
+ [table.query.data]
287
+ );
288
+ const buildBody = useCallback(() => {
289
+ const fields = credentialFieldsFor(form.adapterType, form.authType);
290
+ const filled = fields.filter((f) => (form.credentials[f.key] ?? "").trim() !== "");
291
+ let authConfig;
292
+ if (filled.length > 0) {
293
+ if (filled.length !== fields.length) {
294
+ flash(
295
+ t("terminal_tracking.terminal_config.validation.allCredentials", "Enter all credential fields"),
296
+ "error"
297
+ );
298
+ return null;
299
+ }
300
+ authConfig = Object.fromEntries(fields.map((f) => [f.key, form.credentials[f.key].trim()]));
301
+ } else if (!isEdit) {
302
+ flash(
303
+ t("terminal_tracking.terminal_config.validation.credentialsRequired", "Credentials are required"),
304
+ "error"
305
+ );
306
+ return null;
307
+ }
308
+ const showOAuth = ADAPTER_SPECS[form.adapterType]?.showOAuth ?? false;
309
+ const body = {
310
+ adapterType: form.adapterType.trim() || "n4",
311
+ displayName: form.displayName.trim(),
312
+ baseUrl: form.baseUrl.trim(),
313
+ proxyUrl: form.proxyUrl.trim() || null,
314
+ authType: form.authType,
315
+ tokenUrl: showOAuth ? form.tokenUrl.trim() || null : null,
316
+ scope: showOAuth ? form.scope.trim() || null : null,
317
+ clientId: showOAuth ? form.clientId.trim() || null : null,
318
+ unlocode: form.unlocode.trim() || null,
319
+ bicCodes: csv(form.bicCodes) ?? null,
320
+ smdgCodes: csv(form.smdgCodes) ?? null,
321
+ nameAliases: csv(form.nameAliases) ?? null,
322
+ rateLimitRequests: form.rateLimitRequests,
323
+ rateLimitWindowSeconds: form.rateLimitWindowSeconds,
324
+ isActive: form.isActive
325
+ };
326
+ if (authConfig !== void 0) body.authConfig = authConfig;
327
+ if (!isEdit) body.terminalCode = form.terminalCode.trim();
328
+ else body.id = editingId;
329
+ return body;
330
+ }, [form, isEdit, editingId, t]);
331
+ const handleSubmit = useCallback(async () => {
332
+ if (submitting) return;
333
+ if (!isEdit && !form.terminalCode.trim()) {
334
+ flash(t("terminal_tracking.terminal_config.validation.codeRequired", "Terminal code is required"), "error");
335
+ return;
336
+ }
337
+ if (!form.displayName.trim() || !form.baseUrl.trim()) {
338
+ flash(t("terminal_tracking.terminal_config.validation.required", "Name and base URL are required"), "error");
339
+ return;
340
+ }
341
+ const body = buildBody();
342
+ if (!body) return;
343
+ setSubmitting(true);
344
+ try {
345
+ await apiCallOrThrow(API, { method: isEdit ? "PUT" : "POST", body: JSON.stringify(body) });
346
+ flash(
347
+ isEdit ? t("terminal_tracking.terminal_config.flash.updated", "Terminal updated") : t("terminal_tracking.terminal_config.flash.created", "Terminal created"),
348
+ "success"
349
+ );
350
+ setDialogOpen(false);
351
+ table.refresh();
352
+ } catch (err) {
353
+ flash(err instanceof Error ? err.message : "Failed to save terminal", "error");
354
+ } finally {
355
+ setSubmitting(false);
356
+ }
357
+ }, [submitting, isEdit, form, buildBody, table, t]);
358
+ const handleDelete = useCallback(
359
+ async (id) => {
360
+ if (!confirm(t("terminal_tracking.terminal_config.confirmDelete", "Delete this terminal?"))) return;
361
+ try {
362
+ await apiCallOrThrow(API, { method: "DELETE", body: JSON.stringify({ id }) });
363
+ flash(t("terminal_tracking.terminal_config.flash.deleted", "Terminal deleted"), "success");
364
+ table.refresh();
365
+ } catch (err) {
366
+ flash(err instanceof Error ? err.message : "Failed to delete terminal", "error");
367
+ }
368
+ },
369
+ [table, t]
370
+ );
371
+ const handleTest = useCallback(
372
+ async (id) => {
373
+ setTestResult(null);
374
+ try {
375
+ const { result } = await apiCallOrThrow(
376
+ `${API}/test`,
377
+ { method: "POST", body: JSON.stringify({ id }) }
378
+ );
379
+ flash(
380
+ result?.success ? t("terminal_tracking.terminal_config.flash.testOk", "Connection OK") : result?.message ?? "Connection failed",
381
+ result?.success ? "success" : "error"
382
+ );
383
+ } catch (err) {
384
+ flash(err instanceof Error ? err.message : "Connection test failed", "error");
385
+ }
386
+ },
387
+ [t]
388
+ );
389
+ const handleDialogKeyDown = useCallback(
390
+ (event) => {
391
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
392
+ event.preventDefault();
393
+ handleSubmit();
394
+ }
395
+ },
396
+ [handleSubmit]
397
+ );
398
+ const actionsRenderer = useCallback(
399
+ (rowData) => {
400
+ if (!rowData?.id) return null;
401
+ const items = [
402
+ { label: t("terminal_tracking.terminal_config.actions.test", "Test connection"), onSelect: () => handleTest(rowData.id) },
403
+ { label: t("common.edit", "Edit"), onSelect: () => openEdit(rowData.id) },
404
+ { label: t("common.delete", "Delete"), onSelect: () => handleDelete(rowData.id), destructive: true }
405
+ ];
406
+ return /* @__PURE__ */ jsx(RowActions, { items });
407
+ },
408
+ [t, handleTest, openEdit, handleDelete]
409
+ );
410
+ if (table.isLoading) {
411
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { className: "space-y-6", children: /* @__PURE__ */ jsxs(Card, { children: [
412
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: t("terminal_tracking.terminal_config.title", "Terminals") }) }),
413
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(TableSkeleton, { rows: 5, columns: 5 }) })
414
+ ] }) }) });
415
+ }
416
+ const credPlaceholder = (f) => isEdit && f.secret ? "\u2022\u2022\u2022\u2022 " + t("terminal_tracking.terminal_config.unchanged", "unchanged") : f.placeholder;
417
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
418
+ /* @__PURE__ */ jsxs(Card, { children: [
419
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
420
+ /* @__PURE__ */ jsx(CardTitle, { children: t("terminal_tracking.terminal_config.title", "Terminals") }),
421
+ /* @__PURE__ */ jsx(CardDescription, { children: t(
422
+ "terminal_tracking.terminal_config.description",
423
+ "Configure container terminal APIs (auth + URLs) and the identifiers used to match them."
424
+ ) })
425
+ ] }),
426
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", { style: { height: 420 }, children: /* @__PURE__ */ jsx(DynamicTable, { ...table.props, actionsRenderer }) }) })
427
+ ] }),
428
+ /* @__PURE__ */ jsx(Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-xl max-h-[85vh] overflow-y-auto", onKeyDown: handleDialogKeyDown, children: [
429
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: isEdit ? t("terminal_tracking.terminal_config.edit", "Edit Terminal") : t("terminal_tracking.terminal_config.create", "Add Terminal") }) }),
430
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 py-2", children: [
431
+ /* @__PURE__ */ jsx(SectionLabel, { children: t("terminal_tracking.terminal_config.section.terminal", "Terminal") }),
432
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
433
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
434
+ /* @__PURE__ */ jsx(Label, { htmlFor: "adapterType", children: t("terminal_tracking.terminal_config.adapterType", "Adapter") }),
435
+ /* @__PURE__ */ jsx(
436
+ "select",
437
+ {
438
+ id: "adapterType",
439
+ className: "border rounded h-9 px-2 text-sm",
440
+ value: form.adapterType,
441
+ onChange: (e) => changeAdapter(e.target.value),
442
+ children: ADAPTER_ORDER.map((k) => /* @__PURE__ */ jsx("option", { value: k, children: ADAPTER_SPECS[k].label }, k))
443
+ }
444
+ )
445
+ ] }),
446
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
447
+ /* @__PURE__ */ jsx(Label, { htmlFor: "terminalCode", children: t("terminal_tracking.terminal_config.terminalCode", "Terminal code") }),
448
+ /* @__PURE__ */ jsx(
449
+ Input,
450
+ {
451
+ id: "terminalCode",
452
+ value: form.terminalCode,
453
+ disabled: isEdit,
454
+ onChange: (e) => setField("terminalCode", e.target.value),
455
+ placeholder: "dct"
456
+ }
457
+ )
458
+ ] })
459
+ ] }),
460
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
461
+ /* @__PURE__ */ jsx(Label, { htmlFor: "displayName", children: t("terminal_tracking.terminal_config.displayName", "Name") }),
462
+ /* @__PURE__ */ jsx(
463
+ Input,
464
+ {
465
+ id: "displayName",
466
+ value: form.displayName,
467
+ onChange: (e) => setField("displayName", e.target.value),
468
+ placeholder: "DCT Gda\u0144sk (Baltic Hub)"
469
+ }
470
+ )
471
+ ] }),
472
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
473
+ /* @__PURE__ */ jsx(Label, { htmlFor: "baseUrl", children: t("terminal_tracking.terminal_config.baseUrl", "API base URL") }),
474
+ /* @__PURE__ */ jsx(
475
+ Input,
476
+ {
477
+ id: "baseUrl",
478
+ value: form.baseUrl,
479
+ onChange: (e) => setField("baseUrl", e.target.value),
480
+ placeholder: "https://\u2026"
481
+ }
482
+ )
483
+ ] }),
484
+ /* @__PURE__ */ jsx(SectionLabel, { children: t("terminal_tracking.terminal_config.section.auth", "Authentication") }),
485
+ spec.showOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
486
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
487
+ /* @__PURE__ */ jsx(Label, { htmlFor: "tokenUrl", children: t("terminal_tracking.terminal_config.tokenUrl", "Token URL") }),
488
+ /* @__PURE__ */ jsx(
489
+ Input,
490
+ {
491
+ id: "tokenUrl",
492
+ value: form.tokenUrl,
493
+ onChange: (e) => setField("tokenUrl", e.target.value),
494
+ placeholder: ".../oauth2/v2.0/token?p=b2c_1_ropc_login"
495
+ }
496
+ )
497
+ ] }),
498
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
499
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
500
+ /* @__PURE__ */ jsx(Label, { htmlFor: "clientId", children: t("terminal_tracking.terminal_config.clientId", "Client ID") }),
501
+ /* @__PURE__ */ jsx(
502
+ Input,
503
+ {
504
+ id: "clientId",
505
+ value: form.clientId,
506
+ onChange: (e) => setField("clientId", e.target.value),
507
+ placeholder: "00000000-0000-0000-0000-000000000000"
508
+ }
509
+ )
510
+ ] }),
511
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
512
+ /* @__PURE__ */ jsx(Label, { htmlFor: "scope", children: t("terminal_tracking.terminal_config.scope", "Scope") }),
513
+ /* @__PURE__ */ jsx(
514
+ Input,
515
+ {
516
+ id: "scope",
517
+ value: form.scope,
518
+ onChange: (e) => setField("scope", e.target.value),
519
+ placeholder: "https://<tenant>/<app-id>/<scope>"
520
+ }
521
+ )
522
+ ] })
523
+ ] })
524
+ ] }),
525
+ credFields.map((f) => /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
526
+ /* @__PURE__ */ jsx(Label, { htmlFor: `cred-${f.key}`, children: t(`terminal_tracking.terminal_config.cred.${f.key}`, f.label) }),
527
+ /* @__PURE__ */ jsx(
528
+ Input,
529
+ {
530
+ id: `cred-${f.key}`,
531
+ type: f.secret ? "password" : "text",
532
+ autoComplete: "off",
533
+ value: form.credentials[f.key] ?? "",
534
+ onChange: (e) => setCredential(f.key, e.target.value),
535
+ placeholder: credPlaceholder(f)
536
+ }
537
+ )
538
+ ] }, f.key)),
539
+ form.adapterType === "gct" && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
540
+ "terminal_tracking.terminal_config.gctHint",
541
+ "The base32 key shown in the GCT panel next to the QR code. A fresh one-time code is computed from it on each login."
542
+ ) }),
543
+ isEdit && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("terminal_tracking.terminal_config.credEditHint", "Leave credentials blank to keep them unchanged.") }),
544
+ /* @__PURE__ */ jsx(SectionLabel, { children: t("terminal_tracking.terminal_config.section.matching", "Matching") }),
545
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
546
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
547
+ /* @__PURE__ */ jsx(Label, { htmlFor: "unlocode", children: t("terminal_tracking.terminal_config.unlocode", "UN/LOCODE") }),
548
+ /* @__PURE__ */ jsx(
549
+ Input,
550
+ {
551
+ id: "unlocode",
552
+ value: form.unlocode,
553
+ maxLength: 5,
554
+ onChange: (e) => setField("unlocode", e.target.value.toUpperCase()),
555
+ placeholder: "PLGDN"
556
+ }
557
+ )
558
+ ] }),
559
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
560
+ /* @__PURE__ */ jsx(Label, { htmlFor: "smdgCodes", children: t("terminal_tracking.terminal_config.smdgCodes", "SMDG codes (csv)") }),
561
+ /* @__PURE__ */ jsx(
562
+ Input,
563
+ {
564
+ id: "smdgCodes",
565
+ value: form.smdgCodes,
566
+ onChange: (e) => setField("smdgCodes", e.target.value)
567
+ }
568
+ )
569
+ ] })
570
+ ] }),
571
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
572
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
573
+ /* @__PURE__ */ jsx(Label, { htmlFor: "bicCodes", children: t("terminal_tracking.terminal_config.bicCodes", "BIC codes (csv)") }),
574
+ /* @__PURE__ */ jsx(
575
+ Input,
576
+ {
577
+ id: "bicCodes",
578
+ value: form.bicCodes,
579
+ onChange: (e) => setField("bicCodes", e.target.value)
580
+ }
581
+ )
582
+ ] }),
583
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
584
+ /* @__PURE__ */ jsx(Label, { htmlFor: "nameAliases", children: t("terminal_tracking.terminal_config.nameAliases", "Name aliases (csv)") }),
585
+ /* @__PURE__ */ jsx(
586
+ Input,
587
+ {
588
+ id: "nameAliases",
589
+ value: form.nameAliases,
590
+ onChange: (e) => setField("nameAliases", e.target.value),
591
+ placeholder: "Baltic Hub, DCT Gda\u0144sk"
592
+ }
593
+ )
594
+ ] })
595
+ ] }),
596
+ /* @__PURE__ */ jsx(SectionLabel, { children: t("terminal_tracking.terminal_config.section.advanced", "Advanced") }),
597
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
598
+ /* @__PURE__ */ jsx(Label, { htmlFor: "proxyUrl", children: t("terminal_tracking.terminal_config.proxyUrl", "API proxy URL (optional)") }),
599
+ /* @__PURE__ */ jsx(
600
+ Input,
601
+ {
602
+ id: "proxyUrl",
603
+ value: form.proxyUrl,
604
+ onChange: (e) => setField("proxyUrl", e.target.value),
605
+ placeholder: "http://proxy.example.com:8888"
606
+ }
607
+ )
608
+ ] }),
609
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
610
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
611
+ /* @__PURE__ */ jsx(Label, { htmlFor: "rlReq", children: t("terminal_tracking.terminal_config.rateLimitRequests", "Rate limit (requests)") }),
612
+ /* @__PURE__ */ jsx(
613
+ Input,
614
+ {
615
+ id: "rlReq",
616
+ type: "number",
617
+ min: 1,
618
+ value: form.rateLimitRequests,
619
+ onChange: (e) => setField("rateLimitRequests", Number(e.target.value) || 200)
620
+ }
621
+ )
622
+ ] }),
623
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
624
+ /* @__PURE__ */ jsx(Label, { htmlFor: "rlWin", children: t("terminal_tracking.terminal_config.rateLimitWindowSeconds", "Window (seconds)") }),
625
+ /* @__PURE__ */ jsx(
626
+ Input,
627
+ {
628
+ id: "rlWin",
629
+ type: "number",
630
+ min: 1,
631
+ value: form.rateLimitWindowSeconds,
632
+ onChange: (e) => setField("rateLimitWindowSeconds", Number(e.target.value) || 60)
633
+ }
634
+ )
635
+ ] })
636
+ ] }),
637
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
638
+ /* @__PURE__ */ jsx(
639
+ Checkbox,
640
+ {
641
+ id: "isActive",
642
+ checked: form.isActive,
643
+ onCheckedChange: (c) => setField("isActive", c === true)
644
+ }
645
+ ),
646
+ /* @__PURE__ */ jsx(Label, { htmlFor: "isActive", className: "cursor-pointer", children: t("terminal_tracking.terminal_config.isActive", "Active") })
647
+ ] }),
648
+ testResult && /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 rounded-md p-3 text-sm ${testResult.success ? "bg-green-50 text-green-700" : "bg-red-50 text-red-700"}`, children: [
649
+ testResult.success ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4" }),
650
+ /* @__PURE__ */ jsx("span", { children: testResult.message })
651
+ ] })
652
+ ] }),
653
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
654
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setDialogOpen(false), disabled: submitting, children: t("common.cancel", "Cancel") }),
655
+ /* @__PURE__ */ jsxs(Button, { onClick: handleSubmit, disabled: submitting, children: [
656
+ submitting ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null,
657
+ isEdit ? t("common.save", "Save") : t("common.create", "Create")
658
+ ] })
659
+ ] })
660
+ ] }) })
661
+ ] }) });
662
+ }
663
+ function SectionLabel({ children }) {
664
+ return /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground pt-1", children });
665
+ }
666
+ export {
667
+ TerminalConfigsPage as default
668
+ };
669
+ //# sourceMappingURL=page.js.map