- Aujourd'hui (Fil 13 sections + capture sticky) + Le chemin (tunnel 2 gestes, Q0 inline, 3 chips, dérogation asymétrique, alternatives réglage/consignation) - Registre + fiche décision (timeline, périmètre premier/second lieu auditable, affluence non-ignorable, S'instruire condensé, éléments + cartographie de clôture, épreuve du réel, Remettre en question, PV A4, gravure) - Salle de vote 5 modalités (consentement, nuancé+histogramme, binaire hérité avec jauge inertielle, Réglage collectif complet — faisceau, médiane basse, Pour moi, Explorer, cristallisation-geste —, élection à départage humain) - Textes (Pacte en clair, document vivant, diff, vue projetée, Atelier des formules porté du v1 sur le moteur unique) + Mandats (faits comptés, feux de la rampe, wizard 3 étapes) + Observatoire (consigner→observer→protocoliser) - Onboarding 7 gabarits + Données locales (export/import, attributs, atelier) - voting→framing gardé (Reformuler d'un réglage figé non cristallisé) - Pages v1 supprimées (login, documents, mandates, protocols, sanctuary, tools, decisions/new) — 342 tests verts, build zéro erreur Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
144 lines
6.9 KiB
Vue
144 lines
6.9 KiB
Vue
<script setup lang="ts">
|
||
// <!-- ld-v2 --> Bloc « S'instruire » condensé — résumé toujours visible
|
||
// (titre, effets recherchés, ce que ça engage) + dépliables (texte, avis et
|
||
// objections, provenance). Auto-replié aux visites suivantes (localStorage).
|
||
import { useCollectiveStore } from '~/stores/collective'
|
||
import { BASELINE_PREFIX, ENGAGES_LABEL, INSTRUCT_BLOCK, ROUTE_LABELS } from '~/lexicon'
|
||
import type { Decision, Id } from '~/types/domain'
|
||
|
||
const props = defineProps<{ decision: Decision; secret?: boolean }>()
|
||
const col = useCollectiveStore()
|
||
|
||
const open = ref(true)
|
||
onMounted(() => {
|
||
const key = `ld2-instruct-${props.decision.id}`
|
||
if (localStorage.getItem(key)) open.value = false
|
||
else localStorage.setItem(key, '1')
|
||
})
|
||
|
||
const effects = computed(() => props.decision.brief?.effects ?? [])
|
||
const advices = computed(() => col.advices.filter(a => a.decisionId === props.decision.id))
|
||
const objections = computed(() => col.objections.filter(o => o.decisionId === props.decision.id))
|
||
const parent = computed(() => col.decisions.find(d => d.id === props.decision.parentDecisionId))
|
||
|
||
function name(id: Id): string {
|
||
return col.people.find(p => p.id === id)?.displayName ?? '—'
|
||
}
|
||
function day(iso: string): string {
|
||
return new Date(iso).toLocaleDateString('fr-FR')
|
||
}
|
||
|
||
const POSITION_LABELS = { favorable: 'favorable', reserved: 'réservé', unfavorable: 'défavorable' } as const
|
||
const OBJECTION_STATUS = { open: 'ouverte', withdrawn: 'levée', integrated: 'intégrée', escalated: 'escaladée' } as const
|
||
</script>
|
||
|
||
<template>
|
||
<!-- ld-v2 -->
|
||
<section class="ld-card vi">
|
||
<header class="vi__head">
|
||
<h2 class="vi__title">
|
||
<UIcon name="i-lucide-book-open" />
|
||
<span>{{ INSTRUCT_BLOCK }}</span>
|
||
</h2>
|
||
<button class="ld-btn ld-btn--quiet vi__toggle" type="button" @click="open = !open">
|
||
<span>{{ open ? 'Replier' : 'Voir le détail' }}</span>
|
||
<UIcon :name="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'" />
|
||
</button>
|
||
</header>
|
||
|
||
<!-- Résumé compact — toujours visible -->
|
||
<p class="vi__sentence">{{ decision.title }}</p>
|
||
<ul v-if="effects.length" class="vi__effects">
|
||
<li v-for="(effect, i) in effects" :key="i">
|
||
<UIcon name="i-lucide-target" class="vi__effect-icon" />
|
||
<span>{{ effect.label }}</span>
|
||
<span v-if="effect.target" class="vi__target">{{ effect.target }}</span>
|
||
</li>
|
||
</ul>
|
||
<p v-if="decision.resources?.note" class="vi__engages">
|
||
<span class="vi__engages-label">{{ ENGAGES_LABEL }}</span>
|
||
<span>{{ decision.resources.note }}</span>
|
||
<strong v-if="decision.resources.amount !== undefined">
|
||
{{ decision.resources.amount.toLocaleString('fr-FR') }} {{ decision.resources.unit ?? '' }}
|
||
</strong>
|
||
</p>
|
||
|
||
<!-- Dépliables -->
|
||
<div v-if="open" class="vi__details">
|
||
<details v-if="decision.body || decision.baselineNote">
|
||
<summary>Le texte</summary>
|
||
<p v-if="decision.baselineNote" class="vi__baseline">
|
||
{{ BASELINE_PREFIX }} {{ decision.baselineNote }}
|
||
</p>
|
||
<p class="vi__body">{{ decision.body ?? decision.title }}</p>
|
||
</details>
|
||
|
||
<details v-if="advices.length || objections.length">
|
||
<summary>Avis et objections ({{ advices.length + objections.length }})</summary>
|
||
<ul class="vi__list">
|
||
<li v-for="advice in advices" :key="advice.id">
|
||
<span class="vi__who">{{ secret ? 'quelqu’un' : name(advice.personId) }}</span>
|
||
<span class="vi__pos">{{ POSITION_LABELS[advice.position] }}</span>
|
||
<span v-if="advice.note" class="vi__note">{{ advice.note }}</span>
|
||
</li>
|
||
<li v-for="objection in objections" :key="objection.id">
|
||
<span class="vi__who">{{ secret ? 'quelqu’un' : name(objection.personId) }}</span>
|
||
<span class="vi__pos vi__pos--objection">objection {{ OBJECTION_STATUS[objection.status] }}</span>
|
||
<span class="vi__note">{{ objection.argument }}</span>
|
||
</li>
|
||
</ul>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Provenance</summary>
|
||
<ul class="vi__list">
|
||
<li>Proposé par {{ name(decision.authorId) }} le {{ day(decision.createdAt) }}</li>
|
||
<li>Chemin : {{ ROUTE_LABELS[decision.route] }}</li>
|
||
<li v-if="parent">
|
||
Chaînée à
|
||
<NuxtLink :to="`/decisions/${parent.id}`" class="vi__link">{{ parent.title }}</NuxtLink>
|
||
</li>
|
||
<li v-if="decision.decidedHow">{{ decision.decidedHow }}</li>
|
||
</ul>
|
||
</details>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.vi { padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.6rem; }
|
||
.vi__head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
|
||
.vi__title {
|
||
display: inline-flex; align-items: center; gap: 0.45rem; margin: 0;
|
||
font-size: 0.9375rem; font-weight: 700; color: var(--mood-accent);
|
||
}
|
||
.vi__toggle { padding: 0.25rem 0.75rem; font-size: 0.8125rem; }
|
||
.vi__sentence { margin: 0; font-weight: 700; font-size: 1.0625rem; line-height: 1.35; }
|
||
.vi__effects { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
|
||
.vi__effects li { display: flex; align-items: baseline; gap: 0.45rem; font-size: 0.9375rem; }
|
||
.vi__effect-icon { color: var(--mood-tertiary); flex-shrink: 0; transform: translateY(2px); }
|
||
.vi__target {
|
||
font-size: 0.75rem; font-weight: 700; color: var(--mood-tertiary);
|
||
background: color-mix(in srgb, var(--mood-tertiary) 12%, transparent);
|
||
padding: 1px 8px; border-radius: var(--r-pill); white-space: nowrap;
|
||
}
|
||
.vi__engages {
|
||
margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem;
|
||
font-size: 0.9375rem; color: var(--mood-text);
|
||
background: var(--mood-accent-soft); border-radius: var(--r-input); padding: 0.5rem 0.75rem;
|
||
}
|
||
.vi__engages-label { font-size: 0.75rem; font-weight: 700; color: var(--mood-accent); text-transform: uppercase; letter-spacing: 0.04em; }
|
||
.vi__details { display: flex; flex-direction: column; gap: 0.35rem; }
|
||
.vi__details details { background: var(--mood-bg); border-radius: var(--r-input); padding: 0.55rem 0.8rem; }
|
||
.vi__details summary { cursor: pointer; font-weight: 700; font-size: 0.875rem; color: var(--mood-text-muted); user-select: none; }
|
||
.vi__baseline { font-size: 0.875rem; color: var(--mood-text-muted); font-style: italic; margin: 0.5rem 0 0.25rem; }
|
||
.vi__body { white-space: pre-wrap; font-size: 0.9375rem; line-height: 1.5; margin: 0.5rem 0 0; }
|
||
.vi__list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; font-size: 0.875rem; }
|
||
.vi__who { font-weight: 700; margin-right: 0.35rem; }
|
||
.vi__pos { color: var(--mood-tertiary); font-weight: 600; margin-right: 0.35rem; }
|
||
.vi__pos--objection { color: var(--mood-warning); }
|
||
.vi__note { color: var(--mood-text-muted); }
|
||
.vi__link { color: var(--mood-accent); font-weight: 600; text-decoration: none; }
|
||
.vi__link:hover { text-decoration: underline; }
|
||
</style>
|