Skip to content

Commit

Permalink
Merge pull request #63 from su-its/feat/ranking
Browse files Browse the repository at this point in the history
Feat/ranking/view
  • Loading branch information
araaki12345 authored Apr 3, 2024
2 parents d328038 + e4fa0a4 commit 22822e7
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 9 deletions.
15 changes: 15 additions & 0 deletions typing-app/src/components/atoms/CustomButton.tsx
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>
);
};
40 changes: 40 additions & 0 deletions typing-app/src/components/atoms/RefreshButton.tsx
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;
20 changes: 20 additions & 0 deletions typing-app/src/components/molecules/Pagination.tsx
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>
);
18 changes: 18 additions & 0 deletions typing-app/src/components/molecules/RankingTableBody.tsx
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;
27 changes: 27 additions & 0 deletions typing-app/src/components/molecules/RankingTableHead.tsx
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;
26 changes: 26 additions & 0 deletions typing-app/src/components/molecules/RankingTableRow.tsx
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;
18 changes: 18 additions & 0 deletions typing-app/src/components/organism/RankingTable.tsx
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;
219 changes: 219 additions & 0 deletions typing-app/src/components/organism/RankingTabs.tsx
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(),
},
];
Loading

0 comments on commit 22822e7

Please sign in to comment.