Files
jardin/frontend/src/views/PlantationsView.vue
2026-02-22 15:05:40 +01:00

259 lines
11 KiB
Vue

<template>
<div class="p-4 max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">🌱 Plantations</h1>
<button @click="showCreate = true"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
+ Nouvelle
</button>
</div>
<!-- Filtres statut -->
<div class="flex gap-2 mb-4 flex-wrap">
<button v-for="s in statuts" :key="s.val" @click="filterStatut = s.val"
:class="['px-3 py-1 rounded-full text-xs font-medium transition-colors',
filterStatut === s.val ? 'bg-blue text-bg' : 'bg-bg-soft text-text-muted hover:text-text']">
{{ s.label }}
</button>
</div>
<div v-if="store.loading" class="text-text-muted text-sm">Chargement...</div>
<div v-else-if="!filtered.length" class="text-text-muted text-sm text-center py-8">
Aucune plantation enregistrée.
</div>
<div v-for="p in filtered" :key="p.id"
class="bg-bg-soft rounded-xl mb-3 border border-bg-hard overflow-hidden">
<!-- En-tête plantation -->
<div class="p-4 flex items-start gap-3">
<div class="flex-1">
<div class="flex items-center gap-2 flex-wrap">
<span class="text-text font-semibold">{{ plantName(p.variety_id) }}</span>
<span class="text-text-muted text-xs"> {{ gardenName(p.garden_id) }}</span>
<span :class="['text-xs px-2 py-0.5 rounded-full font-medium', statutClass(p.statut)]">{{ p.statut }}</span>
</div>
<div class="text-text-muted text-xs mt-1 flex gap-3 flex-wrap">
<span>{{ p.quantite }} plant(s)</span>
<span v-if="p.date_plantation">🌱 {{ fmtDate(p.date_plantation) }}</span>
<span v-if="p.notes">📝 {{ p.notes }}</span>
</div>
</div>
<div class="flex gap-2 items-center">
<button @click="toggleRecoltes(p.id!)"
:class="['text-xs px-2 py-1 rounded transition-colors',
openRecoltes === p.id ? 'bg-aqua/20 text-aqua' : 'bg-bg-hard text-text-muted hover:text-aqua']">
🍅 Récoltes
</button>
<button @click="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
<button @click="store.remove(p.id!)" class="text-text-muted hover:text-red text-sm ml-1"></button>
</div>
</div>
<!-- Section récoltes (dépliable) -->
<div v-if="openRecoltes === p.id" class="border-t border-bg-hard px-4 py-3 bg-bg/50">
<div v-if="loadingRecoltes" class="text-text-muted text-xs py-2">Chargement...</div>
<div v-else>
<div v-if="!recoltesList.length" class="text-text-muted text-xs mb-2">Aucune récolte enregistrée.</div>
<div v-for="r in recoltesList" :key="r.id"
class="flex items-center gap-3 text-sm py-1 border-b border-bg-hard last:border-0">
<span class="text-aqua font-mono">{{ r.quantite }} {{ r.unite }}</span>
<span class="text-text-muted text-xs">{{ fmtDate(r.date_recolte) }}</span>
<span v-if="r.notes" class="text-text-muted text-xs flex-1 truncate">{{ r.notes }}</span>
<button @click="deleteRecolte(r.id!, p.id!)" class="text-text-muted hover:text-red text-xs ml-auto"></button>
</div>
<!-- Formulaire ajout récolte -->
<form @submit.prevent="addRecolte(p.id!)" class="flex gap-2 mt-3 flex-wrap items-end">
<div>
<label class="text-text-muted text-xs block mb-1">Quantité *</label>
<input v-model.number="rForm.quantite" type="number" step="0.1" min="0" required
class="bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs w-20 focus:border-aqua outline-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Unité</label>
<select v-model="rForm.unite"
class="bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs focus:border-aqua outline-none">
<option>kg</option><option>g</option><option>unites</option><option>litres</option><option>bottes</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Date *</label>
<input v-model="rForm.date_recolte" type="date" required
class="bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs focus:border-aqua outline-none" />
</div>
<button type="submit"
class="bg-aqua text-bg px-3 py-1 rounded text-xs font-semibold hover:opacity-90 self-end">
+ Ajouter
</button>
</form>
</div>
</div>
</div>
<!-- Modal création / édition plantation -->
<div v-if="showCreate" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
@click.self="closeCreate">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft">
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier la plantation' : 'Nouvelle plantation' }}</h2>
<form @submit.prevent="createPlanting" class="flex flex-col gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Jardin *</label>
<select v-model.number="cForm.garden_id" required
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Choisir un jardin</option>
<option v-for="g in gardensStore.gardens" :key="g.id" :value="g.id">{{ g.nom }}</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Plante *</label>
<select v-model.number="cForm.variety_id" required
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Choisir une plante</option>
<option v-for="p in plantsStore.plants" :key="p.id" :value="p.id">
{{ p.nom_commun }}{{ p.variete ? ' ' + p.variete : '' }}
</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Quantité</label>
<input v-model.number="cForm.quantite" type="number" min="1"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Date plantation</label>
<input v-model="cForm.date_plantation" type="date"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
</div>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Statut</label>
<select v-model="cForm.statut"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="prevu">Prévu</option>
<option value="en_cours">En cours</option>
<option value="termine">Terminé</option>
</select>
</div>
<textarea v-model="cForm.notes" placeholder="Notes..."
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green resize-none h-16" />
<div class="flex gap-2 justify-end">
<button type="button" @click="closeCreate" class="px-4 py-2 text-text-muted hover:text-text text-sm">Annuler</button>
<button type="submit" class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
{{ editId ? 'Enregistrer' : 'Créer' }}
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { usePlantingsStore } from '@/stores/plantings'
import { useGardensStore } from '@/stores/gardens'
import { usePlantsStore } from '@/stores/plants'
import { recoltesApi, type Recolte } from '@/api/recoltes'
const store = usePlantingsStore()
const gardensStore = useGardensStore()
const plantsStore = usePlantsStore()
const showCreate = ref(false)
const editId = ref<number | null>(null)
const filterStatut = ref('')
const openRecoltes = ref<number | null>(null)
const recoltesList = ref<Recolte[]>([])
const loadingRecoltes = ref(false)
const statuts = [
{ val: '', label: 'Toutes' },
{ val: 'prevu', label: '📋 Prévu' },
{ val: 'en_cours', label: '🌿 En cours' },
{ val: 'termine', label: '✅ Terminé' },
{ val: 'echoue', label: '❌ Échoué' },
]
const cForm = reactive({
garden_id: 0, variety_id: 0, quantite: 1,
date_plantation: '', statut: 'prevu', notes: ''
})
const rForm = reactive({
quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10)
})
const filtered = computed(() =>
filterStatut.value ? store.plantings.filter(p => p.statut === filterStatut.value) : store.plantings
)
function plantName(id: number) {
const p = plantsStore.plants.find(x => x.id === id)
return p ? (p.variete ? `${p.nom_commun} (${p.variete})` : p.nom_commun) : `Plante #${id}`
}
function gardenName(id: number) {
return gardensStore.gardens.find(g => g.id === id)?.nom ?? `Jardin #${id}`
}
function fmtDate(s: string) {
return new Date(s + (s.length === 10 ? 'T12:00:00' : '')).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' })
}
const statutClass = (s: string) => ({
prevu: 'bg-blue/20 text-blue',
en_cours: 'bg-green/20 text-green',
termine: 'bg-text-muted/20 text-text-muted',
echoue: 'bg-red/20 text-red',
}[s] || 'bg-bg text-text-muted')
async function toggleRecoltes(id: number) {
if (openRecoltes.value === id) { openRecoltes.value = null; return }
openRecoltes.value = id
loadingRecoltes.value = true
try { recoltesList.value = await recoltesApi.list(id) } catch { recoltesList.value = [] }
finally { loadingRecoltes.value = false }
}
async function addRecolte(plantingId: number) {
const created = await recoltesApi.create(plantingId, { ...rForm })
recoltesList.value.push(created)
Object.assign(rForm, { quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10) })
}
async function deleteRecolte(id: number, plantingId: number) {
if (!confirm('Supprimer cette récolte ?')) return
await recoltesApi.delete(id)
recoltesList.value = recoltesList.value.filter(r => r.id !== id)
}
function startEdit(p: typeof store.plantings[0]) {
editId.value = p.id!
Object.assign(cForm, {
garden_id: p.garden_id, variety_id: p.variety_id,
quantite: p.quantite, date_plantation: p.date_plantation?.slice(0, 10) || '',
statut: p.statut, notes: p.notes || '',
})
showCreate.value = true
}
function closeCreate() { showCreate.value = false; editId.value = null }
async function createPlanting() {
if (editId.value) {
await store.update(editId.value, { ...cForm })
} else {
await store.create({ ...cForm })
}
closeCreate()
Object.assign(cForm, { garden_id: 0, variety_id: 0, quantite: 1, date_plantation: '', statut: 'prevu', notes: '' })
}
onMounted(() => {
store.fetchAll()
gardensStore.fetchAll()
plantsStore.fetchAll()
})
</script>