@fleetbase/fleetops-engine 0.6.52 → 0.6.54

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 (151) hide show
  1. package/addon/components/device/details.hbs +106 -135
  2. package/addon/components/device/details.js +174 -1
  3. package/addon/components/device/panel-header.hbs +2 -2
  4. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  5. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
  6. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  7. package/addon/components/layout/fleet-ops-sidebar.js +364 -395
  8. package/addon/components/maintenance-schedule/form.js +1 -0
  9. package/addon/components/order/form/details.hbs +1 -43
  10. package/addon/components/order/form/details.js +18 -53
  11. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  12. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  13. package/addon/components/order/form/payload.js +50 -2
  14. package/addon/components/order/form/route.js +15 -0
  15. package/addon/components/order/form/service-rate.hbs +62 -11
  16. package/addon/components/order/form/service-rate.js +124 -2
  17. package/addon/components/order/form.hbs +2 -0
  18. package/addon/components/telematic/details.hbs +7 -1
  19. package/addon/components/telematic/form.js +16 -8
  20. package/addon/components/work-order/details.hbs +5 -1
  21. package/addon/components/work-order/form.hbs +26 -4
  22. package/addon/components/work-order/form.js +5 -1
  23. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  24. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  25. package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
  26. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -1
  28. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  29. package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
  30. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  31. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  32. package/addon/controllers/maintenance/index.js +100 -0
  33. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  34. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  35. package/addon/controllers/management/index.js +88 -0
  36. package/addon/controllers/settings/index.js +142 -0
  37. package/addon/extension.js +21 -0
  38. package/addon/routes/analytics/index.js +3 -0
  39. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  40. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  41. package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
  42. package/addon/routes/connectivity/devices/index.js +3 -0
  43. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  44. package/addon/routes/connectivity/telematics/details/devices.js +4 -0
  45. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  46. package/addon/routes/connectivity/telematics/details.js +1 -1
  47. package/addon/routes/connectivity/telematics/edit.js +1 -1
  48. package/addon/routes/maintenance/index.js +3 -0
  49. package/addon/routes/maintenance/work-orders/index.js +3 -0
  50. package/addon/routes/settings/index.js +3 -0
  51. package/addon/routes.js +2 -0
  52. package/addon/services/order-creation.js +54 -1
  53. package/addon/services/order-validation.js +3 -2
  54. package/addon/services/service-rate-actions.js +1 -0
  55. package/addon/styles/fleetops-engine.css +1821 -6
  56. package/addon/templates/analytics/index.hbs +14 -0
  57. package/addon/templates/analytics/reports/index.hbs +4 -4
  58. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  59. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  60. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
  61. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  62. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  63. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  64. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  65. package/addon/templates/maintenance/index.hbs +113 -0
  66. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  67. package/addon/templates/management/index.hbs +109 -73
  68. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  69. package/addon/templates/operations/orders/index.hbs +1 -1
  70. package/addon/templates/operations/service-rates/index.hbs +1 -1
  71. package/addon/templates/settings/index.hbs +83 -0
  72. package/addon/templates/settings/routing.hbs +2 -2
  73. package/addon/utils/fleet-ops-options.js +38 -0
  74. package/app/components/order/form/orchestrator-constraints.js +1 -0
  75. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  76. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  77. package/app/controllers/maintenance/index.js +1 -0
  78. package/app/controllers/management/index.js +1 -0
  79. package/app/controllers/settings/index.js +1 -0
  80. package/app/routes/analytics/index.js +1 -0
  81. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  82. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  83. package/app/routes/maintenance/index.js +1 -0
  84. package/app/routes/settings/index.js +1 -0
  85. package/app/templates/analytics/index.js +1 -0
  86. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  87. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  88. package/app/templates/maintenance/index.js +1 -0
  89. package/app/templates/management/index.js +1 -0
  90. package/app/templates/settings/index.js +1 -0
  91. package/composer.json +1 -1
  92. package/extension.json +1 -1
  93. package/package.json +3 -3
  94. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  95. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  96. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  97. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  98. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  99. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  100. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  101. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  102. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  103. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
  104. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
  105. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  106. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  107. package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
  108. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  109. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  112. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  113. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  114. package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
  115. package/server/src/Http/Filter/DeviceFilter.php +131 -0
  116. package/server/src/Http/Filter/DriverFilter.php +6 -0
  117. package/server/src/Http/Filter/SensorFilter.php +125 -0
  118. package/server/src/Http/Filter/VehicleFilter.php +19 -0
  119. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  120. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  121. package/server/src/Http/Resources/v1/Driver.php +11 -2
  122. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  123. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  124. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  125. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  126. package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
  127. package/server/src/Models/Device.php +15 -1
  128. package/server/src/Models/DeviceEvent.php +1 -1
  129. package/server/src/Models/Driver.php +17 -1
  130. package/server/src/Models/Sensor.php +15 -2
  131. package/server/src/Models/Telematic.php +1 -1
  132. package/server/src/Models/Vehicle.php +5 -1
  133. package/server/src/Models/WorkOrder.php +5 -2
  134. package/server/src/Observers/DriverObserver.php +3 -3
  135. package/server/src/Observers/FleetObserver.php +23 -0
  136. package/server/src/Observers/VehicleObserver.php +3 -3
  137. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  138. package/server/src/Support/LiveCacheService.php +1 -1
  139. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
  140. package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
  141. package/server/src/Support/Telematics/TelematicService.php +330 -30
  142. package/server/src/routes.php +10 -0
  143. package/server/tests/DeviceFilterTest.php +36 -0
  144. package/server/tests/HubRoutesTest.php +62 -0
  145. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  146. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  147. package/server/tests/TelematicsHardeningTest.php +455 -3
  148. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  149. package/translations/ar-ae.yml +1 -1
  150. package/translations/en-us.yaml +1 -0
  151. package/translations/vi-vn.yaml +1 -1
