) => (
+
+);
+const WorkbookCardDetail = {
+ MainImage,
+ WorkbookDetailInfoWrapper,
+ Title,
+ WriterList,
+ PersonCourseWithFewLogo,
+ BottomButton,
+};
+
+export default WorkbookCardDetail;
diff --git a/src/main/components/WorkbookCardList/index.tsx b/src/main/components/WorkbookCardList/index.tsx
new file mode 100644
index 00000000..363676e6
--- /dev/null
+++ b/src/main/components/WorkbookCardList/index.tsx
@@ -0,0 +1,87 @@
+import { WorkbookCardModel } from "@main/models/workbookCardModel";
+import {
+ WorkbookServerInfo,
+ WorkbookSubscriptionInfo,
+} from "@main/types/workbook";
+import WorkbookCard from "../WorkbookCard";
+// TODO : api 연결필요 + mock
+const data: WorkbookServerInfo[] = [
+ {
+ id: 1,
+ mainImageUrl:
+ "https://storage.mrblog.net/files/dosi_draw/a3NgiDGW2H3NhsYp1Qp3RuWNzUx9sg8L2yyooYqF.jpg",
+ title: "몰티즈는 참지않긔",
+ description:
+ "몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔",
+ category: "경제",
+ createdAt: "2024-07-25 14:32:35",
+ writers: [
+ {
+ id: 1,
+ name: "name1",
+ url: "https://example.com",
+ },
+ ],
+ subscriberCount: 1,
+ },
+ {
+ id: 2,
+ mainImageUrl:
+ "https://storage.mrblog.net/files/dosi_draw/a3NgiDGW2H3NhsYp1Qp3RuWNzUx9sg8L2yyooYqF.jpg",
+ title:
+ "몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔",
+ description:
+ "몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔",
+ category: "경제",
+ createdAt: "2024-07-25 14:32:35",
+ writers: [
+ {
+ id: 2,
+ name: "name2",
+ url: "https://example.com",
+ },
+ ],
+ subscriberCount: 2,
+ },
+];
+const subData: WorkbookSubscriptionInfo[] = [
+ {
+ id: 1,
+ status: "ACTIVE",
+ totalDay: 10,
+ currentDay: 1,
+ rank: 0,
+ totalSubscriber: 100,
+ articleInfo: "{}",
+ },
+ {
+ id: 2,
+ status: "DONE",
+ totalDay: 10,
+ currentDay: 10,
+ rank: 22,
+ totalSubscriber: 100,
+ articleInfo: "{}",
+ },
+];
+
+interface WorkbookCardListProps {
+ category: string;
+}
+export default function WorkbookCardList({ category }: WorkbookCardListProps) {
+ const workbookCardModel = new WorkbookCardModel({
+ initWorkbookSeverList: data,
+ initWorkbookSubscriptionInfoList: subData,
+ });
+ return (
+
+ {workbookCardModel
+ .workbookCardList({
+ workbookCombineList: workbookCardModel.workbookCombineListData,
+ })
+ .map((data, idx) => (
+
+ ))}
+
+ );
+}
diff --git a/src/main/components/WorkbookCardsWrapper/index.tsx b/src/main/components/WorkbookCardsWrapper/index.tsx
index a1e042c2..c2a25486 100644
--- a/src/main/components/WorkbookCardsWrapper/index.tsx
+++ b/src/main/components/WorkbookCardsWrapper/index.tsx
@@ -2,6 +2,7 @@
import useCategory from "@main/hooks/useCategory";
import CategoryTabs from "../CategoryTabs";
import MainContentWrapper from "../MainContentWrapper";
+import WorkbookCardList from "../WorkbookCardList";
export default function WorkbookCardsWrapper() {
const { category, handleCategory } = useCategory();
@@ -13,6 +14,7 @@ export default function WorkbookCardsWrapper() {
handleCategory={handleCategory}
category={category}
/>
+
);
}
diff --git a/src/main/models/workbookCardModel.test.tsx b/src/main/models/workbookCardModel.test.tsx
new file mode 100644
index 00000000..97c32f3c
--- /dev/null
+++ b/src/main/models/workbookCardModel.test.tsx
@@ -0,0 +1,165 @@
+import {
+ WorkbookServerInfo,
+ WorkbookSubscriptionInfo,
+} from "@main/types/workbook";
+import { beforeEach, describe, expect, it } from "vitest";
+import { WorkbookCardModel } from "./workbookCardModel";
+
+// 테스트 데이터
+const mockWorkbookServerList: WorkbookServerInfo[] = [
+ {
+ id: 1,
+ mainImageUrl:
+ "https://storage.mrblog.net/files/dosi_draw/a3NgiDGW2H3NhsYp1Qp3RuWNzUx9sg8L2yyooYqF.jpg",
+ title: "몰티즈는 참지않긔",
+ description:
+ "몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔",
+ category: "경제",
+ createdAt: "2024-07-25 14:32:35",
+ writers: [
+ {
+ id: 1,
+ name: "name1",
+ url: "https://example.com",
+ },
+ ],
+ subscriberCount: 1,
+ },
+ {
+ id: 2,
+ mainImageUrl:
+ "https://storage.mrblog.net/files/dosi_draw/a3NgiDGW2H3NhsYp1Qp3RuWNzUx9sg8L2yyooYqF.jpg",
+ title:
+ "몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔",
+ description:
+ "몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔 몰티즈는 참지않긔",
+ category: "경제",
+ createdAt: "2024-07-25 14:32:35",
+ writers: [
+ {
+ id: 2,
+ name: "name2",
+ url: "https://example.com",
+ },
+ ],
+ subscriberCount: 2,
+ },
+];
+const mockWorkbookSubscriptionInfoList: WorkbookSubscriptionInfo[] = [
+ {
+ id: 1,
+ status: "ACTIVE",
+ totalDay: 10,
+ currentDay: 1,
+ rank: 0,
+ totalSubscriber: 100,
+ articleInfo: "{}",
+ },
+ {
+ id: 2,
+ status: "DONE",
+ totalDay: 10,
+ currentDay: 10,
+ rank: 22,
+ totalSubscriber: 100,
+ articleInfo: "{}",
+ },
+];
+
+describe("메인 워크북 카드 모델 테스트", () => {
+ let model: WorkbookCardModel;
+
+ beforeEach(() => {
+ model = new WorkbookCardModel({
+ initWorkbookSeverList: mockWorkbookServerList,
+ initWorkbookSubscriptionInfoList: mockWorkbookSubscriptionInfoList,
+ });
+ });
+
+ it("api 2개/1개 호출 후, 서버 데이터 합친 결과 확인 하기", () => {
+ const combinedData = model.getWorkbookServerCombineData();
+ expect(combinedData).toEqual([
+ expect.objectContaining({
+ ...mockWorkbookServerList[0],
+ ...mockWorkbookSubscriptionInfoList[0],
+ }),
+ expect.objectContaining({
+ ...mockWorkbookServerList[1],
+ ...mockWorkbookSubscriptionInfoList[1],
+ }),
+ ]);
+ });
+
+ it("워크북리스트 및 구독정보 set 형태로 잘 바뀌는지 테스트", () => {
+ const serverSet = model.transformDataToSet({
+ data: mockWorkbookServerList,
+ });
+ const subscriptionSet = model.transformDataToSet({
+ data: mockWorkbookSubscriptionInfoList,
+ });
+
+ expect(serverSet).toHaveProperty("1");
+ expect(subscriptionSet).toHaveProperty("1");
+ });
+
+ it("데이터 합치고 버튼 타이틀 확인하기", () => {
+ const workbookCardList = model.workbookCardList({
+ workbookCombineList: model.getWorkbookServerCombineData(),
+ });
+
+ expect(workbookCardList).toHaveLength(2);
+ expect(workbookCardList[0]).toHaveProperty("buttonTitle", "Day 1 학습하기");
+ expect(workbookCardList[1]).toHaveProperty("buttonTitle", "공유하기");
+ });
+
+ it("작가이름 리스트로 변환", () => {
+ const writerNames = model.getWriterNameList({
+ writers: mockWorkbookServerList[0].writers,
+ });
+ expect(writerNames).toEqual(["name1"]);
+ });
+
+ it("meta component 생성 테스트", () => {
+ const metaComponentActive = model.getMetaComponent({
+ category: "경제",
+ currentDay: 10,
+ totalDay: 1,
+ });
+ expect(metaComponentActive).contains(/Day 1\/10/);
+
+ const metaComponentCompleted = model.getMetaComponent({
+ category: "경제",
+ currentDay: 10,
+ totalDay: 10,
+ });
+ expect(metaComponentCompleted).contains(/Day 10\/10/);
+ });
+
+ it("학습중 상태에 따른 인원 텍스트 함수 테스트", () => {
+ const personCourseActive = model.getPersonCourse({
+ subscriberCount: 10,
+ status: "ACTIVE",
+ });
+ expect(personCourseActive).toBe("10명 학습중");
+
+ const personCourseDone = model.getPersonCourse({
+ subscriberCount: 20,
+ status: "DONE",
+ });
+ expect(personCourseDone).toBe("총 20명");
+ });
+
+ it("구독상태에 따른 버튼 타이틀 테스트", () => {
+ const buttonTitleActive = model.getButtonTitle({
+ status: "ACTIVE",
+ currentDay: 5,
+ });
+ expect(buttonTitleActive).toBe("Day 5 학습하기");
+
+ const buttonTitleDone = model.getButtonTitle({
+ status: "DONE",
+ currentDay: 10,
+ });
+ expect(buttonTitleDone).toBe("공유하기");
+ });
+});
diff --git a/src/main/models/workbookCardModel.tsx b/src/main/models/workbookCardModel.tsx
new file mode 100644
index 00000000..06d6b099
--- /dev/null
+++ b/src/main/models/workbookCardModel.tsx
@@ -0,0 +1,189 @@
+import {
+ WorkbookClientInfo,
+ WorkbookServerInfo,
+ WorkbookSubscriptionInfo,
+} from "@main/types/workbook";
+
+export class WorkbookCardModel {
+ constructor({
+ initWorkbookSeverList,
+ initWorkbookSubscriptionInfoList,
+ }: {
+ initWorkbookSeverList: WorkbookServerInfo[];
+ initWorkbookSubscriptionInfoList?: WorkbookSubscriptionInfo[];
+ }) {
+ this.workbookList = initWorkbookSeverList;
+ if (initWorkbookSubscriptionInfoList)
+ this.workbookSubscriptionInfoList = initWorkbookSubscriptionInfoList;
+ this.workbookCombineList = this.getWorkbookServerCombineData();
+ }
+ get workbookCombineListData() {
+ return this.workbookCombineList;
+ }
+ getWorkbookServerCombineData(): WorkbookCombineInfo[] {
+ if (this.workbookSubscriptionInfoList) {
+ const workbookCombineSet: WorkbookCombineInfoSet = {};
+
+ const workbookSetList = this.transformDataToSet({
+ data: this.workbookList,
+ });
+ const workbookSetSubscriptionInfoList = this.transformDataToSet({
+ data: this.workbookSubscriptionInfoList,
+ });
+
+ for (const workbookKey in workbookSetList) {
+ const isCommonKey =
+ Object.prototype.hasOwnProperty.call(workbookSetList, workbookKey) &&
+ Object.prototype.hasOwnProperty.call(
+ workbookSetSubscriptionInfoList,
+ workbookKey,
+ );
+
+ if (isCommonKey) {
+ const subscriptionItem = workbookSetSubscriptionInfoList[workbookKey];
+ const workbookItem = workbookSetList[workbookKey];
+
+ workbookCombineSet[workbookKey] = {
+ ...workbookItem,
+ ...subscriptionItem,
+ };
+ } else {
+ const workbookItem = workbookSetList[workbookKey];
+ workbookCombineSet[workbookKey] = {
+ ...workbookItem,
+ };
+ }
+ }
+
+ return Object.entries(workbookCombineSet).map(([key, value]) => ({
+ id: Number(key),
+ ...value,
+ })) as WorkbookCombineInfo[];
+ }
+ return this.workbookList;
+ }
+
+ workbookCardList({
+ workbookCombineList,
+ }: {
+ workbookCombineList: WorkbookCombineInfo[];
+ }): WorkbookClientInfo[] {
+ return workbookCombineList.map(
+ ({
+ mainImageUrl,
+ title,
+ description,
+ category,
+ writers,
+ subscriberCount,
+ status,
+ currentDay,
+ totalDay,
+ }) => {
+ const changeToClientData: WorkbookClientInfo = {
+ mainImageUrl,
+ title,
+ writers: this.getWriterNameList({ writers }),
+ metaComponent: this.getMetaComponent({
+ category,
+ currentDay,
+ totalDay,
+ }),
+ personCourse: this.getPersonCourse({
+ subscriberCount,
+ status,
+ }),
+ buttonTitle: this.getButtonTitle({
+ status,
+ currentDay,
+ }),
+ };
+ return changeToClientData;
+ },
+ );
+ }
+
+ getWriterNameList({ writers }: { writers: WorkbookServerInfo["writers"] }) {
+ return writers.map(({ name }) => name);
+ }
+
+ getMetaComponent({
+ category,
+ totalDay,
+ currentDay,
+ }: {
+ category: WorkbookServerInfo["category"];
+ totalDay: WorkbookSubscriptionInfo["totalDay"] | undefined;
+ currentDay: WorkbookSubscriptionInfo["currentDay"] | undefined;
+ }): WorkbookClientInfo["metaComponent"] {
+ if (totalDay && currentDay) {
+ if (totalDay === currentDay)
+ return (
+
+ Day {currentDay}/{totalDay}
+
+ );
+ return (
+
+ Day {currentDay}
+ /{totalDay}
+
+ );
+ }
+ return {category}
;
+ }
+
+ getPersonCourse({
+ subscriberCount,
+ status,
+ }: {
+ subscriberCount: WorkbookServerInfo["subscriberCount"];
+ status: WorkbookSubscriptionInfo["status"] | undefined;
+ }): WorkbookClientInfo["personCourse"] {
+ if (status) {
+ if (status === "ACTIVE") return `${subscriberCount}명 학습중`;
+ if (status === "DONE") return `총 ${subscriberCount}명`;
+ }
+ return `${subscriberCount}명 학습중`;
+ }
+
+ getButtonTitle({
+ status,
+ currentDay,
+ }: {
+ status: WorkbookSubscriptionInfo["status"] | undefined;
+ currentDay: WorkbookSubscriptionInfo["currentDay"] | undefined;
+ }): WorkbookClientInfo["buttonTitle"] {
+ if (status && currentDay) {
+ if (status === "ACTIVE") return `Day ${currentDay} 학습하기`;
+ if (status === "DONE") return "공유하기";
+ }
+ return "구독하기";
+ }
+
+ transformDataToSet({
+ data,
+ }: {
+ data: WorkbookServerInfo[] | WorkbookSubscriptionInfo[];
+ }) {
+ return data.reduce((acc, item) => {
+ const { id, ...rest } = item;
+ acc[id] = {
+ ...rest,
+ };
+ return acc;
+ }, {});
+ }
+
+ private workbookList: WorkbookServerInfo[];
+ private workbookSubscriptionInfoList: WorkbookSubscriptionInfo[] | undefined;
+ private workbookCombineList: WorkbookCombineInfo[];
+}
+
+type WorkbookCombineInfo = WorkbookServerInfo &
+ Partial;
+type WorkbookCombineInfoSet = {
+ [key: number]:
+ | Omit
+ | Omit, "id">;
+};
diff --git a/src/main/remotes/getWorkbookCategoryQueryOptions.ts b/src/main/remotes/getWorkbookCategoryQueryOptions.ts
index 37ba3b3e..71487228 100644
--- a/src/main/remotes/getWorkbookCategoryQueryOptions.ts
+++ b/src/main/remotes/getWorkbookCategoryQueryOptions.ts
@@ -1,20 +1,20 @@
import { ApiResponse, fewFetch } from "@api/fewFetch";
-import { CategoryInfo } from "@common/types/category";
+import { CategoryInfo, CategoryInfoList } from "@common/types/category";
import { UseQueryOptions } from "@tanstack/react-query";
import { API_ROUTE, QUERY_KEY } from ".";
-const getWorkbookCategory = (): Promise> => {
+const getWorkbookCategory = (): Promise> => {
return fewFetch().get(API_ROUTE.CATEGORY);
};
export const getWorkbookCategoryQueryOptions = (): UseQueryOptions<
- ApiResponse,
+ ApiResponse,
unknown,
CategoryInfo[]
> => {
return {
queryKey: [QUERY_KEY.GET_CATEGORY],
queryFn: () => getWorkbookCategory(),
- select: (data) => data.data.data,
+ select: (data) => data.data.data.categories,
};
};
diff --git a/src/main/types/workbook.ts b/src/main/types/workbook.ts
new file mode 100644
index 00000000..4cefa767
--- /dev/null
+++ b/src/main/types/workbook.ts
@@ -0,0 +1,25 @@
+import { WorkbookInfo } from "@workbook/types";
+
+type SubscriptionStatus = "ACTIVE" | "DONE";
+
+export interface WorkbookSubscriptionInfo extends Pick {
+ status: SubscriptionStatus;
+ totalDay: number;
+ currentDay: number;
+ rank: number;
+ totalSubscriber: number;
+ articleInfo: string; // JSON문자열
+}
+
+export type WorkbookServerInfo = {
+ subscriberCount: number;
+} & Omit;
+
+export interface WorkbookClientInfo {
+ mainImageUrl: string;
+ metaComponent: React.ReactElement;
+ title: string;
+ writers: string[];
+ personCourse: string;
+ buttonTitle: string;
+}
diff --git a/src/mocks/response/category.json b/src/mocks/response/category.json
index c1643ac6..f897abda 100644
--- a/src/mocks/response/category.json
+++ b/src/mocks/response/category.json
@@ -1,29 +1,27 @@
{
- "message": "성공",
- "data": [
- {
- "parameterName": "all",
- "displayName": "전체"
- },
- {
- "parameterName": "economy",
- "displayName": "경제"
- },
- {
- "parameterName": "it",
- "displayName": "IT"
- },
- {
- "parameterName": "marketing",
- "displayName": "마케팅"
- },
- {
- "parameterName": "culture",
- "displayName": "문화"
- },
- {
- "parameterName": "science",
- "displayName": "과학"
- }
- ]
+ "data": {
+ "categories": [
+ {
+ "code": 0,
+ "name": "경제"
+ },
+ {
+ "code": 10,
+ "name": "IT"
+ },
+ {
+ "code": 20,
+ "name": "마케팅"
+ },
+ {
+ "code": 30,
+ "name": "교양"
+ },
+ {
+ "code": 40,
+ "name": "과학"
+ }
+ ]
+ },
+ "message": "성공"
}