@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,18 +2,25 @@ 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 FleetListingComponent from './fleet-ops-sidebar/fleet-listing';
6
- import DriverListingComponent from './fleet-ops-sidebar/driver-listing';
5
+ import isMenuItemActive from '@fleetbase/ember-ui/utils/is-menu-item-active';
6
+
7
+ const SECTION_REGISTRY_KEYS = {
8
+ operations: 'universeOperationsMenuItems',
9
+ management: 'universeManagementMenuItems',
10
+ maintenance: 'universeMaintenanceMenuItems',
11
+ connectivity: 'universeConnectivityMenuItems',
12
+ analytics: 'universeAnalyticsMenuItems',
13
+ settings: 'universeSettingsMenuItems',
14
+ };
7
15
 
8
16
  export default class LayoutFleetOpsSidebarComponent extends Component {
9
17
  @service universe;
10
18
  @service('universe/menu-service') menuService;
11
- @service store;
12
19
  @service intl;
13
20
  @service abilities;
14
- @service appCache;
21
+ @service fetch;
22
+
15
23
  @tracked routePrefix = 'console.fleet-ops.';
16
- @tracked menuPanels = [];
17
24
  @tracked universeMenuItems = [];
18
25
  @tracked universeOperationsMenuItems = [];
19
26
  @tracked universeManagementMenuItems = [];
@@ -26,11 +33,210 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
26
33
  constructor() {
27
34
  super(...arguments);
28
35
  this.createMenuItemsFromUniverseRegistry();
29
- this.createMenuPanels();
36
+ }
37
+
38
+ get createOrderAction() {
39
+ return {
40
+ label: this.intl.t('common.create-new-resource', { resource: this.intl.t('resource.order')?.toLowerCase() }),
41
+ icon: 'paper-plane',
42
+ iconPrefix: 'fas',
43
+ buttonClass: 'fleet-ops-sidebar-primary-action',
44
+ permission: 'fleet-ops create order',
45
+ onClick: this.onClickCreateOrder,
46
+ };
47
+ }
48
+
49
+ get navigationItems() {
50
+ const coreBranches = [
51
+ this.createBranch({
52
+ id: 'operations',
53
+ label: this.intl.t('menu.operations'),
54
+ icon: 'circle-nodes',
55
+ route: 'operations',
56
+ defaultRoute: 'operations.orders',
57
+ keywords: ['dispatch', 'orders', 'live map', 'track vehicles', 'drivers online', 'vehicles online'],
58
+ children: this.operationsItems,
59
+ }),
60
+ this.createBranch({
61
+ id: 'resources',
62
+ label: this.intl.t('menu.resources'),
63
+ icon: 'truck',
64
+ route: 'management',
65
+ defaultRoute: 'management.index',
66
+ keywords: ['drivers', 'vehicles', 'fleets', 'contacts', 'places'],
67
+ children: this.resourcesItems,
68
+ }),
69
+ this.createBranch({
70
+ id: 'maintenance',
71
+ label: this.intl.t('menu.maintenance'),
72
+ icon: 'cog',
73
+ route: 'maintenance',
74
+ defaultRoute: 'maintenance.index',
75
+ keywords: ['work orders', 'equipment', 'parts'],
76
+ children: this.maintenanceItems,
77
+ }),
78
+ this.createBranch({
79
+ id: 'connectivity',
80
+ label: this.intl.t('menu.connectivity'),
81
+ icon: 'satellite-dish',
82
+ route: 'connectivity',
83
+ defaultRoute: 'connectivity.telematics',
84
+ keywords: ['telematics', 'fuel integrations', 'devices', 'sensors'],
85
+ children: this.connectivityItems,
86
+ }),
87
+ this.createBranch({
88
+ id: 'analytics',
89
+ label: this.intl.t('menu.analytics'),
90
+ icon: 'chart-line',
91
+ route: 'analytics',
92
+ defaultRoute: 'analytics.index',
93
+ keywords: ['reports', 'metrics'],
94
+ children: this.analyticsItems,
95
+ }),
96
+ this.createBranch({
97
+ id: 'settings',
98
+ label: this.intl.t('menu.settings'),
99
+ icon: 'gear',
100
+ route: 'settings',
101
+ defaultRoute: 'settings.index',
102
+ keywords: ['configuration', 'navigator', 'map', 'routing', 'notifications'],
103
+ children: this.settingsItems,
104
+ }),
105
+ ];
106
+
107
+ return [...coreBranches, ...this.registryRootItems, ...this.registryPanelItems].filter((item) => item.children?.length || item.route || item.url || item.onClick);
108
+ }
109
+
110
+ get operationsItems() {
111
+ return this.withRegistryItems('operations', [
112
+ this.createItem('menu.orders', 'map-location-dot', 'operations.orders', 'fleet-ops list order', 'fleet-ops see order', [
113
+ 'dashboard',
114
+ 'orders',
115
+ 'dispatch',
116
+ 'tracking',
117
+ 'create order',
118
+ 'live map',
119
+ ]),
120
+ this.createItem('menu.orchestrator', 'circle-nodes', 'operations.orchestrator', 'fleet-ops list order', 'fleet-ops see order', ['optimize', 'allocate', 'dispatch']),
121
+ this.createItem('menu.scheduler', 'calendar-day', 'operations.scheduler', 'fleet-ops list order', 'fleet-ops see order', ['schedule', 'calendar']),
122
+ this.createItem('menu.order-config', 'diagram-project', 'operations.order-config', 'fleet-ops list order-config', 'fleet-ops see order-config', [
123
+ 'configuration',
124
+ 'fields',
125
+ 'entities',
126
+ ]),
127
+ this.createItem('menu.service-rates', 'file-invoice-dollar', 'operations.service-rates', 'fleet-ops list service-rate', 'fleet-ops see service-rate', ['rates', 'pricing']),
128
+ ]);
129
+ }
130
+
131
+ get resourcesItems() {
132
+ return this.withRegistryItems('management', [
133
+ this.createHubItem('Resources Hub', 'layer-group', 'management.index', 'fleet-ops list driver', 'fleet-ops see driver', [
134
+ 'resources hub',
135
+ 'resource dashboard',
136
+ 'resource readiness',
137
+ ]),
138
+ this.createItem('menu.drivers', 'id-card', 'management.drivers', 'fleet-ops list driver', 'fleet-ops see driver', ['driver', 'online drivers']),
139
+ this.createItem('menu.vehicles', 'truck', 'management.vehicles', 'fleet-ops list vehicle', 'fleet-ops see vehicle', ['vehicle', 'track vehicles', 'online vehicles']),
140
+ this.createItem('menu.fleets', 'user-group', 'management.fleets', 'fleet-ops list fleet', 'fleet-ops see fleet', ['fleet', 'teams']),
141
+ this.createItem('menu.vendors', 'warehouse', 'management.vendors', 'fleet-ops list vendor', 'fleet-ops see vendor'),
142
+ this.createItem('menu.contacts', 'address-book', 'management.contacts', 'fleet-ops list contact', 'fleet-ops see contact'),
143
+ this.createItem('menu.places', 'location-dot', 'management.places', 'fleet-ops list place', 'fleet-ops see place'),
144
+ this.createItem('menu.fuel-reports', 'gas-pump', 'management.fuel-reports', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
145
+ this.createItem('menu.fuel-transactions', 'credit-card', 'management.fuel-transactions', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
146
+ this.createItem('menu.issues', 'triangle-exclamation', 'management.issues', 'fleet-ops list issue', 'fleet-ops see issue'),
147
+ ]);
148
+ }
149
+
150
+ get maintenanceItems() {
151
+ return this.withRegistryItems('maintenance', [
152
+ this.createHubItem('Maintenance Hub', 'wrench', 'maintenance.index', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule', [
153
+ 'maintenance hub',
154
+ 'service readiness',
155
+ 'maintenance control panel',
156
+ ]),
157
+ this.createItem('menu.schedules', 'calendar-alt', 'maintenance.schedules', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule'),
158
+ this.createItem('menu.work-orders', 'clipboard-list', 'maintenance.work-orders', 'fleet-ops list work-order', 'fleet-ops see work-order'),
159
+ this.createItem('menu.maintenances', 'history', 'maintenance.maintenances', 'fleet-ops list maintenance', 'fleet-ops see maintenance'),
160
+ this.createItem('menu.equipment', 'trailer', 'maintenance.equipment', 'fleet-ops list equipment', 'fleet-ops see equipment'),
161
+ this.createItem('menu.parts', 'cog', 'maintenance.parts', 'fleet-ops list part', 'fleet-ops see part'),
162
+ ]);
163
+ }
164
+
165
+ get connectivityItems() {
166
+ return this.withRegistryItems('connectivity', [
167
+ this.createItem('menu.telematics', 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
168
+ this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report', ['fuel integrations']),
169
+ this.createItem('menu.devices', 'hard-drive', 'connectivity.devices', 'fleet-ops list device', 'fleet-ops see device'),
170
+ this.createItem('menu.sensors', 'temperature-full', 'connectivity.sensors', 'fleet-ops list sensor', 'fleet-ops see sensor'),
171
+ this.createItem('menu.events', 'stream', 'connectivity.events', 'fleet-ops list device-event', 'fleet-ops see device-event'),
172
+ ]);
173
+ }
174
+
175
+ get analyticsItems() {
176
+ return this.withRegistryItems('analytics', [
177
+ this.createHubItem('Dashboard', 'chart-line', 'analytics.index', 'iam list report', 'fleet-ops see report', ['dashboard', 'fleetops dashboard', 'metrics']),
178
+ this.createItem('menu.reports', 'file-import', 'analytics.reports', 'iam list report', 'fleet-ops see report'),
179
+ ]);
180
+ }
181
+
182
+ get settingsItems() {
183
+ return this.withRegistryItems('settings', [
184
+ this.createHubItem('Settings Hub', 'sliders', 'settings.index', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings', [
185
+ 'settings hub',
186
+ 'configuration dashboard',
187
+ 'setup focus',
188
+ ]),
189
+ this.createItem('menu.navigator-app', 'location-arrow', 'settings.navigator-app', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings'),
190
+ this.createItem('menu.map', 'map', 'settings.map', 'fleet-ops view map-settings', 'fleet-ops see map-settings'),
191
+ this.createItem('menu.payments', 'cash-register', 'settings.payments', 'fleet-ops view payments', 'fleet-ops see payments'),
192
+ this.createItem('menu.notifications', 'bell', 'settings.notifications', 'fleet-ops view notification-settings', 'fleet-ops see notification-settings'),
193
+ this.createItem('menu.routing', 'route', 'settings.routing', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
194
+ this.createItem('menu.orchestrator', 'circle-nodes', 'settings.orchestrator', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
195
+ this.createItem('menu.scheduling', 'calendar-days', 'settings.scheduling', 'fleet-ops view scheduling-settings', 'fleet-ops see scheduling-settings'),
196
+ this.createItem('menu.custom-fields', 'pen-to-square', 'settings.custom-fields', 'fleet-ops view custom-field', 'fleet-ops see custom-field'),
197
+ this.createItem('menu.avatars', 'icons', 'settings.avatars', 'fleet-ops view avatar', 'fleet-ops see avatar'),
198
+ ]);
199
+ }
200
+
201
+ get registryRootItems() {
202
+ return this.sortByPriority(this.universeMenuItems.filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item)));
203
+ }
204
+
205
+ get registryPanelItems() {
206
+ return this.sortByPriority(
207
+ this.universeMenuPanels.map((panel) => {
208
+ const children = this.sortByPriority((panel.items ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item)));
209
+
210
+ return {
211
+ id: panel.id ?? panel.slug ?? panel.title,
212
+ label: panel.intl ? this.intl.t(panel.intl) : panel.title,
213
+ icon: panel.icon,
214
+ visible: panel.visible,
215
+ permission: panel.permission,
216
+ priority: panel.priority,
217
+ keywords: [panel.slug, panel.title, panel.intl].filter(Boolean),
218
+ children,
219
+ };
220
+ })
221
+ );
222
+ }
223
+
224
+ get footerRegistryComponents() {
225
+ const sectionItems = Object.entries(SECTION_REGISTRY_KEYS).reduce((components, [section, propertyName]) => {
226
+ components[section] = this[propertyName].filter((item) => item.renderComponentInPlace);
227
+ return components;
228
+ }, {});
229
+
230
+ return {
231
+ root: this.universeMenuItems.filter((item) => item.renderComponentInPlace),
232
+ resources: sectionItems.management,
233
+ ...sectionItems,
234
+ };
30
235
  }
31
236
 
32
237
  createMenuItemsFromUniverseRegistry() {
33
238
  const registeredMenuItems = this.menuService.getMenuItems('engine:fleet-ops');
239
+
34
240
  this.universeMenuPanels = this.menuService.getMenuPanels('engine:fleet-ops');
35
241
  this.universeMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === undefined);
36
242
  this.universeOperationsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'operations');
@@ -41,400 +247,163 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
41
247
  this.universeSettingsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'settings');
42
248
  }
