171 lines
7.3 KiB
Vue
171 lines
7.3 KiB
Vue
<template>
|
|
<div class="min-h-screen" style="background: var(--nav-bg);">
|
|
<div class="max-w-4xl mx-auto px-4 py-6">
|
|
|
|
<!-- ── Bouton retour carte (préserve filtres URL) ─── -->
|
|
<NuxtLink
|
|
:to="retourUrl"
|
|
class="inline-flex items-center gap-1.5 text-sm mb-6 rounded-lg px-3 py-1.5 transition-colors"
|
|
style="color: var(--nav-text); background: var(--nav-bg-alt);"
|
|
aria-label="Retour aux pratiques régénératives"
|
|
>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<line x1="19" y1="12" x2="5" y2="12"/>
|
|
<polyline points="12 19 5 12 12 5"/>
|
|
</svg>
|
|
Retour aux pratiques régénératives
|
|
</NuxtLink>
|
|
|
|
<!-- ── Chargement ──────────────────────────────────── -->
|
|
<div v-if="pending" class="py-16 text-center text-sm" style="color: var(--nav-text-muted);">
|
|
Chargement de la fiche…
|
|
</div>
|
|
|
|
<!-- ── Erreur ──────────────────────────────────────── -->
|
|
<div v-else-if="!pratique" class="py-16 text-center">
|
|
<p class="text-lg font-semibold mb-2" style="color: var(--nav-text);">Fiche introuvable</p>
|
|
<p class="text-sm" style="color: var(--nav-text-muted);">La pratique demandée n'existe pas ou a été supprimée.</p>
|
|
</div>
|
|
|
|
<!-- ── Contenu ─────────────────────────────────────── -->
|
|
<template v-else>
|
|
|
|
<!-- Header fiche -->
|
|
<div class="mb-6">
|
|
<div class="flex items-start justify-between gap-4 mb-2">
|
|
<h1 class="text-2xl font-bold leading-tight" style="color: var(--nav-text);">{{ pratique.nom }}</h1>
|
|
<div class="flex items-center gap-2 shrink-0">
|
|
<span
|
|
class="px-2 py-1 rounded-full text-xs font-semibold uppercase tracking-wide"
|
|
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
|
|
>{{ TYPES_ENTITE_LABELS[pratique.type] ?? pratique.type }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 flex-wrap mb-3">
|
|
<span class="text-sm font-medium" style="color: var(--nav-text-muted);">
|
|
{{ PAYS_LABELS[pratique.pays] ?? pratique.pays }}
|
|
<template v-if="pratique.ville"> · {{ pratique.ville }}</template>
|
|
</span>
|
|
<span v-if="pratique.score" class="px-2 py-0.5 rounded text-xs" style="background: var(--nav-accent); color: var(--nav-text);">
|
|
Score {{ pratique.score }}/5
|
|
</span>
|
|
<a
|
|
v-if="pratique.url"
|
|
:href="pratique.url"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="text-sm underline"
|
|
style="color: var(--nav-primary-solid);"
|
|
>Site web →</a>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<p v-if="pratique.description" class="text-sm leading-relaxed" style="color: var(--nav-text);">
|
|
{{ pratique.description }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Séparateur -->
|
|
<div class="mb-6" style="height: 1px; background: var(--nav-bg-alt);"></div>
|
|
|
|
<!-- Critères régénératifs -->
|
|
<div v-if="pratique.criteres?.length" class="mb-6">
|
|
<h2 class="text-xs font-bold uppercase tracking-wide mb-3" style="color: var(--nav-text-muted);">Critères régénératifs</h2>
|
|
<div class="flex flex-wrap gap-2">
|
|
<span
|
|
v-for="cId in pratique.criteres"
|
|
:key="cId"
|
|
class="px-3 py-1 rounded-full text-sm font-medium"
|
|
style="background: var(--nav-primary); color: var(--nav-text-on-primary);"
|
|
>
|
|
{{ CRITERES.find(c => c.id === cId)?.label ?? `Critère ${cId}` }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tags -->
|
|
<div v-if="pratique.tags?.length" class="mb-6">
|
|
<h2 class="text-xs font-bold uppercase tracking-wide mb-3" style="color: var(--nav-text-muted);">Tags</h2>
|
|
<div class="flex flex-wrap gap-2">
|
|
<span
|
|
v-for="tag in pratique.tags"
|
|
:key="tag"
|
|
class="px-2 py-0.5 rounded text-xs"
|
|
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
|
|
>{{ tag }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Métadonnées -->
|
|
<div class="mb-6">
|
|
<h2 class="text-xs font-bold uppercase tracking-wide mb-3" style="color: var(--nav-text-muted);">Informations</h2>
|
|
<dl class="space-y-1.5">
|
|
<div v-if="pratique.passe" class="flex gap-2 text-sm">
|
|
<dt style="color: var(--nav-text-muted);">Passe :</dt>
|
|
<dd style="color: var(--nav-text);">{{ pratique.passe }}</dd>
|
|
</div>
|
|
<div v-if="pratique.source" class="flex gap-2 text-sm">
|
|
<dt style="color: var(--nav-text-muted);">Source :</dt>
|
|
<dd style="color: var(--nav-text);">{{ pratique.source }}</dd>
|
|
</div>
|
|
<div v-if="pratique.lat != null && pratique.lng != null" class="flex gap-2 text-sm">
|
|
<dt style="color: var(--nav-text-muted);">Coordonnées :</dt>
|
|
<dd style="color: var(--nav-text);">{{ pratique.lat }}, {{ pratique.lng }}</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Pratique } from '~/types/pratique'
|
|
import { CRITERES, TYPES_ENTITE_LABELS, PAYS_LABELS } from '~/types/pratique'
|
|
|
|
// ── Params & route ────────────────────────────────────────────────────
|
|
const route = useRoute()
|
|
const pratiqueId = route.params.id as string
|
|
|
|
// ── Retour carte — préserve les filtres via sessionStorage ────────────
|
|
const retourUrl = ref('/pratiques-regeneratives')
|
|
|
|
onMounted(() => {
|
|
if (typeof window !== 'undefined') {
|
|
const stored = sessionStorage.getItem('pratiques_back_filters')
|
|
if (stored) {
|
|
retourUrl.value = `/pratiques-regeneratives?${stored}`
|
|
}
|
|
}
|
|
})
|
|
|
|
// ── Fetch toutes les pratiques et trouver la bonne ───────────────────
|
|
const { data, pending } = await useFetch<{ list: Pratique[]; source: string }>('/api/pratiques', {
|
|
key: `pratiques-all`,
|
|
})
|
|
|
|
const pratique = computed<Pratique | null>(() => {
|
|
const id = parseInt(pratiqueId, 10)
|
|
if (isNaN(id)) return null
|
|
return data.value?.list?.find(p => p.id === id) ?? null
|
|
})
|
|
|
|
// ── SEO dynamiques ────────────────────────────────────────────────────
|
|
useHead({
|
|
title: computed(() =>
|
|
pratique.value ? `${pratique.value.nom} — Pratiques régénératives — AEP` : 'Pratique régénérative — AEP'
|
|
),
|
|
meta: [
|
|
{
|
|
name: 'description',
|
|
content: computed(() =>
|
|
pratique.value?.description?.substring(0, 160).trim() ?? 'Pratique régénérative — AEP'
|
|
),
|
|
},
|
|
],
|
|
})
|
|
</script>
|