2026-05-25 17:39:38 -04:00
import React , { useEffect , useState } from 'react' ;
import { api } from '../stores/api' ;
import { useStore } from '../stores/store' ;
import PageHeader from '../components/PageHeader' ;
import { dateHeureReunionIso , datesAssembleesAffaires , dateLocale , labelDateReunion } from '../utils/datesReunion' ;
2026-05-31 09:15:12 -04:00
import { heure , moisPrecedent , textePvAuto } from '../features/assemblees/assembleeUtils' ;
import { boutonAction , Card , Champ , DecisionItem , INPUT , MembreSelect , SelectDate } from '../features/assemblees/assembleeUi' ;
2026-05-25 17:39:38 -04:00
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const METHODES = [ [ 'VIREMENT' , 'Virement' ] , [ 'CHEQUE' , 'Chèque' ] ] ;
2026-05-25 17:39:38 -04:00
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
export default function AssembleeAffaires ( { contexte = false } ) {
2026-05-31 09:15:12 -04:00
const { estExecutif , groupe } = useStore ( ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const choixDatesChrono = datesAssembleesAffaires ( groupe ) ;
const choixDates = [ ... choixDatesChrono ] . reverse ( ) ;
const prochaineDate = choixDatesChrono . find ( ( d ) => d >= dateLocale ( ) ) || choixDatesChrono [ choixDatesChrono . length - 1 ] || dateLocale ( ) ;
2026-05-25 17:39:38 -04:00
const [ membres , setMembres ] = useState ( [ ] ) ;
const [ reunions , setReunions ] = useState ( [ ] ) ;
const [ pvs , setPvs ] = useState ( [ ] ) ;
const [ propositions , setPropositions ] = useState ( [ ] ) ;
const [ resolutions , setResolutions ] = useState ( [ ] ) ;
const [ postesVacants , setPostesVacants ] = useState ( [ ] ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const [ soldes , setSoldes ] = useState ( null ) ;
const [ destinataires , setDestinataires ] = useState ( [ ] ) ;
const [ rapportsTresorerie , setRapportsTresorerie ] = useState ( [ ] ) ;
const [ ouverteId , setOuverteId ] = useState ( '' ) ;
const [ action , setAction ] = useState ( '' ) ;
const [ planifier , setPlanifier ] = useState ( false ) ;
const [ dateReunion , setDateReunion ] = useState ( prochaineDate ) ;
2026-05-25 17:39:38 -04:00
const [ note , setNote ] = useState ( '' ) ;
const [ presencesMap , setPresencesMap ] = useState ( { } ) ;
const [ contenu , setContenu ] = useState ( '' ) ;
const [ pvPrecedent , setPvPrecedent ] = useState ( null ) ;
2026-05-26 13:21:16 -04:00
const [ pvCorrection , setPvCorrection ] = useState ( '' ) ;
2026-05-26 09:05:58 -04:00
const [ adoption , setAdoption ] = useState ( { proposeur _id : '' , secondeur _id : '' } ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const [ don , setDon ] = useState ( { destination : 'District 87-16' , montant : '' , methode : 'VIREMENT' , proposeur _id : '' , secondeur _id : '' } ) ;
const [ adoptionTresorier , setAdoptionTresorier ] = useState ( { proposeur _id : '' , secondeur _id : '' , note : '' } ) ;
const [ invitationTexte , setInvitationTexte ] = useState ( '' ) ;
const [ posteOuvert , setPosteOuvert ] = useState ( '' ) ;
const [ posteMode , setPosteMode ] = useState ( '' ) ;
const [ motivation , setMotivation ] = useState ( '' ) ;
const [ membreProposeId , setMembreProposeId ] = useState ( '' ) ;
const [ nouvelleProp , setNouvelleProp ] = useState ( { sujet : '' , description : '' } ) ;
const [ resolutionForm , setResolutionForm ] = useState ( { } ) ;
const [ seconderPour , setSeconderPour ] = useState ( { } ) ;
2026-05-25 17:39:38 -04:00
const [ msg , setMsg ] = useState ( '' ) ;
const [ error , setError ] = useState ( '' ) ;
const [ loading , setLoading ] = useState ( false ) ;
const charger = async ( ) => {
try {
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const [ m , r , pv , props , res , postesDispo , s , dests , rapports ] = await Promise . all ( [
2026-05-25 17:39:38 -04:00
api . get ( '/membres/actifs' ) ,
api . get ( '/presences/reunions' ) ,
api . get ( '/pv/' ) ,
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
api . get ( '/propositions/?limite=100' ) ,
2026-05-25 17:39:38 -04:00
api . get ( '/propositions/resolutions' ) ,
api . get ( '/postes/disponibles' ) ,
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
api . get ( '/collectes/soldes' ) ,
api . get ( '/contributions/destinataires' ) ,
api . get ( '/rapports/adoptes?type_rapport=tresorerie' ) ,
2026-05-25 17:39:38 -04:00
] ) ;
setMembres ( m ) ;
setReunions ( r ) ;
setPvs ( pv ) ;
setPropositions ( props ) ;
setResolutions ( res ) ;
setPostesVacants ( postesDispo ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
setSoldes ( s ) ;
setDestinataires ( dests ) ;
setRapportsTresorerie ( rapports ) ;
2026-05-25 17:39:38 -04:00
const map = { } ;
m . forEach ( ( mb ) => { map [ mb . id ] = true ; } ) ;
setPresencesMap ( map ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const defaut = dests . find ( ( x ) => x . nom === 'District 87-16' ) || dests [ 0 ] ;
setDon ( ( f ) => ( {
... f ,
destination : f . destination || defaut ? . nom || 'District 87-16' ,
montant : f . montant || ( s ? . disponible > 0 ? s . disponible . toFixed ( 2 ) : '' ) ,
proposeur _id : f . proposeur _id || m [ 0 ] ? . id || '' ,
secondeur _id : f . secondeur _id || m [ 1 ] ? . id || '' ,
} ) ) ;
setAdoptionTresorier ( ( f ) => ( {
... f ,
proposeur _id : f . proposeur _id || m [ 0 ] ? . id || '' ,
secondeur _id : f . secondeur _id || m [ 1 ] ? . id || '' ,
} ) ) ;
if ( ! membreProposeId && m [ 0 ] ) setMembreProposeId ( m [ 0 ] . id ) ;
if ( ! ouverteId && r . length ) setOuverteId ( r [ 0 ] . id ) ;
} catch ( e ) {
setError ( e . message ) ;
}
2026-05-25 17:39:38 -04:00
} ;
useEffect ( ( ) => { charger ( ) ; } , [ ] ) ;
useEffect ( ( ) => {
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
if ( choixDates . length > 0 && ! choixDates . includes ( dateReunion ) ) setDateReunion ( prochaineDate ) ;
2026-05-25 17:39:38 -04:00
} , [ groupe ? . jour _reunion , groupe ? . assemblee _affaires _ordre ] ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const reunionOuverte = reunions . find ( ( r ) => r . id === ouverteId ) ;
const dateOuverte = reunionOuverte ? . date _reunion ? . slice ( 0 , 10 ) || prochaineDate ;
const pvJour = pvs . find ( ( p ) => p . date _reunion ? . slice ( 0 , 10 ) === dateOuverte ) ;
const resolutionsJour = resolutions . filter ( ( r ) => ( r . date _vote || r . cree _le ? . slice ( 0 , 10 ) ) === dateOuverte ) ;
const nbPresents = Object . values ( presencesMap ) . filter ( Boolean ) . length ;
const periodeTresorier = moisPrecedent ( dateOuverte ) ;
const rapportTresorierAdopte = rapportsTresorerie . find ( ( r ) => r . annee === periodeTresorier . annee && r . mois === periodeTresorier . mois ) ;
2026-05-25 17:39:38 -04:00
useEffect ( ( ) => {
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
if ( ! reunionOuverte ) return ;
const precedents = pvs
. filter ( ( p ) => p . date _reunion && p . date _reunion . slice ( 0 , 10 ) < dateOuverte )
. sort ( ( a , b ) => b . date _reunion . localeCompare ( a . date _reunion ) ) ;
2026-05-26 13:21:16 -04:00
const precedent = precedents [ 0 ] || null ;
setPvPrecedent ( precedent ) ;
setPvCorrection ( precedent ? . contenu || '' ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
setContenu ( pvJour ? . contenu || textePvAuto ( precedent , propositions , resolutionsJour , postesVacants ) ) ;
} , [ ouverteId , pvs . length , propositions . length , resolutions . length , postesVacants . length ] ) ;
2026-05-25 17:39:38 -04:00
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const ouvrir = ( id ) => {
setOuverteId ( ouverteId === id ? '' : id ) ;
setAction ( '' ) ;
} ;
2026-05-25 17:39:38 -04:00
const toggle = ( membreId ) => setPresencesMap ( { ... presencesMap , [ membreId ] : ! presencesMap [ membreId ] } ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const telechargerRapport = async ( assemblee ) => {
setLoading ( true ) ;
setError ( '' ) ;
try {
const token = api . getToken ( ) ;
const res = await fetch ( ` /api/presences/reunions/ ${ assemblee . id } /rapport.pdf ` , {
headers : { Authorization : ` Bearer ${ token } ` } ,
} ) ;
if ( ! res . ok ) throw new Error ( 'Erreur de génération du rapport PDF' ) ;
const blob = await res . blob ( ) ;
const a = document . createElement ( 'a' ) ;
a . href = URL . createObjectURL ( blob ) ;
a . download = ` rapport_assemblee_ ${ assemblee . date _reunion ? . slice ( 0 , 10 ) } .pdf ` ;
a . click ( ) ;
URL . revokeObjectURL ( a . href ) ;
} catch ( e ) {
setError ( e . message ) ;
} finally {
setLoading ( false ) ;
}
} ;
2026-05-25 17:39:38 -04:00
const creerRapport = async ( ) => {
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
setError ( '' ) ;
setMsg ( '' ) ;
setLoading ( true ) ;
try {
const res = await api . post ( '/presences/reunion' , {
date _reunion : dateHeureReunionIso ( dateReunion , groupe ) ,
note : note . trim ( ) || null ,
} ) ;
setMsg ( 'Assemblée créée' ) ;
setPlanifier ( false ) ;
setNote ( '' ) ;
await charger ( ) ;
setOuverteId ( res . id ) ;
} catch ( e ) {
setError ( e . message ) ;
}
setLoading ( false ) ;
2026-05-25 17:39:38 -04:00
} ;
const enregistrer = async ( ) => {
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
if ( ! reunionOuverte ) return ;
2026-05-25 17:39:38 -04:00
setError ( '' ) ;
setMsg ( '' ) ;
setLoading ( true ) ;
try {
const presences = Object . entries ( presencesMap ) . map ( ( [ membre _id , present ] ) => ( { membre _id , present } ) ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
await api . post ( '/presences/' , { reunion _id : reunionOuverte . id , presences } ) ;
2026-05-25 17:39:38 -04:00
if ( contenu . trim ( ) ) {
await api . post ( '/pv/' , {
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
reunion _id : reunionOuverte . id ,
2026-05-25 17:39:38 -04:00
contenu : contenu . trim ( ) ,
decisions : null ,
} ) ;
}
setMsg ( contenu . trim ( ) ? 'Présences et PV enregistrés' : 'Présences enregistrées' ) ;
await charger ( ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
} catch ( e ) {
setError ( e . message ) ;
}
setLoading ( false ) ;
} ;
const enregistrerDon = async ( ) => {
if ( ! rapportTresorierAdopte ) { setError ( "Le rapport du trésorier doit être adopté avant de décider des contributions" ) ; return ; }
if ( ! don . montant || parseFloat ( don . montant ) <= 0 ) { setError ( 'Montant invalide' ) ; return ; }
if ( ! don . proposeur _id || ! don . secondeur _id ) { setError ( 'Proposeur et secondeur requis' ) ; return ; }
if ( don . proposeur _id === don . secondeur _id ) { setError ( 'Le proposeur et le secondeur doivent être différents' ) ; return ; }
setLoading ( true ) ; setError ( '' ) ;
try {
await api . post ( '/contributions/' , {
date _envoi : dateOuverte ,
destination : don . destination ,
montant : parseFloat ( don . montant ) ,
methode : don . methode ,
proposeur _id : don . proposeur _id ,
secondeur _id : don . secondeur _id ,
} ) ;
setMsg ( 'Don engagé' ) ;
setDon ( { ... don , montant : '' } ) ;
await charger ( ) ;
} catch ( e ) { setError ( e . message ) ; }
setLoading ( false ) ;
} ;
const adopterRapportTresorier = async ( ) => {
if ( ! adoptionTresorier . proposeur _id || ! adoptionTresorier . secondeur _id ) { setError ( 'Proposeur et secondeur requis' ) ; return ; }
setLoading ( true ) ; setError ( '' ) ;
try {
await api . post ( ` /rapports/etat-resultats/ ${ periodeTresorier . annee } / ${ periodeTresorier . mois } /adopter ` , adoptionTresorier ) ;
setMsg ( 'Rapport du trésorier adopté' ) ;
await charger ( ) ;
2026-05-25 17:39:38 -04:00
} catch ( e ) { setError ( e . message ) ; }
setLoading ( false ) ;
} ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
const genererInvitation = async ( assemblee ) => {
setError ( '' ) ;
try {
const inv = await api . post ( ` /presences/reunions/ ${ assemblee . id } /invitation ` ) ;
const url = ` ${ window . location . origin } ${ inv . path } ` ;
setInvitationTexte ( [
` Invitation à l'assemblée d'affaires du ${ labelDateReunion ( assemblee . date _reunion . slice ( 0 , 10 ) ) } ` ,
'' ,
"Vous pouvez consulter l'ordre du jour en lecture seule ici :" ,
url ,
'' ,
` Lien valide jusqu'au ${ new Date ( inv . expire _le ) . toLocaleString ( 'fr-CA' ) } . ` ,
] . join ( '\n' ) ) ;
setAction ( 'Invitation' ) ;
} catch ( e ) { setError ( e . message ) ; }
} ;
const soumettreCandidature = async ( posteId ) => {
setError ( '' ) ;
try {
if ( posteMode === 'proposer' ) {
await api . post ( ` /postes/candidatures/proposer?poste_id= ${ posteId } &membre_id= ${ membreProposeId } &motivation= ${ encodeURIComponent ( motivation . trim ( ) ) } ` ) ;
setMsg ( 'Membre proposé' ) ;
} else {
await api . post ( ` /postes/candidatures?poste_id= ${ posteId } &motivation= ${ encodeURIComponent ( motivation . trim ( ) ) } ` ) ;
setMsg ( 'Candidature soumise' ) ;
}
setPosteMode ( '' ) ;
setMotivation ( '' ) ;
await charger ( ) ;
} catch ( e ) { setError ( e . message ) ; }
} ;
const soumettreProposition = async ( ) => {
if ( ! nouvelleProp . sujet . trim ( ) ) { setError ( "L'objet est obligatoire" ) ; return ; }
setError ( '' ) ;
try {
await api . post ( '/propositions/' , {
sujet : nouvelleProp . sujet . trim ( ) ,
description : nouvelleProp . description . trim ( ) || null ,
} ) ;
setNouvelleProp ( { sujet : '' , description : '' } ) ;
setMsg ( 'Proposition soumise' ) ;
await charger ( ) ;
} catch ( e ) { setError ( e . message ) ; }
} ;
const seconder = async ( id , membreId = '' ) => {
setError ( '' ) ;
try {
const query = membreId ? ` ?membre_id= ${ membreId } ` : '' ;
await api . post ( ` /propositions/ ${ id } /seconder ${ query } ` ) ;
setMsg ( 'Proposition secondée' ) ;
await charger ( ) ;
} catch ( e ) { setError ( e . message ) ; }
} ;
const voter = async ( id , statut ) => {
const form = resolutionForm [ id ] || { } ;
const etantDonneQue = ( form . etant _donne _que || '' ) . trim ( ) ;
const groupeADecideDe = ( form . groupe _a _decide _de || '' ) . trim ( ) ;
if ( ! etantDonneQue || ( statut === 'adoptee' && ! groupeADecideDe ) ) {
setError ( statut === 'adoptee' ? 'Les deux sections de décision sont obligatoires' : 'La section "ÉTANT DONNÉ QUE" est obligatoire' ) ;
return ;
}
setError ( '' ) ;
try {
await api . patch ( ` /propositions/ ${ id } /voter ` , {
statut ,
etant _donne _que : etantDonneQue ,
groupe _a _decide _de : groupeADecideDe || null ,
} ) ;
setResolutionForm ( { ... resolutionForm , [ id ] : { } } ) ;
setMsg ( statut === 'adoptee' ? 'Décision créée' : 'Proposition rejetée' ) ;
await charger ( ) ;
} catch ( e ) { setError ( e . message ) ; }
} ;
2026-05-25 17:39:38 -04:00
const adopterPvPrecedent = async ( ) => {
if ( ! pvPrecedent ) return ;
2026-05-26 09:05:58 -04:00
if ( ! adoption . proposeur _id || ! adoption . secondeur _id ) {
setError ( 'Proposeur et secondeur requis' ) ;
return ;
}
2026-05-25 17:39:38 -04:00
setError ( '' ) ;
try {
2026-05-26 13:21:16 -04:00
await api . patch ( ` /pv/ ${ pvPrecedent . id } /adopter ` , {
... adoption ,
contenu : pvCorrection . trim ( ) ,
} ) ;
2026-05-25 17:39:38 -04:00
setMsg ( 'PV précédent adopté' ) ;
await charger ( ) ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
} catch ( e ) {
setError ( e . message ) ;
}
2026-05-25 17:39:38 -04:00
} ;
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
return (
< div className = "min-h-screen bg-gray-100" >
< PageHeader titre = "Assemblées" sousTitre = "Assemblées d'affaires et procès-verbaux" retour = { ! contexte } / >
< div className = "p-4 space-y-4" >
{ msg && < div className = "bg-green-50 text-green-700 p-3 rounded-xl text-center text-sm font-medium" > { msg } < / div > }
{ error && < div className = "bg-red-50 text-red-600 p-3 rounded-xl text-center text-sm" > { error } < / div > }
{ estExecutif && (
< button onClick = { ( ) => setPlanifier ( ! planifier ) } className = "w-full p-3 bg-aa-blue text-white rounded-xl text-sm font-bold" >
Planifier une assemblée
< / button >
) }
{ planifier && estExecutif && (
< Card titre = "Nouvelle assemblée" >
< Champ label = "Date" > < SelectDate value = { dateReunion } onChange = { setDateReunion } dates = { choixDates } / > < / Champ >
< Champ label = "Note" > < input value = { note } onChange = { ( e ) => setNote ( e . target . value ) } placeholder = "Note optionnelle" className = { INPUT } / > < / Champ >
< button onClick = { creerRapport } disabled = { loading } className = "w-full p-3 bg-green-600 text-white rounded-xl text-sm font-bold disabled:opacity-40" > Créer < / button >
< / Card >
) }
< div className = "space-y-3" >
{ reunions . length === 0 && (
< div className = "rounded-2xl bg-white p-5 text-center text-sm text-gray-500 shadow-sm" >
< p > Aucune assemblée planifiée . < / p >
{ estExecutif && < p className = "mt-1 text-gray-400" > Utilisez le bouton en haut de la page pour créer la première assemblée . < / p > }
< / div >
) }
{ reunions . map ( ( r ) => {
const date = r . date _reunion ? . slice ( 0 , 10 ) ;
const ouvert = ouverteId === r . id ;
const pv = pvs . find ( ( p ) => p . date _reunion ? . slice ( 0 , 10 ) === date ) ;
return (
< div key = { r . id } className = { ` bg-white rounded-2xl shadow-sm overflow-hidden ${ ouvert ? 'print:block' : 'print:hidden' } ` } >
< div className = "flex items-start gap-2 p-4" >
< button onClick = { ( ) => ouvrir ( r . id ) } className = "min-w-0 flex-1 text-left active:bg-gray-50" >
< div className = "flex items-start justify-between gap-3" >
< div className = "min-w-0" >
< p className = "font-extrabold text-gray-900" > { labelDateReunion ( date ) } · { heure ( r . date _reunion ) } < / p >
< p className = "text-sm text-gray-500 truncate" > { pv ? . approuve ? 'PV adopté' : pv ? 'PV à adopter' : 'PV à renseigner' } · { r . presences ? . filter ( ( p ) => p . present ) . length || 0 } présence ( s ) < / p >
< / div >
< span className = "text-xl text-gray-300" > { ouvert ? '⌃' : '⌄' } < / span >
< / div >
< / button >
< button onClick = { ( ) => telechargerRapport ( r ) } disabled = { loading }
className = "shrink-0 rounded-xl bg-gray-900 px-3 py-2 text-xs font-bold text-white disabled:opacity-40 print:hidden" >
Rapport PDF
< / button >
< / div >
{ ouvert && (
< div className = "p-4 pt-0 space-y-3 border-t border-gray-100" >
< div className = "grid grid-cols-2 gap-2 print:hidden" >
< button onClick = { ( ) => genererInvitation ( r ) } className = { boutonAction ( action === 'Invitation' ) } > Invitation < / button >
{ pvPrecedent && < button onClick = { ( ) => setAction ( action === 'PV précédent' ? '' : 'PV précédent' ) } className = { boutonAction ( action === 'PV précédent' ) } > PV précédent < / button > }
{ estExecutif && < button onClick = { ( ) => setAction ( action === 'Présences' ? '' : 'Présences' ) } className = { boutonAction ( action === 'Présences' ) } > Présences < / button > }
{ estExecutif && < button onClick = { ( ) => setAction ( action === 'Trésorier' ? '' : 'Trésorier' ) } className = { boutonAction ( action === 'Trésorier' ) } > Rapport trésorier < / button > }
< button onClick = { ( ) => setAction ( action === 'Donner' ? '' : 'Donner' ) } className = { boutonAction ( action === 'Donner' ) } > Donner au suivant < / button >
< button onClick = { ( ) => setAction ( action === 'Postes' ? '' : 'Postes' ) } className = { boutonAction ( action === 'Postes' ) } > Postes à pourvoir < / button >
< button onClick = { ( ) => setAction ( action === 'Décisions' ? '' : 'Décisions' ) } className = { boutonAction ( action === 'Décisions' ) } > Décisions < / button >
< / div >
{ action === 'PV précédent' && pvPrecedent && (
< Card titre = "Procès-verbal précédent" >
< p className = "text-xs text-gray-400" > { new Date ( pvPrecedent . date _reunion ) . toLocaleDateString ( 'fr-CA' ) } < / p >
{ estExecutif && ! pvPrecedent . approuve ? (
< div className = "space-y-2 pt-2 border-t border-gray-100" >
< textarea value = { pvCorrection } onChange = { ( e ) => setPvCorrection ( e . target . value ) } rows = { 8 } className = { ` ${ INPUT } resize-none text-sm ` } / >
< div className = "grid grid-cols-2 gap-2" >
< MembreSelect label = "Proposeur" value = { adoption . proposeur _id } onChange = { ( v ) => setAdoption ( { ... adoption , proposeur _id : v } ) } membres = { membres } / >
< MembreSelect label = "Secondeur" value = { adoption . secondeur _id } onChange = { ( v ) => setAdoption ( { ... adoption , secondeur _id : v } ) } membres = { membres } / >
< / div >
< button onClick = { adopterPvPrecedent } disabled = { ! adoption . proposeur _id || ! adoption . secondeur _id } className = "w-full p-3 bg-green-50 text-green-700 rounded-xl text-sm font-bold border border-green-100 disabled:opacity-40" > Adopter < / button >
< / div >
) : (
< p className = "text-sm whitespace-pre-wrap text-gray-700" > { pvPrecedent . contenu } < / p >
) }
< / Card >
) }
{ action === 'Invitation' && (
< Card titre = "Invitation" >
< textarea readOnly value = { invitationTexte } rows = { 7 } className = { ` ${ INPUT } resize-none text-sm ` } / >
< button onClick = { ( ) => navigator . clipboard ? . writeText ( invitationTexte ) } className = "w-full p-3 bg-aa-blue text-white rounded-xl text-sm font-bold" > Copier le texte < / button >
< / Card >
) }
{ action === 'Présences' && estExecutif && (
< Card titre = { ` Présences ( ${ nbPresents } / ${ membres . length } ) ` } >
< div className = "space-y-1" > { membres . map ( ( m ) => (
< button key = { m . id } onClick = { ( ) => toggle ( m . id ) } className = { ` w-full flex items-center gap-3 p-3 rounded-xl transition ${ presencesMap [ m . id ] ? 'bg-green-50 border border-green-200' : 'bg-gray-50 border border-gray-100 opacity-50' } ` } >
< span className = { ` w-6 h-6 rounded-lg flex items-center justify-center text-sm font-bold ${ presencesMap [ m . id ] ? 'bg-green-500 text-white' : 'bg-gray-200 text-gray-400' } ` } > { presencesMap [ m . id ] ? '✓' : '' } < / span >
< span className = "font-medium text-gray-800" > { m . prenom } < / span >
< / button >
) ) } < / div >
< button onClick = { enregistrer } disabled = { loading } className = "w-full p-3 bg-green-600 text-white rounded-xl text-sm font-bold disabled:opacity-40" > Enregistrer les présences < / button >
< / Card >
) }
{ action === 'Trésorier' && estExecutif && (
< Card titre = { ` Rapport du trésorier - ${ periodeTresorier . libelle } ` } >
{ rapportTresorierAdopte ? (
< p className = "text-sm text-green-700 bg-green-50 rounded-xl p-3" > Rapport adopté . Les contributions peuvent être décidées . < / p >
) : (
< >
< p className = "text-sm text-gray-600" > Ce rapport doit être adopté avant de décider du montant des contributions . < / p >
< div className = "grid grid-cols-2 gap-2" >
< Champ label = "Proposeur" > < MembreSelect label = "Proposeur" value = { adoptionTresorier . proposeur _id } onChange = { ( v ) => setAdoptionTresorier ( { ... adoptionTresorier , proposeur _id : v } ) } membres = { membres } / > < / Champ >
< Champ label = "Secondeur" > < MembreSelect label = "Secondeur" value = { adoptionTresorier . secondeur _id } onChange = { ( v ) => setAdoptionTresorier ( { ... adoptionTresorier , secondeur _id : v } ) } membres = { membres } / > < / Champ >
< / div >
< Champ label = "Correction ou note" > < textarea value = { adoptionTresorier . note } onChange = { ( e ) => setAdoptionTresorier ( { ... adoptionTresorier , note : e . target . value } ) } rows = { 2 } className = { ` ${ INPUT } resize-none text-sm ` } / > < / Champ >
< button onClick = { adopterRapportTresorier } disabled = { loading } className = "w-full p-3 bg-green-600 text-white rounded-xl text-sm font-bold disabled:opacity-40" > Adopter le rapport < / button >
< / >
) }
< / Card >
) }
{ action === 'Donner' && (
< Card titre = "Donner au suivant" >
{ ! rapportTresorierAdopte && < p className = "text-sm text-red-700 bg-red-50 rounded-xl p-3" > Adoptez d ' abord le rapport du trésorier . < / p > }
{ soldes && < p className = "text-sm text-blue-700 bg-blue-50 rounded-xl p-3" > Disponible : < span className = "font-bold" > { soldes . disponible . toFixed ( 2 ) } $ < / span > < / p > }
< Champ label = "Destinataire" > < select value = { don . destination } onChange = { ( e ) => setDon ( { ... don , destination : e . target . value } ) } className = { INPUT } > { destinataires . map ( ( d ) => < option key = { d . id } value = { d . nom } > { d . nom } < / option > ) } < / select > < / Champ >
< div className = "grid grid-cols-2 gap-2" >
< Champ label = "Montant" > < input type = "number" inputMode = "decimal" step = "0.01" value = { don . montant } onChange = { ( e ) => setDon ( { ... don , montant : e . target . value } ) } className = { INPUT } / > < / Champ >
< Champ label = "Méthode" > < select value = { don . methode } onChange = { ( e ) => setDon ( { ... don , methode : e . target . value } ) } className = { INPUT } > { METHODES . map ( ( [ v , l ] ) => < option key = { v } value = { v } > { l } < / option > ) } < / select > < / Champ >
< / div >
< div className = "grid grid-cols-2 gap-2" >
< Champ label = "Proposeur" > < MembreSelect label = "Proposeur" value = { don . proposeur _id } onChange = { ( v ) => setDon ( { ... don , proposeur _id : v } ) } membres = { membres } / > < / Champ >
< Champ label = "Secondeur" > < MembreSelect label = "Secondeur" value = { don . secondeur _id } onChange = { ( v ) => setDon ( { ... don , secondeur _id : v } ) } membres = { membres } / > < / Champ >
< / div >
< button onClick = { enregistrerDon } disabled = { loading || ! rapportTresorierAdopte } className = "w-full p-3 bg-green-600 text-white rounded-xl text-sm font-bold disabled:opacity-40" > Créer l ' envoi < / button >
< / Card >
) }
{ action === 'Postes' && (
< Card titre = "Postes à pourvoir" >
{ postesVacants . length === 0 ? < p className = "text-sm text-gray-500" > Aucun poste vacant . < / p > : postesVacants . map ( ( p ) => {
const ouvertPoste = posteOuvert === p . id ;
return (
< div key = { p . id } className = "rounded-xl bg-gray-50 overflow-hidden" >
< button onClick = { ( ) => { setPosteOuvert ( ouvertPoste ? '' : p . id ) ; setPosteMode ( '' ) ; } } className = "w-full p-3 text-left" >
< p className = "font-bold text-gray-800" > { p . nom } < / p >
{ p . description && < p className = "text-sm text-gray-500 line-clamp-2" > { p . description } < / p > }
< / button >
{ ouvertPoste && (
< div className = "p-3 pt-0 space-y-2" >
{ p . candidats ? . length ? < p className = "text-xs text-gray-500" > Candidats : { p . candidats . map ( ( c ) => c . prenom ) . join ( ', ' ) } < / p > : < p className = "text-xs text-gray-500" > Aucun candidat . < / p > }
{ posteMode === 'proposer' && < MembreSelect label = "Membre" value = { membreProposeId } onChange = { setMembreProposeId } membres = { membres } / > }
{ posteMode && < textarea value = { motivation } onChange = { ( e ) => setMotivation ( e . target . value ) } rows = { 2 } placeholder = "Motivation optionnelle" className = { ` ${ INPUT } resize-none text-sm ` } / > }
{ posteMode ? (
< div className = "grid grid-cols-2 gap-2" >
< button onClick = { ( ) => soumettreCandidature ( p . id ) } className = "p-2 bg-green-50 text-green-700 rounded-xl text-sm font-bold" > Soumettre < / button >
< button onClick = { ( ) => { setPosteMode ( '' ) ; setMotivation ( '' ) ; } } className = "p-2 bg-gray-100 text-gray-600 rounded-xl text-sm font-bold" > Annuler < / button >
< / div >
) : (
< div className = "grid grid-cols-2 gap-2" >
< button onClick = { ( ) => setPosteMode ( 'postuler' ) } className = "p-2 bg-yellow-50 text-yellow-700 rounded-xl text-sm font-bold" > Postuler < / button >
< button onClick = { ( ) => setPosteMode ( 'proposer' ) } className = "p-2 bg-blue-50 text-blue-700 rounded-xl text-sm font-bold" > Proposer < / button >
< / div >
) }
< / div >
) }
< / div >
) ;
} ) }
< / Card >
) }
{ action === 'Décisions' && (
< Card titre = "Décisions" >
< Champ label = "Objet" > < input value = { nouvelleProp . sujet } onChange = { ( e ) => setNouvelleProp ( { ... nouvelleProp , sujet : e . target . value } ) } className = { INPUT } placeholder = "Nouvelle proposition" / > < / Champ >
< Champ label = "Détails" > < textarea value = { nouvelleProp . description } onChange = { ( e ) => setNouvelleProp ( { ... nouvelleProp , description : e . target . value } ) } rows = { 2 } className = { ` ${ INPUT } resize-none text-sm ` } / > < / Champ >
< button onClick = { soumettreProposition } className = "w-full p-3 bg-aa-blue text-white rounded-xl text-sm font-bold" > Soumettre la proposition < / button >
< div className = "space-y-2 pt-2 border-t border-gray-100" >
{ propositions . filter ( ( p ) => [ 'proposee' , 'secondee' , 'rejetee' ] . includes ( p . statut ) ) . map ( ( p ) => (
< DecisionItem key = { p . id } p = { p } membres = { membres } estExecutif = { estExecutif } form = { resolutionForm [ p . id ] || { } } setForm = { ( champ , valeur ) => setResolutionForm ( { ... resolutionForm , [ p . id ] : { ... ( resolutionForm [ p . id ] || { } ) , [ champ ] : valeur } } ) } seconderPour = { seconderPour [ p . id ] || '' } setSeconderPour = { ( v ) => setSeconderPour ( { ... seconderPour , [ p . id ] : v } ) } seconder = { seconder } voter = { voter } / >
) ) }
{ propositions . filter ( ( p ) => [ 'proposee' , 'secondee' , 'rejetee' ] . includes ( p . statut ) ) . length === 0 && < p className = "text-sm text-gray-500" > Aucune proposition à traiter . < / p > }
< / div >
< / Card >
) }
< / div >
) }
< / div >
) ;
} ) }
< / div >
< / div >
< / div >
) ;
}