Skip to content

Comments

fix: 대학 검색 필터 분기 및 조건 전달 로직 수정#461

Open
manNomi wants to merge 3 commits intomainfrom
fix/university-search-filter-fixes
Open

fix: 대학 검색 필터 분기 및 조건 전달 로직 수정#461
manNomi wants to merge 3 commits intomainfrom
fix/university-search-filter-fixes

Conversation

@manNomi
Copy link
Contributor

@manNomi manNomi commented Feb 20, 2026

Summary

  • /university/search 필터 폼에서 선택한 지역(region)이 결과 페이지로 전달되도록 쿼리 구성 로직을 보완했습니다.
  • 검색 폼 두 곳에서 지역 변경 이후 남는 stale 국가값이 전송되지 않도록, 현재 선택 가능한 국가 코드만 쿼리로 전달하도록 정리했습니다.
  • 검색 결과 페이지 두 구현에서 필터 모드 분기 조건을 보정해 countryCode만 있는 경우에도 필터 검색이 정상 동작하도록 수정했습니다.

Verification

  • pnpm --filter @solid-connect/web run typecheck
  • pnpm --filter @solid-connect/web run build
  • pre-commit/pre-push hooks (ci:check) 통과

@vercel
Copy link

vercel bot commented Feb 20, 2026

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

Project Deployment Actions Updated (UTC)
solid-connection-web Ready Ready Preview, Comment Feb 20, 2026 5:26pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connect-web-admin Skipped Skipped Feb 20, 2026 5:26pm

@github-actions github-actions bot added the web label Feb 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

Walkthrough

이 변경 사항은 대학 검색 기능에서 국가 코드 검증 로직을 강화하는 업데이트입니다. 여러 파일에 걸쳐 COUNTRY_CODE_MAPavailableCountryCodeSet을 활용하여 URL 파라미터로부터 수신한 국가 코드를 필터링합니다. 이를 통해 유효하지 않은 국가 코드가 쿼리에 포함되는 것을 방지합니다. 또한 검색 필터 파라미터의 존재 여부를 판단하는 로직이 재정의되었으며, 지역 파라미터를 쿼리 문자열에 추가하는 기능이 도입되었습니다. 마지막으로 REGIONS_SEARCH 상수에 "중국권"이 새롭게 추가되었습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wibaek
  • enunsnv
  • khwww

변경 사항 상세 내용

  1. SearchResultsContent.tsx 파일들에서 국가 코드 검증 강화

    • COUNTRY_CODE_MAP을 이용한 안전한 국가 코드 필터링이 적용되었습니다.
    • Object.hasOwn()을 통해 타입 가드 기반의 배열 변환이 이루어집니다.
    • 필터 파라미터 존재 여부 판단 로직이 hasFilterParams로 통합되었습니다.
  2. SearchPageContent.tsx와 PageContent.tsx에서 쿼리 매개변수 유효성 검사

    • availableCountryCodeSet을 미리 계산하여 사용 가능한 국가 코드를 확인합니다.
    • 쿼리 매개변수에 포함될 국가 코드가 실제 사용 가능한 지역 목록과 일치하는지 검증됩니다.
  3. 지역 파라미터 쿼리 문자열 추가

    • PageContent.tsx에서 선택된 지역에 따라 쿼리 문자열에 지역 정보를 함께 추가하는 로직이 구현되었습니다.
  4. REGIONS_SEARCH 상수 확장

    • university.ts의 REGIONS_SEARCH 배열에 "중국권"이 새롭게 추가되었습니다.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경사항인 대학 검색 필터 분기 및 조건 전달 로직 수정을 명확하고 간결하게 설명하고 있습니다.
Description check ✅ Passed PR 설명에 작업 내용과 검증 방법이 충분히 포함되어 있으며, 주요 변경사항들이 기능 단위로 명확히 구분되어 설명되었습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/university-search-filter-fixes

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.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/src/app/university/SearchResultsContent.tsx (1)

36-56: ⚠️ Potential issue | 🟡 Minor

