@fleetbase/fleetops-engine 0.6.45 → 0.6.47

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 (148) hide show
  1. package/addon/components/admin/map-settings.hbs +26 -0
  2. package/addon/components/admin/map-settings.js +46 -0
  3. package/addon/components/admin/routing-settings.hbs +11 -0
  4. package/addon/components/cell/order-route-type.hbs +46 -0
  5. package/addon/components/cell/order-route-type.js +90 -0
  6. package/addon/components/cell/place-address.hbs +1 -0
  7. package/addon/components/display-place.hbs +45 -56
  8. package/addon/components/display-place.js +21 -0
  9. package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  10. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  11. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  12. package/addon/components/maintenance-schedule/form.js +2 -1
  13. package/addon/components/map/drawer/driver-listing.js +5 -5
  14. package/addon/components/map/drawer/place-listing.js +5 -5
  15. package/addon/components/map/drawer/position-listing.js +102 -21
  16. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  17. package/addon/components/map/google-live-map.hbs +9 -0
  18. package/addon/components/map/google-live-map.js +445 -0
  19. package/addon/components/map/leaflet-live-map.hbs +186 -142
  20. package/addon/components/map/leaflet-live-map.js +121 -41
  21. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  22. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  23. package/addon/components/map/toolbar.hbs +3 -3
  24. package/addon/components/map/toolbar.js +9 -1
  25. package/addon/components/modals/map-layer-form.hbs +132 -63
  26. package/addon/components/modals/orchestrator-import.hbs +4 -11
  27. package/addon/components/modals/orchestrator-import.js +17 -1
  28. package/addon/components/modals/update-order-activity.hbs +1 -1
  29. package/addon/components/orchestrator/order-pool.js +3 -2
  30. package/addon/components/orchestrator-workbench.js +29 -41
  31. package/addon/components/order/details/detail.js +2 -2
  32. package/addon/components/order/details/payload.hbs +5 -5
  33. package/addon/components/order/details/payload.js +18 -1
  34. package/addon/components/order/details/route.hbs +2 -2
  35. package/addon/components/order/details/route.js +76 -3
  36. package/addon/components/order/form/details.hbs +1 -2
  37. package/addon/components/order/form/details.js +6 -0
  38. package/addon/components/order/form/payload.hbs +33 -0
  39. package/addon/components/order/form/payload.js +1 -0
  40. package/addon/components/order/form/route.hbs +8 -8
  41. package/addon/components/order/form/route.js +86 -18
  42. package/addon/components/order/form.js +2 -2
  43. package/addon/components/order/route-editor.hbs +209 -147
  44. package/addon/components/order/route-editor.js +143 -102
  45. package/addon/components/place/address.hbs +1 -0
  46. package/addon/components/place/address.js +12 -0
  47. package/addon/components/route-list.hbs +43 -55
  48. package/addon/components/route-list.js +47 -0
  49. package/addon/components/route-optimization-wizard-panel.js +3 -4
  50. package/addon/components/vehicle/card.hbs +4 -4
  51. package/addon/controllers/maintenance/schedules/index.js +2 -1
  52. package/addon/controllers/operations/orders/index/details.js +110 -37
  53. package/addon/controllers/operations/orders/index/new.js +1 -1
  54. package/addon/controllers/operations/orders/index.js +8 -1
  55. package/addon/controllers/settings/map.js +97 -0
  56. package/addon/controllers/settings/payments/index.js +1 -1
  57. package/addon/controllers/settings/routing.js +47 -12
  58. package/addon/extension.js +11 -0
  59. package/addon/helpers/place-address-html.js +6 -0
  60. package/addon/instance-initializers/register-google-routes.js +17 -0
  61. package/addon/instance-initializers/register-osrm.js +5 -0
  62. package/addon/routes/application.js +6 -0
  63. package/addon/routes/operations/orchestrator.js +13 -0
  64. package/addon/routes/operations/orders/index/details.js +29 -22
  65. package/addon/routes/operations/orders/index/new.js +9 -0
  66. package/addon/routes/operations/orders/index.js +0 -1
  67. package/addon/routes/settings/map.js +3 -0
  68. package/addon/routes.js +1 -0
  69. package/addon/services/entity-actions.js +1 -1
  70. package/addon/services/geofence-event-bus.js +2 -1
  71. package/addon/services/geofence.js +204 -168
  72. package/addon/services/google-routes.js +300 -0
  73. package/addon/services/leaflet-draw-restriction.js +2 -1
  74. package/addon/services/leaflet-map-manager.js +17 -0
  75. package/addon/services/map-adapter/google.js +2205 -0
  76. package/addon/services/map-adapter/leaflet.js +1042 -0
  77. package/addon/services/map-adapter-interface.js +551 -0
  78. package/addon/services/map-manager.js +768 -0
  79. package/addon/services/map-settings.js +75 -0
  80. package/addon/services/movement-tracker.js +83 -32
  81. package/addon/services/order-actions.js +11 -1
  82. package/addon/services/osrm.js +79 -6
  83. package/addon/services/position-playback.js +135 -62
  84. package/addon/services/route-engine.js +83 -0
  85. package/addon/services/service-areas.js +8 -1
  86. package/addon/styles/fleetops-engine.css +555 -0
  87. package/addon/templates/management/vehicles/index.hbs +0 -1
  88. package/addon/templates/operations/orders/index/details.hbs +0 -1
  89. package/addon/templates/settings/map.hbs +53 -0
  90. package/addon/templates/settings/routing.hbs +15 -4
  91. package/addon/utils/fleet-ops-options.js +1 -0
  92. package/addon/utils/has-valid-coordinates.js +6 -0
  93. package/addon/utils/map-provider-options.js +6 -0
  94. package/addon/utils/order-route-editing.js +86 -0
  95. package/addon/utils/order-route-summary.js +20 -0
  96. package/addon/utils/place-address-html.js +41 -0
  97. package/addon/utils/route-colors.js +43 -12
  98. package/addon/utils/route-visualization.js +93 -0
  99. package/addon/utils/vendor-integration.js +2 -1
  100. package/app/components/admin/map-settings.js +1 -0
  101. package/app/components/admin/routing-settings.js +1 -0
  102. package/app/components/cell/order-route-type.js +1 -0
  103. package/app/components/cell/place-address.js +1 -0
  104. package/app/components/map/google-live-map.js +1 -0
  105. package/app/components/place/address.js +1 -0
  106. package/app/controllers/settings/map.js +1 -0
  107. package/app/helpers/place-address-html.js +1 -0
  108. package/app/routes/settings/map.js +1 -0
  109. package/app/services/map-adapter/google.js +1 -0
  110. package/app/services/map-adapter/leaflet.js +1 -0
  111. package/app/services/map-adapter-interface.js +1 -0
  112. package/app/services/map-manager.js +1 -0
  113. package/app/services/map-settings.js +1 -0
  114. package/app/templates/settings/map.js +1 -0
  115. package/app/utils/has-valid-coordinates.js +1 -0
  116. package/app/utils/order-route-editing.js +2 -0
  117. package/app/utils/order-route-summary.js +1 -0
  118. package/app/utils/place-address-html.js +1 -0
  119. package/app/utils/route-colors.js +1 -1
  120. package/app/utils/route-visualization.js +1 -0
  121. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  122. package/composer.json +1 -1
  123. package/extension.json +1 -1
  124. package/package.json +4 -4
  125. package/pnpm-workspace.yaml +7 -0
  126. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  127. package/server/src/Http/Controllers/Api/v1/OrderController.php +31 -3
  128. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  129. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  130. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +3 -2
  131. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  132. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  133. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  134. package/server/src/Http/Controllers/Internal/v1/OrderController.php +90 -34
  135. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  136. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  137. package/server/src/Http/Filter/OrderFilter.php +2 -1
  138. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  139. package/server/src/Http/Resources/v1/Order.php +20 -1
  140. package/server/src/Models/Order.php +42 -1
  141. package/server/src/Models/OrderConfig.php +15 -0
  142. package/server/src/Models/Payload.php +131 -27
  143. package/server/src/Models/Place.php +182 -48
  144. package/server/src/Models/ServiceRate.php +4 -3
  145. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  146. package/server/src/Support/Utils.php +36 -2
  147. package/server/src/routes.php +4 -0
  148. package/translations/en-us.yaml +1937 -1916
