diff --git a/app/client/page.tsx b/app/client/page.tsx
index dc95dc6..20bf70a 100644
--- a/app/client/page.tsx
+++ b/app/client/page.tsx
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
import { useAuth } from "@/contexts/AuthContext";
import { useRouter } from "next/navigation";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/Card";
-import { Badge } from "@/components/ui/Badge";
+import { StatusBadge } from "@/components/ui/StatusBadge";
import Button from "@/components/Button";
import { Loading } from "@/components/ui/Loading";
import { Ticket } from "@/types";
@@ -64,18 +64,6 @@ export default function ClientPage() {
return null;
}
- const getStatusBadge = (status: string) => {
- switch (status) {
- case "CLAIMED":
- return Réclamé;
- case "PENDING":
- return En attente;
- case "REJECTED":
- return Rejeté;
- default:
- return {status};
- }
- };
return (
@@ -266,7 +254,7 @@ export default function ClientPage() {
- {getStatusBadge(ticket.status)}
+
|
{ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString("fr-FR") : "-"}
diff --git a/app/employe/gains-client/page.tsx b/app/employe/gains-client/page.tsx
index ba097e5..cfcc6fd 100644
--- a/app/employe/gains-client/page.tsx
+++ b/app/employe/gains-client/page.tsx
@@ -1,8 +1,7 @@
'use client';
import { useState } from 'react';
-import { Card } from '@/components/ui/Card';
-import { EmptyState } from '@/components/ui/EmptyState';
+import { Card, EmptyState, StatusBadge } from '@/components/ui';
import Button from '@/components/Button';
import { api } from '@/hooks/useApi';
import toast from 'react-hot-toast';
@@ -11,8 +10,6 @@ import {
User,
Gift,
CheckCircle,
- Clock,
- XCircle,
Phone,
Mail,
Package,
@@ -95,29 +92,6 @@ export default function GainsClientPage() {
}
};
- const getStatusBadge = (status: string) => {
- const badges = {
- PENDING: (
-
-
- À remettre
-
- ),
- CLAIMED: (
-
-
- Remis
-
- ),
- REJECTED: (
-
-
- Rejeté
-
- ),
- };
- return badges[status as keyof typeof badges] || badges.PENDING;
- };
return (
@@ -262,7 +236,7 @@ export default function GainsClientPage() {
{prize.prize.name}
- {getStatusBadge(prize.status)}
+
diff --git a/app/employe/historique/page.tsx b/app/employe/historique/page.tsx
index 67d80f7..90024cd 100644
--- a/app/employe/historique/page.tsx
+++ b/app/employe/historique/page.tsx
@@ -2,14 +2,11 @@
import { useState, useEffect } from 'react';
import { useAuth } from '@/hooks';
-import { Card, EmptyState } from '@/components/ui';
+import { Card, EmptyState, StatusBadge } from '@/components/ui';
import { Loading } from '@/components/ui/Loading';
import { api } from '@/hooks/useApi';
import toast from 'react-hot-toast';
import {
- CheckCircle,
- XCircle,
- Clock,
Calendar,
User,
Gift,
@@ -55,29 +52,6 @@ export default function EmployeeHistoryPage() {
}
};
- const getStatusBadge = (status: string) => {
- const badges = {
- PENDING: (
-
-
- En attente
-
- ),
- CLAIMED: (
-
-
- Validé
-
- ),
- REJECTED: (
-
-
- Rejeté
-
- ),
- };
- return badges[status as keyof typeof badges] || badges.PENDING;
- };
const filteredHistory = history.filter((ticket) => {
if (filter === 'ALL') return true;
@@ -213,7 +187,7 @@ export default function EmployeeHistoryPage() {
{ticket.code}
- {getStatusBadge(ticket.status)}
+
diff --git a/app/employe/verification/page-new.tsx b/app/employe/verification/page-new.tsx
deleted file mode 100644
index 76981f3..0000000
--- a/app/employe/verification/page-new.tsx
+++ /dev/null
@@ -1,462 +0,0 @@
-"use client";
-
-import { useState, useEffect } from "react";
-import { employeeService } from "@/services/employee.service";
-import { Ticket } from "@/types";
-import toast from "react-hot-toast";
-import {
- Search,
- CheckCircle,
- XCircle,
- RefreshCw,
- AlertCircle,
- Users,
- Clock,
- BarChart3,
-} from "lucide-react";
-
-export default function EmployeeVerificationPage() {
- const [tickets, setTickets] = useState ([]);
- const [loading, setLoading] = useState(true);
- const [searchCode, setSearchCode] = useState("");
- const [selectedTicket, setSelectedTicket] = useState(null);
- const [showModal, setShowModal] = useState(false);
- const [validating, setValidating] = useState(false);
- const [rejectReason, setRejectReason] = useState("");
- const [showRejectInput, setShowRejectInput] = useState(false);
-
- useEffect(() => {
- loadPendingTickets();
- }, []);
-
- const loadPendingTickets = async () => {
- try {
- setLoading(true);
- const data = await employeeService.getPendingTickets();
- setTickets(data);
- } catch (error: any) {
- console.error("Error loading tickets:", error);
- toast.error("Erreur lors du chargement des tickets");
- } finally {
- setLoading(false);
- }
- };
-
- const handleSearch = () => {
- if (!searchCode.trim()) {
- toast.error("Veuillez entrer un code de ticket");
- return;
- }
-
- const ticket = tickets.find(
- (t) => t.code.toLowerCase() === searchCode.toLowerCase()
- );
-
- if (ticket) {
- setSelectedTicket(ticket);
- setShowModal(true);
- setSearchCode("");
- } else {
- toast.error("Ticket non trouvé ou déjà traité");
- }
- };
-
- const handleValidate = async () => {
- if (!selectedTicket) return;
-
- setValidating(true);
- try {
- await employeeService.validateTicket(selectedTicket.id, "APPROVE");
- toast.success("✅ Ticket validé ! Le lot peut être remis au client.");
- setShowModal(false);
- setSelectedTicket(null);
- loadPendingTickets();
- } catch (error: any) {
- toast.error(error.message || "Erreur lors de la validation");
- } finally {
- setValidating(false);
- }
- };
-
- const handleReject = async () => {
- if (!selectedTicket) return;
-
- if (!rejectReason.trim()) {
- toast.error("Veuillez indiquer la raison du rejet");
- return;
- }
-
- setValidating(true);
- try {
- await employeeService.validateTicket(
- selectedTicket.id,
- "REJECT",
- rejectReason
- );
- toast.success("Ticket rejeté");
- setShowModal(false);
- setSelectedTicket(null);
- setShowRejectInput(false);
- setRejectReason("");
- loadPendingTickets();
- } catch (error: any) {
- toast.error(error.message || "Erreur lors du rejet");
- } finally {
- setValidating(false);
- }
- };
-
- const getStatusBadge = (status: string) => {
- const badges: Record = {
- PENDING: (
-
-
- En attente
-
- ),
- REJECTED: (
-
-
- Rejeté
-
- ),
- CLAIMED: (
-
-
- Réclamé
-
- ),
- };
- return badges[status] || badges.PENDING;
- };
-
- if (loading) {
- return (
-
- );
- }
-
- return (
-
- {/* Header */}
-
-
- Validation des Tickets
-
-
- Scannez ou recherchez un code pour valider les lots gagnés
-
-
-
- {/* Search Section */}
-
-
- Rechercher un ticket
-
-
-
- setSearchCode(e.target.value.toUpperCase())}
- onKeyPress={(e) => e.key === "Enter" && handleSearch()}
- className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent font-mono text-lg"
- maxLength={10}
- />
-
-
-
-
-
- {/* Statistics Cards */}
-
- }
- color="yellow"
- />
- }
- color="green"
- />
- }
- color="blue"
- />
-
-
- {/* Tickets Table */}
-
-
-
- Tickets en attente ({tickets.length})
-
-
-
-
-
- {tickets.length === 0 ? (
-
- ✨
-
- Aucun ticket en attente
-
-
- Tous les tickets ont été traités !
-
-
- ) : (
-
-
-
- |
- Code Ticket
- |
-
- Client
- |
-
- Lot Gagné
- |
-
- Date
- |
-
- Statut
- |
-
- Actions
- |
-
-
-
- {tickets.map((ticket) => (
-
- |
-
- {ticket.code}
-
- |
-
-
-
- {ticket.user?.firstName} {ticket.user?.lastName}
-
- {ticket.user?.email}
-
- |
-
-
- {ticket.prize?.name || "N/A"}
-
- |
-
- {ticket.playedAt
- ? new Date(ticket.playedAt).toLocaleDateString("fr-FR")
- : "N/A"}
- |
-
- {getStatusBadge(ticket.status)}
- |
-
-
- |
-
- ))}
-
-
- )}
-
-
-
- {/* Modal de validation */}
- {showModal && selectedTicket && (
-
-
-
- Détails du Ticket
-
-
-
- {/* Ticket Info */}
-
-
-
-
- Code du ticket
-
-
- {selectedTicket.code}
-
-
-
- Statut
- {getStatusBadge(selectedTicket.status)}
-
-
-
-
-
- Lot gagné
-
-
- {selectedTicket.prize?.name}
-
-
- {selectedTicket.prize?.description}
-
-
-
-
- Client
-
- {selectedTicket.user?.firstName} {selectedTicket.user?.lastName}
-
-
- {selectedTicket.user?.email}
-
- {selectedTicket.user?.phone && (
-
- {selectedTicket.user?.phone}
-
- )}
-
-
-
- {/* Reject Reason Input */}
- {showRejectInput && (
-
-
-
- )}
-
- {/* Actions */}
-
- {!showRejectInput ? (
- <>
-
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
-
-
-
- )}
-
- );
-}
-
-interface StatCardProps {
- title: string;
- value: number;
- icon: React.ReactNode;
- color: "yellow" | "green" | "blue";
-}
-
-function StatCard({ title, value, icon, color }: StatCardProps) {
- const colors = {
- yellow: "bg-yellow-100 text-yellow-600",
- green: "bg-green-100 text-green-600",
- blue: "bg-blue-100 text-blue-600",
- };
-
- return (
-
-
-
- {title}
-
- {value.toLocaleString("fr-FR")}
-
-
- {icon}
-
-
- );
-}
diff --git a/app/employe/verification/page.tsx b/app/employe/verification/page.tsx
index d255c16..8cb48f8 100644
--- a/app/employe/verification/page.tsx
+++ b/app/employe/verification/page.tsx
@@ -4,12 +4,12 @@ import { useState, useEffect } from "react";
import { employeeService } from "@/services/employee.service";
import { Ticket } from "@/types";
import toast from "react-hot-toast";
+import { StatusBadge, StatCard } from "@/components/ui";
import {
Search,
CheckCircle,
XCircle,
RefreshCw,
- AlertCircle,
Users,
Clock,
BarChart3,
@@ -106,29 +106,6 @@ export default function EmployeeVerificationPage() {
}
};
- const getStatusBadge = (status: string) => {
- const badges: Record = {
- PENDING: (
-
-
- En attente
-
- ),
- REJECTED: (
-
-
- Rejeté
-
- ),
- CLAIMED: (
-
-
- Réclamé
-
- ),
- };
- return badges[status] || badges.PENDING;
- };
if (loading) {
return (
@@ -438,31 +415,3 @@ export default function EmployeeVerificationPage() {
);
}
-interface StatCardProps {
- title: string;
- value: number;
- icon: React.ReactNode;
- color: "yellow" | "green" | "blue";
-}
-
-function StatCard({ title, value, icon, color }: StatCardProps) {
- const colors = {
- yellow: "bg-yellow-100 text-yellow-600",
- green: "bg-green-100 text-green-600",
- blue: "bg-blue-100 text-blue-600",
- };
-
- return (
-
-
-
- {title}
-
- {value.toLocaleString("fr-FR")}
-
-
- {icon}
-
-
- );
-}
diff --git a/app/historique/page.tsx b/app/historique/page.tsx
index a46e6e0..9e7da89 100644
--- a/app/historique/page.tsx
+++ b/app/historique/page.tsx
@@ -3,7 +3,7 @@ import { useEffect, useState, useCallback } from "react";
import { useAuth } from "@/contexts/AuthContext";
import { useRouter } from "next/navigation";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/Card";
-import { Badge } from "@/components/ui/Badge";
+import { StatusBadge } from "@/components/ui/StatusBadge";
import Button from "@/components/Button";
import { Loading } from "@/components/ui/Loading";
import { Ticket } from "@/types";
@@ -89,18 +89,6 @@ export default function HistoriquePage() {
return null;
}
- const getStatusBadge = (status: string) => {
- switch (status) {
- case "CLAIMED":
- return Réclamé;
- case "PENDING":
- return En attente;
- case "REJECTED":
- return Rejeté;
- default:
- return {status};
- }
- };
return (
@@ -331,7 +319,7 @@ export default function HistoriquePage() {
|
- {getStatusBadge(ticket.status)}
+
|
{ticket.playedAt
diff --git a/app/mes-lots/page.tsx b/app/mes-lots/page.tsx
index c29871e..9c742d4 100644
--- a/app/mes-lots/page.tsx
+++ b/app/mes-lots/page.tsx
@@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } 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 { StatusBadge } from "@/components/ui/StatusBadge";
import {
Table,
TableHeader,
@@ -13,7 +13,7 @@ import {
TableCell,
} from "@/components/ui/Table";
import { Loading } from "@/components/ui/Loading";
-import { PRIZE_CONFIG, TICKET_STATUS_LABELS, TICKET_STATUS_COLORS } from "@/utils/constants";
+import { PRIZE_CONFIG } from "@/utils/constants";
import { formatDateTime } from "@/utils/helpers";
import { Ticket } from "@/types";
import { useRouter } from "next/navigation";
@@ -80,20 +80,6 @@ export default function HistoriquePage() {
);
}
- const getStatusBadge = (status: string) => {
- const statusLower = status.toLowerCase();
- const variant =
- statusLower === "claimed"
- ? "success"
- : statusLower === "pending"
- ? "warning"
- : "danger";
- return (
-
- {TICKET_STATUS_LABELS[statusLower as keyof typeof TICKET_STATUS_LABELS] || status}
-
- );
- };
return (
@@ -232,7 +218,7 @@ export default function HistoriquePage() {
)}
- {getStatusBadge(ticket.status)}
+
{ticket.playedAt ? formatDateTime(ticket.playedAt) : ticket.createdAt ? formatDateTime(ticket.createdAt) : '-'}
diff --git a/components/admin/Statistics.tsx b/components/admin/Statistics.tsx
index 59319ff..ac439a7 100644
--- a/components/admin/Statistics.tsx
+++ b/components/admin/Statistics.tsx
@@ -3,6 +3,7 @@
import { useState, useEffect } from 'react';
import { adminService } from '@/services/admin.service';
import { AdminStatistics } from '@/types';
+import { StatCard } from '@/components/ui';
export default function Statistics() {
const [stats, setStats] = useState(null);
@@ -58,27 +59,27 @@ export default function Statistics() {
@@ -90,27 +91,27 @@ export default function Statistics() {
@@ -128,19 +129,3 @@ export default function Statistics() {
);
}
-interface StatCardProps {
- title: string;
- value: number;
- color: string;
-}
-
-function StatCard({ title, value, color }: StatCardProps) {
- return (
-
- {title}
-
- {(value || 0).toLocaleString('fr-FR')}
-
-
- );
-}
diff --git a/components/admin/TicketManagement.tsx b/components/admin/TicketManagement.tsx
index 831ddb5..99a4c39 100644
--- a/components/admin/TicketManagement.tsx
+++ b/components/admin/TicketManagement.tsx
@@ -3,6 +3,7 @@
import { useState, useEffect, useCallback } from 'react';
import { adminService } from '@/services/admin.service';
import { Ticket } from '@/types';
+import { StatusBadge } from '@/components/ui';
export default function TicketManagement() {
const [tickets, setTickets] = useState([]);
@@ -74,31 +75,6 @@ export default function TicketManagement() {
loadTickets();
}, [loadTickets]);
- const getStatusBadgeColor = (status: string) => {
- switch (status) {
- case 'PENDING':
- return 'bg-yellow-100 text-yellow-800';
- case 'REJECTED':
- return 'bg-red-100 text-red-800';
- case 'CLAIMED':
- return 'bg-green-100 text-green-800';
- default:
- return 'bg-gray-100 text-gray-800';
- }
- };
-
- const getStatusLabel = (status: string) => {
- switch (status) {
- case 'PENDING':
- return 'En attente';
- case 'REJECTED':
- return 'Rejeté';
- case 'CLAIMED':
- return 'Réclamé';
- default:
- return status;
- }
- };
if (loading && tickets.length === 0) {
return Chargement des tickets... ;
@@ -264,7 +240,7 @@ export default function TicketManagement() {
{filterStatus
- ? `Aucun ticket avec le statut "${getStatusLabel(filterStatus)}"`
+ ? `Aucun ticket avec le statut "${filterStatus === 'PENDING' ? 'En attente' : filterStatus === 'CLAIMED' ? 'Réclamé' : filterStatus === 'REJECTED' ? 'Rejeté' : filterStatus}"`
: 'Aucun ticket n\'a été créé pour le moment'
}
@@ -295,9 +271,7 @@ export default function TicketManagement() {
{/* STATUT */}
-
- {getStatusLabel(ticket.status)}
-
+
|
{/* DISTRIBUÉ LE (date d'utilisation du ticket) */}
@@ -374,9 +348,7 @@ export default function TicketManagement() {
Statut
-
- {getStatusLabel(selectedTicket.status)}
-
+
diff --git a/components/ui/StatCard.tsx b/components/ui/StatCard.tsx
new file mode 100644
index 0000000..d2e6a80
--- /dev/null
+++ b/components/ui/StatCard.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import React from 'react';
+import { cn } from '@/utils/helpers';
+
+type ColorVariant = 'blue' | 'green' | 'yellow' | 'red' | 'purple' | 'orange' | 'gray';
+
+interface StatCardProps {
+ title: string;
+ value: number | string;
+ icon?: React.ReactNode;
+ color?: ColorVariant;
+ subtitle?: string;
+ className?: string;
+}
+
+const colorClasses: Record = {
+ blue: { bg: 'bg-blue-50', text: 'text-blue-600', iconBg: 'bg-blue-100 text-blue-600' },
+ green: { bg: 'bg-green-50', text: 'text-green-600', iconBg: 'bg-green-100 text-green-600' },
+ yellow: { bg: 'bg-yellow-50', text: 'text-yellow-600', iconBg: 'bg-yellow-100 text-yellow-600' },
+ red: { bg: 'bg-red-50', text: 'text-red-600', iconBg: 'bg-red-100 text-red-600' },
+ purple: { bg: 'bg-purple-50', text: 'text-purple-600', iconBg: 'bg-purple-100 text-purple-600' },
+ orange: { bg: 'bg-orange-50', text: 'text-orange-600', iconBg: 'bg-orange-100 text-orange-600' },
+ gray: { bg: 'bg-gray-50', text: 'text-gray-600', iconBg: 'bg-gray-100 text-gray-600' },
+};
+
+export const StatCard: React.FC = ({
+ title,
+ value,
+ icon,
+ color = 'blue',
+ subtitle,
+ className,
+}) => {
+ const colors = colorClasses[color];
+ const formattedValue = typeof value === 'number' ? value.toLocaleString('fr-FR') : value;
+
+ return (
+
+
+
+ {title}
+
+ {formattedValue}
+
+ {subtitle && (
+ {subtitle}
+ )}
+
+ {icon && (
+
+ {icon}
+
+ )}
+
+
+ );
+};
+
+export default StatCard;
diff --git a/components/ui/StatusBadge.tsx b/components/ui/StatusBadge.tsx
index 2edf8c6..4f5e90f 100644
--- a/components/ui/StatusBadge.tsx
+++ b/components/ui/StatusBadge.tsx
@@ -2,15 +2,17 @@
import React from 'react';
import { cn } from '@/utils/helpers';
+import { CheckCircle, Clock, XCircle, AlertCircle } from 'lucide-react';
type RoleType = 'ADMIN' | 'EMPLOYEE' | 'CLIENT' | string;
-type TicketStatusType = 'AVAILABLE' | 'CLAIMED' | 'PENDING' | 'EXPIRED' | string;
+type TicketStatusType = 'AVAILABLE' | 'CLAIMED' | 'PENDING' | 'EXPIRED' | 'REJECTED' | string;
type StatusType = 'active' | 'inactive' | 'verified' | 'unverified' | string;
interface StatusBadgeProps {
type: 'role' | 'ticket' | 'status' | 'custom';
value: string;
className?: string;
+ showIcon?: boolean;
}
const roleColors: Record = {
@@ -24,6 +26,7 @@ const ticketColors: Record = {
CLAIMED: 'bg-green-100 text-green-800',
PENDING: 'bg-yellow-100 text-yellow-800',
EXPIRED: 'bg-red-100 text-red-800',
+ REJECTED: 'bg-red-100 text-red-800',
};
const statusColors: Record = {
@@ -44,6 +47,7 @@ const ticketLabels: Record = {
CLAIMED: 'Réclamé',
PENDING: 'En attente',
EXPIRED: 'Expiré',
+ REJECTED: 'Rejeté',
};
const statusLabels: Record = {
@@ -53,6 +57,14 @@ const statusLabels: Record = {
unverified: 'Non vérifié',
};
+const ticketIcons: Record = {
+ AVAILABLE: ,
+ CLAIMED: ,
+ PENDING: ,
+ EXPIRED: ,
+ REJECTED: ,
+};
+
/**
* Reusable status badge component for roles, ticket status, and general status
*/
@@ -60,9 +72,11 @@ export const StatusBadge: React.FC = ({
type,
value,
className,
+ showIcon = false,
}) => {
let colorClass = 'bg-gray-100 text-gray-800';
let label = value;
+ let icon: React.ReactNode = null;
switch (type) {
case 'role':
@@ -72,6 +86,9 @@ export const StatusBadge: React.FC = ({
case 'ticket':
colorClass = ticketColors[value] || colorClass;
label = ticketLabels[value] || value;
+ if (showIcon) {
+ icon = ticketIcons[value] || null;
+ }
break;
case 'status':
colorClass = statusColors[value] || colorClass;
@@ -85,11 +102,12 @@ export const StatusBadge: React.FC = ({
return (
+ {icon}
{label}
);
diff --git a/components/ui/index.ts b/components/ui/index.ts
index b9502c8..e1ed9d8 100644
--- a/components/ui/index.ts
+++ b/components/ui/index.ts
@@ -7,5 +7,6 @@ export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '.
export { LoadingState } from './LoadingState';
export { ErrorState } from './ErrorState';
export { StatusBadge, getRoleBadgeColor, getTicketStatusColor, getStatusColor } from './StatusBadge';
+export { StatCard } from './StatCard';
export { EmptyState } from './EmptyState';
export { Pagination } from './Pagination';
|