feat: quiz tebak huruf

This commit is contained in:
mphstar 2025-02-25 20:26:23 +07:00
parent 3cde38dd5e
commit 7c37026804
6 changed files with 264 additions and 61 deletions

View File

@ -10,7 +10,7 @@ const NavLink = ({ href, name, isActive }: NavLinkProps) => {
<a href={href}>
<li
className={cn(
"btn bg-transparent border-none",
"btn bg-transparent shadow-none border-none",
isActive ? "text-primary" : ""
)}
>

View File

@ -1,11 +1,49 @@
import LayoutPage from "@/components/templates/LayoutPage";
import useMenyusunHurufStore from "@/stores/MenyusunHurufStore";
import useNavbarStore from "@/stores/NavbarStore";
import { useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import Swal from "sweetalert2";
const MenyusunHuruf = () => {
const quizStore = useMenyusunHurufStore();
const store = useNavbarStore();
useEffect(() => {
store.setNavSelected("kuis");
}, []);
const saveData = async () => {
try {
await fetch("https://ksuli-api.deno.dev/proses-kuis", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer KSULI_TOKEN_321`,
},
body: JSON.stringify({
kategori_id: "rec_cuum7c5qrj60bgubcjog",
person_name: quizStore.name,
score: parseInt(quizStore.time.toString()),
}),
});
Swal.close();
} catch (error) {
console.error("Error saving data:", error);
Swal.fire({
icon: "error",
title: "Oops...",
text: "Something went wrong while saving your data!",
});
}
Swal.fire({
icon: "success",
title: "Kuis telah selesai",
text: `Anda menyelesaikan kuis dalam waktu ${quizStore.time} detik`,
});
};
const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i > 0; i--) {
@ -19,11 +57,7 @@ const MenyusunHuruf = () => {
useEffect(() => {
if (quizStore.isFinish) {
Swal.fire({
icon: "success",
title: "Kuis telah selesai",
text: `Anda menyelesaikan kuis dalam waktu ${quizStore.time} detik`,
});
saveData();
}
}, [quizStore.isFinish]);

View File

@ -6,7 +6,6 @@ import calcLandmarkList from "@/utils/CalculateLandmark";
import preProcessLandmark from "@/utils/PreProcessLandmark";
import { abjads } from "@/utils/ConvertResult";
import useNavbarStore from "@/stores/NavbarStore";
import ProgressBar from "@/components/molecules/ProgressBar";
import { MdOutlineQuiz } from "react-icons/md";
import useMenyusunHurufStore from "@/stores/MenyusunHurufStore";
import { useNavigate } from "react-router-dom";
@ -94,7 +93,6 @@ const Quiz = () => {
}
};
const [progress, setProgress] = useState(0);
const [answer, setAnswer] = useState("");
let tempAnswer = "";
@ -196,7 +194,6 @@ const Quiz = () => {
Swal.showLoading();
},
});
await saveData(parseInt(answerTime.toString()));
navigate("/kuis/menyusun-huruf/");
}
@ -204,32 +201,6 @@ const Quiz = () => {
}
};
const saveData = async (time: number) => {
try {
await fetch("https://ksuli-api.deno.dev/proses-kuis", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer KSULI_TOKEN_321`,
},
body: JSON.stringify({
kategori_id: "rec_cuum7c5qrj60bgubcjog",
person_name: quizStore.name,
score: time,
}),
});
Swal.close();
} catch (error) {
console.error("Error saving data:", error);
Swal.fire({
icon: "error",
title: "Oops...",
text: "Something went wrong while saving your data!",
});
}
};
const detectHands = async () => {
if (showAnswer) {
return;
@ -256,10 +227,8 @@ const Quiz = () => {
makePrediction(finalResult);
} else {
setHandPresence(false);
setProgress(0);
}
} else {
setProgress(0);
}
}
}
@ -316,13 +285,13 @@ const Quiz = () => {
{!showAnswer && (
<div className="top-6 left-6 absolute flex flex-col gap-2">
<div className="flex gap-2 items-center bg-white text-black rounded-md drop-shadow px-3 py-2">
<h1 className="text-2xl font-semibold text-center">
<h1 className="text-xs md:text-2xl font-semibold text-center">
Susun huruf "{quizStore.listSoal[quizStore.soalIndex]}"
</h1>
</div>
{answer.length > 0 && (
<div className="flex gap-2 items-center bg-white text-black w-fit rounded-md drop-shadow px-3 py-2">
<h1 className="text-2xl font-semibold text-center">
<h1 className="text-xs md:text-2xl font-semibold text-center">
{answer}
</h1>
</div>
@ -334,9 +303,7 @@ const Quiz = () => {
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<span className="loader"></span>
<h1>Tahan Tangan..</h1>
</div>
<ProgressBar progress={progress} />
</div>
</div>
)}

View File

@ -4,10 +4,13 @@ import * as tf from "@tensorflow/tfjs";
import { FilesetResolver, HandLandmarker } from "@mediapipe/tasks-vision";
import calcLandmarkList from "@/utils/CalculateLandmark";
import preProcessLandmark from "@/utils/PreProcessLandmark";
import ConvertResult from "@/utils/ConvertResult";
import ConvertResult, { abjads } from "@/utils/ConvertResult";
import useNavbarStore from "@/stores/NavbarStore";
import ProgressBar from "@/components/molecules/ProgressBar";
import { MdOutlineQuiz } from "react-icons/md";
import useTebakHurufStore from "@/stores/TebakHurufStore";
import { useNavigate } from "react-router-dom";
import Swal from "sweetalert2";
// type PredictResult = {
// abjad: String;
@ -85,6 +88,9 @@ const Quiz = () => {
let previousResult: string[] = [];
const [progress, setProgress] = useState(0);
let noSoal = 0;
let isLoading = false;
const navigate = useNavigate();
const makePrediction = async (finalResult: any) => {
const input = tf.tensor2d([finalResult]);
@ -127,13 +133,45 @@ const Quiz = () => {
}
if (previousResult.length == 11) {
isLoading = true;
if (abjads[parseInt(maxKey)] === quizStore.listSoal[noSoal]) {
quizStore.addJawaban({
jawaban: abjads[parseInt(maxKey)],
isCorrect: true,
});
} else {
quizStore.addJawaban({
jawaban: abjads[parseInt(maxKey)],
isCorrect: false,
});
}
setShowAnswer(true);
previousResult = [];
setProgress(0);
setTimeout(() => {
isLoading = false;
setShowAnswer(false);
setProgress(0);
noSoal++;
previousResult = [];
quizStore.setSoalIndex(noSoal);
if (noSoal === quizStore.listSoal.length) {
quizStore.setSession(false);
quizStore.setIsFinish(true);
Swal.fire({
title: "Loading",
text: "Proses menyimpan data...",
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
},
});
navigate("/kuis/tebak-huruf");
}
}, 2000);
}
@ -163,7 +201,9 @@ const Quiz = () => {
const calt = calcLandmarkList(videoRef.current, landm);
const finalResult = preProcessLandmark(calt);
makePrediction(finalResult);
if (!isLoading) {
makePrediction(finalResult);
}
} else {
setHandPresence(false);
setProgress(0);
@ -179,6 +219,15 @@ const Quiz = () => {
};
const store = useNavbarStore();
const quizStore = useTebakHurufStore();
console.log(quizStore.jawaban);
useEffect(() => {
if (!quizStore.session) {
window.location.href = "/kuis/tebak-huruf";
}
}, []);
useEffect(() => {
store.setNavSelected("kuis");
@ -205,33 +254,48 @@ const Quiz = () => {
<div className="rounded-md px-3 py-2 text-white flex flex-col justify-center items-center gap-3">
<img
className="h-56"
src="/assets/gif/salah.gif"
alt="Jawaban Salah"
src={`/assets/gif/${
quizStore.jawaban[quizStore.soalIndex]?.isCorrect
? "betul"
: "salah"
}.gif`}
alt={`Jawaban ${
quizStore.jawaban[quizStore.soalIndex]?.isCorrect
? "Benar"
: "Salah"
}`}
/>
<p className="text-center text-6xl font-bold">A</p>
<p className="text-center text-6xl font-bold">
{quizStore.jawaban[quizStore.soalIndex]?.jawaban}
</p>
<h1 className="text-2xl font-semibold text-center">
Jawaban kamu Salah
Jawaban kamu{" "}
{quizStore.jawaban[quizStore.soalIndex]?.isCorrect
? "Benar"
: "Salah"}
</h1>
</div>
</div>
<div className="flex items-center gap-2 mt-4">
<MdOutlineQuiz size={18} />
<h1 className="text-xl font-semibold">Soal: 1 / 10</h1>
<h1 className="text-xl font-semibold">
Soal: {quizStore.soalIndex + 1} / {quizStore.listSoal.length}
</h1>
</div>
<div className="flex flex-col flex-1 py-4">
{loadCamera ? (
<div className="rounded-md overflow-hidden relative">
{!showAnswer && (
<div className="top-6 left-6 absolute flex gap-2 items-center bg-white text-black rounded-md drop-shadow px-3 py-2">
<h1 className="text-2xl font-semibold text-center">
Tebak Huruf K
<div className="md:top-6 top-3 left-3 md:left-6 absolute flex gap-2 items-center bg-white text-black rounded-md drop-shadow px-3 py-2">
<h1 className="md:text-2xl font-semibold text-center">
Tebak Huruf {quizStore.listSoal[quizStore.soalIndex]}
</h1>
</div>
)}
{handPresence && !showAnswer && (
<div className="top-6 right-6 absolute flex gap-2 items-center bg-white text-black rounded-md drop-shadow px-3 py-2 w-fit">
<div className="bottom-3 md:bottom-auto md:top-6 right-3 md:right-6 absolute flex gap-2 items-center bg-white text-black rounded-md drop-shadow px-3 py-2 w-fit">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<span className="loader"></span>

View File

@ -1,7 +1,128 @@
import LayoutPage from "@/components/templates/LayoutPage";
import { Link } from "react-router-dom";
import useNavbarStore from "@/stores/NavbarStore";
import useTebakHurufStore from "@/stores/TebakHurufStore";
import { useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import Swal from "sweetalert2";
const TebakHuruf = () => {
const quizStore = useTebakHurufStore();
const store = useNavbarStore();
useEffect(() => {
store.setNavSelected("kuis");
}, []);
const shuffleSoal = () => {
const arr = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
];
const shuffledArr = arr.sort(() => 0.5 - Math.random()).slice(0, 10);
return shuffledArr;
};
const saveData = async () => {
let score = 0;
let benar = 0;
let salah = 0;
try {
quizStore.jawaban.forEach((jawaban) => {
if (jawaban.isCorrect) {
score += 10;
benar += 1;
} else {
salah += 1;
}
});
await fetch("https://ksuli-api.deno.dev/proses-kuis", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer KSULI_TOKEN_321`,
},
body: JSON.stringify({
kategori_id: "rec_cuum78tqrj678tmbcjh0",
person_name: quizStore.name,
score: score,
}),
});
Swal.close();
} catch (error) {
console.error("Error saving data:", error);
Swal.fire({
icon: "error",
title: "Oops...",
text: "Something went wrong while saving your data!",
});
}
Swal.fire({
icon: "success",
title: "Kuis telah selesai",
html: `
<p>Anda menyelesaikan kuis dengan score: ${score}</p>
<p>Jawaban benar: ${benar}</p>
<p>Jawaban salah: ${salah}</p>
`,
});
};
const router = useNavigate();
const ProsesKuis = () => {
if (quizStore.name === "") {
Swal.fire({
icon: "error",
title: "Oops...",
text: "Isi nama terlebih dahulu",
});
return;
}
quizStore.setSoalIndex(0);
quizStore.setSession(true);
quizStore.setListSoal(shuffleSoal());
router("/kuis/tebak-huruf/app");
};
useEffect(() => {
if (quizStore.isFinish) {
saveData();
}
return () => {
quizStore.setIsFinish(false);
};
}, [quizStore.isFinish]);
return (
<LayoutPage>
<div className="flex flex-col flex-1 py-4 relative">
@ -25,13 +146,18 @@ const TebakHuruf = () => {
<input
placeholder="Name.."
type="text"
value={quizStore.name}
onChange={(e) => quizStore.setName(e.target.value)}
className="bg-transparent outline-none p-2 px-8 flex-1 w-full"
/>
<Link to="/kuis/tebak-huruf/app">
<button className="bg-blue-500 hover:bg-blue-700 text-white px-3 py-2 rounded-full whitespace-nowrap">
Mulai Kuis
</button>
</Link>
<button
onClick={() => {
ProsesKuis();
}}
className="bg-blue-500 hover:bg-blue-700 text-white px-3 py-2 rounded-full whitespace-nowrap"
>
Mulai Kuis
</button>
</div>
<span className="text-primary">Lihat Ranking</span>
</div>

View File

@ -15,6 +15,12 @@ type TebakHurufType = {
jawaban: JawabanType[];
setJawaban: (jawaban: JawabanType[]) => void;
addJawaban: (jawaban: JawabanType) => void;
session: boolean;
setSession: (session: boolean) => void;
name: string;
setName: (name: string) => void;
isFinish: boolean;
setIsFinish: (isFinish: boolean) => void;
};
const useTebakHurufStore = create<TebakHurufType>((set) => ({
@ -28,6 +34,12 @@ const useTebakHurufStore = create<TebakHurufType>((set) => ({
setJawaban: (jawaban) => set({ jawaban: jawaban }),
addJawaban: (jawaban) =>
set((state) => ({ jawaban: [...state.jawaban, jawaban] })),
session: false,
setSession: (session) => set({ session: session }),
name: "",
setName: (name) => set({ name: name }),
isFinish: false,
setIsFinish: (isFinish) => set({ isFinish: isFinish }),
}));
export default useTebakHurufStore;