Skip to content

[REFACTOR] API 동작 흐름 컴포넌트 훅으로 분리 - #52

Merged
minsxo merged 13 commits into
mainfrom
feat/analysis-api
Jul 29, 2026
Merged

[REFACTOR] API 동작 흐름 컴포넌트 훅으로 분리#52
minsxo merged 13 commits into
mainfrom
feat/analysis-api

Conversation

@GirimNam

@GirimNam GirimNam commented Jul 29, 2026

Copy link
Copy Markdown
Member

📌 작업 내용

  • API 동작 흐름 컴포넌트 훅으로 분리
  • 태그칩 부분 화살표 버튼 -> 그 컴포넌트가 안 겹치도록 분리

✅ 변경 사항

  • 새로운 기능 추가
  • 버그 수정
  • 코드 개선 및 수정
  • 문서 작성 및 업데이트
  • 배포 관련 수정
  • 브랜치 관련 수정
  • 기타:

🔗 관련 이슈

Closes #

🧪 체크리스트

  • 제가 작성한 코드를 리뷰했습니다.
  • 문서에 변경 사항을 반영했습니다.
  • 새로운 경고를 생성하지 않습니다.
  • 새로운 기능이나 수정 사항이 기존 테스트와 충돌하지 않음을 확인했습니다.

📸 스크린샷

없음

💬 리뷰 요청 사항

기타

Summary by CodeRabbit

  • 개선 사항
    • 선행문헌, 신규성·진보성 분석, 리포트 및 기술 상세 정보를 더 일관된 방식으로 불러오고 표시합니다.
    • 분석 결과와 검색 진행 상태의 로딩·오류 처리가 개선되었습니다.
    • 검색 진행 중 중단, 결과 재조회, 분석 내용 저장 및 비교 결과 저장 기능이 안정적으로 동작합니다.
    • 검색 결과에서 특허번호로 문헌을 추가하고 정렬할 수 있습니다.
    • 문헌 목록의 태그 영역과 결과 헤더 레이아웃이 개선되었습니다.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@GirimNam, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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 reviews.

How do review limits work?

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

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, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3404680d-df1c-439a-ae67-4a3137db1e42

📥 Commits

Reviewing files that changed from the base of the PR and between 44c8956 and 2831803.

📒 Files selected for processing (6)
  • src/app/(report)/myhistory/[id]/report/page.tsx
  • src/hooks/useAnalysisPriorArts.ts
  • src/hooks/useReport.ts
  • src/hooks/useSearchProgress.ts
  • src/hooks/useSearchResult.ts
  • src/hooks/useTechDetail.ts
📝 Walkthrough

Walkthrough

분석·검색·리포트 페이지의 조회와 상태 관리 로직을 커스텀 훅으로 이동했습니다. 페이지는 훅이 제공하는 상태와 액션을 연결하며, 검색 목록 UI의 오버레이와 헤더 치수도 조정되었습니다.

Changes

분석 선행문헌 흐름

Layer / File(s) Summary
선행문헌 조회·선택 훅과 페이지 연결
src/hooks/useAnalysisPriorArts.ts, src/app/(main)/analysis/(workspace)/[id]/page.tsx
목록·상세 조회, 선택 상태, 외부 클릭 해제, 오류 처리를 useAnalysisPriorArts로 이동하고 페이지 렌더링에 연결했습니다.
진보성 분석 조회·저장 훅과 페이지 연결
src/hooks/useInventiveStepAnalysis.ts, src/app/(main)/analysis/[id]/[patentId]/page.tsx
분석 조회, 논리 토글, 인자 내용 저장, 추천 상태, 재조회를 훅에서 관리하도록 변경했습니다.

검색 흐름

Layer / File(s) Summary
검색 진행 폴링과 중단 처리
src/hooks/useSearchProgress.ts, src/app/(main)/search/loading/page.tsx
검색 상태 폴링, 재시도, 중단, 종료 메시지와 라우팅을 useSearchProgress로 이동했습니다.
검색 결과 조회와 특허번호 추가
src/hooks/useSearchResult.ts, src/app/(main)/search/result/page.tsx
결과 조회, 정렬, 수동 특허 추가, import 모달 상태와 핸들러를 useSearchResult로 통합했습니다.

