@fleetbase/fleetops-engine 0.6.69 → 0.6.71

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 (324) hide show
  1. package/RELEASE.md +21 -14
  2. package/addon/components/admin/telematics-settings.hbs +143 -0
  3. package/addon/components/admin/telematics-settings.js +145 -0
  4. package/addon/components/fuel-integration/hub.hbs +1 -1
  5. package/addon/components/layout/fleet-ops-sidebar.js +37 -17
  6. package/addon/components/map/leaflet-live-map.hbs +4 -242
  7. package/addon/components/map/leaflet-live-map.js +2 -1
  8. package/addon/components/map/marker-card/driver.hbs +57 -0
  9. package/addon/components/map/marker-card/driver.js +3 -0
  10. package/addon/components/map/marker-card/vehicle.hbs +78 -0
  11. package/addon/components/map/marker-card/vehicle.js +3 -0
  12. package/addon/components/order/details/activity.hbs +8 -6
  13. package/addon/components/order/details/activity.js +9 -0
  14. package/addon/components/order/details.hbs +29 -0
  15. package/addon/components/order/details.js +26 -0
  16. package/addon/components/order/form/details.hbs +30 -4
  17. package/addon/components/order/form/details.js +46 -0
  18. package/addon/components/order/form/route.hbs +4 -2
  19. package/addon/components/order/form/route.js +11 -0
  20. package/addon/components/order/form.hbs +12 -0
  21. package/addon/components/order/form.js +21 -1
  22. package/addon/components/order/kanban.js +72 -0
  23. package/addon/components/order-config-manager/activity-flow.js +45 -10
  24. package/addon/components/order-config-manager/lifecycle.hbs +250 -0
  25. package/addon/components/order-config-manager/lifecycle.js +358 -0
  26. package/addon/components/order-config-manager.js +5 -1
  27. package/addon/components/telematic/hub.hbs +1 -1
  28. package/addon/components/widget/live-fleet.hbs +8 -40
  29. package/addon/controllers/analytics/reports/index.js +15 -1
  30. package/addon/controllers/connectivity/devices/index/details.js +1 -0
  31. package/addon/controllers/connectivity/devices/index.js +12 -2
  32. package/addon/controllers/connectivity/events/index.js +31 -1
  33. package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
  34. package/addon/controllers/connectivity/sensors/index/details.js +1 -1
  35. package/addon/controllers/connectivity/sensors/index.js +41 -1
  36. package/addon/controllers/connectivity/telematics/index.js +4 -0
  37. package/addon/controllers/maintenance/equipment/index/details.js +2 -2
  38. package/addon/controllers/maintenance/equipment/index.js +36 -10
  39. package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
  40. package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
  41. package/addon/controllers/maintenance/inspection-submissions/index.js +56 -10
  42. package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
  43. package/addon/controllers/maintenance/maintenances/index.js +24 -1
  44. package/addon/controllers/maintenance/parts/index/details.js +2 -2
  45. package/addon/controllers/maintenance/parts/index.js +36 -9
  46. package/addon/controllers/maintenance/schedules/index.js +48 -6
  47. package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
  48. package/addon/controllers/maintenance/work-orders/index.js +47 -9
  49. package/addon/controllers/management/contacts/customers.js +13 -0
  50. package/addon/controllers/management/contacts/index.js +47 -1
  51. package/addon/controllers/management/drivers/index/details.js +1 -0
  52. package/addon/controllers/management/drivers/index.js +36 -2
  53. package/addon/controllers/management/fleets/index/details.js +1 -0
  54. package/addon/controllers/management/fleets/index.js +43 -1
  55. package/addon/controllers/management/fuel-reports/index.js +26 -2
  56. package/addon/controllers/management/fuel-transactions/index.js +45 -7
  57. package/addon/controllers/management/issues/index/details.js +1 -0
  58. package/addon/controllers/management/issues/index.js +26 -2
  59. package/addon/controllers/management/places/index.js +40 -1
  60. package/addon/controllers/management/trailers/index/details.js +1 -0
  61. package/addon/controllers/management/trailers/index.js +50 -2
  62. package/addon/controllers/management/vehicles/index/details.js +1 -0
  63. package/addon/controllers/management/vehicles/index.js +37 -2
  64. package/addon/controllers/management/vendors/index.js +30 -2
  65. package/addon/controllers/operations/orders/index/details.js +61 -5
  66. package/addon/controllers/operations/orders/index.js +66 -4
  67. package/addon/controllers/operations/service-rates/index.js +18 -1
  68. package/addon/controllers/settings/index.js +13 -0
  69. package/addon/controllers/settings/telematics.js +113 -0
  70. package/addon/extension.js +70 -6
  71. package/addon/routes/analytics/index.js +14 -1
  72. package/addon/routes/analytics/reports/index/details.js +1 -1
  73. package/addon/routes/analytics/reports/index/edit.js +1 -1
  74. package/addon/routes/analytics/reports/index/new.js +14 -1
  75. package/addon/routes/analytics/reports.js +14 -1
  76. package/addon/routes/connectivity/devices/index/new.js +14 -1
  77. package/addon/routes/connectivity/devices.js +14 -1
  78. package/addon/routes/connectivity/events.js +14 -1
  79. package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
  80. package/addon/routes/connectivity/fuel-providers/details.js +9 -0
  81. package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
  82. package/addon/routes/connectivity/fuel-providers/new.js +14 -1
  83. package/addon/routes/connectivity/fuel-providers.js +14 -1
  84. package/addon/routes/connectivity/sensors/index/new.js +14 -1
  85. package/addon/routes/connectivity/sensors.js +14 -1
  86. package/addon/routes/connectivity/telematics/new.js +14 -1
  87. package/addon/routes/connectivity/telematics.js +14 -1
  88. package/addon/routes/connectivity/tracking.js +14 -1
  89. package/addon/routes/maintenance/equipment/index/details.js +2 -2
  90. package/addon/routes/maintenance/equipment/index/edit.js +2 -2
  91. package/addon/routes/maintenance/equipment/index/new.js +14 -1
  92. package/addon/routes/maintenance/equipment.js +14 -1
  93. package/addon/routes/maintenance/index.js +14 -1
  94. package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
  95. package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
  96. package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
  97. package/addon/routes/maintenance/inspection-forms.js +14 -1
  98. package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
  99. package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
  100. package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
  101. package/addon/routes/maintenance/inspection-submissions.js +14 -1
  102. package/addon/routes/maintenance/maintenances/index/details.js +2 -2
  103. package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
  104. package/addon/routes/maintenance/maintenances/index/new.js +14 -1
  105. package/addon/routes/maintenance/maintenances.js +14 -1
  106. package/addon/routes/maintenance/parts/index/details.js +2 -2
  107. package/addon/routes/maintenance/parts/index/edit.js +2 -2
  108. package/addon/routes/maintenance/parts/index/new.js +14 -1
  109. package/addon/routes/maintenance/parts.js +14 -1
  110. package/addon/routes/maintenance/schedules/index/details.js +2 -2
  111. package/addon/routes/maintenance/schedules/index/edit.js +2 -2
  112. package/addon/routes/maintenance/schedules/index/new.js +15 -1
  113. package/addon/routes/maintenance/schedules.js +15 -1
  114. package/addon/routes/maintenance/work-orders/index/details.js +2 -2
  115. package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
  116. package/addon/routes/maintenance/work-orders/index/new.js +14 -1
  117. package/addon/routes/maintenance/work-orders.js +14 -1
  118. package/addon/routes/management/fuel-transactions/index/details.js +9 -0
  119. package/addon/routes/management/fuel-transactions.js +14 -1
  120. package/addon/routes/management/index.js +13 -0
  121. package/addon/routes/management/trailers.js +16 -0
  122. package/addon/routes/management/vendors/integrated/details.js +14 -1
  123. package/addon/routes/management/vendors/integrated/edit.js +14 -1
  124. package/addon/routes/management/vendors/integrated/new.js +14 -1
  125. package/addon/routes/management/vendors/integrated.js +14 -1
  126. package/addon/routes/operations/orders/index/details.js +1 -0
  127. package/addon/routes/operations/orders.js +40 -1
  128. package/addon/routes/operations/routes/index/details.js +14 -1
  129. package/addon/routes/operations/routes/index/new.js +13 -0
  130. package/addon/routes/operations/routes.js +14 -1
  131. package/addon/routes/operations/service-rates/index/new.js +11 -0
  132. package/addon/routes/operations/service-rates.js +14 -1
  133. package/addon/routes/settings/avatars.js +14 -1
  134. package/addon/routes/settings/custom-fields.js +14 -1
  135. package/addon/routes/settings/map.js +14 -1
  136. package/addon/routes/settings/notifications.js +11 -0
  137. package/addon/routes/settings/orchestrator.js +1 -1
  138. package/addon/routes/settings/payments/index.js +1 -1
  139. package/addon/routes/settings/routing.js +14 -1
  140. package/addon/routes/settings/scheduling.js +14 -1
  141. package/addon/routes/settings/telematics.js +21 -0
  142. package/addon/routes/virtual.js +11 -0
  143. package/addon/routes.js +1 -0
  144. package/addon/services/contact-actions.js +1 -0
  145. package/addon/services/customer-actions.js +2 -1
  146. package/addon/services/device-actions.js +1 -0
  147. package/addon/services/device-event-actions.js +1 -0
  148. package/addon/services/driver-actions.js +4 -13
  149. package/addon/services/equipment-actions.js +1 -0
  150. package/addon/services/fleet-actions.js +1 -0
  151. package/addon/services/fuel-report-actions.js +1 -0
  152. package/addon/services/inspection-form-actions.js +1 -0
  153. package/addon/services/inspection-submission-actions.js +1 -0
  154. package/addon/services/integrated-vendor-actions.js +1 -0
  155. package/addon/services/issue-actions.js +1 -0
  156. package/addon/services/maintenance-actions.js +1 -0
  157. package/addon/services/maintenance-schedule-actions.js +2 -1
  158. package/addon/services/order-actions.js +7 -0
  159. package/addon/services/order-presentation.js +129 -0
  160. package/addon/services/part-actions.js +1 -0
  161. package/addon/services/place-actions.js +1 -0
  162. package/addon/services/sensor-actions.js +1 -0
  163. package/addon/services/service-area-actions.js +1 -0
  164. package/addon/services/service-rate-actions.js +1 -0
  165. package/addon/services/telematic-actions.js +1 -0
  166. package/addon/services/trailer-actions.js +4 -14
  167. package/addon/services/vehicle-actions.js +4 -13
  168. package/addon/services/vendor-actions.js +1 -0
  169. package/addon/services/work-order-actions.js +1 -0
  170. package/addon/services/zone-actions.js +1 -0
  171. package/addon/templates/analytics/reports/index/details.hbs +1 -0
  172. package/addon/templates/analytics/reports/index.hbs +1 -0
  173. package/addon/templates/connectivity/devices/index/details.hbs +1 -0
  174. package/addon/templates/connectivity/devices/index.hbs +1 -0
  175. package/addon/templates/connectivity/events/index.hbs +1 -0
  176. package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
  177. package/addon/templates/connectivity/sensors/index.hbs +1 -0
  178. package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
  179. package/addon/templates/maintenance/equipment/index.hbs +2 -0
  180. package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
  181. package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
  182. package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
  183. package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
  184. package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
  185. package/addon/templates/maintenance/maintenances/index.hbs +1 -0
  186. package/addon/templates/maintenance/parts/index/details.hbs +1 -0
  187. package/addon/templates/maintenance/parts/index.hbs +2 -0
  188. package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
  189. package/addon/templates/maintenance/schedules/index.hbs +2 -0
  190. package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
  191. package/addon/templates/maintenance/work-orders/index.hbs +1 -0
  192. package/addon/templates/management/contacts/customers/details.hbs +1 -0
  193. package/addon/templates/management/contacts/customers.hbs +1 -0
  194. package/addon/templates/management/contacts/index/details.hbs +1 -0
  195. package/addon/templates/management/contacts/index.hbs +1 -0
  196. package/addon/templates/management/contacts.hbs +1 -0
  197. package/addon/templates/management/drivers/index/details.hbs +1 -0
  198. package/addon/templates/management/drivers/index.hbs +2 -0
  199. package/addon/templates/management/fleets/index/details.hbs +1 -0
  200. package/addon/templates/management/fleets/index.hbs +1 -0
  201. package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
  202. package/addon/templates/management/fuel-reports/index.hbs +1 -0
  203. package/addon/templates/management/fuel-transactions/index.hbs +1 -0
  204. package/addon/templates/management/issues/index/details.hbs +1 -0
  205. package/addon/templates/management/issues/index.hbs +1 -0
  206. package/addon/templates/management/places/index/details.hbs +1 -0
  207. package/addon/templates/management/places/index.hbs +1 -0
  208. package/addon/templates/management/trailers/index/details.hbs +1 -0
  209. package/addon/templates/management/trailers/index.hbs +2 -0
  210. package/addon/templates/management/vehicles/index/details.hbs +1 -0
  211. package/addon/templates/management/vehicles/index.hbs +2 -0
  212. package/addon/templates/management/vendors/index/details.hbs +1 -0
  213. package/addon/templates/management/vendors/index.hbs +2 -0
  214. package/addon/templates/management/vendors/integrated.hbs +1 -0
  215. package/addon/templates/operations/orders/index/details.hbs +1 -0
  216. package/addon/templates/operations/orders/index.hbs +1 -0
  217. package/addon/templates/operations/service-rates/index/details.hbs +1 -0
  218. package/addon/templates/operations/service-rates/index.hbs +1 -0
  219. package/addon/templates/settings/telematics.hbs +70 -0
  220. package/addon/utils/normalize-order-config-flow.js +6 -1
  221. package/app/components/admin/telematics-settings.js +1 -0
  222. package/app/components/map/marker-card/driver.js +1 -0
  223. package/app/components/map/marker-card/vehicle.js +1 -0
  224. package/app/controllers/settings/telematics.js +1 -0
  225. package/app/routes/settings/telematics.js +1 -0
  226. package/app/services/order-presentation.js +1 -0
  227. package/app/templates/settings/telematics.js +1 -0
  228. package/composer.json +2 -2
  229. package/docs/AFAQY.md +1 -1
  230. package/docs/TELEMATICS_QUEUES.md +1 -1
  231. package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
  232. package/extension.json +1 -1
  233. package/package.json +3 -3
  234. package/server/config/telemetry.php +12 -0
  235. package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
  236. package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
  237. package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
  238. package/server/src/Auth/Schemas/FleetOps.php +104 -5
  239. package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
  240. package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
  241. package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
  242. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
  243. package/server/src/Http/Controllers/FleetOpsController.php +21 -0
  244. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
  245. package/server/src/Http/Controllers/Internal/v1/ContactController.php +11 -0
  246. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +14 -0
  247. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
  248. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
  249. package/server/src/Http/Controllers/Internal/v1/DriverController.php +22 -0
  250. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
  251. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
  252. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
  253. package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
  254. package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
  255. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
  256. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
  257. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
  258. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
  259. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
  260. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
  261. package/server/src/Http/Controllers/Internal/v1/OrderController.php +56 -4
  262. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
  263. package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
  264. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
  265. package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
  266. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
  267. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
  268. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
  269. package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
  270. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
  271. package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
  272. package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
  273. package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
  274. package/server/src/Mail/CustomerCredentialsMail.php +1 -1
  275. package/server/src/Models/Order.php +35 -3
  276. package/server/src/Models/OrderConfig.php +129 -0
  277. package/server/src/Models/TrackingNumber.php +8 -4
  278. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
  279. package/server/src/Providers/FleetOpsServiceProvider.php +3 -0
  280. package/server/src/Support/Analytics/LiveFleet.php +18 -2
  281. package/server/src/Support/Authorization.php +103 -0
  282. package/server/src/Support/Database/TableIndexes.php +66 -0
  283. package/server/src/Support/OrderTracker.php +2 -2
  284. package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
  285. package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
  286. package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
  287. package/server/src/Support/Telematics/TelematicService.php +12 -2
  288. package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
  289. package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
  290. package/server/src/Tracking/TrackingOptions.php +2 -2
  291. package/server/src/Traits/AuthorizesMethods.php +37 -0
  292. package/server/src/routes.php +9 -1
  293. package/server/tests/AnalyticsRoutesTest.php +29 -2
  294. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +16 -1
  295. package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
  296. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
  297. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
  298. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
  299. package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
  300. package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
  301. package/server/tests/NavigatorControllerContractsTest.php +36 -4
  302. package/server/tests/NotificationAndMailContractsTest.php +31 -0
  303. package/server/tests/ObserverContractsTest.php +1 -1
  304. package/server/tests/OrchestratorConsumableApiTest.php +42 -0
  305. package/server/tests/OrderConfigLifecycleTest.php +120 -0
  306. package/server/tests/ProviderContractsTest.php +10 -0
  307. package/server/tests/ReportSchemaContractsTest.php +120 -6
  308. package/server/tests/SettingControllerContractsTest.php +429 -18
  309. package/server/tests/SmallControllerContractsTest.php +8 -5
  310. package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
  311. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
  312. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
  313. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
  314. package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
  315. package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
  316. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
  317. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
  318. package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
  319. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
  320. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
  321. package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
  322. package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
  323. package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
  324. package/translations/en-us.yaml +108 -0
