@fleetbase/fleetops-engine 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/addon/components/contact-form-panel.hbs +1 -0
  2. package/addon/components/driver-panel.hbs +1 -0
  3. package/addon/components/edit-order-route-panel.hbs +183 -0
  4. package/addon/components/edit-order-route-panel.js +265 -0
  5. package/addon/components/fleet-form-panel.hbs +1 -0
  6. package/addon/components/fleet-panel.hbs +1 -0
  7. package/addon/components/fleet-panel.js +3 -3
  8. package/addon/components/fuel-report-form-panel.hbs +1 -0
  9. package/addon/components/fuel-report-panel.hbs +1 -0
  10. package/addon/components/issue-form-panel.hbs +1 -0
  11. package/addon/components/issue-panel.hbs +1 -0
  12. package/addon/components/map-container/toolbar.hbs +2 -2
  13. package/addon/components/modals/order-route-form.hbs +1 -1
  14. package/addon/components/place-form-panel.hbs +1 -0
  15. package/addon/components/place-panel.hbs +1 -0
  16. package/addon/components/vehicle-form-panel.hbs +1 -0
  17. package/addon/components/vehicle-panel.hbs +1 -0
  18. package/addon/components/vendor-form-panel.hbs +1 -0
  19. package/addon/components/vendor-panel.hbs +1 -0
  20. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -0
  21. package/addon/components/widget/fleet-ops-key-metrics.js +85 -0
  22. package/addon/controllers/operations/orders/index/new.js +66 -22
  23. package/addon/controllers/operations/orders/index/view.js +131 -126
  24. package/addon/controllers/operations/orders/index.js +1 -1
  25. package/addon/engine.js +18 -12
  26. package/addon/routes/operations/orders/index/view.js +1 -1
  27. package/addon/services/context-panel.js +6 -0
  28. package/addon/styles/fleetops-engine.css +7 -2
  29. package/addon/templates/operations/orders/index/new.hbs +390 -58
  30. package/addon/templates/operations/orders/index/view.hbs +135 -105
  31. package/addon/templates/operations/service-rates/index/new.hbs +3 -1
  32. package/app/components/widget/{issues.js → fleet-ops-key-metrics.js} +1 -1
  33. package/composer.json +6 -6
  34. package/extension.json +1 -1
  35. package/package.json +5 -6
  36. package/server/src/Casts/MultiPolygon.php +3 -3
  37. package/server/src/Casts/Point.php +2 -2
  38. package/server/src/Casts/Polygon.php +3 -3
  39. package/server/src/Console/Commands/DispatchAdhocOrders.php +1 -1
  40. package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
  41. package/server/src/Flow/Activity.php +12 -0
  42. package/server/src/Flow/Config.php +7 -0
  43. package/server/src/Flow/Logic.php +7 -0
  44. package/server/src/Http/Controllers/Api/v1/DriverController.php +61 -21
  45. package/server/src/Http/Controllers/Api/v1/OrderController.php +5 -3
  46. package/server/src/Http/Controllers/Api/v1/PlaceController.php +1 -1
  47. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +1 -1
  48. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +1 -1
  49. package/server/src/Http/Controllers/Api/v1/ZoneController.php +1 -1
  50. package/server/src/Http/Controllers/Internal/v1/DriverController.php +17 -4
  51. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +2 -2
  52. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +6 -50
  53. package/server/src/Http/Controllers/Internal/v1/OrderController.php +13 -2
  54. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +1 -1
  55. package/server/src/Http/Filter/PlaceFilter.php +1 -1
  56. package/server/src/Http/Resources/v1/Driver.php +1 -1
  57. package/server/src/Http/Resources/v1/FuelReport.php +1 -1
  58. package/server/src/Http/Resources/v1/Issue.php +1 -1
  59. package/server/src/Http/Resources/v1/Order.php +3 -0
  60. package/server/src/Http/Resources/v1/Payload.php +1 -0
  61. package/server/src/Http/Resources/v1/Place.php +1 -1
  62. package/server/src/Http/Resources/v1/PurchaseRate.php +2 -1
  63. package/server/src/Http/Resources/v1/ServiceQuote.php +1 -0
  64. package/server/src/Http/Resources/v1/ServiceQuoteItem.php +29 -0
  65. package/server/src/Http/Resources/v1/TrackingStatus.php +1 -1
  66. package/server/src/Http/Resources/v1/Vehicle.php +1 -1
  67. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +1 -1
  68. package/server/src/Http/Resources/v1/Waypoint.php +2 -4
  69. package/server/src/Integrations/Lalamove/LalamoveDeliveryStop.php +2 -2
  70. package/server/src/Jobs/SimulateWaypointReached.php +2 -2
  71. package/server/src/Models/Contact.php +6 -18
  72. package/server/src/Models/Driver.php +8 -20
  73. package/server/src/Models/Fleet.php +6 -25
  74. package/server/src/Models/FuelReport.php +1 -1
  75. package/server/src/Models/Issue.php +1 -1
  76. package/server/src/Models/Order.php +31 -19
  77. package/server/src/Models/Payload.php +7 -7
  78. package/server/src/Models/Place.php +16 -16
  79. package/server/src/Models/Position.php +1 -1
  80. package/server/src/Models/ServiceArea.php +14 -14
  81. package/server/src/Models/TrackingNumber.php +1 -1
  82. package/server/src/Models/TrackingStatus.php +1 -1
  83. package/server/src/Models/Vehicle.php +7 -19
  84. package/server/src/Models/VehicleDeviceEvent.php +1 -1
  85. package/server/src/Models/Vendor.php +8 -25
  86. package/server/src/Models/Waypoint.php +6 -6
  87. package/server/src/Models/Zone.php +8 -8
  88. package/server/src/Observers/DriverObserver.php +7 -2
  89. package/server/src/Observers/TrackingNumberObserver.php +1 -1
  90. package/server/src/Rules/ResolvablePoint.php +1 -1
  91. package/server/src/Support/Encoding/Polyline.php +1 -1
  92. package/server/src/Support/OSRM.php +1 -1
  93. package/server/src/Support/Utils.php +60 -60
  94. package/server/src/Traits/HasTrackingNumber.php +3 -3
  95. package/server/src/routes.php +2 -4
  96. package/translations/en-us.yaml +10 -2
  97. package/addon/components/widget/fleet-ops-quickstart.hbs +0 -40
  98. package/addon/components/widget/fleet-ops-quickstart.js +0 -59
  99. package/addon/components/widget/issues.hbs +0 -1
  100. package/addon/components/widget/key-metrics.hbs +0 -121
  101. package/addon/components/widget/key-metrics.js +0 -46
  102. package/addon/components/widget/live-order-map.hbs +0 -59
  103. package/addon/components/widget/live-order-map.js +0 -11
  104. package/addon/components/widget/recent-orders.hbs +0 -35
  105. package/addon/components/widget/recent-orders.js +0 -24
  106. package/addon/components/widget/transaction-earnings.hbs +0 -1
  107. package/addon/components/widget/transactions.hbs +0 -36
  108. package/addon/components/widget/transactions.js +0 -29
  109. package/addon/components/widget/weekly-orders.hbs +0 -1
  110. package/addon/components/widget/weekly-orders.js +0 -3
  111. package/app/components/widget/key-metrics.js +0 -1
  112. package/app/components/widget/live-order-map.js +0 -1
  113. package/app/components/widget/recent-orders.js +0 -1
  114. package/app/components/widget/transaction-earnings.js +0 -1
  115. package/app/components/widget/transactions.js +0 -1
  116. package/app/components/widget/weekly-orders.js +0 -1