이력·상세 조회

Layer / File(s) Summary
신규성 분석과 리포트 조회
src/hooks/useNoveltyAnalysis.ts, src/hooks/useReport.ts, src/app/(main)/myhistory/[id]/novelty/page.tsx, src/app/(report)/myhistory/[id]/report/page.tsx
인증 기반 조회, 오류 상태, 비교 저장, 리포트 재조회를 각각 훅으로 분리하고 페이지에 연결했습니다.
선행기술 상세 조회
src/hooks/useTechDetail.ts, src/app/(main)/tech/[id]/page.tsx
URL 파라미터 처리, 상세 조회, 오류 처리와 썸네일 계산을 useTechDetail로 이동했습니다.

검색 목록 UI

Layer / File(s) Summary
스크롤 오버레이와 헤더 치수
src/components/searchlist/ProjectList.tsx, src/components/searchlist/ResultListHeader.tsx
태그 목록 오른쪽에 비인터랙티브 오버레이를 추가하고 헤더의 폭·높이 유틸리티를 변경했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 훅으로 API 호출·상태 관리 로직을 분리한 변경점을 잘 요약한 제목입니다.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/analysis-api
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/analysis-api

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.

@github-actions

Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (9)
src/hooks/useReport.ts (1)

30-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

세 훅이 동일한 "인증 게이트 + caseId 대조 + reloadCount" 조회 패턴을 복제하고 있습니다.

로직 분리 자체는 아주 좋은 방향입니다 👍. 다만 isInitialized/accessToken 게이트, {caseId, data} 형태 보관 후 현재 id와 대조, reloadCount로 재조회, errorMessage·isLoading 파생 계산이 세 파일에 그대로 반복되어 있어, 한쪽만 고치면 동작이 어긋나기 쉽습니다. useAuthedResource<T>(id, fetcher, errorMessages) 같은 제네릭 훅으로 추출하고 각 훅은 도메인 상태(선택 논리, 비교 결과 저장 등)만 담당하게 하는 구성을 권합니다.

  • src/hooks/useReport.ts#L30-L65: 조회/파생상태 블록을 공용 훅 호출로 대체하고 REPORT_ERROR_MESSAGES만 주입.
  • src/hooks/useNoveltyAnalysis.ts#L34-L70: 동일하게 공용 훅으로 교체하고 saveComparison만 남기기.
  • src/hooks/useInventiveStepAnalysis.ts#L74-L130: 공용 훅으로 교체하되 성공 콜백에서 selectedLogics/aiRecommendedArgumentIds/placeholderArgumentIds 파생 로직만 유지.
🤖 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 `@src/hooks/useReport.ts` around lines 30 - 65, 중복된 인증 게이트·caseId
대조·reloadCount 재조회·에러 및 로딩 파생 로직을 제네릭 공용 훅 useAuthedResource<T>(id, fetcher,
errorMessages)로 추출하고 세 훅이 이를 사용하도록 변경하세요. src/hooks/useReport.ts#L30-L65에서는
REPORT_ERROR_MESSAGES만 주입하고, src/hooks/useNoveltyAnalysis.ts#L34-L70에서는
saveComparison만 유지하세요. src/hooks/useInventiveStepAnalysis.ts#L74-L130에서는 성공 콜백의
selectedLogics·aiRecommendedArgumentIds·placeholderArgumentIds 파생 로직만 유지하세요.
src/hooks/useInventiveStepAnalysis.ts (3)

45-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

순수 유틸 getArgument는 훅 모듈 밖으로 (선택).

getArgument는 상태와 무관한 순수 함수인데 훅 파일에서 export되어, 페이지가 데이터 로직 훅을 유틸 목적으로도 import하게 됩니다. @/lib/inventiveStep 같은 모듈로 옮기면 모듈 경계와 재사용성이 명확해집니다.

🤖 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 `@src/hooks/useInventiveStepAnalysis.ts` around lines 45 - 51, Move the pure
exported getArgument function and its required logic-key mapping dependencies
from useInventiveStepAnalysis.ts into a reusable utility module such as
`@/lib/inventiveStep`, then update all imports and exports to use that module
while preserving its existing lookup behavior.

212-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

reload의 초기화 대상이 일부만 포함됩니다 (선택).

aiRecommendedArgumentIdsupdateError는 그대로 남아, 재조회가 실패하면 이전 상태의 배지 근거·에러 문구가 화면에 남습니다. 초기화 집합을 한 곳으로 모으면 누락이 줄어듭니다.

♻️ 제안
   const reload = () => {
     setResult(null);
     setRequestError(null);
     setSelectedLogics(new Set());
+    setAiRecommendedArgumentIds(new Set());
     setPlaceholderArgumentIds(new Set());
+    setUpdateError(null);
     setReloadCount((count) => count + 1);
   };
🤖 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 `@src/hooks/useInventiveStepAnalysis.ts` around lines 212 - 218, reload 함수의 초기화
대상에 aiRecommendedArgumentIds와 updateError를 추가해 재조회 시 이전 추천 근거와 오류 메시지가 남지 않도록
하세요. 기존 초기화 항목과 함께 하나의 초기화 집합으로 관리해 누락을 방지하고, 재조회 카운트 증가 동작은 유지하세요.

143-151: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

응답 정규화로 as InventiveStepArgument[]를 제거하세요.

updateInventiveArgument 응답은 InventiveStepArgument이고 현재 content도 서버 응답에 포함되므로 요청 body.content를 덮어써 서버 상태와 불일치할 수 있어요. React/Next 서버 컴포넌트 환경에서는 데이터가 서버 정규화 상태를 기준으로 동기화되므로, updatedArgument.content 사용 후 Record<string, never> 처리를 제외하면 타입 단정도 필요 없어집니다.

🤖 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 `@src/hooks/useInventiveStepAnalysis.ts` around lines 143 - 151, In the
argument update mapping within the inventive step analysis hook, use the
server-normalized updatedArgument.content instead of conditionally overwriting
content with body.content. Remove the `as InventiveStepArgument[]` type
assertion and preserve the existing `updatedArgument` fields so the mapped
result is inferred as the correct type, excluding any `Record<string, never>`
handling.
src/hooks/useAnalysisPriorArts.ts (1)

88-123: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

늦게 도착한 상세 응답이 다른 사건 화면에 반영될 수 있습니다 (권장).

에러 코드 매핑과 fallback 문구 처리는 깔끔합니다 👍. 다만 getPriorArtDetail 응답 대기 중 사용자가 다른 사건으로 이동하면, 뒤늦게 도착한 응답이 새 화면의 우측 패널에 그대로 반영됩니다. id를 캡처해 검증하거나 요청 시퀀스를 두면 안전합니다. 참고로 함수 정체성 안정화가 필요해지면 useCallback(React 문서 "useCallback")로 감싸는 편이 좋습니다.

🤖 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 `@src/hooks/useAnalysisPriorArts.ts` around lines 88 - 123, Prevent stale
getPriorArtDetail results from updating the current panel after the user
switches cases or selections. In handleSelect, capture the relevant
case/selection identity or request sequence before awaiting, then verify it is
still current before applying setSelectedPatent or setSelectError; keep the
existing error mapping and finally cleanup behavior intact.
src/app/(main)/myhistory/[id]/novelty/page.tsx (1)

67-67: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

시그니처는 정확히 맞습니다 👍 (참고)

NoveltyTablePromise.allSettled로 여러 건을 저장하는 구조와 saveComparison(comparisonId, body)가 잘 맞물립니다. 다만 saveComparison은 매 렌더마다 새 참조가 되므로, 향후 NoveltyTableReact.memo로 감싸거나 자식 effect 의존성에 넣게 되면 훅 쪽에서 useCallback(React 문서 "useCallback") 적용이 필요합니다.

🤖 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 `@src/app/`(main)/myhistory/[id]/novelty/page.tsx at line 67, Wrap
saveComparison in useCallback so its function reference remains stable across
renders when passed to NoveltyTable. Include every value used by saveComparison
in the dependency array, preserving its existing behavior and signature.
src/hooks/useNoveltyAnalysis.ts (1)