@@ -83,108 +83,10 @@
83
83
  as |marker|
84
84
  >
85
85
  <marker.popup @maxWidth="500" @minWidth="225">
86
- <div class="w-[280px] max-w-[calc(100vw-2rem)] rounded-lg bg-gray-900 shadow-lg p-1.5 text-white">
87
- <div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
88
- <span class="inline-block w-2 h-2 rounded-full {{if driver.online 'bg-green-400' 'bg-red-500'}}"></span>
89
- <span>{{driver.name}}</span>
90
- <div
91
- class="status-badge
92
- {{if driver.status (dasherize driver.status) 'default'}}-status-badge status-badge-{{if driver.status (dasherize driver.status) 'default'}}
93
- shadow-none ml-auto"
94
- >
95
- <span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a driver.meta.status_label (smart-humanize driver.status)}}</span>
96
- </div>
97
- </div>
98
- <div class="grid grid-cols-2 gap-1">
99
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
100
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">ID</div>
101
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.public_id driver.id}}</div>
102
- </div>
103
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
104
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Phone</div>
105
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.phone}}</div>
106
- </div>
107
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
108
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle</div>
109
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.vehicle_name}}</div>
110
- </div>
111
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
112
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Email</div>
113
- <div class="text-[11px] text-white leading-tight truncate min-w-0 w-full">{{n-a driver.email}}</div>
114
- </div>
115
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
116
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
117
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.meta.current_order_reference}}</div>
118
- </div>
119
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
120
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
121
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.meta.speed_label}}</div>
122
- </div>
123
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
124
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
125
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.meta.heading_label}}</div>
126
- </div>
127
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
128
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
129
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
130
- driver.meta.location_coordinates
131
- (point-coordinates driver.location)
132
- }}</div>
133
- </div>
134
- </div>
135
- </div>
86
+ <Map::MarkerCard::Driver @driver={{driver}} />
136
87
  </marker.popup>
