@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.
- package/addon/components/admin/map-settings.hbs +26 -0
- package/addon/components/admin/map-settings.js +46 -0
- package/addon/components/admin/routing-settings.hbs +11 -0
- package/addon/components/cell/order-route-type.hbs +46 -0
- package/addon/components/cell/order-route-type.js +90 -0
- package/addon/components/cell/place-address.hbs +1 -0
- package/addon/components/display-place.hbs +45 -56
- package/addon/components/display-place.js +21 -0
- package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
- package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
- package/addon/components/layout/fleet-ops-sidebar.js +8 -0
- package/addon/components/maintenance-schedule/form.js +2 -1
- package/addon/components/map/drawer/driver-listing.js +5 -5
- package/addon/components/map/drawer/place-listing.js +5 -5
- package/addon/components/map/drawer/position-listing.js +102 -21
- package/addon/components/map/drawer/vehicle-listing.js +5 -5
- package/addon/components/map/google-live-map.hbs +9 -0
- package/addon/components/map/google-live-map.js +445 -0
- package/addon/components/map/leaflet-live-map.hbs +186 -142
- package/addon/components/map/leaflet-live-map.js +121 -41
- package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
- package/addon/components/map/toolbar/zones-panel.hbs +9 -9
- package/addon/components/map/toolbar.hbs +3 -3
- package/addon/components/map/toolbar.js +9 -1
- package/addon/components/modals/map-layer-form.hbs +132 -63
- package/addon/components/modals/orchestrator-import.hbs +4 -11
- package/addon/components/modals/orchestrator-import.js +17 -1
- package/addon/components/modals/update-order-activity.hbs +1 -1
- package/addon/components/orchestrator/order-pool.js +3 -2
- package/addon/components/orchestrator-workbench.js +29 -41
- package/addon/components/order/details/detail.js +2 -2
- package/addon/components/order/details/payload.hbs +5 -5
- package/addon/components/order/details/payload.js +18 -1
- package/addon/components/order/details/route.hbs +2 -2
- package/addon/components/order/details/route.js +76 -3
- package/addon/components/order/form/details.hbs +1 -2
- package/addon/components/order/form/details.js +6 -0
- package/addon/components/order/form/payload.hbs +33 -0
- package/addon/components/order/form/payload.js +1 -0
- package/addon/components/order/form/route.hbs +8 -8
- package/addon/components/order/form/route.js +86 -18
- package/addon/components/order/form.js +2 -2
- package/addon/components/order/route-editor.hbs +209 -147
- package/addon/components/order/route-editor.js +143 -102
- package/addon/components/place/address.hbs +1 -0
- package/addon/components/place/address.js +12 -0
- package/addon/components/route-list.hbs +43 -55
- package/addon/components/route-list.js +47 -0
- package/addon/components/route-optimization-wizard-panel.js +3 -4
- package/addon/components/vehicle/card.hbs +4 -4
- package/addon/controllers/maintenance/schedules/index.js +2 -1
- package/addon/controllers/operations/orders/index/details.js +110 -37
- package/addon/controllers/operations/orders/index/new.js +1 -1
- package/addon/controllers/operations/orders/index.js +8 -1
- package/addon/controllers/settings/map.js +97 -0
- package/addon/controllers/settings/payments/index.js +1 -1
- package/addon/controllers/settings/routing.js +47 -12
- package/addon/extension.js +11 -0
- package/addon/helpers/place-address-html.js +6 -0
- package/addon/instance-initializers/register-google-routes.js +17 -0
- package/addon/instance-initializers/register-osrm.js +5 -0
- package/addon/routes/application.js +6 -0
- package/addon/routes/operations/orchestrator.js +13 -0
- package/addon/routes/operations/orders/index/details.js +29 -22
- package/addon/routes/operations/orders/index/new.js +9 -0
- package/addon/routes/operations/orders/index.js +0 -1
- package/addon/routes/settings/map.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/entity-actions.js +1 -1
- package/addon/services/geofence-event-bus.js +2 -1
- package/addon/services/geofence.js +204 -168
- package/addon/services/google-routes.js +300 -0
- package/addon/services/leaflet-draw-restriction.js +2 -1
- package/addon/services/leaflet-map-manager.js +17 -0
- package/addon/services/map-adapter/google.js +2205 -0
- package/addon/services/map-adapter/leaflet.js +1042 -0
- package/addon/services/map-adapter-interface.js +551 -0
- package/addon/services/map-manager.js +768 -0
- package/addon/services/map-settings.js +75 -0
- package/addon/services/movement-tracker.js +83 -32
- package/addon/services/order-actions.js +11 -1
- package/addon/services/osrm.js +79 -6
- package/addon/services/position-playback.js +135 -62
- package/addon/services/route-engine.js +83 -0
- package/addon/services/service-areas.js +8 -1
- package/addon/styles/fleetops-engine.css +555 -0
- package/addon/templates/management/vehicles/index.hbs +0 -1
- package/addon/templates/operations/orders/index/details.hbs +0 -1
- package/addon/templates/settings/map.hbs +53 -0
- package/addon/templates/settings/routing.hbs +15 -4
- package/addon/utils/fleet-ops-options.js +1 -0
- package/addon/utils/has-valid-coordinates.js +6 -0
- package/addon/utils/map-provider-options.js +6 -0
- package/addon/utils/order-route-editing.js +86 -0
- package/addon/utils/order-route-summary.js +20 -0
- package/addon/utils/place-address-html.js +41 -0
- package/addon/utils/route-colors.js +43 -12
- package/addon/utils/route-visualization.js +93 -0
- package/addon/utils/vendor-integration.js +2 -1
- package/app/components/admin/map-settings.js +1 -0
- package/app/components/admin/routing-settings.js +1 -0
- package/app/components/cell/order-route-type.js +1 -0
- package/app/components/cell/place-address.js +1 -0
- package/app/components/map/google-live-map.js +1 -0
- package/app/components/place/address.js +1 -0
- package/app/controllers/settings/map.js +1 -0
- package/app/helpers/place-address-html.js +1 -0
- package/app/routes/settings/map.js +1 -0
- package/app/services/map-adapter/google.js +1 -0
- package/app/services/map-adapter/leaflet.js +1 -0
- package/app/services/map-adapter-interface.js +1 -0
- package/app/services/map-manager.js +1 -0
- package/app/services/map-settings.js +1 -0
- package/app/templates/settings/map.js +1 -0
- package/app/utils/has-valid-coordinates.js +1 -0
- package/app/utils/order-route-editing.js +2 -0
- package/app/utils/order-route-summary.js +1 -0
- package/app/utils/place-address-html.js +1 -0
- package/app/utils/route-colors.js +1 -1
- package/app/utils/route-visualization.js +1 -0
- package/assets/images/drawing-controls-spritesheet.svg +156 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/pnpm-workspace.yaml +7 -0
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +31 -3
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +3 -2
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +90 -34
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
- package/server/src/Http/Filter/OrderFilter.php +2 -1
- package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
- package/server/src/Http/Resources/v1/Order.php +20 -1
- package/server/src/Models/Order.php +42 -1
- package/server/src/Models/OrderConfig.php +15 -0
- package/server/src/Models/Payload.php +131 -27
- package/server/src/Models/Place.php +182 -48
- package/server/src/Models/ServiceRate.php +4 -3
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
- package/server/src/Support/Utils.php +36 -2
- package/server/src/routes.php +4 -0
- package/translations/en-us.yaml +1937 -1916
|
@@ -9,9 +9,12 @@ import { camelize, capitalize, dasherize } from '@ember/string';
|
|
|
9
9
|
import { singularize, pluralize } from 'ember-inflector';
|
|
10
10
|
import { all } from 'rsvp';
|
|
11
11
|
import { task } from 'ember-concurrency';
|
|
12
|
+
import { next } from '@ember/runloop';
|
|
12
13
|
import getModelName from '@fleetbase/ember-core/utils/get-model-name';
|
|
13
14
|
|
|
14
15
|
export default class MapLeafletLiveMapComponent extends Component {
|
|
16
|
+
@service mapManager;
|
|
17
|
+
@service mapSettings;
|
|
15
18
|
@service leafletMapManager;
|
|
16
19
|
@service leafletLayerVisibilityManager;
|
|
17
20
|
@service leafletContextmenuManager;
|
|
@@ -47,6 +50,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
47
50
|
@tracked drivers = [];
|
|
48
51
|
@tracked vehicles = [];
|
|
49
52
|
@tracked places = [];
|
|
53
|
+
_viewportReloadLocks = new Set();
|
|
50
54
|
|
|
51
55
|
constructor() {
|
|
52
56
|
super(...arguments);
|
|
@@ -61,9 +65,9 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
61
65
|
this.#updateCoordinatesFromLocation();
|
|
62
66
|
|
|
63
67
|
if (this.currentUser.companyId) {
|
|
64
|
-
this.geofenceEventBus.subscribe(this.currentUser.companyId);
|
|
68
|
+
next(this, () => this.geofenceEventBus.subscribe(this.currentUser.companyId));
|
|
65
69
|
} else {
|
|
66
|
-
this._currentUserLoadedHandler = () => this.geofenceEventBus.subscribe(this.currentUser.companyId);
|
|
70
|
+
this._currentUserLoadedHandler = () => next(this, () => this.geofenceEventBus.subscribe(this.currentUser.companyId));
|
|
67
71
|
this.currentUser.on('user.loaded', this._currentUserLoadedHandler);
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -101,12 +105,35 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
101
105
|
this.#createMapContextMenu(map);
|
|
102
106
|
this.trigger('onLoad', ...arguments);
|
|
103
107
|
this.load.perform();
|
|
104
|
-
|
|
105
108
|
// Listen for map move/zoom events to trigger viewport-based resource reload
|
|
106
109
|
map.on('moveend', () => this.reloadResourcesInViewport.perform());
|
|
107
110
|
map.on('zoomend', () => this.reloadResourcesInViewport.perform());
|
|
108
111
|
}
|
|
109
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Called when the Google Maps adapter is ready.
|
|
115
|
+
* Mirrors `didLoad` but receives the adapter instance instead of a raw Leaflet map.
|
|
116
|
+
*
|
|
117
|
+
* @param {{ target: * }} event
|
|
118
|
+
*/
|
|
119
|
+
@action didLoadGoogleMap({ target: map }) {
|
|
120
|
+
this.#setMapFromAdapter(map);
|
|
121
|
+
this.trigger('onLoad', { target: map });
|
|
122
|
+
this.load.perform();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
suspendViewportReload(lockId = 'default') {
|
|
126
|
+
this._viewportReloadLocks.add(lockId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
resumeViewportReload(lockId = 'default') {
|
|
130
|
+
this._viewportReloadLocks.delete(lockId);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get isViewportReloadSuspended() {
|
|
134
|
+
return this._viewportReloadLocks.size > 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
@action trigger(name, ...rest) {
|
|
111
138
|
if (typeof this[name] === 'function') {
|
|
112
139
|
this[name](...rest);
|
|
@@ -121,11 +148,13 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
121
148
|
|
|
122
149
|
@action didCreateDrawControl(drawControl) {
|
|
123
150
|
this.leafletMapManager.setDrawControl(drawControl);
|
|
151
|
+
this.mapManager.adapter?.setDrawControl?.(drawControl, this.leafletMapManager.drawControlFeatureGroup);
|
|
124
152
|
this.trigger('onDrawControlCreated', ...arguments);
|
|
125
153
|
}
|
|
126
154
|
|
|
127
155
|
@action didCreateDrawControlFeatureGroup(featureGroup) {
|
|
128
156
|
this.leafletMapManager.setDrawControlFeatureGroup(featureGroup);
|
|
157
|
+
this.mapManager.adapter?.setDrawControl?.(this.leafletMapManager.drawControl, featureGroup);
|
|
129
158
|
this.trigger('onDrawFeatureGroupCreated', ...arguments);
|
|
130
159
|
}
|
|
131
160
|
|
|
@@ -139,9 +168,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
139
168
|
this.driverActions.panel.view(driver, {
|
|
140
169
|
size: 'xs',
|
|
141
170
|
onOpen: () => {
|
|
142
|
-
this.
|
|
143
|
-
this.map.panBy([200, 0]);
|
|
144
|
-
});
|
|
171
|
+
this.mapManager.once?.('moveend', () => this.mapManager.panBy?.(200, 0)) ?? this.map?.once?.('moveend', () => this.map?.panBy?.([200, 0]));
|
|
145
172
|
},
|
|
146
173
|
});
|
|
147
174
|
}
|
|
@@ -156,9 +183,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
156
183
|
this.vehicleActions.panel.view(vehicle, {
|
|
157
184
|
size: 'xs',
|
|
158
185
|
onOpen: () => {
|
|
159
|
-
this.
|
|
160
|
-
this.map.panBy([200, 0]);
|
|
161
|
-
});
|
|
186
|
+
this.mapManager.once?.('moveend', () => this.mapManager.panBy?.(200, 0)) ?? this.map?.once?.('moveend', () => this.map?.panBy?.([200, 0]));
|
|
162
187
|
},
|
|
163
188
|
});
|
|
164
189
|
}
|
|
@@ -171,9 +196,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
171
196
|
this.placeActions.panel.view(place, {
|
|
172
197
|
size: 'xs',
|
|
173
198
|
onOpen: () => {
|
|
174
|
-
this.
|
|
175
|
-
this.map.panBy([200, 0]);
|
|
176
|
-
});
|
|
199
|
+
this.mapManager.once?.('moveend', () => this.mapManager.panBy?.(200, 0)) ?? this.map?.once?.('moveend', () => this.map?.panBy?.([200, 0]));
|
|
177
200
|
},
|
|
178
201
|
});
|
|
179
202
|
}
|
|
@@ -191,13 +214,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
191
214
|
/** load resources and wait for stuff here and trigger map ready **/
|
|
192
215
|
@task *load() {
|
|
193
216
|
try {
|
|
194
|
-
// Get initial map bounds for spatial filtering
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
bounds: [bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast()],
|
|
199
|
-
}
|
|
200
|
-
: {};
|
|
217
|
+
// Get initial map bounds for spatial filtering (provider-agnostic)
|
|
218
|
+
const mapBounds = this.mapManager.getBounds?.() ?? (this.map ? this.map.getBounds() : null);
|
|
219
|
+
const bounds = this.#serializeBoundsForRequest(mapBounds);
|
|
220
|
+
const params = bounds ? { bounds } : {};
|
|
201
221
|
|
|
202
222
|
const data = yield all([
|
|
203
223
|
this.loadResource.perform('routes'),
|
|
@@ -216,15 +236,18 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
@task({ restartable: true }) *reloadResourcesInViewport() {
|
|
219
|
-
if (!this.map) {
|
|
239
|
+
if (!this.map && !this.mapManager.isReady) {
|
|
220
240
|
return;
|
|
221
241
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
if (this.isViewportReloadSuspended) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Get current map bounds (provider-agnostic)
|
|
246
|
+
const rawBounds = this.mapManager.getBounds?.() ?? this.map?.getBounds();
|
|
247
|
+
if (!rawBounds) return;
|
|
248
|
+
const bounds = this.#serializeBoundsForRequest(rawBounds);
|
|
249
|
+
if (!bounds) return;
|
|
250
|
+
const params = { bounds };
|
|
228
251
|
|
|
229
252
|
// Reload spatially-filtered resources (drivers, vehicles, places)
|
|
230
253
|
// Orders, routes, and service-areas are not spatially filtered
|
|
@@ -272,6 +295,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
272
295
|
return this.ready === true;
|
|
273
296
|
}
|
|
274
297
|
|
|
298
|
+
get shouldUseGoogleMaps() {
|
|
299
|
+
return this.mapSettings.isGoogleMaps;
|
|
300
|
+
}
|
|
301
|
+
|
|
275
302
|
/**
|
|
276
303
|
* Get valid zoom level for map initialization
|
|
277
304
|
* @returns {number} Valid zoom level between 1-20
|
|
@@ -372,46 +399,93 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
372
399
|
set(map, 'livemap', this);
|
|
373
400
|
this.map = map;
|
|
374
401
|
this.leafletMapManager.setMap(map);
|
|
402
|
+
this.mapManager.setLivemap(this);
|
|
403
|
+
// Also register with the provider-agnostic manager so all services can use it
|
|
404
|
+
this.mapManager.setActiveProvider('leaflet');
|
|
405
|
+
this.mapManager.setMapInstance(map);
|
|
406
|
+
this.universe.trigger('fleet-ops.live-map.loaded', map);
|
|
407
|
+
this.universe.set('component:fleet-ops:live-map', this);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Called when the Google Maps adapter initialises.
|
|
412
|
+
* Registers the adapter as the active provider so all services
|
|
413
|
+
* route through the provider-agnostic MapManagerService.
|
|
414
|
+
*
|
|
415
|
+
* @param {import('../services/map-manager').default} adapter
|
|
416
|
+
*/
|
|
417
|
+
#setMapFromAdapter(map) {
|
|
418
|
+
this.map = map;
|
|
419
|
+
this.mapManager.setLivemap(this);
|
|
420
|
+
this.mapManager.setActiveProvider('google');
|
|
375
421
|
this.universe.trigger('fleet-ops.live-map.loaded', map);
|
|
376
422
|
this.universe.set('component:fleet-ops:live-map', this);
|
|
377
423
|
}
|
|
378
424
|
|
|
425
|
+
#serializeBoundsForRequest(rawBounds) {
|
|
426
|
+
if (!rawBounds) return null;
|
|
427
|
+
|
|
428
|
+
if (isArray(rawBounds) && rawBounds.length === 2) {
|
|
429
|
+
const [[south, west], [north, east]] = rawBounds;
|
|
430
|
+
return [south, west, north, east].every(Number.isFinite) ? [south, west, north, east] : null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (typeof rawBounds.getSouth === 'function') {
|
|
434
|
+
const bounds = [rawBounds.getSouth(), rawBounds.getWest(), rawBounds.getNorth(), rawBounds.getEast()];
|
|
435
|
+
return bounds.every(Number.isFinite) ? bounds : null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (typeof rawBounds.getSouthWest === 'function') {
|
|
439
|
+
const sw = rawBounds.getSouthWest();
|
|
440
|
+
const ne = rawBounds.getNorthEast();
|
|
441
|
+
const bounds = [sw.lat(), sw.lng(), ne.lat(), ne.lng()];
|
|
442
|
+
return bounds.every(Number.isFinite) ? bounds : null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
379
448
|
#setResourceLayer(model, layer, options = {}) {
|
|
380
449
|
const { hidden = false } = options;
|
|
381
450
|
const type = getModelName(model);
|
|
382
|
-
|
|
451
|
+
// Leaflet backward-compat: keep leafletLayer for existing code
|
|
383
452
|
set(model, 'leafletLayer', layer);
|
|
384
453
|
set(layer, 'record_id', model.id);
|
|
385
454
|
set(layer, 'record_type', type);
|
|
386
|
-
|
|
387
455
|
this.leafletLayerVisibilityManager.registerLayer(pluralize(type), layer, { id: model.id, hidden });
|
|
456
|
+
|
|
457
|
+
if (type === 'service-area' || type === 'zone') {
|
|
458
|
+
this.mapManager.registerPolygon?.(model.id, layer, { type, hidden });
|
|
459
|
+
} else {
|
|
460
|
+
this.mapManager.registerMarker?.(model.id, layer, { type, hidden });
|
|
461
|
+
}
|
|
388
462
|
}
|
|
389
463
|
|
|
390
464
|
#createMapContextMenu(map) {
|
|
391
465
|
const items = [
|
|
392
466
|
{
|
|
393
467
|
text: this.intl.t('live-map.show-coordinates'),
|
|
394
|
-
callback: this.leafletMapManager.showCoordinates,
|
|
468
|
+
callback: this.mapManager.showCoordinates?.bind(this.mapManager) ?? this.leafletMapManager.showCoordinates,
|
|
395
469
|
index: 0,
|
|
396
470
|
},
|
|
397
471
|
{
|
|
398
472
|
text: this.intl.t('live-map.center-map'),
|
|
399
|
-
callback: this.leafletMapManager.centerMap,
|
|
473
|
+
callback: this.mapManager.centerMap?.bind(this.mapManager) ?? this.leafletMapManager.centerMap,
|
|
400
474
|
index: 1,
|
|
401
475
|
},
|
|
402
476
|
{
|
|
403
477
|
text: this.intl.t('live-map.zoom-in'),
|
|
404
|
-
callback: this.leafletMapManager.zoomIn,
|
|
478
|
+
callback: this.mapManager.zoomIn?.bind(this.mapManager) ?? this.leafletMapManager.zoomIn,
|
|
405
479
|
index: 2,
|
|
406
480
|
},
|
|
407
481
|
{
|
|
408
482
|
text: this.intl.t('live-map.zoom-out'),
|
|
409
|
-
callback: this.leafletMapManager.zoomOut,
|
|
483
|
+
callback: this.mapManager.zoomOut?.bind(this.mapManager) ?? this.leafletMapManager.zoomOut,
|
|
410
484
|
index: 3,
|
|
411
485
|
},
|
|
412
486
|
{
|
|
413
487
|
text: this.intl.t('live-map.toggle-draw-controls'),
|
|
414
|
-
callback: this.
|
|
488
|
+
callback: this.geofence.toggleDrawControl.bind(this.geofence),
|
|
415
489
|
index: 4,
|
|
416
490
|
},
|
|
417
491
|
{ separator: true },
|
|
@@ -445,16 +519,19 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
445
519
|
text: this.intl.t('live-map.edit-zone', { zoneName: zone.name }),
|
|
446
520
|
callback: () => this.zoneActions.modal.edit(zone),
|
|
447
521
|
},
|
|
448
|
-
{
|
|
449
|
-
text: this.intl.t('live-map.edit-boundaries', { resource: zone.name }),
|
|
450
|
-
callback: () => this.geofence.editZone(zone),
|
|
451
|
-
},
|
|
452
522
|
{
|
|
453
523
|
text: this.intl.t('live-map.delete-zone', { zoneName: zone.name }),
|
|
454
524
|
callback: () => this.zoneActions.delete(zone),
|
|
455
525
|
},
|
|
456
526
|
];
|
|
457
527
|
|
|
528
|
+
if (!this.mapManager.isGoogleMaps) {
|
|
529
|
+
items.splice(2, 0, {
|
|
530
|
+
text: this.intl.t('live-map.edit-boundaries', { resource: zone.name }),
|
|
531
|
+
callback: () => this.geofence.editZone(zone),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
458
535
|
// create contextmenu registry
|
|
459
536
|
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(`zone:${zone.public_id}`, layer, items, { zone });
|
|
460
537
|
this.universe.trigger('fleet-ops:contextmenu:zone:created', contextmenuRegistry, this.leafletContextmenuManager);
|
|
@@ -479,16 +556,19 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
479
556
|
text: this.intl.t('live-map.edit-service', { serviceName: serviceArea.name }),
|
|
480
557
|
callback: () => this.serviceAreaActions.modal.edit(serviceArea),
|
|
481
558
|
},
|
|
482
|
-
{
|
|
483
|
-
text: this.intl.t('live-map.edit-boundaries', { resource: serviceArea.name }),
|
|
484
|
-
callback: () => this.geofence.editServiceArea(serviceArea),
|
|
485
|
-
},
|
|
486
559
|
{
|
|
487
560
|
text: this.intl.t('live-map.delete-service', { serviceName: serviceArea.name }),
|
|
488
561
|
callback: () => this.serviceAreaActions.delete(serviceArea),
|
|
489
562
|
},
|
|
490
563
|
];
|
|
491
564
|
|
|
565
|
+
if (!this.mapManager.isGoogleMaps) {
|
|
566
|
+
items.splice(4, 0, {
|
|
567
|
+
text: this.intl.t('live-map.edit-boundaries', { resource: serviceArea.name }),
|
|
568
|
+
callback: () => this.geofence.editServiceArea(serviceArea),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
492
572
|
// create contextmenu registry
|
|
493
573
|
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(`service-area:${serviceArea.public_id}`, layer, items, { serviceArea });
|
|
494
574
|
this.universe.trigger('fleet-ops:contextmenu:service-area:created', contextmenuRegistry, this.leafletContextmenuManager);
|
|
@@ -6,13 +6,13 @@ import { calculateInPlacePosition } from 'ember-basic-dropdown/utils/calculate-p
|
|
|
6
6
|
|
|
7
7
|
export default class MapToolbarVisibilityControlPanel extends Component {
|
|
8
8
|
@service leafletLayerVisibilityManager;
|
|
9
|
-
@service
|
|
9
|
+
@service mapManager;
|
|
10
10
|
@tracked areVehiclesHidden = false;
|
|
11
11
|
@tracked arePlacesHidden = false;
|
|
12
12
|
@tracked areDriversHidden = false;
|
|
13
13
|
|
|
14
14
|
get livemap() {
|
|
15
|
-
return this.
|
|
15
|
+
return this.mapManager.livemap ?? {};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
get vehicles() {
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
<div class="next-dd-menu-seperator"></div>
|
|
8
8
|
<div class="px-1">
|
|
9
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn @dd this.geofence.createServiceArea)}}>{{t "map.zones-panel.create-service"}}</a>
|
|
10
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn @dd this.geofence.showAllServiceAreas)}}>{{t "map.zones-panel.show"}}</a>
|
|
11
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn @dd this.geofence.hideAllServiceAreas)}}>{{t "map.zones-panel.hide"}}</a>
|
|
9
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn @dd this.geofence.createServiceArea)}}>{{t "map.zones-panel.create-service"}}</a>
|
|
10
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn @dd this.geofence.showAllServiceAreas)}}>{{t "map.zones-panel.show"}}</a>
|
|
11
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn @dd this.geofence.hideAllServiceAreas)}}>{{t "map.zones-panel.hide"}}</a>
|
|
12
12
|
{{#each this.serviceAreaActions.serviceAreas as |serviceArea|}}
|
|
13
13
|
<BasicDropdownHover @horizontalPosition="right" @calculatePosition={{this.calculatePosition}} @renderInPlace={{true}} as |dd|>
|
|
14
14
|
<dd.Trigger class="next-dd-item {{if dd.isOpen 'active'}}">
|
|
@@ -26,24 +26,24 @@
|
|
|
26
26
|
<div class="next-dd-menu-seperator"></div>
|
|
27
27
|
<div class="px-1">
|
|
28
28
|
{{#if (is-model-leaflet-layer-hidden serviceArea)}}
|
|
29
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.geofence.focusServiceArea serviceArea)}}>
|
|
29
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn dd this.geofence.focusServiceArea serviceArea)}}>
|
|
30
30
|
<span>{{t "map.zones-panel.focus-resource" resource=serviceArea.name}}</span>
|
|
31
31
|
</a>
|
|
32
32
|
{{else}}
|
|
33
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.geofence.blurServiceArea serviceArea)}}>
|
|
33
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn dd this.geofence.blurServiceArea serviceArea)}}>
|
|
34
34
|
<span>{{t "map.zones-panel.blur-resource" resource=serviceArea.name}}</span>
|
|
35
35
|
</a>
|
|
36
36
|
{{/if}}
|
|
37
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.geofence.createZone serviceArea)}}>
|
|
37
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn dd this.geofence.createZone serviceArea)}}>
|
|
38
38
|
<span>{{t "map.zones-panel.create-zone-inside" serviceAreaName=serviceArea.name}}</span>
|
|
39
39
|
</a>
|
|
40
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.serviceAreaActions.modal.edit serviceArea)}}>
|
|
40
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn dd this.serviceAreaActions.modal.edit serviceArea)}}>
|
|
41
41
|
<span>{{t "common.edit-resource-name" resourceName=serviceArea.name}}</span>
|
|
42
42
|
</a>
|
|
43
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.geofence.editServiceArea serviceArea)}}>
|
|
43
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn dd this.geofence.editServiceArea serviceArea)}}>
|
|
44
44
|
<span>{{t "map.zones-panel.edit-boundaries" resource=serviceArea.name}}</span>
|
|
45
45
|
</a>
|
|
46
|
-
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.serviceAreaActions.delete serviceArea)}}>
|
|
46
|
+
<a href="javascript:;" class="next-dd-item truncate w-full" {{on "click" (dropdown-fn dd this.serviceAreaActions.delete serviceArea)}}>
|
|
47
47
|
<span>{{t "common.delete-resource-name" resourceName=serviceArea.name}}</span>
|
|
48
48
|
</a>
|
|
49
49
|
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<div class="next-map-container-toolbar-container {{if @horizontal 'toolbar-container-horizontal'}} {{if (eq @position 'bottom') 'position-bottom'}}" ...attributes>
|
|
2
2
|
<div class="next-map-container-toolbar next-map-container-toolbar-zoom-control">
|
|
3
3
|
<div id="map-toolbar-zoom-in-button-wrapper">
|
|
4
|
-
<button type="button" id="map-toolbar-zoom-in-button" class="toolbar-button" {{on "click" this.
|
|
4
|
+
<button type="button" id="map-toolbar-zoom-in-button" class="toolbar-button" {{on "click" this.zoomIn}}>
|
|
5
5
|
<FaIcon @icon="plus" @prefix="fas" class="text-gray-100" />
|
|
6
6
|
</button>
|
|
7
7
|
</div>
|
|
8
8
|
<div id="map-toolbar-zoom-out-button-wrapper">
|
|
9
|
-
<button type="button" class="toolbar-button" {{on "click" this.
|
|
9
|
+
<button type="button" class="toolbar-button" {{on "click" this.zoomOut}}>
|
|
10
10
|
<FaIcon @icon="minus" id="map-toolbar-zoom-out-button" @prefix="fas" class="text-gray-100" />
|
|
11
11
|
</button>
|
|
12
12
|
</div>
|
|
@@ -87,4 +87,4 @@
|
|
|
87
87
|
</Attach::Tooltip>
|
|
88
88
|
</button>
|
|
89
89
|
</div>
|
|
90
|
-
</div>
|
|
90
|
+
</div>
|
|
@@ -6,7 +6,7 @@ import { debug } from '@ember/debug';
|
|
|
6
6
|
import { task } from 'ember-concurrency';
|
|
7
7
|
|
|
8
8
|
export default class MapToolbarComponent extends Component {
|
|
9
|
-
@service
|
|
9
|
+
@service mapManager;
|
|
10
10
|
@service orderListOverlay;
|
|
11
11
|
@service mapDrawer;
|
|
12
12
|
@service globalSearch;
|
|
@@ -30,6 +30,14 @@ export default class MapToolbarComponent extends Component {
|
|
|
30
30
|
return { style };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
@action zoomIn() {
|
|
34
|
+
return this.mapManager.zoomIn?.();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@action zoomOut() {
|
|
38
|
+
return this.mapManager.zoomOut?.();
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
@task *getActiveOrderCount() {
|
|
34
42
|
try {
|
|
35
43
|
const count = yield this.fetch.get('fleet-ops/metrics', { discover: ['orders_in_progress'] });
|
|
@@ -1,67 +1,136 @@
|
|
|
1
1
|
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
|
2
|
-
<div
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<ContentPanel @title={{if (eq @options.selectedLayerType "Zone") "Zone Details" "Service Area Details"}} @open={{true}} @wrapperClass="bordered-top">
|
|
4
|
+
<div class="space-y-2">
|
|
5
|
+
<InputGroup @name={{t "modals.map-layer-form.layer-type"}} @helpText={{t "modals.map-layer-form.layer-type-text"}}>
|
|
6
|
+
<Select
|
|
7
|
+
@options={{@options.layerTypes}}
|
|
8
|
+
@value={{@options.selectedLayerType}}
|
|
9
|
+
@onSelect={{fn (mut @options.selectedLayerType)}}
|
|
10
|
+
@placeholder={{t "modals.map-layer-form.select-layer-type"}}
|
|
11
|
+
class="w-full"
|
|
12
|
+
/>
|
|
13
|
+
</InputGroup>
|
|
14
|
+
|
|
15
|
+
{{#if (eq @options.selectedLayerType "Zone")}}
|
|
16
|
+
<InputGroup @name={{t "modals.map-layer-form.service-area"}} @helpText={{t "modals.map-layer-form.service-area-text"}}>
|
|
17
|
+
<ModelSelect
|
|
18
|
+
@modelName="service-area"
|
|
19
|
+
@selectedModel={{@options.layerOptions.service_area}}
|
|
20
|
+
@placeholder={{t "modals.map-layer-form.select-service-area"}}
|
|
21
|
+
@triggerClass="form-select form-input"
|
|
22
|
+
@infiniteScroll={{false}}
|
|
23
|
+
@renderInPlace={{true}}
|
|
24
|
+
@onChange={{fn (mut @options.layerOptions.service_area)}}
|
|
25
|
+
as |model|
|
|
26
|
+
>
|
|
27
|
+
{{model.name}}
|
|
28
|
+
</ModelSelect>
|
|
29
|
+
</InputGroup>
|
|
30
|
+
{{/if}}
|
|
31
|
+
|
|
32
|
+
<InputGroup
|
|
33
|
+
@value={{@options.layerOptions.name}}
|
|
34
|
+
@name="{{@options.selectedLayerType}} Name"
|
|
35
|
+
@helpText={{t "modals.map-layer-form.set-name" htmlSafe=true options=@options.selectedLayerType}}
|
|
36
|
+
@required={{true}}
|
|
11
37
|
/>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
|
|
39
|
+
{{#if (eq @options.selectedLayerType "Service Area")}}
|
|
40
|
+
<InputGroup @name={{t "modals.map-layer-form.service-type"}} @helpText={{t "modals.map-layer-form.service-type-text"}}>
|
|
41
|
+
<Select
|
|
42
|
+
@options={{@options.serviceAreaTypes}}
|
|
43
|
+
@value={{@options.layerOptions.type}}
|
|
44
|
+
@onSelect={{fn (mut @options.layerOptions.type)}}
|
|
45
|
+
@placeholder={{t "modals.map-layer-form.select-service-type"}}
|
|
46
|
+
class="w-full"
|
|
47
|
+
/>
|
|
48
|
+
</InputGroup>
|
|
49
|
+
{{/if}}
|
|
50
|
+
|
|
51
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
|
|
52
|
+
<InputGroup
|
|
53
|
+
@type="color"
|
|
54
|
+
@value={{@options.layerOptions.stroke_color}}
|
|
55
|
+
@name="{{@options.selectedLayerType}} Border Color"
|
|
56
|
+
@helpText={{t "modals.map-layer-form.customize-border" htmlSafe=true options=@options.selectedLayerType}}
|
|
57
|
+
class="p-0 border-0 m-0 w-8 h-8 bg-transparent rounded-none"
|
|
58
|
+
/>
|
|
59
|
+
<InputGroup
|
|
60
|
+
@type="color"
|
|
61
|
+
@value={{@options.layerOptions.color}}
|
|
62
|
+
@name="{{@options.selectedLayerType}} Color"
|
|
63
|
+
@helpText={{t "modals.map-layer-form.customize-fill-color" htmlSafe=true options=@options.selectedLayerType}}
|
|
64
|
+
class="p-0 border-0 m-0 w-8 h-8 bg-transparent rounded-none"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{{#if (eq @options.selectedLayerType "Service Area")}}
|
|
69
|
+
<InputGroup @name={{t "modals.map-layer-form.service-area-country"}} @helpText={{t "modals.map-layer-form.service-area-country-text"}}>
|
|
70
|
+
<CountrySelect class="w-full form-input" @value={{@options.layerOptions.country}} @onChange={{fn (mut @options.layerOptions.country)}} />
|
|
71
|
+
</InputGroup>
|
|
72
|
+
{{else}}
|
|
73
|
+
<InputGroup @name={{t "modals.zone-form.description-zone"}} @helpText={{t "modals.zone-form.optionally"}}>
|
|
74
|
+
<Textarea @value={{@options.layerOptions.description}} rows={{3}} type="text" class="w-full form-input" placeholder={{t "modals.zone-form.description-zone"}} />
|
|
75
|
+
</InputGroup>
|
|
76
|
+
{{/if}}
|
|
77
|
+
</div>
|
|
78
|
+
</ContentPanel>
|
|
79
|
+
|
|
80
|
+
<ContentPanel @title="Geofence Triggers" @open={{true}} @wrapperClass="bordered-top">
|
|
81
|
+
<div class="space-y-4">
|
|
82
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
83
|
+
Configure when this {{if (eq @options.selectedLayerType "Zone") "zone" "service area"}} fires active geofence events. Entry and exit events are dispatched to webhooks, WebSocket channels, and the notification system whenever a driver crosses the boundary.
|
|
84
|
+
</p>
|
|
85
|
+
|
|
86
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
87
|
+
<div class="flex items-start space-x-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
88
|
+
<div class="flex-shrink-0 mt-0.5">
|
|
89
|
+
<Toggle
|
|
90
|
+
@isToggled={{@options.layerOptions.trigger_on_entry}}
|
|
91
|
+
@onToggle={{fn (mut @options.layerOptions.trigger_on_entry)}}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
<div>
|
|
95
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white">Trigger on Entry</p>
|
|
96
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
97
|
+
Fire a <code class="font-mono">geofence.entered</code> event when a driver enters this {{if (eq @options.selectedLayerType "Zone") "zone" "service area"}}.
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="flex items-start space-x-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
103
|
+
<div class="flex-shrink-0 mt-0.5">
|
|
104
|
+
<Toggle
|
|
105
|
+
@isToggled={{@options.layerOptions.trigger_on_exit}}
|
|
106
|
+
@onToggle={{fn (mut @options.layerOptions.trigger_on_exit)}}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white">Trigger on Exit</p>
|
|
111
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
112
|
+
Fire a <code class="font-mono">geofence.exited</code> event when a driver leaves this {{if (eq @options.selectedLayerType "Zone") "zone" "service area"}}.
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<InputGroup
|
|
119
|
+
@name="Dwell Threshold (minutes)"
|
|
120
|
+
@value={{@options.layerOptions.dwell_threshold_minutes}}
|
|
121
|
+
@type="number"
|
|
122
|
+
@placeholder="e.g. 10"
|
|
123
|
+
@helpText="Fire a geofence.dwelled event after a driver has been inside this boundary for this many minutes. Leave blank to disable dwell tracking."
|
|
44
124
|
/>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@type="color"
|
|
56
|
-
@value={{@options.layerOptions.color}}
|
|
57
|
-
@name="{{@options.selectedLayerType}} Color"
|
|
58
|
-
@helpText={{t "modals.map-layer-form.customize-fill-color" htmlSafe=true options=@options.selectedLayerType}}
|
|
59
|
-
class="p-0 border-0 m-0 w-8 h-8 bg-transparent rounded-none"
|
|
60
|
-
/>
|
|
61
|
-
{{#if (eq @options.selectedLayerType "Service Area")}}
|
|
62
|
-
<InputGroup @name={{t "modals.map-layer-form.service-area-country"}} @helpText={{t "modals.map-layer-form.service-area-country-text"}}>
|
|
63
|
-
<CountrySelect class="w-full form-input" @value={{@options.layerOptions.country}} @onChange={{fn (mut @options.layerOptions.country)}} />
|
|
64
|
-
</InputGroup>
|
|
65
|
-
{{/if}}
|
|
125
|
+
|
|
126
|
+
<InputGroup
|
|
127
|
+
@name="Speed Limit (km/h)"
|
|
128
|
+
@value={{@options.layerOptions.speed_limit_kmh}}
|
|
129
|
+
@type="number"
|
|
130
|
+
@placeholder="e.g. 50"
|
|
131
|
+
@helpText="Optional speed limit enforced within this boundary. Leave blank for no limit."
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
</ContentPanel>
|
|
66
135
|
</div>
|
|
67
|
-
</Modal::Default>
|
|
136
|
+
</Modal::Default>
|