@@ -1,22 +1,73 @@
1
1
  import Controller, { inject as controller } from '@ember/controller';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
- import { action } from '@ember/object';
5
4
  import { isArray } from '@ember/array';
6
5
  import { task } from 'ember-concurrency';
6
+ import { colorForId, routeColorForStatus, routeStyleForStatus } from '../../../../utils/route-colors';
7
+ import { buildRoutePointMarkerPresentation, buildRoutePointsFromPayload } from '../../../../utils/route-visualization';
7
8
 
8
9
  export default class OperationsOrdersIndexDetailsController extends Controller {
9
10
  @controller('operations.orders.index') index;
10
11
  @service('universe/menu-service') menuService;
11
12
  @service orderActions;
12
13
  @service orderSocketEvents;
13
- @service leafletMapManager;
14
+ @service mapManager;
14
15
  @service leafletLayerVisibilityManager;
15
16
  @service hostRouter;
16
17
  @service universe;
17
18
  @service sidebar;
18
19
  @tracked routingControl;
19
20
  @tracked routingCompleted = false;
21
+ @tracked realtimeOrderPublicId = null;
22
+
23
+ get routePoints() {
24
+ return buildRoutePointsFromPayload(this.model?.payload);
25
+ }
26
+
27
+ get routeMarkerFactory() {
28
+ const routeColor = colorForId(this.model.public_id ?? this.model.id ?? 'order-route');
29
+
30
+ return (_waypoint, index) => {
31
+ return buildRoutePointMarkerPresentation(this.routePoints[index], routeColor);
32
+ };
33
+ }
34
+
35
+ get routeMarkerWaypoints() {
36
+ return this.routePoints.map(({ place }) => [place.latitude, place.longitude]);
37
+ }
38
+
39
+ get routeStatus() {
40
+ return this.model.status ?? 'created';
41
+ }
42
+
43
+ get routePolylineOptions() {
44
+ const color = routeColorForStatus(this.routeStatus);
45
+ const styles = routeStyleForStatus(this.routeStatus, color);
46
+
47
+ return {
48
+ color,
49
+ weight: styles.at(-1)?.weight ?? 4,
50
+ opacity: styles.at(-1)?.opacity ?? 0.85,
51
+ styles,
52
+ };
53
+ }
54
+
55
+ get routeControlTag() {
56
+ return this.model?.public_id ? `order-details:${this.model.public_id}` : null;
57
+ }
58
+
59
+ get routingControlOptions() {
60
+ return {
61
+ tag: this.routeControlTag,
62
+ color: this.routePolylineOptions.color,
63
+ status: this.routeStatus,
64
+ markerWaypoints: this.routeMarkerWaypoints,
65
+ polylineOptions: this.routePolylineOptions,
66
+ createMarker: this.routeMarkerFactory,
67
+ onRouteFound: this.handleRoutingComplete,
68
+ onRoutingError: this.handleRoutingComplete,
69
+ };
70
+ }
20
71
 
21
72
  get tabs() {
22
73
  const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:order:details');
@@ -104,56 +155,78 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
104
155
  }
105
156
 
106
157
  @task *refresh() {
107
- yield this.hostRouter.refresh();
158
+ try {
159
+ yield this.hostRouter.refresh();
160
+ } catch (error) {
161
+ return error;
162
+ }
108
163
  }
109
164
 
110
- @action async setup() {
111
- // Change to map layout and display order route
112
- this.index.changeLayout('map');
113
- this.routingCompleted = false;
114
- this.routingControl = await this.leafletMapManager.addRoutingControl(this.model.routeWaypoints, {
115
- onRouteFound: () => {
116
- this.routingCompleted = true;
117
- },
118
- onRoutingError: () => {
119
- this.routingCompleted = true;
120
- },
121
- });
165
+ handleRoutingComplete = () => {
166
+ this.routingCompleted = true;
167
+ };
122
168
 
123
- if (!this.routingControl) {
124
- this.routingCompleted = true;
169
+ async setupRealtime() {
170
+ const currentOrderPublicId = this.model?.public_id;
171
+ if (!currentOrderPublicId) {
172
+ return;
125
173
  }
126
174
 
127
- // Hide sidebar
128
- this.sidebar.hideNow();
175
+ if (this.realtimeOrderPublicId && this.realtimeOrderPublicId !== currentOrderPublicId) {
176
+ this.orderSocketEvents.stop({ public_id: this.realtimeOrderPublicId });
177
+ }
129
178
 
130
- // Show & track driver assigned
131
- this.leafletLayerVisibilityManager.hideCategory('drivers');
132
- this.leafletLayerVisibilityManager.showModelLayer(this.model.driver_assigned);
133
- // Should we hide places & other vehicles?
179
+ this.realtimeOrderPublicId = currentOrderPublicId;
134
180
 
135
- // Listen for this order events
136
181
  this.orderSocketEvents.start(
137
182
  this.model,
138
183
  async (_msg, { reloadable }) => {
139
184
  if (reloadable) {
140
185
  await this.hostRouter.refresh();
141
- this.routingCompleted = false;
142
- this.routingControl = await this.leafletMapManager.replaceRoutingControl(this.model.routeWaypoints, this.routingControl, {
143
- onRouteFound: () => {
144
- this.routingCompleted = true;
145
- },
146
- onRoutingError: () => {
147
- this.routingCompleted = true;
148
- },
149
- });
150
-
151
- if (!this.routingControl) {
152
- this.routingCompleted = true;
153
- }
154
186
  }
155
187
  },
156
188
  { debounceMs: 250 }
157
189
  );
158
190
  }
191
+
192
+ async syncRoutingControl() {
193
+ this.routingCompleted = false;
194
+ this.teardownRoutingControls();
195
+
196
+ this.routingControl = await this.mapManager.addRoutingControl(this.model.routeWaypoints, this.routingControlOptions);
197
+
198
+ if (!this.routingControl) {
199
+ this.routingCompleted = true;
200
+ }
201
+ }
202
+
203
+ syncMapContext() {
204
+ this.leafletLayerVisibilityManager.hideCategory('drivers');
205
+ this.leafletLayerVisibilityManager.showModelLayer(this.model.driver_assigned);
206
+ }
207
+
208
+ teardownRoutingControls() {
209
+ if (this.routeControlTag) {
210
+ this.mapManager.clearRoutingControlsByTag(this.routeControlTag);
211
+ } else if (this.routingControl) {
212
+ this.mapManager.removeRoutingControl(this.routingControl);
213
+ }
214
+
215
+ this.routingControl = undefined;
216
+ this.routingCompleted = false;
217
+ }
218
+
219
+ teardownRealtime() {
220
+ if (this.realtimeOrderPublicId) {
221
+ this.orderSocketEvents.stop({ public_id: this.realtimeOrderPublicId });
222
+ this.realtimeOrderPublicId = null;
223
+ }
224
+ }
225
+
226
+ async setupDetailsSession() {
227
+ this.index.changeLayout('map');
228
+ await this.setupRealtime();
229
+ await this.syncRoutingControl();
230
+ this.syncMapContext();
231
+ }
159
232
  }
@@ -20,6 +20,7 @@ export default class OperationsOrdersIndexNewController extends Controller {
20
20
  @service orderCreation;
21
21
  @service orderValidation;
22
22
  @service events;
23
+ @service sidebar;
23
24
  @tracked order = this.orderCreation.newOrder();
24
25
  @tracked overlay;
25
26
 
@@ -92,7 +93,6 @@ export default class OperationsOrdersIndexNewController extends Controller {
92
93
  }
93
94
 
94
95
  @action setup() {
95
- // Change to map layout
96
96
  this.index.changeLayout('map');
97
97
  }
98
98
 
@@ -142,6 +142,13 @@ export default class OperationsOrdersIndexController extends Controller {
142
142
  filterable: true,
143
143
  filterComponent: 'filter/string',
144
144
  },
145
+ {
146
+ label: this.intl.t('column.route-type'),
147
+ valuePath: 'payload.waypoints.length',
148
+ cellComponent: 'cell/order-route-type',
149
+ cellClassNames: 'overflow-visible',
150
+ resizable: true,
151
+ },
145
152
  {
146
153
  label: this.intl.t('column.internal-id'),
147
154
  valuePath: 'internal_id',
@@ -283,7 +290,7 @@ export default class OperationsOrdersIndexController extends Controller {
283
290
  label: this.intl.t('column.type'),
284
291
  valuePath: 'type',
285
292
  resizable: true,
286
- hidden: true,
293
+ humanize: true,
287
294
  sortable: true,
288
295
  filterable: true,
289
296
  filterComponent: 'filter/model',
@@ -0,0 +1,97 @@
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
+ import MAP_PROVIDER_OPTIONS from '../../utils/map-provider-options';
7
+
8
+ export default class SettingsMapController extends Controller {
9
+ @service fetch;
10
+ @service notifications;
11
+ @service intl;
12
+ @service mapManager;
13
+ @service mapSettings;
14
+
15
+ /**
16
+ * Available map provider options for the provider selector.
17
+ *
18
+ * @memberof SettingsMapController
19
+ * @var {Array}
20
+ */
21
+ @tracked mapProviderOptions = MAP_PROVIDER_OPTIONS;
22
+
23
+ /**
24
+ * The currently-selected map provider key (e.g. 'leaflet' or 'google').
25
+ *
26
+ * @memberof SettingsMapController
27
+ * @var {String}
28
+ */
29
+ @tracked mapProvider = 'leaflet';
30
+
31
+ /**
32
+ * Whether settings have been loaded from the server.
33
+ *
34
+ * @memberof SettingsMapController
35
+ * @var {Boolean}
36
+ */
37
+ @tracked settingsLoaded = false;
38
+
39
+ constructor() {
40
+ super(...arguments);
41
+ this.getSettings.perform();
42
+ }
43
+
44
+ // ─── Computed getters ──────────────────────────────────────────────────────
45
+
46
+ /**
47
+ * True when the user has selected Google Maps as the provider.
48
+ *
49
+ * @memberof SettingsMapController
50
+ * @return {Boolean}
51
+ */
52
+ get isGoogleMapsSelected() {
53
+ return this.mapProvider === 'google';
54
+ }
55
+
56
+ // ─── Actions ───────────────────────────────────────────────────────────────
57
+
58
+ /**
59
+ * Called when the user picks a different map provider from the Select.
60
+ *
61
+ * @param {Object|String} option The selected option object or raw value.
62
+ * @memberof SettingsMapController
63
+ */
64
+ @action selectMapProvider(option) {
65
+ this.mapProvider = option?.value ?? option ?? 'leaflet';
66
+ }
67
+
68
+ @task *saveSettings() {
69
+ const settings = {
70
+ mapProvider: this.mapProvider,
71
+ };
72
+
73
+ try {
74
+ const response = yield this.fetch.post('fleet-ops/settings/map', { settings });
75
+ this.mapSettings.applySettings(response);
76
+
77
+ if (this.mapManager && typeof this.mapManager.setActiveProvider === 'function') {
78
+ this.mapManager.setActiveProvider(this.mapProvider);
79
+ }
80
+
81
+ this.notifications.success(this.intl.t('settings.map.settings-saved'));
82
+ } catch (error) {
83
+ this.notifications.serverError(error);
84
+ }
85
+ }
86
+
87
+ @task *getSettings() {
88
+ try {
89
+ const response = yield this.mapSettings.load({ force: true });
90
+
91
+ this.mapProvider = response?.mapProvider ?? 'leaflet';
92
+ this.settingsLoaded = true;
93
+ } catch (error) {
94
+ this.notifications.serverError(error);
95
+ }
96
+ }
97
+ }
@@ -46,7 +46,7 @@ export default class SettingsPaymentsIndexController extends Controller {
46
46
  ];
47
47
 
48
48
  get isStripeEnabled() {
49
- return window.stripeInstance !== undefined || !isEmpty(config.stripe.publishableKey);
49
+ return !isEmpty(config.stripe.publishableKey);
50
50
  }
51
51
 
52
52
  @task *lookupStripeConnectAccount() {
@@ -7,14 +7,20 @@ export default class SettingsRoutingController extends Controller {
7
7
  @service fetch;
8
8
  @service notifications;
9
9
  @service currentUser;
10
- @service leafletRoutingControl;
11
- @tracked routerService = 'osrm';
10
+ @service routeEngine;
11
+ @service routeOptimization;
12
+ @tracked displayEngine = 'osrm';
13
+ @tracked optimizationEngine = 'osrm';
12
14
  @tracked routingUnit = 'km';
13
15
  @tracked routingUnitOptions = [
14
16
  { label: 'Kilometers', value: 'km' },
15
17
  { label: 'Miles', value: 'mi' },
16
18
  ];
17
- @tracked saveTasks = [];
19
+ saveTasks = new Map();
20
+
21
+ get hasEngineOverrides() {
22
+ return ['vroom', 'valhalla'].includes(this.displayEngine) || ['vroom', 'valhalla'].includes(this.optimizationEngine);
23
+ }
18
24
 
19
25
  constructor() {
20
26
  super(...arguments);
@@ -28,10 +34,19 @@ export default class SettingsRoutingController extends Controller {
28
34
  */
29
35
  @task *saveSettings() {
30
36
  try {
31
- yield this.fetch.post('fleet-ops/settings/routing-settings', { router: this.routerService, unit: this.routingUnit });
37
+ yield this.fetch.post('fleet-ops/settings/routing-settings', {
38
+ display_engine: this.displayEngine,
39
+ optimization_engine: this.optimizationEngine,
40
+ unit: this.routingUnit,
41
+ });
32
42
  yield this.performAdditionalSaveTasks();
33
43
  // Save in local memory too
34
- this.currentUser.setOption('routing', { router: this.routerService, unit: this.routingUnit });
44
+ this.currentUser.setOption('routing', {
45
+ router: this.displayEngine,
46
+ routing_display_engine: this.displayEngine,
47
+ routing_optimization_engine: this.optimizationEngine,
48
+ unit: this.routingUnit,
49
+ });
35
50
  this.notifications.success('Routing setting saved.');
36
51
  } catch (error) {
37
52
  this.notifications.serverError(error);
@@ -45,23 +60,43 @@ export default class SettingsRoutingController extends Controller {
45
60
  */
46
61
  @task *getSettings() {
47
62
  try {
48
- const { router, unit } = yield this.fetch.get('fleet-ops/settings/routing-settings');
49
- this.routerService = router;
63
+ const { router, display_engine, optimization_engine, unit } = yield this.fetch.get('fleet-ops/settings/routing-settings');
64
+ this.displayEngine = display_engine ?? router ?? 'osrm';
65
+ this.optimizationEngine = optimization_engine ?? display_engine ?? router ?? 'osrm';
50
66
  this.routingUnit = unit;
51
67
  } catch (error) {
52
68
  this.notifications.serverError(error);
53
69
  }
54
70
  }
55
71
 
56
- registerSaveTask(task) {
57
- this.saveTasks.push(task);
72
+ registerSaveTask(key, task) {
73
+ if (!key || typeof task?.perform !== 'function') {
74
+ return;
75
+ }
76
+
77
+ this.saveTasks.set(key, task);
78
+ }
79
+
80
+ unregisterSaveTask(key) {
81
+ if (!key) {
82
+ return;
83
+ }
84
+
85
+ this.saveTasks.delete(key);
58
86
  }
59
87
 
60
88
  async performAdditionalSaveTasks() {
61
- for (let i = 0; i < this.saveTasks.length; i++) {
62
- const task = this.saveTasks[i];
63
- if (typeof task.perform === 'function') {
89
+ for (const task of this.saveTasks.values()) {
90
+ try {
64
91
  await task.perform();
92
+ } catch (error) {
93
+ // Ignore explicit ember-concurrency task cancellations from components
94
+ // that may have unmounted during routing-engine selection changes.
95
+ if (typeof error?.name === 'string' && error.name.includes('TaskCancelation')) {
96
+ continue;
97
+ }
98
+
99
+ throw error;
65
100
  }
66
101
  }
67
102
  return true;
@@ -73,6 +73,16 @@ export default {
73
73
  menuService.registerAdminMenuPanel(
74
74
  'Fleet-Ops Config',
75
75
  [
76
+ new MenuItem({
77
+ title: 'Routing',
78
+ icon: 'route',
79
+ component: new ExtensionComponent('@fleetbase/fleetops-engine', 'admin/routing-settings'),
80
+ }),
81
+ new MenuItem({
82
+ title: 'Map',
83
+ icon: 'map',
84
+ component: new ExtensionComponent('@fleetbase/fleetops-engine', 'admin/map-settings'),
85
+ }),
76
86
  new MenuItem({
77
87
  title: 'Navigator App',
78
88
  icon: 'location-arrow',
@@ -179,6 +189,7 @@ export default {
179
189
  'fleet-ops:component:order:form:payload:entity:form',
180
190
  'fleet-ops:template:settings:routing',
181
191
  'fleet-ops:template:settings:orchestrator',
192
+ 'fleet-ops:component:admin:routing-settings',
182
193
  ]);
183
194
  },
184
195
  };
@@ -0,0 +1,6 @@
1
+ import { helper } from '@ember/component/helper';
2
+ import placeAddressHtml from '../utils/place-address-html';
3
+
4
+ export default helper(function placeAddressHtmlHelper([place]) {
5
+ return placeAddressHtml(place);
6
+ });
@@ -0,0 +1,17 @@
1
+ export function initialize(owner) {
2
+ const routeOptimization = owner.lookup('service:route-optimization');
3
+ const routeEngine = owner.lookup('service:route-engine');
4
+ const googleRoutes = owner.lookup('service:google-routes');
5
+
6
+ if (routeOptimization && googleRoutes) {
7
+ routeOptimization.register('google', googleRoutes);
8
+ }
9
+
10
+ if (routeEngine && googleRoutes) {
11
+ routeEngine.register('google', googleRoutes, { display: true });
12
+ }
13
+ }
14
+
15
+ export default {
16
+ initialize,
17
+ };
@@ -5,11 +5,16 @@ import { OSRMv1 } from '@fleetbase/leaflet-routing-machine';
5
5
  export function initialize(owner) {
6
6
  // Register OSRM as route optimization service
7
7
  const routeOptimization = owner.lookup('service:route-optimization');
8
+ const routeEngine = owner.lookup('service:route-engine');
8
9
  const osrm = owner.lookup('service:osrm');
9
10
  if (routeOptimization && osrm) {
10
11
  routeOptimization.register('osrm', osrm);
11
12
  }
12
13
 
14
+ if (routeEngine && osrm) {
15
+ routeEngine.register('osrm', osrm, { display: true });
16
+ }
17
+
13
18
  // Register OSRM as Routing Controler
14
19
  const leafletRoutingControl = owner.lookup('service:leaflet-routing-control');
15
20
  if (leafletRoutingControl) {
@@ -12,6 +12,7 @@ export default class ApplicationRoute extends Route {
12
12
  @service notifications;
13
13
  @service fetch;
14
14
  @service currentUser;
15
+ @service mapSettings;
15
16
 
16
17
  @action loading(transition) {
17
18
  const resource = getResourceNameFromTransition(transition, { humanize: true });
@@ -28,10 +29,15 @@ export default class ApplicationRoute extends Route {
28
29
 
29
30
  await this.location.getUserLocation();
30
31
  await this.#loadRoutingSettings();
32
+ await this.#loadMapSettings();
31
33
  }
32
34
 
33
35
  async #loadRoutingSettings() {
34
36
  const routingSetting = await this.fetch.get('fleet-ops/settings/routing-settings');
35
37
  this.currentUser.setOption('routing', routingSetting);
36
38
  }
39
+
40
+ async #loadMapSettings() {
41
+ await this.mapSettings.load();
42
+ }
37
43
  }
@@ -13,6 +13,19 @@ export default class OperationsAllocationRoute extends Route {
13
13
  @service hostRouter;
14
14
  @service abilities;
15
15
  @service intl;
16
+ @service sidebar;
17
+ @service mapManager;
18
+
19
+ activate() {
20
+ this.sidebar.hide();
21
+ }
22
+
23
+ deactivate() {
24
+ this.sidebar.show();
25
+ this.mapManager.setMapInstance?.(null);
26
+ this.mapManager.setLivemap?.(null);
27
+ this.mapManager.setActiveProvider?.(this.mapManager.getConfiguredProvider?.() ?? 'leaflet');
28
+ }
16
29
 
17
30
  beforeModel() {
18
31
  if (this.abilities.cannot('fleet-ops list order')) {
@@ -7,36 +7,27 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
7
7
  @service store;
8
8
  @service hostRouter;
9
9
  @service orderSocketEvents;
10
- @service leafletMapManager;
10
+ @service mapManager;
11
11
  @service abilities;
12
- @service universe;
13
12
  @service intl;
13
+ @service sidebar;
14
14
 
15
15
  @action willTransition(transition) {
16
16
  const fromName = transition.from?.name;
17
17
  const toName = transition.to?.name;
18
18
  const orderDetailsRoute = 'console.fleet-ops.operations.orders.index.details';
19
+ const isRefreshWithinSameDetailsRoute = fromName?.startsWith(orderDetailsRoute) && toName?.startsWith(orderDetailsRoute) && fromName === toName;
20
+
21
+ if (isRefreshWithinSameDetailsRoute) {
22
+ return true;
23
+ }
19
24
 
20
25
  // only cleanup when leaving the order details route tree entirely
21
26
  const isLeavingOrderDetails = fromName?.startsWith(orderDetailsRoute) && !toName?.startsWith(orderDetailsRoute);
22
-
23
27
  if (isLeavingOrderDetails) {
24
28
  const controller = this.controllerFor('operations.orders.index.details');
25
- const rc = controller.routingControl;
26
-
27
- // Put back sidebar
28
- if (this.universe.sidebarContext) {
29
- this.universe.sidebarContext.show();
30
- }
31
-
32
- // stop listening for events
33
- this.orderSocketEvents.stop(controller.model);
34
-
35
- // cleanup guards
36
- if (rc) {
37
- this.leafletMapManager.removeRoutingControl(rc);
38
- controller.routingControl = undefined;
39
- }
29
+ controller.teardownRealtime();
30
+ controller.teardownRoutingControls();
40
31
  }
41
32
 
42
33
  return true;
@@ -47,6 +38,14 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
47
38
  return this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index');
48
39
  }
49
40
 
41
+ activate() {
42
+ this.sidebar.hide();
43
+ }
44
+
45
+ deactivate() {
46
+ this.sidebar.show();
47
+ }
48
+
50
49
  beforeModel() {
51
50
  if (this.abilities.cannot('fleet-ops view order')) {
52
51
  this.notifications.warning(this.intl.t('common.unauthorized-access'));
@@ -75,10 +74,18 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
75
74
  });
76
75
  }
77
76
 
78
- async afterModel(order) {
79
- await order.loadTrackingActivity();
80
- if (order.meta?._index_resource) {
81
- await order.reload();
77
+ async setupController(controller, model) {
78
+ super.setupController(...arguments);
79
+
80
+ // Change layout to map
81
+ controller.index?.changeLayout?.('map');
82
+
83
+ // load activity and reload order if lightweight payload
84
+ await model.loadTrackingActivity();
85
+ if (model.meta?._index_resource) {
86
+ await model.reload();
82
87
  }
88
+
89
+ await controller.setupDetailsSession();
83
90
  }
84
91
  }
@@ -7,6 +7,7 @@ export default class OperationsOrdersIndexNewRoute extends Route {
7
7
  @service hostRouter;
8
8
  @service abilities;
9
9
  @service intl;
10
+ @service sidebar;
10
11
 
11
12
  @action willTransition() {
12
13
  if (this.controller) {
@@ -14,6 +15,14 @@ export default class OperationsOrdersIndexNewRoute extends Route {
14
15
  }
15
16
  }
16
17
 
18
+ activate() {
19
+ this.sidebar.hide();
20
+ }
21
+
22
+ deactivate() {
23
+ this.sidebar.show();
24
+ }
25
+
17
26
  beforeModel() {
18
27
  if (this.abilities.cannot('fleet-ops create order')) {
19
28
  this.notifications.warning(this.intl.t('common.unauthorized-access'));
@@ -3,7 +3,6 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class OperationsOrdersIndexRoute extends Route {
5
5
  @service store;
6
- @service leafletMapManager;
7
6
 
8
7
  queryParams = {
9
8
  page: { refreshModel: true },
@@ -0,0 +1,3 @@
1
+ import Route from '@ember/routing/route';
2
+
3
+ export default class SettingsMapRoute extends Route {}
package/addon/routes.js CHANGED
@@ -257,6 +257,7 @@ export default buildRoutes(function () {
257
257
  this.route('custom-fields');
258
258
  this.route('avatars');
259
259
  this.route('routing');
260
+ this.route('map');
260
261
  this.route('orchestrator');
261
262
  this.route('scheduling');
262
263
  this.route('payments', function () {