diff --git a/frontend/src/components/Map.vue b/frontend/src/components/Map.vue index fdb19d8..f48665e 100644 --- a/frontend/src/components/Map.vue +++ b/frontend/src/components/Map.vue @@ -28,6 +28,7 @@ </div> <PointInfo :point="selectedPoint" + :device="devicesById[selectedPoint?.deviceId]" ref="popup" @remove="onRemove" @edit="editPoint" @@ -36,6 +37,7 @@ <div class="controls"> <div class="form-container" v-if="showControls"> <FilterForm :value="locationQuery" + :devices="devices" :disabled="loading" :has-next-page="hasNextPage" :has-prev-page="hasPrevPage" @@ -101,6 +103,7 @@ import Routes from '../mixins/Routes.vue'; import Timeline from './Timeline.vue'; import TimelineMetricsConfiguration from '../models/TimelineMetricsConfiguration'; import URLQueryHandler from '../mixins/URLQueryHandler.vue'; +import UserDevice from '../models/UserDevice'; useGeographic() @@ -125,6 +128,7 @@ export default { data() { return { + devices: [] as UserDevice[], loading: false, map: null as Optional<Map>, mapView: null as Optional<View>, @@ -143,6 +147,13 @@ export default { }, computed: { + devicesById(): Record<string, string> { + return this.devices.reduce((acc: Record<string, string>, device: any) => { + acc[device.id] = device + return acc + }, {}) + }, + groupedGPSPoints(): GPSPoint[] { // Reference refreshPoints to force reactivity this.refreshPoints; @@ -450,8 +461,12 @@ export default { }, async mounted() { - this.initQuery() - this.gpsPoints = await this.fetch() + this.initQuery(); + [this.gpsPoints, this.devices] = await Promise.all([ + this.fetch(), + this.getMyDevices(), + ]) + this.map = this.createMap() }, } diff --git a/frontend/src/components/PointInfo.vue b/frontend/src/components/PointInfo.vue index 80fb3ad..37928e6 100644 --- a/frontend/src/components/PointInfo.vue +++ b/frontend/src/components/PointInfo.vue @@ -50,6 +50,11 @@ </span> </p> + <p class="device" v-if="device"> + <font-awesome-icon icon="fas fa-mobile-alt" /> + {{ device.name }} + </p> + <p class="locality" v-if="point.locality">{{ point.locality }}</p> <p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p> <p class="country" v-if="country"> @@ -77,11 +82,15 @@ import { getCountryData, getEmojiFlag } from 'countries-list'; import Dates from '../mixins/Dates.vue'; import GPSPoint from '../models/GPSPoint'; +import UserDevice from '../models/UserDevice'; export default { emit: ['close', 'edit', 'remove'], mixins: [Dates], props: { + device: { + type: [UserDevice, null], + }, point: { type: [GPSPoint, null], }, @@ -271,6 +280,12 @@ export default { } } + .device { + font-size: 0.9em; + font-weight: bold; + letter-spacing: 0.05em; + } + .timestamp { color: var(--color-heading); font-weight: bold; diff --git a/frontend/src/components/filter/Form.vue b/frontend/src/components/filter/Form.vue index 8a8a850..0fc884e 100644 --- a/frontend/src/components/filter/Form.vue +++ b/frontend/src/components/filter/Form.vue @@ -138,6 +138,17 @@ min="0" /> </div> + <div class="device-container"> + <label for="device">Device</label> + <select id="device" + name="device" + v-model="newFilter.deviceId" + :disabled="disabled"> + <option value="">All</option> + <option v-for="device in devices" :key="device.id" :value="device.id">{{ device.name }}</option> + </select> + </div> + <div class="footer"> <button type="submit" :disabled="disabled || !changed"> <font-awesome-icon icon="fas fa-check" /> Apply @@ -149,6 +160,8 @@ <script lang="ts"> import _ from 'lodash' +import UserDevice from '../../models/UserDevice' + export default { emit: [ 'next-page', @@ -157,8 +170,13 @@ export default { 'refresh', 'set-resolution', ], + props: { value: Object, + devices: { + type: Array as () => UserDevice[], + default: () => [], + }, disabled: { type: Boolean, default: false, diff --git a/frontend/src/mixins/api/Devices.vue b/frontend/src/mixins/api/Devices.vue index 865076d..a7b0451 100644 --- a/frontend/src/mixins/api/Devices.vue +++ b/frontend/src/mixins/api/Devices.vue @@ -10,7 +10,7 @@ export default { await this.request(`/devices`) as { devices: UserDevice[] } - ).devices; + ).devices.map((device) => new UserDevice(device)); }, async registerDevice(name: string): Promise<UserDevice> { diff --git a/frontend/src/models/LocationQuery.ts b/frontend/src/models/LocationQuery.ts index 484098c..21d82c1 100644 --- a/frontend/src/models/LocationQuery.ts +++ b/frontend/src/models/LocationQuery.ts @@ -1,6 +1,7 @@ class LocationQuery { public limit: number = 250; public offset: number | null = null; + public deviceId: string | null = null; public startDate: Date | null = null; public endDate: Date | null = null; public minId: number | null = null; @@ -10,9 +11,22 @@ class LocationQuery { public postalCode: string | null = null; public order: string = 'asc'; - constructor(public data: any) { + constructor(data: { + limit?: number; + offset?: number; + deviceId?: string; + startDate?: Date; + endDate?: Date; + minId?: number; + maxId?: number; + country?: string; + locality?: string; + postalCode?: string; + order?: string; + }) { this.limit = data.limit || this.limit; this.offset = data.offset || this.offset; + this.deviceId = data.deviceId || this.deviceId; this.startDate = data.startDate || this.startDate; this.endDate = data.endDate || this.endDate; this.minId = data.minId || this.minId;