@fleetbase/fleetops-engine 0.6.54 → 0.6.56

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 (220) hide show
  1. package/addon/components/admin/map-settings.hbs +1 -1
  2. package/addon/components/admin/routing-settings.hbs +2 -1
  3. package/addon/components/cell/attached-vehicle.hbs +10 -0
  4. package/addon/components/cell/attached-vehicle.js +58 -0
  5. package/addon/components/cell/device-identity.hbs +24 -0
  6. package/addon/components/cell/device-identity.js +129 -0
  7. package/addon/components/cell/driver-identity.hbs +33 -0
  8. package/addon/components/cell/driver-identity.js +143 -0
  9. package/addon/components/cell/equipment-identity.hbs +5 -0
  10. package/addon/components/cell/equipment-identity.js +48 -0
  11. package/addon/components/cell/part-identity.hbs +5 -0
  12. package/addon/components/cell/part-identity.js +63 -0
  13. package/addon/components/cell/telematic-device.hbs +38 -0
  14. package/addon/components/cell/telematic-device.js +44 -0
  15. package/addon/components/cell/telematic-provider.hbs +33 -16
  16. package/addon/components/cell/telematic-provider.js +50 -8
  17. package/addon/components/cell/vehicle-identity.hbs +33 -0
  18. package/addon/components/cell/vehicle-identity.js +138 -0
  19. package/addon/components/device/details.js +1 -1
  20. package/addon/components/device/form.hbs +2 -2
  21. package/addon/components/device/form.js +7 -0
  22. package/addon/components/device/panel-header.hbs +45 -20
  23. package/addon/components/device/panel-header.js +49 -1
  24. package/addon/components/device/panel-tabs/events.hbs +30 -0
  25. package/addon/components/device/panel-tabs/events.js +128 -0
  26. package/addon/components/device/panel-tabs/sensors.hbs +30 -0
  27. package/addon/components/device/panel-tabs/sensors.js +93 -0
  28. package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
  29. package/addon/components/device/panel-tabs/vehicle.js +90 -0
  30. package/addon/components/device-event/details.hbs +192 -0
  31. package/addon/components/device-event/details.js +151 -0
  32. package/addon/components/driver/schedule.js +1 -1
  33. package/addon/components/equipment/form.js +8 -1
  34. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  35. package/addon/components/layout/fleet-ops-sidebar.js +20 -6
  36. package/addon/components/map/drawer/device-event-listing.js +2 -0
  37. package/addon/components/map/drawer/driver-listing.js +2 -0
  38. package/addon/components/map/drawer/place-listing.js +2 -0
  39. package/addon/components/map/drawer/position-listing.hbs +11 -6
  40. package/addon/components/map/drawer/position-listing.js +17 -4
  41. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  42. package/addon/components/sensor/panel-header.hbs +47 -15
  43. package/addon/components/sensor/panel-header.js +59 -1
  44. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  45. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  46. package/addon/components/table/cell/dropdown.hbs +28 -0
  47. package/addon/components/table/cell/dropdown.js +91 -0
  48. package/addon/components/telematic/details.hbs +1 -1
  49. package/addon/components/telematic/form.hbs +2 -2
  50. package/addon/components/telematic/settings.hbs +1 -1
  51. package/addon/components/vendor/panel-header.hbs +82 -0
  52. package/addon/components/vendor/panel-header.js +48 -0
  53. package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
  54. package/addon/controllers/connectivity/devices/index/details/vehicle.js +50 -2
  55. package/addon/controllers/connectivity/devices/index.js +144 -129
  56. package/addon/controllers/connectivity/events/details.js +70 -0
  57. package/addon/controllers/connectivity/events/index.js +187 -11
  58. package/addon/controllers/connectivity/sensors/index.js +160 -14
  59. package/addon/controllers/connectivity/telematics/details/devices.js +126 -134
  60. package/addon/controllers/connectivity/telematics/details/events.js +1 -1
  61. package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
  62. package/addon/controllers/maintenance/equipment/index.js +1 -2
  63. package/addon/controllers/maintenance/parts/index.js +1 -2
  64. package/addon/controllers/management/drivers/index.js +82 -42
  65. package/addon/controllers/management/fuel-reports/index.js +63 -4
  66. package/addon/controllers/management/issues/index.js +63 -4
  67. package/addon/controllers/management/vehicles/index.js +33 -6
  68. package/addon/controllers/settings/map.js +15 -0
  69. package/addon/instance-initializers/register-vroom-allocation.js +1 -1
  70. package/addon/routes/connectivity/devices/index/details/vehicle.js +24 -2
  71. package/addon/routes/connectivity/devices/index.js +7 -0
  72. package/addon/routes/connectivity/events/details.js +28 -0
  73. package/addon/routes/connectivity/events/index.js +6 -2
  74. package/addon/routes/connectivity/sensors/index.js +7 -2
  75. package/addon/routes/connectivity/telematics/details/devices.js +2 -0
  76. package/addon/routes/management/vehicles/index/details.js +3 -1
  77. package/addon/services/device-actions.js +30 -0
  78. package/addon/services/device-event-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +1 -0
  80. package/addon/services/map-adapter/google.js +25 -0
  81. package/addon/services/map-adapter/leaflet.js +1 -1
  82. package/addon/services/map-adapter-interface.js +1 -1
  83. package/addon/services/map-manager.js +4 -1
  84. package/addon/services/map-settings.js +15 -0
  85. package/addon/services/sensor-actions.js +3 -0
  86. package/addon/services/service-rate-actions.js +39 -1
  87. package/addon/services/vehicle-actions.js +24 -12
  88. package/addon/services/vendor-actions.js +3 -0
  89. package/addon/styles/fleetops-engine.css +9 -1
  90. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +66 -2
  91. package/addon/templates/connectivity/events/details.hbs +40 -0
  92. package/addon/templates/management/vendors/index/details.hbs +2 -2
  93. package/addon/templates/settings/map.hbs +38 -9
  94. package/addon/templates/settings/notifications.hbs +2 -1
  95. package/addon/templates/settings/routing.hbs +1 -0
  96. package/addon/utils/device-table-columns.js +288 -0
  97. package/addon/utils/identity-cell-resource.js +20 -0
  98. package/addon/utils/leaflet-plugin-loader.js +5 -5
  99. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  100. package/addon/utils/route-colors.js +1 -1
  101. package/addon/utils/trackable-option.js +93 -0
  102. package/app/components/cell/attached-vehicle.js +1 -0
  103. package/app/components/cell/device-identity.js +1 -0
  104. package/app/components/cell/driver-identity.js +1 -0
  105. package/app/components/cell/equipment-identity.js +1 -0
  106. package/app/components/cell/part-identity.js +1 -0
  107. package/app/components/cell/telematic-device.js +1 -0
  108. package/app/components/cell/vehicle-identity.js +1 -0
  109. package/app/components/device/panel-tabs/events.hbs +30 -0
  110. package/app/components/device/panel-tabs/events.js +128 -0
  111. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  112. package/app/components/device/panel-tabs/sensors.js +93 -0
  113. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  114. package/app/components/device/panel-tabs/vehicle.js +90 -0
  115. package/app/components/device-event/details.hbs +192 -0
  116. package/app/components/device-event/details.js +1 -0
  117. package/app/components/table/cell/dropdown/action-item.js +1 -0
  118. package/app/components/table/cell/dropdown.js +1 -0
  119. package/app/components/vendor/panel-header.js +1 -0
  120. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  121. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  122. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  123. package/app/utils/map-drawer-dropdown-position.js +1 -0
  124. package/app/utils/trackable-option.js +1 -0
  125. package/composer.json +1 -1
  126. package/extension.json +1 -1
  127. package/package.json +4 -4
  128. package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
  129. package/server/src/Console/Commands/SyncTelematics.php +4 -4
  130. package/server/src/Exports/ContactExport.php +12 -5
  131. package/server/src/Exports/DeviceExport.php +92 -0
  132. package/server/src/Exports/DriverExport.php +28 -3
  133. package/server/src/Exports/EquipmentExport.php +94 -0
  134. package/server/src/Exports/FleetExport.php +22 -7
  135. package/server/src/Exports/FuelReportExport.php +16 -1
  136. package/server/src/Exports/IssueExport.php +25 -3
  137. package/server/src/Exports/MaintenanceExport.php +106 -0
  138. package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
  139. package/server/src/Exports/OrderExport.php +24 -5
  140. package/server/src/Exports/PartExport.php +99 -0
  141. package/server/src/Exports/PlaceExport.php +33 -7
  142. package/server/src/Exports/SensorExport.php +94 -0
  143. package/server/src/Exports/ServiceAreaExport.php +31 -7
  144. package/server/src/Exports/ServiceRateExport.php +41 -1
  145. package/server/src/Exports/TelematicExport.php +90 -0
  146. package/server/src/Exports/VehicleExport.php +274 -8
  147. package/server/src/Exports/VendorExport.php +15 -2
  148. package/server/src/Exports/WorkOrderExport.php +92 -0
  149. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
  150. package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
  151. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
  152. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
  153. package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
  154. package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
  155. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
  156. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +36 -1
  157. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +1 -1
  158. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
  159. package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
  160. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
  161. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
  162. package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
  163. package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
  164. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  165. package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
  166. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
  167. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
  168. package/server/src/Http/Filter/DeviceEventFilter.php +21 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +50 -4
  170. package/server/src/Http/Filter/EquipmentFilter.php +23 -0
  171. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
  172. package/server/src/Http/Filter/PartFilter.php +47 -0
  173. package/server/src/Http/Filter/SensorFilter.php +34 -6
  174. package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
  175. package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
  176. package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
  177. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
  178. package/server/src/Http/Requests/CreatePartRequest.php +40 -0
  179. package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
  180. package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
  181. package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
  182. package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
  183. package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
  184. package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
  185. package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
  186. package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
  187. package/server/src/Http/Resources/v1/Device.php +72 -0
  188. package/server/src/Http/Resources/v1/Equipment.php +57 -0
  189. package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
  190. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  191. package/server/src/Http/Resources/v1/Part.php +64 -0
  192. package/server/src/Http/Resources/v1/Sensor.php +68 -0
  193. package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
  194. package/server/src/Models/Device.php +2 -0
  195. package/server/src/Models/DeviceEvent.php +81 -0
  196. package/server/src/Models/Equipment.php +55 -0
  197. package/server/src/Models/Order.php +2 -1
  198. package/server/src/Observers/PurchaseRateObserver.php +2 -1
  199. package/server/src/Support/Analytics/RevenueTrend.php +3 -8
  200. package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
  201. package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
  202. package/server/src/Support/Metrics/EarningsMetric.php +1 -10
  203. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +105 -5
  204. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  205. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  206. package/server/src/Support/Telematics/Providers/SafeeProvider.php +567 -47
  207. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  208. package/server/src/Support/Telematics/TelematicService.php +28 -9
  209. package/server/src/routes.php +67 -15
  210. package/server/tests/DeviceFilterTest.php +9 -0
  211. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  212. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  213. package/server/tests/ExportCoverageTest.php +79 -0
  214. package/server/tests/MetricsRegistryTest.php +45 -5
  215. package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
  216. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  217. package/server/tests/TelematicsHardeningTest.php +873 -7
  218. package/addon/controllers/connectivity/events/index/details.js +0 -3
  219. package/addon/routes/connectivity/events/index/details.js +0 -3
  220. package/addon/templates/connectivity/events/index/details.hbs +0 -1
