@fleetbase/fleetops-engine 0.6.63 → 0.6.65

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 (289) hide show
  1. package/RELEASE.md +7 -6
  2. package/addon/components/attachment-list.hbs +65 -0
  3. package/addon/components/attachment-list.js +8 -0
  4. package/addon/components/cell/attached-vehicle.hbs +1 -1
  5. package/addon/components/cell/attached-vehicle.js +3 -3
  6. package/addon/components/cell/driver-identity.js +4 -4
  7. package/addon/components/cell/driver-name.hbs +1 -1
  8. package/addon/components/cell/translated-value.hbs +9 -0
  9. package/addon/components/cell/translated-value.js +57 -0
  10. package/addon/components/contact/card.hbs +58 -0
  11. package/addon/components/contact/card.js +19 -0
  12. package/addon/components/contact/form.hbs +1 -1
  13. package/addon/components/contact/panel-header.hbs +1 -1
  14. package/addon/components/customer/form.hbs +2 -2
  15. package/addon/components/device/manager.hbs +26 -28
  16. package/addon/components/device/manager.js +28 -2
  17. package/addon/components/device/panel-tabs/vehicle.hbs +13 -13
  18. package/addon/components/device/panel-tabs/vehicle.js +8 -0
  19. package/addon/components/driver/card.hbs +24 -13
  20. package/addon/components/driver/card.js +20 -6
  21. package/addon/components/driver/form.hbs +3 -3
  22. package/addon/components/driver/panel-header.hbs +2 -2
  23. package/addon/components/driver/pill.hbs +2 -2
  24. package/addon/components/equipment/form.js +11 -4
  25. package/addon/components/equipment/pill.hbs +20 -0
  26. package/addon/components/equipment/pill.js +3 -0
  27. package/addon/components/filter/multi-model.hbs +19 -0
  28. package/addon/components/filter/multi-model.js +90 -0
  29. package/addon/components/fleet/driver-listing.hbs +1 -1
  30. package/addon/components/fleet/vehicle-listing.hbs +1 -1
  31. package/addon/components/fleet-panel/driver-listing.hbs +1 -1
  32. package/addon/components/fleet-panel/vehicle-listing.hbs +1 -1
  33. package/addon/components/layout/fleet-ops-sidebar.js +9 -7
  34. package/addon/components/maintenance/form.js +12 -4
  35. package/addon/components/maintenance-schedule/form.js +13 -7
  36. package/addon/components/map/leaflet-live-map.hbs +52 -0
  37. package/addon/components/map/order-list-overlay/order.hbs +4 -4
  38. package/addon/components/modals/attach-equipment.hbs +22 -0
  39. package/addon/components/modals/attach-equipment.js +3 -0
  40. package/addon/components/modals/attach-telematic-device.hbs +13 -8
  41. package/addon/components/modals/attach-telematic-device.js +11 -1
  42. package/addon/components/modals/attach-trailer.hbs +30 -0
  43. package/addon/components/modals/attach-trailer.js +3 -0
  44. package/addon/components/modals/contact-form.hbs +2 -2
  45. package/addon/components/modals/convert-customer-to-vendor.hbs +1 -1
  46. package/addon/components/modals/driver-form.hbs +2 -2
  47. package/addon/components/modals/select-trailer.hbs +35 -0
  48. package/addon/components/modals/select-trailer.js +3 -0
  49. package/addon/components/modals/vehicle-form.hbs +2 -2
  50. package/addon/components/orchestrator/resource-panel.hbs +2 -2
  51. package/addon/components/order/customer-avatar-stack.hbs +2 -2
  52. package/addon/components/order/details/detail.hbs +4 -4
  53. package/addon/components/order/details/payload.hbs +2 -2
  54. package/addon/components/order/kanban-card.hbs +4 -4
  55. package/addon/components/order-list-overlay/order.hbs +4 -4
  56. package/addon/components/trailer/card.hbs +68 -0
  57. package/addon/components/trailer/card.js +22 -0
  58. package/addon/components/trailer/details/connections.hbs +57 -0
  59. package/addon/components/trailer/details/connections.js +59 -0
  60. package/addon/components/trailer/details/equipment.hbs +25 -0
  61. package/addon/components/trailer/details/equipment.js +52 -0
  62. package/addon/components/trailer/details/maintenance-history.hbs +47 -0
  63. package/addon/components/trailer/details/maintenance-history.js +40 -0
  64. package/addon/components/trailer/details/schedules.hbs +45 -0
  65. package/addon/components/trailer/details/schedules.js +40 -0
  66. package/addon/components/trailer/details/work-orders.hbs +47 -0
  67. package/addon/components/trailer/details/work-orders.js +40 -0
  68. package/addon/components/trailer/details.hbs +399 -0
  69. package/addon/components/trailer/details.js +118 -0
  70. package/addon/components/trailer/form.hbs +447 -0
  71. package/addon/components/trailer/form.js +165 -0
  72. package/addon/components/trailer/panel-header.hbs +45 -0
  73. package/addon/components/trailer/panel-header.js +3 -0
  74. package/addon/components/trailer/pill.hbs +21 -0
  75. package/addon/components/trailer/pill.js +3 -0
  76. package/addon/components/vehicle/card.hbs +42 -11
  77. package/addon/components/vehicle/card.js +31 -0
  78. package/addon/components/vehicle/details/equipment.hbs +25 -0
  79. package/addon/components/vehicle/details/equipment.js +55 -0
  80. package/addon/components/vehicle/details/trailers.hbs +35 -0
  81. package/addon/components/vehicle/details/trailers.js +55 -0
  82. package/addon/components/vehicle/form.hbs +2 -2
  83. package/addon/components/vehicle/panel-header.hbs +2 -2
  84. package/addon/components/vehicle/pill.hbs +2 -2
  85. package/addon/components/vendor/card.hbs +58 -0
  86. package/addon/components/vendor/card.js +6 -0
  87. package/addon/components/vendor/panel-header.hbs +2 -2
  88. package/addon/components/vendor/personnel-manager.hbs +2 -2
  89. package/addon/components/work-order/form.js +12 -4
  90. package/addon/controllers/connectivity/devices/index/details/vehicle.js +11 -0
  91. package/addon/controllers/connectivity/devices/index/details.js +3 -3
  92. package/addon/controllers/connectivity/devices/index.js +1 -0
  93. package/addon/controllers/connectivity/telematics/details/attachments.js +28 -47
  94. package/addon/controllers/connectivity/telematics/details/devices.js +1 -0
  95. package/addon/controllers/management/contacts/customers.js +4 -0
  96. package/addon/controllers/management/contacts/index.js +32 -0
  97. package/addon/controllers/management/trailers/index/details.js +119 -0
  98. package/addon/controllers/management/trailers/index/edit.js +67 -0
  99. package/addon/controllers/management/trailers/index/new.js +43 -0
  100. package/addon/controllers/management/trailers/index.js +599 -0
  101. package/addon/controllers/management/vehicles/index/details.js +16 -0
  102. package/addon/controllers/management/vehicles/index.js +78 -4
  103. package/addon/controllers/management/vendors/index.js +32 -0
  104. package/addon/controllers/operations/orders/index.js +3 -1
  105. package/addon/controllers/settings/custom-fields.js +8 -0
  106. package/addon/extension.js +7 -0
  107. package/addon/helpers/placeholder-image.js +6 -0
  108. package/addon/helpers/resource-image.js +10 -0
  109. package/addon/helpers/trailer-placeholder-image.js +6 -0
  110. package/addon/routes/management/trailers/index/details/connections.js +2 -0
  111. package/addon/routes/management/trailers/index/details/devices.js +2 -0
  112. package/addon/routes/management/trailers/index/details/equipment.js +2 -0
  113. package/addon/routes/management/trailers/index/details/index.js +2 -0
  114. package/addon/routes/management/trailers/index/details/maintenance-history.js +2 -0
  115. package/addon/routes/management/trailers/index/details/positions.js +2 -0
  116. package/addon/routes/management/trailers/index/details/schedules.js +2 -0
  117. package/addon/routes/management/trailers/index/details/work-orders.js +2 -0
  118. package/addon/routes/management/trailers/index/details.js +30 -0
  119. package/addon/routes/management/trailers/index/edit.js +23 -0
  120. package/addon/routes/management/trailers/index/new.js +14 -0
  121. package/addon/routes/management/trailers/index.js +42 -0
  122. package/addon/routes/management/vehicles/index/details/trailers.js +2 -0
  123. package/addon/routes/management/vehicles/index.js +7 -1
  124. package/addon/routes.js +17 -0
  125. package/addon/services/device-actions.js +26 -16
  126. package/addon/services/trailer-actions.js +394 -0
  127. package/addon/services/vehicle-actions.js +111 -0
  128. package/addon/styles/fleetops-engine.css +349 -0
  129. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +19 -19
  130. package/addon/templates/connectivity/telematics/details/attachments.hbs +15 -15
  131. package/addon/templates/management/contacts/customers.hbs +48 -29
  132. package/addon/templates/management/contacts/index.hbs +52 -33
  133. package/addon/templates/management/trailers/index/details/connections.hbs +1 -0
  134. package/addon/templates/management/trailers/index/details/devices.hbs +1 -0
  135. package/addon/templates/management/trailers/index/details/equipment.hbs +1 -0
  136. package/addon/templates/management/trailers/index/details/index.hbs +1 -0
  137. package/addon/templates/management/trailers/index/details/maintenance-history.hbs +1 -0
  138. package/addon/templates/management/trailers/index/details/positions.hbs +1 -0
  139. package/addon/templates/management/trailers/index/details/schedules.hbs +1 -0
  140. package/addon/templates/management/trailers/index/details/work-orders.hbs +1 -0
  141. package/addon/templates/management/trailers/index/details.hbs +15 -0
  142. package/addon/templates/management/trailers/index/edit.hbs +12 -0
  143. package/addon/templates/management/trailers/index/new.hbs +11 -0
  144. package/addon/templates/management/trailers/index.hbs +67 -0
  145. package/addon/templates/management/vehicles/index/details/equipment.hbs +1 -1
  146. package/addon/templates/management/vehicles/index/details/trailers.hbs +1 -0
  147. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  148. package/addon/templates/management/vendors/index.hbs +66 -32
  149. package/addon/utils/device-table-columns.js +10 -6
  150. package/addon/utils/live-map-card-content.js +56 -0
  151. package/addon/utils/placeholder-images.js +71 -0
  152. package/addon/utils/trailer-placeholder-image.js +11 -0
  153. package/app/components/attachment-list.js +1 -0
  154. package/app/components/cell/translated-value.js +1 -0
  155. package/app/components/contact/card.js +1 -0
  156. package/app/components/equipment/pill.js +1 -0
  157. package/app/components/filter/multi-model.js +1 -0
  158. package/app/components/modals/attach-equipment.js +1 -0
  159. package/app/components/modals/attach-trailer.js +1 -0
  160. package/app/components/modals/select-trailer.js +1 -0
  161. package/app/components/trailer/card.js +1 -0
  162. package/app/components/trailer/details/connections.js +1 -0
  163. package/app/components/trailer/details/equipment.js +1 -0
  164. package/app/components/trailer/details/maintenance-history.js +1 -0
  165. package/app/components/trailer/details/schedules.js +1 -0
  166. package/app/components/trailer/details/work-orders.js +1 -0
  167. package/app/components/trailer/details.js +1 -0
  168. package/app/components/trailer/form.js +1 -0
  169. package/app/components/trailer/panel-header.js +1 -0
  170. package/app/components/trailer/pill.js +1 -0
  171. package/app/components/vehicle/details/equipment.js +1 -0
  172. package/app/components/vehicle/details/trailers.js +1 -0
  173. package/app/components/vendor/card.js +1 -0
  174. package/app/controllers/management/trailers/index/details.js +1 -0
  175. package/app/controllers/management/trailers/index/edit.js +1 -0
  176. package/app/controllers/management/trailers/index/new.js +1 -0
  177. package/app/controllers/management/trailers/index.js +1 -0
  178. package/app/helpers/placeholder-image.js +1 -0
  179. package/app/helpers/resource-image.js +1 -0
  180. package/app/helpers/trailer-placeholder-image.js +1 -0
  181. package/app/routes/management/trailers/index/details/connections.js +1 -0
  182. package/app/routes/management/trailers/index/details/devices.js +1 -0
  183. package/app/routes/management/trailers/index/details/equipment.js +1 -0
  184. package/app/routes/management/trailers/index/details/index.js +1 -0
  185. package/app/routes/management/trailers/index/details/maintenance-history.js +1 -0
  186. package/app/routes/management/trailers/index/details/positions.js +1 -0
  187. package/app/routes/management/trailers/index/details/schedules.js +1 -0
  188. package/app/routes/management/trailers/index/details/work-orders.js +1 -0
  189. package/app/routes/management/trailers/index/details.js +1 -0
  190. package/app/routes/management/trailers/index/edit.js +1 -0
  191. package/app/routes/management/trailers/index/new.js +1 -0
  192. package/app/routes/management/trailers/index.js +1 -0
  193. package/app/routes/management/vehicles/index/details/trailers.js +1 -0
  194. package/app/services/trailer-actions.js +1 -0
  195. package/app/templates/management/trailers/index/details/connections.js +1 -0
  196. package/app/templates/management/trailers/index/details/devices.js +1 -0
  197. package/app/templates/management/trailers/index/details/equipment.js +1 -0
  198. package/app/templates/management/trailers/index/details/index.js +1 -0
  199. package/app/templates/management/trailers/index/details/maintenance-history.js +1 -0
  200. package/app/templates/management/trailers/index/details/positions.js +1 -0
  201. package/app/templates/management/trailers/index/details/schedules.js +1 -0
  202. package/app/templates/management/trailers/index/details/work-orders.js +1 -0
  203. package/app/templates/management/trailers/index/details.js +1 -0
  204. package/app/templates/management/trailers/index/edit.js +1 -0
  205. package/app/templates/management/trailers/index/new.js +1 -0
  206. package/app/templates/management/trailers/index.js +1 -0
  207. package/app/templates/management/vehicles/index/details/trailers.js +1 -0
  208. package/composer.json +1 -1
  209. package/extension.json +1 -1
  210. package/package.json +1 -1
  211. package/server/migrations/2026_09_03_000001_add_first_class_trailer_columns_to_assets_table.php +58 -0
  212. package/server/migrations/2026_09_03_000002_create_asset_connections_table.php +43 -0
  213. package/server/migrations/2026_09_03_000003_create_device_installations_table.php +33 -0
  214. package/server/src/Auth/Schemas/FleetOps.php +7 -0
  215. package/server/src/Events/TrailerLocationChanged.php +70 -0
  216. package/server/src/Exceptions/DeviceAlreadyAttachedException.php +18 -0
  217. package/server/src/Exports/TrailerExport.php +31 -0
  218. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +8 -2
  219. package/server/src/Http/Controllers/Api/v1/DeviceController.php +22 -15
  220. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +47 -0
  221. package/server/src/Http/Controllers/Api/v1/TrailerController.php +362 -0
  222. package/server/src/Http/Controllers/Api/v1/VehicleController.php +1 -0
  223. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +3 -0
  224. package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
  225. package/server/src/Http/Controllers/Internal/v1/SearchController.php +16 -0
  226. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +360 -0
  227. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +74 -0
  228. package/server/src/Http/Filter/EquipmentFilter.php +51 -0
  229. package/server/src/Http/Filter/OrderFilter.php +15 -0
  230. package/server/src/Http/Filter/TrailerFilter.php +269 -0
  231. package/server/src/Http/Filter/VehicleFilter.php +70 -3
  232. package/server/src/Http/Requests/CreateDeviceRequest.php +1 -1
  233. package/server/src/Http/Requests/CreateEquipmentRequest.php +1 -1
  234. package/server/src/Http/Requests/CreateTrailerRequest.php +54 -0
  235. package/server/src/Http/Requests/UpdateTrailerRequest.php +7 -0
  236. package/server/src/Http/Resources/v1/AssetConnection.php +90 -0
  237. package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +29 -3
  238. package/server/src/Http/Resources/v1/Device.php +3 -1
  239. package/server/src/Http/Resources/v1/Equipment.php +3 -1
  240. package/server/src/Http/Resources/v1/Fleet.php +3 -1
  241. package/server/src/Http/Resources/v1/Index/Vehicle.php +10 -0
  242. package/server/src/Http/Resources/v1/Trailer.php +199 -0
  243. package/server/src/Http/Resources/v1/Vehicle.php +2 -0
  244. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +3 -0
  245. package/server/src/Imports/TrailerImport.php +33 -0
  246. package/server/src/Models/Asset.php +39 -10
  247. package/server/src/Models/AssetConnection.php +43 -0
  248. package/server/src/Models/Device.php +39 -8
  249. package/server/src/Models/DeviceInstallation.php +27 -0
  250. package/server/src/Models/Equipment.php +2 -0
  251. package/server/src/Models/Trailer.php +181 -0
  252. package/server/src/Models/Vehicle.php +13 -0
  253. package/server/src/Providers/FleetOpsServiceProvider.php +4 -2
  254. package/server/src/Support/Ai/FleetOpsAiQueryResources.php +22 -0
  255. package/server/src/Support/Reporting/FleetOpsReportSchema.php +38 -0
  256. package/server/src/Support/Telematics/TelematicService.php +35 -0
  257. package/server/src/Support/TrailerSummary.php +35 -0
  258. package/server/src/routes.php +29 -0
  259. package/server/tests/CompactResourceSerializationTest.php +36 -0
  260. package/server/tests/ControllerFilterContractsTest.php +29 -0
  261. package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +30 -13
  262. package/server/tests/Feature/Http/Api/TrailerControllerLifecycleTest.php +640 -0
  263. package/server/tests/Feature/Http/Api/VehiclePublicContractTest.php +23 -0
  264. package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +20 -5
  265. package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +19 -0
  266. package/server/tests/ModelAccessorContractsTest.php +10 -0
  267. package/server/tests/OrderFilterExecutionTest.php +5 -0
  268. package/server/tests/PublicApiControllerInputContractsTest.php +11 -0
  269. package/server/tests/PublicFleetOpsRestEndpointsTest.php +15 -3
  270. package/server/tests/ReportSchemaContractsTest.php +4 -0
  271. package/server/tests/TelematicsHardeningTest.php +4 -2
  272. package/server/tests/TrailerSupportCoverageTest.php +524 -0
  273. package/server/tests/Unit/Http/Resources/FleetResourceTest.php +17 -0
  274. package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +35 -0
  275. package/server/tests/Unit/Models/EquipmentTest.php +22 -0
  276. package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +7 -3
  277. package/server/tests/Unit/Models/TrailerTest.php +204 -0
  278. package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +93 -0
  279. package/server/tests/VehicleControllerHelperContractsTest.php +14 -0
  280. package/server/tests/VehicleResourceContractsTest.php +39 -0
  281. package/translations/ar-ae.yml +396 -0
  282. package/translations/bg-bg.yaml +424 -0
  283. package/translations/en-us.yaml +423 -0
  284. package/translations/es-pa.yaml +425 -0
  285. package/translations/fr-fr.yaml +424 -0
  286. package/translations/mn-mn.yaml +424 -0
  287. package/translations/pt-br.yaml +424 -0
  288. package/translations/ru-ru.yaml +424 -0
  289. package/translations/vi-vn.yaml +435 -0
