@fleetbase/fleetops-engine 0.2.1 → 0.2.3

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 (30) hide show
  1. package/addon/components/driver-card.hbs +1 -1
  2. package/addon/components/leaflet-drift-marker.js +53 -0
  3. package/addon/components/live-map.hbs +17 -4
  4. package/addon/components/live-map.js +121 -20
  5. package/addon/components/map-container/toolbar.hbs +6 -0
  6. package/addon/controllers/management/contacts/index.js +3 -3
  7. package/addon/controllers/management/drivers/index.js +1 -1
  8. package/addon/controllers/management/vehicles/index.js +1 -1
  9. package/addon/controllers/operations/orders/index/new.js +68 -21
  10. package/addon/controllers/operations/orders/index/view.js +16 -5
  11. package/addon/controllers/operations/orders/index.js +69 -5
  12. package/addon/controllers/operations/service-rates/index/edit.js +1 -1
  13. package/addon/controllers/operations/service-rates/index/new.js +10 -11
  14. package/addon/controllers/operations/service-rates/index.js +8 -0
  15. package/addon/initializers/load-leaflet-assets.js +6 -1
  16. package/addon/instance-initializers/register-leaflet-drift-marker.js +17 -0
  17. package/addon/routes/operations/orders/index/new.js +3 -1
  18. package/addon/routes/operations/orders/index/view.js +46 -0
  19. package/addon/routes/operations/service-rates/index/edit.js +7 -0
  20. package/addon/templates/operations/orders/index.hbs +1 -1
  21. package/addon/templates/operations/service-rates/index/new.hbs +2 -2
  22. package/app/components/leaflet-drift-marker.js +1 -0
  23. package/assets/images/boxes/large.png +0 -0
  24. package/assets/images/boxes/medium.png +0 -0
  25. package/assets/images/boxes/small.png +0 -0
  26. package/assets/images/boxes/x-large.png +0 -0
  27. package/assets/images/building-marker.png +0 -0
  28. package/assets/images/map-marker.png +0 -0
  29. package/index.js +16 -0
  30. package/package.json +6 -5
@@ -1,4 +1,4 @@
1
- <div class="driver-card flex flex-1" {{did-insert this.setupComponent}} ...attributes>
1
+ <div class="driver-card flex flex-1" ...attributes>
2
2
  <a href="javascript:;" class="driver-card-details flex flex-1 bg-black bg-opacity-25 rounded-md px-4 py-3 shadow-sm" {{on "click" this.onClick}}>
3
3
  <div class="flex flex-row">
4
4
  <div class="mr-3">
@@ -0,0 +1,53 @@
1
+ import MarkerLayer from 'ember-leaflet/components/marker-layer';
2
+
3
+ export default class LeafletDriftMarkerComponent extends MarkerLayer {
4
+ leafletOptions = [
5
+ ...this.leafletOptions,
6
+
7
+ /**
8
+ * Required, duration im miliseconds marker will take to destination point.
9
+ *
10
+ * @argument duration
11
+ * @type {Number}
12
+ */
13
+ 'duration',
14
+
15
+ /**
16
+ * Makes map view follow marker.
17
+ *
18
+ * @argument keepAtCenter
19
+ * @type {Boolean}
20
+ */
21
+ 'keepAtCenter',
22
+
23
+ /**
24
+ * Rotation angle, in degrees, clockwise.
25
+ *
26
+ * @argument rotationAngle
27
+ * @type {Number}
28
+ */
29
+ 'rotationAngle',
30
+
31
+ /**
32
+ * The rotation center, as a transform-origin CSS rule.
33
+ *
34
+ * @argument rotationOrigin
35
+ * @type {String}
36
+ */
37
+ 'rotationOrigin',
38
+
39
+ /**
40
+ * The Public ID of the driver.
41
+ *
42
+ * @argument publicId
43
+ * @type {String}
44
+ */
45
+ 'publicId',
46
+ ];
47
+
48
+ createLayer() {
49
+ const { DriftMarker } = window;
50
+
51
+ return new DriftMarker(...this.requiredOptions, this.options);
52
+ }
53
+ }
@@ -4,11 +4,24 @@
4
4
 