@@ -3,24 +3,36 @@ import { action } from '@ember/object';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { tracked } from '@glimmer/tracking';
5
5
  import { task } from 'ember-concurrency';
6
-
7
- const connectionStatusOptions = [
8
- { label: 'Online', value: 'online' },
9
- { label: 'Recently Offline', value: 'recently_offline' },
10
- { label: 'Offline', value: 'offline' },
11
- { label: 'Long Offline', value: 'long_offline' },
12
- { label: 'Never Connected', value: 'never_connected' },
13
- ];
6
+ import buildDeviceTableColumns from '../../../../utils/device-table-columns';
7
+ import fleetOpsOptions from '../../../../utils/fleet-ops-options';
14
8
 
15
9
  export default class ConnectivityTelematicsDetailsDevicesController extends Controller {
16
10
  @service deviceActions;
17
11
  @service fetch;
18
12
  @service hostRouter;
19
13
  @service intl;
14
+ @service mapManager;
20
15
  @service modalsManager;
21
16
  @service notifications;
22
-
23
- @tracked queryParams = ['page', 'limit', 'sort', 'query', 'status', 'provider', 'attachment_state', 'vehicle', 'connection_status', 'device_id', 'last_online_at', 'updated_at'];
17
+ @service store;
18
+ @service vehicleActions;
19
+
20
+ @tracked queryParams = [
21
+ 'page',
22
+ 'limit',
23
+ 'sort',
24
+ 'query',
25
+ 'status',
26
+ 'provider',
27
+ 'attachment_state',
28
+ 'vehicle',
29
+ 'connection_status',
30
+ 'device_id',
31
+ 'type',
32
+ 'serial_number',
33
+ 'last_online_at',
34
+ 'updated_at',
35
+ ];
24
36
  @tracked telematic;
25
37
  @tracked page = 1;
26
38
  @tracked limit;
@@ -32,6 +44,8 @@ export default class ConnectivityTelematicsDetailsDevicesController extends Cont
32
44
  @tracked vehicle;
33
45
  @tracked connection_status;
34
46
  @tracked device_id;
47
+ @tracked type;
48
+ @tracked serial_number;
35
49
  @tracked last_online_at;
36
50
  @tracked updated_at;
37
51
 
@@ -57,7 +71,17 @@ export default class ConnectivityTelematicsDetailsDevicesController extends Cont
57
71
 
58
72
  get hasActiveFilters() {
59
73
  return Boolean(
60
- this.query || this.status || this.provider || this.attachment_state || this.vehicle || this.connection_status || this.device_id || this.last_online_at || this.updated_at
74
+ this.query ||
75
+ this.status ||
76
+ this.provider ||
77
+ this.attachment_state ||
78
+ this.vehicle ||
79
+ this.connection_status ||
80
+ this.device_id ||
81
+ this.type ||
82
+ this.serial_number ||
83
+ this.last_online_at ||
84
+ this.updated_at
61
85
  );
62
86
  }
63
87
 
@@ -194,130 +218,16 @@ export default class ConnectivityTelematicsDetailsDevicesController extends Cont
194
218
 
195
219
  @tracked bulkActions = [];
196
220
 
221
+ get deviceTypeOptions() {
222
+ return fleetOpsOptions('deviceTypes');
223
+ }
224
+
225
+ get deviceStatusOptions() {
226
+ return fleetOpsOptions('deviceStatuses');
227
+ }
228
+
197
229
  get columns() {
198
- return [
199
- {
200
- sticky: true,
201
- label: this.intl.t('column.name'),
202
- valuePath: 'displayName',
203
- cellComponent: 'table/cell/anchor',
204
- action: this.deviceActions.transition.view,
205
- permission: 'fleet-ops view device',
206
- resizable: true,
207
- sortable: true,
208
- filterable: true,
209
- filterParam: 'query',
210
- filterComponent: 'filter/string',
211
- },
212
- {
213
- label: 'Provider ID',
214
- valuePath: 'device_id',
215
- resizable: true,
216
- sortable: true,
217
- filterable: true,
218
- filterParam: 'device_id',
219
- filterComponent: 'filter/string',
220
- },
221
- {
222
- label: 'Vehicle',
223
- valuePath: 'attached_to_name',
224
- resizable: true,
225
- sortable: true,
226
- filterable: true,
227
- filterParam: 'vehicle',
228
- filterComponent: 'filter/model',
229
- filterComponentPlaceholder: 'Select vehicle',
230
- model: 'vehicle',
231
- modelNamePath: 'displayName',
232
- },
233
- {
234
- label: 'Connection',
235
- valuePath: 'connection_status',
236
- cellComponent: 'table/cell/status',
237
- resizable: true,
238
- sortable: true,
239
- filterable: true,
240
- filterParam: 'connection_status',
241
- filterComponent: 'filter/multi-option',
242
- filterOptions: connectionStatusOptions,
243
- filterOptionLabel: 'label',
244
- filterOptionValue: 'value',
245
- },
246
- {
247
- label: 'Last Seen',
248
- valuePath: 'lastOnlineAt',
249
- sortParam: 'last_online_at',
250
- resizable: true,
251
- sortable: true,
252
- filterable: true,
253
- filterParam: 'last_online_at',
254
- filterComponent: 'filter/date',
255
- },
256
- {
257
- label: 'Attachment',
258
- valuePath: 'attachable_uuid',
259
- hidden: true,
260
- resizable: true,
261
- sortable: false,
262
- filterable: true,
263
- filterParam: 'attachment_state',
264
- filterComponent: 'filter/select',
265
- filterOptions: [
266
- { label: 'Attached', value: 'attached' },
267
- { label: 'Unattached', value: 'unattached' },
268
- ],
269
- filterOptionLabel: 'label',
270
- filterOptionValue: 'value',
271
- },
272
- {
273
- label: this.intl.t('column.updated-at'),
274
- valuePath: 'updatedAt',
275
- sortParam: 'updated_at',
276
- resizable: true,
277
- sortable: true,
278
- hidden: true,
279
- filterable: true,
280
- filterComponent: 'filter/date',
281
- },
282
- {
283
- label: '',
284
- cellComponent: 'table/cell/dropdown',
285
- ddButtonText: false,
286
- ddButtonIcon: 'ellipsis-h',
287
- ddButtonIconPrefix: 'fas',
288
- ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device') }),
289
- cellClassNames: 'overflow-visible align-middle',
290
- wrapperClass: 'flex items-center justify-end mx-2',
291
- sticky: 'right',
292
- width: 60,
293
- actions: [
294
- {
295
- label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device') }),
296
- fn: this.deviceActions.transition.view,
297
- permission: 'fleet-ops view device',
298
- },
299
- {
300
- label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.device') }),
301
- fn: this.deviceActions.transition.edit,
302
- permission: 'fleet-ops update device',
303
- },
304
- {
305
- label: 'Attach or change vehicle',
306
- fn: this.openAttachDeviceModal,
307
- permission: 'fleet-ops update device',
308
- },
309
- {
310
- label: 'Review recent events',
311
- fn: this.openDeviceEvents,
312
- permission: 'fleet-ops view device-event',
313
- },
314
- ],
315
- sortable: false,
316
- filterable: false,
317
- resizable: false,
318
- searchable: false,
319
- },
320
- ];
230
+ return buildDeviceTableColumns(this, { showProvider: false, deviceActionMode: 'panel', showDeviceStatus: false });
321
231
  }
322
232
 
323
233
  @action refresh() {
@@ -336,6 +246,8 @@ export default class ConnectivityTelematicsDetailsDevicesController extends Cont
336
246
  this.vehicle = null;
337
247
  this.connection_status = null;
338
248
  this.device_id = null;
249
+ this.type = null;
250
+ this.serial_number = null;
339
251
  this.last_online_at = null;
340
252
  this.updated_at = null;
341
253
  this.page = 1;
@@ -358,6 +270,86 @@ export default class ConnectivityTelematicsDetailsDevicesController extends Cont
358
270
  });