@@ -8534,3 +8534,352 @@ body[data-theme='dark'] .fleetbase-ai-create-order-preview__cancelled {
8534
8534
  animation: none;
8535
8535
  }
8536
8536
  }
8537
+
8538
+ /* ── Trailer lifecycle, attachment & towing badges ──
8539
+ ember-ui's badge palette has no entries for these values, so they would
8540
+ otherwise render as unstyled `x-status-badge` elements. Colors follow the
8541
+ ember-ui badge.css convention (800 background / 700 border / 100 text). */
8542
+ .status-badge.attached-status-badge > span,
8543
+ .status-badge.in-use-status-badge > span {
8544
+ background-color: #1e40af;
8545
+ border-color: #1d4ed8;
8546
+ color: #dbeafe;
8547
+ }
8548
+
8549
+ .status-badge.attached-status-badge > span svg,
8550
+ .status-badge.in-use-status-badge > span svg {
8551
+ color: #93c5fd;
8552
+ }
8553
+
8554
+ .status-badge.detached-status-badge > span,
8555
+ .status-badge.retired-status-badge > span {
8556
+ background-color: #1f2937;
8557
+ border-color: #374151;
8558
+ color: #f3f4f6;
8559
+ }
8560
+
8561
+ .status-badge.detached-status-badge > span svg,
8562
+ .status-badge.retired-status-badge > span svg {
8563
+ color: #d1d5db;
8564
+ }
8565
+
8566
+ .status-badge.out-of-service-status-badge > span {
8567
+ background-color: #991b1b;
8568
+ border-color: #b91c1c;
8569
+ color: #fee2e2;
8570
+ }
8571
+
8572
+ .status-badge.out-of-service-status-badge > span svg {
8573
+ color: #fca5a5;
8574
+ }
8575
+
8576
+ /* ── Attachment lists (devices, equipment, trailers, towing history) ── */
8577
+ .fleetops-attachment-list {
8578
+ display: flex;
8579
+ flex-direction: column;
8580
+ }
8581
+
8582
+ .fleetops-attachment-list-header {
8583
+ display: flex;
8584
+ flex-direction: row;
8585
+ align-items: flex-start;
8586
+ justify-content: space-between;
8587
+ gap: 0.75rem;
8588
+ padding: 1rem 1rem 0.75rem;
8589
+ }
8590
+
8591
+ .fleetops-attachment-list-loading {
8592
+ display: flex;
8593
+ align-items: center;
8594
+ justify-content: center;
8595
+ padding: 2.5rem 0;
8596
+ }
8597
+
8598
+ .fleetops-attachment-list-items {
8599
+ display: flex;
8600
+ flex-direction: column;
8601
+ }
8602
+
8603
+ .fleetops-attachment-list-item {
8604
+ display: flex;
8605
+ flex-direction: row;
8606
+ align-items: center;
8607
+ justify-content: space-between;
8608
+ gap: 0.75rem;
8609
+ padding: 0.75rem 1rem;
8610
+ border-top: 1px solid #e5e7eb;
8611
+ }
8612
+
8613
+ .fleetops-attachment-list-item:last-child {
8614
+ border-bottom: 1px solid #e5e7eb;
8615
+ }
8616
+
8617
+ body[data-theme='dark'] .fleetops-attachment-list-item,
8618
+ body[data-theme='dark'] .fleetops-attachment-list-item:last-child {
8619
+ border-color: #374151;
8620
+ }
8621
+
8622
+ .fleetops-attachment-list-item-content {
8623
+ display: flex;
8624
+ flex-flow: row wrap;
8625
+ align-items: center;
8626
+ gap: 0.375rem 0.75rem;
8627
+ min-width: 0;
8628
+ flex: 1 1 auto;
8629
+ }
8630
+
8631
+ .fleetops-attachment-list-item-actions {
8632
+ display: flex;
8633
+ flex-direction: row;
8634
+ align-items: center;
8635
+ gap: 0.25rem;
8636
+ flex-shrink: 0;
8637
+ }
8638
+
8639
+ .fleetops-attachment-list-item-badge {
8640
+ flex-shrink: 0;
8641
+ }
8642
+
8643
+ .fleetops-attachment-list-item-meta {
8644
+ display: flex;
8645
+ flex-flow: row wrap;
8646
+ align-items: center;
8647
+ gap: 0.25rem 0.75rem;
8648
+ min-width: 0;
8649
+ font-size: 0.75rem;
8650
+ line-height: 1rem;
8651
+ color: #6b7280;
8652
+ }
8653
+
8654
+ body[data-theme='dark'] .fleetops-attachment-list-item-meta {
8655
+ color: #9ca3af;
8656
+ }
8657
+
8658
+ .fleetops-attachment-list-item-meta-entry {
8659
+ display: inline-flex;
8660
+ align-items: center;
8661
+ white-space: nowrap;
8662
+ }
8663
+
8664
+ .fleetops-attachment-list-empty {
8665
+ display: flex;
8666
+ flex-direction: column;
8667
+ align-items: center;
8668
+ justify-content: center;
8669
+ text-align: center;
8670
+ margin: 0 1rem 1rem;
8671
+ padding: 3rem 1.5rem;
8672
+ border-radius: 0.5rem;
8673
+ border: 1px dashed #d1d5db;
8674
+ background-color: #fff;
8675
+ }
8676
+
8677
+ body[data-theme='dark'] .fleetops-attachment-list-empty {
8678
+ border-color: #4b5563;
8679
+ background-color: #1f2937;
8680
+ }
8681
+
8682
+ /* Towing history entries: vehicle on the left, connect/disconnect timeline beside it. */
8683
+ .fleetops-towing-entry {
8684
+ display: flex;
8685
+ flex-direction: column;
8686
+ gap: 0.75rem;
8687
+ width: 100%;
8688
+ min-width: 0;
8689
+ }
8690
+
8691
+ .fleetops-towing-entry-vehicle {
8692
+ flex-shrink: 0;
8693
+ min-width: 0;
8694
+ }
8695
+
8696
+ .fleetops-towing-entry-timeline {
8697
+ display: flex;
8698
+ flex-direction: column;
8699
+ gap: 0.25rem;
8700
+ min-width: 0;
8701
+ flex: 1 1 auto;
8702
+ }
8703
+
8704
+ @media (width >= 640px) {
8705
+ .fleetops-towing-entry {
8706
+ flex-direction: row;
8707
+ align-items: center;
8708
+ }
8709
+
8710
+ .fleetops-towing-entry-vehicle {
8711
+ width: 14rem;
8712
+ }
8713
+
8714
+ .fleetops-towing-entry-timeline {
8715
+ padding-left: 0.75rem;
8716
+ border-left: 2px solid #e5e7eb;
8717
+ }
8718
+
8719
+ body[data-theme='dark'] .fleetops-towing-entry-timeline {
8720
+ border-left-color: #374151;
8721
+ }
8722
+
8723
+ .fleetops-towing-entry--active .fleetops-towing-entry-timeline {
8724
+ border-left-color: #4ade80;
8725
+ }
8726
+
8727
+ body[data-theme='dark'] .fleetops-towing-entry--active .fleetops-towing-entry-timeline {
8728
+ border-left-color: #16a34a;
8729
+ }
8730
+ }
8731
+
8732
+ .fleetops-towing-entry-timeline-row {
8733
+ display: flex;
8734
+ flex-direction: row;
8735
+ align-items: center;
8736
+ gap: 0.5rem;
8737
+ font-size: 0.75rem;
8738
+ line-height: 1rem;
8739
+ color: #374151;
8740
+ }
8741
+
8742
+ body[data-theme='dark'] .fleetops-towing-entry-timeline-row {
8743
+ color: #e5e7eb;
8744
+ }
8745
+
8746
+ /* Resource cards (drivers, vehicles, trailers, vendors, contacts): one title row with an
8747
+ optional online indicator, an image body on the card background, and footer meta rows
8748
+ that style linked records the same way on every card. */
8749
+ .fleetops-card-title {
8750
+ display: flex;
8751
+ align-items: center;
8752
+ width: 100%;
8753
+ min-width: 0;
8754
+ }
8755
+
8756
+ .fleetops-card-title-indicator {
8757
+ margin-left: 0.5rem;
8758
+ flex: none;
8759
+ }
8760
+
8761
+ .fleetops-card-image {
8762
+ display: block;
8763
+ width: 100%;
8764
+ height: 100%;
8765
+ border-radius: 0;
8766
+ }
8767
+
8768
+ .fleetops-card-image--contain {
8769
+ object-fit: contain;
8770
+ padding: 0.5rem;
8771
+ }
8772
+
8773
+ .fleetops-card-image--cover {
8774
+ object-fit: cover;
8775
+ }
8776
+
8777
+ .fleetops-card-meta-list {
8778
+ display: flex;
8779
+ flex-direction: column;
8780
+ gap: 0.125rem;
8781
+ margin-top: 0.375rem;
8782
+ width: 100%;
8783
+ }
8784
+
8785
+ .fleetops-card-meta {
8786
+ display: flex;
8787
+ align-items: center;
8788
+ gap: 0.375rem;
8789
+ min-width: 0;
8790
+ font-size: 0.75rem;
8791
+ line-height: 1rem;
8792
+ color: #9ca3af;
8793
+ }
8794
+
8795
+ body[data-theme='dark'] .fleetops-card-meta {
8796
+ color: #6b7280;
8797
+ }
8798
+
8799
+ .fleetops-card-meta > svg {
8800
+ flex-shrink: 0;
8801
+ width: 0.75rem;
8802
+ }
8803
+
8804
+ .fleetops-card-meta-value {
8805
+ min-width: 0;
8806
+ overflow: hidden;
8807
+ text-overflow: ellipsis;
8808
+ white-space: nowrap;
8809
+ }
8810
+
8811
+ .fleetops-card-meta-link {
8812
+ color: #3b82f6;
8813
+ font-weight: 500;
8814
+ }
8815
+
8816
+ .fleetops-card-meta-link:hover {
8817
+ opacity: 0.75;
8818
+ }
8819
+
8820
+ body[data-theme='dark'] .fleetops-card-meta-link {
8821
+ color: #60a5fa;
8822
+ }
8823
+
8824
+ /* ── Multi-model table filter (mirrors ember-ui's .filter-multi-input clear button) ── */
8825
+ .filter-multi-model {
8826
+ position: relative;
8827
+ }
8828
+
8829
+ .filter-multi-model > .clear-button {
8830
+ position: absolute;
8831
+ top: 0;
8832
+ right: 0;
8833
+ padding: 0.5rem 0.75rem 0 0;
8834
+ color: #374151;
8835
+ }
8836
+
8837
+ .filter-multi-model > .clear-button:hover {
8838
+ opacity: 0.5;
8839
+ }
8840
+
8841
+ .filter-multi-model > .clear-button:disabled {
8842
+ opacity: 0.3;
8843
+ cursor: default;
8844
+ }
8845
+
8846
+ body[data-theme='dark'] .filter-multi-model > .clear-button {
8847
+ color: #6b7280;
8848
+ }
8849
+
8850
+ .filter-multi-model > .fleetbase-model-select,
8851
+ .filter-multi-model > .fleetbase-model-select > .ember-basic-dropdown,
8852
+ .filter-multi-model .ember-power-select-multiple-trigger {
8853
+ flex: 1 1 auto;
8854
+ width: 100%;
8855
+ min-width: 0;
8856
+ }
8857
+
8858
+ /* The small form-input height clips the multiple trigger and turns it into a scroller
8859
+ before anything is selected; let it start at the input height and grow with chips. */
8860
+ .filter-multi-model .ember-power-select-multiple-trigger {
8861
+ display: flex;
8862
+ align-items: center;
8863
+ height: auto !important;
8864
+ min-height: 2.125rem;
8865
+ max-height: none !important;
8866
+ overflow: visible !important;
8867
+ padding-top: 0.125rem;
8868
+ padding-bottom: 0.125rem;
8869
+ padding-right: 2.5rem;
8870
+ }
8871
+
8872
+ .filter-multi-model .ember-power-select-multiple-options {
8873
+ flex: 1 1 auto;
8874
+ min-width: 0;
8875
+ gap: 0.125rem;
8876
+ }
8877
+
8878
+ .filter-multi-model .ember-power-select-multiple-option {
8879
+ margin: 0.125rem 0.125rem 0.125rem 0;
8880
+ }
8881
+
8882
+ .filter-multi-model .ember-power-select-trigger-multiple-input {
8883
+ height: 1.625rem;
8884
+ min-width: 4rem;
8885
+ }
@@ -1,20 +1,20 @@
1
1
  <div class="flex flex-col gap-3 p-3">
