Skip to content

feat: 지원 대학 관리 페이지 추가 (호스트 대학 전체 컬럼 표시 포함)#567

Merged
whqtker merged 12 commits into
mainfrom
feature/admin-univ-manage
Jun 22, 2026
Merged

feat: 지원 대학 관리 페이지 추가 (호스트 대학 전체 컬럼 표시 포함)#567
whqtker merged 12 commits into
mainfrom
feature/admin-univ-manage

Conversation

@whqtker

@whqtker whqtker commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

  • 지원 대학 관리 페이지를 호스트 대학교 / 지원 대학 가져오기 / 지원 대학 관리 탭으로 재편
  • 호스트 대학교 탭: 목록 조회·생성·수정·삭제 기능 추가
  • 호스트 대학교 테이블에 DB 스키마 전체 컬럼 표시 (상세 API 병렬 조회로 누락 필드 보완)
  • 작업 컬럼 우측 sticky 고정으로 스크롤 시 버튼 항상 노출
  • 지원 대학 관리 탭: 검색(대학명·국내 대학·학기 필터)·단건 추가·수정·삭제 기능 추가
  • 지원 대학 관리 탭 진입 시 사이드바 밀림 현상 수정 (min-w-0 추가)

Test plan

  • 호스트 대학교 탭 — 목록 조회, 페이지네이션 동작 확인
  • 호스트 대학교 탭 — 전체 컬럼(로고·배경·홈페이지·영어강좌·숙소·현지 안내) 정상 표시 확인
  • 호스트 대학교 탭 — 가로 스크롤 시 작업 컬럼이 우측에 고정되는지 확인
  • 호스트 대학교 탭 — 생성·수정·삭제 정상 동작 확인
  • 지원 대학 관리 탭 — 검색 필터(대학명·국내 대학·학기) 동작 확인
  • 지원 대학 관리 탭 — 단건 추가·수정·삭제 정상 동작 확인
  • 지원 대학 관리 메뉴 진입 시 사이드바 밀림 없는지 확인

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connect-university-web Error Error Jun 22, 2026 12:42am
solid-connect-web-admin Ready Ready Preview, Comment Jun 22, 2026 12:42am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connection-web Skipped Skipped Jun 22, 2026 12:42am

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@whqtker, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 52 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7f21693d-9400-4ed1-bf2b-c99ba8b6362b

📥 Commits

Reviewing files that changed from the base of the PR and between 9baa4e4 and cb655c1.

📒 Files selected for processing (3)
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx
  • apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoManageTab.tsx
  • apps/admin/src/lib/api/admin.ts

Walkthrough

  1. API 타입 및 엔드포인트 확장

    • admin.tsPageResponse<T>, HostUniversity*, UnivApplyInfo* 인터페이스 10종을 신규 추가
    • adminApi에 호스트 대학교 CRUD 5개, 지원 정보 생성/수정/삭제/검색 4개 메서드를 추가
    • importUnivApplyInfos 엔드포인트를 /admin/univ-apply-infos/import로 변경
  2. 페이지 진입점 재구성

    • UnivApplyInfosPageContent의 단일 폼 구현(464줄)을 제거하고 Tabs 기반 3탭 구조(24줄)로 교체
    • AdminSidebar 메뉴 라벨을 "지원 대학 추가" → "지원 대학 관리"로 변경
    • AdminLayout 메인 섹션에 min-w-0, overflow-hidden 클래스 추가
  3. HostUniversityTab 신규 추가 (434줄)

    • 목록 페이지네이션 쿼리, 행별 상세 병렬 쿼리, 생성/수정/삭제 뮤테이션, 모달 폼 UI 구현
  4. UnivApplyInfoImportTab 신규 추가 (456줄)

    • 마크다운 파싱, 컬럼 자동 매핑, 프리뷰 검증 모달, importUnivApplyInfos 호출 흐름 구현
  5. UnivApplyInfoManageTab 신규 추가 (593줄)

    • 조건부 검색 쿼리, 수정/삭제/단건 추가 뮤테이션, 호스트 대학교 키워드 검색 드롭다운, JSON 파싱 검증 포함 모달 폼 구현

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • wibaek
  • enunsnv
  • manNomi
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명은 필수 템플릿 항목을 대부분 충족하지 못했으며, 관련 이슈 번호와 특이 사항 섹션이 누락되어 있습니다. 관련 이슈 번호(resolves: #이슈번호)와 특이 사항 섹션을 추가하여 템플릿의 필수 정보를 완성해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 핵심 변경사항인 지원 대학 관리 페이지 추가와 호스트 대학 전체 컬럼 표시를 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/admin-univ-manage

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9baa4e4595

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/admin/src/lib/api/admin.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoImportTab.tsx (3)

171-188: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

mappedFieldSetpreviewColumns 계산을 useMemo로 감싸는 것을 권장합니다.

현재 매 렌더마다 다음 연산이 반복됩니다:

  1. mappedFieldSet: Set 생성 및 Object.values 순회
  2. previewColumns: 3개의 배열 필터/맵 연산 + 스프레드 병합

columnMappings가 변경될 때만 재계산하면 충분합니다.

♻️ useMemo 적용 예시
+const mappedFieldSet = useMemo(
+	() => new Set(Object.values(columnMappings).filter(Boolean)),
+	[columnMappings],
+);
-const mappedFieldSet = new Set(Object.values(columnMappings).filter(Boolean));

+const previewColumns = useMemo(() => [
-const previewColumns: { field: string; label: string; required: boolean; mapped: boolean }[] = [
	...UNIV_APPLY_INFO_FIELDS.filter((f) => f.required).map((f) => ({
		field: f.field,
		label: f.label,
		required: true,
		mapped: mappedFieldSet.has(f.field),
	})),
	...UNIV_APPLY_INFO_FIELDS.filter((f) => !f.required && mappedFieldSet.has(f.field)).map((f) => ({
		field: f.field,
		label: f.label,
		required: false,
		mapped: true,
	})),
	...[...mappedFieldSet]
		.filter((f) => !UNIV_APPLY_INFO_FIELDS.some((sf) => sf.field === f))
		.map((f) => ({ field: f, label: f, required: false, mapped: true })),
-];
+], [mappedFieldSet]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoImportTab.tsx`
around lines 171 - 188, The `mappedFieldSet` and `previewColumns` calculations
are being performed on every render, even when their dependencies haven't
changed. Wrap both the `mappedFieldSet` Set creation and the `previewColumns`
array construction in a single `useMemo` hook with `columnMappings` as the
dependency array. This will ensure these expensive computations involving Set
creation, object value filtering, and multiple array filter/map operations only
run when `columnMappings` actually changes, improving performance.

141-156: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

canConfirmImport 변수가 함수 정의 이후에 선언되어 있어 코드 가독성이 저하됩니다.

handleConfirmImport 함수(line 142)에서 참조하는 canConfirmImport가 line 195에서 정의됩니다. JavaScript 클로저 덕분에 런타임에는 문제가 없지만, 코드 흐름을 따라가기 어렵습니다.

  1. 현상: 함수가 아래에서 정의된 변수를 참조
  2. 권장: 파생 데이터(derived data)를 핸들러 정의 위로 이동하거나, 핸들러를 파생 데이터 아래로 이동
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoImportTab.tsx`
around lines 141 - 156, The handleConfirmImport function references the
canConfirmImport variable before it is declared, which reduces code readability
despite working at runtime due to JavaScript closures. Move the canConfirmImport
variable declaration to appear before the handleConfirmImport function
definition so that derived data is available when the handler is defined. This
improves code flow and makes the data dependencies clear at a glance.

285-316: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

nonLangHeaders 필터링이 매 반복마다 중복 실행됩니다.

UNIV_APPLY_INFO_FIELDS.map() 내부(line 287)에서 parsedHeaders.filter(...)가 호출되어, 필드 개수만큼 동일한 필터 연산이 반복됩니다.

  1. 현상: O(fields × headers) 복잡도
  2. 개선: 루프 외부에서 한 번만 계산하여 O(headers)로 감소
♻️ 루프 외부로 이동
+const nonLangHeaders = useMemo(
+	() => parsedHeaders.filter((h) => !(fields?.languageTestTypes.includes(h) ?? false)),
+	[parsedHeaders, fields?.languageTestTypes],
+);

 {UNIV_APPLY_INFO_FIELDS.map((f) => {
 	const mappedHeader = Object.entries(columnMappings).find(([, v]) => v === f.field)?.[0] ?? "";
-	const nonLangHeaders = parsedHeaders.filter((h) => !(fields?.languageTestTypes.includes(h) ?? false));
 	return (
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoImportTab.tsx`
around lines 285 - 316, The nonLangHeaders variable is being computed inside the
UNIV_APPLY_INFO_FIELDS.map() function on line 287, causing the same filter
operation to execute repeatedly for each field iteration. Move the
nonLangHeaders calculation outside and above the map function so it is computed
only once before the loop starts, then reference the already-computed
nonLangHeaders inside the map callback. This will reduce the time complexity
from O(fields × headers) to O(headers).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx`:
- Around line 152-166: The handleOpenEdit function has a race condition where
rapidly selecting different universities can cause stale responses from previous
getHostUniversity calls to overwrite the current form and modal state with
incorrect data. To fix this, implement a mechanism to cancel or ignore responses
from outdated requests, such as using an AbortController to cancel the previous
async call when handleOpenEdit is invoked for a different university ID, or by
tracking a request ID/timestamp and only updating form and modal state if the
response matches the current request. This ensures that only the response from
the most recent getHostUniversity call updates the state.

In
`@apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoManageTab.tsx`:
- Around line 528-545: The select element for host university selection is
uncontrolled because it lacks a value prop binding to
createForm.hostUniversityId. This causes the visual selection to not sync with
the state, so when validation runs it checks the unupdated hostUniversityId
value and fails the required check. Fix this by adding a value prop to the
select element that is set to createForm.hostUniversityId, converting it from an
uncontrolled to a controlled component. This ensures the selected option
visually matches and syncs with the state value that the validation checks.
- Around line 38-44: The parseExtraInfo function validates that the parsed JSON
is an object but does not validate that all values within that object are
strings before casting to Record<string, string>. This can allow non-string
values (like numbers) to pass through, breaking the API contract. After
confirming the parsed value is an object and not null and not an array, add
validation to ensure every value in the object is actually a string type before
returning it. If any value is not a string, return undefined instead of casting.
Apply the same validation fix to the other locations mentioned (lines 165-168
and 185-188) where similar type assertions occur without proper value
validation.

---

Nitpick comments:
In
`@apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoImportTab.tsx`:
- Around line 171-188: The `mappedFieldSet` and `previewColumns` calculations
are being performed on every render, even when their dependencies haven't
changed. Wrap both the `mappedFieldSet` Set creation and the `previewColumns`
array construction in a single `useMemo` hook with `columnMappings` as the
dependency array. This will ensure these expensive computations involving Set
creation, object value filtering, and multiple array filter/map operations only
run when `columnMappings` actually changes, improving performance.
- Around line 141-156: The handleConfirmImport function references the
canConfirmImport variable before it is declared, which reduces code readability
despite working at runtime due to JavaScript closures. Move the canConfirmImport
variable declaration to appear before the handleConfirmImport function
definition so that derived data is available when the handler is defined. This
improves code flow and makes the data dependencies clear at a glance.
- Around line 285-316: The nonLangHeaders variable is being computed inside the
UNIV_APPLY_INFO_FIELDS.map() function on line 287, causing the same filter
operation to execute repeatedly for each field iteration. Move the
nonLangHeaders calculation outside and above the map function so it is computed
only once before the loop starts, then reference the already-computed
nonLangHeaders inside the map callback. This will reduce the time complexity
from O(fields × headers) to O(headers).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a78a5b0a-94ba-404a-adf3-d0eb7b1a08ce

📥 Commits

Reviewing files that changed from the base of the PR and between cc0ee10 and 9baa4e4.

📒 Files selected for processing (7)
  • apps/admin/src/components/features/univ-apply-infos/UnivApplyInfosPageContent.tsx
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx
  • apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoImportTab.tsx
  • apps/admin/src/components/features/univ-apply-infos/tabs/UnivApplyInfoManageTab.tsx
  • apps/admin/src/components/layout/AdminLayout.tsx
  • apps/admin/src/components/layout/AdminSidebar.tsx
  • apps/admin/src/lib/api/admin.ts

@vercel vercel Bot temporarily deployed to Preview – solid-connection-web June 22, 2026 00:41 Inactive
@whqtker whqtker merged commit 4dea9b1 into main Jun 22, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant