@fleetbase/fleetops-engine 0.4.2 → 0.4.4
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/edit-order-route-panel.hbs +182 -0
- package/addon/components/edit-order-route-panel.js +265 -0
- package/addon/components/fleet-panel.js +3 -3
- package/addon/components/map-container/toolbar.hbs +2 -2
- package/addon/components/modals/order-route-form.hbs +1 -1
- package/addon/controllers/operations/orders/index/new.js +73 -36
- package/addon/controllers/operations/orders/index/view.js +131 -126
- package/addon/routes/operations/orders/index/view.js +1 -1
- package/addon/services/context-panel.js +6 -0
- package/addon/styles/fleetops-engine.css +5 -0
- package/addon/templates/operations/orders/index/new.hbs +390 -58
- package/addon/templates/operations/orders/index/view.hbs +135 -105
- package/addon/templates/operations/service-rates/index/new.hbs +3 -1
- package/composer.json +2 -2
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/src/Http/Controllers/Api/v1/DriverController.php +59 -20
- package/server/src/Http/Controllers/Api/v1/OrderController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +16 -3
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +11 -0
- package/server/src/Http/Resources/v1/Order.php +3 -0
- package/server/src/Http/Resources/v1/PurchaseRate.php +2 -1
- package/server/src/Http/Resources/v1/ServiceQuote.php +1 -0
- package/server/src/Http/Resources/v1/ServiceQuoteItem.php +29 -0
- package/server/src/Models/Driver.php +1 -1
- package/server/src/Models/Order.php +16 -0
- package/server/src/Models/Place.php +2 -2
- package/server/src/Observers/DriverObserver.php +6 -1
- package/server/src/routes.php +1 -0
- package/translations/en-us.yaml +9 -2
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<Overlay
|
|
2
|
+
@onLoad={{this.setOverlayContext}}
|
|
3
|
+
@onOpen={{this.onOpen}}
|
|
4
|
+
@onClose={{this.onClose}}
|
|
5
|
+
@onToggle={{this.onToggle}}
|
|
6
|
+
@position="right"
|
|
7
|
+
@noBackdrop={{true}}
|
|
8
|
+
@fullHeight={{true}}
|
|
9
|
+
@isResizeble={{or this.isResizable @isResizable}}
|
|
10
|
+
@width={{or this.width @width "570px"}}
|
|
11
|
+
>
|
|
12
|
+
<Overlay::Header @title={{concat "Edit order " this.order.public_id " route"}}>
|
|
13
|
+
<Button @type="primary" @icon="check" @text="Save" @helpText="Save order route changes" @onClick={{this.onSave}} @wrapperClass="mr-2" />
|
|
14
|
+
<Button @type="default" @icon="times" @helpText={{t "fleet-ops.common.cancel"}} @onClick={{this.onPressCancel}} />
|
|
15
|
+
</Overlay::Header>
|
|
16
|
+
|
|
17
|
+
<Overlay::Body @increaseInnerBodyHeightBy={{1000}}>
|
|
18
|
+
<div class="p-4">
|
|
19
|
+
<div class="flex flex-row justify-between mb-6">
|
|
20
|
+
<div class="flex">
|
|
21
|
+
<Toggle @isToggled={{this.order.payload.isMultiDrop}} @onToggle={{this.toggleMultiDropOrder}} @label={{t "fleet-ops.component.modals.order-route-form.toggle"}} />
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex flex-1 justify-end space-x-2">
|
|
24
|
+
{{#if this.order.payload.isMultiDrop}}
|
|
25
|
+
{{!-- <Button @type="magic" @icon="magic" @text={{t "fleet-ops.component.modals.order-route-form.optimize"}} @size="sm" @onClick={{fn this.optimizeRoute}} @helpText={{t "fleet-ops.component.modals.order-route-form.optimize-text"}} @disabled={{lt this.order.payload.waypoints.length 3}} @isLoading={{this.isOptimizingRoute}} /> --}}
|
|
26
|
+
<Button
|
|
27
|
+
@icon="map-marked-alt"
|
|
28
|
+
@text={{t "fleet-ops.component.modals.order-route-form.add"}}
|
|
29
|
+
@size="sm"
|
|
30
|
+
@onClick={{this.addWaypoint}}
|
|
31
|
+
@helpText={{t "fleet-ops.component.modals.order-route-form.add-text"}}
|
|
32
|
+
/>
|
|
33
|
+
{{/if}}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{{#if this.order.payload.isMultiDrop}}
|
|
38
|
+
<DragSortList class="overflow-visible" @items={{this.order.payload.waypoints}} @dragEndAction={{this.sortWaypoints}} as |waypoint index|>
|
|
39
|
+
<div id={{concat "waypoint_" (add index 1)}} class="py-1 mb-3">
|
|
40
|
+
<div class="flex items-center justify-between">
|
|
41
|
+
<div class="flex items-center justify-between cursor-move">
|
|
42
|
+
<FaIcon @icon="grip-lines" @prefix="fas" class="mr-3 text-gray-100" />
|
|
43
|
+
<label class="waypoint-label dark:text-gray-100 text-sm">
|
|
44
|
+
{{waypoint-label (add index 1)}}
|
|
45
|
+
</label>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="flex-1 px-4">
|
|
49
|
+
<ModelSelect
|
|
50
|
+
@modelName="place"
|
|
51
|
+
@selectedModel={{waypoint}}
|
|
52
|
+
@placeholder={{concat (t "fleet-ops.operations.orders.index.new.select-waypoint-placeholder") " " (add index 1)}}
|
|
53
|
+
@triggerClass="form-select form-input truncate max-w-300px"
|
|
54
|
+
@infiniteScroll={{false}}
|
|
55
|
+
@customSearchEndpoint="places/search"
|
|
56
|
+
@query={{hash geo=true latitude=this.latitude longitude=this.longitude}}
|
|
57
|
+
@renderInPlace={{true}}
|
|
58
|
+
@onChange={{fn this.setWaypointPlace index}}
|
|
59
|
+
as |model|
|
|
60
|
+
>
|
|
61
|
+
{{model.address}}
|
|
62
|
+
</ModelSelect>
|
|
63
|
+
|
|
64
|
+
{{#unless waypoint.isNew}}
|
|
65
|
+
{{#if waypoint.hasInvalidCoordinates}}
|
|
66
|
+
<div class="leading-5 text-sm text-red-400 mt-2">
|
|
67
|
+
<FaIcon @icon="exclamation-triangle" class="mr-1" />
|
|
68
|
+
{{t "fleet-ops.component.modals.order-route-form.invalid"}}
|
|
69
|
+
</div>
|
|
70
|
+
{{/if}}
|
|
71
|
+
{{/unless}}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="flex items-center">
|
|
75
|
+
<Button class="mr-2" @icon="edit" @size="sm" @onClick={{fn this.editPlace waypoint}} />
|
|
76
|
+
<Button @type="danger" @icon="trash" @iconPrefix="fas" @size="sm" @onClick={{fn this.removeWaypoint waypoint}} />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</DragSortList>
|
|
81
|
+
{{else}}
|
|
82
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs dark:text-gray-100">
|
|
83
|
+
<div class="input-group">
|
|
84
|
+
<div class="flex items-center justify-between">
|
|
85
|
+
<label>{{t "fleet-ops.component.modals.order-route-form.pickup"}}</label>
|
|
86
|
+
|
|
87
|
+
{{#if this.order.payload.pickup}}
|
|
88
|
+
<a href="javascript:;" {{on "click" (fn this.editPlace this.order.payload.pickup)}}>{{t "fleet-ops.component.modals.order-route-form.edit-address"}}</a>
|
|
89
|
+
{{/if}}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<ModelSelect
|
|
93
|
+
@modelName="place"
|
|
94
|
+
@selectedModel={{this.order.payload.pickup}}
|
|
95
|
+
@placeholder={{t "fleet-ops.component.modals.order-route-form.select-pickup"}}
|
|
96
|
+
@triggerClass="form-select form-input"
|
|
97
|
+
@infiniteScroll={{false}}
|
|
98
|
+
@customSearchEndpoint="places/search"
|
|
99
|
+
@query={{hash geo=true latitude=this.latitude longitude=this.longitude}}
|
|
100
|
+
@renderInPlace={{true}}
|
|
101
|
+
@onChange={{fn this.setPayloadPlace "pickup"}}
|
|
102
|
+
as |model|
|
|
103
|
+
>
|
|
104
|
+
{{model.address}}
|
|
105
|
+
</ModelSelect>
|
|
106
|
+
|
|
107
|
+
{{#if this.order.payload.pickup.hasInvalidCoordinates}}
|
|
108
|
+
<div class="leading-5 text-sm text-red-400 mt-2">
|
|
109
|
+
<FaIcon @icon="exclamation-triangle" class="mr-1" />
|
|
110
|
+
{{t "fleet-ops.component.modals.order-route-form.invalid"}}
|
|
111
|
+
</div>
|
|
112
|
+
{{/if}}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="input-group">
|
|
116
|
+
<div class="flex items-center justify-between">
|
|
117
|
+
<label>{{t "fleet-ops.component.modals.order-route-form.dropoff"}}</label>
|
|
118
|
+
|
|
119
|
+
{{#if this.order.payload.dropoff}}
|
|
120
|
+
<a href="javascript:;" {{on "click" (fn this.editPlace this.order.payload.dropoff)}}>{{t "fleet-ops.component.modals.order-route-form.edit-address"}}</a>
|
|
121
|
+
{{/if}}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<ModelSelect
|
|
125
|
+
@modelName="place"
|
|
126
|
+
@selectedModel={{this.order.payload.dropoff}}
|
|
127
|
+
@placeholder={{t "fleet-ops.component.modals.order-route-form.select-dropoff"}}
|
|
128
|
+
@triggerClass="form-select form-input"
|
|
129
|
+
@infiniteScroll={{false}}
|
|
130
|
+
@customSearchEndpoint="places/search"
|
|
131
|
+
@query={{hash geo=true latitude=this.latitude longitude=this.longitude}}
|
|
132
|
+
@renderInPlace={{true}}
|
|
133
|
+
@onChange={{fn this.setPayloadPlace "dropoff"}}
|
|
134
|
+
as |model|
|
|
135
|
+
>
|
|
136
|
+
{{model.address}}
|
|
137
|
+
</ModelSelect>
|
|
138
|
+
|
|
139
|
+
{{#if this.order.payload.dropoff.hasInvalidCoordinates}}
|
|
140
|
+
<div class="leading-5 text-sm text-red-400 mt-2">
|
|
141
|
+
<FaIcon @icon="exclamation-triangle" class="mr-1" />
|
|
142
|
+
{{t "fleet-ops.component.modals.order-route-form.invalid"}}
|
|
143
|
+
</div>
|
|
144
|
+
{{/if}}
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div class="input-group">
|
|
148
|
+
<div class="flex items-center justify-between">
|
|
149
|
+
<label>{{t "fleet-ops.component.modals.order-route-form.return"}}</label>
|
|
150
|
+
|
|
151
|
+
{{#if this.order.payload.return}}
|
|
152
|
+
<a href="javascript:;" {{on "click" (fn this.editPlace this.order.payload.return)}}>{{t "fleet-ops.component.modals.order-route-form.edit-address"}}</a>
|
|
153
|
+
{{/if}}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<ModelSelect
|
|
157
|
+
@modelName="place"
|
|
158
|
+
@selectedModel={{this.order.payload.return}}
|
|
159
|
+
@placeholder={{t "fleet-ops.component.modals.order-route-form.select-return"}}
|
|
160
|
+
@triggerClass="form-select form-input"
|
|
161
|
+
@infiniteScroll={{false}}
|
|
162
|
+
@customSearchEndpoint="places/search"
|
|
163
|
+
@query={{hash geo=true latitude=this.latitude longitude=this.longitude}}
|
|
164
|
+
@renderInPlace={{true}}
|
|
165
|
+
@onChange={{fn this.setPayloadPlace "return"}}
|
|
166
|
+
as |model|
|
|
167
|
+
>
|
|
168
|
+
{{model.address}}
|
|
169
|
+
</ModelSelect>
|
|
170
|
+
|
|
171
|
+
{{#if this.order.payload.return.hasInvalidCoordinates}}
|
|
172
|
+
<div class="leading-5 text-sm text-red-400 mt-2">
|
|
173
|
+
<FaIcon @icon="exclamation-triangle" class="mr-1" />
|
|
174
|
+
{{t "fleet-ops.component.modals.order-route-form.invalid"}}
|
|
175
|
+
</div>
|
|
176
|
+
{{/if}}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
{{/if}}
|
|
180
|
+
</div>
|
|
181
|
+
</Overlay::Body>
|
|
182
|
+
</Overlay>
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
5
|
+
import { isArray } from '@ember/array';
|
|
6
|
+
import contextComponentCallback from '../utils/context-component-callback';
|
|
7
|
+
import applyContextComponentArguments from '../utils/apply-context-component-arguments';
|
|
8
|
+
import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
|
|
9
|
+
import Point from '@fleetbase/fleetops-data/utils/geojson/point';
|
|
10
|
+
import findClosestWaypoint from '@fleetbase/ember-core/utils/find-closest-waypoint';
|
|
11
|
+
|
|
12
|
+
export default class EditOrderRoutePanelComponent extends Component {
|
|
13
|
+
/**
|
|
14
|
+
* Fetch service.
|
|
15
|
+
*
|
|
16
|
+
* @type {Service}
|
|
17
|
+
*/
|
|
18
|
+
@service fetch;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Ember data store service.
|
|
22
|
+
*
|
|
23
|
+
* @type {Service}
|
|
24
|
+
*/
|
|
25
|
+
@service store;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Service for managing routing within the host app.
|
|
29
|
+
*
|
|
30
|
+
* @type {Service}
|
|
31
|
+
*/
|
|
32
|
+
@service hostRouter;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Service for managing the modals.
|
|
36
|
+
*
|
|
37
|
+
* @type {Service}
|
|
38
|
+
*/
|
|
39
|
+
@service modalsManager;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Service for managing the context panel.
|
|
43
|
+
*
|
|
44
|
+
* @type {Service}
|
|
45
|
+
*/
|
|
46
|
+
@service contextPanel;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Service for internationalization.
|
|
50
|
+
*
|
|
51
|
+
* @type {Service}
|
|
52
|
+
*/
|
|
53
|
+
@service intl;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Service for notifications
|
|
57
|
+
*
|
|
58
|
+
* @type {Service}
|
|
59
|
+
*/
|
|
60
|
+
@service notifications;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The orderwhich route is being edited.
|
|
64
|
+
*
|
|
65
|
+
* @type {OrderModel}
|
|
66
|
+
* @tracked
|
|
67
|
+
*/
|
|
68
|
+
@tracked order;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Initializes the vehicle panel component.
|
|
72
|
+
*/
|
|
73
|
+
constructor() {
|
|
74
|
+
super(...arguments);
|
|
75
|
+
this.order = this.args.order;
|
|
76
|
+
applyContextComponentArguments(this);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sets the overlay context.
|
|
81
|
+
*
|
|
82
|
+
* @action
|
|
83
|
+
* @param {OverlayContextObject} overlayContext
|
|
84
|
+
*/
|
|
85
|
+
@action setOverlayContext(overlayContext) {
|
|
86
|
+
this.context = overlayContext;
|
|
87
|
+
contextComponentCallback(this, 'onLoad', ...arguments);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Handles save action for the order route.
|
|
92
|
+
*
|
|
93
|
+
* @method
|
|
94
|
+
* @action
|
|
95
|
+
*/
|
|
96
|
+
@action onSave() {
|
|
97
|
+
const isActionOverrided = contextComponentCallback(this, 'onSave', this.order);
|
|
98
|
+
|
|
99
|
+
if (!isActionOverrided) {
|
|
100
|
+
return this.order
|
|
101
|
+
.save()
|
|
102
|
+
.then((order) => {
|
|
103
|
+
this.notifications.success(this.intl.t('fleet-ops.operations.orders.index.view.update-success', { orderId: order.public_id }));
|
|
104
|
+
contextComponentCallback(this, 'onAfterSave', order);
|
|
105
|
+
})
|
|
106
|
+
.catch((error) => {
|
|
107
|
+
this.notifications.serverError(error);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handles the cancel action.
|
|
114
|
+
*
|
|
115
|
+
* @method
|
|
116
|
+
* @action
|
|
117
|
+
* @returns {Boolean} Indicates whether the cancel action was overridden.
|
|
118
|
+
*/
|
|
119
|
+
@action onPressCancel() {
|
|
120
|
+
return contextComponentCallback(this, 'onPressCancel', this.order);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@action async editPlace(place) {
|
|
124
|
+
await this.modalsManager.done();
|
|
125
|
+
|
|
126
|
+
this.contextPanel.focus(place, 'editing', {
|
|
127
|
+
args: {
|
|
128
|
+
onClose: () => {
|
|
129
|
+
this.editOrderRoute(this.order);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@action toggleMultiDropOrder(isMultiDrop) {
|
|
136
|
+
const { pickup, dropoff } = this.order.payload;
|
|
137
|
+
|
|
138
|
+
if (isMultiDrop) {
|
|
139
|
+
// if pickup move it to multipdrop
|
|
140
|
+
if (pickup) {
|
|
141
|
+
this.addWaypointFromExistingPlace(pickup);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// if pickup move it to multipdrop
|
|
145
|
+
if (dropoff) {
|
|
146
|
+
this.addWaypointFromExistingPlace(dropoff);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.order.payload.setProperties({
|
|
150
|
+
pickup_uuid: null,
|
|
151
|
+
dropoff_uuid: null,
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
const waypoint = this.store.createRecord('waypoint');
|
|
155
|
+
this.order.payload.waypoints.pushObject(waypoint);
|
|
156
|
+
|
|
157
|
+
this.order.payload.setProperties({
|
|
158
|
+
pickup_uuid: null,
|
|
159
|
+
dropoff_uuid: null,
|
|
160
|
+
pickup: null,
|
|
161
|
+
dropoff: null,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
contextComponentCallback(this, 'onRouteChanged');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
addWaypointFromExistingPlace(place) {
|
|
169
|
+
const waypoint = this.store.createRecord('waypoint', { place, location: place.location });
|
|
170
|
+
this.order.payload.waypoints.pushObject(waypoint);
|
|
171
|
+
|
|
172
|
+
// fire callback
|
|
173
|
+
contextComponentCallback(this, 'onWaypointAdded', waypoint);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@action removeWaypoint(waypoint) {
|
|
177
|
+
this.order.payload.waypoints.removeObject(waypoint);
|
|
178
|
+
|
|
179
|
+
// fire callback
|
|
180
|
+
contextComponentCallback(this, 'onWaypointRemoved', waypoint);
|
|
181
|
+
contextComponentCallback(this, 'onRouteChanged');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@action addWaypoint() {
|
|
185
|
+
const location = new Point(0, 0);
|
|
186
|
+
const place = this.store.createRecord('place', { location });
|
|
187
|
+
const waypoint = this.store.createRecord('waypoint', { place, location });
|
|
188
|
+
|
|
189
|
+
this.order.payload.waypoints.pushObject(waypoint);
|
|
190
|
+
// fire callback
|
|
191
|
+
contextComponentCallback(this, 'onWaypointAdded', waypoint);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@action setWaypointPlace(index, place) {
|
|
195
|
+
if (isArray(this.order.payload.waypoints) && !this.order.payload.waypoints.objectAt(index)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.order.payload.waypoints.objectAt(index).setProperties({
|
|
200
|
+
name: place.name,
|
|
201
|
+
place_uuid: place.id,
|
|
202
|
+
location: place.location,
|
|
203
|
+
place,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// fire callback waypoint place selected
|
|
207
|
+
contextComponentCallback(this, 'onWaypointPlaceSelected', place);
|
|
208
|
+
contextComponentCallback(this, 'onRouteChanged');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@action setPayloadPlace(prop, place) {
|
|
212
|
+
this.order.payload.set(prop, place);
|
|
213
|
+
|
|
214
|
+
// fire callback
|
|
215
|
+
contextComponentCallback(this, 'onPlaceSelected', place);
|
|
216
|
+
contextComponentCallback(this, 'onRouteChanged');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@action async optimizeRoute() {
|
|
220
|
+
this.isOptimizingRoute = true;
|
|
221
|
+
|
|
222
|
+
const coordinates = this.order.payload.payloadCoordinates;
|
|
223
|
+
const routingHost = getRoutingHost(this.order.payload, this.order.payload.waypoints);
|
|
224
|
+
let sortedWaypoints = [];
|
|
225
|
+
|
|
226
|
+
const response = await this.fetch.routing(coordinates, { source: 'any', destination: 'any', annotations: true }, { host: routingHost }).catch(() => {
|
|
227
|
+
this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.view.route-error'));
|
|
228
|
+
this.isOptimizingRoute = false;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (response && response.code === 'Ok') {
|
|
232
|
+
if (response.waypoints && isArray(response.waypoints)) {
|
|
233
|
+
const responseWaypoints = response.waypoints.sortBy('waypoint_index');
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < responseWaypoints.length; i++) {
|
|
236
|
+
const optimizedWaypoint = responseWaypoints.objectAt(i);
|
|
237
|
+
const optimizedWaypointLongitude = optimizedWaypoint.location.firstObject;
|
|
238
|
+
const optimizedWaypointLatitude = optimizedWaypoint.location.lastObject;
|
|
239
|
+
const waypointModel = findClosestWaypoint(optimizedWaypointLatitude, optimizedWaypointLongitude, this.order.payload.waypoints);
|
|
240
|
+
|
|
241
|
+
sortedWaypoints.pushObject(waypointModel);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.order.payload.waypoints = sortedWaypoints;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.view.route-error'));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.isOptimizingRoute = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@action sortWaypoints({ sourceList, sourceIndex, targetList, targetIndex }) {
|
|
254
|
+
if (sourceList === targetList && sourceIndex === targetIndex) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const item = sourceList.objectAt(sourceIndex);
|
|
259
|
+
|
|
260
|
+
sourceList.removeAt(sourceIndex);
|
|
261
|
+
targetList.insertAt(targetIndex, item);
|
|
262
|
+
|
|
263
|
+
contextComponentCallback(this, 'onRouteChanged');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -61,12 +61,12 @@ export default class FleetPanelComponent extends Component {
|
|
|
61
61
|
@tracked tab;
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* The
|
|
64
|
+
* The fleet being displayed or edited.
|
|
65
65
|
*
|
|
66
|
-
* @type {
|
|
66
|
+
* @type {FleetModel}
|
|
67
67
|
* @tracked
|
|
68
68
|
*/
|
|
69
|
-
@tracked
|
|
69
|
+
@tracked fleet;
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Returns the array of tabs available for the panel.
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
</div>
|
|
34
34
|
<FaIcon @icon="layer-group" />
|
|
35
35
|
<Attach::Tooltip @class="clean" @animation="scale" @placement="right">
|
|
36
|
-
<InputInfo @text={{t "fleet-ops.component.map-container.toolbar.view"}} @iconClass="hidden" class="py-1 px-2 bg-gray-900 -mt-1" />
|
|
36
|
+
<InputInfo @text={{t "fleet-ops.component.map-container.toolbar.view-order"}} @iconClass="hidden" class="py-1 px-2 bg-gray-900 -mt-1" />
|
|
37
37
|
</Attach::Tooltip>
|
|
38
38
|
</button>
|
|
39
39
|
<BasicDropdown id="map-toolbar-service-areas-button" @horizontalPosition="right" @calculatePosition={{this.calculatePosition}} @renderInPlace={{true}} as |dd|>
|
|
40
40
|
<dd.Trigger class="toolbar-button">
|
|
41
41
|
<FaIcon @icon="map-marked-alt" />
|
|
42
42
|
<Attach::Tooltip @class="clean" @animation="scale" @placement="right">
|
|
43
|
-
<InputInfo @text={{t "fleet-ops.component.map-container.toolbar.view"}} @iconClass="hidden" class="{{if dd.isOpen 'hidden'}} py-1 px-2 bg-gray-900 -mt-1" />
|
|
43
|
+
<InputInfo @text={{t "fleet-ops.component.map-container.toolbar.view-service-area"}} @iconClass="hidden" class="{{if dd.isOpen 'hidden'}} py-1 px-2 bg-gray-900 -mt-1" />
|
|
44
44
|
</Attach::Tooltip>
|
|
45
45
|
</dd.Trigger>
|
|
46
46
|
<dd.Content>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="modal-body-container">
|
|
3
3
|
<div class="flex flex-row justify-between mb-6">
|
|
4
4
|
<div class="flex">
|
|
5
|
-
<
|
|
5
|
+
<Toggle @isToggled={{@options.order.payload.isMultiDrop}} @onToggle={{@options.toggleMultiDropOrder}} @label={{t "fleet-ops.component.modals.order-route-form.toggle"}} />
|
|
6
6
|
</div>
|
|
7
7
|
<div class="flex flex-1 justify-end space-x-2">
|
|
8
8
|
{{#if @options.order.payload.isMultiDrop}}
|
|
@@ -161,6 +161,28 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
161
161
|
onClick: this.editMetaData,
|
|
162
162
|
},
|
|
163
163
|
];
|
|
164
|
+
@tracked uploadQueue = [];
|
|
165
|
+
acceptedFileTypes = [
|
|
166
|
+
'application/vnd.ms-excel',
|
|
167
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
168
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
169
|
+
'application/msword',
|
|
170
|
+
'application/pdf',
|
|
171
|
+
'application/x-pdf',
|
|
172
|
+
'image/jpeg',
|
|
173
|
+
'image/png',
|
|
174
|
+
'image/gif',
|
|
175
|
+
'image/webp',
|
|
176
|
+
'video/mp4',
|
|
177
|
+
'video/quicktime',
|
|
178
|
+
'video/x-msvideo',
|
|
179
|
+
'video/x-flv',
|
|
180
|
+
'video/x-ms-wmv',
|
|
181
|
+
'audio/mpeg',
|
|
182
|
+
'video/x-msvideo',
|
|
183
|
+
'application/zip',
|
|
184
|
+
'application/x-tar',
|
|
185
|
+
];
|
|
164
186
|
|
|
165
187
|
@not('isServicable') isNotServicable;
|
|
166
188
|
@alias('currentUser.latitude') userLatitude;
|
|
@@ -211,27 +233,6 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
211
233
|
this.payloadCoordinates = coordinates;
|
|
212
234
|
}
|
|
213
235
|
|
|
214
|
-
// @computed('payload.{dropoff,pickup,waypoints}', 'waypoints.[]')
|
|
215
|
-
// get payloadCoordinates() {
|
|
216
|
-
// let waypoints = [];
|
|
217
|
-
// let coordinates = [];
|
|
218
|
-
|
|
219
|
-
// console.log('[this.waypoints]', this.waypoints);
|
|
220
|
-
|
|
221
|
-
// waypoints.pushObjects([this.payload.pickup, ...this.waypoints.map((waypoint) => waypoint.place), this.payload.dropoff]);
|
|
222
|
-
// waypoints.forEach((place) => {
|
|
223
|
-
// if (place && place.get('longitude') && place.get('latitude')) {
|
|
224
|
-
// if (place.hasInvalidCoordinates) {
|
|
225
|
-
// return;
|
|
226
|
-
// }
|
|
227
|
-
|
|
228
|
-
// coordinates.pushObject([place.get('longitude'), place.get('latitude')]);
|
|
229
|
-
// }
|
|
230
|
-
// });
|
|
231
|
-
|
|
232
|
-
// return coordinates;
|
|
233
|
-
// }
|
|
234
|
-
|
|
235
236
|
@computed('payloadCoordinates.length', 'waypoints.[]') get isServicable() {
|
|
236
237
|
return this.payloadCoordinates.length >= 2;
|
|
237
238
|
}
|
|
@@ -571,7 +572,6 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
571
572
|
is_route_optimized,
|
|
572
573
|
})
|
|
573
574
|
.then((serviceQuotes) => {
|
|
574
|
-
console.log('[serviceQuotes]', serviceQuotes);
|
|
575
575
|
set(this, 'serviceQuotes', isArray(serviceQuotes) ? serviceQuotes : []);
|
|
576
576
|
|
|
577
577
|
if (this.serviceQuotes.length && this.isUsingIntegratedVendor) {
|
|
@@ -588,27 +588,31 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
588
588
|
|
|
589
589
|
_getSerializedPayload(payload) {
|
|
590
590
|
const serialized = {
|
|
591
|
-
pickup: payload.pickup,
|
|
592
|
-
dropoff: payload.dropoff,
|
|
591
|
+
pickup: this._seriailizeModel(payload.pickup),
|
|
592
|
+
dropoff: this._seriailizeModel(payload.dropoff),
|
|
593
|
+
entitities: this._serializeArray(payload.entities),
|
|
594
|
+
waypoints: this._serializeArray(payload.waypoint),
|
|
593
595
|
};
|
|
594
596
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
597
|
+
return serialized;
|
|
598
|
+
}
|
|
598
599
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
600
|
+
_seriailizeModel(model) {
|
|
601
|
+
if (isModel(model)) {
|
|
602
|
+
if (typeof model.toJSON === 'function') {
|
|
603
|
+
return model.toJSON();
|
|
604
|
+
}
|
|
602
605
|
|
|
603
|
-
|
|
604
|
-
|
|
606
|
+
if (typeof model.serialize === 'function') {
|
|
607
|
+
return model.serialize();
|
|
608
|
+
}
|
|
605
609
|
}
|
|
606
610
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
611
|
+
return model;
|
|
612
|
+
}
|
|
610
613
|
|
|
611
|
-
|
|
614
|
+
_serializeArray(array) {
|
|
615
|
+
return isArray(array) ? array.map((item) => this._seriailizeModel(item)) : array;
|
|
612
616
|
}
|
|
613
617
|
|
|
614
618
|
@action scheduleOrder(dateInstance) {
|
|
@@ -1448,4 +1452,37 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
1448
1452
|
}
|
|
1449
1453
|
}
|
|
1450
1454
|
}
|
|
1455
|
+
|
|
1456
|
+
@action queueFile(file) {
|
|
1457
|
+
// since we have dropzone and upload button within dropzone validate the file state first
|
|
1458
|
+
// as this method can be called twice from both functions
|
|
1459
|
+
if (['queued', 'failed', 'timed_out', 'aborted'].indexOf(file.state) === -1) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Queue and upload immediatley
|
|
1464
|
+
this.uploadQueue.pushObject(file);
|
|
1465
|
+
this.fetch.uploadFile.perform(
|
|
1466
|
+
file,
|
|
1467
|
+
{
|
|
1468
|
+
path: 'uploads/fleet-ops/order-files',
|
|
1469
|
+
type: 'order_file',
|
|
1470
|
+
},
|
|
1471
|
+
(uploadedFile) => {
|
|
1472
|
+
this.order.files.pushObject(uploadedFile);
|
|
1473
|
+
this.uploadQueue.removeObject(file);
|
|
1474
|
+
},
|
|
1475
|
+
() => {
|
|
1476
|
+
this.uploadQueue.removeObject(file);
|
|
1477
|
+
// remove file from queue
|
|
1478
|
+
if (file.queue && typeof file.queue.remove === 'function') {
|
|
1479
|
+
file.queue.remove(file);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
@action removeFile(file) {
|
|
1486
|
+
return file.destroyRecord();
|
|
1487
|
+
}
|
|
1451
1488
|
}
|