@fleetbase/fleetops-engine 0.6.68 → 0.6.70

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 (294) hide show
  1. package/RELEASE.md +44 -9
  2. package/addon/components/admin/telematics-settings.hbs +143 -0
  3. package/addon/components/admin/telematics-settings.js +145 -0
  4. package/addon/components/contact/form.hbs +13 -2
  5. package/addon/components/customer/details.hbs +17 -17
  6. package/addon/components/customer/form.hbs +13 -2
  7. package/addon/components/customer/form.js +0 -56
  8. package/addon/components/driver/details.hbs +13 -19
  9. package/addon/components/driver/form.hbs +20 -35
  10. package/addon/components/driver/form.js +0 -58
  11. package/addon/components/fuel-integration/hub.hbs +1 -1
  12. package/addon/components/layout/fleet-ops-sidebar.js +37 -17
  13. package/addon/components/map/leaflet-live-map.hbs +4 -242
  14. package/addon/components/map/leaflet-live-map.js +2 -1
  15. package/addon/components/map/marker-card/driver.hbs +57 -0
  16. package/addon/components/map/marker-card/driver.js +3 -0
  17. package/addon/components/map/marker-card/vehicle.hbs +78 -0
  18. package/addon/components/map/marker-card/vehicle.js +3 -0
  19. package/addon/components/modals/driver-form.hbs +2 -2
  20. package/addon/components/modals/reset-profile-credentials.hbs +23 -0
  21. package/addon/components/modals/reset-profile-credentials.js +70 -0
  22. package/addon/components/profile-identity-hint.hbs +13 -0
  23. package/addon/components/profile-identity-hint.js +49 -0
  24. package/addon/components/telematic/hub.hbs +1 -1
  25. package/addon/components/widget/live-fleet.hbs +8 -40
  26. package/addon/controllers/analytics/reports/index.js +5 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -0
  28. package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
  29. package/addon/controllers/connectivity/sensors/index.js +4 -0
  30. package/addon/controllers/connectivity/telematics/index.js +4 -0
  31. package/addon/controllers/maintenance/equipment/index.js +11 -4
  32. package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
  33. package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
  34. package/addon/controllers/maintenance/maintenances/index.js +4 -0
  35. package/addon/controllers/maintenance/parts/index.js +11 -4
  36. package/addon/controllers/maintenance/schedules/index.js +17 -4
  37. package/addon/controllers/maintenance/work-orders/index.js +11 -4
  38. package/addon/controllers/management/contacts/index.js +4 -0
  39. package/addon/controllers/management/drivers/index.js +8 -0
  40. package/addon/controllers/management/fleets/index.js +4 -0
  41. package/addon/controllers/management/fuel-reports/index.js +4 -0
  42. package/addon/controllers/management/fuel-transactions/index.js +9 -7
  43. package/addon/controllers/management/issues/index.js +4 -0
  44. package/addon/controllers/management/places/index.js +4 -0
  45. package/addon/controllers/management/vehicles/index.js +4 -0
  46. package/addon/controllers/management/vendors/index.js +4 -0
  47. package/addon/controllers/operations/orders/index/details.js +30 -1
  48. package/addon/controllers/operations/orders/index.js +7 -0
  49. package/addon/controllers/operations/service-rates/index.js +3 -0
  50. package/addon/controllers/settings/index.js +13 -0
  51. package/addon/controllers/settings/telematics.js +113 -0
  52. package/addon/extension.js +42 -8
  53. package/addon/routes/analytics/index.js +14 -1
  54. package/addon/routes/analytics/reports/index/details.js +1 -1
  55. package/addon/routes/analytics/reports/index/edit.js +1 -1
  56. package/addon/routes/analytics/reports/index/new.js +14 -1
  57. package/addon/routes/analytics/reports.js +14 -1
  58. package/addon/routes/connectivity/devices/index/new.js +14 -1
  59. package/addon/routes/connectivity/devices.js +14 -1
  60. package/addon/routes/connectivity/events.js +14 -1
  61. package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
  62. package/addon/routes/connectivity/fuel-providers/details.js +9 -0
  63. package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
  64. package/addon/routes/connectivity/fuel-providers/new.js +14 -1
  65. package/addon/routes/connectivity/fuel-providers.js +14 -1
  66. package/addon/routes/connectivity/sensors/index/new.js +14 -1
  67. package/addon/routes/connectivity/sensors.js +14 -1
  68. package/addon/routes/connectivity/telematics/new.js +14 -1
  69. package/addon/routes/connectivity/telematics.js +14 -1
  70. package/addon/routes/connectivity/tracking.js +14 -1
  71. package/addon/routes/maintenance/equipment/index/details.js +2 -2
  72. package/addon/routes/maintenance/equipment/index/edit.js +2 -2
  73. package/addon/routes/maintenance/equipment/index/new.js +14 -1
  74. package/addon/routes/maintenance/equipment.js +14 -1
  75. package/addon/routes/maintenance/index.js +14 -1
  76. package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
  77. package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
  78. package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
  79. package/addon/routes/maintenance/inspection-forms.js +14 -1
  80. package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
  81. package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
  82. package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
  83. package/addon/routes/maintenance/inspection-submissions.js +14 -1
  84. package/addon/routes/maintenance/maintenances/index/details.js +2 -2
  85. package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
  86. package/addon/routes/maintenance/maintenances/index/new.js +14 -1
  87. package/addon/routes/maintenance/maintenances.js +14 -1
  88. package/addon/routes/maintenance/parts/index/details.js +2 -2
  89. package/addon/routes/maintenance/parts/index/edit.js +2 -2
  90. package/addon/routes/maintenance/parts/index/new.js +14 -1
  91. package/addon/routes/maintenance/parts.js +14 -1
  92. package/addon/routes/maintenance/schedules/index/details.js +2 -2
  93. package/addon/routes/maintenance/schedules/index/edit.js +2 -2
  94. package/addon/routes/maintenance/schedules/index/new.js +15 -1
  95. package/addon/routes/maintenance/schedules.js +15 -1
  96. package/addon/routes/maintenance/work-orders/index/details.js +2 -2
  97. package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
  98. package/addon/routes/maintenance/work-orders/index/new.js +14 -1
  99. package/addon/routes/maintenance/work-orders.js +14 -1
  100. package/addon/routes/management/fuel-transactions/index/details.js +9 -0
  101. package/addon/routes/management/fuel-transactions.js +14 -1
  102. package/addon/routes/management/index.js +13 -0
  103. package/addon/routes/management/trailers.js +16 -0
  104. package/addon/routes/management/vendors/integrated/details.js +14 -1
  105. package/addon/routes/management/vendors/integrated/edit.js +14 -1
  106. package/addon/routes/management/vendors/integrated/new.js +14 -1
  107. package/addon/routes/management/vendors/integrated.js +14 -1
  108. package/addon/routes/operations/orders.js +40 -1
  109. package/addon/routes/operations/routes/index/details.js +14 -1
  110. package/addon/routes/operations/routes/index/new.js +13 -0
  111. package/addon/routes/operations/routes.js +14 -1
  112. package/addon/routes/operations/service-rates/index/new.js +11 -0
  113. package/addon/routes/operations/service-rates.js +14 -1
  114. package/addon/routes/settings/avatars.js +14 -1
  115. package/addon/routes/settings/custom-fields.js +14 -1
  116. package/addon/routes/settings/map.js +14 -1
  117. package/addon/routes/settings/notifications.js +11 -0
  118. package/addon/routes/settings/orchestrator.js +1 -1
  119. package/addon/routes/settings/payments/index.js +1 -1
  120. package/addon/routes/settings/routing.js +14 -1
  121. package/addon/routes/settings/scheduling.js +14 -1
  122. package/addon/routes/settings/telematics.js +21 -0
  123. package/addon/routes/virtual.js +11 -0
  124. package/addon/routes.js +1 -0
  125. package/addon/services/contact-actions.js +50 -89
  126. package/addon/services/driver-actions.js +98 -0
  127. package/addon/templates/settings/telematics.hbs +70 -0
  128. package/addon/utils/profile-login-actions.js +172 -0
  129. package/app/components/admin/telematics-settings.js +1 -0
  130. package/app/components/{modals/reset-customer-credentials.js → map/marker-card/driver.js} +1 -1
  131. package/app/components/map/marker-card/vehicle.js +1 -0
  132. package/app/components/modals/reset-profile-credentials.js +1 -0
  133. package/app/components/profile-identity-hint.js +1 -0
  134. package/app/controllers/settings/telematics.js +1 -0
  135. package/app/routes/settings/telematics.js +1 -0
  136. package/app/templates/settings/telematics.js +1 -0
  137. package/composer.json +2 -2
  138. package/docs/AFAQY.md +1 -1
  139. package/docs/TELEMATICS_QUEUES.md +1 -1
  140. package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
  141. package/extension.json +1 -1
  142. package/package.json +3 -3
  143. package/scripts/pest-bootstrap.php +12 -0
  144. package/server/config/telemetry.php +12 -0
  145. package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
  146. package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
  147. package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
  148. package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
  149. package/server/resources/views/mail/driver-credentials.blade.php +21 -0
  150. package/server/src/Auth/Schemas/FleetOps.php +104 -5
  151. package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
  152. package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
  153. package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
  154. package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
  155. package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
  156. package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
  157. package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
  158. package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
  159. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
  160. package/server/src/Http/Controllers/FleetOpsController.php +21 -0
  161. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
  162. package/server/src/Http/Controllers/Internal/v1/ContactController.php +16 -17
  163. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +54 -4
  164. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
  165. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
  166. package/server/src/Http/Controllers/Internal/v1/DriverController.php +204 -162
  167. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
  168. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
  169. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
  170. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
  171. package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
  172. package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
  173. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
  174. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
  175. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
  176. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
  177. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
  178. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
  179. package/server/src/Http/Controllers/Internal/v1/OrderController.php +178 -27
  180. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
  181. package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
  182. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
  183. package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
  184. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
  185. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
  186. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
  187. package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
  188. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
  189. package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
  190. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
  191. package/server/src/Http/Resources/v1/Contact.php +3 -0
  192. package/server/src/Http/Resources/v1/Driver.php +3 -0
  193. package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
  194. package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
  195. package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
  196. package/server/src/Mail/CustomerCredentialsMail.php +1 -1
  197. package/server/src/Mail/DriverCredentialsMail.php +55 -0
  198. package/server/src/Models/Contact.php +55 -90
  199. package/server/src/Models/Sensor.php +3 -5
  200. package/server/src/Observers/ContactObserver.php +2 -22
  201. package/server/src/Observers/DriverObserver.php +5 -6
  202. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
  203. package/server/src/Providers/FleetOpsServiceProvider.php +18 -0
  204. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
  205. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
  206. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
  207. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
  208. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
  209. package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
  210. package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
  211. package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
  212. package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
  213. package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
  214. package/server/src/Support/Analytics/LiveFleet.php +18 -2
  215. package/server/src/Support/Authorization.php +103 -0
  216. package/server/src/Support/Database/TableIndexes.php +66 -0
  217. package/server/src/Support/OrderTracker.php +2 -2
  218. package/server/src/Support/ProfileAccountManager.php +405 -0
  219. package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
  220. package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
  221. package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
  222. package/server/src/Support/Telematics/TelematicService.php +12 -2
  223. package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
  224. package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
  225. package/server/src/Tracking/TrackingOptions.php +2 -2
  226. package/server/src/Traits/AuthorizesMethods.php +37 -0
  227. package/server/src/routes.php +14 -1
  228. package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
  229. package/server/tests/AnalyticsRoutesTest.php +29 -2
  230. package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
  231. package/server/tests/ApiDriverControllerContractsTest.php +102 -20
  232. package/server/tests/CommandContractsTest.php +4 -4
  233. package/server/tests/ControllerHelperContractsTest.php +3 -11
  234. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +21 -1
  235. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
  236. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
  237. package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
  238. package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
  239. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
  240. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
  241. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
  242. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
  243. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
  244. package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
  245. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
  246. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
  247. package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
  248. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
  249. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
  250. package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
  251. package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
  252. package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
  253. package/server/tests/ModelAccessorContractsTest.php +82 -20
  254. package/server/tests/NavigatorControllerContractsTest.php +36 -4
  255. package/server/tests/NotificationAndMailContractsTest.php +31 -0
  256. package/server/tests/ObserverContractsTest.php +28 -3
  257. package/server/tests/OrchestratorConsumableApiTest.php +42 -0
  258. package/server/tests/ProviderContractsTest.php +10 -0
  259. package/server/tests/ReportSchemaContractsTest.php +120 -6
  260. package/server/tests/RequestContractsTest.php +19 -11
  261. package/server/tests/SettingControllerContractsTest.php +429 -18
  262. package/server/tests/SmallControllerContractsTest.php +8 -5
  263. package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
  264. package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
  265. package/server/tests/TelematicControllerContractsTest.php +22 -4
  266. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
  267. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
  268. package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
  269. package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
  270. package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
  271. package/server/tests/Unit/Models/ContactTest.php +48 -5
  272. package/server/tests/Unit/Models/SensorTest.php +2 -2
  273. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
  274. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
  275. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
  276. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
  277. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
  278. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
  279. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
  280. package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
  281. package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
  282. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
  283. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
  284. package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
  285. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
  286. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
  287. package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
  288. package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
  289. package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
  290. package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
  291. package/translations/en-us.yaml +85 -2
  292. package/translations/uk-ua.yaml +3475 -0
  293. package/addon/components/modals/reset-customer-credentials.hbs +0 -18
  294. package/addon/components/modals/reset-customer-credentials.js +0 -55