@@ -2,6 +2,15 @@ import Controller from '@ember/controller';
2
2
  import { action } from '@ember/object';
3
3
  import { inject as service } from '@ember/service';
4
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
+ ];
5
14
 
6
15
  export default class ConnectivityTelematicsDetailsEventsController extends Controller {
7
16
  @service deviceEventActions;
@@ -99,15 +108,19 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
99
108
  ];
100
109
  }
101
110
 
102
- @tracked actionButtons = [
103
- {
104
- icon: 'refresh',
105
- size: 'xs',
106
- onClick: this.refresh,
107
- helpText: this.intl.t('common.refresh'),
108
- wrapperClass: 'fleetops-telematics-action-button',
109
- },
110
- ];
111
+ get actionButtons() {
112
+ return [
113
+ {
114
+ icon: 'refresh',
115
+ size: 'sm',
116
+ onClick: this.refresh,
117
+ helpText: this.intl.t('common.refresh'),
118
+ wrapperClass: 'fleetops-telematics-action-button',
119
+ isLoading: this.refreshTask.isRunning,
120
+ disabled: this.refreshTask.isRunning,
121
+ },
122
+ ];
123
+ }
111
124
 
112
125
  @tracked bulkActions = [];
113
126
 
@@ -136,6 +149,8 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
136
149
  filterComponentPlaceholder: 'Select device',
137
150
  filterParam: 'device_uuid',
138
151
  model: 'device',
152
+ modelNamePath: 'displayName',
153
+ query: { telematic_uuid: this.telematic?.id },
139
154
  },
140
155
  {
141
156
  label: 'Provider',
@@ -155,7 +170,10 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
155
170
  sortable: true,
156
171
  filterable: true,
157
172
  filterParam: 'severity',
158
- filterComponent: 'filter/string',
173
+ filterComponent: 'filter/multi-option',
174
+ filterOptions: severityOptions,
175
+ filterOptionLabel: 'label',
176
+ filterOptionValue: 'value',
159
177
  },
160
178
  {
161
179
  label: 'Processed',
@@ -170,6 +188,8 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
170
188
  { label: 'Processed', value: 'processed' },
171
189
  { label: 'Unprocessed', value: 'unprocessed' },
172
190
  ],
