feat: get ranking
This commit is contained in:
parent
ae17e2d0ba
commit
46978cbde4
|
@ -21,6 +21,7 @@
|
||||||
"react-virtualized-auto-sizer": "^1.0.24",
|
"react-virtualized-auto-sizer": "^1.0.24",
|
||||||
"react-window": "^1.8.10",
|
"react-window": "^1.8.10",
|
||||||
"sweetalert2": "^11.17.2",
|
"sweetalert2": "^11.17.2",
|
||||||
|
"swr": "^2.3.2",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"zustand": "^4.5.5"
|
"zustand": "^4.5.5"
|
||||||
},
|
},
|
||||||
|
@ -2316,6 +2317,15 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dequal": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/didyoumean": {
|
"node_modules/didyoumean": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
|
@ -4360,6 +4370,28 @@
|
||||||
"url": "https://github.com/sponsors/limonte"
|
"url": "https://github.com/sponsors/limonte"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/swr": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-RosxFpiabojs75IwQ316DGoDRmOqtiAj0tg8wCcbEu4CiLZBs/a9QNtHV7TUfDXmmlgqij/NqzKq/eLelyv9xA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swr/node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwind-merge": {
|
"node_modules/tailwind-merge": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"react-virtualized-auto-sizer": "^1.0.24",
|
"react-virtualized-auto-sizer": "^1.0.24",
|
||||||
"react-window": "^1.8.10",
|
"react-window": "^1.8.10",
|
||||||
"sweetalert2": "^11.17.2",
|
"sweetalert2": "^11.17.2",
|
||||||
|
"swr": "^2.3.2",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"zustand": "^4.5.5"
|
"zustand": "^4.5.5"
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.7 KiB |
|
@ -1,77 +1,37 @@
|
||||||
import LayoutPage from "@/components/templates/LayoutPage";
|
import LayoutPage from "@/components/templates/LayoutPage";
|
||||||
import useNavbarStore from "@/stores/NavbarStore";
|
import useNavbarStore from "@/stores/NavbarStore";
|
||||||
import { useEffect, useState } from "react";
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import { HiOutlineHome } from "react-icons/hi";
|
import { HiOutlineHome } from "react-icons/hi";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import useSWR from "swr";
|
||||||
const data = [
|
import { fetcher } from "@/utils/fetcher";
|
||||||
{
|
import { debounce } from "@/utils/debounce";
|
||||||
id: 1,
|
|
||||||
name: "Asep",
|
|
||||||
score: 100,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "Budi",
|
|
||||||
score: 90,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: "Cecep",
|
|
||||||
score: 80,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Dedi",
|
|
||||||
score: 70,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: "Euis",
|
|
||||||
score: 60,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
name: "Fafa",
|
|
||||||
score: 50,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
name: "Gaga",
|
|
||||||
score: 40,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
name: "Haha",
|
|
||||||
score: 30,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
name: "Ii",
|
|
||||||
score: 20,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
name: "Jaja",
|
|
||||||
score: 10,
|
|
||||||
time: "15-08-2021 12:40",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const Ranking = () => {
|
const Ranking = () => {
|
||||||
const store = useNavbarStore();
|
const store = useNavbarStore();
|
||||||
|
|
||||||
const [tabSelected, setTabSelected] = useState(0);
|
const [tabSelected, setTabSelected] = useState(0);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const size = 20;
|
||||||
|
|
||||||
|
const { data, error, isLoading } = useSWR(
|
||||||
|
`https://ksuli-api.deno.dev/ranking?page=${page}&search=${search}&size=${size}&kategori_id=${
|
||||||
|
tabSelected == 0 ? "rec_cuum78tqrj678tmbcjh0" : "rec_cuum7c5qrj60bgubcjog"
|
||||||
|
}`,
|
||||||
|
fetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSearch = debounce((term) => {
|
||||||
|
setSearch(term);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
const handleChangeSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
setPage(1);
|
||||||
|
handleSearch(value);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
store.setNavSelected("kuis");
|
store.setNavSelected("kuis");
|
||||||
|
@ -93,19 +53,25 @@ const Ranking = () => {
|
||||||
<li className="font-medium text-gray-500">Ranking</li>
|
<li className="font-medium text-gray-500">Ranking</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className="md:max-w-[500px] w-full flex flex-col mt-6 items-center">
|
<div className="md:max-w-[500px] w-full flex flex-col mt-6 md:items-center h-full flex-1">
|
||||||
<div className="flex items-center">
|
<div className="flex md:items-center gap-3 md:gap-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setTabSelected(0)}
|
onClick={() => {
|
||||||
className={`btn btn-link ${
|
setTabSelected(0);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className={`btn btn-link p-0 md:p-2 ${
|
||||||
tabSelected === 0 ? "" : "no-underline text-gray-500"
|
tabSelected === 0 ? "" : "no-underline text-gray-500"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Tebak Huruf
|
Tebak Huruf
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setTabSelected(1)}
|
onClick={() => {
|
||||||
className={`btn btn-link ${
|
setTabSelected(1);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className={`btn btn-link p-0 md:p-2 ${
|
||||||
tabSelected === 1 ? "" : "no-underline text-gray-500"
|
tabSelected === 1 ? "" : "no-underline text-gray-500"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -114,7 +80,12 @@ const Ranking = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="input input-bordered flex items-center gap-2 w-full mt-6 mb-4">
|
<label className="input input-bordered flex items-center gap-2 w-full mt-6 mb-4">
|
||||||
<input type="text" className="grow" placeholder="Search" />
|
<input
|
||||||
|
onChange={handleChangeSearch}
|
||||||
|
type="text"
|
||||||
|
className="grow"
|
||||||
|
placeholder="Search"
|
||||||
|
/>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
|
@ -129,7 +100,45 @@ const Ranking = () => {
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{data.map((item, index) => (
|
{isLoading && (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full flex-1">
|
||||||
|
<div className="loader"></div>
|
||||||
|
<p className="mt-4 text-lg text-gray-700">Loading...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && !isLoading && (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full flex-1">
|
||||||
|
<img
|
||||||
|
src="/assets/images/nodata.svg"
|
||||||
|
className="h-44"
|
||||||
|
alt="Error Image"
|
||||||
|
/>
|
||||||
|
<p className="mt-4 text-md text-center text-gray-700">
|
||||||
|
Sedang terjadi error, silakan coba lagi nanti.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data && data.records.length === 0 && !isLoading && (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full flex-1">
|
||||||
|
<img
|
||||||
|
src="/assets/images/nodata.svg"
|
||||||
|
className="h-44"
|
||||||
|
alt="Error Image"
|
||||||
|
/>
|
||||||
|
<p className="mt-4 text-md text-center text-gray-700">
|
||||||
|
Data tidak ditemukan
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data &&
|
||||||
|
data.records
|
||||||
|
.sort((a: any, b: any) =>
|
||||||
|
tabSelected === 0 ? b.score - a.score : a.score - b.score
|
||||||
|
)
|
||||||
|
.map((item: any, index: number) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{
|
initial={{
|
||||||
scale: 0,
|
scale: 0,
|
||||||
|
@ -145,13 +154,26 @@ const Ranking = () => {
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="bg-green-400 rounded-full h-14 w-14 object-cover"
|
className="bg-green-400 rounded-full h-14 w-14 object-cover"
|
||||||
src={`https://api.dicebear.com/9.x/miniavs/svg?seed=${item.name}&backgroundColor=b6e3f4,c0aede,d1d4f9`}
|
src={`https://api.dicebear.com/9.x/miniavs/svg?seed=${item.person_name}&backgroundColor=b6e3f4,c0aede,d1d4f9`}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<p className="font-semibold">{item.name}</p>
|
<p className="font-semibold">{item.person_name}</p>
|
||||||
<p>{item.score} Poin</p>
|
<div className="flex items-center flex-wrap">
|
||||||
<p className="text-xs text-gray-500">{item.time}</p>
|
<p className="text-sm">
|
||||||
|
{item.score} {tabSelected == 0 ? "Point" : "Detik"}
|
||||||
|
</p>
|
||||||
|
<div className="w-2"></div>
|
||||||
|
{index < 3 && (
|
||||||
|
<p className="text-xs text-primary">
|
||||||
|
⭐ Top Score {index + 1}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
{new Date(item.xata.createdAt).toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`w-6 h-6 rounded-full flex justify-center items-center p-5 `}
|
className={`w-6 h-6 rounded-full flex justify-center items-center p-5 `}
|
||||||
|
@ -165,6 +187,50 @@ const Ranking = () => {
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
{data && (
|
||||||
|
<div className="flex justify-center mt-6 gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
|
||||||
|
className="btn btn-primary btn-xs"
|
||||||
|
disabled={page === 1}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
className="w-3 h-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M15 19l-7-7 7-7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setPage((prev) => prev + 1)}
|
||||||
|
className="btn btn-primary btn-xs"
|
||||||
|
disabled={!data.meta.page.more}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
className="w-3 h-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LayoutPage>
|
</LayoutPage>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const debounce = <T extends (...args: any[]) => any>(
|
||||||
|
func: T,
|
||||||
|
delay: number
|
||||||
|
): ((...args: Parameters<T>) => void) => {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => func.apply(this, args), delay);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const fetcher = (url: RequestInfo, options: RequestInit = {}) => {
|
||||||
|
const token = "KSULI_TOKEN_321"; // Replace with your actual token
|
||||||
|
const headers = {
|
||||||
|
...options.headers,
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch(url, { ...options, headers }).then((res) => res.json());
|
||||||
|
};
|
Loading…
Reference in New Issue