359
271
  }
360
272
 
273
+ @action hasAttachedVehicle(device) {
274
+ return Boolean(device?.attachable_uuid && this.isVehicleAttachment(device));
275
+ }
276
+
277
+ @action async viewAttachedVehicle(device) {
278
+ const vehicle = await this.resolveAttachedVehicle(device);
279
+
280
+ if (!vehicle) {
281
+ return;
282
+ }
283
+
284
+ return this.vehicleActions.panel.view(vehicle);
285
+ }
286
+
287
+ @action async locateAttachedVehicle(device) {
288
+ const vehicle = await this.resolveAttachedVehicle(device);
289
+
290
+ if (!vehicle) {
291
+ return;
292
+ }
293
+
294
+ await this.transitionToLiveMap();
295
+ await this.mapManager.waitForMap({ timeoutMs: 8000 });
296
+
297
+ this.mapManager.focusResource(vehicle, 16, {
298
+ paddingBottomRight: [300, 200],
299
+ moveend: () => {
300
+ this.vehicleActions.panel.view(vehicle, { closeOnTransition: true });
301
+ },
302
+ });
303
+ }
304
+
305
+ async transitionToLiveMap() {
306
+ try {
307
+ await this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index', { queryParams: { layout: 'map' } });
308
+ } catch (_) {
309
+ // Keep locate actions usable even if the current transition is already in-flight.
310
+ }
311
+ }
312
+
313
+ isVehicleAttachment(device) {
314
+ const attachableType = `${device?.attachable_type ?? ''}`.toLowerCase();
315
+
316
+ return !attachableType || attachableType.includes('vehicle');
317
+ }
318
+
319
+ async resolveAttachedVehicle(device) {
320
+ if (!device?.attachable_uuid) {
321
+ return null;
322
+ }
323
+
324
+ if (!this.isVehicleAttachment(device)) {
325
+ return null;
326
+ }
327
+
328
+ const attachable = device.attachable;
329
+
330
+ if (attachable && typeof attachable.then !== 'function') {
331
+ return attachable;
332
+ }
333
+
334
+ if (attachable && typeof attachable.then === 'function') {
335
+ return await attachable;
336
+ }
337
+
338
+ const cachedVehicle = this.store.peekRecord('vehicle', device.attachable_uuid);
339
+
340
+ if (cachedVehicle) {
341
+ return cachedVehicle;
342
+ }
343
+
344
+ try {
345
+ return await this.store.findRecord('vehicle', device.attachable_uuid);
346
+ } catch (error) {
347
+ this.notifications.serverError(error);
348
+ }
349
+
350
+ return null;
351
+ }
352
+
361
353
  @action openDeviceEvents(device) {
362
354
  return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.details.events', this.telematic, {
363
355
  queryParams: { device_uuid: device.id },
@@ -40,7 +40,7 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
40
40
  }