@@ -1,39 +1,4 @@
1
1
  <div class="form-wrapper" ...attributes>
2
- <ContentPanel @title={{t "driver.fields.user-account"}} @open={{true}} @actionButtons={{this.userAccountActionButtons}} @wrapperClass="bordered-top">
3
- <div class="space-y-2">
4
- <InputGroup @name={{t "driver.fields.user-account"}} @helpText={{t "driver.fields.user-account-help-text"}}>
5
- <ModelSelect
6
- @modelName="user"
7
- @selectedItemComponent={{component "select-option/user" compact=true}}
8
- @selectedModel={{@resource.user}}
9
- @placeholder={{t "driver.fields.select-user"}}
10
- @query={{hash doesnt_have_driver=true can_be_driver=true}}
11
- @triggerClass="form-select form-input"
12
- @infiniteScroll={{false}}
13
- @renderInPlace={{true}}
14
- @onChange={{fn (mut @resource.user)}}
15
- @permission="fleet-ops update-user-for driver"
16
- as |model|
17
- >
18
- <SelectOption::User @option={{model}} />
19
- </ModelSelect>
20
- </InputGroup>
21
- {{#if @resource.user}}
22
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 no-input-group-padding">
23
- <InputGroup @name={{t "common.name"}} @wrapperClass="col-span-2 mb-0i">
24
- <Input @value={{@resource.user.name}} @type="text" class="w-full form-input" placeholder={{t "common.name"}} disabled={{true}} />
25
- </InputGroup>
26
- <InputGroup @name={{t "common.email"}} @wrapperClass="mb-0i">
27
- <Input @value={{@resource.user.email}} @type="text" class="w-full form-input" placeholder={{t "common.email"}} disabled={{true}} />
28
- </InputGroup>
29
- <InputGroup @name={{t "common.phone"}} @wrapperClass="mb-0i">
30
- <PhoneInput @value={{@resource.user.phone}} @onInput={{fn (mut @resource.phone)}} class="form-input w-full" disabled={{true}} />
31
- </InputGroup>
32
- </div>
33
- {{/if}}
34
- </div>
35
- </ContentPanel>
36
-
37
2
  <ContentPanel @title={{t "driver.fields.driver-details"}} @open={{true}} @wrapperClass="bordered-top">
38
3
  <div class="flex flex-row space-x-3 mb-3">
39
4
  <div class="flex items-center justify-start rounded-full">
@@ -65,6 +30,26 @@
65
30
  </div>
66
31
  </div>
67
32
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs no-input-group-padding">
33
+ <InputGroup @name={{t "common.name"}} @required={{true}}>
34
+ <Input @value={{@resource.name}} @type="text" class="w-full form-input" placeholder={{t "common.name"}} disabled={{cannot-write @resource}} />
35
+ </InputGroup>
36
+ <InputGroup @name={{t "common.email"}}>
37
+ <Input
38
+ @value={{@resource.email}}
39
+ @type="text"
40
+ class="w-full form-input"
41
+ placeholder={{t "common.email"}}
42
+ disabled={{or (cannot-write @resource) @resource.is_staff_linked}}
43
+ />
44
+ </InputGroup>
45
+ <InputGroup @name={{t "common.phone"}}>
46
+ <PhoneInput @value={{@resource.phone}} @onInput={{fn (mut @resource.phone)}} class="form-input w-full" disabled={{or (cannot-write @resource) @resource.is_staff_linked}} />
47
+ </InputGroup>
48
+ {{#if @resource.is_staff_linked}}
49
+ <div class="lg:col-span-2 -mt-1 mb-2 text-xs text-gray-500 dark:text-gray-400" data-test-staff-linked-helper>{{t "profile-account.staff-linked-helper"}}</div>
50
+ {{else if @resource.isNew}}
51
+ <ProfileIdentityHint @email={{@resource.email}} @phone={{@resource.phone}} @type="driver" class="lg:col-span-2" />
52
+ {{/if}}
68
53
  <InputGroup @name={{t "common.internal-id"}}>
69
54
  <Input @value={{@resource.internal_id}} @type="text" class="w-full form-input" placeholder={{t "common.internal-id"}} disabled={{cannot-write @resource}} />
70
55
  </InputGroup>
@@ -3,67 +3,9 @@ import { inject as service } from '@ember/service';
3
3
  import { task } from 'ember-concurrency';
4
4
 
5
5
  export default class DriverFormComponent extends Component {
6
- @service store;
7
6
  @service fetch;
8
7
  @service currentUser;
9
8
  @service notifications;
10
- @service modalsManager;
11
- @service('universe/extension-manager') extensionManager;
12
-
13
- get userAccountActionButtons() {
14
- return [
15
- {
16
- icon: 'user-plus',
17
- size: 'xs',
18
- permission: 'iam create user',
19
- onClick: async () => {
20
- // Load IAM engine for user-form modal component
21
- await this.extensionManager.ensureEngineLoaded('@fleetbase/iam-engine');
22
-
23
- const user = this.store.createRecord('user', {
24
- status: 'pending',
25
- type: 'user',
26
- });
27
-
28
- this.modalsManager.show('modals/user-form', {
29
- title: 'Create a new user',
30
- user,
31
- formPermission: 'iam create user',
32
- uploadNewPhoto: (file) => {
33
- this.fetch.uploadFile.perform(
34
- file,
35
- {
36
- path: `uploads/${this.currentUser.companyId}/users/${user.slug}`,
37
- subject_uui: user.id,
38
- subject_type: 'user',
39
- type: 'user_photo',
40
- },
41
- (uploadedFile) => {
42
- user.setProperties({
43
- avatar_uuid: uploadedFile.id,
44
- avatar_url: uploadedFile.url,
45
- avatar: uploadedFile,
46
- });
47
- }
48
- );
49
- },
50
- confirm: async (modal) => {
51
- modal.startLoading();
52
-
53
- try {
54
- await user.save();
55
- this.notifications.success('New user created successfully!');
56
- modal.done();
57
- } catch (error) {
58
- this.notifications.serverError(error);
59
- modal.stopLoading();
60
- }
61
- },
62
- });
63
- },
64
- },
65
- ];
66
- }
67
9
 
68
10
  @task *handlePhotoUpload(file) {
69
11
  try {
@@ -17,7 +17,7 @@
17
17
  </div>
18
18
  <div class="flex flex-shrink-0 items-center gap-2 whitespace-nowrap">
19
19
  <Button @icon="refresh" @size="sm" @helpText={{t "common.reload"}} @onClick={{@onRefresh}} />
20
- <Button @icon="plus" @text="Connect Integration" @type="primary" @size="sm" @onClick={{this.createIntegration}} />
20
+ <Button @icon="plus" @text="Connect Integration" @type="primary" @size="sm" @permission="fleet-ops create fuel-provider-connection" @onClick={{this.createIntegration}} />
21
21
  </div>
22
22
  </div>
23
23
  </section>
@@ -151,14 +151,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
151
151
  this.createItem('menu.contacts', 'address-book', 'management.contacts', 'fleet-ops list contact', 'fleet-ops see contact'),
152
152
  this.createItem('menu.places', 'location-dot', 'management.places', 'fleet-ops list place', 'fleet-ops see place'),
153
153
  this.createItem('menu.fuel-reports', 'gas-pump', 'management.fuel-reports', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
154
- this.createItem('menu.fuel-transactions', 'credit-card', 'management.fuel-transactions', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
154
+ this.createItem('menu.fuel-transactions', 'credit-card', 'management.fuel-transactions', 'fleet-ops list fuel-provider-transaction', 'fleet-ops see fuel-provider-transaction'),
155
155
  this.createItem('menu.issues', 'triangle-exclamation', 'management.issues', 'fleet-ops list issue', 'fleet-ops see issue'),
156
156
  ]);
157
157
  }
158
158
 
159
159
  get maintenanceItems() {
160
160
  return this.withRegistryItems('maintenance', [
161
- this.createHubItem('Maintenance Hub', 'wrench', 'maintenance.index', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule', [
161
+ this.createHubItem('Maintenance Hub', 'wrench', 'maintenance.index', 'fleet-ops list work-order', 'fleet-ops see work-order', [
162
162
  'maintenance hub',
163
163
  'service readiness',
164
164
  'maintenance control panel',
@@ -182,7 +182,9 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
182
182
  get connectivityItems() {
183
183
  return this.withRegistryItems('connectivity', [
184
184
  this.createHubItem(this.intl.t('menu.telematics'), 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
185
- this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report', ['fuel integrations']),
185
+ this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-provider-connection', 'fleet-ops see fuel-provider-connection', [
186
+ 'fuel integrations',
187
+ ]),
186
188
  this.createItem('menu.devices', 'hard-drive', 'connectivity.devices', 'fleet-ops list device', 'fleet-ops see device'),
187
189
  this.createItem('menu.sensors', 'temperature-full', 'connectivity.sensors', 'fleet-ops list sensor', 'fleet-ops see sensor'),
188
190
  this.createItem('menu.events', 'stream', 'connectivity.events', 'fleet-ops list device-event', 'fleet-ops see device-event'),
@@ -191,18 +193,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
191
193
 
192
194
  get analyticsItems() {
193
195
  return this.withRegistryItems('analytics', [
194
- this.createHubItem('Dashboard', 'chart-line', 'analytics.index', 'iam list report', 'fleet-ops see report', ['dashboard', 'fleetops dashboard', 'metrics']),
195
- this.createItem('menu.reports', 'file-import', 'analytics.reports', 'iam list report', 'fleet-ops see report'),
196
+ this.createHubItem('Dashboard', 'chart-line', 'analytics.index', 'fleet-ops view analytics', 'fleet-ops see analytics', ['dashboard', 'fleetops dashboard', 'metrics']),
197
+ this.createItem('menu.reports', 'file-import', 'analytics.reports', 'iam list report', 'iam see report'),
196
198
  ]);
197
199
  }
198
200
 
199
201
  get settingsItems() {
200
202
  return this.withRegistryItems('settings', [
201
- this.createHubItem('Settings Hub', 'sliders', 'settings.index', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings', [
202
- 'settings hub',
203
- 'configuration dashboard',
204
- 'setup focus',
205
- ]),
203
+ this.createHubItem('Settings Hub', 'sliders', 'settings.index', null, null, ['settings hub', 'configuration dashboard', 'setup focus']),
206
204
  this.createItem('menu.navigator-app', 'location-arrow', 'settings.navigator-app', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings'),
207
205
  this.createItem('menu.map', 'map', 'settings.map', 'fleet-ops view map-settings', 'fleet-ops see map-settings'),
208
206
  this.createItem('menu.payments', 'cash-register', 'settings.payments', 'fleet-ops view payments', 'fleet-ops see payments'),
@@ -210,8 +208,9 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
210
208
  this.createItem('menu.routing', 'route', 'settings.routing', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
211
209
  this.createItem('menu.orchestrator', 'circle-nodes', 'settings.orchestrator', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
212
210
  this.createItem('menu.scheduling', 'calendar-days', 'settings.scheduling', 'fleet-ops view scheduling-settings', 'fleet-ops see scheduling-settings'),
213
- this.createItem('menu.custom-fields', 'pen-to-square', 'settings.custom-fields', 'fleet-ops view custom-field', 'fleet-ops see custom-field'),
214
- this.createItem('menu.avatars', 'icons', 'settings.avatars', 'fleet-ops view avatar', 'fleet-ops see avatar'),
211
+ this.createItem('menu.telematics-settings', 'satellite-dish', 'settings.telematics', 'fleet-ops view telematics-settings', 'fleet-ops see telematics-settings'),
212
+ this.createItem('menu.custom-fields', 'pen-to-square', 'settings.custom-fields', 'fleet-ops list custom-field', 'fleet-ops see custom-field'),
213
+ this.createItem('menu.avatars', 'icons', 'settings.avatars', 'fleet-ops list avatar', 'fleet-ops see avatar'),
215
214
  ]);
216
215
  }
217
216
 
@@ -265,18 +264,36 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
265
264
  }
266
265
 
267
266
  createBranch({ id, label, icon, route, defaultRoute, requiresVisibleChildren = false, children, keywords = [] }) {
267
+ const visibleChildren = children.filter((item) => item.visible !== false);
268
+
268
269
  return {
269
270
  id,
270
271
  label,
271
272
  icon,
272
273
  route: this.fullRoute(route),
273
- defaultRoute: this.fullRoute(defaultRoute),
274
+ defaultRoute: this.resolveDefaultRoute(this.fullRoute(defaultRoute), visibleChildren),
274
275
  requiresVisibleChildren,
275
- children: children.filter((item) => item.visible !== false),
276
+ children: visibleChildren,
276
277
  keywords,
277
278
  };
278
279
  }
279
280
 
281
+ /**
282
+ * Keep the branch's default route when the user may open it; otherwise fall back to the
283
+ * first child route they are permitted to open, so clicking the branch does not land on a
284
+ * route guard that bounces them back out.
285
+ */
286
+ resolveDefaultRoute(defaultRoute, children = []) {
287
+ const permitted = (item) => [item.visiblePermission, item.permission].every((permission) => !permission || this.abilities.can(permission));
288
+ const defaultChild = children.find((item) => item.route === defaultRoute);
289
+
290
+ if (!defaultChild || permitted(defaultChild)) {
291
+ return defaultRoute;
292
+ }
293
+
294
+ return children.find((item) => item.route && permitted(item))?.route ?? defaultRoute;
295
+ }
296
+
280
297
  createItem(intl, icon, route, permission, ability, keywords = []) {
281
298
  return {
282
299
  priority: this.defaultPriorityForRoute(route),
@@ -290,7 +307,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
290
307
  };
291
308
  }
292
309
 
293
- createHubItem(label, icon, route, _permission, _ability, keywords = []) {
310
+ createHubItem(label, icon, route, permission, ability, keywords = []) {
294
311
  return {
295
312
  pinnedFirst: true,
296
313
  priority: this.defaultPriorityForRoute(route),
@@ -298,6 +315,8 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
298
315
  description: label,
299
316
  icon,
300
317
  route: this.fullRoute(route),
318
+ permission,
319
+ visiblePermission: ability,
301
320
  isNavigationHub: true,
302
321
  keywords: [label, route, ...keywords].filter(Boolean),
303
322
  };
@@ -387,8 +406,9 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
387
406
  'settings.routing': 5,
388
407
  'settings.orchestrator': 6,
389
408
  'settings.scheduling': 7,
390
- 'settings.custom-fields': 8,
391
- 'settings.avatars': 9,
409
+ 'settings.telematics': 8,
410
+ 'settings.custom-fields': 9,
411
+ 'settings.avatars': 10,
392
412
  };
393
413
 
394
414
  return priorities[route] ?? 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();