Vocabulary App - Copy this React, Tailwind Component to your project
Hãy sửa lại phần learning theo code của tôi : import React, { useState, useEffect } from 'react'; import { database } from '../firebase'; import { ref, get, update } from 'firebase/database'; import { useAuth } from '../context/AuthContext'; import Link from 'next/link'; export default function LearningMode() { const { user } = useAuth(); const [words, setWords] = useState([]); const [currentGroup, setCurrentGroup] = useState([]); const [currentWordIndex, setCurrentWordIndex] = useState(0); const [showFlashcard, setShowFlashcard] = useState(true); const [isFlipped, setIsFlipped] = useState(false); const [userInput, setUserInput] = useState(''); const [learningHistory, setLearningHistory] = useState({}); const [askForMeaning, setAskForMeaning] = useState(true); const [loading, setLoading] = useState(true); const [isStarted, setIsStarted] = useState(false); const [resultMessage, setResultMessage] = useState(''); const [isGroupCompleted, setIsGroupCompleted] = useState(false); useEffect(() => { if (user) { loadWords(); } }, [user]); useEffect(() => { const handleGlobalKeyPress = (e) => { if (e.key === 'Enter') { if (showFlashcard) { if (!isFlipped) { // Nếu chưa lật thì lật setIsFlipped(true); } else { // Nếu đã lật thì chuyển sang chế độ nhập setShowFlashcard(false); setIsFlipped(false); } } else { // Nếu đang ở chế độ nhập, kiểm tra câu trả lời if (userInput.trim() !== '') { // Kiểm tra nếu userInput không rỗng checkAnswer(); } } } }; // Thêm event listener window.addEventListener('keypress', handleGlobalKeyPress); // Cleanup return () => { window.removeEventListener('keypress', handleGlobalKeyPress); }; }, [showFlashcard, isFlipped, userInput]); // Dependencies const loadWords = async () => { try { setLoading(true); // Đường dẫn đến vocab của user const vocabRef = ref(database, `vocab/${user.uid}`); const snapshot = await get(vocabRef); if (snapshot.exists()) { const vocabSets = snapshot.val(); // Tập hợp tất cả các từ từ tất cả các vocab sets const allWords = Object.entries(vocabSets).reduce((acc, [setId, set]) => { // Kiểm tra xem set có terms không if (set.terms) { // Chuyển đổi terms từ object sang array và thêm setId const setWords = Object.entries(set.terms).map(([termId, term]) => ({ id: termId, setId, ...term, learningCount: 0, lastAsked: null })); return [...acc, ...setWords]; } return acc; }, []); // Lọc các từ có level 0, 1, 2 const eligibleWords = allWords.filter(word => word.level <= 2); if (eligibleWords.length > 0) { console.log("Loaded words:", eligibleWords); // Để debug setWords(eligibleWords); createNewGroup(eligibleWords); } else { console.log("Không có từ nào ở level 0, 1, 2"); } } else { console.log("Không có dữ liệu từ vựng"); } } catch (error) { console.error("Lỗi khi tải từ vựng:", error); } finally { setLoading(false); } }; const createNewGroup = (availableWords) => { try { // Tạo nhóm 6 từ ngẫu nhiên const shuffled = availableWords .filter(word => (learningHistory[word.id]?.count || 0) < 4) // Chỉ chọn các từ có số lần học < 4 .sort(() => 0.5 Math.random()); const newGroup = shuffled.slice(0, 6); console.log("New group created:", newGroup); // Debug log if (newGroup.length === 0) { console.error("No eligible words available"); return false; } setCurrentGroup(newGroup); setCurrentWordIndex(0); setShowFlashcard(true); setIsFlipped(false); setAskForMeaning(Math.random() > 0.5); setIsGroupCompleted(false); return true; } catch (error) { console.error("Error creating new group:", error); return false; } }; const handleKeyPress = (e) => { if (e.key === 'Enter') { if (showFlashcard) { if (!isFlipped) { // Nếu chưa lật thì lật setIsFlipped(true); } else { // Nếu đã lật thì chuyển sang chế độ nhập setShowFlashcard(false); setIsFlipped(false); } } else { // Nếu đang ở chế độ nhập, kiểm tra câu trả lời if (userInput.trim() !== '') { // Kiểm tra nếu userInput không rỗng checkAnswer(); } } } }; const checkAnswer = () => { // Kiểm tra xem có từ hiện tại không if (!currentGroup || currentGroup.length === 0 || currentWordIndex >= currentGroup.length) { console.error("Invalid current word state"); return; } const currentWord = currentGroup[currentWordIndex]; if (!currentWord) { console.error("Current word is undefined"); return; } // Lấy giá trị nghĩa hoặc từ vựng để so sánh const correctAnswer = askForMeaning ? currentWord.meaning : currentWord.vocab; // Hàm để chuẩn hóa chuỗi const normalizeString = (str) => { return str .normalize("NFD") // Normalize to decompose combined characters .replace(/[\u0300 \u036f]/g, "") // Remove accents .replace(/[\/#!$%\^&\*;:{}=\ _`~()]/g, "") // Remove punctuation except commas .trim() .toLowerCase(); }; // Chuẩn hóa các chuỗi const normalizedCorrectAnswer = normalizeString(correctAnswer); const normalizedUserInput = normalizeString(userInput); console.log("Checking answer:", { normalizedUserInput, normalizedCorrectAnswer, currentWordIndex, groupLength: currentGroup.length }); // Chia nhỏ các đáp án hợp lệ thành mảng const validAnswers = normalizedCorrectAnswer.split(',').map(answer => answer.trim()); const isCorrect = validAnswers.includes(normalizedUserInput); // Cập nhật lịch sử học const updatedHistory = { ...learningHistory, [currentWord.id]: { ...(learningHistory[currentWord.id] || {}), count: (learningHistory[currentWord.id]?.count || 0) + 1, lastAsked: Date.now() } }; setLearningHistory(updatedHistory); // Hiển thị thông báo đúng/sai setResultMessage(isCorrect ? 'Đúng rồi!' : 'Sai rồi, hãy thử lại!'); // Chỉ chuyển sang từ tiếp theo nếu trả lời đúng if (isCorrect) { setTimeout(() => { setUserInput(''); moveToNextWord(); setResultMessage(''); }, 1000); } else { // Nếu sai, chỉ xóa input và thông báo sau 1 giây setTimeout(() => { setUserInput(''); setResultMessage(''); }, 1000); } }; const moveToNextWord = () => { console.log("Moving to next word. Current index:", currentWordIndex, "Group length:", currentGroup.length); if (currentWordIndex >= currentGroup.length 1) { // Đã hoàn thành nhóm hiện tại setIsGroupCompleted(true); const success = createNewGroup(words); if (!success) { console.error("Failed to create new group"); return; } } else { // Chuyển sang từ tiếp theo trong nhóm hiện tại setCurrentWordIndex(currentWordIndex + 1); setShowFlashcard(true); setIsFlipped(false); setAskForMeaning(Math.random() > 0.5); } }; // Thêm hàm để bắt đầu học const startLearning = () => { setIsStarted(true); createNewGroup(words); }; // Thêm useEffect để theo dõi thay đổi của currentGroup useEffect(() => { console.log("Current group updated:", { groupLength: currentGroup?.length, currentIndex: currentWordIndex }); }, [currentGroup, currentWordIndex]); // Thêm component Instructions với hiệu ứng hover const Instructions = () => { return ( <div className="relative"> {/* Icon "i" */} <div className="w 8 h 8 rounded full bg blue 500 flex items center justify center cursor pointer group"> <svg xmlns="http://www.w3.org/2000/svg" className="h 5 w 5 text white" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h 1v 4h 1m1 4h.01M21 12a9 9 0 11 18 0 9 9 0 0118 0z" /> </svg> </div> {/* Tooltip content */} <div className="absolute bottom full mb 2 w 64 opacity 0 transition opacity duration 200 group hover:opacity 100"> <div className="bg gray 900/90 backdrop blur sm rounded lg p 4 shadow lg border border white/10"> <h3 className="text lg font bold text blue 400">Quy tắc học tập</h3> <ul className="text xs text gray 400 space y 1"> <li>• Mỗi nhóm học tập gồm 6 từ vựng</li> <li>• Mỗi từ sẽ được học 4 lần</li> <li>• Ít nhất 1 lần học từ Anh sang Việt và 1 lần từ Việt sang Anh</li> <li>• Chỉ áp dụng cho các từ ở level 0, 1, và 2</li> </ul> </div> {/* Mũi tên chỉ xuống */} <div className="w 3 h 3 bg gray 900/90 transform rotate 45 absolute left 1/2 translate x 1/2 bottom 1.5"></div> </div> </div> ); }; if (loading) { return ( <div className="flex flex col items center justify center h 64"> <div className="animate spin rounded full h 12 w 12 border t 2 border b 2 border blue 400 mb 4"></div> <p className="text gray 300">Đang tải dữ liệu...</p> </div> ); } if (!isStarted) { return ( <div className="text center p 8"> <div className="bg white/10 backdrop blur lg rounded 2xl p 8 border border white/20"> <div className="mb 8"> <h3 className="text 2xl font bold text gray 300 mb 2">Sẵn sàng học cha?</h3> <p className="text gray 400"> Có {words.filter(word => word.level <= 2).length} từ cần học </p> </div> <div className="space y 4 mb 8"> <div className="flex items center justify between p 4 bg white/5 rounded lg"> <span className="text gray 300">Level 0</span> <span className="text blue 400 font bold"> {words.filter(word => word.level === 0).length} từ </span> </div> <div className="flex items center justify between p 4 bg white/5 rounded lg"> <span className="text gray 300">Level 1</span> <span className="text purple 400 font bold"> {words.filter(word => word.level === 1).length} từ </span> </div> <div className="flex items center justify between p 4 bg white/5 rounded lg"> <span className="text gray 300">Level 2</span> <span className="text pink 400 font bold"> {words.filter(word => word.level === 2).length} từ </span> </div> </div> <button onClick={startLearning} className="px 8 py 4 bg gradient to r from blue 500 to purple 500 text white rounded lg font bold text lg hover:opacity 90 transition opacity" > Bắt đầu học </button> </div> </div> ); } if (currentGroup.length === 0) { return ( <div className="text center p 8"> <div className="bg white/10 backdrop blur lg rounded 2xl p 8 border border white/20"> <svg xmlns="http://www.w3.org/2000/svg" className="h 16 w 16 mx auto text gray 400 mb 4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0 6h6m 6 0H6" /> </svg> <h3 className="text xl font bold text gray 300 mb 4">Chưa có từ vựng để học</h3> <p className="text gray 400 mb 4">Bạn cần thêm từ vựng mới hoặc đợi các từ reset về level thấp hơn (0 2)</p> <Link href="/vocabulary" className="inline block px 6 py 3 bg gradient to r from blue 500 to purple 500 text white rounded lg font medium hover:opacity 90 transition opacity"> Thêm từ vựng mới </Link> </div> </div> ); } return ( <div className="flex flex col items center"> {/* Progress Bar */} <div className="w full mb 6"> <div className="flex justify between text sm text gray 400 mb 2"> <span>Từ {currentWordIndex + 1}/{currentGroup.length}</span> <span>Lần học: {(learningHistory[currentGroup[currentWordIndex]?.id]?.count || 0) + 1}/4</span> </div> <div className="w full bg gray 700 rounded full h 2"> <div className="bg gradient to r from blue 500 to purple 500 h 2 rounded full transition all duration 300" style={{ width: `${((currentWordIndex) / currentGroup.length) * 100}%` }} ></div> </div> </div> {/* Flashcard */} {showFlashcard ? ( <div className="w full max w lg aspect [3/2] relative"> <div className={`w full h full transition all duration 500 flashcard transition ${ isFlipped ? '[transform:rotateY(180deg)]' : '' }`} style={{ transformStyle: 'preserve 3d' }} onClick={() => setIsFlipped(!isFlipped)} > {/* Front */} <div className="absolute w full h full backface hidden cursor pointer" style={{ backfaceVisibility: 'hidden' }} > <div className="w full h full bg gradient to br from blue 600/20 to purple 600/20 backdrop blur lg rounded 2xl p 8 flex items center justify center border border white/10"> <h2 className="text 4xl font bold text white"> {currentGroup[currentWordIndex]?.vocab} </h2> </div> </div> {/* Back */} <div className="absolute w full h full backface hidden cursor pointer [transform:rotateY(180deg)]" style={{ backfaceVisibility: 'hidden' }} > <div className="w full h full bg gradient to br from purple 600/20 to pink 600/20 backdrop blur lg rounded 2xl p 8 flex items center justify center border border white/10"> <h2 className="text 4xl font bold text white"> {currentGroup[currentWordIndex]?.meaning} </h2> </div> </div> </div> </div> ) : ( <div className="w full max w lg"> <div className="bg white/10 backdrop blur lg rounded 2xl p 6 border border white/20"> <p className="text xl text gray 300 mb 4 text center"> {askForMeaning ? `Nhập nghĩa của từ: "${currentGroup[currentWordIndex]?.vocab}"` : `Nhập từ tiếng Anh cho nghĩa: "${currentGroup[currentWordIndex]?.meaning}"`} </p> <input type="text" value={userInput} onChange={(e) => setUserInput(e.target.value)} onKeyPress={handleKeyPress} className="w full bg white/5 border border white/10 rounded lg p 4 text white text center text xl focus:outline none focus:ring 2 focus:ring blue 500 focus:border transparent" placeholder="Nhập câu trả lời..." autoFocus /> <p className="text gray 400 text sm text center mt 4"> Nhấn Enter để kiểm tra </p> </div> </div> )} {/* Hiển thị thông báo kết quả */} {resultMessage && ( <div className="mt 4 text xl font bold text green 500"> {resultMessage} </div> )} {/* Instructions */} <div className="mt 8 flex justify center"> <Instructions /> </div> </div> ); }