83-91: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

선택 사항입니다: citationstring | null로 정규화하세요.

현재는 NoveltyTable에서 비어 있는 인용문을 null로 보내고 있어서 여기까지 undefined가 전달되는 경로는 보이지 않습니다. 다만 응답이나 request에서 citation property가 누락되면 optional property 접근으로 undefined가 유지될 수 있으니, 타입 안정성을 위해 fallback으로 정규화하는 것이 좋습니다. React Server Component/Client Component 관계에서는 이 부분이 서버-클라이언트 상태 동기화보다는 TypeScript 타입 계약의 명확도로 작용합니다.

♻️ 제안
-                    citation: updated.citation !== undefined ? updated.citation : body.citation,
+                    citation: updated.citation ?? body.citation ?? null,
🤖 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 `@src/hooks/useNoveltyAnalysis.ts` around lines 83 - 91, Normalize the citation
assignment in the comparison update within useNoveltyAnalysis so it always
produces a string or null, including when both updated.citation and
body.citation are undefined. Preserve the existing preference for
updated.citation when provided and use an explicit null fallback for missing
values.
src/hooks/useSearchProgress.ts (2)

25-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

타입 안정성 팁: TERMINAL_MESSAGEstatus.status 유니온에 맞춰 좁혀보면 어떨까요?

지금은 Record<string, (status: SearchStatusResponse) => string>으로 선언돼 있어서, SearchStatusResponse["status"]에 새로운 값(예: "timeout")이 추가돼도 TypeScript가 매핑 누락을 잡아주지 못해요. TypeScript 공식 문서의 Mapped Types처럼 키를 실제 유니온 타입에서 파생시키면, 새 상태가 추가될 때 컴파일 타임에 바로 알 수 있어 유지보수성이 좋아집니다.

