From 178e3ae935bc7be862ab2de291bd16af3ac06e90 Mon Sep 17 00:00:00 2001 From: solo5star Date: Wed, 19 Jul 2023 08:51:10 +0000 Subject: [PATCH 01/22] =?UTF-8?q?feat:=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=ED=95=84=ED=84=B0=20msw=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: WaiNaat --- .../src/components/Lists/QuizAnswerList.tsx | 39 ++ frontend/src/mocks/fixtures/essayAnswers.ts | 87 ++++ frontend/src/mocks/fixtures/keywords.ts | 393 +++++++++--------- frontend/src/mocks/fixtures/quizs.ts | 19 +- frontend/src/mocks/handlers/essayAnswers.ts | 41 ++ frontend/src/mocks/handlers/index.ts | 10 +- frontend/src/mocks/handlers/keywords.ts | 9 +- 7 files changed, 400 insertions(+), 198 deletions(-) create mode 100644 frontend/src/components/Lists/QuizAnswerList.tsx create mode 100644 frontend/src/mocks/fixtures/essayAnswers.ts create mode 100644 frontend/src/mocks/handlers/essayAnswers.ts diff --git a/frontend/src/components/Lists/QuizAnswerList.tsx b/frontend/src/components/Lists/QuizAnswerList.tsx new file mode 100644 index 000000000..3656ae034 --- /dev/null +++ b/frontend/src/components/Lists/QuizAnswerList.tsx @@ -0,0 +1,39 @@ +/** @jsxImportSource @emotion/react */ +import {css} from '@emotion/react'; +import EssayAnswerItem from "../Items/EssayAnswerItem"; +import { NoDefaultHoverLink } from '../Items/EssayAnswerItem.styles'; +import { EssayAnswerResponse } from '../../models/EssayAnswers'; + +interface QuizAnswerListProps { + essayAnswers: EssayAnswerResponse[]; + showQuizTitle?: boolean; +} + +const QuizAnswerList = (props: QuizAnswerListProps) => { + const { essayAnswers, showQuizTitle = false } = props; + + return ( + + ); +}; + +export default QuizAnswerList; diff --git a/frontend/src/mocks/fixtures/essayAnswers.ts b/frontend/src/mocks/fixtures/essayAnswers.ts new file mode 100644 index 000000000..debf18cab --- /dev/null +++ b/frontend/src/mocks/fixtures/essayAnswers.ts @@ -0,0 +1,87 @@ +import { EssayAnswerResponse } from '../../models/EssayAnswers'; + +type EssayAnswersFilterParams = { + curriculumId: number; + keywordId?: number; + quizIds?: number[]; + memberIds?: number[]; + page?: number; + size?: number; +}; + +const data: Array<{ + curriculumId: number; + essayAnswers: (EssayAnswerResponse & { + keywordId: number; + })[]; +}> = [ + { + curriculumId: 1, + essayAnswers: [ + { + id: 71, + keywordId: 1, + quiz: { + quizId: 29, + question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', + }, + answer: + '# Varargs\n***\nVarargs(가변인자)는 JDK 5에서 새로 도입된 기능이다.\nparameter의 수를 가변적으로 조절할 수 있게 해주는 기능이다.\n
\n\n## 사용법\n***\nparameter를 여러개 정의하고 싶은 method를 선언할 때, parameter 부분에 ...을 붙이면 된다.\n\n```java\npublic int calculateSum(int... numbers) {\n return Arrays.stream(numbers).sum();\n}\n```\n\n위 코드에서 유추할 수 있듯이, varargs는 내부적으로 array를 반환한다.\ncompile 시에 Object array가 만들어지고, varargs에 할당된 인자들이 array의 원소로 들어간다.\n
\n\n## 장점\n***\n만약 위의 method에서, varargs를 사용하지 않는다고 가정해보자.\n그렇다면, parameter가 2개, 3개, 4개, ..., 인 calculateSum() method를 전부 overloading해야 할 것이다.\n\n```java\npublic int getSum(int number1, int number2) {\n\treturn number1 + number2;\n}\npublic int getSum(int number1, int number2, int number3) {\n\treturn number1 + number2 + number3;\n}\n\n...\n\n\n```\n\nvarargs를 사용하면 이러한 overloading을 하지 않도록 할 수 있다.', + author: { + id: 304, + username: 'Jaeyoung22', + nickname: 'ReO', + role: 'CREW', + imageUrl: 'https://avatars.githubusercontent.com/u/89302528?v=4', + }, + createdAt: '2023-04-09T17:56:25.642891', + updatedAt: '2023-04-09T17:56:25.642891', + }, + { + id: 69, + keywordId: 1, + quiz: { + quizId: 29, + question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', + }, + answer: + '# Varargs 탄생 배경\n\nJDK 1.4 전까지는 다양한 수의 인자를 가진 메서드를 선언할 수 없었습니다.\n이를 해결하기 위해선 두 가지 방식을 사용했습니다.\n1. 메서드 오버로딩\n이 방법은 코드의 길이를 무한대로 증가시킵니다.\n2. 전달할 값들을 배열로 받기\n\n결국, Varargs는 **다양한 범위의 인자를 받기 위한 해결책**으로 탄생하게 되었습니다.\n\n# Varargs 사용법 및 특징\n\n```java\npublic class VarargsPractice {\n public static void main(String[] args) {\n printValues(); // 특징 1. 0개 이상의 인자를 전달할 수 있습니다.\n printValues("1");\n printValues("1", "2");\n printValues("1", "2", "3");\n printValues(new String[]{"1", "2", "3"}); // 특징 2. 실제로는 배열로 동작합니다.\n }\n\n // 사용법: 파라미터에 타입과 ... 을 함께 작성합니다. (String...)\n public static void printValues(final String... values) {\n for (int i = 0; i < values.length; i++) {\n System.out.println(values[i]); // 배열로 동작하기에 인덱스로 접근 가능합니다.\n }\n }\n}\n```\n\n# 잘못된 Varargs 사용법\n\n1. 한 메서드에 2가지 타입의 가변인자(varargs)는 사용하지 못합니다.\n```java\nvoid method(String... inputs, int... numbers);\n```\n\n2. 가변인자를 첫 번째 파라미터로 선언하지 못합니다.\n```java\nvoid method(int... numbers, String input);\n```\n\n# Varargs의 장점\n\n1. 메서드 인자의 수를 동적으로 결정 및 처리할 수 있습니다.\n2. 코드를 간결하게 유지할 수 있습니다.\n\n# Varargs의 단점\n\n1. 공간 복잡도가 증가합니다.\n가변 인자는 곧, 배열입니다.\n가변 인자를 전달받은 메서드는 매번 새로운 배열을 생성해야 합니다.\n\n# Varargs를 사용하는 기준\n(개인적인 기준입니다.)\n\n1. 인자의 개수를 다양하게 받아야 하는 상황이다.\n2. 성능상 그다지 중요하지 않은 상황이다.', + author: { + id: 287, + username: 'kdkdhoho', + nickname: '도기\uD83D\uDC36', + role: 'CREW', + imageUrl: 'https://avatars.githubusercontent.com/u/66300965?v=4', + }, + createdAt: '2023-04-08T23:54:48.667266', + updatedAt: '2023-04-08T23:54:48.667266', + }, + ], + }, +]; + +const essayAnswersMock = { + data, + filter(params: EssayAnswersFilterParams) { + const { curriculumId, keywordId, memberIds, page = 1, quizIds, size = 10 } = params; + + const essayAnswers = + this.data.find( + (essayAnswersByCurriculum) => essayAnswersByCurriculum.curriculumId === curriculumId + )?.essayAnswers ?? []; + + return essayAnswers + .filter((essayAnswer) => { + if (essayAnswer.keywordId !== keywordId) return false; + + if (memberIds && !memberIds.includes(essayAnswer.author.id)) return false; + + if (quizIds && quizIds.includes(essayAnswer.quiz.quizId)) return false; + + return true; + }) + .slice(0, size); + }, +}; + +export default essayAnswersMock; diff --git a/frontend/src/mocks/fixtures/keywords.ts b/frontend/src/mocks/fixtures/keywords.ts index 0a0a6b805..67c7e33f3 100644 --- a/frontend/src/mocks/fixtures/keywords.ts +++ b/frontend/src/mocks/fixtures/keywords.ts @@ -1,187 +1,199 @@ -import { quizMock } from './quizs'; +import { KeywordResponse, Quiz } from '../../models/Keywords'; +import quizMock from './quizs'; -export default { - data: [ - // 세션 1 - { - keywordId: 1, - sessionId: 1, // for mock - name: 'JavaScript', - // order: 1, - importance: 5, - parentKeywordId: null, - description: '동적 타이핑, 스크립트 언어입니다.', - quizs: { - data: quizMock[1].data, - }, - childrenKeywords: [ - { - keywordId: 2, - sessionId: 1, // for mock - name: 'let, const, var', - // order: 1, - importance: 5, - parentKeywordId: 1, - description: 'let, const, var', - quizs: { - data: quizMock[2].data, - }, - childrenKeywords: [ - { - keywordId: 3, - sessionId: 1, // for mock - name: 'let', - // order: 1, - importance: 5, - parentKeywordId: 2, - description: 'let', - quizs: { - data: quizMock[3].data, - }, - childrenKeywords: {}, - }, - { - keywordId: 4, - sessionId: 1, // for mock - name: 'const', - // order: 1, - importance: 5, - parentKeywordId: 2, - description: 'const', - quizs: { - data: quizMock[4].data, - }, - childrenKeywords: {}, +type WithSession = T & { + sessionId: number; + quizs: { + data: Quiz[]; + }; + childrenKeywords: + | (T extends { childrenKeywords: Array | null } ? WithSession[] : null) + | null; +}; + +const data: Array> = [ + // 세션 1 + { + keywordId: 1, + sessionId: 1, // for mock + name: 'JavaScript', + order: 1, + importance: 5, + parentKeywordId: null, + description: '동적 타이핑, 스크립트 언어입니다.', + quizs: { + data: quizMock.filterByKeyword(1), + }, + childrenKeywords: [ + { + keywordId: 2, + sessionId: 1, // for mock + name: 'let, const, var', + order: 1, + importance: 5, + parentKeywordId: 1, + description: 'let, const, var', + quizs: { + data: quizMock.filterByKeyword(2), + }, + childrenKeywords: [ + { + keywordId: 3, + sessionId: 1, // for mock + name: 'let', + order: 1, + importance: 5, + parentKeywordId: 2, + description: 'let', + quizs: { + data: quizMock.filterByKeyword(3), }, - { - keywordId: 5, - sessionId: 1, // for mock - name: 'var', - // order: 1, - importance: 5, - parentKeywordId: 2, - description: 'var', - quizs: { - data: quizMock[5].data, - }, - childrenKeywords: {}, + childrenKeywords: null, + }, + { + keywordId: 4, + sessionId: 1, // for mock + name: 'const', + order: 1, + importance: 5, + parentKeywordId: 2, + description: 'const', + quizs: { + data: quizMock.filterByKeyword(4), }, - ], - }, - ], - }, - // 세션 1 - React 키워드 - { - keywordId: 6, - sessionId: 1, // for mock - name: 'React', - // order: 1, - importance: 5, - parentKeywordId: null, - description: 'React입니다.', - quizs: { - data: quizMock[6].data, - }, - childrenKeywords: [ - { - keywordId: 7, - sessionId: 1, // for mock - name: 'lifecycle', - // order: 1, - importance: 5, - parentKeywordId: 6, - description: 'lifecycle 설명', - quizs: { - data: quizMock[7].data, + childrenKeywords: null, }, - childrenKeywords: [ - { - keywordId: 8, - sessionId: 1, // for mock - name: 'mount', - // order: 1, - importance: 5, - parentKeywordId: 7, - description: 'mount 설명', - quizs: { - data: quizMock[8].data, - }, - childrenKeywords: {}, + { + keywordId: 5, + sessionId: 1, // for mock + name: 'var', + order: 1, + importance: 5, + parentKeywordId: 2, + description: 'var', + quizs: { + data: quizMock.filterByKeyword(5), }, - { - keywordId: 9, - sessionId: 1, // for mock - name: 'unmount', - // order: 1, - importance: 5, - parentKeywordId: 7, - description: 'unmount 설명', - quizs: { - data: quizMock[9].data, - }, - childrenKeywords: {}, + childrenKeywords: null, + }, + ], + }, + ], + }, + // 세션 1 - React 키워드 + { + keywordId: 6, + sessionId: 1, // for mock + name: 'React', + order: 1, + importance: 5, + parentKeywordId: null, + description: 'React입니다.', + quizs: { + data: quizMock.filterByKeyword(6), + }, + childrenKeywords: [ + { + keywordId: 7, + sessionId: 1, // for mock + name: 'lifecycle', + order: 1, + importance: 5, + parentKeywordId: 6, + description: 'lifecycle 설명', + quizs: { + data: quizMock.filterByKeyword(7), + }, + childrenKeywords: [ + { + keywordId: 8, + sessionId: 1, // for mock + name: 'mount', + order: 1, + importance: 5, + parentKeywordId: 7, + description: 'mount 설명', + quizs: { + data: quizMock.filterByKeyword(8), }, - { - keywordId: 10, - sessionId: 1, // for mock - name: 'update', - // order: 1, - importance: 5, - parentKeywordId: 7, - description: 'update 설명', - quizs: { - data: quizMock[10].data, - }, - childrenKeywords: {}, + childrenKeywords: null, + }, + { + keywordId: 9, + sessionId: 1, // for mock + name: 'unmount', + order: 1, + importance: 5, + parentKeywordId: 7, + description: 'unmount 설명', + quizs: { + data: quizMock.filterByKeyword(9), }, - ], - }, - ], - }, - // 세션 2 - Test - { - keywordId: 11, - sessionId: 2, // for mock - name: 'Test', - // order: 1, - importance: 5, - parentKeywordId: null, - description: 'Test입니다.', - quizs: { - data: quizMock[11].data, - }, - childrenKeywords: [ - { - keywordId: 12, - sessionId: 2, // for mock - name: 'Jest', - // order: 1, - importance: 5, - parentKeywordId: 11, - description: 'Jest 설명', - quizs: { - data: quizMock[12].data, + childrenKeywords: null, }, - childrenKeywords: [ - { - keywordId: 13, - sessionId: 2, // for mock - name: 'ReactTestingLibrary', - // order: 1, - importance: 5, - parentKeywordId: 12, - description: 'ReactTestingLibrary 설명', - quizs: { - data: quizMock[13].data, - }, - childrenKeywords: {}, + { + keywordId: 10, + sessionId: 1, // for mock + name: 'update', + order: 1, + importance: 5, + parentKeywordId: 7, + description: 'update 설명', + quizs: { + data: quizMock.filterByKeyword(10), }, - ], - }, - ], + childrenKeywords: null, + }, + ], + }, + ], + }, + // 세션 2 - Test + { + keywordId: 11, + sessionId: 2, // for mock + name: 'Test', + order: 1, + importance: 5, + parentKeywordId: null, + description: 'Test입니다.', + quizs: { + data: quizMock.filterByKeyword(11), }, - ], - // 5 + childrenKeywords: [ + { + keywordId: 12, + sessionId: 2, // for mock + name: 'Jest', + order: 1, + importance: 5, + parentKeywordId: 11, + description: 'Jest 설명', + quizs: { + data: quizMock.filterByKeyword(12), + }, + childrenKeywords: [ + { + keywordId: 13, + sessionId: 2, // for mock + name: 'ReactTestingLibrary', + order: 1, + importance: 5, + parentKeywordId: 12, + description: 'ReactTestingLibrary 설명', + quizs: { + data: quizMock.filterByKeyword(13), + }, + childrenKeywords: null, + }, + ], + }, + ], + }, +]; + +const keywordMock = { + data, filterKeywordsBySession(sessionId: string | readonly string[]) { const filteredData = this.data.filter((item) => item.sessionId === Number(sessionId)); @@ -189,7 +201,6 @@ export default { data: filteredData, }; }, - // 4 findKeyword(keywordId: string | readonly string[]) { // data를 순회하면서, childrenKeywords를 순회하면서 해당 keyword가 있는지 확인한다. const data = this.data @@ -199,21 +210,25 @@ export default { return depth1Item; } - return depth1Item.childrenKeywords.map((depth2Item) => { - // 2뎁스 순회 - if (depth2Item.keywordId === Number(keywordId)) { - return depth2Item; - } - - return depth2Item.childrenKeywords.map((depth3Item) => { - // 3뎁스 순회 - if (depth3Item.keywordId === Number(keywordId)) { - return depth3Item; + return ( + depth1Item.childrenKeywords?.map((depth2Item) => { + // 2뎁스 순회 + if (depth2Item.keywordId === Number(keywordId)) { + return depth2Item; } - return undefined; - }); - }); + return ( + depth2Item.childrenKeywords?.map((depth3Item) => { + // 3뎁스 순회 + if (depth3Item.keywordId === Number(keywordId)) { + return depth3Item; + } + + return undefined; + }) ?? [] + ); + }) ?? [] + ); }) .find((item) => item !== undefined); @@ -230,3 +245,5 @@ export default { }; }, }; + +export default keywordMock; diff --git a/frontend/src/mocks/fixtures/quizs.ts b/frontend/src/mocks/fixtures/quizs.ts index 95ca4b016..ea18ec268 100644 --- a/frontend/src/mocks/fixtures/quizs.ts +++ b/frontend/src/mocks/fixtures/quizs.ts @@ -1,4 +1,9 @@ -export const quizMock = [ +import { Quiz } from '../../models/Keywords'; + +const data: Array<{ + keywordId: number; + data: Quiz[]; +}> = [ { keywordId: 0, data: [], @@ -225,3 +230,15 @@ export const quizMock = [ ], }, ]; + +const quizMock = { + data, + findQuiz(quizId) { + return this.data.flatMap(({ data }) => data).find((quiz) => quiz.quizId === quizId) ?? null; + }, + filterByKeyword(keywordId) { + return this.data.find((quizs) => quizs.keywordId === keywordId)?.data ?? []; + }, +}; + +export default quizMock; diff --git a/frontend/src/mocks/handlers/essayAnswers.ts b/frontend/src/mocks/handlers/essayAnswers.ts new file mode 100644 index 000000000..1a4f376cd --- /dev/null +++ b/frontend/src/mocks/handlers/essayAnswers.ts @@ -0,0 +1,41 @@ +import { rest } from 'msw'; +import { BASE_URL } from '../../configs/environment'; +import essayAnswersMock from '../fixtures/essayAnswers'; + +export const essayAnswerHandler = [ + rest.get(`${BASE_URL}/essay-answers`, (req, res, ctx) => { + const { searchParams } = req.url; + + const params = { + curriculumId: searchParams.get('curriculumId'), + keywordId: searchParams.get('keywordId'), + quizIds: searchParams.get('quizIds'), + memberIds: searchParams.get('memberIds'), + page: searchParams.get('page'), + size: searchParams.get('size'), + }; + + const curriculumId = Number(params.curriculumId); + if (Number.isNaN(curriculumId)) { + return res(ctx.status(400)); + } + + return res( + ctx.status(200), + ctx.json( + essayAnswersMock.filter({ + curriculumId: curriculumId, + keywordId: params.curriculumId ? Number(params.curriculumId) : undefined, + quizIds: params.curriculumId + ? params.curriculumId.slice(1, -1).split(',').map(Number) ?? [] + : undefined, + memberIds: params.memberIds + ? params.memberIds.slice(1, -1).split(',').map(Number) ?? [] + : undefined, + page: params.page ? Number(params.page) : undefined, + size: params.size ? Number(params.size) : undefined, + }) + ) + ); + }), +]; diff --git a/frontend/src/mocks/handlers/index.ts b/frontend/src/mocks/handlers/index.ts index fe98ad59c..65d0d384a 100644 --- a/frontend/src/mocks/handlers/index.ts +++ b/frontend/src/mocks/handlers/index.ts @@ -1,11 +1,13 @@ -import { popularStudyLogHandler } from './popularStudyLog'; import { commentsHandler } from './comment'; -import { levellogHandler } from './levellog'; +import { essayAnswerHandler } from './essayAnswers'; import { roadmapHandler } from './keywords'; +import { levellogHandler } from './levellog'; +import { popularStudyLogHandler } from './popularStudyLog'; export const handlers = [ - ...popularStudyLogHandler, ...commentsHandler, - ...levellogHandler, + ...essayAnswerHandler, ...roadmapHandler, + ...levellogHandler, + ...popularStudyLogHandler, ]; diff --git a/frontend/src/mocks/handlers/keywords.ts b/frontend/src/mocks/handlers/keywords.ts index 620fb4030..396f5bef8 100644 --- a/frontend/src/mocks/handlers/keywords.ts +++ b/frontend/src/mocks/handlers/keywords.ts @@ -1,9 +1,8 @@ -import { BASE_URL } from '../../configs/environment'; import { rest } from 'msw'; -import keywordsMock from '../fixtures/keywords'; +import { BASE_URL } from '../../configs/environment'; import curriculums from '../fixtures/curriculums'; - -import { quizMock } from '../fixtures/quizs'; +import keywordsMock from '../fixtures/keywords'; +import quizMock from '../fixtures/quizs'; import { sessionsMock } from '../fixtures/sessions'; export const roadmapHandler = [ @@ -62,6 +61,6 @@ export const roadmapHandler = [ params: { sessionId, keywordId }, } = req; - return res(ctx.status(200), ctx.json(quizMock[Number(keywordId)])); + return res(ctx.status(200), ctx.json(quizMock.findQuiz(Number(keywordId)))); }), ]; From 630013261b1d13c7497f42c826047358e78f8dcd Mon Sep 17 00:00:00 2001 From: solo5star Date: Wed, 19 Jul 2023 08:52:31 +0000 Subject: [PATCH 02/22] =?UTF-8?q?refactor:=20=EB=A1=9C=EB=93=9C=EB=A7=B5?= =?UTF-8?q?=20=ED=80=B4=EC=A6=88=20=EB=8B=B5=EB=B3=80=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: WaiNaat --- .../src/components/Items/EssayAnswerItem.tsx | 11 +++- .../src/components/Lists/EssayAnswerList.tsx | 26 ---------- .../src/pages/QuizAnswerListPage/index.tsx | 52 +++++++++++++++++++ .../src/pages/QuizAnswerListPage/styles.ts | 20 +++++++ 4 files changed, 81 insertions(+), 28 deletions(-) delete mode 100644 frontend/src/components/Lists/EssayAnswerList.tsx create mode 100644 frontend/src/pages/QuizAnswerListPage/index.tsx create mode 100644 frontend/src/pages/QuizAnswerListPage/styles.ts diff --git a/frontend/src/components/Items/EssayAnswerItem.tsx b/frontend/src/components/Items/EssayAnswerItem.tsx index f823c5f8a..cafefab4b 100644 --- a/frontend/src/components/Items/EssayAnswerItem.tsx +++ b/frontend/src/components/Items/EssayAnswerItem.tsx @@ -10,13 +10,20 @@ import { import { AlignItemsEndStyle, FlexColumnStyle, FlexStyle } from '../../styles/flex.styles'; import Card from '../Card/Card'; import ProfileChip from '../ProfileChip/ProfileChip'; +import { EssayAnswerResponse } from '../../models/EssayAnswers'; -const EssayAnswerItem = ({ essayAnswer }) => { - const { author, answer } = essayAnswer; +type EssayAnswerItemProps = ( + Pick + & { title?: string; showTitle?: boolean } +); + +const EssayAnswerItem = (props: EssayAnswerItemProps) => { + const { author, answer, title, showTitle } = props; return (
+ { showTitle &&

{title}

}
{answer}
diff --git a/frontend/src/components/Lists/EssayAnswerList.tsx b/frontend/src/components/Lists/EssayAnswerList.tsx deleted file mode 100644 index 5838d5a80..000000000 --- a/frontend/src/components/Lists/EssayAnswerList.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import {css} from '@emotion/react'; -import EssayAnswerItem from "../Items/EssayAnswerItem"; -import { NoDefaultHoverLink } from '../Items/EssayAnswerItem.styles'; - -const EssayAnswerList = ({ essayAnswers }) => { - return ( -
    li:not(:last-child) { - margin-bottom: 1.6rem; - } - `} - > - {essayAnswers.map((essayAnswer) => ( -
  • - - - -
  • - ))} -
- ); -}; - -export default EssayAnswerList; diff --git a/frontend/src/pages/QuizAnswerListPage/index.tsx b/frontend/src/pages/QuizAnswerListPage/index.tsx new file mode 100644 index 000000000..f86215b51 --- /dev/null +++ b/frontend/src/pages/QuizAnswerListPage/index.tsx @@ -0,0 +1,52 @@ +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import QuizAnswerList from '../../components/Lists/QuizAnswerList'; +import MEDIA_QUERY from '../../constants/mediaQuery'; +import { useEssayAnswerList } from '../../hooks/EssayAnswer/useEssayAnswerList'; +import { MainContentStyle } from '../../PageRouter'; +import { + AlignItemsCenterStyle, + FlexStyle, + JustifyContentSpaceBtwStyle +} from '../../styles/flex.styles'; +import { HeaderContainer, PostListContainer } from './styles'; + +const QuizAnswerListPage = () => { + const { quiz, essayAnswers } = useEssayAnswerList(); + + return ( +
+ +
+

+ {!!quiz && quiz.question} 🤔 +

+
+
+ + {(!essayAnswers || essayAnswers.length === 0) && '작성된 글이 없습니다.'} + {!!essayAnswers && } + +
+ ); +}; + +export default QuizAnswerListPage; diff --git a/frontend/src/pages/QuizAnswerListPage/styles.ts b/frontend/src/pages/QuizAnswerListPage/styles.ts new file mode 100644 index 000000000..542b66cfa --- /dev/null +++ b/frontend/src/pages/QuizAnswerListPage/styles.ts @@ -0,0 +1,20 @@ +import styled from '@emotion/styled'; +import MEDIA_QUERY from '../../constants/mediaQuery'; + +const HeaderContainer = styled.div` + display: flex; + flex-direction: column; + margin-bottom: 1.5rem; + + ${MEDIA_QUERY.xs} { + margin-bottom: 0.8rem; + } +`; + +const PostListContainer = styled.div` + display: grid; + grid-row-gap: 2rem; + word-break: break-all; +`; + +export { HeaderContainer, PostListContainer }; From 02a659b236b49f3bf74c9b8578957f992ddfebbc Mon Sep 17 00:00:00 2001 From: solo5star Date: Wed, 19 Jul 2023 08:53:05 +0000 Subject: [PATCH 03/22] =?UTF-8?q?feat:=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: WaiNaat --- frontend/src/models/EssayAnswers.ts | 27 ++++++++++++++++--- frontend/src/models/Keywords.ts | 2 +- .../src/pages/EssayAnswerListPage/index.tsx | 20 +++++++++----- frontend/src/pages/index.js | 21 ++++++++------- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/frontend/src/models/EssayAnswers.ts b/frontend/src/models/EssayAnswers.ts index 5323d79f6..d9adecb87 100644 --- a/frontend/src/models/EssayAnswers.ts +++ b/frontend/src/models/EssayAnswers.ts @@ -1,14 +1,12 @@ +import { Quiz } from './Keywords'; import { Author } from './Studylogs'; -import {Quiz} from "./Keywords"; export interface EssayAnswerRequest { quizId: number; answer: string; } -export interface EssayEditRequest { - answer: string; -} +export type EssayEditRequest = Pick; export interface EssayAnswerResponse { id: number; @@ -18,3 +16,24 @@ export interface EssayAnswerResponse { createdAt: string; updatedAt: string; } + +export type EssayAnswerFilter = { + curriculumId: number; + keywordId?: number; + quizIds?: number[]; + memberIds?: number[]; +}; + +export type EssayAnswerListRequest = EssayAnswerFilter & { + page?: number; + size?: number; +}; + +export type EssayAnswerFilterRequest = { + curriculumId: number; + keywordId?: number; + quizIds?: number[]; + memberIds?: number[]; + page?: number; + size?: number; +}; diff --git a/frontend/src/models/Keywords.ts b/frontend/src/models/Keywords.ts index 0c0c5276f..13f3681f2 100644 --- a/frontend/src/models/Keywords.ts +++ b/frontend/src/models/Keywords.ts @@ -23,7 +23,7 @@ export interface KeywordResponse { keywordId: number; order: number; importance: number; - parentKeywordId: number; + parentKeywordId: number | null; description: string; childrenKeywords: KeywordResponse[] | null; } diff --git a/frontend/src/pages/EssayAnswerListPage/index.tsx b/frontend/src/pages/EssayAnswerListPage/index.tsx index 88b4e1ea7..e348044cc 100644 --- a/frontend/src/pages/EssayAnswerListPage/index.tsx +++ b/frontend/src/pages/EssayAnswerListPage/index.tsx @@ -1,19 +1,27 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import EssayAnswerList from '../../components/Lists/EssayAnswerList'; -import MEDIA_QUERY from '../../constants/mediaQuery'; -import { useEssayAnswerList } from '../../hooks/EssayAnswer/useEssayAnswerList'; import { MainContentStyle } from '../../PageRouter'; +import EssayAnswerList from '../../components/Lists/QuizAnswerList'; +import MEDIA_QUERY from '../../constants/mediaQuery'; +import { useGetCurriculums } from '../../hooks/queries/curriculum'; +import { useGetEssayAnswers } from '../../hooks/queries/essayanswer'; import { AlignItemsCenterStyle, FlexStyle, JustifyContentSpaceBtwStyle } from '../../styles/flex.styles'; import { HeaderContainer, PostListContainer } from './styles'; +import { useLocation } from 'react-router'; const EssayAnswerListPage = () => { - const { quiz, essayAnswers } = useEssayAnswerList(); + const { curriculums } = useGetCurriculums(); + const { search } = useLocation(); + const searchParams = new URLSearchParams(search); + const curriculumId = Number(searchParams.get('curriculumId') ?? '1'); + const selectedCurriculum = (curriculums ?? []).find(curriculum => curriculum.id === curriculumId)?.name ?? '😎'; + + const { data: essayAnswers } = useGetEssayAnswers({ curriculumId }); return (
@@ -37,13 +45,13 @@ const EssayAnswerListPage = () => { font-size: 3.4rem; `} > - {!!quiz && quiz.question} 🤔 + {selectedCurriculum} 분야의 모든 로드맵 답변
{(!essayAnswers || essayAnswers.length === 0) && '작성된 글이 없습니다.'} - {!!essayAnswers && } + {!!essayAnswers && }
); diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js index 9ec9ad6e6..3002a138b 100644 --- a/frontend/src/pages/index.js +++ b/frontend/src/pages/index.js @@ -1,15 +1,16 @@ +export { default as EditEssayAnswerPage } from './EditEssayAnswerPage'; +export { default as EditStudylogPage } from './EditStudylogPage'; +export { default as EssayAnswerListPage } from './EssayAnswerListPage'; +export { default as EssayAnswerPage } from './EssayAnswerPage'; +export { default as LoginCallbackPage } from './LoginCallbackPage'; export { default as MainPage } from './MainPage'; -export { default as StudylogPage } from './StudylogPage'; +export { default as NewEssayAnswerPage } from './NewEssayAnswerPage'; export { default as NewStudylogPage } from './NewStudylogPage'; export { default as ProfilePage } from './ProfilePage'; -export { default as ProfilePageStudylogs } from './ProfilePageStudylogs'; -export { default as ProfilePageScraps } from './ProfilePageScraps'; export { default as ProfilePageAccount } from './ProfilePageAccount'; -export { default as LoginCallbackPage } from './LoginCallbackPage'; -export { default as EditStudylogPage } from './EditStudylogPage'; -export { default as StudylogListPage } from './StudylogListPage'; +export { default as ProfilePageScraps } from './ProfilePageScraps'; +export { default as ProfilePageStudylogs } from './ProfilePageStudylogs'; +export { default as QuizAnswerListPage } from './QuizAnswerListPage'; export { default as RoadmapPage } from './RoadmapPage'; -export { default as NewEssayAnswerPage } from './NewEssayAnswerPage'; -export { default as EssayAnswerPage } from './EssayAnswerPage'; -export { default as EssayAnswerListPage } from './EssayAnswerListPage'; -export { default as EditEssayAnswerPage } from './EditEssayAnswerPage'; +export { default as StudylogListPage } from './StudylogListPage'; +export { default as StudylogPage } from './StudylogPage'; From 1d45708dcc529a8b66bdbf62115359dba6cfad14 Mon Sep 17 00:00:00 2001 From: solo5star Date: Wed, 19 Jul 2023 08:53:50 +0000 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EB=AA=A9=EB=A1=9D=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?api=20=ED=86=B5=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: WaiNaat --- frontend/src/apis/essayanswers.ts | 65 +++++---- frontend/src/constants/reactQueryKey.js | 4 +- .../hooks/EssayAnswer/useEssayAnswerList.ts | 4 +- frontend/src/hooks/queries/essayanswer.ts | 126 +++++++++--------- frontend/src/routes.js | 34 +++-- 5 files changed, 129 insertions(+), 104 deletions(-) diff --git a/frontend/src/apis/essayanswers.ts b/frontend/src/apis/essayanswers.ts index 09a3180dc..f28444664 100644 --- a/frontend/src/apis/essayanswers.ts +++ b/frontend/src/apis/essayanswers.ts @@ -1,44 +1,57 @@ -import { EssayAnswerRequest, EssayEditRequest } from "../models/EssayAnswers"; -import { AxiosPromise, AxiosResponse } from "axios"; -import { Quiz } from "../models/Keywords"; -import { createAxiosInstance } from "../utils/axiosInstance"; +import { AxiosPromise, AxiosResponse } from 'axios'; +import { client } from '.'; +import { EssayAnswerListRequest, EssayAnswerRequest, EssayEditRequest } from '../models/EssayAnswers'; +import { Quiz } from '../models/Keywords'; + +export const requestGetEssayAnswers = async (params: EssayAnswerListRequest) => { + const { + quizIds, + memberIds, + ...otherParams + } = params; + + const axiosParams: Omit & { + quizIds?: string; + memberIds?: string; + } = { ...otherParams }; + + if (quizIds) axiosParams.quizIds = '[' + quizIds.join(',') + ']'; + if (memberIds) axiosParams.memberIds = '[' + memberIds.join(',') + ']'; + + const { data } = await client.get('/essay-answers', { + params: axiosParams, + }); -import LOCAL_STORAGE_KEY from "../constants/localStorage"; - -const anyoneInstance = createAxiosInstance(); -const loggedInUserInstance = createAxiosInstance({ - accessToken: localStorage.getItem(LOCAL_STORAGE_KEY.ACCESS_TOKEN) ?? '', -}); + return data; +}; -export const createNewEssayAnswerRequest = (body: EssayAnswerRequest) => ( - loggedInUserInstance.post(`/essay-answers`, body) -); +export const createNewEssayAnswerRequest = (body: EssayAnswerRequest) => + client.post(`/essay-answers`, body); -export const requestGetEssayAnswer = async (essayAnswerId) => { - const { data } = await anyoneInstance.get(`/essay-answers/${essayAnswerId}`); - return data +export const requestGetEssayAnswer = async (essayAnswerId: number) => { + const { data } = await client.get(`/essay-answers/${essayAnswerId}`); + return data; }; export const requestEditEssayAnswer = async (essayAnswerId: number, body: EssayEditRequest) => { - await loggedInUserInstance.patch(`/essay-answers/${essayAnswerId}`, body); + await client.patch(`/essay-answers/${essayAnswerId}`, body); }; -export const requestDeleteEssayAnswer = async (essayAnswerId) => { - await loggedInUserInstance.delete(`/essay-answers/${essayAnswerId}`); +export const requestDeleteEssayAnswer = async (essayAnswerId: number) => { + await client.delete(`/essay-answers/${essayAnswerId}`); }; -export const requestGetEssayAnswerList = async (quizId) => { - const { data } = await anyoneInstance.get(`/quizzes/${quizId}/essay-answers`); +export const requestGetQuizAnswers = async (quizId: number) => { + const { data } = await client.get(`/quizzes/${quizId}/essay-answers`); return data; }; -export const requestGetQuizAsync = async (quizId) => { - const { data } = await anyoneInstance.get(`/quizzes/${quizId}`); +export const requestGetQuizAsync = async (quizId: number) => { + const { data } = await client.get(`/quizzes/${quizId}`); return data; }; -export const requestGetQuiz = (quizId: Number): AxiosPromise> => ( - anyoneInstance.get>(`/quizzes/${quizId}`) -); +export const requestGetQuiz = (quizId: Number): AxiosPromise> => + client.get>(`/quizzes/${quizId}`); diff --git a/frontend/src/constants/reactQueryKey.js b/frontend/src/constants/reactQueryKey.js index d68ae254d..4f0d9cc86 100644 --- a/frontend/src/constants/reactQueryKey.js +++ b/frontend/src/constants/reactQueryKey.js @@ -1,8 +1,8 @@ const REACT_QUERY_KEY = { STUDYLOG: 'STUDYLOG', - QUIZ:'QUIZ', + QUIZ: 'QUIZ', ESSAY_ANSWER: 'ESSAY_ANSWER', - ESSAY_ANSWER_LIST: 'ESSAY_ANSWER_LIST', + QUIZ_ANSWERS: 'QUIZ_ANSWERS', }; export default REACT_QUERY_KEY; diff --git a/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts b/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts index d94a5285c..b2c702f64 100644 --- a/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts +++ b/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts @@ -1,10 +1,10 @@ -import { useGetEssayAnswerList, useGetQuiz } from '../queries/essayanswer'; +import { useGetQuizAnswerList, useGetQuiz } from '../queries/essayanswer'; import { useParams } from 'react-router-dom'; export const useEssayAnswerList = () => { const { quizId } = useParams<{ quizId: string }>(); - const { data: essayAnswers } = useGetEssayAnswerList({ quizId }); + const { data: essayAnswers } = useGetQuizAnswerList({ quizId }); const { data: quiz } = useGetQuiz({ quizId }) return { quiz, essayAnswers }; diff --git a/frontend/src/hooks/queries/essayanswer.ts b/frontend/src/hooks/queries/essayanswer.ts index 37e929aa7..f21b4b4f2 100644 --- a/frontend/src/hooks/queries/essayanswer.ts +++ b/frontend/src/hooks/queries/essayanswer.ts @@ -1,23 +1,27 @@ +import { AxiosError } from 'axios'; import { useMutation, useQuery } from 'react-query'; +import { useHistory } from 'react-router-dom'; import { createNewEssayAnswerRequest, requestDeleteEssayAnswer, requestEditEssayAnswer, requestGetEssayAnswer, - requestGetEssayAnswerList, - requestGetQuizAsync + requestGetEssayAnswers, + requestGetQuizAnswers, + requestGetQuizAsync, } from '../../apis/essayanswers'; -import { EssayAnswerRequest, EssayAnswerResponse } from '../../models/EssayAnswers'; - -import { AxiosError } from 'axios'; -import { useHistory } from 'react-router-dom'; +import { ResponseError } from '../../apis/studylogs'; import { ALERT_MESSAGE, PATH } from '../../constants'; import ERROR_CODE from '../../constants/errorCode'; -import useSnackBar from '../useSnackBar'; +import { ERROR_MESSAGE, SUCCESS_MESSAGE } from '../../constants/message'; import REACT_QUERY_KEY from '../../constants/reactQueryKey'; +import { + EssayAnswerFilterRequest, + EssayAnswerRequest, + EssayAnswerResponse, +} from '../../models/EssayAnswers'; import { Quiz } from '../../models/Keywords'; -import { ERROR_MESSAGE, SUCCESS_MESSAGE } from '../../constants/message'; -import { ResponseError } from '../../apis/studylogs'; +import useSnackBar from '../useSnackBar'; export const useCreateNewEssayAnswerMutation = ({ onSuccess = () => {}, @@ -32,51 +36,46 @@ export const useCreateNewEssayAnswerMutation = ({ }, }); -export const useEditEssayAnswer = ( - { essayAnswerId }: { essayAnswerId: number }, -) => { +export const useEditEssayAnswer = ({ essayAnswerId }: { essayAnswerId: number }) => { const history = useHistory(); - return useMutation( - (data: { answer: string }) => - requestEditEssayAnswer(essayAnswerId, data), - { - onSuccess: () => { - alert(SUCCESS_MESSAGE.EDIT_POST); - history.push(`/essay-answers/${essayAnswerId}`); - }, + return useMutation((data: { answer: string }) => requestEditEssayAnswer(essayAnswerId, data), { + onSuccess: () => { + alert(SUCCESS_MESSAGE.EDIT_POST); + history.push(`/essay-answers/${essayAnswerId}`); + }, - onError: (error: ResponseError) => { - alert(ERROR_MESSAGE[error.code] ?? ERROR_MESSAGE.FAIL_TO_EDIT_STUDYLOG); - }, - } - ); -} + onError: (error: ResponseError) => { + alert(ERROR_MESSAGE[error.code] ?? ERROR_MESSAGE.FAIL_TO_EDIT_STUDYLOG); + }, + }); +}; export const useGetEssayAnswer = ( { essayAnswerId }, - { - onSuccess = (essayAnswer: EssayAnswerResponse) => {}, - onError = () => {} - } = {} + { onSuccess = (essayAnswer: EssayAnswerResponse) => {}, onError = () => {} } = {} ) => { const history = useHistory(); const { openSnackBar } = useSnackBar(); - return useQuery([REACT_QUERY_KEY.ESSAY_ANSWER, essayAnswerId], () => requestGetEssayAnswer(essayAnswerId), { - onSuccess: (essayAnswer: EssayAnswerResponse) => { - onSuccess?.(essayAnswer); - }, - onError: (error) => { - const { response } = (error as unknown) as AxiosError; + return useQuery( + [REACT_QUERY_KEY.ESSAY_ANSWER, essayAnswerId], + () => requestGetEssayAnswer(essayAnswerId), + { + onSuccess: (essayAnswer: EssayAnswerResponse) => { + onSuccess?.(essayAnswer); + }, + onError: (error) => { + const { response } = (error as unknown) as AxiosError; - if (response?.data.code === ERROR_CODE.NO_CONTENT) { - openSnackBar(ALERT_MESSAGE.NO_EXIST_POST); - history.push(PATH.ROADMAP); - } - }, - refetchOnWindowFocus: false, - retry: false, - }); + if (response?.data.code === ERROR_CODE.NO_CONTENT) { + openSnackBar(ALERT_MESSAGE.NO_EXIST_POST); + history.push(PATH.ROADMAP); + } + }, + refetchOnWindowFocus: false, + retry: false, + } + ); }; export const useDeleteEssayAnswerMutation = ({ @@ -92,35 +91,38 @@ export const useDeleteEssayAnswerMutation = ({ }, }); -export const useGetEssayAnswerList = ( +export const useGetQuizAnswerList = ( { quizId }, - { - onSuccess = (essayAnswer: EssayAnswerResponse[]) => {}, - onError = () => {} - } = {} + { onSuccess = (essayAnswer: EssayAnswerResponse[]) => {}, onError = () => {} } = {} ) => { - return useQuery([REACT_QUERY_KEY.ESSAY_ANSWER_LIST, quizId], () => requestGetEssayAnswerList(quizId), { - onSuccess: (essayAnswer: EssayAnswerResponse[]) => { - onSuccess?.(essayAnswer); - }, - onError: (error) => {}, - refetchOnWindowFocus: false, - retry: false, - }); + return useQuery( + [REACT_QUERY_KEY.QUIZ_ANSWERS, quizId], + () => requestGetQuizAnswers(quizId), + { + onSuccess: (essayAnswer: EssayAnswerResponse[]) => { + onSuccess?.(essayAnswer); + }, + onError: (error) => {}, + refetchOnWindowFocus: false, + retry: false, + } + ); +}; + +export const useGetEssayAnswers = (filter: EssayAnswerFilterRequest) => { + return useQuery([REACT_QUERY_KEY.ESSAY_ANSWER_FILTER_LIST, filter], () => + requestGetEssayAnswers(filter) + ); }; export const useGetQuiz = ( { quizId }, - { - onSuccess = (quiz: Quiz) => {}, - onError = () => {} - } = {} + { onSuccess = (quiz: Quiz) => {}, onError = () => {} } = {} ) => { return useQuery([REACT_QUERY_KEY.QUIZ, quizId], () => requestGetQuizAsync(quizId), { onSuccess: (quiz: Quiz) => { onSuccess?.(quiz); }, - onError: (error) => {}, refetchOnWindowFocus: false, retry: false, }); diff --git a/frontend/src/routes.js b/frontend/src/routes.js index cab11fb6d..dc4951216 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -1,22 +1,27 @@ import { PATH, PROFILE_PAGE_MENU } from './constants'; import { + EditEssayAnswerPage, EditStudylogPage, + EssayAnswerListPage, + EssayAnswerPage, LoginCallbackPage, MainPage, + NewEssayAnswerPage, NewStudylogPage, - StudylogPage, ProfilePage, - ProfilePageStudylogs, ProfilePageScraps, - StudylogListPage, + ProfilePageStudylogs, + QuizAnswerListPage, RoadmapPage, - NewEssayAnswerPage, - EssayAnswerPage, - EssayAnswerListPage, - EditEssayAnswerPage + StudylogListPage, + StudylogPage, } from './pages'; const pageRoutes = [ + { + path: '/essay-answers', + render: () => , + }, { path: [PATH.ROADMAP], render: () => , @@ -25,13 +30,18 @@ const pageRoutes = [ path: [PATH.ROOT], render: () => , }, - { path: [PATH.LOGIN_CALLBACK], render: () => , }, - { path: [PATH.STUDYLOG], render: () => }, - { path: [PATH.NEW_STUDYLOG], render: () => }, + { + path: [PATH.STUDYLOG], + render: () => , + }, + { + path: [PATH.NEW_STUDYLOG], + render: () => , + }, { path: [`${PATH.STUDYLOG}/:id`], render: () => , @@ -70,12 +80,12 @@ const pageRoutes = [ }, { path: [PATH.ESSAY_ANSWER_LIST], - render: () => , + render: () => , }, { path: '/essay-answers/:id/edit', render: () => , - } + }, ]; export default pageRoutes; From 62b06a617411a2746fd10d2a1af6ecd0299bfdf0 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 20 Jul 2023 08:17:03 +0000 Subject: [PATCH 05/22] =?UTF-8?q?feat:=20=EB=AA=87=EB=AA=87=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=83=80=EC=9E=85=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=EB=A1=9C=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/DropdownMenu/DropdownMenu.js | 12 ------- ...nu.stories.js => DropdownMenu.stories.tsx} | 5 +-- ...nMenu.styles.js => DropdownMenu.styles.ts} | 6 ++-- .../components/DropdownMenu/DropdownMenu.tsx | 14 ++++++++ ...erList.styles.js => FilterList.styles.tsx} | 6 ++-- .../{FilterList.js => FilterList.tsx} | 4 ++- ...edFilterList.js => SelectedFilterList.tsx} | 0 .../src/components/SearchBar/SearchBar.js | 16 --------- ...earchBar.styles.js => SearchBar.styles.ts} | 4 ++- .../src/components/SearchBar/SearchBar.tsx | 34 +++++++++++++++++++ frontend/src/pages/StudylogListPage/index.tsx | 5 ++- 11 files changed, 66 insertions(+), 40 deletions(-) delete mode 100644 frontend/src/components/DropdownMenu/DropdownMenu.js rename frontend/src/components/DropdownMenu/{DropdownMenu.stories.js => DropdownMenu.stories.tsx} (78%) rename frontend/src/components/DropdownMenu/{DropdownMenu.styles.js => DropdownMenu.styles.ts} (89%) create mode 100644 frontend/src/components/DropdownMenu/DropdownMenu.tsx rename frontend/src/components/FilterList/{FilterList.styles.js => FilterList.styles.tsx} (96%) rename frontend/src/components/FilterList/{FilterList.js => FilterList.tsx} (96%) rename frontend/src/components/FilterList/{SelectedFilterList.js => SelectedFilterList.tsx} (100%) delete mode 100644 frontend/src/components/SearchBar/SearchBar.js rename frontend/src/components/SearchBar/{SearchBar.styles.js => SearchBar.styles.ts} (78%) create mode 100644 frontend/src/components/SearchBar/SearchBar.tsx diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.js b/frontend/src/components/DropdownMenu/DropdownMenu.js deleted file mode 100644 index 0065f8a93..000000000 --- a/frontend/src/components/DropdownMenu/DropdownMenu.js +++ /dev/null @@ -1,12 +0,0 @@ -import PropTypes from 'prop-types'; -import { Container } from './DropdownMenu.styles'; - -const DropdownMenu = ({ children, cssProps, css }) => { - return {children}; -}; - -DropdownMenu.propTypes = { - children: PropTypes.node, -}; - -export default DropdownMenu; diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.stories.js b/frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx similarity index 78% rename from frontend/src/components/DropdownMenu/DropdownMenu.stories.js rename to frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx index 784c59559..0fddab5a8 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.stories.js +++ b/frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -1,12 +1,13 @@ import DropdownMenu from './DropdownMenu'; +import { Story, Meta } from '@storybook/react'; export default { title: 'Component/DropdownMenu', component: DropdownMenu, argTypes: { children: { control: 'text' } }, -}; +} as Meta; -const Template = (args) => ; +const Template: Story = (args) => ; export const Basic = Template.bind({}); diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.styles.js b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts similarity index 89% rename from frontend/src/components/DropdownMenu/DropdownMenu.styles.js rename to frontend/src/components/DropdownMenu/DropdownMenu.styles.ts index 34d7bae60..9538e5d0e 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.styles.js +++ b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts @@ -1,7 +1,9 @@ +import { InterpolationWithTheme } from '@emotion/core'; +import { Theme } from '@emotion/react'; import styled from '@emotion/styled'; import COLOR from '../../constants/color'; -const Container = styled.div` +const Container = styled.div<{ css?: InterpolationWithTheme }>` height: fit-content; max-height: 32rem; white-space: nowrap; @@ -16,7 +18,7 @@ const Container = styled.div` /* transform: translateY(30%); */ && { - ${(props) => props.css} + ${({ css }) => css} } /* &:before { diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.tsx b/frontend/src/components/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 000000000..ffd785d40 --- /dev/null +++ b/frontend/src/components/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,14 @@ +import { Theme } from '@emotion/react'; +import { InterpolationWithTheme } from '@emotion/core'; +import { PropsWithChildren } from 'react'; +import { Container } from './DropdownMenu.styles'; + +type DropdownMenuProps = PropsWithChildren<{ + css?: InterpolationWithTheme; +}> + +const DropdownMenu = ({ children, css }: DropdownMenuProps) => { + return {children}; +}; + +export default DropdownMenu; diff --git a/frontend/src/components/FilterList/FilterList.styles.js b/frontend/src/components/FilterList/FilterList.styles.tsx similarity index 96% rename from frontend/src/components/FilterList/FilterList.styles.js rename to frontend/src/components/FilterList/FilterList.styles.tsx index 1dead8d96..36721f98f 100644 --- a/frontend/src/components/FilterList/FilterList.styles.js +++ b/frontend/src/components/FilterList/FilterList.styles.tsx @@ -18,7 +18,7 @@ const DropdownToggledStyle = css` } `; -const Container = styled.div` +const Container = styled.div<{ isDropdownToggled: boolean }>` background-color: ${COLOR.LIGHT_GRAY_50}; border: 1px solid ${COLOR.DARK_GRAY_400}; @@ -30,7 +30,7 @@ const Container = styled.div` align-items: center; ${({ isDropdownToggled }) => isDropdownToggled && DropdownToggledStyle} - ${({ css }) => css && css} + ${({ css }) => css} & > div:not(:last-child) { margin-right: 3.2rem; @@ -145,7 +145,7 @@ const ResetFilter = styled.div` flex-shrink: 0; `; -const CheckIcon = styled.img` +const CheckIcon = styled.img<{ checked: boolean }>` ${({ checked }) => !checked && 'visibility: hidden;'} `; diff --git a/frontend/src/components/FilterList/FilterList.js b/frontend/src/components/FilterList/FilterList.tsx similarity index 96% rename from frontend/src/components/FilterList/FilterList.js rename to frontend/src/components/FilterList/FilterList.tsx index 0c2269c84..925d12b6d 100644 --- a/frontend/src/components/FilterList/FilterList.js +++ b/frontend/src/components/FilterList/FilterList.tsx @@ -1,3 +1,5 @@ +/** @jsxImportSource @emotion/react */ + import { useState } from 'react'; import { DropdownMenu } from '..'; import SearchBar from '../SearchBar/SearchBar'; @@ -81,7 +83,7 @@ const FilterList = ({ setSearchKeyword(target.value)} + onChange={(value) => setSearchKeyword(value)} value={searchKeyword} /> diff --git a/frontend/src/components/FilterList/SelectedFilterList.js b/frontend/src/components/FilterList/SelectedFilterList.tsx similarity index 100% rename from frontend/src/components/FilterList/SelectedFilterList.js rename to frontend/src/components/FilterList/SelectedFilterList.tsx diff --git a/frontend/src/components/SearchBar/SearchBar.js b/frontend/src/components/SearchBar/SearchBar.js deleted file mode 100644 index e7f2c2cb3..000000000 --- a/frontend/src/components/SearchBar/SearchBar.js +++ /dev/null @@ -1,16 +0,0 @@ -import Button from '../Button/Button'; -import SearchIcon from '../../assets/images/search_icon.svg'; -import { Container } from './SearchBar.styles'; - -const SearchBar = ({ css, onSubmit, onChange, value }) => { - return ( -
- - - {onSubmit && + {searchKeyword === item && ( + + + + )} + + ); + })} + + {Object.keys(params).length !== 0 && } +
+ ); +}; + +export default RoadmapFilter; From 824ac6a6c8b5990f56dc548e54422f2b70d46353 Mon Sep 17 00:00:00 2001 From: solo5star Date: Fri, 6 Oct 2023 08:26:17 +0000 Subject: [PATCH 07/22] =?UTF-8?q?feat:=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EB=8B=A4=EC=9A=B4=20sheet=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RoadmapFilter/RoadmapSelectedFilter.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx diff --git a/frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx b/frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx new file mode 100644 index 000000000..11839af55 --- /dev/null +++ b/frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx @@ -0,0 +1,18 @@ +interface RoadmapSelectedFilterProps { + itemName: string; + data: string[]; + handleFilter: (filterName, filterItem) => void; +} + +const RoadmapSelectedFilter = ({ itemName, data, handleFilter }: RoadmapSelectedFilterProps) => { + return ( +
    + {data && + data.map((item) => { + return
  • handleFilter(itemName, item)}>{item}
  • ; + })} +
+ ); +}; + +export default RoadmapSelectedFilter; From 4b9eeb7a192fb81244efb1271a2e4d4effbbe1ff Mon Sep 17 00:00:00 2001 From: solo5star Date: Fri, 6 Oct 2023 08:27:45 +0000 Subject: [PATCH 08/22] =?UTF-8?q?refactor:=20essayAnswerHandler=EC=97=90?= =?UTF-8?q?=20=EC=9E=98=EB=AA=BB=20=EC=A0=81=ED=9E=8C=20params=20=EA=B0=92?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/mocks/handlers/essayAnswers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/mocks/handlers/essayAnswers.ts b/frontend/src/mocks/handlers/essayAnswers.ts index 1a4f376cd..01fbb2bb2 100644 --- a/frontend/src/mocks/handlers/essayAnswers.ts +++ b/frontend/src/mocks/handlers/essayAnswers.ts @@ -25,9 +25,9 @@ export const essayAnswerHandler = [ ctx.json( essayAnswersMock.filter({ curriculumId: curriculumId, - keywordId: params.curriculumId ? Number(params.curriculumId) : undefined, - quizIds: params.curriculumId - ? params.curriculumId.slice(1, -1).split(',').map(Number) ?? [] + keywordId: params.keywordId ? Number(params.keywordId) : undefined, + quizIds: params.quizIds + ? params.quizIds.slice(1, -1).split(',').map(Number) ?? [] : undefined, memberIds: params.memberIds ? params.memberIds.slice(1, -1).split(',').map(Number) ?? [] From 6a3fa9dc006cc505c0665365fcebb490bca23dca Mon Sep 17 00:00:00 2001 From: solo5star Date: Fri, 6 Oct 2023 08:28:45 +0000 Subject: [PATCH 09/22] =?UTF-8?q?refactor:=20=EC=83=88=EB=A1=9C=EC=9A=B4?= =?UTF-8?q?=20mock=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20keywordId=20=ED=83=80=EC=9E=85=20=EA=B0=80=EB=93=9C?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/mocks/fixtures/essayAnswers.ts | 47 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/frontend/src/mocks/fixtures/essayAnswers.ts b/frontend/src/mocks/fixtures/essayAnswers.ts index debf18cab..cffc63b6e 100644 --- a/frontend/src/mocks/fixtures/essayAnswers.ts +++ b/frontend/src/mocks/fixtures/essayAnswers.ts @@ -39,7 +39,7 @@ const data: Array<{ }, { id: 69, - keywordId: 1, + keywordId: 2, quiz: { quizId: 29, question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', @@ -58,6 +58,49 @@ const data: Array<{ }, ], }, + { + curriculumId: 2, + essayAnswers: [ + { + id: 71, + keywordId: 1, + quiz: { + quizId: 29, + question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', + }, + answer: + '수를 가변적으로 조절할 수 있게 해주는 기능이다.\n
\n\n## 사용법\n***\nparameter를 여러개 정의하고 싶은 method를 선언할 때, parameter 부분에 ...을 붙이면 된다.\n\n```java\npublic int calculateSum(int... numbers) {\n return Arrays.stream(numbers).sum();\n}\n```\n\n위 코드에서 유추할 수 있듯이, varargs는 내부적으로 array를 반환한다.\ncompile 시에 Object array가 만들어지고, varargs에 할당된 인자들이 array의 원소로 들어간다.\n
\n\n## 장점\n***\n만약 위의 method에서, varargs를 사용하지 않는다고 가정해보자.\n그렇다면, parameter가 2개, 3개, 4개, ..., 인 calculateSum() method를 전부 overloading해야 할 것이다.\n\n```java\npublic int getSum(int number1, int number2) {\n\treturn number1 + number2;\n}\npublic int getSum(int number1, int number2, int number3) {\n\treturn number1 + number2 + number3;\n}\n\n...\n\n\n```\n\nvarargs를 사용하면 이러한 overloading을 하지 않도록 할 수 있다.', + author: { + id: 304, + username: 'Jaeyoung22', + nickname: 'ReO', + role: 'CREW', + imageUrl: 'https://avatars.githubusercontent.com/u/89302528?v=4', + }, + createdAt: '2023-04-09T17:56:25.642891', + updatedAt: '2023-04-09T17:56:25.642891', + }, + { + id: 69, + keywordId: 1, + quiz: { + quizId: 29, + question: 'Vara하고, 사용했을 때 장점은 무엇인가요?', + }, + answer: + '# Va습니다.\n이를 해결하기 위해선 두 가지 방식을 사용했습니다.\n1. 메서드 오버로딩\n이 방법은 코드의 길이를 무한대로 증가시킵니다.\n2. 전달할 값들을 배열로 받기\n\n결국, Varargs는 **다양한 범위의 인자를 받기 위한 해결책**으로 탄생하게 되었습니다.\n\n# Varargs 사용법 및 특징\n\n```java\npublic class VarargsPractice {\n public static void main(String[] args) {\n printValues(); // 특징 1. 0개 이상의 인자를 전달할 수 있습니다.\n printValues("1");\n printValues("1", "2");\n printValues("1", "2", "3");\n printValues(new String[]{"1", "2", "3"}); // 특징 2. 실제로는 배열로 동작합니다.\n }\n\n // 사용법: 파라미터에 타입과 ... 을 함께 작성합니다. (String...)\n public static void printValues(final String... values) {\n for (int i = 0; i < values.length; i++) {\n System.out.println(values[i]); // 배열로 동작하기에 인덱스로 접근 가능합니다.\n }\n }\n}\n```\n\n# 잘못된 Varargs 사용법\n\n1. 한 메서드에 2가지 타입의 가변인자(varargs)는 사용하지 못합니다.\n```java\nvoid method(String... inputs, int... numbers);\n```\n\n2. 가변인자를 첫 번째 파라미터로 선언하지 못합니다.\n```java\nvoid method(int... numbers, String input);\n```\n\n# Varargs의 장점\n\n1. 메서드 인자의 수를 동적으로 결정 및 처리할 수 있습니다.\n2. 코드를 간결하게 유지할 수 있습니다.\n\n# Varargs의 단점\n\n1. 공간 복잡도가 증가합니다.\n가변 인자는 곧, 배열입니다.\n가변 인자를 전달받은 메서드는 매번 새로운 배열을 생성해야 합니다.\n\n# Varargs를 사용하는 기준\n(개인적인 기준입니다.)\n\n1. 인자의 개수를 다양하게 받아야 하는 상황이다.\n2. 성능상 그다지 중요하지 않은 상황이다.', + author: { + id: 287, + username: 'kdkdhoho', + nickname: '도기\uD83D\uDC36', + role: 'CREW', + imageUrl: 'https://avatars.githubusercontent.com/u/66300965?v=4', + }, + createdAt: '2023-04-08T23:54:48.667266', + updatedAt: '2023-04-08T23:54:48.667266', + }, + ], + }, ]; const essayAnswersMock = { @@ -72,7 +115,7 @@ const essayAnswersMock = { return essayAnswers .filter((essayAnswer) => { - if (essayAnswer.keywordId !== keywordId) return false; + if (keywordId && essayAnswer.keywordId !== keywordId) return false; if (memberIds && !memberIds.includes(essayAnswer.author.id)) return false; From e6877ca1d1a46c8e8149ab4393a99a4365a7fc77 Mon Sep 17 00:00:00 2001 From: solo5star Date: Fri, 6 Oct 2023 08:29:13 +0000 Subject: [PATCH 10/22] =?UTF-8?q?refactor:=20quizIds,=20memberIds=20join?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apis/essayanswers.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/apis/essayanswers.ts b/frontend/src/apis/essayanswers.ts index f28444664..b2720fd61 100644 --- a/frontend/src/apis/essayanswers.ts +++ b/frontend/src/apis/essayanswers.ts @@ -1,22 +1,22 @@ import { AxiosPromise, AxiosResponse } from 'axios'; import { client } from '.'; -import { EssayAnswerListRequest, EssayAnswerRequest, EssayEditRequest } from '../models/EssayAnswers'; +import { + EssayAnswerListRequest, + EssayAnswerRequest, + EssayEditRequest, +} from '../models/EssayAnswers'; import { Quiz } from '../models/Keywords'; export const requestGetEssayAnswers = async (params: EssayAnswerListRequest) => { - const { - quizIds, - memberIds, - ...otherParams - } = params; + const { quizIds, memberIds, ...otherParams } = params; const axiosParams: Omit & { quizIds?: string; memberIds?: string; } = { ...otherParams }; - if (quizIds) axiosParams.quizIds = '[' + quizIds.join(',') + ']'; - if (memberIds) axiosParams.memberIds = '[' + memberIds.join(',') + ']'; + if (quizIds) axiosParams.quizIds = quizIds.join(','); + if (memberIds) axiosParams.memberIds = memberIds.join(','); const { data } = await client.get('/essay-answers', { params: axiosParams, From 6563330f4481b51c46d6025c7f94fffb9dfc102d Mon Sep 17 00:00:00 2001 From: solo5star Date: Fri, 6 Oct 2023 08:29:46 +0000 Subject: [PATCH 11/22] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A4=80=EC=9D=B4=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20filter=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A8?= =?UTF-8?q?=EB=91=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/EssayAnswerListPage/index.tsx | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/EssayAnswerListPage/index.tsx b/frontend/src/pages/EssayAnswerListPage/index.tsx index e348044cc..aa4bc28c8 100644 --- a/frontend/src/pages/EssayAnswerListPage/index.tsx +++ b/frontend/src/pages/EssayAnswerListPage/index.tsx @@ -1,27 +1,46 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; +import { useState } from 'react'; +import { useLocation } from 'react-router'; import { MainContentStyle } from '../../PageRouter'; import EssayAnswerList from '../../components/Lists/QuizAnswerList'; +import RoadmapFilter from '../../components/RoadmapFilter/RoadmapFilter'; import MEDIA_QUERY from '../../constants/mediaQuery'; import { useGetCurriculums } from '../../hooks/queries/curriculum'; import { useGetEssayAnswers } from '../../hooks/queries/essayanswer'; import { AlignItemsCenterStyle, FlexStyle, - JustifyContentSpaceBtwStyle + JustifyContentSpaceBtwStyle, } from '../../styles/flex.styles'; import { HeaderContainer, PostListContainer } from './styles'; -import { useLocation } from 'react-router'; + +export interface FilterlingType { + curriculumId: number; + keywordId: number; + quizIds: number[]; + memberIds: number[]; +} const EssayAnswerListPage = () => { const { curriculums } = useGetCurriculums(); const { search } = useLocation(); - const searchParams = new URLSearchParams(search); + const [searchKeyword, setSearchKeyword] = useState(''); + const searchParams = new URLSearchParams(search); // keywordId quizIds memberIds const curriculumId = Number(searchParams.get('curriculumId') ?? '1'); - const selectedCurriculum = (curriculums ?? []).find(curriculum => curriculum.id === curriculumId)?.name ?? '😎'; + const keywordId = Number(searchParams.get('keywordId') ?? undefined); + const quizIds = searchParams.get('quizIds')?.split(',').map(Number) ?? undefined; + const memberIds = searchParams.get('memberIds')?.split(',').map(Number) ?? undefined; + const selectedCurriculum = + (curriculums ?? []).find((curriculum) => curriculum.id === curriculumId)?.name ?? '😎'; - const { data: essayAnswers } = useGetEssayAnswers({ curriculumId }); + const { data: essayAnswers } = useGetEssayAnswers({ + curriculumId, + keywordId, + quizIds, + memberIds, + }); return (
@@ -49,6 +68,8 @@ const EssayAnswerListPage = () => {
+ + {(!essayAnswers || essayAnswers.length === 0) && '작성된 글이 없습니다.'} {!!essayAnswers && } From 338306bd8b3b3cc89327f336fcaee215d36868ec Mon Sep 17 00:00:00 2001 From: solo5star Date: Wed, 11 Oct 2023 08:07:24 +0000 Subject: [PATCH 12/22] refactor: Rename files --- frontend/src/mocks/fixtures/{quizs.ts => quizzes.ts} | 0 frontend/src/mocks/fixtures/{keywords.ts => roadmap.ts} | 2 +- frontend/src/mocks/handlers/index.ts | 2 +- frontend/src/mocks/handlers/{keywords.ts => roadmap.ts} | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename frontend/src/mocks/fixtures/{quizs.ts => quizzes.ts} (100%) rename frontend/src/mocks/fixtures/{keywords.ts => roadmap.ts} (99%) rename frontend/src/mocks/handlers/{keywords.ts => roadmap.ts} (95%) diff --git a/frontend/src/mocks/fixtures/quizs.ts b/frontend/src/mocks/fixtures/quizzes.ts similarity index 100% rename from frontend/src/mocks/fixtures/quizs.ts rename to frontend/src/mocks/fixtures/quizzes.ts diff --git a/frontend/src/mocks/fixtures/keywords.ts b/frontend/src/mocks/fixtures/roadmap.ts similarity index 99% rename from frontend/src/mocks/fixtures/keywords.ts rename to frontend/src/mocks/fixtures/roadmap.ts index 67c7e33f3..0817adeb4 100644 --- a/frontend/src/mocks/fixtures/keywords.ts +++ b/frontend/src/mocks/fixtures/roadmap.ts @@ -1,5 +1,5 @@ import { KeywordResponse, Quiz } from '../../models/Keywords'; -import quizMock from './quizs'; +import quizMock from './quizzes'; type WithSession = T & { sessionId: number; diff --git a/frontend/src/mocks/handlers/index.ts b/frontend/src/mocks/handlers/index.ts index 65d0d384a..c9183fd4b 100644 --- a/frontend/src/mocks/handlers/index.ts +++ b/frontend/src/mocks/handlers/index.ts @@ -1,6 +1,6 @@ import { commentsHandler } from './comment'; import { essayAnswerHandler } from './essayAnswers'; -import { roadmapHandler } from './keywords'; +import { roadmapHandler } from './roadmap'; import { levellogHandler } from './levellog'; import { popularStudyLogHandler } from './popularStudyLog'; diff --git a/frontend/src/mocks/handlers/keywords.ts b/frontend/src/mocks/handlers/roadmap.ts similarity index 95% rename from frontend/src/mocks/handlers/keywords.ts rename to frontend/src/mocks/handlers/roadmap.ts index 396f5bef8..8630772a6 100644 --- a/frontend/src/mocks/handlers/keywords.ts +++ b/frontend/src/mocks/handlers/roadmap.ts @@ -1,8 +1,8 @@ import { rest } from 'msw'; import { BASE_URL } from '../../configs/environment'; import curriculums from '../fixtures/curriculums'; -import keywordsMock from '../fixtures/keywords'; -import quizMock from '../fixtures/quizs'; +import keywordsMock from '../fixtures/roadmap'; +import quizMock from '../fixtures/quizzes'; import { sessionsMock } from '../fixtures/sessions'; export const roadmapHandler = [ From 8a8ce148516bdd33fa8ff12e9efa1c4ec840e303 Mon Sep 17 00:00:00 2001 From: wonyongChoi05 Date: Fri, 6 Oct 2023 17:36:40 +0900 Subject: [PATCH 13/22] =?UTF-8?q?refactor:=20MemberGroup=EC=9D=84=20Depart?= =?UTF-8?q?ment=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../steps/GroupMemberStepDefinitions.java | 37 +++--- .../prolog/docu/StudylogDocumentation.java | 28 ++-- .../application/GroupMemberService.java | 11 +- .../{MemberGroup.java => Department.java} | 28 ++-- ...GroupMember.java => DepartmentMember.java} | 10 +- .../prolog/member/domain/Departments.java | 16 +++ .../prolog/member/domain/MemberGroupType.java | 23 ---- .../prolog/member/domain/MemberGroups.java | 16 --- .../wooteco/prolog/member/domain/Part.java | 32 +++++ .../wooteco/prolog/member/domain/Term.java | 19 +++ .../DepartmentMemberRepository.java | 14 ++ .../repository/DepartmentRepository.java | 8 ++ .../repository/GroupMemberRepository.java | 14 -- .../repository/MemberGroupRepository.java | 8 -- .../application/PopularStudylogService.java | 56 ++++---- ....java => DepartmentMemberServiceTest.java} | 24 ++-- .../prolog/member/domain/DepartmentTest.java | 32 +++++ .../member/domain/DepartmentTypeTest.java | 31 +++++ .../prolog/member/domain/MemberGroupTest.java | 32 ----- .../member/domain/MemberGroupTypeTest.java | 31 ----- .../DepartmentMemberRepositoryTest.java | 44 +++++++ .../repository/GroupMemberRepositoryTest.java | 44 ------- .../PopularStudylogServiceTest.java | 120 +++++++++--------- 23 files changed, 349 insertions(+), 329 deletions(-) rename backend/src/main/java/wooteco/prolog/member/domain/{MemberGroup.java => Department.java} (51%) rename backend/src/main/java/wooteco/prolog/member/domain/{GroupMember.java => DepartmentMember.java} (75%) create mode 100644 backend/src/main/java/wooteco/prolog/member/domain/Departments.java delete mode 100644 backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java delete mode 100644 backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java create mode 100644 backend/src/main/java/wooteco/prolog/member/domain/Part.java create mode 100644 backend/src/main/java/wooteco/prolog/member/domain/Term.java create mode 100644 backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java create mode 100644 backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java delete mode 100644 backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java delete mode 100644 backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java rename backend/src/test/java/wooteco/prolog/member/application/{GroupMemberServiceTest.java => DepartmentMemberServiceTest.java} (55%) create mode 100644 backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java create mode 100644 backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java delete mode 100644 backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java delete mode 100644 backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java create mode 100644 backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java delete mode 100644 backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java diff --git a/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java b/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java index 1747c700e..6c56bcf73 100644 --- a/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java +++ b/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java @@ -2,37 +2,38 @@ import io.cucumber.java.en.Given; import wooteco.prolog.AcceptanceSteps; -import wooteco.prolog.member.domain.GroupMember; +import wooteco.prolog.member.domain.DepartmentMember; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.Department; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.member.domain.repository.MemberRepository; public class GroupMemberStepDefinitions extends AcceptanceSteps { private final MemberRepository memberRepository; - private final MemberGroupRepository memberGroupRepository; - private final GroupMemberRepository groupMemberRepository; + private final DepartmentRepository departmentRepository; + private final DepartmentMemberRepository departmentMemberRepository; public GroupMemberStepDefinitions(MemberRepository memberRepository, - MemberGroupRepository memberGroupRepository, - GroupMemberRepository groupMemberRepository) { + DepartmentRepository departmentRepository, + DepartmentMemberRepository departmentMemberRepository) { this.memberRepository = memberRepository; - this.memberGroupRepository = memberGroupRepository; - this.groupMemberRepository = groupMemberRepository; + this.departmentRepository = departmentRepository; + this.departmentMemberRepository = departmentMemberRepository; } @Given("{string}을 멤버그룹과 그룹멤버에 등록하고") public void 그룹멤버를_생성하고(String title) { Member member = memberRepository.findById(1L).get(); - MemberGroup 프론트엔드 = memberGroupRepository.save( - new MemberGroup(null, "4기 프론트엔드", "4기 프론트엔드 설명")); - MemberGroup 백엔드 = memberGroupRepository.save(new MemberGroup(null, "4기 백엔드", "4기 백엔드 설명")); - MemberGroup 안드로이드 = memberGroupRepository.save( - new MemberGroup(null, "4기 안드로이드", "4기 안드로이드 설명")); - groupMemberRepository.save(new GroupMember(null, member, 백엔드)); - groupMemberRepository.save(new GroupMember(null, member, 프론트엔드)); - groupMemberRepository.save(new GroupMember(null, member, 안드로이드)); + Department 프론트엔드 = departmentRepository.save( + new Department(null, "프론트엔드", "4기")); + Department 백엔드 = departmentRepository.save(new Department(null, "백엔드", "4기")); + Department 안드로이드 = departmentRepository.save( + new Department(null, "안드로이드", "4기")); + departmentMemberRepository.save(new DepartmentMember(null, member, 백엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 프론트엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 안드로이드)); } + } diff --git a/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java b/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java index 96e3341c5..d16f1d992 100644 --- a/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java +++ b/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java @@ -16,11 +16,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import wooteco.prolog.Documentation; -import wooteco.prolog.member.domain.GroupMember; +import wooteco.prolog.member.domain.DepartmentMember; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.Department; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.member.domain.repository.MemberRepository; import wooteco.prolog.session.application.dto.MissionRequest; import wooteco.prolog.session.application.dto.MissionResponse; @@ -37,9 +37,9 @@ class StudylogDocumentation extends Documentation { @Autowired private MemberRepository memberRepository; @Autowired - private MemberGroupRepository memberGroupRepository; + private DepartmentRepository departmentRepository; @Autowired - private GroupMemberRepository groupMemberRepository; + private DepartmentMemberRepository departmentMemberRepository; @Test void 스터디로그를_생성한다() { @@ -312,14 +312,14 @@ private StudylogRequest createStudylogRequest3() { private void 회원과_멤버그룹_그룹멤버를_등록함() { Member member = memberRepository.findById(1L).get(); - MemberGroup 프론트엔드 = memberGroupRepository.save( - new MemberGroup(null, "4기 프론트엔드", "4기 프론트엔드 설명")); - MemberGroup 백엔드 = memberGroupRepository.save(new MemberGroup(null, "4기 백엔드", "4기 백엔드 설명")); - MemberGroup 안드로이드 = memberGroupRepository.save( - new MemberGroup(null, "4기 안드로이드", "4기 안드로이드 설명")); - groupMemberRepository.save(new GroupMember(null, member, 백엔드)); - groupMemberRepository.save(new GroupMember(null, member, 프론트엔드)); - groupMemberRepository.save(new GroupMember(null, member, 안드로이드)); + Department 프론트엔드 = departmentRepository.save( + new Department(null, "프론트엔드", "4기")); + Department 백엔드 = departmentRepository.save(new Department(null, "백엔드", "4기")); + Department 안드로이드 = departmentRepository.save( + new Department(null, "안드로이드", "4기")); + departmentMemberRepository.save(new DepartmentMember(null, member, 백엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 프론트엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 안드로이드)); } private Long 미션_등록함(MissionRequest request) { diff --git a/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java b/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java index cd50ced04..f673c03f5 100644 --- a/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java +++ b/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java @@ -4,17 +4,18 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; +import wooteco.prolog.member.domain.DepartmentMember; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; @Service @AllArgsConstructor @Transactional(readOnly = true) public class GroupMemberService { - private GroupMemberRepository groupMemberRepository; + private DepartmentMemberRepository departmentMemberRepository; - public List findGroupMemberByGroupId(Long groupId) { - return groupMemberRepository.findByGroupId(groupId); + public List findGroupMemberByGroupId(Long groupId) { + return departmentMemberRepository.findByDepartmentId(groupId); } + } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroup.java b/backend/src/main/java/wooteco/prolog/member/domain/Department.java similarity index 51% rename from backend/src/main/java/wooteco/prolog/member/domain/MemberGroup.java rename to backend/src/main/java/wooteco/prolog/member/domain/Department.java index f45b9dc3f..0e802847e 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroup.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/Department.java @@ -9,39 +9,33 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import wooteco.prolog.common.exception.BadRequestCode; import wooteco.prolog.common.exception.BadRequestException; @Entity -@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class MemberGroup { - - private static final String BACKEND = "백엔드"; - private static final String FRONTEND = "프론트엔드"; - private static final String ANDROID = "안드로이드"; +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String name; + private Part part; - private String description; + private Term term; - public MemberGroup(Long id, String name, String description) { + public Department(Long id, String part, String term) { this.id = id; - this.name = name; - this.description = description; + this.part = Part.valueOf(part); + this.term = Term.valueOf(term); } - public MemberGroupType groupType() { - for (MemberGroupType groupType : MemberGroupType.values()) { - if (groupType.isContainedBy(this.name)) { - return groupType; + public Part getPart() { + for (Part part : Part.values()) { + if (part.isContainedBy(this.part.getName())) { + return part; } } throw new BadRequestException(CANT_FIND_GROUP_TYPE); } - } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/GroupMember.java b/backend/src/main/java/wooteco/prolog/member/domain/DepartmentMember.java similarity index 75% rename from backend/src/main/java/wooteco/prolog/member/domain/GroupMember.java rename to backend/src/main/java/wooteco/prolog/member/domain/DepartmentMember.java index 3c6797a56..690ecad41 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/GroupMember.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/DepartmentMember.java @@ -14,7 +14,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class GroupMember { +public class DepartmentMember { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -25,12 +25,12 @@ public class GroupMember { private Member member; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "group_id", nullable = false) - private MemberGroup group; + @JoinColumn(name = "department_id", nullable = false) + private Department department; - public GroupMember(Long id, Member member, MemberGroup group) { + public DepartmentMember(Long id, Member member, Department department) { this.id = id; this.member = member; - this.group = group; + this.department = department; } } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Departments.java b/backend/src/main/java/wooteco/prolog/member/domain/Departments.java new file mode 100644 index 000000000..5ac424066 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Departments.java @@ -0,0 +1,16 @@ +package wooteco.prolog.member.domain; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class Departments { + + private List values; + + public boolean isContainsDepartments(DepartmentMember departmentMember) { + return values.contains(departmentMember.getDepartment()); + } +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java b/backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java deleted file mode 100644 index c17b9e9ae..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java +++ /dev/null @@ -1,23 +0,0 @@ -package wooteco.prolog.member.domain; - -import lombok.Getter; - -@Getter -public enum MemberGroupType { - ANDROID("안드로이드"), - BACKEND("백엔드"), - FRONTEND("프론트엔드"); - - private final String groupName; - - MemberGroupType(String groupName) { - this.groupName = groupName; - } - - public boolean isContainedBy(String value) { - if (value == null) { - return false; - } - return value.contains(this.groupName); - } -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java b/backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java deleted file mode 100644 index 9c9424779..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java +++ /dev/null @@ -1,16 +0,0 @@ -package wooteco.prolog.member.domain; - -import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public class MemberGroups { - - private List values; - - public boolean isContainsMemberGroups(GroupMember groupMember) { - return values.contains(groupMember.getGroup()); - } -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Part.java b/backend/src/main/java/wooteco/prolog/member/domain/Part.java new file mode 100644 index 000000000..95d0ac946 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Part.java @@ -0,0 +1,32 @@ +package wooteco.prolog.member.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum Part { + + BACKEND("백엔드"), + FRONTEND("프론트엔드"), + ANDROID("안드로이드"); + + private final String name; + + public boolean isContainedBy(String name) { + if (name == null) { + return false; + } + return Arrays.stream(values()) + .anyMatch(p -> p.name.equals(name)); + } + + public static Part getPartByName(String name) { + return Arrays.stream(values()) + .filter(part -> part.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("name과 일치하는 part가 존재하지 않습니다.")); + } +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Term.java b/backend/src/main/java/wooteco/prolog/member/domain/Term.java new file mode 100644 index 000000000..ec6f517b7 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Term.java @@ -0,0 +1,19 @@ +package wooteco.prolog.member.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Term { + + FIRST("1기"), + SECOND("2기"), + THIRD("3기"), + FOURTH("4기"), + FIFTH("5기"), + SIXTH("6기"); + + private final String name; + +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java new file mode 100644 index 000000000..b574f4386 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java @@ -0,0 +1,14 @@ +package wooteco.prolog.member.domain.repository; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import wooteco.prolog.member.domain.DepartmentMember; +import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Department; + +public interface DepartmentMemberRepository extends JpaRepository { + + List findByDepartmentId(Long departmentId); + + boolean existsDepartmentMemberByMemberAndDepartment(Member member, Department department); +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java new file mode 100644 index 000000000..08314f19d --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java @@ -0,0 +1,8 @@ +package wooteco.prolog.member.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import wooteco.prolog.member.domain.Department; + +public interface DepartmentRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java deleted file mode 100644 index 5b2c76f1d..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package wooteco.prolog.member.domain.repository; - -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; - -public interface GroupMemberRepository extends JpaRepository { - - List findByGroupId(Long groupId); - - boolean existsGroupMemberByMemberAndGroup(Member member, MemberGroup memberGroup); -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java deleted file mode 100644 index c98165eb2..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package wooteco.prolog.member.domain.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import wooteco.prolog.member.domain.MemberGroup; - -public interface MemberGroupRepository extends JpaRepository { - -} diff --git a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java index cbe1a5ade..5b93813ce 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java +++ b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java @@ -13,13 +13,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.MemberGroupType; -import wooteco.prolog.member.domain.MemberGroups; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.*; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.studylog.application.dto.PopularStudylogsResponse; import wooteco.prolog.studylog.application.dto.StudylogResponse; import wooteco.prolog.studylog.application.dto.StudylogsResponse; @@ -39,26 +35,26 @@ public class PopularStudylogService { private final StudylogService studylogService; private final StudylogRepository studylogRepository; private final PopularStudylogRepository popularStudylogRepository; - private final MemberGroupRepository memberGroupRepository; - private final GroupMemberRepository groupMemberRepository; + private final DepartmentRepository departmentRepository; + private final DepartmentMemberRepository departmentMemberRepository; @Transactional public void updatePopularStudylogs(Pageable pageable) { deleteAllLegacyPopularStudylogs(); - List groupMembers = groupMemberRepository.findAll(); - Map> memberGroupsBygroupType = memberGroupRepository.findAll() + List departmentMembers = departmentMemberRepository.findAll(); + Map> DepartmetsBygroupType = departmentRepository.findAll() .stream() - .collect(Collectors.groupingBy(MemberGroup::groupType)); + .collect(Collectors.groupingBy(Department::getPart)); final List recentStudylogs = findRecentStudylogs(LocalDateTime.now(), pageable.getPageSize()); List popularStudylogs = new ArrayList<>(); - for (MemberGroupType groupType : MemberGroupType.values()) { - popularStudylogs.addAll(filterStudylogsByMemberGroups(recentStudylogs, - new MemberGroups(memberGroupsBygroupType.get(groupType)), groupMembers).stream() + for (Part groupType : Part.values()) { + popularStudylogs.addAll(filterStudylogsByDepartmets(recentStudylogs, + new Departments(DepartmetsBygroupType.get(groupType)), departmentMembers).stream() .sorted(Comparator.comparing(Studylog::getPopularScore).reversed()) .limit(pageable.getPageSize()).collect(toList())); } @@ -89,43 +85,43 @@ private List findRecentStudylogs(final LocalDateTime dateTime, return recentStudylogs; } - private List filterStudylogsByMemberGroups(final List studylogs, - final MemberGroups memberGroups, - final List groupMembers) { + private List filterStudylogsByDepartmets(final List studylogs, + final Departments departments, + final List departmentMembers) { return studylogs.stream() .filter( - studylog -> checkMemberAssignedInMemberGroups(memberGroups, studylog.getMember(), - groupMembers)) + studylog -> checkMemberAssignedInDepartmets(departments, studylog.getMember(), + departmentMembers)) .collect(toList()); } - private boolean checkMemberAssignedInMemberGroups(MemberGroups memberGroups, Member member, - List groupMembers) { - return groupMembers.stream().anyMatch( - it -> it.getMember().equals(member) && memberGroups.isContainsMemberGroups(it)); + private boolean checkMemberAssignedInDepartmets(Departments departments, Member member, + List departmentMembers) { + return departmentMembers.stream().anyMatch( + it -> it.getMember().equals(member) && departments.isContainsDepartments(it)); } public PopularStudylogsResponse findPopularStudylogs(Pageable pageable, Long memberId, boolean isAnonymousMember) { List allPopularStudylogs = getSortedPopularStudyLogs(pageable); - List groupedMembers = groupMemberRepository.findAll(); - Map> memberGroupsBygroupType = memberGroupRepository.findAll() - .stream().collect(Collectors.groupingBy(MemberGroup::groupType)); + List groupedMembers = departmentMemberRepository.findAll(); + Map> DepartmetsBygroupType = departmentRepository.findAll() + .stream().collect(Collectors.groupingBy(Department::getPart)); return PopularStudylogsResponse.of( studylogsResponse(allPopularStudylogs, pageable, memberId), studylogsResponse( - filterStudylogsByMemberGroups(allPopularStudylogs, new MemberGroups(memberGroupsBygroupType.get(MemberGroupType.FRONTEND)), groupedMembers), + filterStudylogsByDepartmets(allPopularStudylogs, new Departments(DepartmetsBygroupType.get(Part.FRONTEND)), groupedMembers), pageable, memberId), studylogsResponse( - filterStudylogsByMemberGroups(allPopularStudylogs, new MemberGroups(memberGroupsBygroupType.get(MemberGroupType.BACKEND)), groupedMembers), + filterStudylogsByDepartmets(allPopularStudylogs, new Departments(DepartmetsBygroupType.get(Part.BACKEND)), groupedMembers), pageable, memberId), studylogsResponse( - filterStudylogsByMemberGroups(allPopularStudylogs, new MemberGroups(memberGroupsBygroupType.get(MemberGroupType.ANDROID)), groupedMembers), + filterStudylogsByDepartmets(allPopularStudylogs, new Departments(DepartmetsBygroupType.get(Part.ANDROID)), groupedMembers), pageable, memberId)); } diff --git a/backend/src/test/java/wooteco/prolog/member/application/GroupMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java similarity index 55% rename from backend/src/test/java/wooteco/prolog/member/application/GroupMemberServiceTest.java rename to backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java index a15d4ff53..db9376bea 100644 --- a/backend/src/test/java/wooteco/prolog/member/application/GroupMemberServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java @@ -12,17 +12,17 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import wooteco.prolog.member.domain.GroupMember; +import wooteco.prolog.member.domain.DepartmentMember; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; +import wooteco.prolog.member.domain.Department; import wooteco.prolog.member.domain.Role; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; @ExtendWith(MockitoExtension.class) -class GroupMemberServiceTest { +class DepartmentMemberServiceTest { @Mock - private GroupMemberRepository groupMemberRepository; + private DepartmentMemberRepository departmentMemberRepository; @InjectMocks private GroupMemberService groupMemberService; @@ -32,20 +32,20 @@ class GroupMemberServiceTest { void findGroupMemberByGroupId() { //given final Long memberId = 1L; - final Long memberGroupId = 2L; + final Long DepartmetId = 2L; final Long groupMemberId = 3L; final Member member = new Member(memberId, "송세연", "아마란스", Role.CREW, 1523L, "image"); - final MemberGroup memberGroup = new MemberGroup(memberGroupId, "백엔드", "2023 백엔드"); - final GroupMember groupMember = new GroupMember(groupMemberId, member, memberGroup); + final Department department = new Department(DepartmetId, "백엔드", "2023 백엔드"); + final DepartmentMember departmentMember = new DepartmentMember(groupMemberId, member, department); - when(groupMemberRepository.findByGroupId(any())).thenReturn(ImmutableList.of(groupMember)); + when(departmentMemberRepository.findByDepartmentId(any())).thenReturn(ImmutableList.of(departmentMember)); //when - final List groupMembers = groupMemberService.findGroupMemberByGroupId( - memberGroupId); + final List departmentMembers = groupMemberService.findGroupMemberByGroupId( + DepartmetId); //then - assertThat(groupMembers).containsExactly(groupMember); + assertThat(departmentMembers).containsExactly(departmentMember); } } diff --git a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java new file mode 100644 index 000000000..e3b6dc443 --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java @@ -0,0 +1,32 @@ +package wooteco.prolog.member.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static wooteco.prolog.member.domain.Part.*; + +import org.junit.jupiter.api.Test; +import wooteco.prolog.common.exception.BadRequestException; + +class DepartmentTest { + + private static final Department ANDROID_GROUP = new Department(null, "안드로이드", "5기"); + private static final Department BACKEND_GROUP = new Department(null, "백엔드", "5기"); + private static final Department FRONTEND_GROUP = new Department(null, "프론트엔드", "4기"); + + @Test + void getGroupType_이름이_그룹명을_포함하면_그룹을_반환한다() { + assertThat(ANDROID_GROUP.getPart()).isEqualTo(ANDROID); + assertThat(BACKEND_GROUP.getPart()).isEqualTo(BACKEND); + assertThat(FRONTEND_GROUP.getPart()).isEqualTo(FRONTEND); + } + + @Test + void getGroupType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { + Department department = new Department(null, "테스트", "test"); + + assertThatThrownBy(department::getPart) + .isInstanceOf(BadRequestException.class) + .hasMessage("해당 그룹의 타입을 결정할 수 없습니다."); + } + +} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java new file mode 100644 index 000000000..a1ea21324 --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java @@ -0,0 +1,31 @@ +package wooteco.prolog.member.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class DepartmentTypeTest { + + @ParameterizedTest + @EnumSource(value = Part.class) + void isContainedBy_null은_그룹명을_포함하지_않는다(Part DepartmentType) { + assertThat(DepartmentType.isContainedBy(null)).isFalse(); + } + + @Test + void isContainedBy() { + assertThat(Part.ANDROID.isContainedBy("안드로이드 5기")).isTrue(); + assertThat(Part.ANDROID.isContainedBy("프론트엔드 5기")).isFalse(); + assertThat(Part.ANDROID.isContainedBy("백엔드 5기")).isFalse(); + + assertThat(Part.FRONTEND.isContainedBy("안드로이드 5기")).isFalse(); + assertThat(Part.FRONTEND.isContainedBy("프론트엔드 5기")).isTrue(); + assertThat(Part.FRONTEND.isContainedBy("백엔드 5기")).isFalse(); + + assertThat(Part.BACKEND.isContainedBy("안드로이드 5기")).isFalse(); + assertThat(Part.BACKEND.isContainedBy("프론트엔드 5기")).isFalse(); + assertThat(Part.BACKEND.isContainedBy("백엔드 5기")).isTrue(); + } +} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java b/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java deleted file mode 100644 index 702640ade..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package wooteco.prolog.member.domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; - -import org.junit.jupiter.api.Test; -import wooteco.prolog.common.exception.BadRequestCode; -import wooteco.prolog.common.exception.BadRequestException; - -class MemberGroupTest { - - private static final MemberGroup ANDROID_GROUP = new MemberGroup(null, " 안드로이드 5기", "A"); - private static final MemberGroup BACKEND_GROUP = new MemberGroup(null, " 백엔드 5기", "B"); - private static final MemberGroup FRONTEND_GROUP = new MemberGroup(null, " 프론트엔드 5기", "F"); - - @Test - void getGroupType_이름이_그룹명을_포함하면_그룹을_반환한다() { - assertThat(ANDROID_GROUP.groupType()).isEqualTo(MemberGroupType.ANDROID); - assertThat(BACKEND_GROUP.groupType()).isEqualTo(MemberGroupType.BACKEND); - assertThat(FRONTEND_GROUP.groupType()).isEqualTo(MemberGroupType.FRONTEND); - } - - @Test - void getGroupType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { - MemberGroup memberGroup = new MemberGroup(null, "테스트", "test"); - - assertThatThrownBy(memberGroup::groupType) - .isInstanceOf(BadRequestException.class) - .hasMessage("해당 그룹의 타입을 결정할 수 없습니다."); - } - -} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java b/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java deleted file mode 100644 index 102652e26..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package wooteco.prolog.member.domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class MemberGroupTypeTest { - - @ParameterizedTest - @EnumSource(value = MemberGroupType.class) - void isContainedBy_null은_그룹명을_포함하지_않는다(MemberGroupType memberGroupType) { - assertThat(memberGroupType.isContainedBy(null)).isFalse(); - } - - @Test - void isContainedBy() { - assertThat(MemberGroupType.ANDROID.isContainedBy("안드로이드 5기")).isTrue(); - assertThat(MemberGroupType.ANDROID.isContainedBy("프론트엔드 5기")).isFalse(); - assertThat(MemberGroupType.ANDROID.isContainedBy("백엔드 5기")).isFalse(); - - assertThat(MemberGroupType.FRONTEND.isContainedBy("안드로이드 5기")).isFalse(); - assertThat(MemberGroupType.FRONTEND.isContainedBy("프론트엔드 5기")).isTrue(); - assertThat(MemberGroupType.FRONTEND.isContainedBy("백엔드 5기")).isFalse(); - - assertThat(MemberGroupType.BACKEND.isContainedBy("안드로이드 5기")).isFalse(); - assertThat(MemberGroupType.BACKEND.isContainedBy("프론트엔드 5기")).isFalse(); - assertThat(MemberGroupType.BACKEND.isContainedBy("백엔드 5기")).isTrue(); - } -} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java b/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java new file mode 100644 index 000000000..95270b33b --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java @@ -0,0 +1,44 @@ +package wooteco.prolog.member.domain.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import wooteco.prolog.member.domain.DepartmentMember; +import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Department; +import wooteco.prolog.member.domain.Role; +import wooteco.support.utils.RepositoryTest; + +@RepositoryTest +class DepartmentMemberRepositoryTest { + + @Autowired + private MemberRepository memberRepository; + @Autowired + private DepartmentMemberRepository departmentMemberRepository; + @Autowired + private DepartmentRepository departmentRepository; + + @Test + @DisplayName("작성된 studylog의 Member가 DepartmentMember의 Department에 포함되는 경우 true를 반환한다.") + void existsDepartmentMemberByMemberAndDepartment() { + // given + Member saveMember = memberRepository.save( + new Member("username", "nickname", Role.CREW, 1L, "imageUrl")); + Department saveDepartment = departmentRepository.save( + new Department(null, "프론트엔드", "5") + ); + departmentMemberRepository.save( + new DepartmentMember(null, saveMember, saveDepartment) + ); + + // when + boolean extract = departmentMemberRepository.existsDepartmentMemberByMemberAndDepartment( + saveMember, saveDepartment); + + // then + assertThat(extract).isTrue(); + } +} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java b/backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java deleted file mode 100644 index ef64b761d..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package wooteco.prolog.member.domain.repository; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.Role; -import wooteco.support.utils.RepositoryTest; - -@RepositoryTest -class GroupMemberRepositoryTest { - - @Autowired - private MemberRepository memberRepository; - @Autowired - private GroupMemberRepository groupMemberRepository; - @Autowired - private MemberGroupRepository memberGroupRepository; - - @Test - @DisplayName("작성된 studylog의 Member가 GroupMember의 MemberGroup에 포함되는 경우 true를 반환한다.") - void existsGroupMemberByMemberAndMemberGroup() { - // given - Member saveMember = memberRepository.save( - new Member("username", "nickname", Role.CREW, 1L, "imageUrl")); - MemberGroup saveMemberGroup = memberGroupRepository.save( - new MemberGroup(null, "프론트엔드", "프론트엔드 설명") - ); - groupMemberRepository.save( - new GroupMember(null, saveMember, saveMemberGroup) - ); - - // when - boolean extract = groupMemberRepository.existsGroupMemberByMemberAndGroup( - saveMember, saveMemberGroup); - - // then - assertThat(extract).isTrue(); - } -} diff --git a/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java b/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java index 0dedf34bd..3c9501037 100644 --- a/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java @@ -20,12 +20,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; -import wooteco.prolog.member.domain.GroupMember; +import wooteco.prolog.member.domain.DepartmentMember; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; +import wooteco.prolog.member.domain.Department; import wooteco.prolog.member.domain.Role; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.session.domain.Mission; import wooteco.prolog.session.domain.Session; import wooteco.prolog.studylog.application.dto.PopularStudylogsResponse; @@ -46,9 +46,9 @@ class PopularStudylogServiceTest { @Mock private PopularStudylogRepository popularStudylogRepository; @Mock - private MemberGroupRepository memberGroupRepository; + private DepartmentRepository departmentRepository; @Mock - private GroupMemberRepository groupMemberRepository; + private DepartmentMemberRepository departmentMemberRepository; @InjectMocks private PopularStudylogService popularStudylogService; @@ -56,15 +56,15 @@ class PopularStudylogServiceTest { @Test void enoughWhileTwoCycle() { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudyLog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -75,16 +75,16 @@ void enoughWhileTwoCycle() { journeyStudylog.getId()); final PageRequest pageRequest = PageRequest.of(1, 2); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); final List studylogs = Arrays.asList(splitStudyLog, journeyStudylog); final List popularStudylogs = Arrays.asList(splitPopularStudylog, journeyPopularStudylog); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findByPastDays(any())).thenReturn(studylogs); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); when(popularStudylogRepository.saveAll(any())).thenReturn(popularStudylogs); @@ -100,15 +100,15 @@ void enoughWhileTwoCycle() { @Test void notEnoughWhileTwoCycle() { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -119,15 +119,15 @@ void notEnoughWhileTwoCycle() { journeyStudylog.getId()); final PageRequest pageRequest = PageRequest.of(1, 5); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); final List studylogs = Arrays.asList(splitStudylog, journeyStudylog); final List popularStudylogs = Arrays.asList(splitPopularStudylog, journeyPopularStudylog); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findByPastDays(any())).thenReturn(studylogs); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); when(popularStudylogRepository.saveAll(any())).thenReturn(popularStudylogs); @@ -142,15 +142,15 @@ void notEnoughWhileTwoCycle() { @DisplayName("익명의 사용자일 경우 스크랩과 읽음 여부가 표시하지 않고 학습로그를 조회한다.") @Test void findPopularStudylogs_IsAnonymousMemberTrue() { - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -163,14 +163,14 @@ void findPopularStudylogs_IsAnonymousMemberTrue() { final List studylogs = Arrays.asList(splitStudylog, journeyStudylog); final PageRequest pageRequest = PageRequest.of(0, 1); final Page pages = new PageImpl<>(studylogs, pageRequest, 2); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); final List popularStudylogs = Arrays.asList(splitPopularStudylog, journeyPopularStudylog); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findAllByIdIn(any(), any())).thenReturn(pages); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); @@ -218,15 +218,15 @@ void findPopularStudylogs_IsAnonymousMemberTrue() { void findPopularStudylogs_IsAnonymousMemberFalse() { { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -234,12 +234,12 @@ void findPopularStudylogs_IsAnonymousMemberFalse() { final List studylogs = Arrays.asList(splitStudylog, journeyStudylog); final PageRequest pageRequest = PageRequest.of(0, 1); final Page pages = new PageImpl<>(studylogs, pageRequest, 2); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findAllByIdIn(any(), any())).thenReturn(pages); when(studylogService.findScrapIds(any())).thenReturn(Arrays.asList(1L, 2L)); doNothing().when(studylogService).updateScrap(any(), any()); @@ -291,17 +291,17 @@ void findPopularStudylogs_IsAnonymousMemberFalse() { void findPopularStudylogs_filterGroupType() { { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); final Member pooh = setUpMember(3L, "백승준", "푸우", 3L); - final GroupMember splitGroupMember = setUpGroupMember(split, frontend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); - final GroupMember poohGroupMember = setUpGroupMember(pooh, android); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, frontend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); + final DepartmentMember poohDepartmentMember = setUpDepartmentMember(pooh, android); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -310,8 +310,8 @@ void findPopularStudylogs_filterGroupType() { final List studylogs = Arrays.asList(splitStudylog, journeyStudylog, poohStudylog); final PageRequest pageRequest = PageRequest.of(0, 3); final Page pages = new PageImpl<>(studylogs, pageRequest, 1); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, journeyGroupMember, poohGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, journeyDepartmentMember, poohDepartmentMember); final PopularStudylog splitPopularStudylog = setUpPopularStudylog(1L, splitStudylog.getId()); @@ -324,8 +324,8 @@ void findPopularStudylogs_filterGroupType() { journeyPopularStudylog, poohPopularStudylog); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findAllByIdIn(any(), any())).thenReturn(pages); //when @@ -366,8 +366,8 @@ void findPopularStudylogs_filterGroupType() { } - private MemberGroup setUpMemberGroup(final String name, final String description) { - return new MemberGroup(null, name, description); + private Department setUpDepartment(final String part, final String term) { + return new Department(null, part, term); } private Member setUpMember(final Long id, final String userName, @@ -375,8 +375,8 @@ private Member setUpMember(final Long id, final String userName, return new Member(id, userName, nickname, Role.CREW, githubId, "image url"); } - private GroupMember setUpGroupMember(final Member member, final MemberGroup memberGroup) { - return new GroupMember(null, member, memberGroup); + private DepartmentMember setUpDepartmentMember(final Member member, final Department department) { + return new DepartmentMember(null, member, department); } private Studylog setUpStudyLog(final Member member) { From 1cc9fceb8af74ab73367614ddb094d1a637548a2 Mon Sep 17 00:00:00 2001 From: mcodnjs Date: Fri, 13 Oct 2023 16:07:31 +0900 Subject: [PATCH 14/22] =?UTF-8?q?refactor:=20ES=20dateTime=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=B0=8F=20=EC=BF=BC=EB=A6=AC=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/wooteco/prolog/studylog/domain/StudylogDocument.java | 2 +- .../prolog/studylog/domain/StudylogDocumentQueryBuilder.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocument.java b/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocument.java index 7a74c40c3..526d41ab3 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocument.java +++ b/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocument.java @@ -41,6 +41,6 @@ public class StudylogDocument { @Field(type = FieldType.Text) private String username; - @Field(type = FieldType.Text) + @Field(type = FieldType.Date, format = DateFormat.date_optional_time) private LocalDateTime dateTime; } diff --git a/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocumentQueryBuilder.java b/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocumentQueryBuilder.java index 46d20d8d0..6358bcdff 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocumentQueryBuilder.java +++ b/backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocumentQueryBuilder.java @@ -66,7 +66,7 @@ private static void makeBoolQuery( .must(defaultField(username, "username")) // .must(defaultField(levels, "levelId")) // .must(defaultField(missions, "missionId")) - .filter(rangeQuery(start, end)) +// .filter(rangeQuery(start, end)) ); } @@ -96,7 +96,6 @@ private static RangeQueryBuilder rangeQuery(LocalDate start, LocalDate end) { if (Objects.isNull(end)) { end = LocalDate.parse("99991231", DateTimeFormatter.BASIC_ISO_DATE); } - return QueryBuilders.rangeQuery("dateTime") .from(start) .to(end); From f7ecf9e9eebc1ad4c1044727c371d2da9e6117d1 Mon Sep 17 00:00:00 2001 From: wonyongChoi05 Date: Fri, 13 Oct 2023 16:45:40 +0900 Subject: [PATCH 15/22] feat: flyway migration script --- .../prolog/member/domain/Department.java | 8 ++--- .../prod/V9__alter_table_group_and_member.sql | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Department.java b/backend/src/main/java/wooteco/prolog/member/domain/Department.java index 0e802847e..caa489391 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/Department.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/Department.java @@ -2,10 +2,8 @@ import static wooteco.prolog.common.exception.BadRequestCode.CANT_FIND_GROUP_TYPE; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; +import javax.persistence.*; + import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -20,8 +18,10 @@ public class Department { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Enumerated(value = EnumType.STRING) private Part part; + @Enumerated(value = EnumType.STRING) private Term term; public Department(Long id, String part, String term) { diff --git a/backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql b/backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql new file mode 100644 index 000000000..6766b3a8e --- /dev/null +++ b/backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql @@ -0,0 +1,30 @@ +CREATE TABLE IF NOT EXISTS department +( + id bigint auto_increment primary key, + part varchar(50) not null, + term varchar(50) not null +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +create table if not exists department_member +( + id bigint auto_increment primary key, + member_id bigint not null, + department_id bigint not null, + constraint FK_DEPARTMENT_MEMBER_ON_MEMBERㅇ + foreign key (member_id) references prolog.member (id), + constraint FK_DEPARTMENT_MEMBER_ON_DEPARTMENT + foreign key (department_id) references prolog.department (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +insert into department(id, part, term) values (1, '백엔드', '3기'); +insert into department(id, part, term) values (2, '프론트엔드', '3기'); +insert into department(id, part, term) values (3, '백엔드', '4기'); +insert into department(id, part, term) values (4, '프론트엔드', '4기'); +insert into department(id, part, term) values (5, '백엔드', '5기'); +insert into department(id, part, term) values (6, '프론트엔드', '5기'); +insert into department(id, part, term) values (7, '안드로이드', '5기'); + +insert into department_member (id, member_id, department_id) + (select id, member_id, group_id from group_member); From 1200cf7afa7acec162a2480d42aeb562d59e631f Mon Sep 17 00:00:00 2001 From: wonyongChoi05 Date: Fri, 13 Oct 2023 17:39:38 +0900 Subject: [PATCH 16/22] =?UTF-8?q?fix:=20jpql=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/application/ArticleService.java | 4 ++-- .../prolog/article/domain/ArticleFilterType.java | 6 +++--- .../domain/repository/ArticleRepository.java | 12 ++++++------ ...Service.java => DepartmentMemberService.java} | 6 +++--- .../application/SessionMemberService.java | 6 +++--- .../application/PopularStudylogService.java | 4 ++-- .../application/DepartmentMemberServiceTest.java | 12 ++++++------ .../prolog/member/domain/DepartmentTest.java | 16 ++++++++-------- .../application/SessionMemberServiceTest.java | 6 +++--- .../application/PopularStudylogServiceTest.java | 2 +- 10 files changed, 37 insertions(+), 37 deletions(-) rename backend/src/main/java/wooteco/prolog/member/application/{GroupMemberService.java => DepartmentMemberService.java} (70%) diff --git a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java index 4dd8f2945..82ea51cff 100644 --- a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java +++ b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java @@ -86,13 +86,13 @@ public List getFilteredArticles(final LoginMember member, final ArticleFilterType course, final boolean onlyBookmarked) { if (member.isMember()) { - return articleRepository.findArticlesByCourseAndMember(course.getGroupName(), + return articleRepository.findArticlesByCourseAndMember(course.getPartName(), member.getId(), onlyBookmarked).stream() .map(article -> ArticleResponse.of(article, member.getId())) .collect(toList()); } - return articleRepository.findArticlesByCourse(course.getGroupName()).stream() + return articleRepository.findArticlesByCourse(course.getPartName()).stream() .map(article -> ArticleResponse.of(article, member.getId())) .collect(toList()); } diff --git a/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java b/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java index 8eddb65f6..94913a001 100644 --- a/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java +++ b/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java @@ -9,9 +9,9 @@ public enum ArticleFilterType { BACKEND("백엔드"), FRONTEND("프론트엔드"); - private final String groupName; + private final String partName; - ArticleFilterType(String groupName) { - this.groupName = groupName; + ArticleFilterType(String partName) { + this.partName = partName; } } diff --git a/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java b/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java index 2e02f23ea..69360be64 100644 --- a/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java +++ b/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java @@ -18,18 +18,18 @@ public interface ArticleRepository extends JpaRepository { Optional
findFetchLikeById(@Param("id") final Long id); @Query("SELECT DISTINCT a FROM Article a " + - "JOIN GroupMember gm ON a.member.id = gm.member.id " + - "JOIN gm.group mg " + - "WHERE mg.name LIKE %:course " + + "JOIN DepartmentMember dm ON dm.member.id = dm.member.id " + + "JOIN dm.department d " + + "WHERE d.part = :course " + "ORDER by a.createdAt desc") List
findArticlesByCourse(@Param("course") String course); @Query("SELECT DISTINCT a FROM Article a " + - "JOIN GroupMember gm ON a.member.id = gm.member.id " + - "JOIN gm.group mg " + + "JOIN DepartmentMember dm ON a.member.id = dm.member.id " + + "JOIN dm.department d " + "LEFT JOIN a.articleBookmarks.articleBookmarks ab " + "LEFT JOIN a.articleLikes.articleLikes al " + - "WHERE mg.name LIKE %:course AND (:onlyBookmarked = false OR (:onlyBookmarked = true and ab.memberId = :memberId))" + + "WHERE d.part = :course AND (:onlyBookmarked = false OR (:onlyBookmarked = true and ab.memberId = :memberId))" + "ORDER by a.createdAt desc") List
findArticlesByCourseAndMember(@Param("course") String course, @Param("memberId") Long memberId, diff --git a/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java b/backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java similarity index 70% rename from backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java rename to backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java index f673c03f5..80fd7d323 100644 --- a/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java +++ b/backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java @@ -10,12 +10,12 @@ @Service @AllArgsConstructor @Transactional(readOnly = true) -public class GroupMemberService { +public class DepartmentMemberService { private DepartmentMemberRepository departmentMemberRepository; - public List findGroupMemberByGroupId(Long groupId) { - return departmentMemberRepository.findByDepartmentId(groupId); + public List findDepartmentMemberByDepartmentId(Long departmentId) { + return departmentMemberRepository.findByDepartmentId(departmentId); } } diff --git a/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java b/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java index b7635b1b3..9bf2df56c 100644 --- a/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java +++ b/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import wooteco.prolog.common.exception.BadRequestException; -import wooteco.prolog.member.application.GroupMemberService; +import wooteco.prolog.member.application.DepartmentMemberService; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.application.dto.MemberResponse; import wooteco.prolog.member.domain.Member; @@ -25,7 +25,7 @@ public class SessionMemberService { private SessionMemberRepository sessionMemberRepository; private SessionRepository sessionRepository; private MemberService memberService; - private GroupMemberService groupMemberService; + private DepartmentMemberService departmentMemberService; @Transactional public void registerMember(Long sessionId, Long memberId) { @@ -42,7 +42,7 @@ public void registerMembersByGroupId(Long sessionId, List alreadySessionMembers = sessionMemberRepository.findAllBySessionId( sessionId); - List members = groupMemberService.findGroupMemberByGroupId( + List members = departmentMemberService.findDepartmentMemberByDepartmentId( sessionGroupMemberRequest.getGroupId()).stream() .map(it -> it.getMember()) .collect(toList()); diff --git a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java index 5b93813ce..00dca4fcd 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java +++ b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java @@ -52,9 +52,9 @@ public void updatePopularStudylogs(Pageable pageable) { List popularStudylogs = new ArrayList<>(); - for (Part groupType : Part.values()) { + for (Part partType : Part.values()) { popularStudylogs.addAll(filterStudylogsByDepartmets(recentStudylogs, - new Departments(DepartmetsBygroupType.get(groupType)), departmentMembers).stream() + new Departments(DepartmetsBygroupType.get(partType)), departmentMembers).stream() .sorted(Comparator.comparing(Studylog::getPopularScore).reversed()) .limit(pageable.getPageSize()).collect(toList())); } diff --git a/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java index db9376bea..4ce3c391a 100644 --- a/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java @@ -25,25 +25,25 @@ class DepartmentMemberServiceTest { private DepartmentMemberRepository departmentMemberRepository; @InjectMocks - private GroupMemberService groupMemberService; + private DepartmentMemberService departmentMemberService; @DisplayName("GroupId로 GroupMember를 찾는다.") @Test - void findGroupMemberByGroupId() { + void findDepartmentMemberByDepartmentId() { //given final Long memberId = 1L; - final Long DepartmetId = 2L; + final Long DepartmentId = 2L; final Long groupMemberId = 3L; final Member member = new Member(memberId, "송세연", "아마란스", Role.CREW, 1523L, "image"); - final Department department = new Department(DepartmetId, "백엔드", "2023 백엔드"); + final Department department = new Department(DepartmentId, "백엔드", "2023 백엔드"); final DepartmentMember departmentMember = new DepartmentMember(groupMemberId, member, department); when(departmentMemberRepository.findByDepartmentId(any())).thenReturn(ImmutableList.of(departmentMember)); //when - final List departmentMembers = groupMemberService.findGroupMemberByGroupId( - DepartmetId); + final List departmentMembers = departmentMemberService.findDepartmentMemberByDepartmentId( + DepartmentId); //then assertThat(departmentMembers).containsExactly(departmentMember); diff --git a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java index e3b6dc443..1a69c9eed 100644 --- a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java +++ b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java @@ -9,19 +9,19 @@ class DepartmentTest { - private static final Department ANDROID_GROUP = new Department(null, "안드로이드", "5기"); - private static final Department BACKEND_GROUP = new Department(null, "백엔드", "5기"); - private static final Department FRONTEND_GROUP = new Department(null, "프론트엔드", "4기"); + private static final Department ANDROID_DEPARTMENT = new Department(null, "안드로이드", "5기"); + private static final Department BACKEND_DEPARTMENT = new Department(null, "백엔드", "5기"); + private static final Department FRONTEND_DEPARTMENT = new Department(null, "프론트엔드", "4기"); @Test - void getGroupType_이름이_그룹명을_포함하면_그룹을_반환한다() { - assertThat(ANDROID_GROUP.getPart()).isEqualTo(ANDROID); - assertThat(BACKEND_GROUP.getPart()).isEqualTo(BACKEND); - assertThat(FRONTEND_GROUP.getPart()).isEqualTo(FRONTEND); + void getPartType_이름이_그룹명을_포함하면_그룹을_반환한다() { + assertThat(ANDROID_DEPARTMENT.getPart()).isEqualTo(ANDROID); + assertThat(BACKEND_DEPARTMENT.getPart()).isEqualTo(BACKEND); + assertThat(FRONTEND_DEPARTMENT.getPart()).isEqualTo(FRONTEND); } @Test - void getGroupType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { + void getPartType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { Department department = new Department(null, "테스트", "test"); assertThatThrownBy(department::getPart) diff --git a/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java index 50ee17c60..5327feb44 100644 --- a/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java @@ -20,7 +20,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import wooteco.prolog.common.exception.BadRequestException; -import wooteco.prolog.member.application.GroupMemberService; +import wooteco.prolog.member.application.DepartmentMemberService; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.application.dto.MemberResponse; import wooteco.prolog.member.domain.Member; @@ -45,7 +45,7 @@ class SessionMemberServiceTest { private MemberService memberService; @Mock - private GroupMemberService groupMemberService; + private DepartmentMemberService departmentMemberService; @DisplayName("Member가 회원가입을 할 수 있어야 한다.") @Test @@ -89,7 +89,7 @@ void registerMembersByGroupId() { // then verify(sessionMemberRepository, atMostOnce()).findAllBySessionId(1L); - verify(groupMemberService, atMostOnce()).findGroupMemberByGroupId(request.getGroupId()); + verify(departmentMemberService, atMostOnce()).findDepartmentMemberByDepartmentId(request.getGroupId()); verify(sessionMemberRepository, atMostOnce()).saveAll(null); } diff --git a/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java b/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java index 3c9501037..86e338c64 100644 --- a/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java @@ -288,7 +288,7 @@ void findPopularStudylogs_IsAnonymousMemberFalse() { @DisplayName("인기학습 로그를 분야별로 나누어서 반한한다.") @Test - void findPopularStudylogs_filterGroupType() { + void findPopularStudylogs_filterDepartmentType() { { //given final Department frontend = setUpDepartment("프론트엔드", "5기"); From 6e559f3569ae2a5255a4816eeb78806a70d62dcb Mon Sep 17 00:00:00 2001 From: mcodnjs Date: Mon, 16 Oct 2023 16:00:13 +0900 Subject: [PATCH 17/22] =?UTF-8?q?refactor:=20=EA=B9=A8=EC=A7=80=EB=8A=94?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../steps/GroupMemberStepDefinitions.java | 13 ++++---- .../prolog/docu/StudylogDocumentation.java | 12 +++---- .../common/exception/BadRequestCode.java | 2 +- .../prolog/member/domain/Department.java | 25 +++++++-------- .../prolog/member/domain/Departments.java | 4 +-- .../wooteco/prolog/member/domain/Part.java | 8 ----- .../wooteco/prolog/member/domain/Term.java | 8 +++++ .../application/PopularStudylogService.java | 2 +- .../DepartmentMemberServiceTest.java | 9 +++--- .../prolog/member/domain/DepartmentTest.java | 15 +++++---- .../member/domain/DepartmentTypeTest.java | 31 ------------------- .../DepartmentMemberRepositoryTest.java | 9 +++--- 12 files changed, 52 insertions(+), 86 deletions(-) delete mode 100644 backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java diff --git a/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java b/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java index 6c56bcf73..c329e46c9 100644 --- a/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java +++ b/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java @@ -2,13 +2,14 @@ import io.cucumber.java.en.Given; import wooteco.prolog.AcceptanceSteps; -import wooteco.prolog.member.domain.DepartmentMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.Department; +import wooteco.prolog.member.domain.*; import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.member.domain.repository.MemberRepository; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; + public class GroupMemberStepDefinitions extends AcceptanceSteps { private final MemberRepository memberRepository; @@ -27,10 +28,10 @@ public GroupMemberStepDefinitions(MemberRepository memberRepository, public void 그룹멤버를_생성하고(String title) { Member member = memberRepository.findById(1L).get(); Department 프론트엔드 = departmentRepository.save( - new Department(null, "프론트엔드", "4기")); - Department 백엔드 = departmentRepository.save(new Department(null, "백엔드", "4기")); + new Department(null, FRONTEND, FOURTH)); + Department 백엔드 = departmentRepository.save(new Department(null, BACKEND, FOURTH)); Department 안드로이드 = departmentRepository.save( - new Department(null, "안드로이드", "4기")); + new Department(null, ANDROID, FOURTH)); departmentMemberRepository.save(new DepartmentMember(null, member, 백엔드)); departmentMemberRepository.save(new DepartmentMember(null, member, 프론트엔드)); departmentMemberRepository.save(new DepartmentMember(null, member, 안드로이드)); diff --git a/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java b/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java index d16f1d992..e88cc6334 100644 --- a/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java +++ b/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java @@ -1,6 +1,8 @@ package wooteco.prolog.docu; import static org.assertj.core.api.Assertions.assertThat; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -16,9 +18,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import wooteco.prolog.Documentation; -import wooteco.prolog.member.domain.DepartmentMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.Department; +import wooteco.prolog.member.domain.*; import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.member.domain.repository.MemberRepository; @@ -313,10 +313,10 @@ private StudylogRequest createStudylogRequest3() { private void 회원과_멤버그룹_그룹멤버를_등록함() { Member member = memberRepository.findById(1L).get(); Department 프론트엔드 = departmentRepository.save( - new Department(null, "프론트엔드", "4기")); - Department 백엔드 = departmentRepository.save(new Department(null, "백엔드", "4기")); + new Department(null, FRONTEND, FOURTH)); + Department 백엔드 = departmentRepository.save(new Department(null, BACKEND, FOURTH)); Department 안드로이드 = departmentRepository.save( - new Department(null, "안드로이드", "4기")); + new Department(null, ANDROID, FOURTH)); departmentMemberRepository.save(new DepartmentMember(null, member, 백엔드)); departmentMemberRepository.save(new DepartmentMember(null, member, 프론트엔드)); departmentMemberRepository.save(new DepartmentMember(null, member, 안드로이드)); diff --git a/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java b/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java index cafc1458f..25a47e25a 100644 --- a/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java +++ b/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java @@ -94,7 +94,7 @@ public enum BadRequestCode { ARTICLE_IMAGE_URL_OVER_LENGTH_EXCEPTION(12007, "ARTICLE_IMAGE_URL_OVER_LENGTH_EXCEPTION"), ARTICLE_INVALID_URL_EXCEPTION(12008, "ARTICLE_INVALID_URL_EXCEPTION"), UNVALIDATED_MEMBER_EXCEPTION(12009, "UNVALIDATED_MEMBER_EXCEPTION"); - + private int code; private String message; } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Department.java b/backend/src/main/java/wooteco/prolog/member/domain/Department.java index caa489391..23fe87c6a 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/Department.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/Department.java @@ -1,13 +1,10 @@ package wooteco.prolog.member.domain; -import static wooteco.prolog.common.exception.BadRequestCode.CANT_FIND_GROUP_TYPE; - -import javax.persistence.*; - import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import wooteco.prolog.common.exception.BadRequestException; + +import javax.persistence.*; @Entity @Getter @@ -24,18 +21,20 @@ public class Department { @Enumerated(value = EnumType.STRING) private Term term; + public Department(Long id, Part part, Term term) { + this.id = id; + this.part = part; + this.term = term; + } + public Department(Long id, String part, String term) { this.id = id; - this.part = Part.valueOf(part); - this.term = Term.valueOf(term); + this.part = Part.getPartByName(part); + this.term = Term.getTermByName(term); } public Part getPart() { - for (Part part : Part.values()) { - if (part.isContainedBy(this.part.getName())) { - return part; - } - } - throw new BadRequestException(CANT_FIND_GROUP_TYPE); + return part; } + } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Departments.java b/backend/src/main/java/wooteco/prolog/member/domain/Departments.java index 5ac424066..04ab1103e 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/Departments.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/Departments.java @@ -10,7 +10,7 @@ public class Departments { private List values; - public boolean isContainsDepartments(DepartmentMember departmentMember) { - return values.contains(departmentMember.getDepartment()); + public boolean isContainsDepartments(Department department) { + return values.contains(department); } } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Part.java b/backend/src/main/java/wooteco/prolog/member/domain/Part.java index 95d0ac946..7f6960af6 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/Part.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/Part.java @@ -15,14 +15,6 @@ public enum Part { private final String name; - public boolean isContainedBy(String name) { - if (name == null) { - return false; - } - return Arrays.stream(values()) - .anyMatch(p -> p.name.equals(name)); - } - public static Part getPartByName(String name) { return Arrays.stream(values()) .filter(part -> part.name.equals(name)) diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Term.java b/backend/src/main/java/wooteco/prolog/member/domain/Term.java index ec6f517b7..162b67402 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/Term.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/Term.java @@ -3,6 +3,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + @Getter @AllArgsConstructor public enum Term { @@ -16,4 +18,10 @@ public enum Term { private final String name; + public static Term getTermByName(String name) { + return Arrays.stream(values()) + .filter(term -> term.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("name과 일치하는 term이 존재하지 않습니다.")); + } } diff --git a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java index 00dca4fcd..aadf4d314 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java +++ b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java @@ -99,7 +99,7 @@ private List filterStudylogsByDepartmets(final List studylog private boolean checkMemberAssignedInDepartmets(Departments departments, Member member, List departmentMembers) { return departmentMembers.stream().anyMatch( - it -> it.getMember().equals(member) && departments.isContainsDepartments(it)); + it -> it.getMember().equals(member) && departments.isContainsDepartments(it.getDepartment())); } public PopularStudylogsResponse findPopularStudylogs(Pageable pageable, Long memberId, diff --git a/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java index 4ce3c391a..f95ed8e80 100644 --- a/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; import com.google.common.collect.ImmutableList; import java.util.List; @@ -12,10 +14,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import wooteco.prolog.member.domain.DepartmentMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.Department; -import wooteco.prolog.member.domain.Role; +import wooteco.prolog.member.domain.*; import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; @ExtendWith(MockitoExtension.class) @@ -36,7 +35,7 @@ void findDepartmentMemberByDepartmentId() { final Long groupMemberId = 3L; final Member member = new Member(memberId, "송세연", "아마란스", Role.CREW, 1523L, "image"); - final Department department = new Department(DepartmentId, "백엔드", "2023 백엔드"); + final Department department = new Department(DepartmentId, BACKEND, FIFTH); final DepartmentMember departmentMember = new DepartmentMember(groupMemberId, member, department); when(departmentMemberRepository.findByDepartmentId(any())).thenReturn(ImmutableList.of(departmentMember)); diff --git a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java index 1a69c9eed..638cb356e 100644 --- a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java +++ b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java @@ -3,15 +3,15 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; import org.junit.jupiter.api.Test; -import wooteco.prolog.common.exception.BadRequestException; class DepartmentTest { - private static final Department ANDROID_DEPARTMENT = new Department(null, "안드로이드", "5기"); - private static final Department BACKEND_DEPARTMENT = new Department(null, "백엔드", "5기"); - private static final Department FRONTEND_DEPARTMENT = new Department(null, "프론트엔드", "4기"); + private static final Department ANDROID_DEPARTMENT = new Department(null, ANDROID, FIFTH); + private static final Department BACKEND_DEPARTMENT = new Department(null, BACKEND, FIFTH); + private static final Department FRONTEND_DEPARTMENT = new Department(null, FRONTEND, FOURTH); @Test void getPartType_이름이_그룹명을_포함하면_그룹을_반환한다() { @@ -22,11 +22,10 @@ class DepartmentTest { @Test void getPartType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { - Department department = new Department(null, "테스트", "test"); - assertThatThrownBy(department::getPart) - .isInstanceOf(BadRequestException.class) - .hasMessage("해당 그룹의 타입을 결정할 수 없습니다."); + assertThatThrownBy(() -> new Department(null, "테스트", "test")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("name과 일치하는 part가 존재하지 않습니다."); } } diff --git a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java deleted file mode 100644 index a1ea21324..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTypeTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package wooteco.prolog.member.domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class DepartmentTypeTest { - - @ParameterizedTest - @EnumSource(value = Part.class) - void isContainedBy_null은_그룹명을_포함하지_않는다(Part DepartmentType) { - assertThat(DepartmentType.isContainedBy(null)).isFalse(); - } - - @Test - void isContainedBy() { - assertThat(Part.ANDROID.isContainedBy("안드로이드 5기")).isTrue(); - assertThat(Part.ANDROID.isContainedBy("프론트엔드 5기")).isFalse(); - assertThat(Part.ANDROID.isContainedBy("백엔드 5기")).isFalse(); - - assertThat(Part.FRONTEND.isContainedBy("안드로이드 5기")).isFalse(); - assertThat(Part.FRONTEND.isContainedBy("프론트엔드 5기")).isTrue(); - assertThat(Part.FRONTEND.isContainedBy("백엔드 5기")).isFalse(); - - assertThat(Part.BACKEND.isContainedBy("안드로이드 5기")).isFalse(); - assertThat(Part.BACKEND.isContainedBy("프론트엔드 5기")).isFalse(); - assertThat(Part.BACKEND.isContainedBy("백엔드 5기")).isTrue(); - } -} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java b/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java index 95270b33b..ae789f84e 100644 --- a/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java +++ b/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java @@ -1,14 +1,13 @@ package wooteco.prolog.member.domain.repository; import static org.assertj.core.api.Assertions.assertThat; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import wooteco.prolog.member.domain.DepartmentMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.Department; -import wooteco.prolog.member.domain.Role; +import wooteco.prolog.member.domain.*; import wooteco.support.utils.RepositoryTest; @RepositoryTest @@ -28,7 +27,7 @@ void existsDepartmentMemberByMemberAndDepartment() { Member saveMember = memberRepository.save( new Member("username", "nickname", Role.CREW, 1L, "imageUrl")); Department saveDepartment = departmentRepository.save( - new Department(null, "프론트엔드", "5") + new Department(null, FRONTEND, FIFTH) ); departmentMemberRepository.save( new DepartmentMember(null, saveMember, saveDepartment) From 221477d96dabeb8c75b6919d0b4296bfda091e36 Mon Sep 17 00:00:00 2001 From: BGuga Date: Mon, 23 Oct 2023 14:50:10 +0900 Subject: [PATCH 18/22] =?UTF-8?q?feat:=20=EC=BB=A4=EB=A6=AC=ED=81=98?= =?UTF-8?q?=EB=9F=BC=20=EB=B3=84=20Quiz=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prolog/roadmap/application/QuizService.java | 5 +++++ .../application/dto/CurriculumQuizResponse.java | 8 ++++++++ .../roadmap/domain/repository/QuizRepository.java | 10 ++++++++++ .../java/wooteco/prolog/roadmap/ui/QuizController.java | 9 +++++++++ 4 files changed, 32 insertions(+) create mode 100644 backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumQuizResponse.java diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/QuizService.java b/backend/src/main/java/wooteco/prolog/roadmap/application/QuizService.java index d83684cdc..06fcae13f 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/QuizService.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/QuizService.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import wooteco.prolog.common.exception.BadRequestException; +import wooteco.prolog.roadmap.application.dto.CurriculumQuizResponse; import wooteco.prolog.roadmap.application.dto.QuizRequest; import wooteco.prolog.roadmap.application.dto.QuizResponse; import wooteco.prolog.roadmap.application.dto.QuizzesResponse; @@ -73,4 +74,8 @@ public QuizResponse findById(Long quizId, Long memberId) { .orElseThrow(() -> new BadRequestException(ROADMAP_QUIZ_NOT_FOUND_EXCEPTION)); return QuizResponse.of(quiz, isLearning(memberId, quizId)); } + + public List findQuizzesByCurriculumId(Long curriculumId) { + return quizRepository.findQuizzesByCurriculum(curriculumId); + } } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumQuizResponse.java b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumQuizResponse.java new file mode 100644 index 000000000..43f1eb238 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumQuizResponse.java @@ -0,0 +1,8 @@ +package wooteco.prolog.roadmap.application.dto; + +public interface CurriculumQuizResponse { + + Long getId(); + + String getQuestion(); +} diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java index 6ecdf58a1..33702685f 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java @@ -3,6 +3,8 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import wooteco.prolog.roadmap.application.dto.CurriculumQuizResponse; import wooteco.prolog.roadmap.domain.Quiz; public interface QuizRepository extends JpaRepository { @@ -11,4 +13,12 @@ public interface QuizRepository extends JpaRepository { + " JOIN FETCH q.keyword k" + " WHERE q.keyword.id = :keywordId") List findFetchQuizByKeywordId(Long keywordId); + + @Query(nativeQuery = true, + value = "SELECT q.id, q.question " + + "FROM curriculum c " + + "JOIN session s ON c.id = :curriculumId AND s.curriculum_id = c.id " + + "JOIN keyword k ON k.session_id = s.id " + + "JOIN quiz q ON q.keyword_id = k.id") + List findQuizzesByCurriculum(@Param("curriculumId") Long curriculumId); } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/ui/QuizController.java b/backend/src/main/java/wooteco/prolog/roadmap/ui/QuizController.java index fd3e0a597..3f610c952 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/ui/QuizController.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/ui/QuizController.java @@ -1,6 +1,7 @@ package wooteco.prolog.roadmap.ui; import java.net.URI; +import java.util.List; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -14,6 +15,7 @@ import wooteco.prolog.login.domain.AuthMemberPrincipal; import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.roadmap.application.QuizService; +import wooteco.prolog.roadmap.application.dto.CurriculumQuizResponse; import wooteco.prolog.roadmap.application.dto.QuizRequest; import wooteco.prolog.roadmap.application.dto.QuizResponse; import wooteco.prolog.roadmap.application.dto.QuizzesResponse; @@ -64,6 +66,13 @@ public ResponseEntity newFindQuizzesByKeyword( return ResponseEntity.ok(quizService.findQuizzesByKeywordId(keywordId, member.getId())); } + @GetMapping("/curriculums/{curriculumId}/quizzes") + public ResponseEntity> findQuizzesByCurriculum( + @PathVariable Long curriculumId + ) { + return ResponseEntity.ok(quizService.findQuizzesByCurriculumId(curriculumId)); + } + @PutMapping("/sessions/{sessionId}/keywords/{keywordId}/quizs/{quizId}") ResponseEntity updateQuiz(@PathVariable Long sessionId, @PathVariable Long keywordId, From 1ca9d9b5cf7e13ccbfbc1cd3ec0923fc52a94808 Mon Sep 17 00:00:00 2001 From: BGuga Date: Mon, 23 Oct 2023 15:00:13 +0900 Subject: [PATCH 19/22] =?UTF-8?q?test:=20repository=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/QuizRepositoryTest.java | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/backend/src/test/java/wooteco/prolog/roadmap/repository/QuizRepositoryTest.java b/backend/src/test/java/wooteco/prolog/roadmap/repository/QuizRepositoryTest.java index 9b0c5cee8..563441382 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/repository/QuizRepositoryTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/repository/QuizRepositoryTest.java @@ -1,5 +1,7 @@ package wooteco.prolog.roadmap.repository; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.Arrays; import java.util.List; import org.assertj.core.api.Assertions; @@ -7,8 +9,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import wooteco.prolog.roadmap.application.dto.CurriculumQuizResponse; +import wooteco.prolog.roadmap.domain.Curriculum; import wooteco.prolog.roadmap.domain.Keyword; import wooteco.prolog.roadmap.domain.Quiz; +import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; import wooteco.prolog.roadmap.domain.repository.KeywordRepository; import wooteco.prolog.roadmap.domain.repository.QuizRepository; import wooteco.prolog.session.domain.Session; @@ -24,9 +29,14 @@ class QuizRepositoryTest { @Autowired private KeywordRepository keywordRepository; + @Autowired + private CurriculumRepository curriculumRepository; + @Autowired private SessionRepository sessionRepository; + private Curriculum 백엔드; + private Session session_백엔드_레벨1; private Keyword 자바; @@ -38,15 +48,15 @@ class QuizRepositoryTest { @BeforeEach void setUp() { - session_백엔드_레벨1 = sessionRepository.save(new Session("백엔드Java 레벨1")); + 백엔드 = curriculumRepository.save(new Curriculum("백엔드")); + + session_백엔드_레벨1 = sessionRepository.save(new Session(백엔드.getId(), "백엔드Java 레벨1")); 자바 = keywordRepository.save( new Keyword(null, "자바", "자바입니다", 1, 1, session_백엔드_레벨1.getId(), null, null)); - session_백엔드_레벨1 = sessionRepository.save(new Session("백엔드Java 레벨1")); 깃 = keywordRepository.save( new Keyword(null, "깃", "깃입니다", 2, 2, session_백엔드_레벨1.getId(), null, null)); - session_백엔드_레벨1 = sessionRepository.save(new Session("백엔드Java 레벨1")); 자바_질문1 = quizRepository.save(new Quiz(자바, "자바의 아버지는 제임스 고슬링일까요 ? 제이슨일까요 ?")); 자바_질문2 = quizRepository.save(new Quiz(자바, "Stream 은 자바 몇 버전부터 지원했을까요?")); @@ -61,6 +71,19 @@ void findQuizzesByKeyword() { final List expect = Arrays.asList(자바_질문1, 자바_질문2); final List actual = quizRepository.findFetchQuizByKeywordId(자바.getId()); - Assertions.assertThat(actual).containsExactlyElementsOf(expect); + assertThat(actual).containsExactlyElementsOf(expect); + } + + @DisplayName("커리큘럼 id 로 퀴즈 List 를 조회한다.") + @Test + void findQuizzesByCurriculum() { + // given + Long 백엔드_커리큘럼_Id = 백엔드.getId(); + + // when + List acutal = quizRepository.findQuizzesByCurriculum(백엔드_커리큘럼_Id); + + // then + assertThat(acutal).hasSize(3); } } From 593982f68c2b4d03eb15dd56d4527bcfa0970192 Mon Sep 17 00:00:00 2001 From: BGuga Date: Mon, 23 Oct 2023 16:24:37 +0900 Subject: [PATCH 20/22] =?UTF-8?q?test:=20=EB=AC=B8=EC=84=9C=ED=99=94=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/documentation/adoc/quiz.adoc | 14 +++++- .../prolog/docu/QuizDocumentation.java | 46 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/backend/src/documentation/adoc/quiz.adoc b/backend/src/documentation/adoc/quiz.adoc index 335881caf..085234723 100644 --- a/backend/src/documentation/adoc/quiz.adoc +++ b/backend/src/documentation/adoc/quiz.adoc @@ -25,11 +25,21 @@ include::{snippets}/quiz/detail/http-response.adoc[] ==== Request -include::{snippets}/quiz/list/http-request.adoc[] +include::{snippets}/quiz/list-keyword/http-request.adoc[] ==== Response -include::{snippets}/quiz/list/http-response.adoc[] +include::{snippets}/quiz/list-keyword/http-response.adoc[] + +=== 커리큘럼별 퀴즈 목록 조회 + +==== Request + +include::{snippets}/quiz/list-curriculum/http-request.adoc[] + +==== Response + +include::{snippets}/quiz/list-curriculum/http-response.adoc[] === 퀴즈 삭제 diff --git a/backend/src/documentation/java/wooteco/prolog/docu/QuizDocumentation.java b/backend/src/documentation/java/wooteco/prolog/docu/QuizDocumentation.java index 45bfeee7c..e6d3a81ec 100644 --- a/backend/src/documentation/java/wooteco/prolog/docu/QuizDocumentation.java +++ b/backend/src/documentation/java/wooteco/prolog/docu/QuizDocumentation.java @@ -2,17 +2,24 @@ import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; +import org.mockito.Mockito; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import wooteco.prolog.NewDocumentation; import wooteco.prolog.roadmap.application.QuizService; +import wooteco.prolog.roadmap.application.dto.CurriculumQuizResponse; import wooteco.prolog.roadmap.application.dto.QuizRequest; import wooteco.prolog.roadmap.application.dto.QuizResponse; import wooteco.prolog.roadmap.application.dto.QuizzesResponse; @@ -61,10 +68,47 @@ public class QuizDocumentation extends NewDocumentation { given .when().get("/sessions/{sessionId}/keywords/{keywordId}/quizs", 1L, 1L) - .then().log().all().apply(document("quiz/list")) + .then().log().all().apply(document("quiz/list-keyword")) .statusCode(HttpStatus.OK.value()); } + @Test + void Curriculum별_Quiz_조회() { + given(quizService.findQuizzesByCurriculumId(anyLong())) + .willReturn(makeMockResponse()); + + given + .when().get("/curriculums/{curriculumId}/quizzes", 1L) + .then().log().all().apply(document("quiz/list-curriculum")) + .statusCode(HttpStatus.OK.value()); + } + + private List makeMockResponse() { + CurriculumQuizResponse response1 = new CurriculumQuizResponse() { + @Override + public Long getId() { + return 1L; + } + + @Override + public String getQuestion() { + return "question1"; + } + }; + CurriculumQuizResponse response2 = new CurriculumQuizResponse() { + @Override + public Long getId() { + return 2L; + } + + @Override + public String getQuestion() { + return "question2"; + } + }; + return Arrays.asList(response1, response2); + } + @Test void 퀴즈_수정() { doNothing().when(quizService).updateQuiz(any(), any()); From ca72aff129cbffaa02925930b2b4a2a8dba306a1 Mon Sep 17 00:00:00 2001 From: solo5star Date: Fri, 17 Nov 2023 08:50:36 +0000 Subject: [PATCH 21/22] =?UTF-8?q?feat:=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EC=A0=84=EC=B2=B4=20=EA=B8=80=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: WaiNaat Co-authored-by: gc-park --- frontend/src/apis/essayanswers.ts | 7 + frontend/src/apis/filter.ts | 11 + .../DropdownMenu/DropdownMenu.stories.tsx | 11 +- .../DropdownMenu/DropdownMenu.styles.ts | 1 - .../DropdownMenu/DropdownMenu.styles.tsx | 42 --- .../components/DropdownMenu/DropdownMenu.tsx | 15 +- .../{FilterList.tsx => FilterList.jsx} | 0 .../src/components/Items/EssayAnswerItem.tsx | 4 +- .../src/components/Lists/QuizAnswerList.tsx | 4 +- .../RoadmapFilter/RoadmapFilter.tsx | 121 ------- .../RoadmapFilter/RoadmapSelectedFilter.tsx | 18 - .../src/components/SearchBar/SearchBar.tsx | 2 +- frontend/src/constants/reactQueryKey.ts | 2 + .../hooks/EssayAnswer/useEssayAnswerList.ts | 11 - frontend/src/hooks/queries/essayanswer.ts | 22 +- frontend/src/hooks/queries/filters.ts | 17 +- frontend/src/hooks/queries/keywords.ts | 10 +- frontend/src/mocks/fixtures/essayAnswers.ts | 8 +- frontend/src/mocks/fixtures/quizzes.ts | 19 +- frontend/src/mocks/fixtures/roadmap.ts | 308 +++++++++++------- frontend/src/mocks/handlers/roadmap.ts | 57 +--- frontend/src/models/EssayAnswers.ts | 9 +- frontend/src/models/filter.ts | 13 + .../src/pages/EditEssayAnswerPage/index.tsx | 4 +- .../RoadmapFilter/RoadmapFilter.styles.ts | 4 +- .../RoadmapFilter/RoadmapFilter.tsx | 132 ++++++++ .../src/pages/EssayAnswerListPage/index.tsx | 51 +-- .../src/pages/QuizAnswerListPage/index.tsx | 8 +- frontend/src/pages/RoadmapPage/index.tsx | 36 +- frontend/src/pages/RoadmapPage/styles.tsx | 5 + frontend/src/pages/StudylogListPage/index.tsx | 2 +- frontend/src/routes.js | 4 - 32 files changed, 500 insertions(+), 458 deletions(-) create mode 100644 frontend/src/apis/filter.ts delete mode 100644 frontend/src/components/DropdownMenu/DropdownMenu.styles.tsx rename frontend/src/components/FilterList/{FilterList.tsx => FilterList.jsx} (100%) delete mode 100644 frontend/src/components/RoadmapFilter/RoadmapFilter.tsx delete mode 100644 frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx delete mode 100644 frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts create mode 100644 frontend/src/models/filter.ts rename frontend/src/{ => pages/EssayAnswerListPage}/components/RoadmapFilter/RoadmapFilter.styles.ts (91%) create mode 100644 frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx diff --git a/frontend/src/apis/essayanswers.ts b/frontend/src/apis/essayanswers.ts index 7c39f3ab4..6ba6b8cde 100644 --- a/frontend/src/apis/essayanswers.ts +++ b/frontend/src/apis/essayanswers.ts @@ -55,3 +55,10 @@ export const requestGetQuizAsync = async (quizId: number) => { export const requestGetQuiz = (quizId: Number): AxiosPromise> => client.get>(`/quizzes/${quizId}`); + +export const requestGetQuizzes = async (curriculumId: number) => { + const { data } = await client.get<{ id: number; question: string }[]>( + `/curriculums/${curriculumId}/quizzes` + ); + return data; +}; diff --git a/frontend/src/apis/filter.ts b/frontend/src/apis/filter.ts new file mode 100644 index 000000000..330950aba --- /dev/null +++ b/frontend/src/apis/filter.ts @@ -0,0 +1,11 @@ +import { client } from '.'; +import { FilterResponse } from '../models/filter'; +import { Author } from '../models/Studylogs'; + +export const getMembersForFilter = async (): Promise => { + const { + data: { members }, + } = await client.get(`/filters`); + + return members; +}; diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx b/frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx index 0fddab5a8..fcc5e656a 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx +++ b/frontend/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -1,13 +1,16 @@ -import DropdownMenu from './DropdownMenu'; +/** @jsxImportSource @emotion/react */ + +import DropdownMenu, { DropdownMenuProps } from './DropdownMenu'; import { Story, Meta } from '@storybook/react'; +import { css } from '@emotion/react'; export default { title: 'Component/DropdownMenu', component: DropdownMenu, argTypes: { children: { control: 'text' } }, -} as Meta; +} as Meta; -const Template: Story = (args) => ; +const Template: Story> = (args) => ; export const Basic = Template.bind({}); @@ -25,5 +28,5 @@ Basic.args = { ), - onLogoClick: () => {}, + css: css``, }; diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts index a9226f09d..9538e5d0e 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts +++ b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts @@ -2,7 +2,6 @@ import { InterpolationWithTheme } from '@emotion/core'; import { Theme } from '@emotion/react'; import styled from '@emotion/styled'; import COLOR from '../../constants/color'; -import { css } from '@emotion/react'; const Container = styled.div<{ css?: InterpolationWithTheme }>` height: fit-content; diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.styles.tsx b/frontend/src/components/DropdownMenu/DropdownMenu.styles.tsx deleted file mode 100644 index 499ae1e5a..000000000 --- a/frontend/src/components/DropdownMenu/DropdownMenu.styles.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import styled from '@emotion/styled'; -import COLOR from '../../constants/color'; - -const Container = styled.div<{ css: {} }>` - height: fit-content; - max-height: 32rem; - white-space: nowrap; - overflow-y: auto; - background-color: ${COLOR.WHITE}; - border-radius: 1.2rem; - box-shadow: 0px 0px 6px ${COLOR.BLACK_OPACITY_300}; - padding: 1rem 1.2rem; - position: absolute; - z-index: 100; - - && { - ${(props) => props.css} - } - - & li { - height: 4rem; - display: flex; - align-items: center; - padding: 0 0.8rem; - width: 100%; - - & > * { - width: 100%; - font-size: 1.4rem; - font-weight: 500; - color: ${COLOR.DARK_GRAY_900}; - transition: font-size 0.1s ease; - text-align: left; - } - } - - & li:not(:last-child) { - border-bottom: 0.7px solid ${COLOR.LIGHT_GRAY_700}; - } -`; - -export { Container }; diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.tsx b/frontend/src/components/DropdownMenu/DropdownMenu.tsx index dd834dfb7..117152974 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.tsx +++ b/frontend/src/components/DropdownMenu/DropdownMenu.tsx @@ -1,12 +1,15 @@ -import PropTypes from 'prop-types'; +/** @jsxImportSource @emotion/react */ + +// import PropTypes from 'prop-types'; import { Container } from './DropdownMenu.styles'; +import { css } from '@emotion/react'; -const DropdownMenu = ({ children, css }) => { - return {children}; -}; +export interface DropdownMenuProps { + css: ReturnType; +} -DropdownMenu.propTypes = { - children: PropTypes.node, +const DropdownMenu = ({ children, css }: React.PropsWithChildren) => { + return {children}; }; export default DropdownMenu; diff --git a/frontend/src/components/FilterList/FilterList.tsx b/frontend/src/components/FilterList/FilterList.jsx similarity index 100% rename from frontend/src/components/FilterList/FilterList.tsx rename to frontend/src/components/FilterList/FilterList.jsx diff --git a/frontend/src/components/Items/EssayAnswerItem.tsx b/frontend/src/components/Items/EssayAnswerItem.tsx index cafefab4b..0b2d055b0 100644 --- a/frontend/src/components/Items/EssayAnswerItem.tsx +++ b/frontend/src/components/Items/EssayAnswerItem.tsx @@ -10,10 +10,10 @@ import { import { AlignItemsEndStyle, FlexColumnStyle, FlexStyle } from '../../styles/flex.styles'; import Card from '../Card/Card'; import ProfileChip from '../ProfileChip/ProfileChip'; -import { EssayAnswerResponse } from '../../models/EssayAnswers'; +import { EssayAnswer } from '../../models/EssayAnswers'; type EssayAnswerItemProps = ( - Pick + Pick & { title?: string; showTitle?: boolean } ); diff --git a/frontend/src/components/Lists/QuizAnswerList.tsx b/frontend/src/components/Lists/QuizAnswerList.tsx index 3656ae034..e3f8e6002 100644 --- a/frontend/src/components/Lists/QuizAnswerList.tsx +++ b/frontend/src/components/Lists/QuizAnswerList.tsx @@ -2,10 +2,10 @@ import {css} from '@emotion/react'; import EssayAnswerItem from "../Items/EssayAnswerItem"; import { NoDefaultHoverLink } from '../Items/EssayAnswerItem.styles'; -import { EssayAnswerResponse } from '../../models/EssayAnswers'; +import { EssayAnswer } from '../../models/EssayAnswers'; interface QuizAnswerListProps { - essayAnswers: EssayAnswerResponse[]; + essayAnswers: EssayAnswer[]; showQuizTitle?: boolean; } diff --git a/frontend/src/components/RoadmapFilter/RoadmapFilter.tsx b/frontend/src/components/RoadmapFilter/RoadmapFilter.tsx deleted file mode 100644 index 55a2d12ad..000000000 --- a/frontend/src/components/RoadmapFilter/RoadmapFilter.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import { getCurriculums } from '../../apis/curriculum'; -import DropdownMenu from '../DropdownMenu/DropdownMenu'; -import { Container, DropdownStyle, FilterContainer } from './RoadmapFilter.styles'; -import RoadmapSelectedFilter from './RoadmapSelectedFilter'; - -const filterKeyword = ['curriculumId', 'keywordId', 'quizIds', 'memberIds']; - -const filterData = { - curriculumId: [1, 2], - keywordId: [1, 2, 3, 4, 5], - quizIds: [29, 2, 3, 4], - memberIds: [304, 287, 3], -}; - -interface RoadmapFilterProps { - searchKeyword: string; - setSearchKeyword: Dispatch>; -} - -const RoadmapFilter = ({ searchKeyword, setSearchKeyword }: RoadmapFilterProps) => { - const history = useHistory(); - const [params, setParams] = useState>({}); - - const handleFilter = (filterName, filterItem) => { - if (filterName === 'curriculumId' || filterName === 'keywordId') { - if (!params[filterName] || params[filterName] !== filterItem) { - setParams({ ...params, [filterName]: filterItem }); - return; - } - - if (params[filterName] === filterItem) { - setParams((prevParams) => { - const updatedParams = { ...prevParams }; - delete updatedParams[filterName]; - - return updatedParams; - }); - return; - } - } - - if (filterName === 'quizIds' || filterName === 'memberIds') { - if (!params[filterName]) { - setParams({ ...params, [filterName]: String(filterItem) }); - return; - } - const idsList = params[filterName].split(','); - if (!idsList.includes(String(filterItem))) { - const ids = params[filterName] + ',' + String(filterItem); - setParams({ ...params, [filterName]: ids }); - return; - } - const idsFilteredList = idsList.filter((id) => Number(id) !== filterItem); - if (idsFilteredList.length === 0) { - setParams((prevParams) => { - const updatedParams = { ...prevParams }; - delete updatedParams[filterName]; - - return updatedParams; - }); - return; - } - - setParams({ ...params, [filterName]: idsFilteredList.join(',') }); - return; - } - }; - - const resetFilter = () => { - setParams({}); - }; - - const closeDropdown = () => { - setSearchKeyword(''); - }; - - useEffect(() => { - history.push(`/essay-answers?${new URLSearchParams(params).toString()}`); - }, [params]); - - useEffect(() => { - window.addEventListener('click', closeDropdown); - - return () => window.removeEventListener('click', closeDropdown); - }, []); - - return ( - - - {filterKeyword && - filterKeyword.map((item, index) => { - return ( -
event.stopPropagation()}> - - {searchKeyword === item && ( - - - - )} -
- ); - })} -
- {Object.keys(params).length !== 0 && } -
- ); -}; - -export default RoadmapFilter; diff --git a/frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx b/frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx deleted file mode 100644 index 11839af55..000000000 --- a/frontend/src/components/RoadmapFilter/RoadmapSelectedFilter.tsx +++ /dev/null @@ -1,18 +0,0 @@ -interface RoadmapSelectedFilterProps { - itemName: string; - data: string[]; - handleFilter: (filterName, filterItem) => void; -} - -const RoadmapSelectedFilter = ({ itemName, data, handleFilter }: RoadmapSelectedFilterProps) => { - return ( -
    - {data && - data.map((item) => { - return
  • handleFilter(itemName, item)}>{item}
  • ; - })} -
- ); -}; - -export default RoadmapSelectedFilter; diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index 9a241ee79..43af72e7a 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -7,7 +7,7 @@ import { ChangeEventHandler, FormEventHandler } from 'react'; interface SearchBarProps { onSubmit?: FormEventHandler; onChange: ChangeEventHandler; - css: ReturnType; + css?: ReturnType; value: string; } diff --git a/frontend/src/constants/reactQueryKey.ts b/frontend/src/constants/reactQueryKey.ts index 4f0d9cc86..5d32e9f12 100644 --- a/frontend/src/constants/reactQueryKey.ts +++ b/frontend/src/constants/reactQueryKey.ts @@ -1,8 +1,10 @@ const REACT_QUERY_KEY = { STUDYLOG: 'STUDYLOG', QUIZ: 'QUIZ', + QUIZZES: 'QUIZZES', ESSAY_ANSWER: 'ESSAY_ANSWER', QUIZ_ANSWERS: 'QUIZ_ANSWERS', + ESSAY_ANSWER_FILTER_LIST: 'ESSAY_ANSWER_FILTER_LIST', }; export default REACT_QUERY_KEY; diff --git a/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts b/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts deleted file mode 100644 index b2c702f64..000000000 --- a/frontend/src/hooks/EssayAnswer/useEssayAnswerList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useGetQuizAnswerList, useGetQuiz } from '../queries/essayanswer'; -import { useParams } from 'react-router-dom'; - -export const useEssayAnswerList = () => { - const { quizId } = useParams<{ quizId: string }>(); - - const { data: essayAnswers } = useGetQuizAnswerList({ quizId }); - const { data: quiz } = useGetQuiz({ quizId }) - - return { quiz, essayAnswers }; -}; diff --git a/frontend/src/hooks/queries/essayanswer.ts b/frontend/src/hooks/queries/essayanswer.ts index f21b4b4f2..c9988ca74 100644 --- a/frontend/src/hooks/queries/essayanswer.ts +++ b/frontend/src/hooks/queries/essayanswer.ts @@ -9,6 +9,7 @@ import { requestGetEssayAnswers, requestGetQuizAnswers, requestGetQuizAsync, + requestGetQuizzes, } from '../../apis/essayanswers'; import { ResponseError } from '../../apis/studylogs'; import { ALERT_MESSAGE, PATH } from '../../constants'; @@ -16,6 +17,7 @@ import ERROR_CODE from '../../constants/errorCode'; import { ERROR_MESSAGE, SUCCESS_MESSAGE } from '../../constants/message'; import REACT_QUERY_KEY from '../../constants/reactQueryKey'; import { + EssayAnswer, EssayAnswerFilterRequest, EssayAnswerRequest, EssayAnswerResponse, @@ -53,15 +55,15 @@ export const useEditEssayAnswer = ({ essayAnswerId }: { essayAnswerId: number }) export const useGetEssayAnswer = ( { essayAnswerId }, - { onSuccess = (essayAnswer: EssayAnswerResponse) => {}, onError = () => {} } = {} + { onSuccess = (essayAnswer: EssayAnswer) => {}, onError = () => {} } = {} ) => { const history = useHistory(); const { openSnackBar } = useSnackBar(); - return useQuery( + return useQuery( [REACT_QUERY_KEY.ESSAY_ANSWER, essayAnswerId], () => requestGetEssayAnswer(essayAnswerId), { - onSuccess: (essayAnswer: EssayAnswerResponse) => { + onSuccess: (essayAnswer: EssayAnswer) => { onSuccess?.(essayAnswer); }, onError: (error) => { @@ -93,13 +95,13 @@ export const useDeleteEssayAnswerMutation = ({ export const useGetQuizAnswerList = ( { quizId }, - { onSuccess = (essayAnswer: EssayAnswerResponse[]) => {}, onError = () => {} } = {} + { onSuccess = (essayAnswer: EssayAnswer[]) => {}, onError = () => {} } = {} ) => { - return useQuery( + return useQuery( [REACT_QUERY_KEY.QUIZ_ANSWERS, quizId], () => requestGetQuizAnswers(quizId), { - onSuccess: (essayAnswer: EssayAnswerResponse[]) => { + onSuccess: (essayAnswer: EssayAnswer[]) => { onSuccess?.(essayAnswer); }, onError: (error) => {}, @@ -110,7 +112,7 @@ export const useGetQuizAnswerList = ( }; export const useGetEssayAnswers = (filter: EssayAnswerFilterRequest) => { - return useQuery([REACT_QUERY_KEY.ESSAY_ANSWER_FILTER_LIST, filter], () => + return useQuery([REACT_QUERY_KEY.ESSAY_ANSWER_FILTER_LIST, filter], () => requestGetEssayAnswers(filter) ); }; @@ -127,3 +129,9 @@ export const useGetQuiz = ( retry: false, }); }; + +export const useGetQuizzes = ({ curriculumId }: { curriculumId: number }) => { + return useQuery>([REACT_QUERY_KEY.QUIZZES], () => + requestGetQuizzes(curriculumId) + ); +}; diff --git a/frontend/src/hooks/queries/filters.ts b/frontend/src/hooks/queries/filters.ts index 86c6d9e38..312a2cf95 100644 --- a/frontend/src/hooks/queries/filters.ts +++ b/frontend/src/hooks/queries/filters.ts @@ -1,14 +1,14 @@ +import { useContext } from 'react'; import { useQuery } from 'react-query'; - +import { getMembersForFilter } from '../../apis/filter'; import { requestGetMissions, requestGetSessions, requestGetTags, ResponseError, } from '../../apis/studylogs'; -import { Mission, Session, Tag } from '../../models/Studylogs'; -import { useContext } from 'react'; import { UserContext } from '../../contexts/UserProvider'; +import { Author, Mission, Session, Tag } from '../../models/Studylogs'; export const useTags = () => useQuery( @@ -47,3 +47,14 @@ export const useGetMySessions = () => { { initialData: [] } ); }; + +export const useGetMembers = () => { + return useQuery( + ['memberList'], + async () => { + const members = await getMembersForFilter(); + return members; + }, + { initialData: [] } + ); +}; diff --git a/frontend/src/hooks/queries/keywords.ts b/frontend/src/hooks/queries/keywords.ts index 0f66e98a7..855db360f 100644 --- a/frontend/src/hooks/queries/keywords.ts +++ b/frontend/src/hooks/queries/keywords.ts @@ -1,6 +1,6 @@ import { useQuery } from 'react-query'; import { getKeyword, getQuizListByKeyword, getRoadmap } from '../../apis/keywords'; -import type { RoadmapRequest } from '../../models/Keywords'; +import type { KeywordResponse, RoadmapRequest } from '../../models/Keywords'; const QUERY_KEY = { keyword: 'keyword', @@ -16,12 +16,8 @@ export const useRoadmap = ({ curriculumId }: RoadmapRequest) => { }); }; -export const useGetKeyword = ({ keywordId }: { keywordId: number }) => { - const { data } = useQuery([QUERY_KEY.keyword, keywordId], () => getKeyword({ keywordId })); - - return { - keyword: data, - }; +export const useGetKeywords = ({ keywordId }: { keywordId: number }) => { + return useQuery([QUERY_KEY.keyword, keywordId], () => getKeyword({ keywordId })); }; export const useGetQuizListByKeyword = ({ keywordId }: { keywordId: number }) => { diff --git a/frontend/src/mocks/fixtures/essayAnswers.ts b/frontend/src/mocks/fixtures/essayAnswers.ts index cffc63b6e..8b1735259 100644 --- a/frontend/src/mocks/fixtures/essayAnswers.ts +++ b/frontend/src/mocks/fixtures/essayAnswers.ts @@ -1,4 +1,4 @@ -import { EssayAnswerResponse } from '../../models/EssayAnswers'; +import { EssayAnswer } from '../../models/EssayAnswers'; type EssayAnswersFilterParams = { curriculumId: number; @@ -11,7 +11,7 @@ type EssayAnswersFilterParams = { const data: Array<{ curriculumId: number; - essayAnswers: (EssayAnswerResponse & { + essayAnswers: (EssayAnswer & { keywordId: number; })[]; }> = [ @@ -23,6 +23,7 @@ const data: Array<{ keywordId: 1, quiz: { quizId: 29, + isLearning: true, question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', }, answer: @@ -42,6 +43,7 @@ const data: Array<{ keywordId: 2, quiz: { quizId: 29, + isLearning: false, question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', }, answer: @@ -66,6 +68,7 @@ const data: Array<{ keywordId: 1, quiz: { quizId: 29, + isLearning: true, question: 'Varargs는 어떻게 사용하고, 사용했을 때 장점은 무엇인가요?', }, answer: @@ -85,6 +88,7 @@ const data: Array<{ keywordId: 1, quiz: { quizId: 29, + isLearning: false, question: 'Vara하고, 사용했을 때 장점은 무엇인가요?', }, answer: diff --git a/frontend/src/mocks/fixtures/quizzes.ts b/frontend/src/mocks/fixtures/quizzes.ts index ea18ec268..95ca4b016 100644 --- a/frontend/src/mocks/fixtures/quizzes.ts +++ b/frontend/src/mocks/fixtures/quizzes.ts @@ -1,9 +1,4 @@ -import { Quiz } from '../../models/Keywords'; - -const data: Array<{ - keywordId: number; - data: Quiz[]; -}> = [ +export const quizMock = [ { keywordId: 0, data: [], @@ -230,15 +225,3 @@ const data: Array<{ ], }, ]; - -const quizMock = { - data, - findQuiz(quizId) { - return this.data.flatMap(({ data }) => data).find((quiz) => quiz.quizId === quizId) ?? null; - }, - filterByKeyword(keywordId) { - return this.data.find((quizs) => quizs.keywordId === keywordId)?.data ?? []; - }, -}; - -export default quizMock; diff --git a/frontend/src/mocks/fixtures/roadmap.ts b/frontend/src/mocks/fixtures/roadmap.ts index 0817adeb4..d8071fc19 100644 --- a/frontend/src/mocks/fixtures/roadmap.ts +++ b/frontend/src/mocks/fixtures/roadmap.ts @@ -1,80 +1,111 @@ -import { KeywordResponse, Quiz } from '../../models/Keywords'; -import quizMock from './quizzes'; +import { RoadmapResponse } from '../../models/Keywords'; -type WithSession = T & { - sessionId: number; - quizs: { - data: Quiz[]; - }; - childrenKeywords: - | (T extends { childrenKeywords: Array | null } ? WithSession[] : null) - | null; -}; - -const data: Array> = [ +const roadmapData: RoadmapResponse['data'] = [ // 세션 1 { keywordId: 1, - sessionId: 1, // for mock name: 'JavaScript', order: 1, - importance: 5, + importance: 4, parentKeywordId: null, description: '동적 타이핑, 스크립트 언어입니다.', - quizs: { - data: quizMock.filterByKeyword(1), - }, + doneQuizCount: 4, + totalQuizCount: 5, + recommendedPosts: [ + { + id: 1, + url: 'https://solo5star.tistory.com', + }, + { + id: 2, + url: 'https://solo5star.tistory.com', + }, + { + id: 3, + url: 'https://solo5star.tistory.com', + }, + { + id: 4, + url: 'https://solo5star.tistory.com', + }, + { + id: 5, + url: 'https://solo5star.tistory.com', + }, + ], childrenKeywords: [ { keywordId: 2, - sessionId: 1, // for mock name: 'let, const, var', order: 1, - importance: 5, + importance: 4, parentKeywordId: 1, description: 'let, const, var', - quizs: { - data: quizMock.filterByKeyword(2), - }, + doneQuizCount: 0, + totalQuizCount: 0, + recommendedPosts: [], childrenKeywords: [ { keywordId: 3, - sessionId: 1, // for mock name: 'let', order: 1, - importance: 5, + importance: 4, parentKeywordId: 2, description: 'let', - quizs: { - data: quizMock.filterByKeyword(3), - }, - childrenKeywords: null, + doneQuizCount: 1, + totalQuizCount: 4, + recommendedPosts: [], + childrenKeywords: [], }, { keywordId: 4, - sessionId: 1, // for mock name: 'const', order: 1, - importance: 5, + importance: 4, parentKeywordId: 2, description: 'const', - quizs: { - data: quizMock.filterByKeyword(4), - }, - childrenKeywords: null, + doneQuizCount: 3, + totalQuizCount: 3, + recommendedPosts: [ + { + id: 1, + url: 'https://solo5star.tistory.com', + }, + { + id: 2, + url: 'https://solo5star.tistory.com', + }, + ], + childrenKeywords: [], }, { keywordId: 5, - sessionId: 1, // for mock name: 'var', order: 1, - importance: 5, + importance: 2, parentKeywordId: 2, description: 'var', - quizs: { - data: quizMock.filterByKeyword(5), - }, - childrenKeywords: null, + doneQuizCount: 0, + totalQuizCount: 5, + recommendedPosts: [ + { + id: 1, + url: 'https://solo5star.tistory.com', + }, + { + id: 12, + url: 'https://solo5star.tistory.com', + }, + { + id: 13, + url: 'https://solo5star.tistory.com', + }, + { + id: 14, + url: 'https://solo5star.tistory.com', + }, + ], + childrenKeywords: [], }, ], }, @@ -83,66 +114,110 @@ const data: Array> = [ // 세션 1 - React 키워드 { keywordId: 6, - sessionId: 1, // for mock name: 'React', order: 1, - importance: 5, + importance: 4, parentKeywordId: null, description: 'React입니다.', - quizs: { - data: quizMock.filterByKeyword(6), - }, + doneQuizCount: 3, + totalQuizCount: 3, + recommendedPosts: [], childrenKeywords: [ { keywordId: 7, - sessionId: 1, // for mock name: 'lifecycle', order: 1, - importance: 5, + importance: 3, parentKeywordId: 6, description: 'lifecycle 설명', - quizs: { - data: quizMock.filterByKeyword(7), - }, + doneQuizCount: 2, + totalQuizCount: 4, + recommendedPosts: [], childrenKeywords: [ { keywordId: 8, - sessionId: 1, // for mock name: 'mount', order: 1, - importance: 5, + importance: 1, parentKeywordId: 7, description: 'mount 설명', - quizs: { - data: quizMock.filterByKeyword(8), - }, - childrenKeywords: null, + doneQuizCount: 1, + totalQuizCount: 0, + recommendedPosts: [], + childrenKeywords: [], }, { keywordId: 9, - sessionId: 1, // for mock name: 'unmount', order: 1, importance: 5, parentKeywordId: 7, description: 'unmount 설명', - quizs: { - data: quizMock.filterByKeyword(9), - }, - childrenKeywords: null, + doneQuizCount: 0, + totalQuizCount: 0, + recommendedPosts: [], + childrenKeywords: [], }, { keywordId: 10, - sessionId: 1, // for mock name: 'update', order: 1, - importance: 5, + importance: 4, parentKeywordId: 7, description: 'update 설명', - quizs: { - data: quizMock.filterByKeyword(10), - }, - childrenKeywords: null, + doneQuizCount: 0, + totalQuizCount: 2, + recommendedPosts: [], + childrenKeywords: [], + }, + ], + }, + { + keywordId: 14, + name: 'hooks', + order: 1, + importance: 4, + parentKeywordId: 6, + description: 'hooks 설명', + doneQuizCount: 0, + totalQuizCount: 1, + recommendedPosts: [], + childrenKeywords: [ + { + keywordId: 15, + name: 'useState', + order: 1, + importance: 4, + parentKeywordId: 14, + description: 'useState 설명', + doneQuizCount: 5, + totalQuizCount: 5, + recommendedPosts: [], + childrenKeywords: [], + }, + { + keywordId: 16, + name: 'useEffect', + order: 1, + importance: 4, + parentKeywordId: 14, + description: 'useEffect 설명', + doneQuizCount: 4, + totalQuizCount: 5, + recommendedPosts: [], + childrenKeywords: [], + }, + { + keywordId: 17, + name: 'useMemo', + order: 1, + importance: 4, + parentKeywordId: 14, + description: 'useMemo 설명', + doneQuizCount: 0, + totalQuizCount: 2, + recommendedPosts: [], + childrenKeywords: [], }, ], }, @@ -151,40 +226,49 @@ const data: Array> = [ // 세션 2 - Test { keywordId: 11, - sessionId: 2, // for mock name: 'Test', order: 1, - importance: 5, + importance: 3, parentKeywordId: null, description: 'Test입니다.', - quizs: { - data: quizMock.filterByKeyword(11), - }, + doneQuizCount: 0, + totalQuizCount: 4, + recommendedPosts: [], childrenKeywords: [ { keywordId: 12, - sessionId: 2, // for mock name: 'Jest', order: 1, - importance: 5, + importance: 1, parentKeywordId: 11, description: 'Jest 설명', - quizs: { - data: quizMock.filterByKeyword(12), - }, + doneQuizCount: 0, + totalQuizCount: 2, + recommendedPosts: [], childrenKeywords: [ { keywordId: 13, - sessionId: 2, // for mock - name: 'ReactTestingLibrary', + name: 'React Testing Library', order: 1, - importance: 5, + importance: 1, parentKeywordId: 12, description: 'ReactTestingLibrary 설명', - quizs: { - data: quizMock.filterByKeyword(13), - }, - childrenKeywords: null, + doneQuizCount: 0, + totalQuizCount: 3, + recommendedPosts: [], + childrenKeywords: [], + }, + { + keywordId: 18, + name: 'jest', + order: 1, + importance: 2, + parentKeywordId: 12, + description: 'jest 설명', + doneQuizCount: 0, + totalQuizCount: 6, + recommendedPosts: [], + childrenKeywords: [], }, ], }, @@ -192,14 +276,10 @@ const data: Array> = [ }, ]; -const keywordMock = { - data, - filterKeywordsBySession(sessionId: string | readonly string[]) { - const filteredData = this.data.filter((item) => item.sessionId === Number(sessionId)); - - return { - data: filteredData, - }; +export default { + data: roadmapData, + getKeywords() { + return this.data; }, findKeyword(keywordId: string | readonly string[]) { // data를 순회하면서, childrenKeywords를 순회하면서 해당 keyword가 있는지 확인한다. @@ -210,40 +290,24 @@ const keywordMock = { return depth1Item; } - return ( - depth1Item.childrenKeywords?.map((depth2Item) => { - // 2뎁스 순회 - if (depth2Item.keywordId === Number(keywordId)) { - return depth2Item; - } + return depth1Item.childrenKeywords.map((depth2Item) => { + // 2뎁스 순회 + if (depth2Item.keywordId === Number(keywordId)) { + return depth2Item; + } - return ( - depth2Item.childrenKeywords?.map((depth3Item) => { - // 3뎁스 순회 - if (depth3Item.keywordId === Number(keywordId)) { - return depth3Item; - } + return depth2Item.childrenKeywords.map((depth3Item) => { + // 3뎁스 순회 + if (depth3Item.keywordId === Number(keywordId)) { + return depth3Item; + } - return undefined; - }) ?? [] - ); - }) ?? [] - ); + return undefined; + }); + }); }) .find((item) => item !== undefined); return data; }, - // 6-1 - filterChildrenKeywords(keywordId: string | readonly string[]) { - const childrenKeywords = this.data.find((depth1Item) => { - return depth1Item.keywordId === Number(keywordId); - })?.childrenKeywords; - - return { - childrenKeywords, - }; - }, }; - -export default keywordMock; diff --git a/frontend/src/mocks/handlers/roadmap.ts b/frontend/src/mocks/handlers/roadmap.ts index 8630772a6..c15c3d1f1 100644 --- a/frontend/src/mocks/handlers/roadmap.ts +++ b/frontend/src/mocks/handlers/roadmap.ts @@ -1,66 +1,37 @@ import { rest } from 'msw'; import { BASE_URL } from '../../configs/environment'; import curriculums from '../fixtures/curriculums'; -import keywordsMock from '../fixtures/roadmap'; -import quizMock from '../fixtures/quizzes'; -import { sessionsMock } from '../fixtures/sessions'; +import { quizMock } from '../fixtures/quizzes'; +import roadmapMock from '../fixtures/roadmap'; export const roadmapHandler = [ - // 커리큘럼 목록 조회 rest.get(`${BASE_URL}/curriculums`, (req, res, ctx) => { return res(ctx.status(200), ctx.json(curriculums)); }), - // 커리큘럼별 Session 목록 조회 - rest.get(`${BASE_URL}/curriculums/:curriculumId/sessions`, (req, res, ctx) => { - const { - params: { curriculumId }, - } = req; - - const sessions = sessionsMock[Number(curriculumId)]; - - return res(ctx.status(200), ctx.json(sessions)); - }), - - /** 5. 세션별 키워드 목록 조회. 1 depth */ - rest.get(`${BASE_URL}/sessions/:sessionId/keywords`, (req, res, ctx) => { - const { - params: { sessionId }, - } = req; + rest.get(`${BASE_URL}/roadmaps`, (req, res, ctx) => { + const { url } = req; + const curriculumId = url.searchParams.get('curriculumId'); - const keywordsList = keywordsMock.filterKeywordsBySession(sessionId); + const keywordsList = roadmapMock.getKeywords(); - return res(ctx.status(200), ctx.json({ ...keywordsList })); + return res(ctx.status(200), ctx.json({ data: keywordsList })); }), - /** 4. 키워드 단건 조회. 1, 2, 3 depth */ - rest.get(`${BASE_URL}/sessions/:sessionId/keywords/:keywordId`, (req, res, ctx) => { + rest.get(`${BASE_URL}/keywords/:keywordId`, (req, res, ctx) => { const { - params: { sessionId, keywordId }, + params: { keywordId }, } = req; - const keywordData = keywordsMock.findKeyword(keywordId); + const keywordData = roadmapMock.findKeyword(keywordId); return res(ctx.status(200), ctx.json({ ...keywordData })); }), - /** 6-1. 1 -> 2,3 키워드 조회 */ - rest.get(`${BASE_URL}/sessions/:sessionId/keywords/:keywordId/children`, (req, res, ctx) => { - const { - params: { sessionId, keywordId }, - } = req; - - const childrenKeywords = keywordsMock.filterChildrenKeywords(keywordId); - - return res(ctx.status(200), ctx.json({ ...childrenKeywords })); - }), - - /** 10. 키워드별 Quiz 조회 */ - rest.get(`${BASE_URL}/sessions/:sessionId/keywords/:keywordId/quizs`, (req, res, ctx) => { - const { - params: { sessionId, keywordId }, - } = req; + rest.get(`${BASE_URL}/quizzes`, (req, res, ctx) => { + const { url } = req; + const keywordId = url.searchParams.get('keywordId'); - return res(ctx.status(200), ctx.json(quizMock.findQuiz(Number(keywordId)))); + return res(ctx.status(200), ctx.json(quizMock[Number(keywordId)])); }), ]; diff --git a/frontend/src/models/EssayAnswers.ts b/frontend/src/models/EssayAnswers.ts index d9adecb87..3b893bfef 100644 --- a/frontend/src/models/EssayAnswers.ts +++ b/frontend/src/models/EssayAnswers.ts @@ -8,7 +8,7 @@ export interface EssayAnswerRequest { export type EssayEditRequest = Pick; -export interface EssayAnswerResponse { +export interface EssayAnswer { id: number; quiz: Quiz; answer: string; @@ -17,6 +17,13 @@ export interface EssayAnswerResponse { updatedAt: string; } +export interface EssayAnswerResponse { + data: EssayAnswer[]; + totalSize: number; + totalPage: number; + currPage: number; +} + export type EssayAnswerFilter = { curriculumId: number; keywordId?: number; diff --git a/frontend/src/models/filter.ts b/frontend/src/models/filter.ts new file mode 100644 index 000000000..e7c66cfbb --- /dev/null +++ b/frontend/src/models/filter.ts @@ -0,0 +1,13 @@ +import type { Author } from './Studylogs'; + +interface FilterData { + id: number; + name: string; +} + +export interface FilterResponse { + sessions: FilterData[]; + missions: unknown[]; + tags: FilterData[]; + members: Author[]; +} diff --git a/frontend/src/pages/EditEssayAnswerPage/index.tsx b/frontend/src/pages/EditEssayAnswerPage/index.tsx index cb142934c..b3b7b18b7 100644 --- a/frontend/src/pages/EditEssayAnswerPage/index.tsx +++ b/frontend/src/pages/EditEssayAnswerPage/index.tsx @@ -12,7 +12,7 @@ import {useContext, useEffect, useRef, useState} from "react"; import { UserContext } from '../../contexts/UserProvider'; import { useHistory, useParams } from 'react-router'; import { useEditEssayAnswer, useGetEssayAnswer } from '../../hooks/queries/essayanswer'; -import { EssayAnswerResponse } from '../../models/EssayAnswers'; +import { EssayAnswer } from '../../models/EssayAnswers'; const EditEssayAnswerPage = () => { const history = useHistory(); @@ -28,7 +28,7 @@ const EditEssayAnswerPage = () => { const previousEssayAnswer = useGetEssayAnswer( { essayAnswerId: id }, { - onSuccess: ({ quiz: { question }, answer }: EssayAnswerResponse) => { + onSuccess: ({ quiz: { question }, answer }: EssayAnswer) => { setAnswer(answer); setQuizTitle(question); }, diff --git a/frontend/src/components/RoadmapFilter/RoadmapFilter.styles.ts b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts similarity index 91% rename from frontend/src/components/RoadmapFilter/RoadmapFilter.styles.ts rename to frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts index 38e7e990c..8fa79dd9a 100644 --- a/frontend/src/components/RoadmapFilter/RoadmapFilter.styles.ts +++ b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; -import COLOR from '../../constants/color'; -import MEDIA_QUERY from '../../constants/mediaQuery'; +import COLOR from '../../../../constants/color'; +import MEDIA_QUERY from '../../../../constants/mediaQuery'; export const Container = styled.div` background-color: ${COLOR.LIGHT_GRAY_50}; diff --git a/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx new file mode 100644 index 000000000..18c560177 --- /dev/null +++ b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx @@ -0,0 +1,132 @@ +import { useEffect, useState } from 'react'; +import DropdownMenu from '../../../../components/DropdownMenu/DropdownMenu'; +import { useGetQuizzes } from '../../../../hooks/queries/essayanswer'; +import { useGetMembers } from '../../../../hooks/queries/filters'; +import { useRoadmap } from '../../../../hooks/queries/keywords'; +import { KeywordResponse } from '../../../../models/Keywords'; +import { Container, DropdownStyle, FilterContainer } from './RoadmapFilter.styles'; + +const filterKoreanNames: Record = { + keywordId: '주제', + memberIds: '회원', + quizIds: '질문', +} + +const useGetKeywords = ({ curriculumId }: { curriculumId: number }) => { + const { data: roadmap } = useRoadmap({ curriculumId }); + + const extractKeywords = ( + keywordResponse: KeywordResponse + ): Pick[] => { + return [ + { keywordId: keywordResponse.keywordId, name: keywordResponse.name }, + ...keywordResponse.childrenKeywords.map(extractKeywords).flat(), + ]; + }; + + return roadmap?.data.map(extractKeywords).flat() ?? []; +}; + +interface RoadmapFilterProps { + curriculumId: number; + filter: Record; + onFilterChange: (filter: Record) => void; +} + +const RoadmapFilter = ({ curriculumId, filter, onFilterChange }: RoadmapFilterProps) => { + const [activeFilterKeyword, setActiveFilterKeyword] = useState(null); + + const keywords = useGetKeywords({ curriculumId }); + const { data: quizzes } = useGetQuizzes({ curriculumId }); + const { data: members } = useGetMembers(); + + const filterData: Record> = { + keywordId: keywords.map((keyword) => ({ key: String(keyword.keywordId), label: keyword.name })), + memberIds: members?.map((member) => ({ key: String(member.id), label: member.nickname })) ?? [], + quizIds: quizzes?.map((quiz) => ({ key: String(quiz.id), label: quiz.question })) ?? [], + }; + + const handleFilter = (filterName: string, filterItemKey: string) => { + if (filterName === 'curriculumId' || filterName === 'keywordId') { + if (!filter[filterName] || filter[filterName] !== filterItemKey) { + onFilterChange({ ...filter, [filterName]: filterItemKey }); + return; + } + + if (filter[filterName] === filterItemKey) { + const updatedFilter = { ...filter }; + delete updatedFilter[filterName]; + + onFilterChange(updatedFilter); + return; + } + } + + if (filterName === 'quizIds' || filterName === 'memberIds') { + if (!filter[filterName]) { + onFilterChange({ ...filter, [filterName]: String(filterItemKey) }); + return; + } + const idsList = filter[filterName].split(','); + if (!idsList.includes(String(filterItemKey))) { + const ids = filter[filterName] + ',' + String(filterItemKey); + onFilterChange({ ...filter, [filterName]: ids }); + return; + } + const idsFilteredList = idsList.filter((id) => id !== filterItemKey); + if (idsFilteredList.length === 0) { + const updatedFilter = { ...filter }; + delete updatedFilter[filterName]; + + onFilterChange(updatedFilter); + return; + } + + onFilterChange({ ...filter, [filterName]: idsFilteredList.join(',') }); + return; + } + }; + + const resetFilter = () => { + onFilterChange({}); + }; + + const closeDropdown = () => { + setActiveFilterKeyword(null); + }; + + useEffect(() => { + window.addEventListener('click', closeDropdown); + + return () => window.removeEventListener('click', closeDropdown); + }, []); + + return ( + + + { + Object.keys(filterData).map((filterKeyword, index) => ( +
event.stopPropagation()}> + + {activeFilterKeyword === filterKeyword && ( + +
    + {filterData[filterKeyword].map((item) => ( +
  • handleFilter(filterKeyword, item.key)}>{item.label}
  • + ))} +
+
+ )} +
+ ))} +
+ {Object.keys(filter).length !== 0 && } +
+ ); +}; + +export default RoadmapFilter; diff --git a/frontend/src/pages/EssayAnswerListPage/index.tsx b/frontend/src/pages/EssayAnswerListPage/index.tsx index aa4bc28c8..18f6a0c4b 100644 --- a/frontend/src/pages/EssayAnswerListPage/index.tsx +++ b/frontend/src/pages/EssayAnswerListPage/index.tsx @@ -1,11 +1,11 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { useState } from 'react'; -import { useLocation } from 'react-router'; +import { useEffect, useMemo, useState } from 'react'; +import { useHistory, useLocation } from 'react-router'; import { MainContentStyle } from '../../PageRouter'; import EssayAnswerList from '../../components/Lists/QuizAnswerList'; -import RoadmapFilter from '../../components/RoadmapFilter/RoadmapFilter'; +import RoadmapFilter from './components/RoadmapFilter/RoadmapFilter'; import MEDIA_QUERY from '../../constants/mediaQuery'; import { useGetCurriculums } from '../../hooks/queries/curriculum'; import { useGetEssayAnswers } from '../../hooks/queries/essayanswer'; @@ -17,31 +17,42 @@ import { import { HeaderContainer, PostListContainer } from './styles'; export interface FilterlingType { - curriculumId: number; - keywordId: number; - quizIds: number[]; - memberIds: number[]; + curriculumId: string; + keywordId: string; + quizIds: string; + memberIds: string; } const EssayAnswerListPage = () => { + const history = useHistory(); const { curriculums } = useGetCurriculums(); const { search } = useLocation(); - const [searchKeyword, setSearchKeyword] = useState(''); - const searchParams = new URLSearchParams(search); // keywordId quizIds memberIds - const curriculumId = Number(searchParams.get('curriculumId') ?? '1'); - const keywordId = Number(searchParams.get('keywordId') ?? undefined); - const quizIds = searchParams.get('quizIds')?.split(',').map(Number) ?? undefined; - const memberIds = searchParams.get('memberIds')?.split(',').map(Number) ?? undefined; + + const [filter, setFilter] = useState>(() => + Object.fromEntries(new URLSearchParams(search).entries()) + ); + const selectedCurriculum = - (curriculums ?? []).find((curriculum) => curriculum.id === curriculumId)?.name ?? '😎'; + (curriculums ?? []).find((curriculum) => curriculum.id === Number(filter.curriculumId))?.name ?? + '😎'; - const { data: essayAnswers } = useGetEssayAnswers({ - curriculumId, - keywordId, - quizIds, - memberIds, + const { curriculumId, keywordId, quizIds, memberIds } = filter; + const { data: { data: essayAnswers } = { data: [] } } = useGetEssayAnswers({ + curriculumId: Number(curriculumId), + keywordId: keywordId ? Number(keywordId) : undefined, + quizIds: quizIds ? quizIds.split(',').map(Number) : undefined, + memberIds: memberIds ? memberIds.split(',').map(Number) : undefined, }); + const handleFilterChange = (filter: Record) => { + filter['curriculumId'] = curriculumId; + setFilter(filter); + } + + useEffect(() => { + history.replace(`/essay-answers?${new URLSearchParams(filter).toString()}`); + }, [filter]); + return (
@@ -68,7 +79,7 @@ const EssayAnswerListPage = () => {
- + {(!essayAnswers || essayAnswers.length === 0) && '작성된 글이 없습니다.'} diff --git a/frontend/src/pages/QuizAnswerListPage/index.tsx b/frontend/src/pages/QuizAnswerListPage/index.tsx index f86215b51..232f8e8ae 100644 --- a/frontend/src/pages/QuizAnswerListPage/index.tsx +++ b/frontend/src/pages/QuizAnswerListPage/index.tsx @@ -1,9 +1,10 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; +import { useParams } from 'react-router'; import QuizAnswerList from '../../components/Lists/QuizAnswerList'; import MEDIA_QUERY from '../../constants/mediaQuery'; -import { useEssayAnswerList } from '../../hooks/EssayAnswer/useEssayAnswerList'; +import { useGetQuiz, useGetQuizAnswerList } from '../../hooks/queries/essayanswer'; import { MainContentStyle } from '../../PageRouter'; import { AlignItemsCenterStyle, @@ -13,7 +14,10 @@ import { import { HeaderContainer, PostListContainer } from './styles'; const QuizAnswerListPage = () => { - const { quiz, essayAnswers } = useEssayAnswerList(); + const { quizId } = useParams<{ quizId: string }>(); + + const { data: essayAnswers } = useGetQuizAnswerList({ quizId }); + const { data: quiz } = useGetQuiz({ quizId }); return (
diff --git a/frontend/src/pages/RoadmapPage/index.tsx b/frontend/src/pages/RoadmapPage/index.tsx index b2ee342a7..ad6806524 100644 --- a/frontend/src/pages/RoadmapPage/index.tsx +++ b/frontend/src/pages/RoadmapPage/index.tsx @@ -9,6 +9,7 @@ import { useGetCurriculums } from '../../hooks/queries/curriculum'; import ImportanceLegend from './components/ImportanceLegend/ImportanceLegend'; import ResponsiveButton from '../../components/Button/ResponsiveButton'; import { COLOR } from '../../constants'; +import { Link } from 'react-router-dom'; const lastSeenCurriculumId = Number(localStorage.getItem('curriculumId') ?? 1); @@ -52,22 +53,25 @@ const RoadmapPage = () => {
커리큘럼 - - {curriculums?.map((curriculum) => ( - handleClickCurriculum(curriculum.id)} - text={curriculum.name} - color={selectedCurriculumId === curriculum.id ? COLOR.WHITE : COLOR.BLACK_600} - backgroundColor={ - selectedCurriculumId === curriculum.id - ? `hsl(${getHueHeuristically(curriculum.name)}, 50%, 40%)` - : COLOR.LIGHT_GRAY_400 - } - height="32px" - /> - ))} - + + + {curriculums?.map((curriculum) => ( + handleClickCurriculum(curriculum.id)} + text={curriculum.name} + color={selectedCurriculumId === curriculum.id ? COLOR.WHITE : COLOR.BLACK_600} + backgroundColor={ + selectedCurriculumId === curriculum.id + ? `hsl(${getHueHeuristically(curriculum.name)}, 50%, 40%)` + : COLOR.LIGHT_GRAY_400 + } + height="32px" + /> + ))} + + {selectedCurriculum && 전체 답변 보러가기} +
diff --git a/frontend/src/pages/RoadmapPage/styles.tsx b/frontend/src/pages/RoadmapPage/styles.tsx index 1f8960834..575de0a4b 100644 --- a/frontend/src/pages/RoadmapPage/styles.tsx +++ b/frontend/src/pages/RoadmapPage/styles.tsx @@ -24,6 +24,11 @@ export const RoadmapContainer = styled.div` margin: 0 -4rem; `; +export const RoadmapHeader = styled.div` + display: flex; + justify-content: space-between; +` + export const CurriculumButtonList = styled.div` display: flex; flex-wrap: wrap; diff --git a/frontend/src/pages/StudylogListPage/index.tsx b/frontend/src/pages/StudylogListPage/index.tsx index f3f43d8a5..6acdf9370 100644 --- a/frontend/src/pages/StudylogListPage/index.tsx +++ b/frontend/src/pages/StudylogListPage/index.tsx @@ -157,7 +157,7 @@ const StudylogListPage = (): JSX.Element => { {/* 타입스크립트 일부 적용 이슈로 인한 css 빈 string 전달 */} onSearchKeywordsChange(value)} value={searchKeywords} />
diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 9893416db..b8b0c7bfb 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -13,10 +13,6 @@ import { ProfilePageStudylogs, QuizAnswerListPage, RoadmapPage, - NewEssayAnswerPage, - EssayAnswerPage, - EssayAnswerListPage, - EditEssayAnswerPage, NewArticlePage, ArticleListPage, StudylogListPage, From aaf5b46d11e49e2c7b35309cda75d232d81e8c11 Mon Sep 17 00:00:00 2001 From: solo5star Date: Tue, 9 Jan 2024 12:33:48 +0000 Subject: [PATCH 22/22] =?UTF-8?q?fix:=20Dropdown=EC=9D=98=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EA=B0=80=20=EC=98=AC=EB=B0=94=EB=A5=B4=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EA=B2=8C=20=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DropdownMenu/DropdownMenu.styles.ts | 55 +------------------ .../components/DropdownMenu/DropdownMenu.tsx | 2 +- .../RoadmapFilter/RoadmapFilter.styles.ts | 5 -- .../RoadmapFilter/RoadmapFilter.tsx | 46 +++++++++------- .../src/pages/EssayAnswerListPage/index.tsx | 1 + 5 files changed, 28 insertions(+), 81 deletions(-) diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts index d4f980325..0e59d6838 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts +++ b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts @@ -2,8 +2,6 @@ import { InterpolationWithTheme } from '@emotion/core'; import { Theme } from '@emotion/react'; import styled from '@emotion/styled'; import COLOR from '../../constants/color'; -import { css } from '@emotion/react'; -import MEDIA_QUERY from '../../constants/mediaQuery'; const Container = styled.div<{ css?: InterpolationWithTheme }>` height: fit-content; @@ -13,60 +11,15 @@ const Container = styled.div<{ css?: InterpolationWithTheme }>` background-color: ${COLOR.WHITE}; border-radius: 1.2rem; box-shadow: 0px 0px 6px ${COLOR.BLACK_OPACITY_300}; + margin-top: 1rem; padding: 1rem 1.2rem; position: absolute; z-index: 100; - right: 30px; - top: 50px; - - ${MEDIA_QUERY.xs} { - right: 10px; - top: 40px; - } - /* transform: translateY(30%); */ && { ${({ css }) => css} } - /* &:before { - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: -1; - content: ' '; - background-color: rgba(0, 0, 0, 0.4); - } */ - - /* 삼각형 입니다 ^^ - &:before { - content: ''; - position: absolute; - border-style: solid; - border-width: 0 14px 15px; - border-color: #ffffff transparent; - display: block; - width: 0; - z-index: 0; - top: -14px; - right: 16px; - } - - &:after { - content: ''; - position: absolute; - border-style: solid; - border-width: 0 14px 15px; - border-color: #ffffff transparent; - display: block; - width: 0; - z-index: 0; - top: -14px; - right: 16px; - } */ - & li { height: 4rem; display: flex; @@ -87,12 +40,6 @@ const Container = styled.div<{ css?: InterpolationWithTheme }>` & li:not(:last-child) { border-bottom: 0.7px solid ${COLOR.LIGHT_GRAY_700}; } - - /* & li:hover { - & > * { - font-size: 2.2rem; - } - } */ `; export { Container }; diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.tsx b/frontend/src/components/DropdownMenu/DropdownMenu.tsx index 117152974..4e0aa7c5d 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.tsx +++ b/frontend/src/components/DropdownMenu/DropdownMenu.tsx @@ -5,7 +5,7 @@ import { Container } from './DropdownMenu.styles'; import { css } from '@emotion/react'; export interface DropdownMenuProps { - css: ReturnType; + css?: ReturnType; } const DropdownMenu = ({ children, css }: React.PropsWithChildren) => { diff --git a/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts index 8fa79dd9a..5773922a4 100644 --- a/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts +++ b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.styles.ts @@ -1,4 +1,3 @@ -import { css } from '@emotion/react'; import styled from '@emotion/styled'; import COLOR from '../../../../constants/color'; import MEDIA_QUERY from '../../../../constants/mediaQuery'; @@ -55,10 +54,6 @@ export const FilterContainer = styled.div` } `; -export const DropdownStyle = css` - padding-top: 0; -`; - export const ResetFilterButton = styled.button` cursor: pointer; `; diff --git a/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx index 18c560177..99106c9ce 100644 --- a/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx +++ b/frontend/src/pages/EssayAnswerListPage/components/RoadmapFilter/RoadmapFilter.tsx @@ -1,16 +1,17 @@ +import { css } from '@emotion/react'; import { useEffect, useState } from 'react'; import DropdownMenu from '../../../../components/DropdownMenu/DropdownMenu'; import { useGetQuizzes } from '../../../../hooks/queries/essayanswer'; import { useGetMembers } from '../../../../hooks/queries/filters'; import { useRoadmap } from '../../../../hooks/queries/keywords'; import { KeywordResponse } from '../../../../models/Keywords'; -import { Container, DropdownStyle, FilterContainer } from './RoadmapFilter.styles'; +import { Container, FilterContainer } from './RoadmapFilter.styles'; const filterKoreanNames: Record = { keywordId: '주제', memberIds: '회원', quizIds: '질문', -} +}; const useGetKeywords = ({ curriculumId }: { curriculumId: number }) => { const { data: roadmap } = useRoadmap({ curriculumId }); @@ -104,25 +105,28 @@ const RoadmapFilter = ({ curriculumId, filter, onFilterChange }: RoadmapFilterPr return ( - { - Object.keys(filterData).map((filterKeyword, index) => ( -
event.stopPropagation()}> - - {activeFilterKeyword === filterKeyword && ( - -
    - {filterData[filterKeyword].map((item) => ( -
  • handleFilter(filterKeyword, item.key)}>{item.label}
  • - ))} -
-
- )} -
- ))} + {Object.keys(filterData).map((filterKeyword, index) => ( +
event.stopPropagation()} + css={css` + position: relative; + `} + > + + {activeFilterKeyword === filterKeyword && ( + +
    + {filterData[filterKeyword].map((item) => ( +
  • handleFilter(filterKeyword, item.key)}>{item.label}
  • + ))} +
+
+ )} +
+ ))}
{Object.keys(filter).length !== 0 && }
diff --git a/frontend/src/pages/EssayAnswerListPage/index.tsx b/frontend/src/pages/EssayAnswerListPage/index.tsx index 18f6a0c4b..0f8d338ef 100644 --- a/frontend/src/pages/EssayAnswerListPage/index.tsx +++ b/frontend/src/pages/EssayAnswerListPage/index.tsx @@ -79,6 +79,7 @@ const EssayAnswerListPage = () => { +