import React, { useState, useEffect, useRef } from 'react';
import {
ArrowRight, ArrowLeft, Thermometer, PieChart, Divide, Box, Triangle, BarChart2,
CheckCircle, XCircle, Calculator, Brain, Lock, Unlock, Key, HelpCircle,
List, Edit3, Link, CheckSquare, FileText, ChevronRight, Home, User,
Award, Star, TrendingUp, Filter, RefreshCw
} from 'lucide-react';
// --- KONFIGURASI ---
const TEACHER_PIN = "1234";
const TOTAL_QUESTIONS_PER_CHAPTER = 100;
// --- UTILS ---
const playSound = (type) => {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain); gain.connect(ctx.destination);
const now = ctx.currentTime;
if (type==='correct' || type==='step_success') {
osc.type='sine'; osc.frequency.setValueAtTime(600, now); osc.frequency.exponentialRampToValueAtTime(1000, now+0.1);
gain.gain.setValueAtTime(0.3, now); gain.gain.linearRampToValueAtTime(0, now+0.5);
} else if (type==='wrong') {
osc.type='sawtooth'; osc.frequency.setValueAtTime(300, now); gain.gain.setValueAtTime(0.3, now); gain.gain.linearRampToValueAtTime(0, now+0.5);
} else {
osc.type='triangle'; osc.frequency.setValueAtTime(800, now); gain.gain.setValueAtTime(0.05, now); gain.gain.exponentialRampToValueAtTime(0.01, now+0.1);
}
osc.start(now); osc.stop(now+0.5);
} catch(e){}
};
// --- DATA BAB ---
const CHAPTERS = [
{ id: 'bab1', title: 'Bilangan Bulat', icon: Thermometer, color: 'bg-blue-600' },
{ id: 'bab2', title: 'Bilangan Rasional', icon: PieChart, color: 'bg-pink-600' },
{ id: 'bab3', title: 'Rasio & Proporsi', icon: Divide, color: 'bg-purple-600' },
{ id: 'bab4', title: 'Bentuk Aljabar', icon: Box, color: 'bg-orange-600' },
{ id: 'bab5', title: 'Kesebangunan', icon: Triangle, color: 'bg-emerald-600' },
{ id: 'bab6', title: 'Data & Diagram', icon: BarChart2, color: 'bg-cyan-600' },
];
// --- GENERATOR SOAL CERDAS (600 VARIASI) ---
const generateQuestion = (chapterId, questionNumber) => {
const r = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const names = ["Budi", "Siti", "Eko", "Ayu", "Dian"];
// Tentukan Tipe
let type = 'pg';
if (questionNumber > 30) type = 'complex';
if (questionNumber > 50) type = 'input'; // Simplifikasi tipe untuk demo ini
if (questionNumber > 80) type = 'pg';
let q = { id: questionNumber, type, text: "", ans: "", options: [], steps: [] };
// --- LOGIKA SOAL PER BAB ---
if (chapterId === 'bab1') { // BILANGAN BULAT
if (type === 'pg') {
const a = r(-20,20), b = r(-20,20), c = r(-10,10);
q.text = `Hasil dari ${a} - (${b}) + ${c} adalah...`;
q.ans = a - b + c;
q.options = [q.ans, q.ans+2, q.ans-2, -q.ans].sort(()=>Math.random()-.5);
q.steps = [{instruction:`Langkah 1: Hitung ${a} - (${b}) = ...`, answer: a-b, type:'input'}, {instruction:`Langkah 2: Hasil tadi ditambah ${c} = ...`, answer:q.ans, type:'input'}];
} else if (type === 'input') {
const t1 = r(20,35), t2 = r(5,15);
q.text = `Suhu kamar ${t1}°C, suhu kulkas -${t2}°C. Selisihnya?`;
q.ans = t1 - (-t2);
q.steps = [{instruction:`Selisih = Besar - Kecil. ${t1} - (-${t2}) = ...`, answer: q.ans, type:'input'}];
} else {
q.text = "Pernyataan yang BENAR tentang bilangan bulat? (Bisa >1)";
q.ans = ["-5 < 2"]; q.options = ["-5 < 2", "-10 > -2", "0 adalah bilangan positif", "-1 > 0"];
q.steps = ["Cek garis bilangan. Positif selalu lebih besar dari negatif."]; q.type = 'complex';
}
}
else if (chapterId === 'bab2') { // RASIONAL
if (type === 'pg') {
const n=1, d=r(2,5);
q.text = `Bentuk persen dari ${n}/${d} adalah...`;
q.ans = (n/d)*100 + "%";
q.options = [q.ans, ((n/d)*100+10)+"%", "50%", "25%"].sort(()=>Math.random()-.5);
q.steps = [{instruction:`${n}/${d} x 100 = ...`, answer: (n/d)*100, type:'input'}];
} else {
const a=0.5, b=0.25;
q.text = `Hitung ${a} + ${b}`; q.ans = 0.75; q.type='input';
q.steps = [{instruction:`0.5 + 0.25 =`, answer: 0.75, type:'input'}];
}
}
else if (chapterId === 'bab3') { // RASIO
if (type === 'pg') {
const p = 5000, n = r(2,5);
q.text = `Harga 1 buku Rp ${p}. Harga ${n} buku?`;
q.ans = p*n;
q.options = [p*n, p*n+2000, p*n-1000, 10000].sort(()=>Math.random()-.5);
q.steps = [{instruction:`${p} x ${n} =`, answer:p*n, type:'input'}];
} else {
q.text = "Sederhanakan 10:15 (Tulis a:b)"; q.ans = "2:3"; q.type='input';
q.steps = [{instruction:"Bagi kedua angka dengan 5.", answer:"2:3", type:'input'}];
}
}
else if (chapterId === 'bab4') { // ALJABAR
const k = r(2,5);
if (type === 'pg') {
q.text = `Sederhanakan: ${k}x + 2x`;
q.ans = `${k+2}x`; q.options = [`${k+2}x`, `${k}x`, `2x`, `${k*2}x`];
q.steps = [{instruction:`${k} + 2 = ...`, answer: k+2, type:'input'}];
} else {
const val = r(2,5);
q.text = `Jika x=${val}, nilai 2x+1 adalah...`; q.ans = 2*val+1; q.type='input';
q.steps = [{instruction:`2(${val}) + 1 =`, answer: 2*val+1, type:'input'}];
}
}
else if (chapterId === 'bab5') { // KESEBANGUNAN
if (type === 'pg') {
const s = r(2,4);
q.text = `Segitiga A alas 5, Segitiga B alas ${5*s}. Jika tinggi A 4, tinggi B?`;
q.ans = 4*s; q.options = [4*s, 4, 5*s, 10].sort(()=>Math.random()-.5);
q.steps = [{instruction:`Faktor skala: ${5*s}/5 = ${s}. Tinggi = 4 x ${s}`, answer: 4*s, type:'input'}];
} else {
q.text = "Syarat segitiga sebangun? (Pilih 2)"; q.type='complex';
q.ans = ["Sudut sama", "Sisi sebanding"]; q.options = ["Sudut sama", "Sisi sebanding", "Luas sama", "Keliling sama"];
q.steps = ["Ingat konsep dasar sebangun."];
}
}
else { // BAB 6 DATA
const d = [r(2,9), r(2,9), r(2,9)];
const sum = d.reduce((a,b)=>a+b,0);
q.text = `Rata-rata dari data: ${d.join(', ')}?`;
q.ans = (sum/3).toFixed(1); q.type='input';
q.steps = [{instruction:`Jumlah: ${sum}. Bagi 3.`, answer: q.ans, type:'input'}];
}
return q;
};
// --- COMPONENTS ---
const GuidedSolver = ({ question, steps, onComplete }) => {
const [currentStep, setCurrentStep] = useState(0);
const [inputVal, setInputVal] = useState("");
const [status, setStatus] = useState("idle");
const [history, setHistory] = useState([]);
const scrollRef = useRef(null);
const rawStep = steps[currentStep];
const step = typeof rawStep === 'string' ? { instruction: rawStep, type: 'info', answer: 'ok' } : rawStep;
useEffect(() => { if(scrollRef.current) scrollRef.current.scrollTo({top: scrollRef.current.scrollHeight, behavior:'smooth'}); }, [currentStep, history]);
const checkAnswer = (val) => {
if (step.type === 'info') { playSound('click'); handleNext(step.instruction, 'Lanjut'); return; }
// Improved Validation Logic
const cleanVal = String(val).trim().toLowerCase().replace(/\s/g,'').replace(',', '.');
const correctVal = String(step.answer).trim().toLowerCase().replace(/\s/g,'').replace(',', '.');
if (cleanVal == correctVal) { playSound('step_success'); setStatus("correct"); handleNext(step.instruction, val); }
else { playSound('wrong'); setStatus("wrong"); }
};
const handleNext = (instruction, userAns) => {
setHistory([...history, { instruction, userAnswer: userAns }]);
setTimeout(() => {
if (currentStep < steps.length - 1) { setCurrentStep(currentStep + 1); setInputVal(""); setStatus("idle"); }
else { onComplete(); }
}, step.type==='info'?300:1000);
};
if (currentStep >= steps.length) return
Pembahasan Selesai!
Kamu telah menyelesaikan semua langkah.
;
return (
{/* HISTORY ITEMS (NOW WITH USER ANSWER VISIBLE) */}
{history.map((h, i) => (
✓
{h.instruction}
Jawab: {h.userAnswer}
))}
{/* CURRENT STEP CARD */}
{currentStep + 1}
Langkah {currentStep + 1}
{step.instruction}
{status==='wrong' &&
Coba lagi
}
{status==='correct' &&
Benar!
}
{/* STICKY INPUT FOOTER */}
{step.type === 'info' ? (
) : step.type === 'select' ? (
{step.options.map((opt, idx) => ())}
) : (
setInputVal(e.target.value)} placeholder="Ketik jawaban..." className="w-full p-3.5 bg-slate-50 border-2 border-slate-200 rounded-xl focus:border-indigo-500 focus:bg-white outline-none font-bold text-center text-lg text-slate-800 transition-all" onKeyDown={(e) => e.key==='Enter' && checkAnswer(inputVal)} autoFocus/>
)}
);
};
const TeacherModal = ({ isOpen, onClose, question, steps }) => {
const [pin, setPin] = useState("");
const [isUnlocked, setIsUnlocked] = useState(false);
useEffect(() => { if(!isOpen) { setPin(""); setIsUnlocked(false); } }, [isOpen]);
const handleUnlock = () => { if (pin === TEACHER_PIN) { setIsUnlocked(true); playSound('correct'); } else { playSound('wrong'); setPin(""); } };
if (!isOpen) return null;
return (
{isUnlocked ? : } Pembahasan Guru
{!isUnlocked ? (
Masukkan PIN
Akses khusus guru/pembimbing
setPin(e.target.value)} maxLength={4} className="text-center text-4xl font-bold border-b-4 border-slate-200 w-32 focus:border-indigo-500 outline-none bg-transparent pb-2 tracking-widest" placeholder="••••"/>
) : (
{}} /> )}
);
};
// --- QUIZ INTERFACE ---
const BankSoalInterface = ({ chapter, goBack }) => {
const [currentQNum, setCurrentQNum] = useState(1);
const [qData, setQData] = useState(null);
const [feedback, setFeedback] = useState(null);
const [showSolution, setShowSolution] = useState(false);
const [score, setScore] = useState(0);
// Load question on mount or change
useEffect(() => {
const q = generateQuestion(chapter.id, currentQNum);
setQData(q);
setFeedback(null);
setShowSolution(false);
}, [chapter.id, currentQNum]);
const handleAnswer = (val) => {
let correct = false;
if (qData.type === 'pg') correct = String(val) === String(qData.ans);
else if (qData.type === 'complex') {
const userSet = new Set(val); const ansSet = new Set(qData.ans);
correct = [...ansSet].every(x => userSet.has(x));
}
else {
// Loose equality for input
correct = String(val).trim().replace(/\s/g,'').toLowerCase() === String(qData.ans).trim().replace(/\s/g,'').toLowerCase();
}
if (correct) {
setFeedback('correct'); playSound('correct'); setScore(score + 10);
} else {
setFeedback('wrong'); playSound('wrong');
}
};
// Complex Answer State Management
const [complexSelected, setComplexSelected] = useState([]);
const toggleComplex = (opt) => {
if (complexSelected.includes(opt)) setComplexSelected(complexSelected.filter(x=>x!==opt));
else setComplexSelected([...complexSelected, opt]);
};
if (!qData) return
Loading...
;
return (
{/* HEADER */}
Bank Soal
{chapter.title}
Skor: {score}
{currentQNum}/{TOTAL_QUESTIONS_PER_CHAPTER}
{/* BODY */}
{/* Progress Bar */}
{/* Question Card */}
Tipe: {qData.type === 'pg' ? 'Pilihan Ganda' : qData.type === 'complex' ? 'PG Kompleks' : qData.type === 'input' ? 'Isian Singkat' : 'Uraian'}
{feedback && (feedback==='correct' ? : )}
{qData.text}
{/* RENDER OPTIONS BASED ON TYPE */}
{qData.type === 'pg' && (
{qData.options.map((opt, i) => (
))}
)}
{qData.type === 'complex' && (
{qData.options.map((opt, i) => (
))}
)}
{qData.type === 'input' && (
handleAnswer(e.target.value)}/>
)}
{/* FEEDBACK PANEL */}
{feedback && (
{feedback==='correct'?'Hebat! Benar 🎉':'Kurang Tepat 🤔'}
Lanjutkan semangatmu!
)}
setShowSolution(false)} question={qData.text} steps={qData.steps}/>
);
};
// --- MAIN APP ---
const App = () => {
const [activeChapter, setActiveChapter] = useState(null);
return (
{!activeChapter ? (
<>
{CHAPTERS.map(chap => (
))}
>
) : (
setActiveChapter(null)} />
)}
);
};
export default App;
Tidak ada komentar:
Posting Komentar