@fleetbase/fleetops-engine 0.2.2 → 0.2.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.
Files changed (101) hide show
  1. package/addon/components/driver-form-panel.hbs +131 -0
  2. package/addon/components/driver-form-panel.js +39 -0
  3. package/addon/components/driver-panel/details.hbs +48 -0
  4. package/addon/components/driver-panel.hbs +60 -0
  5. package/addon/components/driver-panel.js +57 -0
  6. package/addon/components/fleet-form-panel.hbs +46 -0
  7. package/addon/components/fleet-form-panel.js +3 -0
  8. package/addon/components/fleet-panel.hbs +70 -0
  9. package/addon/components/fleet-panel.js +43 -0
  10. package/addon/components/fleet-vehicle-listing.hbs +73 -0
  11. package/addon/components/fleet-vehicle-listing.js +117 -0
  12. package/addon/components/leaflet-drift-marker.js +53 -0
  13. package/addon/components/live-map.hbs +4 -4
  14. package/addon/components/live-map.js +104 -34
  15. package/addon/components/modals/fleet-details.hbs +1 -0
  16. package/addon/components/settings-window.hbs +1 -1
  17. package/addon/components/vehicle-form-panel.hbs +131 -0
  18. package/addon/components/vehicle-form-panel.js +39 -0
  19. package/addon/components/vehicle-panel/details.hbs +34 -0
  20. package/addon/components/vehicle-panel/tracking.hbs +1 -0
  21. package/addon/components/vehicle-panel.hbs +60 -0
  22. package/addon/components/vehicle-panel.js +57 -0
  23. package/addon/controllers/management/drivers/index/details.js +8 -0
  24. package/addon/controllers/management/drivers/index/edit.js +80 -0
  25. package/addon/controllers/management/drivers/index/new.js +128 -0
  26. package/addon/controllers/management/drivers/index.js +212 -202
  27. package/addon/controllers/management/fleets/index/details.js +8 -0
  28. package/addon/controllers/management/fleets/index/edit.js +77 -0
  29. package/addon/controllers/management/fleets/index/new.js +103 -0
  30. package/addon/controllers/management/fleets/index.js +49 -77
  31. package/addon/controllers/management/vehicles/index/details.js +12 -0
  32. package/addon/controllers/management/vehicles/index/edit.js +8 -0
  33. package/addon/controllers/management/vehicles/index/new.js +21 -0
  34. package/addon/controllers/management/vehicles/index.js +6 -129
  35. package/addon/controllers/operations/orders/index/new.js +67 -20
  36. package/addon/controllers/operations/orders/index/view.js +15 -4
  37. package/addon/controllers/operations/orders/index.js +58 -7
  38. package/addon/controllers/operations/service-rates/index/edit.js +1 -1
  39. package/addon/controllers/operations/service-rates/index/new.js +10 -11
  40. package/addon/controllers/operations/service-rates/index.js +7 -0
  41. package/addon/engine.js +6 -0
  42. package/addon/initializers/load-leaflet-assets.js +6 -1
  43. package/addon/instance-initializers/register-leaflet-drift-marker.js +17 -0
  44. package/addon/routes/management/drivers/index/details.js +9 -0
  45. package/addon/routes/management/drivers/index/edit.js +13 -0
  46. package/addon/routes/management/drivers/index/new.js +3 -0
  47. package/addon/routes/management/fleets/index/details.js +9 -0
  48. package/addon/routes/management/fleets/index/edit.js +9 -0
  49. package/addon/routes/management/fleets/index/new.js +3 -0
  50. package/addon/routes/management/fleets/index.js +1 -0
  51. package/addon/routes/management/vehicles/index/details.js +18 -0
  52. package/addon/routes/management/vehicles/index/edit.js +14 -0
  53. package/addon/routes/management/vehicles/index/new.js +3 -0
  54. package/addon/routes/operations/orders/index/new.js +3 -1
  55. package/addon/routes/operations/orders/index/view.js +50 -0
  56. package/addon/routes/operations/service-rates/index/edit.js +7 -0
  57. package/addon/routes.js +15 -3
  58. package/addon/templates/management/drivers/index/details.hbs +1 -0
  59. package/addon/templates/management/drivers/index/edit.hbs +1 -0
  60. package/addon/templates/management/drivers/index/new.hbs +1 -0
  61. package/addon/templates/management/drivers/index.hbs +3 -1
  62. package/addon/templates/management/fleets/index/details.hbs +1 -0
  63. package/addon/templates/management/fleets/index/edit.hbs +1 -0
  64. package/addon/templates/management/fleets/index/new.hbs +1 -0
  65. package/addon/templates/management/vehicles/index/details.hbs +1 -0
  66. package/addon/templates/management/vehicles/index/edit.hbs +1 -0
  67. package/addon/templates/management/vehicles/index/new.hbs +1 -0
  68. package/addon/templates/operations/orders/index/new.hbs +1 -1
  69. package/addon/templates/operations/service-rates/index/new.hbs +4 -4
  70. package/app/components/driver-form-panel.js +1 -0
  71. package/app/components/driver-panel/details.js +1 -0
  72. package/app/components/driver-panel.js +1 -0
  73. package/app/components/fleet-form-panel.js +1 -0
  74. package/app/components/fleet-panel.js +1 -0
  75. package/app/components/fleet-vehicle-listing.js +1 -0
  76. package/app/components/leaflet-drift-marker.js +1 -0
  77. package/app/components/vehicle-form-panel.js +1 -0
  78. package/app/components/vehicle-panel/details.js +1 -0
  79. package/app/components/vehicle-panel/tracking.js +1 -0
  80. package/app/components/vehicle-panel.js +1 -0
  81. package/app/controllers/management/drivers/index/new.js +1 -0
  82. package/app/controllers/management/vehicles/index/details.js +1 -0
  83. package/app/controllers/management/vehicles/index/new.js +1 -0
  84. package/app/routes/management/drivers/index/details.js +1 -0
  85. package/app/routes/management/drivers/index/edit.js +1 -0
  86. package/app/routes/management/drivers/index/new.js +1 -0
  87. package/app/routes/management/vehicles/index/details.js +1 -0
  88. package/app/routes/management/vehicles/index/edit.js +1 -0
  89. package/app/routes/management/vehicles/index/new.js +1 -0
  90. package/app/templates/management/drivers/index/details.js +1 -0
  91. package/app/templates/management/drivers/index/edit.js +1 -0
  92. package/app/templates/management/drivers/index/new.js +1 -0
  93. package/app/templates/management/vehicles/index/details.js +1 -0
  94. package/app/templates/management/vehicles/index/edit.js +1 -0
  95. package/app/templates/management/vehicles/index/new.js +1 -0
  96. package/assets/images/boxes/large.png +0 -0
  97. package/assets/images/boxes/medium.png +0 -0
  98. package/assets/images/boxes/small.png +0 -0
  99. package/assets/images/boxes/x-large.png +0 -0
  100. package/index.js +16 -0
  101. package/package.json +6 -5
