Refactored CountrySelector
into a separate component.
This commit is contained in:
parent
7fdc01a840
commit
30c66d391f
4 changed files with 171 additions and 33 deletions
frontend/src
|
@ -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[])
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
139
frontend/src/elements/CountrySelector.vue
Normal file
139
frontend/src/elements/CountrySelector.vue
Normal 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>
|
26
frontend/src/mixins/Stats.vue
Normal file
26
frontend/src/mixins/Stats.vue
Normal 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>
|
Loading…
Add table
Reference in a new issue