diff --git a/back-end/api-server/database/query.ts b/back-end/api-server/database/query.ts index eb7ab3d..b4815fa 100644 --- a/back-end/api-server/database/query.ts +++ b/back-end/api-server/database/query.ts @@ -19,9 +19,17 @@ export const insertIntoTable = (table, into, values) => { return connectionQuery(queryLine); }; -export const innerJoinTable = async (column, tableA, tableB, on = null, condition = null) => { - let queryLine = `SELECT ${column} FROM ${tableA} INNER JOIN ${tableB} ON ${on} `; - queryLine += condition ? `WHERE ${condition}` : ``; +export const innerJoinTable = async ( + column, + tableA, + tableB, + on = null, + condition = null, + limit = null +) => { + let queryLine = `SELECT ${column} FROM ${tableA} INNER JOIN ${tableB} ON ${on}`; + queryLine += condition ? ` WHERE ${condition}` : ``; + queryLine += limit ? ` LIMIT ${limit}` : ``; return connectionQuery(queryLine); }; diff --git a/back-end/api-server/routes/gameRecord.ts b/back-end/api-server/routes/gameRecord.ts index b2ffc8e..a9a7c16 100644 --- a/back-end/api-server/routes/gameRecord.ts +++ b/back-end/api-server/routes/gameRecord.ts @@ -5,7 +5,6 @@ const GameRecordRouter = express.Router(); GameRecordRouter.post('/', async (req, res, next) => { const { game, players } = req.body; - console.log('hello', req.body); const insertGameInfoResult = await insertGameInfo(game); const insertPlayerInfoResult = await insertPlayerInfo(game.game_id, players); if (insertGameInfoResult && insertPlayerInfoResult) { diff --git a/back-end/api-server/routes/profile.ts b/back-end/api-server/routes/profile.ts index 06076e7..5b5dd1d 100644 --- a/back-end/api-server/routes/profile.ts +++ b/back-end/api-server/routes/profile.ts @@ -23,9 +23,21 @@ ProfileRouter.post('/total', async (req, res, next) => { try { const [{ oauth_id }] = await getOauthId(req.body.nickname); const totalList = await getTotalInDB(oauth_id); - const recentList = await getRecentInDB(oauth_id); const [total, win] = totalList; - res.status(200).json({ total, win, recentList }); + const data = { ...total[0], ...win[0] }; + res.status(200).json(data); + } catch (error) { + console.log(error); + res.status(401).json({ error: '잘못된 인증입니다.' }); + } +}); + +ProfileRouter.post('/recent', async (req, res, next) => { + try { + const { nickname, offset, limit } = req.body; + const [{ oauth_id }] = await getOauthId(nickname); + const data = await getRecentInDB(oauth_id, offset, limit); + res.status(200).json(data); } catch (error) { console.log(error); res.status(401).json({ error: '잘못된 인증입니다.' }); @@ -81,13 +93,14 @@ const getTotalInDB = async (id) => { ]); }; -const getRecentInDB = (id) => { +const getRecentInDB = (id, offset, limit) => { return innerJoinTable( 'game_date, game_mode, ranking, play_time, attack_cnt, attacked_cnt', 'PLAY', 'GAME_INFO', 'PLAY.game_id = GAME_INFO.game_id', - `oauth_id='${id}'` + `oauth_id='${id}'`, + `${offset}, ${limit}` ); }; diff --git a/front-end/src/components/InfiniteScroll/index.tsx b/front-end/src/components/InfiniteScroll/index.tsx new file mode 100644 index 0000000..ceec057 --- /dev/null +++ b/front-end/src/components/InfiniteScroll/index.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import './style.scss'; + +const drawRecent = (list: Array) => { + if (list.length === 0) return; + return ( + <> + {list.map((value) => ( +
+
{value.game_date.slice(0, 10)}
+
{value.game_mode === 'normal' ? '일반전' : '1 vs 1'}
+
{value.ranking}
+
{value.play_time}
+
{value.attack_cnt}
+
{value.attacked_cnt}
+
+ ))} + + ); +}; + +export default function InfiniteScroll({ + nickname, + MAX_ROWS, + fetchURL, + type, +}: { + nickname: string | undefined; + MAX_ROWS: number; + fetchURL: string; + type: string; +}) { + const [pageNum, setPageNum] = useState(0); + const [loading, setLoading] = useState(false); + + const rootRef = useRef(null); + const observerRef = useRef(null); + + const [list, setList] = useState([]); + const [hasMore, setHasMore] = useState(false); + + useEffect(() => { + setLoading(true); + fetch(fetchURL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ nickname, limit: MAX_ROWS, offset: pageNum }), + }) + .then((res) => res.json()) + .then((data) => { + setList((prev: any) => { + return [...prev, ...data]; + }); + setHasMore(data.length > 0); + setLoading(false); + }) + .catch((error) => { + console.log('error:', error); + }); + }, [pageNum]); + + const targetRef = useCallback( + (node) => { + if (loading) return; + let options = { + root: rootRef.current, + rootMargin: '50px', + threshold: 0, + }; + + if (observerRef.current) observerRef.current.disconnect(); + observerRef.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasMore) { + setPageNum((prev) => prev + MAX_ROWS); + } + }, options); + if (node) observerRef.current.observe(node); + }, + [loading, hasMore] + ); + + return ( +
+ {type === 'profile' && drawRecent(list)} +
+ <>{loading &&
로딩중
} +
+ ); +} diff --git a/front-end/src/components/InfiniteScroll/style.scss b/front-end/src/components/InfiniteScroll/style.scss new file mode 100644 index 0000000..b639c88 --- /dev/null +++ b/front-end/src/components/InfiniteScroll/style.scss @@ -0,0 +1,15 @@ +@import 'common/styles/base'; + +.recent__list--scroll { + height: 250px; + overflow-y: scroll; + + .recent__list { + display: grid; + grid-template-columns: repeat(6, 1fr); + text-align: center; + font-size: 17px; + padding: 15px 40px; + box-sizing: border-box; + } +} diff --git a/front-end/src/pages/ProfilePage/index.tsx b/front-end/src/pages/ProfilePage/index.tsx index a466670..3f93da1 100644 --- a/front-end/src/pages/ProfilePage/index.tsx +++ b/front-end/src/pages/ProfilePage/index.tsx @@ -7,6 +7,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useAppDispatch } from '../../app/hooks'; import { updateNickname } from '../../features/user/userSlice'; import { useSocket } from '../../context/SocketContext'; +import InfiniteScroll from '../../components/InfiniteScroll'; export default function Profile() { const { nickname } = useParams(); @@ -18,12 +19,14 @@ export default function Profile() { const translations = [ ['total_game_cnt', '총 게임 수'], ['total_play_time', '총 플레이 시간'], - ['single_player_win', '1vs1 승리 횟수'], + ['single_player_win', '1 vs 1 승리 횟수'], ['multi_player_win', '일반전 승리 횟수'], ['total_attack_cnt', '총 공격 횟수'], ]; + const [recentList, setRecentList] = useState([]); const [statsticsState, setStatsticsState] = useState({}); + const [editMode, setEditMode] = useState(false); const [userState, setUserState] = useState({ id: authProfile.id, @@ -49,24 +52,6 @@ export default function Profile() { ); }; - const drawRecent = (recentList: Array) => { - if (recentList.length === 0) return; - return ( - <> - {recentList.map((value) => ( -
-
{value.game_date.slice(0, 10)}
-
{value.game_mode === 'normal' ? '일반전' : '1 vs 1'}
-
{value.ranking}
-
{value.play_time}
-
{value.attack_cnt}
-
{value.attacked_cnt}
-
- ))} - - ); - }; - const changeTextArea = (e: React.ChangeEvent) => { if (!e.target) return; setUserState({ ...userState, stateMessage: e.target.value }); @@ -98,8 +83,6 @@ export default function Profile() { }; useEffect(() => { - setUserState({ ...userState, nickname }); - fetch('/api/profile/stateMessage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -121,15 +104,13 @@ export default function Profile() { }) .then((res) => res.json()) .then((data) => { - setStatsticsState({ ...statsticsState, ...data.total[0], ...data.win[0] }); - setRecentList([...data.recentList]); + setStatsticsState({ ...statsticsState, ...data }); }) .catch((error) => { navigate('/error/unauthorize', { replace: true }); console.log('error:', error); }); - return () => {}; - }, [nickname]); + }, []); return ( @@ -164,7 +145,7 @@ export default function Profile() { )}
-
+
통계
@@ -179,8 +160,12 @@ export default function Profile() {
{value}
))}
- -
{drawRecent(recentList)}
+
diff --git a/front-end/src/pages/ProfilePage/style.scss b/front-end/src/pages/ProfilePage/style.scss index 8136253..ec163a7 100644 --- a/front-end/src/pages/ProfilePage/style.scss +++ b/front-end/src/pages/ProfilePage/style.scss @@ -124,20 +124,6 @@ margin: 20px 30px 0px; box-sizing: border-box; } - - .recent-list__scroll { - height: 250px; - overflow: auto; - - .recent-list { - display: grid; - grid-template-columns: repeat(6, 1fr); - text-align: center; - font-size: 17px; - padding: 15px 40px; - box-sizing: border-box; - } - } } } }