avant 50
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
<button class="text-text-muted text-sm mb-4 hover:text-text" @click="router.back()">← Retour</button>
|
||||
|
||||
<div v-if="garden">
|
||||
<h1 class="text-2xl font-bold text-green mb-1">{{ garden.nom }}</h1>
|
||||
<div class="flex items-start justify-between mb-1">
|
||||
<h1 class="text-2xl font-bold text-green">{{ garden.nom }}</h1>
|
||||
</div>
|
||||
<p class="text-text-muted text-sm mb-6">
|
||||
{{ garden.type }} · {{ garden.exposition ?? 'exposition non définie' }}
|
||||
<span v-if="garden.sol_type"> · Sol : {{ garden.sol_type }}</span>
|
||||
@@ -39,21 +41,81 @@
|
||||
class="w-full max-h-72 object-cover rounded-lg border border-bg-hard bg-bg-soft" />
|
||||
</div>
|
||||
|
||||
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-3">
|
||||
Grille {{ garden.grille_largeur }}×{{ garden.grille_hauteur }}
|
||||
</h2>
|
||||
<!-- En-tête grille -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="text-text-muted text-xs uppercase tracking-widest">
|
||||
Grille {{ garden.grille_largeur }}×{{ garden.grille_hauteur }}
|
||||
</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="editMode" class="text-xs text-orange">Cliquez pour activer/désactiver une zone</span>
|
||||
<button @click="editMode = !editMode"
|
||||
:class="['px-3 py-1 rounded-full text-xs font-bold border transition-all',
|
||||
editMode ? 'bg-orange text-bg border-orange' : 'border-bg-soft text-text-muted hover:border-orange hover:text-orange']">
|
||||
{{ editMode ? '✓ Terminer' : '✏️ Éditer zones' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Légende -->
|
||||
<div class="flex flex-wrap gap-4 mb-3 text-[10px] text-text-muted font-bold uppercase tracking-wider">
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-3 h-3 rounded bg-bg border border-bg-soft inline-block"></span>Libre
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-3 h-3 rounded bg-green/20 border border-green/60 inline-block"></span>Planté
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-3 h-3 rounded bg-red/20 border border-red/40 inline-block"></span>Non cultivable
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto pb-2">
|
||||
<div
|
||||
class="grid gap-1 w-max"
|
||||
:style="`grid-template-columns: repeat(${garden.grille_largeur}, 52px)`"
|
||||
:style="`grid-template-columns: repeat(${garden.grille_largeur}, 56px)`"
|
||||
>
|
||||
<div
|
||||
v-for="cell in displayCells" :key="`${cell.row}-${cell.col}`"
|
||||
class="w-[52px] h-[52px] bg-bg-soft border border-bg-hard rounded-md flex items-center justify-center text-xs text-text-muted cursor-pointer hover:border-green transition-colors select-none"
|
||||
:class="{ 'border-orange/60 bg-orange/10 text-orange': cell.etat === 'occupe' }"
|
||||
:title="cell.libelle"
|
||||
:class="[
|
||||
'w-[56px] h-[56px] border rounded-md flex flex-col items-center justify-center text-[10px] select-none transition-all overflow-hidden',
|
||||
editMode && cell.etat !== 'occupe' && !getCellPlanting(cell) ? 'cursor-pointer' : '',
|
||||
getCellPlanting(cell)
|
||||
? 'bg-green/10 border-green/60 text-green'
|
||||
: cell.etat === 'non_cultivable'
|
||||
? 'bg-red/10 border-red/30 text-red/50'
|
||||
: 'bg-bg-soft border-bg-hard text-text-muted',
|
||||
editMode && !getCellPlanting(cell) && cell.etat !== 'occupe'
|
||||
? (cell.etat === 'non_cultivable' ? 'hover:bg-red/20 hover:border-red/60' : 'hover:border-orange hover:bg-orange/10')
|
||||
: ''
|
||||
]"
|
||||
:title="getCellTitle(cell)"
|
||||
@click="editMode && !getCellPlanting(cell) && cell.etat !== 'occupe' ? toggleNonCultivable(cell) : undefined"
|
||||
>
|
||||
{{ cell.libelle }}
|
||||
<span :class="['font-mono leading-none', getCellPlanting(cell) ? 'text-[9px] font-bold' : '']">
|
||||
{{ cell.libelle }}
|
||||
</span>
|
||||
<span v-if="getCellPlanting(cell)" class="text-[8px] text-green/80 leading-none mt-0.5 px-0.5 text-center truncate w-full">
|
||||
{{ plantShortName(getCellPlanting(cell)!) }}
|
||||
</span>
|
||||
<span v-else-if="cell.etat === 'non_cultivable'" class="text-[9px] leading-none">✕</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Saving indicator -->
|
||||
<div v-if="saving" class="mt-2 text-xs text-text-muted animate-pulse">Enregistrement…</div>
|
||||
|
||||
<!-- Résumé plantations actives -->
|
||||
<div v-if="activePlantings.length" class="mt-6">
|
||||
<h3 class="text-text-muted text-xs uppercase tracking-widest mb-2">Plantations actives ({{ activePlantings.length }})</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div v-for="p in activePlantings" :key="p.id"
|
||||
class="bg-bg-soft border border-green/30 rounded px-2 py-1 text-xs text-green flex items-center gap-1.5">
|
||||
<span class="text-text-muted font-mono text-[10px]">
|
||||
{{ plantCellLabel(p) }}
|
||||
</span>
|
||||
{{ plantName(p) }}
|
||||
<span class="text-text-muted">· {{ p.statut }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,11 +128,17 @@
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { gardensApi, type Garden, type GardenCell } from '@/api/gardens'
|
||||
import { plantingsApi, type Planting } from '@/api/plantings'
|
||||
import { plantsApi, type Plant } from '@/api/plants'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const garden = ref<Garden | null>(null)
|
||||
const cells = ref<GardenCell[]>([])
|
||||
const plantings = ref<Planting[]>([])
|
||||
const plants = ref<Plant[]>([])
|
||||
const editMode = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const displayCells = computed(() => {
|
||||
if (!garden.value) return []
|
||||
@@ -88,9 +156,87 @@ const displayCells = computed(() => {
|
||||
return result
|
||||
})
|
||||
|
||||
// Plantations actives (ni terminées ni échouées) pour ce jardin
|
||||
const activePlantings = computed(() =>
|
||||
plantings.value.filter(p =>
|
||||
p.garden_id === garden.value?.id &&
|
||||
p.statut !== 'termine' &&
|
||||
p.statut !== 'echoue'
|
||||
)
|
||||
)
|
||||
|
||||
// Map cellId → Planting active
|
||||
const activePlantingsByCellId = computed(() => {
|
||||
const map = new Map<number, Planting>()
|
||||
for (const p of activePlantings.value) {
|
||||
if (p.cell_ids?.length) {
|
||||
p.cell_ids.forEach(cid => map.set(cid, p))
|
||||
} else if (p.cell_id) {
|
||||
map.set(p.cell_id, p)
|
||||
}
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
function getCellPlanting(cell: GardenCell): Planting | undefined {
|
||||
if (cell.id == null) return undefined
|
||||
return activePlantingsByCellId.value.get(cell.id)
|
||||
}
|
||||
|
||||
function plantName(p: Planting): string {
|
||||
const plant = plants.value.find(pl => pl.id === p.variety_id)
|
||||
return plant?.nom_commun ?? `Plante #${p.variety_id}`
|
||||
}
|
||||
|
||||
function plantShortName(p: Planting): string {
|
||||
return plantName(p).slice(0, 8)
|
||||
}
|
||||
|
||||
function plantCellLabel(p: Planting): string {
|
||||
const ids = p.cell_ids?.length ? p.cell_ids : (p.cell_id ? [p.cell_id] : [])
|
||||
if (!ids.length) return '—'
|
||||
return ids
|
||||
.map(cid => cells.value.find(c => c.id === cid)?.libelle ?? `#${cid}`)
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
function getCellTitle(cell: GardenCell): string {
|
||||
const planting = getCellPlanting(cell)
|
||||
if (planting) return `Planté : ${plantName(planting)} (${planting.statut})`
|
||||
if (cell.etat === 'non_cultivable') return 'Non cultivable — cliquer pour rendre cultivable'
|
||||
if (editMode.value) return 'Marquer comme non cultivable'
|
||||
return cell.libelle ?? ''
|
||||
}
|
||||
|
||||
async function toggleNonCultivable(cell: GardenCell) {
|
||||
if (!garden.value?.id || saving.value) return
|
||||
const newEtat = cell.etat === 'non_cultivable' ? 'libre' : 'non_cultivable'
|
||||
saving.value = true
|
||||
try {
|
||||
if (cell.id) {
|
||||
const updated = await gardensApi.updateCell(garden.value.id, cell.id, { ...cell, etat: newEtat })
|
||||
const idx = cells.value.findIndex(c => c.id === cell.id)
|
||||
if (idx !== -1) cells.value[idx] = updated
|
||||
} else {
|
||||
const created = await gardensApi.createCell(garden.value.id, {
|
||||
col: cell.col, row: cell.row,
|
||||
libelle: cell.libelle,
|
||||
etat: newEtat,
|
||||
garden_id: garden.value.id,
|
||||
})
|
||||
cells.value.push(created)
|
||||
}
|
||||
} catch { /* L'intercepteur affiche l'erreur */ }
|
||||
finally { saving.value = false }
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const id = Number(route.params.id)
|
||||
garden.value = await gardensApi.get(id)
|
||||
cells.value = await gardensApi.cells(id)
|
||||
;[garden.value, cells.value, plantings.value, plants.value] = await Promise.all([
|
||||
gardensApi.get(id),
|
||||
gardensApi.cells(id),
|
||||
plantingsApi.list(),
|
||||
plantsApi.list(),
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user