Files
gartenmanager/frontend/src/components/AppLayout.vue
Faultier314 834a3bf4d5 feat: Phase 1 complete – full working application
Backend (FastAPI):
- REST API: auth, plants, beds, plantings
- CRUD layer with CRUDBase
- Pydantic v2 schemas for all entities
- Alembic migration: complete schema + all enums
- Seed data: 28 global plants + 15 compatibilities

Frontend (Vue 3 + PrimeVue):
- Axios client with JWT interceptor + auto-refresh
- Pinia stores: auth, beds, plants
- Views: Login, Beds, BedDetail, PlantLibrary
- Components: AppLayout, BedForm, PlantingForm, PlantForm

Docker:
- docker-compose.yml (production)
- docker-compose.dev.yml (development with hot-reload)
- Nginx config with SPA fallback + API proxy
- Multi-stage frontend Dockerfile
- .env.example, .gitignore

Version: 1.0.0-alpha
2026-04-06 07:45:00 +02:00

89 lines
2.8 KiB
Vue

<template>
<div class="layout">
<nav class="navbar">
<div class="navbar-brand">
<i class="pi pi-leaf" style="color: var(--green-500); font-size: 1.4rem" />
<span class="brand-name">Gartenmanager</span>
</div>
<div class="navbar-menu">
<router-link to="/beete" class="nav-link">
<i class="pi pi-th-large" /> Beete
</router-link>
<router-link to="/pflanzen" class="nav-link">
<i class="pi pi-book" /> Pflanzen
</router-link>
</div>
<div class="navbar-end">
<Dropdown
v-if="auth.tenants.length > 1"
:model-value="auth.activeTenantId"
:options="auth.tenants"
option-label="name"
option-value="id"
placeholder="Tenant wählen"
class="tenant-selector"
@change="auth.setActiveTenant($event.value)"
/>
<span v-else-if="auth.activeTenant" class="tenant-name">
<i class="pi pi-building" /> {{ auth.activeTenant.name }}
</span>
<Button
icon="pi pi-sign-out"
text
severity="secondary"
title="Abmelden"
@click="handleLogout"
/>
</div>
</nav>
<main class="main-content">
<router-view />
</main>
</div>
</template>
<script setup>
import { useAuthStore } from '@/stores/auth'
import { useRouter } from 'vue-router'
import Button from 'primevue/button'
import Dropdown from 'primevue/dropdown'
const auth = useAuthStore()
const router = useRouter()
function handleLogout() {
auth.logout()
router.push('/login')
}
</script>
<style scoped>
.layout { display: flex; flex-direction: column; min-height: 100vh; }
.navbar {
display: flex; align-items: center; gap: 1.5rem;
padding: 0.75rem 1.5rem;
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
box-shadow: 0 1px 4px rgba(0,0,0,.08);
}
.navbar-brand { display: flex; align-items: center; gap: 0.5rem; text-decoration: none; }
.brand-name { font-weight: 700; font-size: 1.1rem; color: var(--green-700); }
.navbar-menu { display: flex; gap: 0.5rem; flex: 1; }
.nav-link {
display: flex; align-items: center; gap: 0.4rem;
padding: 0.4rem 0.9rem; border-radius: 6px;
text-decoration: none; color: var(--text-color-secondary);
font-size: 0.95rem; transition: background 0.15s;
}
.nav-link:hover, .nav-link.router-link-active {
background: var(--green-50); color: var(--green-700);
}
.navbar-end { display: flex; align-items: center; gap: 0.75rem; margin-left: auto; }
.tenant-name { font-size: 0.85rem; color: var(--text-color-secondary); display: flex; align-items: center; gap: 0.3rem; }
.tenant-selector { font-size: 0.85rem; }
.main-content { flex: 1; padding: 1.5rem; max-width: 1400px; margin: 0 auto; width: 100%; }
</style>