43
249
 
44
- /* eslint-disable no-unused-vars */
45
- createMenuPanels() {
46
- const operationsItems = [
47
- {
48
- priority: 0,
49
- intl: 'menu.dashboard',
50
- title: this.intl.t('menu.dashboard'),
51
- icon: 'home',
52
- route: 'operations.orders',
53
- permission: 'fleet-ops list order',
54
- visible: this.abilities.can('fleet-ops see order'),
55
- },
56
- {
57
- priority: 1,
58
- intl: 'menu.orchestrator',
59
- title: this.intl.t('menu.orchestrator'),
60
- icon: 'circle-nodes',
61
- route: 'operations.orchestrator',
62
- permission: 'fleet-ops list order',
63
- visible: this.abilities.can('fleet-ops see order'),
64
- },
65
- {
66
- priority: 2,
67
- intl: 'menu.scheduler',
68
- title: this.intl.t('menu.scheduler'),
69
- icon: 'calendar-day',
70
- route: 'operations.scheduler',
71
- permission: 'fleet-ops list order',
72
- visible: this.abilities.can('fleet-ops see order'),
73
- },
74
- {
75
- priority: 3,
76
- intl: 'menu.order-config',
77
- title: this.intl.t('menu.order-config'),
78
- icon: 'diagram-project',
79
- route: 'operations.order-config',
80
- permission: 'fleet-ops list order-config',
81
- visible: this.abilities.can('fleet-ops see order-config'),
82
- },
83
- {
84
- priority: 4,
85
- intl: 'menu.service-rates',
86
- title: this.intl.t('menu.service-rates'),
87
- icon: 'file-invoice-dollar',
88
- route: 'operations.service-rates',
89
- permission: 'fleet-ops list service-rate',
90
- visible: this.abilities.can('fleet-ops see service-rate'),
91
- },
92
- ...(this.universeOperationsMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
93
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
94
-
95
- const resourcesItems = [
96
- {
97
- priority: 0,
98
- intl: 'menu.drivers',
99
- title: this.intl.t('menu.drivers'),
100
- icon: 'id-card',
101
- route: 'management.drivers',
102
- renderComponentInPlace: true,
103
- component: DriverListingComponent,
104
- permission: 'fleet-ops list driver',
105
- visible: this.abilities.can('fleet-ops see driver'),
106
- },
107
- {
108
- priority: 1,
109
- intl: 'menu.vehicles',
110
- title: this.intl.t('menu.vehicles'),
111
- icon: 'truck',
112
- route: 'management.vehicles',
113
- permission: 'fleet-ops list vehicle',
114
- visible: this.abilities.can('fleet-ops see vehicle'),
115
- },
116
- {
117
- priority: 2,
118
- intl: 'menu.fleets',
119
- title: this.intl.t('menu.fleets'),
120
- icon: 'user-group',
121
- route: 'management.fleets',
122
- renderComponentInPlace: true,
123
- component: FleetListingComponent,
124
- permission: 'fleet-ops list fleet',
125
- visible: this.abilities.can('fleet-ops see fleet'),
126
- },
127
- {
128
- priority: 3,
129
- intl: 'menu.vendors',
130
- title: this.intl.t('menu.vendors'),
131
- icon: 'warehouse',
132
- route: 'management.vendors',
133
- permission: 'fleet-ops list vendor',
134
- visible: this.abilities.can('fleet-ops see vendor'),
135
- },
136
- {
137
- priority: 4,
138
- intl: 'menu.contacts',
139
- title: this.intl.t('menu.contacts'),
140
- icon: 'address-book',
141
- route: 'management.contacts',
142
- permission: 'fleet-ops list contact',
143
- visible: this.abilities.can('fleet-ops see contact'),
144
- },
145
- {
146
- priority: 5,
147
- intl: 'menu.places',
148
- title: this.intl.t('menu.places'),
149
- icon: 'location-dot',
150
- route: 'management.places',
151
- permission: 'fleet-ops list place',
152
- visible: this.abilities.can('fleet-ops see place'),
153
- },
154
- {
155
- priority: 6,
156
- intl: 'menu.fuel-reports',
157
- title: this.intl.t('menu.fuel-reports'),
158
- icon: 'gas-pump',
159
- route: 'management.fuel-reports',
160
- permission: 'fleet-ops list fuel-report',
161
- visible: this.abilities.can('fleet-ops see fuel-report'),
162
- },
163
- {
164
- priority: 7,
165
- intl: 'menu.fuel-transactions',
166
- title: this.intl.t('menu.fuel-transactions'),
167
- icon: 'credit-card',
168
- route: 'management.fuel-transactions',
169
- permission: 'fleet-ops list fuel-report',
170
- visible: this.abilities.can('fleet-ops see fuel-report'),
171
- },
172
- {
173
- priority: 8,
174
- intl: 'menu.issues',
175
- title: this.intl.t('menu.issues'),
176
- icon: 'triangle-exclamation',
177
- route: 'management.issues',
178
- permission: 'fleet-ops list issue',
179
- visible: this.abilities.can('fleet-ops see issue'),
180
- },
181
- ...(this.universeManagementMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
182
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
183
-
184
- const connectivityItems = [
185
- {
186
- priority: 0,
187
- intl: 'menu.telematics',
188
- title: this.intl.t('menu.telematics'),
189
- icon: 'satellite-dish',
190
- route: 'connectivity.telematics',
191
- permission: 'fleet-ops list telematic',
192
- visible: this.abilities.can('fleet-ops see telematic'),
193
- },
194
- {
195
- priority: 1,
196
- intl: 'menu.fuel-providers',
197
- title: this.intl.t('menu.fuel-providers'),
198
- icon: 'gas-pump',
199
- route: 'connectivity.fuel-providers',
200
- permission: 'fleet-ops list fuel-report',
201
- visible: this.abilities.can('fleet-ops see fuel-report'),
202
- },
203
- {
204
- priority: 2,
205
- intl: 'menu.devices',
206
- title: this.intl.t('menu.devices'),
207
- icon: 'hard-drive',
208
- route: 'connectivity.devices',
209
- permission: 'fleet-ops list device',
210
- visible: this.abilities.can('fleet-ops see device'),
211
- },
212
- {
213
- priority: 3,
214
- intl: 'menu.sensors',
215
- title: this.intl.t('menu.sensors'),
216
- icon: 'temperature-full',
217
- route: 'connectivity.sensors',
218
- permission: 'fleet-ops list sensor',
219
- visible: this.abilities.can('fleet-ops see sensor'),
220
- },
221
- {
222
- priority: 4,
223
- intl: 'menu.events',
224
- title: this.intl.t('menu.events'),
225
- icon: 'stream',
226
- route: 'connectivity.events',
227
- permission: 'fleet-ops list device-event',
228
- visible: this.abilities.can('fleet-ops see device-event'),
229
- },
230
- ...(this.universeConnectivityMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
231
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
232
-
233
- const maintenanceItems = [
234
- {
235
- priority: 0,
236
- intl: 'menu.schedules',
237
- title: this.intl.t('menu.schedules'),
238
- icon: 'calendar-alt',
239
- route: 'maintenance.schedules',
240
- permission: 'fleet-ops list maintenance-schedule',
241
- visible: this.abilities.can('fleet-ops see maintenance-schedule'),
242
- },
243
- {
244
- priority: 1,
245
- intl: 'menu.work-orders',
246
- title: this.intl.t('menu.work-orders'),
247
- icon: 'clipboard-list',
248
- route: 'maintenance.work-orders',
249
- permission: 'fleet-ops list work-order',
250
- visible: this.abilities.can('fleet-ops see work-order'),
251
- },
252
- {
253
- priority: 2,
254
- intl: 'menu.maintenances',
255
- title: this.intl.t('menu.maintenances'),
256
- icon: 'history',
257
- route: 'maintenance.maintenances',
258
- permission: 'fleet-ops list maintenance',
259
- visible: this.abilities.can('fleet-ops see maintenance'),
260
- },
261
- {
262
- priority: 3,
263
- intl: 'menu.equipment',
264
- title: this.intl.t('menu.equipment'),
265
- icon: 'trailer',
266
- route: 'maintenance.equipment',
267
- permission: 'fleet-ops list equipment',
268
- visible: this.abilities.can('fleet-ops see equipment'),
269
- },
270
- {
271
- priority: 4,
272
- intl: 'menu.parts',
273
- title: this.intl.t('menu.parts'),
274
- icon: 'cog',
275
- route: 'maintenance.parts',
276
- permission: 'fleet-ops list part',
277
- visible: this.abilities.can('fleet-ops see part'),
278
- },
279
- ...(this.universeMaintenanceMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
280
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
281
-
282
- const analyticsItems = [
283
- {
284
- priority: 0,
285
- intl: 'menu.reports',
286
- title: this.intl.t('menu.reports'),
287
- icon: 'file-import',
288
- route: 'analytics.reports',
289
- permission: 'iam list report',
290
- visible: this.abilities.can('fleet-ops see report'),
291
- },
292
- ...(this.universeAnalyticsMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
293
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
294
-
295
- const settingsItems = [
296
- {
297
- priority: 0,
298
- intl: 'menu.navigator-app',
299
- title: this.intl.t('menu.navigator-app'),
300
- icon: 'location-arrow',
301
- route: 'settings.navigator-app',
302
- permission: 'fleet-ops view navigator-settings',
303
- visible: this.abilities.can('fleet-ops see navigator-settings'),
304
- },
305
- {
306
- priority: 1,
307
- intl: 'menu.payments',
308
- title: this.intl.t('menu.payments'),
309
- icon: 'cash-register',
310
- route: 'settings.payments',
311
- permission: 'fleet-ops view payments',
312
- visible: this.abilities.can('fleet-ops see payments'),
313
- },
314
- {
315
- priority: 2,
316
- intl: 'menu.notifications',
317
- title: this.intl.t('menu.notifications'),
318
- icon: 'bell',
319
- route: 'settings.notifications',
320
- permission: 'fleet-ops view notification-settings',
321
- visible: this.abilities.can('fleet-ops see notification-settings'),
322
- },
323
- {
324
- priority: 3,
325
- intl: 'menu.routing',
326
- title: this.intl.t('menu.routing'),
327
- icon: 'route',
328
- route: 'settings.routing',
329
- permission: 'fleet-ops view routing-settings',
330
- visible: this.abilities.can('fleet-ops see routing-settings'),
331
- },
332
- {
333
- intl: 'menu.map',
334
- title: this.intl.t('menu.map'),
335
- icon: 'map',
336
- route: 'settings.map',
337
- permission: 'fleet-ops view map-settings',
338
- visible: this.abilities.can('fleet-ops see map-settings'),
339
- },
340
- {
341
- priority: 4,
342
- intl: 'menu.orchestrator',
343
- title: this.intl.t('menu.orchestrator'),
344
- icon: 'circle-nodes',
345
- route: 'settings.orchestrator',
346
- permission: 'fleet-ops view routing-settings',
347
- visible: this.abilities.can('fleet-ops see routing-settings'),
348
- },
349
- {
350
- priority: 5,
351
- intl: 'menu.scheduling',
352
- title: this.intl.t('menu.scheduling'),
353
- icon: 'calendar-days',
354
- route: 'settings.scheduling',
355
- permission: 'fleet-ops view scheduling-settings',
356
- visible: this.abilities.can('fleet-ops see scheduling-settings'),
357
- },
358
- {
359
- priority: 6,
360
- intl: 'menu.custom-fields',
361
- title: this.intl.t('menu.custom-fields'),
362
- icon: 'rectangle-list',
363
- route: 'settings.custom-fields',
364
- permission: 'fleet-ops view custom-field',
365
- visible: this.abilities.can('fleet-ops see custom-field'),
366
- },
367
- {
368
- priority: 7,
369
- intl: 'menu.avatars',
370
- title: this.intl.t('menu.avatars'),
371
- icon: 'icons',
372
- route: 'settings.avatars',
373
- permission: 'fleet-ops view avatar',
374
- visible: this.abilities.can('fleet-ops see avatar'),
375
- },
376
- ...(this.universeSettingsMenuItems ?? []).map((item) => ({ ...item, _virtual: true })),
377
- ].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
378
-
379
- const createPanel = (intl, routePrefix, items = [], options = {}) => ({
380
- intl,
381
- title: this.intl.t(intl),
382
- routePrefix,
383
- open: options.open ?? true,
384
- items,
385
- onToggle: options.onToggle,
386
- });
387
-
388
- this.menuPanels = this.removeEmptyMenuPanels([
389
- createPanel('menu.operations', 'operations', operationsItems, {
390
- open: this.appCache.get('fleet-ops:sidebar:operations:open', true),
391
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:operations:open', open),
392
- }),
393
- createPanel('menu.resources', 'management', resourcesItems, {
394
- open: this.appCache.get('fleet-ops:sidebar:management:open', true),
395
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:management:open', open),
396
- }),
397
- createPanel('menu.maintenance', 'maintenance', maintenanceItems, {
398
- open: this.appCache.get('fleet-ops:sidebar:maintenance:open', false),
399
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:maintenance:open', open),
400
- }),
401
- createPanel('menu.connectivity', 'connectivity', connectivityItems, {
402
- open: this.appCache.get('fleet-ops:sidebar:connectivity:open', false),
403
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:connectivity:open', open),
404
- }),
405
- createPanel('menu.analytics', 'analytics', analyticsItems, {
406
- open: this.appCache.get('fleet-ops:sidebar:analytics:open', false),
407
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:analytics:open', open),
408
- }),
409
- createPanel('menu.settings', 'settings', settingsItems, {
410
- open: this.appCache.get('fleet-ops:sidebar:settings:open', true),
411
- onToggle: (open) => this.appCache.set('fleet-ops:sidebar:settings:open', open),
412
- }),
413
- ]);
250
+ createBranch({ id, label, icon, route, defaultRoute, children, keywords = [] }) {
251
+ return {
252
+ id,
253
+ label,
254
+ icon,
255
+ route: this.fullRoute(route),
256
+ defaultRoute: this.fullRoute(defaultRoute),
257
+ children: children.filter((item) => item.visible !== false),
258
+ keywords,
259
+ };
414
260
  }
415
261
 
416
- /**
417
- * Action handler for creating an order.
418
- */
419
- @action onClickCreateOrder() {
420
- const { onClickCreateOrder } = this.args;
262
+ createItem(intl, icon, route, permission, ability, keywords = []) {
263
+ return {
264
+ priority: this.defaultPriorityForRoute(route),
265
+ label: this.intl.t(intl),
266
+ description: this.intl.t(intl),
267
+ icon,
268
+ route: this.fullRoute(route),
269
+ permission,
270
+ visible: this.abilities.can(ability),
271
+ keywords: [intl, route, ...keywords].filter(Boolean),
272
+ };
273
+ }
274
+
275
+ createHubItem(label, icon, route, permission, ability, keywords = []) {
276
+ return {
277
+ pinnedFirst: true,
278
+ priority: this.defaultPriorityForRoute(route),
279
+ label,
280
+ description: label,
281
+ icon,
282
+ route: this.fullRoute(route),
283
+ permission,
284
+ visible: this.abilities.can(ability),
285
+ keywords: [label, route, ...keywords].filter(Boolean),
286
+ };
287
+ }
288
+
289
+ registryItem(menuItem) {
290
+ const registryMenuItem = {
291
+ ...menuItem,
292
+ _virtual: true,
293
+ label: menuItem.intl ? this.intl.t(menuItem.intl) : (menuItem.title ?? menuItem.label),
294
+ description: menuItem.description,
295
+ icon: menuItem.icon,
296
+ iconPrefix: menuItem.iconPrefix,
297
+ permission: menuItem.permission,
298
+ visible: menuItem.visible,
299
+ keywords: [menuItem.slug, menuItem.view, menuItem.section, menuItem.title, menuItem.label, menuItem.intl, ...(menuItem.keywords ?? [])].filter(Boolean),
300
+ activeWhen: () => isMenuItemActive(menuItem.section, menuItem.slug, menuItem.view),
301
+ };
302
+
303
+ registryMenuItem.onClick = () => this.universe.transitionMenuItem(`${this.routePrefix}virtual`, registryMenuItem);
304
+
305
+ return registryMenuItem;
306
+ }
421
307
 
422
- if (typeof onClickCreateOrder === 'function') {
423
- onClickCreateOrder();
308
+ withRegistryItems(section, items) {
309
+ const registryProperty = SECTION_REGISTRY_KEYS[section];
310
+ const registryItems = (this[registryProperty] ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
311
+
312
+ return this.sortByPriority([...items, ...registryItems]);
313
+ }
314
+
315
+ sortByPriority(items = []) {
316
+ return [...items]
317
+ .map((item, index) => ({ item, index }))
318
+ .sort((a, b) => {
319
+ if (a.item.pinnedFirst !== b.item.pinnedFirst) {
320
+ return a.item.pinnedFirst ? -1 : 1;
321
+ }
322
+
323
+ const priorityOrder = (a.item.priority ?? 0) - (b.item.priority ?? 0);
324
+
325
+ if (priorityOrder !== 0) {
326
+ return priorityOrder;
327
+ }
328
+
329
+ return a.index - b.index;
330
+ })
331
+ .map(({ item }) => item);
332
+ }
333
+
334
+ defaultPriorityForRoute(route) {
335
+ const priorities = {
336
+ 'operations.orders': 0,
337
+ 'operations.orchestrator': 1,
338
+ 'operations.scheduler': 2,
339
+ 'operations.order-config': 3,
340
+ 'operations.service-rates': 4,
341
+ 'management.index': 0,
342
+ 'management.drivers': 1,
343
+ 'management.vehicles': 2,
344
+ 'management.fleets': 3,
345
+ 'management.vendors': 4,
346
+ 'management.contacts': 5,
347
+ 'management.places': 6,
348
+ 'management.fuel-reports': 7,
349
+ 'management.fuel-transactions': 8,
350
+ 'management.issues': 9,
351
+ 'maintenance.index': 0,
352
+ 'maintenance.schedules': 1,
353
+ 'maintenance.work-orders': 2,
354
+ 'maintenance.maintenances': 3,
355
+ 'maintenance.equipment': 4,
356
+ 'maintenance.parts': 5,
357
+ 'connectivity.telematics': 0,
358
+ 'connectivity.fuel-providers': 1,
359
+ 'connectivity.devices': 2,
360
+ 'connectivity.sensors': 3,
361
+ 'connectivity.events': 4,
362
+ 'analytics.index': 0,
363
+ 'analytics.reports': 1,
364
+ 'settings.index': 0,
365
+ 'settings.navigator-app': 1,
366
+ 'settings.map': 2,
367
+ 'settings.payments': 3,
368
+ 'settings.notifications': 4,
369
+ 'settings.routing': 5,
370
+ 'settings.orchestrator': 6,
371
+ 'settings.scheduling': 7,
372
+ 'settings.custom-fields': 8,
373
+ 'settings.avatars': 9,
374
+ };
375
+
376
+ return priorities[route] ?? 0;
377
+ }
378
+
379
+ fullRoute(route) {
380
+ if (!route || route.startsWith('console.')) {
381
+ return route;
382
+ }
383
+
384
+ return `${this.routePrefix}${route}`;
385
+ }
386
+
387
+ @action onClickCreateOrder() {
388
+ if (typeof this.args.onClickCreateOrder === 'function') {
389
+ this.args.onClickCreateOrder();
424
390
  }
425
391
  }
426
392
 
427
- /**
428
- * Filters menuPanels, leaving only menuPanels with visible items
429
- *
430
- * @param {Array} [menuPanels=[]]
431
- * @return {Array}
432
- * @memberof LayoutFleetOpsSidebarComponent
433
- */
434
- removeEmptyMenuPanels(menuPanels = []) {
435
- return menuPanels.filter((menuPanel) => {
436
- const visibleItems = menuPanel.items.filter((item) => item.visible);
437
- return visibleItems.length > 0;
438
- });
393
+ @action
394
+ async searchNavigation({ query, limit = 12 }) {
395
+ const trimmedQuery = query?.trim();
396
+
397
+ if (!trimmedQuery) {
398
+ return [];
399
+ }
400
+
401
+ try {
402
+ const response = await this.fetch.get('search', { query: trimmedQuery, limit }, { namespace: 'int/v1' });
403
+
404
+ return response.results ?? [];
405
+ } catch (_) {
406
+ return [];
407
+ }
439
408
  }
440
409
  }