137
88
  <marker.tooltip @permanent={{false}} @sticky={{true}}>
138
- <div class="w-[280px] max-w-[calc(100vw-2rem)]">
139
- <div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
140
- <span class="inline-block w-2 h-2 rounded-full {{if driver.online 'bg-green-400' 'bg-red-500'}}"></span>
141
- <span>{{driver.name}}</span>
142
- <div
143
- class="status-badge
144
- {{if driver.status (dasherize driver.status) 'default'}}-status-badge status-badge-{{if driver.status (dasherize driver.status) 'default'}}
145
- shadow-none ml-auto"
146
- >
147
- <span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a driver.meta.status_label (smart-humanize driver.status)}}</span>
148
- </div>
149
- </div>
150
- <div class="grid grid-cols-2 gap-1">
151
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
152
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">ID</div>
153
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.public_id driver.id}}</div>
154
- </div>
155
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
156
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Phone</div>
157
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.phone}}</div>
158
- </div>
159
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
160
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle</div>
161
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.vehicle_name}}</div>
162
- </div>
163
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
164
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Email</div>
165
- <div class="text-[11px] text-white leading-tight truncate min-w-0 w-full">{{n-a driver.email}}</div>
166
- </div>
167
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
168
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
169
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.meta.current_order_reference}}</div>
170
- </div>
171
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
172
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
173
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.meta.speed_label}}</div>
174
- </div>
175
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
176
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
177
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a driver.meta.heading_label}}</div>
178
- </div>
179
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
180
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
181
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
182
- driver.meta.location_coordinates
183
- (point-coordinates driver.location)
184
- }}</div>
185
- </div>
186
- </div>
187
- </div>
89
+ <Map::MarkerCard::Driver @driver={{driver}} @variant="tooltip" />
188
90
  </marker.tooltip>