191
+ filterOptionLabel: 'label',
192
+ filterOptionValue: 'value',
173
193
  },
174
194
  {
175
195
  label: 'Occurred',
@@ -222,7 +242,11 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
222
242
  }
223
243
 
224
244
  @action refresh() {
225
- return this.hostRouter.refresh();
245
+ if (this.refreshTask.isRunning) {
246
+ return;
247
+ }
248
+
249
+ return this.refreshTask.perform();
226
250
  }
227
251
 
228
252
  @action clearFilters() {
@@ -241,4 +265,8 @@ export default class ConnectivityTelematicsDetailsEventsController extends Contr
241
265
  await this.deviceEventActions.markProcessed(deviceEvent);
242
266
  await this.hostRouter.refresh();
243
267
  }
268
+
269
+ @task *refreshTask() {
270
+ yield this.hostRouter.refresh();
271
+ }
244
272
  }
@@ -2,6 +2,7 @@ import Controller from '@ember/controller';
2
2
  import { action } from '@ember/object';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { tracked } from '@glimmer/tracking';
5
+ import { task } from 'ember-concurrency';
5
6
  import fleetOpsOptions from '../../../../utils/fleet-ops-options';
6
7
 
7
8
  export default class ConnectivityTelematicsDetailsSensorsController extends Controller {
@@ -10,7 +11,7 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
10
11
  @service hostRouter;
11
12
  @service intl;
12
13
 
13
- @tracked queryParams = ['page', 'limit', 'sort', 'query', 'status', 'type', 'device_uuid'];
14
+ @tracked queryParams = ['page', 'limit', 'sort', 'query', 'status', 'type', 'device_uuid', 'last_reading_at'];
14
15
  @tracked telematic;
15
16
  @tracked page = 1;
16
17
  @tracked limit;
@@ -19,6 +20,7 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
19
20
  @tracked status;
20
21
  @tracked type;
21
22
  @tracked device_uuid;
23
+ @tracked last_reading_at;
22
24
 
23
25
  get sensors() {
24
26
  return Array.from(this.model ?? []);
@@ -37,7 +39,7 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
37
39
  }
38
40
 
39
41
  get hasActiveFilters() {
40
- return Boolean(this.query || this.status || this.type || this.device_uuid);
42
+ return Boolean(this.query || this.status || this.type || this.device_uuid || this.last_reading_at);
41
43
  }
42
44
 
43
45
  get hasSensors() {
@@ -122,22 +124,26 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
122
124
  ];
123
125
  }
124
126
 
125
- @tracked actionButtons = [
126
- {
127
- icon: 'refresh',
128
- size: 'xs',
129
- onClick: this.refresh,
130
- helpText: this.intl.t('common.refresh'),
131
- wrapperClass: 'fleetops-telematics-action-button',
132
- },
133
- {
134
- icon: 'microchip',
135
- text: 'View Devices',
136
- size: 'xs',
137
- onClick: this.goToDevices,
138
- wrapperClass: 'fleetops-telematics-action-button',
139
- },
140
- ];
127
+ get actionButtons() {
128
+ return [
129
+ {
130
+ icon: 'refresh',
131
+ size: 'sm',
132
+ onClick: this.refresh,
133
+ helpText: this.intl.t('common.refresh'),
134
+ wrapperClass: 'fleetops-telematics-action-button',
135
+ isLoading: this.refreshTask.isRunning,
136
+ disabled: this.refreshTask.isRunning,
137
+ },
138
+ {
139
+ icon: 'microchip',
140
+ text: 'View Devices',
141
+ size: 'sm',
142
+ onClick: this.goToDevices,
143
+ wrapperClass: 'fleetops-telematics-action-button',
144
+ },
145
+ ];
146
+ }
141
147
 
142
148
  @tracked bulkActions = [];
143
149
 
@@ -179,6 +185,8 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
179
185
  filterComponentPlaceholder: 'Select device',
180
186
  filterParam: 'device_uuid',
