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>
This commit is contained in:
Yvv
2026-02-21 15:26:02 +01:00
commit b30e54a8f7
67 changed files with 16723 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
<template>
<div>
<!-- Hero -->
<section class="hero">
<h1>Tarification participative de l'eau</h1>
<p>
Dessinez votre courbe de tarification idéale et participez aux choix de votre commune.
</p>
</section>
<!-- Communes publiques -->
<section>
<h2 style="margin-bottom: 1rem;">Communes participantes</h2>
<div v-if="loading" style="text-align: center; padding: 2rem;">
<div class="spinner" style="margin: 0 auto;"></div>
</div>
<div v-else-if="error" class="alert alert-error">
{{ error }}
</div>
<div v-else-if="communes.length === 0" class="alert alert-info">
Aucune commune active pour le moment.
</div>
<div v-else class="grid grid-3">
<NuxtLink
v-for="commune in communes"
:key="commune.id"
:to="`/commune/${commune.slug}`"
class="card commune-card"
>
<h3>{{ commune.name }}</h3>
<p>{{ commune.description }}</p>
</NuxtLink>
</div>
</section>
<!-- Accès administration -->
<section style="margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--color-border);">
<div class="grid grid-2">
<div class="card">
<h3>Espace commune</h3>
<p style="margin: 0.75rem 0; color: var(--color-text-muted); font-size: 0.875rem;">
Vous êtes responsable d'une commune ? Connectez-vous pour gérer vos données,
paramétrer la tarification et consulter les votes.
</p>
<NuxtLink to="/login/commune" class="btn btn-secondary">Connexion commune</NuxtLink>
</div>
<div class="card">
<h3>Super administration</h3>
<p style="margin: 0.75rem 0; color: var(--color-text-muted); font-size: 0.875rem;">
Gestion globale des communes et des administrateurs.
</p>
<NuxtLink to="/login/admin" class="btn btn-secondary">Connexion admin</NuxtLink>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
const api = useApi()
const communes = ref<any[]>([])
const loading = ref(true)
const error = ref('')
onMounted(async () => {
try {
communes.value = await api.get<any[]>('/communes/')
} catch (e: any) {
error.value = e.message
} finally {
loading.value = false
}
})
</script>
<style scoped>
.hero {
text-align: center;
padding: 2rem 0 2.5rem;
}
.hero h1 {
font-size: 2rem;
font-weight: 800;
margin-bottom: 0.5rem;
}
.hero p {
color: var(--color-text-muted);
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
}
.commune-card {
cursor: pointer;
transition: box-shadow 0.15s;
}
.commune-card:hover {
box-shadow: var(--shadow-md);
text-decoration: none;
}
.commune-card h3 {
margin-bottom: 0.5rem;
color: var(--color-primary);
}
.commune-card p {
font-size: 0.875rem;
color: var(--color-text-muted);
}
</style>