314 lines
12 KiB
Vue
314 lines
12 KiB
Vue
<script>
|
|
import { defineNuxtComponent } from 'nuxt/app';
|
|
import utils from '~/assets/utils';
|
|
|
|
export default defineNuxtComponent({
|
|
props: {
|
|
modalState: {
|
|
type: Boolean,
|
|
},
|
|
weatherData: {
|
|
type: Object,
|
|
},
|
|
databaseLocations: {
|
|
type: Object,
|
|
},
|
|
locations: {
|
|
type: Object,
|
|
},
|
|
apiBaseURL: {
|
|
type: String,
|
|
},
|
|
},
|
|
computed: {
|
|
cmpDatabaseLocations() {
|
|
return this.databaseLocations;
|
|
},
|
|
},
|
|
data: () => ({
|
|
weatherIcons: utils.weatherIcons,
|
|
apiURL: '',
|
|
searchInput: '',
|
|
selectedIndex: '',
|
|
searchVisible: true,
|
|
userId: 1,
|
|
runtimeCode: 0,
|
|
locationData: [],
|
|
}),
|
|
methods: {
|
|
getDay(date) {
|
|
return utils.getDay(date);
|
|
},
|
|
closeModal() {
|
|
this.$emit('close-modal');
|
|
},
|
|
async addNewLocID(idx) {
|
|
await this.$parent.addNewLocID(idx);
|
|
},
|
|
getWeatherIcon(wmoCode) {
|
|
return utils.getWeatherIcon(wmoCode);
|
|
},
|
|
async addLocation(input) {
|
|
this.apiURL = `${this.apiBaseURL}/locations`;
|
|
if (input) {
|
|
await this.getDatabaseLocations(true);
|
|
let idx = 0;
|
|
for (let i = 0; i < this.cmpDatabaseLocations.length; i++) {
|
|
if (
|
|
this.databaseLocations[i]['name'].includes(input.name) &&
|
|
this.databaseLocations[i]['country'].includes(input.country)
|
|
) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (idx > 0) {
|
|
if (!this.locations.includes(idx + 1)) {
|
|
await this.addNewLocID(idx + 1);
|
|
await this.fetchWeatherData(false, idx + 1);
|
|
}
|
|
|
|
await $fetch(this.apiURL, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
id: idx + 1,
|
|
name: 'existing',
|
|
country: '',
|
|
longitude: 0,
|
|
latitude: 0,
|
|
user: this.userId,
|
|
}),
|
|
});
|
|
this.closeModal('add');
|
|
} else {
|
|
idx = this.databaseLocations.length + 1;
|
|
await $fetch(this.apiURL, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
id: idx,
|
|
name: input.name,
|
|
country: input.country,
|
|
longitude: input.longitude,
|
|
latitude: input.latitude,
|
|
user: this.userId,
|
|
}),
|
|
});
|
|
this.fetchWeatherData(false, idx);
|
|
this.displayNotification('add', input);
|
|
this.closeModal('add');
|
|
}
|
|
}
|
|
},
|
|
async searchLocations() {
|
|
this.apiURL = '';
|
|
if (this.searchInput.length > 1) {
|
|
this.locationData = [];
|
|
|
|
this.apiURL = this.apiBaseURL + '/location/search?query=' + this.searchInput;
|
|
console.log(this.apiURL);
|
|
await fetch(this.apiURL)
|
|
.then((res) => res.json())
|
|
.then((json) => {
|
|
this.locationData = json;
|
|
this.searchVisible = true;
|
|
});
|
|
} else {
|
|
this.locationData = [];
|
|
this.searchVisible = false;
|
|
}
|
|
},
|
|
getCountryFlag(countryCode) {
|
|
this.runtimeCode = 0;
|
|
try {
|
|
countryCode = countryCode.toLowerCase();
|
|
} catch (error) {
|
|
countryCode = 'xx';
|
|
this.runtimeCode = 1;
|
|
}
|
|
|
|
return `/flags/4x3/${countryCode}.svg`;
|
|
},
|
|
toggleSelection(input) {
|
|
if (this.locationData) {
|
|
if (this.selectedIndex) {
|
|
this.locationData[this.selectedIndex].selected = !this.locationData[this.selectedIndex].selected;
|
|
this.selectedIndex = '';
|
|
}
|
|
if (input != 'deselect') {
|
|
this.selectedIndex = input.index;
|
|
this.locationData[input.index].selected = !this.locationData[input.index].selected;
|
|
}
|
|
}
|
|
},
|
|
async getDatabaseLocations() {
|
|
await this.$parent.getDatabaseLocations();
|
|
},
|
|
async fetchWeatherData(bulk = false, location = null) {
|
|
await this.$parent.fetchWeatherData(bulk, location);
|
|
},
|
|
displayNotification(type, input) {
|
|
this.$parent.displayNotification(type, input);
|
|
},
|
|
},
|
|
name: 'Slideover Details',
|
|
emits: ['close-modal'],
|
|
});
|
|
|
|
const addOpen = ref(false);
|
|
|
|
defineShortcuts({
|
|
escape: {
|
|
usingInput: true,
|
|
whenever: [addOpen],
|
|
handler: () => {
|
|
addOpen.value = false;
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<UModal
|
|
:ui="{
|
|
base: 'bg-opacity-0 overflow-visible min-w-80 min-h-50 ',
|
|
overlay: {
|
|
base: 'fixed inset-0 transition-opacity',
|
|
ring: '',
|
|
background: 'bg-black bg-opacity-80',
|
|
transition: {
|
|
enter: 'ease-out duration-300',
|
|
enterFrom: 'opacity-0',
|
|
enterTo: 'opacity-100',
|
|
leave: 'ease-in duration-200',
|
|
leaveFrom: 'opacity-100',
|
|
leaveTo: 'opacity-0',
|
|
},
|
|
},
|
|
}"
|
|
v-model="modalState"
|
|
prevent-close
|
|
:overlay="true"
|
|
@close-prevented="closeModal"
|
|
@close="closeModal"
|
|
>
|
|
<UCard
|
|
:ui="{
|
|
background: 'bg-woodsmoke-950',
|
|
divide: '',
|
|
ring: 'ring-0',
|
|
header: {
|
|
base: '',
|
|
padding: 'pb-3',
|
|
},
|
|
body: {
|
|
base: 'pt-2 px-8 pb-4',
|
|
padding: '',
|
|
},
|
|
}"
|
|
>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-base px-4 font-semibold text-gray-200">Add Location</h3>
|
|
<button
|
|
class="float-right text-justify text-md bg-shark-950 text-gray-200 hover:bg-linkwater-300 px-1 rounded-md"
|
|
@click="closeModal"
|
|
>
|
|
<UIcon name="i-heroicons-x-mark-16-solid" />
|
|
</button>
|
|
</div>
|
|
</template>
|
|
<div class="flex items-center">
|
|
<p class="text-base mx-auto text-center text-gray-200">Add Location</p>
|
|
</div>
|
|
|
|
<UInput
|
|
class="bg-mineshaft-950 border-mineshaft-950 text-gray-200 rounded-md"
|
|
v-model="searchInput"
|
|
name="q"
|
|
placeholder="Search..."
|
|
icon="i-heroicons-magnifying-glass-20-solid"
|
|
autocomplete="off"
|
|
variant="none"
|
|
@keyup="searchLocations(this.searchInput)"
|
|
:ui="{ icon: { trailing: { pointer: '' } } }"
|
|
>
|
|
<template #trailing>
|
|
<UButton
|
|
v-show="this.searchInput !== ''"
|
|
color="gray"
|
|
variant="link"
|
|
icon="i-heroicons-x-mark-20-solid"
|
|
:padded="false"
|
|
@click="
|
|
this.searchInput = '';
|
|
this.locationData = [];
|
|
"
|
|
/>
|
|
</template>
|
|
</UInput>
|
|
<div class="grid grid-rows-1">
|
|
<div
|
|
v-if="searchInput && this.runtimeCode == 0 && this.locationData"
|
|
class="text-center bg-mineshaft-950 z-50 border-mineshaft-950 text-gray-200"
|
|
v-for="(loc, index) in this.locationData"
|
|
:key="loc"
|
|
>
|
|
<div
|
|
@click="
|
|
toggleSelection({
|
|
index: index,
|
|
id: loc.id,
|
|
name: loc.name,
|
|
latitude: loc.latitude,
|
|
longitude: loc.longitude,
|
|
})
|
|
"
|
|
:class="{
|
|
'border-x-0 border-y-0 bg-linkwater-100 text-gray-800': loc.selected,
|
|
}"
|
|
class="p-2 border-b-2 columns-3 cursor-pointer hover:bg-linkwater-900"
|
|
v-if="this.searchVisible"
|
|
>
|
|
<div class="column w-1/2">
|
|
<img class="w-14" :src="getCountryFlag(loc.country_code)" alt="country flag icon" />
|
|
</div>
|
|
<div class="column">
|
|
<p>{{ loc.name }}</p>
|
|
<p>{{ loc.country }}</p>
|
|
</div>
|
|
<div class="column">
|
|
<p v-if="loc.latitude" class="text-sm">Lat: {{ loc.latitude.toFixed(4) }}</p>
|
|
<p v-if="loc.longitude" class="text-sm">Lon: {{ loc.longitude.toFixed(4) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="text-center bg-mineshaft-950 z-50 border-mineshaft-950 text-gray-200"
|
|
v-if="searchInput && !this.searchVisible"
|
|
>
|
|
<p class="my-2">No location found!</p>
|
|
</div>
|
|
</div>
|
|
<div v-if="this.selectedIndex" class="flex items-center mt-3">
|
|
<button
|
|
class="w-full text-center text-sm bg-linkwater-100 text-gray-900 hover:bg-linkwater-300 font-bold py-2 rounded-md"
|
|
@click="this.addLocation(this.locationData[this.selectedIndex])"
|
|
>
|
|
Add Location
|
|
</button>
|
|
</div>
|
|
<div v-else class="flex items-center mt-3">
|
|
<UTooltip class="w-full" text="Select a location first" :popper="{ arrow: true }">
|
|
<button
|
|
class="cursor-not-allowed w-full text-center text-sm bg-linkwater-100 text-gray-900 hover:bg-shark-900 font-bold py-2 rounded-md"
|
|
>
|
|
Add Location
|
|
</button>
|
|
</UTooltip>
|
|
</div>
|
|
</UCard>
|
|
</UModal>
|
|
</div>
|
|
</template>
|