feat: Pulse CRM Frontend v1.0
Vue 3 + Vite + Tailwind CSS Views: - Dashboard mit Stats & Aktivitäten - Kontakte mit Suche & CRUD - Firmen mit Cards & CRUD - Pipeline Kanban Board (Drag & Drop) - Aktivitäten mit Filter & Timeline - Settings mit DSGVO-Info Features: - Dark Theme (Pulse Design) - Responsive Layout - Pinia State Management - Vue Router mit Guards - Axios API Client Task: #13 Frontend UI
This commit is contained in:
108
src/layouts/AppLayout.vue
Normal file
108
src/layouts/AppLayout.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<script setup>
|
||||
import { ref } 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 navItems = [
|
||||
{ name: 'Dashboard', path: '/', icon: 'chart-pie' },
|
||||
{ name: 'Kontakte', path: '/contacts', icon: 'users' },
|
||||
{ name: 'Firmen', path: '/companies', icon: 'building-office' },
|
||||
{ name: 'Pipeline', path: '/pipeline', icon: 'funnel' },
|
||||
{ name: 'Aktivitäten', path: '/activities', icon: 'clipboard-list' },
|
||||
]
|
||||
|
||||
function logout() {
|
||||
auth.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
function isActive(path) {
|
||||
if (path === '/') return route.path === '/'
|
||||
return route.path.startsWith(path)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full">
|
||||
<!-- Sidebar -->
|
||||
<aside
|
||||
:class="[
|
||||
'flex flex-col bg-pulse-card border-r border-pulse-border transition-all duration-300',
|
||||
sidebarOpen ? 'w-64' : 'w-20'
|
||||
]"
|
||||
>
|
||||
<!-- 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>
|
||||
<span v-if="sidebarOpen" class="text-xl font-bold text-white">Pulse</span>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 py-4 px-3 space-y-1">
|
||||
<RouterLink
|
||||
v-for="item in navItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
:class="['sidebar-link', isActive(item.path) && 'active']"
|
||||
>
|
||||
<!-- Icons -->
|
||||
<svg v-if="item.icon === 'chart-pie'" 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="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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
</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">
|
||||
{{ auth.user?.firstName?.[0] || 'U' }}
|
||||
</div>
|
||||
<div v-if="sidebarOpen" class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-white truncate">
|
||||
{{ auth.user?.firstName }} {{ auth.user?.lastName }}
|
||||
</p>
|
||||
<p class="text-xs text-pulse-muted truncate">{{ auth.user?.email }}</p>
|
||||
</div>
|
||||
<button
|
||||
@click="logout"
|
||||
class="p-2 text-pulse-muted hover:text-white transition-colors"
|
||||
title="Abmelden"
|
||||
>
|
||||
<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="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<RouterView />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user