From a4bb5fe26893b95bb1972c53f867aea58e8d1a48 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 21:42:53 +0900 Subject: [PATCH 01/12] =?UTF-8?q?#234=20feat:=20=EC=84=B8=EB=B6=80=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EB=A9=94=EB=89=B4=20=EC=83=9D=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20=EB=8D=94=EB=AF=B8=EB=8D=B0=EC=9D=B4=ED=84=B0=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 --- .../TopNavigation/TopNavigation.jsx | 4 + .../DetailStatisticsPage.jsx | 21 +++ .../DetailStatisticsPage.style.jsx | 45 +++++ src/pages/DetailStatisticsPage/attendee.js | 159 ++++++++++++++++++ src/pages/DetailStatisticsPage/index.js | 1 + src/pages/index.js | 2 + src/router.js | 5 + 7 files changed, 237 insertions(+) create mode 100644 src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx create mode 100644 src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx create mode 100644 src/pages/DetailStatisticsPage/attendee.js create mode 100644 src/pages/DetailStatisticsPage/index.js diff --git a/src/components/TopNavigation/TopNavigation.jsx b/src/components/TopNavigation/TopNavigation.jsx index d43cbb4..a207bcb 100644 --- a/src/components/TopNavigation/TopNavigation.jsx +++ b/src/components/TopNavigation/TopNavigation.jsx @@ -60,6 +60,10 @@ export default function TopNavigation({ eventTitle } = {}) { 통계 + {/* 세부 통계 페이지는 추후 통째로 삭제 */} + + 세부 통계(임시) + {location.pathname.startsWith('/event/dashboard') && ( {eventTitle !== undefined && ( diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx new file mode 100644 index 0000000..ba2ae3d --- /dev/null +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -0,0 +1,21 @@ +import * as S from './DetailStatisticsPage.style'; +import { PageLayout } from '@/Layout'; +import { TopNavigation } from '@/components'; + +const DetailStatisticsPage = () => { + return ( + }> + + + + 행사별 통계 + + + + + + + ); +}; + +export default DetailStatisticsPage; diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx new file mode 100644 index 0000000..cf2cb40 --- /dev/null +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx @@ -0,0 +1,45 @@ +import { BREAKPOINTS } from '@/styles'; +import styled from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +export const DetailStatisticsPage = styled.div` + display: flex; + flex-direction: column; + width: 100%; + max-width: 1100px; + margin: 0 auto; + padding: 50px 20px; + gap: 30px; + + border: 1px solid red; /* 임시 코드 */ +`; + +// 행사 타이틀 + 버튼 +export const TopContainer = styled.div` + display: flex; + justify-content: space-between; + margin-bottom: 20px; + + @media (max-width: ${BREAKPOINTS[0]}px) { + margin-bottom: 10px; + } +`; + +export const Title = styled.h1` + font-size: 24px; + font-weight: bold; +`; + +// 통계 +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + width: 500px; + gap: 32px; + padding-top: 20px; +`; diff --git a/src/pages/DetailStatisticsPage/attendee.js b/src/pages/DetailStatisticsPage/attendee.js new file mode 100644 index 0000000..cf0d847 --- /dev/null +++ b/src/pages/DetailStatisticsPage/attendee.js @@ -0,0 +1,159 @@ +export const ATTENDEE_LIST = [ + { + eventId: 0, + eventTitle: 'string', + eventSchedules: [ + { + date: '2024-09-11T12:18:40.472Z', + students: [ + { + major: '소프트웨어응용학전공', + studentYear: 21, + isAttending: true, + }, + { + major: '컴퓨터과학전공', + studentYear: 21, + isAttending: false, + }, + { + major: '컴퓨터과학전공', + studentYear: 22, + isAttending: true, + }, + { + major: '경영학과', + studentYear: 23, + isAttending: true, + }, + { + major: '경영학과', + studentYear: 23, + isAttending: false, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: true, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: true, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: false, + }, + { + major: '수학과', + studentYear: 24, + isAttending: true, + }, + ], + }, + { + date: '2024-09-12T12:18:40.472Z', + students: [ + { + major: '소프트웨어응용학전공', + studentYear: 21, + isAttending: true, + }, + { + major: '컴퓨터과학전공', + studentYear: 21, + isAttending: false, + }, + { + major: '컴퓨터과학전공', + studentYear: 22, + isAttending: true, + }, + { + major: '경영학과', + studentYear: 23, + isAttending: false, + }, + { + major: '경영학과', + studentYear: 23, + isAttending: true, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: true, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: false, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: true, + }, + { + major: '수학과', + studentYear: 24, + isAttending: false, + }, + ], + }, + { + date: '2024-09-13T12:18:40.472Z', + students: [ + { + major: '소프트웨어응용학전공', + studentYear: 21, + isAttending: false, + }, + { + major: '컴퓨터과학전공', + studentYear: 21, + isAttending: true, + }, + { + major: '컴퓨터과학전공', + studentYear: 22, + isAttending: true, + }, + { + major: '경영학과', + studentYear: 23, + isAttending: true, + }, + { + major: '경영학과', + studentYear: 23, + isAttending: false, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: true, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: false, + }, + { + major: '통계학과', + studentYear: 24, + isAttending: true, + }, + { + major: '수학과', + studentYear: 24, + isAttending: false, + }, + ], + }, + ], + eventImage: 'string', + }, +]; diff --git a/src/pages/DetailStatisticsPage/index.js b/src/pages/DetailStatisticsPage/index.js new file mode 100644 index 0000000..f7dae42 --- /dev/null +++ b/src/pages/DetailStatisticsPage/index.js @@ -0,0 +1 @@ +export { default as DetailStatisticsPage } from './DetailStatisticsPage'; diff --git a/src/pages/index.js b/src/pages/index.js index 2a1bafc..e236c28 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -12,3 +12,5 @@ export { EventCardListPage } from './EventCardListPage'; export { TotalStatisticsPage } from './TotalStatisticsPage'; export { LoadingPage } from './LoadingPage'; export { RegisterCompleted } from './RegisterPage'; +// 추후 삭제 +export { DetailStatisticsPage } from './DetailStatisticsPage'; diff --git a/src/router.js b/src/router.js index 687c4f5..a70169f 100644 --- a/src/router.js +++ b/src/router.js @@ -13,6 +13,7 @@ import { DashboardStatisticPage, LoadingPage, RegisterCompleted, + DetailStatisticsPage, } from './pages'; import Layout from './Layout/Layout'; @@ -60,6 +61,10 @@ const router = createBrowserRouter([ path: '/stats', element: , }, + { + path: '/stats/detail', + element: , + }, { path: '/loading', element: , From 6233a72e15295a2daa0e4b122b7317779c7c2545 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 21:43:34 +0900 Subject: [PATCH 02/12] =?UTF-8?q?#234=20feat:=20chart.js=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 17 +++++++++++++++++ package.json | 1 + 2 files changed, 18 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7f70906..98ecb86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.6.8", + "chart.js": "^4.4.4", "craco-alias": "^3.0.1", "date-fns": "^3.6.0", "moment": "^2.30.1", @@ -3340,6 +3341,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -6325,6 +6331,17 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", + "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", diff --git a/package.json b/package.json index 5d00994..7388764 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.6.8", + "chart.js": "^4.4.4", "craco-alias": "^3.0.1", "date-fns": "^3.6.0", "moment": "^2.30.1", From 7fc85cac9cc21b1f5da3f5ddbb50a23d31c634c8 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 22:05:48 +0900 Subject: [PATCH 03/12] =?UTF-8?q?#234=20feat:=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20chart=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 10 ++ package.json | 1 + .../DetailStatisticsPage.jsx | 106 +++++++++++++++++- .../DetailStatisticsPage.style.jsx | 31 ++++- 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98ecb86..9d96224 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-animated-numbers": "^0.18.0", "react-app-alias": "^2.2.2", "react-calendar": "^5.0.0", + "react-chartjs-2": "^5.2.0", "react-datepicker": "^7.2.0", "react-dom": "^18.3.1", "react-icons": "^5.2.1", @@ -15567,6 +15568,15 @@ } } }, + "node_modules/react-chartjs-2": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-datepicker": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.2.0.tgz", diff --git a/package.json b/package.json index 7388764..6c814d0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react-animated-numbers": "^0.18.0", "react-app-alias": "^2.2.2", "react-calendar": "^5.0.0", + "react-chartjs-2": "^5.2.0", "react-datepicker": "^7.2.0", "react-dom": "^18.3.1", "react-icons": "^5.2.1", diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index ba2ae3d..ab3b9a8 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -1,17 +1,121 @@ import * as S from './DetailStatisticsPage.style'; import { PageLayout } from '@/Layout'; import { TopNavigation } from '@/components'; +import { Doughnut } from 'react-chartjs-2'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; +import { ATTENDEE_LIST } from './attendee'; + +ChartJS.register(ArcElement, Tooltip, Legend); const DetailStatisticsPage = () => { + // 학과 비율 + const departmentAttendance = {}; + ATTENDEE_LIST[0].eventSchedules.forEach((schedule) => { + schedule.students.forEach((student) => { + if (student.isAttending) { + if (departmentAttendance[student.major]) { + departmentAttendance[student.major]++; + } else { + departmentAttendance[student.major] = 1; + } + } + }); + }); + + // 학번별 참석률 + const yearAttendance = {}; + ATTENDEE_LIST[0].eventSchedules.forEach((schedule) => { + schedule.students.forEach((student) => { + if (student.isAttending) { + if (yearAttendance[student.studentYear]) { + yearAttendance[student.studentYear]++; + } else { + yearAttendance[student.studentYear] = 1; + } + } + }); + }); + + // 학과별 참석률 + const departmentData = { + labels: Object.keys(departmentAttendance), + datasets: [ + { + data: Object.values(departmentAttendance), + backgroundColor: [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(255, 159, 64, 0.2)', + ], + borderColor: [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)', + ], + borderWidth: 1, + }, + ], + }; + + // 학번별 참석률 + const yearData = { + labels: Object.keys(yearAttendance).map((year) => `${year}학번`), + datasets: [ + { + data: Object.values(yearAttendance), + backgroundColor: [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(255, 159, 64, 0.2)', + ], + borderColor: [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)', + ], + borderWidth: 1, + }, + ], + }; + return ( }> 행사별 통계 + + 통계 조사 기간 + - + + + 행사에 참석한 학과 비율 + + + + + + + 각 학번별 참석률 + + + + + diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx index cf2cb40..14ba59f 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx @@ -22,8 +22,9 @@ export const DetailStatisticsPage = styled.div` // 행사 타이틀 + 버튼 export const TopContainer = styled.div` display: flex; - justify-content: space-between; margin-bottom: 20px; + gap: 10px; + align-items: flex-end; @media (max-width: ${BREAKPOINTS[0]}px) { margin-bottom: 10px; @@ -35,6 +36,15 @@ export const Title = styled.h1` font-weight: bold; `; +export const EventDate = styled.p` + font-size: 14px; + margin-bottom: 2px; + + & > span { + font-weight: 600; + } +`; + // 통계 export const ContentContainer = styled.div` display: flex; @@ -43,3 +53,22 @@ export const ContentContainer = styled.div` gap: 32px; padding-top: 20px; `; + +export const ChartWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 10px; +`; + +export const ChartTitle = styled.h2` + font-size: 18px; + font-weight: 600; +`; + +export const Chart = styled.div` + border-radius: 20px; + border: 1px solid #aecfff; + background: #fff; + padding: 55px 48px; + height: 300px; +`; From 1e452bd249999d1d9700b388aa5c0794cec1f53a Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 22:22:36 +0900 Subject: [PATCH 04/12] =?UTF-8?q?#234=20feat:=20legend=20=EC=98=A4?= =?UTF-8?q?=EB=A5=B8=EC=AA=BD=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailStatisticsPage/DetailStatisticsPage.jsx | 12 ++++++++++-- .../DetailStatisticsPage.style.jsx | 9 +++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index ab3b9a8..0a425a4 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -63,6 +63,14 @@ const DetailStatisticsPage = () => { ], }; + const options = { + plugins: { + legend: { + position: 'right', + }, + }, + }; + // 학번별 참석률 const yearData = { labels: Object.keys(yearAttendance).map((year) => `${year}학번`), @@ -105,14 +113,14 @@ const DetailStatisticsPage = () => { 행사에 참석한 학과 비율 - + 각 학번별 참석률 - + diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx index 14ba59f..0a57664 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx @@ -66,9 +66,14 @@ export const ChartTitle = styled.h2` `; export const Chart = styled.div` + display: flex; + justify-content: center; + align-items: center; border-radius: 20px; border: 1px solid #aecfff; background: #fff; - padding: 55px 48px; - height: 300px; + padding: 20px 30px; + width: 100%; + height: 100%; + max-height: 300px; `; From c76806509d51b59d148bb1daef01d3c75fd15f00 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 22:25:41 +0900 Subject: [PATCH 05/12] =?UTF-8?q?#234=20feat:=20=ED=86=B5=EA=B3=84=20?= =?UTF-8?q?=EC=A1=B0=EC=82=AC=20=EA=B8=B0=EA=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx | 8 +++++++- .../DetailStatisticsPage/DetailStatisticsPage.style.jsx | 6 ++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index 0a425a4..33ae63a 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -8,6 +8,12 @@ import { ATTENDEE_LIST } from './attendee'; ChartJS.register(ArcElement, Tooltip, Legend); const DetailStatisticsPage = () => { + const startDate = ATTENDEE_LIST[0].eventSchedules[0].date.split('T')[0]; + const endDate = + ATTENDEE_LIST[0].eventSchedules[ + ATTENDEE_LIST[0].eventSchedules.length - 1 + ].date.split('T')[0]; + // 학과 비율 const departmentAttendance = {}; ATTENDEE_LIST[0].eventSchedules.forEach((schedule) => { @@ -105,7 +111,7 @@ const DetailStatisticsPage = () => { 행사별 통계 - 통계 조사 기간 + {startDate} ~ {endDate} diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx index 0a57664..730122b 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx @@ -39,10 +39,8 @@ export const Title = styled.h1` export const EventDate = styled.p` font-size: 14px; margin-bottom: 2px; - - & > span { - font-weight: 600; - } + color: #6b6b6b; + font-weight: 500; `; // 통계 From 1a96b0c056a02c75424182e55c19be535a22e430 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 22:35:25 +0900 Subject: [PATCH 06/12] =?UTF-8?q?#234=20feat:=20=EC=B0=A8=ED=8A=B8=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailStatisticsPage.jsx | 88 +++++++++---------- .../DetailStatisticsPage.style.jsx | 2 +- 2 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index 33ae63a..7b60de6 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -14,7 +14,7 @@ const DetailStatisticsPage = () => { ATTENDEE_LIST[0].eventSchedules.length - 1 ].date.split('T')[0]; - // 학과 비율 + // 학과별 참석 비율을 계산하는 로직 const departmentAttendance = {}; ATTENDEE_LIST[0].eventSchedules.forEach((schedule) => { schedule.students.forEach((student) => { @@ -28,7 +28,30 @@ const DetailStatisticsPage = () => { }); }); - // 학번별 참석률 + // 학과별 참석 비율 정렬 + const sortedDepartmentAttendance = Object.entries(departmentAttendance).sort( + (a, b) => b[1] - a[1], + ); + const departmentLabels = sortedDepartmentAttendance.map((item) => item[0]); + const departmentValues = sortedDepartmentAttendance.map((item) => item[1]); + + const departmentData = { + labels: departmentLabels, + datasets: [ + { + data: departmentValues, + backgroundColor: [ + '#2F7CEF', + '#ACCDFF', + '#2f7cef33', + '#EDF5FF', + '#E4E4E4', + ], + }, + ], + }; + + // 학번별 참석 비율을 계산하는 로직 const yearAttendance = {}; ATTENDEE_LIST[0].eventSchedules.forEach((schedule) => { schedule.students.forEach((student) => { @@ -42,29 +65,25 @@ const DetailStatisticsPage = () => { }); }); - // 학과별 참석률 - const departmentData = { - labels: Object.keys(departmentAttendance), + // 학번별 참석 비율 정렬 + const sortedYearAttendance = Object.entries(yearAttendance).sort( + (a, b) => b[1] - a[1], + ); + const yearLabels = sortedYearAttendance.map((item) => `${item[0]}학번`); + const yearValues = sortedYearAttendance.map((item) => item[1]); + + const yearData = { + labels: yearLabels, datasets: [ { - data: Object.values(departmentAttendance), + data: yearValues, backgroundColor: [ - 'rgba(255, 99, 132, 0.2)', - 'rgba(54, 162, 235, 0.2)', - 'rgba(255, 206, 86, 0.2)', - 'rgba(75, 192, 192, 0.2)', - 'rgba(153, 102, 255, 0.2)', - 'rgba(255, 159, 64, 0.2)', + '#2F7CEF', + '#ACCDFF', + '#2f7cef33', + '#EDF5FF', + '#E4E4E4', ], - borderColor: [ - 'rgba(255, 99, 132, 1)', - 'rgba(54, 162, 235, 1)', - 'rgba(255, 206, 86, 1)', - 'rgba(75, 192, 192, 1)', - 'rgba(153, 102, 255, 1)', - 'rgba(255, 159, 64, 1)', - ], - borderWidth: 1, }, ], }; @@ -77,33 +96,6 @@ const DetailStatisticsPage = () => { }, }; - // 학번별 참석률 - const yearData = { - labels: Object.keys(yearAttendance).map((year) => `${year}학번`), - datasets: [ - { - data: Object.values(yearAttendance), - backgroundColor: [ - 'rgba(255, 99, 132, 0.2)', - 'rgba(54, 162, 235, 0.2)', - 'rgba(255, 206, 86, 0.2)', - 'rgba(75, 192, 192, 0.2)', - 'rgba(153, 102, 255, 0.2)', - 'rgba(255, 159, 64, 0.2)', - ], - borderColor: [ - 'rgba(255, 99, 132, 1)', - 'rgba(54, 162, 235, 1)', - 'rgba(255, 206, 86, 1)', - 'rgba(75, 192, 192, 1)', - 'rgba(153, 102, 255, 1)', - 'rgba(255, 159, 64, 1)', - ], - borderWidth: 1, - }, - ], - }; - return ( }> diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx index 730122b..8a16636 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx @@ -70,7 +70,7 @@ export const Chart = styled.div` border-radius: 20px; border: 1px solid #aecfff; background: #fff; - padding: 20px 30px; + padding: 0 30px; width: 100%; height: 100%; max-height: 300px; From 7f43370b7bee72ef511c2ec176e3d5fdffb7e3a8 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 22:42:49 +0900 Subject: [PATCH 07/12] =?UTF-8?q?#234=20feat:=20=EC=A3=BC=EC=9A=94=20?= =?UTF-8?q?=EB=84=A4=20=EA=B0=9C=20=ED=95=AD=EB=AA=A9=EB=A7=8C=20=EB=B8=94?= =?UTF-8?q?=EB=A3=A8=20=EA=B3=84=EC=97=B4=EB=A1=9C=20=EC=BB=AC=EB=9F=AC?= =?UTF-8?q?=EB=A7=81=20=ED=95=98=EA=B3=A0=20=EA=B7=B8=EC=99=B8=20=EA=B8=B0?= =?UTF-8?q?=ED=83=80=20=ED=95=AD=EB=AA=A9=EC=9D=80=20=ED=9A=8C=EC=83=89?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=BB=AC=EB=9F=AC=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailStatisticsPage.jsx | 78 ++++++++++++++----- src/pages/DetailStatisticsPage/attendee.js | 15 ++++ 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index 7b60de6..0c5beec 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -32,21 +32,42 @@ const DetailStatisticsPage = () => { const sortedDepartmentAttendance = Object.entries(departmentAttendance).sort( (a, b) => b[1] - a[1], ); - const departmentLabels = sortedDepartmentAttendance.map((item) => item[0]); - const departmentValues = sortedDepartmentAttendance.map((item) => item[1]); + + const majorAttendanceLimit = 4; + const departmentLabels = sortedDepartmentAttendance + .slice(0, majorAttendanceLimit) + .map((item) => item[0]); + + const etcValue = sortedDepartmentAttendance + .slice(majorAttendanceLimit) + .reduce((sum, item) => sum + item[1], 0); + + if (etcValue > 0) { + departmentLabels.push('기타'); + } + + const departmentValues = sortedDepartmentAttendance + .slice(0, majorAttendanceLimit) + .map((item) => item[1]); + + if (etcValue > 0) { + departmentValues.push(etcValue); + } + + const departmentColors = departmentValues.map((_, index) => { + if (index < 4) { + return ['#2F7CEF', '#ACCDFF', '#2f7cef33', '#EDF5FF'][index]; + } else { + return '#E4E4E4'; + } + }); const departmentData = { labels: departmentLabels, datasets: [ { data: departmentValues, - backgroundColor: [ - '#2F7CEF', - '#ACCDFF', - '#2f7cef33', - '#EDF5FF', - '#E4E4E4', - ], + backgroundColor: departmentColors, }, ], }; @@ -69,21 +90,42 @@ const DetailStatisticsPage = () => { const sortedYearAttendance = Object.entries(yearAttendance).sort( (a, b) => b[1] - a[1], ); - const yearLabels = sortedYearAttendance.map((item) => `${item[0]}학번`); - const yearValues = sortedYearAttendance.map((item) => item[1]); + + const yearAttendanceLimit = 4; + const yearLabels = sortedYearAttendance + .slice(0, yearAttendanceLimit) + .map((item) => `${item[0]}학번`); + + const etcYearValue = sortedYearAttendance + .slice(yearAttendanceLimit) + .reduce((sum, item) => sum + item[1], 0); + + if (etcYearValue > 0) { + yearLabels.push('기타'); + } + + const yearValues = sortedYearAttendance + .slice(0, yearAttendanceLimit) + .map((item) => item[1]); + + if (etcYearValue > 0) { + yearValues.push(etcYearValue); + } + + const yearColors = yearValues.map((_, index) => { + if (index < 4) { + return ['#2F7CEF', '#ACCDFF', '#2f7cef33', '#EDF5FF'][index]; + } else { + return '#E4E4E4'; + } + }); const yearData = { labels: yearLabels, datasets: [ { data: yearValues, - backgroundColor: [ - '#2F7CEF', - '#ACCDFF', - '#2f7cef33', - '#EDF5FF', - '#E4E4E4', - ], + backgroundColor: yearColors, }, ], }; diff --git a/src/pages/DetailStatisticsPage/attendee.js b/src/pages/DetailStatisticsPage/attendee.js index cf0d847..9538508 100644 --- a/src/pages/DetailStatisticsPage/attendee.js +++ b/src/pages/DetailStatisticsPage/attendee.js @@ -51,6 +51,11 @@ export const ATTENDEE_LIST = [ studentYear: 24, isAttending: true, }, + { + major: '의상학과', + studentYear: 20, + isAttending: true, + }, ], }, { @@ -101,6 +106,11 @@ export const ATTENDEE_LIST = [ studentYear: 24, isAttending: false, }, + { + major: '의상학과', + studentYear: 20, + isAttending: true, + }, ], }, { @@ -151,6 +161,11 @@ export const ATTENDEE_LIST = [ studentYear: 24, isAttending: false, }, + { + major: '의상학과', + studentYear: 20, + isAttending: true, + }, ], }, ], From f426340070d57ced6c87e3b21d78c555746cbc21 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 22:55:03 +0900 Subject: [PATCH 08/12] =?UTF-8?q?#234=20feat:=20chartjs-plugin-datalabels?= =?UTF-8?q?=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B0=A8=ED=8A=B8=20=EC=9C=84=20legend=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EA=B3=BC=20=EB=B9=84=EC=9C=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 9 +++++++++ package.json | 1 + .../DetailStatisticsPage.jsx | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 9d96224..32be59c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.6.8", "chart.js": "^4.4.4", + "chartjs-plugin-datalabels": "^2.2.0", "craco-alias": "^3.0.1", "date-fns": "^3.6.0", "moment": "^2.30.1", @@ -6343,6 +6344,14 @@ "pnpm": ">=8" } }, + "node_modules/chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "peerDependencies": { + "chart.js": ">=3.0.0" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", diff --git a/package.json b/package.json index 6c814d0..96c3736 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.6.8", "chart.js": "^4.4.4", + "chartjs-plugin-datalabels": "^2.2.0", "craco-alias": "^3.0.1", "date-fns": "^3.6.0", "moment": "^2.30.1", diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index 0c5beec..628273c 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -3,9 +3,10 @@ import { PageLayout } from '@/Layout'; import { TopNavigation } from '@/components'; import { Doughnut } from 'react-chartjs-2'; import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; import { ATTENDEE_LIST } from './attendee'; -ChartJS.register(ArcElement, Tooltip, Legend); +ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels); const DetailStatisticsPage = () => { const startDate = ATTENDEE_LIST[0].eventSchedules[0].date.split('T')[0]; @@ -135,6 +136,19 @@ const DetailStatisticsPage = () => { legend: { position: 'right', }, + datalabels: { + color: '#000', + anchor: 'center', + align: 'center', + textAlign: 'center', + + formatter: (value, context) => { + const total = context.dataset.data.reduce((acc, val) => acc + val, 0); + const percentage = ((value / total) * 100).toFixed(0); + const label = context.chart.data.labels[context.dataIndex]; + return `${label} \n ${percentage}%`; + }, + }, }, }; From 9a73a4d5e3cec62bc063ff7b997b78a272e45b85 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 23:03:44 +0900 Subject: [PATCH 09/12] =?UTF-8?q?#234=20feat:=20=EC=9D=B4=EC=88=98?= =?UTF-8?q?=EC=9C=A8=20=EC=B0=A8=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailStatisticsPage.jsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index 628273c..a8664a3 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -29,7 +29,6 @@ const DetailStatisticsPage = () => { }); }); - // 학과별 참석 비율 정렬 const sortedDepartmentAttendance = Object.entries(departmentAttendance).sort( (a, b) => b[1] - a[1], ); @@ -87,7 +86,6 @@ const DetailStatisticsPage = () => { }); }); - // 학번별 참석 비율 정렬 const sortedYearAttendance = Object.entries(yearAttendance).sort( (a, b) => b[1] - a[1], ); @@ -131,6 +129,32 @@ const DetailStatisticsPage = () => { ], }; + // 이수율 계산 로직 + const totalStudents = ATTENDEE_LIST[0].eventSchedules.reduce( + (total, schedule) => total + schedule.students.length, + 0, + ); + + const attendingStudents = ATTENDEE_LIST[0].eventSchedules.reduce( + (total, schedule) => + total + schedule.students.filter((student) => student.isAttending).length, + 0, + ); + + const completionRate = ((attendingStudents / totalStudents) * 100).toFixed(0); + const nonCompletionRate = (100 - completionRate).toFixed(0); + + // 이수율 차트 데이터 + const completionData = { + labels: ['이수', '미이수'], + datasets: [ + { + data: [completionRate, nonCompletionRate], + backgroundColor: ['#2F7CEF', '#ACCDFF'], + }, + ], + }; + const options = { plugins: { legend: { @@ -177,6 +201,13 @@ const DetailStatisticsPage = () => { + + + 전체 학생 중 이수율 + + + + From e75397646ac08b058acc73a3caf4f39733c70cd9 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 23:06:09 +0900 Subject: [PATCH 10/12] =?UTF-8?q?#234=20feat:=20=ED=95=99=EB=B2=88?= =?UTF-8?q?=EB=B3=84=20=EC=B0=B8=EC=84=9D=EB=A5=A0=20=EB=82=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0=EC=A4=80=EC=9D=84=20?= =?UTF-8?q?=ED=95=99=EB=B2=88=EC=9C=BC=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 --- src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index a8664a3..55e4313 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -87,7 +87,7 @@ const DetailStatisticsPage = () => { }); const sortedYearAttendance = Object.entries(yearAttendance).sort( - (a, b) => b[1] - a[1], + (a, b) => b[0] - a[0], ); const yearAttendanceLimit = 4; From 789497c95e627b77bf44da57aea01c4f6e1398b3 Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 23:12:44 +0900 Subject: [PATCH 11/12] =?UTF-8?q?#234=20feat:=20=EC=B0=A8=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=98=EC=9D=91=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailStatisticsPage.style.jsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx index 8a16636..76bfdf9 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.style.jsx @@ -14,7 +14,6 @@ export const DetailStatisticsPage = styled.div` max-width: 1100px; margin: 0 auto; padding: 50px 20px; - gap: 30px; border: 1px solid red; /* 임시 코드 */ `; @@ -46,16 +45,26 @@ export const EventDate = styled.p` // 통계 export const ContentContainer = styled.div` display: flex; - flex-direction: column; - width: 500px; + flex-wrap: wrap; + justify-content: space-between; + width: 100%; gap: 32px; padding-top: 20px; + + @media (max-width: ${BREAKPOINTS[1]}px) { + flex-direction: column; + } `; export const ChartWrapper = styled.div` display: flex; flex-direction: column; gap: 10px; + width: calc(50% - 16px); + + @media (max-width: ${BREAKPOINTS[1]}px) { + width: 100%; + } `; export const ChartTitle = styled.h2` From b26eb2385650b4e71064ba558636c05ac234055d Mon Sep 17 00:00:00 2001 From: "misung.dev" Date: Wed, 11 Sep 2024 23:16:21 +0900 Subject: [PATCH 12/12] =?UTF-8?q?#234=20feat:=20legend=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=EC=9D=84=20=EC=9B=90=ED=98=95=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=ED=98=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailStatisticsPage/DetailStatisticsPage.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx index 55e4313..0c71d0e 100644 --- a/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx +++ b/src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx @@ -159,6 +159,11 @@ const DetailStatisticsPage = () => { plugins: { legend: { position: 'right', + labels: { + usePointStyle: true, + padding: 20, + boxWidth: 10, + }, }, datalabels: { color: '#000', @@ -174,6 +179,11 @@ const DetailStatisticsPage = () => { }, }, }, + layout: { + padding: { + right: 10, + }, + }, }; return (