@@ -0,0 +1,117 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action, computed } from '@ember/object';
5
+ import { isArray } from '@ember/array';
6
+ import { isBlank } from '@ember/utils';
7
+ import { timeout } from 'ember-concurrency';
8
+ import { task } from 'ember-concurrency-decorators';
9
+
10
+ export default class FleetVehicleListingComponent extends Component {
11
+ @service store;
12
+ @service appCache;
13
+ @tracked vehicles = [];
14
+ @tracked selected = [];
15
+ @tracked query;
16
+ @tracked isLoading = false;
17
+ @tracked isLoaded = false;
18
+
19
+ @computed('selected.[]') get ids() {
20
+ return this.selected.map((selection) => selection.id);
21
+ }
22
+
23
+ @action async setupComponent() {
24
+ const { selected, fleet } = this.args;
25
+
26
+ this.selected = isArray(selected) && selected.length ? selected : [];
27
+ this.vehicles = await this.queryFleetVehicles(fleet);
28
+ }
29
+
30
+ @action setSelected() {
31
+ const { selected } = this.args;
32
+
33
+ if (isArray(selected) && selected.length) {
34
+ this.selected = selected;
35
+ }
36
+ }
37
+
38
+ @action select(option, selected) {
39
+ if (selected) {
40
+ this.selected.pushObject(option);
41
+ } else {
42
+ this.selected.removeObject(option);
43
+ }
44
+
45
+ option.setProperties({ selected });
46
+
47
+ if (typeof this.args.onChange === 'function') {
48
+ this.args.onChange(this.selected);
49
+ }
50
+ }
51
+
52
+ @action onAdd(vehicle) {
53
+ if (typeof this.args.onRemoveVehicle === 'function') {
54
+ this.args.onAddVehicle(vehicle);
55
+ }
56
+
57
+ const index = this.vehicles.findIndex((d) => d.id === vehicle.id);
58
+
59
+ if (index === -1) {
60
+ this.vehicles.pushObject(vehicle);
61
+ }
62
+ }
63
+
64
+ @action onRemove(vehicle) {
65
+ if (typeof this.args.onRemoveVehicle === 'function') {
66
+ this.args.onRemoveVehicle(vehicle);
67
+ }
68
+
69
+ this.vehicles.removeObject(vehicle);
70
+ }
71
+
72
+ @action queryFleetVehicles(fleet, query = null) {
73
+ this.isLoading = true;
74
+
75
+ const cacheKey = `${fleet.id}:fleetVehicles`;
76
+ // const cachedResults = this.appCache.getEmberData(cacheKey, 'vehicle');
77
+
78
+ // if (cachedResults) {
79
+ // this.vehicles = this.toggleSelected(cachedResults);
80
+ // this.isLoaded = true;
81
+ // }
82
+
83
+ return new Promise((resolve) => {
84
+ this.store.query('vehicle', { query, fleet: fleet.id }).then((vehicles) => {
85
+ this.isLoading = false;
86
+ this.isLoaded = true;
87
+
88
+ // save to cache
89
+ if (!query) {
90
+ this.appCache.setEmberData(cacheKey, vehicles);
91
+ }
92
+
93
+ resolve(this.toggleSelected(vehicles));
94
+ });
95
+ });
96
+ }
97
+
98
+ @action toggleSelected(options = []) {
99
+ return options.map((option) => {
100
+ option.set('selected', this.ids.includes(option.id));
101
+
102
+ return option;
103
+ });
104
+ }
105
+
106
+ @task({ restartable: true }) *search({ target: { value } }) {
107
+ const { fleet } = this.args;
108
+
109
+ if (isBlank(value)) {
110
+ return;
111
+ }
112
+
113
+ yield timeout(300);
114
+
115
+ this.vehicles = yield this.queryFleetVehicles(fleet, value);
116
+ }
117
+ }
@@ -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,18 +4,18 @@
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 32 32)}} @location={{point-to-coordinates driver.location}} @onAdd={{fn this.onAction "onDriverAdded" driver}} as |marker|>
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
8
  <marker.popup @maxWidth="500" @minWidth="225">
