diff --git a/backend/app/urls.py b/backend/app/urls.py index 7f5f0350dba190680062cb2ef8e4e9335966374a..9198df3ba09cab90c2b3c06f78cf48441d02ede6 100644 --- a/backend/app/urls.py +++ b/backend/app/urls.py @@ -11,4 +11,6 @@ urlpatterns = [ path("api/get_rooms", views.get_rooms, name="get_rooms"), path("api/room_data_csv", views.room_data_csv_view, name="room_data_csv_view"), path("api/room_data_csv_download", views.room_data_csv_download, name="room_data_csv_download"), + path("api/get_rooms_from_building", views.get_rooms_from_building, name="get_rooms_from_building"), + path("api/get_bau", views.get_bau, name="get_bau"), ] diff --git a/backend/app/views.py b/backend/app/views.py index 2224cead7ad1c1c44fa7e5b85d583e156843ce11..fd2519f4a6ddcd83c2c8a70cdc9f6917a8c8bb8d 100644 --- a/backend/app/views.py +++ b/backend/app/views.py @@ -130,6 +130,51 @@ def get_rooms(request): return JsonResponse({"rooms": sorted(rooms)}) +@require_http_methods(["GET"]) +def get_rooms_from_building(request): + building = request.GET.get("building") + + if not building: + return JsonResponse({"error": "Missing 'building' parameter"}, status=400) + + client = InfluxDBHelper( + url=os.getenv("INFLUXDB_URL"), + token=os.getenv("INFLUXDB_TOKEN"), + org=os.getenv("INFLUXDB_ORG"), + bucket=os.getenv("INFLUXDB_BUCKET"), + ) + + tables = client.list_rooms() + rooms = set() + + for table in tables: + for record in table.records: + room = record.get_value() + if isinstance(room, str) and room.startswith(f"{building}/"): + rooms.add(room) + + return JsonResponse({"building": building, "rooms": sorted(rooms)}) + +@require_http_methods(["GET"]) +def get_bau(request): + client = InfluxDBHelper( + url=os.getenv("INFLUXDB_URL"), + token=os.getenv("INFLUXDB_TOKEN"), + org=os.getenv("INFLUXDB_ORG"), + bucket=os.getenv("INFLUXDB_BUCKET"), + ) + + tables = client.list_rooms() + baus = set() + + for table in tables: + for record in table.records: + room = record.get_value() + if isinstance(room, str) and "/" in room: + bau = room.split("/")[0] + baus.add(bau) + + return JsonResponse({"baus": sorted(baus)}) @require_http_methods(["GET"]) def room_data_csv_view(request): diff --git a/frontend/src/assets/bau-1.png b/frontend/src/assets/bau-1.png new file mode 100644 index 0000000000000000000000000000000000000000..64f107be4c6a148800eafc17400aa45bb467a930 Binary files /dev/null and b/frontend/src/assets/bau-1.png differ diff --git a/frontend/src/components/BauCard.vue b/frontend/src/components/BauCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..464807cbdec692cb7c5380b967bd625d35089889 --- /dev/null +++ b/frontend/src/components/BauCard.vue @@ -0,0 +1,59 @@ +<template> + <RouterLink :to="`/buildings/${buildingId}`" class="bau-card"> + <div class="card-background" :style="{ backgroundImage: `url(${image})` }"></div> + <div class="card-content"> + <h3>{{ title }}</h3> + <p>{{ description }}</p> + </div> + </RouterLink> +</template> + +<script setup lang="ts"> +defineProps<{ + title: string + description: string + image: string + buildingId: string +}>() +</script> + +<style scoped> +.bau-card { + display: block; + width: 100%; + max-width: 400px; + border-radius: 8px; + overflow: hidden; + text-decoration: none; + color: inherit; + background-color: white; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + transition: transform 0.2s ease; +} + +.bau-card:hover { + transform: translateY(-4px); +} + +.card-background { + height: 130px; + background-size: cover; + background-position: center; + background-color: #e0e0e0; +} + +.card-content { + padding: 1rem; +} + +.card-content h3 { + margin: 0; + color: darkred; +} + +.card-content p { + margin: 0.3rem 0 0; + color: #555; + font-size: 0.9rem; +} +</style> diff --git a/frontend/src/components/NavBar.vue b/frontend/src/components/NavBar.vue index 47307ca66d399b6ac4415898020fd40ca28e494f..5fa500945787a698f26afd0c1897beb4d0c3331a 100644 --- a/frontend/src/components/NavBar.vue +++ b/frontend/src/components/NavBar.vue @@ -10,6 +10,7 @@ import { RouterLink, RouterView } from 'vue-router' <RouterLink to="/">Home</RouterLink> <RouterLink to="/login">Login</RouterLink> <RouterLink to="/about">About</RouterLink> + <RouterLink to="/buildingsView">Gebäude</RouterLink> <RouterLink to="/register">Register</RouterLink> </nav> </div> diff --git a/frontend/src/components/RoomCard.vue b/frontend/src/components/RoomCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..3156ef22470c25496943329d39aca58af3e859aa --- /dev/null +++ b/frontend/src/components/RoomCard.vue @@ -0,0 +1,37 @@ +<template> + <RouterLink :to="`/rooms/${room}`" class="room-card"> + <div class="card-content"> + <h3>{{ room }}</h3> + </div> + </RouterLink> +</template> + +<script setup lang="ts"> +defineProps<{ + room: string +}>() +</script> + +<style scoped> +.room-card { + display: block; + background: #f9f9f9; + border-radius: 8px; + text-decoration: none; + color: #333; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); + transition: transform 0.2s ease; + padding: 1rem; + text-align: center; +} + +.room-card:hover { + transform: translateY(-4px); + background-color: #ffeaea; +} + +.card-content h3 { + margin: 0; + color: darkred; +} +</style> diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index e983ca53462ab687a886f1154be4b4fe085583a2..2ae103d0f9498c27700606d30421a9e621cacac9 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import LoginView from '../views/LoginView.vue' import RegisterView from '../views/RegisterView.vue' +import RoomNavView from '../views/RoomNavView.vue' +import BuildingsView from '../views/BuildingsView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -21,6 +23,17 @@ const router = createRouter({ name: 'register', component: RegisterView, }, + { + path: '/roomNav', + name: 'roomNav', + component: RoomNavView, + }, + { + path: '/buildingsView', + name: 'buildingsView', + component: BuildingsView, + }, + { path: '/about', name: 'about', diff --git a/frontend/src/views/BuildingsView.vue b/frontend/src/views/BuildingsView.vue new file mode 100644 index 0000000000000000000000000000000000000000..c803e5573c68784ac99139fb0e9c3440f90022d7 --- /dev/null +++ b/frontend/src/views/BuildingsView.vue @@ -0,0 +1,71 @@ +<script setup lang="ts"> +import BauCard from '../components/BauCard.vue' + +const buildings = [ + { + id: '1', + title: 'Bau 1', + description: 'Zentralgebäude mit Laboren', + image: '../assets/bau-1.png', + }, + { + id: '2', + title: 'Bau 2', + description: 'Informatik und IT-Räume', + image: '/images/building2.png', + }, + { + id: '2', + title: 'Bau 2', + description: 'Informatik und IT-Räume', + image: '/images/building2.png', + }, + { + id: '2', + title: 'Bau 2', + description: 'Informatik und IT-Räume', + image: '/images/building2.png', + }, + { + id: '2', + title: 'Bau 2', + description: 'Informatik und IT-Räume', + image: '/images/building2.png', + }, + { + id: '2', + title: 'Bau 2', + description: 'Informatik und IT-Räume', + image: '/images/building2.png', + }, + { + id: '2', + title: 'Bau 2', + description: 'Informatik und IT-Räume', + image: '/images/building2.png', + }, + +] +</script> + +<template> + <div class="building-grid"> + <BauCard + v-for="bau in buildings" + :key="bau.id" + :building-id="bau.id" + :title="bau.title" + :description="bau.description" + :image="bau.image" + /> + </div> +</template> + +<style scoped> +.building-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; + padding: 2rem; +} +</style> diff --git a/frontend/src/views/RoomNavView.vue b/frontend/src/views/RoomNavView.vue new file mode 100644 index 0000000000000000000000000000000000000000..ce7f676c58558d8a37b27a71f0984c0793efea78 --- /dev/null +++ b/frontend/src/views/RoomNavView.vue @@ -0,0 +1,59 @@ +<script setup lang="ts"> +import { ref, onMounted } from 'vue' +import { getCSRFToken } from '../stores/auth' +import RoomCard from '../components/RoomCard.vue' + +const rooms = ref<string[]>([]) +const error = ref<string | null>(null) + +const fetchRooms = async () => { + try { + const response = await fetch('http://localhost:8000/api/get_rooms', { + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + credentials: 'include', + }) + + const data = await response.json() + + if (response.ok && data.rooms) { + rooms.value = data.rooms + } else { + error.value = 'Fehler beim Laden der Räume.' + } + } catch (err) { + error.value = 'Serverfehler: ' + err + } +} + +onMounted(fetchRooms) +</script> + +<template> + <div class="room-container"> + <h2>Räume</h2> + <p v-if="error" class="error">{{ error }}</p> + <div class="card-grid"> + <RoomCard v-for="room in rooms" :key="room" :room="room" /> + </div> + </div> +</template> + +<style scoped> +.room-container { + padding: 2rem; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; + margin-top: 1rem; +} + +.error { + color: red; +} +</style>