weather-app/components/WeatherDashboard.vue
Argiris Deligiannidis 25883a5362 Finalized codebase
2024-04-14 20:33:51 +03:00

414 lines
18 KiB
Vue

<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>