Skip to content

Conversation

@ohinhyuk
Copy link
Contributor

작업 사항

중복된 엑셀 다운로드 로직을 통합하고, 단일 책임 원칙에 맞게 함수를 분리함.
여러 지역 변수를 제거 하는 방향으로 리팩터링을 진행함.

작업 과정

  1. 엑셀 다운로드 e2e 테스트 생성
  • 리팩터링 과정에서 기존 로직의 정상 동작 여부를 검증하기 위해 테스트excel.spec.ts를 작성함
  1. sheetData 가공 로직 분리
  • buildSheetData 함수로 추출

AS-IS

 const handleExcelDownload = () => {
 const sheetData = applicants.map((student) => ({
            ID: student.id,
            Name: student.name,
            StudentId: student.sid,
            Courses: student.courses.map((subject) => subject.name + `(${subject.prof})`).join(', '),
            Friends: student.friends.map((friend) => friend.name).join(', '),
         }));
     ...
}

TO-BE

 const handleExcelDownload = () => {
      const sheetData = buildApplicantsSheetData();
      ...
}
  1. wb, ws 지역 변수 제거
  • 워크북 생성 로직을 buildWorkBook 함수로 추출
  • 함수 인라인 처리를 적용하여 단순화

AS-IS

const ws = xlsx.utils.json_to_sheet([...sheetData]);
const wb = xlsx.utils.book_new();
xlsx.utils.book_append_sheet(wb, ws, 'Sheet1');

TO-BE

const workBook = buildWorkBook(sheetData);

// excel.ts
export function buildWorkBook<T>(sheetData: T[]) {
   const wb = xlsx.utils.book_new();

   const ws = xlsx.utils.json_to_sheet(sheetData);

   xlsx.utils.book_append_sheet(wb, ws);

   return wb;
}
  1. 함수명 명확화
  • xlsx.writeFile을 downloadExcel 함수로 추출해 역할이 명확하게 드러나도록 함.

AS-IS

xlsx.writeFile(wb, '스터디신청자목록.xlsx');

TO-BE

downloadExcel(workBook, "test.xlsx");

//excel.ts
export function downloadExcel(data: xlsx.WorkBook, filename: string) {
   xlsx.writeFile(data, filename);
}
  1. 공용 함수화
  • buildWorkBook, downloadExcel을 공용 유틸 (excel.ts)로 추출하여 다른 페이지에서도 재사용 가능하도록 함.
import * as xlsx from 'xlsx';

export function buildWorkBook<T>(sheetData: T[]) {
   const wb = xlsx.utils.book_new();

   const ws = xlsx.utils.json_to_sheet(sheetData);

   xlsx.utils.book_append_sheet(wb, ws);

   return wb;
}

export function downloadExcel(data: xlsx.WorkBook, filename: string) {
   xlsx.writeFile(data, filename);
}

관련 이슈

#158

1) 엑셀 다운로드 e2e 테스트 생성
2) sheetData 지역 변수 제거
- buildSheetData 함수로 추출
3) wb, ws 지역 변수 제거
- workbook 생성 로직 함수 추출
- 함수 인라인 적용
4) excelDownload 구체적 함수명 지정
5) 공용 함수화
@ohinhyuk ohinhyuk linked an issue Oct 21, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Summary by CodeRabbit

릴리스 노트

  • Tests

    • 엑셀 다운로드 기능에 대한 엔드-투-엔드 테스트 추가
  • Chores

    • 테스트 인프라 구조 개선 및 설정 업데이트

개요

테스트 디렉터리를 tests/에서 e2e-tests/로 마이그레이션하고, 엑셀 다운로드 로직을 새로운 유틸리티 함수로 리팩토링합니다. 엑셀 파일 생성 및 다운로드 기능을 공통 유틸리티로 통합하고 E2E 테스트를 추가합니다.

변경사항

