@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
@@ -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
  import fleetOpsOptions from '../../../utils/fleet-ops-options';
@@ -162,9 +163,37 @@ export default class ManagementIssuesIndexController extends Controller {
162
163
  {
163
164
  label: this.intl.t('column.driver'),
164
165
  valuePath: 'driver_name',
165
- cellComponent: 'table/cell/anchor',
166
+ cellComponent: 'cell/driver-identity',
166
167
  permission: 'fleet-ops view driver',
167
- onClick: this.issueActions.viewDriver,
168
+ action: async (driver) => {
169
+ const resolvedDriver = driver?.loadResource ? await driver.loadResource() : driver;
170
+
171
+ if (resolvedDriver) {
172
+ this.issueActions.driverActions.panel.view(resolvedDriver);
173
+ }
174
+ },
175
+ emptyText: '-',
176
+ showStatusBadge: true,
177
+ resourcePath: (issue) => {
178
+ const driver = get(issue, 'driver');
179
+
180
+ if (driver) {
181
+ return driver;
182
+ }
183
+
184
+ const driverName = get(issue, 'driver_name');
185
+
186
+ if (driverName) {
187
+ return {
188
+ id: get(issue, 'driver_uuid'),
189
+ name: driverName,
190
+ display_name: driverName,
191
+ loadResource: () => issue.loadDriver?.(),
192
+ };
193
+ }
194
+
195
+ return null;
196
+ },
168
197
  resizable: true,
169
198
  sortable: true,
170
199
  filterable: true,
@@ -176,9 +205,39 @@ export default class ManagementIssuesIndexController extends Controller {
176
205
  {
177
206
  label: this.intl.t('column.vehicle'),
178
207
  valuePath: 'vehicle_name',
179
- cellComponent: 'table/cell/anchor',
208
+ cellComponent: 'cell/vehicle-identity',
180
209
  permission: 'fleet-ops view vehicle',
181
- onClick: this.issueActions.viewVehicle,
210
+ action: async (vehicle) => {
211
+ const resolvedVehicle = vehicle?.loadResource ? await vehicle.loadResource() : vehicle;
212
+
213
+ if (resolvedVehicle) {
214
+ this.issueActions.vehicleActions.panel.view(resolvedVehicle);
215
+ }
216
+ },
217
+ emptyText: '-',
218
+ showStatusBadge: true,
219
+ resourcePath: (issue) => {
220
+ const vehicle = get(issue, 'vehicle');
221
+
222
+ if (vehicle) {
223
+ return vehicle;
224
+ }
225
+
226
+ const vehicleName = get(issue, 'vehicle_name');
227
+
228
+ if (vehicleName) {
229
+ return {
230
+ id: get(issue, 'vehicle_uuid'),
231
+ displayName: vehicleName,
232
+ display_name: vehicleName,
233
+ name: vehicleName,
234
+ vehicle_number: get(issue, 'vehicle_uuid'),
235
+ loadResource: () => issue.loadVehicle?.(),
236
+ };
237
+ }
238
+
239
+ return null;
240
+ },
182
241
  resizable: true,
183
242
  sortable: true,
184
243
  filterable: true,
@@ -1,5 +1,5 @@
1
1
  import Controller from '@ember/controller';
2
- import { action } from '@ember/object';
2
+ import { action, get } from '@ember/object';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { tracked } from '@glimmer/tracking';
5
5
 
@@ -126,7 +126,8 @@ export default class ManagementVehiclesIndexController extends Controller {
126
126
  label: this.intl.t('column.name'),
127
127
  valuePath: 'displayName',
128
128
  photoPath: 'avatar_url',
129
- cellComponent: 'table/cell/vehicle-name',
129
+ cellComponent: 'cell/vehicle-identity',
130
+ compact: true,
130
131
  permission: 'fleet-ops view vehicle',
131
132
  action: this.vehicleActions.transition.view,
132
133
  resizable: true,
@@ -135,6 +136,7 @@ export default class ManagementVehiclesIndexController extends Controller {
135
136
  filterComponent: 'filter/string',
136
137
  filterParam: 'name',
137
138
  showOnlineIndicator: true,
139
+ showStatus: false,
138
140
  },
139
141
  {
140
142
  label: this.intl.t('column.plate-number'),
@@ -160,14 +162,39 @@ export default class ManagementVehiclesIndexController extends Controller {
160
162
  },
161
163
  {
162
164
  label: this.intl.t('column.driver-assigned'),
163
- cellComponent: 'table/cell/anchor',
165
+ cellComponent: 'cell/driver-identity',
166
+ compact: true,
167
+ assignedVehicleLabel: (_driver, vehicle) => get(vehicle, 'displayName') ?? get(vehicle, 'display_name') ?? get(vehicle, 'name'),
164
168
  permission: 'fleet-ops view driver',
165
- action: async (vehicle) => {
166
- const driver = await vehicle.loadDriver();
169
+ action: async (driver) => {
170
+ const resolvedDriver = get(driver, 'id') ? driver : await driver?.loadResource?.();
167
171
 
168
- return this.driverActions.panel.view(driver);
172
+ if (resolvedDriver) {
173
+ return this.driverActions.panel.view(resolvedDriver);
174
+ }
169
175
  },
170
176
  valuePath: 'driver_name',
177
+ emptyText: '-',
178
+ resourcePath: (vehicle) => {
179
+ const driver = get(vehicle, 'driver');
180
+
181
+ if (driver) {
182
+ return driver;
183
+ }
184
+
185
+ const driverName = get(vehicle, 'driver_name');
186
+
187
+ if (driverName) {
188
+ return {
189
+ name: driverName,
190
+ display_name: driverName,
191
+ status: 'assigned',
192
+ loadResource: () => vehicle.loadDriver?.(),
193
+ };
194
+ }
195
+
196
+ return null;
197
+ },
171
198
  resizable: true,
172
199
  filterable: true,
173
200
  filterComponent: 'filter/model',
@@ -19,6 +19,12 @@ export default class SettingsMapController extends Controller {
19
19
  * @var {Array}
20
20
  */
21
21
  @tracked mapProviderOptions = MAP_PROVIDER_OPTIONS;
22
+ @tracked googleMapsMapTypeOptions = [
23
+ { label: 'Roadmap', value: 'roadmap' },
24
+ { label: 'Satellite', value: 'satellite' },
25
+ { label: 'Hybrid', value: 'hybrid' },
26
+ { label: 'Terrain', value: 'terrain' },
27
+ ];
22
28
 
23
29
  /**
24
30
  * The currently-selected map provider key (e.g. 'leaflet' or 'google').
@@ -27,6 +33,9 @@ export default class SettingsMapController extends Controller {
27
33
  * @var {String}
28
34
  */
29
35
  @tracked mapProvider = 'leaflet';
36
+ @tracked googleMapsMapType = 'roadmap';
37
+ @tracked showGoogleMapsTrafficLayer = false;
38
+ @tracked showGoogleMapsTransitLayer = false;
30
39
 
31
40
  /**
32
41
  * Whether settings have been loaded from the server.
@@ -68,6 +77,9 @@ export default class SettingsMapController extends Controller {
68
77
  @task *saveSettings() {
69
78
  const settings = {
70
79
  mapProvider: this.mapProvider,
80
+ googleMapsMapType: this.googleMapsMapType,
81
+ showGoogleMapsTrafficLayer: this.showGoogleMapsTrafficLayer,
82
+ showGoogleMapsTransitLayer: this.showGoogleMapsTransitLayer,
71
83
  };
72
84
 
73
85
  try {
@@ -89,6 +101,9 @@ export default class SettingsMapController extends Controller {
89
101
  const response = yield this.mapSettings.load({ force: true });
90
102
 
91
103
  this.mapProvider = response?.mapProvider ?? 'leaflet';
104
+ this.googleMapsMapType = response?.googleMapsMapType ?? 'roadmap';
105
+ this.showGoogleMapsTrafficLayer = Boolean(response?.showGoogleMapsTrafficLayer);
106
+ this.showGoogleMapsTransitLayer = Boolean(response?.showGoogleMapsTransitLayer);
92
107
  this.settingsLoaded = true;
93
108
  } catch (error) {
94
109
  this.notifications.serverError(error);
@@ -10,7 +10,7 @@
10
10
  * Third-party orchestration engines follow the same pattern:
11
11
  * 1. Create a service extending OrchestrationEngineInterfaceService
12
12
  * 2. Create an instance initializer that calls orchestrationEngine.register()
13
- * 3. The engine will appear in the FleetOps orchestrator settings dropdown
13
+ * 3. The engine will appear in the Fleet-Ops orchestrator settings dropdown
14
14
  */
15
15
  export function initialize(appInstance) {
16
16
  const orchestrationEngine = appInstance.lookup('service:orchestration-engine');
@@ -1,7 +1,29 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
4
  export default class ConnectivityDevicesIndexDetailsVehicleRoute extends Route {
4
- model() {
5
- return this.modelFor('connectivity.devices.index.details');
5
+ @service store;
6
+
7
+ async model() {
8
+ const device = this.modelFor('connectivity.devices.index.details');
9
+ const vehicle = await Promise.resolve(device?.attachable);
10
+
11
+ if (!vehicle?.id) {
12
+ return { device, positions: [] };
13
+ }
14
+
15
+ let positions = [];
16
+
17
+ try {
18
+ positions = await this.store.query('position', {
19
+ subject_uuid: vehicle.id,
20
+ sort: '-created_at',
21
+ limit: 5,
22
+ });
23
+ } catch (_) {
24
+ positions = [];
25
+ }
26
+
27
+ return { device, positions };
6
28
  }
7
29
  }
@@ -14,6 +14,13 @@ export default class ConnectivityDevicesIndexRoute extends Route {
14
14
  status: { refreshModel: true },
15
15
  attachment_state: { refreshModel: true },
16
16
  telematic: { refreshModel: true },
17
+ provider: { refreshModel: true },
18
+ vehicle: { refreshModel: true },
19
+ connection_status: { refreshModel: true },
20
+ device_id: { refreshModel: true },
21
+ type: { refreshModel: true },
22
+ serial_number: { refreshModel: true },
23
+ last_online_at: { refreshModel: true },
17
24
  created_at: { refreshModel: true },
18
25
  updated_at: { refreshModel: true },
19
26
  };
@@ -0,0 +1,28 @@
1
+ import Route from '@ember/routing/route';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+
5
+ export default class ConnectivityEventsDetailsRoute extends Route {
6
+ @service abilities;
7
+ @service hostRouter;
8
+ @service intl;
9
+ @service notifications;
10
+ @service store;
11
+
12
+ @action error(error) {
13
+ this.notifications.serverError(error);
14
+
15
+ return this.hostRouter.transitionTo('console.fleet-ops.connectivity.events.index');
16
+ }
17
+
18
+ beforeModel() {
19
+ if (this.abilities.cannot('fleet-ops view device-event')) {
20
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
21
+ return this.hostRouter.transitionTo('console.fleet-ops.connectivity.events.index');
22
+ }
23
+ }
24
+
25
+ model({ public_id }) {
26
+ return this.store.findRecord('device-event', public_id);
27
+ }
28
+ }
@@ -9,8 +9,12 @@ export default class ConnectivityEventsIndexRoute extends Route {
9
9
  limit: { refreshModel: true },
10
10
  sort: { refreshModel: true },
11
11
  query: { refreshModel: true },
12
- name: { refreshModel: true },
13
- public_id: { refreshModel: true },
12
+ telematic: { refreshModel: true },
13
+ device: { refreshModel: true },
14
+ event_type: { refreshModel: true },
15
+ severity: { refreshModel: true },
16
+ processed: { refreshModel: true },
17
+ occurred_at: { refreshModel: true },
14
18
  created_at: { refreshModel: true },
15
19
  updated_at: { refreshModel: true },
16
20
  };
@@ -9,8 +9,13 @@ export default class ConnectivitySensorsIndexRoute extends Route {
9
9
  limit: { refreshModel: true },
10
10
  sort: { refreshModel: true },
11
11
  query: { refreshModel: true },
12
- name: { refreshModel: true },
13
- public_id: { refreshModel: true },
12
+ telematic: { refreshModel: true },
13
+ device: { refreshModel: true },
14
+ type: { refreshModel: true },
15
+ status: { refreshModel: true },
16
+ serial_number: { refreshModel: true },
17
+ imei: { refreshModel: true },
18
+ last_reading_at: { refreshModel: true },
14
19
  created_at: { refreshModel: true },
15
20
  updated_at: { refreshModel: true },
16
21
  };
@@ -15,6 +15,8 @@ export default class ConnectivityTelematicsDetailsDevicesRoute extends Route {
15
15
  vehicle: { refreshModel: true },
16
16
  connection_status: { refreshModel: true },
17
17
  device_id: { refreshModel: true },
18
+ type: { refreshModel: true },
19
+ serial_number: { refreshModel: true },
18
20
  last_online_at: { refreshModel: true },
19
21
  updated_at: { refreshModel: true },
20
22
  };
@@ -28,6 +28,8 @@ export default class ManagementVehiclesIndexDetailsRoute extends Route {
28
28
  }
29
29
 
30
30
  afterModel(model) {
31
- model.loadDriver();
31
+ if (model && typeof model.loadDriver === 'function') {
32
+ model.loadDriver();
33
+ }
32
34
  }
33
35
  }
@@ -1,8 +1,10 @@
1
1
  import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
2
2
  import { action } from '@ember/object';
3
+ import { isArray } from '@ember/array';
3
4
 
4
5
  export default class DeviceActionsService extends ResourceActionService {
5
6
  @service fetch;
7
+ @service('universe/menu-service') menuService;
6
8
 
7
9
  constructor() {
8
10
  super(...arguments);
@@ -37,13 +39,41 @@ export default class DeviceActionsService extends ResourceActionService {
37
39
  });
38
40
  },
39
41
  view: (device) => {
42
+ if (!device?.id) {
43
+ return this.notifications?.warning?.(this.intl.t('common.invalid-resource'));
44
+ }
45
+
46
+ const registeredTabs = this.menuService?.getMenuItems?.('fleet-ops:component:device:details');
47
+
40
48
  return this.resourceContextPanel.open({
41
49
  device,
50
+ header: 'device/panel-header',
42
51
  tabs: [
43
52
  {
53
+ key: 'overview',
54
+ id: 'overview',
44
55
  label: this.intl.t('common.overview'),
45
56
  component: 'device/details',
46
57
  },
58
+ {
59
+ key: 'vehicle',
60
+ id: 'vehicle',
61
+ label: this.intl.t('resource.vehicle'),
62
+ component: 'device/panel-tabs/vehicle',
63
+ },
64
+ {
65
+ key: 'sensors',
66
+ id: 'sensors',
67
+ label: this.intl.t('resource.sensors'),
68
+ component: 'device/panel-tabs/sensors',
69
+ },
70
+ {
71
+ key: 'events',
72
+ id: 'events',
73
+ label: this.intl.t('resource.device-events'),
74
+ component: 'device/panel-tabs/events',
75
+ },
76
+ ...(isArray(registeredTabs) ? registeredTabs.filter((tab) => tab.component || tab.render) : []),
47
77
  ],
48
78
  });
49
79
  },
@@ -11,7 +11,7 @@ export default class DeviceEventActionsService extends ResourceActionService {
11
11
  }
12
12
 
13
13
  transition = {
14
- view: (deviceEvent) => this.transitionTo('connectivity.events.index.details', deviceEvent),
14
+ view: (deviceEvent) => this.transitionTo('connectivity.events.details', deviceEvent),
15
15
  };
16
16
 
17
17
  panel = {
@@ -9,6 +9,7 @@ export default class EquipmentActionsService extends ResourceActionService {
9
9
  super(...arguments);
10
10
  this.initialize('equipment', {
11
11
  defaultAttributes: {
12
+ status: 'available',
12
13
  currency: this.defaultCurrency,
13
14
  },
14
15
  });
@@ -108,6 +108,12 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
108
108
  /** @type {google.maps.ImageMapType|null} */
109
109
  _customTileLayer = null;
110
110
 
111
+ /** @type {google.maps.TrafficLayer|null} */
112
+ _trafficLayer = null;
113
+
114
+ /** @type {google.maps.TransitLayer|null} */
115
+ _transitLayer = null;
116
+
111
117
  /** @type {Map<string, Function[]>} Normalized event name → array of [handler, gmListener] pairs */
112
118
  _eventListeners = new Map();
113
119
 
@@ -176,6 +182,7 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
176
182
  styles: mergedMapStyles,
177
183
  });
178
184
 
185
+ this.#syncGoogleLayers(options);
179
186
  await this.#initDrawingManager();
180
187
 
181
188
  debug('[GoogleMapsAdapter] Map initialized');
@@ -231,6 +238,10 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
231
238
  this._tooltipOverlayView?.setMap?.(null);
232
239
  this._tooltipOverlayView = null;
233
240
  this._supportsAdvancedMarkers = false;
241
+ this._trafficLayer?.setMap(null);
242
+ this._trafficLayer = null;
243
+ this._transitLayer?.setMap(null);
244
+ this._transitLayer = null;
234
245
 
235
246
  this._map = null;
236
247
  debug('[GoogleMapsAdapter] Map destroyed');
@@ -1184,6 +1195,20 @@ export default class GoogleMapsAdapter extends MapAdapterInterface {
1184
1195
  }
1185
1196
  }
1186
1197
 
1198
+ #syncGoogleLayers(options = {}) {
1199
+ if (!this._map) return;
1200
+
1201
+ if (options.showTrafficLayer) {
1202
+ this._trafficLayer = new google.maps.TrafficLayer();
1203
+ this._trafficLayer.setMap(this._map);
1204
+ }
1205
+
1206
+ if (options.showTransitLayer) {
1207
+ this._transitLayer = new google.maps.TransitLayer();
1208
+ this._transitLayer.setMap(this._map);
1209
+ }
1210
+ }
1211
+
1187
1212
  #ensureDrawToolbar() {
1188
1213
  if (this._drawControlEl || !this._map) return;
1189
1214
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Implements the MapAdapterInterface using the Leaflet library (via ember-leaflet).
5
5
  * This adapter wraps all existing Leaflet-specific logic, preserving 100% of
6
- * the original FleetOps map functionality while conforming to the provider-agnostic
6
+ * the original Fleet-Ops map functionality while conforming to the provider-agnostic
7
7
  * interface.
8
8
  *
9
9
  * All Leaflet-specific plugins are supported:
@@ -2,7 +2,7 @@
2
2
  * MapAdapterInterface
3
3
  *
4
4
  * Abstract base class that defines the standardized contract all map provider
5
- * adapters must implement. This enables FleetOps to support multiple map
5
+ * adapters must implement. This enables Fleet-Ops to support multiple map
6
6
  * providers (Leaflet, Google Maps, Mapbox, etc.) without changing any
7
7
  * application-level business logic.
8
8
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * MapManagerService
3
3
  *
4
- * Provider-agnostic central map service for FleetOps. Replaces the
4
+ * Provider-agnostic central map service for Fleet-Ops. Replaces the
5
5
  * Leaflet-specific `leaflet-map-manager` as the single point of contact
6
6
  * for all map operations across the application.
7
7
  *
@@ -139,6 +139,9 @@ export default class MapManagerService extends Service {
139
139
  ...options,
140
140
  apiKey: options.apiKey ?? this.mapSettings.googleMapsApiKey,
141
141
  mapId: options.mapId ?? this.mapSettings.googleMapsMapId,
142
+ mapTypeId: options.mapTypeId ?? this.mapSettings.googleMapsMapType,
143
+ showTrafficLayer: options.showTrafficLayer ?? this.mapSettings.showGoogleMapsTrafficLayer,
144
+ showTransitLayer: options.showTransitLayer ?? this.mapSettings.showGoogleMapsTransitLayer,
142
145
  });
143
146
 
144
147
  return Promise.resolve(map).then((nativeMap) => {
@@ -6,6 +6,9 @@ const DEFAULT_SETTINGS = {
6
6
  mapProvider: 'leaflet',
7
7
  googleMapsApiKey: '',
8
8
  googleMapsMapId: '',
9
+ googleMapsMapType: 'roadmap',
10
+ showGoogleMapsTrafficLayer: false,
11
+ showGoogleMapsTransitLayer: false,
9
12
  };
10
13
 
11
14
  export default class MapSettingsService extends Service {
@@ -26,6 +29,18 @@ export default class MapSettingsService extends Service {
26
29
  return this.settings.googleMapsMapId ?? '';
27
30
  }
28
31
 
32
+ get googleMapsMapType() {
33
+ return this.settings.googleMapsMapType ?? 'roadmap';
34
+ }
35
+
36
+ get showGoogleMapsTrafficLayer() {
37
+ return Boolean(this.settings.showGoogleMapsTrafficLayer);
38
+ }
39
+
40
+ get showGoogleMapsTransitLayer() {
41
+ return Boolean(this.settings.showGoogleMapsTransitLayer);
42
+ }
43
+
29
44
  get isGoogleMaps() {
30
45
  return this.mapProvider === 'google';
31
46
  }
@@ -36,8 +36,11 @@ export default class SensorActionsService extends ResourceActionService {
36
36
  view: (sensor) => {
37
37
  return this.resourceContextPanel.open({
38
38
  sensor,
39
+ header: 'sensor/panel-header',
39
40
  tabs: [
40
41
  {
42
+ key: 'overview',
43
+ id: 'overview',
41
44
  label: this.intl.t('common.overview'),
42
45
  component: 'sensor/details',
43
46
  },
@@ -96,8 +96,15 @@ export default class ServiceRateActionsService extends ResourceActionService {
96
96
  };
97
97
 
98
98
  @task *queryServiceRatesForOrder(order, params = {}) {
99
+ const payloadCoordinates = order.payload?.payloadCoordinates ?? [];
100
+ const routeCoordinates = payloadCoordinates.map((coordinate) => this.#serializeRouteCoordinate(coordinate)).filter(Boolean);
101
+
102
+ if (routeCoordinates.length < 2) {
103
+ return [];
104
+ }
105
+
99
106
  const queryParams = {
100
- coordinates: order.payload.payloadCoordinates.join(';'),
107
+ coordinates: routeCoordinates.join(';'),
101
108
  facilitator: order.facilitator?.get('isIntegratedVendor') ? order.facilitator.get('public_id') : null,
102
109
  service_type: order.order_config?.get('key'),
103
110
  ...params,
@@ -117,6 +124,37 @@ export default class ServiceRateActionsService extends ResourceActionService {
117
124
  }
118
125
  }
119
126
 
127
+ #serializeRouteCoordinate(coordinate) {
128
+ if (Array.isArray(coordinate)) {
129
+ const [longitude, latitude] = coordinate;
130
+
131
+ if (!this.#isValidCoordinate(latitude) || !this.#isValidCoordinate(longitude)) {
132
+ return null;
133
+ }
134
+
135
+ return `${latitude},${longitude}`;
136
+ }
137
+
138
+ if (typeof coordinate === 'string') {
139
+ const parts = coordinate
140
+ .split(',')
141
+ .map((part) => part.trim())
142
+ .filter(Boolean);
143
+
144
+ if (parts.length !== 2 || !parts.every((part) => this.#isValidCoordinate(part))) {
145
+ return null;
146
+ }
147
+
148
+ return parts.join(',');
149
+ }
150
+
151
+ return null;
152
+ }
153
+
154
+ #isValidCoordinate(value) {
155
+ return value !== null && value !== undefined && value !== '' && Number.isFinite(Number(value));
156
+ }
157
+
120
158
  @task *getServiceQuotes(serviceRate, order) {
121
159
  if (order.payload.payloadCoordinates.length < 2) return;
122
160