@@ -0,0 +1,85 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { task } from 'ember-concurrency-decorators';
5
+
6
+ export default class WidgetFleetOpsKeyMetricsComponent extends Component {
7
+ /**
8
+ * Inject the fetch service.
9
+ *
10
+ * @memberof WidgetKeyMetricsComponent
11
+ */
12
+ @service fetch;
13
+
14
+ /**
15
+ * Property for loading metrics to.
16
+ *
17
+ * @memberof WidgetKeyMetricsComponent
18
+ */
19
+ @tracked metrics = {};
20
+
21
+ /**
22
+ * Creates an instance of WidgetKeyMetricsComponent.
23
+ * @memberof WidgetKeyMetricsComponent
24
+ */
25
+ constructor() {
26
+ super(...arguments);
27
+ this.getDashboardMetrics.perform();
28
+ }
29
+
30
+ /**
31
+ * Task which fetches key metrics.
32
+ *
33
+ * @memberof WidgetKeyMetricsComponent
34
+ */
35
+ @task *getDashboardMetrics() {
36
+ this.metrics = yield this.fetch.get('fleet-ops/metrics').then((response) => {
37
+ return this.createMetricsMapFromResponse(response);
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Creates a map of metrics from the response data. This method organizes the metrics data into a more usable format.
43
+ *
44
+ * @param {Object} metrics - The metrics object fetched from the server.
45
+ * @returns {Object} A map of metrics where each key is a metric name and its value is an object of metric options.
46
+ * @memberof WidgetKeyMetricsComponent
47
+ */
48
+ createMetricsMapFromResponse(metrics = {}) {
49
+ const keys = Object.keys(metrics);
50
+ const map = {};
51
+
52
+ for (let i = 0; i < keys.length; i++) {
53
+ const key = keys[i];
54
+ map[key] = this.createMetricOptionsHash(key, metrics[key]);
55
+ }
56
+
57
+ return map;
58
+ }
59
+
60
+ /**
61
+ * Creates a hash of options for a given metric. Depending on the metric key, it assigns a specific format.
62
+ *
63
+ * @param {string} key - The key representing the specific metric.
64
+ * @param {number} value - The value of the metric.
65
+ * @returns {Object} An object containing the metric value and its format.
66
+ * @memberof WidgetKeyMetricsComponent
67
+ */
68
+ createMetricOptionsHash(key, value) {
69
+ const options = { value };
70
+
71
+ if (key === 'earnings') {
72
+ options.format = 'money';
73
+ }
74
+
75
+ if (key === 'fuel_costs') {
76
+ options.format = 'money';
77
+ }
78
+
79
+ if (key === 'total_distance_traveled') {
80
+ options.format = 'meters';
81
+ }
82
+
83
+ return options;
84
+ }
85
+ }
@@ -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
  }
@@ -597,7 +598,17 @@ export default class OperationsOrdersIndexNewController extends BaseController {
597
598
  }
598
599
 
599
600
  _seriailizeModel(model) {
600
- return isModel(model) ? model.toJSON() : model;
601
+ if (isModel(model)) {
602
+ if (typeof model.toJSON === 'function') {
603
+ return model.toJSON();
604
+ }
605
+
606
+ if (typeof model.serialize === 'function') {
607
+ return model.serialize();
608
+ }
609
+ }
610
+
611
+ return model;
601
612
  }
602
613
 
603
614
  _serializeArray(array) {
@@ -1441,4 +1452,37 @@ export default class OperationsOrdersIndexNewController extends BaseController {
1441
1452
  }
1442
1453
  }
1443
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
+ }
1444
1488
  }
