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 (
<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">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
Tableau de bord employé
<h1 className="text-4xl font-bold text-[#2d5a3d] mb-2">
Bonjour {user?.firstName} ! 👋
</h1>
<p className="text-gray-600">
Bienvenue {user?.firstName}, voici un aperçu de votre activité
<p className="text-gray-600 text-lg">
Bienvenue dans votre espace employé. Voici un aperçu de votre activité.
</p>
</div>
{/* Statistics Cards */}
<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>
<p className="text-sm font-medium text-gray-600">
<p className="text-sm font-medium text-gray-500 mb-2">
Tickets en attente
</p>
<p className="text-3xl font-bold text-yellow-600 mt-2">
<p className="text-4xl font-bold text-yellow-500">
{stats.pendingTickets}
</p>
</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>
</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>
<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
</p>
<p className="text-3xl font-bold text-green-600 mt-2">
<p className="text-4xl font-bold text-green-600">
{stats.claimedToday}
</p>
</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>
</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>
<p className="text-sm font-medium text-gray-600">
<p className="text-sm font-medium text-gray-500 mb-2">
Total réclamés
</p>
<p className="text-3xl font-bold text-blue-600 mt-2">
<p className="text-4xl font-bold text-blue-600">
{stats.totalClaimed}
</p>
</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>
</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>
{/* Quick Actions */}
<div className="grid md:grid-cols-3 gap-6">
<Link href="/employe/verification">
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer">
<div className="flex items-center">
<div className="text-5xl mr-4">🔍</div>
<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 gap-4">
<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>
<h3 className="text-xl font-bold text-gray-900 mb-2">
<h3 className="text-xl font-bold mb-1">
Validation des gains
</h3>
<p className="text-gray-600">
Rechercher et valider les tickets des clients
<p className="text-white/80 text-sm">
Rechercher et valider les tickets
</p>
</div>
</div>
</Card>
</div>
</Link>
<Link href="/employe/gains-client">
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer">
<div className="flex items-center">
<div className="text-5xl mr-4">🎁</div>
<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 gap-4">
<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>
<h3 className="text-xl font-bold text-gray-900 mb-2">
<h3 className="text-xl font-bold mb-1">
Gains du Client
</h3>
<p className="text-gray-600">
Rechercher tous les gains d'un client
<p className="text-white/80 text-sm">
Rechercher les gains d'un client
</p>
</div>
</div>
</Card>
</div>
</Link>
<Link href="/employe/historique">
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer">
<div className="flex items-center">
<div className="text-5xl mr-4">📜</div>
<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 gap-4">
<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>
<h3 className="text-xl font-bold text-gray-900 mb-2">
<h3 className="text-xl font-bold mb-1">
Historique
</h3>
<p className="text-gray-600">
<p className="text-white/80 text-sm">
Consulter l'historique des validations
</p>
</div>
</div>
</Card>
</div>
</Link>
</div>
</div>

View File

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

View File

@ -73,148 +73,169 @@ export default function EmployeeHistoryPage() {
}
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 */}
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2 flex items-center gap-3">
<Calendar className="w-10 h-10 text-purple-600" />
<div className="mb-8">
<h1 className="text-4xl font-bold text-[#2d5a3d] mb-2">
Historique des Validations
</h1>
<p className="text-gray-600">
<p className="text-gray-600 text-lg">
Consultez l'historique de vos validations de tickets
</p>
</div>
{/* Statistics Cards */}
<div className="grid md:grid-cols-3 gap-6 mb-6">
<Card className="p-6 bg-gradient-to-br from-blue-50 to-blue-100">
<div className="grid md:grid-cols-3 gap-6 mb-8">
<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>
<p className="text-sm font-medium text-gray-600">Total traités</p>
<p className="text-3xl font-bold text-blue-600 mt-2">{stats.total}</p>
<p className="text-sm font-medium text-gray-500 mb-2">Total traités</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 className="text-4xl">📊</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>
<p className="text-sm font-medium text-gray-600">Validés</p>
<p className="text-3xl font-bold text-green-600 mt-2">{stats.claimed}</p>
<p className="text-sm font-medium text-gray-500 mb-2">Validés</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 className="text-4xl"></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>
<p className="text-sm font-medium text-gray-600">Rejetés</p>
<p className="text-3xl font-bold text-red-600 mt-2">{stats.rejected}</p>
<p className="text-sm font-medium text-gray-500 mb-2">Rejetés</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 className="text-4xl"></div>
</div>
</Card>
</div>
</div>
{/* Filters */}
<Card className="p-4 mb-6">
<div className="flex items-center justify-between">
<div className="flex gap-2">
{/* Filters & History List */}
<div className="bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden">
<div className="px-6 py-5 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
<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
onClick={() => setFilter('ALL')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
filter === 'ALL'
? 'bg-purple-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
onClick={loadHistory}
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"
>
Tous ({history.length})
</button>
<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})
<RefreshCw className="w-4 h-4" />
Actualiser
</button>
</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>
</Card>
{/* History List */}
<Card className="p-6">
{filteredHistory.length === 0 ? (
<EmptyState
icon="📝"
title="Aucun ticket dans l'historique"
message={filter === 'ALL'
? 'Vous n\'avez pas encore validé de tickets'
: `Aucun ticket ${filter === 'CLAIMED' ? 'validé' : 'rejeté'}`}
/>
) : (
<div className="space-y-4">
{filteredHistory.map((ticket) => (
<div
key={ticket.id}
className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-3">
<span className="font-mono font-bold text-lg text-gray-900">
{ticket.code}
</span>
<StatusBadge type="ticket" value={ticket.status} showIcon />
</div>
<div className="p-6">
{filteredHistory.length === 0 ? (
<div className="text-center py-12">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Calendar className="w-8 h-8 text-gray-400" />
</div>
<p className="text-gray-800 font-semibold mb-1">Aucun ticket dans l'historique</p>
<p className="text-sm text-gray-500">
{filter === 'ALL'
? 'Vous n\'avez pas encore validé de tickets'
: `Aucun ticket ${filter === 'CLAIMED' ? 'validé' : 'rejeté'}`}
</p>
</div>
) : (
<div className="space-y-4">
{filteredHistory.map((ticket) => (
<div
key={ticket.id}
className={`border-2 rounded-xl p-5 transition-all hover:shadow-md ${
ticket.status === 'REJECTED'
? 'border-red-200 bg-red-50/30'
: 'border-gray-100 bg-white'
}`}
>
<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="flex items-start gap-2">
<User className="w-5 h-5 text-gray-400 mt-0.5" />
<div>
<p className="text-sm font-medium text-gray-600">Client</p>
<p className="text-sm text-gray-900">{ticket.user_name}</p>
<p className="text-xs text-gray-500">{ticket.user_email}</p>
<div className="grid md:grid-cols-2 gap-6 mb-4">
<div className="flex items-start gap-3">
<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">
{ticket.user_name.split(' ').map(n => n.charAt(0)).join('').slice(0, 2)}
</div>
<div>
<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 className="flex items-start gap-2">
<Gift className="w-5 h-5 text-gray-400 mt-0.5" />
<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" />
<div className="flex items-center gap-2 text-sm text-gray-500">
<Calendar className="w-4 h-4" />
<span>
Validé le{' '}
{ticket.status === 'REJECTED' ? 'Rejeté' : 'Validé'} le{' '}
{ticket.validated_at
? new Date(ticket.validated_at).toLocaleDateString('fr-FR', {
day: 'numeric',
@ -226,23 +247,23 @@ export default function EmployeeHistoryPage() {
: 'N/A'}
</span>
</div>
</div>
{ticket.rejection_reason && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-lg">
<p className="text-sm font-medium text-red-800 mb-1">
Raison du rejet:
</p>
<p className="text-sm text-red-700">{ticket.rejection_reason}</p>
</div>
)}
{ticket.rejection_reason && (
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-xl">
<p className="text-sm font-semibold text-red-800 mb-1">
Raison du rejet:
</p>
<p className="text-sm text-red-700">{ticket.rejection_reason}</p>
</div>
)}
</div>
</div>
</div>
</div>
))}
</div>
)}
</Card>
))}
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -51,16 +51,16 @@ export default function EmployeLayout({
}
const navItems = [
{
label: "Validation des Tickets",
href: "/employe/verification",
icon: <Ticket className="w-5 h-5" />,
},
{
label: "Dashboard",
href: "/employe/dashboard",
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;

View File

@ -125,37 +125,42 @@ export default function EmployeeVerificationPage() {
}
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 */}
<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
</h1>
<p className="text-gray-600">
<p className="text-gray-600 text-lg">
Scannez ou recherchez un code pour valider les lots gagnés
</p>
</div>
{/* Search Section */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-4">
Rechercher un ticket
</h2>
<div className="bg-gradient-to-r from-green-600 to-green-700 rounded-2xl shadow-lg p-8 mb-8">
<div className="flex items-center gap-3 mb-4">
<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 ticket
</h2>
</div>
<div className="flex gap-4">
<div className="flex-1">
<input
type="text"
placeholder="Entrez le code du ticket (ex: ABC123)"
placeholder="Entrez le code du ticket (ex: TTABC123)"
value={searchCode}
onChange={(e) => 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}
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={12}
/>
</div>
<button
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" />
Rechercher
@ -164,14 +169,22 @@ export default function EmployeeVerificationPage() {
</div>
{/* Lots en attente de remise */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900">
Lots en attente de remise
</h2>
<div className="bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden">
<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">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-yellow-100 rounded-lg flex items-center justify-center">
<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
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" />
Actualiser
@ -181,8 +194,10 @@ export default function EmployeeVerificationPage() {
<div className="overflow-x-auto">
{tickets.length === 0 ? (
<div className="text-center py-16">
<div className="text-6xl mb-4"></div>
<p className="text-gray-600 mb-2 font-medium">
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<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
</p>
<p className="text-sm text-gray-500">
@ -190,60 +205,70 @@ export default function EmployeeVerificationPage() {
</p>
</div>
) : (
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<table className="min-w-full">
<thead className="bg-gray-50 border-b border-gray-100">
<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
</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é
</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
</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
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{tickets.map((ticket) => (
<tr key={ticket.id} className="hover:bg-gray-50">
<td className="px-6 py-4">
<div>
<div className="text-sm font-medium text-gray-900">
{ticket.user ? `${ticket.user.firstName} ${ticket.user.lastName}` : 'N/A'}
<tbody className="divide-y divide-gray-50">
{tickets.map((ticket, index) => (
<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-5">
<div className="flex items-center gap-3">
<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?.charAt(0) || ''}${ticket.user.lastName?.charAt(0) || ''}` : 'N/A'}
</div>
<div className="text-sm text-gray-500">
{ticket.user?.email || 'N/A'}
<div>
<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>
</td>
<td className="px-6 py-4">
<td className="px-6 py-5">
<div>
<div className="text-sm font-medium text-gray-900">
<div className="text-sm font-semibold text-gray-900">
{ticket.prize?.name || 'N/A'}
</div>
<div className="text-xs text-gray-500">
Code: {ticket.code}
<div className="text-xs text-gray-500 font-mono bg-gray-100 inline-block px-2 py-1 rounded mt-1">
{ticket.code}
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{ticket.playedAt
? new Date(ticket.playedAt).toLocaleDateString('fr-FR')
: 'N/A'}
<td className="px-6 py-5 whitespace-nowrap">
<div className="text-sm font-medium text-gray-700">
{ticket.playedAt
? new Date(ticket.playedAt).toLocaleDateString('fr-FR')
: 'N/A'}
</div>
</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
onClick={() => {
setSelectedTicket(ticket);
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>
</td>
</tr>
@ -318,14 +343,6 @@ export default function EmployeeVerificationPage() {
{selectedTicket.prize?.description || 'Description non disponible'}
</p>
</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 && (
<div>
<p className="text-sm font-medium text-gray-600">Date de gain</p>