Ajuster gestion des groupes multi-groupes

This commit is contained in:
Daniel Allaire 2026-05-31 23:30:57 -04:00
parent 1946b8741e
commit ab6ba0c13e
14 changed files with 99 additions and 254 deletions

View file

@ -10,8 +10,8 @@ from app.services.admin_database import (
creer_groupe,
initialiser_demo,
modifier_groupe,
reinitialiser_groupe,
reinitialiser_demo,
remettre_a_zero,
supprimer_groupe,
)
from app.models.groupe import Groupe
@ -30,7 +30,6 @@ class SysadminLoginRequest(BaseModel):
class GroupeAdminRequest(BaseModel):
nom: str
district: str = "87-16"
region: str = "87"
adresse_local: str | None = None
jour_reunion: str | None = "mardi"
heure_reunion: str | None = "19:30"
@ -55,14 +54,6 @@ async def sysadmin_login(req: SysadminLoginRequest, db: AsyncSession = Depends(g
raise HTTPException(status_code=401, detail="PIN Sysadmin invalide")
@router.post("/reset")
async def reset_database(
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
return await remettre_a_zero(db)
@router.post("/demo")
async def demo_database(
_sysadmin=Depends(require_sysadmin),
@ -79,13 +70,26 @@ async def reset_demo_database(
return await reinitialiser_demo(db)
@router.post("/groupes/{groupe_id}/reset")
async def reset_groupe_admin(
groupe_id: str,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
return await reinitialiser_groupe(db, groupe_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/groupes")
async def liste_groupes_admin(
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
await initialiser_demo(db)
groupes = (await db.execute(select(Groupe).order_by(Groupe.nom))).scalars().all()
groupes = (await db.execute(select(Groupe))).scalars().all()
groupes.sort(key=lambda g: (g.nom == "Groupe Démonstration", g.nom.lower()))
out = []
for groupe in groupes:
nb_membres = (await db.execute(
@ -101,7 +105,6 @@ async def liste_groupes_admin(
"id": str(groupe.id),
"nom": groupe.nom,
"district": groupe.district,
"region": groupe.region,
"adresse_local": groupe.adresse_local,
"jour_reunion": groupe.jour_reunion,
"heure_reunion": groupe.heure_reunion.strftime("%H:%M") if groupe.heure_reunion else None,
@ -112,8 +115,8 @@ async def liste_groupes_admin(
"membres": nb_membres,
"reunions": nb_reunions,
"traces": nb_traces,
"demo": groupe.nom == "Groupe Résilience",
"protege": groupe.nom == "Groupe Résilience",
"demo": groupe.nom == "Groupe Démonstration",
"protege": groupe.nom == "Groupe Démonstration",
})
return out

View file

@ -29,8 +29,9 @@ async def health(db: AsyncSession = Depends(get_db)):
@router.get("/groupes")
async def liste_groupes(db: AsyncSession = Depends(get_db)):
await initialiser_demo(db)
result = await db.execute(select(Groupe).order_by(Groupe.nom))
result = await db.execute(select(Groupe))
groupes = result.scalars().all()
groupes.sort(key=lambda g: (g.nom == "Groupe Démonstration", g.nom.lower()))
return [{
"id": str(g.id),
"nom": g.nom,

View file

@ -352,7 +352,6 @@ async def rapport_assemblee_pdf(
item for item in [
groupe.adresse_local if groupe else None,
f"District {groupe.district}" if groupe and groupe.district else None,
f"Région {groupe.region}" if groupe and groupe.region else None,
] if item
])
html = f"""<!doctype html><html><head><meta charset="utf-8"><style>{css}</style></head><body>

View file

@ -119,7 +119,7 @@ async def rapport_finances(
html_content = f"""<!DOCTYPE html><html><head><style>{_css()}</style></head><body>
<div class="header">
<div class="groupe">{groupe.nom if groupe else 'Groupe'}</div>
<div class="sub">District {groupe.district if groupe else '87-16'} · Région {groupe.region if groupe else '87'}</div>
<div class="sub">District {groupe.district if groupe else '87-16'}</div>
</div>
<h1>Rapport financier — {MOIS_FR[mois]} {annee}</h1>
@ -190,7 +190,7 @@ async def rapport_reunions(
html_content = f"""<!DOCTYPE html><html><head><style>{_css()}</style></head><body>
<div class="header">
<div class="groupe">{groupe.nom if groupe else 'Groupe'}</div>
<div class="sub">District {groupe.district if groupe else '87-16'} · Région {groupe.region if groupe else '87'}</div>
<div class="sub">District {groupe.district if groupe else '87-16'}</div>
</div>
<h1>Réunions — {MOIS_FR[mois]} {annee}</h1>
<table><tr><th>Date</th><th>Type</th><th>Animateur</th><th>Thème</th><th>Annonces</th></tr>
@ -391,7 +391,7 @@ async def rapport_resolutions(
<style>.muted {{ color: #777; font-size: 8pt; }}</style>
<div class="header">
<div class="groupe">{groupe.nom if groupe else 'Groupe'}</div>
<div class="sub">District {groupe.district if groupe else '87-16'} · Région {groupe.region if groupe else '87'}</div>
<div class="sub">District {groupe.district if groupe else '87-16'}</div>
</div>
<h1>Registre des résolutions — {annee}</h1>
<p>Décisions prises en Groupe</p>
@ -760,12 +760,10 @@ async def _html_etat_resultats(
groupe_nom = groupe.nom if groupe and groupe.nom else "Groupe"
district = str(groupe.district if groupe and groupe.district else "87-16")
region = str(groupe.region if groupe and groupe.region else "87")
html_content = f"""<!DOCTYPE html><html><head><style>{css}</style></head><body>
<div class="header">
<div class="groupe">{escape(groupe_nom)}</div>
<div class="sub">District {escape(district)} · Région {escape(region)}</div>
<div class="sub">District {escape(district)}</div>
</div>
<h1>État des résultats — {MOIS_FR[mois]} {annee}</h1>

View file

@ -394,7 +394,6 @@ async def rapport_reunion_pdf(
item for item in [
groupe.adresse_local if groupe else None,
f"District {groupe.district}" if groupe and groupe.district else None,
f"Région {groupe.region}" if groupe and groupe.region else None,
] if item
])
ventes_lit_html = (

View file

@ -40,6 +40,9 @@ from app.models.transaction_comptable import TransactionComptable
from app.models.vente_litterature import VenteLitterature
from app.models.vente_jeton import VenteJeton
DEMO_GROUP_NAME = "Groupe Démonstration"
OLD_DEMO_GROUP_NAMES = ("Groupe Résilience",)
POSTES = [
("RSG", "executif", "Représente le groupe auprès du district et rapporte les informations de service.", "2 ans de stabilité", 1, ["M3", "C2", "C5"]),
@ -106,8 +109,21 @@ async def remettre_a_zero(db: AsyncSession) -> dict:
}
async def _groupe_demo(db: AsyncSession) -> Groupe | None:
result = await db.execute(select(Groupe).where(Groupe.nom.in_([DEMO_GROUP_NAME, *OLD_DEMO_GROUP_NAMES])))
groupe = result.scalars().first()
if groupe and groupe.nom != DEMO_GROUP_NAME:
groupe.nom = DEMO_GROUP_NAME
await db.flush()
return groupe
def _est_groupe_demo(groupe: Groupe | None) -> bool:
return bool(groupe and groupe.nom in {DEMO_GROUP_NAME, *OLD_DEMO_GROUP_NAMES})
async def initialiser_demo(db: AsyncSession) -> dict:
existant = (await db.execute(select(Groupe).where(Groupe.nom == "Groupe Résilience"))).scalar_one_or_none()
existant = await _groupe_demo(db)
if existant:
membres_existants = await _membres_par_courriel(db, existant.id)
return {
@ -121,7 +137,7 @@ async def initialiser_demo(db: AsyncSession) -> dict:
groupe, sysadmin, postes, litterature = await _creer_base(
db,
nom_groupe="Groupe Résilience",
nom_groupe=DEMO_GROUP_NAME,
adresse_local="Salle communautaire Saint-Louis, Montréal",
)
membres = {sysadmin.courriel: sysadmin}
@ -141,7 +157,7 @@ async def initialiser_demo(db: AsyncSession) -> dict:
async def reinitialiser_demo(db: AsyncSession) -> dict:
groupe = (await db.execute(select(Groupe).where(Groupe.nom == "Groupe Résilience"))).scalar_one_or_none()
groupe = await _groupe_demo(db)
if not groupe:
return await initialiser_demo(db)
await _vider_groupe(db, groupe.id)
@ -161,16 +177,28 @@ async def reinitialiser_demo(db: AsyncSession) -> dict:
}
async def reinitialiser_groupe(db: AsyncSession, groupe_id) -> dict:
groupe = await db.get(Groupe, groupe_id)
if not groupe:
raise ValueError("Groupe introuvable")
if _est_groupe_demo(groupe):
return await reinitialiser_demo(db)
await _vider_groupe(db, groupe.id)
await _peupler_groupe_base(db, groupe)
await db.flush()
return {"message": f"Groupe {groupe.nom} réinitialisé", "groupe_id": groupe.id}
async def creer_groupe(db: AsyncSession, data: dict) -> Groupe:
nom = (data.get("nom") or "").strip()
if not nom:
raise ValueError("Nom requis")
if nom == "Groupe Résilience":
if nom == DEMO_GROUP_NAME or nom in OLD_DEMO_GROUP_NAMES:
raise ValueError("Ce nom est réservé au groupe de démonstration")
groupe = Groupe(
nom=nom,
district=(data.get("district") or "87-16").strip(),
region=(data.get("region") or "87").strip(),
region="",
adresse_local=(data.get("adresse_local") or "").strip() or None,
jour_reunion=(data.get("jour_reunion") or "mardi").strip() or None,
heure_reunion=_parse_heure(data.get("heure_reunion")),
@ -190,12 +218,12 @@ async def modifier_groupe(db: AsyncSession, groupe_id, data: dict) -> Groupe:
if not groupe:
raise ValueError("Groupe introuvable")
nom = (data.get("nom") or groupe.nom).strip()
if groupe.nom == "Groupe Résilience" and nom != "Groupe Résilience":
if _est_groupe_demo(groupe) and nom != DEMO_GROUP_NAME:
raise ValueError("Le groupe de démonstration ne peut pas être renommé")
if nom == "Groupe Résilience" and groupe.nom != "Groupe Résilience":
if (nom == DEMO_GROUP_NAME or nom in OLD_DEMO_GROUP_NAMES) and not _est_groupe_demo(groupe):
raise ValueError("Ce nom est réservé au groupe de démonstration")
groupe.nom = nom
for attr in ["district", "region", "adresse_local", "jour_reunion", "assemblee_affaires_ordre", "format_reunion", "lien_aa87"]:
for attr in ["district", "adresse_local", "jour_reunion", "assemblee_affaires_ordre", "format_reunion", "lien_aa87"]:
if attr in data:
valeur = data.get(attr)
setattr(groupe, attr, valeur.strip() if isinstance(valeur, str) and valeur.strip() else None)
@ -209,7 +237,7 @@ async def supprimer_groupe(db: AsyncSession, groupe_id) -> dict:
groupe = await db.get(Groupe, groupe_id)
if not groupe:
raise ValueError("Groupe introuvable")
if groupe.nom == "Groupe Résilience":
if _est_groupe_demo(groupe):
raise ValueError("Le groupe de démonstration est protégé")
nb_groupes = (await db.execute(select(Groupe))).scalars().all()
if len(nb_groupes) <= 1:
@ -222,13 +250,13 @@ async def supprimer_groupe(db: AsyncSession, groupe_id) -> dict:
async def _creer_base(
db: AsyncSession,
nom_groupe: str = "Groupe Méditation",
nom_groupe: str = DEMO_GROUP_NAME,
adresse_local: str = "Salle communautaire, Montréal",
):
groupe = Groupe(
nom=nom_groupe,
district="87-16",
region="87",
region="",
adresse_local=adresse_local,
jour_reunion="mardi",
heure_reunion=time(19, 30),

View file

@ -1,22 +1,16 @@
"""
Script d'initialisation — Groupe Résilience
Crée les postes, le groupe initial et le premier membre.
Script d'initialisation.
Crée le schéma et le groupe de démonstration initial.
Usage: cd /opt/groupe-meditation/backend && venv/bin/python ../deploy/seed.py
"""
import asyncio
import sys
import os
import uuid
import secrets
import csv
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
# Ajouter le backend au path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
from app.core.database import engine, Base, async_session
from app.core.security import hash_pin
from app.services.admin_database import initialiser_demo
# Importer TOUS les modèles pour que create_all crée toutes les tables
from app.models.groupe import Groupe
@ -48,173 +42,20 @@ from app.models.mouvement_reserve import MouvementReserve
from app.models.destinataire_contribution import DestinataireContribution
POSTES = [
("RSG", "executif", "Représentant aux Services du Groupe", "730 days", "2 ans sobriété", 1),
("RSG Substitut(e)", "executif", "Remplace le RSG, se prépare à assumer le rôle", "730 days", "1 an sobriété", 2),
("Trésorier(ière)", "executif", "Gestion financière, 7e Tradition", "548 days", "1 an sobriété", 3),
("Secrétaire", "executif", "PV, correspondance, mémoire du groupe", "548 days", "6 mois sobriété", 4),
("Animateur(trice)", "service", "Anime les réunions régulières", None, "6 mois sobriété", 5),
("Responsable accueil", "physique", "Accueil des membres et nouveaux", "365 days", "Sobriété stable", 6),
("Responsable café", "physique", "Café, entretien de la salle", "365 days", "Aucun", 7),
("Responsable littérature", "service", "Inventaire et vente de littérature AA", "365 days", "Connaissance littérature AA", 8),
("Responsable jetons", "service", "Jetons et anniversaires de sobriété", "365 days", "Aucun", 9),
("Responsable clés", "physique", "Ouverture et fermeture du local", None, "Fiabilité", 10),
]
def charger_catalogue_litterature():
chemin = Path(__file__).resolve().parents[1] / "data" / "import" / "Liste-de-prix-rev.-Fevrier-2026.csv"
if not chemin.exists():
return [
{"titre": "Les Alcooliques anonymes (Gros Livre)", "categorie": "livre", "prix_coutant": 12.00, "code_bsg": "B-1"},
{"titre": "Les Douze Étapes et les Douze Traditions", "categorie": "livre", "prix_coutant": 11.00, "code_bsg": "B-2"},
{"titre": "Vivre sans alcool", "categorie": "livre", "prix_coutant": 9.00, "code_bsg": "B-7"},
{"titre": "Les AA sont-ils pour vous?", "categorie": "brochure", "prix_coutant": 0.00, "code_bsg": "P-3"},
{"titre": "Un nouveau arrive", "categorie": "depliant", "prix_coutant": 0.00, "code_bsg": "P-16"},
]
with chemin.open(newline="", encoding="utf-8") as fichier:
return [
{
"code_bsg": row["code_bsg"].strip() or None,
"titre": row["titre"].strip(),
"categorie": row["categorie"].strip(),
"prix_coutant": float(row["prix_coutant"]) if row["prix_coutant"].strip() else None,
}
for row in csv.DictReader(fichier)
if row.get("titre", "").strip()
]
async def seed():
# Créer les tables
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with async_session() as db:
# ── Créer le groupe ──
print("\n═══ Initialisation du Groupe Résilience ═══\n")
groupe = Groupe(
nom="Groupe Résilience",
district="87-16",
region="87",
adresse_local="Salle communautaire Saint-Louis, Montréal",
jour_reunion="mardi",
heure_reunion=datetime.strptime("19:30", "%H:%M").time(),
format_reunion="Discussion ouverte",
lien_aa87="https://aa87.org",
)
db.add(groupe)
await db.flush()
print(f"✓ Groupe créé: {groupe.nom} (district {groupe.district})")
# ── Créer les postes ──
postes_map = {}
for nom, cat, desc, duree, prereq, ordre in POSTES:
poste = Poste(
groupe_id=groupe.id,
nom=nom,
categorie=cat,
description=desc,
prerequis=prereq,
ordre_affichage=ordre,
)
db.add(poste)
await db.flush()
postes_map[nom] = poste
print(f" ✓ Poste: {nom} ({cat})")
# ── Premier membre ──
print("\n── Premier membre (vous) ──")
prenom = input(" Prénom: ").strip().title()
nom_membre = input(" Nom (optionnel, Entrée pour passer): ").strip()
nom_membre = nom_membre.title() if nom_membre else None
courriel = input(" Courriel: ").strip().lower()
while not courriel or ("@" not in courriel and courriel != "sysadmin"):
courriel = input(" Courriel invalide. Réessayer: ").strip().lower()
telephone = input(" Téléphone: ").strip()
while not telephone:
telephone = input(" Téléphone obligatoire: ").strip()
pin = input(" PIN (4 chiffres): ").strip()
while not pin.isdigit() or len(pin) != 4:
pin = input(" PIN invalide. 4 chiffres: ").strip()
poste_choisi = None
if courriel != "sysadmin":
print("\n Postes disponibles:")
for i, (nom_poste, _, _, _, _, _) in enumerate(POSTES[:4], 1):
print(f" {i}. {nom_poste}")
choix = input(" Votre poste (1-4): ").strip()
poste_idx = int(choix) - 1 if choix.isdigit() and 1 <= int(choix) <= 4 else 0
poste_choisi = POSTES[poste_idx][0]
membre = Membre(
groupe_id=groupe.id,
prenom=prenom,
nom=nom_membre,
courriel=courriel,
telephone=telephone,
pin_hash=hash_pin(pin),
)
db.add(membre)
await db.flush()
if poste_choisi:
affectation = Affectation(
groupe_id=groupe.id,
membre_id=membre.id,
poste_id=postes_map[poste_choisi].id,
date_debut=date.today(),
statut="actif",
)
db.add(affectation)
# ── Première invitation ──
code = secrets.token_hex(6).upper()
invitation = Invitation(
groupe_id=groupe.id,
code=code,
cree_par=membre.id,
expire_le=datetime.now(timezone.utc) + timedelta(days=7),
)
db.add(invitation)
# ── Littérature de base ──
catalogue_litterature = charger_catalogue_litterature()
for item in catalogue_litterature:
db.add(Litterature(
groupe_id=groupe.id,
titre=item["titre"],
categorie=item["categorie"],
prix_coutant=item["prix_coutant"],
quantite_stock=0,
seuil_commande=2,
code_bsg=item["code_bsg"],
))
print(f" ✓ Littérature de base : {len(catalogue_litterature)} titres")
db.add(DestinataireContribution(
groupe_id=groupe.id,
nom="District 87-16",
protege=True,
actif=True,
))
print(" ✓ Destinataire de contribution : District 87-16")
demo = await initialiser_demo(db)
print(f" ✓ Groupe de démonstration : {demo['message']}")
await db.commit()
print(f"\n═══ Initialisation terminée ═══")
print(f" Membre: {prenom} ({courriel})")
print(f" Groupe: Groupe Méditation")
print(f" Poste: {poste_choisi or 'Sysadmin'}")
print(f"\n Code d'invitation pour les prochains membres: {code}")
print(f" (Expire dans 7 jours)")
print(f"\n Connectez-vous sur l'app avec:")
print(f" Courriel: {courriel}")
print(f" PIN: {pin}")
print("\n═══ Initialisation terminée ═══")
print(" Groupe: Groupe Démonstration")
print(f" {demo['message']}")
print("\n Connectez-vous avec:")
print(" Identifiant: sysadmin")
print(" PIN: 0000")
if __name__ == "__main__":

View file

@ -2,7 +2,7 @@ import React from 'react';
import { useStore } from '../stores/store';
export default function PageHeader({ titre, sousTitre, retour = true, action = null }) {
const { membre, setPage, logout } = useStore();
const { membre, groupe, setPage, logout } = useStore();
return (
<div className="sticky top-0 z-30 bg-aa-blue text-white px-4 pt-4 pb-5 rounded-b-2xl shadow-lg">
<div className="flex items-start justify-between gap-3">
@ -17,7 +17,9 @@ export default function PageHeader({ titre, sousTitre, retour = true, action = n
</button>
)}
<div className="min-w-0">
<p className="text-blue-200 text-xs">Bonjour {membre?.prenom}</p>
<p className="text-blue-200 text-xs truncate">
{groupe?.nom ? `${groupe.nom} · ` : ''}Bonjour {membre?.prenom}
</p>
<h1 className="text-xl font-bold leading-tight truncate">{titre}</h1>
{sousTitre && <p className="text-sm text-blue-100 mt-1 truncate">{sousTitre}</p>}
</div>

View file

@ -87,6 +87,7 @@ export default function Accueil() {
modulesGestion,
navContext,
setPage,
groupe,
} = useStore();
const [recherche, setRecherche] = useState('');
@ -126,7 +127,9 @@ export default function Accueil() {
<div className="bg-aa-blue text-white px-4 pt-4 pb-5 rounded-b-2xl shadow-lg">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<p className="text-blue-200 text-xs">Bonjour {membre?.prenom}</p>
<p className="text-blue-200 text-xs truncate">
{groupe?.nom ? `${groupe.nom} · ` : ''}Bonjour {membre?.prenom}
</p>
<h1 className="text-xl font-bold leading-tight">{contexte.titre}</h1>
<p className="text-sm text-blue-100 mt-1">{contexte.description}</p>
</div>

View file

@ -4,7 +4,7 @@ import { useStore } from '../stores/store';
import PageHeader from '../components/PageHeader';
export default function GererMembres() {
const { membre, estSysadmin, impersoner } = useStore();
const { membre, groupe, estSysadmin, impersoner } = useStore();
const [membres, setMembres] = useState([]);
const [loading, setLoading] = useState(true);
const [detail, setDetail] = useState(null);
@ -54,7 +54,7 @@ export default function GererMembres() {
};
const partagerSMS = () => {
const texte = `Rejoins le Groupe Méditation sur l'app. Ton code d'invitation : ${code}`;
const texte = `Rejoins ${groupe?.nom || 'le groupe'} sur l'app. Ton code d'invitation : ${code}`;
window.open(`sms:?body=${encodeURIComponent(texte)}`);
};

View file

@ -9,7 +9,7 @@ const jourLabel = (jour) => {
};
export default function GestionGroupes() {
const { loadGroupes, logout } = useStore();
const { loadGroupes } = useStore();
const [groupes, setGroupes] = useState([]);
const [ouvertId, setOuvertId] = useState('');
const [editionId, setEditionId] = useState('');
@ -35,30 +35,11 @@ export default function GestionGroupes() {
useEffect(() => { charger(); }, []);
const resetDemo = async () => {
const confirmation = window.prompt('Cette action réinitialise seulement les données fictives du groupe de démonstration. Tapez CONFIRMER pour continuer.');
if (confirmation !== 'CONFIRMER') return;
setBusy(true);
setError('');
setMsg('');
try {
const res = await api.post('/admin/demo/reset');
setMsg(res.message || 'Groupe de démonstration réinitialisé');
await charger();
await loadGroupes();
} catch (e) {
setError(e.message);
} finally {
setBusy(false);
}
};
const nouveauGroupe = () => {
setEditionId('nouveau');
setForm({
nom: '',
district: '87-16',
region: '87',
adresse_local: '',
jour_reunion: 'mardi',
heure_reunion: '19:30',
@ -73,7 +54,6 @@ export default function GestionGroupes() {
setForm({
nom: groupe.nom || '',
district: groupe.district || '87-16',
region: groupe.region || '87',
adresse_local: groupe.adresse_local || '',
jour_reunion: groupe.jour_reunion || 'mardi',
heure_reunion: groupe.heure_reunion || '19:30',
@ -124,17 +104,20 @@ export default function GestionGroupes() {
}
};
const remettreAZero = async () => {
const confirmation = window.prompt('Cette action efface toute la base et recrée seulement le compte Sysadmin. Tapez EFFACER pour continuer.');
if (confirmation !== 'EFFACER') return;
const reinitialiser = async (groupe) => {
const confirmation = window.prompt(`Réinitialiser les données du groupe "${groupe.nom}"? Tapez REINIT pour continuer.`);
if (confirmation !== 'REINIT') return;
setBusy(true);
setError('');
setMsg('');
try {
await api.post('/admin/reset');
alert('Base remise à zéro. Reconnectez-vous avec Sysadmin / 0000.');
logout();
const res = await api.post(`/admin/groupes/${groupe.id}/reset`);
setMsg(res.message || 'Groupe réinitialisé');
await charger();
await loadGroupes();
} catch (e) {
setError(e.message);
} finally {
setBusy(false);
}
};
@ -155,13 +138,6 @@ export default function GestionGroupes() {
>
Créer un groupe
</button>
<button
onClick={remettreAZero}
disabled={busy}
className="w-full min-h-[48px] p-3 bg-red-50 border border-red-200 text-red-700 rounded-xl text-sm font-bold disabled:opacity-40"
>
Remettre toute la BD à vide
</button>
</div>
{form && (
@ -173,9 +149,8 @@ export default function GestionGroupes() {
<input value={form.jour_reunion} onChange={(e) => setForm({ ...form, jour_reunion: e.target.value })} placeholder="Jour" className="w-full p-3 bg-gray-50 rounded-xl border-0" />
<input type="time" value={form.heure_reunion} onChange={(e) => setForm({ ...form, heure_reunion: e.target.value })} className="w-full p-3 bg-gray-50 rounded-xl border-0" />
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<input value={form.district} onChange={(e) => setForm({ ...form, district: e.target.value })} placeholder="District" className="w-full p-3 bg-gray-50 rounded-xl border-0" />
<input value={form.region} onChange={(e) => setForm({ ...form, region: e.target.value })} placeholder="Région" className="w-full p-3 bg-gray-50 rounded-xl border-0" />
</div>
<select value={form.assemblee_affaires_ordre} onChange={(e) => setForm({ ...form, assemblee_affaires_ordre: e.target.value })} className="w-full p-3 bg-gray-50 rounded-xl border-0">
<option value="premiere">Assemblée la première réunion du mois</option>
@ -210,14 +185,12 @@ export default function GestionGroupes() {
<span className="block truncate text-lg font-bold text-gray-900">{groupe.nom}</span>
<span className="mt-1 block text-sm font-semibold text-aa-blue">{horaire}</span>
</span>
{groupe.demo && (
<span
onClick={(e) => { e.stopPropagation(); resetDemo(); }}
onClick={(e) => { e.stopPropagation(); reinitialiser(groupe); }}
className="shrink-0 rounded-xl bg-indigo-50 px-3 py-2 text-xs font-bold text-indigo-700"
>
Réinitialiser
Réinit
</span>
)}
<span className={`shrink-0 text-2xl text-aa-blue transition ${ouvert ? 'rotate-180' : ''}`}>⌄</span>
</button>
@ -232,7 +205,6 @@ export default function GestionGroupes() {
{groupe.adresse_local && <p><span className="font-semibold text-gray-900">Lieu : </span>{groupe.adresse_local}</p>}
{groupe.format_reunion && <p><span className="font-semibold text-gray-900">Format : </span>{groupe.format_reunion}</p>}
<p><span className="font-semibold text-gray-900">District : </span>{groupe.district || '—'}</p>
<p><span className="font-semibold text-gray-900">Région : </span>{groupe.region || '—'}</p>
</div>
<div className="grid grid-cols-2 gap-2">
<button onClick={() => modifier(groupe)} className="p-3 bg-gray-100 text-gray-700 rounded-xl text-sm font-bold">Modifier</button>

View file

@ -21,7 +21,7 @@ export default function Inscription() {
<div className="min-h-screen bg-gradient-to-b from-aa-blue to-blue-900 flex flex-col items-center justify-center p-6">
<div className="text-center mb-6">
<h1 className="text-xl font-bold text-white">Nouveau membre</h1>
<p className="text-blue-200 text-sm">Groupe Méditation</p>
<p className="text-blue-200 text-sm">Inscription au groupe</p>
</div>
<form onSubmit={handleSubmit} className="w-full max-w-sm bg-white rounded-2xl shadow-xl p-6 space-y-3">

View file

@ -72,7 +72,7 @@ export default function Login() {
<div className="mx-auto flex min-h-screen w-full max-w-md flex-col py-6">
<div className="text-center mb-5">
<h1 className="text-2xl font-bold text-white">Choisir un groupe</h1>
<p className="text-blue-200 text-sm mt-1">District 87-16 · Région 87</p>
<p className="text-blue-200 text-sm mt-1">District 87-16</p>
</div>
<a

View file

@ -68,7 +68,6 @@ export default function Parametres() {
<>
<Field label="Nom du groupe" value={groupe.nom} />
<Field label="District" value={groupe.district} />
<Field label="Région" value={groupe.region} />
<Field label="Adresse du local" value={groupe.adresse_local} />
<Field label="Jour de réunion" value={groupe.jour_reunion} />
<Field label="Heure" value={groupe.heure_reunion} />