@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
@@ -63,6 +63,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
63
63
  icon: 'truck',
64
64
  route: 'management',
65
65
  defaultRoute: 'management.index',
66
+ requiresVisibleChildren: true,
66
67
  keywords: ['drivers', 'vehicles', 'fleets', 'contacts', 'places'],
67
68
  children: this.resourcesItems,
68
69
  }),
@@ -72,6 +73,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
72
73
  icon: 'cog',
73
74
  route: 'maintenance',
74
75
  defaultRoute: 'maintenance.index',
76
+ requiresVisibleChildren: true,
75
77
  keywords: ['work orders', 'equipment', 'parts'],
76
78
  children: this.maintenanceItems,
77
79
  }),
@@ -81,6 +83,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
81
83
  icon: 'satellite-dish',
82
84
  route: 'connectivity',
83
85
  defaultRoute: 'connectivity.telematics',
86
+ requiresVisibleChildren: true,
84
87
  keywords: ['telematics', 'fuel integrations', 'devices', 'sensors'],
85
88
  children: this.connectivityItems,
86
89
  }),
@@ -90,6 +93,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
90
93
  icon: 'chart-line',
91
94
  route: 'analytics',
92
95
  defaultRoute: 'analytics.index',
96
+ requiresVisibleChildren: true,
93
97
  keywords: ['reports', 'metrics'],
94
98
  children: this.analyticsItems,
95
99
  }),
@@ -99,6 +103,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
99
103
  icon: 'gear',
100
104
  route: 'settings',
101
105
  defaultRoute: 'settings.index',
106
+ requiresVisibleChildren: true,
102
107
  keywords: ['configuration', 'navigator', 'map', 'routing', 'notifications'],
103
108
  children: this.settingsItems,
104
109
  }),
@@ -164,7 +169,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
164
169
 
165
170
  get connectivityItems() {
166
171
  return this.withRegistryItems('connectivity', [
167
- this.createItem('menu.telematics', 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
172
+ this.createHubItem(this.intl.t('menu.telematics'), 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
168
173
  this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report', ['fuel integrations']),
169
174
  this.createItem('menu.devices', 'hard-drive', 'connectivity.devices', 'fleet-ops list device', 'fleet-ops see device'),
170
175
  this.createItem('menu.sensors', 'temperature-full', 'connectivity.sensors', 'fleet-ops list sensor', 'fleet-ops see sensor'),
@@ -247,13 +252,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
247
252
  this.universeSettingsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'settings');
248
253
  }
249
254
 
250
- createBranch({ id, label, icon, route, defaultRoute, children, keywords = [] }) {
255
+ createBranch({ id, label, icon, route, defaultRoute, requiresVisibleChildren = false, children, keywords = [] }) {
251
256
  return {
252
257
  id,
253
258
  label,
254
259
  icon,
255
260
  route: this.fullRoute(route),
256
261
  defaultRoute: this.fullRoute(defaultRoute),
262
+ requiresVisibleChildren,
257
263
  children: children.filter((item) => item.visible !== false),
258
264
  keywords,
259
265
  };
@@ -267,12 +273,12 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
267
273
  icon,
268
274
  route: this.fullRoute(route),
269
275
  permission,
270
- visible: this.abilities.can(ability),
276
+ visiblePermission: ability,
271
277
  keywords: [intl, route, ...keywords].filter(Boolean),
272
278
  };
273
279
  }
274
280
 
275
- createHubItem(label, icon, route, permission, ability, keywords = []) {
281
+ createHubItem(label, icon, route, _permission, _ability, keywords = []) {
276
282
  return {
277
283
  pinnedFirst: true,
278
284
  priority: this.defaultPriorityForRoute(route),
@@ -280,8 +286,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
280
286
  description: label,
281
287
  icon,
282
288
  route: this.fullRoute(route),
283
- permission,
284
- visible: this.abilities.can(ability),
289
+ isNavigationHub: true,
285
290
  keywords: [label, route, ...keywords].filter(Boolean),
286
291
  };
287
292
  }
@@ -390,6 +395,15 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
390
395
  }
391
396
  }
392
397
 
398
+ @action shouldSyncInitialActiveParent({ activePath = [], currentURL }) {
399
+ const [parent, child] = activePath;
400
+ const normalizedURL = (currentURL ?? '').split('?')[0].replace(/\/+$/, '') || '/';
401
+ const isFleetOpsRootURL = normalizedURL === '/fleet-ops';
402
+ const isDefaultOrdersLanding = parent?.id === 'operations' && child?.route === this.fullRoute('operations.orders') && isFleetOpsRootURL;
403
+
404
+ return !isDefaultOrdersLanding;
405
+ }
406
+
393
407
  @action
394
408
  async searchNavigation({ query, limit = 12 }) {
395
409
  const trimmedQuery = query?.trim();
@@ -5,6 +5,7 @@ import { action } from '@ember/object';
5
5
  import { task } from 'ember-concurrency';
6
6
  import { isArray } from '@ember/array';
7
7
  import { startOfWeek, endOfWeek, format } from 'date-fns';
8
+ import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
8
9
 
9
10
  export default class MapDrawerDeviceEventListingComponent extends Component {
10
11
  @service store;
@@ -115,6 +116,7 @@ export default class MapDrawerDeviceEventListingComponent extends Component {
115
116
  ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
116
117
  cellClassNames: 'overflow-visible',
117
118
  wrapperClass: 'flex items-center justify-end mx-2',
119
+ calculatePosition: calculateMapDrawerDropdownPosition,
118
120
  width: '10%',
119
121
  actions: [
120
122
  {
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
+ import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
5
6
 
6
7
  export default class MapDrawerDriverListingComponent extends Component {
7
8
  @service driverActions;
@@ -71,6 +72,7 @@ export default class MapDrawerDriverListingComponent extends Component {
71
72
  ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.driver') }),
72
73
  cellClassNames: 'overflow-visible',
73
74
  wrapperClass: 'flex items-center justify-end mx-2',
75
+ calculatePosition: calculateMapDrawerDropdownPosition,
74
76
  width: '90px',
75
77
  actions: [
76
78
  {
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
+ import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
5
6
 
6
7
  export default class MapDrawerPlaceListingComponent extends Component {
7
8
  @service placeActions;
@@ -53,6 +54,7 @@ export default class MapDrawerPlaceListingComponent extends Component {
53
54
  ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.place') }),
54
55
  cellClassNames: 'overflow-visible',
55
56
  wrapperClass: 'flex items-center justify-end mx-2',
57
+ calculatePosition: calculateMapDrawerDropdownPosition,
56
58
  width: '90px',
57
59
  actions: [
58
60
  {
@@ -4,23 +4,28 @@
4
4
  <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
5
5
  <PowerSelect
6
6
  @options={{this.trackables}}
7
- @selected={{this.resource}}
7
+ @selected={{this.selectedTrackable}}
8
8
  @onChange={{this.onResourceSelected}}
9
+ @searchEnabled={{true}}
10
+ @searchField="trackableSearchText"
9
11
  @placeholder={{t "common.select-field" field="trackable"}}
10
12
  @triggerClass="form-select form-input form-input-sm w-48"
11
13
  as |option|
12
14
  >
13
15
  <div class="flex flex-row items-start space-x-2">
14
16
  <div class="inline-block">
15
- {{#if (eq (get-model-name option) "driver")}}
16
- <Image src={{option.photo_url}} @fallbackSrc={{config "defaultValues.driverAvatar"}} class="w-4 h-4" />
17
+ {{#if (eq option.modelName "driver")}}
18
+ <Image src={{option.resource.photo_url}} @fallbackSrc={{config "defaultValues.driverAvatar"}} class="w-4 h-4" />
17
19
  {{else}}
18
- <Image src={{option.avatar_url}} @fallbackSrc={{config "defaultValues.vehicleAvatar"}} class="w-4 h-4" />
20
+ <Image src={{option.resource.avatar_url}} @fallbackSrc={{config "defaultValues.vehicleAvatar"}} class="w-4 h-4" />
19
21
  {{/if}}
20
22
  </div>
21
23
  <div class="inline-block text-sm truncate">
22
- <div class="font-semibold text-sm normalize-in-trigger">{{or option.name option.displayName option.public_id}}</div>
23
- <div class="hide-from-trigger text-xs">{{or option.email option.serial_number option.plate_number option.internal_id}}</div>
24
+ <div class="font-semibold text-sm normalize-in-trigger">{{option.primaryLabel}}</div>
25
+ <div class="hide-from-trigger text-xs">{{option.secondaryLabel}}</div>
26
+ {{#if option.deviceLabel}}
27
+ <div class="hide-from-trigger text-xs text-gray-500 dark:text-gray-400 truncate">{{option.deviceLabel}}</div>
28
+ {{/if}}
24
29
  </div>
25
30
  </div>
26
31
  </PowerSelect>
@@ -8,6 +8,7 @@ import { htmlSafe } from '@ember/template';
8
8
  import { startOfWeek, endOfWeek, format } from 'date-fns';
9
9
  import getModelName from '@fleetbase/ember-core/utils/get-model-name';
10
10
  import ensureLeafletDrawEditNamespace from '../../../utils/leaflet-draw-namespace-guard';
11
+ import { buildTrackableOption } from '../../../utils/trackable-option';
11
12
 
12
13
  const L = window.leaflet || window.L;
13
14
 
@@ -29,6 +30,7 @@ export default class MapDrawerPositionListingComponent extends Component {
29
30
  @tracked replaySpeed = '1';
30
31
  @tracked positionsLayer = null;
31
32
  @tracked positionOverlayIds = [];
33
+ @tracked selectedTrackableModelName = null;
32
34
 
33
35
  /** Computed properties - read state from service */
34
36
  get isReplaying() {
@@ -47,7 +49,17 @@ export default class MapDrawerPositionListingComponent extends Component {
47
49
  const vehicles = this.mapManager.livemap?.vehicles ?? [];
48
50
  const drivers = this.mapManager.livemap?.drivers ?? [];
49
51
 
50
- return [...vehicles, ...drivers];
52
+ return [...vehicles.map((vehicle) => buildTrackableOption(vehicle, 'vehicle')), ...drivers.map((driver) => buildTrackableOption(driver, 'driver'))];
53
+ }
54
+
55
+ get selectedTrackable() {
56
+ if (!this.resource) {
57
+ return null;
58
+ }
59
+
60
+ const resourceType = this.resourceType;
61
+
62
+ return this.trackables.find((trackable) => trackable.resource?.id === this.resource?.id && trackable.modelName === resourceType) ?? null;
51
63
  }
52
64
 
53
65
  get replayProgressWidth() {
@@ -72,7 +84,7 @@ export default class MapDrawerPositionListingComponent extends Component {
72
84
  if (!this.resource) {
73
85
  return 'resource';
74
86
  }
75
- return getModelName(this.resource) || 'resource';
87
+ return this.selectedTrackableModelName || getModelName(this.resource) || 'resource';
76
88
  }
77
89
 
78
90
  get hasPositions() {
@@ -179,8 +191,9 @@ export default class MapDrawerPositionListingComponent extends Component {
179
191
  this.positionOverlayIds = [];
180
192
  }
181
193
 
182
- @action onResourceSelected(resource) {
183
- this.resource = resource;
194
+ @action onResourceSelected(trackable) {
195
+ this.resource = trackable?.resource ?? null;
196
+ this.selectedTrackableModelName = trackable?.modelName ?? null;
184
197
  this.loadPositions.perform();
185
198
  }
186
199
 
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
+ import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
5
6
 
6
7
  export default class MapDrawerVehicleListingComponent extends Component {
7
8
  @service vehicleActions;
@@ -65,6 +66,7 @@ export default class MapDrawerVehicleListingComponent extends Component {
65
66
  ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.vehicle') }),
66
67
  cellClassNames: 'overflow-visible',
67
68
  wrapperClass: 'flex items-center justify-end mx-2',
69
+ calculatePosition: calculateMapDrawerDropdownPosition,
68
70
  width: '90px',
69
71
  actions: [
70
72
  {
@@ -1,29 +1,61 @@
1
- <div class="px-4 py-2">
2
- <div class="flex flex-1 flex-row items-start justify-between">
3
- <div class="flex flex-row space-x-3">
4
- <div class="flex items-start justify-start rounded-full">
1
+ <div class="px-4 py-3" ...attributes>
2
+ <div class="flex flex-1 flex-row items-start justify-between gap-4">
3
+ <div class="flex min-w-0 flex-row items-start gap-3">
4
+ <div class="relative flex flex-shrink-0 items-start justify-start">
5
5
  <Image
6
6
  src={{@resource.photo_url}}
7
7
  @fallbackSrc={{config "defaultValues.placeholderImage"}}
8
- alt={{@resource.displayName}}
8
+ alt={{this.name}}
9
9
  height="48"
10
10
  width="48"
11
- class="h-14 w-14 rounded-full shadow-sm"
11
+ class="h-14 w-14 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700"
12
12
  />
13
13
  <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
14
- <InputInfo @text={{@resource.displayName}} />
14
+ <InputInfo @text={{this.name}} />
15
15
  </Attach::Tooltip>
16
16
  </div>
17
- <div class="flex flex-col">
18
- <h1 class="text-gray-900 dark:text-white font-semibold text-lg">{{@resource.displayName}}</h1>
19
- <div>
20
- <div class="text-xs text-gray-400 dark:text-gray-500">{{n-a (get-fleet-ops-option-label "sensorTypes" @resource.type)}}</div>
21
- <div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.serial_number}}</div>
17
+ <div class="flex min-w-0 flex-col">
18
+ <div class="flex min-w-0 flex-wrap items-center gap-2">
19
+ <h1 class="truncate text-base font-semibold leading-5 text-gray-900 dark:text-gray-100">{{this.name}}</h1>
20
+ <Badge @status={{this.status}} @size="xs">{{smart-humanize this.status}}</Badge>
21
+ {{#if this.thresholdStatus}}
22
+ <Badge @status={{if (eq this.thresholdStatus "normal") "success" "warning"}} @size="xs">{{smart-humanize this.thresholdStatus}}</Badge>
23
+ {{/if}}
24
+ </div>
25
+
26
+ <div class="mt-1 flex max-w-2xl flex-wrap items-center gap-1.5">
27
+ {{#if this.type}}
28
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
29
+ <FaIcon @icon="gauge-high" class="text-gray-400 dark:text-gray-300" />
30
+ <span class="truncate">{{n-a (get-fleet-ops-option-label "sensorTypes" this.type)}}</span>
31
+ </span>
32
+ {{/if}}
33
+ {{#if this.identifier}}
34
+ <span class="inline-flex max-w-[12rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
35
+ <FaIcon @icon="id-card" class="text-gray-400 dark:text-gray-300" />
36
+ <span class="truncate">{{this.identifier}}</span>
37
+ </span>
38
+ {{/if}}
39
+ {{#if this.reading}}
40
+ <span class="inline-flex max-w-[10rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
41
+ <FaIcon @icon="chart-line" class="text-gray-400 dark:text-gray-300" />
42
+ <span class="truncate">{{this.reading}}</span>
43
+ </span>
44
+ {{/if}}
45
+ {{#if this.deviceName}}
46
+ <span class="inline-flex max-w-[12rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
47
+ <FaIcon @icon="microchip" class="text-gray-400 dark:text-gray-300" />
48
+ <span class="truncate">{{this.deviceName}}</span>
49
+ </span>
50
+ {{/if}}
51
+ <span class="inline-flex max-w-[13rem] items-center gap-1 truncate rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-medium leading-4 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
52
+ <FaIcon @icon="clock" class="text-gray-400 dark:text-gray-300" />
53
+ <span class="truncate">{{#if this.lastReadingAt}}{{format-date-fns this.lastReadingAt "dd MMM yyyy, HH:mm"}}{{else}}No reading yet{{/if}}</span>
54
+ </span>
22
55
  </div>
23
- <Badge @status={{if @resource.online "online" "offline"}}>{{if @resource.online (t "common.online") (t "common.offline")}}</Badge>
24
56
  </div>
25
57
  </div>
26
- <div class="next-view-header-right">
58
+ <div class="next-view-header-right flex-shrink-0">
27
59
  <Layout::Resource::Panel::HeaderActions
28
60
  @resource={{@resource}}
29
61
  @saveTask={{@saveTask}}
@@ -36,4 +68,4 @@
36
68
  />
37
69
  </div>
38
70
  </div>
39
- </div>
71
+ </div>
@@ -1,3 +1,61 @@
1
1
  import Component from '@glimmer/component';
2
+ import { get } from '@ember/object';
2
3
 
3
- export default class SensorPanelHeaderComponent extends Component {}
4
+ export default class SensorPanelHeaderComponent extends Component {
5
+ get resource() {
6
+ return this.args.resource;
7
+ }
8
+
9
+ get name() {
10
+ return (
11
+ get(this.resource, 'displayName') ??
12
+ get(this.resource, 'display_name') ??
13
+ get(this.resource, 'name') ??
14
+ get(this.resource, 'serial_number') ??
15
+ get(this.resource, 'imei') ??
16
+ get(this.resource, 'public_id') ??
17
+ '-'
18
+ );
19
+ }
20
+
21
+ get status() {
22
+ return get(this.resource, 'status') ?? (get(this.resource, 'is_active') ? 'active' : 'inactive');
23
+ }
24
+
25
+ get thresholdStatus() {
26
+ return get(this.resource, 'threshold_status');
27
+ }
28
+
29
+ get type() {
30
+ return get(this.resource, 'type');
31
+ }
32
+
33
+ get identifier() {
34
+ return get(this.resource, 'serial_number') ?? get(this.resource, 'imei') ?? get(this.resource, 'internal_id') ?? get(this.resource, 'public_id');
35
+ }
36
+
37
+ get reading() {
38
+ const value = get(this.resource, 'last_value');
39
+ const unit = get(this.resource, 'unit');
40
+
41
+ if (value === null || value === undefined || value === '') {
42
+ return null;
43
+ }
44
+
45
+ return unit ? `${value} ${unit}` : String(value);
46
+ }
47
+
48
+ get deviceName() {
49
+ return (
50
+ get(this.resource, 'device.displayName') ??
51
+ get(this.resource, 'device.display_name') ??
52
+ get(this.resource, 'device.name') ??
53
+ get(this.resource, 'device_name') ??
54
+ get(this.resource, 'device_uuid')
55
+ );
56
+ }
57
+
58
+ get lastReadingAt() {
59
+ return get(this.resource, 'last_reading_at') ?? get(this.resource, 'lastReadingAt');
60
+ }
61
+ }
@@ -0,0 +1,31 @@
1
+ {{#if this.visible}}
2
+ {{#if @columnAction.separator}}
3
+ <div class="next-dd-menu-seperator"></div>
4
+ {{else}}
5
+ <div role="group" class="px-1">
6
+ <a
7
+ href="javascript:;"
8
+ role="menuitem"
9
+ class="next-dd-item {{@columnAction.class}} {{if this.disabled 'disabled'}}"
10
+ disabled={{this.disabled}}
11
+ {{on "click" (fn this.onClick @columnAction @row @dd)}}
12
+ >
13
+ {{#if @columnAction.icon}}
14
+ <div class="w-6">
15
+ <FaIcon class={{@columnAction.iconClass}} @icon={{@columnAction.icon}} @prefix={{@columnAction.iconPrefix}} />
16
+ </div>
17
+ {{/if}}
18
+ {{@columnAction.label}}
19
+ {{#if this.doesntHavePermissions}}
20
+ <Attach::Tooltip @class="clean" @animation="scale" @placement={{or @columnAction.tooltipPlacement "right"}}>
21
+ <InputInfo @text={{t "common.unauthorized"}} />
22
+ </Attach::Tooltip>
23
+ {{else if @columnAction.helpText}}
24
+ <Attach::Tooltip @class="clean" @animation="scale" @placement={{or @columnAction.tooltipPlacement "right"}}>
25
+ <InputInfo @text={{@columnAction.helpText}} @exampleText={{@columnAction.exampleText}} />
26
+ </Attach::Tooltip>
27
+ {{/if}}
28
+ </a>
29
+ </div>
30
+ {{/if}}
31
+ {{/if}}
@@ -0,0 +1,62 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+ import { isNone } from '@ember/utils';
6
+
7
+ export default class TableCellDropdownActionItemComponent extends Component {
8
+ @service abilities;
9
+ @tracked permissionRequired;
10
+ @tracked doesntHavePermissions = false;
11
+ @tracked disabled = false;
12
+ @tracked visible = true;
13
+
14
+ constructor(owner, { columnAction = {}, row = {}, disabled = false, permission = null }) {
15
+ super(...arguments);
16
+ this.permissionRequired = columnAction.permission ?? permission;
17
+ this.disabled = this.disabledCheck(columnAction, this.permissionRequired, disabled);
18
+ this.visible = (columnAction.visible ?? true) && this.visibilityCheck(columnAction, row);
19
+ }
20
+
21
+ @action onClick(columnAction, row, dd) {
22
+ if (this.disabled) {
23
+ return;
24
+ }
25
+
26
+ if (typeof dd?.actions?.close === 'function') {
27
+ dd.actions.close();
28
+ }
29
+
30
+ if (typeof columnAction?.fn === 'function') {
31
+ columnAction.fn(row);
32
+ }
33
+ }
34
+
35
+ disabledCheck(columnAction, permission, defaultValue = false) {
36
+ let disabled = columnAction.disabled ?? defaultValue;
37
+ if (!disabled) {
38
+ disabled = permission && this.abilities.cannot(permission);
39
+ this.doesntHavePermissions = disabled;
40
+ }
41
+
42
+ return disabled;
43
+ }
44
+
45
+ visibilityCheck(columnAction, context) {
46
+ const isVisible = columnAction.isVisible;
47
+
48
+ if (isNone(context) || !isVisible) {
49
+ return true;
50
+ }
51
+
52
+ if (typeof isVisible === 'boolean') {
53
+ return isVisible;
54
+ }
55
+
56
+ if (typeof isVisible === 'function') {
57
+ return isVisible(context);
58
+ }
59
+
60
+ return true;
61
+ }
62
+ }
@@ -0,0 +1,28 @@
1
+ <div class="cell-dropdown-button overflow-visible {{@column.wrapperClass}}" {{did-insert this.setupComponent}} ...attributes>
2
+ <DropdownButton
3
+ @icon={{@column.ddButtonIcon}}
4
+ @iconPrefix={{@column.ddButtonIconPrefix}}
5
+ @text={{this.buttonText}}
6
+ @size="xs"
7
+ @horizontalPosition="left"
8
+ @calculatePosition={{this.calculatePosition}}
9
+ @renderInPlace={{true}}
10
+ @onOpen={{this.onOpen}}
11
+ @onClose={{this.onClose}}
12
+ as |dd|
13
+ >
14
+ <div class="next-dd-menu next-dd-menu-table-dd mt-0i {{@column.ddMenuWrapperClass}}" role="menu" aria-orientation="vertical" aria-labelledby="user-menu">
15
+ {{#if @column.ddMenuLabel}}
16
+ <div class="next-dd-menu-label-container px-1">
17
+ <div class="next-dd-menu-label {{@column.ddMenuLabelClass}}">
18
+ {{@column.ddMenuLabel}}
19
+ </div>
20
+ </div>
21
+ <div class="next-dd-menu-seperator"></div>
22
+ {{/if}}
23
+ {{#each @column.actions as |columnAction|}}
24
+ <Table::Cell::Dropdown::ActionItem @columnAction={{columnAction}} @row={{@row}} @column={{@column}} @dd={{dd}} />
25
+ {{/each}}
26
+ </div>
27
+ </DropdownButton>
28
+ </div>
@@ -0,0 +1,91 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { action, computed } from '@ember/object';
4
+
5
+ export default class TableCellDropdownComponent extends Component {
6
+ @tracked tableCellNode;
7
+ defaultButtonText = 'Actions';
8
+
9
+ @computed('args.column.ddButtonText', 'defaultButtonText') get buttonText() {
10
+ const { ddButtonText } = this.args.column;
11
+
12
+ if (ddButtonText === undefined) {
13
+ return this.defaultButtonText;
14
+ }
15
+
16
+ if (ddButtonText === false) {
17
+ return null;
18
+ }
19
+
20
+ return ddButtonText;
21
+ }
22
+
23
+ @action setupComponent(dropdownWrapperNode) {
24
+ const tableCellNode = this.getOwnerTableCell(dropdownWrapperNode);
25
+
26
+ if (tableCellNode) {
27
+ tableCellNode.style.overflow = 'visible';
28
+ }
29
+
30
+ this.tableCellNode = tableCellNode;
31
+ }
32
+
33
+ @action onOpen() {
34
+ if (!this.tableCellNode) {
35
+ return;
36
+ }
37
+
38
+ const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 0;
39
+ this.tableCellNode.style.zIndex = currentZIndex + 1;
40
+ }
41
+
42
+ @action onClose() {
43
+ if (!this.tableCellNode) {
44
+ return;
45
+ }
46
+
47
+ const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 1;
48
+ this.tableCellNode.style.zIndex = currentZIndex - 1;
49
+ }
50
+
51
+ @action getOwnerTableCell(dropdownWrapperNode) {
52
+ while (dropdownWrapperNode) {
53
+ dropdownWrapperNode = dropdownWrapperNode.parentNode;
54
+
55
+ if (dropdownWrapperNode?.tagName?.toLowerCase() === 'td') {
56
+ return dropdownWrapperNode;
57
+ }
58
+ }
59
+
60
+ return undefined;
61
+ }
62
+
63
+ @action onDropdownItemClick(columnAction, row, dd) {
64
+ if (typeof dd?.actions?.close === 'function') {
65
+ dd.actions.close();
66
+ }
67
+
68
+ if (typeof columnAction?.fn === 'function') {
69
+ columnAction.fn(row);
70
+ }
71
+ }
72
+
73
+ @action calculatePosition(trigger, content) {
74
+ if (typeof this.args.column?.calculatePosition === 'function') {
75
+ return this.args.column.calculatePosition(trigger, content);
76
+ }
77
+
78
+ const triggerRect = trigger.getBoundingClientRect();
79
+ const contentRect = content?.getBoundingClientRect?.();
80
+ const contentWidth = contentRect?.width || 224;
81
+
82
+ const style = {
83
+ position: 'fixed',
84
+ marginTop: '0px',
85
+ left: `${triggerRect.left - contentWidth - 3}px`,
86
+ top: `${triggerRect.top}px`,
87
+ };
88
+
89
+ return { style };
90
+ }
91
+ }
@@ -92,7 +92,7 @@
92
92
  <div class="rounded-md border border-gray-200 p-3 dark:border-gray-700 md:col-span-2">
93
93
  <div class="text-gray-400 dark:text-gray-500">Update Mode</div>
94
94
  <div class="mt-1 font-semibold text-gray-900 dark:text-gray-100">Provider polling</div>
95
- <div class="mt-1 text-gray-500 dark:text-gray-400">FleetOps polls this provider for device snapshots and telemetry updates.</div>
95
+ <div class="mt-1 text-gray-500 dark:text-gray-400">Fleet-Ops polls this provider for device snapshots and telemetry updates.</div>
96
96
  </div>
97
97
  {{/if}}
98
98
  </div>
@@ -70,7 +70,7 @@
70
70
  <div class="mb-3">
71
71
  <div>
72
72
  <h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Credentials</h2>
73
- <p class="text-xs text-gray-500 dark:text-gray-300">FleetOps uses these credentials to test the connection and sync devices.</p>
73
+ <p class="text-xs text-gray-500 dark:text-gray-300">Fleet-Ops uses these credentials to test the connection and sync devices.</p>
74
74
  </div>
75
75
  </div>
76
76
 
@@ -266,7 +266,7 @@
266
266
  </InputGroup>
267
267
  {{else}}
268
268
  <div class="rounded-md border border-dashed border-gray-300 p-3 text-xs text-gray-500 dark:border-gray-700 dark:text-gray-300 lg:col-span-2">
269
- This provider does not require FleetOps webhook configuration.
269
+ This provider does not require Fleet-Ops webhook configuration.
270
270
  </div>
271
271
  {{/if}}
272
272
  </div>