Commit 4c761710 authored by 22stmo1bif's avatar 22stmo1bif
Browse files

Refactor sensor schemas for improved data structure; update...

Refactor sensor schemas for improved data structure; update RoomDataRangeResponse to use nested dictionaries
Add authentication handling and logout functionality in NavBar component
Enhance AboutView with additional project details and links
Revamp ChartView for better data loading and display; implement default time window and improved styling
Update HomeView styles for better readability; remove deprecated HomeViewOLD component
parent 7e8ad6c5
......@@ -2,9 +2,6 @@ from ninja import Schema
from typing import List, Dict, Optional
from datetime import datetime
class SensorData(Schema):
co2: int
humidity: float
......@@ -13,7 +10,7 @@ class SensorData(Schema):
class RoomDataRangeResponse(Schema):
room: str
data: Dict[datetime, SensorData]
data: Dict[str, Dict[str, float]]
class RoomDataPoint(Schema):
......
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { useRouter } from 'vue-router'
const authStore = useAuthStore()
const router = useRouter()
const logout = async () => {
try {
await authStore.logout(router)
} catch (error) {
console.error('Logout error:', error)
}
}
</script>
<template>
......@@ -14,10 +27,20 @@ import { RouterLink, RouterView } from 'vue-router'
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink to="/login">Login</RouterLink>
<RouterLink to="/register">Registrieren</RouterLink>
<RouterLink to="/buildingsView">Gebäude</RouterLink>
<RouterLink to="/dashboard">Device Dashboard</RouterLink>
<!-- Nur anzeigen wenn NICHT eingeloggt -->
<template v-if="!authStore.isAuthenticated">
<RouterLink to="/login">Login</RouterLink>
<RouterLink to="/register">Registrieren</RouterLink>
</template>
<!-- Nur anzeigen wenn eingeloggt -->
<template v-if="authStore.isAuthenticated">
<RouterLink to="/buildingsView">Gebäude</RouterLink>
<RouterLink to="/dashboard">Device Dashboard</RouterLink>
<span class="user-info">{{ authStore.user?.username }}</span>
<button @click="logout" class="logout-btn">Logout</button>
</template>
</nav>
</div>
</header>
......@@ -66,6 +89,28 @@ nav a:hover {
color: darkred;
}
.user-info {
margin-left: 1rem;
color: #333;
font-weight: 500;
}
.logout-btn {
margin-left: 1rem;
padding: 0.4rem 0.8rem;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.logout-btn:hover {
background: #c82333;
}
main {
padding-top: 80px; /* Abstand für den festen Header */
}
......
......@@ -51,17 +51,30 @@
<h2>Projektteam</h2>
<p>
Das Projekt wurde an der HFT Stuttgart unter anderem von Eric Duminil, Tobias Erhart,
Das Ursprüngliche Projekt wurde an der HFT Stuttgart unter anderem von Eric Duminil, Tobias Erhart,
Myriam Guedey, Nathalie Heimsch, Benjamin Hueber, Robert Otto und Jonas Stave entwickelt.
</p>
<p>Die Implementierung dieser Webseite und eines Servers mit MQTT-Broker, Stream-Processing,
InfluxDB Datenbank, django backend, vue.js frontend
entsand in einem Team-Projekt im Pervasive Computing Wahlpflichfach.
</p>
<p>
Weitere Infos und Bauanleitungen unter:
Weitere Infos und zum ursprünglichen Projekt unter:
<a href="https://transfer.hft-stuttgart.de/gitlab/co2ampel" target="_blank">
https://transfer.hft-stuttgart.de/gitlab/co2ampel
</a>
</p>
<p>
Weitere Infos zum weiterführenden Projekt:
<a href="https://transfer.hft-stuttgart.de/gitlab/co2ampel2" target="_blank">
https://transfer.hft-stuttgart.de/gitlab/co2ampel2
</a>
</p>
<h2>Technologie</h2>
<ul>
......
<template>
<div class="chart-view">
<h2>Daten für Raum: {{ room }}</h2>
<div class="time-form">
<label>
Start:
<input class="datetime" type="datetime-local" v-model="start" />
</label>
<label>
Stop:
<input class="datetime" type="datetime-local" v-model="stop" />
</label>
<button @click="loadData">Zeitraum aktualisieren</button>
<button @click="loadDataWithDefaultWindow" class="default-btn">Zeige die letzte Woche</button>
</div>
<div class="chart-grid">
<ChartCard
title="CO₂ Verlauf"
:labels="displayLabels"
:data="co2Data"
borderColor="#ff6384"
/>
<ChartCard
title="Temperatur Verlauf"
:labels="displayLabels"
:data="temperatureData"
borderColor="#36a2eb"
/>
<ChartCard
title="Luftfeuchtigkeit Verlauf"
:labels="displayLabels"
:data="humidityData"
borderColor="#4bc0c0"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import ChartCard from '../components/ChartCard.vue'
import api from '@/services/apiClient' // Axios instance
export default defineComponent({
name: 'ChartView',
components: { ChartCard },
setup() {
const route = useRoute()
const room = decodeURIComponent(route.params.room as string)
const labels = ref<string[]>([])
const co2Data = ref<number[]>([])
const temperatureData = ref<number[]>([])
const humidityData = ref<number[]>([])
const start = ref('')
const stop = ref('')
const displayLabels = computed(() => {
const total = labels.value.length
if (total <= 6) return labels.value
const result: string[] = Array(total).fill('')
for (let i = 0; i < 6; i++) {
const idx = Math.round((i * (total - 1)) / 5)
result[idx] = labels.value[idx]
}
return result
})
function toISOStringSafe(value: string): string {
try {
return new Date(value).toISOString()
} catch {
return ''
}
}
function setDefaultTimeWindow() {
const now = new Date()
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
const formatForInput = (date: Date) => {
const year = date.getFullYear()
const month = ('0' + (date.getMonth() + 1)).slice(-2)
const day = ('0' + date.getDate()).slice(-2)
const hours = ('0' + date.getHours()).slice(-2)
const minutes = ('0' + date.getMinutes()).slice(-2)
return `${year}-${month}-${day}T${hours}:${minutes}`
}
start.value = formatForInput(weekAgo)
stop.value = formatForInput(now)
}
async function loadData() {
if (!start.value || !stop.value) return
const startISO = toISOStringSafe(start.value)
const stopISO = toISOStringSafe(stop.value)
if (new Date(startISO) >= new Date(stopISO)) {
alert('❌ Der Startzeitpunkt muss vor dem Endzeitpunkt liegen.')
return
}
try {
const response = await api.get('/room_data_range', {
params: {
room,
start: startISO,
stop: stopISO,
},
})
const entries = Object.entries(response.data.data).sort(
([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
)
labels.value = entries.map(([timestamp]) =>
new Date(timestamp).toLocaleString([], {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
)
co2Data.value = entries.map(([, values]: any) => Number(values.co2))
temperatureData.value = entries.map(([, values]: any) => Number(values.temperature))
humidityData.value = entries.map(([, values]: any) => Number(values.humidity))
} catch (err: any) {
console.error('❌ Fehler beim Laden der Daten:', err)
alert('Fehler beim Laden der Daten')
}
}
async function loadDataWithDefaultWindow() {
setDefaultTimeWindow()
await loadData()
}
onMounted(async () => {
await loadDataWithDefaultWindow()
})
return {
room,
start,
stop,
labels,
displayLabels,
co2Data,
temperatureData,
humidityData,
loadData,
loadDataWithDefaultWindow,
}
},
name: 'ChartView',
components: { ChartCard },
setup() {
const route = useRoute()
const room = decodeURIComponent(route.params.room as string)
const labels = ref<string[]>([])
const co2Data = ref<number[]>([])
const temperatureData = ref<number[]>([])
const humidityData = ref<number[]>([])
const start = ref('')
const stop = ref('')
// Computed property for displaying max 6 labels
const displayLabels = computed(() => {
const total = labels.value.length
if (total <= 6) return labels.value
const result: string[] = []
for (let i = 0; i < total; i++) {
result.push('')
}
for (let i = 0; i < 6; i++) {
const idx = Math.round(i * (total - 1) / 5)
result[idx] = labels.value[idx]
}
return result
})
function toISOStringSafe(value: string): string {
try {
return new Date(value).toISOString()
} catch {
return ''
}
}
function setDefaultTimeWindow() {
const now = new Date()
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
// Format dates properly for datetime-local inputs
const formatForInput = (date: Date) => {
const year = date.getFullYear()
const month = ('0' + (date.getMonth() + 1)).slice(-2)
const day = ('0' + date.getDate()).slice(-2)
const hours = ('0' + date.getHours()).slice(-2)
const minutes = ('0' + date.getMinutes()).slice(-2)
return `${year}-${month}-${day}T${hours}:${minutes}`
}
start.value = formatForInput(weekAgo)
stop.value = formatForInput(now)
}
async function loadData() {
if (!start.value || !stop.value) return
const startISO = toISOStringSafe(start.value)
const stopISO = toISOStringSafe(stop.value)
if (new Date(startISO) >= new Date(stopISO)) {
alert('❌ Der Startzeitpunkt muss vor dem Endzeitpunkt liegen.')
return
}
const url = `http://localhost:8000/api/room_data_range?room=${encodeURIComponent(
room
)}&start=${startISO}&stop=${stopISO}`
try {
const response = await fetch(url)
if (!response.ok) throw new Error('Fehler beim Laden')
const json = await response.json()
const entries = Object.entries(json.data).sort(
([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
)
labels.value = entries.map(([timestamp]) =>
new Date(timestamp).toLocaleString([], {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
)
co2Data.value = entries.map(([, values]: any) => Number(values.co2))
temperatureData.value = entries.map(([, values]: any) =>
Number(values.temperature)
)
humidityData.value = entries.map(([, values]: any) =>
Number(values.humidity)
)
} catch (err) {
console.error('❌ Fehler beim Laden der Daten:', err)
}
}
// Function to load data with default last week window
async function loadDataWithDefaultWindow() {
setDefaultTimeWindow()
await loadData()
}
onMounted(async () => {
await loadDataWithDefaultWindow()
})
return {
room,
start,
stop,
labels,
displayLabels,
co2Data,
temperatureData,
humidityData,
loadData,
loadDataWithDefaultWindow,
}
},
})
</script>
<style scoped>
.chart-view {
padding: 2rem;
color: #1a1a1a;
}
.time-form {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
align-items: center;
flex-wrap: wrap;
}
.time-form input {
padding: 0.5rem;
font-size: 1rem;
border-radius: 5px;
}
.time-form button {
background: #007bff;
color: white;
padding: 0.6rem 1.2rem;
border: none;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease;
}
.time-form button:hover {
background-color: #0056b3;
}
.time-form .default-btn {
background: #28a745;
}
.time-form .default-btn:hover {
background-color: #218838;
}
.chart-grid {
display: flex;
flex-wrap: nowrap;
gap: 2rem;
justify-content: flex-start;
flex-direction: row;
}
</style>
......@@ -66,11 +66,13 @@ export default {
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
text-align: center;
color: #333333;
}
h1 {
font-size: 2rem;
margin-bottom: 1rem;
color: #222222;
}
.status-box {
......@@ -79,6 +81,7 @@ h1 {
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 8px;
color: #333333;
}
button {
......
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment