New Responsive design

This commit is contained in:
2026-05-23 18:08:27 +02:00
parent 3fdbaf0285
commit 0d436d8190
19 changed files with 2152 additions and 607 deletions
+335 -112
View File
@@ -1,5 +1,5 @@
<script setup>
import {reactive, onMounted} from 'vue';
import {reactive, onMounted, ref} from 'vue';
import Icon from "../../../app/Views/Components/Icon.vue";
import GlobalWidgets from "../../../app/Views/Partials/GlobalWidgets/GlobalWidgets.vue";
import {toast} from "vue3-toastify";
@@ -13,6 +13,7 @@ const globalProps = reactive({
common: [],
costunits: [],
events: [],
eventControl: [],
},
tenant: '',
user: null,
@@ -22,6 +23,16 @@ const globalProps = reactive({
message: ''
});
const sidebarOpen = ref(false);
function toggleSidebar() {
sidebarOpen.value = !sidebarOpen.value;
}
function closeSidebar() {
sidebarOpen.value = false;
}
onMounted(async () => {
const response = await fetch('/api/v1/core/retrieve-global-data');
const data = await response.json();
@@ -52,130 +63,106 @@ const props = defineProps({
flash: { type: Object, default: () => ({}) }
});
console.log(globalProps)
</script>
<template>
<div class="app-layout">
<!-- Sidebar -->
<!-- Main content -->
<!-- Mobile Overlay -->
<div class="sidebar-overlay" :class="{ active: sidebarOpen }" @click="closeSidebar"></div>
<div class="main">
<!-- Header -->
<div class="header">
<button class="hamburger-btn" @click="toggleSidebar" aria-label="Menü öffnen">
<span></span>
<span></span>
<span></span>
</button>
<div class="left-side">
<h1>{{ props.title }}</h1>
<label id="show_username" v-if="globalProps.user !== null">Willkommen, {{ globalProps.user.nicename }}</label>
</div>
<div class="header-actions">
<div class="user-info" v-if="globalProps.user !== null">
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
<Icon name="envelope" />
</a>
<a href="/profile" class="header-link-anonymous" title="Mein Profil">
<Icon name="user" />
</a>
<a href="/logout" class="header-link-anonymous-logout" title="Abmelden">
<Icon name="lock" />
</a>
</div>
<div class="anonymous-actions" v-else>
<a href="/register" class="header-link-anonymous">Registrieren</a>
<a href="/login" class="header-link-anonymous">Anmelden</a>
</div>
</div>
</div>
<!-- Flexbox: Sidebar + Content -->
<div class="flexbox">
<div class="sidebar">
<div class="sidebar" :class="{ 'sidebar-open': sidebarOpen }">
<div class="logo">
<img src="../../../public/images/logo.png" alt="Logo" />
</div>
<nav class="nav">
<ul class="nav-links" v-if="globalProps.navbar.personal.length > 0">
<li v-for="navlink in globalProps.navbar.personal">
<a
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url"
>{{navlink.display}}</a>
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
</li>
</ul>
<ul class="nav-links" v-if="globalProps.navbar.common.length > 0">
<li v-for="navlink in globalProps.navbar.common">
<a
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url"
>{{navlink.display}}</a>
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
</li>
</ul>
<ul class="nav-links" v-if="globalProps.navbar.costunits.length > 0">
<li v-for="navlink in globalProps.navbar.costunits">
<a
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url"
>{{navlink.display}}</a>
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
</li>
</ul>
<ul class="nav-links" v-if="globalProps.navbar.events.length > 0">
<li v-for="navlink in globalProps.navbar.events">
<a
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url"
>{{navlink.display}}</a>
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
</li>
</ul>
<ul class="nav-links" v-if="globalProps.navbar.eventControl.length > 0">
<ul class="nav-links" v-if="globalProps.navbar.eventControl && globalProps.navbar.eventControl.length > 0">
<li v-for="navlink in globalProps.navbar.eventControl">
<a
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url"
>{{navlink.display}}</a>
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
</li>
</ul>
</nav>
</div>
<div class="main">
<div class="header">
<div class="left-side">
<h1>{{ props.title }}</h1>
<label id="show_username" v-if="globalProps.user !== null">Willkommen, {{ globalProps.user.nicename }}</label>
</div>
<div class="user-info" v-if="globalProps.user !== null">
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
<Icon name="envelope" />
</a>
<a href="/profile" class="header-link-anonymous" title="Mein Profil">
<Icon name="user" />
</a>
<a href="/logout" class="header-link-anonymous-logout" title="Abmelden">
<Icon name="lock" />
</a>
</div>
<div class="anonymous-actions" v-else>
<a href="/register" class="header-link-anonymous">Registrieren</a>
<a href="/login" class="header-link-anonymous"> Anmelden </a>
</div>
</div>
<div class="content-area">
<global-widgets :user="globalProps.user" :tenant="globalProps.tenant" v-if="globalProps.user !== null" />
<div class="content">
<slot />
</div>
</div>
</div>
<!-- Footer -->
<!-- Footer -->
<footer class="footer">
<table>
<tr>
<td>Version {{ globalProps.version }}</td>
<td>
mareike - Mdodernes Anmeldesystem und richtig einfache Kostenerfassung
</td>
<td>
Impressum
</td>
<td>
Datenschutzerklärung
</td>
<td>
&copy 2022 - 2026
</td>
</tr>
</table>
<div class="footer-inner">
<span>Version {{ globalProps.version }}</span>
<span class="footer-hide-mobile">mareike Modernes Anmeldesystem und richtig einfache Kostenerfassung</span>
<span>Impressum</span>
<span>Datenschutzerklärung</span>
<span>&copy; 2022 2026</span>
</div>
</footer>
</div>
<!-- Toaster -->
<transition name="fade">
<div v-if="flash.message" class="toaster">
{{ flash.message }}
@@ -185,6 +172,47 @@ console.log(globalProps)
</template>
<style scoped>
/* ─── Header ─── */
.header {
display: flex;
align-items: center;
height: 80px;
background: #ffffff;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
padding: 0;
position: relative;
z-index: 50;
flex-shrink: 0;
}
.left-side {
flex: 1;
padding: 0 20px;
overflow: hidden;
}
.left-side h1 {
margin: 0;
font-size: 1.4rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#show_username {
display: block;
font-weight: bold;
font-size: 0.85rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header-actions {
display: flex;
align-items: center;
flex-shrink: 0;
}
.header-link-anonymous,
.header-link-anonymous-logout {
@@ -192,13 +220,8 @@ console.log(globalProps)
font-weight: bold;
text-decoration: none;
background-color: #ffffff;
padding: 10px 30px;
margin: 0 -20px 0 30px;
overflow: hidden !important;
}
.header-link-anonymous-logout {
padding-right: 35px !important;
padding: 10px 20px;
display: inline-block;
}
.header-link-anonymous:hover {
@@ -211,45 +234,110 @@ console.log(globalProps)
color: #ffffff;
}
.nav-links {
border-bottom: 1px solid #ddd;
width: 285px;
/* ─── Hamburger ─── */
.hamburger-btn {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
width: 50px;
height: 80px;
border: none;
background: transparent;
cursor: pointer;
flex-shrink: 0;
padding: 0 12px;
}
.nav-links li a {
color: #b6b6b6;
background-color: #fff;
padding: 20px 25px;
.hamburger-btn span {
display: block;
text-decoration: none;
width: calc(100% - 50px);
font-weight: bold;
margin-bottom: 0px;
width: 24px;
height: 3px;
background-color: #333;
border-radius: 2px;
transition: all 0.2s;
}
/* ─── Layout ─── */
.app-layout {
display: flex;
height: 100vh;
background: #f0f2f5;
font-family: sans-serif;
overflow: hidden;
}
.main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
margin: 20px;
box-shadow: 20px 20px 15px rgba(0, 0, 0, 0.1);
border-radius: 0 10px 0 0;
}
.flexbox {
display: flex;
flex: 1;
background-color: #FAFAFB;
overflow: hidden;
gap: 1px;
}
/* ─── Sidebar ─── */
.sidebar {
flex-basis: 275px;
flex-shrink: 0;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
background-color: #ffffff;
overflow-y: auto;
transition: transform 0.3s ease;
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.4);
z-index: 99;
}
.logo {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 0;
}
.logo img {
width: 135px;
height: 70px;
object-fit: contain;
}
/* ─── Nav ─── */
.nav {
flex: 1;
margin-left: -10px;
width: 275px;
}
.nav ul {
list-style: none;
padding: 0;
margin: 0;
border-bottom: 1px solid #ddd;
}
.nav li {
margin-bottom: 10px;
}
.nav a {
text-decoration: none;
color: #333;
padding: 8px 12px;
.nav-links li a {
color: #b6b6b6;
background-color: #fff;
padding: 16px 25px;
display: block;
transition: background 0.2s;
text-decoration: none;
font-weight: bold;
}
.nav a:hover {
@@ -259,6 +347,141 @@ console.log(globalProps)
.navlink_active {
background-color: #fae39c !important;
color: #1d4899 !important; /* Dunklere Schrift beim Hover */
color: #1d4899 !important;
}
/* ─── Content ─── */
.content-area {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
}
.content {
padding: 30px 20px;
flex: 1;
}
/* ─── Footer ─── */
.footer {
background: #666666;
border-top: 1px solid #ddd;
color: #ffffff;
padding: 10px 15px;
font-size: 11pt;
font-weight: bold;
flex-shrink: 0;
}
.footer-inner {
display: flex;
flex-wrap: wrap;
gap: 10px 20px;
justify-content: space-between;
align-items: center;
}
/* ═══════════════════════════════════════════
TABLET (640px 1023px)
═══════════════════════════════════════════ */
@media (max-width: 1023px) {
.app-layout {
margin: 0;
height: 100vh;
}
.main {
margin: 0;
border-radius: 0;
box-shadow: none;
}
.hamburger-btn {
display: flex;
}
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 100;
transform: translateX(-100%);
width: 260px;
flex-basis: 260px;
}
.sidebar.sidebar-open {
transform: translateX(0);
}
.sidebar-overlay.active {
display: block;
}
.header-link-anonymous,
.header-link-anonymous-logout {
padding: 10px 12px;
font-size: 0.9rem;
}
.left-side h1 {
font-size: 1.1rem;
}
}
/* ═══════════════════════════════════════════
SMARTPHONE (< 640px)
═══════════════════════════════════════════ */
@media (max-width: 639px) {
.header {
height: 60px;
}
.hamburger-btn {
height: 60px;
}
.left-side h1 {
font-size: 1rem;
}
#show_username {
display: none;
}
.anonymous-actions {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 0.8rem;
}
.header-link-anonymous,
.header-link-anonymous-logout {
padding: 6px 8px;
font-size: 0.75rem;
}
.footer-hide-mobile {
display: none;
}
.footer-inner {
justify-content: center;
font-size: 9pt;
gap: 6px 12px;
}
.content {
padding: 15px 10px;
}
.sidebar {
width: 240px;
flex-basis: 240px;
}
}
</style>