From 630fd72fadd79b5bb8578bf74512ab991376ade8 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:25:19 +0900 Subject: [PATCH 1/9] =?UTF-8?q?chore(httpClient):=20meister=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/httpClient/httpClient.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/apis/httpClient/httpClient.ts b/src/apis/httpClient/httpClient.ts index bb25cce1..423eb18e 100644 --- a/src/apis/httpClient/httpClient.ts +++ b/src/apis/httpClient/httpClient.ts @@ -33,6 +33,13 @@ export class HttpClient { return this.api.get("", { ...HttpClient.clientConfig, ...requestConfig }); } + getDetail(data: unknown, requestConfig?: AxiosRequestConfig) { + return this.api.post("/detail", data, { + ...HttpClient.clientConfig, + ...requestConfig, + }); + } + getById(requestConfig?: AxiosRequestConfig) { return this.api.get("/:id", { ...HttpClient.clientConfig, @@ -171,4 +178,6 @@ export default { meal: new HttpClient("api/meal", axiosConfig), calender: new HttpClient("api/calender", axiosConfig), reserve: new HttpClient("api/ber", axiosConfig), + meister: new HttpClient("api/meister", axiosConfig), + ranking: new HttpClient("api/meister/ranking", axiosConfig), }; From c951d5266e64647baba58cc7b6b36f5424baea28 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:25:59 +0900 Subject: [PATCH 2/9] =?UTF-8?q?chore(data):=20=EB=A7=88=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=20=EA=B8=B0=EB=B3=B8=20=EC=86=8D=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/emptyMeister.ts | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/assets/data/emptyMeister.ts diff --git a/src/assets/data/emptyMeister.ts b/src/assets/data/emptyMeister.ts new file mode 100644 index 00000000..616b5b3b --- /dev/null +++ b/src/assets/data/emptyMeister.ts @@ -0,0 +1,38 @@ +import IMeister from "@/interfaces/meister.interface"; + +const emptyMeister: IMeister = { + meister: { + score: 0, + positivePoint: 0, + negativePoint: 0, + lastUpdate: "2000-03-01T00:00:00.000000", + loginError: false, + basicJobSkills: 0, + professionalTech: 0, + workEthic: 0, + humanities: 0, + foreignScore: 0, + }, + avg: { + score: 0, + basicJobSkills: 0, + professionalTech: 0, + workEthic: 0, + humanities: 0, + foreignScore: 0, + positivePoint: 0, + negativePoint: 0, + }, + max: { + score: 0, + basicJobSkills: 0, + professionalTech: 0, + workEthic: 0, + humanities: 0, + foreignScore: 0, + positivePoint: 0, + negativePoint: 0, + }, +}; + +export default emptyMeister; From 21e8bf8c7d0bfd333ceca44ed031c91997ccce59 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:26:18 +0900 Subject: [PATCH 3/9] =?UTF-8?q?chore(key):=20meister=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=82=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/key.constant.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/constants/key.constant.ts b/src/constants/key.constant.ts index 6293a309..4f772ac8 100644 --- a/src/constants/key.constant.ts +++ b/src/constants/key.constant.ts @@ -9,6 +9,9 @@ const KEY = { MEAL: "useMeal", CALENDER: "useCalender", RESERVE: "useReserve", + MEISTER: "useMeister", + MEISTER_DETAIL: "useMeisterDetail", + RANKING: "useRanking", } as const; export default KEY; From 9fa346613a09cc9d3d663eb3db4df3f502afeaa0 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:26:42 +0900 Subject: [PATCH 4/9] =?UTF-8?q?chore(helper):=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=20=EB=B6=84=EB=A5=98=20=ED=95=9C=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BF=94=EC=A3=BC=EB=8A=94=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/getMeisterChapter.helper.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/helpers/getMeisterChapter.helper.ts diff --git a/src/helpers/getMeisterChapter.helper.ts b/src/helpers/getMeisterChapter.helper.ts new file mode 100644 index 00000000..1b39bd91 --- /dev/null +++ b/src/helpers/getMeisterChapter.helper.ts @@ -0,0 +1,16 @@ +const getMeisterChapter = (chapter: string) => { + switch (chapter) { + case "professionalTech": + return "전문 기술 역량"; + case "workEthic": + return "인성 직업 의식"; + case "humanities": + return "인문학적 소양"; + case "foreignScore": + return "외국어 능력"; + default: + return chapter; + } +}; + +export default getMeisterChapter; From 4fc43330f08094bb41e20fbeba5663e270f3a964 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:26:45 +0900 Subject: [PATCH 5/9] =?UTF-8?q?chore(helper):=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=20=EB=B6=84=EB=A5=98=20=ED=95=9C=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BF=94=EC=A3=BC=EB=8A=94=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 33fb3934..34b9b478 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -12,3 +12,4 @@ export { default as getMealName } from "./getMealName.helper"; export { default as getTimetableType } from "./getTimetableType.helper"; export { default as getDay } from "./getDay.helper"; export { default as getClassName } from "./getClassName.helper"; +export { default as getMeisterChapter } from "./getMeisterChapter.helper"; From 86caaf696b4e18f1b1af22020a330ef6e64933f0 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:27:13 +0900 Subject: [PATCH 6/9] =?UTF-8?q?chore(hook):=20useDidMountEffect=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useDidMountEffect.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/hooks/useDidMountEffect.ts diff --git a/src/hooks/useDidMountEffect.ts b/src/hooks/useDidMountEffect.ts new file mode 100644 index 00000000..62933c14 --- /dev/null +++ b/src/hooks/useDidMountEffect.ts @@ -0,0 +1,13 @@ +import { useRef, useEffect } from "react"; + +export const useDidMountEffect = ( + func: () => void, + deps: React.DependencyList, +) => { + const didMount = useRef(false); + + useEffect(() => { + if (didMount.current) func(); + else didMount.current = true; + }, deps); +}; From 7023d5531a7ddd3147564703f1d41b44f9c86e0d Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:27:29 +0900 Subject: [PATCH 7/9] =?UTF-8?q?chore(hook):=20useMeisterHTML=20=ED=9B=85?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMeisterHTML.ts | 124 ++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/hooks/useMeisterHTML.ts diff --git a/src/hooks/useMeisterHTML.ts b/src/hooks/useMeisterHTML.ts new file mode 100644 index 00000000..de51f995 --- /dev/null +++ b/src/hooks/useMeisterHTML.ts @@ -0,0 +1,124 @@ +import { color } from "@/styles"; + +// 심신미약자나 노약자, 임산부, 유아는 코드를 읽는 것을 삼가하기바람 + +export const useMeisterHTML = () => { + const getBasicJobSkills = (html: string) => { + return [ + { + title: "의사소통 국어", + value: +html.substring( + html.lastIndexOf("의사소통(국어) : ") + 11, + html.lastIndexOf("의사소통(국어) : ") + 12, + ), + status: color.primary_blue, + }, + { + title: "의사소통 영어", + value: +html.substring( + html.lastIndexOf("의사소통(영어) : ") + 11, + html.lastIndexOf("의사소통(영어) : ") + 12, + ), + status: color.primary_mint, + }, + { + title: "수리 활용", + value: +html.substring( + html.lastIndexOf("수리활용 : ") + 7, + html.lastIndexOf("수리활용 : ") + 8, + ), + status: color.primary_red, + }, + { + title: "문제 해결", + value: +html.substring( + html.lastIndexOf("문제해결 : ") + 7, + html.lastIndexOf("문제해결 : ") + 8, + ), + status: color.primary_yellow, + }, + ]; + }; + + const scoreParser = (html: string) => { + const parsedHTML = html; + return ( + parsedHTML + .replaceAll( + '
[ ', + (match) => { + return `
${match}`; + }, + ) + .replace(/\[ (\d{4}-\d{2}-\d{2}) \]/g, (match) => { + return `
+
`; + }) + .replaceAll("- 시도와 전국은 중복부여 안함", "") + .replace(/[①-⑮].*?: -
/gi, "") + .replace(/[①-⑮].*?: -/gi, "") + .replace(/\([^)]*\)/gi, "") + .replace(/총점\s+([\d.]+)\s*점/gi, (match) => { + return `${match}`; + }) + .replace(/ \d+ 점/gi, (match) => { + return `${match.replaceAll( + " ", + "", + )}`; + }) + .replaceAll( + '[ 종합 ]', + "", + ) + .replaceAll(",", "") + .replaceAll("font-weight:bold; color:blue;", "") + .replaceAll( + '⑥ 헌혈 : 0 점 ( 총 : 시간 )
', + "", + ) + .replaceAll( + '⑧ 스포츠관련 행사 : ( 총 : 0 건 )
', + "", + ) + .replaceAll( + '⑬ 직업관련 교육 : ( 총 : 0 건 )
', + "", + ) + .replaceAll( + '⑪ 체육/음악 관련 대회 : 0 점 - 시도와 전국은 중복부여 안함 (최고 득점 1개만 적용)
', + "", + ) + .replaceAll("font-weight:bold;", "") + .replaceAll( + '⑪ 체육/음악 관련 대회 : - 시도와 전국은 중복부여 안함 (최고 득점 1개만 적용)
', + "", + ) + .replace(/[①-⑮]/gi, "") + // .replaceAll(":", "·") + .replaceAll( + '[ 종합 ]', + "", + ) + .replaceAll(": 점", ": 0점") + .replaceAll(": 점", ": 0점") + .replaceAll("\n", "") + .replaceAll("\t", "") + .replaceAll(" ", "") + ); + }; + + const pointParser = (html: string) => { + return html.replaceAll( + 'style="border:1px solid #ccc;margin-bottom:10px;border-radius:3px;padding:10px;box-shadow: 2px 2px 1px 2px #ddd;"', + "", + ); + }; + + return { getBasicJobSkills, scoreParser, pointParser }; +}; From 4efceb45f920b7a8432db182dca2a63612864aac Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:28:18 +0900 Subject: [PATCH 8/9] =?UTF-8?q?chore(interface):=20meister=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/index.ts | 1 + src/interfaces/meister.interface.ts | 34 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/interfaces/meister.interface.ts diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 155e9291..315a5395 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -21,3 +21,4 @@ export type { default as IPlan } from "./plan.interface"; export type { default as IReserve } from "./reserve.interface"; export type { default as IReserveList } from "./reserveList.interface"; export type { default as ICreateReserve } from "./createReserve.interface"; +export type { default as IMeister } from "./meister.interface"; diff --git a/src/interfaces/meister.interface.ts b/src/interfaces/meister.interface.ts new file mode 100644 index 00000000..07b97ee7 --- /dev/null +++ b/src/interfaces/meister.interface.ts @@ -0,0 +1,34 @@ +export default interface IMeister { + meister: { + score: number; + positivePoint: number; + negativePoint: number; + lastUpdate: string; + loginError: boolean; + basicJobSkills: number; + professionalTech: number; + workEthic: number; + humanities: number; + foreignScore: number; + }; + avg: { + score: number; + basicJobSkills: number; + professionalTech: number; + workEthic: number; + humanities: number; + foreignScore: number; + positivePoint: number; + negativePoint: number; + }; + max: { + score: number; + basicJobSkills: number; + professionalTech: number; + workEthic: number; + humanities: number; + foreignScore: number; + positivePoint: number; + negativePoint: number; + }; +} From 90e32b834ee1101ee811ded680bafeef21e87d8b Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Thu, 5 Oct 2023 20:28:33 +0900 Subject: [PATCH 9/9] =?UTF-8?q?chore(meister):=20API=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/meister/chart/MeisterChart.tsx | 80 +++--- src/page/meister/chart/RadarChart.tsx | 69 ++--- src/page/meister/index.tsx | 256 +++++++++++++++--- .../meister/layouts/BasicJobSkillCard.tsx | 77 ++++++ .../meister/layouts/CreditDemeritCard.tsx | 113 -------- src/page/meister/layouts/Distribution.tsx | 9 +- src/page/meister/layouts/HistoryCard.tsx | 95 ------- .../meister/layouts/MeisterProfileBox.tsx | 74 +++-- src/page/meister/layouts/Ranking.tsx | 81 ++++++ src/page/meister/layouts/RankingListItem.tsx | 127 +++++++++ src/page/meister/layouts/StatusCard.tsx | 10 +- .../meister/layouts/YearlyMeisterScore.tsx | 18 +- src/page/meister/services/api.service.ts | 21 ++ src/page/meister/services/query.service.ts | 30 ++ 14 files changed, 715 insertions(+), 345 deletions(-) create mode 100644 src/page/meister/layouts/BasicJobSkillCard.tsx delete mode 100644 src/page/meister/layouts/CreditDemeritCard.tsx delete mode 100644 src/page/meister/layouts/HistoryCard.tsx create mode 100644 src/page/meister/layouts/Ranking.tsx create mode 100644 src/page/meister/layouts/RankingListItem.tsx create mode 100644 src/page/meister/services/api.service.ts create mode 100644 src/page/meister/services/query.service.ts diff --git a/src/page/meister/chart/MeisterChart.tsx b/src/page/meister/chart/MeisterChart.tsx index 4903226d..c2ad2244 100644 --- a/src/page/meister/chart/MeisterChart.tsx +++ b/src/page/meister/chart/MeisterChart.tsx @@ -1,57 +1,66 @@ import React from "react"; import * as d3 from "d3"; import { color } from "@/styles"; - -type YearType = "2021" | "2022" | "2023"; +import { IMeister } from "@/interfaces"; +import { useDidMountEffect } from "@/hooks/useDidMountEffect"; interface MeisterChartData { - group: string; - year: YearType; - points: number; + group: + | "basicJobSkills" + | "professionalTech" + | "workEthic" + | "humanities" + | "foreignScore"; + scoreType: "avg" | "meister" | "max"; } const graphColor = { - "2021": color.primary_yellow, - "2022": color.primary_blue, - "2023": color.primary_red, + avg: color.primary_yellow, + meister: color.primary_blue, + max: color.primary_red, }; const groups = [ - "외국어 능력", - "인성 직업 의식", + "직업 기초 능력", "전문 기술 역량", + "인성 직업 의식", "인문학적 소양", - "직업 기초 능력", + "외국어 능력", ]; const data: MeisterChartData[] = [ - { group: "외국어 능력", year: "2021", points: 32 }, - { group: "외국어 능력", year: "2022", points: 43 }, - { group: "외국어 능력", year: "2023", points: 44 }, + { group: "basicJobSkills", scoreType: "avg" }, + { group: "basicJobSkills", scoreType: "meister" }, + { group: "basicJobSkills", scoreType: "max" }, - { group: "인성 직업 의식", year: "2021", points: 40 }, - { group: "인성 직업 의식", year: "2022", points: 36 }, - { group: "인성 직업 의식", year: "2023", points: 31 }, + { group: "professionalTech", scoreType: "avg" }, + { group: "professionalTech", scoreType: "meister" }, + { group: "professionalTech", scoreType: "max" }, - { group: "전문 기술 역량", year: "2021", points: 37 }, - { group: "전문 기술 역량", year: "2022", points: 10 }, - { group: "전문 기술 역량", year: "2023", points: 17 }, + { group: "workEthic", scoreType: "avg" }, + { group: "workEthic", scoreType: "meister" }, + { group: "workEthic", scoreType: "max" }, - { group: "인문학적 소양", year: "2021", points: 10 }, - { group: "인문학적 소양", year: "2022", points: 23 }, - { group: "인문학적 소양", year: "2023", points: 4 }, + { group: "humanities", scoreType: "avg" }, + { group: "humanities", scoreType: "meister" }, + { group: "humanities", scoreType: "max" }, - { group: "직업 기초 능력", year: "2021", points: 38 }, - { group: "직업 기초 능력", year: "2022", points: 2 }, - { group: "직업 기초 능력", year: "2023", points: 25 }, + { group: "foreignScore", scoreType: "avg" }, + { group: "foreignScore", scoreType: "meister" }, + { group: "foreignScore", scoreType: "max" }, ]; const meisterSeparation = [0, 30, 60]; -const MeisterChart = () => { +interface IMeisterChartProps { + meisterData: IMeister; +} + +const MeisterChart = ({ meisterData }: IMeisterChartProps) => { const canvasRef = React.useRef(null); - React.useEffect(() => { + useDidMountEffect(() => { + if (meisterData.avg.basicJobSkills === 0) return; const canvas = d3.select(canvasRef.current); const svg = canvas .append("svg") @@ -74,11 +83,14 @@ const MeisterChart = () => { .attr("height", "0%") .attr("y", "100%") .attr("width", 15) - .attr("fill", graphColor[item.year]) + .attr("fill", graphColor[item.scoreType]) .transition() .duration(1000) - .attr("height", `${(item.points / data.length) * 100}%`) - .attr("y", `${100 - item.points}%`); + .attr( + "height", + `${(meisterData[item.scoreType][item.group] / data.length) * 120}%`, + ) + .attr("y", `${110 - meisterData[item.scoreType][item.group]}%`); svg .append("text") @@ -89,8 +101,8 @@ const MeisterChart = () => { .style("font-size", "10px") .transition() .duration(1000) - .attr("y", `${98 - item.points}%`) - .text(item.points) + .attr("y", `${108 - meisterData[item.scoreType][item.group]}%`) + .text(Math.round(meisterData[item.scoreType][item.group])) .style("fill", color.gray); }); @@ -125,7 +137,7 @@ const MeisterChart = () => { .style("font-weight", "600") .style("font-size", "12px"); }); - }, []); + }, [meisterData]); return
; }; diff --git a/src/page/meister/chart/RadarChart.tsx b/src/page/meister/chart/RadarChart.tsx index 8b568adb..f79b6d89 100644 --- a/src/page/meister/chart/RadarChart.tsx +++ b/src/page/meister/chart/RadarChart.tsx @@ -1,3 +1,4 @@ +import { IMeister } from "@/interfaces"; import React, { useState } from "react"; import Radar from "react-d3-radar"; @@ -21,41 +22,45 @@ interface ChartData { sets: DataSet[]; } -const chartData: ChartData = { - variables: [ - { key: "anxiety", label: "직업기초능력" }, - { key: "illness", label: "전문기술역량" }, - { key: "sucidal", label: "인성/직업의식" }, - { key: "distress", label: "인문학적 소양" }, - { key: "depression", label: "외국어 능력" }, - ], - sets: [ - { - key: "served", - label: "학년 평균", - values: { - anxiety: 20, - illness: 30, - sucidal: 26, - distress: 31, - openness: 8, +interface IRadarChartProps { + meisterData: IMeister; +} + +const RadarChart = ({ meisterData }: IRadarChartProps) => { + const chartData: ChartData = { + variables: [ + { key: "a", label: "직업기초능력" }, + { key: "b", label: "전문기술역량" }, + { key: "c", label: "인성/직업의식" }, + { key: "d", label: "인문학적 소양" }, + { key: "e", label: "외국어 능력" }, + ], + sets: [ + { + key: "avg", + label: "학년 평균", + values: { + a: meisterData.avg.basicJobSkills, + b: meisterData.avg.professionalTech, + c: meisterData.avg.workEthic, + d: meisterData.avg.humanities, + e: meisterData.avg.foreignScore, + }, }, - }, - { - key: "civilians", - label: "Civilians", - values: { - anxiety: 20, - illness: 52, - sucidal: 20, - distress: 10, - openness: 24, + { + key: "my", + label: "내 점수", + values: { + a: meisterData.meister.basicJobSkills, + b: meisterData.meister.professionalTech, + c: meisterData.meister.workEthic, + d: meisterData.meister.humanities, + e: meisterData.meister.foreignScore, + }, }, - }, - ], -}; + ], + }; -const RadarChart = () => { const [highlighted, setHighlighted] = useState(null); const onHover = (hovered: Variable | null) => { diff --git a/src/page/meister/index.tsx b/src/page/meister/index.tsx index 7876d6bf..4b7a4866 100644 --- a/src/page/meister/index.tsx +++ b/src/page/meister/index.tsx @@ -1,46 +1,135 @@ import flex from "@/styles/flex"; -import { Row } from "@/components/Flex"; -import { color } from "@/styles"; +import emptyMeister from "@/assets/data/emptyMeister"; +import { Column, Row } from "@/components/Flex"; +import { color, font } from "@/styles"; +import { Button, Category } from "@/components/atoms"; +import { useRouter } from "next/navigation"; +import { ROUTER } from "@/constants"; +import { useMeisterHTML } from "@/hooks/useMeisterHTML"; import React from "react"; import styled from "styled-components"; import MeisterProfileBox from "./layouts/MeisterProfileBox"; import YearlyMeisterScore from "./layouts/YearlyMeisterScore"; import Distribution from "./layouts/Distribution"; import StatusCard from "./layouts/StatusCard"; -import HistoryCard from "./layouts/HistoryCard"; -import CreditDemeritCard from "./layouts/CreditDemeritCard"; - -const meisterData = [ - { name: "외국어 능력", score: 25, color: color.primary_blue }, - { name: "인성 직업 의식", score: 12, color: color.primary_mint }, - { name: "전문 기술 역량", score: 28, color: color.primary_red }, - { name: "인문학적 소양", score: 42, color: color.primary_yellow }, +import { + useMeisterDetailQuery, + useMeisterQuery, +} from "./services/query.service"; +import Ranking from "./layouts/Ranking"; +import BasicJobSkillCard from "./layouts/BasicJobSkillCard"; + +interface IMeisterData { + name: "professionalTech" | "workEthic" | "humanities" | "foreignScore"; + color: string; +} + +const meisterList: Array = [ + { name: "professionalTech", color: color.primary_mint }, + { name: "workEthic", color: color.primary_red }, + { name: "humanities", color: color.primary_yellow }, + { name: "foreignScore", color: color.primary_blue }, ]; const MeisterPage = () => { + const meisterData = useMeisterQuery(); + const meisterDetail = useMeisterDetailQuery(); + const { scoreParser, pointParser, getBasicJobSkills } = useMeisterHTML(); + const [meister, setMeister] = React.useState(emptyMeister); + const [checked, setChecked] = React.useState("내 정보"); + const router = useRouter(); + + React.useEffect(() => { + if (meisterData.isSuccess) { + setMeister(meisterData.data); + } + }, [meisterData]); + return ( - - - - - - - - {meisterData.map((meister) => ( - - ))} - - {Array.from({ length: 2 }).map((_, i) => ( - - ))} - - + {meisterData.isSuccess && ( + + 조회 형식 + + {["내 정보", "랭킹"].map((title) => ( + setChecked(e.target.id)} + /> + ))} + + {checked === "내 정보" && ( + <> + + + {meisterList.map(({ name, color: status }) => ( + + ))} + + + {meisterData.isSuccess && ( + + )} + + + 💼 직업 기초 능력 + + {meisterDetail.isSuccess && + getBasicJobSkills(meisterDetail.data.scoreHtmlContent).map( + (item) => ( + + ), + )} + + {meisterDetail.isSuccess && ( + <> + + + + )} + + )} + {checked === "랭킹" && } + + )} + {meisterData.isError && ( + + + + )} ); }; @@ -53,7 +142,7 @@ const Layout = styled.div` const Container = styled.div` width: 76%; ${flex.COLUMN_CENTER}; - gap: 18px; + gap: 12px; `; const StatusCardBox = styled.div` @@ -62,4 +151,107 @@ const StatusCardBox = styled.div` gap: 12px; `; +const StyledTitle = styled.span` + ${font.p2}; + font-weight: 500; + margin-right: auto; +`; + +const CategoryBox = styled.div` + display: flex; + gap: 8px; + margin-right: auto; +`; + +const ScoreHTMLContent = styled.div` + width: 100%; + white-space: pre-wrap; + & > div { + display: none; + } + + .item-score { + ${font.p3}; + color: ${color.gray}; + } + + .total-score-item { + ${font.H6}; + } + + .list-item { + ${font.p2}; + font-weight: 600; + padding: 20px; + margin: 10px 0; + border-radius: 4px; + box-shadow: 4px 4px 20px 0 rgba(0, 0, 0, 0.05); + } + + .section-date { + margin: 0; + padding: 0; + text-align: left; + ${font.p3}; + color: ${color.gray}; + font-weight: 500; + } + + & > .titleBarA { + ${font.H5}; + + &:nth-child(2), + &:nth-child(3), + &:nth-child(4) { + display: none; + } + } + + & > table { + &:nth-child(3), + &:nth-child(4), + &:nth-child(5) { + display: none; + } + background-color: ${color.white}; + width: 100%; + padding: 16px 20px; + border-radius: 4px; + + tbody { + tr { + &:first-child, + &:last-child { + display: none; + } + + td { + &:nth-child(1), + &:nth-child(2), + &:nth-child(3) { + display: none; + } + } + } + } + } +`; + +const PointHTMLContent = styled.div` + width: 100%; + & > div { + display: none; + } + + li { + width: 100%; + background-color: ${color.white}; + + & > div { + border: none; + box-shadow: 0; + } + } +`; + export default MeisterPage; diff --git a/src/page/meister/layouts/BasicJobSkillCard.tsx b/src/page/meister/layouts/BasicJobSkillCard.tsx new file mode 100644 index 00000000..d77e6ba2 --- /dev/null +++ b/src/page/meister/layouts/BasicJobSkillCard.tsx @@ -0,0 +1,77 @@ +import { Column, Row } from "@/components/Flex"; +import { color, font } from "@/styles"; +import flex from "@/styles/flex"; +import React from "react"; +import { CircularProgressbar, buildStyles } from "react-circular-progressbar"; +import styled from "styled-components"; + +import "react-circular-progressbar/dist/styles.css"; +import { getMeisterChapter } from "@/helpers"; + +interface IStatusCard { + chapter: string; + score: number; + statusColor: string; +} + +const rankList = ["A", "B", "C", "D"]; + +const scoreList = [100, 75, 50, 25]; + +const BasicJobSkillCard = ({ chapter, score, statusColor }: IStatusCard) => { + return ( + + + + {getMeisterChapter(chapter)} + {score} + + + + + + + ); +}; + +const Container = styled.div` + width: 100%; + height: 120px; + padding: 16px 30px; + background-color: ${color.white}; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const ChapterTitle = styled.span` + ${font.context}; +`; + +const ScoreBox = styled.div<{ statusColor: string }>` + ${font.H3}; + height: 36px; + ${flex.HORIZONTAL} + padding-left: 10px; + border-left: ${({ statusColor }) => `4px solid ${statusColor}`}; + + &:after { + content: "등급"; + } +`; + +const CircularProgressBox = styled.div` + width: 80px; +`; + +export default BasicJobSkillCard; diff --git a/src/page/meister/layouts/CreditDemeritCard.tsx b/src/page/meister/layouts/CreditDemeritCard.tsx deleted file mode 100644 index 0d6c2853..00000000 --- a/src/page/meister/layouts/CreditDemeritCard.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { HistorySeparator } from "@/assets/icons"; -import { Column, Row } from "@/components/Flex"; -import { color, font } from "@/styles"; -import flex from "@/styles/flex"; -import React from "react"; -import styled from "styled-components"; - -const CreditDemeritCard = () => { - return ( - - 상벌점 내역 - {Array.from({ length: 2 }).map((_, i) => ( - - - - - - - - - 2022년 7월 - {Array.from({ length: 2 }).map((__, idx) => ( - - - {!(idx % 2) ? "타호실 취침" : "교내 면학 환경 조성"} - - - - 2022.07.06 - 5 - 홍길동 - - - ))} - - - ))} - - ); -}; - -const Container = styled.div` - width: 100%; - padding: 14px 20px; - background-color: ${color.white}; - ${flex.COLUMN}; - gap: 12px; -`; - -const Title = styled.span` - ${font.H6}; -`; - -const HistorySeparatorBox = styled.div``; - -const HistorySeparatorLine = styled.div` - width: 2px; - height: 100%; - border-radius: 2px; - background-color: ${color.on_tertiary}; -`; - -const HistorySeparatorDate = styled.span` - ${font.p4}; - color: ${color.gray}; -`; - -const HistoryBox = styled.div` - width: 100%; - background-color: ${color.white}; - box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.05); - border-radius: 6px; - padding: 10px 14px; - ${flex.COLUMN} -`; - -const HistoryName = styled.span` - ${font.p3}; - font-weight: 500; -`; - -const HistorySubText = styled.span` - ${font.caption}; - color: ${color.content}; -`; - -const HistoryDate = styled(HistorySubText)``; - -const HistoryScore = styled(HistorySubText)` - &:before { - content: " · "; - } - - &:after { - content: "점"; - } -`; - -const HisotryTeacher = styled(HistoryScore)` - &:after { - content: " 선생님"; - } -`; - -const StatusCircle = styled.div<{ type: "credit" | "demerit" }>` - width: 10px; - height: 10px; - border-radius: 50%; - background-color: ${({ type }) => - type === "credit" ? color.green : color.red}; -`; - -export default CreditDemeritCard; diff --git a/src/page/meister/layouts/Distribution.tsx b/src/page/meister/layouts/Distribution.tsx index 61d7c56a..40c08b2e 100644 --- a/src/page/meister/layouts/Distribution.tsx +++ b/src/page/meister/layouts/Distribution.tsx @@ -1,5 +1,6 @@ import DistributionIcon from "@/assets/icons/DistributionIcon"; import { Row } from "@/components/Flex"; +import { IMeister } from "@/interfaces"; import { color, font } from "@/styles"; import flex from "@/styles/flex"; import React from "react"; @@ -11,7 +12,11 @@ const spiderChartValue = [ { name: "학년 평균", color: color.spider_second }, ]; -const Distribution = () => { +interface IDistributionProps { + meisterData: IMeister; +} + +const Distribution = ({ meisterData }: IDistributionProps) => { return ( @@ -19,7 +24,7 @@ const Distribution = () => { - + {spiderChartValue.map((chart) => ( diff --git a/src/page/meister/layouts/HistoryCard.tsx b/src/page/meister/layouts/HistoryCard.tsx deleted file mode 100644 index e3e94b88..00000000 --- a/src/page/meister/layouts/HistoryCard.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { HistorySeparator } from "@/assets/icons"; -import { Column, Row } from "@/components/Flex"; -import { color, font } from "@/styles"; -import flex from "@/styles/flex"; -import React from "react"; -import styled from "styled-components"; - -const HistoryCard = () => { - return ( - - 인성 직업 의식 - {Array.from({ length: 2 }).map((_, i) => ( - - - - - - - - - 2022년 7월 - {Array.from({ length: 2 }).map((__, idx) => ( - - SW관련 기사 링크 본인의 생각(500자) - - 2022.07.06 - 5 - - - ))} - - - ))} - - ); -}; - -const Container = styled.div` - width: 100%; - padding: 14px 20px; - background-color: ${color.white}; - ${flex.COLUMN}; - gap: 12px; -`; - -const Title = styled.span` - ${font.H6}; -`; - -const HistorySeparatorBox = styled.div``; - -const HistorySeparatorLine = styled.div` - width: 2px; - height: 100%; - border-radius: 2px; - background-color: ${color.on_tertiary}; -`; - -const HistorySeparatorDate = styled.span` - ${font.p4}; - color: ${color.gray}; -`; - -const HistoryBox = styled.div` - width: 100%; - background-color: ${color.white}; - box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.05); - border-radius: 6px; - padding: 10px 14px; - ${flex.COLUMN} -`; - -const HistoryName = styled.span` - ${font.p3}; - font-weight: 500; -`; - -const HistorySubText = styled.span` - ${font.caption}; - color: ${color.content}; -`; - -const HistoryDate = styled(HistorySubText)``; - -const HistoryScore = styled(HistorySubText)` - &:before { - content: " · "; - } - - &:after { - content: "점"; - } -`; - -export default HistoryCard; diff --git a/src/page/meister/layouts/MeisterProfileBox.tsx b/src/page/meister/layouts/MeisterProfileBox.tsx index 90c12ce3..b45e0028 100644 --- a/src/page/meister/layouts/MeisterProfileBox.tsx +++ b/src/page/meister/layouts/MeisterProfileBox.tsx @@ -1,37 +1,61 @@ import { defaultProfile } from "@/assets/images"; import { Column, Row } from "@/components/Flex"; import { ImageWithFallback } from "@/components/atoms"; +import useUser from "@/hooks/useUser"; import { color, font } from "@/styles"; import React from "react"; import styled from "styled-components"; -const MeisterProfileBox = () => { +interface IMeisterProfileBoxProps { + meister: { + score: number; + positivePoint: number; + negativePoint: number; + }; +} + +const MeisterProfileBox = ({ meister }: IMeisterProfileBoxProps) => { + const { user, isLogined } = useUser(); + return ( - - - - 소프트웨어 - 2 - 2 - 10 - 박우빈 - - 135.2 - - 48 - - 22 - - - + {isLogined && ( + <> + + + + + {user.grade >= 2 ? ( + <> + {user.classNum <= 2 && "소프트웨어"} + {user.classNum >= 3 && "임베디드소프트웨어"} + + ) : ( + "통합" + )} + + {user.grade} + {user.classNum} + {user.studentNumber} + {user.name} + + {meister.score} + + {meister.positivePoint} + + {meister.negativePoint} + + + + + )} ); }; diff --git a/src/page/meister/layouts/Ranking.tsx b/src/page/meister/layouts/Ranking.tsx new file mode 100644 index 00000000..b8362797 --- /dev/null +++ b/src/page/meister/layouts/Ranking.tsx @@ -0,0 +1,81 @@ +import { Category } from "@/components/atoms"; +import { flex, font } from "@/styles"; +import React from "react"; +import styled from "styled-components"; +import useUser from "@/hooks/useUser"; +import { useMeisterRankingQuery } from "../services/query.service"; +import RankingListItem from "./RankingListItem"; + +const Ranking = () => { + const { user } = useUser(); + const [currentGrade, setCurrentGrade] = React.useState(user.grade); + const { data, isSuccess, refetch } = useMeisterRankingQuery({ + grade: currentGrade, + }); + + React.useEffect(() => { + refetch(); + }, [currentGrade, refetch]); + + return ( + + + + {[1, 2, 3].map((grade) => ( + setCurrentGrade(+e.target.id)} + checked={currentGrade === grade} + label={`${grade}학년`} + /> + ))} + + + {isSuccess && + data.map( + (rankingItem: { + score: number; + positivePoint: number; + negativePoint: number; + student: { + grade: number; + classNo: number; + studentNo: number; + name: string; + }; + }) => , + )} + + + ); +}; + +const Container = styled.div` + width: 100%; + height: 100%; + ${flex.COLUMN}; + gap: 12px; +`; + +const CategoryBox = styled.div` + display: flex; + gap: 8px; + flex-wrap: wrap; +`; + +const RankingTitle = styled.span` + ${font.H2}; + + &:after { + content: "📊 마이스터역량인증제 순위"; + } +`; + +const RankingList = styled.div` + width: 100%; + ${flex.COLUMN}; + gap: 12px; +`; + +export default Ranking; diff --git a/src/page/meister/layouts/RankingListItem.tsx b/src/page/meister/layouts/RankingListItem.tsx new file mode 100644 index 00000000..9b0fe119 --- /dev/null +++ b/src/page/meister/layouts/RankingListItem.tsx @@ -0,0 +1,127 @@ +import { Column, Row } from "@/components/Flex"; +import { color, font } from "@/styles"; +import React from "react"; +import styled from "styled-components"; + +interface IMeisterProfileBoxProps { + score: number; + positivePoint: number; + negativePoint: number; + student: { + grade: number; + classNo: number; + studentNo: number; + name: string; + }; +} + +const RankingListItem = ({ + score, + student, + positivePoint, + negativePoint, +}: IMeisterProfileBoxProps) => { + return ( + + + + + {student.grade >= 2 ? ( + + {student.classNo <= 2 ? "소프트웨어" : "임베디드소프트웨어"} + + ) : ( + "통합" + )} + + {student.grade} + {student.classNo} + {student.studentNo} + {student.name} + + {score} + + {positivePoint} + + {negativePoint} + + + + ); +}; + +const Container = styled.div` + width: 100%; + padding: 14px 24px; + background-color: ${color.white}; + display: flex; + align-items: center; + gap: 18px; +`; + +const InfomationText = styled.span` + ${font.H6}; + color: ${color.black}; +`; + +const Department = styled(InfomationText)` + &:after { + content: "개발과"; + } +`; + +const Grade = styled(InfomationText)` + &:after { + content: "학년"; + } +`; + +const ClassNo = styled(InfomationText)` + &:after { + content: "반"; + } +`; + +const StudentNo = styled(InfomationText)` + &:after { + content: "번"; + } +`; + +const Name = styled(InfomationText)``; + +const RewardPointText = styled.span` + ${font.p3}; + line-height: 130%; + color: ${color.gray}; + + &:after { + content: "점"; + } +`; + +const MeisterPoint = styled(RewardPointText)` + &:before { + content: "마이스터역량인증제 점수 ㆍ "; + } +`; + +const CreditPoint = styled(RewardPointText)` + &:before { + content: "상점 ㆍ "; + } +`; + +const Separator = styled.div` + width: 1.5px; + background-color: ${color.on_tertiary}; + height: 12px; +`; + +const DemeritPoint = styled(RewardPointText)` + &:before { + content: "벌점 ㆍ "; + } +`; + +export default RankingListItem; diff --git a/src/page/meister/layouts/StatusCard.tsx b/src/page/meister/layouts/StatusCard.tsx index 3d964940..83acfc23 100644 --- a/src/page/meister/layouts/StatusCard.tsx +++ b/src/page/meister/layouts/StatusCard.tsx @@ -6,25 +6,27 @@ import { CircularProgressbar, buildStyles } from "react-circular-progressbar"; import styled from "styled-components"; import "react-circular-progressbar/dist/styles.css"; +import { getMeisterChapter } from "@/helpers"; interface IStatusCard { chapter: string; score: number; + maxScore: number; statusColor: string; } -const StatusCard = ({ chapter, score, statusColor }: IStatusCard) => { +const StatusCard = ({ chapter, score, statusColor, maxScore }: IStatusCard) => { return ( - {chapter} + {getMeisterChapter(chapter)} {score} { - const { currentYearsWithSchool } = useDate(); +interface IYearlyMeisterScroeProps { + meisterData: IMeister; +} +const YearlyMeisterScore = ({ meisterData }: IYearlyMeisterScroeProps) => { return ( - + - {currentYearsWithSchool.map((year, index) => ( + {["학년 평균", "내 점수", "최고 점수"].map((year, index) => ( - + {year} ))} @@ -55,11 +57,11 @@ const StatusBox = styled.div` gap: 16px; `; -const StatusCircle = styled.div<{ statusColor: string }>` +const StatusCircle = styled.div<{ statuscolor: string }>` width: 12px; height: 12px; border-radius: 50%; - background-color: ${({ statusColor }) => statusColor}; + background-color: ${({ statuscolor }) => statuscolor}; `; const StatusText = styled.span` diff --git a/src/page/meister/services/api.service.ts b/src/page/meister/services/api.service.ts new file mode 100644 index 00000000..7c0697ef --- /dev/null +++ b/src/page/meister/services/api.service.ts @@ -0,0 +1,21 @@ +import httpClient from "@/apis/httpClient"; + +export const getMeister = async () => { + const { data } = await httpClient.meister.get(); + return data; +}; + +export const getMeisterRanking = async (grade: number) => { + const { data } = await httpClient.ranking.getById({ params: { id: grade } }); + return data; +}; + +export const getMeisterDetail = async () => { + const { data } = await httpClient.meister.getDetail({ + grade: 2, + classNo: 3, + studentNo: 1, + pw: "", + }); + return data; +}; diff --git a/src/page/meister/services/query.service.ts b/src/page/meister/services/query.service.ts new file mode 100644 index 00000000..91a6b23b --- /dev/null +++ b/src/page/meister/services/query.service.ts @@ -0,0 +1,30 @@ +import { KEY } from "@/constants"; +import { useQuery } from "@tanstack/react-query"; +import { getMeister, getMeisterDetail, getMeisterRanking } from "./api.service"; + +export const useMeisterQuery = () => { + const { data, ...queryRest } = useQuery([KEY.MEISTER], async () => + getMeister(), + ); + return { data, ...queryRest }; +}; + +interface IUseMeisterRankingQueryProps { + grade: number; +} + +export const useMeisterRankingQuery = ({ + grade, +}: IUseMeisterRankingQueryProps) => { + const { data, ...queryRest } = useQuery([KEY.RANKING], async () => + getMeisterRanking(grade), + ); + return { data, ...queryRest }; +}; + +export const useMeisterDetailQuery = () => { + const { data, ...queryRest } = useQuery([KEY.MEISTER_DETAIL], async () => + getMeisterDetail(), + ); + return { data, ...queryRest }; +};