before gemiin
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-4xl mx-auto">
|
||||
<div class="p-4 max-w-6xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-green">🌱 Plantes</h1>
|
||||
<button @click="showForm = true" class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">+ Ajouter</button>
|
||||
@@ -18,61 +18,63 @@
|
||||
<!-- Liste -->
|
||||
<div v-if="plantsStore.loading" class="text-text-muted text-sm">Chargement...</div>
|
||||
<div v-else-if="!filteredPlants.length" class="text-text-muted text-sm py-4">Aucune plante.</div>
|
||||
<div v-for="p in filteredPlants" :key="p.id"
|
||||
class="bg-bg-soft rounded-lg mb-2 border border-bg-hard overflow-hidden">
|
||||
<!-- En-tête cliquable -->
|
||||
<div class="p-4 flex items-start justify-between gap-4 cursor-pointer"
|
||||
@click="toggleDetail(p.id!)">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<span class="text-text font-semibold">{{ p.nom_commun }}</span>
|
||||
<span v-if="p.variete" class="text-text-muted text-xs">— {{ p.variete }}</span>
|
||||
<span v-if="p.categorie" :class="['text-xs px-2 py-0.5 rounded-full font-medium', catClass(p.categorie)]">{{ catLabel(p.categorie) }}</span>
|
||||
</div>
|
||||
<div class="text-text-muted text-xs flex gap-3 flex-wrap">
|
||||
<span v-if="p.famille">🌿 {{ p.famille }}</span>
|
||||
<span v-if="p.espacement_cm">↔ {{ p.espacement_cm }}cm</span>
|
||||
<span v-if="p.besoin_eau">💧 {{ p.besoin_eau }}</span>
|
||||
<span v-if="p.plantation_mois">🌱 Plantation: mois {{ p.plantation_mois }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<span class="text-text-muted text-xs">{{ openId === p.id ? '▲' : '▼' }}</span>
|
||||
<button @click.stop="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
|
||||
<button @click.stop="removePlant(p.id!)" class="text-red text-xs hover:underline">Suppr.</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panneau détail -->
|
||||
<div v-if="openId === p.id" class="border-t border-bg-hard px-4 pb-4 pt-3">
|
||||
<!-- Notes -->
|
||||
<p v-if="p.notes" class="text-text-muted text-sm mb-3 italic">{{ p.notes }}</p>
|
||||
|
||||
<!-- Galerie photos -->
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-text-muted text-xs font-medium uppercase tracking-wide">Photos</span>
|
||||
<button @click="openUpload(p)" class="text-green text-xs hover:underline">+ Ajouter une photo</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingPhotos" class="text-text-muted text-xs">Chargement...</div>
|
||||
<div v-else-if="!plantPhotos.length" class="text-text-muted text-xs mb-3">Aucune photo pour cette plante.</div>
|
||||
<div v-else class="grid grid-cols-4 gap-2 mb-3">
|
||||
<div v-for="m in plantPhotos" :key="m.id"
|
||||
class="aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer"
|
||||
@click="lightbox = m">
|
||||
<img :src="m.thumbnail_url || m.url" class="w-full h-full object-cover" />
|
||||
<div v-if="m.identified_common"
|
||||
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">
|
||||
{{ m.identified_common }}
|
||||
<div v-else class="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-3">
|
||||
<div v-for="p in filteredPlants" :key="p.id"
|
||||
class="bg-bg-soft rounded-lg border border-bg-hard overflow-hidden">
|
||||
<!-- En-tête cliquable -->
|
||||
<div class="p-4 flex items-start justify-between gap-4 cursor-pointer"
|
||||
@click="toggleDetail(p.id!)">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<span class="text-text font-semibold">{{ p.nom_commun }}</span>
|
||||
<span v-if="p.variete" class="text-text-muted text-xs">— {{ p.variete }}</span>
|
||||
<span v-if="p.categorie" :class="['text-xs px-2 py-0.5 rounded-full font-medium', catClass(p.categorie)]">{{ catLabel(p.categorie) }}</span>
|
||||
</div>
|
||||
<button @click.stop="deletePhoto(m)" class="hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1">✕</button>
|
||||
<div class="text-text-muted text-xs flex gap-3 flex-wrap">
|
||||
<span v-if="p.famille">🌿 {{ p.famille }}</span>
|
||||
<span v-if="p.espacement_cm">↔ {{ p.espacement_cm }}cm</span>
|
||||
<span v-if="p.besoin_eau">💧 {{ p.besoin_eau }}</span>
|
||||
<span v-if="p.plantation_mois">🌱 Plantation: mois {{ p.plantation_mois }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<span class="text-text-muted text-xs">{{ openId === p.id ? '▲' : '▼' }}</span>
|
||||
<button @click.stop="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
|
||||
<button @click.stop="removePlant(p.id!)" class="text-red text-xs hover:underline">Suppr.</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lier une photo existante de la bibliothèque -->
|
||||
<button @click="openLinkPhoto(p)" class="text-blue text-xs hover:underline">
|
||||
🔗 Lier une photo existante de la bibliothèque
|
||||
</button>
|
||||
<!-- Panneau détail -->
|
||||
<div v-if="openId === p.id" class="border-t border-bg-hard px-4 pb-4 pt-3">
|
||||
<!-- Notes -->
|
||||
<p v-if="p.notes" class="text-text-muted text-sm mb-3 italic">{{ p.notes }}</p>
|
||||
|
||||
<!-- Galerie photos -->
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-text-muted text-xs font-medium uppercase tracking-wide">Photos</span>
|
||||
<button @click="openUpload(p)" class="text-green text-xs hover:underline">+ Ajouter une photo</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingPhotos" class="text-text-muted text-xs">Chargement...</div>
|
||||
<div v-else-if="!plantPhotos.length" class="text-text-muted text-xs mb-3">Aucune photo pour cette plante.</div>
|
||||
<div v-else class="grid grid-cols-4 gap-2 mb-3">
|
||||
<div v-for="m in plantPhotos" :key="m.id"
|
||||
class="aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer"
|
||||
@click="lightbox = m">
|
||||
<img :src="m.thumbnail_url || m.url" class="w-full h-full object-cover" />
|
||||
<div v-if="m.identified_common"
|
||||
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">
|
||||
{{ m.identified_common }}
|
||||
</div>
|
||||
<button @click.stop="deletePhoto(m)" class="hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lier une photo existante de la bibliothèque -->
|
||||
<button @click="openLinkPhoto(p)" class="text-blue text-xs hover:underline">
|
||||
🔗 Lier une photo existante de la bibliothèque
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,7 +195,7 @@
|
||||
<!-- Modal upload photo pour une plante -->
|
||||
<div v-if="uploadTarget" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="uploadTarget = null">
|
||||
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft">
|
||||
<h3 class="text-text font-bold mb-4">Photo pour "{{ uploadTarget.nom_commun }}"</h3>
|
||||
<h3 class="text-text font-bold mb-4">Photo pour "{{ formatPlantLabel(uploadTarget) }}"</h3>
|
||||
<label class="block border-2 border-dashed border-bg-soft rounded-lg p-6 text-center cursor-pointer hover:border-green transition-colors">
|
||||
<input type="file" accept="image/*" class="hidden" @change="uploadPhoto" />
|
||||
<div class="text-text-muted text-sm">📷 Choisir une image</div>
|
||||
@@ -205,7 +207,7 @@
|
||||
<!-- Modal lier photo existante -->
|
||||
<div v-if="linkTarget" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="linkTarget = null">
|
||||
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-2xl border border-bg-soft max-h-[80vh] flex flex-col">
|
||||
<h3 class="text-text font-bold mb-3">Lier une photo à "{{ linkTarget.nom_commun }}"</h3>
|
||||
<h3 class="text-text font-bold mb-3">Lier une photo à "{{ formatPlantLabel(linkTarget) }}"</h3>
|
||||
<p class="text-text-muted text-xs mb-3">Sélectionne une photo de la bibliothèque (non liée à une plante)</p>
|
||||
<div v-if="!unlinkPhotos.length" class="text-text-muted text-sm py-4 text-center">Aucune photo disponible.</div>
|
||||
<div v-else class="grid grid-cols-4 gap-2 overflow-y-auto flex-1">
|
||||
@@ -247,6 +249,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { usePlantsStore } from '@/stores/plants'
|
||||
import type { Plant } from '@/api/plants'
|
||||
import { formatPlantLabel } from '@/utils/plants'
|
||||
|
||||
const plantsStore = usePlantsStore()
|
||||
const showForm = ref(false)
|
||||
@@ -285,9 +288,16 @@ const form = reactive({
|
||||
plantation_mois: '', recolte_mois: '', notes: '',
|
||||
})
|
||||
|
||||
const filteredPlants = computed(() =>
|
||||
selectedCat.value ? plantsStore.plants.filter(p => p.categorie === selectedCat.value) : plantsStore.plants
|
||||
)
|
||||
const filteredPlants = computed(() => {
|
||||
const source = selectedCat.value
|
||||
? plantsStore.plants.filter(p => p.categorie === selectedCat.value)
|
||||
: plantsStore.plants
|
||||
return [...source].sort((a, b) => {
|
||||
const byName = (a.nom_commun || '').localeCompare(b.nom_commun || '', 'fr', { sensitivity: 'base' })
|
||||
if (byName !== 0) return byName
|
||||
return (a.variete || '').localeCompare(b.variete || '', 'fr', { sensitivity: 'base' })
|
||||
})
|
||||
})
|
||||
|
||||
const catClass = (cat: string) => ({
|
||||
potager: 'bg-green/20 text-green',
|
||||
|
||||
Reference in New Issue
Block a user