feat: improve employee dashboard UI design

- Reorder sidebar: Dashboard first, then Validation des Tickets
- Add gradient backgrounds and modern card designs
- Replace emojis with SVG icons in statistics cards
- Add avatars with initials for client display
- Improve action cards with colored gradients (green, purple, slate)
- Style search sections with gradient backgrounds
- Add hover effects and transitions
- Remove value display from prize details (already in name)
- Improve filter buttons with gradient when active
- Add zebra striping and better table styling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
soufiane 2025-12-03 14:13:08 +01:00
parent d60c03cb0e
commit c578b81645
5 changed files with 419 additions and 337 deletions

View File

@ -97,109 +97,140 @@ export default function EmployeDashboardPage() {
} }
return ( return (
<div className="py-8"> <div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* Welcome Section */}
<div className="mb-8"> <div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2"> <h1 className="text-4xl font-bold text-[#2d5a3d] mb-2">
Tableau de bord employé Bonjour {user?.firstName} ! 👋
</h1> </h1>
<p className="text-gray-600"> <p className="text-gray-600 text-lg">
Bienvenue {user?.firstName}, voici un aperçu de votre activité Bienvenue dans votre espace employé. Voici un aperçu de votre activité.
</p> </p>
</div> </div>
{/* Statistics Cards */} {/* Statistics Cards */}
<div className="grid md:grid-cols-3 gap-6 mb-8"> <div className="grid md:grid-cols-3 gap-6 mb-8">
<Card className="p-6"> <div className="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600"> <p className="text-sm font-medium text-gray-500 mb-2">
Tickets en attente Tickets en attente
</p> </p>
<p className="text-3xl font-bold text-yellow-600 mt-2"> <p className="text-4xl font-bold text-yellow-500">
{stats.pendingTickets} {stats.pendingTickets}
</p> </p>
</div> </div>
<div className="text-4xl"></div> <div className="w-16 h-16 bg-yellow-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div> </div>
</Card> </div>
<Card className="p-6"> <div className="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600"> <p className="text-sm font-medium text-gray-500 mb-2">
Réclamés aujourd'hui Réclamés aujourd'hui
</p> </p>
<p className="text-3xl font-bold text-green-600 mt-2"> <p className="text-4xl font-bold text-green-600">
{stats.claimedToday} {stats.claimedToday}
</p> </p>
</div> </div>
<div className="text-4xl"></div> <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div> </div>
</Card> </div>
<Card className="p-6"> <div className="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600"> <p className="text-sm font-medium text-gray-500 mb-2">
Total réclamés Total réclamés
</p> </p>
<p className="text-3xl font-bold text-blue-600 mt-2"> <p className="text-4xl font-bold text-blue-600">
{stats.totalClaimed} {stats.totalClaimed}
</p> </p>
</div> </div>
<div className="text-4xl">📊</div> <div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
</div> </div>
</Card> </div>
</div>
{/* Quick Actions Title */}
<div className="mb-6">
<h2 className="text-2xl font-bold text-gray-800">Actions rapides</h2>
<p className="text-gray-500">Accédez rapidement aux fonctionnalités principales</p>
</div> </div>
{/* Quick Actions */} {/* Quick Actions */}
<div className="grid md:grid-cols-3 gap-6"> <div className="grid md:grid-cols-3 gap-6">
<Link href="/employe/verification"> <Link href="/employe/verification">
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer"> <div className="bg-gradient-to-br from-green-600 to-green-800 text-white rounded-xl shadow-md p-6 hover:shadow-xl transition-all hover:scale-[1.02] cursor-pointer">
<div className="flex items-center"> <div className="flex items-center gap-4">
<div className="text-5xl mr-4">🔍</div> <div className="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<div> <div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> <h3 className="text-xl font-bold mb-1">
Validation des gains Validation des gains
</h3> </h3>
<p className="text-gray-600"> <p className="text-white/80 text-sm">
Rechercher et valider les tickets des clients Rechercher et valider les tickets
</p> </p>
</div> </div>
</div> </div>
</Card> </div>
</Link> </Link>
<Link href="/employe/gains-client"> <Link href="/employe/gains-client">
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer"> <div className="bg-gradient-to-br from-purple-600 to-purple-800 text-white rounded-xl shadow-md p-6 hover:shadow-xl transition-all hover:scale-[1.02] cursor-pointer">
<div className="flex items-center"> <div className="flex items-center gap-4">
<div className="text-5xl mr-4">🎁</div> <div className="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7" />
</svg>
</div>
<div> <div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> <h3 className="text-xl font-bold mb-1">
Gains du Client Gains du Client
</h3> </h3>
<p className="text-gray-600"> <p className="text-white/80 text-sm">
Rechercher tous les gains d'un client Rechercher les gains d'un client
</p> </p>
</div> </div>
</div> </div>
</Card> </div>
</Link> </Link>
<Link href="/employe/historique"> <Link href="/employe/historique">
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer"> <div className="bg-gradient-to-br from-slate-600 to-slate-800 text-white rounded-xl shadow-md p-6 hover:shadow-xl transition-all hover:scale-[1.02] cursor-pointer">
<div className="flex items-center"> <div className="flex items-center gap-4">
<div className="text-5xl mr-4">📜</div> <div className="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
</div>
<div> <div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> <h3 className="text-xl font-bold mb-1">
Historique Historique
</h3> </h3>
<p className="text-gray-600"> <p className="text-white/80 text-sm">
Consulter l'historique des validations Consulter l'historique des validations
</p> </p>
</div> </div>
</div> </div>
</Card> </div>
</Link> </Link>
</div> </div>
</div> </div>

View File

@ -94,49 +94,55 @@ export default function GainsClientPage() {
return ( return (
<div className="p-6 max-w-7xl mx-auto"> <div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-8">
<h1 className="text-3xl font-bold mb-2 flex items-center gap-3"> <h1 className="text-4xl font-bold text-[#2d5a3d] mb-2">
<Gift className="w-10 h-10 text-purple-600" />
Gains du Client Gains du Client
</h1> </h1>
<p className="text-gray-600"> <p className="text-gray-600 text-lg">
Recherchez un client pour visualiser tous ses gains et les remettre Recherchez un client pour visualiser tous ses gains et les remettre
</p> </p>
</div> </div>
{/* Search Section */} {/* Search Section */}
<Card className="p-6 mb-6"> <div className="bg-gradient-to-r from-purple-600 to-purple-700 rounded-2xl shadow-lg p-8 mb-8">
<h2 className="text-xl font-bold mb-4">Rechercher un client</h2> <div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 bg-white/20 rounded-lg flex items-center justify-center">
<Search className="w-5 h-5 text-white" />
</div>
<h2 className="text-xl font-bold text-white">
Rechercher un client
</h2>
</div>
<div className="space-y-4"> <div className="space-y-4">
{/* Search Type Selection */} {/* Search Type Selection */}
<div className="flex gap-4"> <div className="flex gap-6">
<label className="flex items-center gap-2 cursor-pointer"> <label className={`flex items-center gap-3 cursor-pointer px-4 py-2 rounded-lg transition-all ${searchType === 'email' ? 'bg-white/20' : 'hover:bg-white/10'}`}>
<input <input
type="radio" type="radio"
name="searchType" name="searchType"
value="email" value="email"
checked={searchType === 'email'} checked={searchType === 'email'}
onChange={(e) => setSearchType(e.target.value as 'email')} onChange={(e) => setSearchType(e.target.value as 'email')}
className="w-4 h-4" className="w-4 h-4 accent-white"
/> />
<Mail className="w-5 h-5 text-gray-600" /> <Mail className="w-5 h-5 text-white" />
<span className="text-sm font-medium">Email</span> <span className="text-sm font-medium text-white">Email</span>
</label> </label>
<label className="flex items-center gap-2 cursor-pointer"> <label className={`flex items-center gap-3 cursor-pointer px-4 py-2 rounded-lg transition-all ${searchType === 'phone' ? 'bg-white/20' : 'hover:bg-white/10'}`}>
<input <input
type="radio" type="radio"
name="searchType" name="searchType"
value="phone" value="phone"
checked={searchType === 'phone'} checked={searchType === 'phone'}
onChange={(e) => setSearchType(e.target.value as 'phone')} onChange={(e) => setSearchType(e.target.value as 'phone')}
className="w-4 h-4" className="w-4 h-4 accent-white"
/> />
<Phone className="w-5 h-5 text-gray-600" /> <Phone className="w-5 h-5 text-white" />
<span className="text-sm font-medium">Téléphone</span> <span className="text-sm font-medium text-white">Téléphone</span>
</label> </label>
</div> </div>
@ -148,30 +154,29 @@ export default function GainsClientPage() {
value={searchValue} value={searchValue}
onChange={(e) => setSearchValue(e.target.value)} onChange={(e) => setSearchValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()} onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" className="flex-1 px-5 py-4 border-2 border-white/20 bg-white/10 text-white placeholder-white/60 rounded-xl focus:ring-2 focus:ring-white focus:border-transparent text-lg backdrop-blur-sm"
/> />
<Button <button
onClick={handleSearch} onClick={handleSearch}
isLoading={loading}
disabled={loading} disabled={loading}
className="bg-purple-600 hover:bg-purple-700" className="flex items-center gap-2 px-8 py-4 bg-white text-purple-700 rounded-xl hover:bg-purple-50 transition-all font-bold shadow-lg hover:shadow-xl hover:scale-[1.02] disabled:opacity-50"
> >
<Search className="w-5 h-5 mr-2" /> <Search className="w-5 h-5" />
Rechercher {loading ? 'Recherche...' : 'Rechercher'}
</Button> </button>
</div> </div>
</div> </div>
</Card> </div>
{/* Client Info & Prizes */} {/* Client Info & Prizes */}
{clientData && ( {clientData && (
<> <>
{/* Client Info Card */} {/* Client Info Card */}
<Card className="p-6 mb-6 bg-gradient-to-r from-purple-50 to-blue-50 border-purple-200"> <div className="bg-white rounded-2xl shadow-md border border-gray-100 p-6 mb-6">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="p-3 bg-white rounded-lg shadow-sm"> <div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center text-white font-bold text-xl shadow-lg">
<User className="w-8 h-8 text-purple-600" /> {clientData.client.firstName?.charAt(0)}{clientData.client.lastName?.charAt(0)}
</div> </div>
<div> <div>
<h2 className="text-2xl font-bold text-gray-900"> <h2 className="text-2xl font-bold text-gray-900">
@ -179,12 +184,12 @@ export default function GainsClientPage() {
</h2> </h2>
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
<p className="text-sm text-gray-600 flex items-center gap-2"> <p className="text-sm text-gray-600 flex items-center gap-2">
<Mail className="w-4 h-4" /> <Mail className="w-4 h-4 text-purple-500" />
{clientData.client.email} {clientData.client.email}
</p> </p>
{clientData.client.phone && ( {clientData.client.phone && (
<p className="text-sm text-gray-600 flex items-center gap-2"> <p className="text-sm text-gray-600 flex items-center gap-2">
<Phone className="w-4 h-4" /> <Phone className="w-4 h-4 text-purple-500" />
{clientData.client.phone} {clientData.client.phone}
</p> </p>
)} )}
@ -192,108 +197,116 @@ export default function GainsClientPage() {
</div> </div>
</div> </div>
<div className="text-right"> <div className="flex gap-4">
<div className="grid grid-cols-3 gap-4"> <div className="bg-gray-50 rounded-xl p-4 text-center min-w-[100px]">
<div className="bg-white rounded-lg p-3 shadow-sm"> <p className="text-xs font-medium text-gray-500 mb-1">Total</p>
<p className="text-xs text-gray-600">Total</p> <p className="text-3xl font-bold text-gray-900">{clientData.totalPrizes}</p>
<p className="text-2xl font-bold text-gray-900">{clientData.totalPrizes}</p> </div>
</div> <div className="bg-yellow-50 rounded-xl p-4 text-center min-w-[100px]">
<div className="bg-white rounded-lg p-3 shadow-sm"> <p className="text-xs font-medium text-yellow-600 mb-1">À remettre</p>
<p className="text-xs text-yellow-600">À remettre</p> <p className="text-3xl font-bold text-yellow-600">{clientData.pendingPrizes}</p>
<p className="text-2xl font-bold text-yellow-600">{clientData.pendingPrizes}</p> </div>
</div> <div className="bg-green-50 rounded-xl p-4 text-center min-w-[100px]">
<div className="bg-white rounded-lg p-3 shadow-sm"> <p className="text-xs font-medium text-green-600 mb-1">Remis</p>
<p className="text-xs text-green-600">Remis</p> <p className="text-3xl font-bold text-green-600">{clientData.claimedPrizes}</p>
<p className="text-2xl font-bold text-green-600">{clientData.claimedPrizes}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</Card> </div>
{/* Prizes List */} {/* Prizes List */}
<Card className="p-6"> <div className="bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden">
<h2 className="text-xl font-bold mb-4 flex items-center gap-2"> <div className="px-6 py-5 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
<Package className="w-6 h-6 text-purple-600" /> <div className="flex items-center gap-3">
Lots gagnés ({clientData.prizes.length}) <div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
</h2> <Gift className="w-5 h-5 text-purple-600" />
</div>
<div>
<h2 className="text-xl font-bold text-gray-800">
Lots gagnés
</h2>
<p className="text-sm text-gray-500">{clientData.prizes.length} lot(s) au total</p>
</div>
</div>
</div>
{clientData.prizes.length === 0 ? ( <div className="p-6">
<EmptyState {clientData.prizes.length === 0 ? (
icon="🎁" <div className="text-center py-12">
message="Ce client n'a pas encore gagné de lots" <div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
/> <Gift className="w-8 h-8 text-purple-600" />
) : ( </div>
<div className="space-y-4"> <p className="text-gray-600 font-medium">Ce client n'a pas encore gagné de lots</p>
{clientData.prizes.map((prize) => ( </div>
<div ) : (
key={prize.ticketId} <div className="space-y-4">
className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow" {clientData.prizes.map((prize) => (
> <div
<div className="flex items-start justify-between"> key={prize.ticketId}
<div className="flex-1"> className={`border-2 rounded-xl p-5 transition-all hover:shadow-md ${
<div className="flex items-center gap-3 mb-2"> prize.status === 'PENDING'
<h3 className="text-lg font-bold text-gray-900"> ? 'border-yellow-200 bg-yellow-50/30'
{prize.prize.name} : 'border-gray-100 bg-white'
</h3> }`}
<StatusBadge type="ticket" value={prize.status} showIcon /> >
</div> <div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-3">
<h3 className="text-lg font-bold text-gray-900">
{prize.prize.name}
</h3>
<StatusBadge type="ticket" value={prize.status} showIcon />
</div>
<p className="text-sm text-gray-600 mb-3"> <p className="text-sm text-gray-600 mb-4">
{prize.prize.description} {prize.prize.description}
</p> </p>
<div className="grid grid-cols-2 gap-4 text-sm"> <div className="grid grid-cols-2 gap-4 text-sm">
<div> <div className="flex items-center gap-2">
<span className="text-gray-600">Code ticket:</span> <span className="text-gray-500">Code:</span>
<span className="ml-2 font-mono font-semibold">{prize.ticketCode}</span> <span className="font-mono font-semibold bg-gray-100 px-2 py-1 rounded">{prize.ticketCode}</span>
</div> </div>
<div> <div className="flex items-center gap-2">
<span className="text-gray-600">Valeur:</span> <span className="text-gray-500">Gagné le:</span>
<span className="ml-2 font-semibold text-purple-600"> <span className="font-medium">
{prize.prize.value} {new Date(prize.playedAt).toLocaleDateString('fr-FR')}
</span>
</div>
<div>
<span className="text-gray-600">Gagné le:</span>
<span className="ml-2">
{new Date(prize.playedAt).toLocaleDateString('fr-FR')}
</span>
</div>
{prize.claimedAt && (
<div>
<span className="text-gray-600">Remis le:</span>
<span className="ml-2">
{new Date(prize.claimedAt).toLocaleDateString('fr-FR')}
</span> </span>
</div> </div>
)} {prize.claimedAt && (
{prize.validatedBy && ( <div className="flex items-center gap-2">
<div className="col-span-2"> <span className="text-gray-500">Remis le:</span>
<span className="text-gray-600">Remis par:</span> <span className="font-medium text-green-600">
<span className="ml-2 font-medium">{prize.validatedBy}</span> {new Date(prize.claimedAt).toLocaleDateString('fr-FR')}
</div> </span>
)} </div>
)}
{prize.validatedBy && (
<div className="col-span-2 flex items-center gap-2">
<span className="text-gray-500">Remis par:</span>
<span className="font-medium">{prize.validatedBy}</span>
</div>
)}
</div>
</div> </div>
</div>
{prize.status === 'PENDING' && ( {prize.status === 'PENDING' && (
<Button <button
onClick={() => handleValidatePrize(prize.ticketId)} onClick={() => handleValidatePrize(prize.ticketId)}
isLoading={validatingTicketId === prize.ticketId} disabled={validatingTicketId === prize.ticketId}
disabled={validatingTicketId === prize.ticketId} className="ml-4 flex items-center gap-2 px-5 py-3 bg-gradient-to-r from-green-600 to-green-700 text-white font-semibold rounded-xl hover:from-green-700 hover:to-green-800 transition-all shadow-sm hover:shadow-md disabled:opacity-50"
className="ml-4 bg-green-600 hover:bg-green-700" >
> <CheckCircle className="w-5 h-5" />
<CheckCircle className="w-5 h-5 mr-2" /> {validatingTicketId === prize.ticketId ? 'Validation...' : 'Marquer comme remis'}
Marquer comme remis </button>
</Button> )}
)} </div>
</div> </div>
</div> ))}
))} </div>
</div> )}
)} </div>
</Card> </div>
</> </>
)} )}
</div> </div>

View File

@ -73,148 +73,169 @@ export default function EmployeeHistoryPage() {
} }
return ( return (
<div className="p-6 max-w-7xl mx-auto"> <div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-8">
<h1 className="text-3xl font-bold mb-2 flex items-center gap-3"> <h1 className="text-4xl font-bold text-[#2d5a3d] mb-2">
<Calendar className="w-10 h-10 text-purple-600" />
Historique des Validations Historique des Validations
</h1> </h1>
<p className="text-gray-600"> <p className="text-gray-600 text-lg">
Consultez l'historique de vos validations de tickets Consultez l'historique de vos validations de tickets
</p> </p>
</div> </div>
{/* Statistics Cards */} {/* Statistics Cards */}
<div className="grid md:grid-cols-3 gap-6 mb-6"> <div className="grid md:grid-cols-3 gap-6 mb-8">
<Card className="p-6 bg-gradient-to-br from-blue-50 to-blue-100"> <div className="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">Total traités</p> <p className="text-sm font-medium text-gray-500 mb-2">Total traités</p>
<p className="text-3xl font-bold text-blue-600 mt-2">{stats.total}</p> <p className="text-4xl font-bold text-blue-600">{stats.total}</p>
</div>
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div> </div>
<div className="text-4xl">📊</div>
</div> </div>
</Card> </div>
<Card className="p-6 bg-gradient-to-br from-green-50 to-green-100"> <div className="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">Validés</p> <p className="text-sm font-medium text-gray-500 mb-2">Validés</p>
<p className="text-3xl font-bold text-green-600 mt-2">{stats.claimed}</p> <p className="text-4xl font-bold text-green-600">{stats.claimed}</p>
</div>
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div> </div>
<div className="text-4xl"></div>
</div> </div>
</Card> </div>
<Card className="p-6 bg-gradient-to-br from-red-50 to-red-100"> <div className="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-shadow">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">Rejetés</p> <p className="text-sm font-medium text-gray-500 mb-2">Rejetés</p>
<p className="text-3xl font-bold text-red-600 mt-2">{stats.rejected}</p> <p className="text-4xl font-bold text-red-600">{stats.rejected}</p>
</div>
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div> </div>
<div className="text-4xl"></div>
</div> </div>
</Card> </div>
</div> </div>
{/* Filters */} {/* Filters & History List */}
<Card className="p-4 mb-6"> <div className="bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden">
<div className="flex items-center justify-between"> <div className="px-6 py-5 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
<div className="flex gap-2"> <div className="flex items-center justify-between">
<div className="flex gap-2">
<button
onClick={() => setFilter('ALL')}
className={`px-5 py-2.5 rounded-xl font-semibold transition-all ${
filter === 'ALL'
? 'bg-gradient-to-r from-slate-600 to-slate-700 text-white shadow-md'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
Tous ({history.length})
</button>
<button
onClick={() => setFilter('CLAIMED')}
className={`px-5 py-2.5 rounded-xl font-semibold transition-all ${
filter === 'CLAIMED'
? 'bg-gradient-to-r from-green-600 to-green-700 text-white shadow-md'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
Validés ({stats.claimed})
</button>
<button
onClick={() => setFilter('REJECTED')}
className={`px-5 py-2.5 rounded-xl font-semibold transition-all ${
filter === 'REJECTED'
? 'bg-gradient-to-r from-red-600 to-red-700 text-white shadow-md'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
Rejetés ({stats.rejected})
</button>
</div>
<button <button
onClick={() => setFilter('ALL')} onClick={loadHistory}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${ className="flex items-center gap-2 px-4 py-2 text-green-700 bg-green-50 hover:bg-green-100 rounded-lg transition-colors font-medium"
filter === 'ALL'
? 'bg-purple-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
> >
Tous ({history.length}) <RefreshCw className="w-4 h-4" />
</button> Actualiser
<button
onClick={() => setFilter('CLAIMED')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
filter === 'CLAIMED'
? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Validés ({stats.claimed})
</button>
<button
onClick={() => setFilter('REJECTED')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
filter === 'REJECTED'
? 'bg-red-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Rejetés ({stats.rejected})
</button> </button>
</div> </div>
<button
onClick={loadHistory}
className="flex items-center gap-2 px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
>
<RefreshCw className="w-4 h-4" />
Actualiser
</button>
</div> </div>
</Card>
{/* History List */} <div className="p-6">
<Card className="p-6"> {filteredHistory.length === 0 ? (
{filteredHistory.length === 0 ? ( <div className="text-center py-12">
<EmptyState <div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
icon="📝" <Calendar className="w-8 h-8 text-gray-400" />
title="Aucun ticket dans l'historique" </div>
message={filter === 'ALL' <p className="text-gray-800 font-semibold mb-1">Aucun ticket dans l'historique</p>
? 'Vous n\'avez pas encore validé de tickets' <p className="text-sm text-gray-500">
: `Aucun ticket ${filter === 'CLAIMED' ? 'validé' : 'rejeté'}`} {filter === 'ALL'
/> ? 'Vous n\'avez pas encore validé de tickets'
) : ( : `Aucun ticket ${filter === 'CLAIMED' ? 'validé' : 'rejeté'}`}
<div className="space-y-4"> </p>
{filteredHistory.map((ticket) => ( </div>
<div ) : (
key={ticket.id} <div className="space-y-4">
className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow" {filteredHistory.map((ticket) => (
> <div
<div className="flex items-start justify-between"> key={ticket.id}
<div className="flex-1"> className={`border-2 rounded-xl p-5 transition-all hover:shadow-md ${
<div className="flex items-center gap-3 mb-3"> ticket.status === 'REJECTED'
<span className="font-mono font-bold text-lg text-gray-900"> ? 'border-red-200 bg-red-50/30'
{ticket.code} : 'border-gray-100 bg-white'
</span> }`}
<StatusBadge type="ticket" value={ticket.status} showIcon /> >
</div> <div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-4">
<span className="font-mono font-bold text-lg bg-gray-100 px-3 py-1 rounded-lg text-gray-900">
{ticket.code}
</span>
<StatusBadge type="ticket" value={ticket.status} showIcon />
</div>
<div className="grid md:grid-cols-2 gap-4 mb-3"> <div className="grid md:grid-cols-2 gap-6 mb-4">
<div className="flex items-start gap-2"> <div className="flex items-start gap-3">
<User className="w-5 h-5 text-gray-400 mt-0.5" /> <div className="w-10 h-10 bg-gradient-to-br from-green-500 to-green-600 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
<div> {ticket.user_name.split(' ').map(n => n.charAt(0)).join('').slice(0, 2)}
<p className="text-sm font-medium text-gray-600">Client</p> </div>
<p className="text-sm text-gray-900">{ticket.user_name}</p> <div>
<p className="text-xs text-gray-500">{ticket.user_email}</p> <p className="text-xs font-medium text-gray-500 mb-1">Client</p>
<p className="text-sm font-semibold text-gray-900">{ticket.user_name}</p>
<p className="text-xs text-gray-500">{ticket.user_email}</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0">
<Gift className="w-5 h-5 text-purple-600" />
</div>
<div>
<p className="text-xs font-medium text-gray-500 mb-1">Lot</p>
<p className="text-sm font-semibold text-gray-900">{ticket.prize_name}</p>
</div>
</div> </div>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-center gap-2 text-sm text-gray-500">
<Gift className="w-5 h-5 text-gray-400 mt-0.5" /> <Calendar className="w-4 h-4" />
<div>
<p className="text-sm font-medium text-gray-600">Lot</p>
<p className="text-sm text-gray-900">{ticket.prize_name}</p>
<p className="text-xs text-gray-500">{ticket.prize_value}</p>
</div>
</div>
</div>
<div className="flex items-center gap-4 text-xs text-gray-500">
<div className="flex items-center gap-1">
<Calendar className="w-3 h-3" />
<span> <span>
Validé le{' '} {ticket.status === 'REJECTED' ? 'Rejeté' : 'Validé'} le{' '}
{ticket.validated_at {ticket.validated_at
? new Date(ticket.validated_at).toLocaleDateString('fr-FR', { ? new Date(ticket.validated_at).toLocaleDateString('fr-FR', {
day: 'numeric', day: 'numeric',
@ -226,23 +247,23 @@ export default function EmployeeHistoryPage() {
: 'N/A'} : 'N/A'}
</span> </span>
</div> </div>
</div>
{ticket.rejection_reason && ( {ticket.rejection_reason && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-lg"> <div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-xl">
<p className="text-sm font-medium text-red-800 mb-1"> <p className="text-sm font-semibold text-red-800 mb-1">
Raison du rejet: Raison du rejet:
</p> </p>
<p className="text-sm text-red-700">{ticket.rejection_reason}</p> <p className="text-sm text-red-700">{ticket.rejection_reason}</p>
</div> </div>
)} )}
</div>
</div> </div>
</div> </div>
</div> ))}
))} </div>
</div> )}
)} </div>
</Card> </div>
</div> </div>
); );
} }

View File

@ -51,16 +51,16 @@ export default function EmployeLayout({
} }
const navItems = [ const navItems = [
{
label: "Validation des Tickets",
href: "/employe/verification",
icon: <Ticket className="w-5 h-5" />,
},
{ {
label: "Dashboard", label: "Dashboard",
href: "/employe/dashboard", href: "/employe/dashboard",
icon: <BarChart3 className="w-5 h-5" />, icon: <BarChart3 className="w-5 h-5" />,
}, },
{
label: "Validation des Tickets",
href: "/employe/verification",
icon: <Ticket className="w-5 h-5" />,
},
]; ];
const isActive = (href: string) => pathname === href; const isActive = (href: string) => pathname === href;

View File

@ -125,37 +125,42 @@ export default function EmployeeVerificationPage() {
} }
return ( return (
<div className="p-8 bg-gray-50 min-h-screen"> <div className="min-h-full bg-gradient-to-br from-gray-50 via-white to-gray-50 p-8">
{/* Header */} {/* Header */}
<div className="mb-8"> <div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2"> <h1 className="text-4xl font-bold text-[#2d5a3d] mb-2">
Validation des Tickets Validation des Tickets
</h1> </h1>
<p className="text-gray-600"> <p className="text-gray-600 text-lg">
Scannez ou recherchez un code pour valider les lots gagnés Scannez ou recherchez un code pour valider les lots gagnés
</p> </p>
</div> </div>
{/* Search Section */} {/* Search Section */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8"> <div className="bg-gradient-to-r from-green-600 to-green-700 rounded-2xl shadow-lg p-8 mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-4"> <div className="flex items-center gap-3 mb-4">
Rechercher un ticket <div className="w-10 h-10 bg-white/20 rounded-lg flex items-center justify-center">
</h2> <Search className="w-5 h-5 text-white" />
</div>
<h2 className="text-xl font-bold text-white">
Rechercher un ticket
</h2>
</div>
<div className="flex gap-4"> <div className="flex gap-4">
<div className="flex-1"> <div className="flex-1">
<input <input
type="text" type="text"
placeholder="Entrez le code du ticket (ex: ABC123)" placeholder="Entrez le code du ticket (ex: TTABC123)"
value={searchCode} value={searchCode}
onChange={(e) => setSearchCode(e.target.value.toUpperCase())} onChange={(e) => setSearchCode(e.target.value.toUpperCase())}
onKeyPress={(e) => e.key === "Enter" && handleSearch()} 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" className="w-full px-5 py-4 border-2 border-white/20 bg-white/10 text-white placeholder-white/60 rounded-xl focus:ring-2 focus:ring-white focus:border-transparent font-mono text-lg backdrop-blur-sm"
maxLength={10} maxLength={12}
/> />
</div> </div>
<button <button
onClick={handleSearch} onClick={handleSearch}
className="flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium" className="flex items-center gap-2 px-8 py-4 bg-white text-green-700 rounded-xl hover:bg-green-50 transition-all font-bold shadow-lg hover:shadow-xl hover:scale-[1.02]"
> >
<Search className="w-5 h-5" /> <Search className="w-5 h-5" />
Rechercher Rechercher
@ -164,14 +169,22 @@ export default function EmployeeVerificationPage() {
</div> </div>
{/* Lots en attente de remise */} {/* Lots en attente de remise */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200"> <div className="bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between"> <div className="px-6 py-5 border-b border-gray-100 flex items-center justify-between bg-gradient-to-r from-gray-50 to-white">
<h2 className="text-lg font-semibold text-gray-900"> <div className="flex items-center gap-3">
Lots en attente de remise <div className="w-10 h-10 bg-yellow-100 rounded-lg flex items-center justify-center">
</h2> <Clock className="w-5 h-5 text-yellow-600" />
</div>
<div>
<h2 className="text-xl font-bold text-gray-800">
Lots en attente de remise
</h2>
<p className="text-sm text-gray-500">{tickets.length} ticket(s) en attente</p>
</div>
</div>
<button <button
onClick={loadPendingTickets} onClick={loadPendingTickets}
className="flex items-center gap-2 px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg transition-colors" className="flex items-center gap-2 px-4 py-2 text-green-700 bg-green-50 hover:bg-green-100 rounded-lg transition-colors font-medium"
> >
<RefreshCw className="w-4 h-4" /> <RefreshCw className="w-4 h-4" />
Actualiser Actualiser
@ -181,8 +194,10 @@ export default function EmployeeVerificationPage() {
<div className="overflow-x-auto"> <div className="overflow-x-auto">
{tickets.length === 0 ? ( {tickets.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<div className="text-6xl mb-4"></div> <div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<p className="text-gray-600 mb-2 font-medium"> <CheckCircle className="w-10 h-10 text-green-600" />
</div>
<p className="text-gray-800 mb-2 font-semibold text-lg">
Aucun lot en attente Aucun lot en attente
</p> </p>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
@ -190,60 +205,70 @@ export default function EmployeeVerificationPage() {
</p> </p>
</div> </div>
) : ( ) : (
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full">
<thead className="bg-gray-50"> <thead className="bg-gray-50 border-b border-gray-100">
<tr> <tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">
Client Client
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">
Lot Gagné Lot Gagné
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">
Date Date
</th> </th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-4 text-right text-xs font-bold text-gray-500 uppercase tracking-wider">
Action Action
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="divide-y divide-gray-50">
{tickets.map((ticket) => ( {tickets.map((ticket, index) => (
<tr key={ticket.id} className="hover:bg-gray-50"> <tr key={ticket.id} className={`hover:bg-green-50/50 transition-colors ${index % 2 === 0 ? 'bg-white' : 'bg-gray-50/30'}`}>
<td className="px-6 py-4"> <td className="px-6 py-5">
<div> <div className="flex items-center gap-3">
<div className="text-sm font-medium text-gray-900"> <div className="w-10 h-10 bg-gradient-to-br from-green-500 to-green-600 rounded-full flex items-center justify-center text-white font-bold text-sm">
{ticket.user ? `${ticket.user.firstName} ${ticket.user.lastName}` : 'N/A'} {ticket.user ? `${ticket.user.firstName?.charAt(0) || ''}${ticket.user.lastName?.charAt(0) || ''}` : 'N/A'}
</div> </div>
<div className="text-sm text-gray-500"> <div>
{ticket.user?.email || 'N/A'} <div className="text-sm font-semibold text-gray-900">
{ticket.user ? `${ticket.user.firstName} ${ticket.user.lastName}` : 'N/A'}
</div>
<div className="text-sm text-gray-500">
{ticket.user?.email || 'N/A'}
</div>
</div> </div>
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-5">
<div> <div>
<div className="text-sm font-medium text-gray-900"> <div className="text-sm font-semibold text-gray-900">
{ticket.prize?.name || 'N/A'} {ticket.prize?.name || 'N/A'}
</div> </div>
<div className="text-xs text-gray-500"> <div className="text-xs text-gray-500 font-mono bg-gray-100 inline-block px-2 py-1 rounded mt-1">
Code: {ticket.code} {ticket.code}
</div> </div>
</div> </div>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-5 whitespace-nowrap">
{ticket.playedAt <div className="text-sm font-medium text-gray-700">
? new Date(ticket.playedAt).toLocaleDateString('fr-FR') {ticket.playedAt
: 'N/A'} ? new Date(ticket.playedAt).toLocaleDateString('fr-FR')
: 'N/A'}
</div>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td className="px-6 py-5 whitespace-nowrap text-right">
<button <button
onClick={() => { onClick={() => {
setSelectedTicket(ticket); setSelectedTicket(ticket);
setShowModal(true); setShowModal(true);
}} }}
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500" className="inline-flex items-center gap-2 px-5 py-2.5 bg-gradient-to-r from-green-600 to-green-700 text-white text-sm font-semibold rounded-lg hover:from-green-700 hover:to-green-800 transition-all shadow-sm hover:shadow-md"
> >
Traiter Traiter
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button> </button>
</td> </td>
</tr> </tr>
@ -318,14 +343,6 @@ export default function EmployeeVerificationPage() {
{selectedTicket.prize?.description || 'Description non disponible'} {selectedTicket.prize?.description || 'Description non disponible'}
</p> </p>
</div> </div>
{selectedTicket.prize?.value && (
<div className="mt-2">
<p className="text-sm font-medium text-gray-600">Valeur</p>
<p className="text-base font-semibold text-gray-900">
{selectedTicket.prize.value}
</p>
</div>
)}
{selectedTicket.playedAt && ( {selectedTicket.playedAt && (
<div> <div>
<p className="text-sm font-medium text-gray-600">Date de gain</p> <p className="text-sm font-medium text-gray-600">Date de gain</p>