Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#234 feat: 세부 통계 페이지 임시 생성 후 더미데이터로 구현 #270

Merged
merged 12 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"@testing-library/react": "^13.4.0",
"@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",
Expand All @@ -17,6 +19,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",
Expand Down
4 changes: 4 additions & 0 deletions src/components/TopNavigation/TopNavigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export default function TopNavigation({ eventTitle } = {}) {
<S.Menu to="/stats" activeClassName="active">
통계
</S.Menu>
{/* 세부 통계 페이지는 추후 통째로 삭제 */}
<S.Menu to="/stats/detail" activeClassName="active">
세부 통계(임시)
</S.Menu>
{location.pathname.startsWith('/event/dashboard') && (
<S.PageNameWrapper>
{eventTitle !== undefined && (
Expand Down
228 changes: 228 additions & 0 deletions src/pages/DetailStatisticsPage/DetailStatisticsPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
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 ChartDataLabels from 'chartjs-plugin-datalabels';
import { ATTENDEE_LIST } from './attendee';

ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels);

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) => {
schedule.students.forEach((student) => {
if (student.isAttending) {
if (departmentAttendance[student.major]) {
departmentAttendance[student.major]++;
} else {
departmentAttendance[student.major] = 1;
}
}
});
});

const sortedDepartmentAttendance = Object.entries(departmentAttendance).sort(
(a, b) => b[1] - a[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: departmentColors,
},
],
};

// 학번별 참석 비율을 계산하는 로직
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 sortedYearAttendance = Object.entries(yearAttendance).sort(
(a, b) => b[0] - a[0],
);

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: yearColors,
},
],
};

// 이수율 계산 로직
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: {
position: 'right',
labels: {
usePointStyle: true,
padding: 20,
boxWidth: 10,
},
},
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}%`;
},
},
},
layout: {
padding: {
right: 10,
},
},
};

return (
<PageLayout topNavigation={<TopNavigation />}>
<S.Container>
<S.DetailStatisticsPage>
<S.TopContainer>
<S.Title>행사별 통계</S.Title>
<S.EventDate>
{startDate} ~ {endDate}
</S.EventDate>
</S.TopContainer>

<S.ContentContainer>
<S.ChartWrapper>
<S.ChartTitle>행사에 참석한 학과 비율</S.ChartTitle>
<S.Chart>
<Doughnut data={departmentData} options={options} />
</S.Chart>
</S.ChartWrapper>

<S.ChartWrapper>
<S.ChartTitle>각 학번별 참석률</S.ChartTitle>
<S.Chart>
<Doughnut data={yearData} options={options} />
</S.Chart>
</S.ChartWrapper>

<S.ChartWrapper>
<S.ChartTitle>전체 학생 중 이수율</S.ChartTitle>
<S.Chart>
<Doughnut data={completionData} options={options} />
</S.Chart>
</S.ChartWrapper>
</S.ContentContainer>
</S.DetailStatisticsPage>
</S.Container>
</PageLayout>
);
};

export default DetailStatisticsPage;
Loading