Daily fixes and loading animations

This commit is contained in:
2026-06-24 11:08:23 -04:00
parent 811780125e
commit 8019dabe2a
4 changed files with 96 additions and 23 deletions

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import Loading from '@/components/Loading.vue';
import {onMounted, ref} from 'vue';
import {api, BASE} from '../services/api';
import {api} from '../services/api';
const data = ref<any>({});
const data = ref<any>();
function dir(deg: number) {
if(deg > 22.5 && deg <= 67.5) return 'NE';
@@ -17,13 +18,12 @@ function dir(deg: number) {
onMounted(async () => {
data.value = await api.current();
console.log(data.value);
});
</script>
<style scoped lang="scss">
.today-card {
position: relative;
border-radius: 12px;
background: var(--surface);
border: 1px solid var(--border);
@@ -123,18 +123,26 @@ onMounted(async () => {
<template>
<div class="today-card">
<div class="flex-r align-items-center justify-between px-3">
<div class="flex-c justify-center align-items-center">
<img v-if="data.icon" :src="'https://openweathermap.org/img/wn/' + data.icon + '@2x.png'" class="today-icon" alt="weather" />
<div class="flex-r align-items-center justify-between">
<div v-if="!data" class="pos-rel br-2 overflow-hidden" style="width: 75px; height: 50px"><Loading/></div>
<div v-else class="flex-c justify-center align-items-center mx-3">
<img :src="'https://openweathermap.org/img/wn/' + data.icon + '@2x.png'" class="today-icon" alt="weather" />
<div class="today-label">{{ data.label }}</div>
</div>
<div class="flex-r align-items-center gap-2">
<div class="fs-6">
{{ data.temperature?.toFixed(0) }}°C
<div class="fs-6 m-0 p-0 flex-r align-items-center">
<div v-if="!data" class="d-inline-block pos-rel br-2 overflow-hidden" style="width: 50px; height: 1em"><Loading/></div>
<template v-else>{{ Math.round(data.temperature) }}°C</template>
</div>
<div class="flex-c fg-muted">
<span class="hi"> {{ 0 }}°</span>
<span class="lo"> {{ 0 }}°</span>
<div class="hi">
<div v-if="!data" class="d-inline-block pos-rel br-2 overflow-hidden" style="width: 25px; height: 1em"><Loading/></div>
<template v-else>{{ 0 }}°</template>
</div>
<div class="lo">
<div v-if="!data" class="d-inline-block pos-rel br-2 overflow-hidden" style="width: 25px; height: 1em"><Loading/></div>
<template v-else>{{ 0 }}°</template>
</div>
</div>
</div>
</div>
@@ -142,12 +150,35 @@ onMounted(async () => {
<div class="divider" />
<div class="stat-grid">
<div class="stat"><span class="stat-label">Precipitation</span><span class="stat-value">{{ !data.precipitation && data.raining == 'True' ? 'Spitting' : (data.precipitation || 0).toFixed(1) + 'mm'}}</span></div>
<div class="stat"><span class="stat-label">Humidity</span><span class="stat-value">{{ (data.humidity || 0).toFixed(0) }}%</span></div>
<div class="stat"><span class="stat-label">Wind</span><span class="stat-value">{{ data.wind_gusts || 0 }} Kph <span class="fg-muted">({{ dir(data.wind_direction || 0) }})</span></span></div>
<div class="stat"><span class="stat-label">Clouds</span><span class="stat-value">{{ (data.clouds || 0).toFixed(0) }}%</span></div>
<div class="stat"><span class="stat-label">Air Quality</span><span class="stat-value">{{ data.air_quality }} <span class="fg-muted">({{data.air_quality_label}})</span></span></div>
<div class="stat"><span class="stat-label">UV index</span><span class="stat-value">{{ (data.uv_index || 0).toFixed(1) }} <span class="fg-muted">({{data.uv_index_label}})</span></span></div>
<div class="stat">
<span class="stat-label">Precipitation</span>
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
<span v-else class="stat-value">{{ !data.precipitation && data.raining == 'True' ? 'Spitting' : data.precipitation.toFixed(1) + 'mm'}}</span>
</div>
<div class="stat">
<span class="stat-label">Humidity</span>
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
<span v-else class="stat-value">{{ Math.round(data.humidity) }}%</span>
</div>
<div class="stat">
<span class="stat-label">Wind</span>
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
<span v-else class="stat-value">{{ data.wind_gusts }} Kph <span class="fg-muted">({{ dir(data.wind_direction) }})</span></span>
</div>
<div class="stat">
<span class="stat-label">Clouds</span>
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
<span v-else class="stat-value">{{ Math.round(data.clouds) }}%</span></div>
<div class="stat">
<span class="stat-label">Air Quality</span>
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
<span v-else class="stat-value">{{ data.air_quality }} <span class="fg-muted">({{data.air_quality_label}})</span></span>
</div>
<div class="stat">
<span class="stat-label">UV index</span>
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
<span v-else class="stat-value">{{ data.uv_index.toFixed(1) }} <span class="fg-muted">({{data.uv_index_label}})</span></span>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,43 @@
<style scoped lang="scss">
.shimmer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.stroke {
height: 100%;
width: 100%;
background: #777;
}
.dark .animate {
animation: shimmer 3s infinite;
background: linear-gradient(90deg, #ffffff10 25%, #ffffff30 50%, #ffffff10 75%);
background-size: 200% 100%;
}
.animate {
animation: shimmer 3s infinite;
background: linear-gradient(to right, #e6e6e6 0%, #cccccc 50%, #e6e6e6 100%);
background-size: 200% 100%;
}
@keyframes shimmer {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
</style>
<template>
<div class="shimmer">
<div class="stroke animate"></div>
</div>
</template>

View File

@@ -1,7 +1,7 @@
`<script setup lang="ts">
import {formatDate} from '@ztimson/utils';
import {onMounted, onUnmounted, ref, computed} from 'vue';
import {current, hourly, daily, fetchAll, loading} from '../services/weather';
import {current, daily, fetchAll, loading} from '../services/weather';
import {METRICS, GROUPS} from '../services/units';
import MapView from '../components/MapView.vue';
import CurrentWeather from '../components/CurrentWeather.vue';
@@ -9,9 +9,9 @@ import ForecastStrip from '../components/ForecastStrip.vue';
import MetricCard from '../components/MetricCard.vue';
import GraphModal from '../components/GraphModal.vue';
const day = ref();
const date = ref();
const time = ref();
const day = ref(formatDate('dddd'));
const date = ref(formatDate('MMMM D'));
const time = ref(formatDate('h:mm A'));
const props = defineProps<{dark: boolean}>();
const selectedMetric = ref<string | null>(null);
let interval: ReturnType<typeof setInterval>;

View File

@@ -86,13 +86,12 @@ app.get('/api/daily', async (req, res) => {
const {fields} = req.query;
const start = req.query.start || new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
let now = new Date();
now.setDate(now.getDate() - 1);
now.setHours(0, 0, 0, 0);
now = now.toISOString();
const end = req.query.end || new Date().toISOString();
const coords = await getCoords();
const [sensor, meteo] = await Promise.allSettled([
queryDaily(start, now),
now > start ? queryDaily(start, now) : Promise.resolve([]),
getOpenMeteo(coords.latitude, coords.longitude, now, end),
]);
const history = sensor.status === 'fulfilled' ? sensor.value : [];