변경 단위 / 파일 변경 요약
디렉터리 구조 마이그레이션
.gitignore, playwright.config.ts
tests/ 디렉터리 참조를 e2e-tests/로 변경. 테스트 설정 경로 및 무시 대상 디렉터리 업데이트
E2E 테스트 마이그레이션 및 신규 추가
e2e-tests/reports/report.member.spec.ts, e2e-tests/excel/excel.download.spec.ts
기존 테스트의 스토리지 상태 경로를 e2e-tests/auth/로 변경. 엑셀 다운로드 기능에 대한 신규 E2E 테스트 2개 추가 (관리-스터디, 관리-학생 페이지)
엑셀 유틸리티 함수 추가
src/utils/excel.ts
새로운 헬퍼 함수 추가: buildWorkBook<T>() (워크북 생성), downloadExcel() (파일 다운로드)
엑셀 다운로드 로직 리팩토링
src/pages/Admin/ManageStudent/Page.tsx, src/pages/Admin/ManageStudy/Page.tsx
인라인 xlsx 라이브러리 사용을 제거하고 새로운 유틸리티 함수 기반으로 변경. 데이터 매핑 로직을 별도 헬퍼 함수(buildApplicantsSheetData(), buildStudySheetData())로 분리

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 "엑셀 다운로드 핸들러 함수 (handle Excel Download) 리팩터링"은 변경 세트의 주요 내용을 정확하게 반영하고 있습니다. 엑셀 다운로드 로직을 리팩터링하는 것이 변경의 핵심이며, 이는 src/utils/excel.ts의 새로운 유틸 함수 추가, ManageStudent 및 ManageStudy 페이지의 로직 재구성, 그리고 관련 테스트 설정 변경 등 모든 변경사항을 대표합니다. 제목은 간결하고 명확하며 팀원이 변경 내역을 빠르게 이해할 수 있도록 충분한 정보를 제공합니다.
Description Check ✅ Passed PR 설명은 변경 세트와 명확하게 관련이 있습니다. 중복된 엑셀 다운로드 로직 통합, 단일 책임 원칙에 따른 함수 분리, 지역 변수 제거라는 리팩터링 목표를 상세히 설명하고 있으며, 각 단계별 작업 과정(e2e 테스트 생성, sheetData 가공 로직 분리, buildWorkBook/downloadExcel 함수 추출)을 구체적으로 나열합니다. AS-IS와 TO-BE 코드 예제를 포함하여 변경 전후의 차이를 명확하게 보여주며, 관련 이슈 번호(#158)도 명시되어 있습니다.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ohinhyuk ohinhyuk requested a review from minzziPark October 21, 2025 16:15
@ohinhyuk ohinhyuk added the refactoring 리팩터링 label Oct 21, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
e2e-tests/excel/excel.download.spec.ts (1)

7-7: paths 상수 사용을 권장합니다.

하드코딩된 URL 대신 기존 codebase의 @/const/paths 상수를 사용하면 유지보수성이 향상됩니다.

다음과 같이 수정할 수 있습니다:

+import { paths } from '@/const/paths';
+
 test.use({ storageState: 'e2e-tests/auth/admin.json' });

 test.describe('엑셀 다운로드 테스트', () => {
    test('스터디 관리 페이지 (manage-study) 엑셀 다운로드 테스트', async ({ page }) => {
-      await page.goto('http://localhost:3000/admin/manage-study');
+      await page.goto(paths.admin.manageStudy);
       ...
    });

    test('스터디 신청자 목록 페이지 (manage-student) 엑셀 다운로드 테스트', async ({ page }) => {
-      await page.goto('http://localhost:3000/admin/manage-student');
+      await page.goto(paths.admin.manageStudent);
       ...
    });
 });

Also applies to: 21-21

src/utils/excel.ts (2)

8-8: 시트 이름 파라미터 추가를 고려해보세요.

book_append_sheet 호출 시 시트 이름을 명시하지 않으면 기본 이름(예: "Sheet1")이 사용됩니다. 명시적인 시트 이름을 파라미터로 받으면 더 유연하게 사용할 수 있습니다.

다음과 같이 개선할 수 있습니다:

-export function buildWorkBook<T>(sheetData: T[]) {
+export function buildWorkBook<T>(sheetData: T[], sheetName: string = 'Sheet1') {
    const wb = xlsx.utils.book_new();

    const ws = xlsx.utils.json_to_sheet(sheetData);

-   xlsx.utils.book_append_sheet(wb, ws);
+   xlsx.utils.book_append_sheet(wb, ws, sheetName);

    return wb;
 }

1-15: JSDoc 주석 추가를 권장합니다.

공용 유틸리티 함수이므로 JSDoc 주석을 추가하면 개발자 경험이 향상됩니다.

예시:

/**
 * 주어진 데이터로부터 Excel 워크북을 생성합니다.
 * @template T - 시트 데이터의 타입
 * @param sheetData - JSON 형식의 시트 데이터 배열
 * @param sheetName - 시트 이름 (기본값: 'Sheet1')
 * @returns 생성된 워크북 객체
 */
export function buildWorkBook<T>(sheetData: T[], sheetName: string = 'Sheet1') {
   // ...
}

/**
 * 워크북을 Excel 파일로 다운로드합니다.
 * @param data - 다운로드할 워크북 객체
 * @param filename - 저장할 파일명 (확장자 포함)
 */
export function downloadExcel(data: xlsx.WorkBook, filename: string) {
   // ...
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdedeb3 and 899b88e.

📒 Files selected for processing (7)
  • .gitignore (1 hunks)
  • e2e-tests/excel/excel.download.spec.ts (1 hunks)
  • e2e-tests/reports/report.member.spec.ts (1 hunks)
  • playwright.config.ts (1 hunks)
  • src/pages/Admin/ManageStudent/Page.tsx (2 hunks)
  • src/pages/Admin/ManageStudy/Page.tsx (3 hunks)
  • src/utils/excel.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/Admin/ManageStudent/Page.tsx (1)
src/utils/excel.ts (2)
  • downloadExcel (13-15)
  • buildWorkBook (3-11)
src/pages/Admin/ManageStudy/Page.tsx (1)
src/utils/excel.ts (2)
  • downloadExcel (13-15)
  • buildWorkBook (3-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-test
🔇 Additional comments (9)
playwright.config.ts (1)

20-20: 테스트 디렉토리 변경이 올바르게 적용되었습니다.

테스트 디렉토리를 ./e2e-tests로 변경하여 PR 전반의 구조 개편과 일치합니다.

.gitignore (1)

37-37: 무시 경로가 새 디렉토리 구조와 일치합니다.

e2e-tests/auth/ 경로로 업데이트하여 Playwright 설정 변경사항과 일관성을 유지합니다.

e2e-tests/reports/report.member.spec.ts (1)

31-31: storageState 경로가 올바르게 업데이트되었습니다.

새로운 e2e-tests 디렉토리 구조에 맞춰 인증 상태 파일 경로가 정확하게 변경되었습니다.

e2e-tests/excel/excel.download.spec.ts (1)

1-32: 엑셀 다운로드 e2e 테스트가 잘 작성되었습니다.

테스트가 다운로드 이벤트를 올바르게 대기하고 파일명과 다운로드 성공을 검증합니다. 리팩터링된 Excel 유틸리티의 동작을 효과적으로 검증합니다.

src/utils/excel.ts (1)

3-11: 리팩터링 목표를 잘 달성한 유틸리티 함수입니다.

단일 책임 원칙에 따라 워크북 생성 로직을 명확하게 분리했습니다. 제네릭 타입을 사용하여 재사용성도 좋습니다.

src/pages/Admin/ManageStudent/Page.tsx (2)

12-12: 공용 Excel 유틸리티 import가 올바릅니다.

새로 추출된 유틸리티 함수를 활용하여 코드 재사용성을 높였습니다.


89-106: 리팩터링이 성공적으로 완료되었습니다.

단일 책임 원칙에 따라 시트 데이터 생성 로직을 buildApplicantsSheetData 함수로 명확하게 분리했습니다. applicants가 없는 경우 빈 배열을 반환하여 안전하게 처리하고 있으며, 워크북 생성과 다운로드 로직도 공용 유틸리티를 사용하여 중복을 제거했습니다.

src/pages/Admin/ManageStudy/Page.tsx (2)

8-8: 공용 Excel 유틸리티 import가 적절합니다.

ManageStudent 페이지와 동일한 패턴으로 공용 유틸리티를 활용하여 일관성을 유지합니다.


35-57: 리팩터링이 효과적으로 적용되었습니다.

buildStudySheetData 함수로 데이터 가공 로직을 분리하고, flatMap을 사용하여 그룹별 멤버 데이터를 효율적으로 변환합니다. activities가 없는 경우 빈 배열을 반환하여 안전성을 보장하며, ManageStudent 페이지와 동일한 구조로 일관성도 우수합니다.

@ohinhyuk
Copy link
Contributor Author

@minzziPark 리팩터링 도중에 두 가지 질문이 생겼는데 의견 부탁드려요.

1) sheetData를 inline 하는 것에 대해 어떻게 생각하시나요??

Before

const sheetData = buildStudySheetData();
downloadExcel(buildWorkBook(sheetData), '스터디그룹활동.xlsx');

After

downloadExcel(buildWorkBook(buildStudySheetData()), '스터디그룹활동.xlsx'); // sheetData inline 처리

2) buildSheetData 함수에서 sheetData를 선언, 대입, 반환을 명시적으로 하는 것에 대해 어떻게 생각하시나요??

Before

function buildStudySheetData() {
   if (!activities) return [];

   return activities.flatMap((group) => {
      const sheetRow = group.members.map((member) => ({
         Group: group.group,
         MemberID: member.id,
         MemberName: member.name,
         MemberNumber: member.sid,
         Friends: member.friends.map((friend) => friend.name).join(', '),
         Subjects: member.courses.map((subject) => subject.name).join(', '),
         Reports: group.reports,
         Times: group.times,
      }));

      return sheetRow;
   });
}

After

function buildStudySheetData() {
   if (!activities) return [];
   let sheetData; // 명시적으로 선언

   sheetData = activities.flatMap((group) => {
      const sheetRow = group.members.map((member) => ({
         Group: group.group,
         MemberID: member.id,
         MemberName: member.name,
         MemberNumber: member.sid,
         Friends: member.friends.map((friend) => friend.name).join(', '),
         Subjects: member.courses.map((subject) => subject.name).join(', '),
         Reports: group.reports,
         Times: group.times,
      }));

      return sheetRow;
   });

   return sheetData;
}

@ohinhyuk ohinhyuk changed the base branch from main to dev-refactoring October 22, 2025 10:48
@minzziPark
Copy link
Collaborator

minzziPark commented Oct 22, 2025

@ohinhyuk

  1. sheetData를 inline 하는 것에 대해 어떻게 생각하시나요??

이미 buildWorkBook를 인라인으로 호출하고 있기 때문에 그 안에 함수를 호출하면 저는 동작하는 방식을 이해하기 힘든 것 같습니다.

  1. buildSheetData 함수에서 sheetData를 선언, 대입, 반환을 명시적으로 하는 것에 대해 어떻게 생각하시나요??

sheetData를 임시 변수로 두면 사이드 이펙트가 커질 가능성이 높은 것 같습니다. 기존 형태가 좋은 것 같습니다.

}, [applicants, searchTerm]);

const handleExcelDownload = () => {
if (applicants) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildApplicantsSheetData 함수에서 applicants의 존재 유무를 확인한다면 기존 로직과 다르게 동작할 것 같습니다.
handleExcelDownload 함수 상단에서 처리해주는 건 어떨까요?

Suggested change
if (applicants) {
const handleExcelDownload = () => {
if(applicants) return ;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bb95559 에서 수정하였습니다.

downloadExcel(buildWorkBook(sheetData), '스터디신청자목록.xlsx');

function buildApplicantsSheetData() {
if (!applicants) return [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 제안에 동의하신다면 해당 부분도 삭제해야할 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bb95559 에서 수정하였습니다.

Comment on lines 4 to 6
const wb = xlsx.utils.book_new();

const ws = xlsx.utils.json_to_sheet(sheetData);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wb, ws가 무엇을 의미하는 변수인지 짐작하기가 어렵습니다. book_new(), json_to_sheet()을 보고 추론할 수는 있겠지만, 그럼에도 w가 의미하는 건 뭔지 잘 모르겠습니다. 변수명을 좀 더 직관적으로 수정해주실 수 있으신가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그렇네요. 변경 할 필요가 있어 보입니다.

wb -> workBook
ws -> workSheet

로 변경하도록 하겠습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7a35d50 에서 수정하였습니다.

const sheetData = activities.flatMap((group) =>
group.members.map((member) => ({
const sheetData = buildStudySheetData();
downloadExcel(buildWorkBook(sheetData), '스터디그룹활동.xlsx');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유틸 함수로 buildWorkBook 로직을 분리하니 함수가 어떻게 동작하는지 이해하기 쉬워졌습니다! 고생하셨습니다 :)

@ohinhyuk ohinhyuk merged commit 2ef8d3a into dev-refactoring Oct 25, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring 리팩터링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

handleExcelDownload 함수 리팩터링

3 participants