Skip to content

Commit

Permalink
[공통] - 퀴즈 이벤트 추가 (#105)
Browse files Browse the repository at this point in the history
* feat: quiz-session socket 연결

* fix: socket transports 방식 websocket으로 고정

* feat: User Type 식별 및 퀴즈 시작 소켓 연결

* fix: 퀴즈 로딩 화면 UI 수정

* fix: quizLoading UI 변경

* feat: spin keyframe 추가

* feat: quiz loading UI 추가

* feat: quiz end UI 추가

* feat: quiz end 상태 추가

* feat: master-session 이벤트 추가

* fix: show quiz 응답 수정

* temp: 소켓 테스트를 위한 소켓 응답 코드 추가

* fix: user type fetch function added

* test: 테스트 내용 커밋

* refactor: 이벤트명 변경

* fix: participant entry, statistics 이벤트 에러 수정

---------

Co-authored-by: byeong <[email protected]>
Co-authored-by: ByeongChan Choi <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2024
1 parent 4e74082 commit ac3e37a
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 27 deletions.
20 changes: 18 additions & 2 deletions packages/client/src/pages/quiz-master-session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ProgressBar from '@/shared/ui/progress-bar/ProgressBar';
import { CustomButton } from '@/shared/ui/buttons';
import AnswerGraph from '@/pages/quiz-master-session/ui/AnswerChart';
import RecentSubmittedAnswers from './ui/RecentSubmittedAnswers';
import { getQuizSocket } from '@/shared/utils/socket';

interface AnswerStat {
answer: string;
Expand Down Expand Up @@ -47,7 +48,8 @@ const statisticsCardItems = [
const limitedTime = 20;

export default function QuizMasterSession() {
const { id } = useParams();
const { pinCode, id } = useParams();
const socket = getQuizSocket();

const [answerStats, setAnswerStats] = useState<AnswerStat[]>([
{ answer: '1번', count: 10, color: '#3B82F6' },
Expand Down Expand Up @@ -75,7 +77,21 @@ export default function QuizMasterSession() {
});
};

const handleNextQuiz = () => {
socket.emit('start quiz', { pinCode });
};

useEffect(() => {
socket.emit('show quiz', { pinCode });

socket.emit('total status', { pinCode }, (response: any) => {
console.log(response);
});

socket.on('timer end', (response) => {
console.log(response);
});

const timer = setInterval(() => {
tick();
}, 1000);
Expand All @@ -92,7 +108,7 @@ export default function QuizMasterSession() {
<div>
<p className="font-bold text-gray-500 mb-2">제한 시간 {time}</p>
<div className="mb-2">
<CustomButton label="다음 퀴즈" type="full" />
<CustomButton label="다음 퀴즈" type="full" onClick={handleNextQuiz} />
</div>
</div>
</div>
Expand Down
11 changes: 8 additions & 3 deletions packages/client/src/pages/quiz-session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ export default function QuizSession() {

useEffect(() => {
const quizPromise = new Promise((resolve, reject) => {
const handleShowQuiz = (data: QuizData) => {
setQuiz(data);
const handleShowQuiz = (response: any) => {
const { currentQuizData, isLast } = response;
setQuiz(currentQuizData);
setIsLoading(true);
setIsQuizEnd(false);
resolve(data);
resolve(currentQuizData);
};
socket.on('show quiz', handleShowQuiz);

Expand Down Expand Up @@ -64,6 +65,10 @@ export default function QuizSession() {
socket.on('timer end', () => {
setIsQuizEnd(true);
});

return () => {
socket.off('timer end', () => {});
};
}, []);

return (
Expand Down
33 changes: 23 additions & 10 deletions packages/client/src/pages/quiz-session/ui/QuizBox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Dispatch, SetStateAction, useState, useRef, useEffect, useCallback } from 'react';

import { getQuizSocket } from '@/shared/utils/socket';
import { getCookie } from '@/shared/utils/cookie';
import { useParams } from 'react-router-dom';
interface ReactionData {
easy: number;
hard: number;
Expand All @@ -13,12 +15,13 @@ interface QuizBoxProps {
}

export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizBoxProps) {
const { pinCode } = useParams();
const [selectedAnswer, setSelectedAnswer] = useState<number[]>([]);
const [hasSubmitted, setHasSubmitted] = useState(false);
const easyButtonRef = useRef<HTMLButtonElement>(null);
const hardButtonRef = useRef<HTMLButtonElement>(null);
const socket = getQuizSocket();
console.log(quiz);
const [tick, setTick] = useState({ currentTime: 0, elapsedTime: 0, remainingTime: 0 });
const handleSelectAnswer = (idx: number) => {
setSelectedAnswer((prev) => {
if (prev.includes(idx)) {
Expand All @@ -29,7 +32,12 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB
};

const handleSubmit = () => {
socket.emit('submit answer', { selectAnswer: selectedAnswer });
socket.emit('submit answer', {
selectedAnswer: selectedAnswer,
sid: getCookie('sid'),
pinCode: pinCode,
submitTime: tick.elapsedTime,
});
console.log(selectedAnswer);
setHasSubmitted(true);
};
Expand Down Expand Up @@ -57,15 +65,20 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB
setReactionStats(data);
}, []);

const handleSubmitUpdate = useCallback(() => {
// 제출자에게 제출 완료에 대한 피드백 보여주기
}, []);

useEffect(() => {
socket.on('emoji', handleReactionUpdate);

socket.on('submit answer', handleSubmitUpdate);
socket.on('timer tick', (response) => {
setTick(response);
});

socket.on('participant statistics', (response) => {
console.log('participant statistics', response);
});

socket.on('time end', (response) => {
console.log('time end', response);
});
return () => {
socket.off('emoji', handleReactionUpdate);
};
Expand All @@ -77,7 +90,7 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB
<div className="bg-white/80 backdrop-blur-sm rounded-2xl shadow-lg p-6 mb-12">
<div className="flex items-center justify-between mb-6">
<span className="text-sm text-gray-500">Question 1/10</span>
<span className="text-sm text-gray-500">난이도</span>
<span className="text-sm text-gray-500">난이도 {tick.remainingTime}</span>
</div>
{/* 문제 */}
<div className="mb-8">
Expand All @@ -88,9 +101,9 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB
{quiz?.choices.map((choice, idx) => (
<button
key={choice.id}
onClick={() => handleSelectAnswer(choice.id)}
onClick={() => handleSelectAnswer(idx)}
className={`w-full p-4 text-left rounded-xl border transition-all ${
selectedAnswer.includes(choice.id)
selectedAnswer.includes(idx)
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-200'
}`}
Expand Down
12 changes: 11 additions & 1 deletion packages/client/src/pages/quiz-wait/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getCookie } from '@/shared/utils/cookie';
import { toastController } from '@/features/toast/model/toastController';
import LoadingSpinner from '@/shared/assets/icons/loading-alt-loop.svg?react';
import { useGetUserType } from '@/shared/hooks/useGetUserType';
import { apiClient } from '@/shared/api';

const GUEST_DISPLAY_SIZE = { width: 940, height: 568 };
const SPACING = 10;
Expand All @@ -26,18 +27,27 @@ export default function QuizWait() {
const socket = getQuizSocket();
const navigate = useNavigate();
const toast = toastController();
const [userType, setUserType] = useState<string>('');

const { data: userType } = useGetUserType({ pinCode: pinCode!, sid: getCookie('sid')! });
// const { data: userType } = useGetUserType({ pinCode: pinCode!, sid: getCookie('sid')! });

useEffect(() => {
socket.on('nickname', (response) => {
setGuests([...response]);
});

const fetchUserType = async () => {
const response = await apiClient.get(`/games/${pinCode}/sid/${getCookie('sid')}`);
console.log(response);
setUserType(response.type);
};

socket.on('start quiz', (response) => {
console.log('start quiz', response);
navigate(`/quiz/session/${pinCode}/1`);
});

fetchUserType();
}, []);

useLayoutEffect(() => {
Expand Down
26 changes: 15 additions & 11 deletions packages/server/src/module/game/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,13 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
const quizData = JSON.parse(await this.redisService.get(`classId=${classId}`));

const currentQuizData = quizData[currentOrder];
const currentTimeLimit = currentQuizData.timeLimit;
const currentTimeLimit = currentQuizData['timeLimit'];

const choicesLength = currentQuizData['choices'].length;

const choiceStatus = new Map(Array.from({ length: choicesLength }, (_, index) => [index, 0]));
console.log(choiceStatus); ///////////////////////
const choiceStatus = Object.fromEntries(
Array.from({ length: choicesLength }, (_, i) => [i, 0]),
);

const gameStatus = {
totalSubmit: 0,
Expand All @@ -138,7 +139,6 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
choiceStatus,
submitHistory: [],
};
console.log(gameStatus); ///////////////////////
await this.redisService.set(
`gameId=${pinCode}:quizId=${currentOrder}`,
JSON.stringify(gameStatus),
Expand Down Expand Up @@ -208,26 +208,29 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
const pariticipantInfo = JSON.parse(await this.redisService.get(`participant_sid=${sid}`));
// 현재 퀴즈 데이터 가져옴
const { classId, currentOrder, participantList } = gameInfo;
const submittedQuizOrder = currentOrder - 1;
const quizData = JSON.parse(await this.redisService.get(`classId=${classId}`));
const currentQuizData = quizData[currentOrder - 1];
const currentQuizData = quizData[submittedQuizOrder];

// 현재 퀴즈의 초이스 데이터 가져옴
const currentChoicesData = currentQuizData['choices'];

const gameStatus = JSON.parse(
await this.redisService.get(`gameId=${pinCode}:quizId=${currentOrder}`),
await this.redisService.get(`gameId=${pinCode}:quizId=${submittedQuizOrder}`),
);

// 제출 기록 저장 [[nickname, solveTime]]
gameStatus.submitHistory.push([pariticipantInfo.nickname, submitTime]);
gameStatus['submitHistory'].push([pariticipantInfo.nickname, submitTime]);
const submitHistory = gameStatus.submitHistory;

//totalSubmit
gameStatus.totalSubmit += 1;
const totalSubmit = gameStatus.totalSubmit;

//totalCorrect
console.log(selectedAnswer);
const isFlag = selectedAnswer.every((answer) => {
console.log('여기에요', answer);
return currentChoicesData[answer]['isCorrect'];
});
if (isFlag) {
Expand Down Expand Up @@ -256,17 +259,18 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
const solveRate = (totalCorrect / totalSubmit) * 100;
const averageTime = (totalTime / totalSubmit) * 100;
const participantRate = (totalSubmit / participantNum) * 100;
const participantStatic = { totalSubmit, solveRate, averageTime, participantRate };
const participantStatistics = { totalSubmit, solveRate, averageTime, participantRate };

const masterStatic = {
const masterStatistics = {
totalSubmit,
solveRate,
averageTime,
participantRate,
choiceStatus,
submitHistory,
};
client.to(pinCode).emit('participant static', participantStatic);
this.server.to(pinCode).emit('master static', masterStatic);
console.log(participantStatistics);
this.server.to(pinCode).emit('participant statistics', participantStatistics);
this.server.to(pinCode).emit('master statistics', masterStatistics);
}
}

0 comments on commit ac3e37a

Please sign in to comment.