181
187
  model: 'device',
188
+ modelNamePath: 'displayName',
189
+ query: { telematic_uuid: this.telematic?.id },
182
190
  },
183
191
  {
184
192
  label: 'Value',
@@ -208,6 +216,9 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
208
216
  sortParam: 'last_reading_at',
209
217
  resizable: true,
210
218
  sortable: true,
219
+ filterable: true,
220
+ filterParam: 'last_reading_at',
221
+ filterComponent: 'filter/date',
211
222
  },
212
223
  {
213
224
  label: '',
@@ -241,7 +252,11 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
241
252
  }
242
253
 
243
254
  @action refresh() {
244
- return this.hostRouter.refresh();
255
+ if (this.refreshTask.isRunning) {
256
+ return;
257
+ }
258
+
259
+ return this.refreshTask.perform();
245
260
  }
246
261
 
247
262
  @action clearFilters() {
@@ -249,6 +264,7 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
249
264
  this.status = null;
250
265
  this.type = null;
251
266
  this.device_uuid = null;
267
+ this.last_reading_at = null;
252
268
  this.page = 1;
253
269
  }
254
270
 
@@ -267,4 +283,8 @@ export default class ConnectivityTelematicsDetailsSensorsController extends Cont
267
283
  queryParams: { device_id: sensor.device_uuid },
268
284
  });
269
285
  }
286
+
287
+ @task *refreshTask() {
288
+ yield this.hostRouter.refresh();
289
+ }
270
290
  }
@@ -0,0 +1,100 @@
1
+ import Controller from '@ember/controller';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ export default class MaintenanceIndexController extends Controller {
8
+ @service fetch;
9
+ @service docsPanel;
10
+
11
+ @tracked hub = null;
12
+
13
+ constructor() {
14
+ super(...arguments);
15
+ this.loadHub.perform();
16
+ }
17
+
18
+ get kpis() {
19
+ return this.hub?.kpis ?? this.loadingKpis;
20
+ }
21
+
22
+ get actions() {
23
+ return (this.hub?.actions ?? []).map((action) => this.normalizeAction(action));
24
+ }
25
+
26
+ get sections() {
27
+ return this.hub?.sections ?? [];
28
+ }
29
+
30
+ get docs() {
31
+ return (this.hub?.docs ?? []).map((doc) => ({
32
+ ...doc,
33
+ description: doc.description ?? this.docDescriptions[doc.slug] ?? '',
34
+ }));
35
+ }
36
+
37
+ docDescriptions = {
38
+ 'fleet-ops/maintenance/schedules/overview': 'Plan recurring service windows and convert due schedules into work orders.',
39
+ 'fleet-ops/maintenance/work-orders/overview': 'Coordinate assigned maintenance work, vendors, due dates, and completion.',
40
+ 'fleet-ops/maintenance/equipment/overview': 'Track serviceable equipment that participates in maintenance operations.',
41
+ 'fleet-ops/maintenance/parts/overview': 'Manage parts inventory and restocking signals used by maintenance teams.',
42
+ };
43
+
44
+ normalizeAction(action = {}) {
45
+ const query = action.query && typeof action.query === 'object' && !Array.isArray(action.query) ? action.query : {};
46
+
47
+ return {
48
+ ...action,
49
+ query,
50
+ };
51
+ }
52
+
53
+ get loadingKpis() {
54
+ return [
55
+ {
56
+ key: 'overdue_schedules',
57
+ label: 'Overdue Schedules',
58
+ value: '...',
59
+ caption: 'Loading recurring service risk.',
60
+ tone: 'rose',
61
+ icon: 'calendar-xmark',
62
+ route: 'maintenance.schedules',
63
+ },
64
+ {
65
+ key: 'upcoming_schedules',
66
+ label: 'Due This Week',
67
+ value: '...',
68
+ caption: 'Loading upcoming service windows.',
69
+ tone: 'blue',
70
+ icon: 'calendar-day',
71
+ route: 'maintenance.schedules',
72
+ },
73
+ {
74
+ key: 'open_work_orders',
75
+ label: 'Open Work Orders',
76
+ value: '...',
77
+ caption: 'Loading active maintenance work.',
78
+ tone: 'amber',
79
+ icon: 'clipboard-list',
80
+ route: 'maintenance.work-orders',
81
+ },
82
+ { key: 'low_stock_parts', label: 'Low Stock Parts', value: '...', caption: 'Loading parts inventory signals.', tone: 'amber', icon: 'cog', route: 'maintenance.parts' },
83
+ ];
84
+ }
85
+
86
+ @action openDocs(link) {
87
+ if (!link?.slug) {
88
+ return;
89
+ }
90
+
91
+ return this.docsPanel.open(link.slug, {
92
+ title: link.title ?? link.label,
93
+ source: 'fleet-ops-maintenance-hub',
94
+ });
95
+ }
96
+
97
+ @task *loadHub() {
98
+ this.hub = yield this.fetch.get('fleet-ops/hubs/maintenance');
99
+ }
100
+ }
@@ -1,16 +1,18 @@
1
1
  import Controller from '@ember/controller';
