Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"axios": "^1.11.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^7.8.1"
},
"devDependencies": {
Expand Down
56 changes: 34 additions & 22 deletions src/components/profile/LearningHistoryTab.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import styled from '@emotion/styled';

const learningHistoryData = [
{
icon: 'history_edu',
title: '역사 카드 학습 완료!',
date: '2025년 8월 28일',
},
{
icon: 'science',
title: '과학 카드 학습 완료!',
date: '2025년 8월 27일',
},
];
import { useEffect, useState } from 'react';
import type { PaginationResponse, StudyLog } from '@@types/index.ts';
import { defaultPaginationValue } from '@@types/defaultValues.ts';
import useStudylogs from '@hooks/useStudylogs.tsx';
import InfiniteScroll from 'react-infinite-scroll-component';
import { CARD_CATEGORY_KO, categoryMaterialIcons } from '@utils/index.ts';

export const TabContent = styled.div<{ isActive: boolean }>`
display: ${({ isActive }) => (isActive ? 'block' : 'none')};
Expand Down Expand Up @@ -54,17 +47,36 @@ interface LearningHistoryTabProps {
}

const LearningHistoryTab = ({ isActive }: LearningHistoryTabProps) => {
const [response, setResponse] = useState<PaginationResponse<StudyLog[]>>(defaultPaginationValue);
const [studylogs, setStudylods] = useState<StudyLog[]>([]);
const { fetchStudylogs } = useStudylogs();

const fetchMoreData = async () => {
const nextPage = response.pageable.pageNumber + 1;
const res = await fetchStudylogs(nextPage, response.pageable.pageSize);
setStudylods((prev) => [...prev, ...res.content]);
setResponse(res);
};

useEffect(() => {
fetchMoreData();
}, []);

return (
<TabContent isActive={isActive}>
{learningHistoryData.map((item, index) => (
<HistoryItem key={index}>
<HistoryIcon className="material-symbols-outlined">{item.icon}</HistoryIcon>
<HistoryDetails>
<h3>{item.title}</h3>
<p>{item.date}</p>
</HistoryDetails>
</HistoryItem>
))}
<InfiniteScroll next={fetchMoreData} hasMore={!response.last} loader={null} dataLength={studylogs.length}>
{studylogs.map((item, index) => (
<HistoryItem key={index}>
<HistoryIcon className="material-symbols-outlined">{categoryMaterialIcons[item.category]}</HistoryIcon>
<HistoryDetails>
<h3>{item.title}</h3>
<p>
{item.date.split('T')[0]} ・ {CARD_CATEGORY_KO[item.category]}
</p>
</HistoryDetails>
</HistoryItem>
))}
</InfiniteScroll>
</TabContent>
);
};
Expand Down
70 changes: 36 additions & 34 deletions src/components/profile/QuizHistoryTab.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import styled from '@emotion/styled';

const quizHistoryData = [
{
icon: 'lightbulb',
title: '상식 퀴즈 완료!',
date: '2025년 8월 28일',
score: '8 / 10',
},
{
icon: 'menu_book',
title: '속담 퀴즈 완료!',
date: '2025년 8월 26일',
score: '10 / 10',
},
{
icon: 'account_balance',
title: '수도 퀴즈 완료!',
date: '2025년 8월 25일',
score: '6 / 10',
},
];
import { useEffect, useState } from 'react';
import type { PaginationResponse, QuizLog } from '@@types/index.ts';
import InfiniteScroll from 'react-infinite-scroll-component';
import { defaultPaginationValue } from '@@types/defaultValues.ts';
import useQuizLog from '@hooks/useQuizLog.tsx';
import { CARD_CATEGORY_KO, categoryMaterialIcons } from '@utils/index.ts';

export const TabContent = styled.div<{ isActive: boolean }>`
display: ${({ isActive }) => (isActive ? 'block' : 'none')};
Expand Down Expand Up @@ -57,29 +42,46 @@ export const HistoryDetails = styled.div`
}
`;

export const QuizScore = styled.span`
export const QuizScore = styled.span<{ isCorrect: boolean }>`
font-weight: 700;
color: #84cc16;
color: ${({ isCorrect }) => (isCorrect ? '#22c55e' : '#ef4444')};
`;

interface QuizHistoryTabProps {
isActive: boolean;
}

const QuizHistoryTab = ({ isActive }: QuizHistoryTabProps) => {
const [response, setResponse] = useState<PaginationResponse<QuizLog[]>>(defaultPaginationValue);
const [quizlogs, setQuizlogs] = useState<QuizLog[]>([]);
const { getQuizLogs } = useQuizLog();

useEffect(() => {
fetchMoreData();
}, []);

const fetchMoreData = async () => {
const nextPage = response.pageable.pageNumber + 1;
const res = await getQuizLogs(nextPage, response.pageable.pageSize);
setQuizlogs((prev) => [...prev, ...res.content]);
setResponse(res);
};

return (
<TabContent isActive={isActive}>
{quizHistoryData.map((item, index) => (
<HistoryItem key={index}>
<HistoryIcon className="material-symbols-outlined">{item.icon}</HistoryIcon>
<HistoryDetails>
<h3>{item.title}</h3>
<p>
{item.date} ・ <QuizScore>점수: {item.score}</QuizScore>
</p>
</HistoryDetails>
</HistoryItem>
))}
<InfiniteScroll next={fetchMoreData} hasMore={!response.last} loader={null} dataLength={quizlogs.length}>
{quizlogs.map((item, index) => (
<HistoryItem key={index}>
<HistoryIcon className="material-symbols-outlined">{categoryMaterialIcons[item.category]}</HistoryIcon>
<HistoryDetails>
<h3>{CARD_CATEGORY_KO[item.category]} 퀴즈 완료!</h3>
<p>
{item.date.split('T')[0]} ・ <QuizScore isCorrect={item.isCorrect}>{item.isCorrect ? '정답' : '오답'}</QuizScore>
</p>
</HistoryDetails>
</HistoryItem>
))}
</InfiniteScroll>
</TabContent>
);
};
Expand Down
18 changes: 18 additions & 0 deletions src/hooks/useQuizLog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import useApi from '@hooks/useApi.tsx';
import type { ApiResponse, PaginationResponse, QuizLog } from '@@types/index.ts';
import { defaultPaginationValue } from '@@types/defaultValues.ts';

function useQuizLog() {
const { api } = useApi();

const getQuizLogs = (page: number, size: number) => {
return api
.get<ApiResponse<PaginationResponse<QuizLog[]>>>(`/quizlogs`, { params: { page, size } })
.then((response) => response.data.data)
.catch(() => defaultPaginationValue);
};

return { getQuizLogs };
}

export default useQuizLog;
18 changes: 18 additions & 0 deletions src/hooks/useStudylogs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import useApi from '@hooks/useApi.tsx';
import type { ApiResponse, PaginationResponse, StudyLog } from '@@types/index.ts';
import { defaultPaginationValue } from '@@types/defaultValues.ts';

function useStudylogs() {
const { api } = useApi();

const fetchStudylogs = (page: number, size: number) => {
return api
.get<ApiResponse<PaginationResponse<StudyLog[]>>>('/studylogs', { params: { page, size } })
.then((response) => response.data.data)
.catch(() => defaultPaginationValue);
};

return { fetchStudylogs };
}

export default useStudylogs;
32 changes: 31 additions & 1 deletion src/types/defaultValues.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Card, Quiz } from '@@types/index.ts';
import type { Card, PaginationResponse, Quiz } from '@@types/index.ts';

export const defaultQuizValue: Quiz = {
todayQuizId: 0,
Expand All @@ -15,3 +15,33 @@ export const defaultCardValue: Card = {
meaning: 'Loading...',
difficulty: 1,
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const defaultPaginationValue: PaginationResponse<any> = {
content: [],
pageable: {
pageNumber: -1,
pageSize: 10,
sort: {
sorted: false,
empty: true,
unsorted: true,
},
offset: 0,
paged: true,
unpaged: false,
},
totalPages: 0,
totalElements: 0,
last: false,
number: 0,
size: 6,
numberOfElements: 0,
sort: {
sorted: false,
empty: true,
unsorted: true,
},
first: true,
empty: true,
};
52 changes: 52 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ export interface ApiResponse<T> {
data: T;
}

export interface Sort {
sorted: boolean;
empty: boolean;
unsorted: boolean;
}

export interface Pageable {
pageNumber: number;
pageSize: number;
sort: Sort;
offset: number;
paged: boolean;
unpaged: boolean;
}

export interface PaginationResponse<T> {
content: T[];
pageable: Pageable;
totalPages: number;
totalElements: number;
last: boolean;
number: number;
size: number;
numberOfElements: number;
sort: Sort;
first: boolean;
empty: boolean;
}

export interface SignupResponse {
id: number;
nickname: string;
Expand Down Expand Up @@ -61,3 +90,26 @@ export interface SubmitTodayQuizAnswerResponse {
isCorrect: boolean;
correctAnswer: string;
}

export interface QuizLog {
quizLogId: number;
date: string;
category: CardCategory;
isCorrect: boolean;
}

export interface DetailQuizLog {
quizLogId: number;
isCorrect: boolean;
selectedAnswer: string;
date: string;
quiz: Quiz;
}

export interface StudyLog {
logId: number;
category: CardCategory;
title: string;
difficulty: number;
date: string;
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,13 @@ react-dom@^19.1.1:
dependencies:
scheduler "^0.26.0"

react-infinite-scroll-component@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz#7e511e7aa0f728ac3e51f64a38a6079ac522407f"
integrity sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==
dependencies:
throttle-debounce "^2.1.0"

react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
Expand Down Expand Up @@ -1897,6 +1904,11 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==

throttle-debounce@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2"
integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==

tinyglobby@^0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
Expand Down