feat: Full responsive design - mobile sidebar, card lists, modals
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user