diff --git a/package.json b/package.json index 887b75d..c7fbcfa 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,14 @@ "private": true, "type": "module", "dependencies": { - "@emotion/react": "^11.11.1", + "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^4.5.8", "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.12.3", + "@mui/material": "^5.15.11", "@mui/styled-engine-sc": "^5.14.7", "@mui/system": "^5.14.7", + "@mui/x-data-grid": "^6.19.5", "@react-oauth/google": "^0.11.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b727517..52600ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: dependencies: '@emotion/react': - specifier: ^11.11.1 + specifier: ^11.11.4 version: 11.11.4(react@18.2.0) '@emotion/styled': specifier: ^11.11.0 @@ -18,7 +18,7 @@ dependencies: specifier: ^5.11.16 version: 5.15.11(@mui/material@5.15.11)(react@18.2.0) '@mui/material': - specifier: ^5.12.3 + specifier: ^5.15.11 version: 5.15.11(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0) '@mui/styled-engine-sc': specifier: ^5.14.7 @@ -26,6 +26,9 @@ dependencies: '@mui/system': specifier: ^5.14.7 version: 5.15.11(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/x-data-grid': + specifier: ^6.19.5 + version: 6.19.5(@mui/material@5.15.11)(@mui/system@5.15.11)(react-dom@18.2.0)(react@18.2.0) '@react-oauth/google': specifier: ^0.11.0 version: 0.11.1(react-dom@18.2.0)(react@18.2.0) @@ -3180,6 +3183,28 @@ packages: react-is: 18.2.0 dev: false + /@mui/x-data-grid@6.19.5(@mui/material@5.15.11)(@mui/system@5.15.11)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-jV1ZqwyFslKqFScSn4t+xc/tNxLHOeJjz3HoeK+Wdf5t3bPM69pg/jLeg8TmOkAUY62JmQKCLVmcGWiR3AqUKQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^5.4.1 + '@mui/system': ^5.4.1 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.24.0 + '@mui/material': 5.15.11(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.15.11(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/utils': 5.15.11(react@18.2.0) + clsx: 2.1.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + reselect: 4.1.8 + transitivePeerDependencies: + - '@types/react' + dev: false + /@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -10951,6 +10976,10 @@ packages: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: false + /reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + dev: false + /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} diff --git a/src/components/Rank/RankGridView.js b/src/components/Rank/RankGridView.js new file mode 100644 index 0000000..0ca32c6 --- /dev/null +++ b/src/components/Rank/RankGridView.js @@ -0,0 +1,82 @@ +import { Box, styled, Grid, ImageList } from "@mui/material"; +import HoverBox from "./HoverBox"; +import Item from "./Item"; +import { motion } from "framer-motion"; +import { useRecoilValue } from "recoil"; +import { useSetRecoilState } from "recoil"; +import { authorityState, isShowFullImageState } from "../../store/atom"; +import { AnimatePresence } from "framer-motion"; + +const StyledItemSize = styled(Box)({ + position: "relative", + width: "300px", + height: "300px", +}); + +export default function RankGridView({ + setFullImageUrl, + setItemsHover, + teams, + itemsHover, +}) { + const setIsShowFullImage = useSetRecoilState(isShowFullImageState); + const myAuthority = useRecoilValue(authorityState); + + const handleFullImageOpen = async (imageUrl) => { + if (myAuthority !== "ADMIN") return; + if (!imageUrl) { + setFullImageUrl("/img/mainImg2.png"); + } else setFullImageUrl(imageUrl); + setIsShowFullImage(true); + }; + + const handleMouseOver = (index) => { + setItemsHover((prev) => [ + ...prev?.slice(0, index), + true, + ...prev?.slice(index + 1), + ]); + }; + const handleMouseOut = (index) => { + setItemsHover((prev) => [ + ...prev?.slice(0, index), + false, + ...prev?.slice(index + 1), + ]); + }; + + return ( + + {teams.map((item, index) => ( + handleFullImageOpen(item?.thumbnail)} + > + handleMouseOver(index)} + onMouseOut={() => handleMouseOut(index)} + > + + {itemsHover[index] && ( + + )} + + + + + + ))} + + ); +} diff --git a/src/components/Rank/RankListView.js b/src/components/Rank/RankListView.js new file mode 100644 index 0000000..2b28b25 --- /dev/null +++ b/src/components/Rank/RankListView.js @@ -0,0 +1,58 @@ +import { DataGrid } from "@mui/x-data-grid"; +const columns = [ + { field: "id", headerName: "순위", width: 100 }, + { field: "groupNm", headerName: "그룹", width: 100 }, + { + field: "reportNm", + headerName: "제출한 레포트 수", + type: "number", + width: 300, + }, + { + field: "totalMins", + headerName: "총 시간(분)", + type: "number", + width: 150, + }, + { + field: "members", + headerName: "팀원", + type: "text", + width: 600, + }, +]; + +export default function RankListView({ teams }) { + const addMedalEmogi = (id) => { + switch (id) { + case 1: + return "🥇"; + case 2: + return "🥈"; + case 3: + return "🥉"; + default: + return id; + } + }; + + const convertTeams2List = (teams) => { + return teams?.map((team, idx) => { + return { + id: addMedalEmogi(idx + 1), + groupNm: team.id, + reportNm: team.reports, + totalMins: team.totalMinutes, + members: team.members.join(", "), + }; + }); + }; + + return ( + + ); +} diff --git a/src/components/Rank/ViewToggleButton.js b/src/components/Rank/ViewToggleButton.js new file mode 100644 index 0000000..31e732e --- /dev/null +++ b/src/components/Rank/ViewToggleButton.js @@ -0,0 +1,29 @@ +import { Stack, ToggleButton, ToggleButtonGroup } from "@mui/material"; + +import ViewListIcon from "@mui/icons-material/ViewList"; +import ViewModuleIcon from "@mui/icons-material/ViewModule"; + +export default function ViewToggleButton({ view, setView }) { + const handleViewChange = (event, nextView) => { + setView(nextView); + }; + return ( + + + + + + + + + + + ); +} diff --git a/src/pages/Rank/Rank.js b/src/pages/Rank/Rank.js index f68e491..0435442 100644 --- a/src/pages/Rank/Rank.js +++ b/src/pages/Rank/Rank.js @@ -1,73 +1,45 @@ -import { Grid, ImageList, styled } from "@mui/material"; +import { styled } from "@mui/material"; import { Box } from "@mui/system"; import { useState } from "react"; import { getAllTeamsForRank } from "../../apis/rank"; -import HoverBox from "../../components/Rank/HoverBox"; -import { AnimatePresence, motion } from "framer-motion"; +import { motion } from "framer-motion"; import NoDataLottie from "../../components/common/NoDataLottie"; import { StyledColumnAlignLayout } from "../../components/common/StyledLayout"; import Title from "../../components/common/Title"; import { useQuery } from "react-query"; -import Item from "../../components/Rank/Item"; -import { useRecoilValue, useSetRecoilState } from "recoil"; -import { authorityState, isShowFullImageState } from "../../store/atom"; import FullImage from "../../components/Rank/FullImage"; +import ViewToggleButton from "../../components/Rank/ViewToggleButton"; +import { DataGrid } from "@mui/x-data-grid"; +import RankListView from "../../components/Rank/RankListView"; +import RankGridView from "../../components/Rank/RankGridView"; + const StyledScrollBox = styled(Box)({ maxWidth: "1245px", width: "100%", overflow: "scroll", }); -const StyledItemSize = styled(Box)({ - position: "relative", - width: "300px", - height: "300px", -}); - export default function Rank() { const [teams, setTeams] = useState([]); const [itemsHover, setItemsHover] = useState([]); + const [view, setView] = useState("list"); - const { isLoading } = useQuery(["AllTeamRanks"], getAllTeamsForRank, { + useQuery(["AllTeamRanks"], getAllTeamsForRank, { casheTime: 10 * 60 * 1000, onSuccess: (data) => { setTeams(data.teams); setItemsHover(new Array(data.teams.length).fill(false)); }, + refetchOnWindowFocus: false, }); - const handleMouseOver = (index) => { - setItemsHover((prev) => [ - ...prev?.slice(0, index), - true, - ...prev?.slice(index + 1), - ]); - }; - const handleMouseOut = (index) => { - setItemsHover((prev) => [ - ...prev?.slice(0, index), - false, - ...prev?.slice(index + 1), - ]); - }; - - const setIsShowFullImage = useSetRecoilState(isShowFullImageState); - const myAuthority = useRecoilValue(authorityState); const [fullImageUrl, setFullImageUrl] = useState(null); - const handleFullImageOpen = async (imageUrl) => { - if (myAuthority !== "ADMIN") return; - if (!imageUrl) { - setFullImageUrl("/img/mainImg2.png"); - } else setFullImageUrl(imageUrl); - setIsShowFullImage(true); - }; - return ( <> @@ -82,38 +54,18 @@ export default function Rank() { ) : ( - - {teams.map((item, index) => ( - handleFullImageOpen(item?.thumbnail)} - > - handleMouseOver(index)} - onMouseOut={() => handleMouseOut(index)} - > - - {itemsHover[index] && ( - - )} - - - - - - ))} - + + + {view === "list" ? ( + + ) : ( + + )} )}