415 lines
18 KiB
Vue
415 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: 'https://api-weather.argideli.com',
|
|
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;
|
|
},
|
|
|
|
/**
|
|
* Calls the API to fetch weather data for the specified location.
|
|
* a bulk parameter can be passed to fetch weather data for a list oflocations
|
|
* or a single location.
|
|
*
|
|
* @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>
|