'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 [activeTab, setActiveTab] = useState<'acak' | 'hadiah' | 'winner'>('acak'); const [shuffledDisplayOrder, setShuffledDisplayOrder] = useState([]); 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); }; // Generate random order ID const generateRandomOrderId = () => { return Math.floor(100000000 + Math.random() * 900000000).toString(); }; // Mock data useEffect(() => { const mockOrders: Order[] = Array.from({ length: 28 }, (_, i) => ({ orderId: generateRandomOrderId(), customerName: `Customer ${i + 1}`, email: `customer${i + 1}@email.com`, amount: 150000 + (i * 10000), status: 'completed' })); setOrders(mockOrders); setCurrentWheelItems(mockOrders); setShuffledDisplayOrder(shuffleArray(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); setShuffledDisplayOrder(shuffleArray(orders)); // Shuffle display order too 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; const GridTable = ({ data, columns = 4 }: { data: Order[], columns?: number }) => { const rows = Math.ceil(data.length / columns); return (
{Array.from({ length: columns }, (_, colIndex) => (
{Array.from({ length: rows }, (_, rowIndex) => { const itemIndex = rowIndex * columns + colIndex; const item = shuffledDisplayOrder[itemIndex]; if (!item) return
; const isWinner = winners.some(winner => winner.order.orderId === item.orderId); return (
{item.orderId} {isWinner && ( )}
); })}
))}
); }; const renderTabContent = () => { switch (activeTab) { case 'acak': return (
{/* 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} />
{/* Grid Table */}

Eligible Orders

); case 'hadiah': return (

Prize Management

Prize configuration will be available here.

); case 'winner': return (
{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()}
))}
) : (

No Winners Yet

Start spinning to see the winners here!

)}
); default: return null; } }; return (
{/* Header */}

Random Order Draw

Select random winners from order database

{/* Tabs */}
{[ { id: 'acak', label: 'acak' }, { id: 'hadiah', label: 'hadiah' }, { id: 'winner', label: 'winner' } ].map((tab) => ( ))}
{/* Main Layout */}
{/* Left Panel - Tab Content */}
{renderTabContent()}
{/* 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 */}
); }; export default RandomDrawApp;