2
2
  import { inject as service } from '@ember/service';
3
3
  import { tracked } from '@glimmer/tracking';
4
+ import fleetOpsOptions from '../../../utils/fleet-ops-options';
4
5
 
5
6
  export default class MaintenanceWorkOrdersIndexController extends Controller {
6
7
  @service workOrderActions;
7
8
  @service intl;
8
9
 
9
- @tracked queryParams = ['status', 'priority', 'page', 'limit', 'sort', 'query', 'public_id', 'created_at', 'updated_at'];
10
+ @tracked queryParams = ['category', 'status', 'priority', 'page', 'limit', 'sort', 'query', 'public_id', 'created_at', 'updated_at'];
10
11
  @tracked page = 1;
11
12
  @tracked limit;
12
13
  @tracked sort = '-created_at';
13
14
  @tracked public_id;
15
+ @tracked category;
14
16
  @tracked status;
15
17
  @tracked priority;
16
18
 
@@ -43,6 +45,21 @@ export default class MaintenanceWorkOrdersIndexController extends Controller {
43
45
  filterComponent: 'filter/string',
44
46
  },
45
47
  { label: this.intl.t('column.subject'), valuePath: 'subject', resizable: true, sortable: true, filterable: true, filterParam: 'subject', filterComponent: 'filter/string' },
48
+ {
49
+ label: this.intl.t('column.category'),
50
+ valuePath: 'category',
51
+ cellComponent: 'table/cell/base',
52
+ humanize: true,
53
+ resizable: true,
54
+ sortable: true,
55
+ filterable: true,
56
+ filterParam: 'category',
57
+ filterComponent: 'filter/select',
58
+ filterOptionLabel: 'label',
59
+ filterOptionValue: 'value',
60
+ filterOptions: fleetOpsOptions('workOrderCategories'),
61
+ placeholder: 'Select work order category',
62
+ },
46
63
  {
47
64
  label: this.intl.t('column.status'),
48
65
  valuePath: 'status',
@@ -40,7 +40,7 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
40
40
 
41
41
  get emptyStateMessage() {
42
42
  if (this.sync_status === 'unmatched') {
43
- return 'FleetOps did not find imported provider bills that still need vehicle or trip matching.';
43
+ return 'Fleet-Ops did not find imported provider bills that still need vehicle or trip matching.';
44
44
  }
45
45
 
46
46
  if (this.connection) {
@@ -0,0 +1,88 @@
1
+ import Controller from '@ember/controller';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ export default class ManagementIndexController extends Controller {
8
+ @service fetch;
9
+ @service docsPanel;
10
+
11
+ @tracked hub = null;
12
+
13
+ constructor() {
14
+ super(...arguments);
15
+ this.loadHub.perform();
16
+ }
17
+
18
+ get kpis() {
19
+ return (this.hub?.kpis ?? this.loadingKpis).map((kpi) => ({
20
+ ...kpi,
21
+ actionLabel: kpi.actionLabel ?? this.kpiActionLabels[kpi.key] ?? `Open ${kpi.label}`,
22
+ }));
23
+ }
24
+
25
+ get actions() {
26
+ return (this.hub?.actions ?? []).map((action) => this.normalizeAction(action));
27
+ }
28
+
29
+ get sections() {
30
+ return this.hub?.sections ?? [];
31
+ }
32
+
33
+ get docs() {
34
+ return (this.hub?.docs ?? []).map((doc) => ({
35
+ ...doc,
36
+ description: doc.description ?? this.docDescriptions[doc.slug] ?? '',
37
+ }));
38
+ }
39
+
40
+ docDescriptions = {
41
+ 'fleet-ops/resources/drivers/overview': 'Create driver profiles, app access, assignment context, and operating status.',
42
+ 'fleet-ops/resources/vehicles/overview': 'Track fleet assets, assignment readiness, and vehicle operating details.',
43
+ 'fleet-ops/resources/fleets/overview': 'Group drivers and vehicles by team, region, or service coverage.',
44
+ 'fleet-ops/resources/contacts/overview': 'Manage people and organizations used across orders and operational records.',
45
+ 'fleet-ops/resources/places/overview': 'Manage reusable pickup, dropoff, hub, and facility locations.',
46
+ 'fleet-ops/resources/issues/overview': 'Track incidents, service exceptions, and operational follow-up.',
47
+ };
48
+
49
+ kpiActionLabels = {
50
+ drivers: 'Manage Drivers',
51
+ vehicles: 'Track Vehicles',
52
+ issues: 'Review Issues',
53
+ fuel_records: 'Review Fuel Records',
54
+ };
55
+
56
+ normalizeAction(action = {}) {
57
+ const query = action.query && typeof action.query === 'object' && !Array.isArray(action.query) ? action.query : {};
58
+
59
+ return {
60
+ ...action,
61
+ query,
62
+ };
63
+ }
64
+
65
+ get loadingKpis() {
66
+ return [
67
+ { key: 'drivers', label: 'Drivers', value: '...', caption: 'Loading resource readiness.', tone: 'blue', icon: 'id-card', route: 'management.drivers' },
68
+ { key: 'vehicles', label: 'Vehicles', value: '...', caption: 'Loading fleet asset coverage.', tone: 'green', icon: 'truck', route: 'management.vehicles' },
69
+ { key: 'issues', label: 'Open Issues', value: '...', caption: 'Loading exception health.', tone: 'rose', icon: 'triangle-exclamation', route: 'management.issues' },
70
+ { key: 'fuel_records', label: 'Fuel Records', value: '...', caption: 'Loading fuel activity.', tone: 'amber', icon: 'gas-pump', route: 'management.fuel-reports' },
71
+ ];
72
+ }
73
+
74
+ @action openDocs(link) {
75
+ if (!link?.slug) {
76
+ return;
77
+ }
78
+
79
+ return this.docsPanel.open(link.slug, {
80
+ title: link.title ?? link.label,
81
+ source: 'fleet-ops-resources-hub',
82
+ });
83
+ }
84
+
85
+ @task *loadHub() {
86
+ this.hub = yield this.fetch.get('fleet-ops/hubs/resources');
87
+ }
88
+ }
@@ -0,0 +1,142 @@
1
+ import Controller from '@ember/controller';
2
+ import { inject as service } from '@ember/service';
3
+ import { action } from '@ember/object';
4
+
5
+ export default class SettingsIndexController extends Controller {
6
+ @service docsPanel;
7
+
8
+ groups = [
9
+ {
10
+ title: 'Driver And Map Experience',
11
+ description: 'Tune what dispatchers and drivers see while work is moving.',
12
+ links: [
13
+ { label: 'Navigator App', route: 'settings.navigator-app', icon: 'location-arrow', description: 'Driver app behavior and workflow defaults.' },
14
+ { label: 'Map', route: 'settings.map', icon: 'map', description: 'Map providers, defaults, and display behavior.' },
15
+ ],
16
+ },
17
+ {
18
+ title: 'Dispatch Automation',
19
+ description: 'Shape route planning, assignment, and scheduling behavior.',
20
+ links: [
21
+ { label: 'Routing', route: 'settings.routing', icon: 'route', description: 'Routing engines and routing defaults.' },
22
+ { label: 'Orchestrator', route: 'settings.orchestrator', icon: 'circle-nodes', description: 'Dispatch orchestration and assignment behavior.' },
23
+ { label: 'Scheduling', route: 'settings.scheduling', icon: 'calendar-days', description: 'Schedule planning and operational timing rules.' },
24
+ ],
25
+ },
26
+ {
27
+ title: 'Business And Data',
28
+ description: 'Keep commerce, metadata, and visual conventions aligned.',
29
+ links: [
30
+ { label: 'Payments', route: 'settings.payments', icon: 'cash-register', description: 'Payment setup for operational commerce workflows.' },
31
+ { label: 'Custom Fields', route: 'settings.custom-fields', icon: 'pen-to-square', description: 'Operational metadata fields for Fleet-Ops records.' },
32
+ { label: 'Avatars', route: 'settings.avatars', icon: 'icons', description: 'Visual assets for driver, vehicle, and map displays.' },
33
+ ],
34
+ },
35
+ {
36
+ title: 'Communication',
37
+ description: 'Control operational alerts and notification behavior.',
38
+ links: [{ label: 'Notifications', route: 'settings.notifications', icon: 'bell', description: 'Operational status alerts and notification rules.' }],
39
+ },
40
+ ];
41
+
42
+ actions = [
43
+ {
44
+ label: 'Start with driver experience',
45
+ description: 'Navigator App and Map settings should be reviewed before operators rely on live dispatch defaults.',
46
+ icon: 'location-arrow',
47
+ route: 'settings.navigator-app',
48
+ tone: 'info',
49
+ },
50
+ {
51
+ label: 'Review automation rules',
52
+ description: 'Routing, orchestration, and scheduling settings affect assignment and planning behavior.',
53
+ icon: 'route',
54
+ route: 'settings.routing',
55
+ tone: 'warning',
56
+ },
57
+ {
58
+ label: 'Keep forms focused',
59
+ description: 'Add custom fields after core workflows are stable so operational screens stay easy to scan.',
60
+ icon: 'pen-to-square',
61
+ route: 'settings.custom-fields',
62
+ tone: 'success',
63
+ },
64
+ ];
65
+
66
+ docs = [
67
+ {
68
+ label: 'Navigator App',
69
+ icon: 'location-arrow',
70
+ slug: 'fleet-ops/settings/navigator-app',
71
+ title: 'Navigator App settings',
72
+ description: 'Configure driver app behavior, onboarding, and live workflow defaults.',
73
+ },
74
+ {
75
+ label: 'Map',
76
+ icon: 'map',
77
+ slug: 'fleet-ops/settings/map',
78
+ title: 'Map settings',
79
+ description: 'Set map providers, display defaults, and operator map behavior.',
80
+ },
81
+ {
82
+ label: 'Payments',
83
+ icon: 'cash-register',
84
+ slug: 'fleet-ops/settings/payments',
85
+ title: 'Payments guide',
86
+ description: 'Connect payment providers and payment behavior for Fleet-Ops workflows.',
87
+ },
88
+ {
89
+ label: 'Notifications',
90
+ icon: 'bell',
91
+ slug: 'fleet-ops/settings/notifications',
92
+ title: 'Notification settings',
93
+ description: 'Tune operational alerts sent to dispatchers, customers, and drivers.',
94
+ },
95
+ {
96
+ label: 'Routing',
97
+ icon: 'route',
98
+ slug: 'fleet-ops/settings/routing',
99
+ title: 'Routing settings',
100
+ description: 'Configure routing engines, tracking defaults, and distance units.',
101
+ },
102
+ {
103
+ label: 'Orchestrator',
104
+ icon: 'circle-nodes',
105
+ slug: 'fleet-ops/settings/orchestrator',
106
+ title: 'Orchestrator settings',
107
+ description: 'Select dispatch automation engines and assignment behavior.',
108
+ },
109
+ {
110
+ label: 'Scheduling',
111
+ icon: 'calendar-days',
112
+ slug: 'fleet-ops/settings/scheduling',
113
+ title: 'Scheduling settings',
114
+ description: 'Manage schedule templates and timing rules for planned work.',
115
+ },
116
+ {
117
+ label: 'Custom Fields',
118
+ icon: 'pen-to-square',
119
+ slug: 'fleet-ops/settings/custom-fields',
120
+ title: 'Custom field settings',
121
+ description: 'Add structured operational metadata to Fleet-Ops records.',
122
+ },
123
+ {
124
+ label: 'Avatars',
125
+ icon: 'icons',
126
+ slug: 'fleet-ops/settings/avatars',
127
+ title: 'Avatar settings',
128
+ description: 'Manage visual markers used across maps and operational screens.',
129
+ },
130
+ ];
131
+
132
+ @action openDocs(link) {
133
+ if (!link?.slug) {
134
+ return;
135
+ }
136
+
137
+ return this.docsPanel.open(link.slug, {
138
+ title: link.title ?? link.label,
139
+ source: 'fleet-ops-settings-hub',
140
+ });
141
+ }
142
+ }
@@ -321,7 +321,28 @@ export default {
321
321
  }),
322
322
  ];
323
323
 
324
+ const defaultWidgets = widgets
325
+ .filter((widget) => (typeof widget.isDefault === 'function' ? widget.isDefault() : widget.default === true))
326
+ .map((widget) => {
327
+ const definition = typeof widget.toObject === 'function' ? widget.toObject() : widget;
328
+
329
+ return {
330
+ ...definition,
331
+ widgetId: definition.widgetId ?? definition.id,
332
+ };
333
+ });
334
+
324
335
  widgetService.registerWidgets('dashboard', widgets);
336
+
337
+ if (typeof widgetService.registerDashboard === 'function') {
338
+ widgetService.registerDashboard('fleet-ops');
339
+ }
340
+
341
+ widgetService.registerWidgets('fleet-ops', widgets);
342
+
343
+ if (typeof widgetService.registerDefaultWidgets === 'function') {
344
+ widgetService.registerDefaultWidgets('fleet-ops', defaultWidgets);
345
+ }
325
346
  },
326
347
 
327
348
  registerHomeComponents(registryService) {
@@ -0,0 +1,3 @@
1
+ import Route from '@ember/routing/route';
2
+
3
+ export default class AnalyticsIndexRoute extends Route {}
@@ -1,3 +1,39 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
- export default class ConnectivityDevicesIndexDetailsEventsRoute extends Route {}
4
+ export default class ConnectivityDevicesIndexDetailsEventsRoute extends Route {
5
+ @service store;
6
+
7
+ queryParams = {
8
+ events_page: { refreshModel: true },
9
+ events_limit: { refreshModel: true },
10
+ events_sort: { refreshModel: true },
11
+ events_query: { refreshModel: true },
12
+ events_event_type: { refreshModel: true },
13
+ events_severity: { refreshModel: true },
14
+ events_processed: { refreshModel: true },
15
+ events_occurred_at: { refreshModel: true },
16
+ events_created_at: { refreshModel: true },
17
+ };
18
+
19
+ model(params) {
20
+ const device = this.modelFor('connectivity.devices.index.details');
21
+ return this.store.query('device-event', {
22
+ page: params.events_page,
23
+ limit: params.events_limit,
24
+ sort: params.events_sort,
25
+ query: params.events_query,
26
+ event_type: params.events_event_type,
27
+ severity: params.events_severity,
28
+ processed: params.events_processed,
29
+ occurred_at: params.events_occurred_at,
30
+ created_at: params.events_created_at,
31
+ device_uuid: device.id,
32
+ });
33
+ }
34
+
35
+ setupController(controller, model) {
36
+ super.setupController(controller, model);
37
+ controller.device = this.modelFor('connectivity.devices.index.details');
38
+ }
39
+ }