feat: Full responsive design - mobile sidebar, card lists, modals

This commit is contained in:
FluxKit
2026-02-22 15:08:09 +00:00
parent ba1646d863
commit e58dfc9a39
5 changed files with 384 additions and 126 deletions

View File

@@ -1,12 +1,13 @@
<script setup>
import { ref } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const route = useRoute()
const auth = useAuthStore()
const sidebarOpen = ref(true)
const sidebarOpen = ref(false)
const isMobile = ref(false)
const navItems = [
{ name: 'Dashboard', path: '/', icon: 'chart-pie' },
@@ -16,6 +17,34 @@ const navItems = [
{ name: 'Aktivitäten', path: '/activities', icon: 'clipboard-list' },
]
function checkMobile() {
isMobile.value = window.innerWidth < 768
if (!isMobile.value) {
sidebarOpen.value = true
} else {
sidebarOpen.value = false
}
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
function toggleSidebar() {
sidebarOpen.value = !sidebarOpen.value
}
function closeSidebarOnMobile() {
if (isMobile.value) {
sidebarOpen.value = false
}
}
function logout() {
auth.logout()
router.push('/login')
@@ -28,60 +57,105 @@ function isActive(path) {
</script>
<template>
<div class="flex h-full">
<div class="flex h-full relative">
<!-- Mobile Overlay -->
<div
v-if="isMobile && sidebarOpen"
class="fixed inset-0 bg-black/50 z-40"
@click="closeSidebarOnMobile"
></div>
<!-- Mobile Header -->
<header
v-if="isMobile"
class="fixed top-0 left-0 right-0 h-14 bg-pulse-card border-b border-pulse-border flex items-center px-4 z-30"
>
<button
@click="toggleSidebar"
class="p-2 text-white hover:bg-pulse-border rounded-lg transition-colors"
>
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<div class="flex items-center gap-2 ml-3">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<span class="text-lg font-bold text-white">Pulse</span>
</div>
</header>
<!-- Sidebar -->
<aside
:class="[
'flex flex-col bg-pulse-card border-r border-pulse-border transition-all duration-300',
sidebarOpen ? 'w-64' : 'w-20'
isMobile ? 'fixed top-0 left-0 h-full z-50' : '',
isMobile && !sidebarOpen ? '-translate-x-full' : 'translate-x-0',
'w-64'
]"
>
<!-- Logo -->
<div class="flex items-center gap-3 px-6 py-5 border-b border-pulse-border">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<svg class="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<div class="flex items-center justify-between gap-3 px-6 py-5 border-b border-pulse-border">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<svg class="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<span class="text-xl font-bold text-white">Pulse</span>
</div>
<span v-if="sidebarOpen" class="text-xl font-bold text-white">Pulse</span>
<!-- Close button on mobile -->
<button
v-if="isMobile"
@click="closeSidebarOnMobile"
class="p-2 text-pulse-muted hover:text-white transition-colors"
>
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Navigation -->
<nav class="flex-1 py-4 px-3 space-y-1">
<nav class="flex-1 py-4 px-3 space-y-1 overflow-y-auto">
<RouterLink
v-for="item in navItems"
:key="item.path"
:to="item.path"
:class="['sidebar-link', isActive(item.path) && 'active']"
@click="closeSidebarOnMobile"
>
<!-- Icons -->
<svg v-if="item.icon === 'chart-pie'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg v-if="item.icon === 'chart-pie'" class="w-5 h-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" />
</svg>
<svg v-if="item.icon === 'users'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg v-if="item.icon === 'users'" class="w-5 h-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg v-if="item.icon === 'building-office'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg v-if="item.icon === 'building-office'" class="w-5 h-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<svg v-if="item.icon === 'funnel'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg v-if="item.icon === 'funnel'" class="w-5 h-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
<svg v-if="item.icon === 'clipboard-list'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg v-if="item.icon === 'clipboard-list'" class="w-5 h-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
<span v-if="sidebarOpen">{{ item.name }}</span>
<span>{{ item.name }}</span>
</RouterLink>
</nav>
<!-- User -->
<div class="border-t border-pulse-border p-4">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium">
<div class="w-9 h-9 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium flex-shrink-0">
{{ auth.user?.firstName?.[0] || 'U' }}
</div>
<div v-if="sidebarOpen" class="flex-1 min-w-0">
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">
{{ auth.user?.firstName }} {{ auth.user?.lastName }}
</p>
@@ -89,7 +163,7 @@ function isActive(path) {
</div>
<button
@click="logout"
class="p-2 text-pulse-muted hover:text-white transition-colors"
class="p-2 text-pulse-muted hover:text-white transition-colors flex-shrink-0"
title="Abmelden"
>
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -101,7 +175,7 @@ function isActive(path) {
</aside>
<!-- Main Content -->
<main class="flex-1 overflow-auto">
<main :class="['flex-1 overflow-auto', isMobile ? 'pt-14' : '']">
<RouterView />
</main>
</div>