@fleetbase/fleetops-engine 0.6.53 → 0.6.55

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 (212) hide show
  1. package/addon/components/admin/routing-settings.hbs +2 -1
  2. package/addon/components/cell/attached-vehicle.hbs +10 -0
  3. package/addon/components/cell/attached-vehicle.js +58 -0
  4. package/addon/components/cell/device-identity.hbs +24 -0
  5. package/addon/components/cell/device-identity.js +129 -0
  6. package/addon/components/cell/driver-identity.hbs +33 -0
  7. package/addon/components/cell/driver-identity.js +143 -0
  8. package/addon/components/cell/equipment-identity.hbs +5 -0
  9. package/addon/components/cell/equipment-identity.js +48 -0
  10. package/addon/components/cell/part-identity.hbs +5 -0
  11. package/addon/components/cell/part-identity.js +63 -0
  12. package/addon/components/cell/telematic-device.hbs +38 -0
  13. package/addon/components/cell/telematic-device.js +44 -0
  14. package/addon/components/cell/telematic-provider.hbs +33 -16
  15. package/addon/components/cell/telematic-provider.js +50 -8
  16. package/addon/components/cell/vehicle-identity.hbs +33 -0
  17. package/addon/components/cell/vehicle-identity.js +138 -0
  18. package/addon/components/device/details.hbs +106 -135
  19. package/addon/components/device/details.js +174 -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 +46 -21
  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/equipment/form.js +8 -1
  31. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
  32. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  33. package/addon/components/layout/fleet-ops-sidebar.js +101 -17
  34. package/addon/components/map/drawer/device-event-listing.js +2 -0
  35. package/addon/components/map/drawer/driver-listing.js +2 -0
  36. package/addon/components/map/drawer/place-listing.js +2 -0
  37. package/addon/components/map/drawer/position-listing.hbs +11 -6
  38. package/addon/components/map/drawer/position-listing.js +17 -4
  39. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  40. package/addon/components/order/form/details.hbs +1 -43
  41. package/addon/components/order/form/details.js +18 -53
  42. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  43. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  44. package/addon/components/order/form/payload.js +48 -2
  45. package/addon/components/order/form/route.js +15 -0
  46. package/addon/components/order/form/service-rate.hbs +62 -11
  47. package/addon/components/order/form/service-rate.js +124 -2
  48. package/addon/components/order/form.hbs +2 -0
  49. package/addon/components/sensor/panel-header.hbs +47 -15
  50. package/addon/components/sensor/panel-header.js +59 -1
  51. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  52. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  53. package/addon/components/table/cell/dropdown.hbs +28 -0
  54. package/addon/components/table/cell/dropdown.js +91 -0
  55. package/addon/components/telematic/details.hbs +7 -1
  56. package/addon/components/telematic/form.js +16 -8
  57. package/addon/components/vendor/panel-header.hbs +82 -0
  58. package/addon/components/vendor/panel-header.js +48 -0
  59. package/addon/components/work-order/details.hbs +5 -1
  60. package/addon/components/work-order/form.hbs +26 -4
  61. package/addon/components/work-order/form.js +5 -1
  62. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  63. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  64. package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
  65. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  66. package/addon/controllers/connectivity/devices/index.js +144 -129
  67. package/addon/controllers/connectivity/events/details.js +3 -0
  68. package/addon/controllers/connectivity/events/index.js +187 -11
  69. package/addon/controllers/connectivity/sensors/index.js +156 -14
  70. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  71. package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
  72. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  73. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  74. package/addon/controllers/maintenance/equipment/index.js +1 -2
  75. package/addon/controllers/maintenance/parts/index.js +1 -2
  76. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  77. package/addon/controllers/management/drivers/index.js +82 -42
  78. package/addon/controllers/management/fuel-reports/index.js +63 -4
  79. package/addon/controllers/management/issues/index.js +63 -4
  80. package/addon/controllers/management/vehicles/index.js +33 -6
  81. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  82. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  83. package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
  84. package/addon/routes/connectivity/devices/index.js +7 -0
  85. package/addon/routes/connectivity/events/details.js +3 -0
  86. package/addon/routes/connectivity/events/index.js +6 -2
  87. package/addon/routes/connectivity/sensors/index.js +7 -2
  88. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  89. package/addon/routes/connectivity/telematics/details/devices.js +6 -0
  90. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  91. package/addon/routes/connectivity/telematics/details.js +1 -1
  92. package/addon/routes/connectivity/telematics/edit.js +1 -1
  93. package/addon/routes/maintenance/work-orders/index.js +1 -0
  94. package/addon/routes.js +2 -0
  95. package/addon/services/device-actions.js +30 -0
  96. package/addon/services/device-event-actions.js +1 -1
  97. package/addon/services/equipment-actions.js +1 -0
  98. package/addon/services/order-creation.js +54 -1
  99. package/addon/services/order-validation.js +3 -2
  100. package/addon/services/sensor-actions.js +3 -0
  101. package/addon/services/service-rate-actions.js +40 -1
  102. package/addon/services/vehicle-actions.js +24 -12
  103. package/addon/services/vendor-actions.js +3 -0
  104. package/addon/styles/fleetops-engine.css +28 -4
  105. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  106. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  107. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
  108. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  109. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  110. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  111. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  112. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  113. package/addon/templates/management/vendors/index/details.hbs +2 -2
  114. package/addon/templates/settings/notifications.hbs +2 -1
  115. package/addon/templates/settings/routing.hbs +1 -0
  116. package/addon/utils/device-table-columns.js +288 -0
  117. package/addon/utils/fleet-ops-options.js +38 -0
  118. package/addon/utils/identity-cell-resource.js +20 -0
  119. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  120. package/addon/utils/trackable-option.js +93 -0
  121. package/app/components/cell/attached-vehicle.js +1 -0
  122. package/app/components/cell/device-identity.js +1 -0
  123. package/app/components/cell/driver-identity.js +1 -0
  124. package/app/components/cell/equipment-identity.js +1 -0
  125. package/app/components/cell/part-identity.js +1 -0
  126. package/app/components/cell/telematic-device.js +1 -0
  127. package/app/components/cell/vehicle-identity.js +1 -0
  128. package/app/components/device/panel-tabs/events.hbs +30 -0
  129. package/app/components/device/panel-tabs/events.js +128 -0
  130. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  131. package/app/components/device/panel-tabs/sensors.js +93 -0
  132. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  133. package/app/components/device/panel-tabs/vehicle.js +90 -0
  134. package/app/components/order/form/orchestrator-constraints.js +1 -0
  135. package/app/components/table/cell/dropdown/action-item.js +1 -0
  136. package/app/components/table/cell/dropdown.js +1 -0
  137. package/app/components/vendor/panel-header.js +1 -0
  138. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  139. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  140. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  141. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  142. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  143. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  144. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  145. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  146. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  147. package/app/utils/map-drawer-dropdown-position.js +1 -0
  148. package/app/utils/trackable-option.js +1 -0
  149. package/composer.json +1 -1
  150. package/extension.json +1 -1
  151. package/package.json +4 -4
  152. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  153. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  154. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  155. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  156. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  157. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  158. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  159. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
  160. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
  161. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  162. package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
  163. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  164. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  165. package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
  166. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  167. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  168. package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +80 -0
  170. package/server/src/Http/Filter/SensorFilter.php +125 -0
  171. package/server/src/Http/Filter/VehicleFilter.php +13 -0
  172. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  173. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  174. package/server/src/Http/Resources/v1/Driver.php +11 -2
  175. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  176. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  177. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  178. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  179. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  180. package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
  181. package/server/src/Models/Device.php +17 -1
  182. package/server/src/Models/DeviceEvent.php +82 -1
  183. package/server/src/Models/Driver.php +16 -0
  184. package/server/src/Models/Equipment.php +55 -0
  185. package/server/src/Models/Sensor.php +15 -2
  186. package/server/src/Models/Telematic.php +1 -1
  187. package/server/src/Models/Vehicle.php +5 -1
  188. package/server/src/Models/WorkOrder.php +5 -2
  189. package/server/src/Observers/DriverObserver.php +3 -3
  190. package/server/src/Observers/FleetObserver.php +23 -0
  191. package/server/src/Observers/VehicleObserver.php +3 -3
  192. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  193. package/server/src/Support/LiveCacheService.php +1 -1
  194. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
  195. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  196. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  197. package/server/src/Support/Telematics/Providers/SafeeProvider.php +70 -5
  198. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  199. package/server/src/Support/Telematics/TelematicService.php +330 -30
  200. package/server/src/routes.php +1 -1
  201. package/server/tests/DeviceFilterTest.php +45 -0
  202. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  203. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  204. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  205. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  206. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  207. package/server/tests/TelematicsHardeningTest.php +598 -6
  208. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  209. package/translations/en-us.yaml +1 -0
  210. package/addon/controllers/connectivity/events/index/details.js +0 -3
  211. package/addon/routes/connectivity/events/index/details.js +0 -3
  212. /package/addon/templates/connectivity/events/{index/details.hbs → details.hbs} +0 -0
