Skip to content

Commit

Permalink
メインページに終了後・開催中・予定されたコンテストおよびユーザーのランキングの表示をした (#261)
Browse files Browse the repository at this point in the history
* 予定されたコンテストのAPI作成 close #1

* 開催中のコンテスト一覧実装 close #1

* 終了したコンテスト一覧を取得できるようにした close #1

* メインページにランキング追加 close #1

* LatestContestsInfo -> ContestInfoList
  • Loading branch information
stmtk1 committed Dec 8, 2021
1 parent 0ecfff3 commit d4fdab5
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 26,522 deletions.
26,708 changes: 244 additions & 26,464 deletions frontend/package-lock.json

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions frontend/src/components/ContestTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { VFC } from 'react';
import Table from 'react-bootstrap/Table';
import { Link } from 'react-router-dom';
import { ContestInfo } from '../types';

type Props = { contests: ContestInfo[] | null };

export const ContestTable: VFC<Props> = ({ contests }) => {
return (
<>
<Table bordered hover size="sm" striped>
<thead>
<tr>
<th className="text-center">開始時刻</th>
<th className="text-center">コンテスト名</th>
<th className="text-center">種類</th>
<th className="text-center">時間</th>
<th className="text-center">Rated対象</th>
</tr>
</thead>
<tbody>
{contests?.map((contest) => (
<tr key={contest.id}>
<td className="text-center">{contest.startTimeAMPM}</td>
<td>
<Link to={`/contest/${contest.id}`}>{contest.name}</Link>
</td>
<td className="text-center">{contest.contestType}</td>
<td className="text-center">
{Math.floor(
(contest.unixEndTime - contest.unixStartTime) / (60 * 1000)
)}
</td>
<td className="text-center">
{contest.ratedBound > 0 ? `~ ${contest.ratedBound - 1}` : '-'}
</td>
</tr>
))}
</tbody>
</Table>
</>
);
};
20 changes: 17 additions & 3 deletions frontend/src/functions/HttpRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
AccountInfo,
AccountRankingInfo,
ContestInfo,
LatestContestsInfo,
ContestsInfoList,
ProblemInfo,
ContestStandingsInfo,
SubmissionInfo,
Expand Down Expand Up @@ -126,8 +126,22 @@ export async function getAccountInformation(
/**
* @param page
*/
export function getLatestContests(page: number): Promise<LatestContestsInfo> {
return httpRequest<LatestContestsInfo>('/api/contests/latest', 'GET', {
export function getLatestContests(page: number): Promise<ContestsInfoList> {
return httpRequest<ContestsInfoList>('/api/contests/latest', 'GET', {
contest_page: page,
});
}

export function getUpcomingContests(): Promise<ContestsInfoList> {
return httpRequest<ContestsInfoList>('/api/contests/upcoming', 'GET');
}

export function getActiveContests(): Promise<ContestsInfoList> {
return httpRequest<ContestsInfoList>('/api/contests/active', 'GET');
}

export function getPastContests(page: number): Promise<ContestsInfoList> {
return httpRequest<ContestsInfoList>('/api/contests/past', 'GET', {
contest_page: page,
});
}
Expand Down
100 changes: 50 additions & 50 deletions frontend/src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,78 @@
import { VFC, useEffect, useState, useCallback } from 'react';
import Button from 'react-bootstrap/Button';
import Table from 'react-bootstrap/Table';
import { Link } from 'react-router-dom';
import { ContestTable } from '../components/ContestTable';
import { PagingElement } from '../components/PagingElement';
import { getLatestContests } from '../functions/HttpRequest';
import {
getUpcomingContests,
getActiveContests,
getPastContests,
} from '../functions/HttpRequest';
import { ContestInfo } from '../types';
import Ranking from './RankingPage';

// URL: /

const CONTEST_IN_ONE_PAGE = 10;

const ContestList: VFC = () => {
const [contests, setContests] = useState<ContestInfo[] | null>(null);
const [pageNum, setPageNum] = useState<number>(0);
const [currentPage, setCurrentPage] = useState(0);
const ContestList = () => {
const [upcomingContests, setUpcomingContests] = useState<
ContestInfo[] | null
>(null);
const [activeContests, setActiveContests] = useState<ContestInfo[] | null>(
null
);
const [pastContests, setPastContests] = useState<ContestInfo[] | null>(null);
const [contestPageNum, setContestPageNum] = useState<number>(0);
const [contestCurrentPage, setContestCurrentPage] = useState(0);

const updatePage = useCallback(
(newPage: number) => {
getLatestContests(newPage).then((latestContestsInfo) => {
setContests(latestContestsInfo.contests);
setCurrentPage(newPage);
const updateContestPage = useCallback(
(page) => {
getPastContests(page).then((pastContestsInfo) => {
setPastContests(pastContestsInfo.contests);
setContestCurrentPage(page);
});
},
[pageNum]
[contestPageNum]
);

useEffect(() => {
getLatestContests(0).then((latestContestsInfo) => {
setPageNum(
Math.ceil(latestContestsInfo.allContestNum / CONTEST_IN_ONE_PAGE)
getUpcomingContests().then((upcomingContestsInfo) => {
setUpcomingContests(upcomingContestsInfo.contests);
});

getActiveContests().then((activeContestsInfo) => {
setActiveContests(activeContestsInfo.contests);
});

getPastContests(0).then((pastContestsInfo) => {
setContestPageNum(
Math.ceil(pastContestsInfo.allContestNum / CONTEST_IN_ONE_PAGE)
);
setContests(latestContestsInfo.contests);
setPastContests(pastContestsInfo.contests);
});
}, []);

return (
<>
<Table bordered hover size="sm" striped>
<thead>
<tr>
<th className="text-center">開始時刻</th>
<th className="text-center">コンテスト名</th>
<th className="text-center">種類</th>
<th className="text-center">時間</th>
<th className="text-center">Rated対象</th>
</tr>
</thead>
<tbody>
{contests?.map((contest) => (
<tr key={contest.id}>
<td className="text-center">{contest.startTimeAMPM}</td>
<td>
<Link to={`/contest/${contest.id}`}>{contest.name}</Link>
</td>
<td className="text-center">{contest.contestType}</td>
<td className="text-center">
{Math.floor(
(contest.unixEndTime - contest.unixStartTime) / (60 * 1000)
)}
</td>
<td className="text-center">
{contest.ratedBound > 0 ? `~ ${contest.ratedBound - 1}` : '-'}
</td>
</tr>
))}
</tbody>
</Table>
<h2>開催中のコンテスト</h2>
{activeContests && activeContests.length > 0 ? (
<ContestTable contests={activeContests} />
) : (
<div>現在開催中のコンテストはありません</div>
)}
<h2>予定されたコンテスト</h2>
<ContestTable contests={upcomingContests} />
<h2>終了したコンテスト</h2>
<ContestTable contests={pastContests} />
<PagingElement
currentPage={currentPage}
onChange={updatePage}
currentPage={contestCurrentPage}
onChange={updateContestPage}
savePaging={true}
totalPages={pageNum}
totalPages={contestPageNum}
/>
<h2>ランキング</h2>
<Ranking />
<Link to={'/ranking'}>
<Button variant={'primary'}>順位表へ</Button>
</Link>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export interface ContestInfo {
contestCreators: ContestCreator[];
}

export interface LatestContestsInfo {
export interface ContestsInfoList {
contests: ContestInfo[];
allContestNum: number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,25 @@ class ContestController(
}

@GetMapping("api/contests/latest")
fun getLatestContestsInfoResponse(@RequestParam("contest_page") contestPage: Int): LatestContestsInfo {
fun getLatestContestsInfoResponse(@RequestParam("contest_page") contestPage: Int): ContestInfoList {
return contestService.getLatestContestsInfo(contestPage)
}

@GetMapping("api/contests/upcoming")
fun getUpcomingContestsInfoResponse(): ContestInfoList {
return contestService.getUpcomingContestsInfo()
}

@GetMapping("api/contests/active")
fun getActiveContestsInfoResponse(): ContestInfoList {
return contestService.getActiveContestsInfo()
}

@GetMapping("api/contests/past")
fun getPastContestsInfoResponse(@RequestParam("contest_page") contestPage: Int): ContestInfoList {
return contestService.getPastContestsInfo(contestPage)
}

@PostMapping("api/contests", headers = ["Content-Type=application/json"])
fun addContestResponse(
@RequestBody requestContest: RequestContestInfoForUpdate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ class ContestRepository(val jdbcTemplate: JdbcTemplate) {
FROM contestInfo ORDER BY startTime desc LIMIT ? OFFSET ?""",
rowMapperForContestInfo, LATEST_CONTEST_PAGE_SIZE, page * LATEST_CONTEST_PAGE_SIZE)
}
fun findUpcomingContest(): List<ContestInfo> {
return jdbcTemplate.query("""
SELECT id, name, statement, startTime, endTime, contestType, ratedBound , penalty, ratingCalculated
FROM contestInfo where startTime > now() ORDER BY startTime desc""",
rowMapperForContestInfo)
}
fun findActiveContest(): List<ContestInfo> {
return jdbcTemplate.query("""
SELECT id, name, statement, startTime, endTime, contestType, ratedBound , penalty, ratingCalculated
FROM contestInfo where startTime < now() AND now() < endTime ORDER BY startTime desc""",
rowMapperForContestInfo)
}
fun findPastContest(page: Int): List<ContestInfo> {
return jdbcTemplate.query("""
SELECT id, name, statement, startTime, endTime, contestType, ratedBound , penalty, ratingCalculated
FROM contestInfo where now() > endTime ORDER BY startTime desc LIMIT ? OFFSET ?""",
rowMapperForContestInfo, LATEST_CONTEST_PAGE_SIZE, page * LATEST_CONTEST_PAGE_SIZE)
}
fun findContestCreators(contestId: String): List<ContestCreator> =
jdbcTemplate.query("""
SELECT accountName, contestId, position FROM contestCreator WHERE contestId = ?
Expand All @@ -66,6 +84,9 @@ class ContestRepository(val jdbcTemplate: JdbcTemplate) {
fun findAllContestNum(): Int =
jdbcTemplate.queryForObject("""SELECT count(*) from contestInfo""", Int::class.java)!!

fun findPastContestNum(): Int =
jdbcTemplate.queryForObject("""SELECT count(*) from contestInfo where now() > endTime""", Int::class.java)!!

fun addContest(contest: ContestInfo) {
jdbcTemplate.update("""
INSERT INTO contestInfo(id, name, statement, startTime, endTime, contestType, ratedBound, penalty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,30 @@ class ContestService(
fun getContestInfoByContestId(id: String): ContestInfo? =
contestRepository.findByContestId(id)

fun getLatestContestsInfo(page: Int): LatestContestsInfo {
fun getLatestContestsInfo(page: Int): ContestInfoList {
val contests = contestRepository.findLatestContest(page)
.map { ResponseContestInfoInterface.build(it) }
val allContestNum = contestRepository.findAllContestNum()
return LatestContestsInfo(contests, allContestNum)
return ContestInfoList(contests, allContestNum)
}

fun getUpcomingContestsInfo(): ContestInfoList {
val contests = contestRepository.findUpcomingContest()
.map { ResponseContestInfoInterface.build(it) }
return ContestInfoList(contests, contests.size)
}

fun getActiveContestsInfo(): ContestInfoList {
val contests = contestRepository.findActiveContest()
.map { ResponseContestInfoInterface.build(it) }
return ContestInfoList(contests, contests.size)
}

fun getPastContestsInfo(contestPage: Int): ContestInfoList {
val contests = contestRepository.findPastContest(contestPage)
.map { ResponseContestInfoInterface.build(it) }
val allContestNum = contestRepository.findPastContestNum()
return ContestInfoList(contests, allContestNum)
}

fun addContest(requestContest: RequestContestInfoForUpdate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ data class ResponseContestInfo(
override val endTimeAMPM: String
) : ResponseContestInfoInterface

data class LatestContestsInfo(
data class ContestInfoList(
val contests: List<ResponseContestInfo>,
val allContestNum: Int
)

0 comments on commit d4fdab5

Please sign in to comment.