189
91
  </layers.tracking-marker>
190
92
  {{/each}}
@@ -202,150 +104,10 @@
202
104
  as |marker|
203
105
  >
204
106
  <marker.popup @permanent={{false}} @sticky={{true}}>
205
- <div class="w-[280px] max-w-[calc(100vw-2rem)] rounded-lg bg-gray-900 shadow-lg p-1.5 text-white">
206
- <div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
207
- <span class="inline-block w-2 h-2 rounded-full {{if vehicle.online 'bg-green-400' 'bg-red-500'}}"></span>
208
- <span>{{vehicle.displayName}}</span>
209
- <div
210
- class="status-badge
211
- {{if vehicle.status (dasherize vehicle.status) 'default'}}-status-badge status-badge-{{if vehicle.status (dasherize vehicle.status) 'default'}}
212
- shadow-none ml-auto"
213
- >
214
- <span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a vehicle.meta.status_label}}</span>
215
- </div>
216
- </div>
217
- <div class="grid grid-cols-2 gap-1">
218
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
219
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle #</div>
220
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
221
- vehicle.internal_id
222
- vehicle.public_id
223
- vehicle.plate_number
224
- vehicle.serial_number
225
- vehicle.vin
226
- }}</div>
227
- </div>
228
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
229
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Driver</div>
230
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.driver_name}}</div>
231
- </div>
232
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
233
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.trailers"}}</div>
234
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">
235
- {{#each vehicle.trailers as |trailer|}}
236
- <div class="flex items-center gap-1 min-w-0">
237
- <span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if trailer.online 'bg-green-400' 'bg-gray-500'}}"></span>
238
- <span class="truncate">{{or trailer.displayName trailer.display_name trailer.name trailer.public_id}}</span>
239
- </div>
240
- {{else}}
241
- -
242
- {{/each}}
243
- </div>
244
- </div>
245
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
246
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.devices"}}</div>
247
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">
248
- {{#each vehicle.devices as |device|}}
249
- <div class="flex items-center gap-1 min-w-0">
250
- <span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if device.online 'bg-green-400' 'bg-gray-500'}}"></span>
251
- <span class="truncate">{{or device.displayName device.display_name device.name device.device_id device.public_id}}</span>
252
- </div>
253
- {{else}}
254
- -
255
- {{/each}}
256
- </div>
257
- </div>
258
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
259
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
260
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.current_order_reference}}</div>
261
- </div>
262
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
263
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
264
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.speed_label}}</div>
265
- </div>
266
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
267
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
268
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.heading_label}}</div>
269
- </div>
270
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
271
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
272
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.location_coordinates}}</div>
273
- </div>
274
- </div>
275
- </div>
107
+ <Map::MarkerCard::Vehicle @vehicle={{vehicle}} />
276
108
  </marker.popup>
277
109
  <marker.tooltip @permanent={{false}} @sticky={{true}}>
278
- <div class="w-[280px] max-w-[calc(100vw-2rem)]">
279
- <div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
280
- <span class="inline-block w-2 h-2 rounded-full {{if vehicle.online 'bg-green-400' 'bg-red-500'}}"></span>
281
- <span>{{vehicle.displayName}}</span>
282
- <div
283
- class="status-badge
284
- {{if vehicle.status (dasherize vehicle.status) 'default'}}-status-badge status-badge-{{if vehicle.status (dasherize vehicle.status) 'default'}}
285
- shadow-none ml-auto"
286
- >
287
- <span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a vehicle.meta.status_label}}</span>
288
- </div>
289
- </div>
290
- <div class="grid grid-cols-2 gap-1">
291
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
292
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle #</div>
293
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
294
- vehicle.internal_id
295
- vehicle.public_id
296
- vehicle.plate_number
297
- vehicle.serial_number
298
- vehicle.vin
299
- }}</div>
300
- </div>
301
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
302
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Driver</div>
303
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.driver_name}}</div>
304
- </div>
305
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
306
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.trailers"}}</div>
307
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">
308
- {{#each vehicle.trailers as |trailer|}}
309
- <div class="flex items-center gap-1 min-w-0">
310
- <span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if trailer.online 'bg-green-400' 'bg-gray-500'}}"></span>
311
- <span class="truncate">{{or trailer.displayName trailer.display_name trailer.name trailer.public_id}}</span>
312
- </div>
313
- {{else}}
314
- -
315
- {{/each}}
316
- </div>
317
- </div>
318
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
319
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.devices"}}</div>
320
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">
321
- {{#each vehicle.devices as |device|}}
322
- <div class="flex items-center gap-1 min-w-0">
323
- <span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if device.online 'bg-green-400' 'bg-gray-500'}}"></span>
324
- <span class="truncate">{{or device.displayName device.display_name device.name device.device_id device.public_id}}</span>
325
- </div>
326
- {{else}}
327
- -
328
- {{/each}}
329
- </div>
330
- </div>
331
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
332
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
333
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.current_order_reference}}</div>
334
- </div>
335
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
336
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
337
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.speed_label}}</div>
338
- </div>
339
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
340
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
341
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.heading_label}}</div>
342
- </div>
343
- <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
344
- <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
345
- <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a vehicle.meta.location_coordinates}}</div>
346
- </div>
347
- </div>
348
- </div>
110
+ <Map::MarkerCard::Vehicle @vehicle={{vehicle}} @variant="tooltip" />
349
111
  </marker.tooltip>
350
112
  </layers.tracking-marker>
351
113
  {{/each}}
@@ -298,7 +298,8 @@ export default class MapLeafletLiveMapComponent extends Component {
298
298
  }
299
299
 
300
300
  @task *loadResource(path, options = {}) {
301
- if (this.abilities.cannot(`fleet-ops list ${path}`)) return [];
301
+ // Permissions use the singular resource name (e.g. `fleet-ops list vehicle`), not the plural endpoint path.
302
+ if (this.abilities.cannot(`fleet-ops list ${singularize(path)}`)) return [];
302
303
 
303
304
  if (path === 'service-areas') {
304
305
  const serviceAreas = yield this.serviceAreaActions.loadAll.perform();
@@ -0,0 +1,57 @@
1
+ {{!
2
+ The driver card the fleet-ops live map shows in a marker's popup and hover tooltip.
3
+ Shared with the dashboard's Live Fleet widget so both look the same. @driver is a driver
4
+ model or its index resource payload (the same attributes, plain JSON).
5
+ @variant: "popup" (default, a dark panel) or "tooltip" (inherits the tooltip's panel).
6
+ }}
7
+ <div class="w-[280px] max-w-[calc(100vw-2rem)] {{unless (eq @variant 'tooltip') 'rounded-lg bg-gray-900 shadow-lg p-1.5 text-white'}}" ...attributes>
8
+ <div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
9
+ <span class="inline-block w-2 h-2 rounded-full {{if @driver.online 'bg-green-400' 'bg-red-500'}}"></span>
10
+ <span>{{@driver.name}}</span>
11
+ <div
12
+ class="status-badge
13
+ {{if @driver.status (dasherize @driver.status) 'default'}}-status-badge status-badge-{{if @driver.status (dasherize @driver.status) 'default'}}
14
+ shadow-none ml-auto"
15
+ >
16
+ <span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a @driver.meta.status_label (smart-humanize @driver.status)}}</span>
17
+ </div>
18
+ </div>
19
+ <div class="grid grid-cols-2 gap-1">
20
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
21
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">ID</div>
22
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.public_id @driver.id}}</div>
23
+ </div>
24
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
25
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Phone</div>
26
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.phone}}</div>
27
+ </div>
28
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
29
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle</div>
30
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.vehicle_name}}</div>
31
+ </div>
32
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
33
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Email</div>
34
+ <div class="text-[11px] text-white leading-tight truncate min-w-0 w-full">{{n-a @driver.email}}</div>
35
+ </div>
36
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
37
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
38
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.meta.current_order_reference}}</div>
39
+ </div>
40
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
41
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
42
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.meta.speed_label}}</div>
43
+ </div>
44
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
45
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
46
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.meta.heading_label}}</div>
47
+ </div>
48
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
49
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
50
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
51
+ @driver.meta.location_coordinates
52
+ (point-coordinates @driver.location)
53
+ }}</div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
@@ -0,0 +1,3 @@
1
+ import templateOnly from '@ember/component/template-only';
2
+
3
+ export default templateOnly();
@@ -0,0 +1,78 @@
1
+ {{!
2
+ The vehicle card the fleet-ops live map shows in a marker's popup and hover tooltip.
3
+ Shared with the dashboard's Live Fleet widget so both look the same. @vehicle is a vehicle
4
+ model or its index resource payload (the same attributes, plain JSON).
5
+ @variant: "popup" (default, a dark panel) or "tooltip" (inherits the tooltip's panel).
6
+ }}
7
+ <div class="w-[280px] max-w-[calc(100vw-2rem)] {{unless (eq @variant 'tooltip') 'rounded-lg bg-gray-900 shadow-lg p-1.5 text-white'}}" ...attributes>
8
+ <div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
9
+ <span class="inline-block w-2 h-2 rounded-full {{if @vehicle.online 'bg-green-400' 'bg-red-500'}}"></span>
10
+ <span>{{or @vehicle.displayName @vehicle.display_name @vehicle.name}}</span>
11
+ <div
12
+ class="status-badge
13
+ {{if @vehicle.status (dasherize @vehicle.status) 'default'}}-status-badge status-badge-{{if @vehicle.status (dasherize @vehicle.status) 'default'}}
14
+ shadow-none ml-auto"
15
+ >
16
+ <span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a @vehicle.meta.status_label}}</span>
17
+ </div>
18
+ </div>
19
+ <div class="grid grid-cols-2 gap-1">
20
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
21
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle #</div>
22
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
23
+ @vehicle.internal_id
24
+ @vehicle.public_id
25
+ @vehicle.plate_number
26
+ @vehicle.serial_number
27
+ @vehicle.vin
28
+ }}</div>
29
+ </div>
30
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
31
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Driver</div>
32
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.driver_name}}</div>
33
+ </div>
34
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
35
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.trailers"}}</div>
36
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">
37
+ {{#each @vehicle.trailers as |trailer|}}
38
+ <div class="flex items-center gap-1 min-w-0">
39
+ <span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if trailer.online 'bg-green-400' 'bg-gray-500'}}"></span>
40
+ <span class="truncate">{{or trailer.displayName trailer.display_name trailer.name trailer.public_id}}</span>
41
+ </div>
42
+ {{else}}
43
+ -
44
+ {{/each}}
45
+ </div>
46
+ </div>
47
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
48
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.devices"}}</div>
49
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">
50
+ {{#each @vehicle.devices as |device|}}
51
+ <div class="flex items-center gap-1 min-w-0">
52
+ <span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if device.online 'bg-green-400' 'bg-gray-500'}}"></span>
53
+ <span class="truncate">{{or device.displayName device.display_name device.name device.device_id device.public_id}}</span>
54
+ </div>
55
+ {{else}}
56
+ -
57
+ {{/each}}
58
+ </div>
59
+ </div>
60
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
61
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
62
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.current_order_reference}}</div>
63
+ </div>
64
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
65
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
66
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.speed_label}}</div>
67
+ </div>
68
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
69
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
70
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.heading_label}}</div>
71
+ </div>
72
+ <div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
73
+ <div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
74
+ <div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.location_coordinates}}</div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
@@ -0,0 +1,3 @@
1
+ import templateOnly from '@ember/component/template-only';
2
+
3
+ export default templateOnly();
@@ -5,9 +5,11 @@
5
5
  @isLoading={{or @isLoadingActivity this.loadActivity.isRunning @isLoading}}
