feat: quiz tebak huruf
This commit is contained in:
parent
3cde38dd5e
commit
7c37026804
|
@ -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" : ""
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue