Files
gartenmanager/frontend/src/views/BedsView.vue

137 lines
4.7 KiB
Vue
Raw Normal View History

<template>
<div>
<div class="page-header">
<div>
<h2>Beete</h2>
<p class="subtitle">Übersicht aller Beete in diesem Tenant</p>
</div>
<Button label="Neues Beet" icon="pi pi-plus" @click="openCreateDialog" />
</div>
<DataTable
:value="bedsStore.beds"
:loading="bedsStore.loading"
striped-rows
hover
responsive-layout="scroll"
class="mt-3"
>
<template #empty>Noch keine Beete vorhanden.</template>
<Column field="name" header="Name" sortable>
<template #body="{ data }">
<router-link :to="`/beete/${data.id}`" class="bed-link">{{ data.name }}</router-link>
</template>
</Column>
<Column header="Größe (m²)" sortable sort-field="area_m2">
<template #body="{ data }">
{{ data.area_m2 }}
<span class="dim">({{ data.width_m }} × {{ data.length_m }} m)</span>
</template>
</Column>
<Column field="location" header="Lage" sortable>
<template #body="{ data }">
<Tag :value="locationLabel(data.location)" :severity="locationSeverity(data.location)" />
</template>
</Column>
<Column field="soil_type" header="Bodentyp" sortable>
<template #body="{ data }">{{ soilLabel(data.soil_type) }}</template>
</Column>
<Column header="Aktionen" style="width: 8rem">
<template #body="{ data }">
<div class="actions">
<Button icon="pi pi-pencil" text severity="secondary" @click="openEditDialog(data)" />
<Button icon="pi pi-trash" text severity="danger" @click="confirmDelete(data)" />
</div>
</template>
</Column>
</DataTable>
<!-- Create/Edit Dialog -->
<Dialog
v-model:visible="dialogVisible"
:header="editingBed ? 'Beet bearbeiten' : 'Neues Beet'"
modal
style="width: 480px"
>
<BedForm :initial="editingBed" @save="handleSave" @cancel="dialogVisible = false" />
</Dialog>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useBedsStore } from '@/stores/beds'
import { useConfirm } from 'primevue/useconfirm'
import { useToast } from 'primevue/usetoast'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import Button from 'primevue/button'
import Tag from 'primevue/tag'
import Dialog from 'primevue/dialog'
import BedForm from '@/components/BedForm.vue'
const bedsStore = useBedsStore()
const confirm = useConfirm()
const toast = useToast()
const dialogVisible = ref(false)
const editingBed = ref(null)
onMounted(() => bedsStore.fetchBeds())
function openCreateDialog() {
editingBed.value = null
dialogVisible.value = true
}
function openEditDialog(bed) {
editingBed.value = bed
dialogVisible.value = true
}
async function handleSave(payload) {
try {
if (editingBed.value) {
await bedsStore.updateBed(editingBed.value.id, payload)
toast.add({ severity: 'success', summary: 'Gespeichert', detail: 'Beet aktualisiert.', life: 3000 })
} else {
await bedsStore.createBed(payload)
toast.add({ severity: 'success', summary: 'Erstellt', detail: 'Neues Beet angelegt.', life: 3000 })
}
dialogVisible.value = false
} catch (err) {
toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.detail || 'Speichern fehlgeschlagen.', life: 5000 })
}
}
function confirmDelete(bed) {
confirm.require({
message: `Beet „${bed.name}" wirklich löschen?`,
header: 'Bestätigung',
icon: 'pi pi-exclamation-triangle',
acceptLabel: 'Löschen',
rejectLabel: 'Abbrechen',
acceptClass: 'p-button-danger',
accept: async () => {
await bedsStore.deleteBed(bed.id)
toast.add({ severity: 'info', summary: 'Gelöscht', detail: 'Beet wurde entfernt.', life: 3000 })
},
})
}
const locationLabel = (v) => ({ sonnig: 'Sonnig', halbschatten: 'Halbschatten', schatten: 'Schatten' }[v] || v)
const locationSeverity = (v) => ({ sonnig: 'warning', halbschatten: 'info', schatten: 'secondary' }[v] || 'secondary')
const soilLabel = (v) => ({ normal: 'Normal', sandig: 'Sandig', lehmig: 'Lehmig', humusreich: 'Humusreich' }[v] || v)
</script>
<style scoped>
.page-header { display: flex; align-items: flex-start; justify-content: space-between; }
.page-header h2 { font-size: 1.4rem; font-weight: 700; margin-bottom: 0.15rem; }
.subtitle { color: var(--text-color-secondary); font-size: 0.875rem; }
.bed-link { color: var(--green-700); text-decoration: none; font-weight: 600; }
.bed-link:hover { text-decoration: underline; }
.dim { color: var(--text-color-secondary); font-size: 0.8rem; margin-left: 0.3rem; }
.actions { display: flex; gap: 0.25rem; }
.mt-3 { margin-top: 1rem; }
</style>