-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #63 from su-its/feat/ranking
Feat/ranking/view
- Loading branch information
Showing
9 changed files
with
385 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Button } from "@chakra-ui/react"; | ||
|
||
interface ButtonProps { | ||
onClick: () => void; | ||
isDisabled: boolean; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const CustomButton = ({ onClick, isDisabled, children }: ButtonProps) => { | ||
return ( | ||
<Button bg={"#2B6CB0"} color={"white"} onClick={onClick} isDisabled={isDisabled}> | ||
{children} | ||
</Button> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from "react"; | ||
import { IconButton } from "@chakra-ui/react"; | ||
|
||
const RefreshIcon = () => ( | ||
<svg | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
stroke-width="2" | ||
stroke-linecap="round" | ||
stroke-linejoin="round" | ||
> | ||
<polyline points="23 4 23 10 17 10"></polyline> | ||
<polyline points="1 20 1 14 7 14"></polyline> | ||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"></path> | ||
<path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"></path> | ||
</svg> | ||
); | ||
|
||
interface RefreshButtonProps { | ||
onClick: () => void; | ||
isDisabled?: boolean; | ||
} | ||
|
||
const RefreshButton: React.FC<RefreshButtonProps> = ({ onClick, isDisabled = false }) => ( | ||
<IconButton | ||
width={"50px"} | ||
right={"250px"} | ||
bg="#2B6CB0" | ||
color="White" | ||
aria-label="refresh" | ||
icon={<RefreshIcon />} | ||
onClick={onClick} | ||
isDisabled={isDisabled} | ||
/> | ||
); | ||
|
||
export default RefreshButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Flex } from "@chakra-ui/react"; | ||
import { CustomButton } from "../atoms/CustomButton"; | ||
|
||
interface PaginationProps { | ||
onPrev: () => void; | ||
onNext: () => void; | ||
isPrevDisabled: boolean; | ||
isNextDisabled: boolean; | ||
} | ||
|
||
export const Pagination = ({ onPrev, onNext, isPrevDisabled, isNextDisabled }: PaginationProps) => ( | ||
<Flex> | ||
<CustomButton onClick={onPrev} isDisabled={isPrevDisabled}> | ||
前のページ | ||
</CustomButton> | ||
<CustomButton onClick={onNext} isDisabled={isNextDisabled}> | ||
次のページ | ||
</CustomButton> | ||
</Flex> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Tbody } from "@chakra-ui/react"; | ||
import RankingTableRow from "./RankingTableRow"; | ||
import { ScoreRanking } from "../organism/RankingTabs"; | ||
|
||
export type RankingTableBodyProps = { | ||
scoreRankings: ScoreRanking[]; | ||
}; | ||
|
||
const RankingTableBody: React.FC<RankingTableBodyProps> = ({ scoreRankings }) => { | ||
return ( | ||
<Tbody> | ||
{scoreRankings.map((scoreRanking) => ( | ||
<RankingTableRow key={String(scoreRanking.rank)} {...scoreRanking} /> | ||
))} | ||
</Tbody> | ||
); | ||
}; | ||
export default RankingTableBody; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Thead, Tr, Th } from "@chakra-ui/react"; | ||
|
||
const RankingTableHead: React.FC = () => { | ||
return ( | ||
<Thead> | ||
<Tr bg={"midnightblue"}> | ||
<Th color={"silver"} width={"128px"} textAlign={"center"}> | ||
順位 | ||
</Th> | ||
<Th color={"silver"} width={"256px"} textAlign={"center"}> | ||
学籍番号 | ||
</Th> | ||
<Th color={"silver"} width={"320px"} textAlign={"center"}> | ||
入力文字数 | ||
</Th> | ||
<Th color={"silver"} width={"256px"} textAlign={"center"}> | ||
正打率 | ||
</Th> | ||
<Th color={"silver"} width={"320px"} textAlign={"center"}> | ||
記録日 | ||
</Th> | ||
</Tr> | ||
</Thead> | ||
); | ||
}; | ||
|
||
export default RankingTableHead; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Td, Tr } from "@chakra-ui/react"; | ||
import { ScoreRanking } from "../organism/RankingTabs"; | ||
|
||
const RankingTableRow: React.FC<ScoreRanking> = (scoreRanking) => { | ||
return ( | ||
<Tr key={String(scoreRanking.rank)} _even={{ bg: "midnightblue" }} _odd={{ bg: "#192f70" }} color={"silver"}> | ||
<Td width={"128px"} textAlign={"center"}> | ||
{String(scoreRanking.rank)} | ||
</Td> | ||
<Td width={"256px"} textAlign={"center"}> | ||
{scoreRanking.user.studentNumber} | ||
</Td> | ||
<Td width={"320px"} textAlign={"center"}> | ||
{String(scoreRanking.keystrokes)} | ||
</Td> | ||
<Td width={"256px"} textAlign={"center"}> | ||
{String(scoreRanking.accuracy)} | ||
</Td> | ||
<Td width={"320px"} textAlign={"center"}> | ||
{scoreRanking.createdAt.toLocaleDateString("ja-JP")} | ||
</Td> | ||
</Tr> | ||
); | ||
}; | ||
|
||
export default RankingTableRow; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Table, TableContainer, Container } from "@chakra-ui/react"; | ||
import RankingTableHead from "../molecules/RankingTableHead"; | ||
import RankingTableBody, { RankingTableBodyProps } from "../molecules/RankingTableBody"; | ||
|
||
const RankingTable: React.FC<RankingTableBodyProps> = ({ scoreRankings }) => { | ||
return ( | ||
<TableContainer> | ||
<Container maxW={"container.xl"}> | ||
<Table rounded="base" shadow="md"> | ||
<RankingTableHead /> | ||
<RankingTableBody scoreRankings={scoreRankings} /> | ||
</Table> | ||
</Container> | ||
</TableContainer> | ||
); | ||
}; | ||
|
||
export default RankingTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
"use client"; | ||
import { Tabs, TabList, TabPanels, Tab, TabPanel, Flex, Center, Box, Grid } from "@chakra-ui/react"; | ||
import RankingTable from "../organism/RankingTable"; | ||
import { Pagination } from "../molecules/Pagination"; | ||
//import { CustomButton } from "../atoms/CustomButton"; | ||
import RefreshButton from "../atoms/RefreshButton"; | ||
import { useEffect, useState } from "react"; | ||
// import { client } from "@/libs/api"; | ||
|
||
const RankingTabs = () => { | ||
const [scoreRankings, setScoreRankings] = useState<ScoreRanking[]>(demoAccuracyRankings); | ||
const [rankingStartFrom, setRankingStartFrom] = useState(0); | ||
const [sortBy, setSortBy] = useState<"accuracy" | "keystrokes">("accuracy"); | ||
const LIMIT = 10; | ||
|
||
const MAXIMUM = 100; // TODO: MAXIMUMをAPIから取得する | ||
|
||
useEffect(() => { | ||
// ページが読み込まれたときにデータを取得 | ||
fetchData(); | ||
}); | ||
|
||
const handleTabChange = (index: number) => { | ||
if (index === 0) { | ||
setSortBy("accuracy"); | ||
} else if (index === 1) { | ||
setSortBy("keystrokes"); | ||
} | ||
|
||
fetchData; | ||
}; | ||
|
||
// 演算子を引数にとる、ボタンを押したときのハンドラ関数 | ||
const handlePaginationClick = (direction: "next" | "prev") => { | ||
const newStartFrom = | ||
direction === "prev" ? Math.max(rankingStartFrom - LIMIT, 0) : Math.min(rankingStartFrom + LIMIT, MAXIMUM); | ||
setRankingStartFrom(newStartFrom); | ||
|
||
fetchData; | ||
}; | ||
|
||
const fetchData = async () => { | ||
// TODO: APIを使ってデータをフェッチ | ||
if (sortBy == "accuracy") { | ||
setScoreRankings(demoAccuracyRankings); | ||
} else if (sortBy == "keystrokes") { | ||
setScoreRankings(demoKeyStrokeRankings); | ||
} | ||
}; | ||
|
||
return ( | ||
<Tabs onChange={handleTabChange}> | ||
<Flex justifyContent={"center"}> | ||
<Grid templateColumns={"repeat(3, 1fr)"} gap={"300px"}> | ||
<Box opacity={"0"}>{/* 幅を揃えるためだけの要素,視覚的な意味はなし */}</Box> | ||
<TabList color={"white"}> | ||
<Tab _selected={{ color: "#00ace6" }}>正打率</Tab> | ||
<Tab _selected={{ color: "#00ace6" }}>入力文字数</Tab> | ||
</TabList> | ||
<RefreshButton onClick={() => fetchData()} isDisabled={false} /> | ||
</Grid> | ||
</Flex> | ||
|
||
<TabPanels> | ||
<TabPanel> | ||
<RankingTable scoreRankings={scoreRankings} /> | ||
</TabPanel> | ||
<TabPanel> | ||
<RankingTable scoreRankings={scoreRankings} /> | ||
</TabPanel> | ||
</TabPanels> | ||
<Center> | ||
<Pagination | ||
onPrev={() => handlePaginationClick("prev")} | ||
onNext={() => handlePaginationClick("next")} | ||
isPrevDisabled={rankingStartFrom <= 0} | ||
isNextDisabled={rankingStartFrom + LIMIT >= MAXIMUM} | ||
/> | ||
</Center> | ||
</Tabs> | ||
); | ||
}; | ||
export default RankingTabs; | ||
|
||
export interface User { | ||
id: string; | ||
studentNumber: string; | ||
handleName: string; | ||
} | ||
|
||
export interface ScoreRanking { | ||
rank: Number; | ||
user: User; | ||
keystrokes: Number; | ||
accuracy: Number; | ||
createdAt: Date; | ||
} | ||
|
||
const demoUsers: User[] = [ | ||
{ | ||
id: "1", | ||
studentNumber: "70310000", | ||
handleName: "X", | ||
}, | ||
{ | ||
id: "2", | ||
studentNumber: "70310000", | ||
handleName: "Y", | ||
}, | ||
{ | ||
id: "3", | ||
studentNumber: "70310000", | ||
handleName: "Z", | ||
}, | ||
{ | ||
id: "4", | ||
studentNumber: "70310000", | ||
handleName: "A", | ||
}, | ||
{ | ||
id: "5", | ||
studentNumber: "70310000", | ||
handleName: "B", | ||
}, | ||
]; | ||
|
||
const demoKeyStrokeRankings: ScoreRanking[] = [ | ||
{ | ||
rank: 1, | ||
user: { | ||
id: "1", | ||
studentNumber: "70310000", | ||
handleName: "X", | ||
}, | ||
keystrokes: 100, | ||
accuracy: 100, | ||
createdAt: new Date(), | ||
}, | ||
{ | ||
rank: 2, | ||
user: { | ||
id: "2", | ||
studentNumber: "70310000", | ||
handleName: "Y", | ||
}, | ||
keystrokes: 90, | ||
accuracy: 90, | ||
createdAt: new Date(), | ||
}, | ||
{ | ||
rank: 3, | ||
user: { | ||
id: "3", | ||
studentNumber: "70310000", | ||
handleName: "Z", | ||
}, | ||
keystrokes: 80, | ||
accuracy: 80, | ||
createdAt: new Date(), | ||
}, | ||
]; | ||
|
||
const demoAccuracyRankings: ScoreRanking[] = [ | ||
{ | ||
rank: 1, | ||
user: { | ||
id: "1", | ||
studentNumber: "70310000", | ||
handleName: "X", | ||
}, | ||
keystrokes: 100, | ||
accuracy: 100, | ||
createdAt: new Date(), | ||
}, | ||
{ | ||
rank: 2, | ||
user: { | ||
id: "2", | ||
studentNumber: "70310000", | ||
handleName: "Y", | ||
}, | ||
keystrokes: 90, | ||
accuracy: 90, | ||
createdAt: new Date(), | ||
}, | ||
{ | ||
rank: 3, | ||
user: { | ||
id: "3", | ||
studentNumber: "70310000", | ||
handleName: "Z", | ||
}, | ||
keystrokes: 80, | ||
accuracy: 80, | ||
createdAt: new Date(), | ||
}, | ||
{ | ||
rank: 4, | ||
user: { | ||
id: "4", | ||
studentNumber: "70310000", | ||
handleName: "A", | ||
}, | ||
keystrokes: 70, | ||
accuracy: 70, | ||
createdAt: new Date(), | ||
}, | ||
{ | ||
rank: 5, | ||
user: { | ||
id: "5", | ||
studentNumber: "70310000", | ||
handleName: "B", | ||
}, | ||
keystrokes: 60, | ||
accuracy: 60, | ||
createdAt: new Date(), | ||
}, | ||
]; |
Oops, something went wrong.