Finalized codebase
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
<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>
|
||||
@@ -0,0 +1,167 @@
|
||||
<script>
|
||||
import { defineNuxtComponent } from 'nuxt/app';
|
||||
import utils from '~/assets/utils';
|
||||
|
||||
export default defineNuxtComponent({
|
||||
props: {
|
||||
modalState: {
|
||||
type: Boolean,
|
||||
},
|
||||
weatherData: {
|
||||
type: Object,
|
||||
},
|
||||
databaseLocations: {
|
||||
type: Object,
|
||||
},
|
||||
locID: {
|
||||
type: Number,
|
||||
},
|
||||
locations: {
|
||||
type: Object,
|
||||
},
|
||||
apiBaseURL: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
apiURL: '',
|
||||
userId: 1,
|
||||
deleteBootstrapData: false,
|
||||
}),
|
||||
methods: {
|
||||
getDay(date) {
|
||||
return utils.getDay(date);
|
||||
},
|
||||
closeModal() {
|
||||
this.$emit('close-modal');
|
||||
},
|
||||
getWeatherIcon(wmoCode) {
|
||||
return utils.getWeatherIcon(wmoCode);
|
||||
},
|
||||
async removeLocID(input) {
|
||||
await this.$parent.removeLocID(input);
|
||||
},
|
||||
async deleteLocation(input) {
|
||||
this.apiURL = '';
|
||||
this.apiURL = `${this.apiBaseURL}/locations/${input}`;
|
||||
await this.$emit('close-modal');
|
||||
|
||||
await this.removeLocID(input);
|
||||
await delete this.weatherData[input];
|
||||
|
||||
if (this.deleteBootstrapData == true) {
|
||||
await fetch(`${this.apiBaseURL}/locations/${input}`, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({
|
||||
id: input,
|
||||
user: this.userId,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(async (json) => {
|
||||
console.log('Deleted: ' + input);
|
||||
});
|
||||
}
|
||||
this.apiURL = '';
|
||||
await this.getDatabaseLocations();
|
||||
},
|
||||
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: 'Delete Location',
|
||||
emits: ['close-modal'],
|
||||
});
|
||||
|
||||
const delOpen = ref(false);
|
||||
|
||||
defineShortcuts({
|
||||
escape: {
|
||||
usingInput: true,
|
||||
whenever: [delOpen],
|
||||
handler: () => {
|
||||
delOpen.value = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UModal
|
||||
:ui="{
|
||||
base: 'w-80 min-w-20 h-50 bg-opacity-0',
|
||||
overlay: {
|
||||
base: 'fixed inset-0 transition-opacity',
|
||||
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: '',
|
||||
header: {
|
||||
base: '',
|
||||
padding: 'pb-3 px-4',
|
||||
},
|
||||
body: {
|
||||
base: 'py-2 px-4',
|
||||
padding: '',
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold text-gray-200">Delete 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">
|
||||
Remove the selected location ? ({{ this.weatherData[this.locID].data.location.name }})
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center m-6">
|
||||
<button
|
||||
class="mx-auto text-center text-xs bg-linkwater-100 text-gray-900 hover:bg-linkwater-300 font-bold py-2 px-4 rounded-md"
|
||||
@click="deleteLocation(this.locID)"
|
||||
>
|
||||
Yes
|
||||
</button>
|
||||
<button
|
||||
class="mx-auto text-center text-xs bg-linkwater-100 text-gray-900 hover:bg-linkwater-300 font-bold py-2 px-4 rounded-md"
|
||||
@click="closeModal"
|
||||
>
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,150 @@
|
||||
<script>
|
||||
import { defineNuxtComponent } from 'nuxt/app';
|
||||
import utils from '~/assets/utils';
|
||||
|
||||
export default defineNuxtComponent({
|
||||
props: {
|
||||
modalState: {
|
||||
type: Boolean,
|
||||
},
|
||||
weatherData: {
|
||||
type: Object,
|
||||
},
|
||||
locID: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
weatherIcons: utils.weatherIcons,
|
||||
}),
|
||||
methods: {
|
||||
getDay(date) {
|
||||
return utils.getDay(date);
|
||||
},
|
||||
closeModal() {
|
||||
this.$emit('close-modal');
|
||||
},
|
||||
getWeatherIcon(wmoCode) {
|
||||
return utils.getWeatherIcon(wmoCode);
|
||||
},
|
||||
},
|
||||
name: 'Slideover Details',
|
||||
emits: ['close-modal'],
|
||||
});
|
||||
|
||||
const detailsOpen = ref(false);
|
||||
|
||||
defineShortcuts({
|
||||
escape: {
|
||||
usingInput: true,
|
||||
whenever: [detailsOpen],
|
||||
handler: () => {
|
||||
detailsOpen.value = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<USlideover
|
||||
:ui="{
|
||||
base: 'min-w-100 min-h-70 h-full',
|
||||
background: 'bg-opacity-0',
|
||||
overlay: {
|
||||
base: 'fixed inset-0 transition-opacity',
|
||||
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',
|
||||
},
|
||||
},
|
||||
}"
|
||||
prevent-close
|
||||
v-model="modalState"
|
||||
:overlay="true"
|
||||
@close-prevented="closeModal"
|
||||
@close="closeModal"
|
||||
>
|
||||
<UCard
|
||||
:ui="{
|
||||
background: 'bg-woodsmoke-950 h-full',
|
||||
divide: '',
|
||||
ring: '',
|
||||
preventClose: True,
|
||||
header: {
|
||||
base: '',
|
||||
padding: 'pb-3',
|
||||
},
|
||||
body: {
|
||||
base: 'pt-2 px-6 pb-4',
|
||||
padding: '',
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-2xl px-2 font-semibold text-gray-200">
|
||||
{{ this.weatherData[this.locID].data.location.name }}
|
||||
</p>
|
||||
<button
|
||||
class="float-right rounded-sm text-justify text-md bg-shark-950 text-gray-200 hover:bg-linkwater-300 px-1"
|
||||
@click="closeModal"
|
||||
>
|
||||
<UIcon class="mt-1" name="i-heroicons-x-mark-16-solid" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="text-sm text-silver-400 pb-4">This Week</p>
|
||||
<div class="mb-4 mx-1" v-for="index in 7" :key="index">
|
||||
<div class="container grid grid-cols-3 py-5 bg-mineshaft-900 text-gray-200 px-5 rounded-3xl">
|
||||
<div class="mt-2 col-span-2 text-left items-start">
|
||||
<img
|
||||
class="float-left w-10 mr-3"
|
||||
:src="getWeatherIcon(this.weatherData[this.locID].data.daily.weather_code[index - 1])"
|
||||
alt="weather icon"
|
||||
/>
|
||||
<div class="items-start text-left py-1 align-middle">
|
||||
<strong class="text-left text-3xl font-semibold font-sans align-middle">
|
||||
{{ getDay(this.weatherData[this.locID].data.daily.date[index - 1]) }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="float-right">
|
||||
<span class="text-md text-silver-500 mx-4">Min.</span>
|
||||
<span class="text-md"
|
||||
><strong class="mr-1 text-silver-200">
|
||||
{{
|
||||
Math.ceil(
|
||||
this.weatherData[this.locID].data.daily.temperature_2m_min[index - 1]
|
||||
)
|
||||
}}°C
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="float-right mt-2">
|
||||
<span class="text-md text-silver-500 mx-4">Max.</span>
|
||||
<span class="text-md"
|
||||
><strong class="mr-1 text-silver-200">
|
||||
{{
|
||||
Math.ceil(
|
||||
this.weatherData[this.locID].data.daily.temperature_2m_max[index - 1]
|
||||
)
|
||||
}}°C
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user