mirror of
https://github.com/ckaczor/HomeMonitor.git
synced 2026-02-16 18:47:40 -05:00
Add indoor summary
This commit is contained in:
1
WebDisplay/components.d.ts
vendored
1
WebDisplay/components.d.ts
vendored
@@ -13,6 +13,7 @@ declare module 'vue' {
|
|||||||
CurrentWeather: typeof import('./src/components/CurrentWeather.vue')['default']
|
CurrentWeather: typeof import('./src/components/CurrentWeather.vue')['default']
|
||||||
DashboardItem: typeof import('./src/components/DashboardItem.vue')['default']
|
DashboardItem: typeof import('./src/components/DashboardItem.vue')['default']
|
||||||
Indoor: typeof import('./src/components/Indoor.vue')['default']
|
Indoor: typeof import('./src/components/Indoor.vue')['default']
|
||||||
|
IndoorSummary: typeof import('./src/components/IndoorSummary.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
TimeRange: typeof import('./src/components/TimeRange.vue')['default']
|
TimeRange: typeof import('./src/components/TimeRange.vue')['default']
|
||||||
|
|||||||
84
WebDisplay/src/components/IndoorSummary.vue
Normal file
84
WebDisplay/src/components/IndoorSummary.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { createIndoorStore } from '@/stores/indoorStore';
|
||||||
|
import ReadingsAggregate from '@/models/environment/readingsAggregate';
|
||||||
|
import { ConvertPascalToInchesOfMercury } from '@/pressureConverter';
|
||||||
|
import { ConvertCToF } from '@/temperatureConverter';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
title: { type: String, required: true },
|
||||||
|
start: { type: Date, required: true },
|
||||||
|
end: { type: Date, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const readingAggregates = ref<ReadingsAggregate | undefined>();
|
||||||
|
|
||||||
|
const indoorStore = createIndoorStore(props.name);
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
indoorStore.getReadingsAggregate(props.name, props.start, props.end).then((newReadingAggregates) => {
|
||||||
|
readingAggregates.value = newReadingAggregates;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.start, props.end],
|
||||||
|
() => load
|
||||||
|
);
|
||||||
|
|
||||||
|
load();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DashboardItem :title="props.title">
|
||||||
|
<div className="reading-summary">
|
||||||
|
<div v-if="!readingAggregates">Loading...</div>
|
||||||
|
<table v-else>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Minimum</th>
|
||||||
|
<th>Average</th>
|
||||||
|
<th>Maximum</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="reading-summary-header">Temperature</td>
|
||||||
|
<td>{{ ConvertCToF(readingAggregates!.minimumTemperature).toFixed(2) }}°F</td>
|
||||||
|
<td>{{ ConvertCToF(readingAggregates!.averageTemperature).toFixed(2) }}°F</td>
|
||||||
|
<td>{{ ConvertCToF(readingAggregates!.maximumTemperature).toFixed(2) }}°F</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="reading-summary-header">Humidity</td>
|
||||||
|
<td>{{ readingAggregates!.minimumHumidity.toFixed(2) }}%</td>
|
||||||
|
<td>{{ readingAggregates!.averageHumidity.toFixed(2) }}%</td>
|
||||||
|
<td>{{ readingAggregates!.maximumHumidity.toFixed(2) }}%</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="reading-summary-header">Pressure</td>
|
||||||
|
<td>{{ ConvertPascalToInchesOfMercury(readingAggregates!.minimumPressure).toFixed(2) }}"</td>
|
||||||
|
<td>{{ ConvertPascalToInchesOfMercury(readingAggregates!.averagePressure).toFixed(2) }}"</td>
|
||||||
|
<td>{{ ConvertPascalToInchesOfMercury(readingAggregates!.maximumPressure).toFixed(2) }}"</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</DashboardItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.reading-summary {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reading-summary-header {
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
12
WebDisplay/src/models/environment/readingsAggregate.ts
Normal file
12
WebDisplay/src/models/environment/readingsAggregate.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export default interface ReadingsAggregate {
|
||||||
|
name: string;
|
||||||
|
minimumTemperature: number;
|
||||||
|
averageTemperature: number;
|
||||||
|
maximumTemperature: number;
|
||||||
|
minimumPressure: number;
|
||||||
|
averagePressure: number;
|
||||||
|
maximumPressure: number;
|
||||||
|
minimumHumidity: number;
|
||||||
|
averageHumidity: number;
|
||||||
|
maximumHumidity: number;
|
||||||
|
}
|
||||||
@@ -30,6 +30,20 @@
|
|||||||
:start="start"
|
:start="start"
|
||||||
:end="end"></WeatherSummary>
|
:end="end"></WeatherSummary>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
<v-card class="main-summary">
|
||||||
|
<IndoorSummary
|
||||||
|
name="main"
|
||||||
|
title="Upstairs"
|
||||||
|
:start="start"
|
||||||
|
:end="end"></IndoorSummary>
|
||||||
|
</v-card>
|
||||||
|
<v-card class="basement-summary">
|
||||||
|
<IndoorSummary
|
||||||
|
name="basement"
|
||||||
|
title="Downstairs"
|
||||||
|
:start="start"
|
||||||
|
:end="end"></IndoorSummary>
|
||||||
|
</v-card>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,41 +56,34 @@
|
|||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, max-content);
|
grid-template-columns: repeat(2, max-content);
|
||||||
grid-template-rows: repeat(2, max-content);
|
grid-template-rows: repeat(2, max-content);
|
||||||
gap: 15px 15px;
|
gap: 15px 15px;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
grid-template-areas:
|
grid-template-areas: 'weather-summary .' 'main-summary basement-summary';
|
||||||
'weather-summary weather-summary weather-summary'
|
|
||||||
'weather-summary weather-summary weather-summary';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, max-content);
|
|
||||||
grid-template-rows: repeat(2, max-content);
|
|
||||||
gap: 15px 15px;
|
|
||||||
grid-auto-flow: row;
|
|
||||||
grid-template-areas:
|
|
||||||
'weather-summary weather-summary weather-summary'
|
|
||||||
'weather-summary weather-summary weather-summary';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.container {
|
.container {
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(1, max-content);
|
grid-template-columns: repeat(1, max-content);
|
||||||
grid-template-rows: repeat(1, max-content);
|
grid-template-rows: repeat(3, max-content);
|
||||||
gap: 10px 0px;
|
gap: 10px 0px;
|
||||||
grid-template-areas: 'weather-summary';
|
grid-template-areas: 'weather-summary' 'main-summary' 'basement-summary';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.weather-summary {
|
.weather-summary {
|
||||||
grid-area: weather-summary;
|
grid-area: weather-summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-summary {
|
||||||
|
grid-area: main-summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basement-summary {
|
||||||
|
grid-area: basement-summary;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { defineStore } from 'pinia';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Environment from '@/environment';
|
import Environment from '@/environment';
|
||||||
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
||||||
import { LatestReadings } from '@/models/environment.ts/latestReadings';
|
import { LatestReadings } from '@/models/environment/latestReadings';
|
||||||
import { ReadingsGrouped } from '@/models/environment.ts/readingsGrouped';
|
import { ReadingsGrouped } from '@/models/environment/readingsGrouped';
|
||||||
|
import ReadingsAggregate from '@/models/environment/readingsAggregate';
|
||||||
|
|
||||||
export function createIndoorStore(name: string) {
|
export function createIndoorStore(name: string) {
|
||||||
return defineStore(`indoor-${name}`, {
|
return defineStore(`indoor-${name}`, {
|
||||||
@@ -56,6 +57,16 @@ export function createIndoorStore(name: string) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
|
},
|
||||||
|
async getReadingsAggregate(name: string, start: Date, end: Date): Promise<ReadingsAggregate | undefined> {
|
||||||
|
const startString = start.toISOString();
|
||||||
|
const endString = end.toISOString();
|
||||||
|
|
||||||
|
const response = await axios.get<ReadingsAggregate[]>(
|
||||||
|
Environment.getUrlPrefix() + `/api/environment/readings/aggregate?start=${startString}&end=${endString}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.find((aggregate) => aggregate.name === name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user