@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.
- package/addon/components/driver-form-panel.hbs +131 -0
- package/addon/components/driver-form-panel.js +39 -0
- package/addon/components/driver-panel/details.hbs +48 -0
- package/addon/components/driver-panel.hbs +60 -0
- package/addon/components/driver-panel.js +57 -0
- package/addon/components/fleet-form-panel.hbs +46 -0
- package/addon/components/fleet-form-panel.js +3 -0
- package/addon/components/fleet-panel.hbs +70 -0
- package/addon/components/fleet-panel.js +43 -0
- package/addon/components/fleet-vehicle-listing.hbs +73 -0
- package/addon/components/fleet-vehicle-listing.js +117 -0
- package/addon/components/leaflet-drift-marker.js +53 -0
- package/addon/components/live-map.hbs +4 -4
- package/addon/components/live-map.js +104 -34
- package/addon/components/modals/fleet-details.hbs +1 -0
- package/addon/components/settings-window.hbs +1 -1
- package/addon/components/vehicle-form-panel.hbs +131 -0
- package/addon/components/vehicle-form-panel.js +39 -0
- package/addon/components/vehicle-panel/details.hbs +34 -0
- package/addon/components/vehicle-panel/tracking.hbs +1 -0
- package/addon/components/vehicle-panel.hbs +60 -0
- package/addon/components/vehicle-panel.js +57 -0
- package/addon/controllers/management/drivers/index/details.js +8 -0
- package/addon/controllers/management/drivers/index/edit.js +80 -0
- package/addon/controllers/management/drivers/index/new.js +128 -0
- package/addon/controllers/management/drivers/index.js +212 -202
- package/addon/controllers/management/fleets/index/details.js +8 -0
- package/addon/controllers/management/fleets/index/edit.js +77 -0
- package/addon/controllers/management/fleets/index/new.js +103 -0
- package/addon/controllers/management/fleets/index.js +49 -77
- package/addon/controllers/management/vehicles/index/details.js +12 -0
- package/addon/controllers/management/vehicles/index/edit.js +8 -0
- package/addon/controllers/management/vehicles/index/new.js +21 -0
- package/addon/controllers/management/vehicles/index.js +6 -129
- package/addon/controllers/operations/orders/index/new.js +67 -20
- package/addon/controllers/operations/orders/index/view.js +15 -4
- package/addon/controllers/operations/orders/index.js +58 -7
- package/addon/controllers/operations/service-rates/index/edit.js +1 -1
- package/addon/controllers/operations/service-rates/index/new.js +10 -11
- package/addon/controllers/operations/service-rates/index.js +7 -0
- package/addon/engine.js +6 -0
- package/addon/initializers/load-leaflet-assets.js +6 -1
- package/addon/instance-initializers/register-leaflet-drift-marker.js +17 -0
- package/addon/routes/management/drivers/index/details.js +9 -0
- package/addon/routes/management/drivers/index/edit.js +13 -0
- package/addon/routes/management/drivers/index/new.js +3 -0
- package/addon/routes/management/fleets/index/details.js +9 -0
- package/addon/routes/management/fleets/index/edit.js +9 -0
- package/addon/routes/management/fleets/index/new.js +3 -0
- package/addon/routes/management/fleets/index.js +1 -0
- package/addon/routes/management/vehicles/index/details.js +18 -0
- package/addon/routes/management/vehicles/index/edit.js +14 -0
- package/addon/routes/management/vehicles/index/new.js +3 -0
- package/addon/routes/operations/orders/index/new.js +3 -1
- package/addon/routes/operations/orders/index/view.js +50 -0
- package/addon/routes/operations/service-rates/index/edit.js +7 -0
- package/addon/routes.js +15 -3
- package/addon/templates/management/drivers/index/details.hbs +1 -0
- package/addon/templates/management/drivers/index/edit.hbs +1 -0
- package/addon/templates/management/drivers/index/new.hbs +1 -0
- package/addon/templates/management/drivers/index.hbs +3 -1
- package/addon/templates/management/fleets/index/details.hbs +1 -0
- package/addon/templates/management/fleets/index/edit.hbs +1 -0
- package/addon/templates/management/fleets/index/new.hbs +1 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -0
- package/addon/templates/management/vehicles/index/edit.hbs +1 -0
- package/addon/templates/management/vehicles/index/new.hbs +1 -0
- package/addon/templates/operations/orders/index/new.hbs +1 -1
- package/addon/templates/operations/service-rates/index/new.hbs +4 -4
- package/app/components/driver-form-panel.js +1 -0
- package/app/components/driver-panel/details.js +1 -0
- package/app/components/driver-panel.js +1 -0
- package/app/components/fleet-form-panel.js +1 -0
- package/app/components/fleet-panel.js +1 -0
- package/app/components/fleet-vehicle-listing.js +1 -0
- package/app/components/leaflet-drift-marker.js +1 -0
- package/app/components/vehicle-form-panel.js +1 -0
- package/app/components/vehicle-panel/details.js +1 -0
- package/app/components/vehicle-panel/tracking.js +1 -0
- package/app/components/vehicle-panel.js +1 -0
- package/app/controllers/management/drivers/index/new.js +1 -0
- package/app/controllers/management/vehicles/index/details.js +1 -0
- package/app/controllers/management/vehicles/index/new.js +1 -0
- package/app/routes/management/drivers/index/details.js +1 -0
- package/app/routes/management/drivers/index/edit.js +1 -0
- package/app/routes/management/drivers/index/new.js +1 -0
- package/app/routes/management/vehicles/index/details.js +1 -0
- package/app/routes/management/vehicles/index/edit.js +1 -0
- package/app/routes/management/vehicles/index/new.js +1 -0
- package/app/templates/management/drivers/index/details.js +1 -0
- package/app/templates/management/drivers/index/edit.js +1 -0
- package/app/templates/management/drivers/index/new.js +1 -0
- package/app/templates/management/vehicles/index/details.js +1 -0
- package/app/templates/management/vehicles/index/edit.js +1 -0
- package/app/templates/management/vehicles/index/new.js +1 -0
- package/assets/images/boxes/large.png +0 -0
- package/assets/images/boxes/medium.png +0 -0
- package/assets/images/boxes/small.png +0 -0
- package/assets/images/boxes/x-large.png +0 -0
- package/index.js +16 -0
- 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.
|
|
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={{
|
|
12
|
-
</layers.
|
|
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
|
|
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.
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
driver.
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
|
833
|
-
|
|
887
|
+
this.fetch
|
|
888
|
+
.get('fleet-ops/live/routes')
|
|
889
|
+
.then((routes) => {
|
|
890
|
+
this.isLoading = false;
|
|
834
891
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
892
|
+
if (typeof this.args.onRoutesLoaded === 'function') {
|
|
893
|
+
this.args.onRoutesLoaded(routes);
|
|
894
|
+
}
|
|
838
895
|
|
|
839
|
-
|
|
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
|
|
849
|
-
|
|
908
|
+
this.fetch
|
|
909
|
+
.get('fleet-ops/live/drivers', {}, { normalizeToEmberData: true, normalizeModelType: 'driver' })
|
|
910
|
+
.then((drivers) => {
|
|
911
|
+
this.isLoading = false;
|
|
850
912
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
913
|
+
if (typeof this.args.onDriversLoaded === 'function') {
|
|
914
|
+
this.args.onDriversLoaded(drivers);
|
|
915
|
+
}
|
|
854
916
|
|
|
855
|
-
|
|
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
|
|
868
|
-
|
|
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
|
-
|
|
871
|
-
|
|
872
|
-
|
|
937
|
+
if (typeof this.args.onPlacesLoaded === 'function') {
|
|
938
|
+
this.args.onPlacesLoaded(places);
|
|
939
|
+
}
|
|
873
940
|
|
|
874
|
-
|
|
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}}
|
|
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}}
|