'use client' import React, { useState, useEffect, useRef } from 'react' import { Users, RotateCw, Trophy, Trash2, RefreshCw, Database } from 'lucide-react' interface Order { orderId: string customerName: string email: string amount: number status: string } interface WinnerResult { order: Order timestamp: Date position: number } const RandomDrawApp: React.FC = () => { const [orders, setOrders] = useState([]) const [isSpinning, setIsSpinning] = useState(false) const [winners, setWinners] = useState([]) const [numberOfWinners, setNumberOfWinners] = useState(1) const [currentWheelItems, setCurrentWheelItems] = useState([]) const [selectedWinner, setSelectedWinner] = useState(null) const wheelRef = useRef(null) const audioContextRef = useRef(null) const tickIntervalRef = useRef(null) // Initialize Audio Context useEffect(() => { const initAudio = () => { if (!audioContextRef.current) { audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)() } } document.addEventListener('click', initAudio, { once: true }) return () => { document.removeEventListener('click', initAudio) if (tickIntervalRef.current) { clearTimeout(tickIntervalRef.current) } } }, []) // Create tick sound effect const playTickSound = () => { if (!audioContextRef.current) return const ctx = audioContextRef.current const oscillator = ctx.createOscillator() const gainNode = ctx.createGain() oscillator.connect(gainNode) gainNode.connect(ctx.destination) oscillator.frequency.setValueAtTime(800, ctx.currentTime) oscillator.frequency.exponentialRampToValueAtTime(200, ctx.currentTime + 0.05) gainNode.gain.setValueAtTime(0, ctx.currentTime) gainNode.gain.linearRampToValueAtTime(0.1, ctx.currentTime + 0.01) gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05) oscillator.start(ctx.currentTime) oscillator.stop(ctx.currentTime + 0.05) } // Start tick sound during spinning const startTickSound = (duration: number) => { if (tickIntervalRef.current) { clearTimeout(tickIntervalRef.current) } let tickInterval = 50 const maxInterval = 200 const intervalIncrement = (maxInterval - tickInterval) / (duration / 100) const tick = () => { playTickSound() tickInterval += intervalIncrement if (tickInterval < maxInterval) { tickIntervalRef.current = setTimeout(tick, Math.min(tickInterval, maxInterval)) } } tick() setTimeout(() => { if (tickIntervalRef.current) { clearTimeout(tickIntervalRef.current) tickIntervalRef.current = null } }, duration + 100) } // Mock data useEffect(() => { const mockOrders: Order[] = [ { orderId: 'ORD-001', customerName: 'John Doe', email: 'john@email.com', amount: 150000, status: 'completed' }, { orderId: 'ORD-002', customerName: 'Jane Smith', email: 'jane@email.com', amount: 250000, status: 'completed' }, { orderId: 'ORD-003', customerName: 'Bob Johnson', email: 'bob@email.com', amount: 180000, status: 'completed' }, { orderId: 'ORD-004', customerName: 'Alice Brown', email: 'alice@email.com', amount: 320000, status: 'completed' }, { orderId: 'ORD-005', customerName: 'Charlie Wilson', email: 'charlie@email.com', amount: 95000, status: 'completed' }, { orderId: 'ORD-006', customerName: 'Diana Davis', email: 'diana@email.com', amount: 420000, status: 'completed' }, { orderId: 'ORD-007', customerName: 'Edward Miller', email: 'edward@email.com', amount: 280000, status: 'completed' }, { orderId: 'ORD-008', customerName: 'Fiona Garcia', email: 'fiona@email.com', amount: 190000, status: 'completed' }, { orderId: 'ORD-009', customerName: 'George Martinez', email: 'george@email.com', amount: 350000, status: 'completed' }, { orderId: 'ORD-010', customerName: 'Helen Lopez', email: 'helen@email.com', amount: 210000, status: 'completed' } ] setOrders(mockOrders) setCurrentWheelItems(mockOrders) }, []) const shuffleArray = (array: T[]): T[] => { const shuffled = [...array] for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]] } return shuffled } const performSpin = (finalWinners: Order[], mainWinner: Order) => { const wheelElement = wheelRef.current if (!wheelElement) return const CARD_HEIGHT = 100 const WINNER_ZONE_CENTER = 250 // Find where the winner already exists in current wheel items (no manipulation) let winnerIndex = currentWheelItems.findIndex(item => item.orderId === mainWinner.orderId) // If winner not found in wheel, find a suitable position if (winnerIndex === -1) { winnerIndex = Math.floor(Math.random() * currentWheelItems.length) } // Reset wheel position wheelElement.style.transition = 'none' wheelElement.style.transform = 'translateY(0px)' wheelElement.offsetHeight setTimeout(() => { // Calculate spin parameters const minSpins = 15 const maxSpins = 25 const spins = minSpins + Math.random() * (maxSpins - minSpins) const totalItems = currentWheelItems.length // Calculate final position to land on winner const totalRotations = Math.floor(spins) const baseScrollDistance = totalRotations * totalItems * CARD_HEIGHT const winnerScrollPosition = winnerIndex * CARD_HEIGHT const finalPosition = -(baseScrollDistance + winnerScrollPosition) + WINNER_ZONE_CENTER - CARD_HEIGHT / 2 // Dynamic duration const distance = Math.abs(finalPosition) const baseDuration = 3000 const maxDuration = 5000 const duration = Math.min(baseDuration + (distance / 10000) * 1000, maxDuration) console.log('🎲 NATURAL SPIN:') console.log('Selected Winner:', mainWinner.orderId, '-', mainWinner.customerName) console.log('Found at Index:', winnerIndex) console.log('Will show in result:', mainWinner.orderId) // Animate wheel wheelElement.style.transform = `translateY(${finalPosition}px)` wheelElement.style.transition = `transform ${duration}ms cubic-bezier(0.17, 0.67, 0.12, 0.99)` // Start sound effect startTickSound(duration) // Complete spinning after animation setTimeout(() => { // Stop sound if (tickIntervalRef.current) { clearTimeout(tickIntervalRef.current) tickIntervalRef.current = null } // Set the ACTUAL pre-selected winner (not what's visually in wheel) setSelectedWinner(mainWinner) // Add PRE-SELECTED winners to results (guaranteed match) const newWinners = finalWinners.map((order, index) => ({ order, timestamp: new Date(), position: winners.length + index + 1 })) setWinners(prev => [...prev, ...newWinners]) setIsSpinning(false) console.log('🏆 RESULT WINNER:', newWinners[0].order.orderId) console.log('🎯 Visual winner may differ from result (result is guaranteed correct)') // Reset wheel for next spin setTimeout(() => { wheelElement.style.transition = 'none' wheelElement.style.transform = 'translateY(0px)' wheelElement.offsetHeight }, 1500) }, duration) }, 100) } const spinWheel = async () => { // Filter orders yang belum menang const availableOrders = orders.filter(order => !winners.some(winner => winner.order.orderId === order.orderId)) if (availableOrders.length === 0 || isSpinning) return setIsSpinning(true) setSelectedWinner(null) // Reset wheel position before every spin const wheelElement = wheelRef.current if (wheelElement) { wheelElement.style.transition = 'none' wheelElement.style.transform = 'translateY(0px)' wheelElement.offsetHeight // Force reflow } // Pilih winner yang akan ditampilkan di result const shuffledAvailable = shuffleArray(availableOrders) const finalWinners = shuffledAvailable.slice(0, Math.min(numberOfWinners, availableOrders.length)) const mainWinner = finalWinners[0] console.log('🎯 PRE-SELECTED WINNER:', mainWinner.orderId, '-', mainWinner.customerName) console.log('🎯 THIS WINNER WILL BE GUARANTEED IN RESULT') // Always call performSpin with the guaranteed winner if (wheelElement) { performSpin(finalWinners, mainWinner) } } const clearResults = () => { // Stop any ongoing sound effects if (tickIntervalRef.current) { clearTimeout(tickIntervalRef.current) tickIntervalRef.current = null } setWinners([]) setSelectedWinner(null) setIsSpinning(false) // Force reset spinning state // Reset wheel position when clearing const wheelElement = wheelRef.current if (wheelElement) { wheelElement.style.transition = 'none' wheelElement.style.transform = 'translateY(0px)' } } const refreshOrders = () => { // Stop any ongoing sound effects if (tickIntervalRef.current) { clearTimeout(tickIntervalRef.current) tickIntervalRef.current = null } const shuffled = shuffleArray(orders) setCurrentWheelItems(shuffled) setSelectedWinner(null) setIsSpinning(false) // Force reset spinning state // Reset wheel position const wheelElement = wheelRef.current if (wheelElement) { wheelElement.style.transition = 'none' wheelElement.style.transform = 'translateY(0px)' } } const formatCurrency = (amount: number) => { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(amount) } // Get available orders for spinning const availableOrdersCount = orders.filter( order => !winners.some(winner => winner.order.orderId === order.orderId) ).length return (
{/* Header */}

Random Order Draw

Select random winners from order database

{/* Main Layout */}
{/* Left Panel - Table & Controls */}
{/* Controls */}

Order Database ({orders.length} total, {availableOrdersCount} available)

setNumberOfWinners(Math.min(parseInt(e.target.value) || 1, availableOrdersCount))} className='w-32 bg-white border border-gray-300 rounded-lg p-2 text-gray-900 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20' disabled={isSpinning} />
{/* Orders Table */}

