@fleetbase/fleetops-engine 0.6.20 → 0.6.21
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/details.hbs +92 -43
- package/addon/components/device/form.hbs +108 -60
- package/addon/components/device/form.js +36 -8
- package/addon/components/device/panel-header.hbs +32 -0
- package/addon/components/device/panel-header.js +3 -0
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/driver/form.js +49 -47
- 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 +58 -0
- package/addon/components/map/drawer/device-event-listing.js +181 -0
- package/addon/components/map/drawer/position-listing.hbs +84 -0
- package/addon/components/map/drawer/position-listing.js +289 -0
- package/addon/components/map/drawer.js +2 -0
- package/addon/components/map/leaflet-live-map.hbs +7 -2
- package/addon/components/order/details/payload.hbs +6 -4
- package/addon/components/order/details/payload.js +2 -0
- package/addon/components/order-config-manager/custom-fields.js +1 -1
- package/addon/components/positions-replay.hbs +333 -0
- package/addon/components/positions-replay.js +372 -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 +1 -1
- 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 +21 -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/vehicles/index/details/positions.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/movement-tracker.js +81 -30
- 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/vehicles/index/details/positions.hbs +1 -0
- package/addon/utils/fleet-ops-options.js +95 -0
- package/app/components/device/panel-header.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/positions-replay.js +1 -0
- package/app/components/sensor/panel-header.js +1 -0
- package/app/helpers/get-fleet-ops-option-label.js +1 -0
- package/app/routes/management/vehicles/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/Internal/v1/TelematicWebhookController.php +170 -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/Device.php +72 -10
- package/server/src/Models/DeviceEvent.php +7 -0
- package/server/src/Models/Driver.php +28 -1
- package/server/src/Models/Payload.php +0 -1
- package/server/src/Models/Place.php +4 -1
- package/server/src/Models/Position.php +17 -17
- package/server/src/Models/Sensor.php +78 -13
- package/server/src/Models/Telematic.php +116 -6
- package/server/src/Models/Vehicle.php +8 -11
- 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 +12 -1
|
@@ -0,0 +1,333 @@
|
|
|
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 @value={{this.dateFilter}} @range={{true}} @onSelect={{this.onDateRangeChanged}} @autoClose={{true}} @placeholder="Select date range" class="w-full form-input form-input-sm" />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="filter-group">
|
|
91
|
+
<label class="block uppercase tracking-wide text-xs font-medium text-gray-700 dark:text-gray-400 mb-0.5">
|
|
92
|
+
Order
|
|
93
|
+
</label>
|
|
94
|
+
<ModelSelect
|
|
95
|
+
@modelName="order"
|
|
96
|
+
@selectedModel={{this.selectedOrder}}
|
|
97
|
+
@query={{this.orderFilters}}
|
|
98
|
+
@placeholder="Filter by order"
|
|
99
|
+
@triggerClass="form-select form-input form-input-sm"
|
|
100
|
+
@infiniteScroll={{false}}
|
|
101
|
+
@renderInPlace={{true}}
|
|
102
|
+
@onChange={{this.onOrderSelected}}
|
|
103
|
+
as |order|
|
|
104
|
+
>
|
|
105
|
+
<div class="flex items-center justify-between">
|
|
106
|
+
<span>{{order.tracking}}</span>
|
|
107
|
+
<span class="text-xs text-gray-500">{{order.createdAt}}</span>
|
|
108
|
+
</div>
|
|
109
|
+
</ModelSelect>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="filter-actions flex items-end space-x-2">
|
|
113
|
+
<Button @icon="search" @text={{t "common.search"}} @isLoading={{this.loadPositions.isRunning}} @onClick={{perform this.loadPositions}} />
|
|
114
|
+
<Button @type="danger" @icon="times" @text={{t "common.clear"}} @disabled={{this.loadPositions.isRunning}} @onClick={{this.clearFilters}} />
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="positions-replay-map-container border-b border-gray-200 dark:border-gray-700">
|
|
120
|
+
<div class="replay-controls px-3 py-2">
|
|
121
|
+
<div class="flex items-center justify-between space-x-2">
|
|
122
|
+
<div class="flex items-center space-x-2">
|
|
123
|
+
<Button @type="danger" @text="Stop" @icon="stop" @size="xs" @onClick={{this.stopReplay}} />
|
|
124
|
+
<Button
|
|
125
|
+
@type="success"
|
|
126
|
+
@text="Play"
|
|
127
|
+
@icon="play"
|
|
128
|
+
@size="xs"
|
|
129
|
+
@isLoading={{this.isReplaying}}
|
|
130
|
+
@disabled={{or this.replayPositions.isRunning (not this.hasPositions)}}
|
|
131
|
+
@onClick={{this.startReplay}}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<div class="speed-control">
|
|
135
|
+
<label class="text-xs uppercase tracking-wide font-medium text-gray-700 dark:text-gray-400 mr-1">
|
|
136
|
+
Speed:
|
|
137
|
+
</label>
|
|
138
|
+
<Select
|
|
139
|
+
@value={{this.replaySpeed}}
|
|
140
|
+
@options={{this.speedOptions}}
|
|
141
|
+
@optionValue="value"
|
|
142
|
+
@optionLabel="label"
|
|
143
|
+
@onSelect={{this.onSpeedChanged}}
|
|
144
|
+
class="form-select speed-select"
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{{#if this.isReplaying}}
|
|
150
|
+
<div class="replay-progress flex items-center space-x-1">
|
|
151
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">
|
|
152
|
+
{{this.currentReplayIndex}}/{{this.totalPositions}}
|
|
153
|
+
</span>
|
|
154
|
+
<div class="progress-bar w-32 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
155
|
+
<div class="progress-fill h-full bg-blue-600 transition-all duration-300" style={{this.replayProgressWidth}}></div>
|
|
156
|
+
</div>
|
|
157
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
158
|
+
{{this.replayProgress}}%
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
{{/if}}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
{{#if this.hasPositions}}
|
|
167
|
+
<div class="flex flex-col border-b border-gray-200 dark:border-gray-700">
|
|
168
|
+
<div class="px-3 mb-1">
|
|
169
|
+
<h3 class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Position Metrics</h3>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2 px-2 pb-2">
|
|
172
|
+
<div class="metric-card">
|
|
173
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Total Distance</div>
|
|
174
|
+
<div class="metric-value font-semibold text-blue-600 dark:text-blue-400">
|
|
175
|
+
{{this.totalDistance}}
|
|
176
|
+
km
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="metric-card">
|
|
180
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Duration</div>
|
|
181
|
+
<div class="metric-value font-semibold text-green-600 dark:text-green-400">
|
|
182
|
+
{{this.formattedDuration}}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="metric-card">
|
|
186
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Max Speed</div>
|
|
187
|
+
<div class="metric-value font-semibold text-orange-600 dark:text-orange-400">
|
|
188
|
+
{{this.maxSpeed}}
|
|
189
|
+
km/h
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="metric-card">
|
|
193
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Avg Speed</div>
|
|
194
|
+
<div class="metric-value font-semibold text-purple-600 dark:text-purple-400">
|
|
195
|
+
{{this.avgSpeed}}
|
|
196
|
+
km/h
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="metric-card">
|
|
200
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Speeding Events</div>
|
|
201
|
+
<div class="metric-value font-semibold text-red-600 dark:text-red-400">
|
|
202
|
+
{{this.speedingCount}}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="metric-card">
|
|
206
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Dwell Times</div>
|
|
207
|
+
<div class="metric-value font-semibold text-yellow-600 dark:text-yellow-400">
|
|
208
|
+
{{this.dwellCount}}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="metric-card">
|
|
212
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Acceleration Events</div>
|
|
213
|
+
<div class="metric-value font-semibold text-indigo-600 dark:text-indigo-400">
|
|
214
|
+
{{this.accelerationCount}}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="metric-card">
|
|
218
|
+
<div class="metric-label truncate text-xs text-gray-500 dark:text-gray-400">Total Positions</div>
|
|
219
|
+
<div class="metric-value font-semibold text-gray-600 dark:text-gray-400">
|
|
220
|
+
{{this.totalPositions}}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
{{/if}}
|
|
226
|
+
|
|
227
|
+
{{!-- {{#if this.hasPositions}}
|
|
228
|
+
<div class="positions-replay-timeline">
|
|
229
|
+
<div class="px-3">
|
|
230
|
+
<h3 class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Position Timeline</h3>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="timeline-container overflow-x-auto">
|
|
233
|
+
<div class="timeline-track flex space-x-2 pb-4">
|
|
234
|
+
{{#each this.positions as |position index|}}
|
|
235
|
+
<div
|
|
236
|
+
class="timeline-item flex-shrink-0 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded transition-colors
|
|
237
|
+
{{if (eq index this.currentReplayIndex) 'bg-blue-100 dark:bg-blue-900'}}"
|
|
238
|
+
role="button"
|
|
239
|
+
{{on "click" (fn this.onPositionClicked position)}}
|
|
240
|
+
>
|
|
241
|
+
<div class="timeline-marker w-3 h-3 rounded-full bg-blue-600 mx-auto mb-1"></div>
|
|
242
|
+
<div class="timeline-content text-xs text-center whitespace-nowrap">
|
|
243
|
+
<div class="font-medium">{{add index 1}}</div>
|
|
244
|
+
<div class="text-gray-500 dark:text-gray-400">{{position.speedKmh}} km/h</div>
|
|
245
|
+
<div class="text-gray-400 dark:text-gray-500 text-xxs">
|
|
246
|
+
{{format-date position.created_at "HH:mm:ss"}}
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
{{/each}}
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
{{/if}} --}}
|
|
255
|
+
|
|
256
|
+
{{#if this.hasPositions}}
|
|
257
|
+
<div class="positions-replay-table">
|
|
258
|
+
<div class="px-3">
|
|
259
|
+
<h3 class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Position Data</h3>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="overflow-x-auto">
|
|
262
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
263
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
264
|
+
<tr>
|
|
265
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
266
|
+
#
|
|
267
|
+
</th>
|
|
268
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
269
|
+
Timestamp
|
|
270
|
+
</th>
|
|
271
|
+
<th class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
272
|
+
Latitude
|
|
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
|
+
Longitude
|
|
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
|
+
Speed (km/h)
|
|
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
|
+
Heading
|
|
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
|
+
Altitude (m)
|
|
285
|
+
</th>
|
|
286
|
+
</tr>
|
|
287
|
+
</thead>
|
|
288
|
+
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
289
|
+
{{#each this.positions as |position index|}}
|
|
290
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 {{if (eq index this.currentReplayIndex) 'bg-blue-50 dark:bg-blue-900'}}">
|
|
291
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
292
|
+
{{add index 1}}
|
|
293
|
+
</td>
|
|
294
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
295
|
+
{{position.timestamp}}
|
|
296
|
+
</td>
|
|
297
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
298
|
+
{{format-number position.latitude minimumFractionDigits=6 maximumFractionDigits=6}}
|
|
299
|
+
</td>
|
|
300
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
301
|
+
{{format-number position.longitude minimumFractionDigits=6 maximumFractionDigits=6}}
|
|
302
|
+
</td>
|
|
303
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
304
|
+
{{position.speedKmh}}
|
|
305
|
+
</td>
|
|
306
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
307
|
+
{{or position.heading "N/A"}}
|
|
308
|
+
</td>
|
|
309
|
+
<td class="px-2 py-1 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
310
|
+
{{or position.altitude "N/A"}}
|
|
311
|
+
</td>
|
|
312
|
+
</tr>
|
|
313
|
+
{{/each}}
|
|
314
|
+
</tbody>
|
|
315
|
+
</table>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
{{/if}}
|
|
319
|
+
|
|
320
|
+
{{#unless this.hasPositions}}
|
|
321
|
+
{{#if this.loadPositions.isIdle}}
|
|
322
|
+
<div class="empty-state bg-white dark:bg-gray-800 rounded-lg shadow-sm p-12 text-center">
|
|
323
|
+
<FaIcon @icon="map-marked-alt" @size="2x" class="text-gray-400 mb-2" />
|
|
324
|
+
<h3 class="text-base font-semibold text-gray-700 dark:text-gray-300">
|
|
325
|
+
No Positions Found
|
|
326
|
+
</h3>
|
|
327
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
328
|
+
Try adjusting your filters or select a different date range.
|
|
329
|
+
</p>
|
|
330
|
+
</div>
|
|
331
|
+
{{/if}}
|
|
332
|
+
{{/unless}}
|
|
333
|
+
</div>
|