-
Notifications
You must be signed in to change notification settings - Fork 0
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
[#138] 대회 세부정보 페이지 생성 #149
The head ref may contain hidden characters: "138-\uB300\uD68C-\uC138\uBD80\uC815\uBCF4-\uD398\uC774\uC9C0-\uC0DD\uC131"
Changes from 13 commits
de2b449
9dcc76b
d53e611
09e7a43
f482971
df0c94d
826df7b
fd3cbfc
16e8c11
1e0f8b7
f8439ef
15235b8
3c7f46f
7dc0fb3
a9243a3
82231ee
80981df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { css } from '@style/css'; | ||
|
||
import { CompetitionInfo } from '@/apis/competitions'; | ||
|
||
import CompetitionDetailInfo from './CompetitionDetailInfo'; | ||
import ProblemList from './ProblemList'; | ||
|
||
interface Props { | ||
competitionId: number; | ||
competition: CompetitionInfo; | ||
competitionSchedule: string; | ||
} | ||
|
||
const AFTER_COMPETITION_TEXT = ' 종료'; | ||
|
||
export default function AfterCompetition({ | ||
competitionId, | ||
competition, | ||
competitionSchedule, | ||
}: Props) { | ||
return ( | ||
<div className={containerStyle}> | ||
<CompetitionDetailInfo | ||
competition={competition} | ||
text={AFTER_COMPETITION_TEXT} | ||
competitionSchedule={competitionSchedule} | ||
/> | ||
<ProblemList competitionId={competitionId} /> | ||
</div> | ||
); | ||
} | ||
|
||
const containerStyle = css({ | ||
justifyContent: 'space-between', | ||
alignItems: 'center', | ||
padding: '16px', | ||
border: '1px solid #ccc', | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { css } from '@style/css'; | ||
|
||
import { CompetitionInfo } from '@/apis/competitions'; | ||
|
||
import JoinCompetitionButton from '../Main/Buttons/JoinCompetitionButton'; | ||
import EnterCompetitionButton from './Buttons/EnterCompetitionButton'; | ||
import CompetitionDetailInfo from './CompetitionDetailInfo'; | ||
|
||
interface Props { | ||
competitionId: number; | ||
competition: CompetitionInfo; | ||
startsAt: Date; | ||
endsAt: Date; | ||
competitionSchedule: string; | ||
} | ||
|
||
export default function BeforeCompetition({ | ||
competitionId, | ||
competition, | ||
startsAt, | ||
endsAt, | ||
competitionSchedule, | ||
}: Props) { | ||
const BEFORE_COMPETITION_TEXT = ` 시작 전`; | ||
|
||
return ( | ||
<div className={containerStyle}> | ||
<CompetitionDetailInfo | ||
competition={competition} | ||
text={BEFORE_COMPETITION_TEXT} | ||
competitionSchedule={competitionSchedule} | ||
/> | ||
<div className={buttonContainerStyle}> | ||
<JoinCompetitionButton id={competitionId} /> | ||
<EnterCompetitionButton id={competitionId} startsAt={startsAt} endsAt={endsAt} /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
const containerStyle = css({ | ||
justifyContent: 'space-between', | ||
alignItems: 'center', | ||
padding: '16px', | ||
border: '1px solid #ccc', | ||
}); | ||
|
||
const buttonContainerStyle = css({ | ||
display: 'flex', | ||
gap: '16px', | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
import useAuth from '@/hooks/login/useAuth'; | ||
|
||
interface Props { | ||
id: number; | ||
startsAt: Date; | ||
endsAt: Date; | ||
} | ||
|
||
export default function EnterCompetitionButton({ id, startsAt, endsAt }: Props) { | ||
const competitionLink = `/contest/${id}`; | ||
const { isLoggedin } = useAuth(); | ||
const navigate = useNavigate(); | ||
|
||
const handleNavigate = () => { | ||
const currentTime = new Date(); | ||
|
||
if (!isLoggedin) { | ||
alert('로그인이 필요합니다.'); | ||
navigate('/login'); | ||
} else if (currentTime < startsAt) { | ||
alert('아직 대회가 시작되지 않았습니다. 다시 시도해주세요'); | ||
window.location.reload(); | ||
} else if (currentTime >= endsAt) { | ||
alert('해당 대회는 종료되었습니다.'); | ||
window.location.reload(); | ||
} else { | ||
navigate(competitionLink); | ||
} | ||
}; | ||
|
||
return <button onClick={handleNavigate}>대회 입장</button>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// CompetitionDetailContent.js | ||
import { CompetitionInfo } from '@/apis/competitions'; | ||
import AfterCompetition from '@/components/CompetitionDetail/AfterCompetition'; | ||
import BeforeCompetition from '@/components/CompetitionDetail/BeforeCompetition'; | ||
import DuringCompetition from '@/components/CompetitionDetail/DuringCompetition'; | ||
|
||
interface Props { | ||
competitionId: number; | ||
competition: CompetitionInfo; | ||
startsAt: Date; | ||
endsAt: Date; | ||
competitionSchedule: string; | ||
} | ||
|
||
export function CompetitionDetailContent({ | ||
competitionId, | ||
competition, | ||
startsAt, | ||
endsAt, | ||
competitionSchedule, | ||
}: Props) { | ||
const currentDate = new Date(); | ||
|
||
if (currentDate < startsAt) { | ||
return ( | ||
<BeforeCompetition | ||
{...{ competitionId, competition, startsAt, endsAt, competitionSchedule }} | ||
/> | ||
); | ||
} else if (currentDate < endsAt) { | ||
return ( | ||
<DuringCompetition | ||
{...{ competitionId, competition, startsAt, endsAt, competitionSchedule }} | ||
/> | ||
); | ||
} else { | ||
return <AfterCompetition {...{ competitionId, competition, competitionSchedule }} />; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { css } from '@style/css'; | ||
|
||
import { CompetitionInfo } from '@/apis/competitions'; | ||
|
||
interface Props { | ||
competition: CompetitionInfo; | ||
text: string; | ||
competitionSchedule: string; | ||
} | ||
|
||
export default function CompetitionDetailInfo({ competition, text, competitionSchedule }: Props) { | ||
return ( | ||
<div className={infoContainerStyle}> | ||
<div> | ||
<span className={competitionNameStyle}>{competition.name}</span> | ||
<span className={statusTextStyle}>{text}</span> | ||
</div> | ||
<span className={statusTextStyle}>{competitionSchedule}</span> | ||
<div className={additionalTextStyle}>{competition.detail}</div> | ||
</div> | ||
); | ||
} | ||
|
||
const infoContainerStyle = css({ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
}); | ||
|
||
const competitionNameStyle = css({ | ||
fontSize: '18px', | ||
fontWeight: 'bold', | ||
color: 'black', | ||
marginBottom: '8px', | ||
}); | ||
|
||
const statusTextStyle = css({ | ||
fontSize: '12px', | ||
color: 'gray', | ||
marginBottom: '8px', | ||
}); | ||
|
||
const additionalTextStyle = css({ | ||
fontSize: '14px', | ||
color: 'black', | ||
marginBottom: '8px', | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { css } from '@style/css'; | ||
|
||
import { CompetitionInfo } from '@/apis/competitions'; | ||
|
||
import EnterCompetitionButton from './Buttons/EnterCompetitionButton'; | ||
import CompetitionDetailInfo from './CompetitionDetailInfo'; | ||
|
||
interface Props { | ||
competitionId: number; | ||
competition: CompetitionInfo; | ||
startsAt: Date; | ||
endsAt: Date; | ||
competitionSchedule: string; | ||
} | ||
|
||
const DURING_COMPETITION_TEXT = ' 진행중'; | ||
|
||
export default function DuringCompetition({ | ||
competitionId, | ||
competition, | ||
startsAt, | ||
endsAt, | ||
competitionSchedule, | ||
}: Props) { | ||
return ( | ||
<div className={containerStyle}> | ||
<CompetitionDetailInfo | ||
competition={competition} | ||
text={DURING_COMPETITION_TEXT} | ||
competitionSchedule={competitionSchedule} | ||
/> | ||
<EnterCompetitionButton id={competitionId} startsAt={startsAt} endsAt={endsAt} /> | ||
</div> | ||
); | ||
} | ||
|
||
const containerStyle = css({ | ||
justifyContent: 'space-between', | ||
alignItems: 'center', | ||
padding: '16px', | ||
border: '1px solid #ccc', | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Link } from 'react-router-dom'; | ||
|
||
import { useCompetitionProblemList } from '@/hooks/problem'; | ||
|
||
interface Props { | ||
competitionId: number; | ||
} | ||
|
||
export default function ProblemList({ competitionId }: Props) { | ||
const { problemList } = useCompetitionProblemList(competitionId); | ||
|
||
return ( | ||
<div> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th>번호</th> | ||
<th>문제 제목</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{problemList.map((problem) => ( | ||
<tr key={problem.id}> | ||
<td>{problem.id}</td> | ||
<td> | ||
<Link to={`/problem/${problem.id}`}>{problem.title}</Link> | ||
</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { useParams } from 'react-router-dom'; | ||
|
||
import { CompetitionDetailContent } from '@/components/CompetitionDetail/CompetitionDetailContent'; | ||
import Header from '@/components/Header'; | ||
import { useCompetition } from '@/hooks/competition'; | ||
import { formatKoreanDateTime } from '@/utils/date'; | ||
|
||
export default function CompetitionDetailPage() { | ||
const { id } = useParams<{ id: string }>(); | ||
const competitionId: number = id ? parseInt(id, 10) : -1; | ||
const { competition } = useCompetition(competitionId); | ||
// 대회 상태에 따른 페이지를 구성하기 위해 현재 날짜, 시작 시간, 종료 시간을 가져옴 | ||
const startsAt = new Date(competition?.startsAt || ''); | ||
const endsAt = new Date(competition?.endsAt || ''); | ||
const formattedStartsAt = formatKoreanDateTime(startsAt); | ||
const formattedEndsAt = formatKoreanDateTime(endsAt); | ||
|
||
const competitionSchedule = `시작: ${formattedStartsAt} 종료: ${formattedEndsAt}`; | ||
|
||
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 취향이긴 한데, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확실히 그렇네요, 감사합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우찬님 변경하시고 어떻게 나오는지 스샷 하나만 첨부해주세요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return ( | ||
<div> | ||
<Header /> | ||
<CompetitionDetailContent | ||
competitionId={competitionId} | ||
competition={competition} | ||
startsAt={startsAt} | ||
endsAt={endsAt} | ||
competitionSchedule={competitionSchedule} | ||
/> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,3 +31,14 @@ export const formatMilliSecond = (ms: number, form: string) => { | |
} | ||
return ''; | ||
}; | ||
|
||
export const formatKoreanDateTime = (date: Date) => { | ||
return date.toLocaleString('ko-KR', { | ||
year: 'numeric', | ||
month: 'numeric', | ||
day: 'numeric', | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
hour12: false, | ||
}); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 약간은 개인적인 욕심인데, formatDate를 확장해서 'yyyy. mm. dd. hh:mm' 를 받으면 이 형식으로 반환하도록 할 수 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵, 알겠습니다!:D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
생각 못해본 방법인데 좋네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 이번에 하다가 써봤는데 되더라고요:D