From 4d46456adaaebf9902271ccfa11f72f2d4b23e98 Mon Sep 17 00:00:00 2001 From: soufiane Date: Mon, 1 Dec 2025 16:50:05 +0100 Subject: [PATCH] refactor: reduce code duplication by using reusable components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete duplicate page-new.tsx in verification folder - Create reusable StatCard component in components/ui - Enhance StatusBadge component with icons and REJECTED status - Refactor 7 files to use StatusBadge instead of local getStatusBadge - Refactor Statistics.tsx to use shared StatCard component - Reduces overall code duplication from 9.85% to lower đŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/client/page.tsx | 16 +- app/employe/gains-client/page.tsx | 30 +- app/employe/historique/page.tsx | 30 +- app/employe/verification/page-new.tsx | 462 -------------------------- app/employe/verification/page.tsx | 53 +-- app/historique/page.tsx | 16 +- app/mes-lots/page.tsx | 20 +- components/admin/Statistics.tsx | 37 +-- components/admin/TicketManagement.tsx | 36 +- components/ui/StatCard.tsx | 60 ++++ components/ui/StatusBadge.tsx | 22 +- components/ui/index.ts | 1 + 12 files changed, 108 insertions(+), 675 deletions(-) delete mode 100644 app/employe/verification/page-new.tsx create mode 100644 components/ui/StatCard.tsx 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 ! -

-
- ) : ( - - - - - - - - - - - - - {tickets.map((ticket) => ( - - - - - - - - - ))} - -
- Code Ticket - - Client - - Lot Gagné - - Date - - Statut - - Actions -
- - {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 && ( -
- -