This commit is contained in:
Emma781227
2026-06-26 11:26:45 +02:00
parent 1cf2a24ede
commit 2d25c91c55
14 changed files with 540 additions and 165 deletions

View File

@@ -1,72 +1,179 @@
const MOCK_STATS = {
totalParticipants: 1248,
gainsRemis: 342,
gainsEnAttente: 906,
tauxParticipation: '78%',
import { useState, useEffect } from 'react'
import { getStatsApi, getUsersApi, drawWinnerApi, getWinnerApi } from '../services/admin'
function StatCard({ label, value, sub }) {
return (
<div className="bg-white rounded-xl shadow p-4 text-center">
<p className="text-2xl sm:text-3xl font-bold text-green-700">{value ?? '—'}</p>
<p className="text-xs sm:text-sm text-gray-500 mt-1">{label}</p>
{sub && <p className="text-xs text-gray-400 mt-0.5">{sub}</p>}
</div>
)
}
const MOCK_USERS = [
{ id: 1, name: 'Jean Dupont', email: 'jean.dupont@email.com', gains: 2 },
{ id: 2, name: 'Marie Martin', email: 'marie.martin@email.com', gains: 1 },
{ id: 3, name: 'Paul Bernard', email: 'paul.bernard@email.com', gains: 3 },
]
export default function Admin() {
const [stats, setStats] = useState(null)
const [users, setUsers] = useState([])
const [winner, setWinner] = useState(undefined)
const [drawing, setDrawing] = useState(false)
const [drawError, setDrawError] = useState('')
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
useEffect(() => {
Promise.all([getStatsApi(), getUsersApi(), getWinnerApi()])
.then(([s, u, w]) => {
setStats(s)
setUsers(u.users)
setWinner(w)
})
.catch((e) => setError(e.message))
.finally(() => setLoading(false))
}, [])
const handleDraw = async () => {
if (!window.confirm('Lancer le tirage au sort ? Cette action est irréversible.')) return
setDrawing(true)
setDrawError('')
try {
const data = await drawWinnerApi()
setWinner(data)
} catch (e) {
setDrawError(e.message)
} finally {
setDrawing(false)
}
}
if (loading) return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<p className="text-gray-400">Chargement...</p>
</div>
)
if (error) return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center px-4">
<p className="text-red-500 text-sm">{error}</p>
</div>
)
return (
<div className="min-h-screen bg-gray-50 p-4 sm:p-6">
<div className="max-w-4xl mx-auto">
<h1 className="text-xl sm:text-2xl font-bold text-gray-800 mb-4 sm:mb-6">Tableau de bord Admin</h1>
<div className="max-w-5xl mx-auto space-y-6">
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 sm:gap-4 mb-6 sm:mb-8">
{[
{ label: 'Participants', value: MOCK_STATS.totalParticipants },
{ label: 'Gains remis', value: MOCK_STATS.gainsRemis },
{ label: 'En attente', value: MOCK_STATS.gainsEnAttente },
{ label: 'Taux participation', value: MOCK_STATS.tauxParticipation },
].map((stat) => (
<div key={stat.label} className="bg-white rounded-xl shadow p-3 sm:p-4 text-center">
<p className="text-xl sm:text-2xl font-bold text-green-700">{stat.value}</p>
<p className="text-xs sm:text-sm text-gray-500 mt-1">{stat.label}</p>
</div>
))}
<h1 className="text-xl sm:text-2xl font-bold text-gray-800">Tableau de bord Admin</h1>
{/* Stats principales */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
<StatCard label="Participants" value={stats?.users} />
<StatCard label="Participations" value={stats?.participations} />
<StatCard label="Gains en attente" value={stats?.claimsPending} />
<StatCard label="Gains remis" value={stats?.claimsDelivered} />
</div>
{/* Stats tickets */}
<div className="grid grid-cols-3 gap-3 sm:gap-4">
<StatCard label="Tickets total" value={stats?.ticketsTotal} />
<StatCard label="Tickets utilisés" value={stats?.ticketsUsed} />
<StatCard label="Tickets restants" value={stats?.ticketsRemaining} />
</div>
{/* Répartition par lot */}
{stats?.lotsStats?.length > 0 && (
<div className="bg-white rounded-xl shadow p-4 sm:p-6">
<h2 className="text-base sm:text-lg font-bold text-gray-800 mb-4">Répartition des lots</h2>
<div className="overflow-x-auto -mx-4 sm:mx-0">
<table className="w-full text-sm min-w-[480px]">
<thead>
<tr className="text-left text-gray-500 border-b text-xs sm:text-sm">
<th className="pb-2 px-4 sm:px-0">Lot</th>
<th className="pb-2 px-4 sm:px-0 text-center">%</th>
<th className="pb-2 px-4 sm:px-0 text-center">Tickets</th>
<th className="pb-2 px-4 sm:px-0 text-center">Participations</th>
</tr>
</thead>
<tbody>
{stats.lotsStats.map((lot) => (
<tr key={lot.id} className="border-b last:border-0">
<td className="py-2.5 px-4 sm:px-0 font-medium text-gray-800 text-xs sm:text-sm">{lot.name}</td>
<td className="py-2.5 px-4 sm:px-0 text-center text-gray-500 text-xs sm:text-sm">{lot.percentage}%</td>
<td className="py-2.5 px-4 sm:px-0 text-center text-gray-600 text-xs sm:text-sm">{lot._count.tickets.toLocaleString('fr-FR')}</td>
<td className="py-2.5 px-4 sm:px-0 text-center text-green-700 font-semibold text-xs sm:text-sm">{lot._count.participations}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Tirage au sort */}
<div className="bg-white rounded-xl shadow p-4 sm:p-6 mb-4 sm:mb-6">
<div className="bg-white rounded-xl shadow p-4 sm:p-6">
<h2 className="text-base sm:text-lg font-bold text-gray-800 mb-3">Grand tirage au sort</h2>
<button
onClick={() => alert('TODO: connecter au backend')}
className="bg-green-700 hover:bg-green-800 text-white font-semibold px-5 sm:px-6 py-2 sm:py-2.5 rounded-lg transition-colors text-sm sm:text-base"
>
Lancer le tirage
</button>
{winner?.winner ? (
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<p className="text-green-700 font-semibold mb-1">🏆 Gagnant tiré au sort</p>
<p className="text-gray-800 font-medium">
{winner.winner.user.firstname} {winner.winner.user.lastname}
</p>
<p className="text-gray-500 text-sm">{winner.winner.user.email}</p>
<p className="text-gray-400 text-xs mt-1">
Lot : {winner.winner.prize}
</p>
</div>
) : (
<div className="space-y-2">
<button
onClick={handleDraw}
disabled={drawing}
className="bg-green-700 hover:bg-green-800 disabled:opacity-60 text-white font-semibold px-5 py-2.5 rounded-lg transition-colors text-sm"
>
{drawing ? 'Tirage en cours...' : 'Lancer le tirage'}
</button>
{drawError && <p className="text-red-500 text-xs">{drawError}</p>}
</div>
)}
</div>
{/* Liste participants */}
<div className="bg-white rounded-xl shadow p-4 sm:p-6">
<h2 className="text-base sm:text-lg font-bold text-gray-800 mb-4">Participants</h2>
<h2 className="text-base sm:text-lg font-bold text-gray-800 mb-4">
Participants <span className="text-gray-400 font-normal text-sm">({users.length})</span>
</h2>
<div className="overflow-x-auto -mx-4 sm:mx-0">
<table className="w-full text-sm min-w-[400px]">
<thead>
<tr className="text-left text-gray-500 border-b">
<tr className="text-left text-gray-500 border-b text-xs sm:text-sm">
<th className="pb-2 px-4 sm:px-0">Nom</th>
<th className="pb-2 px-4 sm:px-0">Email</th>
<th className="pb-2 px-4 sm:px-0 text-right">Gains</th>
<th className="pb-2 px-4 sm:px-0 text-right">Participations</th>
</tr>
</thead>
<tbody>
{MOCK_USERS.map((user) => (
{users.length === 0 && (
<tr>
<td colSpan={3} className="py-6 text-center text-gray-400 text-sm">
Aucun participant pour le moment
</td>
</tr>
)}
{users.map((user) => (
<tr key={user.id} className="border-b last:border-0">
<td className="py-3 px-4 sm:px-0 font-medium text-gray-800 whitespace-nowrap">{user.name}</td>
<td className="py-3 px-4 sm:px-0 text-gray-500 text-xs sm:text-sm">{user.email}</td>
<td className="py-3 px-4 sm:px-0 text-right text-green-700 font-semibold">{user.gains}</td>
<td className="py-3 px-4 sm:px-0 font-medium text-gray-800 whitespace-nowrap text-xs sm:text-sm">
{user.firstname} {user.lastname}
</td>
<td className="py-3 px-4 sm:px-0 text-gray-500 text-xs">{user.email}</td>
<td className="py-3 px-4 sm:px-0 text-right text-green-700 font-semibold text-xs sm:text-sm">
{user._count.participations}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)