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>
120 lines
2.9 KiB
Vue
120 lines
2.9 KiB
Vue
<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>
|