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 , datesReunion , dateLocale , labelDateReunion } from '../utils/datesReunion' ;
const TYPES _JETONS = [ [ 'desir' , 'Désir' ] , [ '24h' , '24 heures' ] , [ '1mois' , '1 mois' ] , [ '2mois' , '2 mois' ] , [ '3mois' , '3 mois' ] , [ '6mois' , '6 mois' ] , [ '9mois' , '9 mois' ] , [ '1an' , '1 an' ] , [ 'multiple' , 'Multiple' ] ] ;
const INPUT = 'w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue' ;
export default function ReunionHebdomadaire ( ) {
const { postes , groupe } = useStore ( ) ;
const estExecutif = postes . some ( ( p ) => [ 'RSG' , 'RSG Substitut(e)' , 'Trésorier(ière)' , 'Secrétaire' ] . includes ( p ) ) ;
const choixDates = datesReunion ( groupe , { joursAvant : 365 , joursApres : 180 } ) ;
const [ membres , setMembres ] = useState ( [ ] ) ;
const [ catalogue , setCatalogue ] = useState ( [ ] ) ;
const [ reunions , setReunions ] = useState ( [ ] ) ;
const [ dateSelection , setDateSelection ] = useState ( dateLocale ( ) ) ;
const [ dateReunion , setDateReunion ] = useState ( dateLocale ( ) ) ;
const [ theme , setTheme ] = useState ( '' ) ;
const [ annonces , setAnnonces ] = useState ( '' ) ;
const [ note , setNote ] = useState ( '' ) ;
const [ collecteMontant , setCollecteMontant ] = useState ( '' ) ;
const [ detenteurId , setDetenteurId ] = useState ( '' ) ;
const [ jetonType , setJetonType ] = useState ( '' ) ;
const [ jetonMembreId , setJetonMembreId ] = useState ( '' ) ;
const [ jetonAnnees , setJetonAnnees ] = useState ( '' ) ;
const [ litteratureId , setLitteratureId ] = useState ( '' ) ;
const [ quantite , setQuantite ] = useState ( '1' ) ;
const [ venteMontant , setVenteMontant ] = useState ( '' ) ;
const [ msg , setMsg ] = useState ( '' ) ;
const [ error , setError ] = useState ( '' ) ;
const [ loading , setLoading ] = useState ( false ) ;
2026-05-26 08:15:28 -04:00
const [ rapportVisible , setRapportVisible ] = useState ( false ) ;
2026-05-25 17:39:38 -04:00
const charger = async ( ) => {
try {
const [ m , l , r ] = await Promise . all ( [
api . get ( '/membres/actifs' ) ,
api . get ( '/litterature/' ) ,
api . get ( '/reunions/?limite=80' ) ,
] ) ;
setMembres ( m ) ;
setCatalogue ( l ) ;
setReunions ( r . filter ( ( x ) => x . type === 'reguliere' ) ) ;
if ( m . length > 0 && ! detenteurId ) setDetenteurId ( m [ 0 ] . id ) ;
} catch ( e ) { setError ( e . message ) ; }
} ;
useEffect ( ( ) => { charger ( ) ; } , [ ] ) ;
useEffect ( ( ) => {
if ( choixDates . length > 0 ) {
if ( ! choixDates . includes ( dateSelection ) ) setDateSelection ( choixDates . find ( ( d ) => d >= dateLocale ( ) ) || choixDates [ choixDates . length - 1 ] ) ;
if ( ! choixDates . includes ( dateReunion ) ) setDateReunion ( choixDates . find ( ( d ) => d >= dateLocale ( ) ) || choixDates [ choixDates . length - 1 ] ) ;
}
} , [ groupe ? . jour _reunion ] ) ;
const rapport = reunions . find ( ( r ) => r . date _reunion ? . slice ( 0 , 10 ) === dateSelection ) ;
const dateSimple = ( ) => dateReunion ;
const choisirLitterature = ( id ) => {
setLitteratureId ( id ) ;
const item = catalogue . find ( ( c ) => c . id === id ) ;
if ( item ? . prix _coutant ) setVenteMontant ( ( item . prix _coutant * parseInt ( quantite || '1' ) ) . toFixed ( 2 ) ) ;
} ;
const changerQuantite = ( valeur ) => {
setQuantite ( valeur ) ;
const item = catalogue . find ( ( c ) => c . id === litteratureId ) ;
if ( item ? . prix _coutant && valeur ) setVenteMontant ( ( item . prix _coutant * parseInt ( valeur ) ) . toFixed ( 2 ) ) ;
} ;
const imprimer = ( ) => window . print ( ) ;
const enregistrer = async ( ) => {
setError ( '' ) ;
setMsg ( '' ) ;
setLoading ( true ) ;
const faits = [ ] ;
try {
if ( theme . trim ( ) || annonces . trim ( ) || note . trim ( ) ) {
const annoncesList = annonces . split ( '\n' ) . map ( ( a ) => a . trim ( ) ) . filter ( Boolean ) ;
await api . post ( '/reunions/' , {
type : 'reguliere' ,
date _reunion : dateHeureReunionIso ( dateReunion , groupe ) ,
theme : theme . trim ( ) || 'Rencontre hebdomadaire' ,
annonces : annoncesList . length ? annoncesList : null ,
note : note . trim ( ) || null ,
} ) ;
faits . push ( 'rapport' ) ;
}
if ( collecteMontant ) {
await api . post ( '/collectes/' , { date _collecte : dateSimple ( ) , montant : parseFloat ( collecteMontant ) , detenteur _id : detenteurId } ) ;
faits . push ( 'collecte' ) ;
}
if ( jetonType ) {
const body = { type _jeton : jetonType , date _remise : dateSimple ( ) } ;
if ( jetonMembreId ) body . membre _id = jetonMembreId ;
if ( jetonType === 'multiple' ) body . annees = parseInt ( jetonAnnees ) ;
await api . post ( '/jetons/' , body ) ;
faits . push ( 'jeton' ) ;
}
if ( litteratureId ) {
await api . post ( '/ventes-litterature/' , { litterature _id : litteratureId , quantite : parseInt ( quantite ) , montant : parseFloat ( venteMontant ) , date _vente : dateSimple ( ) } ) ;
faits . push ( 'littérature' ) ;
}
if ( faits . length === 0 ) setError ( 'Remplissez au moins une section' ) ;
else {
setMsg ( ` Enregistré : ${ faits . join ( ', ' ) } ` ) ;
setDateSelection ( dateSimple ( ) ) ;
await charger ( ) ;
}
} catch ( e ) { setError ( e . message ) ; }
setLoading ( false ) ;
} ;
return (
< div className = "min-h-screen bg-gray-100" >
< PageHeader titre = "Rencontre hebdomadaire" sousTitre = "Assistant et rapport imprimable" / >
< 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 > }
2026-05-26 08:15:28 -04:00
< Card titre = "Rapport imprimable" >
2026-05-25 17:39:38 -04:00
< Champ label = "Date" >
< SelectDate value = { dateSelection } onChange = { setDateSelection } dates = { choixDates } / >
< / Champ >
2026-05-26 08:15:28 -04:00
< button onClick = { ( ) => setRapportVisible ( true ) } className = "w-full p-3 bg-aa-blue text-white rounded-xl text-sm font-bold" >
Produire le rapport
< / button >
{ rapportVisible && < button onClick = { imprimer } className = "w-full p-3 bg-gray-900 text-white rounded-xl text-sm font-bold" > Imprimer format lettre < / button > }
2026-05-25 17:39:38 -04:00
< / Card >
2026-05-26 08:15:28 -04:00
{ rapportVisible && < div className = "bg-white rounded-2xl p-5 shadow-sm print:shadow-none print:rounded-none print:p-0" >
2026-05-25 17:39:38 -04:00
< h2 className = "text-xl font-bold text-gray-900" > Rapport de réunion hebdomadaire < / h2 >
< p className = "text-sm text-gray-500" > { dateSelection } < / p >
{ rapport ? (
< div className = "mt-4 space-y-3 text-sm" >
< p > < strong > Thème : < / strong > { rapport . theme || '____________________________' } < / p >
< div >
< strong > Annonces : < / strong >
{ ( rapport . annonces || [ ] ) . length > 0 ? (
< ul className = "list-disc ml-5 mt-1" > { rapport . annonces . map ( ( a , i ) => < li key = { i } > { a } < / li > ) } < / ul >
) : < p className = "mt-1 whitespace-pre-wrap" > { "\n\n\n" } < / p > }
< / div >
< p > < strong > Notes : < / strong > { rapport . note || '____________________________________________' } < / p >
< / div >
) : (
< div className = "mt-4 space-y-4 text-sm" >
< p > < strong > Thème : < / strong > _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ < / p >
< p > < strong > Annonces : < / strong > < / p >
< div className = "h-28 border border-gray-200 rounded-xl" / >
< p > < strong > Notes : < / strong > < / p >
< div className = "h-20 border border-gray-200 rounded-xl" / >
< / div >
) }
< div className = "grid grid-cols-2 gap-4 mt-6 text-sm" >
< p > Collecte : _ _ _ _ _ _ _ _ _ _ $ < / p >
< p > Détenteur : _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ < / p >
< p > Jeton : _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ < / p >
< p > Littérature : _ _ _ _ _ _ _ _ _ _ $ < / p >
< / div >
2026-05-26 08:15:28 -04:00
< / div > }
2026-05-25 17:39:38 -04:00
{ estExecutif && (
< >
< Card titre = "Renseigner le rapport" >
< Champ label = "Date" > < SelectDate value = { dateReunion } onChange = { setDateReunion } dates = { choixDates } / > < / Champ >
< Champ label = "Thème" > < input value = { theme } onChange = { ( e ) => setTheme ( e . target . value ) } placeholder = "Thème abordé" className = { INPUT } / > < / Champ >
< Champ label = "Annonces" > < textarea value = { annonces } onChange = { ( e ) => setAnnonces ( e . target . value ) } rows = { 3 } placeholder = "Une annonce par ligne" className = { ` ${ INPUT } resize-none ` } / > < / Champ >
< Champ label = "Notes" > < textarea value = { note } onChange = { ( e ) => setNote ( e . target . value ) } rows = { 2 } placeholder = "Notes optionnelles" className = { ` ${ INPUT } resize-none ` } / > < / Champ >
< / Card >
< Card titre = "Saisies liées" >
< div className = "grid grid-cols-2 gap-3" >
< Champ label = "Collecte" > < input type = "number" inputMode = "decimal" step = "0.01" value = { collecteMontant } onChange = { ( e ) => setCollecteMontant ( e . target . value ) } placeholder = "0.00" className = { INPUT } / > < / Champ >
< Champ label = "Détenteur" > < select value = { detenteurId } onChange = { ( e ) => setDetenteurId ( e . target . value ) } className = { INPUT } > { membres . map ( ( m ) => < option key = { m . id } value = { m . id } > { m . prenom } < / option > ) } < / select > < / Champ >
< / div >
< Champ label = "Jeton" > < select value = { jetonType } onChange = { ( e ) => setJetonType ( e . target . value ) } className = { INPUT } > < option value = "" > Aucun jeton < / option > { TYPES _JETONS . map ( ( [ value , label ] ) => < option key = { value } value = { value } > { label } < / option > ) } < / select > < / Champ >
< Champ label = "Membre jeton" > < select value = { jetonMembreId } onChange = { ( e ) => setJetonMembreId ( e . target . value ) } className = { INPUT } > < option value = "" > Anonyme / non applicable < / option > { membres . map ( ( m ) => < option key = { m . id } value = { m . id } > { m . prenom } < / option > ) } < / select > < / Champ >
{ jetonType === 'multiple' && < Champ label = "Années" > < input type = "number" min = "2" value = { jetonAnnees } onChange = { ( e ) => setJetonAnnees ( e . target . value ) } className = { INPUT } / > < / Champ > }
< Champ label = "Littérature" > < select value = { litteratureId } onChange = { ( e ) => choisirLitterature ( e . target . value ) } className = { INPUT } > < option value = "" > Aucune vente < / option > { catalogue . map ( ( c ) => < option key = { c . id } value = { c . id } > { c . titre } ( { c . quantite _stock } ) < / option > ) } < / select > < / Champ >
< div className = "grid grid-cols-2 gap-3" >
< Champ label = "Quantité" > < input type = "number" min = "1" value = { quantite } onChange = { ( e ) => changerQuantite ( e . target . value ) } className = { INPUT } / > < / Champ >
< Champ label = "Montant" > < input type = "number" inputMode = "decimal" step = "0.01" value = { venteMontant } onChange = { ( e ) => setVenteMontant ( e . target . value ) } placeholder = "0.00" className = { INPUT } / > < / Champ >
< / div >
< / Card >
< button onClick = { enregistrer } disabled = { loading } className = "w-full p-5 bg-aa-blue text-white rounded-2xl text-lg font-bold disabled:opacity-40 active:scale-[0.97] transition shadow-lg" > { loading ? '...' : 'Enregistrer les sections remplies' } < / button >
< / >
) }
< / div >
< / div >
) ;
}
function Card ( { titre , children } ) {
return < div className = "bg-white rounded-2xl p-4 shadow-sm space-y-3" > < h2 className = "font-bold text-gray-800" > { titre } < / h2 > { children } < / div > ;
}
function Champ ( { label , children } ) {
return < label className = "block" > < span className = "text-xs text-gray-400 block mb-1" > { label } < / span > { children } < / label > ;
}
function SelectDate ( { value , onChange , dates } ) {
if ( ! dates . length ) {
return < div className = "rounded-xl bg-gray-50 p-3 text-sm text-gray-500" > Configurez le jour de réunion dans les paramètres du groupe . < / div > ;
}
return (
< select value = { value } onChange = { ( e ) => onChange ( e . target . value ) } className = { INPUT } >
{ dates . map ( ( d ) => < option key = { d } value = { d } > { labelDateReunion ( d ) } < / option > ) }
< / select >
) ;
}