Initial commit: Club DynaSQY

This commit is contained in:
2026-08-23 09:04:47 +00:00
commit 0875890dc7
145 changed files with 16947 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
import { useState, useEffect } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
export default function GuestDetail() {
const { id } = useParams()
const navigate = useNavigate()
const { authFetch, user } = useAuth()
const [guest, setGuest] = useState(null)
useEffect(() => {
authFetch(`/api/guests/${id}`).then((r) => (r.ok ? r.json() : null)).then(setGuest)
}, [id])
const handleCandidature = async (action) => {
const confirmMsg = action === 'start' ? 'Proposer la candidature de cet invité ?' :
action === 'accept' ? 'Accepter → créer un compte membre et supprimer cet invité ?' :
'Refuser cette candidature ?'
if (!confirm(confirmMsg)) return
const r = await authFetch(`/api/guests/${id}/candidature`, {
method: 'POST',
body: JSON.stringify({ action }),
})
if (r.ok) {
if (action === 'accept') {
alert('Membre créé ! Mot de passe par défaut : Bienvenue2026')
navigate('/members')
} else {
const data = await r.json()
alert(data.message)
setGuest((g) => ({ ...g, statut_candidature: data.statut_candidature }))
}
} else {
const err = await r.json()
alert(err.error || 'Erreur')
}
}
if (!guest) return <p className="text-center text-gray-400 py-8">Chargement...</p>
const isOwner = guest.invite_par_id === user?.id || user?.role === 'admin'
const statutBadge = (s) => {
if (s === 'acceptee') return <span className="text-green-600 text-sm font-medium">✓ Acceptée</span>
if (s === 'refusee') return <span className="text-red-600 text-sm font-medium">✗ Refusée</span>
return <span className="text-orange-400 text-sm font-medium">En attente</span>
}
const candidatureBadge = (s) => {
if (s === 'candidature') return <span className="inline-block text-yellow-800 bg-yellow-100 text-xs font-bold px-2 py-0.5 rounded-full">Candidature en cours</span>
if (s === 'refuse') return <span className="inline-block text-red-800 bg-red-100 text-xs font-bold px-2 py-0.5 rounded-full">Candidature refusée</span>
return null
}
return (
<div className="space-y-4">
<Link to="/guests" className="text-blue-700 text-sm">&larr; Retour</Link>
<div className="bg-white rounded-xl p-6 shadow-sm text-center">
{guest.photo_url ? (
<img src={guest.photo_url} alt="" className="w-20 h-20 rounded-full object-cover mx-auto mb-3" />
) : (
<div className="w-20 h-20 bg-blue-900 rounded-full flex items-center justify-center text-white text-2xl font-bold mx-auto mb-3">
{guest.prenom[0]}{guest.nom[0]}
</div>
)}
<h2 className="text-xl font-bold text-gray-800">{guest.prenom} {guest.nom}</h2>
{guest.societe && <p className="text-gray-500">{guest.societe}</p>}
{guest.activite && <p className="text-gray-400 text-sm">{guest.activite}</p>}
{guest.visit_number === 2 && <span className="inline-block mt-1 text-xs font-bold text-red-600 bg-red-100 px-2 py-0.5 rounded-full">2</span>}
{guest.visit_number >= 3 && <span className="inline-block mt-1 text-xs font-bold text-purple-600 bg-purple-100 px-2 py-0.5 rounded-full">{guest.visit_number}</span>}
<div className="mt-2">{statutBadge(guest.statut)}</div>
<div className="mt-1">{candidatureBadge(guest.statut_candidature)}</div>
{isOwner && (
<Link to={`/guests/edit/${guest.id}`} className="inline-block mt-3 bg-blue-900 text-white px-4 py-2 rounded-lg text-sm font-semibold">Modifier</Link>
)}
</div>
{user?.role === 'admin' && (
<div className="bg-white rounded-xl p-4 shadow-sm space-y-2">
<h3 className="font-semibold text-gray-700 text-sm">Candidature membre</h3>
{!guest.statut_candidature && (
<button onClick={() => handleCandidature('start')} className="w-full bg-yellow-500 text-white py-2 rounded-lg text-sm font-semibold active:bg-yellow-600">
Proposer candidature
</button>
)}
{guest.statut_candidature === 'candidature' && (
<div className="flex gap-2">
<button onClick={() => handleCandidature('accept')} className="flex-1 bg-green-600 text-white py-2 rounded-lg text-sm font-semibold active:bg-green-700">
Accepter → Membre
</button>
<button onClick={() => handleCandidature('reject')} className="flex-1 bg-red-500 text-white py-2 rounded-lg text-sm font-semibold active:bg-red-600">
Refuser
</button>
</div>
)}
{guest.statut_candidature === 'refuse' && (
<p className="text-red-600 text-sm text-center">Candidature refusée</p>
)}
</div>
)}
<div className="bg-white rounded-xl p-4 shadow-sm space-y-3">
{guest.telephone && (
<a href={`tel:${guest.telephone}`} className="flex items-center gap-3 text-green-600">
<span className="text-lg">📞</span>
<span className="font-medium">{guest.telephone}</span>
</a>
)}
{guest.email && (
<a href={`mailto:${guest.email}`} className="flex items-center gap-3 text-blue-600">
<span className="text-lg">✉</span>
<span className="font-medium">{guest.email}</span>
</a>
)}
{guest.site_web && (
<a href={guest.site_web.startsWith('http') ? guest.site_web : 'https://' + guest.site_web} target="_blank" rel="noopener noreferrer" className="flex items-center gap-3 text-purple-600">
<span className="text-lg">🌐</span>
<span className="font-medium">{guest.site_web}</span>
</a>
)}
{guest.commentaires && <p className="text-sm text-gray-600">Note : {guest.commentaires}</p>}
{guest.date_club && (
<p className="text-sm text-gray-500">{new Date(guest.date_club) > new Date() ? 'Vient le' : 'Venue le'} {new Date(guest.date_club).toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</p>
)}
{guest.invite_par_prenom && guest.invite_par_nom && (
<p className="text-xs text-gray-400">Invité par {guest.invite_par_prenom} {guest.invite_par_nom}</p>
)}
{guest.mail_envoye && guest.invitation_sent_at && (
<p className="text-xs text-gray-400">Email envoyé le {new Date(guest.invitation_sent_at).toLocaleDateString('fr-FR')}</p>
)}
</div>
</div>
)
}