Eligible Orders

{orders.map(order => { const isWinner = winners.some(winner => winner.order.orderId === order.orderId) return ( ) })}
Order ID Customer Amount Status
{order.orderId}
{order.customerName}
{order.email}
{formatCurrency(order.amount)} {order.status} {isWinner && }
{/* Right Panel - Spin Wheel */}

Spin Wheel

{/* Wheel Container */}
{/* Winner Selection Zone */}
{/* Left Arrow */}
{/* Winner Badge */}
🎯 WINNER 🎯
{/* Right Arrow */}
{/* Scrolling Cards */}
{/* Generate enough cards for smooth scrolling */} {Array.from({ length: 40 }, (_, repeatIndex) => currentWheelItems.map((order, orderIndex) => { const isCurrentWinner = winners.some(winner => winner.order.orderId === order.orderId) const isSelectedWinner = selectedWinner?.orderId === order.orderId return (
{/* Order Info */}
{order.orderId}
{order.customerName}
{/* Amount */}
{formatCurrency(order.amount)}
{(isCurrentWinner || isSelectedWinner) && ( )}
) }) )}
{/* Gradient Overlays */}
{/* Results Section */} {winners.length > 0 && (

Draw Results ({winners.length} Winners)

{/* Simple Winner List */}
{winners.map(winner => (
#{winner.position}
{winner.order.customerName}
{winner.order.orderId} • {winner.order.email}
{formatCurrency(winner.order.amount)}
Won at: {winner.timestamp.toLocaleTimeString()}
))}
)}
) } export default RandomDrawApp