6
6
  @wrapperClass="bordered-top"
7
7
  >
8
- {{#if (eq this.layout "timeline")}}
9
- <Order::ActivityTimeline @resource={{@resource}} @activity={{this.parentOrderActivity}} />
10
- {{else}}
11
- <Order::ActivityList @resource={{@resource}} @activity={{this.parentOrderActivity}} />
12
- {{/if}}
13
- </ContentPanel>
8
+ <div {{did-update this.onStatusChanged @resource.status}}>
9
+ {{#if (eq this.layout "timeline")}}
10
+ <Order::ActivityTimeline @resource={{@resource}} @activity={{this.parentOrderActivity}} />
11
+ {{else}}
12
+ <Order::ActivityList @resource={{@resource}} @activity={{this.parentOrderActivity}} />
13
+ {{/if}}
14
+ </div>
15
+ </ContentPanel>
@@ -1,6 +1,7 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
4
5
  import { task } from 'ember-concurrency';
5
6
 
6
7
  export default class OrderDetailsActivityComponent extends Component {
@@ -64,6 +65,14 @@ export default class OrderDetailsActivityComponent extends Component {
64
65
  ];
65
66
  }
66
67
 
68
+ /**
69
+ * A status change recorded outside this panel (a configured lifecycle advancing on save, an
70
+ * extension action, a realtime update) means new tracking statuses exist server-side.
71
+ */
72
+ @action onStatusChanged() {
73
+ this.loadActivity.perform();
74
+ }
75
+
67
76
  @task *loadActivity() {
68
77
  if (!this.args.resource) {
69
78
  return;
@@ -44,6 +44,35 @@
44
44
  )
45
45
  )
46
46
  }}
47
+ {{else if this.profile}}
48
+ {{#each this.profileSections key="key" as |section|}}
49
+ {{#if section.native}}
50
+ {{#let
51
+ (component
52
+ section.componentName
53
+ resource=@resource
54
+ onChange=section.onChange
55
+ isLoading=@isLoading
56
+ isLoadingActivity=@isLoadingActivity
57
+ reloadToken=this.effectiveProofReloadToken
58
+ )
59
+ as |NativeSection|
60
+ }}
61
+ <NativeSection />
62
+ {{/let}}
63
+ {{else}}
64
+ <LazyEngineComponent @component={{section.component}} as |ProfileSection|>
65
+ <ProfileSection
66
+ @resource={{@resource}}
67
+ @controller={{@controller}}
68
+ @permission={{get-write-permission @resource}}
69
+ @isLoading={{@isLoading}}
70
+ @profile={{this.profile}}
71
+ @onChange={{@onDetailsChanged}}
72
+ />
73
+ </LazyEngineComponent>
74
+ {{/if}}
75
+ {{/each}}
47
76
  {{else}}
48
77
  <RegistryYield @registry="fleet-ops:component:order:details:start" as |RegistryComponent|>
49
78
  <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
@@ -1,14 +1,40 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { action } from '@ember/object';
3
3
  import { tracked } from '@glimmer/tracking';
4
+ import { inject as service } from '@ember/service';
4
5
 
5
6
  export default class OrderDetailsComponent extends Component {
7
+ @service orderPresentation;
6
8
  @tracked proofReloadToken = 0;
7
9
 
8
10
  get effectiveProofReloadToken() {
9
11
  return `${this.args.proofReloadToken ?? 0}:${this.proofReloadToken}`;
10
12
  }
11
13
 
14
+ /** The extension presentation profile applying to this order's config, if any. */
15
+ get profile() {
16
+ return this.orderPresentation.profileFor(this.args.resource);
17
+ }
18
+
19
+ /** Profile sections with the change handler each native section expects. */
20
+ get profileSections() {
21
+ const handlers = {
22
+ activity: this.handleActivityChanged,
23
+ detail: this.args.onDetailsChanged,
24
+ 'custom-fields': this.args.onCustomFieldsChanged,
25
+ notes: this.args.onNotesChanged,
26
+ route: this.args.onRouteChanged,
27
+ payload: this.args.onPayloadChanged,
28
+ documents: this.args.onDocumentsChanged,
29
+ comments: this.args.onCommentsChanged,
30
+ metadata: this.args.onMetadataChanged,
31
+ };
32
+
33
+ return this.orderPresentation.renderableSectionsFor(this.profile, 'details').map((section) => {
34
+ return section.native ? { ...section, onChange: handlers[section.name] } : section;
35
+ });
36
+ }
37
+
12
38
  @action handleActivityChanged(optionsOrActivity) {
13
39
  if (typeof this.args.onActivityChanged === 'function') {
14
40
  this.args.onActivityChanged(optionsOrActivity?.activityCreated ?? optionsOrActivity);