@fleetbase/fleetops-engine 0.6.56 → 0.6.57
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/ai/create-order-preview.hbs +297 -0
- package/addon/components/ai/create-order-preview.js +362 -0
- package/addon/components/ai/route-preview-map.hbs +79 -0
- package/addon/components/ai/route-preview-map.js +527 -0
- package/addon/components/fleet/details.hbs +21 -8
- package/addon/components/fleet/details.js +55 -1
- package/addon/components/fleet/hierarchy-tree.hbs +137 -0
- package/addon/components/fleet/hierarchy-tree.js +528 -0
- package/addon/components/map/drawer/device-event-listing.js +1 -0
- package/addon/components/map/drawer/driver-listing.js +1 -0
- package/addon/components/map/drawer/place-listing.js +1 -0
- package/addon/components/map/drawer/vehicle-listing.js +1 -0
- package/addon/components/order/details.hbs +34 -1
- package/addon/components/order/form/details.hbs +4 -0
- package/addon/components/order/form.hbs +9 -1
- package/addon/components/vehicle/card.hbs +14 -2
- package/addon/components/vehicle/form.hbs +13 -1
- package/addon/controllers/settings/map.js +4 -0
- package/addon/extension.js +14 -0
- package/addon/routes/management/fleets/index/details.js +1 -1
- package/addon/services/map-adapter/google.js +56 -16
- package/addon/services/map-manager.js +12 -0
- package/addon/styles/fleetops-engine.css +487 -1
- package/addon/utils/map-drawer-dropdown-position.js +11 -13
- package/app/components/ai/create-order-preview.js +1 -0
- package/app/components/ai/route-preview-map.js +1 -0
- package/app/components/fleet/hierarchy-tree.js +1 -0
- package/app/services/route-engine.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +6 -4
- package/scripts/normalize-rgba-alpha.js +90 -0
- package/server/src/Http/Resources/v1/Fleet.php +8 -2
- package/server/src/Models/Driver.php +6 -0
- package/server/src/Models/Order.php +18 -6
- package/server/src/Models/Place.php +11 -3
- package/server/src/Providers/FleetOpsServiceProvider.php +26 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +88 -0
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +108 -0
- package/server/src/Support/Ai/Capabilities/ConsoleNavigationCapability.php +55 -0
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +508 -0
- package/server/src/Support/Ai/Capabilities/DocsHelpCapability.php +49 -0
- package/server/src/Support/Ai/Capabilities/ImportOrdersPreviewCapability.php +66 -0
- package/server/src/Support/Ai/Capabilities/OperationalQueryCapability.php +319 -0
- package/server/src/Support/Ai/Capabilities/OptimizeOrderRouteCapability.php +240 -0
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +95 -0
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +195 -0
- package/server/src/Support/Ai/FleetOpsAiQueryResources.php +150 -0
- package/server/src/Support/Utils.php +52 -0
- package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
- package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
- package/server/tests/FleetHierarchyResourceTest.php +12 -0
- package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
- package/server/tests/PointResolutionTest.php +63 -0
- package/addon/components/table/cell/dropdown/action-item.hbs +0 -31
- package/addon/components/table/cell/dropdown/action-item.js +0 -62
- package/addon/components/table/cell/dropdown.hbs +0 -28
- package/addon/components/table/cell/dropdown.js +0 -91
- package/app/components/table/cell/dropdown/action-item.js +0 -1
- package/app/components/table/cell/dropdown.js +0 -1
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
5
|
+
import { debug } from '@ember/debug';
|
|
6
|
+
|
|
7
|
+
const FILTER_ALL = 'all';
|
|
8
|
+
const FILTER_DRIVERS = 'drivers';
|
|
9
|
+
const FILTER_VEHICLES = 'vehicles';
|
|
10
|
+
const FILTER_ONLINE = 'online';
|
|
11
|
+
|
|
12
|
+
export default class FleetHierarchyTreeComponent extends Component {
|
|
13
|
+
@service store;
|
|
14
|
+
|
|
15
|
+
@tracked expandedFleetIds = new Set();
|
|
16
|
+
@tracked query = '';
|
|
17
|
+
@tracked activeFilter = FILTER_ALL;
|
|
18
|
+
@tracked loadedDriversByFleetKey = {};
|
|
19
|
+
@tracked loadedVehiclesByFleetKey = {};
|
|
20
|
+
loadingDriverFleetKeys = new Set();
|
|
21
|
+
loadingVehicleFleetKeys = new Set();
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
this.expandedFleetIds = new Set(this.initialExpandedFleetIds);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get filters() {
|
|
29
|
+
return [
|
|
30
|
+
{ id: FILTER_ALL, label: 'All' },
|
|
31
|
+
{ id: FILTER_DRIVERS, label: 'Drivers' },
|
|
32
|
+
{ id: FILTER_VEHICLES, label: 'Vehicles' },
|
|
33
|
+
{ id: FILTER_ONLINE, label: 'Online' },
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get rootFleet() {
|
|
38
|
+
return this.args.fleet;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get fleets() {
|
|
42
|
+
return this.rootFleet ? [this.rootFleet] : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get normalizedQuery() {
|
|
46
|
+
return String(this.query ?? '')
|
|
47
|
+
.trim()
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get hasQuery() {
|
|
52
|
+
return this.normalizedQuery.length > 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get hasActiveFilter() {
|
|
56
|
+
return this.activeFilter !== FILTER_ALL;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get hasControlsApplied() {
|
|
60
|
+
return this.hasQuery || this.hasActiveFilter;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get hasHierarchyResources() {
|
|
64
|
+
return this.fleets.some((fleet) => this.fleetHasChildren(fleet));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get allFleetNodesExpanded() {
|
|
68
|
+
const fleetKeys = this.collectFleetKeys(this.fleets);
|
|
69
|
+
|
|
70
|
+
return fleetKeys.length > 0 && fleetKeys.every((key) => this.expandedFleetIds.has(key));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get expandCollapseIcon() {
|
|
74
|
+
return this.allFleetNodesExpanded ? 'down-left-and-up-right-to-center' : 'up-right-and-down-left-from-center';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get expandCollapseHelpText() {
|
|
78
|
+
return this.allFleetNodesExpanded ? 'Collapse all' : 'Expand all';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get visibleRows() {
|
|
82
|
+
if (this.hasControlsApplied) {
|
|
83
|
+
return this.buildFilteredFleetRows(this.fleets);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.buildFleetRows(this.fleets);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get emptyTitle() {
|
|
90
|
+
if (this.hasControlsApplied) {
|
|
91
|
+
return 'No matching resources';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return 'No fleet resources assigned';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get emptyDescription() {
|
|
98
|
+
if (this.hasControlsApplied) {
|
|
99
|
+
return 'Clear the current search or filter to show the full fleet hierarchy.';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return 'Assign drivers or vehicles to make this fleet operationally useful.';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get initialExpandedFleetIds() {
|
|
106
|
+
return this.fleets.flatMap((fleet) => {
|
|
107
|
+
const key = this.fleetKey(fleet);
|
|
108
|
+
const childKeys = this.fleetSubfleets(fleet)
|
|
109
|
+
.map((subfleet) => this.fleetKey(subfleet))
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
|
|
112
|
+
return [key, ...childKeys].filter(Boolean);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
resourceArray(resources) {
|
|
117
|
+
if (!resources) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof resources.toArray === 'function') {
|
|
122
|
+
return resources.toArray();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return Array.isArray(resources) ? resources : [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@action displayName(resource) {
|
|
129
|
+
return resource?.displayName ?? resource?.display_name ?? resource?.name ?? resource?.public_id ?? 'Unnamed resource';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fleetKey(fleet) {
|
|
133
|
+
return String(fleet?.id ?? fleet?.uuid ?? fleet?.public_id ?? fleet?.name ?? '');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fleetSubfleets(fleet) {
|
|
137
|
+
return this.sortByName(this.resourceArray(fleet?.subfleets));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@action fleetSubfleetCount(fleet) {
|
|
141
|
+
return this.fleetSubfleets(fleet).length;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fleetDrivers(fleet) {
|
|
145
|
+
const embeddedDrivers = this.resourceArray(fleet?.drivers);
|
|
146
|
+
|
|
147
|
+
if (embeddedDrivers.length) {
|
|
148
|
+
return this.sortOnlineFirst(embeddedDrivers);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return this.sortOnlineFirst(this.loadedDriversByFleetKey[this.fleetKey(fleet)] ?? []);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fleetVehicles(fleet) {
|
|
155
|
+
const embeddedVehicles = this.resourceArray(fleet?.vehicles);
|
|
156
|
+
|
|
157
|
+
if (embeddedVehicles.length) {
|
|
158
|
+
return this.sortOnlineFirst(embeddedVehicles);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return this.sortOnlineFirst(this.loadedVehiclesByFleetKey[this.fleetKey(fleet)] ?? []);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fleetDriverCount(fleet) {
|
|
165
|
+
return Number(fleet?.drivers_count ?? fleet?.driver_count ?? this.fleetDrivers(fleet).length ?? 0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fleetVehicleCount(fleet) {
|
|
169
|
+
return Number(fleet?.vehicles_count ?? fleet?.vehicle_count ?? this.fleetVehicles(fleet).length ?? 0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fleetOnlineDriverCount(fleet) {
|
|
173
|
+
return Number(fleet?.drivers_online_count ?? this.fleetDrivers(fleet).filter((driver) => driver?.online).length ?? 0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fleetOnlineVehicleCount(fleet) {
|
|
177
|
+
return Number(fleet?.vehicles_online_count ?? this.fleetVehicles(fleet).filter((vehicle) => vehicle?.online).length ?? 0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@action fleetSubtitle(fleet) {
|
|
181
|
+
return `${this.fleetOnlineDriverCount(fleet)}/${this.fleetDriverCount(fleet)} drivers online - ${this.fleetOnlineVehicleCount(fleet)}/${this.fleetVehicleCount(fleet)} vehicles online`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@action driverSubtitle(driver) {
|
|
185
|
+
return [driver?.vehicle_name, driver?.status, driver?.public_id].filter(Boolean).join(' - ') || (driver?.online ? 'Online' : 'Offline');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@action vehicleSubtitle(vehicle) {
|
|
189
|
+
return [vehicle?.driver_name, vehicle?.plate_number, vehicle?.vin, vehicle?.status, vehicle?.public_id].filter(Boolean).join(' - ') || (vehicle?.online ? 'Online' : 'Offline');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@action fleetHasChildren(fleet) {
|
|
193
|
+
return this.fleetSubfleets(fleet).length > 0 || this.fleetDriverCount(fleet) > 0 || this.fleetVehicleCount(fleet) > 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@action isFleetExpanded(fleet) {
|
|
197
|
+
return this.expandedFleetIds.has(this.fleetKey(fleet));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
buildFleetRows(fleets = [], depth = 0) {
|
|
201
|
+
return fleets.flatMap((fleet) => {
|
|
202
|
+
const rows = [this.fleetRow(fleet, depth)];
|
|
203
|
+
|
|
204
|
+
if (!this.isFleetExpanded(fleet)) {
|
|
205
|
+
return rows;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
rows.push(...this.childRows(fleet, depth + 1));
|
|
209
|
+
|
|
210
|
+
return rows;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
buildFilteredFleetRows(fleets = [], depth = 0) {
|
|
215
|
+
return fleets.flatMap((fleet) => {
|
|
216
|
+
const fleetMatches = this.fleetMatches(fleet);
|
|
217
|
+
const childDepth = depth + 1;
|
|
218
|
+
const subfleetRows = this.buildFilteredFleetRows(this.fleetSubfleets(fleet), childDepth);
|
|
219
|
+
const driverRows = this.fleetDrivers(fleet)
|
|
220
|
+
.filter((driver) => this.driverVisible(driver))
|
|
221
|
+
.map((driver) => this.driverRow(driver, childDepth));
|
|
222
|
+
const vehicleRows = this.fleetVehicles(fleet)
|
|
223
|
+
.filter((vehicle) => this.vehicleVisible(vehicle))
|
|
224
|
+
.map((vehicle) => this.vehicleRow(vehicle, childDepth));
|
|
225
|
+
|
|
226
|
+
if (fleetMatches) {
|
|
227
|
+
return [this.fleetRow(fleet, depth), ...this.childRows(fleet, childDepth, true)];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!subfleetRows.length && !driverRows.length && !vehicleRows.length) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return [this.fleetRow(fleet, depth), ...subfleetRows, ...driverRows, ...vehicleRows];
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
childRows(fleet, depth, forceExpanded = false) {
|
|
239
|
+
const rows = [];
|
|
240
|
+
|
|
241
|
+
this.fleetSubfleets(fleet).forEach((subfleet) => {
|
|
242
|
+
if (forceExpanded) {
|
|
243
|
+
rows.push(this.fleetRow(subfleet, depth), ...this.childRows(subfleet, depth + 1, true));
|
|
244
|
+
} else {
|
|
245
|
+
rows.push(...this.buildFleetRows([subfleet], depth));
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (this.activeFilter !== FILTER_VEHICLES) {
|
|
250
|
+
this.fleetDrivers(fleet)
|
|
251
|
+
.filter((driver) => this.activeFilter !== FILTER_ONLINE || driver?.online)
|
|
252
|
+
.forEach((driver) => rows.push(this.driverRow(driver, depth)));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this.activeFilter !== FILTER_DRIVERS) {
|
|
256
|
+
this.fleetVehicles(fleet)
|
|
257
|
+
.filter((vehicle) => this.activeFilter !== FILTER_ONLINE || vehicle?.online)
|
|
258
|
+
.forEach((vehicle) => rows.push(this.vehicleRow(vehicle, depth)));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return rows;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
fleetRow(fleet, depth) {
|
|
265
|
+
return {
|
|
266
|
+
type: 'fleet',
|
|
267
|
+
key: `fleet:${this.fleetKey(fleet)}`,
|
|
268
|
+
fleet,
|
|
269
|
+
depth,
|
|
270
|
+
depthClass: this.depthClass(depth),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
driverRow(driver, depth) {
|
|
275
|
+
return {
|
|
276
|
+
type: 'driver',
|
|
277
|
+
key: `driver:${driver?.id ?? driver?.uuid ?? driver?.public_id ?? this.displayName(driver)}`,
|
|
278
|
+
driver,
|
|
279
|
+
depth,
|
|
280
|
+
depthClass: this.depthClass(depth),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
vehicleRow(vehicle, depth) {
|
|
285
|
+
return {
|
|
286
|
+
type: 'vehicle',
|
|
287
|
+
key: `vehicle:${vehicle?.id ?? vehicle?.uuid ?? vehicle?.public_id ?? this.displayName(vehicle)}`,
|
|
288
|
+
vehicle,
|
|
289
|
+
depth,
|
|
290
|
+
depthClass: this.depthClass(depth),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
depthClass(depth) {
|
|
295
|
+
return `fleet-hierarchy-tree-row-depth-${Math.min(Number(depth) || 0, 5)}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
fleetMatches(fleet) {
|
|
299
|
+
return this.resourceMatches(fleet, ['name', 'public_id', 'status', 'slug'], [this.fleetSubtitle(fleet)]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
driverMatches(driver) {
|
|
303
|
+
return this.resourceMatches(driver, ['displayName', 'display_name', 'name', 'public_id', 'status', 'vehicle_name'], [this.driverSubtitle(driver)]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
vehicleMatches(vehicle) {
|
|
307
|
+
return this.resourceMatches(vehicle, ['displayName', 'display_name', 'name', 'public_id', 'status', 'driver_name', 'plate_number', 'vin'], [this.vehicleSubtitle(vehicle)]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
driverVisible(driver) {
|
|
311
|
+
if (this.activeFilter === FILTER_VEHICLES) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (this.activeFilter === FILTER_ONLINE && !driver?.online) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return !this.hasQuery || this.driverMatches(driver);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
vehicleVisible(vehicle) {
|
|
323
|
+
if (this.activeFilter === FILTER_DRIVERS) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.activeFilter === FILTER_ONLINE && !vehicle?.online) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return !this.hasQuery || this.vehicleMatches(vehicle);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
resourceMatches(resource, fields = [], extraValues = []) {
|
|
335
|
+
if (!this.hasQuery) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const values = [...fields.map((field) => resource?.[field]), ...extraValues];
|
|
340
|
+
|
|
341
|
+
return values.filter(Boolean).some((value) => String(value).toLowerCase().includes(this.normalizedQuery));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
sortOnlineFirst(resources = []) {
|
|
345
|
+
return [...resources].sort((a, b) => {
|
|
346
|
+
const onlineSort = Number(Boolean(b?.online)) - Number(Boolean(a?.online));
|
|
347
|
+
|
|
348
|
+
if (onlineSort !== 0) {
|
|
349
|
+
return onlineSort;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return String(this.displayName(a)).localeCompare(String(this.displayName(b)));
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
sortByName(resources = []) {
|
|
357
|
+
return [...resources].sort((a, b) => String(this.displayName(a)).localeCompare(String(this.displayName(b))));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@action toggleFleet(fleet) {
|
|
361
|
+
const key = this.fleetKey(fleet);
|
|
362
|
+
const expandedFleetIds = new Set(this.expandedFleetIds);
|
|
363
|
+
const willExpand = !expandedFleetIds.has(key);
|
|
364
|
+
|
|
365
|
+
if (!willExpand) {
|
|
366
|
+
expandedFleetIds.delete(key);
|
|
367
|
+
} else {
|
|
368
|
+
expandedFleetIds.add(key);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this.expandedFleetIds = expandedFleetIds;
|
|
372
|
+
|
|
373
|
+
if (willExpand) {
|
|
374
|
+
this.loadFleetResources(fleet);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@action expandAll() {
|
|
379
|
+
this.expandedFleetIds = new Set(this.collectFleetKeys(this.fleets));
|
|
380
|
+
this.loadVisibleFleetResources();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@action collapseAll() {
|
|
384
|
+
this.expandedFleetIds = new Set(this.fleets.map((fleet) => this.fleetKey(fleet)).filter(Boolean));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@action toggleExpandCollapseAll() {
|
|
388
|
+
if (this.allFleetNodesExpanded) {
|
|
389
|
+
this.collapseAll();
|
|
390
|
+
} else {
|
|
391
|
+
this.expandAll();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
collectFleetKeys(fleets = []) {
|
|
396
|
+
return fleets.flatMap((fleet) => [this.fleetKey(fleet), ...this.collectFleetKeys(this.fleetSubfleets(fleet))].filter(Boolean));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
visibleFleets(fleets = this.fleets) {
|
|
400
|
+
return fleets.flatMap((fleet) => {
|
|
401
|
+
if (!this.isFleetExpanded(fleet)) {
|
|
402
|
+
return [fleet];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return [fleet, ...this.visibleFleets(this.fleetSubfleets(fleet))];
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@action loadVisibleFleetResources() {
|
|
410
|
+
this.visibleFleets().forEach((fleet) => this.loadFleetResources(fleet));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
loadFleetResources(fleet) {
|
|
414
|
+
if (!fleet) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!this.fleetDrivers(fleet).length && this.fleetDriverCount(fleet) > 0) {
|
|
419
|
+
this.loadFleetDrivers(fleet);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!this.fleetVehicles(fleet).length && this.fleetVehicleCount(fleet) > 0) {
|
|
423
|
+
this.loadFleetVehicles(fleet);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async loadFleetDrivers(fleet) {
|
|
428
|
+
const key = this.fleetKey(fleet);
|
|
429
|
+
|
|
430
|
+
if (!key || this.loadingDriverFleetKeys.has(key) || this.loadedDriversByFleetKey[key]) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
this.loadingDriverFleetKeys.add(key);
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
const drivers = await this.store.query('driver', { fleet: fleet.id, limit: -1 });
|
|
438
|
+
this.loadedDriversByFleetKey = {
|
|
439
|
+
...this.loadedDriversByFleetKey,
|
|
440
|
+
[key]: this.resourceArray(drivers),
|
|
441
|
+
};
|
|
442
|
+
} catch (error) {
|
|
443
|
+
debug(`Unable to load hierarchy drivers for fleet ${key}: ${error.message}`);
|
|
444
|
+
} finally {
|
|
445
|
+
this.loadingDriverFleetKeys.delete(key);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async loadFleetVehicles(fleet) {
|
|
450
|
+
const key = this.fleetKey(fleet);
|
|
451
|
+
|
|
452
|
+
if (!key || this.loadingVehicleFleetKeys.has(key) || this.loadedVehiclesByFleetKey[key]) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
this.loadingVehicleFleetKeys.add(key);
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
const vehicles = await this.store.query('vehicle', { fleet: fleet.id, limit: -1 });
|
|
460
|
+
this.loadedVehiclesByFleetKey = {
|
|
461
|
+
...this.loadedVehiclesByFleetKey,
|
|
462
|
+
[key]: this.resourceArray(vehicles),
|
|
463
|
+
};
|
|
464
|
+
} catch (error) {
|
|
465
|
+
debug(`Unable to load hierarchy vehicles for fleet ${key}: ${error.message}`);
|
|
466
|
+
} finally {
|
|
467
|
+
this.loadingVehicleFleetKeys.delete(key);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
@action updateQuery(event) {
|
|
472
|
+
this.query = event.target.value;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@action clearFilters() {
|
|
476
|
+
this.query = '';
|
|
477
|
+
this.activeFilter = FILTER_ALL;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
@action setFilter(filter) {
|
|
481
|
+
this.activeFilter = filter;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@action viewFleet(fleet) {
|
|
485
|
+
if (typeof this.args.onViewFleet === 'function') {
|
|
486
|
+
return this.args.onViewFleet(fleet);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
@action viewDriver(driver) {
|
|
491
|
+
if (typeof this.args.onViewDriver === 'function') {
|
|
492
|
+
return this.args.onViewDriver(driver);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
@action viewVehicle(vehicle) {
|
|
497
|
+
if (typeof this.args.onViewVehicle === 'function') {
|
|
498
|
+
return this.args.onViewVehicle(vehicle);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@action assignDriver(fleet) {
|
|
503
|
+
if (typeof this.args.onAssignDriver === 'function') {
|
|
504
|
+
return this.args.onAssignDriver(fleet);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
@action assignVehicle(fleet) {
|
|
509
|
+
if (typeof this.args.onAssignVehicle === 'function') {
|
|
510
|
+
return this.args.onAssignVehicle(fleet);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
@action calculateDropdownLeftPosition(trigger, content) {
|
|
515
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
516
|
+
const contentRect = content?.getBoundingClientRect?.();
|
|
517
|
+
const contentWidth = contentRect?.width || 224;
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
style: {
|
|
521
|
+
position: 'fixed',
|
|
522
|
+
marginTop: '0px',
|
|
523
|
+
left: triggerRect.left - triggerRect.width / 2 - contentWidth - 9,
|
|
524
|
+
top: triggerRect.top - triggerRect.height / 2 + 5,
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
@@ -116,6 +116,7 @@ export default class MapDrawerDeviceEventListingComponent extends Component {
|
|
|
116
116
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
|
|
117
117
|
cellClassNames: 'overflow-visible',
|
|
118
118
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
119
|
+
renderInPlace: false,
|
|
119
120
|
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
120
121
|
width: '10%',
|
|
121
122
|
actions: [
|
|
@@ -72,6 +72,7 @@ export default class MapDrawerDriverListingComponent extends Component {
|
|
|
72
72
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.driver') }),
|
|
73
73
|
cellClassNames: 'overflow-visible',
|
|
74
74
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
75
|
+
renderInPlace: false,
|
|
75
76
|
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
76
77
|
width: '90px',
|
|
77
78
|
actions: [
|
|
@@ -54,6 +54,7 @@ export default class MapDrawerPlaceListingComponent extends Component {
|
|
|
54
54
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.place') }),
|
|
55
55
|
cellClassNames: 'overflow-visible',
|
|
56
56
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
57
|
+
renderInPlace: false,
|
|
57
58
|
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
58
59
|
width: '90px',
|
|
59
60
|
actions: [
|
|
@@ -66,6 +66,7 @@ export default class MapDrawerVehicleListingComponent extends Component {
|
|
|
66
66
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.vehicle') }),
|
|
67
67
|
cellClassNames: 'overflow-visible',
|
|
68
68
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
69
|
+
renderInPlace: false,
|
|
69
70
|
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
70
71
|
width: '90px',
|
|
71
72
|
actions: [
|
|
@@ -2,8 +2,24 @@
|
|
|
2
2
|
{{#if (has-block)}}
|
|
3
3
|
{{yield
|
|
4
4
|
(hash
|
|
5
|
+
RegistryYieldStart=(component
|
|
6
|
+
"registry-yield"
|
|
7
|
+
registry="fleet-ops:component:order:details:start"
|
|
8
|
+
resource=@resource
|
|
9
|
+
controller=@controller
|
|
10
|
+
permission=(get-write-permission @resource)
|
|
11
|
+
isLoading=@isLoading
|
|
12
|
+
)
|
|
5
13
|
Activity=(component "order/details/activity" resource=@resource onChange=this.handleActivityChanged isLoadingActivity=@isLoadingActivity isLoading=@isLoading)
|
|
6
14
|
Detail=(component "order/details/detail" resource=@resource onChange=@onDetailsChanged isLoading=@isLoading)
|
|
15
|
+
RegistryYieldAfterDetails=(component
|
|
16
|
+
"registry-yield"
|
|
17
|
+
registry="fleet-ops:component:order:details:after-details"
|
|
18
|
+
resource=@resource
|
|
19
|
+
controller=@controller
|
|
20
|
+
permission=(get-write-permission @resource)
|
|
21
|
+
isLoading=@isLoading
|
|
22
|
+
)
|
|
7
23
|
CustomFields=(component "order/details/custom-fields" resource=@resource onChange=@onCustomFieldsChanged isLoading=@isLoading)
|
|
8
24
|
PurchaseRate=(component "order/details/purchase-rate" resource=@resource isLoading=@isLoading)
|
|
9
25
|
Tracking=(component "order/details/tracking" resource=@resource isLoading=@isLoading)
|
|
@@ -18,11 +34,25 @@
|
|
|
18
34
|
Documents=(component "order/details/documents" resource=@resource onChange=@onDocumentsChanged isLoading=@isLoading)
|
|
19
35
|
Comments=(component "order/details/comments" resource=@resource onChange=@onCommentsChanged isLoading=@isLoading)
|
|
20
36
|
Metadata=(component "order/details/metadata" resource=@resource onChange=@onMetadataChanged isLoading=@isLoading)
|
|
37
|
+
RegistryYieldEnd=(component
|
|
38
|
+
"registry-yield"
|
|
39
|
+
registry="fleet-ops:component:order:details:end"
|
|
40
|
+
resource=@resource
|
|
41
|
+
controller=@controller
|
|
42
|
+
permission=(get-write-permission @resource)
|
|
43
|
+
isLoading=@isLoading
|
|
44
|
+
)
|
|
21
45
|
)
|
|
22
46
|
}}
|
|
23
47
|
{{else}}
|
|
48
|
+
<RegistryYield @registry="fleet-ops:component:order:details:start" as |RegistryComponent|>
|
|
49
|
+
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
|
|
50
|
+
</RegistryYield>
|
|
24
51
|
<Order::Details::Activity @resource={{@resource}} @onChange={{this.handleActivityChanged}} @isLoadingActivity={{@isLoadingActivity}} @isLoading={{@isLoading}} />
|
|
25
52
|
<Order::Details::Detail @resource={{@resource}} @onChange={{@onDetailsChanged}} @isLoading={{@isLoading}} />
|
|
53
|
+
<RegistryYield @registry="fleet-ops:component:order:details:after-details" as |RegistryComponent|>
|
|
54
|
+
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
|
|
55
|
+
</RegistryYield>
|
|
26
56
|
<Order::Details::CustomFields @resource={{@resource}} @onChange={{@onCustomFieldsChanged}} @isLoading={{@isLoading}} />
|
|
27
57
|
<Order::Details::PurchaseRate @resource={{@resource}} @isLoading={{@isLoading}} />
|
|
28
58
|
<Order::Details::Tracking @resource={{@resource}} @isLoading={{@isLoading}} />
|
|
@@ -37,5 +67,8 @@
|
|
|
37
67
|
<Order::Details::Documents @resource={{@resource}} @onChange={{@onDocumentsChanged}} @isLoading={{@isLoading}} />
|
|
38
68
|
<Order::Details::Comments @resource={{@resource}} @onChange={{@onCommentsChanged}} @isLoading={{@isLoading}} />
|
|
39
69
|
<Order::Details::Metadata @resource={{@resource}} @onChange={{@onMetadataChanged}} @isLoading={{@isLoading}} />
|
|
70
|
+
<RegistryYield @registry="fleet-ops:component:order:details:end" as |RegistryComponent|>
|
|
71
|
+
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
|
|
72
|
+
</RegistryYield>
|
|
40
73
|
{{/if}}
|
|
41
|
-
</div>
|
|
74
|
+
</div>
|
|
@@ -234,6 +234,10 @@
|
|
|
234
234
|
/>
|
|
235
235
|
</InputGroup>
|
|
236
236
|
</div>
|
|
237
|
+
|
|
238
|
+
<RegistryYield @registry="fleet-ops:component:order:form:details" as |RegistryComponent|>
|
|
239
|
+
<RegistryComponent @order={{@resource}} @resource={{@resource}} />
|
|
240
|
+
</RegistryYield>
|
|
237
241
|
</div>
|
|
238
242
|
</ContentPanel>
|
|
239
243
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
{{#if (has-block)}}
|
|
3
3
|
{{yield
|
|
4
4
|
(hash
|
|
5
|
+
RegistryYieldStart=(component "registry-yield" registry="fleet-ops:component:order:form:start" order=@resource)
|
|
5
6
|
Details=(component "order/form/details" resource=@resource)
|
|
6
7
|
CustomFields=(component "order/form/custom-fields" resource=@resource customFields=@customFields)
|
|
7
8
|
Route=(component "order/form/route" resource=@resource)
|
|
@@ -12,9 +13,13 @@
|
|
|
12
13
|
Documents=(component "order/form/documents" resource=@resource)
|
|
13
14
|
OrchestratorConstraints=(component "order/form/orchestrator-constraints" resource=@resource)
|
|
14
15
|
Metadata=(component "order/form/metadata" resource=@resource)
|
|
16
|
+
RegistryYieldEnd=(component "registry-yield" registry="fleet-ops:component:order:form:end" order=@resource)
|
|
15
17
|
)
|
|
16
18
|
}}
|
|
17
19
|
{{else}}
|
|
20
|
+
<RegistryYield @registry="fleet-ops:component:order:form:start" as |RegistryComponent|>
|
|
21
|
+
<RegistryComponent @order={{@resource}} @resource={{@resource}} />
|
|
22
|
+
</RegistryYield>
|
|
18
23
|
<Order::Form::Details @resource={{@resource}} />
|
|
19
24
|
<Order::Form::Route @resource={{@resource}} />
|
|
20
25
|
<RegistryYield @registry="fleet-ops:component:order:form" as |RegistryComponent|>
|
|
@@ -26,5 +31,8 @@
|
|
|
26
31
|
<Order::Form::Documents @resource={{@resource}} />
|
|
27
32
|
<Order::Form::OrchestratorConstraints @resource={{@resource}} />
|
|
28
33
|
<Order::Form::Metadata @resource={{@resource}} />
|
|
34
|
+
<RegistryYield @registry="fleet-ops:component:order:form:end" as |RegistryComponent|>
|
|
35
|
+
<RegistryComponent @order={{@resource}} @resource={{@resource}} />
|
|
36
|
+
</RegistryYield>
|
|
29
37
|
{{/if}}
|
|
30
|
-
</div>
|
|
38
|
+
</div>
|