9
9
  <DriverCard @driver={{driver}} @showCoordinates={{true}} @textClass="text-gray-900 dark:text-gray-100" />
10
10
  </marker.popup>
11
- <marker.tooltip @permanent={{true}} @sticky={{true}}>{{driver.name}}</marker.tooltip>
12
- </layers.rotated-marker>
11
+ <marker.tooltip @permanent={{false}} @sticky={{true}}>{{driver.name}}</marker.tooltip>
12
+ </layers.drift-marker>
13
13
  {{/each}}
14
14
  {{/if}}
15
15
 
16
16
  {{#if this.isPlacesVisible}}
17
17
  {{#each this.places as |place|}}
18
- <layers.marker @icon={{icon iconUrl="/engines-dist/images/building-marker.png" iconSize=(array 32 32)}} @location={{point-to-coordinates place.location}} @riseOnHover={{true}} @title={{place.address}} @alt={{place.address}} as |marker|>
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
19
  <marker.popup>
20
20
  <div>{{place.address}}</div>
21
21
  <div>{{format-point place.location}}</div>
@@ -112,6 +112,7 @@ export default class LiveMapComponent extends Component {
112
112
  this.isReady = true;
113
113
 
114
114
  this.watchDrivers(this.drivers);
115
+ this.listenForOrders();
115
116
 
116
117
  if (typeof this.args.onReady === 'function') {
117
118
  this.args.onReady(this);
@@ -334,6 +335,11 @@ export default class LiveMapComponent extends Component {
334
335
  @action onDriverAdded(driver, event) {
335
336
  const { target } = event;
336
337
 
338
+ // set the marker instance to the driver model
339
+ set(driver, '_marker', target);
340
+
341
+ console.log('onDriverAdded()', ...arguments);
342
+
337
343
  this.createContextMenuForDriver(driver, target);
338
344
  }
339
345
 
@@ -721,6 +727,30 @@ export default class LiveMapComponent extends Component {
721
727
  * Functions are used to fetch date or handle socket callbacks/initializations.
722
728
  *
723
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
+
724
754
  @action watchDrivers(drivers = []) {
725
755
  // setup socket
726
756
  const socket = this.socket.instance();
@@ -734,29 +764,54 @@ export default class LiveMapComponent extends Component {
734
764
 
735
765
  @action async listenForDriver(driver, socket) {
736
766
  // listen on company channel
737
- const channelId = `driver.${driver.uuid}`;
767
+ const channelId = `driver.${driver.id}`;
738
768
  const channel = socket.subscribe(channelId);
739
769
 
740
- // debug
741
- // console.log(`socket subscribed to ${channelId}`);
742
-
743
770
  // track channel
744
771
  this.channels.pushObject(channel);
745
772
 
746
773
  // listen to channel for events
747
774
  await channel.listener('subscribe').once();
748
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
+
749
804
  // get incoming data and console out
750
- for await (let output of channel) {
751
- const { event, data } = output;
752
-
753
- if (event === 'driver.location_changed') {
754
- driver.location = data.location;
755
- driver.altitude = data.altitude;
756
- driver.heading = data.heading;
757
- driver.speed = data.speed;
805
+ (async () => {
806
+ for await (let output of channel) {
807
+ const { event } = 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
+ }
758
813
  }
759
- }
814
+ })();
760
815
  }
761
816
 
762
817
  @action closeChannels() {
@@ -829,15 +884,20 @@ export default class LiveMapComponent extends Component {
829
884
  this.isLoading = true;
830
885
 
831
886
  return new Promise((resolve) => {
832
- this.fetch.get('fleet-ops/live/routes').then((routes) => {
833
- this.isLoading = false;
887
+ this.fetch
888
+ .get('fleet-ops/live/routes')
889
+ .then((routes) => {
890
+ this.isLoading = false;
834
891
 
835
- if (typeof this.args.onRoutesLoaded === 'function') {
836
- this.args.onRoutesLoaded(routes);
837
- }
892
+ if (typeof this.args.onRoutesLoaded === 'function') {
893
+ this.args.onRoutesLoaded(routes);
894
+ }
838
895
 
839
- resolve(routes);
840
- });
896
+ resolve(routes);
897
+ })
898
+ .catch(() => {
899
+ resolve([]);
900
+ });
841
901
  });
842
902
  }
843
903
 
@@ -845,15 +905,20 @@ export default class LiveMapComponent extends Component {
845
905
  this.isLoading = true;
846
906
 
847
907
  return new Promise((resolve) => {
848
- this.fetch.get('fleet-ops/live/drivers', {}, { normalizeToEmberData: true, normalizeModelType: 'driver' }).then((drivers) => {
849
- this.isLoading = false;
908
+ this.fetch
909
+ .get('fleet-ops/live/drivers', {}, { normalizeToEmberData: true, normalizeModelType: 'driver' })
910
+ .then((drivers) => {
911
+ this.isLoading = false;
850
912
 
851
- if (typeof this.args.onDriversLoaded === 'function') {
852
- this.args.onDriversLoaded(drivers);
853
- }
913
+ if (typeof this.args.onDriversLoaded === 'function') {
914
+ this.args.onDriversLoaded(drivers);
915
+ }
854
916
 
855
- resolve(drivers);
856
- });
917
+ resolve(drivers);
918
+ })
919
+ .catch(() => {
920
+ resolve([]);
921
+ });
857
922
  });
858
923
  }
859
924
 
@@ -864,15 +929,20 @@ export default class LiveMapComponent extends Component {
864
929
  const center = this.leafletMap.getCenter();
865
930
 
866
931
  return new Promise((resolve) => {
867
- this.fetch.get('fleet-ops/live/places', { within: center }, { normalizeToEmberData: true, normalizeModelType: 'place' }).then((places) => {
868
- this.isLoading = false;
932
+ this.fetch
933
+ .get('fleet-ops/live/places', { within: center }, { normalizeToEmberData: true, normalizeModelType: 'place' })
934
+ .then((places) => {
935
+ this.isLoading = false;
869
936
 
870
- if (typeof this.args.onPlacesLoaded === 'function') {
871
- this.args.onPlacesLoaded(places);
872
- }
937
+ if (typeof this.args.onPlacesLoaded === 'function') {
938
+ this.args.onPlacesLoaded(places);
939
+ }
873
940
 
874
- resolve(places);
875
- });
941
+ resolve(places);
942
+ })
943
+ .catch(() => {
944
+ resolve([]);
945
+ });
876
946
  });
877
947
  }
878
948
 
@@ -27,5 +27,6 @@
27
27
  </div>
28
28
  </div>
29
29
  <FleetDriverListing @fleet={{@options.fleet}} @onAddDriver={{@options.addDriver}} @onRemoveDriver={{@options.removeDriver}} />
30
+ <FleetVehicleListing @fleet={{@options.fleet}} @onAddVehicle={{@options.addVehicle}} @onRemoveVehicle={{@options.removeVehicle}} />
30
31
  </div>
31
32
  </Modal::Default>
@@ -1,6 +1,6 @@
1
1
  <Overlay @onLoad={{this.onLoad}} @isOpen={{@isOpen}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @isResizable={{true}} @isMaximizable={{true}} @isMinimizable={{true}} as |overlay|>
2
2
  <Overlay::Header @overlay={{overlay}} @title="FleetOps Settings" @onPressCancel={{@onPressCancel}}>
3
- <Button @icon="save" @type="primary" @text={{if (media 'isMobile') "" "Save Changes"}} @onClick={{this.saveSettings}} @isLoading={{this.isSaving}} @disabled={{or this.isLoading this.isSaving}} @isSubscriptionRequired={{true}} />
3
+ <Button @icon="save" @type="primary" @text={{if (media 'isMobile') "" "Save Changes"}} @onClick={{this.saveSettings}} @isLoading={{this.isSaving}} @disabled={{or this.isLoading this.isSaving}} />
4
4
  </Overlay::Header>
5
5
 
6
6
  <Overlay::Body>
@@ -0,0 +1,131 @@
1
+ <Overlay @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width="700">
2
+ <Overlay::Header @hideLeftSection={{true}} @actionsWrapperClass="flex-1 flex-col py-3" class="h-auto-i min-h-[127px]">
3
+ <div class="flex flex-row items-center justify-between w-full mb-4">
4
+ <div class="flex flex-1 space-x-2">
5
+ {{#if @vehicle.id}}
6
+ <Button @type="default" @icon="car" @helpText="View vehicle details" @onClick={{this.viewDetails}} />
7
+ {{/if}}
8
+ </div>
9
+ <div class="flex flex-1 justify-end">
10
+ <div class="mr-2">
11
+ <Button @icon={{if @vehicle.id "save" "check"}} @type="primary" @text={{if @vehicle.id "Save Vehicle" "Create Vehicle"}} @onClick={{this.save}} />
12
+ </div>
13
+ <Button @type="default" @icon="times" @helpText={{if @vehicle.id "Cancel edit vehicle" "Cancel new vehicle"}} @onClick={{@onPressCancel}} />
14
+ </div>
15
+ </div>
16
+ <div class="flex flex-row justify-between w-full">
17
+ <div class="flex flex-col flex-1 w-3/4">
18
+ <div class="flex flex-row">
19
+ <div class="w-14 flex items-center justify-center">
20
+ <Image src={{@vehicle.photo_url}} @fallbackSrc={{config "defaultValues.vehicleImage"}} alt={{@vehicle.name}} height="48" width="48" class="h-12 w-12" />
21
+ <Attach::Tooltip @class="clean" @animation="scale" @placement={{"top"}}>
22
+ <InputInfo @text={{@vehicle.public_id}} />
23
+ </Attach::Tooltip>
24
+ </div>
25
+ <div class="flex flex-col">
26
+ <h1 class="text-gray-900 dark:text-white text-2xl">
27
+ {{#if @vehicle.id}}
28
+ {{@vehicle.displayName}}
29
+ {{else}}
30
+ {{#if @vehicle.displayName}}
31
+ {{@vehicle.displayName}}
32
+ {{else}}
33
+ <span>New Vehicle</span>
34
+ {{/if}}
35
+ {{/if}}
36
+ </h1>
37
+ <div class="-mt-1">
38
+ {{#if @vehicle.driver}}
39
+ <div class="flex flex-row items-center">
40
+ <span class="text-sm dark:text-gray-500 text-gray-700 mr-3">{{@vehicle.driver.name}}</span>
41
+ </div>
42
+ {{else}}
43
+ <div class="flex flex-row items-center">
44
+ <span class="text-sm dark:text-gray-500 text-gray-700 mr-3">No driver assigned</span>
45
+ </div>
46
+ {{/if}}
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div class="flex justify-end w-1/4">
52
+ <Badge @status={{@vehicle.status}} />
53
+ </div>
54
+ </div>
55
+ </Overlay::Header>
56
+
57
+ <Overlay::Body @wrapperClass="new-service-rate-overlay-body px-4 space-y-4 pt-4" @increaseInnerBodyHeightBy={{1000}}>
58
+ <div class="flex-1 space-y-4">
59
+ <ContentPanel @title="Details" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
60
+ <div class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-2">
61
+ <div class="input-group mb-2">
62
+ <label>
63
+ Internal ID
64
+ </label>
65
+ <Input @value={{@vehicle.internal_id}} @type="text" class="w-full form-input" placeholder="Internal ID" />
66
+ </div>
67
+
68
+ <div class="input-group mb-2">
69
+ <label>
70
+ Plate Number
71
+ </label>
72
+ <Input @value={{@vehicle.plate_number}} @type="text" class="w-full form-input" placeholder="Plate Number" />
73
+ </div>
74
+
75
+ <div class="input-group mb-2">
76
+ <label>
77
+ VIN Number
78
+ </label>
79
+ <Input @value={{@vehicle.vin}} @type="text" class="w-full form-input" placeholder="VIN Number" />
80
+ </div>
81
+
82
+ <div class="input-group mb-2">
83
+ <label>
84
+ Make
85
+ </label>
86
+ <Input @value={{@vehicle.make}} @type="text" class="w-full form-input" placeholder="Vehicle Make" />
87
+ </div>
88
+
89
+ <div class="input-group mb-2">
90
+ <label>
91
+ Model
92
+ </label>
93
+ <Input @value={{@vehicle.model}} @type="text" class="w-full form-input" placeholder="Vehicle Model" />
94
+ </div>
95
+
96
+ <div class="input-group mb-2">
97
+ <label>
98
+ Year
99
+ </label>
100
+ <Input @value={{@vehicle.year}} @type="text" class="w-full form-input" placeholder="Vehicle Year" />
101
+ </div>
102
+
103
+ <div class="input-group mb-2">
104
+ <label>
105
+ Driver Assigned
106
+ </label>
107
+ <ModelSelect @modelName="driver" @selectedModel={{@vehicle.driver}} @placeholder="Select Driver" @triggerClass="form-select form-input" @infiniteScroll={{false}} @renderInPlace={{true}} @onChange={{fn (mut
108
+ @vehicle.driver)}} as |model|>
109
+ {{model.name}}
110
+ </ModelSelect>
111
+ </div>
112
+ </div>
113
+ </ContentPanel>
114
+
115
+ <ContentPanel @title="Avatar" @open={{false}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
116
+ <div class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-2">
117
+ <div class="input-group mb-2">
118
+ <InputLabel @labelText="Select map avatar" @helpText="Select an avatar to be used for rendering on maps when using geolocation tracking features." />
119
+ <FetchSelect class="w-60" @placeholder="Select avatar" @path="vehicles/avatars" @value={{@vehicle.avatar_url}} @onSelect={{fn (mut
120
+ @vehicle.avatar_url)}} as |option key|>
121
+ {{titleize (humanize key)}}
122
+ </FetchSelect>
123
+ </div>
124
+ <div>
125
+ <img alt={{@vehicle.public_id}} src={{@vehicle.avatar_url}} class="w-24 h-24" />
126
+ </div>
127
+ </div>
128
+ </ContentPanel>
129
+ </div>
130
+ </Overlay::Body>
131
+ </Overlay>
@@ -0,0 +1,39 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { action } from '@ember/object';
4
+
5
+ export default class VehicleFormPanelComponent extends Component {
6
+ @service store;
7
+ @service notifications;
8
+ @service hostRouter;
9
+ @service loader;
10
+
11
+ @action save() {
12
+ const { vehicle, onAfterSave } = this.args;
13
+
14
+ this.loader.showLoader('.overlay-inner-content', 'Saving vehicle...');
15
+
16
+ try {
17
+ return vehicle
18
+ .save()
19
+ .then((vehicle) => {
20
+ this.notifications.success(`Vehicle (${vehicle.displayName}) saved successfully.`);
21
+
22
+ if (typeof onAfterSave === 'function') {
23
+ onAfterSave(vehicle);
24
+ }
25
+ })
26
+ .catch(this.notifications.serverError)
27
+ .finally(() => {
28
+ this.loader.removeLoader();
29
+ });
30
+ } catch (error) {
31
+ this.loader.removeLoader();
32
+ }
33
+ }
34
+
35
+ @action viewDetails() {
36
+ const { vehicle } = this.args;
37
+ return this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details', vehicle.public_id);
38
+ }
39
+ }
@@ -0,0 +1,34 @@
1
+ <div class="p-4">
2
+ <ContentPanel @title="Details" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-900">
3
+ <div class="grid grid-cols-2 gap-2 text-xs md:mb-6 dark:text-gray-100">
4
+ <div class="field-info-container">
5
+ <div class="field-name">Name</div>
6
+ <div class="field-value">{{n-a @vehicle.display_name}}</div>
7
+ </div>
8
+ <div class="field-info-container">
9
+ <div class="field-name">Internal ID</div>
10
+ <div class="field-value">{{n-a @vehicle.internal_id}}</div>
11
+ </div>
12
+ <div class="field-info-container">
13
+ <div class="field-name">Plate Number</div>
14
+ <div class="field-value">{{n-a @vehicle.plate_number}}</div>
15
+ </div>
16
+ <div class="field-info-container">
17
+ <div class="field-name">VIN Number</div>
18
+ <div class="field-value">{{n-a @vehicle.vin}}</div>
19
+ </div>
20
+ <div class="field-info-container">
21
+ <div class="field-name">Make</div>
22
+ <div class="field-value">{{n-a @vehicle.name}}</div>
23
+ </div>
24
+ <div class="field-info-container">
25
+ <div class="field-name">Model</div>
26
+ <div class="field-value">{{n-a @vehicle.model}}</div>
27
+ </div>
28
+ <div class="field-info-container">
29
+ <div class="field-name">Year</div>
30
+ <div class="field-value">{{n-a @vehicle.year}}</div>
31
+ </div>
32
+ </div>
33
+ </ContentPanel>
34
+ </div>
@@ -0,0 +1 @@
1
+ {{yield}}