💡 제안: 키 타입을 좁혀서 누락을 컴파일 타임에 잡기
-const TERMINAL_MESSAGE: Record<string, (status: SearchStatusResponse) => string> = {
+type TerminalStatus = Exclude<SearchStatusResponse["status"], "in_progress" | "completed">;
+
+const TERMINAL_MESSAGE: Record<TerminalStatus, (status: SearchStatusResponse) => string> = {
   no_results: () => "조건에 맞는 선행기술을 찾지 못했어요.",
   invalid_input: (status) => status.reasonInvalid || "입력하신 내용을 다시 확인해주세요.",
   failed: (status) => status.error || status.reasonInvalid || "탐색 중 오류가 발생했습니다.",
   cancelled: () => "탐색이 취소되었습니다.",
 };

SearchStatusResponse["status"]가 실제로 리터럴 유니온인지 확인이 필요해서 검증 태그를 붙였어요.

🤖 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 `@src/hooks/useSearchProgress.ts` around lines 25 - 30, Update TERMINAL_MESSAGE
to derive its keys from SearchStatusResponse["status"] using a mapped type or
equivalent exhaustive type, while preserving the existing status handlers and
callback signatures. Ensure adding a new status such as timeout causes a
compile-time missing-key error.

96-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

"ApiError → 메시지 매핑 → fallback" 패턴, 공용 헬퍼로 뽑아내면 4곳이 한 줄로 줄어요.

두 훅 파일에서 동일한 에러 변환 로직이 4번 반복되고 있어요. 로직 자체는 정확하지만, 나중에 useNoveltyAnalysis, useReport 등 다른 훅에서도 같은 패턴이 또 나올 가능성이 높아 보여서(PR 스택 개요 참고) 지금 헬퍼로 뽑아두면 유지보수성이 확 좋아질 거예요.

  • src/hooks/useSearchProgress.ts#L96-L113: poll의 catch 블록을 아래 헬퍼로 교체.
  • src/hooks/useSearchProgress.ts#L55-L64: handleStop의 catch 블록을 동일 헬퍼로 교체.
  • src/hooks/useSearchResult.ts#L44-L55: getPriorArts 조회 catch 블록을 동일 헬퍼로 교체.
  • src/hooks/useSearchResult.ts#L92-L102: handleImportPatentNumber의 catch 블록을 동일 헬퍼로 교체.
🔧 제안: `src/lib/api/error.ts`에 공용 헬퍼 추가
 export class ApiError extends Error {
   status: number;
   errorCode: string;
   errors?: ApiFieldError[];
   // ...
 }
+
+export function resolveApiErrorMessage(
+  err: unknown,
+  messages: Record<string, string>,
+  fallback: string
+): string {
+  if (err instanceof ApiError) {
+    return messages[err.errorCode] || err.message || fallback;
+  }
+  return fallback;
+}

그리고 각 catch 블록은 이렇게 단순해집니다:

-    } catch (err) {
-      if (err instanceof ApiError) {
-        setCancelError(
-          SEARCH_CANCEL_ERROR_MESSAGES[err.errorCode] ||
-            err.message ||
-            "탐색 중단 중 오류가 발생했습니다."
-        );
-      } else {
-        setCancelError("탐색 중단 중 오류가 발생했습니다.");
-      }
-    } finally {
+    } catch (err) {
+      setCancelError(
+        resolveApiErrorMessage(err, SEARCH_CANCEL_ERROR_MESSAGES, "탐색 중단 중 오류가 발생했습니다.")
+      );
+    } finally {
🤖 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 `@src/hooks/useSearchProgress.ts` around lines 96 - 113, 중복된 ApiError 메시지 매핑과
fallback 로직을 공용 에러 메시지 헬퍼로 추출하세요. src/lib/api/error.ts에 헬퍼를 추가하고,
src/hooks/useSearchProgress.ts의 96-113 및 55-64, src/hooks/useSearchResult.ts의
44-55 및 92-102 각 catch 블록에서 해당 헬퍼를 사용하도록 교체해 동일한 메시지 동작을 유지하세요.
🤖 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 `@src/hooks/useAnalysisPriorArts.ts`:
- Around line 53-85: Update the useEffect in useAnalysisPriorArts to reset
isLoading, priorArts, and error when the effect starts, before validating id.
For missing or non-numeric id values, explicitly stop loading and return; for
valid ids, retain the existing cancellation and request behavior so prior
results are cleared while the new request is pending.

In `@src/hooks/useReport.ts`:
- Around line 73-79: Update the useReport return value to omit the raw
accessToken and expose an isAuthenticated boolean instead; derive it from token
availability within useReport. Update the report page consumer to destructure
and use isAuthenticated for the reload-button condition, preserving the existing
report, errorMessage, isLoading, and reload behavior.

In `@src/hooks/useSearchResult.ts`:
- Around line 32-64: Update the caseId guard in the useSearchResult effect to
set an appropriate error message when caseId is absent before returning, so
invalid direct access is distinguishable from a valid empty result. Preserve the
existing fetch and cancellation behavior when caseId is present.

In `@src/hooks/useTechDetail.ts`:
- Around line 21-64: Update the id-change synchronization in useTechDetail so
its existing useEffect resets detail, error, and loading state before requesting
the new prior-art record. Initialize loading through the state’s normal default
rather than relying on Boolean(priorArtId), and preserve the existing
cancellation and fetch-result handling for each priorArtId.

---

Nitpick comments:
In `@src/app/`(main)/myhistory/[id]/novelty/page.tsx:
- Line 67: Wrap saveComparison in useCallback so its function reference remains
stable across renders when passed to NoveltyTable. Include every value used by
saveComparison in the dependency array, preserving its existing behavior and
signature.

In `@src/hooks/useAnalysisPriorArts.ts`:
- Around line 88-123: Prevent stale getPriorArtDetail results from updating the
current panel after the user switches cases or selections. In handleSelect,
capture the relevant case/selection identity or request sequence before
awaiting, then verify it is still current before applying setSelectedPatent or
setSelectError; keep the existing error mapping and finally cleanup behavior
intact.

In `@src/hooks/useInventiveStepAnalysis.ts`:
- Around line 45-51: Move the pure exported getArgument function and its
required logic-key mapping dependencies from useInventiveStepAnalysis.ts into a
reusable utility module such as `@/lib/inventiveStep`, then update all imports and
exports to use that module while preserving its existing lookup behavior.
- Around line 212-218: reload 함수의 초기화 대상에 aiRecommendedArgumentIds와 updateError를
추가해 재조회 시 이전 추천 근거와 오류 메시지가 남지 않도록 하세요. 기존 초기화 항목과 함께 하나의 초기화 집합으로 관리해 누락을 방지하고,
재조회 카운트 증가 동작은 유지하세요.
- Around line 143-151: In the argument update mapping within the inventive step
analysis hook, use the server-normalized updatedArgument.content instead of
conditionally overwriting content with body.content. Remove the `as
InventiveStepArgument[]` type assertion and preserve the existing
`updatedArgument` fields so the mapped result is inferred as the correct type,
excluding any `Record<string, never>` handling.

In `@src/hooks/useNoveltyAnalysis.ts`:
- Around line 83-91: Normalize the citation assignment in the comparison update
within useNoveltyAnalysis so it always produces a string or null, including when
both updated.citation and body.citation are undefined. Preserve the existing
preference for updated.citation when provided and use an explicit null fallback
for missing values.

In `@src/hooks/useReport.ts`:
- Around line 30-65: 중복된 인증 게이트·caseId 대조·reloadCount 재조회·에러 및 로딩 파생 로직을 제네릭 공용
훅 useAuthedResource<T>(id, fetcher, errorMessages)로 추출하고 세 훅이 이를 사용하도록 변경하세요.
src/hooks/useReport.ts#L30-L65에서는 REPORT_ERROR_MESSAGES만 주입하고,
src/hooks/useNoveltyAnalysis.ts#L34-L70에서는 saveComparison만 유지하세요.
src/hooks/useInventiveStepAnalysis.ts#L74-L130에서는 성공 콜백의
selectedLogics·aiRecommendedArgumentIds·placeholderArgumentIds 파생 로직만 유지하세요.

In `@src/hooks/useSearchProgress.ts`:
- Around line 25-30: Update TERMINAL_MESSAGE to derive its keys from
SearchStatusResponse["status"] using a mapped type or equivalent exhaustive
type, while preserving the existing status handlers and callback signatures.
Ensure adding a new status such as timeout causes a compile-time missing-key
error.
- Around line 96-113: 중복된 ApiError 메시지 매핑과 fallback 로직을 공용 에러 메시지 헬퍼로 추출하세요.
src/lib/api/error.ts에 헬퍼를 추가하고, src/hooks/useSearchProgress.ts의 96-113 및 55-64,
src/hooks/useSearchResult.ts의 44-55 및 92-102 각 catch 블록에서 해당 헬퍼를 사용하도록 교체해 동일한
메시지 동작을 유지하세요.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 50eb5b7a-76d4-4f49-9269-ee030370feda

📥 Commits

Reviewing files that changed from the base of the PR and between 62f5785 and 44c8956.

📒 Files selected for processing (16)
  • src/app/(main)/analysis/(workspace)/[id]/page.tsx
  • src/app/(main)/analysis/[id]/[patentId]/page.tsx
  • src/app/(main)/myhistory/[id]/novelty/page.tsx
  • src/app/(main)/search/loading/page.tsx
  • src/app/(main)/search/result/page.tsx
  • src/app/(main)/tech/[id]/page.tsx
  • src/app/(report)/myhistory/[id]/report/page.tsx
  • src/components/searchlist/ProjectList.tsx
  • src/components/searchlist/ResultListHeader.tsx
  • src/hooks/useAnalysisPriorArts.ts
  • src/hooks/useInventiveStepAnalysis.ts
  • src/hooks/useNoveltyAnalysis.ts
  • src/hooks/useReport.ts
  • src/hooks/useSearchProgress.ts
  • src/hooks/useSearchResult.ts
  • src/hooks/useTechDetail.ts

Comment on lines +53 to +85
useEffect(() => {
const caseId = Number(id);
if (!id || Number.isNaN(caseId)) return;

let cancelled = false;

getPriorArts(caseId)
.then((result) => {
if (cancelled) return;
setPriorArts(result.priorArts);
setError(null);
})
.catch((err) => {
if (cancelled) return;
if (err instanceof ApiError) {
setError(
PRIOR_ARTS_ERROR_MESSAGES[err.errorCode] ||
err.message ||
"선행문헌 목록을 불러오는 중 오류가 발생했습니다."
);
} else {
setError("선행문헌 목록을 불러오는 중 오류가 발생했습니다.");
}
})
.finally(() => {
if (cancelled) return;
setIsLoading(false);
});

return () => {
cancelled = true;
};
}, [id]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

id가 유효하지 않거나 바뀔 때 로딩/목록 상태가 정리되지 않습니다.

두 가지 경로가 문제입니다.

  1. id가 비어 있거나 숫자가 아니면 55번 줄에서 early return 하므로 finally가 실행되지 않고 isLoading이 초기값 true로 영구 고정됩니다. 페이지는 "불러오는 중..."만 계속 보여주고 에러 메시지도 뜨지 않습니다.
  2. id가 바뀌어 effect가 재실행될 때 setIsLoading(true) / setPriorArts([])가 없어, 새 요청이 끝날 때까지 이전 사건의 선행문헌 목록이 로딩 표시 없이 그대로 노출됩니다.

React 공식 문서의 "Fetching data with Effects" 예시처럼, effect 진입 시 결과 상태를 리셋해두면 두 문제를 함께 막을 수 있습니다.

🐛 제안: effect 진입 시 상태 리셋 + 유효하지 않은 id 처리
   useEffect(() => {
     const caseId = Number(id);
-    if (!id || Number.isNaN(caseId)) return;
+    if (!id || Number.isNaN(caseId)) {
+      setPriorArts([]);
+      setError("잘못된 사건 번호입니다.");
+      setIsLoading(false);
+      return;
+    }
 
     let cancelled = false;
+    setIsLoading(true);
+    setPriorArts([]);
+    setError(null);
 
     getPriorArts(caseId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const caseId = Number(id);
if (!id || Number.isNaN(caseId)) return;
let cancelled = false;
getPriorArts(caseId)
.then((result) => {
if (cancelled) return;
setPriorArts(result.priorArts);
setError(null);
})
.catch((err) => {
if (cancelled) return;
if (err instanceof ApiError) {
setError(
PRIOR_ARTS_ERROR_MESSAGES[err.errorCode] ||
err.message ||
"선행문헌 목록을 불러오는 중 오류가 발생했습니다."
);
} else {
setError("선행문헌 목록을 불러오는 중 오류가 발생했습니다.");
}
})
.finally(() => {
if (cancelled) return;
setIsLoading(false);
});
return () => {
cancelled = true;
};
}, [id]);
useEffect(() => {
const caseId = Number(id);
if (!id || Number.isNaN(caseId)) {
setPriorArts([]);
setError("잘못된 사건 번호입니다.");
setIsLoading(false);
return;
}
let cancelled = false;
setIsLoading(true);
setPriorArts([]);
setError(null);
getPriorArts(caseId)
.then((result) => {
if (cancelled) return;
setPriorArts(result.priorArts);
setError(null);
})
.catch((err) => {
if (cancelled) return;
if (err instanceof ApiError) {
setError(
PRIOR_ARTS_ERROR_MESSAGES[err.errorCode] ||
err.message ||
"선행문헌 목록을 불러오는 중 오류가 발생했습니다."
);
} else {
setError("선행문헌 목록을 불러오는 중 오류가 발생했습니다.");
}
})
.finally(() => {
if (cancelled) return;
setIsLoading(false);
});
return () => {
cancelled = true;
};
}, [id]);
🤖 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 `@src/hooks/useAnalysisPriorArts.ts` around lines 53 - 85, Update the useEffect
in useAnalysisPriorArts to reset isLoading, priorArts, and error when the effect
starts, before validating id. For missing or non-numeric id values, explicitly
stop loading and return; for valid ids, retain the existing cancellation and
request behavior so prior results are cleared while the new request is pending.

Comment thread src/hooks/useReport.ts
Comment on lines +32 to +64
useEffect(() => {
if (!caseId) return;

let cancelled = false;

getPriorArts(Number(caseId))
.then((result) => {
if (cancelled) return;
setPriorArts(result.priorArts);
setTotalCount(result.totalCount);
setError(null);
})
.catch((err) => {
if (cancelled) return;
if (err instanceof ApiError) {
setError(
PRIOR_ARTS_ERROR_MESSAGES[err.errorCode] ||
err.message ||
"선행문헌 목록을 불러오는 중 오류가 발생했습니다."
);
} else {
setError("선행문헌 목록을 불러오는 중 오류가 발생했습니다.");
}
})
.finally(() => {
if (cancelled) return;
setIsLoading(false);
});

return () => {
cancelled = true;
};
}, [caseId]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

caseId가 없을 때 사용자에게 아무 안내도 없어요.

useSearchProgresscaseId가 없으면 /search?resume=1로 돌려보내는데, 이 훅은 32번째 줄에서 그냥 return만 하고 끝나요. 그러면 isLoading은 계속 false(초기값이 Boolean(caseId)라 애초에 false), errornull이라서 사용자는 "탐색한 선행기술 0건"이라는 텅 빈 화면만 보게 됩니다. 잘못된 진입(예: caseId 없이 직접 URL 접근)을 구분할 수 있는 안내가 있으면 좋겠어요.

🔧 제안: caseId 없을 때 에러 상태로 안내
   useEffect(() => {
-    if (!caseId) return;
+    if (!caseId) {
+      setError("사건 정보를 찾을 수 없습니다. 다시 탐색을 시작해주세요.");
+      return;
+    }

     let cancelled = false;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!caseId) return;
let cancelled = false;
getPriorArts(Number(caseId))
.then((result) => {
if (cancelled) return;
setPriorArts(result.priorArts);
setTotalCount(result.totalCount);
setError(null);
})
.catch((err) => {
if (cancelled) return;
if (err instanceof ApiError) {
setError(
PRIOR_ARTS_ERROR_MESSAGES[err.errorCode] ||
err.message ||
"선행문헌 목록을 불러오는 중 오류가 발생했습니다."
);
} else {
setError("선행문헌 목록을 불러오는 중 오류가 발생했습니다.");
}
})
.finally(() => {
if (cancelled) return;
setIsLoading(false);
});
return () => {
cancelled = true;
};
}, [caseId]);
useEffect(() => {
if (!caseId) {
setError("사건 정보를 찾을 수 없습니다. 다시 탐색을 시작해주세요.");
return;
}
let cancelled = false;
getPriorArts(Number(caseId))
.then((result) => {
if (cancelled) return;
setPriorArts(result.priorArts);
setTotalCount(result.totalCount);
setError(null);
})
.catch((err) => {
if (cancelled) return;
if (err instanceof ApiError) {
setError(
PRIOR_ARTS_ERROR_MESSAGES[err.errorCode] ||
err.message ||
"선행문헌 목록을 불러오는 중 오류가 발생했습니다."
);
} else {
setError("선행문헌 목록을 불러오는 중 오류가 발생했습니다.");
}
})
.finally(() => {
if (cancelled) return;
setIsLoading(false);
});
return () => {
cancelled = true;
};
}, [caseId]);
🤖 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 `@src/hooks/useSearchResult.ts` around lines 32 - 64, Update the caseId guard
in the useSearchResult effect to set an appropriate error message when caseId is
absent before returning, so invalid direct access is distinguishable from a
valid empty result. Preserve the existing fetch and cancellation behavior when
caseId is present.

Comment thread src/hooks/useTechDetail.ts
GirimNam added 3 commits July 30, 2026 00:41
# Conflicts:
#	src/app/(main)/analysis/[id]/[patentId]/page.tsx
#	src/app/(main)/search/loading/page.tsx
병합 커밋 시 useSearchProgress.ts의 activeSearchStore/resetSearchForm
연동 변경사항이 스테이징되지 않아 이전 버전으로 커밋된 것을 바로잡음.
@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

@minsxo
minsxo merged commit 90f1758 into main Jul 29, 2026
2 checks passed
GirimNam added a commit to GirimNam/IPX-FE that referenced this pull request Jul 29, 2026
[REFACTOR] API 동작 흐름 컴포넌트 훅으로 분리
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants