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>
78 lines
2.4 KiB
Vue
78 lines
2.4 KiB
Vue
<template>
|
|
<div>
|
|
<div class="page-header">
|
|
<NuxtLink :to="`/admin/communes/${slug}`" style="color: var(--color-text-muted);">← {{ slug }}</NuxtLink>
|
|
<h1>Paramètres tarifs</h1>
|
|
</div>
|
|
|
|
<div v-if="saved" class="alert alert-success">Paramètres enregistrés.</div>
|
|
<div v-if="error" class="alert alert-error">{{ error }}</div>
|
|
|
|
<div class="card" style="max-width: 600px;">
|
|
<form @submit.prevent="save">
|
|
<div class="form-group">
|
|
<label>Recettes cibles (€)</label>
|
|
<input v-model.number="form.recettes" type="number" class="form-input" step="1000" min="0" />
|
|
</div>
|
|
<div class="grid grid-2">
|
|
<div class="form-group">
|
|
<label>Abonnement RP/PRO (€)</label>
|
|
<input v-model.number="form.abop" type="number" class="form-input" step="1" min="0" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Abonnement RS (€)</label>
|
|
<input v-model.number="form.abos" type="number" class="form-input" step="1" min="0" />
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-2">
|
|
<div class="form-group">
|
|
<label>Prix max/m³ (€)</label>
|
|
<input v-model.number="form.pmax" type="number" class="form-input" step="0.5" min="0" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Volume max (m³)</label>
|
|
<input v-model.number="form.vmax" type="number" class="form-input" step="100" min="0" />
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" :disabled="loading">
|
|
Enregistrer
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
definePageMeta({ middleware: 'admin' })
|
|
|
|
const route = useRoute()
|
|
const api = useApi()
|
|
const slug = route.params.slug as string
|
|
|
|
const form = reactive({ recettes: 75000, abop: 100, abos: 100, pmax: 20, vmax: 2100 })
|
|
const loading = ref(false)
|
|
const saved = ref(false)
|
|
const error = ref('')
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const params = await api.get<typeof form>(`/communes/${slug}/params`)
|
|
Object.assign(form, params)
|
|
} catch {}
|
|
})
|
|
|
|
async function save() {
|
|
loading.value = true
|
|
saved.value = false
|
|
error.value = ''
|
|
try {
|
|
await api.put(`/communes/${slug}/params`, form)
|
|
saved.value = true
|
|
} catch (e: any) {
|
|
error.value = e.message
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|