Finalized codebase

This commit is contained in:
Argiris Deligiannidis
2024-04-14 20:33:51 +03:00
parent e1b2880a71
commit 25883a5362
564 changed files with 36069 additions and 9 deletions
+413
View File
@@ -0,0 +1,413 @@
<script>
import { defineNuxtComponent } from 'nuxt/app';
import SlideDetails from './WeatherModals/SlideDetails.vue';
import AddLocation from './WeatherModals/AddLocation.vue';
import DeleteLocation from './WeatherModals/DeleteLocation.vue';
export default defineNuxtComponent({
name: 'WeatherDashboard',
components: {
SlideDetails,
AddLocation,
DeleteLocation,
},
data: () => ({
apiBaseURL: 'http://10.1.1.2:8880',
apiURL: '',
addModalOpen: false,
addConfirmModalOpen: false,
deleteModalOpen: false,
detailsModalOpen: false,
selectedLocationID: false,
userId: 1,
runtimeCode: 0,
locID: null,
locations: [],
databaseLocations: [],
weatherData: [],
weatherIcons: {
1: [0],
2: [57, 63, 65, 66, 67, 73, 75, 77, 80, 81, 82, 85, 86, 95, 96, 99],
3: [1, 2, 3],
4: [45, 48],
5: [51, 53, 61, 66, 71, 55, 56],
},
}),
/**
* Asynchronously fetches weather data for each location on mount.
*
* @return {Promise<void>} A promise that resolves once weather data is fetched.
*/
async mounted() {
this.locations = [];
await this.getDatabaseLocations(true);
if (this.locations.length > 0) {
for (let i = 0; i < this.locations.length; i++) {
await this.fetchWeatherData(false, this.locations[i]);
}
}
},
methods: {
/**
* Adds a new location ID to the locations array.
*
* @param {type} idx - The index of the new location ID to add.
* @return {type}
*/
async addNewLocID(idx) {
await this.locations.push(idx);
},
/**
* Removes the input location ID from the locations array.
*
* @param {type} input - description of parameter
* @return {type} description of return value
*/
removeLocID(input) {
this.locations = this.locations.filter((l) => l !== input);
},
/**
* Sets the specified modal type to open.
*
* @param {type} modalType - The type of modal to open
* @return {type}
*/
openModal(modalType) {
this[`${modalType}ModalOpen`] = true;
},
/**
* Closes a specific modal and performs additional actions if needed.
*
* @param {type} modalType - The type of modal to close
*/
closeModal(modalType) {
this[`${modalType}ModalOpen`] = false;
},
/**
* A description of the entire function.
*
* @param {type} bulk - description of parameter
* @param {type} location - description of parameter
* @return {type} description of return value
*/
async fetchWeatherData(bulk = false, location = null) {
await this.getDatabaseLocations(true);
this.apiURL = '';
if (bulk) {
this.apiURL = `${this.apiBaseURL}/locations?places=${this.locations}`;
} else {
this.apiURL = `${this.apiBaseURL}/locations/${location}`;
}
await fetch(this.apiURL)
.then((res) => res.json())
.then((json) => {
for (let d in json) {
if (!(d in this.locations.values())) {
const mergedJSON = Object.assign({}, this.weatherData, json);
this.weatherData = mergedJSON;
}
}
});
},
/**
* Returns the weather icon for the specified WMO code.
*
* @param {type} wmoCode - description of parameter
* @return {type} description of return value
*/
getWeatherIcon(wmoCode) {
for (let idx in this.weatherIcons) {
if (this.weatherIcons[idx].includes(wmoCode)) {
return '/' + idx + '.svg';
}
}
},
/**
* Returns the locations from the database and the configured locations by the user.
*
* @param {type} configured - description of parameter
* @return {type} description of return value
*/
async getDatabaseLocations(configured = false) {
if (configured == true) {
this.locations = [];
this.apiURL = `${this.apiBaseURL}/locations?places=configured&user=${this.userId}`;
await fetch(this.apiURL)
.then((res) => res.json())
.then((json) => {
for (let idx in json) {
this.locations.push(json[idx].location_id);
}
});
this.apiURL = '';
} else {
this.apiURL = `${this.apiBaseURL}/locations?places=database_locations`;
await fetch(this.apiURL)
.then((res) => res.json())
.then((json) => {
this.databaseLocations = json;
});
this.apiURL = '';
}
},
/**
* A function that takes a date in the format 'dd/mm/yyyy' and returns the day of the week.
*
* @param {type} date - description of parameter
* @return {type} description of return value
*/
getDay(date) {
let isoDate = date.split('/');
date = isoDate[2] + '-' + isoDate[1] + '-' + isoDate[0];
const names = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const day = new Date(date).getDay();
return names[day];
},
/**
* Displays a notification with the specified type and input.
*
* @param {type} type - description of parameter
* @param {type} input - description of parameter
* @return {type} description of return value
*/
displayNotification(type, input) {
if (type == 'add') {
var info = input.name + ', ' + input.country + ' was added succesfully to the list.';
}
useToast().add({
id: 'location_add',
title: 'Locations updated.',
description: info,
background: 'bg-white dark:bg-gray-900',
shadow: 'shadow-lg',
rounded: 'rounded-lg',
padding: 'p-4',
gap: 'gap-3',
timeout: 5000,
});
},
},
});
</script>
<template>
<div class="w-screen mx-auto pt-8">
<div class="max-w-5xl mx-auto container">
<div class="section">
<div>
<img class="float-left w-10 mr-2" src="/icon.svg" alt="dashboard icon" />
<div class="text-container">
<p class="text-md pt-1.5 font-semibold font-sans align-middle">Weather App</p>
</div>
<div class="min-h-10 mt-14 container">
<div>
<p class="float-left font-semibold font-sans w-8 mr-2 text-left text-3xl">Locations</p>
<div>
<button
class="float-right text-justify text-xs bg-linkwater-100 text-gray-900 hover:bg-linkwater-300 font-bold py-2 px-4 rounded-md"
@click="openModal('add')"
>
+ Add Location
</button>
</div>
</div>
</div>
<div class="mt-5 container overflow-visible">
<!--
Component: AddLocation
Description: This component renders a modal for adding a location to the dashboard.
Props:
- addModalOpen: boolean. Determines if the modal is open or not.
- locations: Array. List of locations from the database.
- weatherData: Array. List of locations from the API.
- databaseLocations: Array. List of locations from the database.
- locID: String|Null. ID of the location from the API.
Events:
- close-modal: Emitted when the modal is closed.
- close-prevented: Emitted when the modal is closed by preventing the default action.
-->
<div>
<div v-if="this.addModalOpen">
<AddLocation
@close-modal="closeModal('add')"
@close-prevented="closeModal('add')"
:modalState="this.addModalOpen"
:locations="this.locations"
:weatherData="this.weatherData"
:databaseLocations="this.databaseLocations"
:locID="this.locID"
:apiBaseURL="this.apiBaseURL"
/>
</div>
</div>
<!--
Component: DeleteLocation
Description: This component renders a modal for deleting a location from the dashboard.
Props:
- deleteModalOpen: boolean. Determines if the modal is open or not.
- weatherData: Array. List of locations from the API.
- locations: Array. List of locations from the database.
- locID: String|Null. ID of the location from the API.
Events:
- close-modal: Emitted when the modal is closed.
- close-prevented: Emitted when the modal is closed by preventing the default action.
-->
<div>
<div v-if="this.deleteModalOpen">
<DeleteLocation
@close-modal="closeModal('delete')"
@close-prevented="closeModal('delete')"
:modalState="this.deleteModalOpen"
:weatherData="this.weatherData"
:locations="this.locations"
:locID="this.locID"
:apiBaseURL="this.apiBaseURL"
/>
</div>
</div>
<!--
Component: SlideDetails
Description: This component renders a modal for viewing the details of a specific location.
Props:
- detailsModalOpen: boolean. Determines if the modal is open or not.
- weatherData: Array. List of locations from the API.
- locID: String. ID of the location from the API.
Events:
- close-modal: Emitted when the modal is closed.
- close-prevented: Emitted when the modal is closed by preventing the default action.
-->
<div>
<div v-if="this.detailsModalOpen">
<SlideDetails
@close-modal="closeModal('details')"
@close-prevented="closeModal('details')"
:modalState="this.detailsModalOpen"
:weatherData="this.weatherData"
:locID="this.locID"
/>
</div>
</div>
<!--
Table: Weather Data Table
Description: This table displays the weather data for each location.
Props:
- weatherData: Object. List of locations from the API.
-->
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<!-- Table Header -->
<thead class="text-md text-gray-100 bg-mineshaft-900">
<tr>
<th scope="col" class="px-6 py-3">Location</th>
<th scope="col" class="px-6 py-3">Temperature</th>
<th scope="col" class="px-6 py-3">Rainfall</th>
<th scope="col" class="px-6 py-3"></th>
</tr>
</thead>
<!-- Table Body -->
<tbody>
<!-- Check if weatherData is not empty -->
<template v-if="Object.keys(this.weatherData) != 0">
<!-- Loop through each location in weatherData -->
<tr
v-for="loc in this.weatherData"
class="bg-woodsmoke-950 border-tuatara-900 border-b-2 text-gray-300"
>
<!-- Location Name -->
<td class="px-6 py-4">
<div
class="cursor-pointer"
@click="
openModal('details');
this.locID = loc.id;
"
>
<img
class="float-left w-8 mr-3"
:src="getWeatherIcon(loc.data.current.weather_code)"
alt="weather icon"
/>
<div class="text-container">
<p class="text-sm pt-1.5 font-semibold font-sans align-middle">
{{ loc.data.location.name }}
</p>
</div>
</div>
</td>
<!-- Temperature -->
<td class="px-6 py-4">{{ Math.ceil(loc.data.current.temperature_2m) }} °C</td>
<!-- Rainfall -->
<td class="px-6 py-4">
{{ Math.round(loc.data.current.rain * 100) / 100 }} mm
</td>
<!-- Delete Button -->
<td class="float-right px-6 py-4">
<UButton
icon="i-heroicons-trash"
size="sm"
color="mineshaft"
square
variant="ghost"
@click="
openModal('delete');
this.locID = loc.id;
"
/>
</td>
</tr>
</template>
<!-- Show message if weatherData is empty -->
<template v-else>
<tr class="bg-woodsmoke-950 border-tuatara-900 border-b-2 text-gray-300">
<td colspan="4" class="px-6 pt-10 pb-8">
<p class="text-pretty text-center">Please add a location</p>
</td>
</tr>
</template>
</tbody>
</table>
<!-- Notification Component -->
<UNotifications />
</div>
</div>
</div>
</div>
</div>
<!-- <strong>DEBUG</strong>
<div class="pt-10">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-md text-gray-100 bg-mineshaft-900">
<tr>
<th scope="col" class="px-6 py-3">Location</th>
<th scope="col" class="px-6 py-3">Temperature</th>
<th scope="col" class="px-6 py-3">Rainfall</th>
<th scope="col" class="px-6 py-3"></th>
</tr>
</thead>
<tbody>
<template v-if="locations.length > 0" v-for="loc in this.weatherData">
<tr colspan="4" class="w-full bg-woodsmoke-950 border-tuatara-900 border-b-2 text-gray-300">
<td class="w-full">
<pre>{{ loc }}</pre>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<strong>DEBUG</strong> -->
</template>
+313
View File
@@ -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>
+167
View File
@@ -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>
+150
View File
@@ -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>