@fleetbase/fleetops-engine 0.3.2 → 0.3.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.
- package/addon/components/admin/visibility-controls.js +1 -0
- package/addon/components/layout/fleet-ops-sidebar.js +2 -1
- package/addon/components/live-map-drawer/driver-listing.hbs +6 -0
- package/addon/components/live-map-drawer/driver-listing.js +240 -0
- package/addon/components/live-map-drawer/place-listing.hbs +6 -0
- package/addon/components/live-map-drawer/place-listing.js +204 -0
- package/addon/components/live-map-drawer/vehicle-listing.hbs +6 -0
- package/addon/components/live-map-drawer/vehicle-listing.js +216 -0
- package/addon/components/live-map-drawer.hbs +17 -0
- package/addon/components/live-map-drawer.js +104 -0
- package/addon/components/live-map.hbs +5 -1
- package/addon/components/live-map.js +107 -15
- package/addon/components/map-container/toolbar.hbs +6 -0
- package/addon/components/modals/order-event.hbs +1 -1
- package/addon/components/order/schedule-card.hbs +4 -4
- package/addon/components/order/schedule-card.js +13 -6
- package/addon/components/place-panel/details.hbs +2 -2
- package/addon/controllers/operations/orders/index.js +29 -15
- package/addon/controllers/operations/scheduler/index.js +45 -30
- package/addon/routes/operations/scheduler/index.js +25 -0
- package/addon/styles/fleetops-engine.css +28 -0
- package/addon/templates/operations/orders/index.hbs +2 -2
- package/addon/templates/operations/scheduler/index.hbs +33 -9
- package/addon/utils/create-full-calendar-event-from-order.js +8 -0
- package/app/components/live-map-drawer/driver-listing.js +1 -0
- package/app/components/live-map-drawer/place-listing.js +1 -0
- package/app/components/live-map-drawer/vehicle-listing.js +1 -0
- package/app/components/live-map-drawer.js +1 -0
- package/app/utils/create-full-calendar-event-from-order.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/src/Models/FuelReport.php +1 -1
- package/server/src/Models/Order.php +22 -0
- package/server/src/Models/VehicleDevice.php +1 -1
- package/server/src/Models/VehicleDeviceEvent.php +1 -1
- package/server/src/Observers/FleetObserver.php +19 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
- package/addon/components/full-calendar/draggable.js +0 -1
- package/addon/components/full-calendar.js +0 -1
- package/app/components/full-calendar/draggable.js +0 -1
- package/app/components/full-calendar.js +0 -1
|
@@ -9,6 +9,7 @@ export default class AdminVisibilityControlsComponent extends Component {
|
|
|
9
9
|
@tracked visibilitySettings = [
|
|
10
10
|
{ name: 'Dashboard', route: 'operations.orders', visible: true },
|
|
11
11
|
{ name: 'Service Rates', route: 'operations.service-rates', visible: true },
|
|
12
|
+
{ name: 'Scheduler', route: 'operations.scheduler', visible: true },
|
|
12
13
|
{ name: 'Drivers', route: 'management.drivers', visible: true },
|
|
13
14
|
{ name: 'Vehicles', route: 'management.vehicles', visible: true },
|
|
14
15
|
{ name: 'Fleets', route: 'management.fleets', visible: true },
|
|
@@ -34,10 +34,11 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
34
34
|
const operationsItems = [
|
|
35
35
|
{ title: 'Dashboard', icon: 'home', route: 'operations.orders' },
|
|
36
36
|
{ title: 'Service Rates', icon: 'file-invoice-dollar', route: 'operations.service-rates' },
|
|
37
|
+
{ title: 'Scheduler', icon: 'calendar-day', route: 'operations.scheduler' },
|
|
37
38
|
];
|
|
38
39
|
|
|
39
40
|
const resourcesItems = [
|
|
40
|
-
{ title: 'Drivers', icon: '
|
|
41
|
+
{ title: 'Drivers', icon: 'id-card', route: 'management.drivers' },
|
|
41
42
|
{ title: 'Vehicles', icon: 'truck', route: 'management.vehicles' },
|
|
42
43
|
{ title: 'Fleets', icon: 'user-group', route: 'management.fleets' },
|
|
43
44
|
{ title: 'Vendors', icon: 'warehouse', route: 'management.vendors' },
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div class="flex flex-row items-center px-5 py-2 border-b border-gray-200 dark:border-gray-700">
|
|
2
|
+
<div>
|
|
3
|
+
<Input @value={{this.query}} {{on "input" this.performSearch}} @type="text" class="form-input form-input-sm w-60" placeholder="Filter drivers by keyword..." />
|
|
4
|
+
</div>
|
|
5
|
+
</div>
|
|
6
|
+
<Table @rows={{this.drivers}} @columns={{this.columns}} @onSetup={{fn (mut this.table)}} />
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { isBlank } from '@ember/utils';
|
|
6
|
+
import { later } from '@ember/runloop';
|
|
7
|
+
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a live map drawer driver listing component.
|
|
11
|
+
* This component is responsible for displaying and interacting with a list of drivers on a live map.
|
|
12
|
+
*
|
|
13
|
+
* @extends Component
|
|
14
|
+
*/
|
|
15
|
+
export default class LiveMapDrawerDriverListingComponent extends Component {
|
|
16
|
+
/**
|
|
17
|
+
* Service for managing context panels within the application.
|
|
18
|
+
* @service
|
|
19
|
+
*/
|
|
20
|
+
@service contextPanel;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Service for triggering notifications.
|
|
24
|
+
* @service
|
|
25
|
+
*/
|
|
26
|
+
@service notifications;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Service for triggering notifications.
|
|
30
|
+
* @service
|
|
31
|
+
*/
|
|
32
|
+
@service hostRouter;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Service for CRUD operations.
|
|
36
|
+
* @service
|
|
37
|
+
*/
|
|
38
|
+
@service crud;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The list of drivers to display, tracked for reactivity.
|
|
42
|
+
* @tracked
|
|
43
|
+
*/
|
|
44
|
+
@tracked drivers = [];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The internal list of drivers used for searching, tracked for reactivity.
|
|
48
|
+
* @tracked
|
|
49
|
+
*/
|
|
50
|
+
@tracked _drivers = [];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The current search query, tracked for reactivity.
|
|
54
|
+
* @tracked
|
|
55
|
+
*/
|
|
56
|
+
@tracked query = '';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The table component reference, tracked for reactivity.
|
|
60
|
+
* @tracked
|
|
61
|
+
*/
|
|
62
|
+
@tracked table = null;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The configuration for table columns including details like label, valuePath, and cellComponent,
|
|
66
|
+
* tracked for reactivity.
|
|
67
|
+
* @tracked
|
|
68
|
+
*/
|
|
69
|
+
@tracked columns = [
|
|
70
|
+
{
|
|
71
|
+
label: 'Driver',
|
|
72
|
+
valuePath: 'name',
|
|
73
|
+
photoPath: 'photo_url',
|
|
74
|
+
width: '100px',
|
|
75
|
+
cellComponent: 'cell/driver-name',
|
|
76
|
+
onClick: this.focus,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: 'Location',
|
|
80
|
+
valuePath: 'location',
|
|
81
|
+
width: '80px',
|
|
82
|
+
cellComponent: 'table/cell/point',
|
|
83
|
+
onClick: this.locate,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: 'Current Job',
|
|
87
|
+
valuePath: 'current_job_id',
|
|
88
|
+
width: '80px',
|
|
89
|
+
cellComponent: 'table/cell/anchor',
|
|
90
|
+
onClick: this.job,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'Status',
|
|
94
|
+
valuePath: 'status',
|
|
95
|
+
cellComponent: 'table/cell/status',
|
|
96
|
+
width: '60px',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: 'Last Seen',
|
|
100
|
+
valuePath: 'updatedAgo',
|
|
101
|
+
width: '60px',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: '',
|
|
105
|
+
cellComponent: 'table/cell/dropdown',
|
|
106
|
+
ddButtonText: false,
|
|
107
|
+
ddButtonIcon: 'ellipsis-h',
|
|
108
|
+
ddButtonIconPrefix: 'fas',
|
|
109
|
+
ddMenuLabel: 'Actions',
|
|
110
|
+
cellClassNames: 'overflow-visible',
|
|
111
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
112
|
+
width: '90px',
|
|
113
|
+
actions: [
|
|
114
|
+
{
|
|
115
|
+
label: 'View driver details...',
|
|
116
|
+
fn: this.focus,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: 'Edit driver...',
|
|
120
|
+
fn: (driver) => {
|
|
121
|
+
return this.focus(driver, 'editing');
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: 'Locate driver...',
|
|
126
|
+
fn: this.locate,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: 'Delete driver...',
|
|
130
|
+
fn: this.delete,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
sortable: false,
|
|
134
|
+
filterable: false,
|
|
135
|
+
resizable: false,
|
|
136
|
+
searchable: false,
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Initializes the component with drivers passed in from `this.args` and sets up the live map reference.
|
|
142
|
+
*/
|
|
143
|
+
constructor() {
|
|
144
|
+
super(...arguments);
|
|
145
|
+
this.drivers = getWithDefault(this.args, 'drivers', []);
|
|
146
|
+
this._drivers = [...this.drivers];
|
|
147
|
+
this.liveMap = this.args.liveMap;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Filters the drivers list based on a query.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} query - The query string to filter the drivers list.
|
|
154
|
+
*/
|
|
155
|
+
search(query) {
|
|
156
|
+
if (typeof query !== 'string' && !isBlank(query)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.drivers = [
|
|
161
|
+
...this._drivers.filter((driver) => {
|
|
162
|
+
return typeof driver.name === 'string' && driver.name.toLowerCase().includes(query.toLowerCase());
|
|
163
|
+
}),
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Action to perform a search based on the input event's value.
|
|
169
|
+
*
|
|
170
|
+
* @param {Event} event - The input event containing the search value.
|
|
171
|
+
*/
|
|
172
|
+
@action performSearch({ target: { value } }) {
|
|
173
|
+
this.search(value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Action to focus on a driver in the live map and context panel.
|
|
178
|
+
*
|
|
179
|
+
* @param {object} driver - The driver object to focus on.
|
|
180
|
+
* @param {string} intent - The intent for focusing, default is 'viewing'.
|
|
181
|
+
*/
|
|
182
|
+
@action focus(driver, intent = 'viewing') {
|
|
183
|
+
if (this.liveMap) {
|
|
184
|
+
this.liveMap.focusLayerByRecord(driver, 16, {
|
|
185
|
+
onAfterFocusWithRecord: () => {
|
|
186
|
+
later(
|
|
187
|
+
this,
|
|
188
|
+
() => {
|
|
189
|
+
this.contextPanel.focus(driver, intent);
|
|
190
|
+
},
|
|
191
|
+
600 * 2
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
this.contextPanel.focus(driver, intent);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Action to locate a driver on the live map.
|
|
202
|
+
*
|
|
203
|
+
* @param {object} driver - The driver object to locate.
|
|
204
|
+
*/
|
|
205
|
+
@action locate(driver) {
|
|
206
|
+
if (this.liveMap) {
|
|
207
|
+
this.liveMap.focusLayerByRecord(driver, 18);
|
|
208
|
+
} else {
|
|
209
|
+
this.notifications.warning('Unable to locate driver.');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Transitino to view the drivers current job
|
|
215
|
+
*
|
|
216
|
+
* @param {DriverModel} driver
|
|
217
|
+
* @memberof LiveMapDrawerDriverListingComponent
|
|
218
|
+
*/
|
|
219
|
+
@action job(driver) {
|
|
220
|
+
if (driver.current_job_id) {
|
|
221
|
+
this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index.view', driver.current_job_id);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Action to delete a driver from the list and perform cleanup.
|
|
227
|
+
*
|
|
228
|
+
* @param {object} driver - The driver object to delete.
|
|
229
|
+
* @param {object} options - Additional options for the delete operation.
|
|
230
|
+
*/
|
|
231
|
+
@action delete(driver, options = {}) {
|
|
232
|
+
this.crud.delete(driver, {
|
|
233
|
+
onSuccess: () => {
|
|
234
|
+
this._drivers.removeObject(driver);
|
|
235
|
+
this.drivers.removeObject(driver);
|
|
236
|
+
},
|
|
237
|
+
...options,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div class="flex flex-row items-center px-5 py-2 border-b border-gray-200 dark:border-gray-700">
|
|
2
|
+
<div>
|
|
3
|
+
<Input @value={{this.query}} {{on "input" this.performSearch}} @type="text" class="form-input form-input-sm w-60" placeholder="Filter places by keyword..." />
|
|
4
|
+
</div>
|
|
5
|
+
</div>
|
|
6
|
+
<Table @rows={{this.places}} @columns={{this.columns}} @onSetup={{fn (mut this.table)}} />
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { isBlank } from '@ember/utils';
|
|
6
|
+
import { later } from '@ember/runloop';
|
|
7
|
+
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a live map drawer place listing component.
|
|
11
|
+
* This component is responsible for displaying and interacting with a list of places on a live map.
|
|
12
|
+
*
|
|
13
|
+
* @extends Component
|
|
14
|
+
*/
|
|
15
|
+
export default class LiveMapDrawerPlaceListingComponent extends Component {
|
|
16
|
+
/**
|
|
17
|
+
* Service for managing context panels within the application.
|
|
18
|
+
* @service
|
|
19
|
+
*/
|
|
20
|
+
@service contextPanel;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Service for triggering notifications.
|
|
24
|
+
* @service
|
|
25
|
+
*/
|
|
26
|
+
@service notifications;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Service for CRUD operations.
|
|
30
|
+
* @service
|
|
31
|
+
*/
|
|
32
|
+
@service crud;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The list of places to display, tracked for reactivity.
|
|
36
|
+
* @tracked
|
|
37
|
+
*/
|
|
38
|
+
@tracked places = [];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The internal list of places used for searching, tracked for reactivity.
|
|
42
|
+
* @tracked
|
|
43
|
+
*/
|
|
44
|
+
@tracked _places = [];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The current search query, tracked for reactivity.
|
|
48
|
+
* @tracked
|
|
49
|
+
*/
|
|
50
|
+
@tracked query = '';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The table component reference, tracked for reactivity.
|
|
54
|
+
* @tracked
|
|
55
|
+
*/
|
|
56
|
+
@tracked table = null;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The configuration for table columns including details like label, valuePath, and cellComponent,
|
|
60
|
+
* tracked for reactivity.
|
|
61
|
+
* @tracked
|
|
62
|
+
*/
|
|
63
|
+
@tracked columns = [
|
|
64
|
+
{
|
|
65
|
+
label: 'Place',
|
|
66
|
+
valuePath: 'address',
|
|
67
|
+
width: '200px',
|
|
68
|
+
cellComponent: 'table/cell/anchor',
|
|
69
|
+
onClick: this.focus,
|
|
70
|
+
showOnlineIndicator: true,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: 'Location',
|
|
74
|
+
valuePath: 'location',
|
|
75
|
+
width: '80px',
|
|
76
|
+
cellComponent: 'table/cell/point',
|
|
77
|
+
onClick: this.locate,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: '',
|
|
81
|
+
cellComponent: 'table/cell/dropdown',
|
|
82
|
+
ddButtonText: false,
|
|
83
|
+
ddButtonIcon: 'ellipsis-h',
|
|
84
|
+
ddButtonIconPrefix: 'fas',
|
|
85
|
+
ddMenuLabel: 'Actions',
|
|
86
|
+
cellClassNames: 'overflow-visible',
|
|
87
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
88
|
+
width: '90px',
|
|
89
|
+
actions: [
|
|
90
|
+
{
|
|
91
|
+
label: 'View place details...',
|
|
92
|
+
fn: this.focus,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: 'Edit place...',
|
|
96
|
+
fn: (place) => {
|
|
97
|
+
return this.focus(place, 'editing');
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'Locate place...',
|
|
102
|
+
fn: this.locate,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: 'Delete place...',
|
|
106
|
+
fn: this.delete,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
sortable: false,
|
|
110
|
+
filterable: false,
|
|
111
|
+
resizable: false,
|
|
112
|
+
searchable: false,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Initializes the component with places passed in from `this.args` and sets up the live map reference.
|
|
118
|
+
*/
|
|
119
|
+
constructor() {
|
|
120
|
+
super(...arguments);
|
|
121
|
+
this.places = getWithDefault(this.args, 'places', []);
|
|
122
|
+
this._places = [...this.places];
|
|
123
|
+
this.liveMap = this.args.liveMap;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Filters the places list based on a query.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} query - The query string to filter the places list.
|
|
130
|
+
*/
|
|
131
|
+
search(query) {
|
|
132
|
+
if (typeof query !== 'string' && !isBlank(query)) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.places = [
|
|
137
|
+
...this._places.filter((place) => {
|
|
138
|
+
return typeof place.address === 'string' && place.address.toLowerCase().includes(query.toLowerCase());
|
|
139
|
+
}),
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Action to perform a search based on the input event's value.
|
|
145
|
+
*
|
|
146
|
+
* @param {Event} event - The input event containing the search value.
|
|
147
|
+
*/
|
|
148
|
+
@action performSearch({ target: { value } }) {
|
|
149
|
+
this.search(value);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Action to focus on a place in the live map and context panel.
|
|
154
|
+
*
|
|
155
|
+
* @param {object} place - The place object to focus on.
|
|
156
|
+
* @param {string} intent - The intent for focusing, default is 'viewing'.
|
|
157
|
+
*/
|
|
158
|
+
@action focus(place, intent = 'viewing') {
|
|
159
|
+
if (this.liveMap) {
|
|
160
|
+
this.liveMap.focusLayerByRecord(place, 17, {
|
|
161
|
+
onAfterFocusWithRecord: () => {
|
|
162
|
+
later(
|
|
163
|
+
this,
|
|
164
|
+
() => {
|
|
165
|
+
this.contextPanel.focus(place, intent);
|
|
166
|
+
},
|
|
167
|
+
600 * 2
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
this.contextPanel.focus(place, intent);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Action to locate a place on the live map.
|
|
178
|
+
*
|
|
179
|
+
* @param {object} place - The place object to locate.
|
|
180
|
+
*/
|
|
181
|
+
@action locate(place) {
|
|
182
|
+
if (this.liveMap) {
|
|
183
|
+
this.liveMap.focusLayerByRecord(place, 18);
|
|
184
|
+
} else {
|
|
185
|
+
this.notifications.warning('Unable to locate place.');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Action to delete a place from the list and perform cleanup.
|
|
191
|
+
*
|
|
192
|
+
* @param {object} place - The place object to delete.
|
|
193
|
+
* @param {object} options - Additional options for the delete operation.
|
|
194
|
+
*/
|
|
195
|
+
@action delete(place, options = {}) {
|
|
196
|
+
this.crud.delete(place, {
|
|
197
|
+
onSuccess: () => {
|
|
198
|
+
this._places.removeObject(place);
|
|
199
|
+
this.places.removeObject(place);
|
|
200
|
+
},
|
|
201
|
+
...options,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div class="flex flex-row items-center px-5 py-2 border-b border-gray-200 dark:border-gray-700">
|
|
2
|
+
<div>
|
|
3
|
+
<Input @value={{this.query}} {{on "input" this.performSearch}} @type="text" class="form-input form-input-sm w-60" placeholder="Filter vehicles by keyword..." />
|
|
4
|
+
</div>
|
|
5
|
+
</div>
|
|
6
|
+
<Table @rows={{this.vehicles}} @columns={{this.columns}} @onSetup={{fn (mut this.table)}} />
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { isBlank } from '@ember/utils';
|
|
6
|
+
import { later } from '@ember/runloop';
|
|
7
|
+
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a live map drawer vehicle listing component.
|
|
11
|
+
* This component is responsible for displaying and interacting with a list of vehicles on a live map.
|
|
12
|
+
*
|
|
13
|
+
* @extends Component
|
|
14
|
+
*/
|
|
15
|
+
export default class LiveMapDrawerVehicleListingComponent extends Component {
|
|
16
|
+
/**
|
|
17
|
+
* Service for managing context panels within the application.
|
|
18
|
+
* @service
|
|
19
|
+
*/
|
|
20
|
+
@service contextPanel;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Service for triggering notifications.
|
|
24
|
+
* @service
|
|
25
|
+
*/
|
|
26
|
+
@service notifications;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Service for CRUD operations.
|
|
30
|
+
* @service
|
|
31
|
+
*/
|
|
32
|
+
@service crud;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The list of vehicles to display, tracked for reactivity.
|
|
36
|
+
* @tracked
|
|
37
|
+
*/
|
|
38
|
+
@tracked vehicles = [];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The internal list of vehicles used for searching, tracked for reactivity.
|
|
42
|
+
* @tracked
|
|
43
|
+
*/
|
|
44
|
+
@tracked _vehicles = [];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The current search query, tracked for reactivity.
|
|
48
|
+
* @tracked
|
|
49
|
+
*/
|
|
50
|
+
@tracked query = '';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The table component reference, tracked for reactivity.
|
|
54
|
+
* @tracked
|
|
55
|
+
*/
|
|
56
|
+
@tracked table = null;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The configuration for table columns including details like label, valuePath, and cellComponent,
|
|
60
|
+
* tracked for reactivity.
|
|
61
|
+
* @tracked
|
|
62
|
+
*/
|
|
63
|
+
@tracked columns = [
|
|
64
|
+
{
|
|
65
|
+
label: 'Vehicle',
|
|
66
|
+
valuePath: 'display_name',
|
|
67
|
+
photoPath: 'avatar_url',
|
|
68
|
+
width: '100px',
|
|
69
|
+
cellComponent: 'table/cell/vehicle-name',
|
|
70
|
+
onClick: this.focus,
|
|
71
|
+
showOnlineIndicator: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Location',
|
|
75
|
+
valuePath: 'location',
|
|
76
|
+
width: '80px',
|
|
77
|
+
cellComponent: 'table/cell/point',
|
|
78
|
+
onClick: this.locate,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'Status',
|
|
82
|
+
valuePath: 'status',
|
|
83
|
+
cellComponent: 'table/cell/status',
|
|
84
|
+
width: '60px',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: 'Last Seen',
|
|
88
|
+
valuePath: 'updatedAgo',
|
|
89
|
+
width: '60px',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: '',
|
|
93
|
+
cellComponent: 'table/cell/dropdown',
|
|
94
|
+
ddButtonText: false,
|
|
95
|
+
ddButtonIcon: 'ellipsis-h',
|
|
96
|
+
ddButtonIconPrefix: 'fas',
|
|
97
|
+
ddMenuLabel: 'Actions',
|
|
98
|
+
cellClassNames: 'overflow-visible',
|
|
99
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
100
|
+
width: '90px',
|
|
101
|
+
actions: [
|
|
102
|
+
{
|
|
103
|
+
label: 'View vehicle details...',
|
|
104
|
+
fn: this.focus,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: 'Edit vehicle...',
|
|
108
|
+
fn: (vehicle) => {
|
|
109
|
+
return this.focus(vehicle, 'editing');
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: 'Locate vehicle...',
|
|
114
|
+
fn: this.locate,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: 'Delete vehicle...',
|
|
118
|
+
fn: this.delete,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
sortable: false,
|
|
122
|
+
filterable: false,
|
|
123
|
+
resizable: false,
|
|
124
|
+
searchable: false,
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Initializes the component with vehicles passed in from `this.args` and sets up the live map reference.
|
|
130
|
+
*/
|
|
131
|
+
constructor() {
|
|
132
|
+
super(...arguments);
|
|
133
|
+
this.vehicles = getWithDefault(this.args, 'vehicles', []);
|
|
134
|
+
this._vehicles = [...this.vehicles];
|
|
135
|
+
this.liveMap = this.args.liveMap;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Filters the vehicles list based on a query.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} query - The query string to filter the vehicles list.
|
|
142
|
+
*/
|
|
143
|
+
search(query) {
|
|
144
|
+
if (typeof query !== 'string' && !isBlank(query)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.vehicles = [
|
|
149
|
+
...this._vehicles.filter((vehicle) => {
|
|
150
|
+
return typeof vehicle.displayName === 'string' && vehicle.displayName.toLowerCase().includes(query.toLowerCase());
|
|
151
|
+
}),
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Action to perform a search based on the input event's value.
|
|
157
|
+
*
|
|
158
|
+
* @param {Event} event - The input event containing the search value.
|
|
159
|
+
*/
|
|
160
|
+
@action performSearch({ target: { value } }) {
|
|
161
|
+
this.search(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Action to focus on a vehicle in the live map and context panel.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} vehicle - The vehicle object to focus on.
|
|
168
|
+
* @param {string} intent - The intent for focusing, default is 'viewing'.
|
|
169
|
+
*/
|
|
170
|
+
@action focus(vehicle, intent = 'viewing') {
|
|
171
|
+
if (this.liveMap) {
|
|
172
|
+
this.liveMap.focusLayerByRecord(vehicle, 16, {
|
|
173
|
+
onAfterFocusWithRecord: () => {
|
|
174
|
+
later(
|
|
175
|
+
this,
|
|
176
|
+
() => {
|
|
177
|
+
this.contextPanel.focus(vehicle, intent);
|
|
178
|
+
},
|
|
179
|
+
600 * 2
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
} else {
|
|
184
|
+
this.contextPanel.focus(vehicle, intent);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Action to locate a vehicle on the live map.
|
|
190
|
+
*
|
|
191
|
+
* @param {object} vehicle - The vehicle object to locate.
|
|
192
|
+
*/
|
|
193
|
+
@action locate(vehicle) {
|
|
194
|
+
if (this.liveMap) {
|
|
195
|
+
this.liveMap.focusLayerByRecord(vehicle, 18);
|
|
196
|
+
} else {
|
|
197
|
+
this.notifications.warning('Unable to locate vehicle.');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Action to delete a vehicle from the list and perform cleanup.
|
|
203
|
+
*
|
|
204
|
+
* @param {object} vehicle - The vehicle object to delete.
|
|
205
|
+
* @param {object} options - Additional options for the delete operation.
|
|
206
|
+
*/
|
|
207
|
+
@action delete(vehicle, options = {}) {
|
|
208
|
+
this.crud.delete(vehicle, {
|
|
209
|
+
onSuccess: () => {
|
|
210
|
+
this._vehicles.removeObject(vehicle);
|
|
211
|
+
this.vehicles.removeObject(vehicle);
|
|
212
|
+
},
|
|
213
|
+
...options,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|