Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#131] 대회 종료시 대시보드로 이동 기능 #150

Merged
merged 4 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { css } from '@style/css';

import { useEffect } from 'react';

import Loading from '@/components/Common/Loading';
import useTimer from '@/hooks/timer/useTimer';
import useSocketTimer from '@/hooks/timer/useSocketTimer';
import { formatMilliSecond } from '@/utils/date';
import type { Socket } from '@/utils/socket';

interface Props {
socket: Socket;
isConnected: boolean;
endsAt: Date;
isConnected?: boolean;
onTimeout?: () => void;
}

export default function Timer(props: Props) {
let { socket, endsAt, isConnected } = props;
export default function SocketTimer(props: Props) {
let { socket, endsAt, isConnected, onTimeout } = props;
// api 연결이 X endsAt 대신 임시로 만들어놓은 것.
endsAt = new Date('2023-11-29T13:10:10.000Z');
const { remainMiliSeconds } = useTimer({ socket, endsAt });
// min 1 => 60초 동안 돌아갑니다. 변경해서 쓰세요 일단은..
const min = 120;
endsAt = new Date(new Date().getTime() + min * 60 * 1000);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2시간을 본다고 가정하는거죠?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 정확합니다. 직접 데이터 받아오니 끝나는 시간이 항상 지금보다 전이라 대시보드로 바로 이동해서 개발용으로 열어뒀습니다.


const { remainMiliSeconds, isTimeout } = useSocketTimer({
socket,
endsAt,
socketEvent: 'ping',
});

useEffect(() => {
if (isTimeout && typeof onTimeout === 'function') onTimeout();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여담인데 isFunction 같은 유틸을 하나 만들어야겠네요. 제쪽에서 할게요

}, [isTimeout]);

if (isConnected && remainMiliSeconds !== -1) {
// 연결도 되어있고, 서버 시간도 도착해서 count down을 시작할 수 있을 때
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Submission/Connecting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface Props {
}

export default function Connecting(props: Props) {
if (!props.isConnected) return null;
if (props.isConnected) return null;

return (
<div className={rowStyle}>
Expand All @@ -16,6 +16,7 @@ export default function Connecting(props: Props) {
</div>
);
}

const rowStyle = css({
display: 'flex',
gap: '0.5rem',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,36 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { Socket } from '@/utils/socket';

interface UseTimer {
interface Props {
socket: Socket;
endsAt: Date;
onTimeoutHandler?: () => void;
socketEvent: string;
}

export default function useTimer({ socket, endsAt, onTimeoutHandler }: UseTimer) {
export default function useSocketTimer({ socket, endsAt, socketEvent }: Props) {
const timerIntervalId = useRef<NodeJS.Timeout | null>(null);
const endTime = useMemo(() => endsAt.getTime(), [endsAt]);
const [isTimeout, setIsTimeout] = useState(false);
const [remainMiliSeconds, setRemainMiliSeconds] = useState<number>(-1);

useEffect(() => {
console.log('타이머 실행');
// 웹 소켓 대신 사용.
mockWebSocket();
if (!socket.hasListeners('ping')) {
socket.on('ping', handlePingMessage);
socket.emit(socketEvent);
if (socket.hasListeners(socketEvent)) {
socket.on(socketEvent, handlePingMessage);
}
}, [socket]);

const handlePingMessage = useCallback((time: Date | string) => {
console.log(time);
if (timerIntervalId.current) clearInterval(timerIntervalId.current);

time = typeof time === 'string' ? new Date(time) : time;
const remainMiliSec = endTime - time.getTime();
setRemainMiliSeconds(remainMiliSec);
timerIntervalId.current = setInterval(() => {
console.log('1초마다 실행');
setRemainMiliSeconds((prev) => prev - 1000);
}, 1000);
}, []);
Expand All @@ -37,20 +40,20 @@ export default function useTimer({ socket, endsAt, onTimeoutHandler }: UseTimer)
// 웹 소켓 연결 후 삭제 예정
const mockWebSocket = useCallback(() => {
const delayFactor = 2000;
const serverTime = new Date();
handlePingMessage(serverTime);
setInterval(() => {
console.log('ping 5초( + 네트워크 지연) 마다 실행');
const serverTime = new Date();
handlePingMessage(serverTime);
}, 5000 + Math.random() * delayFactor);
}, []);

useEffect(() => {
// TODO time 0이면 대시보드로 이동하는 로직
// 해당 PR에서 해결할 문제는 아니라 PASS
// 초기 값인 -1 => 서버에서 시간이 오지 않았다.
if (remainMiliSeconds === -1) return;
if (Math.floor(remainMiliSeconds / 1000) <= 0) {
if (typeof onTimeoutHandler === 'function') onTimeoutHandler();
// 나가는 로직
setIsTimeout(true);
}
}, [remainMiliSeconds]);
return { remainMiliSeconds };
return { remainMiliSeconds, isTimeout };
}
23 changes: 17 additions & 6 deletions frontend/src/pages/ContestPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { css } from '@style/css';

import { useContext, useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';

import { ModalContext } from '@/components/Common/Modal/ModalContext';
import CompetitionHeader from '@/components/Contest/CompetitionHeader';
Expand All @@ -10,8 +11,8 @@ import Editor from '@/components/Editor/Editor';
import ProblemViewer from '@/components/Problem/ProblemViewer';
import { SimulationInputModal } from '@/components/Simulation/SimulationInputModal';
import { SimulationResultList } from '@/components/Simulation/SimulationResultList';
import SocketTimer from '@/components/SocketTimer';
import { SubmissionResult } from '@/components/Submission';
import Timer from '@/components/Timer';
import { SITE } from '@/constants';
import type { SubmissionForm } from '@/hooks/competition';
import { useCompetition } from '@/hooks/competition';
Expand All @@ -22,13 +23,16 @@ import { isNil } from '@/utils/type';

const RUN_SIMULATION = '테스트 실행';
const CANCEL_SIMULATION = '실행 취소';
const DASHBOARD_URL = '/contest/dashboard';

export default function ContestPage() {
const { id } = useParams<{ id: string }>();
const competitionId: number = id ? parseInt(id, 10) : -1;
const [currentProblemIndex, setCurrentProblemIndex] = useState(0);
const modal = useContext(ModalContext);

const navigate = useNavigate();

const simulation = useSimulation();

const { socket, competition, submitSolution, isConnected } = useCompetition(competitionId);
Expand Down Expand Up @@ -82,22 +86,29 @@ export default function ContestPage() {
submitSolution(form);
}


const { endsAt } = competition;

function handleOpenModal() {
modal.open();
}


function handleTimeout() {
navigate(`${DASHBOARD_URL}/${competitionId}`);
}

const problems = problemList.map((problem) => problem.id);


return (
<main className={style}>
<CompetitionHeader crumbs={crumbs} id={competitionId} />
<section>
<section className={rowStyle}>
<span className={problemTitleStyle}>{problem.title}</span>
<Timer socket={socket.current} isConnected={isConnected} endsAt={new Date(endsAt)} />
<SocketTimer
socket={socket.current}
isConnected={isConnected}
endsAt={new Date(endsAt)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 식으로 new Date를 안에 작성해줄 수 있군요:D

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 endsAt가 스웨거에선 string으로 오더라구요. 바뀔수도 있겠지만 string으로 온다고 생각해서 Date로 변경해서 사용하고 있습니다.

onTimeout={handleTimeout}
/>
</section>
<section className={rowListStyle}>
<ContestProblemSelector
Expand Down
Loading