@fleetbase/fleetops-engine
Version:
Fleet & Transport Management Extension for Fleetbase
275 lines (219 loc) • 8.16 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import { buildRoutePointsFromPayload, describeRoutePoint } from '../../utils/route-visualization';
import {
applyOptimizedIntermediateWaypoints,
buildRouteOptimizationInput,
createWaypointRecord,
getPayloadIntermediateWaypoints,
getPayloadRouteCoordinates,
} from '../../utils/order-route-editing';
export default class OrderRouteEditorComponent extends Component {
store;
intl;
notifications;
placeActions;
routeEngine;
routeOptimization;
route;
get payload() {
return this.args.resource.payload;
}
get routePoints() {
return buildRoutePointsFromPayload(this.payload);
}
get routeStops() {
return this.routePoints.map((routePoint) => ({
routePoint,
place: routePoint.place,
badgeStyle: this.badgeStyleForRoutePoint(routePoint),
...describeRoutePoint(routePoint, '#8B5CF6'),
}));
}
get routeSummary() {
const segments = [];
if (this.payload?.pickup) {
segments.push('Pickup');
}
if (this.intermediateWaypoints.length) {
segments.push(`${this.intermediateWaypoints.length} ${this.intermediateWaypoints.length === 1 ? 'Stop' : 'Stops'}`);
}
if (this.payload?.dropoff) {
segments.push('Dropoff');
}
if (this.payload?.return) {
segments.push('Return');
}
return segments.join(' • ');
}
get pickupStop() {
return this.routeStops.find((stop) => stop.routePoint?.role === 'pickup') ?? this.fallbackStop('pickup');
}
get dropoffStop() {
return this.routeStops.find((stop) => stop.routePoint?.role === 'dropoff') ?? this.fallbackStop('dropoff');
}
get returnStop() {
return {
label: 'R',
title: 'Return',
badgeStyle: this.badgeStyleForColor('#6B7280'),
};
}
get stopsSectionBadgeStyle() {
return this.badgeStyleForColor('#8B5CF6');
}
get intermediateRouteStops() {
return this.routeStops.filter((stop) => stop.routePoint?.role === 'waypoint');
}
get coordinates() {
return getPayloadRouteCoordinates(this.payload);
}
get intermediateWaypoints() {
return getPayloadIntermediateWaypoints(this.payload);
}
get canOptimizeRoute() {
return this.intermediateWaypoints.length >= 2;
}
badgeStyleForRoutePoint(routePoint) {
const { markerColor } = describeRoutePoint(routePoint, '#8B5CF6');
return this.badgeStyleForColor(markerColor);
}
badgeStyleForColor(color) {
const normalizedColor = color?.toLowerCase?.();
const isYellow = normalizedColor === '#facc15' || normalizedColor === '#ca8a04';
const textColor = isYellow ? '#111827' : '#ffffff';
return `background-color: ${color}; color: ${textColor};`;
}
fallbackStop(role) {
const routePoint = {
role,
stopNumber: role === 'pickup' ? null : role === 'dropoff' ? null : 1,
};
return {
badgeStyle: this.badgeStyleForRoutePoint(routePoint),
...describeRoutePoint(routePoint, '#8B5CF6'),
};
}
removeWaypoint(waypoint) {
this.payload.waypoints.removeObject(waypoint);
if (typeof this.args.onWaypointRemoved === 'function') {
this.args.onWaypointRemoved(waypoint);
}
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
addWaypoint() {
const waypoint = createWaypointRecord(this.store);
this.payload.waypoints.pushObject(waypoint);
if (typeof this.args.onWaypointAdded === 'function') {
this.args.onWaypointAdded(waypoint);
}
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
setWaypointPlace(index, place) {
const waypoints = this.intermediateWaypoints;
const waypoint = waypoints[index];
if (!waypoint) {
return;
}
const json = typeof place?.serialize === 'function' ? place.serialize() : {};
waypoint.setProperties({
uuid: waypoint.uuid ?? place.id,
place_uuid: place.id,
location: place.location,
place,
...json,
});
if (typeof this.args.onWaypointPlaceSelected === 'function') {
this.args.onWaypointPlaceSelected(place);
}
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
setPayloadPlace(prop, place) {
this.payload.set(prop, place);
if (typeof this.args.onPlaceSelected === 'function') {
this.args.onPlaceSelected(place);
}
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
clearPayloadPlace(prop) {
this.payload.set(prop, null);
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
sortWaypoints({ sourceList, sourceIndex, targetList, targetIndex }) {
if (sourceList === targetList && sourceIndex === targetIndex) return;
const item = sourceList.objectAt(sourceIndex);
sourceList.removeAt(sourceIndex);
targetList.insertAt(targetIndex, item);
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
*optimizeRouteWithService(service) {
const optimizationInput = buildRouteOptimizationInput(this.args.resource);
try {
const result = yield this.routeOptimization.optimize(service, {
context: 'edit_order_route',
...optimizationInput,
});
this.handleRouteOptimization(result);
} catch (err) {
this.notifications.error(err.message ?? this.intl.t('fleet-ops.operations.orders.index.new.route-error'));
}
}
*optimizeRoute() {
const optimizationInput = buildRouteOptimizationInput(this.args.resource);
const service = this.routeEngine.getOptimizationEngine('osrm');
try {
const result = yield this.routeOptimization.optimize(service, {
context: 'edit_order_route',
...optimizationInput,
});
this.handleRouteOptimization(result);
} catch (_err) {
this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.new.route-error'));
}
}
handleRouteOptimization({ sortedWaypoints, route, trip, result, engine = 'osrm' }) {
applyOptimizedIntermediateWaypoints(this.payload, sortedWaypoints);
if (route) {
this.setOptimizedRoute(route, trip, result?.waypoints, engine);
}
this.args.resource.set('optimized', true);
if (typeof this.args.onRouteChanged === 'function') {
this.args.onRouteChanged();
}
}
setOptimizedRoute(route, trip, waypoints, engine = 'osrm') {
const summary = {
totalDistance: trip?.distance ?? trip?.totalDistance ?? 0,
totalTime: trip?.duration ?? trip?.totalTime ?? 0,
};
const payload = {
optimized: true,
coordinates: route,
waypoints,
trip,
summary,
engine,
};
this.setRoute(payload);
}
setRoute(payload) {
const routeModel = this.store.createRecord('route', payload);
this.args.resource.route = routeModel;
this.route = payload;
}
}