@freighttech/shipment-tracking 0.6.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 (456) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +25 -0
  3. package/dist/generated/entities/bic_config/index.js +21 -0
  4. package/dist/generated/entities/bic_config/index.js.map +7 -0
  5. package/dist/generated/entities/carrier_config/index.js +27 -0
  6. package/dist/generated/entities/carrier_config/index.js.map +7 -0
  7. package/dist/generated/entities/location_override/index.js +33 -0
  8. package/dist/generated/entities/location_override/index.js.map +7 -0
  9. package/dist/generated/entities/shipment/index.js +73 -0
  10. package/dist/generated/entities/shipment/index.js.map +7 -0
  11. package/dist/generated/entities/ships_go_config/index.js +31 -0
  12. package/dist/generated/entities/ships_go_config/index.js.map +7 -0
  13. package/dist/generated/entities/tracking_event/index.js +101 -0
  14. package/dist/generated/entities/tracking_event/index.js.map +7 -0
  15. package/dist/generated/entities/tracking_job/index.js +47 -0
  16. package/dist/generated/entities/tracking_job/index.js.map +7 -0
  17. package/dist/generated/entities/webhook/index.js +25 -0
  18. package/dist/generated/entities/webhook/index.js.map +7 -0
  19. package/dist/generated/entities/webhook_delivery/index.js +25 -0
  20. package/dist/generated/entities/webhook_delivery/index.js.map +7 -0
  21. package/dist/generated/entities.ids.generated.js +21 -0
  22. package/dist/generated/entities.ids.generated.js.map +7 -0
  23. package/dist/index.js +41 -0
  24. package/dist/index.js.map +7 -0
  25. package/dist/modules/shipment_tracking/acl.js +22 -0
  26. package/dist/modules/shipment_tracking/acl.js.map +7 -0
  27. package/dist/modules/shipment_tracking/api/bic-configs/route.js +176 -0
  28. package/dist/modules/shipment_tracking/api/bic-configs/route.js.map +7 -0
  29. package/dist/modules/shipment_tracking/api/bic-configs/test/route.js +95 -0
  30. package/dist/modules/shipment_tracking/api/bic-configs/test/route.js.map +7 -0
  31. package/dist/modules/shipment_tracking/api/cargo-events/route.js +93 -0
  32. package/dist/modules/shipment_tracking/api/cargo-events/route.js.map +7 -0
  33. package/dist/modules/shipment_tracking/api/carrier-configs/route.js +139 -0
  34. package/dist/modules/shipment_tracking/api/carrier-configs/route.js.map +7 -0
  35. package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js +177 -0
  36. package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js.map +7 -0
  37. package/dist/modules/shipment_tracking/api/location-overrides/route.js +179 -0
  38. package/dist/modules/shipment_tracking/api/location-overrides/route.js.map +7 -0
  39. package/dist/modules/shipment_tracking/api/openapi.js +27 -0
  40. package/dist/modules/shipment_tracking/api/openapi.js.map +7 -0
  41. package/dist/modules/shipment_tracking/api/shipments/route.js +192 -0
  42. package/dist/modules/shipment_tracking/api/shipments/route.js.map +7 -0
  43. package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js +185 -0
  44. package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js.map +7 -0
  45. package/dist/modules/shipment_tracking/api/tracking-jobs/route.js +144 -0
  46. package/dist/modules/shipment_tracking/api/tracking-jobs/route.js.map +7 -0
  47. package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js +107 -0
  48. package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js.map +7 -0
  49. package/dist/modules/shipment_tracking/api/utils.js +12 -0
  50. package/dist/modules/shipment_tracking/api/utils.js.map +7 -0
  51. package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js +76 -0
  52. package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js.map +7 -0
  53. package/dist/modules/shipment_tracking/api/webhooks/route.js +135 -0
  54. package/dist/modules/shipment_tracking/api/webhooks/route.js.map +7 -0
  55. package/dist/modules/shipment_tracking/api/webhooks/test/route.js +71 -0
  56. package/dist/modules/shipment_tracking/api/webhooks/test/route.js.map +7 -0
  57. package/dist/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
  58. package/dist/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
  59. package/dist/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
  60. package/dist/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
  61. package/dist/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
  62. package/dist/modules/shipment_tracking/assets/carriers/msc.png +0 -0
  63. package/dist/modules/shipment_tracking/assets/carriers/one.png +0 -0
  64. package/dist/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
  65. package/dist/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
  66. package/dist/modules/shipment_tracking/assets/carriers/zim.png +0 -0
  67. package/dist/modules/shipment_tracking/assets/index.js +52 -0
  68. package/dist/modules/shipment_tracking/assets/index.js.map +7 -0
  69. package/dist/modules/shipment_tracking/backend/carrier-configs/page.js +621 -0
  70. package/dist/modules/shipment_tracking/backend/carrier-configs/page.js.map +7 -0
  71. package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js +19 -0
  72. package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js.map +7 -0
  73. package/dist/modules/shipment_tracking/backend/location-overrides/page.js +198 -0
  74. package/dist/modules/shipment_tracking/backend/location-overrides/page.js.map +7 -0
  75. package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js +19 -0
  76. package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js.map +7 -0
  77. package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js +279 -0
  78. package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js.map +7 -0
  79. package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js +19 -0
  80. package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js.map +7 -0
  81. package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js +10 -0
  82. package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js.map +7 -0
  83. package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js +19 -0
  84. package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js.map +7 -0
  85. package/dist/modules/shipment_tracking/backend/webhooks/page.js +198 -0
  86. package/dist/modules/shipment_tracking/backend/webhooks/page.js.map +7 -0
  87. package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js +19 -0
  88. package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js.map +7 -0
  89. package/dist/modules/shipment_tracking/ce.js +52 -0
  90. package/dist/modules/shipment_tracking/ce.js.map +7 -0
  91. package/dist/modules/shipment_tracking/cli.js +229 -0
  92. package/dist/modules/shipment_tracking/cli.js.map +7 -0
  93. package/dist/modules/shipment_tracking/commands/bic-configs.js +66 -0
  94. package/dist/modules/shipment_tracking/commands/bic-configs.js.map +7 -0
  95. package/dist/modules/shipment_tracking/commands/carrier-configs.js +65 -0
  96. package/dist/modules/shipment_tracking/commands/carrier-configs.js.map +7 -0
  97. package/dist/modules/shipment_tracking/commands/index.js +9 -0
  98. package/dist/modules/shipment_tracking/commands/index.js.map +7 -0
  99. package/dist/modules/shipment_tracking/commands/location-overrides.js +93 -0
  100. package/dist/modules/shipment_tracking/commands/location-overrides.js.map +7 -0
  101. package/dist/modules/shipment_tracking/commands/shipments.js +200 -0
  102. package/dist/modules/shipment_tracking/commands/shipments.js.map +7 -0
  103. package/dist/modules/shipment_tracking/commands/shipsgo-configs.js +75 -0
  104. package/dist/modules/shipment_tracking/commands/shipsgo-configs.js.map +7 -0
  105. package/dist/modules/shipment_tracking/commands/tracking-jobs.js +120 -0
  106. package/dist/modules/shipment_tracking/commands/tracking-jobs.js.map +7 -0
  107. package/dist/modules/shipment_tracking/commands/tracking.js +140 -0
  108. package/dist/modules/shipment_tracking/commands/tracking.js.map +7 -0
  109. package/dist/modules/shipment_tracking/commands/webhooks.js +115 -0
  110. package/dist/modules/shipment_tracking/commands/webhooks.js.map +7 -0
  111. package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js +168 -0
  112. package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js.map +7 -0
  113. package/dist/modules/shipment_tracking/components/FacilityLocationInput.js +202 -0
  114. package/dist/modules/shipment_tracking/components/FacilityLocationInput.js.map +7 -0
  115. package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js +501 -0
  116. package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js.map +7 -0
  117. package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js +818 -0
  118. package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js.map +7 -0
  119. package/dist/modules/shipment_tracking/components/ShipmentDrawer.js +404 -0
  120. package/dist/modules/shipment_tracking/components/ShipmentDrawer.js.map +7 -0
  121. package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js +200 -0
  122. package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js.map +7 -0
  123. package/dist/modules/shipment_tracking/components/TimestampCell.js +108 -0
  124. package/dist/modules/shipment_tracking/components/TimestampCell.js.map +7 -0
  125. package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js +343 -0
  126. package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js.map +7 -0
  127. package/dist/modules/shipment_tracking/components/VesselTrackingMap.js +496 -0
  128. package/dist/modules/shipment_tracking/components/VesselTrackingMap.js.map +7 -0
  129. package/dist/modules/shipment_tracking/components/WebhookDrawer.js +332 -0
  130. package/dist/modules/shipment_tracking/components/WebhookDrawer.js.map +7 -0
  131. package/dist/modules/shipment_tracking/data/entities.js +686 -0
  132. package/dist/modules/shipment_tracking/data/entities.js.map +7 -0
  133. package/dist/modules/shipment_tracking/data/validators.js +434 -0
  134. package/dist/modules/shipment_tracking/data/validators.js.map +7 -0
  135. package/dist/modules/shipment_tracking/di.js +60 -0
  136. package/dist/modules/shipment_tracking/di.js.map +7 -0
  137. package/dist/modules/shipment_tracking/encryption.js +12 -0
  138. package/dist/modules/shipment_tracking/encryption.js.map +7 -0
  139. package/dist/modules/shipment_tracking/events.js +87 -0
  140. package/dist/modules/shipment_tracking/events.js.map +7 -0
  141. package/dist/modules/shipment_tracking/hooks/useVesselTracking.js +137 -0
  142. package/dist/modules/shipment_tracking/hooks/useVesselTracking.js.map +7 -0
  143. package/dist/modules/shipment_tracking/i18n/de.json +327 -0
  144. package/dist/modules/shipment_tracking/i18n/en.json +327 -0
  145. package/dist/modules/shipment_tracking/i18n/es.json +327 -0
  146. package/dist/modules/shipment_tracking/i18n/pl.json +327 -0
  147. package/dist/modules/shipment_tracking/index.js +15 -0
  148. package/dist/modules/shipment_tracking/index.js.map +7 -0
  149. package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js +66 -0
  150. package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js.map +7 -0
  151. package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js +1 -0
  152. package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js.map +7 -0
  153. package/dist/modules/shipment_tracking/lib/adapters/cosco.js +236 -0
  154. package/dist/modules/shipment_tracking/lib/adapters/cosco.js.map +7 -0
  155. package/dist/modules/shipment_tracking/lib/adapters/evergreen.js +87 -0
  156. package/dist/modules/shipment_tracking/lib/adapters/evergreen.js.map +7 -0
  157. package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js +68 -0
  158. package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js.map +7 -0
  159. package/dist/modules/shipment_tracking/lib/adapters/index.js +20 -0
  160. package/dist/modules/shipment_tracking/lib/adapters/index.js.map +7 -0
  161. package/dist/modules/shipment_tracking/lib/adapters/maersk.js +84 -0
  162. package/dist/modules/shipment_tracking/lib/adapters/maersk.js.map +7 -0
  163. package/dist/modules/shipment_tracking/lib/adapters/msc.js +170 -0
  164. package/dist/modules/shipment_tracking/lib/adapters/msc.js.map +7 -0
  165. package/dist/modules/shipment_tracking/lib/adapters/zim.js +85 -0
  166. package/dist/modules/shipment_tracking/lib/adapters/zim.js.map +7 -0
  167. package/dist/modules/shipment_tracking/lib/audit-helpers.js +20 -0
  168. package/dist/modules/shipment_tracking/lib/audit-helpers.js.map +7 -0
  169. package/dist/modules/shipment_tracking/lib/auth/base64url.js +8 -0
  170. package/dist/modules/shipment_tracking/lib/auth/base64url.js.map +7 -0
  171. package/dist/modules/shipment_tracking/lib/auth/oauth-client.js +38 -0
  172. package/dist/modules/shipment_tracking/lib/auth/oauth-client.js.map +7 -0
  173. package/dist/modules/shipment_tracking/lib/background.js +15 -0
  174. package/dist/modules/shipment_tracking/lib/background.js.map +7 -0
  175. package/dist/modules/shipment_tracking/lib/bic-api-client.js +106 -0
  176. package/dist/modules/shipment_tracking/lib/bic-api-client.js.map +7 -0
  177. package/dist/modules/shipment_tracking/lib/carrier-adapter.js +17 -0
  178. package/dist/modules/shipment_tracking/lib/carrier-adapter.js.map +7 -0
  179. package/dist/modules/shipment_tracking/lib/current-vessel.js +149 -0
  180. package/dist/modules/shipment_tracking/lib/current-vessel.js.map +7 -0
  181. package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js +124 -0
  182. package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js.map +7 -0
  183. package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js +112 -0
  184. package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js.map +7 -0
  185. package/dist/modules/shipment_tracking/lib/dcsa-params.js +16 -0
  186. package/dist/modules/shipment_tracking/lib/dcsa-params.js.map +7 -0
  187. package/dist/modules/shipment_tracking/lib/empty-return.js +48 -0
  188. package/dist/modules/shipment_tracking/lib/empty-return.js.map +7 -0
  189. package/dist/modules/shipment_tracking/lib/location-overrides.js +85 -0
  190. package/dist/modules/shipment_tracking/lib/location-overrides.js.map +7 -0
  191. package/dist/modules/shipment_tracking/lib/location-types.js +59 -0
  192. package/dist/modules/shipment_tracking/lib/location-types.js.map +7 -0
  193. package/dist/modules/shipment_tracking/lib/logger.js +113 -0
  194. package/dist/modules/shipment_tracking/lib/logger.js.map +7 -0
  195. package/dist/modules/shipment_tracking/lib/poi-event-processor.js +183 -0
  196. package/dist/modules/shipment_tracking/lib/poi-event-processor.js.map +7 -0
  197. package/dist/modules/shipment_tracking/lib/poi-nats-config.js +52 -0
  198. package/dist/modules/shipment_tracking/lib/poi-nats-config.js.map +7 -0
  199. package/dist/modules/shipment_tracking/lib/poi-types.js +43 -0
  200. package/dist/modules/shipment_tracking/lib/poi-types.js.map +7 -0
  201. package/dist/modules/shipment_tracking/lib/rate-limiter.js +31 -0
  202. package/dist/modules/shipment_tracking/lib/rate-limiter.js.map +7 -0
  203. package/dist/modules/shipment_tracking/lib/route-extraction.js +187 -0
  204. package/dist/modules/shipment_tracking/lib/route-extraction.js.map +7 -0
  205. package/dist/modules/shipment_tracking/lib/route-inference.js +106 -0
  206. package/dist/modules/shipment_tracking/lib/route-inference.js.map +7 -0
  207. package/dist/modules/shipment_tracking/lib/schedule-generator.js +63 -0
  208. package/dist/modules/shipment_tracking/lib/schedule-generator.js.map +7 -0
  209. package/dist/modules/shipment_tracking/lib/shipsgo-client.js +143 -0
  210. package/dist/modules/shipment_tracking/lib/shipsgo-client.js.map +7 -0
  211. package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js +204 -0
  212. package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js.map +7 -0
  213. package/dist/modules/shipment_tracking/lib/status-machine.js +131 -0
  214. package/dist/modules/shipment_tracking/lib/status-machine.js.map +7 -0
  215. package/dist/modules/shipment_tracking/lib/time-extraction.js +41 -0
  216. package/dist/modules/shipment_tracking/lib/time-extraction.js.map +7 -0
  217. package/dist/modules/shipment_tracking/lib/timestamp-utils.js +100 -0
  218. package/dist/modules/shipment_tracking/lib/timestamp-utils.js.map +7 -0
  219. package/dist/modules/shipment_tracking/lib/vessel-api.js +111 -0
  220. package/dist/modules/shipment_tracking/lib/vessel-api.js.map +7 -0
  221. package/dist/modules/shipment_tracking/lib/vessel-cache.js +50 -0
  222. package/dist/modules/shipment_tracking/lib/vessel-cache.js.map +7 -0
  223. package/dist/modules/shipment_tracking/lib/vessel-extraction.js +13 -0
  224. package/dist/modules/shipment_tracking/lib/vessel-extraction.js.map +7 -0
  225. package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js +62 -0
  226. package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js.map +7 -0
  227. package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js +48 -0
  228. package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js.map +7 -0
  229. package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js +15 -0
  230. package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js.map +7 -0
  231. package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js +47 -0
  232. package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js.map +7 -0
  233. package/dist/modules/shipment_tracking/notifications.js +145 -0
  234. package/dist/modules/shipment_tracking/notifications.js.map +7 -0
  235. package/dist/modules/shipment_tracking/services/carrierRegistry.js +24 -0
  236. package/dist/modules/shipment_tracking/services/carrierRegistry.js.map +7 -0
  237. package/dist/modules/shipment_tracking/services/poiNatsService.js +397 -0
  238. package/dist/modules/shipment_tracking/services/poiNatsService.js.map +7 -0
  239. package/dist/modules/shipment_tracking/services/shipsGoProvider.js +163 -0
  240. package/dist/modules/shipment_tracking/services/shipsGoProvider.js.map +7 -0
  241. package/dist/modules/shipment_tracking/services/trackingService.js +1283 -0
  242. package/dist/modules/shipment_tracking/services/trackingService.js.map +7 -0
  243. package/dist/modules/shipment_tracking/services/webhookService.js +263 -0
  244. package/dist/modules/shipment_tracking/services/webhookService.js.map +7 -0
  245. package/dist/modules/shipment_tracking/setup.js +72 -0
  246. package/dist/modules/shipment_tracking/setup.js.map +7 -0
  247. package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js +60 -0
  248. package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js.map +7 -0
  249. package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js +58 -0
  250. package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js.map +7 -0
  251. package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js +82 -0
  252. package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js.map +7 -0
  253. package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js +53 -0
  254. package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js.map +7 -0
  255. package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js +28 -0
  256. package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js.map +7 -0
  257. package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js +67 -0
  258. package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js.map +7 -0
  259. package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js +30 -0
  260. package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js.map +7 -0
  261. package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js +19 -0
  262. package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js.map +7 -0
  263. package/dist/modules/shipment_tracking/tables/index.js +6 -0
  264. package/dist/modules/shipment_tracking/tables/index.js.map +7 -0
  265. package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js +284 -0
  266. package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js.map +7 -0
  267. package/dist/modules/shipment_tracking/tables/tracking_job/table.js +36 -0
  268. package/dist/modules/shipment_tracking/tables/tracking_job/table.js.map +7 -0
  269. package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js +25 -0
  270. package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js.map +7 -0
  271. package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js +355 -0
  272. package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js.map +7 -0
  273. package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js +26 -0
  274. package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js.map +7 -0
  275. package/dist/types/assets.d.js +1 -0
  276. package/dist/types/assets.d.js.map +7 -0
  277. package/generated/entities/bic_config/index.ts +20 -0
  278. package/generated/entities/carrier_config/index.ts +23 -0
  279. package/generated/entities/location_override/index.ts +26 -0
  280. package/generated/entities/shipment/index.ts +46 -0
  281. package/generated/entities/ships_go_config/index.ts +25 -0
  282. package/generated/entities/tracking_event/index.ts +60 -0
  283. package/generated/entities/tracking_job/index.ts +33 -0
  284. package/generated/entities/webhook/index.ts +22 -0
  285. package/generated/entities/webhook_delivery/index.ts +22 -0
  286. package/generated/entities.ids.generated.ts +47 -0
  287. package/package.json +81 -0
  288. package/src/index.ts +39 -0
  289. package/src/modules/shipment_tracking/__integration__/TC-TRACK-410-shipsgo-config-api.spec.ts +133 -0
  290. package/src/modules/shipment_tracking/__integration__/meta.ts +4 -0
  291. package/src/modules/shipment_tracking/__tests__/empty-return-stop.test.ts +231 -0
  292. package/src/modules/shipment_tracking/__tests__/fixtures/hapag-lloyd-transship-container.json +702 -0
  293. package/src/modules/shipment_tracking/__tests__/fixtures/index.ts +434 -0
  294. package/src/modules/shipment_tracking/__tests__/fixtures/maersk-multi-transship-completed.json +1518 -0
  295. package/src/modules/shipment_tracking/__tests__/fixtures/maersk-transship-intransit.json +558 -0
  296. package/src/modules/shipment_tracking/__tests__/fixtures/msc-direct.json +222 -0
  297. package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi-transship-completed.json +974 -0
  298. package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi.json +4846 -0
  299. package/src/modules/shipment_tracking/__tests__/fixtures/msc-transship.json +409 -0
  300. package/src/modules/shipment_tracking/__tests__/fixtures/poi-events.json +240 -0
  301. package/src/modules/shipment_tracking/__tests__/fixtures/synthetic/index.ts +138 -0
  302. package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-eta-updates.json +288 -0
  303. package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-route-inference.json +201 -0
  304. package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-voyage-progression.json +410 -0
  305. package/src/modules/shipment_tracking/__tests__/shipsgo-air-mapper.test.ts +157 -0
  306. package/src/modules/shipment_tracking/__tests__/shipsgo-client.test.ts +155 -0
  307. package/src/modules/shipment_tracking/__tests__/shipsgo-mapper.test.ts +201 -0
  308. package/src/modules/shipment_tracking/__tests__/shipsgo-poll.test.ts +357 -0
  309. package/src/modules/shipment_tracking/__tests__/shipsgo-provider.test.ts +364 -0
  310. package/src/modules/shipment_tracking/__tests__/trackingService.integration.test.ts +2248 -0
  311. package/src/modules/shipment_tracking/__tests__/webhookPayload.test.ts +151 -0
  312. package/src/modules/shipment_tracking/acl.ts +18 -0
  313. package/src/modules/shipment_tracking/api/bic-configs/route.ts +206 -0
  314. package/src/modules/shipment_tracking/api/bic-configs/test/route.ts +108 -0
  315. package/src/modules/shipment_tracking/api/cargo-events/route.ts +101 -0
  316. package/src/modules/shipment_tracking/api/carrier-configs/route.ts +142 -0
  317. package/src/modules/shipment_tracking/api/dashboard/ships-map/route.ts +246 -0
  318. package/src/modules/shipment_tracking/api/location-overrides/route.ts +185 -0
  319. package/src/modules/shipment_tracking/api/openapi.ts +28 -0
  320. package/src/modules/shipment_tracking/api/shipments/route.ts +197 -0
  321. package/src/modules/shipment_tracking/api/shipsgo-configs/route.ts +215 -0
  322. package/src/modules/shipment_tracking/api/tracking-jobs/route.ts +149 -0
  323. package/src/modules/shipment_tracking/api/tracking-jobs/sync/route.ts +116 -0
  324. package/src/modules/shipment_tracking/api/utils.ts +10 -0
  325. package/src/modules/shipment_tracking/api/webhooks/deliveries/route.ts +82 -0
  326. package/src/modules/shipment_tracking/api/webhooks/route.ts +138 -0
  327. package/src/modules/shipment_tracking/api/webhooks/test/route.ts +84 -0
  328. package/src/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
  329. package/src/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
  330. package/src/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
  331. package/src/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
  332. package/src/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
  333. package/src/modules/shipment_tracking/assets/carriers/msc.png +0 -0
  334. package/src/modules/shipment_tracking/assets/carriers/one.png +0 -0
  335. package/src/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
  336. package/src/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
  337. package/src/modules/shipment_tracking/assets/carriers/zim.png +0 -0
  338. package/src/modules/shipment_tracking/assets/index.ts +66 -0
  339. package/src/modules/shipment_tracking/backend/carrier-configs/page.meta.ts +16 -0
  340. package/src/modules/shipment_tracking/backend/carrier-configs/page.tsx +782 -0
  341. package/src/modules/shipment_tracking/backend/location-overrides/page.meta.ts +16 -0
  342. package/src/modules/shipment_tracking/backend/location-overrides/page.tsx +241 -0
  343. package/src/modules/shipment_tracking/backend/shipment-tracking/page.meta.ts +16 -0
  344. package/src/modules/shipment_tracking/backend/shipment-tracking/page.tsx +340 -0
  345. package/src/modules/shipment_tracking/backend/tracking-jobs/page.meta.ts +16 -0
  346. package/src/modules/shipment_tracking/backend/tracking-jobs/page.tsx +16 -0
  347. package/src/modules/shipment_tracking/backend/webhooks/page.meta.ts +16 -0
  348. package/src/modules/shipment_tracking/backend/webhooks/page.tsx +228 -0
  349. package/src/modules/shipment_tracking/ce.ts +48 -0
  350. package/src/modules/shipment_tracking/cli.ts +321 -0
  351. package/src/modules/shipment_tracking/commands/bic-configs.ts +94 -0
  352. package/src/modules/shipment_tracking/commands/carrier-configs.ts +89 -0
  353. package/src/modules/shipment_tracking/commands/index.ts +8 -0
  354. package/src/modules/shipment_tracking/commands/location-overrides.ts +119 -0
  355. package/src/modules/shipment_tracking/commands/shipments.ts +247 -0
  356. package/src/modules/shipment_tracking/commands/shipsgo-configs.ts +101 -0
  357. package/src/modules/shipment_tracking/commands/tracking-jobs.ts +158 -0
  358. package/src/modules/shipment_tracking/commands/tracking.ts +230 -0
  359. package/src/modules/shipment_tracking/commands/webhooks.ts +159 -0
  360. package/src/modules/shipment_tracking/components/CombinedTimestampCell.tsx +236 -0
  361. package/src/modules/shipment_tracking/components/FacilityLocationInput.tsx +235 -0
  362. package/src/modules/shipment_tracking/components/LocationOverrideDrawer.tsx +586 -0
  363. package/src/modules/shipment_tracking/components/ShipmentDetailsDrawer.tsx +1364 -0
  364. package/src/modules/shipment_tracking/components/ShipmentDrawer.tsx +498 -0
  365. package/src/modules/shipment_tracking/components/ShipsGoConfigSection.tsx +266 -0
  366. package/src/modules/shipment_tracking/components/TimestampCell.tsx +146 -0
  367. package/src/modules/shipment_tracking/components/TrackingJobDrawer.tsx +467 -0
  368. package/src/modules/shipment_tracking/components/VesselTrackingMap.tsx +761 -0
  369. package/src/modules/shipment_tracking/components/WebhookDrawer.tsx +434 -0
  370. package/src/modules/shipment_tracking/data/entities.ts +748 -0
  371. package/src/modules/shipment_tracking/data/validators.ts +523 -0
  372. package/src/modules/shipment_tracking/di.ts +73 -0
  373. package/src/modules/shipment_tracking/encryption.ts +17 -0
  374. package/src/modules/shipment_tracking/events.ts +122 -0
  375. package/src/modules/shipment_tracking/hooks/useVesselTracking.ts +297 -0
  376. package/src/modules/shipment_tracking/i18n/de.json +327 -0
  377. package/src/modules/shipment_tracking/i18n/en.json +327 -0
  378. package/src/modules/shipment_tracking/i18n/es.json +327 -0
  379. package/src/modules/shipment_tracking/i18n/pl.json +327 -0
  380. package/src/modules/shipment_tracking/index.ts +13 -0
  381. package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-mapping.test.ts +385 -0
  382. package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-parser.test.ts +398 -0
  383. package/src/modules/shipment_tracking/lib/__tests__/empty-return.test.ts +174 -0
  384. package/src/modules/shipment_tracking/lib/__tests__/poi-event-processor.test.ts +699 -0
  385. package/src/modules/shipment_tracking/lib/__tests__/route-inference.test.ts +402 -0
  386. package/src/modules/shipment_tracking/lib/__tests__/status-machine.test.ts +447 -0
  387. package/src/modules/shipment_tracking/lib/__tests__/time-extraction.test.ts +331 -0
  388. package/src/modules/shipment_tracking/lib/__tests__/timestamp-utils.test.ts +260 -0
  389. package/src/modules/shipment_tracking/lib/__tests__/vessel-api.test.ts +125 -0
  390. package/src/modules/shipment_tracking/lib/__tests__/vessel-extraction.test.ts +108 -0
  391. package/src/modules/shipment_tracking/lib/adapters/cma-cgm.ts +84 -0
  392. package/src/modules/shipment_tracking/lib/adapters/cosco-types.ts +145 -0
  393. package/src/modules/shipment_tracking/lib/adapters/cosco.ts +314 -0
  394. package/src/modules/shipment_tracking/lib/adapters/evergreen.ts +107 -0
  395. package/src/modules/shipment_tracking/lib/adapters/hapag-lloyd.ts +88 -0
  396. package/src/modules/shipment_tracking/lib/adapters/index.ts +18 -0
  397. package/src/modules/shipment_tracking/lib/adapters/maersk.ts +104 -0
  398. package/src/modules/shipment_tracking/lib/adapters/msc.ts +223 -0
  399. package/src/modules/shipment_tracking/lib/adapters/zim.ts +106 -0
  400. package/src/modules/shipment_tracking/lib/audit-helpers.ts +29 -0
  401. package/src/modules/shipment_tracking/lib/auth/base64url.ts +4 -0
  402. package/src/modules/shipment_tracking/lib/auth/oauth-client.ts +53 -0
  403. package/src/modules/shipment_tracking/lib/background.ts +24 -0
  404. package/src/modules/shipment_tracking/lib/bic-api-client.ts +199 -0
  405. package/src/modules/shipment_tracking/lib/carrier-adapter.ts +136 -0
  406. package/src/modules/shipment_tracking/lib/current-vessel.ts +274 -0
  407. package/src/modules/shipment_tracking/lib/dcsa-event-mapping.ts +202 -0
  408. package/src/modules/shipment_tracking/lib/dcsa-event-parser.ts +163 -0
  409. package/src/modules/shipment_tracking/lib/dcsa-params.ts +14 -0
  410. package/src/modules/shipment_tracking/lib/empty-return.ts +122 -0
  411. package/src/modules/shipment_tracking/lib/location-overrides.ts +150 -0
  412. package/src/modules/shipment_tracking/lib/location-types.ts +154 -0
  413. package/src/modules/shipment_tracking/lib/logger.ts +195 -0
  414. package/src/modules/shipment_tracking/lib/poi-event-processor.ts +302 -0
  415. package/src/modules/shipment_tracking/lib/poi-nats-config.ts +134 -0
  416. package/src/modules/shipment_tracking/lib/poi-types.ts +198 -0
  417. package/src/modules/shipment_tracking/lib/rate-limiter.ts +58 -0
  418. package/src/modules/shipment_tracking/lib/route-extraction.ts +439 -0
  419. package/src/modules/shipment_tracking/lib/route-inference.ts +238 -0
  420. package/src/modules/shipment_tracking/lib/schedule-generator.ts +92 -0
  421. package/src/modules/shipment_tracking/lib/shipsgo-client.ts +350 -0
  422. package/src/modules/shipment_tracking/lib/shipsgo-mapper.ts +296 -0
  423. package/src/modules/shipment_tracking/lib/status-machine.ts +230 -0
  424. package/src/modules/shipment_tracking/lib/time-extraction.ts +85 -0
  425. package/src/modules/shipment_tracking/lib/timestamp-utils.ts +244 -0
  426. package/src/modules/shipment_tracking/lib/vessel-api.ts +299 -0
  427. package/src/modules/shipment_tracking/lib/vessel-cache.ts +102 -0
  428. package/src/modules/shipment_tracking/lib/vessel-extraction.ts +39 -0
  429. package/src/modules/shipment_tracking/lib/webhook-dispatcher.ts +93 -0
  430. package/src/modules/shipment_tracking/migrations/.snapshot-open-mercato.json +2914 -0
  431. package/src/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.ts +60 -0
  432. package/src/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.ts +22 -0
  433. package/src/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.ts +65 -0
  434. package/src/modules/shipment_tracking/notifications.ts +151 -0
  435. package/src/modules/shipment_tracking/services/carrierRegistry.ts +29 -0
  436. package/src/modules/shipment_tracking/services/poiNatsService.ts +526 -0
  437. package/src/modules/shipment_tracking/services/shipsGoProvider.ts +231 -0
  438. package/src/modules/shipment_tracking/services/trackingService.ts +1777 -0
  439. package/src/modules/shipment_tracking/services/webhookService.ts +347 -0
  440. package/src/modules/shipment_tracking/setup.ts +99 -0
  441. package/src/modules/shipment_tracking/subscribers/__tests__/terminal-event-ingest.test.ts +141 -0
  442. package/src/modules/shipment_tracking/subscribers/cargo-event-created.ts +115 -0
  443. package/src/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.ts +115 -0
  444. package/src/modules/shipment_tracking/subscribers/poi-event-notification.ts +144 -0
  445. package/src/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.ts +94 -0
  446. package/src/modules/shipment_tracking/subscribers/shipment-status-changed.ts +41 -0
  447. package/src/modules/shipment_tracking/subscribers/terminal-event-ingest.ts +109 -0
  448. package/src/modules/shipment_tracking/subscribers/tracking-job-initial-poll.ts +53 -0
  449. package/src/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.ts +50 -0
  450. package/src/modules/shipment_tracking/tables/index.ts +4 -0
  451. package/src/modules/shipment_tracking/tables/tracking_job/table.client.tsx +337 -0
  452. package/src/modules/shipment_tracking/tables/tracking_job/table.ts +34 -0
  453. package/src/modules/shipment_tracking/widgets/dashboard/ships-map/config.ts +32 -0
  454. package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.tsx +448 -0
  455. package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.ts +25 -0
  456. package/src/types/assets.d.ts +5 -0
