Refactored CountrySelector into a separate component.

This commit is contained in:
Fabio Manganiello 2025-04-15 01:43:22 +02:00
parent 7fdc01a840
commit 30c66d391f
Signed by: blacklight
GPG key ID: D90FBA7F76362774
4 changed files with 171 additions and 33 deletions
frontend/src
components
elements
mixins

View file

@ -120,13 +120,12 @@ import FloatingButton from '../elements/FloatingButton.vue';
import GPSPoint from '../models/GPSPoint';
import LocationQuery from '../models/LocationQuery';
import LocationQueryMixin from '../mixins/LocationQuery.vue';
import LocationStats from '../models/LocationStats';
import MapSelectOverlay from './MapSelectOverlay.vue';
import MapView from '../mixins/MapView.vue';
import Paginate from '../mixins/Paginate.vue';
import Points from '../mixins/Points.vue';
import Routes from '../mixins/Routes.vue';
import StatsRequest from '../models/StatsRequest';
import StatsMixin from '../mixins/Stats.vue';
import Timeline from './Timeline.vue';
import TimelineMetricsConfiguration from '../models/TimelineMetricsConfiguration';
import URLQueryHandler from '../mixins/URLQueryHandler.vue';
@ -143,6 +142,7 @@ export default {
Paginate,
Points,
Routes,
StatsMixin,
URLQueryHandler,
],
@ -314,22 +314,6 @@ export default {
await this.processQueryChange(this.locationQuery, oldQuery)
},
async getCountries(): Promise<Country[]> {
return (
await this.getStats(
new StatsRequest({
// @ts-ignore
userId: this.$root.user.id,
groupBy: ['country'],
order: 'desc',
})
)
)
.filter((record: LocationStats) => !!record.key.country)
.map((record: LocationStats) => Country.fromCode(record.key.country))
.filter((country: Optional<Country>) => !!country)
},
createMap(): Map {
this.pointsLayer = this.createPointsLayer(Object.values(this.mappedPoints) as Point[])
this.routesLayer = this.createRoutesLayer(Object.values(this.mappedPoints) as Point[])

View file

@ -167,13 +167,10 @@
</p>
</label>
<Autocomplete
id="country"
name="country"
<CountrySelector
placeholder="Filter by country"
allow-only-values
:value="newFilter.country || ''"
:values="autocompleteCountries"
:countries="countries"
:disabled="disabled"
@input="newFilter.country = $event" />
</div>
@ -274,9 +271,8 @@
<script lang="ts">
import _ from 'lodash'
import Autocomplete from '../../elements/Autocomplete.vue'
import AutocompleteValue from '../../models/AutocompleteValue'
import Country from '../../models/Country'
import CountrySelector from '../../elements/CountrySelector.vue'
import LocationQuery from '../../models/LocationQuery'
import LocationQueryMixin from '../../mixins/LocationQuery.vue'
import UserDevice from '../../models/UserDevice'
@ -289,7 +285,7 @@ export default {
],
components: {
Autocomplete,
CountrySelector,
},
props: {
@ -313,13 +309,6 @@ export default {
},
computed: {
autocompleteCountries(): AutocompleteValue[] {
return this.countries.map((country: Country) => ({
value: country.code,
label: `${country.flag} ${country.name}`,
data: country,
}))
},
maxDate() {
return this.toLocalString(this.endPlusHours(new Date(), 0))
}

View file

@ -0,0 +1,139 @@
<template>
<div class="country-selector">
<Loading v-if="loading" />
<Autocomplete
:id="id"
:name="name"
:placeholder="placeholder"
:value="value"
:values="autocompleteCountries"
:disabled="disabled || loading"
allow-only-values
@input="onInput" />
</div>
</template>
<script lang="ts">
import { countries } from 'countries-list'
import Autocomplete from './Autocomplete.vue'
import AutocompleteValue from '../models/AutocompleteValue'
import Country from '../models/Country'
import Loading from './Loading.vue'
import Stats from '../mixins/Stats.vue'
export default {
mixins: [Stats],
components: {
Autocomplete,
Loading,
},
props: {
countries: {
type: Array as () => Country[],
},
disabled: {
type: Boolean,
default: false,
},
id: {
type: String,
default: 'country',
},
name: {
type: String,
default: 'country',
},
placeholder: {
type: String,
default: 'Select a country',
},
showAll: {
type: Boolean,
default: false,
},
value: {
type: String,
default: '',
},
},
data() {
return {
countries_: this.countries ? [...this.countries] : [],
loading: false,
}
},
computed: {
autocompleteCountries(): AutocompleteValue[] {
const visitedCountries = this.countries_.reduce(
(acc: Record<string, AutocompleteValue>, country: Country) => {
acc[country.code] = this.toAutocompleteValue(country)
return acc
}, {}
);
if (!this.showAll) {
return Object.values(visitedCountries)
}
const unvisitedCountries = Object.keys(countries).reduce(
(acc: Record<string, AutocompleteValue>, key: string) => {
if (visitedCountries[key]) {
return acc
}
acc[key] = this.toAutocompleteValue(countries[key])
return acc
}, {}
)
return [
...Object.values(visitedCountries) as AutocompleteValue[],
...Object.values(unvisitedCountries) as AutocompleteValue[],
]
},
},
methods: {
onInput(value: string) {
this.$emit('input', value)
},
toAutocompleteValue(country: {
code: string
name: string
flag: string
}): AutocompleteValue {
return new AutocompleteValue({
value: country.code,
label: `${country.flag} ${country.name}`,
data: country,
})
},
},
created: function () {
if (!this.countries) {
this.getCountries().then((countries: Country[]) => {
this.countries_ = countries
})
}
},
}
</script>
<style scoped lang="scss">
.country-selector {
position: relative;
width: 100%;
margin: 0 auto;
}
</style>

View file

@ -0,0 +1,26 @@
<script lang="ts">
import type { Optional } from '../models/Types';
import Country from '../models/Country';
import LocationStats from '../models/LocationStats';
import StatsRequest from '../models/StatsRequest';
export default {
methods: {
async getCountries(): Promise<Country[]> {
return (
await this.getStats(
new StatsRequest({
// @ts-ignore
userId: this.$root.user.id,
groupBy: ['country'],
order: 'desc',
})
)
)
.filter((record: LocationStats) => !!record.key.country)
.map((record: LocationStats) => Country.fromCode(record.key.country))
.filter((country: Optional<Country>) => !!country)
},
},
}
</script>