@fleetbase/fleetops-engine 0.6.55 → 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/admin/map-settings.hbs +1 -1
- 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/device/details.js +1 -1
- package/addon/components/device-event/details.hbs +192 -0
- package/addon/components/device-event/details.js +151 -0
- package/addon/components/driver/schedule.js +1 -1
- 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/layout/fleet-ops-sidebar.js +15 -9
- 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/telematic/details.hbs +1 -1
- package/addon/components/telematic/form.hbs +2 -2
- package/addon/components/telematic/settings.hbs +1 -1
- package/addon/components/vehicle/card.hbs +14 -2
- package/addon/components/vehicle/form.hbs +13 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +2 -2
- package/addon/controllers/connectivity/events/details.js +68 -1
- package/addon/controllers/connectivity/sensors/index.js +4 -0
- package/addon/controllers/connectivity/telematics/details/events.js +1 -1
- package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
- package/addon/controllers/settings/map.js +19 -0
- package/addon/extension.js +14 -0
- package/addon/instance-initializers/register-vroom-allocation.js +1 -1
- package/addon/routes/connectivity/events/details.js +26 -1
- package/addon/routes/management/fleets/index/details.js +1 -1
- package/addon/routes/management/vehicles/index/details.js +3 -1
- package/addon/services/map-adapter/google.js +66 -1
- package/addon/services/map-adapter/leaflet.js +1 -1
- package/addon/services/map-adapter-interface.js +1 -1
- package/addon/services/map-manager.js +16 -1
- package/addon/services/map-settings.js +15 -0
- package/addon/styles/fleetops-engine.css +487 -1
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +2 -1
- package/addon/templates/connectivity/events/details.hbs +40 -1
- package/addon/templates/settings/map.hbs +38 -9
- package/addon/utils/leaflet-plugin-loader.js +5 -5
- package/addon/utils/map-drawer-dropdown-position.js +11 -13
- package/addon/utils/route-colors.js +1 -1
- package/app/components/ai/create-order-preview.js +1 -0
- package/app/components/ai/route-preview-map.js +1 -0
- package/app/components/device-event/details.hbs +192 -0
- package/app/components/device-event/details.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/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
- package/server/src/Exports/ContactExport.php +12 -5
- package/server/src/Exports/DeviceExport.php +92 -0
- package/server/src/Exports/DriverExport.php +28 -3
- package/server/src/Exports/EquipmentExport.php +94 -0
- package/server/src/Exports/FleetExport.php +22 -7
- package/server/src/Exports/FuelReportExport.php +16 -1
- package/server/src/Exports/IssueExport.php +25 -3
- package/server/src/Exports/MaintenanceExport.php +106 -0
- package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
- package/server/src/Exports/OrderExport.php +24 -5
- package/server/src/Exports/PartExport.php +99 -0
- package/server/src/Exports/PlaceExport.php +33 -7
- package/server/src/Exports/SensorExport.php +94 -0
- package/server/src/Exports/ServiceAreaExport.php +31 -7
- package/server/src/Exports/ServiceRateExport.php +41 -1
- package/server/src/Exports/TelematicExport.php +90 -0
- package/server/src/Exports/VehicleExport.php +274 -8
- package/server/src/Exports/VendorExport.php +15 -2
- package/server/src/Exports/WorkOrderExport.php +92 -0
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
- package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
- package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
- package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
- package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
- package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
- package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
- package/server/src/Http/Filter/DeviceFilter.php +33 -10
- package/server/src/Http/Filter/EquipmentFilter.php +23 -0
- package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
- package/server/src/Http/Filter/PartFilter.php +47 -0
- package/server/src/Http/Filter/SensorFilter.php +34 -6
- package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
- package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
- package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
- package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
- package/server/src/Http/Requests/CreatePartRequest.php +40 -0
- package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
- package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
- package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
- package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
- package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
- package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
- package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
- package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
- package/server/src/Http/Resources/v1/Device.php +72 -0
- package/server/src/Http/Resources/v1/Equipment.php +57 -0
- package/server/src/Http/Resources/v1/Fleet.php +8 -2
- package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
- package/server/src/Http/Resources/v1/Part.php +64 -0
- package/server/src/Http/Resources/v1/Sensor.php +68 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
- package/server/src/Models/Driver.php +6 -0
- package/server/src/Models/Order.php +20 -7
- package/server/src/Models/Place.php +11 -3
- package/server/src/Observers/PurchaseRateObserver.php +2 -1
- 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/Analytics/RevenueTrend.php +3 -8
- package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
- package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
- package/server/src/Support/Metrics/EarningsMetric.php +1 -10
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +104 -5
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +550 -54
- package/server/src/Support/Telematics/TelematicService.php +28 -9
- package/server/src/Support/Utils.php +52 -0
- package/server/src/routes.php +67 -14
- package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
- package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
- package/server/tests/ExportCoverageTest.php +79 -0
- package/server/tests/FleetHierarchyResourceTest.php +12 -0
- package/server/tests/MetricsRegistryTest.php +45 -5
- package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
- package/server/tests/PointResolutionTest.php +63 -0
- package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
- package/server/tests/TelematicsHardeningTest.php +636 -1
- 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
|
+
}
|
|
@@ -63,6 +63,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
63
63
|
icon: 'truck',
|
|
64
64
|
route: 'management',
|
|
65
65
|
defaultRoute: 'management.index',
|
|
66
|
+
requiresVisibleChildren: true,
|
|
66
67
|
keywords: ['drivers', 'vehicles', 'fleets', 'contacts', 'places'],
|
|
67
68
|
children: this.resourcesItems,
|
|
68
69
|
}),
|
|
@@ -72,6 +73,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
72
73
|
icon: 'cog',
|
|
73
74
|
route: 'maintenance',
|
|
74
75
|
defaultRoute: 'maintenance.index',
|
|
76
|
+
requiresVisibleChildren: true,
|
|
75
77
|
keywords: ['work orders', 'equipment', 'parts'],
|
|
76
78
|
children: this.maintenanceItems,
|
|
77
79
|
}),
|
|
@@ -81,6 +83,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
81
83
|
icon: 'satellite-dish',
|
|
82
84
|
route: 'connectivity',
|
|
83
85
|
defaultRoute: 'connectivity.telematics',
|
|
86
|
+
requiresVisibleChildren: true,
|
|
84
87
|
keywords: ['telematics', 'fuel integrations', 'devices', 'sensors'],
|
|
85
88
|
children: this.connectivityItems,
|
|
86
89
|
}),
|
|
@@ -90,6 +93,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
90
93
|
icon: 'chart-line',
|
|
91
94
|
route: 'analytics',
|
|
92
95
|
defaultRoute: 'analytics.index',
|
|
96
|
+
requiresVisibleChildren: true,
|
|
93
97
|
keywords: ['reports', 'metrics'],
|
|
94
98
|
children: this.analyticsItems,
|
|
95
99
|
}),
|
|
@@ -99,6 +103,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
99
103
|
icon: 'gear',
|
|
100
104
|
route: 'settings',
|
|
101
105
|
defaultRoute: 'settings.index',
|
|
106
|
+
requiresVisibleChildren: true,
|
|
102
107
|
keywords: ['configuration', 'navigator', 'map', 'routing', 'notifications'],
|
|
103
108
|
children: this.settingsItems,
|
|
104
109
|
}),
|
|
@@ -164,7 +169,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
164
169
|
|
|
165
170
|
get connectivityItems() {
|
|
166
171
|
return this.withRegistryItems('connectivity', [
|
|
167
|
-
this.
|
|
172
|
+
this.createHubItem(this.intl.t('menu.telematics'), 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
|
|
168
173
|
this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report', ['fuel integrations']),
|
|
169
174
|
this.createItem('menu.devices', 'hard-drive', 'connectivity.devices', 'fleet-ops list device', 'fleet-ops see device'),
|
|
170
175
|
this.createItem('menu.sensors', 'temperature-full', 'connectivity.sensors', 'fleet-ops list sensor', 'fleet-ops see sensor'),
|
|
@@ -247,13 +252,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
247
252
|
this.universeSettingsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'settings');
|
|
248
253
|
}
|
|
249
254
|
|
|
250
|
-
createBranch({ id, label, icon, route, defaultRoute, children, keywords = [] }) {
|
|
255
|
+
createBranch({ id, label, icon, route, defaultRoute, requiresVisibleChildren = false, children, keywords = [] }) {
|
|
251
256
|
return {
|
|
252
257
|
id,
|
|
253
258
|
label,
|
|
254
259
|
icon,
|
|
255
260
|
route: this.fullRoute(route),
|
|
256
261
|
defaultRoute: this.fullRoute(defaultRoute),
|
|
262
|
+
requiresVisibleChildren,
|
|
257
263
|
children: children.filter((item) => item.visible !== false),
|
|
258
264
|
keywords,
|
|
259
265
|
};
|
|
@@ -267,12 +273,12 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
267
273
|
icon,
|
|
268
274
|
route: this.fullRoute(route),
|
|
269
275
|
permission,
|
|
270
|
-
|
|
276
|
+
visiblePermission: ability,
|
|
271
277
|
keywords: [intl, route, ...keywords].filter(Boolean),
|
|
272
278
|
};
|
|
273
279
|
}
|
|
274
280
|
|
|
275
|
-
createHubItem(label, icon, route,
|
|
281
|
+
createHubItem(label, icon, route, _permission, _ability, keywords = []) {
|
|
276
282
|
return {
|
|
277
283
|
pinnedFirst: true,
|
|
278
284
|
priority: this.defaultPriorityForRoute(route),
|
|
@@ -280,8 +286,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
280
286
|
description: label,
|
|
281
287
|
icon,
|
|
282
288
|
route: this.fullRoute(route),
|
|
283
|
-
|
|
284
|
-
visible: this.abilities.can(ability),
|
|
289
|
+
isNavigationHub: true,
|
|
285
290
|
keywords: [label, route, ...keywords].filter(Boolean),
|
|
286
291
|
};
|
|
287
292
|
}
|
|
@@ -390,10 +395,11 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
390
395
|
}
|
|
391
396
|
}
|
|
392
397
|
|
|
393
|
-
@action shouldSyncInitialActiveParent({ activePath = [],
|
|
398
|
+
@action shouldSyncInitialActiveParent({ activePath = [], currentURL }) {
|
|
394
399
|
const [parent, child] = activePath;
|
|
395
|
-
const
|
|
396
|
-
const
|
|
400
|
+
const normalizedURL = (currentURL ?? '').split('?')[0].replace(/\/+$/, '') || '/';
|
|
401
|
+
const isFleetOpsRootURL = normalizedURL === '/fleet-ops';
|
|
402
|
+
const isDefaultOrdersLanding = parent?.id === 'operations' && child?.route === this.fullRoute('operations.orders') && isFleetOpsRootURL;
|
|
397
403
|
|
|
398
404
|
return !isDefaultOrdersLanding;
|
|
399
405
|
}
|
|
@@ -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: [
|