@fleetbase/fleetops-engine 0.6.53 → 0.6.55
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/routing-settings.hbs +2 -1
- package/addon/components/cell/attached-vehicle.hbs +10 -0
- package/addon/components/cell/attached-vehicle.js +58 -0
- package/addon/components/cell/device-identity.hbs +24 -0
- package/addon/components/cell/device-identity.js +129 -0
- package/addon/components/cell/driver-identity.hbs +33 -0
- package/addon/components/cell/driver-identity.js +143 -0
- package/addon/components/cell/equipment-identity.hbs +5 -0
- package/addon/components/cell/equipment-identity.js +48 -0
- package/addon/components/cell/part-identity.hbs +5 -0
- package/addon/components/cell/part-identity.js +63 -0
- package/addon/components/cell/telematic-device.hbs +38 -0
- package/addon/components/cell/telematic-device.js +44 -0
- package/addon/components/cell/telematic-provider.hbs +33 -16
- package/addon/components/cell/telematic-provider.js +50 -8
- package/addon/components/cell/vehicle-identity.hbs +33 -0
- package/addon/components/cell/vehicle-identity.js +138 -0
- package/addon/components/device/details.hbs +106 -135
- package/addon/components/device/details.js +174 -1
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/device/form.js +7 -0
- package/addon/components/device/panel-header.hbs +46 -21
- package/addon/components/device/panel-header.js +49 -1
- package/addon/components/device/panel-tabs/events.hbs +30 -0
- package/addon/components/device/panel-tabs/events.js +128 -0
- package/addon/components/device/panel-tabs/sensors.hbs +30 -0
- package/addon/components/device/panel-tabs/sensors.js +93 -0
- package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
- package/addon/components/device/panel-tabs/vehicle.js +90 -0
- package/addon/components/equipment/form.js +8 -1
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
- package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +101 -17
- package/addon/components/map/drawer/device-event-listing.js +2 -0
- package/addon/components/map/drawer/driver-listing.js +2 -0
- package/addon/components/map/drawer/place-listing.js +2 -0
- package/addon/components/map/drawer/position-listing.hbs +11 -6
- package/addon/components/map/drawer/position-listing.js +17 -4
- package/addon/components/map/drawer/vehicle-listing.js +2 -0
- package/addon/components/order/form/details.hbs +1 -43
- package/addon/components/order/form/details.js +18 -53
- package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
- package/addon/components/order/form/orchestrator-constraints.js +61 -0
- package/addon/components/order/form/payload.js +48 -2
- package/addon/components/order/form/route.js +15 -0
- package/addon/components/order/form/service-rate.hbs +62 -11
- package/addon/components/order/form/service-rate.js +124 -2
- package/addon/components/order/form.hbs +2 -0
- package/addon/components/sensor/panel-header.hbs +47 -15
- package/addon/components/sensor/panel-header.js +59 -1
- package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
- package/addon/components/table/cell/dropdown/action-item.js +62 -0
- package/addon/components/table/cell/dropdown.hbs +28 -0
- package/addon/components/table/cell/dropdown.js +91 -0
- package/addon/components/telematic/details.hbs +7 -1
- package/addon/components/telematic/form.js +16 -8
- package/addon/components/vendor/panel-header.hbs +82 -0
- package/addon/components/vendor/panel-header.js +48 -0
- package/addon/components/work-order/details.hbs +5 -1
- package/addon/components/work-order/form.hbs +26 -4
- package/addon/components/work-order/form.js +5 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
- package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
- package/addon/controllers/connectivity/devices/index/details.js +12 -0
- package/addon/controllers/connectivity/devices/index.js +144 -129
- package/addon/controllers/connectivity/events/details.js +3 -0
- package/addon/controllers/connectivity/events/index.js +187 -11
- package/addon/controllers/connectivity/sensors/index.js +156 -14
- package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
- package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
- package/addon/controllers/connectivity/telematics/details/events.js +39 -11
- package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
- package/addon/controllers/maintenance/equipment/index.js +1 -2
- package/addon/controllers/maintenance/parts/index.js +1 -2
- package/addon/controllers/maintenance/work-orders/index.js +18 -1
- package/addon/controllers/management/drivers/index.js +82 -42
- package/addon/controllers/management/fuel-reports/index.js +63 -4
- package/addon/controllers/management/issues/index.js +63 -4
- package/addon/controllers/management/vehicles/index.js +33 -6
- package/addon/routes/connectivity/devices/index/details/events.js +37 -1
- package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
- package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
- package/addon/routes/connectivity/devices/index.js +7 -0
- package/addon/routes/connectivity/events/details.js +3 -0
- package/addon/routes/connectivity/events/index.js +6 -2
- package/addon/routes/connectivity/sensors/index.js +7 -2
- package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
- package/addon/routes/connectivity/telematics/details/devices.js +6 -0
- package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
- package/addon/routes/connectivity/telematics/details.js +1 -1
- package/addon/routes/connectivity/telematics/edit.js +1 -1
- package/addon/routes/maintenance/work-orders/index.js +1 -0
- package/addon/routes.js +2 -0
- package/addon/services/device-actions.js +30 -0
- package/addon/services/device-event-actions.js +1 -1
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/order-creation.js +54 -1
- package/addon/services/order-validation.js +3 -2
- package/addon/services/sensor-actions.js +3 -0
- package/addon/services/service-rate-actions.js +40 -1
- package/addon/services/vehicle-actions.js +24 -12
- package/addon/services/vendor-actions.js +3 -0
- package/addon/styles/fleetops-engine.css +28 -4
- package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
- package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
- package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
- package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
- package/addon/templates/management/vehicles/index/edit.hbs +1 -1
- package/addon/templates/management/vendors/index/details.hbs +2 -2
- package/addon/templates/settings/notifications.hbs +2 -1
- package/addon/templates/settings/routing.hbs +1 -0
- package/addon/utils/device-table-columns.js +288 -0
- package/addon/utils/fleet-ops-options.js +38 -0
- package/addon/utils/identity-cell-resource.js +20 -0
- package/addon/utils/map-drawer-dropdown-position.js +47 -0
- package/addon/utils/trackable-option.js +93 -0
- package/app/components/cell/attached-vehicle.js +1 -0
- package/app/components/cell/device-identity.js +1 -0
- package/app/components/cell/driver-identity.js +1 -0
- package/app/components/cell/equipment-identity.js +1 -0
- package/app/components/cell/part-identity.js +1 -0
- package/app/components/cell/telematic-device.js +1 -0
- package/app/components/cell/vehicle-identity.js +1 -0
- package/app/components/device/panel-tabs/events.hbs +30 -0
- package/app/components/device/panel-tabs/events.js +128 -0
- package/app/components/device/panel-tabs/sensors.hbs +30 -0
- package/app/components/device/panel-tabs/sensors.js +93 -0
- package/app/components/device/panel-tabs/vehicle.hbs +68 -0
- package/app/components/device/panel-tabs/vehicle.js +90 -0
- package/app/components/order/form/orchestrator-constraints.js +1 -0
- package/app/components/table/cell/dropdown/action-item.js +1 -0
- package/app/components/table/cell/dropdown.js +1 -0
- package/app/components/vendor/panel-header.js +1 -0
- package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
- package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
- package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
- package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/utils/map-drawer-dropdown-position.js +1 -0
- package/app/utils/trackable-option.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
- package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
- package/server/src/Console/Commands/SyncTelematics.php +89 -0
- package/server/src/Exceptions/TelematicProviderException.php +13 -2
- package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
- package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
- package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
- package/server/src/Http/Filter/DeviceFilter.php +80 -0
- package/server/src/Http/Filter/SensorFilter.php +125 -0
- package/server/src/Http/Filter/VehicleFilter.php +13 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
- package/server/src/Http/Resources/v1/Driver.php +11 -2
- package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
- package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
- package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
- package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
- package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
- package/server/src/Models/Device.php +17 -1
- package/server/src/Models/DeviceEvent.php +82 -1
- package/server/src/Models/Driver.php +16 -0
- package/server/src/Models/Equipment.php +55 -0
- package/server/src/Models/Sensor.php +15 -2
- package/server/src/Models/Telematic.php +1 -1
- package/server/src/Models/Vehicle.php +5 -1
- package/server/src/Models/WorkOrder.php +5 -2
- package/server/src/Observers/DriverObserver.php +3 -3
- package/server/src/Observers/FleetObserver.php +23 -0
- package/server/src/Observers/VehicleObserver.php +3 -3
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/LiveCacheService.php +1 -1
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +70 -5
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
- package/server/src/Support/Telematics/TelematicService.php +330 -30
- package/server/src/routes.php +1 -1
- package/server/tests/DeviceFilterTest.php +45 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
- package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
- package/server/tests/OperationsMonitorEndpointTest.php +38 -0
- package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
- package/server/tests/ServiceRateRouteValidationTest.php +101 -0
- package/server/tests/TelematicsHardeningTest.php +598 -6
- package/server/tests/VehicleDriverAssignmentTest.php +27 -0
- package/translations/en-us.yaml +1 -0
- package/addon/controllers/connectivity/events/index/details.js +0 -3
- package/addon/routes/connectivity/events/index/details.js +0 -3
- /package/addon/templates/connectivity/events/{index/details.hbs → details.hbs} +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3 p-3" ...attributes>
|
|
2
|
+
<div class="flex items-start justify-between gap-3">
|
|
3
|
+
<div class="min-w-0">
|
|
4
|
+
<h2 class="text-base font-bold text-gray-900 dark:text-gray-100">Vehicle Attachment</h2>
|
|
5
|
+
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-300">Fleet context for telemetry from this device.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="flex flex-wrap items-center justify-end gap-2">
|
|
8
|
+
{{#if this.hasVehicle}}
|
|
9
|
+
{{#if this.canOpenVehicle}}
|
|
10
|
+
<Button @icon="eye" @text="Open Vehicle" @size="xs" @onClick={{this.openVehicle}} />
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if this.canLocateVehicle}}
|
|
13
|
+
<Button @icon="location-dot" @text="Locate" @size="xs" @onClick={{this.locateVehicle}} />
|
|
14
|
+
{{/if}}
|
|
15
|
+
<Button @icon="shuffle" @text="Change Vehicle" @size="xs" @onClick={{this.attachToVehicle}} />
|
|
16
|
+
<Button @icon="unlink" @text="Detach" @size="xs" @type="danger" @onClick={{this.detachFromVehicle}} />
|
|
17
|
+
{{else}}
|
|
18
|
+
<Button @icon="link" @text="Attach Vehicle" @size="xs" @onClick={{this.attachToVehicle}} />
|
|
19
|
+
{{/if}}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
{{#if this.hasVehicle}}
|
|
24
|
+
<section class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
|
25
|
+
<div class="flex gap-3 p-3">
|
|
26
|
+
<Image
|
|
27
|
+
src={{this.vehiclePhotoUrl}}
|
|
28
|
+
@fallbackSrc={{config "defaultValues.vehicleAvatar"}}
|
|
29
|
+
alt={{this.vehicleName}}
|
|
30
|
+
class="h-16 w-16 flex-shrink-0 rounded-md object-cover shadow-sm"
|
|
31
|
+
/>
|
|
32
|
+
<div class="min-w-0 flex-1">
|
|
33
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
34
|
+
<h3 class="truncate text-base font-bold text-gray-900 dark:text-gray-100">{{this.vehicleName}}</h3>
|
|
35
|
+
{{#if this.vehicleStatus}}
|
|
36
|
+
<Badge @status={{this.vehicleStatus}} @size="xs">{{smart-humanize this.vehicleStatus}}</Badge>
|
|
37
|
+
{{/if}}
|
|
38
|
+
</div>
|
|
39
|
+
<div class="mt-1 text-xs text-gray-500 dark:text-gray-300">{{n-a this.vehicleSubtitle}}</div>
|
|
40
|
+
<div class="mt-3 grid grid-cols-1 gap-2 text-xs sm:grid-cols-3">
|
|
41
|
+
<div>
|
|
42
|
+
<div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Driver</div>
|
|
43
|
+
<div class="truncate text-gray-800 dark:text-gray-100">{{n-a this.vehicleDriverName}}</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div>
|
|
46
|
+
<div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Attached Device</div>
|
|
47
|
+
<div class="truncate text-gray-800 dark:text-gray-100">{{n-a this.device.displayName this.device.name}}</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<div class="font-semibold uppercase text-gray-400 dark:text-gray-500">Device Last Seen</div>
|
|
51
|
+
<div class="truncate text-gray-800 dark:text-gray-100">{{n-a (format-date-fns this.device.last_online_at "dd MMM yyyy, HH:mm")}}</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
{{else}}
|
|
58
|
+
<Telematic::TabEmptyState
|
|
59
|
+
@tone="warning"
|
|
60
|
+
@icon="link-slash"
|
|
61
|
+
@title="No vehicle attached"
|
|
62
|
+
@message="Attach this device to a vehicle so telemetry has fleet context."
|
|
63
|
+
@primaryText="Attach Vehicle"
|
|
64
|
+
@primaryIcon="link"
|
|
65
|
+
@primaryAction={{this.attachToVehicle}}
|
|
66
|
+
/>
|
|
67
|
+
{{/if}}
|
|
68
|
+
</div>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
|
|
5
|
+
export default class DevicePanelTabsVehicleComponent extends Component {
|
|
6
|
+
@service deviceActions;
|
|
7
|
+
@service hostRouter;
|
|
8
|
+
@service mapManager;
|
|
9
|
+
@service vehicleActions;
|
|
10
|
+
|
|
11
|
+
get device() {
|
|
12
|
+
return this.args.resource ?? this.args.model;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get vehicle() {
|
|
16
|
+
return this.device?.attachable;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get vehicleName() {
|
|
20
|
+
return this.device?.attached_to_name ?? this.vehicle?.displayName ?? this.vehicle?.display_name ?? this.vehicle?.name;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get vehicleSubtitle() {
|
|
24
|
+
return this.vehicle?.plate_number ?? this.vehicle?.call_sign ?? this.vehicle?.vin ?? this.vehicle?.public_id ?? this.device?.attachable_uuid;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get vehiclePhotoUrl() {
|
|
28
|
+
return this.vehicle?.photo_url ?? this.vehicle?.avatar_url;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get vehicleStatus() {
|
|
32
|
+
return this.vehicle?.status ?? (this.vehicle?.online ? 'online' : null);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get vehicleDriverName() {
|
|
36
|
+
return this.vehicle?.driver?.displayName ?? this.vehicle?.driver?.display_name ?? this.vehicle?.driver?.name ?? this.vehicle?.driver_name;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get hasVehicle() {
|
|
40
|
+
return Boolean(this.vehicleName || this.device?.attachable_uuid);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get canOpenVehicle() {
|
|
44
|
+
return Boolean(this.vehicle?.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get canLocateVehicle() {
|
|
48
|
+
return Boolean(this.vehicle?.id && (this.vehicle?.location || this.vehicle?.last_position));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@action attachToVehicle() {
|
|
52
|
+
return this.deviceActions.attachToVehicle(this.device, { callback: () => this.device?.reload?.() });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@action detachFromVehicle() {
|
|
56
|
+
return this.deviceActions.detachFromVehicle(this.device, { callback: () => this.device?.reload?.() });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@action openVehicle() {
|
|
60
|
+
if (this.vehicle?.id) {
|
|
61
|
+
return this.vehicleActions.panel?.view
|
|
62
|
+
? this.vehicleActions.panel.view(this.vehicle)
|
|
63
|
+
: this.hostRouter.transitionTo('console.fleet-ops.management.vehicles.index.details', this.vehicle);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@action async locateVehicle() {
|
|
68
|
+
if (!this.vehicle?.id) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await this.transitionToLiveMap();
|
|
73
|
+
await this.mapManager.waitForMap({ timeoutMs: 8000 });
|
|
74
|
+
|
|
75
|
+
this.mapManager.focusResource(this.vehicle, 16, {
|
|
76
|
+
paddingBottomRight: [300, 200],
|
|
77
|
+
moveend: () => {
|
|
78
|
+
this.vehicleActions.panel?.view?.(this.vehicle, { closeOnTransition: true });
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async transitionToLiveMap() {
|
|
84
|
+
try {
|
|
85
|
+
await this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index', { queryParams: { layout: 'map' } });
|
|
86
|
+
} catch (_) {
|
|
87
|
+
// Keep locate usable if another transition is already active.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -11,9 +11,16 @@ import { task } from 'ember-concurrency';
|
|
|
11
11
|
const TYPE_TO_MODEL = {
|
|
12
12
|
'fleet-ops:vehicle': 'vehicle',
|
|
13
13
|
'fleet-ops:driver': 'driver',
|
|
14
|
+
'Fleetbase\\FleetOps\\Models\\Vehicle': 'vehicle',
|
|
15
|
+
'Fleetbase\\FleetOps\\Models\\Driver': 'driver',
|
|
14
16
|
'fleet-ops:equipment': 'equipment',
|
|
15
17
|
};
|
|
16
18
|
|
|
19
|
+
const TYPE_TO_OPTION_VALUE = {
|
|
20
|
+
'Fleetbase\\FleetOps\\Models\\Vehicle': 'fleet-ops:vehicle',
|
|
21
|
+
'Fleetbase\\FleetOps\\Models\\Driver': 'fleet-ops:driver',
|
|
22
|
+
};
|
|
23
|
+
|
|
17
24
|
export default class EquipmentFormComponent extends Component {
|
|
18
25
|
@service fetch;
|
|
19
26
|
@service currentUser;
|
|
@@ -45,7 +52,7 @@ export default class EquipmentFormComponent extends Component {
|
|
|
45
52
|
const { resource } = args;
|
|
46
53
|
if (resource?.equipable_type) {
|
|
47
54
|
this.equipableModelName = TYPE_TO_MODEL[resource.equipable_type] ?? null;
|
|
48
|
-
this.selectedEquipableType = this.equipableTypeOptions.find((o) => o.value === resource.equipable_type) ?? null;
|
|
55
|
+
this.selectedEquipableType = this.equipableTypeOptions.find((o) => o.value === (TYPE_TO_OPTION_VALUE[resource.equipable_type] ?? resource.equipable_type)) ?? null;
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
58
|
|
|
@@ -7,6 +7,7 @@ import { task } from 'ember-concurrency';
|
|
|
7
7
|
|
|
8
8
|
export default class LayoutFleetOpsSidebarOperationsMonitorComponent extends Component {
|
|
9
9
|
@service store;
|
|
10
|
+
@service fetch;
|
|
10
11
|
@service universe;
|
|
11
12
|
@service hostRouter;
|
|
12
13
|
@service mapManager;
|
|
@@ -203,11 +204,11 @@ export default class LayoutFleetOpsSidebarOperationsMonitorComponent extends Com
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
fleetDrivers(fleet) {
|
|
206
|
-
return this.
|
|
207
|
+
return this.resolveFleetResources(fleet, 'driver');
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
fleetVehicles(fleet) {
|
|
210
|
-
return this.
|
|
211
|
+
return this.resolveFleetResources(fleet, 'vehicle');
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
fleetSubfleets(fleet) {
|
|
@@ -339,6 +340,53 @@ export default class LayoutFleetOpsSidebarOperationsMonitorComponent extends Com
|
|
|
339
340
|
});
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
get driverById() {
|
|
344
|
+
return this.resourcesById(this.fallbackDrivers);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
get vehicleById() {
|
|
348
|
+
return this.resourcesById(this.fallbackVehicles);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
resourcesById(resources = []) {
|
|
352
|
+
return this.resourceArray(resources).reduce((map, resource) => {
|
|
353
|
+
this.resourceIdentifiers(resource).forEach((id) => map.set(id, resource));
|
|
354
|
+
|
|
355
|
+
return map;
|
|
356
|
+
}, new Map());
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
resourceIdentifiers(resource) {
|
|
360
|
+
return [resource?.id, resource?.uuid, resource?.public_id].filter(Boolean).map((id) => String(id));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
resolveFleetResources(fleet, type) {
|
|
364
|
+
const embeddedResources = this.resourceArray(fleet?.[`${type}s`]);
|
|
365
|
+
|
|
366
|
+
if (embeddedResources.length) {
|
|
367
|
+
return embeddedResources;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const ids = this.resourceArray(fleet?.[`${type}_ids`]);
|
|
371
|
+
const resources = type === 'driver' ? this.driverById : this.vehicleById;
|
|
372
|
+
|
|
373
|
+
return ids.map((id) => resources.get(String(id))).filter(Boolean);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
normalizeMonitorResource(type, resource) {
|
|
377
|
+
if (!resource) {
|
|
378
|
+
return resource;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
this.store.pushPayload?.(type, { [type]: resource });
|
|
383
|
+
} catch (_) {
|
|
384
|
+
// Keep the monitor usable in tests and older hosts without pushPayload support.
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return this.store.peekRecord?.(type, resource.id) ?? this.store.peekRecord?.(type, resource.public_id) ?? resource;
|
|
388
|
+
}
|
|
389
|
+
|
|
342
390
|
listenForChanges() {
|
|
343
391
|
this.universe.on('fleet-ops.driver.saved', () => this.loadFallbackResources.perform());
|
|
344
392
|
this.universe.on('fleet-ops.vehicle.saved', () => this.loadFallbackResources.perform());
|
|
@@ -572,18 +620,35 @@ export default class LayoutFleetOpsSidebarOperationsMonitorComponent extends Com
|
|
|
572
620
|
|
|
573
621
|
@task *loadFallbackResources() {
|
|
574
622
|
try {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
this.
|
|
582
|
-
this.
|
|
583
|
-
this.fallbackFleets = fleets.toArray?.() ?? fleets;
|
|
623
|
+
const response = yield this.fetch.get('fleet-ops/live/operations-monitor', {}, { namespace: 'int/v1' });
|
|
624
|
+
const drivers = this.resourceArray(response?.drivers).map((driver) => this.normalizeMonitorResource('driver', driver));
|
|
625
|
+
const vehicles = this.resourceArray(response?.vehicles).map((vehicle) => this.normalizeMonitorResource('vehicle', vehicle));
|
|
626
|
+
const fleets = this.resourceArray(response?.fleets).map((fleet) => this.normalizeMonitorFleet(fleet));
|
|
627
|
+
|
|
628
|
+
this.fallbackDrivers = drivers;
|
|
629
|
+
this.fallbackVehicles = vehicles;
|
|
630
|
+
this.fallbackFleets = fleets;
|
|
584
631
|
this.expandedFleetIds = new Set(this.collectFleetKeys(this.fallbackFleets));
|
|
585
632
|
} catch (error) {
|
|
586
633
|
this.notifications.serverError(error);
|
|
587
634
|
}
|
|
588
635
|
}
|
|
636
|
+
|
|
637
|
+
normalizeMonitorFleet(fleet) {
|
|
638
|
+
const normalizedFleet = this.normalizeMonitorResource('fleet', fleet);
|
|
639
|
+
const subfleets = this.resourceArray(fleet?.subfleets).map((subfleet) => this.normalizeMonitorFleet(subfleet));
|
|
640
|
+
|
|
641
|
+
if (normalizedFleet?.set) {
|
|
642
|
+
normalizedFleet.set('driver_ids', this.resourceArray(fleet?.driver_ids));
|
|
643
|
+
normalizedFleet.set('vehicle_ids', this.resourceArray(fleet?.vehicle_ids));
|
|
644
|
+
normalizedFleet.set('subfleets', subfleets);
|
|
645
|
+
|
|
646
|
+
return normalizedFleet;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return {
|
|
650
|
+
...fleet,
|
|
651
|
+
subfleets,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
589
654
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
@primaryAction={{this.createOrderAction}}
|
|
4
4
|
@searchPlaceholder="Search Fleet-Ops..."
|
|
5
5
|
@searchProvider={{this.searchNavigation}}
|
|
6
|
-
@
|
|
6
|
+
@shouldSyncInitialActiveParent={{this.shouldSyncInitialActiveParent}}
|
|
7
7
|
class="fleet-ops-sidebar-navigator"
|
|
8
8
|
>
|
|
9
9
|
<:footer as |state|>
|
|
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
|
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
|
+
import isMenuItemActive from '@fleetbase/ember-ui/utils/is-menu-item-active';
|
|
5
6
|
|
|
6
7
|
const SECTION_REGISTRY_KEYS = {
|
|
7
8
|
operations: 'universeOperationsMenuItems',
|
|
@@ -198,23 +199,26 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
get registryRootItems() {
|
|
201
|
-
return this.universeMenuItems.filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
|
|
202
|
+
return this.sortByPriority(this.universeMenuItems.filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item)));
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
get registryPanelItems() {
|
|
205
|
-
return this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
206
|
+
return this.sortByPriority(
|
|
207
|
+
this.universeMenuPanels.map((panel) => {
|
|
208
|
+
const children = this.sortByPriority((panel.items ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item)));
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
id: panel.id ?? panel.slug ?? panel.title,
|
|
212
|
+
label: panel.intl ? this.intl.t(panel.intl) : panel.title,
|
|
213
|
+
icon: panel.icon,
|
|
214
|
+
visible: panel.visible,
|
|
215
|
+
permission: panel.permission,
|
|
216
|
+
priority: panel.priority,
|
|
217
|
+
keywords: [panel.slug, panel.title, panel.intl].filter(Boolean),
|
|
218
|
+
children,
|
|
219
|
+
};
|
|
220
|
+
})
|
|
221
|
+
);
|
|
218
222
|
}
|
|
219
223
|
|
|
220
224
|
get footerRegistryComponents() {
|
|
@@ -257,6 +261,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
257
261
|
|
|
258
262
|
createItem(intl, icon, route, permission, ability, keywords = []) {
|
|
259
263
|
return {
|
|
264
|
+
priority: this.defaultPriorityForRoute(route),
|
|
260
265
|
label: this.intl.t(intl),
|
|
261
266
|
description: this.intl.t(intl),
|
|
262
267
|
icon,
|
|
@@ -269,6 +274,8 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
269
274
|
|
|
270
275
|
createHubItem(label, icon, route, permission, ability, keywords = []) {
|
|
271
276
|
return {
|
|
277
|
+
pinnedFirst: true,
|
|
278
|
+
priority: this.defaultPriorityForRoute(route),
|
|
272
279
|
label,
|
|
273
280
|
description: label,
|
|
274
281
|
icon,
|
|
@@ -280,8 +287,9 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
280
287
|
}
|
|
281
288
|
|
|
282
289
|
registryItem(menuItem) {
|
|
283
|
-
|
|
290
|
+
const registryMenuItem = {
|
|
284
291
|
...menuItem,
|
|
292
|
+
_virtual: true,
|
|
285
293
|
label: menuItem.intl ? this.intl.t(menuItem.intl) : (menuItem.title ?? menuItem.label),
|
|
286
294
|
description: menuItem.description,
|
|
287
295
|
icon: menuItem.icon,
|
|
@@ -289,15 +297,83 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
289
297
|
permission: menuItem.permission,
|
|
290
298
|
visible: menuItem.visible,
|
|
291
299
|
keywords: [menuItem.slug, menuItem.view, menuItem.section, menuItem.title, menuItem.label, menuItem.intl, ...(menuItem.keywords ?? [])].filter(Boolean),
|
|
292
|
-
|
|
300
|
+
activeWhen: () => isMenuItemActive(menuItem.section, menuItem.slug, menuItem.view),
|
|
293
301
|
};
|
|
302
|
+
|
|
303
|
+
registryMenuItem.onClick = () => this.universe.transitionMenuItem(`${this.routePrefix}virtual`, registryMenuItem);
|
|
304
|
+
|
|
305
|
+
return registryMenuItem;
|
|
294
306
|
}
|
|
295
307
|
|
|
296
308
|
withRegistryItems(section, items) {
|
|
297
309
|
const registryProperty = SECTION_REGISTRY_KEYS[section];
|
|
298
310
|
const registryItems = (this[registryProperty] ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
|
|
299
311
|
|
|
300
|
-
return [...items, ...registryItems];
|
|
312
|
+
return this.sortByPriority([...items, ...registryItems]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
sortByPriority(items = []) {
|
|
316
|
+
return [...items]
|
|
317
|
+
.map((item, index) => ({ item, index }))
|
|
318
|
+
.sort((a, b) => {
|
|
319
|
+
if (a.item.pinnedFirst !== b.item.pinnedFirst) {
|
|
320
|
+
return a.item.pinnedFirst ? -1 : 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const priorityOrder = (a.item.priority ?? 0) - (b.item.priority ?? 0);
|
|
324
|
+
|
|
325
|
+
if (priorityOrder !== 0) {
|
|
326
|
+
return priorityOrder;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return a.index - b.index;
|
|
330
|
+
})
|
|
331
|
+
.map(({ item }) => item);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
defaultPriorityForRoute(route) {
|
|
335
|
+
const priorities = {
|
|
336
|
+
'operations.orders': 0,
|
|
337
|
+
'operations.orchestrator': 1,
|
|
338
|
+
'operations.scheduler': 2,
|
|
339
|
+
'operations.order-config': 3,
|
|
340
|
+
'operations.service-rates': 4,
|
|
341
|
+
'management.index': 0,
|
|
342
|
+
'management.drivers': 1,
|
|
343
|
+
'management.vehicles': 2,
|
|
344
|
+
'management.fleets': 3,
|
|
345
|
+
'management.vendors': 4,
|
|
346
|
+
'management.contacts': 5,
|
|
347
|
+
'management.places': 6,
|
|
348
|
+
'management.fuel-reports': 7,
|
|
349
|
+
'management.fuel-transactions': 8,
|
|
350
|
+
'management.issues': 9,
|
|
351
|
+
'maintenance.index': 0,
|
|
352
|
+
'maintenance.schedules': 1,
|
|
353
|
+
'maintenance.work-orders': 2,
|
|
354
|
+
'maintenance.maintenances': 3,
|
|
355
|
+
'maintenance.equipment': 4,
|
|
356
|
+
'maintenance.parts': 5,
|
|
357
|
+
'connectivity.telematics': 0,
|
|
358
|
+
'connectivity.fuel-providers': 1,
|
|
359
|
+
'connectivity.devices': 2,
|
|
360
|
+
'connectivity.sensors': 3,
|
|
361
|
+
'connectivity.events': 4,
|
|
362
|
+
'analytics.index': 0,
|
|
363
|
+
'analytics.reports': 1,
|
|
364
|
+
'settings.index': 0,
|
|
365
|
+
'settings.navigator-app': 1,
|
|
366
|
+
'settings.map': 2,
|
|
367
|
+
'settings.payments': 3,
|
|
368
|
+
'settings.notifications': 4,
|
|
369
|
+
'settings.routing': 5,
|
|
370
|
+
'settings.orchestrator': 6,
|
|
371
|
+
'settings.scheduling': 7,
|
|
372
|
+
'settings.custom-fields': 8,
|
|
373
|
+
'settings.avatars': 9,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
return priorities[route] ?? 0;
|
|
301
377
|
}
|
|
302
378
|
|
|
303
379
|
fullRoute(route) {
|
|
@@ -314,6 +390,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
314
390
|
}
|
|
315
391
|
}
|
|
316
392
|
|
|
393
|
+
@action shouldSyncInitialActiveParent({ activePath = [], routeName }) {
|
|
394
|
+
const [parent, child] = activePath;
|
|
395
|
+
const defaultOrdersRoutes = [this.fullRoute('operations.orders'), this.fullRoute('operations.orders.index')];
|
|
396
|
+
const isDefaultOrdersLanding = parent?.id === 'operations' && child?.route === this.fullRoute('operations.orders') && defaultOrdersRoutes.includes(routeName);
|
|
397
|
+
|
|
398
|
+
return !isDefaultOrdersLanding;
|
|
399
|
+
}
|
|
400
|
+
|
|
317
401
|
@action
|
|
318
402
|
async searchNavigation({ query, limit = 12 }) {
|
|
319
403
|
const trimmedQuery = query?.trim();
|
|
@@ -5,6 +5,7 @@ import { action } from '@ember/object';
|
|
|
5
5
|
import { task } from 'ember-concurrency';
|
|
6
6
|
import { isArray } from '@ember/array';
|
|
7
7
|
import { startOfWeek, endOfWeek, format } from 'date-fns';
|
|
8
|
+
import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
|
|
8
9
|
|
|
9
10
|
export default class MapDrawerDeviceEventListingComponent extends Component {
|
|
10
11
|
@service store;
|
|
@@ -115,6 +116,7 @@ export default class MapDrawerDeviceEventListingComponent extends Component {
|
|
|
115
116
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.device-event') }),
|
|
116
117
|
cellClassNames: 'overflow-visible',
|
|
117
118
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
119
|
+
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
118
120
|
width: '10%',
|
|
119
121
|
actions: [
|
|
120
122
|
{
|
|
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
|
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
|
+
import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
|
|
5
6
|
|
|
6
7
|
export default class MapDrawerDriverListingComponent extends Component {
|
|
7
8
|
@service driverActions;
|
|
@@ -71,6 +72,7 @@ export default class MapDrawerDriverListingComponent extends Component {
|
|
|
71
72
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.driver') }),
|
|
72
73
|
cellClassNames: 'overflow-visible',
|
|
73
74
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
75
|
+
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
74
76
|
width: '90px',
|
|
75
77
|
actions: [
|
|
76
78
|
{
|
|
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
|
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
|
+
import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
|
|
5
6
|
|
|
6
7
|
export default class MapDrawerPlaceListingComponent extends Component {
|
|
7
8
|
@service placeActions;
|
|
@@ -53,6 +54,7 @@ export default class MapDrawerPlaceListingComponent extends Component {
|
|
|
53
54
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.place') }),
|
|
54
55
|
cellClassNames: 'overflow-visible',
|
|
55
56
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
57
|
+
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
56
58
|
width: '90px',
|
|
57
59
|
actions: [
|
|
58
60
|
{
|
|
@@ -4,23 +4,28 @@
|
|
|
4
4
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
5
5
|
<PowerSelect
|
|
6
6
|
@options={{this.trackables}}
|
|
7
|
-
@selected={{this.
|
|
7
|
+
@selected={{this.selectedTrackable}}
|
|
8
8
|
@onChange={{this.onResourceSelected}}
|
|
9
|
+
@searchEnabled={{true}}
|
|
10
|
+
@searchField="trackableSearchText"
|
|
9
11
|
@placeholder={{t "common.select-field" field="trackable"}}
|
|
10
12
|
@triggerClass="form-select form-input form-input-sm w-48"
|
|
11
13
|
as |option|
|
|
12
14
|
>
|
|
13
15
|
<div class="flex flex-row items-start space-x-2">
|
|
14
16
|
<div class="inline-block">
|
|
15
|
-
{{#if (eq
|
|
16
|
-
<Image src={{option.photo_url}} @fallbackSrc={{config "defaultValues.driverAvatar"}} class="w-4 h-4" />
|
|
17
|
+
{{#if (eq option.modelName "driver")}}
|
|
18
|
+
<Image src={{option.resource.photo_url}} @fallbackSrc={{config "defaultValues.driverAvatar"}} class="w-4 h-4" />
|
|
17
19
|
{{else}}
|
|
18
|
-
<Image src={{option.avatar_url}} @fallbackSrc={{config "defaultValues.vehicleAvatar"}} class="w-4 h-4" />
|
|
20
|
+
<Image src={{option.resource.avatar_url}} @fallbackSrc={{config "defaultValues.vehicleAvatar"}} class="w-4 h-4" />
|
|
19
21
|
{{/if}}
|
|
20
22
|
</div>
|
|
21
23
|
<div class="inline-block text-sm truncate">
|
|
22
|
-
<div class="font-semibold text-sm normalize-in-trigger">{{
|
|
23
|
-
<div class="hide-from-trigger text-xs">{{
|
|
24
|
+
<div class="font-semibold text-sm normalize-in-trigger">{{option.primaryLabel}}</div>
|
|
25
|
+
<div class="hide-from-trigger text-xs">{{option.secondaryLabel}}</div>
|
|
26
|
+
{{#if option.deviceLabel}}
|
|
27
|
+
<div class="hide-from-trigger text-xs text-gray-500 dark:text-gray-400 truncate">{{option.deviceLabel}}</div>
|
|
28
|
+
{{/if}}
|
|
24
29
|
</div>
|
|
25
30
|
</div>
|
|
26
31
|
</PowerSelect>
|
|
@@ -8,6 +8,7 @@ import { htmlSafe } from '@ember/template';
|
|
|
8
8
|
import { startOfWeek, endOfWeek, format } from 'date-fns';
|
|
9
9
|
import getModelName from '@fleetbase/ember-core/utils/get-model-name';
|
|
10
10
|
import ensureLeafletDrawEditNamespace from '../../../utils/leaflet-draw-namespace-guard';
|
|
11
|
+
import { buildTrackableOption } from '../../../utils/trackable-option';
|
|
11
12
|
|
|
12
13
|
const L = window.leaflet || window.L;
|
|
13
14
|
|
|
@@ -29,6 +30,7 @@ export default class MapDrawerPositionListingComponent extends Component {
|
|
|
29
30
|
@tracked replaySpeed = '1';
|
|
30
31
|
@tracked positionsLayer = null;
|
|
31
32
|
@tracked positionOverlayIds = [];
|
|
33
|
+
@tracked selectedTrackableModelName = null;
|
|
32
34
|
|
|
33
35
|
/** Computed properties - read state from service */
|
|
34
36
|
get isReplaying() {
|
|
@@ -47,7 +49,17 @@ export default class MapDrawerPositionListingComponent extends Component {
|
|
|
47
49
|
const vehicles = this.mapManager.livemap?.vehicles ?? [];
|
|
48
50
|
const drivers = this.mapManager.livemap?.drivers ?? [];
|
|
49
51
|
|
|
50
|
-
return [...vehicles, ...drivers];
|
|
52
|
+
return [...vehicles.map((vehicle) => buildTrackableOption(vehicle, 'vehicle')), ...drivers.map((driver) => buildTrackableOption(driver, 'driver'))];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get selectedTrackable() {
|
|
56
|
+
if (!this.resource) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const resourceType = this.resourceType;
|
|
61
|
+
|
|
62
|
+
return this.trackables.find((trackable) => trackable.resource?.id === this.resource?.id && trackable.modelName === resourceType) ?? null;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
get replayProgressWidth() {
|
|
@@ -72,7 +84,7 @@ export default class MapDrawerPositionListingComponent extends Component {
|
|
|
72
84
|
if (!this.resource) {
|
|
73
85
|
return 'resource';
|
|
74
86
|
}
|
|
75
|
-
return getModelName(this.resource) || 'resource';
|
|
87
|
+
return this.selectedTrackableModelName || getModelName(this.resource) || 'resource';
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
get hasPositions() {
|
|
@@ -179,8 +191,9 @@ export default class MapDrawerPositionListingComponent extends Component {
|
|
|
179
191
|
this.positionOverlayIds = [];
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
@action onResourceSelected(
|
|
183
|
-
this.resource = resource;
|
|
194
|
+
@action onResourceSelected(trackable) {
|
|
195
|
+
this.resource = trackable?.resource ?? null;
|
|
196
|
+
this.selectedTrackableModelName = trackable?.modelName ?? null;
|
|
184
197
|
this.loadPositions.perform();
|
|
185
198
|
}
|
|
186
199
|
|
|
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
|
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
|
+
import calculateMapDrawerDropdownPosition from '../../../utils/map-drawer-dropdown-position';
|
|
5
6
|
|
|
6
7
|
export default class MapDrawerVehicleListingComponent extends Component {
|
|
7
8
|
@service vehicleActions;
|
|
@@ -65,6 +66,7 @@ export default class MapDrawerVehicleListingComponent extends Component {
|
|
|
65
66
|
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.vehicle') }),
|
|
66
67
|
cellClassNames: 'overflow-visible',
|
|
67
68
|
wrapperClass: 'flex items-center justify-end mx-2',
|
|
69
|
+
calculatePosition: calculateMapDrawerDropdownPosition,
|
|
68
70
|
width: '90px',
|
|
69
71
|
actions: [
|
|
70
72
|
{
|