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:
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user