import React, { useState, useRef, useEffect } from 'react'; import { Eye, Target, CheckCircle, XCircle, AlertTriangle, Lightbulb, Zap } from 'lucide-react'; import { cn } from '../../utils/cn'; interface InteractiveImageHotspotProps { imageSrc: string; title: string; description: string; hotspots: ImageHotspot[]; onComplete: (results: HotspotResult[]) => void; className?: string; showHints?: boolean; allowMultipleAttempts?: boolean; } interface ImageHotspot { id: string; x: number; // percentage (0-100) y: number; // percentage (0-100) width: number; // percentage height: number; // percentage type: 'correct' | 'incorrect' | 'neutral' | 'warning'; title: string; description: string; feedback: string; points: number; hint?: string; consequence?: string; isRequired?: boolean; } interface HotspotResult { hotspotId: string; clicked: boolean; timestamp: number; attempts: number; isCorrect: boolean; points: number; } const InteractiveImageHotspot: React.FC = ({ imageSrc, title, description, hotspots, onComplete, className, showHints = true, allowMultipleAttempts = true }) => { const imageRef = useRef(null); const [imageLoaded, setImageLoaded] = useState(false); const [clickedHotspots, setClickedHotspots] = useState>(new Set()); const [hotspotResults, setHotspotResults] = useState>(new Map()); const [showFeedback, setShowFeedback] = useState(null); const [currentFeedback, setCurrentFeedback] = useState(null); const [hintsVisible, setHintsVisible] = useState(false); const [attempts, setAttempts] = useState>(new Map()); useEffect(() => { // Check if all required hotspots are clicked const requiredHotspots = hotspots.filter(h => h.isRequired); const clickedRequired = requiredHotspots.filter(h => clickedHotspots.has(h.id)); if (requiredHotspots.length > 0 && clickedRequired.length === requiredHotspots.length) { const results = Array.from(hotspotResults.values()); onComplete(results); } }, [clickedHotspots, hotspots, hotspotResults, onComplete]); const handleHotspotClick = (hotspot: ImageHotspot, event: React.MouseEvent) => { event.preventDefault(); const currentAttempts = attempts.get(hotspot.id) || 0; const newAttempts = currentAttempts + 1; // Update attempts setAttempts(prev => new Map(prev.set(hotspot.id, newAttempts))); // Check if already clicked and multiple attempts not allowed if (clickedHotspots.has(hotspot.id) && !allowMultipleAttempts) { return; } // Add to clicked hotspots setClickedHotspots(prev => new Set([...Array.from(prev), hotspot.id])); // Calculate points (reduce points for multiple attempts) let points = hotspot.points; if (newAttempts > 1) { points = Math.max(0, hotspot.points - (newAttempts - 1) * 2); } // Create result const result: HotspotResult = { hotspotId: hotspot.id, clicked: true, timestamp: Date.now(), attempts: newAttempts, isCorrect: hotspot.type === 'correct', points: hotspot.type === 'correct' ? points : 0 }; // Update results setHotspotResults(prev => new Map(prev.set(hotspot.id, result))); // Show feedback setCurrentFeedback(hotspot); setShowFeedback(hotspot.id); // Auto-hide feedback after 3 seconds setTimeout(() => { setShowFeedback(null); setCurrentFeedback(null); }, 3000); }; const getHotspotIcon = (type: string) => { switch (type) { case 'correct': return ; case 'incorrect': return ; case 'warning': return ; default: return ; } }; const getHotspotColor = (hotspot: ImageHotspot) => { const isClicked = clickedHotspots.has(hotspot.id); if (isClicked) { switch (hotspot.type) { case 'correct': return 'border-green-500 bg-green-500/20'; case 'incorrect': return 'border-red-500 bg-red-500/20'; case 'warning': return 'border-yellow-500 bg-yellow-500/20'; default: return 'border-blue-500 bg-blue-500/20'; } } return showHints ? 'border-blue-400 bg-blue-400/10 animate-pulse' : 'border-transparent bg-transparent hover:border-blue-400 hover:bg-blue-400/10'; }; const totalPoints = Array.from(hotspotResults.values()).reduce((sum, result) => sum + result.points, 0); const maxPoints = hotspots.reduce((sum, hotspot) => sum + hotspot.points, 0); const requiredHotspots = hotspots.filter(h => h.isRequired); const clickedRequired = requiredHotspots.filter(h => clickedHotspots.has(h.id)); const progress = requiredHotspots.length > 0 ? (clickedRequired.length / requiredHotspots.length) * 100 : 0; return (
{/* Header */}

{title}

{description}

{/* Instructions */}

📍 Instruksi: Klik pada area-area bermasalah dalam gambar. Area yang wajib ditemukan: {requiredHotspots.length}

{clickedRequired.length}/{requiredHotspots.length} Wajib {clickedHotspots.size}/{hotspots.length} Total {totalPoints}/{maxPoints} Poin
{/* Progress Bar */}
Progress Area Wajib {Math.round(progress)}%
{progress === 100 && (

✅ Semua area wajib telah ditemukan!

)}
{/* Interactive Image */}
{title} setImageLoaded(true)} /> {/* Hotspots Overlay */} {imageLoaded && (
{hotspots.map((hotspot) => ( ))}
)} {/* Feedback Overlay */} {showFeedback && currentFeedback && (
{getHotspotIcon(currentFeedback.type)}

{currentFeedback.title}

{currentFeedback.feedback}

{currentFeedback.consequence && (

Konsekuensi: {currentFeedback.consequence}

)}
{currentFeedback.type === 'correct' ? '+' : ''}{currentFeedback.points} poin
)}
{/* Summary */}
Area Ditemukan: {clickedHotspots.size}/{hotspots.length}
Total Poin: {totalPoints}/{maxPoints}
); }; export default InteractiveImageHotspot;