@@ -3,13 +3,11 @@ import { inject as controller } from '@ember/controller';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { tracked } from '@glimmer/tracking';
5
5
  import { action, computed } from '@ember/object';
6
- import { isArray } from '@ember/array';
7
6
  import { later } from '@ember/runloop';
8
7
  import { not, notEmpty, alias } from '@ember/object/computed';
9
8
  import { task } from 'ember-concurrency-decorators';
10
9
  import { OSRMv1, Control as RoutingControl } from '@fleetbase/leaflet-routing-machine';
11
10
  import groupBy from '@fleetbase/ember-core/utils/macros/group-by';
12
- import findClosestWaypoint from '@fleetbase/ember-core/utils/find-closest-waypoint';
13
11
  import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
14
12
 
15
13
  export default class OperationsOrdersIndexViewController extends BaseController {
@@ -115,6 +113,29 @@ export default class OperationsOrdersIndexViewController extends BaseController
115
113
  @tracked isWaypointsCollapsed;
116
114
  @tracked leafletRoute;
117
115
  @tracked routeControl;
116
+ @tracked commentInput = '';
117
+ @tracked uploadQueue = [];
118
+ acceptedFileTypes = [
119
+ 'application/vnd.ms-excel',
120
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
121
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
122
+ 'application/msword',
123
+ 'application/pdf',
124
+ 'application/x-pdf',
125
+ 'image/jpeg',
126
+ 'image/png',
127
+ 'image/gif',
128
+ 'image/webp',
129
+ 'video/mp4',
130
+ 'video/quicktime',
131
+ 'video/x-msvideo',
132
+ 'video/x-flv',
133
+ 'video/x-ms-wmv',
134
+ 'audio/mpeg',
135
+ 'video/x-msvideo',
136
+ 'application/zip',
137
+ 'application/x-tar',
138
+ ];
118
139
 
119
140
  @alias('currentUser.latitude') userLatitude;
120
141
  @alias('currentUser.longitude') userLongitude;
@@ -133,16 +154,16 @@ export default class OperationsOrdersIndexViewController extends BaseController
133
154
  ];