@@ -0,0 +1,761 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * VesselTrackingMap - Interactive map showing vessel position and trace.
5
+ *
6
+ * Features:
7
+ * - Real-time vessel position with heading-rotated ship icon
8
+ * - Trace polyline limited to viewport bounds (debounced fetch)
9
+ * - Vessel info overlay with status and destination
10
+ * - Auto-refresh vessel position every 30 seconds
11
+ */
12
+
13
+ import * as React from 'react'
14
+ import { useState, useCallback, useMemo, useEffect } from 'react'
15
+ import { GoogleMap, useJsApiLoader, MarkerF, PolylineF, CircleF, InfoWindowF } from '@react-google-maps/api'
16
+ import { Ship, Navigation, Anchor, MapPin, Clock, Building2, Ruler, Info, X } from 'lucide-react'
17
+ import { useVesselTracking } from '../hooks/useVesselTracking'
18
+ import { Spinner } from '@freighttech/ui/primitives/spinner'
19
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
20
+ import type { VesselTrackingStatus } from '../lib/current-vessel'
21
+ import type { VesselInfo, PoiInfo, PoiType } from '../lib/vessel-api'
22
+
23
+ // ─── Types ───────────────────────────────────────────────────
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ type GoogleMapInstance = any
27
+
28
+ export interface VesselTrackingMapProps {
29
+ /** Vessel IMO number */
30
+ vesselImo: string | null | undefined
31
+ /** Vessel name (fallback if API doesn't return name) */
32
+ vesselName?: string | null
33
+ /** Map height (CSS value, default: '250px') */
34
+ height?: string
35
+ /** Auto-refresh vessel position (default: true) */
36
+ autoRefresh?: boolean
37
+ /** Vessel refresh interval in ms (default: 30000) */
38
+ refreshInterval?: number
39
+ /** Show vessel info overlay (default: true) */
40
+ showInfoOverlay?: boolean
41
+ /** Show points of interest (ports/terminals/waypoints) as clickable circles (default: true) */
42
+ showPois?: boolean
43
+ /** Container tracking status - affects display messaging */
44
+ containerStatus?: VesselTrackingStatus
45
+ /** Port where container currently is (for at_port/not_departed status) */
46
+ currentPort?: string
47
+ /** Whether this is a planned vessel (not yet carrying the container) */
48
+ isPlannedVessel?: boolean
49
+ /** Current leg info (for in_transit status) */
50
+ currentLeg?: {
51
+ fromPort: string
52
+ toPort: string
53
+ }
54
+ /** Start date for trace (ISO 8601) - limits trace to current leg */
55
+ traceFrom?: string
56
+ }
57
+
58
+ // ─── Map Configuration ───────────────────────────────────────
59
+
60
+ const containerStyle: React.CSSProperties = {
61
+ width: '100%',
62
+ height: '100%',
63
+ }
64
+
65
+ // Map options - typed as Record to avoid google namespace dependency at compile time
66
+ const mapOptions: Record<string, unknown> = {
67
+ zoomControl: true,
68
+ mapTypeControl: false,
69
+ scaleControl: false,
70
+ streetViewControl: false,
71
+ rotateControl: false,
72
+ fullscreenControl: false,
73
+ disableDefaultUI: false,
74
+ gestureHandling: 'cooperative',
75
+ styles: [
76
+ {
77
+ featureType: 'water',
78
+ elementType: 'geometry',
79
+ stylers: [{ color: '#bfdbfe' }], // Light blue for water
80
+ },
81
+ {
82
+ featureType: 'landscape',
83
+ elementType: 'geometry',
84
+ stylers: [{ color: '#f5f3ff' }], // Light purple for land
85
+ },
86
+ {
87
+ featureType: 'road',
88
+ elementType: 'geometry',
89
+ stylers: [{ color: '#e0e7ff' }], // Light indigo for roads
90
+ },
91
+ ],
92
+ }
93
+
94
+ // Default center: Baltic Sea
95
+ const defaultCenter = { lat: 54.5, lng: 18.5 }
96
+
97
+ // Below this zoom the viewport is too large — too many POIs to be useful or performant,
98
+ // so we skip fetching/drawing them entirely (keeps the map uncluttered when zoomed out).
99
+ const MIN_POI_ZOOM = 6
100
+
101
+ // Subtle per-type colors for POI circles. Kept faint (low opacity) so they don't
102
+ // compete with the vessel marker and trace.
103
+ const POI_COLORS: Record<PoiType, string> = {
104
+ PORT: '#2563eb', // blue
105
+ TERMINAL: '#059669', // emerald
106
+ WAYPOINT: '#d97706', // amber
107
+ PORT_GEOMETRIC_CENTER: '#64748b', // slate
108
+ }
109
+
110
+ // ─── Helpers ─────────────────────────────────────────────────
111
+
112
+ /**
113
+ * Resolve a POI's human-friendly primary name, following the same per-type fallback the
114
+ * backend uses: terminals prefer the terminal name, ports prefer the city, waypoints use
115
+ * the localized maritime region. Falls back to LOCODE / code when nothing else is set.
116
+ */
117
+ function getPoiPrimaryName(poi: PoiInfo, locale: string): string {
118
+ if (poi.type === 'TERMINAL') {
119
+ return poi.terminalName || poi.portName || poi.city || poi.locode || poi.code
120
+ }
121
+ if (poi.type === 'PORT' || poi.type === 'PORT_GEOMETRIC_CENTER') {
122
+ return poi.city || poi.portName || poi.locode || poi.code
123
+ }
124
+ // WAYPOINT — localized region name
125
+ const region = locale === 'pl'
126
+ ? (poi.regionNamePl || poi.regionNameEn)
127
+ : (poi.regionNameEn || poi.regionNamePl)
128
+ return region || poi.locode || poi.code
129
+ }
130
+
131
+ function getPoiTypeLabel(type: PoiType, t: ReturnType<typeof useT>): string {
132
+ switch (type) {
133
+ case 'PORT':
134
+ return t('shipment_tracking.map.poi.port', 'Port')
135
+ case 'TERMINAL':
136
+ return t('shipment_tracking.map.poi.terminal', 'Terminal')
137
+ case 'WAYPOINT':
138
+ return t('shipment_tracking.map.poi.waypoint', 'Waypoint')
139
+ case 'PORT_GEOMETRIC_CENTER':
140
+ return t('shipment_tracking.map.poi.portCenter', 'Port center')
141
+ default:
142
+ return type
143
+ }
144
+ }
145
+
146
+
147
+ /**
148
+ * Calculate vessel dimensions from AIS dimension fields.
149
+ * a = bow to reference, b = reference to stern → length = a + b
150
+ * c = port to reference, d = reference to starboard → width = c + d
151
+ */
152
+ function calculateVesselDimensions(vessel: VesselInfo | null | undefined): {
153
+ length: number
154
+ width: number
155
+ hasValidDimensions: boolean
156
+ } {
157
+ const dims = vessel?.dimensions
158
+ if (!dims) {
159
+ return { length: 0, width: 0, hasValidDimensions: false }
160
+ }
161
+ const length = (dims.a ?? 0) + (dims.b ?? 0)
162
+ const width = (dims.c ?? 0) + (dims.d ?? 0)
163
+ // Only consider valid if both length and width are positive
164
+ const hasValidDimensions = length > 0 && width > 0
165
+ return { length, width, hasValidDimensions }
166
+ }
167
+
168
+ /**
169
+ * Format relative time for last update (e.g., "2 min ago", "1 hour ago").
170
+ */
171
+ function formatRelativeTime(dateString: string | null | undefined, t: ReturnType<typeof useT>): string {
172
+ if (!dateString) return ''
173
+ const date = new Date(dateString)
174
+ if (Number.isNaN(date.getTime())) return ''
175
+
176
+ const now = new Date()
177
+ const diffMs = now.getTime() - date.getTime()
178
+ const diffMinutes = Math.floor(diffMs / 60000)
179
+ const diffHours = Math.floor(diffMinutes / 60)
180
+ const diffDays = Math.floor(diffHours / 24)
181
+
182
+ if (diffMinutes < 1) {
183
+ return t('shipment_tracking.map.justNow', 'Just now')
184
+ }
185
+ if (diffMinutes < 60) {
186
+ return t('shipment_tracking.map.minutesAgo', '{{count}} min ago', { count: diffMinutes })
187
+ }
188
+ if (diffHours < 24) {
189
+ return t('shipment_tracking.map.hoursAgo', '{{count}}h ago', { count: diffHours })
190
+ }
191
+ return t('shipment_tracking.map.daysAgo', '{{count}}d ago', { count: diffDays })
192
+ }
193
+
194
+ // ─── Vessel Info Card ────────────────────────────────────────
195
+
196
+ interface VesselInfoCardProps {
197
+ vessel: VesselInfo | null | undefined
198
+ displayName: string
199
+ isPlannedVessel: boolean
200
+ containerStatus?: VesselTrackingStatus
201
+ currentPort?: string
202
+ currentLeg?: { fromPort: string; toPort: string }
203
+ t: ReturnType<typeof useT>
204
+ }
205
+
206
+ function VesselInfoCard({
207
+ vessel,
208
+ displayName,
209
+ isPlannedVessel,
210
+ containerStatus,
211
+ currentPort,
212
+ currentLeg,
213
+ t,
214
+ }: VesselInfoCardProps) {
215
+ const { length, width, hasValidDimensions } = calculateVesselDimensions(vessel)
216
+ const lastUpdate = formatRelativeTime(vessel?.updatedAt, t)
217
+
218
+ // Determine vessel status display
219
+ const getVesselStatusDisplay = () => {
220
+ if (vessel?.status === 'IN_PORT') {
221
+ return {
222
+ icon: <Anchor className="w-3 h-3 text-green-600 dark:text-green-400" />,
223
+ text: vessel.currentPort?.name || t('shipment_tracking.map.inPort', 'In Port'),
224
+ color: 'text-green-600 dark:text-green-400',
225
+ }
226
+ }
227
+ if (vessel?.status === 'AT_SEA') {
228
+ const destination = vessel.lastDestinationPort?.name || vessel.lastDestination
229
+ return {
230
+ icon: <Navigation className="w-3 h-3 text-blue-600 dark:text-blue-400" />,
231
+ text: destination
232
+ ? t('shipment_tracking.map.atSeaTo', 'At Sea -> {{destination}}', { destination })
233
+ : t('shipment_tracking.map.atSea', 'At Sea'),
234
+ color: 'text-blue-600 dark:text-blue-400',
235
+ }
236
+ }
237
+ return {
238
+ icon: <Info className="w-3 h-3 text-muted-foreground" />,
239
+ text: t('shipment_tracking.map.unknown', 'Unknown'),
240
+ color: 'text-muted-foreground',
241
+ }
242
+ }
243
+
244
+ const statusDisplay = getVesselStatusDisplay()
245
+
246
+ return (
247
+ <div className="bg-card border border-border rounded-l-lg border-r-0 px-3 py-2.5 h-full overflow-y-auto">
248
+ {/* Header: Vessel name + planned badge */}
249
+ <div className="flex items-center justify-between gap-2 mb-1">
250
+ <div className="flex items-center gap-1.5 min-w-0">
251
+ <Ship className="w-4 h-4 text-blue-500 dark:text-blue-400 flex-shrink-0" />
252
+ <h2 className="font-semibold text-sm truncate text-foreground">{displayName}</h2>
253
+ </div>
254
+ {isPlannedVessel && (
255
+ <span className="text-[10px] px-1.5 py-0.5 bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 rounded flex-shrink-0">
256
+ {t('shipment_tracking.map.planned', 'Planned')}
257
+ </span>
258
+ )}
259
+ </div>
260
+
261
+ {/* IMO number */}
262
+ {vessel?.imo && (
263
+ <div className="text-xs text-muted-foreground font-mono mb-2">
264
+ IMO {vessel.imo}
265
+ </div>
266
+ )}
267
+
268
+ {/* Vessel details section */}
269
+ <div className="border-t border-border pt-2 space-y-1.5">
270
+ {/* Status row */}
271
+ <div className="flex items-center gap-2 text-xs">
272
+ <span className="text-muted-foreground/70 w-14 flex-shrink-0 truncate" title={t('shipment_tracking.map.status', 'Status')}>
273
+ {t('shipment_tracking.map.status', 'Status')}
274
+ </span>
275
+ <div className={`flex items-center gap-1 ${statusDisplay.color} truncate`}>
276
+ {statusDisplay.icon}
277
+ <span className="truncate">{statusDisplay.text}</span>
278
+ </div>
279
+ </div>
280
+
281
+ {/* Ship type row */}
282
+ {vessel?.shipType && (
283
+ <div className="flex items-center gap-2 text-xs">
284
+ <span className="text-muted-foreground/70 w-14 flex-shrink-0 truncate" title={t('shipment_tracking.map.type', 'Type')}>
285
+ {t('shipment_tracking.map.type', 'Type')}
286
+ </span>
287
+ <span className="text-foreground truncate" title={vessel.shipType}>{vessel.shipType}</span>
288
+ </div>
289
+ )}
290
+
291
+ {/* Size row */}
292
+ {hasValidDimensions && (
293
+ <div className="flex items-center gap-2 text-xs">
294
+ <span className="text-muted-foreground/70 w-14 flex-shrink-0 truncate" title={t('shipment_tracking.map.size', 'Size')}>
295
+ {t('shipment_tracking.map.size', 'Size')}
296
+ </span>
297
+ <div className="flex items-center gap-1 text-foreground">
298
+ <Ruler className="w-3 h-3 text-muted-foreground" />
299
+ <span>{length}m x {width}m</span>
300
+ </div>
301
+ </div>
302
+ )}
303
+
304
+ {/* Operator row */}
305
+ {vessel?.operatorName && (
306
+ <div className="flex items-center gap-2 text-xs">
307
+ <span className="text-muted-foreground/70 w-14 flex-shrink-0 truncate" title={t('shipment_tracking.map.operator', 'Operator')}>
308
+ {t('shipment_tracking.map.operator', 'Operator')}
309
+ </span>
310
+ <div className="flex items-center gap-1 text-foreground truncate" title={vessel.operatorName}>
311
+ <Building2 className="w-3 h-3 text-muted-foreground flex-shrink-0" />
312
+ <span className="truncate">{vessel.operatorName}</span>
313
+ </div>
314
+ </div>
315
+ )}
316
+
317
+ {/* Last update row */}
318
+ {lastUpdate && (
319
+ <div className="flex items-center gap-2 text-xs">
320
+ <span className="text-muted-foreground/70 w-14 flex-shrink-0 truncate" title={t('shipment_tracking.map.updated', 'Updated')}>
321
+ {t('shipment_tracking.map.updated', 'Updated')}
322
+ </span>
323
+ <div className="flex items-center gap-1 text-muted-foreground">
324
+ <Clock className="w-3 h-3 text-muted-foreground" />
325
+ <span>{lastUpdate}</span>
326
+ </div>
327
+ </div>
328
+ )}
329
+ </div>
330
+
331
+ {/* Container status section (when container tracking is active) */}
332
+ {containerStatus && containerStatus !== 'delivered' && (
333
+ <div className="border-t border-border mt-2 pt-2">
334
+ {containerStatus === 'in_transit' && currentLeg ? (
335
+ <div className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400">
336
+ <MapPin className="w-3 h-3" />
337
+ <span className="truncate">
338
+ {t('shipment_tracking.map.inTransitTo', 'In transit to {{destination}}', {
339
+ destination: currentLeg.toPort,
340
+ })}
341
+ </span>
342
+ </div>
343
+ ) : containerStatus === 'at_port' && currentPort ? (
344
+ <div className="space-y-0.5">
345
+ <div className="flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
346
+ <MapPin className="w-3 h-3" />
347
+ <span className="truncate">
348
+ {t('shipment_tracking.map.containerAtPort', 'Container at {{port}}', {
349
+ port: currentPort,
350
+ })}
351
+ </span>
352
+ </div>
353
+ {isPlannedVessel && (
354
+ <div className="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400">
355
+ <Clock className="w-3 h-3" />
356
+ <span className="truncate">
357
+ {t('shipment_tracking.map.awaitingVessel', 'Awaiting this vessel')}
358
+ </span>
359
+ </div>
360
+ )}
361
+ </div>
362
+ ) : containerStatus === 'not_departed' ? (
363
+ <div className="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400">
364
+ <Clock className="w-3 h-3" />
365
+ <span className="truncate">
366
+ {t('shipment_tracking.map.awaitingDeparture', 'Awaiting departure')}
367
+ </span>
368
+ </div>
369
+ ) : null}
370
+ </div>
371
+ )}
372
+ </div>
373
+ )
374
+ }
375
+
376
+ // ─── Main Component ──────────────────────────────────────────
377
+
378
+ export function VesselTrackingMap({
379
+ vesselImo,
380
+ vesselName,
381
+ height = '250px',
382
+ autoRefresh = true,
383
+ refreshInterval = 30_000,
384
+ showInfoOverlay = true,
385
+ showPois = true,
386
+ containerStatus,
387
+ currentPort,
388
+ isPlannedVessel = false,
389
+ currentLeg,
390
+ traceFrom,
391
+ }: VesselTrackingMapProps) {
392
+ const t = useT()
393
+ const locale = useLocale()
394
+
395
+ // Load Google Maps API
396
+ const { isLoaded, loadError } = useJsApiLoader({
397
+ id: 'google-map-script',
398
+ googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY || '',
399
+ })
400
+
401
+ // Map instance reference
402
+ const [map, setMap] = useState<GoogleMapInstance | null>(null)
403
+ // Current zoom (gates POI fetching) and the POI selected for the info window
404
+ const [zoom, setZoom] = useState(8)
405
+ const [selectedPoi, setSelectedPoi] = useState<PoiInfo | null>(null)
406
+
407
+ // Only fetch POIs once zoomed in enough to keep the count and clutter manageable
408
+ const enablePois = showPois && zoom >= MIN_POI_ZOOM
409
+
410
+ // Drop the details popup when POIs are hidden (e.g. zoomed out) to avoid an orphan window
411
+ useEffect(() => {
412
+ if (!enablePois) setSelectedPoi(null)
413
+ }, [enablePois])
414
+
415
+ // Vessel tracking with debounced bounds
416
+ const {
417
+ vessel,
418
+ isVesselLoading,
419
+ vesselError,
420
+ trace,
421
+ isTraceLoading,
422
+ isDebouncing,
423
+ setBounds,
424
+ pois,
425
+ } = useVesselTracking(vesselImo, {
426
+ autoRefresh,
427
+ vesselRefreshInterval: refreshInterval,
428
+ boundsDebounceDelay: 500,
429
+ traceLimit: 5000,
430
+ traceFrom,
431
+ enablePois,
432
+ })
433
+
434
+ // Handle map load
435
+ const onMapLoad = useCallback((mapInstance: GoogleMapInstance) => {
436
+ setMap(mapInstance)
437
+ }, [])
438
+
439
+ // Handle map idle (fires after load and after pan/zoom completes)
440
+ const handleMapIdle = useCallback(() => {
441
+ if (!map) return
442
+ const bounds = map.getBounds()
443
+ if (!bounds) return
444
+
445
+ // Track zoom so POI fetching can be gated on it
446
+ const currentZoom = map.getZoom()
447
+ if (typeof currentZoom === 'number') setZoom(currentZoom)
448
+
449
+ const ne = bounds.getNorthEast()
450
+ const sw = bounds.getSouthWest()
451
+
452
+ // Add fixed margin (~30-35km) to fetch trace points outside visible viewport
453
+ // This ensures smooth curves at map edges instead of straight lines
454
+ const MARGIN_DEGREES = 0.3
455
+
456
+ setBounds({
457
+ north: Math.min(90, ne.lat() + MARGIN_DEGREES),
458
+ south: Math.max(-90, sw.lat() - MARGIN_DEGREES),
459
+ east: Math.min(180, ne.lng() + MARGIN_DEGREES),
460
+ west: Math.max(-180, sw.lng() - MARGIN_DEGREES),
461
+ })
462
+ }, [map, setBounds])
463
+
464
+ // Create container ship icon with heading rotation
465
+ // Ship silhouette viewed from above: pointed bow (top), wide stern (bottom), bridge at rear
466
+ const shipIcon = useMemo(() => {
467
+ if (!isLoaded || typeof window === 'undefined') return undefined
468
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
469
+ const google = (window as any).google
470
+ if (!google) return undefined
471
+
472
+ const heading = vessel?.lastHeading ?? 0
473
+
474
+ // Container ship silhouette (top-down view, pointing up/north)
475
+ // Elongated shape with slightly pointed bow - typical container ship proportions
476
+ // Features: tapered bow (not too sharp), narrow hull, bridge at stern
477
+ const containerShipPath = `
478
+ M 12 2
479
+ L 8 7
480
+ L 8 30
481
+ Q 8 32, 10 32
482
+ L 10 34
483
+ L 14 34
484
+ L 14 32
485
+ Q 16 32, 16 30
486
+ L 16 7
487
+ L 12 2
488
+ Z
489
+ M 10 32
490
+ L 10 36
491
+ L 14 36
492
+ L 14 32
493
+ Z
494
+ `
495
+
496
+ return {
497
+ path: containerShipPath,
498
+ fillColor: '#0f172a', // Dark slate hull
499
+ fillOpacity: 0.95,
500
+ strokeColor: '#ffffff',
501
+ strokeWeight: 1.5,
502
+ scale: 1.0,
503
+ rotation: heading,
504
+ anchor: new google.maps.Point(12, 18), // Center point for rotation
505
+ }
506
+ }, [isLoaded, vessel?.lastHeading])
507
+
508
+ // Factory for the small clickable POI center dot. Markers live in a higher map pane than
509
+ // circles, so these stay above the translucent circles and are the precise click target —
510
+ // the circles themselves are non-clickable, so overlapping circles never steal a click.
511
+ // Pixel-scaled (not meters), so the dot stays a consistent small size at any zoom.
512
+ const poiDotIcon = useMemo(() => {
513
+ if (!isLoaded || typeof window === 'undefined') return null
514
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
515
+ const google = (window as any).google
516
+ if (!google) return null
517
+ return (color: string, selected: boolean) => ({
518
+ path: google.maps.SymbolPath.CIRCLE,
519
+ scale: selected ? 6 : 4,
520
+ fillColor: color,
521
+ fillOpacity: 0.95,
522
+ strokeColor: '#ffffff',
523
+ strokeWeight: 1.5,
524
+ })
525
+ }, [isLoaded])
526
+
527
+ // Options for the POI info window: drop Google's default header row (the empty top bar +
528
+ // default close button that crowds the content), nudge the bubble just above the center
529
+ // dot, and cap its width. We render our own close button instead.
530
+ const infoWindowOptions = useMemo(() => {
531
+ if (!isLoaded || typeof window === 'undefined') return undefined
532
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
533
+ const google = (window as any).google
534
+ if (!google) return undefined
535
+ return {
536
+ headerDisabled: true,
537
+ pixelOffset: new google.maps.Size(0, -4),
538
+ maxWidth: 260,
539
+ }
540
+ }, [isLoaded])
541
+
542
+ // Center map on vessel position
543
+ const center = useMemo(() => {
544
+ if (vessel?.lastPosition) {
545
+ return { lat: vessel.lastPosition.lat, lng: vessel.lastPosition.lng }
546
+ }
547
+ return defaultCenter
548
+ }, [vessel?.lastPosition])
549
+
550
+ // Filter trace points to only position updates for cleaner polyline
551
+ const tracePath = useMemo(() => {
552
+ if (!trace?.trace) return []
553
+ return trace.trace
554
+ .filter((p) => p.lat && p.lng && p.eventType === 'POSITION_UPDATE')
555
+ .map((p) => ({ lat: p.lat, lng: p.lng }))
556
+ }, [trace?.trace])
557
+
558
+ // ─── Loading State ─────────────────────────────────────────
559
+
560
+ if (loadError) {
561
+ return (
562
+ <div className="flex w-full" style={{ height }}>
563
+ <div className="w-full flex items-center justify-center bg-muted rounded-lg border border-border">
564
+ <div className="text-center">
565
+ <Ship className="w-8 h-8 text-muted-foreground mx-auto mb-2" />
566
+ <p className="text-sm text-muted-foreground">
567
+ {t('shipment_tracking.map.loadError', 'Failed to load map')}
568
+ </p>
569
+ </div>
570
+ </div>
571
+ </div>
572
+ )
573
+ }
574
+
575
+ if (!isLoaded || (isVesselLoading && !vessel)) {
576
+ return (
577
+ <div className="flex w-full" style={{ height }}>
578
+ <div className="w-full flex items-center justify-center bg-muted rounded-lg border border-border">
579
+ <div className="flex flex-col items-center gap-2">
580
+ <Spinner className="h-6 w-6" />
581
+ <span className="text-sm text-muted-foreground">
582
+ {t('shipment_tracking.map.loading', 'Loading map...')}
583
+ </span>
584
+ </div>
585
+ </div>
586
+ </div>
587
+ )
588
+ }
589
+
590
+ // ─── No Vessel Data ────────────────────────────────────────
591
+
592
+ if (!vessel && !isVesselLoading) {
593
+ return (
594
+ <div className="flex w-full" style={{ height }}>
595
+ <div className="w-full flex items-center justify-center bg-muted rounded-lg border border-border">
596
+ <div className="text-center">
597
+ <Ship className="w-8 h-8 text-muted-foreground mx-auto mb-2" />
598
+ <p className="text-sm text-muted-foreground">
599
+ {vesselError
600
+ ? t('shipment_tracking.map.vesselError', 'Failed to load vessel data')
601
+ : t('shipment_tracking.map.vesselNotFound', 'Vessel not found')}
602
+ </p>
603
+ </div>
604
+ </div>
605
+ </div>
606
+ )
607
+ }
608
+
609
+ // ─── Map Render ────────────────────────────────────────────
610
+
611
+ const displayName = vesselName || vessel?.name || 'Unknown Vessel'
612
+
613
+ return (
614
+ <div className="flex w-full" style={{ height }}>
615
+ {/* Vessel Info Card - 1/3 width */}
616
+ {showInfoOverlay && (
617
+ <div className="w-1/3 flex-shrink-0">
618
+ <VesselInfoCard
619
+ vessel={vessel}
620
+ displayName={displayName}
621
+ isPlannedVessel={isPlannedVessel}
622
+ containerStatus={containerStatus}
623
+ currentPort={currentPort}
624
+ currentLeg={currentLeg}
625
+ t={t}
626
+ />
627
+ </div>
628
+ )}
629
+
630
+ {/* Map - 2/3 width (or full if no info overlay) */}
631
+ <div className={`relative border border-border overflow-hidden ${showInfoOverlay ? 'w-2/3 rounded-r-lg' : 'w-full rounded-lg'}`}>
632
+ <GoogleMap
633
+ mapContainerStyle={containerStyle}
634
+ center={center}
635
+ zoom={8}
636
+ onLoad={onMapLoad}
637
+ onIdle={handleMapIdle}
638
+ options={mapOptions}
639
+ >
640
+ {/* Vessel Marker */}
641
+ {vessel?.lastPosition && (
642
+ <MarkerF
643
+ position={{ lat: vessel.lastPosition.lat, lng: vessel.lastPosition.lng }}
644
+ icon={shipIcon}
645
+ title={displayName}
646
+ zIndex={2000}
647
+ />
648
+ )}
649
+
650
+ {/* Trace Polyline */}
651
+ {tracePath.length > 1 && (
652
+ <PolylineF
653
+ path={tracePath}
654
+ options={{
655
+ strokeColor: '#8b5cf6', // Purple
656
+ strokeOpacity: 0.6,
657
+ strokeWeight: 2,
658
+ geodesic: true,
659
+ }}
660
+ />
661
+ )}
662
+
663
+ {/* POIs — faint, non-intrusive circles (visual only) plus a small clickable
664
+ center dot. Making the circles non-clickable means a large translucent circle
665
+ can never swallow clicks for a small port/terminal sitting inside it; the dot
666
+ is always the precise, overlap-resistant click target. */}
667
+ {enablePois && pois?.map((poi) => {
668
+ const color = POI_COLORS[poi.type] ?? '#64748b'
669
+ const isSelected = selectedPoi?.code === poi.code
670
+ return (
671
+ <React.Fragment key={poi.code}>
672
+ <CircleF
673
+ center={poi.center}
674
+ radius={poi.radiusMeters}
675
+ options={{
676
+ fillColor: color,
677
+ fillOpacity: isSelected ? 0.2 : 0.08,
678
+ strokeColor: color,
679
+ strokeOpacity: isSelected ? 0.9 : 0.5,
680
+ strokeWeight: 1,
681
+ clickable: false,
682
+ // Smaller circles on top so they aren't buried under large translucent fills
683
+ zIndex: Math.round(1_000_000 / Math.max(poi.radiusMeters, 1)),
684
+ }}
685
+ />
686
+ <MarkerF
687
+ position={poi.center}
688
+ icon={poiDotIcon ? poiDotIcon(color, isSelected) : undefined}
689
+ title={getPoiPrimaryName(poi, locale)}
690
+ onClick={() => setSelectedPoi(poi)}
691
+ zIndex={isSelected ? 1000 : 500}
692
+ />
693
+ </React.Fragment>
694
+ )
695
+ })}
696
+
697
+ {/* POI details popup. Position-anchored: rendering InfoWindowF inside MarkerF does
698
+ NOT inject the marker anchor in this version of @react-google-maps/api, which
699
+ throws "must provide either an anchor or a position" on open. The pixelOffset in
700
+ infoWindowOptions lifts the bubble just above the center dot. */}
701
+ {selectedPoi && (
702
+ <InfoWindowF
703
+ position={selectedPoi.center}
704
+ onCloseClick={() => setSelectedPoi(null)}
705
+ options={infoWindowOptions}
706
+ >
707
+ <div className="relative min-w-[150px] max-w-[240px] text-slate-800 pr-5">
708
+ {/* Custom close button (Google's default header is disabled) */}
709
+ <button
710
+ type="button"
711
+ onClick={() => setSelectedPoi(null)}
712
+ aria-label={t('shipment_tracking.map.close', 'Close')}
713
+ className="absolute top-0 right-0 flex h-5 w-5 items-center justify-center rounded text-slate-400 hover:text-slate-700 hover:bg-slate-100 leading-none"
714
+ >
715
+ <X className="h-3.5 w-3.5" />
716
+ </button>
717
+ <div className="flex items-center gap-1.5 mb-1">
718
+ <span
719
+ className="inline-block w-2.5 h-2.5 rounded-full flex-shrink-0"
720
+ style={{ backgroundColor: POI_COLORS[selectedPoi.type] ?? '#64748b' }}
721
+ />
722
+ <span className="text-[11px] font-medium uppercase tracking-wide text-slate-500">
723
+ {getPoiTypeLabel(selectedPoi.type, t)}
724
+ </span>
725
+ </div>
726
+ <div className="text-sm font-semibold leading-tight text-slate-900">
727
+ {getPoiPrimaryName(selectedPoi, locale)}
728
+ </div>
729
+ {selectedPoi.terminalName && selectedPoi.portName && (
730
+ <div className="text-xs text-slate-500 mt-0.5">{selectedPoi.portName}</div>
731
+ )}
732
+ {(selectedPoi.city || selectedPoi.country) && (
733
+ <div className="text-xs text-slate-500 mt-0.5">
734
+ {[selectedPoi.city, selectedPoi.country].filter(Boolean).join(', ')}
735
+ </div>
736
+ )}
737
+ <div className="text-[11px] font-mono text-slate-400 mt-1">
738
+ {selectedPoi.locode || selectedPoi.code}
739
+ </div>
740
+ </div>
741
+ </InfoWindowF>
742
+ )}
743
+ </GoogleMap>
744
+
745
+ {/* Loading Indicator for Trace */}
746
+ {(isTraceLoading || isDebouncing) && (
747
+ <div className="absolute top-3 right-3 bg-card/95 backdrop-blur-sm rounded-lg shadow-sm px-2 py-1 border border-border">
748
+ <div className="flex items-center gap-1">
749
+ <div className="w-3 h-3 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" />
750
+ <span className="text-xs text-muted-foreground">
751
+ {t('shipment_tracking.map.loadingTrace', 'Loading...')}
752
+ </span>
753
+ </div>
754
+ </div>
755
+ )}
756
+ </div>
757
+ </div>
758
+ )
759
+ }
760
+
761
+ export default VesselTrackingMap