the-tip-top-frontend/app/employe/gains-client/page.tsx

328 lines
14 KiB
TypeScript

'use client';
import { useState } from 'react';
import { StatusBadge } from '@/components/ui';
import { api } from '@/hooks/useApi';
import toast from 'react-hot-toast';
import {
Search,
Gift,
CheckCircle,
Phone,
Mail,
Package,
Clock,
User,
} from 'lucide-react';
interface ClientPrize {
ticketId: string;
ticketCode: string;
status: string;
playedAt: string;
claimedAt: string | null;
validatedAt: string | null;
validatedBy: string | null;
prize: {
id: string;
name: string;
type: string;
value: string;
description: string;
};
}
interface ClientData {
client: {
id: string;
email: string;
firstName: string;
lastName: string;
phone: string;
};
prizes: ClientPrize[];
totalPrizes: number;
pendingPrizes: number;
claimedPrizes: number;
}
export default function GainsClientPage() {
const [searchType, setSearchType] = useState<'email' | 'phone'>('email');
const [searchValue, setSearchValue] = useState('');
const [loading, setLoading] = useState(false);
const [clientData, setClientData] = useState<ClientData | null>(null);
const [validatingTicketId, setValidatingTicketId] = useState<string | null>(null);
const handleSearch = async () => {
if (!searchValue.trim()) {
toast.error('Veuillez entrer un email ou un téléphone');
return;
}
setLoading(true);
try {
const queryParam = searchType === 'email' ? `email=${encodeURIComponent(searchValue)}` : `phone=${encodeURIComponent(searchValue)}`;
const data = await api.get<{ data: ClientData }>(`/employee/client-prizes?${queryParam}`);
setClientData(data.data);
toast.success(`Client trouvé: ${data.data.client.firstName} ${data.data.client.lastName}`);
} catch (error: any) {
console.error('Error searching client:', error);
toast.error(error.message || 'Erreur lors de la recherche');
setClientData(null);
} finally {
setLoading(false);
}
};
const handleValidatePrize = async (ticketId: string) => {
if (!confirm('Confirmer la remise de ce lot au client?')) {
return;
}
setValidatingTicketId(ticketId);
try {
await api.post('/employee/validate-ticket', { ticketId, action: 'APPROVE' });
toast.success('Lot marqué comme remis!');
handleSearch();
} catch (error: any) {
console.error('Error validating prize:', error);
toast.error(error.message || 'Erreur lors de la validation');
} finally {
setValidatingTicketId(null);
}
};
return (
<div className="p-6">
{/* Header */}
<div className="mb-6">
<div className="flex items-center gap-3 mb-2">
<div className="w-12 h-12 bg-gradient-to-br from-purple-600 to-pink-600 rounded-xl flex items-center justify-center shadow-lg">
<Gift className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
Gains du Client
</h1>
<p className="text-gray-500">
Recherchez un client pour visualiser tous ses gains et les remettre
</p>
</div>
</div>
</div>
{/* Search Section */}
<div className="bg-[#faf8f5] rounded-2xl p-6 mb-6 border border-gray-200">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Search className="w-5 h-5 text-purple-600" />
</div>
<h2 className="text-lg font-bold text-gray-800">
Rechercher un client
</h2>
</div>
<div className="space-y-4">
{/* Search Type Selection */}
<div className="flex gap-4">
<label className={`flex items-center gap-3 cursor-pointer px-4 py-2 rounded-lg transition-all border ${searchType === 'email' ? 'bg-purple-100 border-purple-300' : 'bg-white border-gray-200 hover:bg-gray-50'}`}>
<input
type="radio"
name="searchType"
value="email"
checked={searchType === 'email'}
onChange={(e) => setSearchType(e.target.value as 'email')}
className="w-4 h-4 accent-purple-600"
/>
<Mail className="w-5 h-5 text-purple-600" />
<span className="text-sm font-medium text-gray-700">Email</span>
</label>
<label className={`flex items-center gap-3 cursor-pointer px-4 py-2 rounded-lg transition-all border ${searchType === 'phone' ? 'bg-purple-100 border-purple-300' : 'bg-white border-gray-200 hover:bg-gray-50'}`}>
<input
type="radio"
name="searchType"
value="phone"
checked={searchType === 'phone'}
onChange={(e) => setSearchType(e.target.value as 'phone')}
className="w-4 h-4 accent-purple-600"
/>
<Phone className="w-5 h-5 text-purple-600" />
<span className="text-sm font-medium text-gray-700">Téléphone</span>
</label>
</div>
{/* Search Input */}
<div className="flex gap-4">
<input
type={searchType === 'email' ? 'email' : 'tel'}
placeholder={searchType === 'email' ? 'exemple@email.com' : '06 12 34 56 78'}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
className="flex-1 px-4 py-3 border border-gray-200 bg-white rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent text-gray-800"
/>
<button
onClick={handleSearch}
disabled={loading}
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-xl hover:from-purple-700 hover:to-pink-700 transition-all font-semibold shadow-lg hover:shadow-xl disabled:opacity-50"
>
<Search className="w-5 h-5" />
{loading ? 'Recherche...' : 'Rechercher'}
</button>
</div>
</div>
</div>
{/* Client Info & Prizes */}
{clientData && (
<>
{/* Client Info Card */}
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 mb-6">
<div className="flex flex-col lg:flex-row items-start justify-between gap-6">
<div className="flex items-start gap-4">
<div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-500 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">
{clientData.client.firstName} {clientData.client.lastName}
</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 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 text-purple-500" />
{clientData.client.phone}
</p>
)}
</div>
</div>
</div>
<div className="flex gap-4">
<div className="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-4 text-center min-w-[100px] border border-gray-200">
<p className="text-xs font-medium text-gray-500 mb-1">Total</p>
<p className="text-3xl font-bold text-gray-700">{clientData.totalPrizes}</p>
</div>
<div className="bg-gradient-to-br from-amber-50 to-amber-100 rounded-xl p-4 text-center min-w-[100px] border border-amber-200">
<p className="text-xs font-medium text-amber-600 mb-1">À remettre</p>
<p className="text-3xl font-bold text-amber-700">{clientData.pendingPrizes}</p>
</div>
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-4 text-center min-w-[100px] border border-green-200">
<p className="text-xs font-medium text-green-600 mb-1">Remis</p>
<p className="text-3xl font-bold text-green-700">{clientData.claimedPrizes}</p>
</div>
</div>
</div>
</div>
{/* Prizes List */}
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div className="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Package className="w-5 h-5 text-purple-600" />
</div>
<div>
<h2 className="text-lg 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>
<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-amber-200 bg-amber-50/30'
: 'border-gray-100 bg-white'
}`}
>
<div className="flex flex-col lg:flex-row items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-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>
<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-4">
{prize.prize.description}
</p>
<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">
<Clock className="w-4 h-4 text-gray-400" />
<span className="text-gray-500">Gagné le:</span>
<span className="font-medium">
{new Date(prize.playedAt).toLocaleDateString('fr-FR')}
</span>
</div>
{prize.claimedAt && (
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4 text-green-500" />
<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="flex items-center gap-2">
<User className="w-4 h-4 text-gray-400" />
<span className="text-gray-500">Par:</span>
<span className="font-medium">{prize.validatedBy}</span>
</div>
)}
</div>
</div>
{prize.status === 'PENDING' && (
<button
onClick={() => handleValidatePrize(prize.ticketId)}
disabled={validatingTicketId === prize.ticketId}
className="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>
</div>
</>
)}
</div>
);
}