diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 54592bd..04dbfc3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@tsconfig/node22": "^22.0.0", + "@types/lodash": "^4.17.15", "@types/node": "^22.13.4", "@vitejs/plugin-vue": "^5.2.1", "@vue/eslint-config-prettier": "^10.2.0", @@ -1734,6 +1735,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index abce684..76e0c80 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ }, "devDependencies": { "@tsconfig/node22": "^22.0.0", + "@types/lodash": "^4.17.15", "@types/node": "^22.13.4", "@vitejs/plugin-vue": "^5.2.1", "@vue/eslint-config-prettier": "^10.2.0", diff --git a/frontend/src/components/Map.vue b/frontend/src/components/Map.vue index c6762be..0aa855a 100644 --- a/frontend/src/components/Map.vue +++ b/frontend/src/components/Map.vue @@ -3,12 +3,12 @@ <div class="loading" v-if="loading">Loading...</div> <div class="map-wrapper" v-else> <div id="map"> - <div class="time-range" v-if="gpsPoints?.length"> + <div class="time-range" v-if="oldestPoint && newestPoint"> <div class="row"> <div class="key">From</div> <div class="value"> <a href="#" @click.prevent.stop="onStartDateClick"> - {{ displayDate(oldestPoint?.timestamp) }} + {{ displayDate(oldestPoint.timestamp) }} </a> </div> </div> @@ -16,7 +16,7 @@ <div class="key">To</div> <div class="value"> <a href="#" @click.prevent.stop="onEndDateClick"> - {{ displayDate(newestPoint?.timestamp) }} + {{ displayDate(newestPoint.timestamp) }} </a> </div> </div> @@ -38,7 +38,7 @@ :has-next-page="hasNextPage" :has-prev-page="hasPrevPage" @refresh="locationQuery = $event" - @reset-page="locationQuery.minId = locationQuery.maxId = undefined" + @reset-page="locationQuery.minId = locationQuery.maxId = null" @next-page="fetchNextPage" @prev-page="fetchPrevPage" /> </div> @@ -94,7 +94,6 @@ export default { data() { return { loading: false, - locationQuery: new LocationQuery({}), map: null as Nullable<Map>, mappedPoints: [] as Point[], mapView: null as Nullable<View>, @@ -140,16 +139,19 @@ export default { createMap(gpsPoints: GPSPoint[]): Map { this.mappedPoints = this.toMappedPoints(gpsPoints) - this.pointsLayer = this.createPointsLayer(this.mappedPoints) - this.routesLayer = this.createRoutesLayer(this.mappedPoints) + this.pointsLayer = this.createPointsLayer(this.mappedPoints as Point[]) + this.routesLayer = this.createRoutesLayer(this.mappedPoints as Point[]) this.mapView = this.mapView || this.createMapView(gpsPoints) const map = new Map({ target: 'map', layers: [ this.createMapLayer(), + // @ts-ignore this.pointsLayer, + // @ts-ignore this.routesLayer, ], + // @ts-ignore view: this.mapView, }) @@ -194,15 +196,15 @@ export default { }, onStartDateClick() { - this.locationQuery.startDate = this.oldestPoint?.timestamp - this.locationQuery.minId = undefined - this.locationQuery.maxId = undefined + this.locationQuery.startDate = this.oldestPoint?.timestamp || null + this.locationQuery.minId = null + this.locationQuery.maxId = null }, onEndDateClick() { - this.locationQuery.endDate = this.newestPoint?.timestamp - this.locationQuery.minId = undefined - this.locationQuery.maxId = undefined + this.locationQuery.endDate = this.newestPoint?.timestamp || null + this.locationQuery.minId = null + this.locationQuery.maxId = null }, }, @@ -215,8 +217,8 @@ export default { if (!isFirstQuery && (newQuery.startDate !== oldQuery.startDate || newQuery.endDate !== oldQuery.endDate) ) { - newQuery.minId = undefined - newQuery.maxId = undefined + newQuery.minId = null + newQuery.maxId = null this.hasNextPage = true this.hasPrevPage = true } @@ -254,8 +256,11 @@ export default { this.mappedPoints = this.toMappedPoints(this.gpsPoints) if (this.mapView) { + // @ts-ignore this.refreshMapView(this.mapView, this.gpsPoints) + // @ts-ignore this.refreshPointsLayer(this.pointsLayer, this.mappedPoints) + // @ts-ignore this.refreshRoutesLayer(this.routesLayer, this.mappedPoints) } }, diff --git a/frontend/src/components/filter/Form.vue b/frontend/src/components/filter/Form.vue index 105cbe1..f0bf303 100644 --- a/frontend/src/components/filter/Form.vue +++ b/frontend/src/components/filter/Form.vue @@ -8,8 +8,8 @@ <input type="datetime-local" id="start-date" name="start-date" - @input="newFilter.startDate = startPlusHours($event.target.value, 0)" - @change="newFilter.startDate = startPlusHours($event.target.value, 0)" + @input="newFilter.startDate = startPlusHours($event, 0)" + @change="newFilter.startDate = startPlusHours($event, 0)" :value="toLocalString(newFilter.startDate)" :disabled="disabled" :max="maxDate" /> @@ -44,8 +44,8 @@ <input type="datetime-local" id="end-date" name="end-date" - @input="newFilter.endDate = endPlusHours($event.target.value, 0)" - @change="newFilter.endDate = endPlusHours($event.target.value, 0)" + @input="newFilter.endDate = endPlusHours($event, 0)" + @change="newFilter.endDate = endPlusHours($event, 0)" :value="toLocalString(newFilter.endDate)" :disabled="disabled" :max="maxDate" /> @@ -80,7 +80,7 @@ <div class="page-button-container"> <button type="button" :disabled="disabled" - v-if="value.minId || value.maxId" + v-if="value?.minId || value?.maxId" @click.stop="$emit('reset-page')"> <font-awesome-icon icon="fas fa-undo" /> </button> @@ -99,8 +99,8 @@ <input type="number" id="limit" name="limit" - @input="newFilter.limit = Number($event.target.value)" - @change="newFilter.limit = Number($event.target.value)" + @input="setLimit" + @change="setLimit" :value="newFilter.limit" :disabled="disabled" min="1" /> @@ -168,8 +168,8 @@ export default { return !_.isEqual( { ...oldValue, - startDate: this.normalizeDate(this.value.startDate), - endDate: this.normalizeDate(this.value.endDate), + startDate: this.normalizeDate(this.value?.startDate), + endDate: this.normalizeDate(this.value?.endDate), }, { ...newValue, @@ -179,7 +179,7 @@ export default { ) }, - normalizeDate(date: Date | number | string | null): Date | null { + normalizeDate(date: any): Date | null { if (!date) { return null } @@ -203,7 +203,11 @@ export default { ).toISOString().slice(0, -8) }, - startPlusHours(date: Date | number | null, hours: number): Date | null { + startPlusHours(date: Date | number | Event | undefined | null, hours: number): Date | null { + if ((date as any)?.target?.value) { + date = (date as any).target.value + } + let d = this.normalizeDate(date) if (!d) { return null @@ -219,11 +223,15 @@ export default { return d }, - startPlusDays(date: Date | number | null, days: number): Date | null { + startPlusDays(date: Date | number | Event | undefined | null, days: number): Date | null { return this.startPlusHours(date, days * 24) }, - endPlusHours(date: Date | number | null, hours: number): Date | null { + endPlusHours(date: Date | number | Event | undefined | null, hours: number): Date | null { + if ((date as any)?.target?.value) { + date = (date as any).target.value + } + let d = this.normalizeDate(date) if (!d) { return null @@ -244,13 +252,17 @@ export default { return d }, - endPlusDays(date: Date | number | null, days: number): Date | null { + endPlusDays(date: Date | number | Event | undefined | null, days: number): Date | null { return this.endPlusHours(date, days * 24) }, handleSubmit() { this.$emit('refresh', this.newFilter) }, + + setLimit(event: Event) { + this.newFilter.limit = Number((event.target as HTMLInputElement).value) + }, }, watch: { diff --git a/frontend/src/mixins/Dates.vue b/frontend/src/mixins/Dates.vue index 926d775..089b3e9 100644 --- a/frontend/src/mixins/Dates.vue +++ b/frontend/src/mixins/Dates.vue @@ -1,7 +1,7 @@ <script lang="ts"> export default { methods: { - displayDate(date: Date | number | string | null): string { + displayDate(date: Date | number | string | null | undefined): string { if (!date) { return '-' } diff --git a/frontend/src/mixins/Paginate.vue b/frontend/src/mixins/Paginate.vue index f1254f7..1cc62b7 100644 --- a/frontend/src/mixins/Paginate.vue +++ b/frontend/src/mixins/Paginate.vue @@ -8,16 +8,17 @@ export default { gpsPoints: [] as GPSPoint[], hasNextPage: true, hasPrevPage: true, + locationQuery: new LocationQuery({}), } }, computed: { - newestPoint(): GPSPoint | undefined { - return this.gpsPoints[this.gpsPoints.length - 1] || undefined + newestPoint(): GPSPoint | null { + return this.gpsPoints[this.gpsPoints.length - 1] || null }, - oldestPoint(): GPSPoint | undefined { - return this.gpsPoints[0] || undefined + oldestPoint(): GPSPoint | null { + return this.gpsPoints[0] || null }, }, diff --git a/frontend/src/mixins/Points.vue b/frontend/src/mixins/Points.vue index e10b2a4..9a0c0a6 100644 --- a/frontend/src/mixins/Points.vue +++ b/frontend/src/mixins/Points.vue @@ -83,6 +83,10 @@ export default { refreshPointsLayer(layer: VectorLayer, points: Point[]) { const source = layer.getSource() + if (!source) { + return + } + source.clear() source.addFeatures(points.map((point: Point) => new Feature(point))) source.changed() diff --git a/frontend/src/mixins/Routes.vue b/frontend/src/mixins/Routes.vue index 0e0e90f..e7b8cc7 100644 --- a/frontend/src/mixins/Routes.vue +++ b/frontend/src/mixins/Routes.vue @@ -27,13 +27,17 @@ export default { refreshRoutesLayer(layer: VectorLayer, points: Point[]) { const routeFeatures = this.extractRouteFeatures(points) const source = layer.getSource() + if (!source) { + return + } + source.clear() source.addFeatures(routeFeatures) source.changed() }, extractRouteFeatures(points: Point[]): Feature[] { - const routeFeatures = [] + const routeFeatures: Feature[] = [] points.forEach((point: Point, index: number) => { if (index === 0) { return diff --git a/frontend/src/mixins/URLQueryHandler.vue b/frontend/src/mixins/URLQueryHandler.vue index dde2fb6..c4f3aa9 100644 --- a/frontend/src/mixins/URLQueryHandler.vue +++ b/frontend/src/mixins/URLQueryHandler.vue @@ -11,7 +11,7 @@ function isDate(key: string, value: string): boolean { ) } -function parseValue(key: string, value: string | null): string | number | boolean | Date { +function parseValue(key: string, value: string | null): string | number | boolean | Date | undefined { value = decodeURI(value?.trim() || '') if (!value.length) { return undefined