Files
Yvv cede2a585f Sprint 3 : protocoles de vote et boite a outils
Backend:
- Sessions de vote : list, close, tally, threshold details, auto-expiration
- Protocoles : update, simulate, meta-gouvernance, formulas CRUD
- Service vote enrichi : close_session, get_threshold_details, nuanced breakdown
- Schemas : ThresholdDetailOut, VoteResultOut, FormulaSimulationRequest/Result
- WebSocket broadcast sur chaque vote + fermeture session
- 25 nouveaux tests (threshold details, close, nuanced, simulation)

Frontend:
- 5 composants vote : VoteBinary, VoteNuanced, ThresholdGauge, FormulaDisplay, VoteHistory
- 3 composants protocoles : ProtocolPicker, FormulaEditor, ModeParamsDisplay
- Simulateur de formules interactif (page /protocols/formulas)
- Page detail protocole (/protocols/[id])
- Composable useWebSocket (live updates)
- Composable useVoteFormula (calcul client-side reactif)
- Integration KaTeX pour rendu LaTeX des formules

Documentation:
- API reference : 8 nouveaux endpoints documentes
- Formules : tables d'inertie, parametres detailles, simulation API
- Guide vote : vote binaire/nuance, jauge, historique, simulateur, meta-gouvernance

55 tests passes (+ 1 skipped), 126 fichiers total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:29:31 +01:00

181 lines
5.6 KiB
Vue

<script setup lang="ts">
/**
* Protocol detail page.
*
* Displays full protocol information including name, type, description,
* mode params, formula config, and links to the formula simulator.
*/
const route = useRoute()
const protocols = useProtocolsStore()
const votes = useVotesStore()
const protocolId = computed(() => route.params.id as string)
onMounted(async () => {
await protocols.fetchProtocolById(protocolId.value)
})
const protocol = computed(() => protocols.currentProtocol)
const voteTypeLabel = (voteType: string) => {
switch (voteType) {
case 'binary': return 'Binaire'
case 'nuanced': return 'Nuance'
default: return voteType
}
}
const voteTypeColor = (voteType: string) => {
switch (voteType) {
case 'binary': return 'primary'
case 'nuanced': return 'info'
default: return 'neutral'
}
}
function formatDate(dateStr: string): string {
return new Date(dateStr).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
}
/** Build simulator URL with prefilled params. */
const simulatorLink = computed(() => {
if (!protocol.value?.mode_params) return '/protocols/formulas'
return `/protocols/formulas`
})
</script>
<template>
<div class="space-y-8">
<!-- Header with back link -->
<div class="flex items-center gap-3">
<NuxtLink to="/protocols" class="text-gray-400 hover:text-gray-600">
<UIcon name="i-lucide-arrow-left" />
</NuxtLink>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
Detail du protocole
</h1>
</div>
<!-- Loading -->
<template v-if="protocols.loading">
<div class="space-y-3">
<USkeleton class="h-12 w-3/4" />
<USkeleton class="h-6 w-1/2" />
<USkeleton class="h-48 w-full" />
</div>
</template>
<!-- Error -->
<template v-else-if="protocols.error">
<UCard>
<div class="flex items-center gap-3 text-red-500">
<UIcon name="i-lucide-alert-circle" class="text-xl" />
<p>{{ protocols.error }}</p>
</div>
</UCard>
</template>
<!-- Protocol detail -->
<template v-else-if="protocol">
<!-- Protocol header card -->
<UCard>
<div class="space-y-4">
<div class="flex items-start justify-between">
<div>
<h2 class="text-xl font-bold text-gray-900 dark:text-white">
{{ protocol.name }}
</h2>
<p v-if="protocol.description" class="text-gray-600 dark:text-gray-400 mt-1">
{{ protocol.description }}
</p>
<p class="text-xs text-gray-500 mt-2">
Cree le {{ formatDate(protocol.created_at) }}
</p>
</div>
<div class="flex items-center gap-2">
<UBadge :color="(voteTypeColor(protocol.vote_type) as any)" variant="subtle">
{{ voteTypeLabel(protocol.vote_type) }}
</UBadge>
<UBadge v-if="protocol.is_meta_governed" color="warning" variant="subtle">
Meta-gouverne
</UBadge>
</div>
</div>
</div>
</UCard>
<!-- Mode params -->
<UCard v-if="protocol.mode_params">
<template #header>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Parametres du mode
</h3>
</template>
<ModeParamsDisplay :mode-params="protocol.mode_params" />
</UCard>
<!-- Formula config -->
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Configuration de la formule : {{ protocol.formula_config.name }}
</h3>
<NuxtLink :to="simulatorLink">
<UButton variant="outline" size="sm" icon="i-lucide-calculator">
Simuler
</UButton>
</NuxtLink>
</div>
</template>
<FormulaDisplay :formula-config="protocol.formula_config" />
</UCard>
<!-- Meta-governance info -->
<UCard v-if="protocol.is_meta_governed">
<div class="flex items-center gap-3">
<UIcon name="i-lucide-shield" class="text-2xl text-amber-500" />
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">
Protocole meta-gouverne
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
Les modifications de ce protocole sont soumises au vote selon ses propres regles.
</p>
</div>
</div>
</UCard>
<!-- Related vote sessions -->
<UCard v-if="votes.sessions && votes.sessions.length > 0">
<template #header>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Sessions de vote utilisant ce protocole
</h3>
</template>
<div class="space-y-2">
<div
v-for="session in votes.sessions"
:key="session.id"
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
>
<div>
<span class="font-mono text-xs text-gray-600 dark:text-gray-400">
{{ session.id.slice(0, 8) }}...
</span>
<StatusBadge :status="session.status" type="vote" class="ml-2" />
</div>
<div class="text-sm text-gray-500">
{{ session.votes_total }} votes
</div>
</div>
</div>
</UCard>
</template>
</div>
</template>