@@ -0,0 +1,128 @@
1
+ import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+ import { tracked } from '@glimmer/tracking';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ const severityOptions = [
8
+ { label: 'Info', value: 'info' },
9
+ { label: 'Warning', value: 'warning' },
10
+ { label: 'Error', value: 'error' },
11
+ { label: 'Critical', value: 'critical' },
12
+ { label: 'High', value: 'high' },
13
+ ];
14
+
15
+ function toRecentList(records) {
16
+ const list = Array.from(records ?? []);
17
+
18
+ list.meta = {
19
+ current_page: 1,
20
+ last_page: 1,
21
+ per_page: list.length,
22
+ total: list.length,
23
+ from: list.length > 0 ? 1 : 0,
24
+ to: list.length,
25
+ };
26
+
27
+ return list;
28
+ }
29
+
30
+ export default class DevicePanelTabsEventsComponent extends Component {
31
+ @service deviceEventActions;
32
+ @service store;
33
+
34
+ @tracked events = toRecentList();
35
+
36
+ constructor() {
37
+ super(...arguments);
38
+ this.loadEvents.perform();
39
+ }
40
+
41
+ get device() {
42
+ return this.args.resource ?? this.args.model;
43
+ }
44
+
45
+ get columns() {
46
+ return [
47
+ {
48
+ sticky: true,
49
+ label: 'Event',
50
+ valuePath: 'event_type',
51
+ cellComponent: 'table/cell/anchor',
52
+ action: this.deviceEventActions.panel?.view ?? this.deviceEventActions.transition.view,
53
+ permission: 'fleet-ops view device-event',
54
+ resizable: true,
55
+ },
56
+ {
57
+ label: 'Severity',
58
+ valuePath: 'severity',
59
+ cellComponent: 'table/cell/status',
60
+ filterOptions: severityOptions,
61
+ resizable: true,
62
+ },
63
+ {
64
+ label: 'Message',
65
+ valuePath: 'message',
66
+ resizable: true,
67
+ },
68
+ {
69
+ label: 'Code',
70
+ valuePath: 'code',
71
+ resizable: true,
72
+ },
73
+ {
74
+ label: 'Processed',
75
+ valuePath: 'processedAt',
76
+ sortParam: 'processed_at',
77
+ resizable: true,
78
+ },
79
+ {
80
+ label: 'Occurred',
81
+ valuePath: 'occurredAt',
82
+ sortParam: 'occurred_at',
83
+ resizable: true,
84
+ },
85
+ {
86
+ label: '',
87
+ cellComponent: 'table/cell/dropdown',
88
+ ddButtonText: false,
89
+ ddButtonIcon: 'ellipsis-h',
90
+ ddButtonIconPrefix: 'fas',
91
+ wrapperClass: 'flex items-center justify-end mx-2',
92
+ sticky: 'right',
93
+ width: 60,
94
+ actions: [
95
+ {
96
+ label: 'View event',
97
+ fn: this.deviceEventActions.panel?.view ?? this.deviceEventActions.transition.view,
98
+ permission: 'fleet-ops view device-event',
99
+ },
100
+ {
101
+ label: 'Mark processed',
102
+ fn: this.markProcessed,
103
+ permission: 'fleet-ops update device-event',
104
+ },
105
+ ],
106
+ },
107
+ ];
108
+ }
109
+
110
+ @action async markProcessed(deviceEvent) {
111
+ await this.deviceEventActions.markProcessed(deviceEvent);
112
+ await this.loadEvents.perform();
113
+ }
114
+
115
+ @action refreshEvents() {
116
+ return this.loadEvents.perform();
117
+ }
118
+
119
+ @task *loadEvents() {
120
+ if (!this.device?.id) {
121
+ this.events = toRecentList();
122
+ return;
123
+ }
124
+
125
+ const events = yield this.store.query('device-event', { device_uuid: this.device.id, limit: 10, sort: '-created_at' });
126
+ this.events = toRecentList(events);
127
+ }
128
+ }
@@ -0,0 +1,30 @@
1
+ <div class="flex flex-col gap-3 p-3" ...attributes>
2
+ <div class="flex items-start justify-between gap-3">
3
+ <div class="min-w-0">
4
+ <h2 class="text-base font-bold text-gray-900 dark:text-gray-100">Sensor Inventory</h2>
5
+ <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Recent sensor readings and health for this device.</p>
6
+ </div>
7
+ <Button @icon="refresh" @size="xs" @onClick={{this.refreshSensors}} @isLoading={{this.loadSensors.isRunning}} />
8
+ </div>
9
+
10
+ <Layout::Resource::Tabular
11
+ @resource="sensor"
12
+ @title="Device Sensors"
13
+ @data={{this.sensors}}
14
+ @columns={{this.columns}}
15
+ @controller={{this}}
16
+ @pagination={{false}}
17
+ @withoutHeader={{true}}
18
+ @tableWrapperClass="no-table-extra-spacing"
19
+ @emptyStateComponent={{component
20
+ "table/empty-state"
21
+ variant="compact"
22
+ icon="gauge-high"
23
+ title="No sensors connected"
24
+ description="Sensors appear here after provider telemetry links them to this device."
25
+ primaryText="Refresh"
26
+ primaryIcon="refresh"
27
+ primaryAction=this.refreshSensors
28
+ }}
29
+ />
30
+ </div>
@@ -0,0 +1,93 @@
1
+ import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+ import { tracked } from '@glimmer/tracking';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ function toRecentList(records) {
8
+ const list = Array.from(records ?? []);
9
+
10
+ list.meta = {
11
+ current_page: 1,
12
+ last_page: 1,
13
+ per_page: list.length,
14
+ total: list.length,
15
+ from: list.length > 0 ? 1 : 0,
16
+ to: list.length,
17
+ };
18
+
19
+ return list;
20
+ }
21
+
22
+ export default class DevicePanelTabsSensorsComponent extends Component {
23
+ @service sensorActions;
24
+ @service store;
25
+
26
+ @tracked sensors = toRecentList();
27
+
28
+ constructor() {
29
+ super(...arguments);
30
+ this.loadSensors.perform();
31
+ }
32
+
33
+ get device() {
34
+ return this.args.resource ?? this.args.model;
35
+ }
36
+
37
+ get columns() {
38
+ return [
39
+ {
40
+ sticky: true,
41
+ label: 'Sensor',
42
+ valuePath: 'name',
43
+ cellComponent: 'table/cell/anchor',
44
+ action: this.sensorActions.panel?.view ?? this.sensorActions.transition.view,
45
+ permission: 'fleet-ops view sensor',
46
+ resizable: true,
47
+ },
48
+ {
49
+ label: 'Type',
50
+ valuePath: 'type',
51
+ cellComponent: 'table/cell/base',
52
+ humanize: true,
53
+ resizable: true,
54
+ },
55
+ {
56
+ label: 'Value',
57
+ valuePath: 'last_value',
58
+ resizable: true,
59
+ },
60
+ {
61
+ label: 'Unit',
62
+ valuePath: 'unit',
63
+ resizable: true,
64
+ },
65
+ {
66
+ label: 'Status',
67
+ valuePath: 'status',
68
+ cellComponent: 'table/cell/status',
69
+ resizable: true,
70
+ },
71
+ {
72
+ label: 'Last Reading',
73
+ valuePath: 'lastReadingAt',
74
+ sortParam: 'last_reading_at',
75
+ resizable: true,
76
+ },
77
+ ];
78
+ }
79
+
80
+ @action refreshSensors() {
81
+ return this.loadSensors.perform();
82
+ }
83
+
84
+ @task *loadSensors() {
85
+ if (!this.device?.id) {
86
+ this.sensors = toRecentList();
87
+ return;
88
+ }
89
+
90
+ const sensors = yield this.store.query('sensor', { device_uuid: this.device.id, limit: 10, sort: '-updated_at' });
91
+ this.sensors = toRecentList(sensors);
92
+ }
93
+ }
@@ -0,0 +1,68 @@
1
+ <div class="flex flex-col gap-3 p-3" ...attributes>
2
+ <div class="flex items-start justify-between gap-3">
3
+ <div class="min-w-0">
4
+ <h2 class="text-base font-bold text-gray-900 dark:text-gray-100">Vehicle Attachment</h2>
5
+ <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Fleet context for telemetry from this device.</p>
6
+ </div>
7
+ <div class="flex flex-wrap items-center justify-end gap-2">
8
+ {{#if this.hasVehicle}}
9
+ {{#if this.canOpenVehicle}}
10
+ <Button @icon="eye" @text="Open Vehicle" @size="xs" @onClick={{this.openVehicle}} />
11
+ {{/if}}
12
+ {{#if this.canLocateVehicle}}
13
+ <Button @icon="location-dot" @text="Locate" @size="xs" @onClick={{this.locateVehicle}} />
14
+ {{/if}}
15
+ <Button @icon="shuffle" @text="Change Vehicle" @size="xs" @onClick={{this.attachToVehicle}} />
16
+ <Button @icon="unlink" @text="Detach" @size="xs" @type="danger" @onClick={{this.detachFromVehicle}} />
17
+ {{else}}
18
+ <Button @icon="link" @text="Attach Vehicle" @size="xs" @onClick={{this.attachToVehicle}} />
19
+ {{/if}}
20
+ </div>
21
+ </div>
22
+
23
+ {{#if this.hasVehicle}}
24
+ <section class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
25
+ <div class="flex gap-3 p-3">
26
+ <Image
27
+ src={{this.vehiclePhotoUrl}}
28
+ @fallbackSrc={{config "defaultValues.vehicleAvatar"}}
29
+ alt={{this.vehicleName}}
30
+ class="h-16 w-16 flex-shrink-0 rounded-md object-cover shadow-sm"
31
+ />
32
+ <div class="min-w-0 flex-1">
33
+ <div class="flex flex-wrap items-center gap-2">
34
+ <h3 class="truncate text-base font-bold text-gray-900 dark:text-gray-100">{{this.vehicleName}}</h3>
35
+ {{#if this.vehicleStatus}}
36
+ <Badge @status={{this.vehicleStatus}} @size="xs">{{smart-humanize this.vehicleStatus}}</Badge>
37
+ {{/if}}
38
+ </div>
39
+ <div class="mt-1 text-xs text-gray-500 dark:text-gray-300">{{n-a this.vehicleSubtitle}}</div>
40
+ <div class="mt-3 grid grid-cols-1 gap-2 text-xs sm:grid-cols-3">
41
+ <div>
42
+ <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Driver</div>
43
+ <div class="truncate text-gray-800 dark:text-gray-100">{{n-a this.vehicleDriverName}}</div>
44
+ </div>
45
+ <div>
46
+ <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Attached Device</div>
47
+ <div class="truncate text-gray-800 dark:text-gray-100">{{n-a this.device.displayName this.device.name}}</div>
48
+ </div>
49
+ <div>
50
+ <div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Device Last Seen</div>
51
+ <div class="truncate text-gray-800 dark:text-gray-100">{{n-a (format-date-fns this.device.last_online_at "dd MMM yyyy, HH:mm")}}</div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </section>
57
+ {{else}}
58
+ <Telematic::TabEmptyState
59
+ @tone="warning"
60
+ @icon="link-slash"
61
+ @title="No vehicle attached"
62
+ @message="Attach this device to a vehicle so telemetry has fleet context."
63
+ @primaryText="Attach Vehicle"
64
+ @primaryIcon="link"
65
+ @primaryAction={{this.attachToVehicle}}
66
+ />
67
+ {{/if}}
68
+ </div>
@@ -0,0 +1,90 @@
1
+ import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
4
+
5
+ export default class DevicePanelTabsVehicleComponent extends Component {
6
+ @service deviceActions;
7
+ @service hostRouter;
8
+ @service mapManager;
9
+ @service vehicleActions;
10
+
11
+ get device() {
12
+ return this.args.resource ?? this.args.model;
13
+ }
14
+
15
+ get vehicle() {
16
+ return this.device?.attachable;
17
+ }
18
+
19
+ get vehicleName() {
20
+ return this.device?.attached_to_name ?? this.vehicle?.displayName ?? this.vehicle?.display_name ?? this.vehicle?.name;
21
+ }
22
+
23
+ get vehicleSubtitle() {
24
+ return this.vehicle?.plate_number ?? this.vehicle?.call_sign ?? this.vehicle?.vin ?? this.vehicle?.public_id ?? this.device?.attachable_uuid;
25
+ }
26
+
27
+ get vehiclePhotoUrl() {
28
+ return this.vehicle?.photo_url ?? this.vehicle?.avatar_url;
29
+ }
30
+
31
+ get vehicleStatus() {
32
+ return this.vehicle?.status ?? (this.vehicle?.online ? 'online' : null);
33
+ }
34
+
35
+ get vehicleDriverName() {
36
+ return this.vehicle?.driver?.displayName ?? this.vehicle?.driver?.display_name ?? this.vehicle?.driver?.name ?? this.vehicle?.driver_name;
37
+ }
38
+
39
+ get hasVehicle() {
40
+ return Boolean(this.vehicleName || this.device?.attachable_uuid);
41
+ }
42
+
43
+ get canOpenVehicle() {
44
+ return Boolean(this.vehicle?.id);
45
+ }
46
+
47
+ get canLocateVehicle() {
48
+ return Boolean(this.vehicle?.id && (this.vehicle?.location || this.vehicle?.last_position));
49
+ }
50
+
51
+ @action attachToVehicle() {
52
+ return this.deviceActions.attachToVehicle(this.device, { callback: () => this.device?.reload?.() });
53
+ }
54
+
55
+ @action detachFromVehicle() {
56
+ return this.deviceActions.detachFromVehicle(this.device, { callback: () => this.device?.reload?.() });
57
+ }
58
+
59
+ @action openVehicle() {
60
+ if (this.vehicle?.id) {
61
+ return this.vehicleActions.panel?.view
62
+ ? this.vehicleActions.panel.view(this.vehicle)
63
+ : this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details', this.vehicle);
64
+ }
65
+ }
66
+
67
+ @action async locateVehicle() {
68
+ if (!this.vehicle?.id) {
69
+ return;
70
+ }
71
+
72
+ await this.transitionToLiveMap();
73
+ await this.mapManager.waitForMap({ timeoutMs: 8000 });
74
+
75
+ this.mapManager.focusResource(this.vehicle, 16, {
76
+ paddingBottomRight: [300, 200],
77
+ moveend: () => {
78
+ this.vehicleActions.panel?.view?.(this.vehicle, { closeOnTransition: true });
79
+ },
80
+ });
81
+ }
82
+
83
+ async transitionToLiveMap() {
84
+ try {
85
+ await this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index', { queryParams: { layout: 'map' } });
86
+ } catch (_) {
87
+ // Keep locate usable if another transition is already active.
88
+ }
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/order/form/orchestrator-constraints';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/table/cell/dropdown/action-item';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/table/cell/dropdown';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/vendor/panel-header';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/connectivity/devices/index/details/sensors';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/connectivity/devices/index/details/vehicle';
@@ -1 +1 @@
1
- export { default } from '@fleetbase/fleetops-engine/controllers/connectivity/events/index/details';
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/connectivity/events/details';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/routes/connectivity/devices/index/details/sensors';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/routes/connectivity/devices/index/details/vehicle';
@@ -1 +1 @@
1
- export { default } from '@fleetbase/fleetops-engine/routes/connectivity/events/index/details';
1
+ export { default } from '@fleetbase/fleetops-engine/routes/connectivity/events/details';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/templates/connectivity/devices/index/details/sensors';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/templates/connectivity/devices/index/details/vehicle';
@@ -1 +1 @@
1
- export { default } from '@fleetbase/fleetops-engine/templates/connectivity/events/index/details';
1
+ export { default } from '@fleetbase/fleetops-engine/templates/connectivity/events/details';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/utils/map-drawer-dropdown-position';
@@ -0,0 +1 @@
1
+ export * from '@fleetbase/fleetops-engine/utils/trackable-option';
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.53",
3
+ "version": "0.6.55",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Fleet-Ops",
3
- "version": "0.6.53",
3
+ "version": "0.6.55",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.6.53",
3
+ "version": "0.6.55",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -42,9 +42,9 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@babel/core": "^7.23.2",
45
- "@fleetbase/ember-core": "^0.3.22",
46
- "@fleetbase/ember-ui": "^0.3.35",
47
- "@fleetbase/fleetops-data": "^0.1.38",
45
+ "@fleetbase/ember-core": "^0.3.23",
46
+ "@fleetbase/ember-ui": "^0.3.37",
47
+ "@fleetbase/fleetops-data": "^0.1.40",
48
48
  "@fleetbase/leaflet-routing-machine": "^3.2.17",
49
49
  "@fortawesome/ember-fontawesome": "^2.0.0",
50
50
  "@fortawesome/fontawesome-svg-core": "6.4.0",
@@ -19,6 +19,7 @@ return new class extends Migration {
19
19
 
20
20
  $table->string('code')->nullable()->index(); // external WO number
21
21
  $table->string('subject')->index();
22
+ $table->string('category')->nullable()->index(); // preventive_maintenance, tire_issue, breakdown, etc.
22
23
  $table->string('status')->default('open')->index(); // open, in_progress, blocked, done, canceled
23
24
  $table->string('priority')->nullable()->index(); // low, normal, high, critical
24
25
 
@@ -0,0 +1,32 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ */
11
+ public function up(): void
12
+ {
13
+ if (!Schema::hasColumn('work_orders', 'category')) {
14
+ Schema::table('work_orders', function (Blueprint $table) {
15
+ $table->string('category')->nullable()->index()->after('subject');
16
+ });
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Reverse the migrations.
22
+ */
23
+ public function down(): void
24
+ {
25
+ if (Schema::hasColumn('work_orders', 'category')) {
26
+ Schema::table('work_orders', function (Blueprint $table) {
27
+ $table->dropIndex(['category']);
28
+ $table->dropColumn('category');
29
+ });
30
+ }
31
+ }
32
+ };
@@ -155,18 +155,19 @@ class MaintenanceSeeder extends Seeder
155
155
  $driver = $this->seededModel(Driver::class, 'driver_mira');
156
156
  $vehicle = $this->seededModel(Vehicle::class, 'truck_maintenance');
157
157
  $workOrders = [
158
- 'work_order_open' => ['WO-TEST-OPEN', 'Open Inspection', 'open', 'medium', $this->timestamp(-4), $this->timestamp(24), null, 'schedule_due_soon'],
159
- 'work_order_overdue' => ['WO-TEST-OVERDUE', 'Overdue Service', 'open', 'high', $this->timestamp(-120), $this->timestamp(-24), null, 'schedule_overdue'],
160
- 'work_order_completed' => ['WO-TEST-CLOSED', 'Completed Repair', 'completed', 'low', $this->timestamp(-240), $this->timestamp(-120), $this->timestamp(-96), 'schedule_overdue'],
158
+ 'work_order_open' => ['WO-TEST-OPEN', 'Open Inspection', 'inspection_request', 'open', 'medium', $this->timestamp(-4), $this->timestamp(24), null, 'schedule_due_soon'],
159
+ 'work_order_overdue' => ['WO-TEST-OVERDUE', 'Overdue Service', 'preventive_maintenance', 'open', 'high', $this->timestamp(-120), $this->timestamp(-24), null, 'schedule_overdue'],
160
+ 'work_order_completed' => ['WO-TEST-CLOSED', 'Completed Repair', 'general_repair', 'completed', 'low', $this->timestamp(-240), $this->timestamp(-120), $this->timestamp(-96), 'schedule_overdue'],
161
161
  ];
162
162
 
163
163
  $models = [];
164
- foreach ($workOrders as $seedId => [$code, $subject, $status, $priority, $openedAt, $dueAt, $closedAt, $scheduleSeedId]) {
164
+ foreach ($workOrders as $seedId => [$code, $subject, $category, $status, $priority, $openedAt, $dueAt, $closedAt, $scheduleSeedId]) {
165
165
  $models[$seedId] = $this->createRecord(WorkOrder::class, [
166
166
  '_key' => $this->fixtureKey($seedId),
167
167
  'company_uuid' => $company->uuid,
168
168
  'code' => $code,
169
169
  'subject' => $subject,
170
+ 'category' => $category,
170
171
  'status' => $status,
171
172
  'priority' => $priority,
172
173
  'target_type' => Vehicle::class,
@@ -124,6 +124,7 @@ class ProcessMaintenanceTriggers extends Command
124
124
  'company_uuid' => $schedule->company_uuid,
125
125
  'schedule_uuid' => $schedule->uuid,
126
126
  'subject' => $schedule->name,
127
+ 'category' => 'preventive_maintenance',
127
128
  'code' => $woCode,
128
129
  'status' => 'open',
129
130
  'priority' => $schedule->default_priority ?? 'normal',