@fleetbase/fleetops-engine 0.6.45 → 0.6.46

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 (144) 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/payload.hbs +33 -0
  37. package/addon/components/order/form/payload.js +1 -0
  38. package/addon/components/order/form/route.hbs +8 -8
  39. package/addon/components/order/form/route.js +78 -18
  40. package/addon/components/order/form.js +2 -2
  41. package/addon/components/order/route-editor.hbs +209 -147
  42. package/addon/components/order/route-editor.js +143 -102
  43. package/addon/components/place/address.hbs +1 -0
  44. package/addon/components/place/address.js +12 -0
  45. package/addon/components/route-list.hbs +43 -55
  46. package/addon/components/route-list.js +47 -0
  47. package/addon/components/route-optimization-wizard-panel.js +3 -4
  48. package/addon/components/vehicle/card.hbs +4 -4
  49. package/addon/controllers/maintenance/schedules/index.js +2 -1
  50. package/addon/controllers/operations/orders/index/details.js +110 -37
  51. package/addon/controllers/operations/orders/index/new.js +1 -1
  52. package/addon/controllers/operations/orders/index.js +8 -1
  53. package/addon/controllers/settings/map.js +97 -0
  54. package/addon/controllers/settings/routing.js +47 -12
  55. package/addon/extension.js +11 -0
  56. package/addon/helpers/place-address-html.js +6 -0
  57. package/addon/instance-initializers/register-google-routes.js +17 -0
  58. package/addon/instance-initializers/register-osrm.js +5 -0
  59. package/addon/routes/application.js +6 -0
  60. package/addon/routes/operations/orchestrator.js +13 -0
  61. package/addon/routes/operations/orders/index/details.js +29 -22
  62. package/addon/routes/operations/orders/index/new.js +9 -0
  63. package/addon/routes/operations/orders/index.js +0 -1
  64. package/addon/routes/settings/map.js +3 -0
  65. package/addon/routes.js +1 -0
  66. package/addon/services/entity-actions.js +1 -1
  67. package/addon/services/geofence-event-bus.js +2 -1
  68. package/addon/services/geofence.js +204 -168
  69. package/addon/services/google-routes.js +300 -0
  70. package/addon/services/leaflet-draw-restriction.js +2 -1
  71. package/addon/services/leaflet-map-manager.js +17 -0
  72. package/addon/services/map-adapter/google.js +2205 -0
  73. package/addon/services/map-adapter/leaflet.js +1042 -0
  74. package/addon/services/map-adapter-interface.js +551 -0
  75. package/addon/services/map-manager.js +768 -0
  76. package/addon/services/map-settings.js +75 -0
  77. package/addon/services/movement-tracker.js +83 -32
  78. package/addon/services/order-actions.js +11 -1
  79. package/addon/services/osrm.js +79 -6
  80. package/addon/services/position-playback.js +135 -62
  81. package/addon/services/route-engine.js +83 -0
  82. package/addon/services/service-areas.js +8 -1
  83. package/addon/styles/fleetops-engine.css +555 -0
  84. package/addon/templates/management/vehicles/index.hbs +0 -1
  85. package/addon/templates/operations/orders/index/details.hbs +0 -1
  86. package/addon/templates/settings/map.hbs +53 -0
  87. package/addon/templates/settings/routing.hbs +15 -4
  88. package/addon/utils/fleet-ops-options.js +1 -0
  89. package/addon/utils/has-valid-coordinates.js +6 -0
  90. package/addon/utils/map-provider-options.js +6 -0
  91. package/addon/utils/order-route-editing.js +86 -0
  92. package/addon/utils/order-route-summary.js +20 -0
  93. package/addon/utils/place-address-html.js +41 -0
  94. package/addon/utils/route-colors.js +43 -12
  95. package/addon/utils/route-visualization.js +93 -0
  96. package/addon/utils/vendor-integration.js +2 -1
  97. package/app/components/admin/map-settings.js +1 -0
  98. package/app/components/admin/routing-settings.js +1 -0
  99. package/app/components/cell/order-route-type.js +1 -0
  100. package/app/components/cell/place-address.js +1 -0
  101. package/app/components/map/google-live-map.js +1 -0
  102. package/app/components/place/address.js +1 -0
  103. package/app/controllers/settings/map.js +1 -0
  104. package/app/helpers/place-address-html.js +1 -0
  105. package/app/routes/settings/map.js +1 -0
  106. package/app/services/map-adapter/google.js +1 -0
  107. package/app/services/map-adapter/leaflet.js +1 -0
  108. package/app/services/map-adapter-interface.js +1 -0
  109. package/app/services/map-manager.js +1 -0
  110. package/app/services/map-settings.js +1 -0
  111. package/app/templates/settings/map.js +1 -0
  112. package/app/utils/has-valid-coordinates.js +1 -0
  113. package/app/utils/order-route-editing.js +2 -0
  114. package/app/utils/order-route-summary.js +1 -0
  115. package/app/utils/place-address-html.js +1 -0
  116. package/app/utils/route-colors.js +1 -1
  117. package/app/utils/route-visualization.js +1 -0
  118. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  119. package/composer.json +1 -1
  120. package/extension.json +1 -1
  121. package/package.json +3 -3
  122. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  123. package/server/src/Http/Controllers/Api/v1/OrderController.php +31 -3
  124. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  125. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  126. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +3 -2
  127. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  128. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  129. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  130. package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
  131. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  132. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  133. package/server/src/Http/Filter/OrderFilter.php +2 -1
  134. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  135. package/server/src/Http/Resources/v1/Order.php +20 -1
  136. package/server/src/Models/Order.php +42 -1
  137. package/server/src/Models/OrderConfig.php +15 -0
  138. package/server/src/Models/Payload.php +131 -27
  139. package/server/src/Models/Place.php +182 -48
  140. package/server/src/Models/ServiceRate.php +4 -3
  141. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  142. package/server/src/Support/Utils.php +36 -2
  143. package/server/src/routes.php +4 -0
  144. package/translations/en-us.yaml +1937 -1916
@@ -11,6 +11,7 @@ import getModelName from '@fleetbase/ember-core/utils/get-model-name';
11
11
  const L = window.leaflet || window.L;
12
12
 
13
13
  export default class MapDrawerPositionListingComponent extends Component {
14
+ @service mapManager;
14
15
  @service leafletMapManager;
15
16
  @service store;
16
17
  @service fetch;
@@ -26,6 +27,7 @@ export default class MapDrawerPositionListingComponent extends Component {
26
27
  @tracked dateFilter = [format(startOfWeek(new Date(), { weekStartsOn: 1 }), 'yyyy-MM-dd'), format(endOfWeek(new Date(), { weekStartsOn: 1 }), 'yyyy-MM-dd')];
27
28
  @tracked replaySpeed = '1';
28
29
  @tracked positionsLayer = null;
30
+ @tracked positionOverlayIds = [];
29
31
 
30
32
  /** Computed properties - read state from service */
31
33
  get isReplaying() {
@@ -41,8 +43,8 @@ export default class MapDrawerPositionListingComponent extends Component {
41
43
  }
42
44
 
43
45
  get trackables() {
44
- const vehicles = this.leafletMapManager._livemap?.vehicles ?? [];
45
- const drivers = this.leafletMapManager._livemap?.drivers ?? [];
46
+ const vehicles = this.mapManager.livemap?.vehicles ?? [];
47
+ const drivers = this.mapManager.livemap?.drivers ?? [];
46
48
 
47
49
  return [...vehicles, ...drivers];
48
50
  }
@@ -168,15 +170,16 @@ export default class MapDrawerPositionListingComponent extends Component {
168
170
 
169
171
  // Clean up replay tracker
170
172
  this.positionPlayback.reset();
173
+ this.#resumeViewportReload();
171
174
 
172
175
  // Remove our layer group from the map
173
176
  this.#clearPositionsLayer(true);
174
177
  this.positionsLayer = null;
178
+ this.positionOverlayIds = [];
175
179
  }
176
180
 
177
181
  @action onResourceSelected(resource) {
178
182
  this.resource = resource;
179
- this.focusResource(resource);
180
183
  this.loadPositions.perform();
181
184
  }
182
185
 
@@ -219,6 +222,7 @@ export default class MapDrawerPositionListingComponent extends Component {
219
222
  }
220
223
 
221
224
  // Start new replay
225
+ this.#suspendViewportReload();
222
226
  this.#initializeReplay();
223
227
  this.positionPlayback.play();
224
228
  }
@@ -233,9 +237,11 @@ export default class MapDrawerPositionListingComponent extends Component {
233
237
 
234
238
  @action stopReplay() {
235
239
  this.positionPlayback.stop();
240
+ this.#resumeViewportReload();
236
241
  }
237
242
 
238
243
  @action stepForward() {
244
+ this.#suspendViewportReload();
239
245
  if (this.isReplaying) {
240
246
  this.pauseReplay();
241
247
  }
@@ -243,6 +249,7 @@ export default class MapDrawerPositionListingComponent extends Component {
243
249
  }
244
250
 
245
251
  @action stepBackward() {
252
+ this.#suspendViewportReload();
246
253
  if (this.isReplaying) {
247
254
  this.pauseReplay();
248
255
  }
@@ -256,21 +263,15 @@ export default class MapDrawerPositionListingComponent extends Component {
256
263
  }
257
264
 
258
265
  @action onPositionClicked(position) {
259
- if (this.leafletMapManager.map && position.latitude && position.longitude) {
260
- this.leafletMapManager.map.setView([position.latitude, position.longitude], 16);
266
+ if (position.latitude && position.longitude) {
267
+ this.mapManager.setCenter(position.latitude, position.longitude, 16);
261
268
  }
262
269
  }
263
270
 
264
271
  @action focusResource(resource) {
265
272
  const hasValidCoordinates = resource?.hasValidCoordinates || (Number.isFinite(resource?.latitude) && Number.isFinite(resource?.longitude));
266
273
  if (hasValidCoordinates) {
267
- const coordinates = [resource.latitude, resource.longitude];
268
-
269
- // Use flyTo with a zoom level of 18 for a smooth animation
270
- this.leafletMapManager.map.flyTo(coordinates, 18, {
271
- animate: true,
272
- duration: 0.8,
273
- });
274
+ this.mapManager.focusResource(resource, 18);
274
275
  }
275
276
  }
276
277
 
@@ -300,7 +301,11 @@ export default class MapDrawerPositionListingComponent extends Component {
300
301
  })
301
302
  : [];
302
303
 
303
- this.#renderPositionsOnMap({ fitLast: 5 });
304
+ if (this.positions.length > 0) {
305
+ this.#renderPositionsOnMap({ fitLast: 5 });
306
+ } else if (this.resource) {
307
+ this.focusResource(this.resource);
308
+ }
304
309
 
305
310
  // Reset replay state when positions change
306
311
  this.stopReplay();
@@ -331,18 +336,31 @@ export default class MapDrawerPositionListingComponent extends Component {
331
336
  subject: this.resource,
332
337
  positions: this.positions,
333
338
  speed: parseFloat(this.replaySpeed),
334
- map: this.leafletMapManager.map,
339
+ map: this.mapManager.isGoogleMaps ? null : this.leafletMapManager.map,
335
340
  callback: (data) => {
336
341
  if (data.type === 'complete') {
337
342
  // Replay completed
343
+ this.#resumeViewportReload();
338
344
  this.notifications.success('Replay completed');
339
345
  }
340
346
  },
341
347
  });
342
348
  }
343
349
 
350
+ #suspendViewportReload() {
351
+ this.mapManager.livemap?.suspendViewportReload?.(`position-playback:${this.id}`);
352
+ }
353
+
354
+ #resumeViewportReload() {
355
+ this.mapManager.livemap?.resumeViewportReload?.(`position-playback:${this.id}`);
356
+ }
357
+
344
358
  /** Position Rendering */
345
359
  #ensurePositionsLayer() {
360
+ if (this.mapManager.isGoogleMaps) {
361
+ return true;
362
+ }
363
+
346
364
  if (!this.leafletMapManager.map) return null;
347
365
 
348
366
  if (!this.positionsLayer) {
@@ -353,10 +371,23 @@ export default class MapDrawerPositionListingComponent extends Component {
353
371
  }
354
372
 
355
373
  #clearPositionsLayer(removeFromMap = false) {
374
+ if (this.mapManager.isGoogleMaps) {
375
+ for (const overlayId of this.positionOverlayIds) {
376
+ this.mapManager.removeMarker(overlayId);
377
+ }
378
+ this.positionOverlayIds = [];
379
+ return;
380
+ }
381
+
356
382
  if (this.positionsLayer) {
357
- this.positionsLayer.clearLayers();
358
383
  if (removeFromMap && this.leafletMapManager.map) {
359
384
  this.positionsLayer.removeFrom(this.leafletMapManager.map);
385
+ } else {
386
+ try {
387
+ this.positionsLayer.clearLayers();
388
+ } catch {
389
+ // Ignore teardown race when Leaflet renderer is already gone.
390
+ }
360
391
  }
361
392
  }
362
393
  }
@@ -366,6 +397,21 @@ export default class MapDrawerPositionListingComponent extends Component {
366
397
  const lng = parseFloat(pos.longitude);
367
398
  if (!this.#isValidLatLng(lat, lng)) return;
368
399
 
400
+ if (this.mapManager.isGoogleMaps) {
401
+ const overlayId = `position-history:${this.resource?.id ?? 'resource'}:${index}`;
402
+ const dot = document.createElement('div');
403
+ dot.className = 'fleetops-position-history-dot';
404
+ dot.title = `Position ${index + 1}`;
405
+
406
+ this.mapManager.addMarker(overlayId, lat, lng, {
407
+ content: dot,
408
+ title: `Position ${index + 1}`,
409
+ onClick: () => this.onPositionClicked(pos),
410
+ });
411
+ this.positionOverlayIds = [...this.positionOverlayIds, overlayId];
412
+ return;
413
+ }
414
+
369
415
  const marker = L.circleMarker([lat, lng], {
370
416
  radius: 3,
371
417
  color: '#3b82f6',
@@ -374,13 +420,13 @@ export default class MapDrawerPositionListingComponent extends Component {
374
420
  });
375
421
 
376
422
  // Popup content (mirrors your template)
377
- const html = htmlSafe(`<div class="text-xs">
423
+ const html = `<div class="text-xs">
378
424
  <div><strong>Position ${index + 1}</strong></div>
379
425
  <div>Time: ${pos.timestamp ?? ''}</div>
380
426
  <div>Speed: ${pos.speedKmh ?? 'N/A'} km/h</div>
381
427
  <div>Heading: ${pos.heading ?? 'N/A'}°</div>
382
428
  <div>Altitude: ${pos.altitude ?? 'N/A'} m</div>
383
- </div>`);
429
+ </div>`;
384
430
 
385
431
  marker.bindPopup(html);
386
432
 
@@ -395,13 +441,13 @@ export default class MapDrawerPositionListingComponent extends Component {
395
441
  }
396
442
 
397
443
  #renderPositionsOnMap({ fitLast = 0, minZoom = 15, maxZoom = 18 } = {}) {
398
- if (!this.leafletMapManager.map || !this.positions?.length) {
444
+ if ((!this.mapManager.isGoogleMaps && !this.leafletMapManager.map) || !this.positions?.length) {
399
445
  this.#clearPositionsLayer(false);
400
446
  return;
401
447
  }
402
448
 
403
449
  this.#ensurePositionsLayer();
404
- this.positionsLayer.clearLayers();
450
+ this.#clearPositionsLayer(false);
405
451
 
406
452
  const latlngs = [];
407
453
  this.positions.forEach((pos, i) => {
@@ -417,13 +463,34 @@ export default class MapDrawerPositionListingComponent extends Component {
417
463
 
418
464
  // choose subset (e.g., last 5 points) to bias the view local
419
465
  const slice = fitLast > 0 ? latlngs.slice(-fitLast) : latlngs;
466
+ const zoomBounds = this.#getReplayViewportZoomBounds({ minZoom, maxZoom });
420
467
 
421
468
  // Clamp zoom to neighborhood-level
422
- this.#fitNeighborhood(slice, { zoom: 16, minZoom, maxZoom, padding: [24, 24], animate: true });
469
+ this.#fitNeighborhood(slice, { zoom: 16, minZoom: zoomBounds.minZoom, maxZoom: zoomBounds.maxZoom, padding: [24, 24], animate: true });
423
470
  }
424
471
 
425
472
  #fitNeighborhood(latlngs, { zoom = null, minZoom = 15, maxZoom = 18, padding = [16, 16], animate = true } = {}) {
426
- if (!this.leafletMapManager.map || !latlngs?.length) return;
473
+ if (!latlngs?.length) return;
474
+
475
+ if (this.mapManager.isGoogleMaps) {
476
+ if (latlngs.length === 1) {
477
+ this.mapManager.setCenter(latlngs[0][0], latlngs[0][1], minZoom);
478
+ return;
479
+ }
480
+
481
+ const lats = latlngs.map(([lat]) => lat);
482
+ const lngs = latlngs.map(([, lng]) => lng);
483
+ this.mapManager.fitBounds(
484
+ [
485
+ [Math.min(...lats), Math.min(...lngs)],
486
+ [Math.max(...lats), Math.max(...lngs)],
487
+ ],
488
+ { paddingBottomRight: padding, animate, maxZoom, minZoom }
489
+ );
490
+ return;
491
+ }
492
+
493
+ if (!this.leafletMapManager.map) return;
427
494
 
428
495
  const map = this.leafletMapManager.map;
429
496
 
@@ -452,4 +519,18 @@ export default class MapDrawerPositionListingComponent extends Component {
452
519
  const center = bounds.getCenter();
453
520
  map.setView(center, targetZoom, { animate });
454
521
  }
522
+
523
+ #getReplayViewportZoomBounds({ minZoom = 15, maxZoom = 18 } = {}) {
524
+ if (this.mapManager.isGoogleMaps) {
525
+ return {
526
+ minZoom,
527
+ maxZoom: Math.min(maxZoom, 16),
528
+ };
529
+ }
530
+
531
+ return {
532
+ minZoom,
533
+ maxZoom,
534
+ };
535
+ }
455
536
  }
@@ -5,13 +5,13 @@ import { action } from '@ember/object';
5
5
 
6
6
  export default class MapDrawerVehicleListingComponent extends Component {
7
7
  @service vehicleActions;
8
- @service leafletMapManager;
8
+ @service mapManager;
9
9
  @service hostRouter;
10
10
  @service intl;
11
11
  @tracked query = '';
12
12
 
13
13
  get filteredVehicles() {
14
- const vehicles = this.leafletMapManager._livemap?.vehicles ?? [];
14
+ const vehicles = this.mapManager.livemap?.vehicles ?? [];
15
15
  const query = this.query?.toLowerCase();
16
16
  if (!query) {
17
17
  return vehicles;
@@ -100,7 +100,7 @@ export default class MapDrawerVehicleListingComponent extends Component {
100
100
  }
101
101
 
102
102
  @action view(vehicle) {
103
- this.leafletMapManager.flyToRecordLayer(vehicle, 16, {
103
+ this.mapManager.focusResource(vehicle, 16, {
104
104
  paddingBottomRight: [300, 200],
105
105
  moveend: () => {
106
106
  this.vehicleActions.panel.view(vehicle);
@@ -109,7 +109,7 @@ export default class MapDrawerVehicleListingComponent extends Component {
109
109
  }
110
110
 
111
111
  @action edit(vehicle) {
112
- this.leafletMapManager.flyToRecordLayer(vehicle, 16, {
112
+ this.mapManager.focusResource(vehicle, 16, {
113
113
  paddingBottomRight: [300, 200],
114
114
  moveend: () => {
115
115
  this.vehicleActions.panel.edit(vehicle);
@@ -118,7 +118,7 @@ export default class MapDrawerVehicleListingComponent extends Component {
118
118
  }
119
119
 
120
120
  @action locate(vehicle) {
121
- this.leafletMapManager.flyToRecordLayer(vehicle, 18, {
121
+ this.mapManager.focusResource(vehicle, 18, {
122
122
  paddingBottomRight: [300, 200],
123
123
  });
124
124
  }
@@ -0,0 +1,9 @@
1
+ <div
2
+ id={{this.id}}
3
+ class="google-live-map-container next-google-container-map {{@mapClass}}"
4
+ style={{html-safe "width:100%;height:100%"}}
5
+ {{did-insert this.setupMap}}
6
+ {{did-update this.syncResources @drivers @vehicles @places @serviceAreas}}
7
+ ...attributes
8
+ >
9
+ </div>