quiz[id]/netije route added

This commit is contained in:
Ilgeldi 2025-03-07 15:18:56 +05:00
parent ce4d7348d3
commit 30dd824526
16 changed files with 991 additions and 128 deletions

View File

@ -191,6 +191,12 @@ export class Queries {
}).then((res) => res.json().then((res) => res as IQuizQuestions)); }).then((res) => res.json().then((res) => res as IQuizQuestions));
} }
public static async getQuizById(quiz_id: string) {
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuiz(quiz_id)}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res));
}
public static async getQuizHistory( public static async getQuizHistory(
id: number id: number
): Promise<IQuizQuestionsHistory> { ): Promise<IQuizQuestionsHistory> {
@ -289,17 +295,77 @@ export const getTossData = async ({
} }
}; };
export const getQuizWinnersById = async (id: number) => { export const getQuizNetijeData = async (id: string) => {
try { try {
const res = await fetch( const res = await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(id)}`, `${baseUrl.QUIZ_SRC}${routes.getQuizNetijeWinners(id)}`,
{ {
next: { revalidate: 3600 }, next: {
revalidate: 300,
tags: ["netije"],
},
} }
); );
if (!res.ok) {
return undefined;
}
const result = await res.json();
return result;
} catch (err) {
console.log(err);
}
};
export const getNextQuizNetijeData = async (
id: string,
limit: number,
offset: number
) => {
try {
const res = await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizNetijeWinners(
id
)}?limit=${limit}&offset=${offset}`,
{
next: {
revalidate: 300,
tags: ["netije"],
},
}
);
if (!res.ok) {
return undefined;
}
const result = await res.json();
return result;
} catch (err) {
console.log(err);
}
};
export const getQuizWinnersById = async (id: number, step?: number) => {
try {
const res = step
? await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(
id
)}?tapgyr=${step}`,
{
next: { revalidate: 3600 },
}
)
: await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(id)}`,
{
next: { revalidate: 3600 },
}
);
const result = await res.json(); const result = await res.json();
console.log(result);
return result as IQuizQuestionsWinners; return result as IQuizQuestionsWinners;
} catch (err) { } catch (err) {
@ -310,17 +376,27 @@ export const getQuizWinnersById = async (id: number) => {
export const getNextQuizWinnners = async ( export const getNextQuizWinnners = async (
id: number, id: number,
limit: number, limit: number,
offset: number offset: number,
step?: number
) => { ) => {
try { try {
const res = await fetch( const res = step
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners( ? await fetch(
id `${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(
)}?limit=${limit}&offset=${offset}`, id
{ )}?tapgyr=${step}&limit=${limit}&offset=${offset}`,
next: { revalidate: 3600 }, {
} next: { revalidate: 3600 },
); }
)
: await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(
id
)}?limit=${limit}&offset=${offset}`,
{
next: { revalidate: 3600 },
}
);
const result = await res.json(); const result = await res.json();
console.log(result); console.log(result);

View File

@ -2,19 +2,18 @@
import { Queries } from "@/api/queries"; import { Queries } from "@/api/queries";
import Loader from "@/components/Loader"; import Loader from "@/components/Loader";
import QuizQuestion from "@/components/quiz/QuizQuestion";
import QuizQuestionList from "@/components/quiz/QuizQuestionList"; import QuizQuestionList from "@/components/quiz/QuizQuestionList";
import QuizSearch from "@/components/quiz/QuizSearch"; import QuizSearch from "@/components/quiz/QuizSearch";
import QuizTable from "@/components/quiz/QuizTable"; import QuizTable from "@/components/quiz/QuizTable";
import QuizWinnerTable from "@/components/quiz/QuizWinnerTable"; import QuizWinnerTable from "@/components/quiz/QuizWinnerTable";
import GradientTitle from "@/components/vote/GradientTitle"; import GradientTitle from "@/components/vote/GradientTitle";
import QuizContext from "@/context/QuizContext"; import { IQuizQuestions } from "@/models/quizQuestions.model";
import { IQuizQuestions, Question } from "@/models/quizQuestions.model";
import QuizProvider from "@/providers/QuizProvider"; import QuizProvider from "@/providers/QuizProvider";
import { useQuizSearchActive } from "@/store/store"; import { useQuizSearchActive, useSteps } from "@/store/store";
import { Validator } from "@/utils/validator"; import { Validator } from "@/utils/validator";
import Image from "next/image"; import Image from "next/image";
import { useContext, useEffect, useState } from "react"; import Link from "next/link";
import { useEffect, useState } from "react";
import { useMediaQuery } from "usehooks-ts"; import { useMediaQuery } from "usehooks-ts";
interface IParams { interface IParams {
@ -27,35 +26,48 @@ const page = ({ params }: IParams) => {
const [quizFinished, setQuizFinished] = useState<boolean>(false); const [quizFinished, setQuizFinished] = useState<boolean>(false);
const [data, setData] = useState<IQuizQuestions>(); const [data, setData] = useState<IQuizQuestions>();
const { active } = useQuizSearchActive(); const { active } = useQuizSearchActive();
const { step, setStep } = useSteps();
// const { data, error, isFetching } = useQuery(
// ['quiz_questions'],
// () => Queries.getQuizQuestions(),
// {
// keepPreviousData: true,
// },
// );
useEffect(() => { useEffect(() => {
if (!params.quiz_id) { if (!params.quiz_id) {
Queries.getQuizQuestions().then((res) => { Queries.getQuizQuestions().then((res) => {
setData(res); setData(res);
res if (res.data.questions) {
? res.data.questions[res.data.questions.length - 1].status === res.data.questions.map((question) =>
"closed" question.status === "active" || question.status === "new"
? setQuizFinished(true) ? setQuizFinished(false)
: setQuizFinished(false) : setQuizFinished(true)
: null; );
} else if (res.data.steps && res.data.steps?.length > 0) {
setStep(res.data.steps[0].tapgyr);
for (let i = 0; i < res.data.steps.length; i++) {
res.data.steps[i].questions.map((question) =>
question.status === "active" || question.status === "new"
? setQuizFinished(false)
: setQuizFinished(true)
);
}
}
}); });
} else { } else {
Queries.getQuiz(params.quiz_id).then((res) => { Queries.getQuiz(params.quiz_id).then((res) => {
setData(res); setData(res);
res if (res.data.questions) {
? res.data.questions[res.data.questions.length - 1]?.status === res.data.questions.map((question) =>
"closed" question.status === "active" || question.status === "new"
? setQuizFinished(true) ? setQuizFinished(false)
: setQuizFinished(false) : setQuizFinished(true)
: null; );
} else if (res.data.steps && res.data.steps?.length > 0) {
setStep(res.data.steps[0].tapgyr);
for (let i = 0; i < res.data.steps.length; i++) {
res.data.steps[i].questions.map((question) =>
question.status === "active" || question.status === "new"
? setQuizFinished(false)
: setQuizFinished(true)
);
}
}
}); });
} }
}, []); }, []);
@ -129,6 +141,37 @@ const page = ({ params }: IParams) => {
</div> </div>
<div className="flex flex-col md:gap-[160px] gap-[80px]"> <div className="flex flex-col md:gap-[160px] gap-[80px]">
{data.data.has_steps !== 0 && data.data.steps && (
<div className="flex flex-col gap-4 items-center w-full">
<h1 className="text-textBlack md:text-[60px] leading-[100%] font-semibold">
Tapgyr
</h1>
<div className="flex w-full md:w-1/2 gap-[10px]">
{data.data.steps.map((item) => (
<button
onClick={() => {
setStep(item.tapgyr);
}}
key={item.tapgyr}
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 ${
step === item.tapgyr
? "bg-lightPrimary text-white"
: "bg-lightPrimaryContainer text-textLight"
}`}
>
{item.tapgyr}
</button>
))}
<Link
href={`/quiz/${params.quiz_id}/results`}
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 bg-lightPrimaryContainer text-center text-textLight`}
>
Netije
</Link>
</div>
</div>
)}
{data?.data && !active ? ( {data?.data && !active ? (
<QuizQuestionList <QuizQuestionList
paramsId={params.quiz_id} paramsId={params.quiz_id}
@ -138,15 +181,14 @@ const page = ({ params }: IParams) => {
/> />
) : null} ) : null}
{data?.data.id && quizFinished ? ( {data?.data.id && quizFinished && data.data.has_steps === 0 ? (
<QuizSearch quizId={data?.data.id} /> <QuizSearch quizId={data?.data.id} />
) : null} ) : null}
{data?.data.id && ( {data?.data.id && data.data.has_steps === 0 && (
<QuizWinnerTable <QuizWinnerTable
smsNumber={data.data.sms_number}
quizId={data?.data.id} quizId={data?.data.id}
quizFinished={quizFinished} questionsData={data.data.questions}
/> />
)} )}
</div> </div>

View File

@ -0,0 +1,43 @@
"use client";
import { Queries } from "@/api/queries";
import QuizHeader from "@/components/quiz/QuizHeader";
import QuizTapgyrResults from "@/components/quiz/QuizTapgyrResults";
import QuizTapgyrWinners from "@/components/quiz/QuizTapgyrWinners";
import { Data } from "@/models/quizQuestions.model";
import React, { useEffect, useState } from "react";
interface IParams {
params: {
quiz_id: string;
};
}
const Page = ({ params }: IParams) => {
const [data, setData] = useState<Data>();
useEffect(() => {
Queries.getQuizById(params.quiz_id).then((res) => setData(res.data));
}, []);
return (
<section className="container py-[40px]">
<div className="flex flex-col w-full py-[40px] gap-[80px]">
<QuizHeader data={data} />
<QuizTapgyrResults
id={params.quiz_id}
steps={
data?.steps ? data?.steps?.map((item) => String(item.tapgyr)) : []
}
/>
{data?.has_steps &&
data.steps &&
data.steps?.length > 0 &&
data.steps.map((step) => (
<QuizTapgyrWinners id={params.quiz_id} tapgyr={step.tapgyr} questions={step.questions} />
))}
</div>
</section>
);
};
export default Page;

View File

@ -1,15 +1,15 @@
'use client'; "use client";
import { Queries } from '@/api/queries'; import { Queries } from "@/api/queries";
import QuizQuestionList from '@/components/quiz/QuizQuestionList'; import QuizQuestionList from "@/components/quiz/QuizQuestionList";
import QuizSearch from '@/components/quiz/QuizSearch'; import QuizSearch from "@/components/quiz/QuizSearch";
import QuizTable from '@/components/quiz/QuizTable'; import QuizTable from "@/components/quiz/QuizTable";
import QuizWinnerTable from '@/components/quiz/QuizWinnerTable'; import QuizWinnerTable from "@/components/quiz/QuizWinnerTable";
import { IQuizQuestions, Question } from '@/models/quizQuestions.model'; import { IQuizQuestions, Question } from "@/models/quizQuestions.model";
import QuizProvider from '@/providers/QuizProvider'; import QuizProvider from "@/providers/QuizProvider";
import { Validator } from '@/utils/validator'; import { Validator } from "@/utils/validator";
import Image from 'next/image'; import Image from "next/image";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { useMediaQuery } from 'usehooks-ts'; import { useMediaQuery } from "usehooks-ts";
const page = () => { const page = () => {
const [quizFinished, setQuizFinished] = useState<boolean>(false); const [quizFinished, setQuizFinished] = useState<boolean>(false);
@ -18,19 +18,19 @@ const page = () => {
useEffect(() => { useEffect(() => {
Queries.getQuizQuestions().then((res) => { Queries.getQuizQuestions().then((res) => {
setData(res); setData(res);
res res && res.data.questions
? res.data.questions[res.data.questions.length - 1]?.status === 'closed' ? res.data.questions[res.data.questions.length - 1]?.status === "closed"
? setQuizFinished(true) ? setQuizFinished(true)
: setQuizFinished(false) : setQuizFinished(false)
: null; : null;
}); });
}, []); }, []);
const mobile = useMediaQuery('(max-width: 768px)'); const mobile = useMediaQuery("(max-width: 768px)");
return ( return (
<main className="pt-[60px] pb-[200px]"> <main className="pt-[60px] pb-[200px]">
{typeof data !== 'string' ? ( {typeof data !== "string" ? (
<div className="container flex flex-col md:gap-[200px] gap-[80px]"> <div className="container flex flex-col md:gap-[200px] gap-[80px]">
<QuizProvider> <QuizProvider>
<div className="flex flex-col gap-[100px]"> <div className="flex flex-col gap-[100px]">
@ -55,7 +55,7 @@ const page = () => {
? data.data.banner_mobile ? data.data.banner_mobile
: data.data.banner : data.data.banner
} }
alt={'banner'} alt={"banner"}
unoptimized unoptimized
unselectable="off" unselectable="off"
fill fill
@ -64,7 +64,7 @@ const page = () => {
) : ( ) : (
<Image <Image
src={data?.data.banner} src={data?.data.banner}
alt={'banner'} alt={"banner"}
unoptimized unoptimized
unselectable="off" unselectable="off"
fill fill
@ -80,7 +80,9 @@ const page = () => {
) : null} ) : null}
</div> </div>
{data?.data.id && quizFinished ? <QuizSearch quizId={data?.data.id} /> : null} {data?.data.id && quizFinished ? (
<QuizSearch quizId={data?.data.id} />
) : null}
<div className="flex flex-col md:gap-[160px] gap-[80px]"> <div className="flex flex-col md:gap-[160px] gap-[80px]">
{data?.data ? ( {data?.data ? (
@ -96,9 +98,8 @@ const page = () => {
) : null} */} ) : null} */}
{data?.data.id && ( {data?.data.id && (
<QuizWinnerTable <QuizWinnerTable
smsNumber={data.data.sms_number}
quizId={data?.data.id} quizId={data?.data.id}
quizFinished={quizFinished} questionsData={data.data.questions}
/> />
)} )}
</div> </div>

View File

@ -96,7 +96,9 @@ const VideoPlayer = ({ maxHeight, maxWidth, video_id }: IProps) => {
</div> </div>
<audio <audio
controls controls
controlsList={canDownload ? "" : "nodownload"} // Conditionally enable/disable download controlsList={
data?.data.is_downloadable === 0 ? "nodownload" : ""
} // Conditionally enable/disable download
className="w-full rounded bg-white" className="w-full rounded bg-white"
onPlay={() => onPlayHandler()} onPlay={() => onPlayHandler()}
> >

View File

@ -0,0 +1,46 @@
"use client";
import { Data } from "@/models/quizQuestions.model";
import Image from "next/image";
import React from "react";
const QuizHeader = ({ data }: { data: Data | undefined }) => {
return (
<header className="flex flex-col gap-[40px]">
<article>
<span className="text-[#46464F]">{data?.date}</span>
<h1 className="text-[44px] font-[500] text-[#1B1B21] leading-10">
<span className="md:hidden">Bäsleşik </span>
<span className="hidden md:inline">"Bäsleşigiň" netijeleri</span>
</h1>
<p className="text-[#46464F] mt-[8px]">{data?.description}</p>
</article>
{(data?.banner || data?.banner_mobile) && (
<>
{data.banner_mobile && (
<div className="w-full relative bg-[#E1E0FF] rounded-[8px] overflow-hidden md:hidden object-cover">
<Image
src={data.banner_mobile ? data.banner_mobile : data.banner}
alt="banner"
height={100}
unselectable="off"
className="object-cover"
fill
/>
</div>
)}
<div className="w-full h-[100px] hidden md:block bg-[#E1E0FF] rounded-[8px] relative overflow-hidden">
<Image
src={data.banner}
alt="banner"
unselectable="off"
className="object-cover"
fill
/>
</div>
</>
)}
</header>
);
};
export default QuizHeader;

View File

@ -1,10 +1,11 @@
'use client'; "use client";
import QuizQuestion from './QuizQuestion'; import QuizQuestion from "./QuizQuestion";
import { Queries } from '@/api/queries'; import { Queries } from "@/api/queries";
import { v4 } from 'uuid'; import { v4 } from "uuid";
import { Dispatch, useContext, useEffect, useState } from 'react'; import { Dispatch, useContext, useEffect, useState } from "react";
import { IQuizQuestions, Question } from '@/models/quizQuestions.model'; import { IQuizQuestions, Question } from "@/models/quizQuestions.model";
import QuizContext from '@/context/QuizContext'; import QuizContext from "@/context/QuizContext";
import { useSteps } from "@/store/store";
interface IProps { interface IProps {
setQuizFinished: Dispatch<boolean>; setQuizFinished: Dispatch<boolean>;
@ -19,32 +20,37 @@ const QuizQuestionList = ({
initialQuestionsData, initialQuestionsData,
paramsId, paramsId,
}: IProps) => { }: IProps) => {
const [data, setData] = useState<IQuizQuestions>(initialQuestionsData);
const { quizSearchData } = useContext(QuizContext).quizSearchContext; const { quizSearchData } = useContext(QuizContext).quizSearchContext;
const { setQuestionsData } = useContext(QuizContext).quizQuestionsContext; const { step } = useSteps();
const [questionData, setQuestionsData] = useState<Question[] | undefined>(
initialQuestionsData.data.questions
? initialQuestionsData.data.questions
: initialQuestionsData.data.steps
? initialQuestionsData.data.steps[0].questions
: []
);
useEffect(() => { useEffect(() => {
// Queries.getQuizQuestions().then((res) => setData(res));
setQuestionsData(initialQuestionsData.data.questions);
// data?.data.questions.map((question) =>
// question.status === 'active' || question.status === 'new'
// ? setQuizFinished(false)
// : setQuizFinished(true),
// );
if (paramsId && !quizFinished) { if (paramsId && !quizFinished) {
const interval = setInterval(() => { const interval = setInterval(() => {
Queries.getQuiz(paramsId).then((res) => { Queries.getQuiz(paramsId).then((res) => {
setData(res); if (res.data.questions) {
setQuestionsData(res.data.questions); setQuestionsData(res.data.questions);
res.data.questions.map((question) =>
res.data.questions.map((question) => question.status === "active" || question.status === "new"
question.status === 'active' || question.status === 'new' ? setQuizFinished(false)
? setQuizFinished(false) : setQuizFinished(true)
: setQuizFinished(true), );
); } else if (res.data.steps && res.data.steps?.length > 0) {
setQuestionsData(res.data.steps[0].questions);
for (let i = 0; i < res.data.steps.length; i++) {
res.data.steps[i].questions.map((question) =>
question.status === "active" || question.status === "new"
? setQuizFinished(false)
: setQuizFinished(true)
);
}
}
}); });
}, 60000); }, 60000);
return () => clearInterval(interval); return () => clearInterval(interval);
@ -53,45 +59,47 @@ const QuizQuestionList = ({
if (!paramsId && !quizFinished) { if (!paramsId && !quizFinished) {
const interval = setInterval(() => { const interval = setInterval(() => {
Queries.getQuizQuestions().then((res) => { Queries.getQuizQuestions().then((res) => {
setData(res); if (res.data.questions) {
setQuestionsData(res.data.questions); setQuestionsData(res.data.questions);
res.data.questions.map((question) =>
res.data.questions.map((question) => question.status === "active" || question.status === "new"
question.status === 'active' || question.status === 'new' ? setQuizFinished(false)
? setQuizFinished(false) : setQuizFinished(true)
: setQuizFinished(true), );
); } else if (res.data.steps && res.data.steps?.length > 0) {
setQuestionsData(res.data.steps[0].questions);
for (let i = 0; i < res.data.steps.length; i++) {
res.data.steps[i].questions.map((question) =>
question.status === "active" || question.status === "new"
? setQuizFinished(false)
: setQuizFinished(true)
);
}
}
}); });
}, 60000); }, 60000);
return () => clearInterval(interval); return () => clearInterval(interval);
} }
}, [quizFinished]); }, [quizFinished]);
useEffect(() => {
if (initialQuestionsData.data.steps) {
const tapgyrQuestions = initialQuestionsData.data.steps.find(
(item) => item.tapgyr === step
);
setQuestionsData(tapgyrQuestions?.questions);
}
}, [step]);
return ( return (
<div className="flex flex-col gap-[40px] md:gap-[160px]"> <div className="flex flex-col gap-[40px] md:gap-[160px]">
{data && !quizSearchData ? ( {quizSearchData && Object.values(quizSearchData.data).length === 0 ? (
data.data.questions.map((question, paramsId) =>
question.status !== 'new' ? (
<QuizQuestion
score={question.score}
questionId={question.id}
questionNumber={paramsId}
finished={question.status}
question={question.question}
key={v4()}
startsAt={question.starts_at}
endsAt={question.ends_at}
/>
) : null,
)
) : quizSearchData && Object.values(quizSearchData.data).length === 0 ? (
<h2 className="text-textDarkt text-center text-[28px] ms:text-[32px] flex items-center justify-center bg-fillBGBlockbg px-[20px] py-[40px] md:px-[40px] md:py-[80px] rounded-[24px]"> <h2 className="text-textDarkt text-center text-[28px] ms:text-[32px] flex items-center justify-center bg-fillBGBlockbg px-[20px] py-[40px] md:px-[40px] md:py-[80px] rounded-[24px]">
Вы не участвовали ни в одном вопросе Вы не участвовали ни в одном вопросе
</h2> </h2>
) : data ? ( ) : questionData ? (
data.data.questions.map((question, id) => questionData.map((question, id) =>
question.status !== 'new' ? ( question.status !== "new" ? (
<QuizQuestion <QuizQuestion
score={question.score} score={question.score}
questionId={question.id} questionId={question.id}
@ -102,7 +110,7 @@ const QuizQuestionList = ({
startsAt={question.starts_at} startsAt={question.starts_at}
endsAt={question.ends_at} endsAt={question.ends_at}
/> />
) : null, ) : null
) )
) : null} ) : null}
</div> </div>

View File

@ -36,8 +36,6 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
event: React.FormEvent<HTMLFormElement> event: React.FormEvent<HTMLFormElement>
) => { ) => {
event.preventDefault(); event.preventDefault();
setActive(true);
try { try {
const response = await fetch( const response = await fetch(
`https://sms.turkmentv.gov.tm/api/quiz/${quizId}/search`, `https://sms.turkmentv.gov.tm/api/quiz/${quizId}/search`,
@ -54,6 +52,7 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
const data = await response.json(); const data = await response.json();
setQuizSearchData(data); setQuizSearchData(data);
setSearchActive(true); setSearchActive(true);
setActive(true);
} catch (error) {} } catch (error) {}
}; };

View File

@ -0,0 +1,313 @@
"use client";
import { getNextQuizNetijeData, getQuizNetijeData } from "@/api/queries";
import { Datum } from "@/models/quizQuestionsWinners.model";
import { notFound } from "next/navigation";
import React, { useEffect, useState } from "react";
import Loader from "../Loader";
const padding = "py-4";
const QuizTapgyrResults = ({ id, steps }: { id: string; steps: string[] }) => {
const [data, setData] = useState<Datum[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [phone, setPhone] = useState<string>("");
const [total, setTotal] = useState<number>(0);
const [nextPageQueries, setQueries] = useState<{
limit: number;
offset: number;
}>({
limit: 0,
offset: 0,
});
async function getData(next?: boolean) {
if (next) {
const res = await getNextQuizNetijeData(
id,
nextPageQueries.limit,
nextPageQueries.offset
);
if (res) {
setQueries({
limit: res.per_page,
offset: nextPageQueries.offset + res.per_page,
});
setData([...data, ...res.data]);
}
} else {
setLoading(true);
const res = await getQuizNetijeData(id);
if (res) {
setTotal(res.total);
setQueries({
limit: res.per_page,
offset: res.per_page,
});
setData(res.data);
setLoading(false);
} else {
notFound();
}
}
}
// const handleSearchSubmit = async (event: any) => {
// if (event.key === "Enter") {
// event.preventDefault();
// try {
// setLoading(true);
// const response = await fetch(
// `https://sms.turkmentv.gov.tm/api/quiz/${id}/search`,
// {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify({ phone, tapgyr: 1 }),
// }
// );
// // Handle the response as needed
// const data = await response.json();
// setLoading(false);
// // setData(data);
// console.log(data);
// } catch (error) {
// console.log(error);
// }
// }
// };
useEffect(() => {
getData();
}, []);
return (
<article className="flex flex-col gap-[24px]">
<header className="flex justify-center">
<h1 className="text-[28px] text-[#1B1B21] md:text-[36px]">Netijeler</h1>
{/* <div className="flex items-center gap-[14px] lg:col-span-2 relative">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute left-4"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.93853 0.608964C5.90914 0.206926 6.94943 0 8 0C9.05058 0 10.0909 0.206926 11.0615 0.608964C12.0321 1.011 12.914 1.60028 13.6569 2.34315C14.3997 3.08601 14.989 3.96793 15.391 4.93853C15.7931 5.90914 16 6.94943 16 8C16 9.05057 15.7931 10.0909 15.391 11.0615C15.1172 11.7226 14.7565 12.3425 14.3196 12.9054L19.7071 18.2929C20.0976 18.6834 20.0976 19.3166 19.7071 19.7071C19.3166 20.0976 18.6834 20.0976 18.2929 19.7071L12.9054 14.3196C12.3425 14.7565 11.7226 15.1172 11.0615 15.391C10.0909 15.7931 9.05057 16 8 16C6.94943 16 5.90914 15.7931 4.93853 15.391C3.96793 14.989 3.08601 14.3997 2.34315 13.6569C1.60028 12.914 1.011 12.0321 0.608964 11.0615C0.206926 10.0909 0 9.05058 0 8C0 6.94942 0.206926 5.90914 0.608964 4.93853C1.011 3.96793 1.60028 3.08601 2.34315 2.34315C3.08601 1.60028 3.96793 1.011 4.93853 0.608964ZM8 2C7.21207 2 6.43185 2.15519 5.7039 2.45672C4.97595 2.75825 4.31451 3.20021 3.75736 3.75736C3.20021 4.31451 2.75825 4.97595 2.45672 5.7039C2.15519 6.43185 2 7.21207 2 8C2 8.78793 2.15519 9.56815 2.45672 10.2961C2.75825 11.0241 3.20021 11.6855 3.75736 12.2426C4.31451 12.7998 4.97595 13.2417 5.7039 13.5433C6.43185 13.8448 7.21207 14 8 14C8.78793 14 9.56815 13.8448 10.2961 13.5433C11.0241 13.2417 11.6855 12.7998 12.2426 12.2426C12.7998 11.6855 13.2417 11.0241 13.5433 10.2961C13.8448 9.56815 14 8.78793 14 8C14 7.21207 13.8448 6.43185 13.5433 5.7039C13.2417 4.97595 12.7998 4.31451 12.2426 3.75736C11.6855 3.20021 11.0241 2.75825 10.2961 2.45672C9.56815 2.15519 8.78793 2 8 2Z"
fill="#46464F"
/>
</svg>
<input
type="tel"
className="text-[#46464F] focus:outline-[#1B1B21] flex-1 bg-[#E4E1E9] rounded-[12px] py-4 pr-4 pl-12 w-full"
placeholder="63879809"
required
value={phone}
onChange={(e) => setPhone(e.target.value)}
onKeyDown={(e) => handleSearchSubmit(e)}
maxLength={8}
minLength={8}
/>
</div> */}
</header>
{data.length > 0 && !loading ? (
// Table Head
<div className="flex flex-col bg-[#F6F2FA] rounded-[12px] overflow-hidden">
<div className="flex justify-between bg-[#EAE7EF] p-[20px]">
{data[0].client?.id && (
<span className={`${padding} max-w-[20px] w-full text-center`}>
Ýeri
</span>
)}
{data[0].client?.phone && (
<span className={`${padding} max-w-[150px] w-full text-center`}>
Telefon beligisi
</span>
)}
{data[0].tapgyr_breakdown &&
steps.map((item) => (
<span
key={item}
className={`hidden md:inline-block min-w-[60px] max-w-[150px] w-full text-center ${padding}`}
>
Tapgyr {item}
</span>
))}
{data[0].correct_answers_time && (
<span
className={`${padding} min-w-[50px] text-center md:min-w-[115px] max-w-[150px] w-full flex justify-center`}
>
Nobatlaryň jemi
</span>
)}
{data[0].total_score_of_client && (
<span
className={`${padding} min-w-[50px] text-center md:min-w-[115px] max-w-[150px] w-full flex justify-center`}
>
Utuklaryň jemi
</span>
)}
</div>
{/* Table body */}
<div className=" text-[#46464F]">
{data.map((winner, id) => (
<React.Fragment key={id}>
<div
key={id}
className={`${
id !== data.length - 1 && "md:border-b md:border-[#C7C5D0]"
} flex justify-between px-[20px]`}
>
<span
className={`${padding} max-w-[20px] w-full text-center`}
>
{id > 0 &&
winner.correct_answers_time ===
data[id - 1].correct_answers_time
? id
: id + 1}
</span>
<div
className={`${padding} max-w-[150px] w-full text-center flex flex-col gap-[7px]`}
>
<span className="h-[36px]">
+{winner.client?.phone ? winner.client.phone : "-"}
</span>
{steps.map((step, i) => (
<span
key={i}
className="py-[5px] text-center col-span-2 md:hidden"
>
Tapgyr {step}
</span>
))}
</div>
{steps.map((step, i) => {
const tapgyr = winner.tapgyr_breakdown?.find(
(i) => i.tapgyr === Number(step)
);
return (
<div
key={i}
className="min-w-[60px] max-w-[150px] w-full justify-center hidden md:flex"
>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[50px] w-[100%]">
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
{tapgyr ? tapgyr.tapgyr_correct_time : "-"}
</span>
</div>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[50px] w-[100%]">
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
{tapgyr ? tapgyr.tapgyr_score : "-"}
</span>
</div>
</div>
);
})}
<div
className={`min-w-[50px] md:min-w-[115px] max-w-[150px] w-full flex flex-col md:flex-row justify-center ${padding}`}
>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
{winner.correct_answers_time
? winner.correct_answers_time
: "-"}
</span>
</div>
{steps.map((step, i) => {
const tapgyr = winner.tapgyr_breakdown?.find(
(i) => i.tapgyr === Number(step)
);
return (
<div key={i} className="py-[5px] md:hidden">
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[30px] h-[30px] flex justify-center items-center text-base leading-[125%] ">
{tapgyr ? tapgyr.tapgyr_correct_time : "-"}
</span>
</div>
</div>
);
})}
</div>
<div
className={`min-w-[50px] md:min-w-[115px] max-w-[150px] w-full flex flex-col md:flex-row justify-center ${padding}`}
>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
{winner.total_score_of_client
? winner.total_score_of_client
: "-"}
</span>
</div>
{steps.map((step, i) => {
const tapgyr = winner.tapgyr_breakdown?.find(
(i) => i.tapgyr === Number(step)
);
return (
<div key={i} className="py-[5px] md:hidden">
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
<span className="bg-fillOrange rounded-full w-[30px] h-[30px] flex justify-center items-center text-base leading-[125%] text-white">
{tapgyr ? tapgyr.tapgyr_correct_time : "-"}
</span>
</div>
</div>
);
})}
</div>
</div>
{/* {steps.map((step, i) => {
const tapgyr = winner.tapgyr_breakdown?.find(
(i) => i.tapgyr === Number(step)
);
return (
<div
key={i}
className={`${
i === steps.length - 1 && "border-b border-[#C7C5D0]"
} md:border-none md:hidden grid grid-cols-4`}
>
<span className="py-[5px] text-center col-span-2">
Tapgyr {step}
</span>
<div className="py-[5px]">
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[30px] h-[30px] flex justify-center items-center text-base leading-[125%] ">
{tapgyr ? tapgyr.tapgyr_correct_time : "-"}
</span>
</div>
</div>
<div className="py-[5px]">
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
<span className="bg-fillOrange rounded-full w-[30px] h-[30px] flex justify-center items-center text-base leading-[125%] text-white">
{tapgyr ? tapgyr.tapgyr_correct_time : "-"}
</span>
</div>
</div>
</div>
);
})} */}
</React.Fragment>
))}
</div>
</div>
) : (
<Loader />
)}
{data.length < total && (
<button
onClick={() => getData(true)}
className="py-[5px] self-center px-[10px] md:w-fit rounded-md bg-blue-500 text-white border border-blue-500 lg:hover:bg-white lg:hover:text-blue-500 transition-all duration-300"
>
Dowamy
</button>
)}
</article>
);
};
export default QuizTapgyrResults;

View File

@ -0,0 +1,309 @@
"use client";
import { getNextQuizWinnners, getQuizWinnersById } from "@/api/queries";
import { Question } from "@/models/quizQuestions.model";
import {
Datum,
IQuizQuestionsWinners,
} from "@/models/quizQuestionsWinners.model";
import { notFound } from "next/navigation";
import React, { useEffect, useState } from "react";
const padding = "py-4";
interface IProps {
id: string;
tapgyr: number;
questions: Question[];
}
const QuizTapgyrWinners = ({ id, tapgyr, questions }: IProps) => {
const [data, setData] = useState<Datum[] | []>([]);
const [loading, setLoading] = useState<boolean>(false);
const [phone, setPhone] = useState<string>("");
const [total, setTotal] = useState<number>(0);
const [nextPageQueries, setQueries] = useState<{
limit: number;
offset: number;
}>({
limit: 0,
offset: 0,
});
async function getData(next?: boolean) {
if (next) {
const res = await getNextQuizWinnners(
Number(id),
nextPageQueries.limit,
nextPageQueries.offset,
tapgyr
);
if (res) {
setQueries({
limit: res.meta.per_page,
offset: nextPageQueries.offset + res.meta.per_page,
});
setData([...data, ...res.data]);
}
} else {
setLoading(true);
const res = await getQuizWinnersById(Number(id), tapgyr);
if (res) {
setTotal(res.meta.total);
setQueries({
limit: res.meta.per_page,
offset: res.meta.per_page,
});
setData(res.data);
setLoading(false);
} else {
notFound();
}
}
}
// const handleSearchSubmit = async (event: any) => {
// if (event.key === "Enter") {
// event.preventDefault();
// try {
// setLoading(true);
// const response = await fetch(
// `https://sms.turkmentv.gov.tm/api/quiz/${id}/search`,
// {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify({ phone, tapgyr: tapgyr }),
// }
// );
// // Handle the response as needed
// const data = await response.json();
// setLoading(false);
// // setData(data);
// console.log(data);
// } catch (error) {
// console.log(error);
// }
// }
// };
useEffect(() => {
getData();
}, []);
return (
<article className="flex flex-col gap-[24px]">
<header className="grid grid-rows-2 md:grid-rows-1 md:grid-cols-2 lg:grid-cols-3">
<h1 className="text-[28px] text-[#1B1B21] md:text-[36px]">
Tapgyr {tapgyr}
</h1>
{/* <div className="flex items-center gap-[14px] lg:col-span-2 relative">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute left-4"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.93853 0.608964C5.90914 0.206926 6.94943 0 8 0C9.05058 0 10.0909 0.206926 11.0615 0.608964C12.0321 1.011 12.914 1.60028 13.6569 2.34315C14.3997 3.08601 14.989 3.96793 15.391 4.93853C15.7931 5.90914 16 6.94943 16 8C16 9.05057 15.7931 10.0909 15.391 11.0615C15.1172 11.7226 14.7565 12.3425 14.3196 12.9054L19.7071 18.2929C20.0976 18.6834 20.0976 19.3166 19.7071 19.7071C19.3166 20.0976 18.6834 20.0976 18.2929 19.7071L12.9054 14.3196C12.3425 14.7565 11.7226 15.1172 11.0615 15.391C10.0909 15.7931 9.05057 16 8 16C6.94943 16 5.90914 15.7931 4.93853 15.391C3.96793 14.989 3.08601 14.3997 2.34315 13.6569C1.60028 12.914 1.011 12.0321 0.608964 11.0615C0.206926 10.0909 0 9.05058 0 8C0 6.94942 0.206926 5.90914 0.608964 4.93853C1.011 3.96793 1.60028 3.08601 2.34315 2.34315C3.08601 1.60028 3.96793 1.011 4.93853 0.608964ZM8 2C7.21207 2 6.43185 2.15519 5.7039 2.45672C4.97595 2.75825 4.31451 3.20021 3.75736 3.75736C3.20021 4.31451 2.75825 4.97595 2.45672 5.7039C2.15519 6.43185 2 7.21207 2 8C2 8.78793 2.15519 9.56815 2.45672 10.2961C2.75825 11.0241 3.20021 11.6855 3.75736 12.2426C4.31451 12.7998 4.97595 13.2417 5.7039 13.5433C6.43185 13.8448 7.21207 14 8 14C8.78793 14 9.56815 13.8448 10.2961 13.5433C11.0241 13.2417 11.6855 12.7998 12.2426 12.2426C12.7998 11.6855 13.2417 11.0241 13.5433 10.2961C13.8448 9.56815 14 8.78793 14 8C14 7.21207 13.8448 6.43185 13.5433 5.7039C13.2417 4.97595 12.7998 4.31451 12.2426 3.75736C11.6855 3.20021 11.0241 2.75825 10.2961 2.45672C9.56815 2.15519 8.78793 2 8 2Z"
fill="#46464F"
/>
</svg>
<input
type="tel"
className="text-[#46464F] focus:outline-[#1B1B21] flex-1 bg-[#E4E1E9] rounded-[12px] py-4 pr-4 pl-12 w-full"
placeholder="63879809"
required
value={phone}
onChange={(e) => setPhone(e.target.value)}
onKeyDown={(e) => handleSearchSubmit(e)}
maxLength={8}
minLength={8}
/>
</div> */}
</header>
{data.length > 0 && !loading ? (
<table className="bg-[#F6F2FA] rounded-[12px] overflow-hidden">
<thead className="bg-[#EAE7EF] p-[20px]">
<tr>
{data[0].client_id && (
<th scope="col" className={`${padding} text-center`}>
Ýeri
</th>
)}
{data[0].phone && (
<th scope="col" className={`${padding} text-center`}>
Telefon beligisi
</th>
)}
{data[0].answers.length > 0 && (
<th
scope="col"
className={`${padding} text-center hidden md:inline-block md:w-full`}
>
Jogap beriş nobatlary
</th>
)}
{data[0].correct_answers_time && (
<th scope="col" className={`${padding} text-center`}>
Nobatlaryň jemi
</th>
)}
{data[0].total_score_of_client && (
<th scope="col" className={`${padding} text-center`}>
Utuklaryň jemi
</th>
)}
</tr>
</thead>
<tbody className=" text-[#46464F]">
{data.map((winner, id) => (
<React.Fragment key={id}>
<tr
className={`${
id !== data.length - 1 && "md:border-b md:border-[#C7C5D0]"
}`}
>
{/* Yeri */}
<th scope="row" className={`${padding} text-center`}>
{id > 0 &&
winner.correct_answers_time ===
data[id - 1].correct_answers_time
? id
: id + 1}
</th>
{/* Phone number */}
<td className={`${padding} text-center`}>
+{winner.phone ? winner.phone : "-"}
</td>
{/* Jogap berish nobaty */}
<td className={`${padding} hidden md:block`}>
<div className="w-full flex gap-[5px] justify-center">
{questions.map((question) => {
const matchingAnswer =
winner.answers.find(
(answer) =>
answer.question_id === question.id &&
answer.score > 0
) ||
winner.answers.find(
(answer) => answer.question_id === question.id
);
return (
<span
key={question.id}
className={`text-sm font-semibold leading-[125%] ${
matchingAnswer &&
matchingAnswer.serial_number_for_correct !== 0
? "text-fillGreen"
: matchingAnswer &&
matchingAnswer?.serial_number_for_correct ===
0
? "text-fillRed"
: "text-textLight"
}`}
>
{matchingAnswer && matchingAnswer.score !== 0
? matchingAnswer.serial_number_for_correct
: matchingAnswer && matchingAnswer?.score === 0
? "X"
: "0"}
</span>
);
})}
</div>
</td>
{/* Answer time */}
<td className={`${padding}`}>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] w-[100%]">
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
{winner.correct_answers_time
? winner.correct_answers_time
: "-"}
</span>
</div>
</td>
{/* Total score */}
<td className={`${padding}`}>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] w-[100%]">
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
{winner.total_score_of_client
? winner.total_score_of_client
: "-"}
</span>
</div>
</td>
</tr>
{/* Mobile jogab berish nobatlary */}
<tr
className={`${
id !== data.length - 1 && "border-b border-[#C7C5D0]"
} md:hidden`}
>
<th scope="row" colSpan={2}>
Jogap beriş nobatlary
</th>
<td colSpan={2}>
<div className="w-full flex gap-[5px] justify-center">
{questions.map((question) => {
const matchingAnswer =
winner.answers.find(
(answer) =>
answer.question_id === question.id &&
answer.score > 0
) ||
winner.answers.find(
(answer) => answer.question_id === question.id
);
return (
<span
key={question.id}
className={`text-sm font-semibold leading-[125%] ${
matchingAnswer &&
matchingAnswer.serial_number_for_correct !== 0
? "text-fillGreen"
: matchingAnswer &&
matchingAnswer?.serial_number_for_correct ===
0
? "text-fillRed"
: "text-textLight"
}`}
>
{matchingAnswer && matchingAnswer.score !== 0
? matchingAnswer.serial_number_for_correct
: matchingAnswer && matchingAnswer?.score === 0
? "X"
: "0"}
</span>
);
})}
</div>
</td>
</tr>
</React.Fragment>
))}
</tbody>
</table>
) : (
<h1>Loading...</h1>
)}
{data.length < total && (
<button
onClick={() => getData(true)}
className="py-[5px] self-center px-[10px] md:w-fit rounded-md bg-blue-500 text-white border border-blue-500 lg:hover:bg-white lg:hover:text-blue-500 transition-all duration-300"
>
Dowamy
</button>
)}
</article>
);
};
export default QuizTapgyrWinners;

View File

@ -13,15 +13,14 @@ import {
} from "@/models/quizQuestionsWinners.model"; } from "@/models/quizQuestionsWinners.model";
import QuizContext from "@/context/QuizContext"; import QuizContext from "@/context/QuizContext";
import { useQuizSearchActive } from "@/store/store"; import { useQuizSearchActive } from "@/store/store";
import { Question } from "@/models/quizQuestions.model";
interface IProps { interface IProps {
quizId: number; quizId: number;
quizFinished: boolean; questionsData: Question[] | undefined
smsNumber: string;
} }
const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => { const QuizWinnerTable = ({ quizId, questionsData }: IProps) => {
const [winnersData, setWinnersData] = useState<Datum[] | []>([]); const [winnersData, setWinnersData] = useState<Datum[] | []>([]);
const { questionsData } = useContext(QuizContext).quizQuestionsContext;
const [winnersTotal, setWinnersTotal] = useState<number>(0); const [winnersTotal, setWinnersTotal] = useState<number>(0);
const [nextPageQueries, setQueries] = useState<{ const [nextPageQueries, setQueries] = useState<{
limit: number; limit: number;
@ -54,7 +53,7 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
setWinnersData(res.data); setWinnersData(res.data);
setQueries({ setQueries({
limit: res?.meta.per_page, limit: res?.meta.per_page,
offset: nextPageQueries.offset + res?.meta.per_page, offset: res?.meta.per_page,
}); });
} }
} }

View File

@ -13,7 +13,12 @@ export interface Data {
description: string; description: string;
rules: Note[]; rules: Note[];
notes: Note[]; notes: Note[];
questions: Question[]; questions?: Question[];
has_steps: 0 | 1;
steps?: {
tapgyr: number;
questions: Question[];
}[];
} }
export interface Note { export interface Note {

View File

@ -9,6 +9,15 @@ export interface Datum {
client_id: number; client_id: number;
phone: string; phone: string;
answers: Answer[]; answers: Answer[];
client?: {
id: number;
phone: string;
};
tapgyr_breakdown?: {
tapgyr: number;
tapgyr_correct_time: number;
tapgyr_score: number;
}[];
} }
export interface Answer { export interface Answer {
id: number; id: number;

0
notes.txt Normal file
View File

View File

@ -12,6 +12,7 @@ export default {
getQuizQuestions: `/quiz/active`, getQuizQuestions: `/quiz/active`,
getQuiz: (quiz_id: string) => `/quiz/${quiz_id}`, getQuiz: (quiz_id: string) => `/quiz/${quiz_id}`,
getQuizQuestionsWinners: (id: number) => `/quiz/${id}/winners`, getQuizQuestionsWinners: (id: number) => `/quiz/${id}/winners`,
getQuizNetijeWinners: (id: string) => `/quiz/${id}/netije`,
getQuizQuestionHistory: (id: number) => `/question/${id}/history`, getQuizQuestionHistory: (id: number) => `/question/${id}/history`,
// =================================================================== // ===================================================================

View File

@ -10,6 +10,11 @@ interface IQuizSearch {
setActive: (value: boolean) => void; setActive: (value: boolean) => void;
} }
interface IStep {
step: number | null;
setStep: (value: number | null) => void;
}
export const useLotteryStatus = create<ILotteryStatus>((set) => ({ export const useLotteryStatus = create<ILotteryStatus>((set) => ({
status: "Upcoming", status: "Upcoming",
setStatus: (value: "Upcoming" | "Finished" | "Ongoing") => setStatus: (value: "Upcoming" | "Finished" | "Ongoing") =>
@ -20,3 +25,8 @@ export const useQuizSearchActive = create<IQuizSearch>((set) => ({
active: false, active: false,
setActive: (value: boolean) => set({ active: value }), setActive: (value: boolean) => set({ active: value }),
})); }));
export const useSteps = create<IStep>((set) => ({
step: null,
setStep: (value: number | null) => set({ step: value }),
}));