Files
weather-station/client/src/views/Dashboard.vue

187 lines
3.9 KiB
Vue

`<script setup lang="ts">
import GraphModal from '@/components/GraphModal.vue';
import HourlyForecast from '@/components/HourlyForecast.vue';
import Moon from '@/components/Moon.vue';
import Precipitation from '@/components/Precipitation.vue';
import Seismic from '@/components/Seismic.vue';
import Sun from '@/components/Sun.vue';
import Wind from '@/components/Wind.vue';
import {BASE, useWeather} from '@/services/api.ts';
import {formatDate} from '@ztimson/utils';
import {onMounted, onUnmounted, provide, ref} from 'vue';
import MapView from '../components/MapView.vue';
import CurrentWeather from '../components/CurrentWeather.vue';
import ForecastStrip from '../components/ForecastStrip.vue';
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 clock: ReturnType<typeof setInterval>;
provide('openGraph', (key: string) => selectedMetric.value = key);
onMounted(async () => {
useWeather();
clock = setInterval(() => {
day.value = formatDate('dddd');
date.value = formatDate('MMMM D');
time.value = formatDate('h:mm A');
}, 1000);
});
onUnmounted(() => {
clearInterval(clock);
});
</script>
<style scoped lang="scss">
.dashboard {
display: flex;
height: 100vh;
overflow: hidden;
background: var(--bg);
color: var(--text);
}
.map-col {
flex: 1;
min-width: 0;
padding: 16px;
display: flex;
flex-direction: column;
}
.panel-col {
width: 380px;
padding: 16px;
overflow-y: auto;
border-left: 1px solid var(--border);
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 2px;
}
}
.group-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted);
margin-top: 4px;
margin-bottom: 2px;
padding: 0 2px;
}
.metric-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
> * {
flex: 1 1 140px; // grow, shrink, min width before wrapping
min-width: 0;
}
}
.loading-bar {
height: 2px;
background: var(--accent);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
@media (max-width: 768px) {
.dashboard {
flex-direction: column;
overflow-y: auto;
overflow-x: hidden; // ← added
height: 100dvh;
}
.map-col {
width: 100%;
height: 100vw;
min-height: 100vw;
padding: 8px;
flex-shrink: 0;
}
.panel-col {
width: 100%;
flex-shrink: 0;
border-left: none;
border-top: 1px solid var(--border);
overflow-y: visible;
overflow-x: hidden; // ← added
-webkit-overflow-scrolling: touch;
box-sizing: border-box; // ← added
&::before {
display: none;
}
&::-webkit-scrollbar {
display: none;
}
}
}
</style>
<template>
<div class="dashboard">
<div class="map-col">
<MapView :dark="props.dark"/>
</div>
<div class="panel-col">
<div class="flex-r align-items-center gap-3 mb-3">
<div class="pe-3" style="border-right: 2px solid var(--border)">
<h2 class="fs-6 m-0" style="line-height: 1em">{{time}}</h2>
</div>
<div class="flex-c">
<h1 class="m-0 fs-2" style="line-height: 1em">{{day}}</h1>
<h2 class="m-0 fs-1 fg-muted" style="line-height: 1em">{{date}}</h2>
</div>
</div>
<CurrentWeather class="mb-3" />
<HourlyForecast class="mb-3" />
<ForecastStrip class="mb-3" />
<Seismic class="mb-3" />
<Precipitation class="mb-3" />
<Wind class="mb-3" />
<Sun class="mb-3" />
<Moon class="mb-3" />
<div class="align-x">
<a :href="BASE + '/docs'" style="font-size: 0.75em;" target="_blank">OpenAPI Docs</a> | <a href="https://zakscode.com" style="font-size: 0.75em;" target="_blank">ZaksCode</a>
</div>
</div>
<GraphModal :metric-key="selectedMetric" @close="selectedMetric = null" />
</div>
</template>
`