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:
111
frontend/app/pages/admin/communes/[slug]/import.vue
Normal file
111
frontend/app/pages/admin/communes/[slug]/import.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<NuxtLink :to="`/admin/communes/${slug}`" style="color: var(--color-text-muted);">← {{ slug }}</NuxtLink>
|
||||
<h1>Import des foyers</h1>
|
||||
</div>
|
||||
|
||||
<div class="card" style="max-width: 700px;">
|
||||
<p style="margin-bottom: 1rem;">
|
||||
Importez un fichier CSV ou XLSX avec les colonnes :
|
||||
<code>identifier, status, volume_m3, price_eur</code>
|
||||
</p>
|
||||
|
||||
<a :href="`${apiBase}/communes/${slug}/households/template`" class="btn btn-secondary" style="margin-bottom: 1rem;">
|
||||
Télécharger le template
|
||||
</a>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Fichier (CSV ou XLSX)</label>
|
||||
<input type="file" accept=".csv,.xlsx,.xls" @change="onFileChange" class="form-input" />
|
||||
</div>
|
||||
|
||||
<!-- Preview -->
|
||||
<div v-if="preview" style="margin: 1rem 0;">
|
||||
<div v-if="preview.errors.length" class="alert alert-error">
|
||||
<strong>Erreurs :</strong>
|
||||
<ul style="margin: 0.5rem 0 0 1rem;">
|
||||
<li v-for="err in preview.errors" :key="err">{{ err }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else class="alert alert-success">
|
||||
{{ preview.valid_rows }} foyers valides prêts à importer.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result -->
|
||||
<div v-if="result" class="alert alert-success">
|
||||
{{ result.created }} foyers importés.
|
||||
<span v-if="result.errors.length"> ({{ result.errors.length }} avertissements)</span>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
:disabled="!file || previewLoading"
|
||||
@click="doPreview"
|
||||
>
|
||||
Vérifier
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!file || importLoading || (preview && preview.errors.length > 0)"
|
||||
@click="doImport"
|
||||
>
|
||||
Importer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: 'admin' })
|
||||
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
const api = useApi()
|
||||
const slug = route.params.slug as string
|
||||
const apiBase = config.public.apiBase as string
|
||||
|
||||
const file = ref<File | null>(null)
|
||||
const preview = ref<any>(null)
|
||||
const result = ref<any>(null)
|
||||
const previewLoading = ref(false)
|
||||
const importLoading = ref(false)
|
||||
|
||||
function onFileChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement
|
||||
file.value = input.files?.[0] || null
|
||||
preview.value = null
|
||||
result.value = null
|
||||
}
|
||||
|
||||
async function doPreview() {
|
||||
if (!file.value) return
|
||||
previewLoading.value = true
|
||||
const fd = new FormData()
|
||||
fd.append('file', file.value)
|
||||
try {
|
||||
preview.value = await api.post(`/communes/${slug}/households/import/preview`, fd)
|
||||
} catch (e: any) {
|
||||
preview.value = { valid_rows: 0, errors: [e.message], sample: [] }
|
||||
} finally {
|
||||
previewLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function doImport() {
|
||||
if (!file.value) return
|
||||
importLoading.value = true
|
||||
const fd = new FormData()
|
||||
fd.append('file', file.value)
|
||||
try {
|
||||
result.value = await api.post(`/communes/${slug}/households/import`, fd)
|
||||
} catch (e: any) {
|
||||
result.value = { created: 0, errors: [e.message] }
|
||||
} finally {
|
||||
importLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user