2
2
  <div class="flex flex-col items-start justify-between gap-3">
3
3
  <div class="min-w-0">
4
- <h2 class="text-base font-bold text-gray-900 dark:text-gray-100">Vehicle Attachment</h2>
5
- <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Fleet context for telemetry from this device.</p>
4
+ <h2 class="text-base font-bold text-gray-900 dark:text-gray-100">{{t "device.attachment.title"}}</h2>
5
+ <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">{{t "device.attachment.description"}}</p>
6
6
  </div>
7
7
 
8
8
  {{#if this.hasVehicle}}
9
9
  <div class="flex flex-nowrap items-center gap-2 whitespace-nowrap">
10
10
  {{#if this.canOpenVehicle}}
11
- <Button @icon="eye" @text="Open Vehicle" @size="xs" @onClick={{this.openVehicle}} />
11
+ <Button @icon="eye" @text={{t "device.attachment.open"}} @size="xs" @onClick={{this.openVehicle}} />
12
12
  {{/if}}
13
13
  {{#if this.canLocateVehicle}}
14
- <Button @icon="location-dot" @text="Locate" @size="xs" @onClick={{this.locateVehicle}} />
14
+ <Button @icon="location-dot" @text={{t "device.attachment.locate"}} @size="xs" @onClick={{this.locateVehicle}} />
15
15
  {{/if}}
16
- <Button @icon="shuffle" @text="Change Vehicle" @size="xs" @onClick={{this.attachToVehicle}} />
17
- <Button @icon="unlink" @text="Detach" @size="xs" @type="danger" @onClick={{this.detachFromVehicle}} />
16
+ <Button @icon="shuffle" @text={{t "device.attachment.change"}} @size="xs" @onClick={{this.attachToVehicle}} />
17
+ <Button @icon="unlink" @text={{t "device.attachment.detach"}} @size="xs" @type="danger" @onClick={{this.detachFromVehicle}} />
18
18
  </div>
19
19
  {{/if}}
20
20
  </div>
@@ -23,8 +23,8 @@
23
23
  <section class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
24
24
  <div class="flex gap-3 p-3">
25
25
  <Image
26
- src={{this.vehiclePhotoUrl}}
27
- @fallbackSrc={{config "defaultValues.placeholderImage"}}
26
+ src={{resource-image this.vehiclePhotoUrl "vehicle"}}
27
+ @fallbackSrc={{(placeholder-image "vehicle")}}
28
28
  alt={{this.vehicleName}}
29
29
  class="h-20 w-20 flex-shrink-0 rounded-md object-cover shadow-sm"
30
30
  />
@@ -62,11 +62,11 @@
62
62
  <div class="truncate text-gray-800 dark:text-gray-100">{{n-a this.yearMakeModel}}</div>
63
63
  </div>
64
64
  <div>
65
- <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Attached Device</div>
65
+ <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">{{t "device.attachment.attached-device"}}</div>
66
66
  <div class="truncate text-gray-800 dark:text-gray-100">{{n-a this.device.displayName}}</div>
67
67
  </div>
68
68
  <div>
69
- <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Device Last Seen</div>
69
+ <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">{{t "device.attachment.last-seen"}}</div>
70
70
  <div class="truncate text-gray-800 dark:text-gray-100">{{n-a (format-date-fns this.device.last_online_at "dd MMM yyyy, HH:mm")}}</div>
71
71
  </div>
72
72
  </div>
@@ -77,11 +77,11 @@
77
77
  <section class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
78
78
  <div class="flex items-start justify-between gap-3 border-b border-gray-200 px-3 py-2 dark:border-gray-700">
79
79
  <div class="min-w-0">
80
- <h3 class="text-sm font-bold text-gray-900 dark:text-gray-100">Latest Positions</h3>
81
- <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Recent vehicle coordinates reported through this device attachment.</p>
80
+ <h3 class="text-sm font-bold text-gray-900 dark:text-gray-100">{{t "device.attachment.latest-positions"}}</h3>
81
+ <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">{{t "device.attachment.positions-description"}}</p>
82
82
  </div>
83
83
  {{#if this.canOpenVehicle}}
84
- <Button @icon="route" @text="Full History" @size="xs" @onClick={{this.openVehiclePositions}} />
84
+ <Button @icon="route" @text={{t "device.attachment.full-history"}} @size="xs" @onClick={{this.openVehiclePositions}} />
85
85
  {{/if}}
86
86
  </div>
87
87
 
@@ -125,9 +125,9 @@
125
125
  <Telematic::TabEmptyState
126
126
  @tone="info"
127
127
  @icon="location-dot"
128
- @title="No recent positions"
129
- @message="This attached vehicle does not have recent position history available yet."
130
- @primaryText="Locate Vehicle"
128
+ @title={{t "device.attachment.no-positions"}}
129
+ @message={{t "device.attachment.no-positions-message"}}
130
+ @primaryText={{t "device.attachment.locate"}}
131
131
  @primaryIcon="location-dot"
132
132
  @primaryAction={{this.locateVehicle}}
133
133
  />
@@ -138,9 +138,9 @@
138
138
  <Telematic::TabEmptyState
139
139
  @tone="warning"
140
140
  @icon="link-slash"
141
- @title="No vehicle attached"
142
- @message="Attach this device to a vehicle so telemetry has fleet context."
143
- @primaryText="Attach Vehicle"
141
+ @title={{t "device.attachment.none-title"}}
142
+ @message={{t "device.attachment.none-message"}}
143
+ @primaryText={{t "device.actions.attach-to-asset"}}
144
144
  @primaryIcon="link"
145
145
  @primaryAction={{this.attachToVehicle}}
146
146
  />
@@ -18,7 +18,7 @@
18
18
  @value={{this.query}}
19
19
  @type="text"
20
20
  class="form-input form-input-sm h-10 w-full text-sm"
21
- placeholder="Search vehicles or devices"
21
+ placeholder="Search assets or devices"
22
22
  {{on "input" this.updateQuery}}
23
23
  />
24
24
  <select class="form-select form-select-sm h-10 w-full text-sm" {{on "change" this.updateStatus}}>
@@ -83,10 +83,10 @@
83
83
  <p class="mt-1 truncate text-xs text-blue-600 dark:text-blue-300">
84
84
  Selected:
85
85
  {{this.selectedDeviceName}}.
86
- Choose a vehicle on the right.
86
+ Choose an asset on the right.
87
87
  </p>
88
88
  {{else}}
89
- <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Select a device, then choose a vehicle target.</p>
89
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Select a device, then choose a vehicle or trailer target.</p>
90
90
  {{/if}}
91
91
  </div>
92
92
  {{#if this.selectedDevice}}
@@ -139,7 +139,7 @@
139
139
  {{else}}
140
140
  <div class="p-4">
141
141
  <div class="rounded-md border border-green-200 bg-green-50 p-3 text-sm text-green-800 dark:border-green-700 dark:bg-green-900/20 dark:text-green-300">
142
- All visible synced devices are mapped to vehicles.
142
+ All visible synced devices are mapped to assets.
143
143
  </div>
144
144
  </div>
145
145
  {{/if}}
@@ -151,38 +151,38 @@
151
151
  <div class="flex items-start justify-between gap-3">
152
152
  <div class="min-w-0">
153
153
  <div class="flex items-center gap-2">
154
- <h4 class="text-sm font-bold text-gray-900 dark:text-gray-100">Vehicles</h4>
155
- <span class="text-xs font-semibold text-gray-500 dark:text-gray-400">{{this.vehicleGroups.length}} mapped</span>
154
+ <h4 class="text-sm font-bold text-gray-900 dark:text-gray-100">Assets</h4>
155
+ <span class="text-xs font-semibold text-gray-500 dark:text-gray-400">{{this.assetGroups.length}} mapped</span>
156
156
  </div>
157
157
  {{#if this.selectedDevice}}
158
158
  <p class="mt-1 truncate text-xs text-blue-600 dark:text-blue-300">
159
- Choose a vehicle for
159
+ Choose an asset for
160
160
  {{this.selectedDeviceName}}
161
161
  </p>
162
162
  {{else}}
163
- <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Attached devices grouped by vehicle.</p>
163
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Attached devices grouped by vehicle or trailer.</p>
164
164
  {{/if}}
165
165
  </div>
166
166
  </div>
167
167
  </div>
168
168
 
169
169
  <div class="h-[calc(100vh-24rem)] min-h-[28rem] overflow-y-auto">
170
- {{#if this.vehicleGroups.length}}
170
+ {{#if this.assetGroups.length}}
171
171
  <div class="divide-y divide-gray-200 dark:divide-gray-700">
172
- {{#each this.vehicleGroups as |group|}}
172
+ {{#each this.assetGroups as |group|}}
173
173
  <div class="border-l-2 border-transparent px-4 py-3 transition hover:border-blue-300 hover:bg-blue-50/40 dark:hover:bg-blue-900/10">
174
174
  <div class="mb-2 flex items-center justify-between gap-3">
175
175
  <div class="flex min-w-0 items-center gap-3">
176
176
  <Image
177
- src={{or group.vehicle.photo_url group.vehicle.avatar_url}}
178
- @fallbackSrc={{config "defaultValues.vehicleImage"}}
177
+ src={{resource-image (or group.asset.photo_url group.asset.avatar_url) (if (eq group.asset.asset_class "trailer") "trailer" "vehicle")}}
178
+ @fallbackSrc={{(placeholder-image (if (eq group.asset.asset_class "trailer") "trailer" "vehicle"))}}
179
179
  alt={{group.name}}
180
180
  class="h-10 w-9 flex-shrink-0 rounded-md object-cover"
181
181
  />
182
182
  <div class="min-w-0">
183
- <div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{or group.vehicle.displayName group.vehicle.display_name group.vehicle.name group.name}}</div>
183
+ <div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{or group.asset.displayName group.asset.display_name group.asset.name group.name}}</div>
184
184
  <div class="mt-0.5 truncate text-xs text-gray-500 dark:text-gray-400">
185
- {{n-a (or group.vehicle.plate_number group.vehicle.internal_id group.vehicle.call_sign group.vehicle.vin)}}
185
+ {{n-a (or group.asset.plate_number group.asset.internal_id group.asset.call_sign group.asset.vin)}}
186
186
  <span class="mx-1">·</span>
187
187
  {{group.devices.length}}
188
188
  devices
@@ -228,7 +228,7 @@
228
228
  {{else}}
229
229
  <div class="p-4">
230
230
  <div class="rounded-md border border-gray-200 bg-gray-50 p-4 text-sm text-gray-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300">
231
- No visible vehicle groups. Attach a device from the left pane or adjust the filters.
231
+ No visible asset groups. Attach a device from the left pane or adjust the filters.
232
232
  </div>
233
233
  </div>
234
234
  {{/if}}
@@ -1,30 +1,49 @@
1
- <Layout::Resource::Tabular
2
- @resource="contact"
3
- @title={{t "resource.customers"}}
4
- @searchQuery={{this.query}}
5
- @onSearch={{perform this.customerActions.controllerSearchTask this}}
6
- @data={{@model}}
7
- @columns={{this.columns}}
8
- @page={{this.page}}
9
- @onPageChange={{fn (mut this.page)}}
10
- @setupTable={{fn (mut this.table)}}
11
- @actionButtons={{this.actionButtons}}
12
- @bulkActions={{this.bulkActions}}
13
- @controller={{this}}
14
- @emptyStateComponent={{component
15
- "table/empty-state"
16
- icon="user-group"
17
- title="Add your first customer"
18
- description="Customers give orders an owner, contact details, and account context for service history."
19
- filteredTitle="No customers match your filters"
20
- filteredDescription="Adjust the search or filters to find another customer."
21
- primaryText="New customer"
22
- primaryIcon="plus"
23
- primaryAction=this.customerActions.transition.create
24
- secondaryText="Import"
25
- secondaryIcon="upload"
26
- secondaryAction=this.customerActions.import
27
- }}
28
- @withoutHeader={{true}}
29
- />
1
+ {{#if (eq this.layout "table")}}
2
+ <Layout::Resource::Tabular
3
+ @resource="contact"
4
+ @title={{t "resource.customers"}}
5
+ @searchQuery={{this.query}}
6
+ @onSearch={{perform this.customerActions.controllerSearchTask this}}
7
+ @data={{@model}}
8
+ @columns={{this.columns}}
9
+ @page={{this.page}}
10
+ @onPageChange={{fn (mut this.page)}}
11
+ @setupTable={{fn (mut this.table)}}
12
+ @actionButtons={{this.actionButtons}}
13
+ @bulkActions={{this.bulkActions}}
14
+ @controller={{this}}
15
+ @emptyStateComponent={{component
16
+ "table/empty-state"
17
+ icon="user-group"
18
+ title="Add your first customer"
19
+ description="Customers give orders an owner, contact details, and account context for service history."
20
+ filteredTitle="No customers match your filters"
21
+ filteredDescription="Adjust the search or filters to find another customer."
22
+ primaryText="New customer"
23
+ primaryIcon="plus"
24
+ primaryAction=this.customerActions.transition.create
25
+ secondaryText="Import"
26
+ secondaryIcon="upload"
27
+ secondaryAction=this.customerActions.import
28
+ }}
29
+ @withoutHeader={{true}}
30
+ />
31
+ {{else}}
32
+ <Layout::Section::Body class="overflow-y-scroll-i h-full">
33
+ <Layout::Resource::CardsGrid
34
+ @resource="contact"
35
+ @data={{@model}}
36
+ @columns="4"
37
+ @showPagination={{true}}
38
+ @page={{this.page}}
39
+ @onPageChange={{fn (mut this.page)}}
40
+ @paginationMeta={{@model.meta}}
41
+ @containerClass="p-4"
42
+ class="h-full"
43
+ as |Item|
44
+ >
45
+ <Contact::Card @resource={{Item.model}} @resourceActions={{this.customerActions}} @placeholderType="customer" />
46
+ </Layout::Resource::CardsGrid>
47
+ </Layout::Section::Body>
48
+ {{/if}}
30
49
  {{outlet}}