Commit 6068d9e3 authored by Gezer's avatar Gezer
Browse files

modified frontend so it looks great and works

parent 3dd20147
No related merge requests found
Showing with 234 additions and 138 deletions
+234 -138
...@@ -3,12 +3,14 @@ import NavBar from './components/NavBar.vue'; ...@@ -3,12 +3,14 @@ import NavBar from './components/NavBar.vue';
</script> </script>
<template> <template>
<NavBar></NavBar>
<main class="content">
<NavBar />
</main>
</template> </template>
<style scoped> <style scoped>
header { .content {
line-height: 1.5; padding-top: 100px; /* Platz für fixierten Header (60px Logo + Padding) */
max-height: 100vh;
} }
</style> </style>
frontend/src/assets/bau-1.png

1.25 MB | W: 0px | H: 0px

frontend/src/assets/bau-1.png

2.15 MB | W: 0px | H: 0px

frontend/src/assets/bau-1.png
frontend/src/assets/bau-1.png
frontend/src/assets/bau-1.png
frontend/src/assets/bau-1.png
  • 2-up
  • Swipe
  • Onion skin
frontend/src/assets/bau-2.png

259 KB

frontend/src/assets/bau-3.png

253 KB

frontend/src/assets/bau-8.png

118 KB

<template> <template>
<div class="chart-card"> <div class="chart-card">
<h3>{{ title }}</h3> <h3>{{ title }}</h3>
<Line :data="chartData" :options="chartOptions" /> <Line :data="chartData" :options="chartOptions" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import { import {
Chart as ChartJS, Chart as ChartJS,
Title, Title,
Tooltip, Tooltip,
Legend, Legend,
LineElement, LineElement,
PointElement, PointElement,
CategoryScale, CategoryScale,
LinearScale LinearScale
} from 'chart.js'; } from 'chart.js'
import { Line } from 'vue-chartjs'; import { Line } from 'vue-chartjs'
import type { ChartData, ChartOptions } from 'chart.js'; import type { ChartData, ChartOptions } from 'chart.js'
// ⛳️ WICHTIG: Registrierung außerhalb von defineComponent ChartJS.register(
ChartJS.register( Title,
Title, Tooltip,
Tooltip, Legend,
Legend, LineElement,
LineElement, PointElement,
PointElement, CategoryScale,
CategoryScale, LinearScale
LinearScale )
);
export default defineComponent({
export default defineComponent({ name: 'ChartCard',
name: 'ChartCard', components: { Line },
components: { props: {
Line title: { type: String, required: true },
}, labels: { type: Array as PropType<string[]>, required: true },
props: { data: { type: Array as PropType<number[]>, required: true },
title: { borderColor: { type: String, required: true }
type: String, },
required: true computed: {
}, chartData(): ChartData<'line'> {
labels: { return {
type: Array as PropType<string[]>, labels: this.labels,
required: true datasets: [
}, {
data: { label: this.title,
type: Array as PropType<number[]>, data: this.data,
required: true borderColor: this.borderColor,
}, backgroundColor: this.borderColor + '33',
borderColor: { fill: true,
type: String, tension: 0.3
required: true }
]
} }
}, },
computed: { chartOptions(): ChartOptions<'line'> {
chartData(): ChartData<'line'> { return {
return { responsive: true,
labels: this.labels, maintainAspectRatio: false,
datasets: [ plugins: {
{ legend: { position: 'top' },
label: this.title, title: { display: false }
data: this.data, },
fill: false, scales: {
borderColor: this.borderColor, y: { beginAtZero: true }
tension: 0.3 }
}
]
};
},
chartOptions(): ChartOptions<'line'> {
return {
responsive: true,
maintainAspectRatio: 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> })
</script>
\ No newline at end of file
<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>
...@@ -9,10 +9,8 @@ import { RouterLink, RouterView } from 'vue-router' ...@@ -9,10 +9,8 @@ import { RouterLink, RouterView } from 'vue-router'
<nav> <nav>
<RouterLink to="/">Home</RouterLink> <RouterLink to="/">Home</RouterLink>
<RouterLink to="/login">Login</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="/register">Registrieren</RouterLink>
<RouterLink to="/buildingsView">Gebäude</RouterLink>
</nav> </nav>
</div> </div>
</header> </header>
......
...@@ -8,25 +8,25 @@ const buildings = [ ...@@ -8,25 +8,25 @@ const buildings = [
id: '1', id: '1',
title: 'Bau 1', title: 'Bau 1',
description: 'Zentralgebäude mit Laboren', description: 'Zentralgebäude mit Laboren',
image: '../assets/bau-1.png', image: new URL('@/assets/bau-1.png', import.meta.url).href,
}, },
{ {
id: '2', id: '2',
title: 'Bau 2', title: 'Bau 2',
description: 'Informatik und IT-Räume', description: 'Informatik und IT-Räume',
image: '/images/building2.png', image: new URL('@/assets/bau-2.png', import.meta.url).href,
}, },
{ {
id: '3', id: '3',
title: 'Bau 3', title: 'Bau 3',
description: 'Informatik und IT-Räume', description: 'Informatik und IT-Räume',
image: '/images/building2.png', image: new URL('@/assets/bau-3.png', import.meta.url).href,
}, },
{ {
id: '8', id: '8',
title: 'Bau 8', title: 'Bau 8',
description: 'Informatik und IT-Räume', 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) { ...@@ -90,19 +90,20 @@ async function loadRooms(buildingId: string) {
display: flex; display: flex;
padding: 2rem; padding: 2rem;
gap: 2rem; gap: 2rem;
align-items: flex-start; /* wichtige Ergänzung */
} }
.building-grid { .building-grid {
display: grid; display: flex;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); flex-direction: column; /* Gebäude untereinander */
gap: 1.5rem; gap: 1.5rem;
flex: 2; flex-shrink: 0;
} }
.room-panel { .room-panel {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
flex-grow: 1;
} }
</style> </style>
...@@ -2,16 +2,38 @@ ...@@ -2,16 +2,38 @@
<div class="chart-view"> <div class="chart-view">
<h2>Daten für Raum: {{ room }}</h2> <h2>Daten für Raum: {{ room }}</h2>
<ChartCard <div class="time-form">
title="CO₂ Verlauf" <label>
:labels="labels" Start:
:data="co2Data" <input class="datetime" type="datetime-local" v-model="start" />
borderColor="#ff6384" </label>
/> <label>
Stop:
<!-- Debug-Ausgabe --> <input class="datetime" type="datetime-local" v-model="stop" />
<pre>Labels: {{ labels }}</pre> </label>
<pre>CO₂: {{ co2Data }}</pre> <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> </div>
</template> </template>
...@@ -29,36 +51,83 @@ export default defineComponent({ ...@@ -29,36 +51,83 @@ export default defineComponent({
const labels = ref<string[]>([]) const labels = ref<string[]>([])
const co2Data = ref<number[]>([]) 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 { try {
const url = `http://localhost:8000/api/room_data_range?room=${encodeURIComponent(room)}&start=-7d&stop=now()` return new Date(value).toISOString()
const response = await fetch(url) } catch {
return ''
}
}
async function loadData() {
if (!start.value || !stop.value) return
const startISO = toISOStringSafe(start.value)
const stopISO = toISOStringSafe(stop.value)
if (!response.ok) { if (new Date(startISO) >= new Date(stopISO)) {
throw new Error(`HTTP-Fehler: ${response.status}`) 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 json = await response.json()
const entries = Object.entries(json.data).sort( const entries = Object.entries(json.data).sort(
([a], [b]) => new Date(a).getTime() - new Date(b).getTime() ([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
) )
const labelsArray = entries.map(([timestamp]) => labels.value = entries.map(([timestamp]) =>
new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) 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) { } catch (err) {
console.error('❌ Fehler beim Laden der Daten:', 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> </script>
...@@ -66,4 +135,41 @@ export default defineComponent({ ...@@ -66,4 +135,41 @@ export default defineComponent({
.chart-view { .chart-view {
padding: 2rem; 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> </style>
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