+ {targetAreas.map((targetArea, index) => {
+ console.log('๐ฏ RENDERING TARGET AREA:', {
+ targetArea: targetArea.id,
+ index,
+ label: targetArea.label,
+ hoveredDropZone,
+ isHovered: hoveredDropZone === targetArea.id
+ });
+ const targetAreaId = targetArea.id;
+ const matchedPiece = items.find(p => p.currentPosition === targetAreaId);
+ const isCorrect = matchedPiece && matchedPiece.correctPosition === targetAreaId;
+ const isHovered = hoveredDropZone === targetAreaId;
return (
handleDropInMatching(e, index)}
+ key={targetAreaId}
+ onDragOver={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('๐ DRAG OVER:', { targetAreaId, draggedPiece, hoveredDropZone });
+ }}
+ onDragEnter={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('๐ฅ DRAG ENTER:', { targetAreaId, draggedPiece });
+ if (draggedPiece) {
+ setHoveredDropZone(targetAreaId);
+ console.log('โ
SET HOVERED ZONE:', targetAreaId);
+ }
+ }}
+ onDragLeave={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('๐ค DRAG LEAVE:', { targetAreaId, hoveredDropZone });
+ // Check if we're really leaving the drop zone
+ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
+ const x = e.clientX;
+ const y = e.clientY;
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
+ setHoveredDropZone(null);
+ console.log('โ CLEAR HOVERED ZONE');
+ }
+ }}
+ onDrop={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('๐ฏ DROP EVENT:', {
+ targetAreaId,
+ index,
+ draggedPiece,
+ dataTransfer: e.dataTransfer.getData('text/plain')
+ });
+ handleDropInMatching(e, index);
+ setHoveredDropZone(null);
+ }}
+ onClick={() => {
+ // Handle click-to-place for mobile devices
+ if (selectedPieces.length === 1) {
+ const selectedPieceId = selectedPieces[0];
+ const piece = items.find(p => p.id === selectedPieceId);
+ if (piece && !piece.isLocked) {
+ // Simulate drop event
+ const fakeEvent = {
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ dataTransfer: {
+ getData: () => selectedPieceId
+ }
+ } as unknown as React.DragEvent;
+ console.log('Click-to-place triggered for:', selectedPieceId, 'to target:', targetAreaId);
+ handleDropInMatching(fakeEvent, index);
+ setSelectedPieces([]); // Clear selection
+ }
+ }
+ }}
className={cn(
- 'min-h-16 p-3 border-2 border-dashed rounded-lg transition-all duration-200',
- 'flex items-center justify-between relative',
- draggedPiece && 'border-green-400 bg-green-100 scale-102',
- isCorrect && 'bg-green-100 border-green-400 border-solid',
- !isCorrect && !draggedPiece && 'border-gray-300 bg-gray-50',
- !isCorrect && draggedPiece && 'hover:border-green-500 hover:bg-green-100'
+ 'min-h-20 p-4 border-3 border-dashed rounded-xl transition-all duration-300',
+ 'flex items-center justify-between relative cursor-pointer',
+ 'bg-white shadow-lg hover:shadow-xl',
+ // Hover state when dragging
+ isHovered && draggedPiece && 'border-green-500 bg-green-100 scale-105 shadow-2xl ring-2 ring-green-300',
+ // Correct match state
+ isCorrect && 'bg-green-100 border-green-500 border-solid shadow-xl ring-2 ring-green-200',
+ // Default state
+ !isCorrect && !draggedPiece && selectedPieces.length === 0 && 'border-gray-500 bg-gray-50 hover:border-blue-400 hover:bg-blue-50',
+ // Dragging but not hovering this zone
+ !isCorrect && draggedPiece && !isHovered && 'border-gray-400 bg-gray-100 opacity-75',
+ // Selected piece ready to place
+ !isCorrect && selectedPieces.length === 1 && 'border-blue-500 bg-blue-100 hover:border-blue-600 hover:bg-blue-200 shadow-xl ring-2 ring-blue-300 animate-pulse'
)}
>
{/* Definition label */}
-
Definisi {index + 1}:
+
Target {index + 1}:
- {/* You can add actual definitions here based on your data structure */}
- Definisi untuk item {index + 1}
+ {targetArea.content || targetArea.label || `Target area ${index + 1}`}
@@ -644,10 +841,28 @@ const PuzzleQuestion: React.FC
= ({
-
- {draggedPiece ? 'Lepas di sini' : 'Kosong'}
+
+
+ {isHovered && draggedPiece ? '๐ฏ LEPAS DI SINI!' :
+ selectedPieces.length === 1 ? '๐ KLIK UNTUK MENEMPATKAN' :
+ '๐ AREA DROP'}
+
+ {isHovered && draggedPiece ? 'Lepaskan item yang sedang diseret' :
+ selectedPieces.length === 1 ? 'Klik untuk menempatkan item terpilih' :
+ 'Seret item ke sini atau klik setelah memilih'}
+
+ {matchedPiece && !isCorrect && (
+
+ โ {matchedPiece.content}
+
+ )}
)}
@@ -660,9 +875,125 @@ const PuzzleQuestion: React.FC
= ({
);
};
+ // Render slider puzzle
+ const renderSliderPuzzle = () => {
+ if (!localPuzzleState) return null;
+
+ const sliders = question.puzzleData?.sliders || [];
+
+ const handleSliderChange = (sliderId: string, value: number) => {
+ const newPieces = [...localPuzzleState.pieces];
+ const pieceIndex = newPieces.findIndex(p => p.id === sliderId);
+
+ if (pieceIndex !== -1) {
+ newPieces[pieceIndex].currentPosition = value;
+
+ const isComplete = checkSliderCompletion(newPieces);
+
+ const newState: PuzzleState = {
+ ...localPuzzleState,
+ pieces: newPieces,
+ isComplete,
+ moves: localPuzzleState.moves + 1
+ };
+
+ setLocalPuzzleState(newState);
+ setPuzzleState(questionIndex.toString(), newState);
+ onAnswerChange(newState);
+ }
+ };
+
+ const checkSliderCompletion = (pieces: PuzzlePiece[]) => {
+ return pieces.every(piece => {
+ const slider = sliders.find(s => s.id === piece.id);
+ if (!slider) return false;
+
+ // Ensure both positions are numbers for arithmetic operations
+ const currentPos = typeof piece.currentPosition === 'number' ? piece.currentPosition : 0;
+ const correctPos = typeof piece.correctPosition === 'number' ? piece.correctPosition : 0;
+
+ // Allow tolerance of ยฑ1 for correct value
+ const tolerance = 1;
+ return Math.abs(currentPos - correctPos) <= tolerance;
+ });
+ };
+
+ return (
+
+
+
+
+ Atur nilai yang tepat untuk setiap parameter:
+
+
+
+ {sliders.map((slider, index) => {
+ const piece = localPuzzleState.pieces.find(p => p.id === slider.id);
+ const currentValue = typeof piece?.currentPosition === 'number' ? piece.currentPosition : slider.minValue;
+ const isCorrect = piece && Math.abs(currentValue - slider.correctValue) <= 1;
+
+ return (
+
+
+
+
+ {currentValue}{slider.unit}
+
+
+
+
+
handleSliderChange(slider.id, parseInt(e.target.value))}
+ className={cn(
+ "w-full h-2 rounded-lg appearance-none cursor-pointer",
+ "bg-gray-200 slider-thumb",
+ isCorrect ? "accent-green-500" : "accent-blue-500"
+ )}
+ style={{
+ background: `linear-gradient(to right, ${isCorrect ? '#10b981' : '#3b82f6'} 0%, ${isCorrect ? '#10b981' : '#3b82f6'} ${((currentValue - slider.minValue) / (slider.maxValue - slider.minValue)) * 100}%, #e5e7eb ${((currentValue - slider.minValue) / (slider.maxValue - slider.minValue)) * 100}%, #e5e7eb 100%)`
+ }}
+ />
+
+ {/* Range labels */}
+
+ {slider.minValue}{slider.unit}
+
+ Target: {slider.correctValue}{slider.unit}
+
+ {slider.maxValue}{slider.unit}
+
+
+
+ {/* Correctness indicator */}
+ {isCorrect && (
+
+
+ Nilai sudah tepat!
+
+ )}
+
+ );
+ })}
+
+
+
+ );
+ };
+
// Render puzzle stats
const renderPuzzleStats = () => {
- const timeElapsed = Math.floor((Date.now() - puzzleState.startTime) / 1000);
+ if (!localPuzzleState) return null;
+
+ const timeElapsed = Math.floor((Date.now() - localPuzzleState.startTime) / 1000);
const minutes = Math.floor(timeElapsed / 60);
const seconds = timeElapsed % 60;
@@ -676,19 +1007,19 @@ const PuzzleQuestion: React.FC = ({
- {puzzleState.moves} {adminLanguage === 'id' ? 'gerakan' : 'moves'}
+ {localPuzzleState.moves} {adminLanguage === 'id' ? 'gerakan' : 'moves'}
- {puzzleState.hints}/3 {adminLanguage === 'id' ? 'petunjuk' : 'hints'}
+ {localPuzzleState.hints}/3 {adminLanguage === 'id' ? 'petunjuk' : 'hints'}