Files
Yvv b30e54a8f7 Initial commit: SejeteralO water tarification platform
Full-stack app for participatory water pricing using Bezier curves.
- Backend: FastAPI + SQLAlchemy + SQLite with JWT auth
- Frontend: Nuxt 4 + TypeScript with interactive SVG editor
- Math engine: cubic Bezier tarification with Cardano solver
- Admin: commune management, household import, vote monitoring, CMS
- Citizen: interactive curve editor, vote submission
- Docker-compose deployment ready

Includes fixes for:
- Impact table snake_case/camelCase property mismatch
- CMS content backend API + frontend editor (was stub)
- Admin route protection middleware
- Public content display on commune page
- Vote confirmation page link fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 15:26:02 +01:00

90 lines
2.9 KiB
Vue

<template>
<div>
<div class="page-header">
<NuxtLink :to="`/admin/communes/${slug}`" style="color: var(--color-text-muted);">&larr; {{ slug }}</NuxtLink>
<h1>Votes</h1>
</div>
<!-- Median -->
<div v-if="median" class="card" style="margin-bottom: 1.5rem;">
<h3>Médiane ({{ median.vote_count }} votes)</h3>
<div class="grid grid-4" style="margin-top: 1rem;">
<div><strong>vinf:</strong> {{ median.vinf.toFixed(0) }}</div>
<div><strong>a:</strong> {{ median.a.toFixed(3) }}</div>
<div><strong>b:</strong> {{ median.b.toFixed(3) }}</div>
<div><strong>c:</strong> {{ median.c.toFixed(3) }}</div>
<div><strong>d:</strong> {{ median.d.toFixed(3) }}</div>
<div><strong>e:</strong> {{ median.e.toFixed(3) }}</div>
<div><strong>p0:</strong> {{ median.computed_p0.toFixed(2) }} /</div>
</div>
</div>
<!-- Vote overlay chart placeholder -->
<div class="card" style="margin-bottom: 1.5rem;">
<h3>Overlay des courbes</h3>
<VoteOverlayChart v-if="overlayData.length" :votes="overlayData" :slug="slug" />
<p v-else style="color: var(--color-text-muted); padding: 2rem; text-align: center;">
Aucun vote pour le moment.
</p>
</div>
<!-- Vote list -->
<div class="card">
<h3 style="margin-bottom: 1rem;">Liste des votes actifs</h3>
<table class="table" v-if="votes.length">
<thead>
<tr>
<th>Foyer</th>
<th>vinf</th>
<th>a</th>
<th>b</th>
<th>c</th>
<th>d</th>
<th>e</th>
<th>p0</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr v-for="v in votes" :key="v.id">
<td>#{{ v.household_id }}</td>
<td>{{ v.vinf.toFixed(0) }}</td>
<td>{{ v.a.toFixed(2) }}</td>
<td>{{ v.b.toFixed(2) }}</td>
<td>{{ v.c.toFixed(2) }}</td>
<td>{{ v.d.toFixed(2) }}</td>
<td>{{ v.e.toFixed(2) }}</td>
<td>{{ v.computed_p0?.toFixed(2) }}</td>
<td>{{ new Date(v.submitted_at).toLocaleDateString() }}</td>
</tr>
</tbody>
</table>
<p v-else style="color: var(--color-text-muted);">Aucun vote actif.</p>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({ middleware: 'admin' })
const route = useRoute()
const api = useApi()
const slug = route.params.slug as string
const votes = ref<any[]>([])
const median = ref<any>(null)
const overlayData = ref<any[]>([])
onMounted(async () => {
try {
[votes.value, overlayData.value] = await Promise.all([
api.get<any[]>(`/communes/${slug}/votes`),
api.get<any[]>(`/communes/${slug}/votes/overlay`),
])
} catch {}
try {
median.value = await api.get(`/communes/${slug}/votes/median`)
} catch {}
})
</script>