@fleetbase/fleetops-engine 0.6.20 → 0.6.22
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/custom-entity/form.hbs +14 -14
- package/addon/components/device/card.hbs +1 -0
- package/addon/components/device/card.js +3 -0
- package/addon/components/device/details.hbs +92 -43
- package/addon/components/device/form.hbs +108 -60
- package/addon/components/device/form.js +36 -8
- package/addon/components/device/manager.hbs +29 -0
- package/addon/components/device/manager.js +95 -0
- package/addon/components/device/panel-header.hbs +32 -0
- package/addon/components/device/panel-header.js +3 -0
- package/addon/components/device/pill.hbs +16 -0
- package/addon/components/device/pill.js +3 -0
- package/addon/components/driver/details.hbs +4 -0
- package/addon/components/driver/details.js +19 -1
- package/addon/components/driver/form.hbs +14 -3
- package/addon/components/driver/form.js +49 -47
- package/addon/components/driver/pill.hbs +17 -0
- package/addon/components/driver/pill.js +3 -0
- package/addon/components/entity/form.hbs +7 -5
- package/addon/components/layout/fleet-ops-sidebar.js +12 -12
- package/addon/components/map/drawer/device-event-listing.hbs +64 -0
- package/addon/components/map/drawer/device-event-listing.js +181 -0
- package/addon/components/map/drawer/position-listing.hbs +100 -0
- package/addon/components/map/drawer/position-listing.js +455 -0
- package/addon/components/map/drawer.js +2 -0
- package/addon/components/map/leaflet-live-map.hbs +7 -2
- package/addon/components/modals/attach-device.hbs +18 -0
- package/addon/components/modals/attach-device.js +3 -0
- package/addon/components/order/details/detail.hbs +2 -54
- package/addon/components/order/details/detail.js +1 -0
- package/addon/components/order/details/payload.hbs +6 -4
- package/addon/components/order/details/payload.js +2 -0
- package/addon/components/order/pill.hbs +34 -0
- package/addon/components/order/pill.js +3 -0
- package/addon/components/order-config-manager/custom-fields.js +1 -1
- package/addon/components/positions-replay.hbs +339 -0
- package/addon/components/positions-replay.js +409 -0
- package/addon/components/sensor/details.hbs +64 -38
- package/addon/components/sensor/form.hbs +112 -63
- package/addon/components/sensor/form.js +36 -24
- package/addon/components/sensor/panel-header.hbs +32 -0
- package/addon/components/sensor/panel-header.js +3 -0
- package/addon/components/telematic/details.hbs +40 -16
- package/addon/components/telematic/form.hbs +63 -64
- package/addon/components/telematic/form.js +73 -4
- package/addon/components/vehicle/card.hbs +2 -2
- package/addon/components/vehicle/details.hbs +4 -0
- package/addon/components/vehicle/details.js +19 -1
- package/addon/components/vehicle/form.hbs +4 -0
- package/addon/components/vehicle/pill.hbs +34 -0
- package/addon/components/vehicle/pill.js +3 -0
- package/addon/controllers/analytics/reports/index/edit.js +1 -1
- package/addon/controllers/connectivity/devices/index/details.js +22 -1
- package/addon/controllers/connectivity/devices/index/edit.js +66 -1
- package/addon/controllers/connectivity/devices/index.js +51 -9
- package/addon/controllers/connectivity/events/index.js +65 -16
- package/addon/controllers/connectivity/sensors/index/details.js +22 -1
- package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
- package/addon/controllers/connectivity/sensors/index.js +66 -6
- package/addon/controllers/connectivity/telematics/index/details.js +22 -1
- package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
- package/addon/controllers/connectivity/telematics/index.js +20 -11
- package/addon/controllers/management/fleets/index/details.js +26 -21
- package/addon/controllers/management/fleets/index/edit.js +9 -6
- package/addon/controllers/management/vehicles/index/details.js +26 -13
- package/addon/controllers/settings/custom-fields.js +6 -0
- package/addon/helpers/get-fleet-ops-option-label.js +11 -0
- package/addon/routes/connectivity/devices/index/details.js +27 -1
- package/addon/routes/connectivity/devices/index/edit.js +27 -1
- package/addon/routes/connectivity/sensors/index/details.js +27 -1
- package/addon/routes/connectivity/sensors/index/edit.js +27 -1
- package/addon/routes/connectivity/telematics/index/details.js +27 -1
- package/addon/routes/connectivity/telematics/index/edit.js +27 -1
- package/addon/routes/management/drivers/index/details/positions.js +3 -0
- package/addon/routes/management/vehicles/index/details/positions.js +3 -0
- package/addon/routes.js +4 -0
- package/addon/services/movement-tracker.js +81 -30
- package/addon/services/position-playback.js +486 -0
- package/addon/services/resource-metadata.js +46 -0
- package/addon/styles/fleetops-engine.css +157 -0
- package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/devices/index/details.hbs +15 -2
- package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
- package/addon/templates/connectivity/events/index.hbs +1 -1
- package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
- package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
- package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
- package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
- package/addon/templates/management/drivers/index/details/positions.hbs +2 -0
- package/addon/templates/management/vehicles/index/details/devices.hbs +1 -2
- package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
- package/addon/utils/fleet-ops-options.js +95 -0
- package/app/components/device/card.js +1 -0
- package/app/components/device/manager.js +1 -0
- package/app/components/device/panel-header.js +1 -0
- package/app/components/device/pill.js +1 -0
- package/app/components/driver/pill.js +1 -0
- package/app/components/map/drawer/device-event-listing.js +1 -0
- package/app/components/map/drawer/position-listing.js +1 -0
- package/app/components/modals/attach-device.js +1 -0
- package/app/components/order/pill.js +1 -0
- package/app/components/positions-replay.js +1 -0
- package/app/components/sensor/panel-header.js +1 -0
- package/app/components/vehicle/pill.js +1 -0
- package/app/helpers/get-fleet-ops-option-label.js +1 -0
- package/app/routes/management/drivers/index/details/positions.js +1 -0
- package/app/routes/management/vehicles/index/details/positions.js +1 -0
- package/app/services/position-playback.js +1 -0
- package/app/services/resource-metadata.js +1 -0
- package/app/templates/management/drivers/index/details/positions.js +1 -0
- package/app/templates/management/vehicles/index/details/positions.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/config/telematics.php +111 -0
- package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
- package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
- package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
- package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
- package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
- package/server/src/Contracts/TelematicProviderInterface.php +119 -0
- package/server/src/Exceptions/TelematicProviderException.php +14 -0
- package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
- package/server/src/Http/Controllers/TelematicWebhookController.php +169 -0
- package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
- package/server/src/Http/Filter/PositionFilter.php +35 -0
- package/server/src/Http/Resources/v1/Position.php +44 -0
- package/server/src/Jobs/ReplayPositions.php +64 -0
- package/server/src/Jobs/SendPositionReplay.php +65 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
- package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
- package/server/src/Models/Asset.php +10 -8
- package/server/src/Models/Device.php +79 -12
- package/server/src/Models/DeviceEvent.php +33 -3
- package/server/src/Models/Driver.php +28 -1
- package/server/src/Models/Maintenance.php +15 -12
- package/server/src/Models/Part.php +2 -0
- package/server/src/Models/Payload.php +0 -1
- package/server/src/Models/Place.php +4 -1
- package/server/src/Models/Position.php +27 -17
- package/server/src/Models/Sensor.php +78 -13
- package/server/src/Models/Telematic.php +116 -6
- package/server/src/Models/TrackingNumber.php +3 -1
- package/server/src/Models/Vehicle.php +8 -11
- package/server/src/Models/WorkOrder.php +8 -5
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
- package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
- package/server/src/Support/Telematics/TelematicService.php +223 -0
- package/server/src/Support/Utils.php +1 -1
- package/server/src/routes.php +24 -1
|
@@ -16,25 +16,7 @@
|
|
|
16
16
|
<span>{{t "order.fields.driver-assigned"}}</span>
|
|
17
17
|
</div>
|
|
18
18
|
<div class="field-value">
|
|
19
|
-
<
|
|
20
|
-
<a href="javascript:;" class="flex flex-row space-x-2" {{on "click" (fn this.focusOrderAssignedDriver @resource.driver_assigned)}}>
|
|
21
|
-
<div class="relative shrink-0">
|
|
22
|
-
<Image
|
|
23
|
-
src={{avatar-url @resource.driver_assigned.photo_url}}
|
|
24
|
-
@fallbackSrc={{config "defaultValues.driverImage"}}
|
|
25
|
-
width="28"
|
|
26
|
-
height="28"
|
|
27
|
-
class="w-7 h-7 rounded-full ring-2 ring-gray-800 dark:ring-gray-700 shadow transition-shadow hover:shadow-md focus:shadow-md"
|
|
28
|
-
alt={{@resource.driver_assigned.name}}
|
|
29
|
-
/>
|
|
30
|
-
<FaIcon @icon="circle" @size="2xs" class="absolute left-0 top-0 h-2 w-2 {{if @resource.driver_assigned.online 'text-green-500' 'text-yellow-200'}}" />
|
|
31
|
-
</div>
|
|
32
|
-
<div>
|
|
33
|
-
<div class="text-sm">{{n-a @resource.driver_assigned.name}}</div>
|
|
34
|
-
<div class="text-xs text-gray-400 dark:text-gray-500">{{n-a @resource.driver_assigned.phone "No Phone"}}</div>
|
|
35
|
-
</div>
|
|
36
|
-
</a>
|
|
37
|
-
</div>
|
|
19
|
+
<Driver::Pill @driver={{@resource.driver_assigned}} @onClick={{this.focusOrderAssignedDriver}} />
|
|
38
20
|
</div>
|
|
39
21
|
</div>
|
|
40
22
|
<div class="field-info-container">
|
|
@@ -42,41 +24,7 @@
|
|
|
42
24
|
<span>{{t "order.fields.vehicle-assigned"}}</span>
|
|
43
25
|
</div>
|
|
44
26
|
<div class="field-value">
|
|
45
|
-
<
|
|
46
|
-
<div class="relative shrink-0">
|
|
47
|
-
<Image
|
|
48
|
-
src={{@resource.vehicle_assigned.photo_url}}
|
|
49
|
-
@fallbackSrc={{config "defaultValues.vehicleImage"}}
|
|
50
|
-
width="28"
|
|
51
|
-
height="28"
|
|
52
|
-
class="w-7 h-7 rounded-full ring-2 ring-gray-800 dark:ring-gray-700 shadow transition-shadow hover:shadow-md focus:shadow-md"
|
|
53
|
-
alt={{n-a @resource.vehicle_assigned.name @resource.vehicle_assigned.yearMakeModel}}
|
|
54
|
-
/>
|
|
55
|
-
<FaIcon @icon="circle" @size="2xs" class="absolute left-0 top-0 h-2 w-2 {{if @resource.vehicle_assigned.online 'text-green-500' 'text-yellow-200'}}" />
|
|
56
|
-
</div>
|
|
57
|
-
<div>
|
|
58
|
-
<div class="text-sm">{{n-a @resource.vehicle_assigned.name @resource.vehicle_assigned.yearMakeModel}}</div>
|
|
59
|
-
<div class="text-xs text-gray-400 dark:text-gray-500">{{or
|
|
60
|
-
@resource.vehicle_assigned.plate_number
|
|
61
|
-
@resource.vehicle_assigned.vin
|
|
62
|
-
@resource.vehicle_assigned.serial_number
|
|
63
|
-
@resource.vehicle_assigned.call_sign
|
|
64
|
-
}}</div>
|
|
65
|
-
</div>
|
|
66
|
-
<Attach::Tooltip @class="clean" @animation="scale" @placement="top">
|
|
67
|
-
<InputInfo>
|
|
68
|
-
<div class="text-xs font-semibold">{{@resource.vehicle_assigned.name @resource.vehicle_assigned.yearMakeModel}}</div>
|
|
69
|
-
<div class="text-xs">Driver: {{n-a @resource.vehicle_assigned.driver_name}}</div>
|
|
70
|
-
<div class="text-xs">
|
|
71
|
-
<span>Status:</span>
|
|
72
|
-
<span class="{{if @resource.vehicle_assigned.online 'text-green-500' 'text-red-400'}}">
|
|
73
|
-
{{if @resource.vehicle_assigned.online "Online" "Offline"}}
|
|
74
|
-
</span>
|
|
75
|
-
</div>
|
|
76
|
-
<div class="text-xs truncate">Pos: {{point-coordinates @resource.vehicle_assigned.location}}</div>
|
|
77
|
-
</InputInfo>
|
|
78
|
-
</Attach::Tooltip>
|
|
79
|
-
</a>
|
|
27
|
+
<Vehicle::Pill @vehicle={{@resource.vehicle_assigned}} />
|
|
80
28
|
</div>
|
|
81
29
|
</div>
|
|
82
30
|
<div class="field-info-container">
|
|
@@ -36,6 +36,7 @@ export default class OrderDetailsDetailComponent extends Component {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
@action focusOrderAssignedDriver(driver) {
|
|
39
|
+
console.log('[focusOrderAssignedDriver]', ...arguments);
|
|
39
40
|
this.driverActions.panel.view(driver);
|
|
40
41
|
this.leafletMapManager.map?.flyTo(driver.coordinates, 18);
|
|
41
42
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<ContentPanel @title={{t "order.fields.payload"}} @open={{true}} @actionButtons={{this.actionButtons}} @wrapperClass="bordered-top">
|
|
2
|
-
{{#if @resource.
|
|
2
|
+
{{#if @resource.isMultiDrop}}
|
|
3
3
|
<div class="space-y-2">
|
|
4
4
|
{{#each @resource.entitiesByDestination as |group|}}
|
|
5
5
|
<div class="rounded-md border border-gray-200 dark:border-gray-900 p-3">
|
|
@@ -40,13 +40,15 @@
|
|
|
40
40
|
<DropdownButton
|
|
41
41
|
@triggerClass="mr-2"
|
|
42
42
|
@iconClass="icon-text-height"
|
|
43
|
-
@
|
|
43
|
+
@size="xs"
|
|
44
|
+
@buttonSize="xs"
|
|
44
45
|
@icon="ellipsis-h"
|
|
45
46
|
@iconPrefix="fas"
|
|
46
47
|
@contentClass="dropdown-menu"
|
|
48
|
+
@renderInPlace={{true}}
|
|
47
49
|
as |dd|
|
|
48
50
|
>
|
|
49
|
-
<div class="next-dd-menu
|
|
51
|
+
<div class="next-dd-menu mx-0 mt-0i" aria-labelledby="user-menu">
|
|
50
52
|
<div class="px-1">
|
|
51
53
|
<div class="text-sm flex flex-row items-center px-3 py-1 rounded-md my-1 text-gray-300">
|
|
52
54
|
{{t "order.fields.waypoint-actions"}}
|
|
@@ -72,7 +74,7 @@
|
|
|
72
74
|
@icon="plus"
|
|
73
75
|
@iconPrefix="fas"
|
|
74
76
|
@text={{t "order.fields.add-item-button"}}
|
|
75
|
-
@size="
|
|
77
|
+
@size="xs"
|
|
76
78
|
@onClick={{perform this.addEntity group.waypoint}}
|
|
77
79
|
@permission="fleet-ops update order"
|
|
78
80
|
/>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{{#let (or @order @resource) as |resource|}}
|
|
2
|
+
<Pill
|
|
3
|
+
@resource={{resource}}
|
|
4
|
+
@onClick={{@onClick}}
|
|
5
|
+
@anchorClass={{@anchorClass}}
|
|
6
|
+
@imageClass={{@imageClass}}
|
|
7
|
+
@imageWrapperClass={{@imageWrapperClass}}
|
|
8
|
+
@contentWrapperClass={{@contentWrapperClass}}
|
|
9
|
+
@titleClass={{@titleClass}}
|
|
10
|
+
@subtitleClass={{@subtitleClass}}
|
|
11
|
+
...attributes
|
|
12
|
+
>
|
|
13
|
+
<:image>
|
|
14
|
+
<div class="pt-1">
|
|
15
|
+
<div class="rounded-md bg-white p-1">
|
|
16
|
+
<img src={{concat "data:image/png;base64," resource.tracking_number.qr_code}} class="w-12 h-12" alt={{resource.public_id}} />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</:image>
|
|
20
|
+
<:default>
|
|
21
|
+
<div class="text-sm font-semibold">{{n-a resource.tracking}}</div>
|
|
22
|
+
<div class="flex flex-row space-x-1">
|
|
23
|
+
<Badge @status={{@resource.status}} />
|
|
24
|
+
{{#if @resource.dispatched_at}}
|
|
25
|
+
<Badge @status="dispatched">{{concat "Dispatched at " @resource.dispatchedAt}}</Badge>
|
|
26
|
+
{{/if}}
|
|
27
|
+
</div>
|
|
28
|
+
<div class="mt-1">
|
|
29
|
+
<div class="text-xs text-gray-400 dark:text-gray-500">Date Created: {{@resource.createdAt}}</div>
|
|
30
|
+
<div class="text-xs text-gray-400 dark:text-gray-500">Type: {{smart-humanize @resource.type}}</div>
|
|
31
|
+
</div>
|
|
32
|
+
</:default>
|
|
33
|
+
</Pill>
|
|
34
|
+
{{/let}}
|
|
@@ -142,7 +142,7 @@ export default class OrderConfigManagerCustomFieldsComponent extends Component {
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
this.modalsManager.show('modals/custom-field-group-form', {
|
|
145
|
-
title: this.intl.t('
|
|
145
|
+
title: this.intl.t('modals.new-custom-field-group.modal-title'),
|
|
146
146
|
acceptButtonIcon: 'check',
|
|
147
147
|
acceptButtonIconPrefix: 'fas',
|
|
148
148
|
declineButtonIcon: 'times',
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
<div class="positions-replay-component" ...attributes>
|
|
2
|
+
<div class="fleetbase-leaflet-map-container map-wrapper relative" {{set-height (or @mapHeight 300)}}>
|
|
3
|
+
{{#if this.loadPositions.isRunning}}
|
|
4
|
+
<div class="absolute inset-0 h-full w-full">
|
|
5
|
+
<div class="flex items-center justify-center h-full">
|
|
6
|
+
<Spinner />
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
{{/if}}
|
|
10
|
+
<div class="next-map-container">
|
|
11
|
+
<LeafletMap @lat={{this.latitude}} @lng={{this.longitude}} @zoom={{this.zoom}} @onLoad={{this.didLoadMap}} class="next-leaflet-container-map positions-replay-map" as |layers|>
|
|
12
|
+
<layers.tile @url={{this.tileUrl}} />
|
|
13
|
+
{{#if this.resource}}
|
|
14
|
+
<layers.tracking-marker
|
|
15
|
+
@id={{this.resource.id}}
|
|
16
|
+
@publicId={{this.resource.public_id}}
|
|
17
|
+
@location={{point-to-coordinates this.resource.location}}
|
|
18
|
+
@rotationAngle={{or this.resource.heading 0}}
|
|
19
|
+
@icon={{icon iconUrl=(or this.resource.avatar_url this.resource.photo_url (config "defaultValues.vehicleAvatar")) iconSize=(array 28 28)}}
|
|
20
|
+
@onAdd={{fn this.onTrackingMarkerAdded this.resource}}
|
|
21
|
+
@draggable={{false}}
|
|
22
|
+
as |marker|
|
|
23
|
+
>
|
|
24
|
+
<marker.popup @permanent={{false}} @sticky={{true}}>
|
|
25
|
+
<div class="flex flex-row">
|
|
26
|
+
<div class="w-14 mr-2">
|
|
27
|
+
<img src={{or this.resource.photo_url this.resource.avatar_url}} alt={{this.resourceName}} class="rounded-md w-14 h-12 shadow-sm" />
|
|
28
|
+
</div>
|
|
29
|
+
<div class="flex-1">
|
|
30
|
+
<div class="text-xs font-semibold">{{this.resourceName}}</div>
|
|
31
|
+
<div class="text-xs">ID: {{n-a this.resource.public_id}}</div>
|
|
32
|
+
{{#if (eq this.resourceType "vehicle")}}
|
|
33
|
+
<div class="text-xs">Serial No: {{n-a this.resource.serial_number this.resource.vin this.resource.internal_id this.resource.id}}</div>
|
|
34
|
+
<div class="text-xs">Driver: {{n-a this.resource.driver_name}}</div>
|
|
35
|
+
{{/if}}
|
|
36
|
+
<div class="text-xs">Status:
|
|
37
|
+
<span class="{{if this.resource.online 'text-green-500' 'text-red-400'}}">{{if this.resource.online "Online" "Offline"}}</span></div>
|
|
38
|
+
<div class="text-xs truncate">Pos: {{point-coordinates this.resource.location}}</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</marker.popup>
|
|
42
|
+
<marker.tooltip @permanent={{false}} @sticky={{true}}>
|
|
43
|
+
<div class="flex items-center space-x-1">
|
|
44
|
+
<div class="text-xs font-semibold">{{this.resource.displayName}}</div>
|
|
45
|
+
<div>•</div>
|
|
46
|
+
<div class="text-xs {{if this.resource.online 'text-green-500' 'text-red-400'}}">{{if this.resource.online "Online" "Offline"}}</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="text-xs">ID: {{n-a this.resource.public_id}}</div>
|
|
49
|
+
{{#if (eq this.resourceType "vehicle")}}
|
|
50
|
+
<div class="text-xs">Serial No: {{or this.resource.serial_number this.resource.vin this.resource.internal_id this.resource.id "-"}}</div>
|
|
51
|
+
{{/if}}
|
|
52
|
+
<div class="text-xs truncate"><FaIcon @icon="location-dot" @size="xs" class="mr-0.5" />{{point-coordinates this.resource.location}}</div>
|
|
53
|
+
</marker.tooltip>
|
|
54
|
+
</layers.tracking-marker>
|
|
55
|
+
{{/if}}
|
|
56
|
+
{{#each this.positions as |position index|}}
|
|
57
|
+
<layers.circle-marker
|
|
58
|
+
@location={{array position.latitude position.longitude}}
|
|
59
|
+
@radius={{3}}
|
|
60
|
+
@color="#3b82f6"
|
|
61
|
+
@fillColor="#3b82f6"
|
|
62
|
+
@fillOpacity={{0.6}}
|
|
63
|
+
@onClick={{fn this.onPositionClicked position}}
|
|
64
|
+
as |marker|
|
|
65
|
+
>
|
|
66
|
+
<marker.popup>
|
|
67
|
+
<div class="text-xs">
|
|
68
|
+
<div><strong>Position {{add index 1}}</strong></div>
|
|
69
|
+
<div>Time: {{position.timestamp}}</div>
|
|
70
|
+
<div>Speed: {{position.speedKmh}} km/h</div>
|
|
71
|
+
<div>Heading: {{or position.heading "N/A"}}°</div>
|
|
72
|
+
<div>Altitude: {{or position.altitude "N/A"}} m</div>
|
|
73
|
+
</div>
|
|
74
|
+
</marker.popup>
|
|
75
|
+
</layers.circle-marker>
|
|
76
|
+
{{/each}}
|
|
77
|
+
</LeafletMap>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="positions-replay-filters px-3 pt-2">
|
|
82
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
|
|
83
|
+
<div class="filter-group relative">
|
|
84
|
+
<label class="block uppercase tracking-wide text-xs font-medium text-gray-700 dark:text-gray-400 mb-0.5">
|
|
85
|
+
Date Range
|
|
86
|
+
</label>
|
|
87
|
+
<DatePicker
|
|
88
|
+
@value={{this.dateFilter}}
|
|
89
|
+
@range={{true}}
|
|
90
|
+
@onSelect={{this.onDateRangeChanged}}
|
|
91
|
+
@autoClose={{true}}
|
|
92
|
+
@placeholder="Select date range"
|
|
93
|
+
class="w-full form-input form-input-sm"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="filter-group">
|
|
98
|
+
<label class="block uppercase tracking-wide text-xs font-medium text-gray-700 dark:text-gray-400 mb-0.5">
|
|
99
|
+
Order
|
|
100
|
+
</label>
|
|
101
|
+
<ModelSelect
|
|
102
|
+
@modelName="order"
|
|
103
|
+
@selectedModel={{this.selectedOrder}}
|
|
104
|
+
@query={{this.orderFilters}}
|
|
105
|
+
@placeholder="Filter by order"
|
|
106
|
+
@triggerClass="form-select form-input form-input-sm"
|
|
107
|
+
@infiniteScroll={{false}}
|
|
108
|
+
@renderInPlace={{true}}
|
|
109
|
+
@onChange={{this.onOrderSelected}}
|
|
110
|
+
as |order|
|
|
111
|
+
>
|
|
112
|
+
<div class="flex items-center justify-between">
|
|
113
|
+
<span>{{order.tracking}}</span>
|
|
114
|
+
<span class="text-xs text-gray-500">{{order.createdAt}}</span>
|
|
115
|
+
</div>
|
|
116
|
+
</ModelSelect>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="filter-actions flex items-end space-x-2">
|
|
120
|
+
<Button @type="danger" @icon="times" @text={{t "common.clear"}} @disabled={{this.loadPositions.isRunning}} @onClick={{this.clearFilters}} />
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div class="positions-replay-map-container border-b border-gray-200 dark:border-gray-700">
|
|
126
|
+
<div class="replay-controls px-3 py-2">
|
|
127
|
+
<div class="flex items-center justify-between space-x-2">
|
|
128
|
+
<div class="flex items-center space-x-2">
|
|
129
|
+
<Button @type="danger" @text="Stop" @icon="stop" @size="xs" @disabled={{not (or this.isReplaying this.isPaused)}} @onClick={{this.stopReplay}} />
|
|
130
|
+
|
|
131
|
+
{{#if (or this.isReplaying this.isPaused)}}
|
|
132
|
+
{{#if this.isReplaying}}
|
|
133
|
+
<Button @type="warning" @text="Pause" @icon="pause" @size="xs" @onClick={{this.pauseReplay}} />
|
|
134
|
+
{{else}}
|
|
135
|
+
<Button @type="success" @text="Resume" @icon="play" @size="xs" @onClick={{this.startReplay}} />
|
|
136
|
+
{{/if}}
|
|
137
|
+
{{else}}
|
|
138
|
+
<Button @type="success" @text="Play" @icon="play" @size="xs" @disabled={{not this.hasPositions}} @onClick={{this.startReplay}} />
|
|
139
|
+
{{/if}}
|
|
140
|
+
|
|
141
|
+
<div class="step-controls flex items-center space-x-1 border-l border-gray-300 dark:border-gray-700 pl-2 ml-2">
|
|
142
|
+
<Button @type="default" @icon="backward-step" @size="xs" @disabled={{not this.hasPositions}} @onClick={{this.stepBackward}} title="Step backward" />
|
|
143
|
+
<Button @type="default" @icon="forward-step" @size="xs" @disabled={{not this.hasPositions}} @onClick={{this.stepForward}} title="Step forward" />
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="speed-control border-l border-gray-300 dark:border-gray-700 pl-2 ml-2">
|
|
147
|
+
<label class="text-xs uppercase tracking-wide font-medium text-gray-700 dark:text-gray-400 mr-1">
|
|
148
|
+
Speed:
|
|
149
|
+
</label>
|
|
150
|
+
<Select
|
|
151
|
+
@value={{this.replaySpeed}}
|
|
152
|
+
@options={{this.speedOptions}}
|
|
153
|
+
@optionValue="value"
|
|
154
|
+
@optionLabel="label"
|
|
155
|
+
@onSelect={{this.onSpeedChanged}}
|
|
156
|
+
class="form-select speed-select"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{{#if (or this.isReplaying this.isPaused)}}
|
|
162
|
+
<div class="replay-progress flex items-center space-x-1">
|
|
163
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">
|
|
164
|
+
{{this.currentReplayIndex}}/{{this.totalPositions}}
|
|
165
|
+
</span>
|
|
166
|
+
<div class="progress-bar w-32 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
167
|
+
<div class="progress-fill h-full bg-blue-600 transition-all duration-300" style={{this.replayProgressWidth}}></div>
|
|
168
|
+
</div>
|
|
169
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
170
|
+
{{this.replayProgress}}%
|
|
171
|
+
</span>
|
|
172
|
+
</div>
|
|
173
|
+
{{/if}}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{{#if this.hasPositions}}
|
|
179
|
+
<div class="flex flex-col border-b border-gray-200 dark:border-gray-700">
|
|
180
|
+
<div class="px-3 mb-1">
|
|
181
|
+
<h3 class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Position Metrics</h3>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2 px-2 pb-2">
|
|
184
|
+
<div class="metric-card">
|
|
185
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Total Distance</div>
|
|
186
|
+
<div class="metric-value font-semibold text-blue-600 dark:text-blue-400">
|
|
187
|
+
{{this.totalDistance}}
|
|
188
|
+
km
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="metric-card">
|
|
192
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Duration</div>
|
|
193
|
+
<div class="metric-value font-semibold text-green-600 dark:text-green-400">
|
|
194
|
+
{{this.formattedDuration}}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
<div class="metric-card">
|
|
198
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Max Speed</div>
|
|
199
|
+
<div class="metric-value font-semibold text-orange-600 dark:text-orange-400">
|
|
200
|
+
{{this.maxSpeed}}
|
|
201
|
+
km/h
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="metric-card">
|
|
205
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Avg Speed</div>
|
|
206
|
+
<div class="metric-value font-semibold text-purple-600 dark:text-purple-400">
|
|
207
|
+
{{this.avgSpeed}}
|
|
208
|
+
km/h
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="metric-card">
|
|
212
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Speeding Events</div>
|
|
213
|
+
<div class="metric-value font-semibold text-red-600 dark:text-red-400">
|
|
214
|
+
{{this.speedingCount}}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="metric-card">
|
|
218
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Dwell Times</div>
|
|
219
|
+
<div class="metric-value font-semibold text-yellow-600 dark:text-yellow-400">
|
|
220
|
+
{{this.dwellCount}}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="metric-card">
|
|
224
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Acceleration Events</div>
|
|
225
|
+
<div class="metric-value font-semibold text-indigo-600 dark:text-indigo-400">
|
|
226
|
+
{{this.accelerationCount}}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
{{/if}}
|
|
232
|
+
|
|
233
|
+
{{!-- {{#if this.hasPositions}}
|
|
234
|
+
<div class="positions-replay-timeline">
|
|
235
|
+
<div class="px-3">
|
|
236
|
+
<h3 class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Position Timeline</h3>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="timeline-container overflow-x-auto">
|
|
239
|
+
<div class="timeline-track flex space-x-2 pb-4">
|
|
240
|
+
{{#each this.positions as |position index|}}
|
|
241
|
+
<div
|
|
242
|
+
class="timeline-item flex-shrink-0 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded transition-colors
|
|
243
|
+
{{if (eq index this.currentReplayIndex) 'bg-blue-100 dark:bg-blue-900'}}"
|
|
244
|
+
role="button"
|
|
245
|
+
{{on "click" (fn this.onPositionClicked position)}}
|
|
246
|
+
>
|
|
247
|
+
<div class="timeline-marker w-3 h-3 rounded-full bg-blue-600 mx-auto mb-1"></div>
|
|
248
|
+
<div class="timeline-content text-xs text-center whitespace-nowrap">
|
|
249
|
+
<div class="font-medium">{{add index 1}}</div>
|
|
250
|
+
<div class="text-gray-500 dark:text-gray-400">{{position.speedKmh}} km/h</div>
|
|
251
|
+
<div class="text-gray-400 dark:text-gray-500 text-xxs">
|
|
252
|
+
{{format-date position.created_at "HH:mm:ss"}}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
{{/each}}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
{{/if}} --}}
|
|
261
|
+
|
|
262
|
+
{{#if this.hasPositions}}
|
|
263
|
+
<div class="positions-replay-table">
|
|
264
|
+
<div class="px-3">
|
|
265
|
+
<h3 class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Position Data</h3>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="overflow-x-auto">
|
|
268
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
269
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
270
|
+
<tr>
|
|
271
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
272
|
+
#
|
|
273
|
+
</th>
|
|
274
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
275
|
+
Timestamp
|
|
276
|
+
</th>
|
|
277
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
278
|
+
Latitude
|
|
279
|
+
</th>
|
|
280
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
281
|
+
Longitude
|
|
282
|
+
</th>
|
|
283
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
284
|
+
Speed (km/h)
|
|
285
|
+
</th>
|
|
286
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
287
|
+
Heading
|
|
288
|
+
</th>
|
|
289
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
290
|
+
Altitude (m)
|
|
291
|
+
</th>
|
|
292
|
+
</tr>
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
295
|
+
{{#each this.positions as |position index|}}
|
|
296
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 {{if (eq index this.currentReplayIndex) 'bg-blue-50 dark:bg-blue-900'}}">
|
|
297
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
298
|
+
{{add index 1}}
|
|
299
|
+
</td>
|
|
300
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
301
|
+
{{position.timestamp}}
|
|
302
|
+
</td>
|
|
303
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
304
|
+
{{format-number position.latitude minimumFractionDigits=6 maximumFractionDigits=6}}
|
|
305
|
+
</td>
|
|
306
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
307
|
+
{{format-number position.longitude minimumFractionDigits=6 maximumFractionDigits=6}}
|
|
308
|
+
</td>
|
|
309
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
310
|
+
{{position.speedKmh}}
|
|
311
|
+
</td>
|
|
312
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
313
|
+
{{or position.heading "N/A"}}
|
|
314
|
+
</td>
|
|
315
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
316
|
+
{{or position.altitude "N/A"}}
|
|
317
|
+
</td>
|
|
318
|
+
</tr>
|
|
319
|
+
{{/each}}
|
|
320
|
+
</tbody>
|
|
321
|
+
</table>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
{{/if}}
|
|
325
|
+
|
|
326
|
+
{{#unless this.hasPositions}}
|
|
327
|
+
{{#if this.loadPositions.isIdle}}
|
|
328
|
+
<div class="empty-state bg-white dark:bg-gray-800 rounded-lg shadow-sm p-12 text-center">
|
|
329
|
+
<FaIcon @icon="map-marked-alt" @size="2x" class="text-gray-400 mb-2" />
|
|
330
|
+
<h3 class="text-base font-semibold text-gray-700 dark:text-gray-300">
|
|
331
|
+
No Positions Found
|
|
332
|
+
</h3>
|
|
333
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
334
|
+
Try adjusting your filters or select a different date range.
|
|
335
|
+
</p>
|
|
336
|
+
</div>
|
|
337
|
+
{{/if}}
|
|
338
|
+
{{/unless}}
|
|
339
|
+
</div>
|