280 lines
10 KiB
TypeScript
280 lines
10 KiB
TypeScript
"use client";
|
|
import { useState, useEffect } from "react";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
import { useGame } from "@/hooks/useGame";
|
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/Card";
|
|
import { Badge } from "@/components/ui/Badge";
|
|
import {
|
|
Table,
|
|
TableHeader,
|
|
TableBody,
|
|
TableRow,
|
|
TableHead,
|
|
TableCell,
|
|
} from "@/components/ui/Table";
|
|
import { Loading } from "@/components/ui/Loading";
|
|
import { PRIZE_CONFIG, TICKET_STATUS_LABELS, TICKET_STATUS_COLORS } from "@/utils/constants";
|
|
import { formatDateTime } from "@/utils/helpers";
|
|
import { Ticket } from "@/types";
|
|
import { useRouter } from "next/navigation";
|
|
import { ROUTES } from "@/utils/constants";
|
|
import Button from "@/components/Button";
|
|
|
|
export default function HistoriquePage() {
|
|
const { user, isAuthenticated } = useAuth();
|
|
const { getMyTickets, isLoadingTickets } = useGame();
|
|
const router = useRouter();
|
|
const [tickets, setTickets] = useState<Ticket[]>([]);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated && user?.role?.toLowerCase() === "client") {
|
|
loadTickets();
|
|
}
|
|
}, [isAuthenticated, user, currentPage]);
|
|
|
|
const loadTickets = async () => {
|
|
try {
|
|
setError(null);
|
|
console.log('🎫 Chargement des tickets, page:', currentPage);
|
|
const result = await getMyTickets(currentPage, 10);
|
|
console.log('📦 Résultat API:', result);
|
|
if (result) {
|
|
console.log('✅ Tickets reçus:', result.data);
|
|
setTickets(result.data || []);
|
|
setTotalPages(result.totalPages || 1);
|
|
} else {
|
|
console.log('❌ Aucun résultat reçu');
|
|
setTickets([]);
|
|
setError('Impossible de charger vos tickets. Veuillez réessayer.');
|
|
}
|
|
} catch (err) {
|
|
console.error('❌ Erreur lors du chargement:', err);
|
|
setError('Erreur lors du chargement de vos tickets.');
|
|
setTickets([]);
|
|
}
|
|
};
|
|
|
|
if (!isAuthenticated || user?.role?.toLowerCase() !== "client") {
|
|
return (
|
|
<div className="min-h-[calc(100vh-8rem)] flex items-center justify-center">
|
|
<Card className="max-w-md text-center">
|
|
<CardContent className="py-8">
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
|
Accès non autorisé
|
|
</h2>
|
|
<p className="text-gray-600 mb-6">
|
|
Cette page est réservée aux clients
|
|
</p>
|
|
<p className="text-sm text-gray-500 mb-4">
|
|
Votre rôle actuel : {user?.role || 'Non défini'}
|
|
</p>
|
|
<Button onClick={() => router.push(ROUTES.HOME)}>
|
|
Retour à l'accueil
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const getStatusBadge = (status: string) => {
|
|
const statusLower = status.toLowerCase();
|
|
const variant =
|
|
statusLower === "claimed"
|
|
? "success"
|
|
: statusLower === "pending"
|
|
? "warning"
|
|
: "danger";
|
|
return (
|
|
<Badge variant={variant}>
|
|
{TICKET_STATUS_LABELS[statusLower as keyof typeof TICKET_STATUS_LABELS] || status}
|
|
</Badge>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="py-8">
|
|
<div className="max-w-6xl mx-auto">
|
|
<div className="flex items-center justify-between mb-8">
|
|
<h1 className="text-3xl font-bold text-gray-900">
|
|
Historique de mes gains
|
|
</h1>
|
|
<Button onClick={() => router.push(ROUTES.GAME)}>
|
|
Jouer à nouveau
|
|
</Button>
|
|
</div>
|
|
|
|
{error && (
|
|
<Card className="mb-6">
|
|
<CardContent className="py-6">
|
|
<div className="flex items-center gap-3 text-red-600">
|
|
<span className="text-2xl">⚠️</span>
|
|
<div>
|
|
<p className="font-semibold">Erreur de chargement</p>
|
|
<p className="text-sm text-red-500">{error}</p>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={loadTickets}
|
|
className="mt-3"
|
|
>
|
|
Réessayer
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{isLoadingTickets ? (
|
|
<Loading text="Chargement de l'historique..." />
|
|
) : tickets.length === 0 && !error ? (
|
|
<Card>
|
|
<CardContent className="py-16 text-center">
|
|
<div className="text-6xl mb-4">🎲</div>
|
|
<h2 className="text-2xl font-semibold text-gray-900 mb-2">
|
|
Aucun ticket pour le moment
|
|
</h2>
|
|
<p className="text-gray-600 mb-6">
|
|
Commencez à jouer pour voir vos gains apparaître ici
|
|
</p>
|
|
<Button onClick={() => router.push(ROUTES.GAME)} size="lg" className="bg-white text-black hover:bg-gray-50 border-2 border-black">
|
|
Jouer maintenant
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
) : tickets.length > 0 ? (
|
|
<>
|
|
{/* Stats Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-500">
|
|
Total de tickets
|
|
</p>
|
|
<p className="text-3xl font-bold text-gray-900">
|
|
{tickets.length}
|
|
</p>
|
|
</div>
|
|
<div className="text-4xl">🎫</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-500">
|
|
Gains réclamés
|
|
</p>
|
|
<p className="text-3xl font-bold text-green-600">
|
|
{tickets.filter((t) => t.status.toLowerCase() === "claimed").length}
|
|
</p>
|
|
</div>
|
|
<div className="text-4xl">✅</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-500">
|
|
En attente
|
|
</p>
|
|
<p className="text-3xl font-bold text-yellow-600">
|
|
{tickets.filter((t) => t.status.toLowerCase() === "pending").length}
|
|
</p>
|
|
</div>
|
|
<div className="text-4xl">⏳</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Tickets Table */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Mes tickets</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Code</TableHead>
|
|
<TableHead>Lot gagné</TableHead>
|
|
<TableHead>Statut</TableHead>
|
|
<TableHead>Date</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{tickets.map((ticket) => (
|
|
<TableRow key={ticket.id}>
|
|
<TableCell className="font-mono font-semibold">
|
|
{ticket.code}
|
|
</TableCell>
|
|
<TableCell>
|
|
{ticket.prize && (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-2xl">
|
|
{PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG]?.icon || '🎁'}
|
|
</span>
|
|
<span>
|
|
{ticket.prize.name || PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG]?.name}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>{getStatusBadge(ticket.status)}</TableCell>
|
|
<TableCell className="text-gray-600">
|
|
{ticket.playedAt ? formatDateTime(ticket.playedAt) : ticket.createdAt ? formatDateTime(ticket.createdAt) : '-'}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
{/* Pagination */}
|
|
{totalPages > 1 && (
|
|
<div className="flex items-center justify-between mt-6 pt-6 border-t">
|
|
<p className="text-sm text-gray-600">
|
|
Page {currentPage} sur {totalPages}
|
|
</p>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
|
disabled={currentPage === 1}
|
|
>
|
|
Précédent
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() =>
|
|
setCurrentPage((p) => Math.min(totalPages, p + 1))
|
|
}
|
|
disabled={currentPage === totalPages}
|
|
>
|
|
Suivant
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|