Add markdown editor toolbar and data year display on import page

CMS editor: formatting toolbar with H1-H3, bold, italic, strikethrough,
links, images, lists, blockquotes, code blocks, horizontal rules.
Keyboard shortcuts (Ctrl+B/I/D/K). Improved markdown preview rendering.

Import page: shows current data summary with year badge, stats grid,
last import date. Year input for new imports. Preview with sample table.

Backend: added data_year and data_imported_at fields to TariffParams,
returned in stats endpoint. Import sets data_imported_at automatically.
Seed sets data_year=2018.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-21 18:05:18 +01:00
parent 1365f4c86c
commit 2af95ebcf1
6 changed files with 564 additions and 29 deletions

View File

@@ -47,6 +47,8 @@ class TariffParams(Base):
recettes = Column(Float, default=75000.0)
pmax = Column(Float, default=20.0)
vmax = Column(Float, default=2100.0)
data_year = Column(Integer, nullable=True)
data_imported_at = Column(DateTime, nullable=True)
commune = relationship("Commune", back_populates="tariff_params")

View File

@@ -1,3 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
@@ -6,7 +8,7 @@ import io
import numpy as np
from app.database import get_db
from app.models import Commune, Household, AdminUser
from app.models import Commune, Household, TariffParams, AdminUser
from app.schemas import HouseholdOut, HouseholdStats, ImportPreview, ImportResult
from app.services.auth_service import get_current_admin
from app.services.import_service import parse_import_file, import_households, generate_template_csv
@@ -31,6 +33,12 @@ async def household_stats(slug: str, db: AsyncSession = Depends(get_db)):
if not commune:
raise HTTPException(status_code=404, detail="Commune introuvable")
# Load tariff params for data_year
params_result = await db.execute(
select(TariffParams).where(TariffParams.commune_id == commune.id)
)
params = params_result.scalar_one_or_none()
hh_result = await db.execute(
select(Household).where(Household.commune_id == commune.id)
)
@@ -40,6 +48,8 @@ async def household_stats(slug: str, db: AsyncSession = Depends(get_db)):
return HouseholdStats(
total=0, rs_count=0, rp_count=0, pro_count=0,
total_volume=0, avg_volume=0, median_volume=0, voted_count=0,
data_year=params.data_year if params else None,
data_imported_at=params.data_imported_at if params else None,
)
volumes = [h.volume_m3 for h in households]
@@ -52,6 +62,8 @@ async def household_stats(slug: str, db: AsyncSession = Depends(get_db)):
avg_volume=float(np.mean(volumes)),
median_volume=float(np.median(volumes)),
voted_count=sum(1 for h in households if h.has_voted),
data_year=params.data_year if params else None,
data_imported_at=params.data_imported_at if params else None,
)
@@ -82,6 +94,7 @@ async def preview_import(
async def do_import(
slug: str,
file: UploadFile = File(...),
data_year: int | None = None,
db: AsyncSession = Depends(get_db),
admin: AdminUser = Depends(get_current_admin),
):
@@ -97,6 +110,18 @@ async def do_import(
raise HTTPException(status_code=400, detail={"errors": parse_errors})
created, import_errors = await import_households(db, commune.id, df)
# Update data_imported_at and optional data_year on tariff params
params_result = await db.execute(
select(TariffParams).where(TariffParams.commune_id == commune.id)
)
params = params_result.scalar_one_or_none()
if params:
params.data_imported_at = datetime.utcnow()
if data_year is not None:
params.data_year = data_year
await db.commit()
return ImportResult(created=created, errors=import_errors)

View File

@@ -56,6 +56,7 @@ class TariffParamsUpdate(BaseModel):
recettes: float | None = None
pmax: float | None = None
vmax: float | None = None
data_year: int | None = None
class TariffParamsOut(BaseModel):
@@ -64,6 +65,8 @@ class TariffParamsOut(BaseModel):
recettes: float
pmax: float
vmax: float
data_year: int | None = None
data_imported_at: datetime | None = None
model_config = {"from_attributes": True}
@@ -91,6 +94,8 @@ class HouseholdStats(BaseModel):
avg_volume: float
median_volume: float
voted_count: int
data_year: int | None = None
data_imported_at: datetime | None = None
class ImportPreview(BaseModel):

View File

@@ -3,6 +3,7 @@
import asyncio
import sys
import os
from datetime import datetime
sys.path.insert(0, os.path.dirname(__file__))
@@ -44,6 +45,8 @@ async def seed():
recettes=75000,
pmax=20,
vmax=2100,
data_year=2018,
data_imported_at=datetime.utcnow(),
)
db.add(params)