Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGNSEFZdoKvbdsGyfSnu5S
292 lines
11 KiB
Vue
292 lines
11 KiB
Vue
<!--
|
|
FicheReseauContenu — B12-M4. Le corps de l'ancienne FicheModalV2 (retirée),
|
|
devenu un contenu : la vue fiches le pose dans son panneau de détail
|
|
(slot #detail de VueFiches). Badges, hashtags, descriptions, pensées,
|
|
projets, structures liées, sources.
|
|
|
|
Hashtag cliqué → `etiquette` (la vue fiches ajoute le filtre) ; structure
|
|
liée cliquée → `voisin` (la page l'ouvre dans le même panneau).
|
|
-->
|
|
<template>
|
|
<article v-if="structure" class="frc">
|
|
<div class="frc-famille" :style="`border-bottom-color: ${familleColor};`">
|
|
<span class="frc-famille__pastille" :style="`background: ${familleColor};`" aria-hidden="true" />
|
|
<span class="frc-famille__libelle">{{ familleLabel }}</span>
|
|
<span v-if="structure.badges.centre_ressources" class="frc-badge" style="background: #2d8a6b22; color: #1f6650;">Centre ressources</span>
|
|
<span v-if="structure.badges.mouvement_manifeste" class="frc-badge" style="background: #c44a2f22; color: #9a3520;">Manifeste</span>
|
|
<span v-if="structure.badges.contre_pouvoir_spatial" class="frc-badge" style="background: #1a3a6b22; color: #1a3a6b;">Contre-pouvoir</span>
|
|
<span v-if="structure.badges.f6_recherche_politique" class="frc-badge" style="background: #6b3fa022; color: #5a3388;">Recherche pol.</span>
|
|
</div>
|
|
|
|
<h2 class="frc-nom">{{ structure.nom }}</h2>
|
|
<p class="frc-meta">
|
|
<span>{{ structure.type_principal }}</span>
|
|
<span aria-hidden="true"> · </span>
|
|
<span>{{ structure.ville }}, {{ structure.pays }}</span>
|
|
</p>
|
|
|
|
<a
|
|
v-if="structure.url"
|
|
:href="structure.url"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="frc-site"
|
|
>
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
<polyline points="15 3 21 3 21 9" />
|
|
<line x1="10" y1="14" x2="21" y2="3" />
|
|
</svg>
|
|
Site web<span class="frc-sr"> (nouvel onglet)</span>
|
|
</a>
|
|
|
|
<ChipGroup v-if="structure.hashtags.length" :aria-label="`Hashtags de ${structure.nom}`" class="frc-tags">
|
|
<Chip
|
|
v-for="tag in structure.hashtags"
|
|
:key="tag"
|
|
variant="fonction"
|
|
:label="tag"
|
|
:active="etiquettesActives?.includes(tag)"
|
|
:aria-label="`Filtrer par ${tag}`"
|
|
@toggle="emit('etiquette', tag)"
|
|
/>
|
|
</ChipGroup>
|
|
|
|
<p class="frc-texte">{{ structure.description_courte }}</p>
|
|
|
|
<div v-if="structure.description_longue" class="frc-bloc">
|
|
<div class="frc-texte frc-long" :class="{ 'frc-long--replie': !showFullDesc }">{{ structure.description_longue }}</div>
|
|
<button type="button" class="frc-lien" :aria-expanded="showFullDesc" @click="showFullDesc = !showFullDesc">
|
|
{{ showFullDesc ? 'Réduire' : 'Lire la suite…' }}
|
|
</button>
|
|
</div>
|
|
|
|
<section v-if="structure.pensees && structure.pensees.length" class="frc-bloc">
|
|
<h3 class="frc-titre">Pensées rattachées</h3>
|
|
<div class="frc-liste-inline">
|
|
<span
|
|
v-for="pensee in structure.pensees"
|
|
:key="pensee.id"
|
|
class="frc-pensee"
|
|
:class="{ 'frc-pensee--ia': pensee.confiance === 'ia_suggested' }"
|
|
:title="pensee.confiance === 'ia_suggested' ? 'IA suggéré' : ''"
|
|
>{{ pensee.label }}<span v-if="pensee.confiance === 'ia_suggested'" aria-hidden="true"> ~</span><span v-if="pensee.confiance === 'ia_suggested'" class="frc-sr"> (suggéré par l'IA)</span></span>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-if="projetsStructure.length" class="frc-bloc">
|
|
<h3 class="frc-titre">Projets emblématiques</h3>
|
|
<div class="frc-projets">
|
|
<div v-for="projet in projetsStructure.slice(0, 5)" :key="projet.id" class="frc-projet">
|
|
<div class="frc-projet__tete">
|
|
<span class="frc-projet__nom">{{ projet.nom }}</span>
|
|
<span v-if="projet.annee" class="frc-discret">{{ projet.annee }}</span>
|
|
</div>
|
|
<div v-if="projet.lieu" class="frc-discret">{{ projet.lieu }}</div>
|
|
<p class="frc-projet__desc">{{ projet.description.slice(0, 120) }}{{ projet.description.length > 120 ? '…' : '' }}</p>
|
|
<a v-if="projet.url" :href="projet.url" target="_blank" rel="noopener noreferrer" class="frc-lien">En savoir plus<span class="frc-sr"> sur {{ projet.nom }} (nouvel onglet)</span> →</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-if="structuresVoisines.length" class="frc-bloc">
|
|
<h3 class="frc-titre">Structures liées</h3>
|
|
<div class="frc-liste-inline">
|
|
<button
|
|
v-for="voisine in structuresVoisines.slice(0, 6)"
|
|
:key="voisine.id"
|
|
type="button"
|
|
class="frc-voisin"
|
|
@click="emit('voisin', voisine.id)"
|
|
>{{ voisine.nom }}</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-if="structure.sources && structure.sources.length" class="frc-bloc">
|
|
<h3 class="frc-titre">Sources</h3>
|
|
<ul class="frc-sources">
|
|
<li v-for="(source, i) in structure.sources" :key="i">
|
|
<span class="frc-source__type">{{ source.type }}</span>
|
|
<a v-if="source.url" :href="source.url" target="_blank" rel="noopener noreferrer" class="frc-lien">{{ source.titre }}</a>
|
|
<span v-else>{{ source.titre }}</span>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<p class="frc-cta">
|
|
<NuxtLink to="/contribuer" class="frc-lien">Signaler une erreur</NuxtLink>
|
|
<NuxtLink to="/contribuer" class="frc-lien">Réclamer cette fiche</NuxtLink>
|
|
</p>
|
|
</article>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ReseauxBifurcationData, StructureV2, ProjetEmblematique } from '~/types/structure-v2'
|
|
|
|
const FAMILLE_COLORS: Record<number, string> = {
|
|
1: '#a85d3e',
|
|
2: '#c4a472',
|
|
3: '#d4a017',
|
|
4: '#5a7a4a',
|
|
5: '#3d6a8c',
|
|
}
|
|
|
|
const FAMILLE_LABELS: Record<number, string> = {
|
|
1: 'Réemploi & filières',
|
|
2: 'Frugalité & low-tech',
|
|
3: 'Architecture sociale',
|
|
4: 'Collectifs & AMO',
|
|
5: 'Urbanisme de transition',
|
|
}
|
|
|
|
const props = defineProps<{
|
|
structureId: string
|
|
data: ReseauxBifurcationData | null
|
|
/** Hashtags actifs dans les filtres (avec leur « # ») : la chip s'allume. */
|
|
etiquettesActives?: string[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
etiquette: [hashtag: string]
|
|
voisin: [id: string]
|
|
}>()
|
|
|
|
const showFullDesc = ref(false)
|
|
watch(() => props.structureId, () => { showFullDesc.value = false })
|
|
|
|
const structure = computed<StructureV2 | null>(() =>
|
|
props.data?.structures.find((s) => s.id === props.structureId) ?? null,
|
|
)
|
|
|
|
const familleColor = computed(() => FAMILLE_COLORS[structure.value?.famille_principale ?? 1] ?? '#888')
|
|
const familleLabel = computed(() => FAMILLE_LABELS[structure.value?.famille_principale ?? 1] ?? '')
|
|
|
|
const projetsStructure = computed<ProjetEmblematique[]>(() =>
|
|
props.data?.projets?.filter((p) => p.structure_parent === props.structureId) ?? [],
|
|
)
|
|
|
|
const structuresVoisines = computed<StructureV2[]>(() => {
|
|
if (!props.data) return []
|
|
const edges = props.data.graphe?.edges ?? []
|
|
return edges
|
|
.filter((e) => e.source === props.structureId || e.target === props.structureId)
|
|
.sort((a, b) => (b.score ?? 0) - (a.score ?? 0))
|
|
.map((e) => (e.source === props.structureId ? e.target : e.source))
|
|
.slice(0, 8)
|
|
.map((id) => props.data!.structures.find((s) => s.id === id))
|
|
.filter(Boolean) as StructureV2[]
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.frc { color: var(--text); font-size: var(--fs-sm); }
|
|
.frc-sr {
|
|
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
}
|
|
|
|
.frc-famille {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: var(--space-2xs) var(--space-xs);
|
|
padding-bottom: var(--space-xs);
|
|
margin-bottom: var(--space-sm);
|
|
border-bottom: 3px solid;
|
|
}
|
|
.frc-famille__pastille { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
|
.frc-famille__libelle { font-weight: var(--fw-semibold); color: color-mix(in srgb, var(--text) 78%, transparent); }
|
|
.frc-badge {
|
|
padding: 2px var(--space-xs);
|
|
border-radius: var(--radius-full);
|
|
font-size: var(--fs-xs, 0.75rem);
|
|
font-weight: var(--fw-medium);
|
|
}
|
|
|
|
.frc-nom { margin: 0 0 var(--space-2xs); font-size: 1.25rem; font-weight: var(--fw-bold, 700); line-height: 1.25; }
|
|
.frc-meta { margin: 0 0 var(--space-sm); color: color-mix(in srgb, var(--text) 78%, transparent); }
|
|
|
|
.frc-site {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-2xs);
|
|
min-height: 32px;
|
|
padding: 0 var(--space-sm);
|
|
margin-bottom: var(--space-sm);
|
|
border-radius: var(--radius-md);
|
|
background: var(--bg-alt);
|
|
color: var(--text);
|
|
font-weight: var(--fw-medium);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.frc-tags { margin-bottom: var(--space-sm); }
|
|
|
|
.frc-texte { margin: 0 0 var(--space-sm); line-height: var(--lh-base, 1.55); }
|
|
.frc-long { white-space: pre-wrap; margin: 0; }
|
|
.frc-long--replie { max-height: 120px; overflow: hidden; }
|
|
|
|
.frc-bloc { margin-bottom: var(--space-md); }
|
|
.frc-titre {
|
|
margin: 0 0 var(--space-xs);
|
|
font-size: var(--fs-xs, 0.75rem);
|
|
font-weight: var(--fw-bold, 700);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: color-mix(in srgb, var(--text) 78%, transparent);
|
|
}
|
|
|
|
.frc-liste-inline { display: flex; flex-wrap: wrap; gap: var(--space-2xs); }
|
|
.frc-pensee {
|
|
padding: 2px var(--space-xs);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-alt);
|
|
font-size: var(--fs-xs, 0.75rem);
|
|
}
|
|
.frc-pensee--ia { color: color-mix(in srgb, var(--text) 78%, transparent); border: 1px dashed var(--border); }
|
|
|
|
.frc-projets { display: flex; flex-direction: column; gap: var(--space-xs); }
|
|
.frc-projet { padding: var(--space-sm); border-radius: var(--radius-md); background: var(--bg); border: 1px solid var(--border); }
|
|
.frc-projet__tete { display: flex; justify-content: space-between; gap: var(--space-xs); }
|
|
.frc-projet__nom { font-weight: var(--fw-medium); }
|
|
.frc-projet__desc { margin: var(--space-2xs) 0; font-size: var(--fs-xs, 0.75rem); line-height: 1.5; color: color-mix(in srgb, var(--text) 78%, transparent); }
|
|
.frc-discret { font-size: var(--fs-xs, 0.75rem); color: color-mix(in srgb, var(--text) 78%, transparent); }
|
|
|
|
.frc-voisin {
|
|
min-height: 32px;
|
|
padding: 0 var(--space-xs);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-alt);
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: var(--fs-xs, 0.75rem);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
|
|
.frc-sources { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2xs); }
|
|
.frc-sources li { display: flex; align-items: baseline; gap: var(--space-xs); min-width: 0; font-size: var(--fs-xs, 0.75rem); overflow-wrap: anywhere; }
|
|
.frc-source__type { flex-shrink: 0; padding: 1px var(--space-2xs); border-radius: var(--radius-sm); background: var(--bg-alt); }
|
|
|
|
.frc-lien {
|
|
padding: 0;
|
|
border: none;
|
|
background: none;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: var(--fs-xs, 0.75rem);
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.frc-cta { display: flex; gap: var(--space-sm); padding-top: var(--space-xs); margin: 0; border-top: 1px solid var(--border); }
|
|
|
|
.frc-site:focus-visible,
|
|
.frc-lien:focus-visible,
|
|
.frc-voisin:focus-visible {
|
|
outline: 2px solid var(--focus-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
</style>
|