134
155
 
135
156
  @tracked routePanelButtons = [
136
- {
137
- type: 'default',
138
- text: 'Edit',
139
- icon: 'pencil',
140
- iconPrefix: 'fas',
141
- onClick: () => {
142
- const order = this.model;
143
- this.editOrderRoute(order);
144
- },
145
- },
157
+ // {
158
+ // type: 'default',
159
+ // text: 'Edit',
160
+ // icon: 'pencil',
161
+ // iconPrefix: 'fas',
162
+ // onClick: () => {
163
+ // const order = this.model;
164
+ // this.editOrderRoute(order);
165
+ // },
166
+ // },
146
167
  ];
147
168
 
148
169
  @not('isWaypointsCollapsed') waypointsIsNotCollapsed;
@@ -179,6 +200,25 @@ export default class OperationsOrdersIndexViewController extends BaseController
179
200
  return groups;
180
201
  }
181
202
 
203
+ @computed('model.payload.{dropoff,pickup,waypoints}') get routeWaypoints() {
204
+ const { payload } = this.model;
205
+ let waypoints = [];
206
+ let coordinates = [];
207
+
208
+ waypoints.pushObjects([payload.pickup, ...payload.waypoints.toArray(), payload.dropoff]);
209
+ waypoints.forEach((place) => {
210
+ if (place && place.get('longitude') && place.get('latitude')) {
211
+ if (place.hasInvalidCoordinates) {
212
+ return;
213
+ }
214
+
215
+ coordinates.pushObject([place.get('latitude'), place.get('longitude')]);
216
+ }
217
+ });
218
+
219
+ return coordinates;
220
+ }
221
+
182
222
  @task *loadOrderRelations(order) {
183
223
  yield order.loadPayload();
184
224
  yield order.loadDriver();
@@ -186,6 +226,8 @@ export default class OperationsOrdersIndexViewController extends BaseController
186
226
  yield order.loadCustomer();
187
227
  yield order.loadTrackingActivity();
188
228
  yield order.loadOrderConfig();
229
+ yield order.loadPurchaseRate();
230
+ yield order.loadFiles();
189
231
  }
190
232
 
191
233
  @action resetView() {
@@ -280,8 +322,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
280
322
  setup();
281
323
  }
282
324
 
283
- @computed('model.payload.{dropoff,pickup,waypoints}') get routeWaypoints() {
284
- const { payload } = this.model;
325
+ getPayloadCoordinates(payload) {
285
326
  let waypoints = [];
286
327
  let coordinates = [];
287
328
 
@@ -299,11 +340,19 @@ export default class OperationsOrdersIndexViewController extends BaseController
299
340
  return coordinates;
300
341
  }
301
342
 
343
+ getPayloadWaypointsAsArray() {
344
+ if (this.model.payload && this.model.payload.waypoints) {
345
+ return this.model.payload.waypoints.toArray();
346
+ }
347
+
348
+ return [];
349
+ }
350
+
302
351
  @action displayOrderRoute() {
303
- const payload = this.model.payload;
304
- const waypoints = this.routeWaypoints;
305
352
  const leafletMap = this.leafletMap;
306
- const routingHost = getRoutingHost(payload, payload?.waypoints?.toArray());
353
+ const payload = this.model.payload;
354
+ const waypoints = this.getPayloadCoordinates(payload);
355
+ const routingHost = getRoutingHost(payload, this.getPayloadWaypointsAsArray());
307
356
 
308
357
  if (!waypoints || waypoints.length < 2 || !leafletMap) {
309
358
  return;
@@ -334,7 +383,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
334
383
  router,
335
384
  }).addTo(leafletMap);
336
385
 
337
- this.routeControl?.on('routesfound', (event) => {
386
+ this.routeControl.on('routesfound', (event) => {
338
387
  const { routes } = event;
339
388
 
340
389
  this.leafletRoute = routes.firstObject;
@@ -343,7 +392,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
343
392
  later(
344
393
  this,
345
394
  () => {
346
- leafletMap?.flyToBounds(waypoints, {
395
+ leafletMap.flyToBounds(waypoints, {
347
396
  paddingBottomRight: [200, 0],
348
397
  maxZoom: 14,
349
398
  animate: true,
@@ -412,9 +461,14 @@ export default class OperationsOrdersIndexViewController extends BaseController
412
461
  order,
413
462
  confirm: (modal) => {
414
463
  modal.startLoading();
415
- return order.save().then(() => {
416
- this.notifications.success(options.successNotification || this.intl.t('fleet-ops.operations.orders.index.view.update-success', { orderId: order.public_id }));
417
- });
464
+ return order
465
+ .save()
466
+ .then(() => {
467
+ this.notifications.success(options.successNotification || this.intl.t('fleet-ops.operations.orders.index.view.update-success', { orderId: order.public_id }));
468
+ })
469
+ .catch((error) => {
470
+ this.notifications.serverError(error);
471
+ });
418
472
  },
419
473
  decline: () => {
420
474
  order.payload.rollbackAttributes();
@@ -475,114 +529,30 @@ export default class OperationsOrdersIndexViewController extends BaseController
475
529
  * @void
476
530
  */
477
531
  @action async editOrderRoute(order, options = {}) {
478
- options = options === null ? {} : options;
479
-
480
- await this.modalsManager.done();
481
-
482
- this.modalsManager.show('modals/order-route-form', {
483
- title: this.intl.t('fleet-ops.operations.orders.index.view.edit-route-title'),
484
- acceptButtonText: 'Save Changes',
485
- acceptButtonIcon: 'save',
486
- order,
487
- userLatitude: this.userLatitude,
488
- userLongitude: this.userLongitude,
489
- isOptimizingRoute: false,
490
- editPlace: async (place) => {
491
- await this.modalsManager.done();
492
-
493
- this.contextPanel.focus(place, 'editing', {
494
- args: {
495
- onClose: () => {
496
- this.editOrderRoute(order);
497
- },
498
- },
499
- });
500
- },
501
- toggleMultiDropOrder: () => {
502
- if (order.payload.isMultiDrop) {
503
- order.payload.waypoints.clear();
504
- order.payload.setProperties({
505
- pickup_uuid: '#',
506
- dropoff_uuid: '#',
507
- });
508
- } else {
509
- const waypoint = this.store.createRecord('waypoint');
510
- order.payload.waypoints.pushObject(waypoint);
511
-
512
- order.payload.setProperties({
513
- pickup_uuid: null,
514
- dropoff_uuid: null,
515
- pickup: null,
516
- dropoff: null,
517
- });
518
- }
519
- },
520
- removeWaypoint: (waypoint) => {
521
- order.payload.waypoints.removeObject(waypoint);
522
- },
523
- addWaypoint: () => {
524
- const waypoint = this.store.createRecord('waypoint');
525
- order.payload.waypoints.pushObject(waypoint);
526
- },
527
- setWaypointPlace: (index, place) => {
528
- if (!order.payload.waypoints?.objectAt(index)) {
529
- return;
530
- }
531
-
532
- order.payload.waypoints.objectAt(index).setProperties({
533
- name: place.name,
534
- place_uuid: place.id,
535
- place,
536
- });
537
- },
538
- setPayloadPlace: (prop, place) => {
539
- order.payload.set(prop, place);
540
- },
541
- optimizeRoute: async () => {
542
- this.modalsManager.setOption('isOptimizingRoute', true);
543
-
544
- const coordinates = order.payload.payloadCoordinates;
545
- const routingHost = getRoutingHost(order.payload, order.payload.waypoints);
546
-
547
- const response = await this.fetch.routing(coordinates, { source: 'any', destination: 'any', annotations: true }, { host: routingHost }).catch(() => {
548
- this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.view.route-error'));
549
- this.modalsManager.setOption('isOptimizingRoute', false);
550
- });
551
-
552
- if (response && response.code === 'Ok') {
553
- if (response.waypoints && isArray(response.waypoints)) {
554
- const responseWaypoints = response.waypoints.sortBy('waypoint_index');
555
- const sortedWaypoints = [];
556
-
557
- for (let i = 0; i < responseWaypoints.length; i++) {
558
- const optimizedWaypoint = responseWaypoints.objectAt(i);
559
- const optimizedWaypointLongitude = optimizedWaypoint.location.firstObject;
560
- const optimizedWaypointLatitude = optimizedWaypoint.location.lastObject;
561
- const waypointModel = findClosestWaypoint(optimizedWaypointLatitude, optimizedWaypointLongitude, order.payload.waypoints);
562
-
563
- sortedWaypoints.pushObject(waypointModel);
564
- }
565
-
566
- order.payload.waypoints = sortedWaypoints;
567
- }
568
- } else {
569
- this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.view.route-error'));
570
- }
532
+ const updateRouteDisplay = () => {
533
+ later(
534
+ this,
535
+ () => {
536
+ this.displayOrderRoute();
537
+ },
538
+ 100
539
+ );
540
+ };
571
541
 
572
- this.modalsManager.setOption('isOptimizingRoute', false);
542
+ this.contextPanel.focus(order, 'editingRoute', {
543
+ args: {
544
+ isResizable: true,
545
+ latitude: this.userLatitude,
546
+ longitude: this.userLongitude,
547
+ ...options,
573
548
  },
574
- confirm: (modal) => {
575
- modal.startLoading();
576
-
577
- return order.payload.save().then(() => {
578
- this.notifications.success(options.successNotification ?? this.intl.t('fleet-ops.operations.orders.index.view.route-update-success', { orderId: order.public_id }));
579
- });
549
+ onRouteChanged: () => {
550
+ updateRouteDisplay();
580
551
  },
581
- decline: () => {
582
- order.payload.rollbackAttributes();
583
- this.modalsManager.done();
552
+ onAfterSave: () => {
553
+ this.contextPanel.clear();
554
+ updateRouteDisplay();
584
555
  },
585
- ...options,
586
556
  });
587
557
  }
588
558
 
@@ -911,4 +881,39 @@ export default class OperationsOrdersIndexViewController extends BaseController
911
881
  },
912
882
  });
913
883
  }
884
+
885
+ @action queueFile(file) {
886
+ // since we have dropzone and upload button within dropzone validate the file state first
887
+ // as this method can be called twice from both functions
888
+ if (['queued', 'failed', 'timed_out', 'aborted'].indexOf(file.state) === -1) {
889
+ return;
890
+ }
891
+
892
+ // Queue and upload immediatley
893
+ this.uploadQueue.pushObject(file);
894
+ this.fetch.uploadFile.perform(
895
+ file,
896
+ {
897
+ path: 'uploads/fleet-ops/order-files',
898
+ subject_uuid: this.model.id,
899
+ subject_type: 'fleet-ops:order',
900
+ type: 'order_file',
901
+ },
902
+ (uploadedFile) => {
903
+ this.model.files.pushObject(uploadedFile);
904
+ this.uploadQueue.removeObject(file);
905
+ },
906
+ () => {
907
+ this.uploadQueue.removeObject(file);
908
+ // remove file from queue
909
+ if (file.queue && typeof file.queue.remove === 'function') {
910
+ file.queue.remove(file);
911
+ }
912
+ }
913
+ );
914
+ }
915
+
916
+ @action removeFile(file) {
917
+ return file.destroyRecord();
918
+ }
914
919
  }
@@ -949,7 +949,7 @@ export default class OperationsOrdersIndexController extends BaseController {
949
949
  * @memberof OperationsOrdersIndexController
950
950
  */
951
951
  @action fetchActiveOrdersCount() {
952
- this.fetch.get('fleet-ops/metrics/all', { discover: ['orders_in_progress'] }).then((response) => {
952
+ this.fetch.get('fleet-ops/metrics', { discover: ['orders_in_progress'] }).then((response) => {
953
953
  this.activeOrdersCount = response.orders_in_progress;
954
954
  });
955
955
  }
package/addon/engine.js CHANGED
@@ -5,6 +5,7 @@ import config from './config/environment';
5
5
  import services from '@fleetbase/ember-core/exports/services';
6
6
  import AdminVisibilityControlsComponent from './components/admin/visibility-controls';
7
7
  import NavigatorAppConfigComponent from './components/admin/navigator-app';
8
+ import FleetOpsKeyMetricsWidget from './components/widget/fleet-ops-key-metrics';
8
9
 
9
10
  const { modulePrefix } = config;
10
11
  const externalRoutes = ['console', 'extensions'];
@@ -40,20 +41,25 @@ export default class FleetOpsEngine extends Engine {
40
41
  }
41
42
  );
42
43
 
43
- // create primary registry for engine
44
- universe.createRegistry('engine:fleet-ops');
44
+ // widgets for registry
45
+ const KeyMetricsWidgetDefinition = {
46
+ did: 'fleetops-metrics',
47
+ name: 'Fleet-Ops Metrics',
48
+ description: 'Key metrics from Fleet-Ops.',
49
+ icon: 'truck',
50
+ component: FleetOpsKeyMetricsWidget,
51
+ grid_options: { w: 12, h: 12, minW: 8, minH: 12 },
52
+ options: {
53
+ title: 'Fleet-Ops Metrics',
54
+ },
55
+ };
45
56
 
46
- // register the vehicle panel
47
- universe.createRegistry('component:vehicle-panel');
57
+ // register widgets
58
+ universe.registerDefaultDashboardWidgets([KeyMetricsWidgetDefinition]);
59
+ universe.registerDashboardWidgets([KeyMetricsWidgetDefinition]);
48
60
 
49
- // register the driver panel
50
- universe.createRegistry('component:driver-panel');
51
-
52
- // register vehicle context menu
53
- universe.createRegistry('contextmenu:vehicle');
54
-
55
- // register driver context menu
56
- universe.createRegistry('contextmenu:driver');
61
+ // create all registries necessary
62
+ universe.createRegistries(['engine:fleet-ops', 'component:vehicle-panel', 'component:driver-panel', 'contextmenu:vehicle', 'contextmenu:driver']);
57
63
  };
58
64
  }
59
65
 
@@ -29,7 +29,7 @@ export default class OperationsOrdersIndexViewRoute extends Route {
29
29
  return this.store.queryRecord('order', {
30
30
  public_id,
31
31
  single: true,
32
- with: ['payload', 'driverAssigned', 'customer', 'facilitator', 'trackingStatuses', 'trackingNumber'],
32
+ with: ['payload', 'driverAssigned', 'customer', 'facilitator', 'trackingStatuses', 'trackingNumber', 'purchaseRate', 'comments', 'files'],
33
33
  });
34
34
  }
35
35
 
@@ -97,6 +97,12 @@ export default class ContextPanelService extends Service {
97
97
  componentArguments: [{ isResizable: true }, { width: '500px' }],
98
98
  },
99
99
  },
100
+ order: {
101
+ editingRoute: {
102
+ component: 'edit-order-route-panel',
103
+ componentArguments: [{ isResizable: true }, { width: '500px' }],
104
+ },
105
+ },
100
106
  };
101
107
 
102
108
  /**
@@ -51,12 +51,12 @@ body[data-theme='dark'] .scheduler-left-sidebar {
51
51
  --fleet-summary-nav-item-base-padding: 1.5rem;
52
52
  }
53
53
 
54
- .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-toggle,
54
+ .next-sidebar .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-toggle,
55
55
  .next-sidebar .next-sidebar-panel-container.next-fleet-summary > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-toggle {
56
56
  padding-left: 2rem;
57
57
  }
58
58
 
59
- .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-content-panel-body .next-nav-item,
59
+ .next-sidebar .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-content-panel-body .next-nav-item,
60
60
  .next-sidebar .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-body .next-nav-item.driver-nav-item {
61
61
  padding-left: 3.5rem;
62
62
  }
@@ -66,3 +66,8 @@ body[data-theme='dark'] .scheduler-left-sidebar {
66
66
  border: none;
67
67
  box-shadow: none;
68
68
  }
69
+
70
+ body[data-theme='dark'] .next-table-wrapper.order-view-purchase-rate-table-wrapper > table#order-view-purchase-rate-table tbody tr td {
71
+ background-color: rgb(31, 41, 55);
72
+ color: rgb(249, 250, 251);
73
+ }