hasFilterParams 분기 로직은 올바르게 수정되었습니다. 단, lang 값의 유효성 검증이 누락되어 있습니다.

  1. 승인 사항

    • Object.hasOwn(COUNTRY_CODE_MAP, country) 타입 가드를 통해 프로토타입 체인 없이 안전하게 유효한 국가 코드만 필터링합니다.
    • hasFilterParams|| 조건 추가로, countryCode만 있는 경우에도 필터 검색이 정상 동작하는 핵심 버그가 수정되었습니다. ✅
  2. 사소한 우려 사항 — lang 미검증 캐스트

    • lang as LanguageTestType은 URL에 임의로 잘못된 값을 넣으면(?languageTestType=INVALID) 검증 없이 API로 그대로 전달됩니다.
    • countryCodeCOUNTRY_CODE_MAP으로 검증하면서 languageTestType은 동일한 방어 로직이 없습니다.
🛡️ languageTestType 유효성 검증 제안
+import { COUNTRY_CODE_MAP, LANGUAGE_TEST_TYPE_MAP } from "@/constants/university";

-    const hasFilterParams = Boolean(lang) || filteredCountries.length > 0;
+    const validLang = lang && Object.hasOwn(LANGUAGE_TEST_TYPE_MAP, lang) ? (lang as LanguageTestType) : undefined;
+    const hasFilterParams = Boolean(validLang) || filteredCountries.length > 0;

     if (!hasFilterParams) {
       ...
     } else {
       return {
         isTextSearch: false,
         searchText: "",
         filterParams: {
-          languageTestType: (lang as LanguageTestType) || undefined,
+          languageTestType: validLang,
           countryCode: filteredCountries.length > 0 ? filteredCountries : undefined,
         },
       };
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/university/SearchResultsContent.tsx` around lines 36 - 56,
The lang value is being unsafely cast to LanguageTestType when building
filterParams (languageTestType: (lang as LanguageTestType) || undefined) so
invalid URL values could be passed to the API; update the logic where
filterParams is constructed (in the same block that references
filteredCountries, hasFilterParams and UniversitySearchFilterParams) to validate
lang against the canonical LanguageTestType values (e.g., check membership in
the LanguageTestType enum or an explicit allowed-values set derived from
LanguageTestType) and only assign languageTestType when the value is valid,
otherwise set it to undefined.
apps/web/src/app/university/list/[homeUniversityName]/SearchResultsContent.tsx (1)

40-59: ⚠️ Potential issue | 🟡 Minor

핵심 로직은 apps/web/src/app/university/SearchResultsContent.tsx와 동일하게 올바르게 적용되었습니다.

  1. 승인 사항

    • Object.hasOwn 타입 가드와 hasFilterParams 분기 조건이 동일하게 적용되어 countryCode만 있는 경우에도 필터 검색이 작동합니다. ✅
  2. 동일한 사소한 우려 사항

    • Line 56의 (lang as LanguageTestType) || undefined 역시 URL 파라미터를 검증 없이 캐스트합니다. 상위 파일(apps/web/src/app/university/SearchResultsContent.tsx)에 남긴 코멘트와 동일한 방어 로직을 적용하면 일관성이 유지됩니다.
  3. SearchResultsContent.tsx의 중복 코드

    • apps/web/src/app/university/SearchResultsContent.tsx와 이 파일의 useMemo 내부 로직(filteredCountries 필터링, hasFilterParams 계산, filterParams 반환 구조)이 사실상 동일합니다. 향후 로직 변경 시 두 곳을 동시에 수정해야 하는 부담이 생길 수 있습니다.
♻️ 공통 훅 추출 제안

두 파일의 공통 로직을 커스텀 훅으로 추출하면 중복을 없앨 수 있습니다.

// 예: apps/web/src/hooks/useUniversitySearchParams.ts
export function useUniversitySearchParams() {
  const searchParams = useSearchParams();

  return useMemo(() => {
    const text = searchParams.get("searchText");
    const lang = searchParams.get("languageTestType");
    const countries = searchParams.getAll("countryCode");

    const filteredCountries = countries.filter(
      (country): country is CountryCode => Object.hasOwn(COUNTRY_CODE_MAP, country),
    );
    const hasFilterParams = Boolean(lang) || filteredCountries.length > 0;

    if (!hasFilterParams) {
      return { isTextSearch: true, searchText: text, filterParams: {} as UniversitySearchFilterParams };
    }
    return {
      isTextSearch: false,
      searchText: "",
      filterParams: {
        languageTestType: (lang as LanguageTestType) || undefined,
        countryCode: filteredCountries.length > 0 ? filteredCountries : undefined,
      },
    };
  }, [searchParams]);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/university/list/`[homeUniversityName]/SearchResultsContent.tsx
around lines 40 - 59, The code unsafely casts URL param `lang` using `(lang as
LanguageTestType) || undefined` in SearchResultsContent.tsx inside the useMemo
block and duplicates the same filtering/hasFilterParams logic across two files;
replace the cast with a validated lookup (e.g., check `lang` against the allowed
LanguageTestType values or a LANGUAGE_TEST_TYPE_MAP using Object.hasOwn/Set
membership and only return the validated value or undefined) and extract the
shared logic (filteredCountries, hasFilterParams, and resulting object) into a
single custom hook (suggested name: useUniversitySearchParams) so both
SearchResultsContent.tsx instances call that hook and remove duplicated code.
🧹 Nitpick comments (3)
apps/web/src/app/university/search/PageContent.tsx (1)

49-68: availableCountryCodeSetonSubmit 내부에서 매번 생성하는 대신 메모이제이션하는 것이 좋습니다.

  1. 현재 코드 동작

    • availableCountryCodeSetonSubmit(Line 49) 내부에서 생성되지만, availableCountries는 Line 74의 useMemo에서 선언됩니다.
    • JavaScript의 TDZ는 시간(실행 순서) 기반이기 때문에, onSubmit이 폼 제출 시점(컴포넌트 렌더링 이후)에 호출될 때는 availableCountries가 이미 초기화되어 있어 런타임 오류는 발생하지 않습니다.
    • 그러나 선언 순서가 역전되어 있어 가독성이 떨어지고, 폼을 제출할 때마다 Set을 새로 생성합니다.
  2. 개선 제안

    • availableCountryCodeSet을 별도의 useMemo로 분리하면, watchedRegions 변경 시에만 재계산되고 선언 순서도 자연스러워집니다.
♻️ useMemo로 분리하는 방법
   const availableCountries = useMemo(() => {
     if (watchedRegions.length === 0) return Object.entries(COUNTRY_CODE_MAP);
     const countrySet = new Set<string>();
     watchedRegions.forEach((region) => REGION_TO_COUNTRIES_MAP[region]?.forEach((country) => countrySet.add(country)));
     return Object.entries(COUNTRY_CODE_MAP).filter(([_, name]) => countrySet.has(name));
   }, [watchedRegions]);

+  const availableCountryCodeSet = useMemo(
+    () => new Set(availableCountries.map(([code]) => code as CountryCode)),
+    [availableCountries],
+  );

   const onSubmit: SubmitHandler<SearchFormData> = (data) => {
     const queryParams = new URLSearchParams();
-    const availableCountryCodeSet = new Set(availableCountries.map(([code]) => code as CountryCode));
     // ... 이하 동일
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/university/search/PageContent.tsx` around lines 49 - 68,
Move creation of availableCountryCodeSet out of the onSubmit handler and memoize
it with useMemo so it’s only recomputed when availableCountries (or
watchedRegions if relevant) changes: create a const availableCountryCodeSet =
useMemo(() => new Set(availableCountries.map(([code]) => code as CountryCode)),
[availableCountries]) near where availableCountries is declared; then update
onSubmit to use that memoized availableCountryCodeSet instead of recreating it
each submit. Ensure the identifier names (availableCountryCodeSet, onSubmit,
availableCountries, useMemo, watchedRegions) are used to locate and update the
code.
apps/web/src/constants/university.ts (1)

10-12: REGIONS_SEARCHREGIONS_KO의 내용이 완전히 동일해졌습니다.

  1. 변경 내용 확인

    • REGIONS_SEARCH"중국권"을 추가한 것은 올바른 수정입니다. REGION_KO_MAP, regionCountryMap, REGION_TO_COUNTRIES_MAP 등 기존 데이터 구조들이 이미 중국권을 지원하고 있어 일관성이 맞춰졌습니다.
    • 다만, 이제 두 상수의 내용이 동일해졌습니다. REGIONS_SEARCHas const 튜플이고 REGIONS_KO는 일반 배열로 타입만 다릅니다.
  2. 선택적 개선 제안

    • 두 상수를 별도로 유지하는 것보다 하나를 다른 것에서 파생시키면 향후 지역 추가 시 한 곳만 수정하면 됩니다.
♻️ 중복 제거 제안
-export const REGIONS_SEARCH = ["유럽권", "미주권", "아시아권", "중국권"] as const;
+export const REGIONS_SEARCH = ["유럽권", "미주권", "아시아권", "중국권"] as const;

-export const REGIONS_KO = ["유럽권", "미주권", "아시아권", "중국권"];
+export const REGIONS_KO: string[] = [...REGIONS_SEARCH];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/constants/university.ts` around lines 10 - 12, REGIONS_SEARCH
and REGIONS_KO are now identical; to avoid duplication, derive one from the
other so future changes only require a single edit. Replace the duplicated
literal array by exporting REGIONS_SEARCH as the canonical const tuple (export
const REGIONS_SEARCH = [... ] as const) and then build REGIONS_KO from it (e.g.,
Array.from(REGIONS_SEARCH) or REGIONS_SEARCH.slice()) so REGIONS_KO stays a
plain string[] while REGIONS_SEARCH remains the readonly tuple; update any
usages expecting the specific types accordingly.
apps/web/src/app/university/[homeUniversity]/search/_ui/SearchPageContent.tsx (1)

55-75: availableCountryCodeSetonSubmit 내부에서 생성하는 패턴이 apps/web/src/app/university/search/PageContent.tsx와 동일하게 반복됩니다.

  1. 동일한 개선 포인트

    • availableCountries(Line 77)가 onSubmit(Line 55) 이후에 선언되지만, 런타임 오류는 발생하지 않습니다. 단, 가독성과 성능 측면에서 useMemo로 분리하는 것을 권장합니다.
    • PageContent.tsx에 남긴 리팩터링 제안과 동일하게 적용하면 두 파일의 일관성도 유지됩니다.
  2. 코드 중복

    • onSubmitavailableCountries 로직이 PageContent.tsx와 거의 동일합니다. 두 파일을 동시에 수정하는 부담을 줄이려면 공통 로직을 추출하는 방안도 고려해 볼 수 있습니다.
♻️ useMemo 분리 제안 (PageContent.tsx와 동일)
   const availableCountries = useMemo(() => {
     if (watchedRegions.length === 0) return Object.entries(COUNTRY_CODE_MAP);
     const countrySet = new Set<string>();
     watchedRegions.forEach((region) => REGION_TO_COUNTRIES_MAP[region]?.forEach((country) => countrySet.add(country)));
     return Object.entries(COUNTRY_CODE_MAP).filter(([_, name]) => countrySet.has(name));
   }, [watchedRegions]);

+  const availableCountryCodeSet = useMemo(
+    () => new Set(availableCountries.map(([code]) => code as CountryCode)),
+    [availableCountries],
+  );

   const onSubmit: SubmitHandler<SearchFormData> = (data) => {
     const queryParams = new URLSearchParams();
-    const availableCountryCodeSet = new Set(availableCountries.map(([code]) => code as CountryCode));
     // ...
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/university/`[homeUniversity]/search/_ui/SearchPageContent.tsx
around lines 55 - 75, The onSubmit handler builds availableCountryCodeSet from
availableCountries inside the function which duplicates logic from
PageContent.tsx and hurts readability/perf; move the set creation out of
onSubmit by memoizing it with useMemo (e.g., const availableCountryCodeSet =
useMemo(() => new Set(availableCountries.map(([code]) => code as CountryCode)),
[availableCountries])) and then let onSubmit reference that memoized
availableCountryCodeSet, and optionally extract this into a shared helper (e.g.,
getAvailableCountryCodeSet) used by both SearchPageContent.tsx and
PageContent.tsx to remove duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apps/web/src/app/university/list/`[homeUniversityName]/SearchResultsContent.tsx:
- Around line 40-59: The code unsafely casts URL param `lang` using `(lang as
LanguageTestType) || undefined` in SearchResultsContent.tsx inside the useMemo
block and duplicates the same filtering/hasFilterParams logic across two files;
replace the cast with a validated lookup (e.g., check `lang` against the allowed
LanguageTestType values or a LANGUAGE_TEST_TYPE_MAP using Object.hasOwn/Set
membership and only return the validated value or undefined) and extract the
shared logic (filteredCountries, hasFilterParams, and resulting object) into a
single custom hook (suggested name: useUniversitySearchParams) so both
SearchResultsContent.tsx instances call that hook and remove duplicated code.

In `@apps/web/src/app/university/SearchResultsContent.tsx`:
- Around line 36-56: The lang value is being unsafely cast to LanguageTestType
when building filterParams (languageTestType: (lang as LanguageTestType) ||
undefined) so invalid URL values could be passed to the API; update the logic
where filterParams is constructed (in the same block that references
filteredCountries, hasFilterParams and UniversitySearchFilterParams) to validate
lang against the canonical LanguageTestType values (e.g., check membership in
the LanguageTestType enum or an explicit allowed-values set derived from
LanguageTestType) and only assign languageTestType when the value is valid,
otherwise set it to undefined.

---

Nitpick comments:
In
`@apps/web/src/app/university/`[homeUniversity]/search/_ui/SearchPageContent.tsx:
- Around line 55-75: The onSubmit handler builds availableCountryCodeSet from
availableCountries inside the function which duplicates logic from
PageContent.tsx and hurts readability/perf; move the set creation out of
onSubmit by memoizing it with useMemo (e.g., const availableCountryCodeSet =
useMemo(() => new Set(availableCountries.map(([code]) => code as CountryCode)),
[availableCountries])) and then let onSubmit reference that memoized
availableCountryCodeSet, and optionally extract this into a shared helper (e.g.,
getAvailableCountryCodeSet) used by both SearchPageContent.tsx and
PageContent.tsx to remove duplication.

In `@apps/web/src/app/university/search/PageContent.tsx`:
- Around line 49-68: Move creation of availableCountryCodeSet out of the
onSubmit handler and memoize it with useMemo so it’s only recomputed when
availableCountries (or watchedRegions if relevant) changes: create a const
availableCountryCodeSet = useMemo(() => new Set(availableCountries.map(([code])
=> code as CountryCode)), [availableCountries]) near where availableCountries is
declared; then update onSubmit to use that memoized availableCountryCodeSet
instead of recreating it each submit. Ensure the identifier names
(availableCountryCodeSet, onSubmit, availableCountries, useMemo, watchedRegions)
are used to locate and update the code.

In `@apps/web/src/constants/university.ts`:
- Around line 10-12: REGIONS_SEARCH and REGIONS_KO are now identical; to avoid
duplication, derive one from the other so future changes only require a single
edit. Replace the duplicated literal array by exporting REGIONS_SEARCH as the
canonical const tuple (export const REGIONS_SEARCH = [... ] as const) and then
build REGIONS_KO from it (e.g., Array.from(REGIONS_SEARCH) or
REGIONS_SEARCH.slice()) so REGIONS_KO stays a plain string[] while
REGIONS_SEARCH remains the readonly tuple; update any usages expecting the
specific types accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant