From 6caea1b809a403120b0fdcb9fe16f181b8d964a1 Mon Sep 17 00:00:00 2001 From: Yvv Date: Mon, 23 Feb 2026 00:46:48 +0100 Subject: [PATCH] Restructure citizen vote page + add vote deadline Reorganize the citizen page (/commune/[slug]) for voters: full-width interactive chart with "Population" zoom by default, separate auth and vote sections, countdown timer for vote deadline. Backend: add vote_deadline column to communes with Alembic migration. Admin: add deadline configuration card with datetime-local input. Co-Authored-By: Claude Opus 4.6 --- ...cc7e3efb9_add_vote_deadline_to_communes.py | 30 + backend/app/models/models.py | 1 + backend/app/routers/communes.py | 2 + backend/app/schemas/schemas.py | 2 + .../components/charts/VoteOverlayChart.vue | 16 +- .../app/pages/admin/communes/[slug]/index.vue | 47 ++ frontend/app/pages/commune/[slug]/index.vue | 542 +++++++++++------- 7 files changed, 434 insertions(+), 206 deletions(-) create mode 100644 backend/alembic/versions/0d7cc7e3efb9_add_vote_deadline_to_communes.py diff --git a/backend/alembic/versions/0d7cc7e3efb9_add_vote_deadline_to_communes.py b/backend/alembic/versions/0d7cc7e3efb9_add_vote_deadline_to_communes.py new file mode 100644 index 0000000..efb4816 --- /dev/null +++ b/backend/alembic/versions/0d7cc7e3efb9_add_vote_deadline_to_communes.py @@ -0,0 +1,30 @@ +"""add vote_deadline to communes + +Revision ID: 0d7cc7e3efb9 +Revises: 25f534648ea7 +Create Date: 2026-02-23 00:37:23.451137 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '0d7cc7e3efb9' +down_revision: Union[str, None] = '25f534648ea7' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('communes', sa.Column('vote_deadline', sa.DateTime(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('communes', 'vote_deadline') + # ### end Alembic commands ### diff --git a/backend/app/models/models.py b/backend/app/models/models.py index b9b9438..a209f52 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -29,6 +29,7 @@ class Commune(Base): description = Column(Text, default="") is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=datetime.utcnow) + vote_deadline = Column(DateTime, nullable=True) tariff_params = relationship("TariffParams", back_populates="commune", uselist=False) households = relationship("Household", back_populates="commune") diff --git a/backend/app/routers/communes.py b/backend/app/routers/communes.py index a4f3d53..302e117 100644 --- a/backend/app/routers/communes.py +++ b/backend/app/routers/communes.py @@ -67,6 +67,8 @@ async def update_commune( commune.description = data.description if data.is_active is not None: commune.is_active = data.is_active + if data.vote_deadline is not None: + commune.vote_deadline = data.vote_deadline await db.commit() await db.refresh(commune) diff --git a/backend/app/schemas/schemas.py b/backend/app/schemas/schemas.py index cce8571..c6a3509 100644 --- a/backend/app/schemas/schemas.py +++ b/backend/app/schemas/schemas.py @@ -35,6 +35,7 @@ class CommuneUpdate(BaseModel): name: str | None = None description: str | None = None is_active: bool | None = None + vote_deadline: datetime | None = None class CommuneOut(BaseModel): @@ -44,6 +45,7 @@ class CommuneOut(BaseModel): description: str is_active: bool created_at: datetime + vote_deadline: datetime | None = None model_config = {"from_attributes": True} diff --git a/frontend/app/components/charts/VoteOverlayChart.vue b/frontend/app/components/charts/VoteOverlayChart.vue index 592c8dc..77cff19 100644 --- a/frontend/app/components/charts/VoteOverlayChart.vue +++ b/frontend/app/components/charts/VoteOverlayChart.vue @@ -19,14 +19,14 @@ - - + + - - + + @@ -108,5 +108,13 @@ onMounted(async () => { .overlay-chart svg { width: 100%; height: auto; + background: white; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.overlay-chart svg path { + stroke-linecap: round; + stroke-linejoin: round; } diff --git a/frontend/app/pages/admin/communes/[slug]/index.vue b/frontend/app/pages/admin/communes/[slug]/index.vue index 1df709e..aaeb1b8 100644 --- a/frontend/app/pages/admin/communes/[slug]/index.vue +++ b/frontend/app/pages/admin/communes/[slug]/index.vue @@ -29,6 +29,29 @@ + +
+

Parametres du vote

+
+
+ + +
+ + Enregistre ! +
+
+

Statistiques foyers

@@ -145,6 +168,26 @@ const search = ref('') const page = ref(1) const perPage = 20 +// Vote deadline +const voteDeadline = ref('') +const savingDeadline = ref(false) +const deadlineSaved = ref(false) + +async function saveDeadline() { + savingDeadline.value = true + deadlineSaved.value = false + try { + await api.put(`/communes/${slug}`, { + vote_deadline: voteDeadline.value ? new Date(voteDeadline.value).toISOString() : null, + }) + deadlineSaved.value = true + } catch (e: any) { + alert(e.message || 'Erreur') + } finally { + savingDeadline.value = false + } +} + const filteredHouseholds = computed(() => { if (!search.value) return households.value const q = search.value.toLowerCase() @@ -173,6 +216,10 @@ function statusBadge(status: string) { onMounted(async () => { try { commune.value = await api.get(`/communes/${slug}`) + if (commune.value.vote_deadline) { + // Format for datetime-local input (YYYY-MM-DDTHH:mm) + voteDeadline.value = commune.value.vote_deadline.slice(0, 16) + } } catch (e: any) { return } diff --git a/frontend/app/pages/commune/[slug]/index.vue b/frontend/app/pages/commune/[slug]/index.vue index 258dba4..aae4e5b 100644 --- a/frontend/app/pages/commune/[slug]/index.vue +++ b/frontend/app/pages/commune/[slug]/index.vue @@ -1,5 +1,6 @@