41
41
 
42
42
  get unprocessedEventsCount() {
43
- return this.events.filter((event) => !event.processed_at && !event.is_processed).length;
43
+ return this.events.filter((event) => !event.processed_at).length;
44
44
  }
45
45
 
46
46
  get deviceCount() {
@@ -171,6 +171,8 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
171
171
  filterParam: 'type',
172
172
  filterComponent: 'filter/multi-option',
173
173
  filterOptions: fleetOpsOptions('sensorTypes'),
174
+ filterOptionLabel: 'label',
175
+ filterOptionValue: 'value',
174
176
  },
175
177
  {
176
178
  label: 'Device',
@@ -209,6 +211,8 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
209
211
  filterable: true,
210
212
  filterComponent: 'filter/multi-option',
211
213
  filterOptions: fleetOpsOptions('sensorStatuses'),
214
+ filterOptionLabel: 'label',
215
+ filterOptionValue: 'value',
212
216
  },
213
217
  {
214
218
  label: 'Last Reading',
@@ -61,8 +61,7 @@ export default class MaintenanceEquipmentIndexController extends Controller {
61
61
  {
62
62
  label: this.intl.t('column.name'),
63
63
  valuePath: 'name',
64
- cellComponent: 'table/cell/anchor',
65
- cellClassNames: 'uppercase',
64
+ cellComponent: 'cell/equipment-identity',
66
65
  action: this.equipmentActions.transition.view,
67
66
  permission: 'fleet-ops view equipment',
68
67
  resizable: true,
@@ -61,8 +61,7 @@ export default class MaintenancePartsIndexController extends Controller {
61
61
  {
62
62
  label: this.intl.t('column.name'),
63
63
  valuePath: 'name',
64
- cellComponent: 'table/cell/anchor',
65
- cellClassNames: 'uppercase',
64
+ cellComponent: 'cell/part-identity',
66
65
  action: this.partActions.transition.view,
67
66
  permission: 'fleet-ops view part',
68
67
  resizable: true,
@@ -1,7 +1,7 @@
1
1
  import Controller from '@ember/controller';
2
2
  import { inject as service } from '@ember/service';
3
3
  import { tracked } from '@glimmer/tracking';
4
- import { action } from '@ember/object';
4
+ import { action, get } from '@ember/object';
5
5
 
6
6
  export default class ManagementDriversIndexController extends Controller {
7
7
  @service driverActions;
@@ -52,6 +52,22 @@ export default class ManagementDriversIndexController extends Controller {
52
52
  return Boolean(driver?.vehicle_uuid || driver?.vehicle?.id || driver?.vehicle?.uuid || driver?.vehicle_name);
53
53
  }
54
54
 
55
+ async resolveAssignedVehicle(vehicle) {
56
+ if (!vehicle) {
57
+ return null;
58
+ }
59
+
60
+ if (typeof vehicle.then === 'function') {
61
+ return await vehicle;
62
+ }
63
+
64
+ if (typeof vehicle.loadResource === 'function') {
65
+ return (await vehicle.loadResource()) ?? vehicle;
66
+ }
67
+
68
+ return vehicle;
69
+ }
70
+
55
71
  /** action buttons */
56
72
  get actionButtons() {
57
73
  return [
@@ -102,7 +118,8 @@ export default class ManagementDriversIndexController extends Controller {
102
118
  sticky: true,
103
119
  label: this.intl.t('column.name'),
104
120
  valuePath: 'name',
105
- cellComponent: 'table/cell/driver-name',
121
+ cellComponent: 'cell/driver-identity',
122
+ compact: true,
106
123
  permission: 'fleet-ops view driver',
107
124
  action: this.driverActions.transition.view,
108
125
  resizable: true,
@@ -120,6 +137,69 @@ export default class ManagementDriversIndexController extends Controller {
120
137
  hidden: false,
121
138
  filterComponent: 'filter/string',
122
139
  },
140
+ {
141
+ label: this.intl.t('column.phone'),
142
+ valuePath: 'phone',
143
+ cellComponent: 'table/cell/base',
144
+ resizable: true,
145
+ sortable: true,
146
+ filterable: true,
147
+ filterParam: 'phone',
148
+ filterComponent: 'filter/string',
149
+ },
150
+ {
151
+ label: this.intl.t('column.license'),
152
+ valuePath: 'drivers_license_number',
153
+ cellComponent: 'table/cell/base',
154
+ resizable: true,
155
+ sortable: true,
156
+ filterable: true,
157
+ filterComponent: 'filter/string',
158
+ },
159
+ {
160
+ label: this.intl.t('column.vehicle'),
161
+ cellComponent: 'cell/vehicle-identity',
162
+ compact: true,
163
+ permission: 'fleet-ops view vehicle',
164
+ action: async (vehicle) => {
165
+ try {
166
+ const resolvedVehicle = await this.resolveAssignedVehicle(vehicle);
167
+ if (resolvedVehicle) this.vehicleActions.panel.view(resolvedVehicle);
168
+ } catch (err) {
169
+ this.notifications.serverError(err);
170
+ }
171
+ },
172
+ valuePath: 'vehicle.display_name',
173
+ emptyText: '-',
174
+ showStatusBadge: true,
175
+ resourcePath: (driver) => {
176
+ const vehicle = get(driver, 'vehicle_assigned') ?? get(driver, 'vehicle');
177
+
178
+ if (vehicle) {
179
+ return vehicle;
180
+ }
181
+
182
+ const vehicleName = get(driver, 'vehicle_name');
183
+
184
+ if (vehicleName) {
185
+ return {
186
+ display_name: vehicleName,
187
+ name: vehicleName,
188
+ status: 'assigned',
189
+ loadResource: () => driver.loadVehicle?.(),
190
+ };
191
+ }
192
+
193
+ return null;
194
+ },
195
+ modelNamePath: 'display_name',
196
+ resizable: true,
197
+ filterable: true,
198
+ filterComponent: 'filter/model',
199
+ filterComponentPlaceholder: 'Select vehicle to filter by',
200
+ filterParam: 'vehicle',
201
+ model: 'vehicle',
202
+ },
123
203
  {
124
204
  label: this.intl.t('column.internal-id'),
125
205
  valuePath: 'internal_id',
@@ -150,27 +230,6 @@ export default class ManagementDriversIndexController extends Controller {
150
230
  filterParam: 'vendor',
151
231
  model: 'vendor',
152
232
  },
153
- {
154
- label: this.intl.t('column.vehicle'),
155
- cellComponent: 'table/cell/anchor',
156
- permission: 'fleet-ops view vehicle',
157
- onClick: async (driver) => {
158
- try {
159
- const vehicle = await driver.loadVehicle();
160
- if (vehicle) this.vehicleActions.panel.view(vehicle);
161
- } catch (err) {
162
- this.notifications.serverError(err);
163
- }
164
- },
165
- valuePath: 'vehicle.display_name',
166
- modelNamePath: 'display_name',
167
- resizable: true,
168
- filterable: true,
169
- filterComponent: 'filter/model',
170
- filterComponentPlaceholder: 'Select vehicle to filter by',
171
- filterParam: 'vehicle',
172
- model: 'vehicle',
173
- },
174
233
  {
175
234
  label: this.intl.t('column.fleet'),
176
235
  cellComponent: 'table/cell/link-list',
@@ -185,25 +244,6 @@ export default class ManagementDriversIndexController extends Controller {
185
244
  filterParam: 'fleet',
186
245
  model: 'fleet',
187
246
  },
188
- {
189
- label: this.intl.t('column.license'),
190
- valuePath: 'drivers_license_number',
191
- cellComponent: 'table/cell/base',
192
- resizable: true,
193
- sortable: true,
194
- filterable: true,
195
- filterComponent: 'filter/string',
196
- },
197
- {
198
- label: this.intl.t('column.phone'),
199
- valuePath: 'phone',
200
- cellComponent: 'table/cell/base',
201
- resizable: true,
202
- sortable: true,
203
- filterable: true,
204
- filterParam: 'phone',
205
- filterComponent: 'filter/string',
206
- },
207
247
  {
208
248
  label: this.intl.t('column.country'),
209
249
  valuePath: 'country',
@@ -1,4 +1,5 @@
1
1
  import Controller from '@ember/controller';
2
+ import { get } from '@ember/object';
2
3
  import { inject as service } from '@ember/service';
3
4
  import { tracked } from '@glimmer/tracking';
4
5
 
@@ -113,9 +114,37 @@ export default class ManagementFuelReportsIndexController extends Controller {
113
114
  {
114
115
  label: this.intl.t('column.driver'),
115
116
  valuePath: 'driver_name',
116
- cellComponent: 'table/cell/anchor',
117
+ cellComponent: 'cell/driver-identity',
117
118
  permission: 'fleet-ops view driver',
118
- onClick: this.fuelReportActions.viewDriver,
119
+ action: async (driver) => {
120
+ const resolvedDriver = driver?.loadResource ? await driver.loadResource() : driver;
121
+
122
+ if (resolvedDriver) {
123
+ this.fuelReportActions.driverActions.panel.view(resolvedDriver);
124
+ }
125
+ },
126
+ emptyText: '-',
127
+ showStatusBadge: true,
128
+ resourcePath: (fuelReport) => {
129
+ const driver = get(fuelReport, 'driver');
130
+
131
+ if (driver) {
132
+ return driver;
133
+ }
134
+
135
+ const driverName = get(fuelReport, 'driver_name');
136
+
137
+ if (driverName) {
138
+ return {
139
+ id: get(fuelReport, 'driver_uuid'),
140
+ name: driverName,
141
+ display_name: driverName,
142
+ loadResource: () => fuelReport.loadDriver?.(),
143
+ };
144
+ }
145
+
146
+ return null;
147
+ },
119
148
  resizable: true,
120
149
  sortable: true,
121
150
  filterable: true,
@@ -127,9 +156,39 @@ export default class ManagementFuelReportsIndexController extends Controller {
127
156
  {
128
157
  label: this.intl.t('column.vehicle'),
129
158
  valuePath: 'vehicle_name',
130
- cellComponent: 'table/cell/anchor',
159
+ cellComponent: 'cell/vehicle-identity',
131
160
  permission: 'fleet-ops view vehicle',
132
- onClick: this.fuelReportActions.viewVehicle,
161
+ action: async (vehicle) => {
162
+ const resolvedVehicle = vehicle?.loadResource ? await vehicle.loadResource() : vehicle;
163
+
164
+ if (resolvedVehicle) {
165
+ this.fuelReportActions.vehicleActions.panel.view(resolvedVehicle);
166
+ }
167
+ },
168
+ emptyText: '-',
169
+ showStatusBadge: true,
170
+ resourcePath: (fuelReport) => {
171
+ const vehicle = get(fuelReport, 'vehicle');
172
+
173
+ if (vehicle) {
174
+ return vehicle;
175
+ }
176
+
177
+ const vehicleName = get(fuelReport, 'vehicle_name');
178
+
179
+ if (vehicleName) {
180
+ return {
181
+ id: get(fuelReport, 'vehicle_uuid'),
182
+ displayName: vehicleName,
183
+ display_name: vehicleName,
184
+ name: vehicleName,
185
+ vehicle_number: get(fuelReport, 'vehicle_uuid'),
186
+ loadResource: () => fuelReport.loadVehicle?.(),
187
+ };
188
+ }
189
+
190
+ return null;
191
+ },
133
192
  resizable: true,
134
193
  sortable: true,
135
194
  filterable: true,