@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
|
@@ -2,75 +2,127 @@ import Component from '@glimmer/component';
|
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { action } from '@ember/object';
|
|
4
4
|
import { inject as service } from '@ember/service';
|
|
5
|
-
import { isArray } from '@ember/array';
|
|
6
5
|
import { task } from 'ember-concurrency';
|
|
7
|
-
import {
|
|
6
|
+
import { buildRoutePointsFromPayload, describeRoutePoint } from '../../utils/route-visualization';
|
|
7
|
+
import {
|
|
8
|
+
applyOptimizedIntermediateWaypoints,
|
|
9
|
+
buildRouteOptimizationInput,
|
|
10
|
+
createWaypointRecord,
|
|
11
|
+
getPayloadIntermediateWaypoints,
|
|
12
|
+
getPayloadRouteCoordinates,
|
|
13
|
+
} from '../../utils/order-route-editing';
|
|
8
14
|
|
|
9
15
|
export default class OrderRouteEditorComponent extends Component {
|
|
10
16
|
@service store;
|
|
11
17
|
@service intl;
|
|
12
18
|
@service notifications;
|
|
13
19
|
@service placeActions;
|
|
20
|
+
@service routeEngine;
|
|
14
21
|
@service routeOptimization;
|
|
15
|
-
@service osrm;
|
|
16
22
|
@tracked route;
|
|
17
23
|
|
|
18
|
-
get
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
get payload() {
|
|
25
|
+
return this.args.resource.payload;
|
|
26
|
+
}
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
get routePoints() {
|
|
29
|
+
return buildRoutePointsFromPayload(this.payload);
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
get routeStops() {
|
|
33
|
+
return this.routePoints.map((routePoint) => ({
|
|
34
|
+
routePoint,
|
|
35
|
+
place: routePoint.place,
|
|
36
|
+
badgeStyle: this.badgeStyleForRoutePoint(routePoint),
|
|
37
|
+
...describeRoutePoint(routePoint, '#8B5CF6'),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (pickup) {
|
|
31
|
-
this.#addWaypointFromExistingPlace(pickup);
|
|
32
|
-
}
|
|
41
|
+
get routeSummary() {
|
|
42
|
+
const segments = [];
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
44
|
+
if (this.payload?.pickup) {
|
|
45
|
+
segments.push('Pickup');
|
|
46
|
+
}
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
dropoff: null,
|
|
42
|
-
return: null,
|
|
43
|
-
pickup_uuid: null,
|
|
44
|
-
dropoff_uuid: null,
|
|
45
|
-
return_uuid: null,
|
|
46
|
-
});
|
|
47
|
-
} else {
|
|
48
|
-
// get pickup from payload waypoints if available
|
|
49
|
-
const waypoints =
|
|
50
|
-
typeof this.args.resource.payload.waypoints.toArray === 'function' ? this.args.resource.payload.waypoints.toArray() : Array.from(this.args.resource.payload.waypoints);
|
|
51
|
-
|
|
52
|
-
if (waypoints[0]) {
|
|
53
|
-
const pickup = this.#createPlaceFromWaypoint(waypoints[0]);
|
|
54
|
-
this.args.resource.payload.set('pickup', pickup);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (waypoints[1]) {
|
|
58
|
-
const dropoff = this.#createPlaceFromWaypoint(waypoints[1]);
|
|
59
|
-
this.args.resource.payload.set('dropoff', dropoff);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.args.resource.payload.setProperties({
|
|
63
|
-
waypoints: [],
|
|
64
|
-
});
|
|
48
|
+
if (this.intermediateWaypoints.length) {
|
|
49
|
+
segments.push(`${this.intermediateWaypoints.length} ${this.intermediateWaypoints.length === 1 ? 'Stop' : 'Stops'}`);
|
|
65
50
|
}
|
|
66
51
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
52
|
+
if (this.payload?.dropoff) {
|
|
53
|
+
segments.push('Dropoff');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.payload?.return) {
|
|
57
|
+
segments.push('Return');
|
|
69
58
|
}
|
|
59
|
+
|
|
60
|
+
return segments.join(' • ');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get pickupStop() {
|
|
64
|
+
return this.routeStops.find((stop) => stop.routePoint?.role === 'pickup') ?? this.fallbackStop('pickup');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get dropoffStop() {
|
|
68
|
+
return this.routeStops.find((stop) => stop.routePoint?.role === 'dropoff') ?? this.fallbackStop('dropoff');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get returnStop() {
|
|
72
|
+
return {
|
|
73
|
+
label: 'R',
|
|
74
|
+
title: 'Return',
|
|
75
|
+
badgeStyle: this.badgeStyleForColor('#6B7280'),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get stopsSectionBadgeStyle() {
|
|
80
|
+
return this.badgeStyleForColor('#8B5CF6');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get intermediateRouteStops() {
|
|
84
|
+
return this.routeStops.filter((stop) => stop.routePoint?.role === 'waypoint');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get coordinates() {
|
|
88
|
+
return getPayloadRouteCoordinates(this.payload);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get intermediateWaypoints() {
|
|
92
|
+
return getPayloadIntermediateWaypoints(this.payload);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get canOptimizeRoute() {
|
|
96
|
+
return this.intermediateWaypoints.length >= 2;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
badgeStyleForRoutePoint(routePoint) {
|
|
100
|
+
const { markerColor } = describeRoutePoint(routePoint, '#8B5CF6');
|
|
101
|
+
return this.badgeStyleForColor(markerColor);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
badgeStyleForColor(color) {
|
|
105
|
+
const normalizedColor = color?.toLowerCase?.();
|
|
106
|
+
const isYellow = normalizedColor === '#facc15' || normalizedColor === '#ca8a04';
|
|
107
|
+
const textColor = isYellow ? '#111827' : '#ffffff';
|
|
108
|
+
|
|
109
|
+
return `background-color: ${color}; color: ${textColor};`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fallbackStop(role) {
|
|
113
|
+
const routePoint = {
|
|
114
|
+
role,
|
|
115
|
+
stopNumber: role === 'pickup' ? null : role === 'dropoff' ? null : 1,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
badgeStyle: this.badgeStyleForRoutePoint(routePoint),
|
|
120
|
+
...describeRoutePoint(routePoint, '#8B5CF6'),
|
|
121
|
+
};
|
|
70
122
|
}
|
|
71
123
|
|
|
72
124
|
@action removeWaypoint(waypoint) {
|
|
73
|
-
this.
|
|
125
|
+
this.payload.waypoints.removeObject(waypoint);
|
|
74
126
|
|
|
75
127
|
if (typeof this.args.onWaypointRemoved === 'function') {
|
|
76
128
|
this.args.onWaypointRemoved(waypoint);
|
|
@@ -82,23 +134,29 @@ export default class OrderRouteEditorComponent extends Component {
|
|
|
82
134
|
}
|
|
83
135
|
|
|
84
136
|
@action addWaypoint() {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const waypoint = this.store.createRecord('waypoint', { place, location });
|
|
88
|
-
|
|
89
|
-
this.args.resource.payload.waypoints.pushObject(waypoint);
|
|
137
|
+
const waypoint = createWaypointRecord(this.store);
|
|
138
|
+
this.payload.waypoints.pushObject(waypoint);
|
|
90
139
|
|
|
91
140
|
if (typeof this.args.onWaypointAdded === 'function') {
|
|
92
141
|
this.args.onWaypointAdded(waypoint);
|
|
93
142
|
}
|
|
143
|
+
|
|
144
|
+
if (typeof this.args.onRouteChanged === 'function') {
|
|
145
|
+
this.args.onRouteChanged();
|
|
146
|
+
}
|
|
94
147
|
}
|
|
95
148
|
|
|
96
149
|
@action setWaypointPlace(index, place) {
|
|
97
|
-
|
|
150
|
+
const waypoints = this.intermediateWaypoints;
|
|
151
|
+
const waypoint = waypoints[index];
|
|
98
152
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
153
|
+
if (!waypoint) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const json = typeof place?.serialize === 'function' ? place.serialize() : {};
|
|
158
|
+
waypoint.setProperties({
|
|
159
|
+
uuid: waypoint.uuid ?? place.id,
|
|
102
160
|
place_uuid: place.id,
|
|
103
161
|
location: place.location,
|
|
104
162
|
place,
|
|
@@ -115,7 +173,7 @@ export default class OrderRouteEditorComponent extends Component {
|
|
|
115
173
|
}
|
|
116
174
|
|
|
117
175
|
@action setPayloadPlace(prop, place) {
|
|
118
|
-
this.
|
|
176
|
+
this.payload.set(prop, place);
|
|
119
177
|
|
|
120
178
|
if (typeof this.args.onPlaceSelected === 'function') {
|
|
121
179
|
this.args.onPlaceSelected(place);
|
|
@@ -126,6 +184,14 @@ export default class OrderRouteEditorComponent extends Component {
|
|
|
126
184
|
}
|
|
127
185
|
}
|
|
128
186
|
|
|
187
|
+
@action clearPayloadPlace(prop) {
|
|
188
|
+
this.payload.set(prop, null);
|
|
189
|
+
|
|
190
|
+
if (typeof this.args.onRouteChanged === 'function') {
|
|
191
|
+
this.args.onRouteChanged();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
129
195
|
@action sortWaypoints({ sourceList, sourceIndex, targetList, targetIndex }) {
|
|
130
196
|
if (sourceList === targetList && sourceIndex === targetIndex) return;
|
|
131
197
|
|
|
@@ -140,18 +206,12 @@ export default class OrderRouteEditorComponent extends Component {
|
|
|
140
206
|
}
|
|
141
207
|
|
|
142
208
|
@task *optimizeRouteWithService(service) {
|
|
143
|
-
const
|
|
144
|
-
const payload = order.payload;
|
|
145
|
-
const waypoints = this.args.resource.payload.waypoints;
|
|
146
|
-
const coordinates = this.coordinates.map((coord) => coord.reverse());
|
|
209
|
+
const optimizationInput = buildRouteOptimizationInput(this.args.resource);
|
|
147
210
|
|
|
148
211
|
try {
|
|
149
212
|
const result = yield this.routeOptimization.optimize(service, {
|
|
150
213
|
context: 'edit_order_route',
|
|
151
|
-
|
|
152
|
-
payload,
|
|
153
|
-
waypoints,
|
|
154
|
-
coordinates,
|
|
214
|
+
...optimizationInput,
|
|
155
215
|
});
|
|
156
216
|
this.handleRouteOptimization(result);
|
|
157
217
|
} catch (err) {
|
|
@@ -160,39 +220,41 @@ export default class OrderRouteEditorComponent extends Component {
|
|
|
160
220
|
}
|
|
161
221
|
|
|
162
222
|
@task *optimizeRoute() {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const waypoints = this.args.resource.payload.waypoints;
|
|
166
|
-
const coordinates = this.coordinates.map((coord) => coord.reverse());
|
|
223
|
+
const optimizationInput = buildRouteOptimizationInput(this.args.resource);
|
|
224
|
+
const service = this.routeEngine.getOptimizationEngine('osrm');
|
|
167
225
|
|
|
168
226
|
try {
|
|
169
|
-
const result = yield this.
|
|
227
|
+
const result = yield this.routeOptimization.optimize(service, {
|
|
170
228
|
context: 'edit_order_route',
|
|
171
|
-
|
|
172
|
-
payload,
|
|
173
|
-
waypoints,
|
|
174
|
-
coordinates,
|
|
229
|
+
...optimizationInput,
|
|
175
230
|
});
|
|
176
231
|
|
|
177
232
|
this.handleRouteOptimization(result);
|
|
178
|
-
} catch (
|
|
233
|
+
} catch (_err) {
|
|
179
234
|
this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.new.route-error'));
|
|
180
235
|
}
|
|
181
236
|
}
|
|
182
237
|
|
|
183
238
|
@action handleRouteOptimization({ sortedWaypoints, route, trip, result, engine = 'osrm' }) {
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
applyOptimizedIntermediateWaypoints(this.payload, sortedWaypoints);
|
|
240
|
+
|
|
186
241
|
if (route) {
|
|
187
|
-
this.setOptimizedRoute(route, trip, result
|
|
242
|
+
this.setOptimizedRoute(route, trip, result?.waypoints, engine);
|
|
188
243
|
}
|
|
189
|
-
|
|
244
|
+
|
|
190
245
|
this.args.resource.set('optimized', true);
|
|
246
|
+
|
|
247
|
+
if (typeof this.args.onRouteChanged === 'function') {
|
|
248
|
+
this.args.onRouteChanged();
|
|
249
|
+
}
|
|
191
250
|
}
|
|
192
251
|
|
|
193
252
|
@action setOptimizedRoute(route, trip, waypoints, engine = 'osrm') {
|
|
194
|
-
|
|
195
|
-
|
|
253
|
+
const summary = {
|
|
254
|
+
totalDistance: trip?.distance ?? trip?.totalDistance ?? 0,
|
|
255
|
+
totalTime: trip?.duration ?? trip?.totalTime ?? 0,
|
|
256
|
+
};
|
|
257
|
+
const payload = {
|
|
196
258
|
optimized: true,
|
|
197
259
|
coordinates: route,
|
|
198
260
|
waypoints,
|
|
@@ -209,25 +271,4 @@ export default class OrderRouteEditorComponent extends Component {
|
|
|
209
271
|
this.args.resource.route = routeModel;
|
|
210
272
|
this.route = payload;
|
|
211
273
|
}
|
|
212
|
-
|
|
213
|
-
#createPlaceFromWaypoint(waypoint) {
|
|
214
|
-
const json = waypoint.serialize();
|
|
215
|
-
return this.store.createRecord('place', json);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
#addWaypointFromExistingPlace(place) {
|
|
219
|
-
const json = place.serialize();
|
|
220
|
-
const waypoint = this.store.createRecord('waypoint', {
|
|
221
|
-
uuid: place.id,
|
|
222
|
-
place_uuid: place.id,
|
|
223
|
-
location: place.location,
|
|
224
|
-
place,
|
|
225
|
-
...json,
|
|
226
|
-
});
|
|
227
|
-
this.args.resource.payload.waypoints.pushObject(waypoint);
|
|
228
|
-
|
|
229
|
-
if (typeof this.args.onWaypointAdded === 'function') {
|
|
230
|
-
this.args.onWaypointAdded(waypoint);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
274
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{{this.addressHtml}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import placeAddressHtml from '../../utils/place-address-html';
|
|
3
|
+
|
|
4
|
+
export default class PlaceAddressComponent extends Component {
|
|
5
|
+
get place() {
|
|
6
|
+
return this.args.resource ?? this.args.place;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get addressHtml() {
|
|
10
|
+
return placeAddressHtml(this.place);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,70 +1,58 @@
|
|
|
1
1
|
<div class="order-route-list" ...attributes>
|
|
2
|
-
{{#if
|
|
3
|
-
{{
|
|
4
|
-
<div class="order-route-stop {{@
|
|
5
|
-
<div class="
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
<DisplayPlace @place={{@order.payload.firstWaypoint}} @eta={{get @eta @order.payload.firstWaypoint.id}} @waypointActions={{@waypointActions}} />
|
|
10
|
-
</div>
|
|
2
|
+
{{#if this.firstStop}}
|
|
3
|
+
<div class="order-route-stop {{@routeStopClass}}">
|
|
4
|
+
<div class="order-route-stop-index {{@routeStopIndexClass}}">
|
|
5
|
+
<div class="index-count fleetops-route-stop-badge" style={{this.firstStop.badgeStyle}}>{{this.firstStop.label}}</div>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
8
|
+
<DisplayPlace @place={{this.firstStop.place}} @eta={{get @eta this.firstStop.place.id}} @waypointActions={{@waypointActions}} />
|
|
11
9
|
</div>
|
|
12
|
-
|
|
10
|
+
</div>
|
|
11
|
+
{{/if}}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
{{#if this.shouldCollapseWaypoints}}
|
|
14
|
+
<Button class="btn-reset w-full" @wrapperClass="w-full" @onClick={{this.toggleWaypointsCollapse}}>
|
|
15
|
+
<div class="px-3 py-2 w-full bg-yellow-200 text-yellow-900 rounded shadow-sm flex flex-col justify-center items-start mb-3">
|
|
16
|
+
<div class="flex flex-row items-center">
|
|
17
|
+
<FaIcon @icon="eye" class="mr-2 text-yellow-900" />
|
|
18
|
+
<span class="font-bold">{{if this.isWaypointsCollapsed (t "route-list.expand") (t "route-list.collapse")}}</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div>{{this.middleStops.length}} {{t "route-list.more-waypoints"}}</div>
|
|
21
|
+
</div>
|
|
22
|
+
</Button>
|
|
23
|
+
{{#unless this.isWaypointsCollapsed}}
|
|
24
|
+
{{#each this.middleStops as |stop|}}
|
|
25
|
+
<div class="order-route-stop {{@routeStopClass}}">
|
|
26
|
+
<div class="order-route-stop-index {{@routeStopIndexClass}}">
|
|
27
|
+
<div class="index-count fleetops-route-stop-badge" style={{stop.badgeStyle}}>{{stop.label}}</div>
|
|
20
28
|
</div>
|
|
21
|
-
<div
|
|
22
|
-
|
|
23
|
-
</Button>
|
|
24
|
-
{{#unless this.isWaypointsCollapsed}}
|
|
25
|
-
{{#each @order.payload.middleWaypoints as |waypoint index|}}
|
|
26
|
-
<div class="order-route-stop {{@routeStopClass}}">
|
|
27
|
-
<div class="order-route-stop-index {{@routeStopIndexClass}}">
|
|
28
|
-
<div class="index-count">{{add index 2}}</div>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
31
|
-
<DisplayPlace @place={{waypoint}} @eta={{get @eta waypoint.id}} @waypointActions={{@waypointActions}} />
|
|
32
|
-
</div>
|
|
29
|
+
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
30
|
+
<DisplayPlace @place={{stop.place}} @eta={{get @eta stop.place.id}} @waypointActions={{@waypointActions}} />
|
|
33
31
|
</div>
|
|
34
|
-
{{/each}}
|
|
35
|
-
{{/unless}}
|
|
36
|
-
{{/if}}
|
|
37
|
-
|
|
38
|
-
{{#if @order.payload.lastWaypoint}}
|
|
39
|
-
<div class="order-route-stop {{@routeStopClass}}">
|
|
40
|
-
<div class="order-route-stop-index {{@routeStopIndexClass}}">
|
|
41
|
-
<div class="index-count">{{add @order.payload.middleWaypoints.length 2}}</div>
|
|
42
32
|
</div>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{{/if}}
|
|
48
|
-
{{else}}
|
|
49
|
-
{{#if @order.payload.pickup}}
|
|
33
|
+
{{/each}}
|
|
34
|
+
{{/unless}}
|
|
35
|
+
{{else if this.hasExtraStops}}
|
|
36
|
+
{{#each this.middleStops as |stop|}}
|
|
50
37
|
<div class="order-route-stop {{@routeStopClass}}">
|
|
51
38
|
<div class="order-route-stop-index {{@routeStopIndexClass}}">
|
|
52
|
-
<div class="index-count">
|
|
39
|
+
<div class="index-count fleetops-route-stop-badge" style={{stop.badgeStyle}}>{{stop.label}}</div>
|
|
53
40
|
</div>
|
|
54
41
|
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
55
|
-
<DisplayPlace @place={{
|
|
42
|
+
<DisplayPlace @place={{stop.place}} @eta={{get @eta stop.place.id}} @waypointActions={{@waypointActions}} />
|
|
56
43
|
</div>
|
|
57
44
|
</div>
|
|
58
|
-
{{/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<div class="
|
|
65
|
-
|
|
66
|
-
|
|
45
|
+
{{/each}}
|
|
46
|
+
{{/if}}
|
|
47
|
+
|
|
48
|
+
{{#if this.lastStop}}
|
|
49
|
+
<div class="order-route-stop {{@routeStopClass}}">
|
|
50
|
+
<div class="order-route-stop-index {{@routeStopIndexClass}}">
|
|
51
|
+
<div class="index-count fleetops-route-stop-badge" style={{this.lastStop.badgeStyle}}>{{this.lastStop.label}}</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
54
|
+
<DisplayPlace @place={{this.lastStop.place}} @eta={{get @eta this.lastStop.place.id}} @waypointActions={{@waypointActions}} />
|
|
67
55
|
</div>
|
|
68
|
-
|
|
56
|
+
</div>
|
|
69
57
|
{{/if}}
|
|
70
58
|
</div>
|
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { action } from '@ember/object';
|
|
4
|
+
import { buildRoutePointsFromPayload, describeRoutePoint } from '../utils/route-visualization';
|
|
5
|
+
import { colorForId } from '../utils/route-colors';
|
|
4
6
|
|
|
5
7
|
export default class RouteListComponent extends Component {
|
|
6
8
|
@tracked isWaypointsCollapsed = true;
|
|
7
9
|
|
|
10
|
+
get routeColor() {
|
|
11
|
+
const order = this.args.order;
|
|
12
|
+
|
|
13
|
+
return colorForId(order?.public_id ?? order?.id ?? 'order-route');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get routeStops() {
|
|
17
|
+
const routePoints = buildRoutePointsFromPayload(this.args.order?.payload);
|
|
18
|
+
|
|
19
|
+
return routePoints.map((routePoint) => ({
|
|
20
|
+
routePoint,
|
|
21
|
+
place: routePoint.place,
|
|
22
|
+
badgeStyle: this.badgeStyleForStop(routePoint),
|
|
23
|
+
...describeRoutePoint(routePoint, this.routeColor),
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get firstStop() {
|
|
28
|
+
return this.routeStops[0] ?? null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get middleStops() {
|
|
32
|
+
return this.routeStops.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get lastStop() {
|
|
36
|
+
return this.routeStops.length > 1 ? this.routeStops.at(-1) : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get hasExtraStops() {
|
|
40
|
+
return this.routeStops.length > 2;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get shouldCollapseWaypoints() {
|
|
44
|
+
return this.args.isCollapsible !== false && this.hasExtraStops;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
badgeStyleForStop(routePoint) {
|
|
48
|
+
const { markerColor } = describeRoutePoint(routePoint, this.routeColor);
|
|
49
|
+
const isYellow = markerColor?.toLowerCase?.() === '#facc15' || markerColor?.toLowerCase?.() === '#ca8a04';
|
|
50
|
+
const textColor = isYellow ? '#111827' : '#ffffff';
|
|
51
|
+
|
|
52
|
+
return `background-color: ${markerColor}; color: ${textColor};`;
|
|
53
|
+
}
|
|
54
|
+
|
|
8
55
|
@action toggleWaypointsCollapse() {
|
|
9
56
|
this.isWaypointsCollapsed = !this.isWaypointsCollapsed;
|
|
10
57
|
}
|
|
@@ -6,7 +6,7 @@ import contextComponentCallback from '@fleetbase/ember-core/utils/context-compon
|
|
|
6
6
|
import applyContextComponentArguments from '@fleetbase/ember-core/utils/apply-context-component-arguments';
|
|
7
7
|
|
|
8
8
|
export default class RouteOptimizationWizardPanelComponent extends Component {
|
|
9
|
-
@service
|
|
9
|
+
@service sidebar;
|
|
10
10
|
@tracked context;
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -27,9 +27,8 @@ export default class RouteOptimizationWizardPanelComponent extends Component {
|
|
|
27
27
|
@action setOverlayContext(overlayContext) {
|
|
28
28
|
this.context = overlayContext;
|
|
29
29
|
// hide sidebar
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
30
|
+
this.sidebar.hide();
|
|
31
|
+
|
|
33
32
|
contextComponentCallback(this, 'onLoad', ...arguments);
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<Layout::Resource::Card ...attributes as |Card|>
|
|
2
|
-
<Card.header class="{{@headerClass}}
|
|
3
|
-
<div class="font-semibold">{{or @resource.name @resource.yearMakeModel}}</div>
|
|
4
|
-
<div class="text-gray-300 dark:text-gray-500 text-sm">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
|
|
2
|
+
<Card.header class="{{@headerClass}}">
|
|
3
|
+
<div class="font-semibold w-full truncate">{{or @resource.name @resource.yearMakeModel}}</div>
|
|
4
|
+
<div class="text-gray-300 dark:text-gray-500 text-sm w-full truncate">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
|
|
5
5
|
{{#if (has-block "header")}}
|
|
6
6
|
{{yield to="header"}}
|
|
7
7
|
{{/if}}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
{{#if (has-block "footer")}}
|
|
22
22
|
{{yield to="footer"}}
|
|
23
23
|
{{/if}}
|
|
24
|
-
<div class="mt-1">
|
|
24
|
+
<div class="mt-1.5">
|
|
25
25
|
<div class="text-gray-300 dark:text-gray-500 text-xs">Last Modified: {{@resource.updatedAt}}</div>
|
|
26
26
|
</div>
|
|
27
27
|
</Card.footer>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
|
+
import { isArray } from '@ember/array';
|
|
2
3
|
import { inject as service } from '@ember/service';
|
|
3
4
|
import { tracked } from '@glimmer/tracking';
|
|
4
5
|
import { action } from '@ember/object';
|
|
@@ -75,7 +76,7 @@ export default class MaintenanceSchedulesIndexController extends Controller {
|
|
|
75
76
|
})
|
|
76
77
|
.then((response) => {
|
|
77
78
|
// The API returns { events: [...] } — unwrap the array.
|
|
78
|
-
const raw =
|
|
79
|
+
const raw = isArray(response) ? response : (response?.events ?? []);
|
|
79
80
|
const events = raw.map((event) => ({
|
|
80
81
|
id: event.id,
|
|
81
82
|
title: event.title,
|