diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 8ecdbbd6f99b5dd387ba984ab36ae796db13a8fe..436aa65287477096e91cd4e0788f6c8982af3d9c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,12 +3,14 @@ import NavBar from './components/NavBar.vue'; </script> <template> - <NavBar></NavBar> + + <main class="content"> + <NavBar /> + </main> </template> <style scoped> -header { - line-height: 1.5; - max-height: 100vh; +.content { + padding-top: 100px; /* Platz für fixierten Header (60px Logo + Padding) */ } </style> diff --git a/frontend/src/assets/bau-1.png b/frontend/src/assets/bau-1.png index 64f107be4c6a148800eafc17400aa45bb467a930..589e95ef249868f19bdddca4315ffd25013f18f5 100644 Binary files a/frontend/src/assets/bau-1.png and b/frontend/src/assets/bau-1.png differ diff --git a/frontend/src/assets/bau-2.png b/frontend/src/assets/bau-2.png new file mode 100644 index 0000000000000000000000000000000000000000..3683f00e27535730e341b54951a043f0e8839327 Binary files /dev/null and b/frontend/src/assets/bau-2.png differ diff --git a/frontend/src/assets/bau-3.png b/frontend/src/assets/bau-3.png new file mode 100644 index 0000000000000000000000000000000000000000..a6b7e7125aa0117e2c5f4a3b5f5f2ca8ffb050a9 Binary files /dev/null and b/frontend/src/assets/bau-3.png differ diff --git a/frontend/src/assets/bau-8.png b/frontend/src/assets/bau-8.png new file mode 100644 index 0000000000000000000000000000000000000000..98cc18f5ab61dbba8b80322d162f453ff69fd470 Binary files /dev/null and b/frontend/src/assets/bau-8.png differ diff --git a/frontend/src/components/ChartCard.vue b/frontend/src/components/ChartCard.vue index 5f5347b3f00d1e86885decdf5e7c70b19ed189fc..26a7fc345f56341736f95ff4fac7f99b0006577e 100644 --- a/frontend/src/components/ChartCard.vue +++ b/frontend/src/components/ChartCard.vue @@ -1,104 +1,93 @@ <template> - <div class="chart-card"> - <h3>{{ title }}</h3> - <Line :data="chartData" :options="chartOptions" /> - </div> - </template> - - <script lang="ts"> - import { defineComponent } from 'vue'; - import type { PropType } from 'vue'; - - import { - Chart as ChartJS, - Title, - Tooltip, - Legend, - LineElement, - PointElement, - CategoryScale, - LinearScale - } from 'chart.js'; - import { Line } from 'vue-chartjs'; - import type { ChartData, ChartOptions } from 'chart.js'; - - // â›³ï¸ WICHTIG: Registrierung außerhalb von defineComponent - ChartJS.register( - Title, - Tooltip, - Legend, - LineElement, - PointElement, - CategoryScale, - LinearScale - ); - - export default defineComponent({ - name: 'ChartCard', - components: { - Line - }, - props: { - title: { - type: String, - required: true - }, - labels: { - type: Array as PropType<string[]>, - required: true - }, - data: { - type: Array as PropType<number[]>, - required: true - }, - borderColor: { - type: String, - required: true + <div class="chart-card"> + <h3>{{ title }}</h3> + <Line :data="chartData" :options="chartOptions" /> + </div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue' +import type { PropType } from 'vue' + +import { + Chart as ChartJS, + Title, + Tooltip, + Legend, + LineElement, + PointElement, + CategoryScale, + LinearScale +} from 'chart.js' +import { Line } from 'vue-chartjs' +import type { ChartData, ChartOptions } from 'chart.js' + +ChartJS.register( + Title, + Tooltip, + Legend, + LineElement, + PointElement, + CategoryScale, + LinearScale +) + +export default defineComponent({ + name: 'ChartCard', + components: { Line }, + props: { + title: { type: String, required: true }, + labels: { type: Array as PropType<string[]>, required: true }, + data: { type: Array as PropType<number[]>, required: true }, + borderColor: { type: String, required: true } + }, + computed: { + chartData(): ChartData<'line'> { + return { + labels: this.labels, + datasets: [ + { + label: this.title, + data: this.data, + borderColor: this.borderColor, + backgroundColor: this.borderColor + '33', + fill: true, + tension: 0.3 + } + ] } }, - computed: { - chartData(): ChartData<'line'> { - return { - labels: this.labels, - datasets: [ - { - label: this.title, - data: this.data, - fill: false, - borderColor: this.borderColor, - tension: 0.3 - } - ] - }; - }, - chartOptions(): ChartOptions<'line'> { - return { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true - } - } - }; + chartOptions(): ChartOptions<'line'> { + return { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { position: 'top' }, + title: { display: false } + }, + scales: { + y: { beginAtZero: true } + } } } - }); - </script> - - <style scoped> - .chart-card { - width: 800px; - height: 750px; - background: white; - padding: 1rem; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - } - .chart-card h3 { - text-align: center; - margin-bottom: 0.5rem; - font-size: 1rem; } - </style> - \ No newline at end of file +}) +</script> + +<style scoped> +.chart-card { + width: 1000px; + height: 500px; + background: white; + padding: 1.5rem; + border-radius: 10px; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +.chart-card h3 { + text-align: center; + margin-bottom: 1rem; + font-size: 1.25rem; + font-weight: 600; +} +</style> diff --git a/frontend/src/components/NavBar.vue b/frontend/src/components/NavBar.vue index 58791972070ce73c59dd6438374aadf93dbb3bed..bbeef708d7f352cca68fa63149cc3b436c7dcdaa 100644 --- a/frontend/src/components/NavBar.vue +++ b/frontend/src/components/NavBar.vue @@ -9,10 +9,8 @@ import { RouterLink, RouterView } from 'vue-router' <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/login">Login</RouterLink> - <RouterLink to="/about">About</RouterLink> - <RouterLink to="/buildingsView">Gebäude</RouterLink> - <RouterLink to="/charts">Charts</RouterLink> <RouterLink to="/register">Registrieren</RouterLink> + <RouterLink to="/buildingsView">Gebäude</RouterLink> </nav> </div> </header> diff --git a/frontend/src/views/BuildingsView.vue b/frontend/src/views/BuildingsView.vue index 749485265844e78d74e33848b6f0b4b329804d9d..4854c76f7184549c58ae2b3c473441c75d086281 100644 --- a/frontend/src/views/BuildingsView.vue +++ b/frontend/src/views/BuildingsView.vue @@ -8,25 +8,25 @@ const buildings = [ id: '1', title: 'Bau 1', description: 'Zentralgebäude mit Laboren', - image: '../assets/bau-1.png', + image: new URL('@/assets/bau-1.png', import.meta.url).href, }, { id: '2', title: 'Bau 2', description: 'Informatik und IT-Räume', - image: '/images/building2.png', + image: new URL('@/assets/bau-2.png', import.meta.url).href, }, { id: '3', title: 'Bau 3', description: 'Informatik und IT-Räume', - image: '/images/building2.png', + image: new URL('@/assets/bau-3.png', import.meta.url).href, }, { id: '8', title: 'Bau 8', description: 'Informatik und IT-Räume', - image: '/images/building2.png', + image: new URL('@/assets/bau-8.png', import.meta.url).href, } ] @@ -90,19 +90,20 @@ async function loadRooms(buildingId: string) { display: flex; padding: 2rem; gap: 2rem; + align-items: flex-start; /* wichtige Ergänzung */ } .building-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + display: flex; + flex-direction: column; /* Gebäude untereinander */ gap: 1.5rem; - flex: 2; + flex-shrink: 0; } .room-panel { - flex: 1; display: flex; flex-direction: column; gap: 1rem; + flex-grow: 1; } </style> diff --git a/frontend/src/views/ChartView.vue b/frontend/src/views/ChartView.vue index e0ec4d4128abeea2766c4f586b7296d67ed3ddaf..93d4e411d38c837118fccb26f54755db14226cc7 100644 --- a/frontend/src/views/ChartView.vue +++ b/frontend/src/views/ChartView.vue @@ -2,16 +2,38 @@ <div class="chart-view"> <h2>Daten für Raum: {{ room }}</h2> - <ChartCard - title="COâ‚‚ Verlauf" - :labels="labels" - :data="co2Data" - borderColor="#ff6384" - /> - - <!-- Debug-Ausgabe --> - <pre>Labels: {{ labels }}</pre> - <pre>COâ‚‚: {{ co2Data }}</pre> + <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> + </div> + + <div class="chart-grid"> + <ChartCard + title="COâ‚‚ Verlauf" + :labels="labels" + :data="co2Data" + borderColor="#ff6384" + /> + <ChartCard + title="Temperatur Verlauf" + :labels="labels" + :data="temperatureData" + borderColor="#36a2eb" + /> + <ChartCard + title="Luftfeuchtigkeit Verlauf" + :labels="labels" + :data="humidityData" + borderColor="#4bc0c0" + /> + </div> </div> </template> @@ -29,36 +51,83 @@ export default defineComponent({ const labels = ref<string[]>([]) const co2Data = ref<number[]>([]) + const temperatureData = ref<number[]>([]) + const humidityData = ref<number[]>([]) - onMounted(async () => { + const start = ref('') + const stop = ref('') + + function toISOStringSafe(value: string): string { try { - const url = `http://localhost:8000/api/room_data_range?room=${encodeURIComponent(room)}&start=-7d&stop=now()` - const response = await fetch(url) + return new Date(value).toISOString() + } catch { + return '' + } + } + + async function loadData() { + if (!start.value || !stop.value) return + + const startISO = toISOStringSafe(start.value) + const stopISO = toISOStringSafe(stop.value) - if (!response.ok) { - throw new Error(`HTTP-Fehler: ${response.status}`) - } + 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() ) - const labelsArray = entries.map(([timestamp]) => - new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + 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) ) - - const co2DataArray = entries.map(([, values]) => values.co2) - - labels.value = labelsArray - co2Data.value = co2DataArray } catch (err) { console.error('⌠Fehler beim Laden der Daten:', err) } + } + + onMounted(async () => { + const now = new Date() + const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) + start.value = weekAgo.toISOString().slice(0, 16) + stop.value = now.toISOString().slice(0, 16) + await loadData() }) - return { room, labels, co2Data } - } + return { + room, + start, + stop, + labels, + co2Data, + temperatureData, + humidityData, + loadData, + } + }, }) </script> @@ -66,4 +135,41 @@ export default defineComponent({ .chart-view { padding: 2rem; } + +.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; +} + +.chart-grid { + display: flex; + flex-wrap: nowrap; + gap: 2rem; + justify-content: flex-start; + flex-direction: row; +} </style>