5
5
  {{#if this.isDriversVisible}}
6
6
  {{#each this.drivers as |driver|}}
7
- <layers.rotated-marker @icon={{icon iconUrl=driver.vehicle_avatar iconSize=(array 40 40)}} @location={{point-to-coordinates driver.location}} @onAdd={{fn this.onAction "onDriverAdded" driver}}>
8
- {{!-- <marker.popup @maxWidth="500" @minWidth="225">
7
+ <layers.drift-marker @rotationAngle={{driver.heading}} @duration={{300}} @icon={{icon iconUrl=driver.vehicle_avatar iconSize=(array 24 24)}} @location={{point-to-coordinates driver.location}} @publicId={{driver.public_id}} @onAdd={{fn this.onAction "onDriverAdded" driver}} as |marker|>
8
+ <marker.popup @maxWidth="500" @minWidth="225">
9
9
  <DriverCard @driver={{driver}} @showCoordinates={{true}} @textClass="text-gray-900 dark:text-gray-100" />
10
- </marker.popup> --}}
11
- </layers.rotated-marker>
10
+ </marker.popup>
11
+ <marker.tooltip @permanent={{false}} @sticky={{true}}>{{driver.name}}</marker.tooltip>
12
+ </layers.drift-marker>
13
+ {{/each}}
14
+ {{/if}}
15
+
16
+ {{#if this.isPlacesVisible}}
17
+ {{#each this.places as |place|}}
18
+ <layers.marker @icon={{icon iconUrl="/engines-dist/images/building-marker.png" iconSize=(array 16 16)}} @location={{point-to-coordinates place.location}} @riseOnHover={{true}} @title={{place.address}} @alt={{place.address}} as |marker|>
19
+ <marker.popup>
20
+ <div>{{place.address}}</div>
21
+ <div>{{format-point place.location}}</div>
22
+ </marker.popup>
23
+ <marker.tooltip @permanent={{false}} @sticky={{true}} @direction="bottom">{{place.name}}</marker.tooltip>
24
+ </layers.marker>
12
25
  {{/each}}
13
26
  {{/if}}
14
27
 
@@ -25,10 +25,12 @@ export default class LiveMapComponent extends Component {
25
25
 
26
26
  @tracked routes = [];
27
27
  @tracked drivers = [];
28
+ @tracked places = [];
28
29
  @tracked channels = [];
29
30
  @tracked isLoading = true;
30
31
  @tracked isReady = false;
31
32
  @tracked isDriversVisible = true;
33
+ @tracked isPlacesVisible = true;
32
34
  @tracked isRoutesVisible = true;
33
35
  @tracked isDrawControlsVisible = false;
34
36
  @tracked isCreatingServiceArea = false;
@@ -105,10 +107,12 @@ export default class LiveMapComponent extends Component {
105
107
 
106
108
  this.routes = await this.fetchActiveRoutes();
107
109
  this.drivers = await this.fetchActiveDrivers();
110
+ this.places = await this.fetchActivePlaces();
108
111
  this.serviceAreaRecords = await this.fetchServiceAreas();
109
112
  this.isReady = true;
110
113
 
111
114
  this.watchDrivers(this.drivers);
115
+ this.listenForOrders();
112
116
 
113
117
  if (typeof this.args.onReady === 'function') {
114
118
  this.args.onReady(this);
@@ -331,6 +335,11 @@ export default class LiveMapComponent extends Component {
331
335
  @action onDriverAdded(driver, event) {
332
336
  const { target } = event;
333
337
 
338
+ // set the marker instance to the driver model
339
+ set(driver, '_marker', target);
340
+
341
+ console.log('onDriverAdded()', ...arguments);
342
+
334
343
  this.createContextMenuForDriver(driver, target);
335
344
  }
336
345
 
@@ -478,6 +487,18 @@ export default class LiveMapComponent extends Component {
478
487
  this.isDriversVisible = !this.isDriversVisible;
479
488
  }
480
489
 
490
+ @action hidePlaces() {
491
+ this.isPlacesVisible = false;
492
+ }
493
+
494
+ @action showPlaces() {
495
+ this.isPlacesVisible = true;
496
+ }
497
+
498
+ @action togglePlaces() {
499
+ this.isPlacesVisible = !this.isPlacesVisible;
500
+ }
501
+
481
502
  @action hideRoutes() {
482
503
  this.isRoutesVisible = false;
483
504
  }
@@ -706,6 +727,30 @@ export default class LiveMapComponent extends Component {
706
727
  * Functions are used to fetch date or handle socket callbacks/initializations.
707
728
  *
708
729
  */
730
+ @action async listenForOrders() {
731
+ // setup socket
732
+ const socket = this.socket.instance();
733
+
734
+ // listen on company channel
735
+ const channelId = `company.${this.currentUser.companyId}`;
736
+ const channel = socket.subscribe(channelId);
737
+
738
+ // track channel
739
+ this.channels.pushObject(channel);
740
+
741
+ // listen to channel for events
742
+ await channel.listener('subscribe').once();
743
+
744
+ // get incoming data and console out
745
+ (async () => {
746
+ for await (let output of channel) {
747
+ const { event, data } = output;
748
+
749
+ console.log(`[channel ${channelId}]`, output, event, data);
750
+ }
751
+ })();
752
+ }
753
+
709
754
  @action watchDrivers(drivers = []) {
710
755
  // setup socket
711
756
  const socket = this.socket.instance();
@@ -719,29 +764,54 @@ export default class LiveMapComponent extends Component {
719
764
 
720
765
  @action async listenForDriver(driver, socket) {
721
766
  // listen on company channel
722
- const channelId = `driver.${driver.uuid}`;
767
+ const channelId = `driver.${driver.id}`;
723
768
  const channel = socket.subscribe(channelId);
724
769
 
725
- // debug
726
- // console.log(`socket subscribed to ${channelId}`);
727
-
728
770
  // track channel
729
771
  this.channels.pushObject(channel);
730
772
 
731
773
  // listen to channel for events
732
774
  await channel.listener('subscribe').once();
733
775
 
776
+ // Initialize an empty buffer to store incoming events
777
+ const eventBuffer = [];
778
+
779
+ // Time to wait in milliseconds before processing buffered events
780
+ const bufferTime = 1000 * 10;
781
+
782
+ // Function to process buffered events
783
+ const processBuffer = () => {
784
+ // Sort events by created_at
785
+ eventBuffer.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
786
+
787
+ // Process sorted events
788
+ for (const output of eventBuffer) {
789
+ const { event, data } = output;
790
+ console.log(`${event} - #${data.additionalData.index} (${output.created_at}) [ ${data.location.coordinates.join(' ')} ]`);
791
+ // update driver heading degree
792
+ driver._marker.setRotationAngle(data.heading);
793
+ // move driver's marker to new coordinates
794
+ driver._marker.slideTo(data.location.coordinates, { duration: 2000 });
795
+ }
796
+
797
+ // Clear the buffer
798
+ eventBuffer.length = 0;
799
+ };
800
+
801
+ // Start a timer to process the buffer at intervals
802
+ setInterval(processBuffer, bufferTime);
803
+
734
804
  // get incoming data and console out
735
- for await (let output of channel) {
736
- const { event, data } = output;
737
-
738
- if (event === 'driver.location_changed') {
739
- driver.location = data.location;
740
- driver.altitude = data.altitude;
741
- driver.heading = data.heading;
742
- driver.speed = data.speed;
805
+ (async () => {
806
+ for await (let output of channel) {
807
+ const { event, data } = output;
808
+
809
+ if (event === 'driver.location_changed' || event === 'driver.simulated_location_changed') {
810
+ // Add the incoming event to the buffer
811
+ eventBuffer.push(output);
812
+ }
743
813
  }
744
- }
814
+ })();
745
815
  }
746
816
 
747
817
  @action closeChannels() {
@@ -814,15 +884,20 @@ export default class LiveMapComponent extends Component {
814
884
  this.isLoading = true;
815
885
 
816
886
  return new Promise((resolve) => {
817
- this.fetch.get('fleet-ops/live/routes').then((routes) => {
818
- this.isLoading = false;
887
+ this.fetch
888
+ .get('fleet-ops/live/routes')
889
+ .then((routes) => {
890
+ this.isLoading = false;
819
891
 
820
- if (typeof this.args.onRoutesLoaded === 'function') {
821
- this.args.onRoutesLoaded(routes);
822
- }
892
+ if (typeof this.args.onRoutesLoaded === 'function') {
893
+ this.args.onRoutesLoaded(routes);
894
+ }
823
895
 
824
- resolve(routes);
825
- });
896
+ resolve(routes);
897
+ })
898
+ .catch(() => {
899
+ resolve([]);
900
+ });
826
901
  });
827
902
  }
828
903
 
@@ -839,6 +914,32 @@ export default class LiveMapComponent extends Component {
839
914
 
840
915
  resolve(drivers);
841
916
  });
917
+ }).catch(() => {
918
+ resolve([]);
919
+ });
920
+ }
921
+
922
+ @action fetchActivePlaces() {
923
+ this.isLoading = true;
924
+
925
+ // get the center of map
926
+ const center = this.leafletMap.getCenter();
927
+
928
+ return new Promise((resolve) => {
929
+ this.fetch
930
+ .get('fleet-ops/live/places', { within: center }, { normalizeToEmberData: true, normalizeModelType: 'place' })
931
+ .then((places) => {
932
+ this.isLoading = false;
933
+
934
+ if (typeof this.args.onPlacesLoaded === 'function') {
935
+ this.args.onPlacesLoaded(places);
936
+ }
937
+
938
+ resolve(places);
939
+ })
940
+ .catch(() => {
941
+ resolve([]);
942
+ });
842
943
  });
843
944
  }
844
945
 
@@ -47,5 +47,11 @@
47
47
  <MapContainer::Toolbar::ZonesPanel @dd={{dd}} @map={{@map}} @onPressCreateServiceArea={{@onPressCreateServiceArea}} />
48
48
  </dd.Content>
49
49
  </BasicDropdown>
50
+ <button type="button" class="toolbar-button relative" {{on "click" (fn this.onAction "onPressDisplayPlaces")}}>
51
+ <FaIcon @icon="city" />
52
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="right">
53
+ <InputInfo @text="Toggle Places" @iconClass="hidden" class="py-1 px-2 bg-gray-900 -mt-1" />
54
+ </Attach::Tooltip>
55
+ </button>
50
56
  </div>
51
57
  </MapContainer::ToolbarContainer>
@@ -384,13 +384,13 @@ export default class ManagementContactsIndexController extends Controller {
384
384
  {
385
385
  path: `uploads/${contact.company_uuid}/contacts/${contact.slug}`,
386
386
  subject_uuid: contact.id,
387
- subject_type: `contact`,
388
- type: `contact_photo`,
387
+ subject_type: 'contact',
388
+ type: 'contact_photo',
389
389
  },
390
390
  (uploadedFile) => {
391
391
  contact.setProperties({
392
392
  photo_uuid: uploadedFile.id,
393
- photo_url: uploadedFile.s3url,
393
+ photo_url: uploadedFile.url,
394
394
  photo: uploadedFile,
395
395
  });
396
396
  }
@@ -580,7 +580,7 @@ export default class ManagementDriversIndexController extends Controller {
580
580
  icon: 'headphones',
581
581
  action: () => {
582
582
  this.modalsManager.done().then(() => {
583
- this.hostRouter.transitionTo('console.developers-console.sockets.watch', `driver.${driver.public_id}`);
583
+ this.hostRouter.transitionTo('console.developers.sockets.view', `driver.${driver.public_id}`);
584
584
  });
585
585
  },
586
586
  },
@@ -562,7 +562,7 @@ export default class ManagementVehiclesIndexController extends ManagementControl
562
562
  (uploadedFile) => {
563
563
  vehicle.setProperties({
564
564
  photo_uuid: uploadedFile.id,
565
- photo_url: uploadedFile.s3url,
565
+ photo_url: uploadedFile.url,
566
566
  photo: uploadedFile,
567
567
  });
568
568
  }
@@ -5,7 +5,7 @@ import { action, computed, setProperties, set, get } from '@ember/object';
5
5
  import { not, equal, alias } from '@ember/object/computed';
6
6
  import { isArray } from '@ember/array';
7
7
  import { dasherize } from '@ember/string';
8
- import { later } from '@ember/runloop';
8
+ import { later, next } from '@ember/runloop';
9
9
  import { OSRMv1, Control as RoutingControl } from '@fleetbase/leaflet-routing-machine';
10
10
  import polyline from '@fleetbase/ember-core/utils/polyline';
11
11
  import findClosestWaypoint from '@fleetbase/ember-core/utils/find-closest-waypoint';
@@ -494,7 +494,9 @@ export default class OperationsOrdersIndexNewController extends Controller {
494
494
  }
495
495
 
496
496
  @action newPlace() {
497
- this.placesController?.createPlace();
497
+ if (this.placesController) {
498
+ return this.placesController.createPlace();
499
+ }
498
500
  }
499
501
 
500
502
  @action async getQuotes(service) {
@@ -564,26 +566,39 @@ export default class OperationsOrdersIndexNewController extends Controller {
564
566
  }
565
567
 
566
568
  @action setupInterface() {
567
- this.leafletMap?.liveMap?.hideDrivers();
568
- this.leafletMap?.liveMap?.hideRoutes();
569
-
570
- // track all layers added from this view
571
- this.leafletMap?.on('layeradd', ({ layer }) => {
572
- // disable dragging of layer
573
- layer.dragging?.disable();
569
+ if (this.leafletMap && this.leafletMap.liveMap) {
570
+ this.leafletMap.liveMap.hideDrivers();
571
+ this.leafletMap.liveMap.hideRoutes();
572
+
573
+ // track all layers added from this view
574
+ this.leafletMap.on('layeradd', ({ layer }) => {
575
+ // disable dragging of layer
576
+ if (layer.dragging && typeof layer.dragging.disable === 'function') {
577
+ layer.dragging.disable();
578
+ }
574
579
 
575
- if (!this.leafletLayers?.includes(layer)) {
576
- this.leafletLayers.pushObject(layer);
577
- }
578
- });
580
+ next(this, function () {
581
+ if (isArray(this.leafletLayers) && !this.leafletLayers.includes(layer)) {
582
+ this.leafletLayers.pushObject(layer);
583
+ }
584
+ });
585
+ });
586
+ } else {
587
+ // setup interface when livemap is ready
588
+ this.universe.on('livemap.ready', () => {
589
+ this.setupInterface();
590
+ });
591
+ }
579
592
 
580
593
  // switch to map mode
581
594
  this.ordersController.setLayoutMode('map');
582
595
  }
583
596
 
584
597
  @action resetInterface() {
585
- this.leafletMap?.liveMap?.showDrivers();
586
- this.leafletMap?.liveMap?.showRoutes();
598
+ if (this.leafletMap && this.leafletMap.liveMap) {
599
+ this.leafletMap.liveMap.showDrivers();
600
+ this.leafletMap.liveMap.showRoutes();
601
+ }
587
602
  }
588
603
 
589
604
  @action getRoute() {
@@ -674,15 +689,47 @@ export default class OperationsOrdersIndexNewController extends Controller {
674
689
  }
675
690
 
676
691
  @action clearLayers() {
677
- const { leafletLayers, leafletMap } = this;
692
+ if (this.leafletMap) {
693
+ try {
694
+ this.leafletMap.eachLayer((layer) => {
695
+ if (isArray(this.leafletLayers) && this.leafletLayers.includes(layer)) {
696
+ this.leafletMap.removeLayer(layer);
697
+ }
698
+ });
699
+ } catch (error) {
700
+ // fallback method with tracked layers
701
+ if (isArray(this.leafletLayers)) {
702
+ this.leafletLayers.forEach((layer) => {
703
+ try {
704
+ this.leafletMap.removeLayer(layer);
705
+ } catch (error) {
706
+ // silent error just continue with order processing if any
707
+ }
708
+ });
709
+ }
710
+ }
711
+ }
712
+ }
678
713
 
679
- leafletLayers?.forEach((layer) => {
714
+ @action clearAllLayers() {
715
+ if (this.leafletMap) {
680
716
  try {
681
- leafletMap.removeLayer(layer);
717
+ this.leafletMap.eachLayer((layer) => {
718
+ this.leafletMap.removeLayer(layer);
719
+ });
682
720
  } catch (error) {
683
- // silent error just continue with order processing if any
721
+ // fallback method with tracked layers
722
+ if (isArray(this.leafletLayers)) {
723
+ this.leafletLayers.forEach((layer) => {
724
+ try {
725
+ this.leafletMap.removeLayer(layer);
726
+ } catch (error) {
727
+ // silent error just continue with order processing if any
728
+ }
729
+ });
730
+ }
684
731
  }
685
- });
732
+ }
686
733
  }
687
734
 
688
735
  @action createPlaceArrayFromPayload(payload, waypoints, isMultipleDropoffOrder = false) {
@@ -1220,7 +1267,7 @@ export default class OperationsOrdersIndexNewController extends Controller {
1220
1267
  (uploadedFile) => {
1221
1268
  entity.setProperties({
1222
1269
  photo_uuid: uploadedFile.id,
1223
- photo_url: uploadedFile.s3url,
1270
+ photo_url: uploadedFile.url,
1224
1271
  photo: uploadedFile,
1225
1272
  });
1226
1273
  }
@@ -81,6 +81,13 @@ export default class OperationsOrdersIndexViewController extends Controller {
81
81
  */
82
82
  @service hostRouter;
83
83
 
84
+ /**
85
+ * Inject the `socket` service
86
+ *
87
+ * @var {Service}
88
+ */
89
+ @service socket;
90
+
84
91
  /**
85
92
  * Inject the `universe` service
86
93
  *
@@ -162,8 +169,10 @@ export default class OperationsOrdersIndexViewController extends Controller {
162
169
  }
163
170
 
164
171
  @action resetInterface() {
165
- this.leafletMap?.liveMap?.showDrivers();
166
- this.leafletMap?.liveMap?.showRoutes();
172
+ if (this.leafletMap && this.leafletMap.liveMap) {
173
+ this.leafletMap.liveMap.showDrivers();
174
+ this.leafletMap.liveMap.showRoutes();
175
+ }
167
176
  }
168
177
 
169
178
  @action removeRoutingControlPreview() {
@@ -209,8 +218,10 @@ export default class OperationsOrdersIndexViewController extends Controller {
209
218
  return later(
210
219
  this,
211
220
  () => {
212
- this.leafletMap?.liveMap?.hideDrivers();
213
- this.leafletMap?.liveMap?.hideRoutes();
221
+ if (this.leafletMap && this.leafletMap.liveMap) {
222
+ this.leafletMap.liveMap.hideDrivers();
223
+ this.leafletMap.liveMap.hideRoutes();
224
+ }
214
225
 
215
226
  // display order route on map
216
227
  this.displayOrderRoute();
@@ -833,7 +844,7 @@ export default class OperationsOrdersIndexViewController extends Controller {
833
844
  (uploadedFile) => {
834
845
  entity.setProperties({
835
846
  photo_uuid: uploadedFile.id,
836
- photo_url: uploadedFile.s3url,
847
+ photo_url: uploadedFile.url,
837
848
  photo: uploadedFile,
838
849
  });
839
850
  }
@@ -80,6 +80,13 @@ export default class OperationsOrdersIndexController extends Controller {
80
80
  */
81
81
  @service universe;
82
82
 
83
+ /**
84
+ * Inject the `socket` service
85
+ *
86
+ * @var {Service}
87
+ */
88
+ @service socket;
89
+
83
90
  /**
84
91
  * Queryable parameters for this controller's model
85
92
  *
@@ -537,6 +544,54 @@ export default class OperationsOrdersIndexController extends Controller {
537
544
  },
538
545
  ];
539
546
 
547
+ /**
548
+ * Creates an instance of OperationsOrdersIndexController.
549
+ * @memberof OperationsOrdersIndexController
550
+ */
551
+ constructor() {
552
+ super(...arguments);
553
+ this.listenForOrderEvents();
554
+ }
555
+
556
+ /**
557
+ * Listen for incoming order events to refresh listing.
558
+ *
559
+ * @memberof OperationsOrdersIndexController
560
+ */
561
+ @action async listenForOrderEvents() {
562
+ // wait for user to be loaded into service
563
+ this.currentUser.on('user.loaded', () => {
564
+ // Get socket instance
565
+ const socket = this.socket.instance();
566
+
567
+ // The channel ID to listen on
568
+ const channelId = `company.${this.currentUser.companyId}`;
569
+
570
+ // Listed on company channel
571
+ const channel = socket.subscribe(channelId);
572
+
573
+ // Events which should trigger refresh
574
+ const listening = ['order.ready', 'order.driver_assigned'];
575
+
576
+ // Listen for channel subscription
577
+ (async () => {
578
+ for await (let output of channel) {
579
+ const { event } = output;
580
+
581
+ // if an order event refresh orders
582
+ if (typeof event === 'string' && listening.includes(event)) {
583
+ this.hostRouter.refresh();
584
+ }
585
+ }
586
+ })();
587
+
588
+ // disconnect when transitioning
589
+ this.hostRouter.on('routeWillChange', () => {
590
+ channel.close();
591
+ });
592
+ });
593
+ }
594
+
540
595
  /**
541
596
  * The search task.
542
597
  *
@@ -561,12 +616,21 @@ export default class OperationsOrdersIndexController extends Controller {
561
616
  this.query = value;
562
617
  }
563
618
 
564
- @action resetView() {
565
- const { leafletMap } = this;
619
+ @action viewPlacesOnMap() {
620
+ const {
621
+ leafletMap: { liveMap },
622
+ } = this;
566
623
 
567
- leafletMap?.liveMap?.hideDrivers();
568
- leafletMap?.liveMap?.hideRoutes();
569
- // leafletMap?.remove();
624
+ if (liveMap) {
625
+ liveMap.togglePlaces();
626
+ }
627
+ }
628
+
629
+ @action resetView() {
630
+ if (this.leafletMap && this.leafletMap.liveMap) {
631
+ this.leafletMap.liveMap.hideDrivers();
632
+ this.leafletMap.liveMap.hideRoutes();
633
+ }
570
634
  }
571
635
 
572
636
  @action toggleSearch() {
@@ -1,7 +1,6 @@
1
1
  import OperationsServiceRatesIndexNewController from './new';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { action } from '@ember/object';
4
- // import { alias } from '@ember/object/computed';
5
4
 
6
5
  export default class OperationsServiceRatesIndexEditController extends OperationsServiceRatesIndexNewController {
7
6
  /**
@@ -44,6 +43,7 @@ export default class OperationsServiceRatesIndexEditController extends Operation
44
43
  return this.transitionToRoute('operations.service-rates.index').then(() => {
45
44
  this.notifications.success(`Service rate '${serviceRate.service_name}' updated`);
46
45
  this.resetForm();
46
+ this.hostRouter.refresh();
47
47
  });
48
48
  })
49
49
  .catch(this.notifications.serverError)
@@ -1,17 +1,10 @@
1
- import Controller, { inject as controller } from '@ember/controller';
1
+ import Controller from '@ember/controller';
2
2
  import { inject as service } from '@ember/service';
3
3
  import { tracked } from '@glimmer/tracking';
4
4
  import { action, computed } from '@ember/object';
5
5
  import { isBlank } from '@ember/utils';
6
6
 
7
7
  export default class OperationsServiceRatesIndexNewController extends Controller {
8
- /**
9
- * Inject the `operations.service-rates.index` controller
10
- *
11
- * @var {Controller}
12
- */
13
- @controller('operations.service-rates.index') index;
14
-
15
8
  /**
16
9
  * Inject the `currentUser` service
17
10
  *
@@ -33,12 +26,19 @@ export default class OperationsServiceRatesIndexNewController extends Controller
33
26
  */
34
27
  @service loader;
35
28
 
29
+ /**
30
+ * Inject the `hostRouter` service
31
+ *
32
+ * @var {Service}
33
+ */
34
+ @service hostRouter;
35
+
36
36
  /**
37
37
  * The service rate being created.
38
38
  *
39
39
  * @var {ServiceRateModel}
40
40
  */
41
- @tracked serviceRate = this.store.createRecord('service-rate');
41
+ @tracked serviceRate = this.store.createRecord('service-rate', { per_meter_unit: 'm' });
42
42
 
43
43
  /**
44
44
  * Different service types available, based on order type.
@@ -300,11 +300,10 @@ export default class OperationsServiceRatesIndexNewController extends Controller
300
300
  return serviceRate
301
301
  .save()
302
302
  .then((serviceRate) => {
303
- this.index.table.addRow(serviceRate);
304
-
305
303
  return this.transitionToRoute('operations.service-rates.index').then(() => {
306
304
  this.notifications.success(`New Service Rate ${serviceRate.service_name} Created`);
307
305
  this.resetForm();
306
+ this.hostRouter.refresh();
308
307
  });
309
308
  })
310
309
  .catch((error) => {
@@ -56,6 +56,14 @@ export default class OperationsServiceRatesIndexController extends Controller {
56
56
  */
57
57
  @service modalsManager;
58
58
 
59
+
60
+ /**
61
+ * Inject the `hostRouter` service
62
+ *
63
+ * @var {Service}
64
+ */
65
+ @service hostRouter;
66
+
59
67
  /**
60
68
  * Inject the `loader` service
61
69
  *
@@ -1,8 +1,13 @@
1
1
  export function initialize() {
2
2
  const path = '/engines-dist/leaflet/';
3
- const scripts = ['leaflet.contextmenu.js', 'leaflet.draw-src.js', 'leaflet.rotatedMarker.js'];
3
+ const scripts = ['leaflet.contextmenu.js', 'leaflet.draw-src.js', 'leaflet.rotatedMarker.js', 'leaflet-drift-marker.js'];
4
4
  const stylesheets = ['leaflet.contextmenu.css', 'leaflet.draw.css'];
5
5
 
6
+ // Define exports on window
7
+ const exportsScript = document.createElement('script');
8
+ exportsScript.innerHTML = 'window.exports = window.exports || {};';
9
+ document.body.appendChild(exportsScript);
10
+
6
11
  for (let i = 0; i < scripts.length; i++) {
7
12
  const script = document.createElement('script');
8
13
  script.src = path + scripts[i];
@@ -0,0 +1,17 @@
1
+ import LeafletDriftMarkerComponent from '../components/leaflet-drift-marker';
2
+
3
+ export function initialize(owner) {
4
+ let emberLeafletService = owner.lookup('service:ember-leaflet');
5
+
6
+ if (emberLeafletService) {
7
+ // we then invoke the `registerComponent` method
8
+ emberLeafletService.registerComponent('leaflet-drift-marker', {
9
+ as: 'drift-marker',
10
+ component: LeafletDriftMarkerComponent,
11
+ });
12
+ }
13
+ }
14
+
15
+ export default {
16
+ initialize,
17
+ };
@@ -7,7 +7,9 @@ export default class OperationsOrdersIndexNewRoute extends Route {
7
7
  @service loader;
8
8
 
9
9
  @action willTransition() {
10
- this.controller?.resetForm();
10
+ if (this.controller) {
11
+ this.controller.resetForm();
12
+ }
11
13
  }
12
14
 
13
15
  async setupController(controller) {
@@ -4,7 +4,9 @@ import { action } from '@ember/object';
4
4
 
5
5
  export default class OperationsOrdersIndexViewRoute extends Route {
6
6
  @service currentUser;
7
+ @service notifications;
7
8
  @service store;
9
+ @service socket;
8
10
 
9
11
  @action willTransition() {
10
12
  if (this.controller) {
@@ -12,6 +14,11 @@ export default class OperationsOrdersIndexViewRoute extends Route {
12
14
  }
13
15
  }
14
16
 
17
+ @action error(error, transition) {
18
+ this.notifications.serverError(error);
19
+ return this.transitionTo('operations.orders.index');
20
+ }
21
+
15
22
  model(order) {
16
23
  const { public_id } = order;
17
24
 
@@ -22,6 +29,45 @@ export default class OperationsOrdersIndexViewRoute extends Route {
22
29
  });
23
30
  }
24
31
 
32
+ /**
33
+ * Handle resolved model
34
+ *
35
+ * @param {OrderModel} model
36
+ * @memberof OperationsOrdersIndexViewRoute
37
+ */
38
+ afterModel(model) {
39
+ this.listenForOrderEvents(model);
40
+ }
41
+
42
+ /**
43
+ * Listen to order channel for update events to refresh data
44
+ *
45
+ * @param {OrderModel} model
46
+ * @memberof OperationsOrdersIndexViewRoute
47
+ */
48
+ async listenForOrderEvents(model) {
49
+ // Get socket instance
50
+ const socket = this.socket.instance();
51
+
52
+ // The channel ID to listen on
53
+ const channelId = `order.${model.public_id}`;
54
+
55
+ // Listed on company channel
56
+ const channel = socket.subscribe(channelId);
57
+
58
+ // Listen for channel subscription
59
+ (async () => {
60
+ for await (let output of channel) {
61
+ this.refresh();
62
+ }
63
+ })();
64
+
65
+ // disconnect when transitioning
66
+ this.on('willTransition', () => {
67
+ channel.close();
68
+ });
69
+ }
70
+
25
71
  async setupController(controller, model) {
26
72
  super.setupController(controller, model);
27
73
 
@@ -1,12 +1,19 @@
1
1
  import Route from '@ember/routing/route';
2
2
  import { inject as service } from '@ember/service';
3
+ import { action } from '@ember/object';
3
4
 
4
5
  export default class OperationsServiceRatesIndexEditRoute extends Route {
5
6
  @service store;
6
7
  @service currentUser;
8
+ @service notifications;
7
9
 
8
10
  templateName = 'operations.service-rates.index.new';
9
11
 
12
+ @action error(error, transition) {
13
+ this.notifications.serverError(error);
14
+ return this.transitionTo('operations.service-rates.index');
15
+ }
16
+
10
17
  model({ public_id }) {
11
18
  return this.store.queryRecord('service-rate', {
12
19
  public_id,
@@ -13,7 +13,7 @@
13
13
  {{#if this.isMapLayout}}
14
14
  <MapContainer @layout={{this.layout}} @onReady={{this.onMapContainerReady}}>
15
15
  <GlobalSearch @visible={{this.isSearchVisible}} @onPressOrder={{this.viewOrder}} />
16
- <MapContainer::Toolbar @activeOrdersCount={{this.activeOrdersCount}} @map={{this.leafletMap}} @onPressViewOrders={{this.toggleOrdersPanel}} @onPressSearch={{this.toggleSearch}} @onPressCreateOrder={{this.createOrder}} @onPressCreateDriver={{this.driversController.createDriver}} @onPressDriverProfile={{this.driversController.viewDriver}} />
16
+ <MapContainer::Toolbar @activeOrdersCount={{this.activeOrdersCount}} @map={{this.leafletMap}} @onPressDisplayPlaces={{this.viewPlacesOnMap}} @onPressViewOrders={{this.toggleOrdersPanel}} @onPressSearch={{this.toggleSearch}} @onPressCreateOrder={{this.createOrder}} @onPressCreateDriver={{this.driversController.createDriver}} @onPressDriverProfile={{this.driversController.viewDriver}} />
17
17
  <OrderListOverlay @noBackdrop={{true}} @fullHeight={{true}} @outView={{not this.isOrdersPanelVisible}} @width={{if (media 'isMobile') "100%" "338px"}} @onPressCancel={{this.hideOrdersPanel}} @onPressCreateOrder={{this.createOrder}} @onPressViewOrder={{this.viewOrder}} @onPressCreateFleet={{this.fleetController.createFleet}} @onPressManageFleet={{this.fleetController.viewFleet}} @onPressCancelOrders={{this.bulkCancelOrders}} @onPressDeleteOrders={{this.bulkDeleteOrders}} />
18
18
  <LiveMap id="leafletMap" class="next-leaflet-container-map" @lat={{1.3521}} @lng={{103.8198}} @zoom={{12}} @zoomControl={{false}} @onLoad={{this.setMapReference}} {{set-container-dimensions}} />
19
19
  </MapContainer>
@@ -1,4 +1,4 @@
1
- <Overlay @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width="700">
1
+ <Overlay @position="right" @noBackdrop={{true}} @isResizable={{true}} @fullHeight={{true}} @width="800px">
2
2
  <Overlay::Header @title={{if this.serviceRate.id this.serviceRate.service_name "New service rate"}} @status={{this.serviceRate.public_id}} @hideStatusDot={{true}} @createdAt={{this.serviceRate.createdAt}} @onPressCancel={{this.transitionBack}}>
3
3
  {{#if this.serviceRate.id}}
4
4
  <Button @icon="save" @type="primary" @text="Save Service Rate" @onClick={{this.updateServiceRate}} @isLoading={{this.isUpdatingServiceRate}} @isSubscriptionRequired={{true}} />
@@ -179,7 +179,7 @@
179
179
  <div class="dark:text-gray-100">
180
180
  <div class="grid grid-cols-7">
181
181
  <div class="flex flex-col items-start justify-start pt-3">
182
- <img src="/images/boxes/{{parcelFee.size}}.png" alt="parcel size {{parcelFee.size}}" width="44" class="w-11">
182
+ <img src="/engines-dist/images/boxes/{{parcelFee.size}}.png" alt="parcel size {{parcelFee.size}}" width="44" class="w-11">
183
183
  <label class="mt-2">{{humanize parcelFee.size}}</label>
184
184
  </div>
185
185
  <div class="col-span-6 dimensions-box">
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/leaflet-drift-marker';
Binary file
Binary file
Binary file
Binary file
Binary file
package/index.js CHANGED
@@ -33,6 +33,21 @@ module.exports = buildEngine({
33
33
  { package: 'leaflet-contextmenu', include: undefined, exclude: [...alwaysExclude], path: ['dist'] },
34
34
  { package: 'leaflet-draw', include: undefined, exclude: [...alwaysExclude], path: ['dist'] },
35
35
  { package: 'leaflet-rotatedmarker', include: undefined, exclude: [...alwaysExclude], path: [] },
36
+ {
37
+ package: 'leaflet-drift-marker',
38
+ include: ['index.js', 'index.js.map'],
39
+ exclude: [...alwaysExclude],
40
+ path: ['lib'],
41
+ getDestinationPath: (relativePath) => {
42
+ if (relativePath === 'index.js') {
43
+ return 'leaflet-drift-marker.js';
44
+ }
45
+ if (relativePath === 'index.js.map') {
46
+ return 'leaflet-drift-marker.js.map';
47
+ }
48
+ return relativePath;
49
+ },
50
+ },
36
51
  ];
37
52
  const trees = [];
38
53
 
@@ -45,6 +60,7 @@ module.exports = buildEngine({
45
60
  destDir: 'leaflet',
46
61
  include: leafletAdddon.include,
47
62
  exclude: leafletAdddon.exclude,
63
+ getDestinationPath: leafletAdddon.getDestinationPath,
48
64
  })
49
65
  );
50
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Fleetbase Fleet-Ops extension provides a fleet management and last mile delivery operations system module.",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops",
@@ -39,9 +39,9 @@
39
39
  "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish"
40
40
  },
41
41
  "dependencies": {
42
- "@fleetbase/ember-core": "^0.1.1",
43
- "@fleetbase/ember-ui": "^0.1.7",
44
- "@fleetbase/fleetops-data": "^0.0.6",
42
+ "@fleetbase/ember-core": "^0.1.2",
43
+ "@fleetbase/ember-ui": "^0.1.8",
44
+ "@fleetbase/fleetops-data": "^0.0.7",
45
45
  "@fleetbase/leaflet-routing-machine": "^3.2.16",
46
46
  "@fortawesome/ember-fontawesome": "^0.4.1",
47
47
  "@fortawesome/fontawesome-svg-core": "^6.4.0",
@@ -50,16 +50,17 @@
50
50
  "@zestia/ember-dragula": "^12.0.0",
51
51
  "broccoli-funnel": "^3.0.8",
52
52
  "broccoli-merge-trees": "^4.2.0",
53
- "ember-intl": "^6.0.0-beta.6",
54
53
  "ember-cli-babel": "^7.26.11",
55
54
  "ember-cli-es6-transform": "^1.0.0",
56
55
  "ember-cli-htmlbars": "^6.1.0",
56
+ "ember-intl": "^6.0.0-beta.6",
57
57
  "ember-leaflet": "^5.1.1",
58
58
  "ember-radio-button": "^3.0.0-beta.1",
59
59
  "ember-wormhole": "^0.6.0",
60
60
  "leaflet": "^1.9.4",
61
61
  "leaflet-contextmenu": "^1.4.0",
62
62
  "leaflet-draw": "^1.0.4",
63
+ "leaflet-drift-marker": "^2.0.0",
63
64
  "leaflet-rotatedmarker": "^0.2.0"
64
65
  },
65
66
  "devDependencies": {