Skip to content

Conversation

@YouD0313
Copy link
Collaborator

@YouD0313 YouD0313 commented Jun 17, 2025

구현내용

포지션, 스킬 태그 관리

연관이슈

close #336

Summary by CodeRabbit

  • 신규 기능

    • 관리자 페이지에서 스킬 태그와 포지션 태그를 각각 별도로 생성, 수정, 삭제할 수 있는 관리 UI가 추가되었습니다.
    • 스킬 태그 등록 시 이미지 업로드 및 미리보기 기능이 제공됩니다.
    • 태그명 중복, 미입력, 이미지 미첨부 등에 대한 안내 메시지가 추가되었습니다.
  • UI/스타일

    • 태그 관리 인터페이스 및 태그 리스트에 대한 전용 스타일이 적용되었습니다.
    • 사이드바 메뉴가 ‘스킬 태그’와 ‘포지션 태그’로 분리되어 표시됩니다.
  • 버그 수정/개선

    • 태그 선택 및 필터링 기능이 개선되어, 보다 직관적으로 태그를 선택할 수 있습니다.
    • 이미지 캐싱 문제를 방지하여 태그 이미지가 항상 최신 상태로 표시됩니다.
  • 기타

    • 기존 통합 태그 관리 메뉴가 스킬 태그와 포지션 태그로 분리되었습니다.
    • 관련 경로 및 라우팅이 각각의 태그 관리 페이지로 변경되었습니다.

@YouD0313 YouD0313 requested a review from layout-SY June 17, 2025 12:02
@YouD0313 YouD0313 self-assigned this Jun 17, 2025
@YouD0313 YouD0313 linked an issue Jun 17, 2025 that may be closed by this pull request
4 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jun 17, 2025

"""

Walkthrough

태그 관리 기능이 대규모로 도입되었습니다. 스킬 태그와 포지션 태그 각각에 대한 CRUD(생성, 수정, 삭제) UI 및 API 연동이 구현되었으며, 관련 라우팅, 스타일, 쿼리 키, 상수, 훅 등이 추가 및 정비되었습니다. 기존 태그 관리 메뉴가 스킬/포지션으로 분리되었습니다.

Changes

파일/그룹 변경 요약
src/api/admin/tag.api.ts 스킬/포지션 태그 CRUD API 함수 신설
src/components/admin/adminTags/* 태그 관리 UI 컴포넌트 및 스타일, CRUD 폼, 리스트, 타입 분리 및 구현
src/components/common/positionButton/*, src/components/common/skillTagBox/* 포지션 버튼 및 스킬 태그박스 컴포넌트 prop/이벤트 개선
src/constants/admin/sidebar.ts, src/constants/routes.ts 태그 관리 메뉴 및 라우트 분리(스킬/포지션)
src/constants/user/modalMessage.ts 태그 중복/빈값/이미지 누락 메시지 상수 추가
src/hooks/admin/useAdminTag.ts 태그 CRUD를 위한 React Query 훅 추가
src/hooks/queries/keys.ts 태그 관련 쿼리 키 추가 및 as const 적용
src/hooks/user/useSearchFilteringTags.ts 태그 조회 훅 리팩토링 및 네이밍 변경
src/models/tags.ts 태그 폼 타입 신설
src/pages/admin/adminTags/* 태그 관리 페이지 분리 및 불필요 파일 삭제
src/routes/AdminRoutes.tsx 태그 관리 라우트 분리 및 User 상세 라우트 정리
src/components/user/*, src/components/user/home/* 태그 관련 훅/prop 변경에 따른 연동 수정

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin User
    participant UI as AdminTagCRUD
    participant API as tag.api.ts
    participant Server as Backend

    Admin->>UI: 태그명/이미지 입력 및 등록/수정/삭제 클릭
    UI->>API: post/put/deleteSkillTag 또는 post/put/deletePositionTag 호출
    API->>Server: HTTP 요청 (POST/PUT/DELETE)
    Server-->>API: 처리 결과 반환
    API-->>UI: Promise resolve/reject
    UI-->>Admin: 성공/실패 안내 및 목록 갱신
Loading

Assessment against linked issues

Objective Addressed Explanation
스킬 태그 관리 UI (#336), 스킬 태그 등록/삭제 API (#336)
포지션 태그 관리 UI (#336), 포지션 태그 등록/삭제 API (#336)

Assessment against linked issues: Out-of-scope changes

해당 PR의 변경사항은 모두 이슈 #336의 태그 관리 페이지 구현 목적에 부합합니다.

Suggested labels

⛏기능, 🛠개선

Suggested reviewers

  • layout-SY

Poem

🐇
태그가 쏙쏙, 관리가 척척
스킬과 포지션, 나란히 정렬
버튼 한 번에 등록과 삭제
토끼도 깡총, 기쁜 마음에
관리의 봄이 찾아왔네!
🏷️✨
"""

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-18T15_25_41_051Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 8

🔭 Outside diff range comments (1)
src/components/common/positionButton/PositionButton.tsx (1)

1-6: 타입 네임스페이스 누락으로 컴파일 오류 가능

React.MouseEvent 타입을 사용하지만 react 를 import 하지 않았습니다. TypeScript 5+ 환경에서는 네임스페이스 참조 시 import type 이 필요합니다.

-import * as S from './PositionButton.styled';
+import * as S from './PositionButton.styled';
+import type React from 'react';
🧹 Nitpick comments (18)
src/components/admin/adminTags/positions/AdminPositionItems.styled.ts (1)

1-8: Styled 컴포넌트 네이밍 개선 제안: Container라는 범용 이름이 다른 스타일 컴포넌트와 충돌할 수 있으므로, PositionItemsContainer와 같이 보다 구체적인 이름으로 변경을 권장합니다.

src/models/tags.ts (1)

34-36: img 타입 간소화 제안: img?: File | undefined 대신 img?: File로만 선언해도 동일한 의미이므로 타입을 간결하게 유지하는 것을 권장합니다.

src/hooks/queries/keys.ts (1)

73-77: 키 네이밍 일관성 제안: Tag보다는 TagKey 또는 tagKeys와 같이 접미사를 붙여 목적을 명확히 구분하는 네이밍을 고려해보세요.

src/components/admin/adminTags/AdminTagsBasic.styled.ts (1)

11-19: CRUDContainer 명칭이 다른 파일과 중복되어 혼란을 줄 수 있습니다
AdminTagCRUD.styled.ts에도 동일한 이름의 CRUDContainer가 존재합니다. import alias 가 달라도 코드리뷰·협업 시 혼동될 수 있으니 BasicCRUDContainer 등으로 구체화하는 것을 제안합니다.

-export const CRUDContainer = styled.section`
+export const BasicCRUDContainer = styled.section`
src/components/admin/adminTags/AdminTagCRUD.styled.ts (2)

4-7: CRUDContainer 이름 충돌 가능성
동일 프로젝트 내에 같은 이름의 styled-component 가 복수 존재합니다. IDE 자동 import 시 잘못된 파일을 가져올 위험이 있으므로, 컨텍스트에 맞는 접두어(예: TagCRUDContainer)를 붙여 구분하는 편이 안전합니다.


24-25: 불필요한 주석 코드는 제거하여 가독성을 높여 주세요
/* align-items: center; */ 주석은 구현과 무관하므로 삭제를 권장합니다.

src/components/admin/adminTags/skills/AdminSkillTagItems.styled.ts (1)

1-4: 빈 styled 컴포넌트 감축 검토
SkillTagItemWrapper에 현재 CSS 스타일이 정의되어 있지 않아 일반 <section> 태그를 사용하는 것과 기능상 차이가 없습니다. 추후 스타일이 추가될 예정이라면 그대로 두셔도 되지만, 당장 필요 없다면 불필요한 래퍼 제거를 고려해보세요.

src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx (1)

21-31: 이벤트 제네릭 타입이 과도하게 제한적입니다

React.MouseEvent<HTMLDivElement> 으로 한정하면 e.currentTarget 타입 확인에는 도움이 되지만, 실제 e.target<svg>, <span> 등 다양한 요소일 수 있습니다. 이미 const target = e.target as HTMLElement 로 캐스팅하고 있으므로 굳이 HTMLDivElement 로 좁힐 실익이 적고, 오히려 추후 <ul> 등 다른 컨테이너로 교체될 때 타입 오류를 유발할 여지가 있습니다.

-const handleSkillTagFilterClick = (e: React.MouseEvent<HTMLDivElement>) => {
+const handleSkillTagFilterClick = (e: React.MouseEvent<HTMLElement>) => {

타입 범위를 한 단계 넓혀 유지보수성을 확보하는 편이 안전합니다.

src/hooks/admin/useAdminTag.ts (1)

14-97: 훅 이름이 실제 역할과 불일치합니다

useAdminSkillTag 라는 이름이지만 내부에서 포지션 태그까지 모두 다룹니다. 네이밍은 의도를 표현해야 하므로 useAdminTagMutations 처럼 범용 이름으로 변경하거나, 두 개의 전용 훅으로 분리하여 관심사를 명확히 하는 편이 좋습니다.

src/components/user/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.tsx (1)

31-37: 인덱스 오프셋 하드코딩 대신 ID 직접 사용 권장

idx + 1 로 ID 를 보정하고 있는데, 태그 ID 가 배열 순서와 항상 1씩 offset 된다는 보장이 깨질 경우 오류가 발생합니다. position.id 값을 직접 넘기는 방식이 더 견고합니다.

-const handleClickSelect = (
-  e: React.MouseEvent<HTMLButtonElement>,
-  idx: number
-) => {
-  handleClick(e, idx + 1);
+const handleClickSelect = (
+  e: React.MouseEvent<HTMLButtonElement>,
+  id: number
+) => {
+  handleClick(e, id);
 }

맵핑 부분에서도 idx 대신 position.id 를 전달하도록 수정해 주세요.

src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (2)

4-7: 타입·컴포넌트 명 오탈자

AdminSKillTagItemsProps 처럼 대문자 K 가 빠져 있습니다. 컴파일에는 영향이 없으나 일관성을 위해 수정해 주세요.

-interface AdminSKillTagItemsProps {
+interface AdminSkillTagItemsProps {

13-23: data-id 파싱 실패 시 안전장치 부족

Number() 변환 결과가 NaN 이면 현재 if (!id) return; 으로 필터링하지만 NaN 도 falsy 로 평가되므로 의도대로 동작합니다. 다만 0 번 태그를 제외하려는 목적이라면 isNaN(id) || id === 0 처럼 명시적으로 체크해 주면 가독성이 좋아집니다.

src/components/admin/adminTags/positions/AdminPositionItems.tsx (1)

12-24: 데이터 로딩 상태 및 빈 배열 처리 필요

useSearchFilteringSkillTag 훅은 네트워크 요청 완료 전까지 positionTagsData 를 빈 배열로 유지합니다. 현재 구현에서는 빈 배열일 경우 아무것도 렌더링되지 않아 관리 페이지가 ‘비어있는 화면’처럼 보일 수 있습니다.

아래와 같이 스켈레톤/로딩 UI 또는 “등록된 포지션 태그가 없습니다” 메시지를 추가해 UX 를 개선하는 방안을 고려해 주세요.

+  if (!positionTagsData.length)
+    return <S.EmptyMessage>포지션 태그가 없습니다.</S.EmptyMessage>;
src/components/common/skillTagBox/SkillTagBox.tsx (1)

17-18: width 기본값 하드-코딩 대신 Theme 활용 권장

width='100%' 를 매번 기본값으로 두는 대신, styled-components 테마에 정의된 레이아웃 변수(예: theme.layout.fullWidth)를 사용하는 것이 재사용성과 일관성을 높입니다.

src/api/admin/tag.api.ts (1)

38-45: API 응답값 반환 누락

모든 API 함수가 await httpClient.* 호출 결과를 반환하지 않고 있습니다. 호출부에서 성공/실패 여부나 메시지를 활용하려면 응답 객체를 그대로 반환하거나 필요한 데이터만 추출해 반환하는 편이 좋습니다.

src/components/admin/adminTags/AdminTagsBasic.tsx (2)

10-16: TWitchTag · 오타 및 가독성 문제

타입과 변수명이 모두 witch 로 표기돼 있습니다. Which 또는 더 명확한 네이밍(TTagType 등)으로 교체하면 오해를 줄일 수 있습니다.


28-54: tagState 타입 안정성 개선 필요

type 필드를 문자열로 두면 오타로 인한 런타임 오류 가능성이 있습니다.
아래처럼 리터럴 유니온을 활용하면 컴파일 단계에서 오류를 잡을 수 있습니다.

type TagKind = 'skill' | 'position';

interface TagState<T> { type: TagKind; /* ... */ }
src/components/admin/adminTags/AdminTagCRUD.tsx (1)

150-157: setTimeout 사용 불필요

입력 초기화에 1 초 지연을 둘 이유가 없습니다. 즉시 초기화하거나, 필요 시 setTimeout 0 으로 충분합니다.
지연은 UX 저하·메모리 누수 원인이 될 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebef691 and 902ce49.

📒 Files selected for processing (32)
  • src/api/admin/tag.api.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagCRUD.styled.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagCRUD.tsx (1 hunks)
  • src/components/admin/adminTags/AdminTagsBasic.styled.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagsBasic.tsx (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionItems.styled.ts (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionItems.tsx (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionTags.tsx (1 hunks)
  • src/components/admin/adminTags/skills/AdminSkillTagItems.styled.ts (1 hunks)
  • src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (1 hunks)
  • src/components/admin/adminTags/skills/AdminSkillTags.tsx (1 hunks)
  • src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx (1 hunks)
  • src/components/common/positionButton/PositionButton.styled.ts (0 hunks)
  • src/components/common/positionButton/PositionButton.tsx (1 hunks)
  • src/components/common/skillTagBox/SkillTagBox.tsx (2 hunks)
  • src/components/user/home/projectCardLists/cardList/CardList.tsx (0 hunks)
  • src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx (1 hunks)
  • src/components/user/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.tsx (3 hunks)
  • src/constants/admin/sidebar.ts (1 hunks)
  • src/constants/routes.ts (1 hunks)
  • src/constants/user/modalMessage.ts (1 hunks)
  • src/hooks/admin/useAdminInquiry.ts (1 hunks)
  • src/hooks/admin/useAdminNotice.ts (1 hunks)
  • src/hooks/admin/useAdminTag.ts (1 hunks)
  • src/hooks/admin/useGetAllInquiries.ts (1 hunks)
  • src/hooks/queries/keys.ts (1 hunks)
  • src/hooks/user/useSearchFilteringSkillTag.ts (2 hunks)
  • src/models/tags.ts (1 hunks)
  • src/pages/admin/adminTags/AdminTags.tsx (0 hunks)
  • src/pages/admin/adminTags/position/AdminPositionTagsPage.tsx (1 hunks)
  • src/pages/admin/adminTags/skill/AdminSkillTagsPage.tsx (1 hunks)
  • src/routes/AdminRoutes.tsx (2 hunks)
💤 Files with no reviewable changes (3)
  • src/components/user/home/projectCardLists/cardList/CardList.tsx
  • src/pages/admin/adminTags/AdminTags.tsx
  • src/components/common/positionButton/PositionButton.styled.ts
🧰 Additional context used
🧬 Code Graph Analysis (13)
src/components/admin/adminTags/positions/AdminPositionItems.styled.ts (1)
src/components/admin/adminTags/AdminTagsBasic.styled.ts (1)
  • Container (3-9)
src/hooks/user/useSearchFilteringSkillTag.ts (1)
src/hooks/queries/keys.ts (1)
  • Tag (73-77)
src/pages/admin/adminTags/skill/AdminSkillTagsPage.tsx (1)
src/pages/admin/CommonAdminPage.tsx (1)
  • CommonAdminPage (9-20)
src/components/admin/adminTags/skills/AdminSkillTags.tsx (1)
src/components/admin/adminTags/AdminTagsBasic.tsx (1)
  • AdminTagsBasic (12-89)
src/pages/admin/adminTags/position/AdminPositionTagsPage.tsx (1)
src/pages/admin/CommonAdminPage.tsx (1)
  • CommonAdminPage (9-20)
src/constants/admin/sidebar.ts (1)
src/constants/routes.ts (1)
  • ADMIN_ROUTE (34-54)
src/components/admin/adminTags/positions/AdminPositionItems.tsx (2)
src/hooks/user/useSearchFilteringSkillTag.ts (1)
  • useSearchFilteringSkillTag (11-49)
src/components/common/positionButton/PositionButton.tsx (1)
  • PositionButton (11-29)
src/components/common/positionButton/PositionButton.tsx (1)
src/components/common/positionButton/PositionButton.styled.ts (1)
  • PositionButton (3-39)
src/components/admin/adminTags/AdminTagsBasic.styled.ts (2)
src/components/admin/adminTags/positions/AdminPositionItems.styled.ts (1)
  • Container (3-8)
src/components/admin/adminTags/AdminTagCRUD.styled.ts (1)
  • CRUDContainer (4-7)
src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (1)
src/components/common/skillTagBox/SkillTagBox.tsx (1)
  • SkillTagBox (16-61)
src/hooks/admin/useAdminTag.ts (3)
src/api/admin/tag.api.ts (6)
  • postSkillTag (5-12)
  • putSkillTag (14-27)
  • deleteSkillTag (29-36)
  • postPositionTag (38-45)
  • putPositionTag (47-60)
  • deletePositionTag (62-69)
src/hooks/queries/keys.ts (1)
  • Tag (73-77)
src/models/tags.ts (1)
  • TagFormType (34-37)
src/components/admin/adminTags/AdminTagCRUD.styled.ts (1)
src/components/user/customerService/inquiry/Inquiry.styled.ts (1)
  • SendButton (205-222)
src/api/admin/tag.api.ts (3)
src/api/http.api.ts (1)
  • httpClient (80-80)
src/models/apiCommon.ts (1)
  • ApiCommonBasicType (6-8)
src/models/tags.ts (1)
  • TagFormType (34-37)
🪛 Biome (1.9.4)
src/components/common/skillTagBox/SkillTagBox.tsx

[error] 40-44: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test
🔇 Additional comments (16)
src/hooks/admin/useAdminInquiry.ts (1)

7-7: 대소문자 수정으로 인한 런타임 오류 예방 👍

파일명 대소문자를 inquiry.api로 통일하여 case-sensitive 환경(Linux, CI)에서 발생할 수 있는 MODULE_NOT_FOUND 오류를 사전에 차단했습니다. 변경 내용 이상 없습니다.

src/hooks/admin/useGetAllInquiries.ts (2)

3-3: 올바른 경로로 수정 완료

inquiry.api로 경로가 정정되어 OS별 모듈 해상도 문제가 해결되었습니다.


10-13: select 제거로 반환 데이터량 증가 – 영향 확인 필요

기존에는 select 옵션으로 상위 5개 항목만 노출했던 것으로 보입니다. 옵션을 제거하면서 전체 문의 목록이 그대로 컴포넌트에 전달됩니다. 데이터 양이 많아질 경우 렌더링·메모리·네트워크 비용이 상승할 수 있으니,

  1. 실제 소비처에서 페이지네이션/가상 스크롤 등의 보호장치가 있는지
  2. 불필요하게 모든 데이터를 한 번에 가져올 필요가 없는지

를 한 번 더 점검해주세요.

src/components/admin/adminTags/positions/AdminPositionItems.styled.ts (1)

3-8: 레이아웃 스타일 적절: section 요소에 flex-wrap과 gap 설정을 적용해 태그 버튼 배치가 명확해졌습니다. UI 요구사항에 잘 부합하는 선택입니다.

src/hooks/admin/useAdminNotice.ts (1)

6-6: 경로 수정 일관성 확인: Notice.api에서 notice.api로 import 경로 변경은 파일 시스템 대소문자 일치를 위해 적절합니다. 다른 admin API 훅들도 동일하게 수정되었는지 검토해주세요.

src/models/tags.ts (1)

33-37: 새로운 TagFormType 인터페이스 추가: 태그 생성/수정 시 name과 선택적 img를 명확히 타입으로 정의한 점이 좋습니다. Form 상태 관리와 확장성도 확보되었습니다.

src/constants/user/modalMessage.ts (1)

30-32: 추가된 모달 메시지 검토: duplicationTag, emptyTag, emptySkillImg 메시지가 태그 관리 흐름에서 유용하며 기존 메시지 스타일과 일관됩니다.

src/constants/routes.ts (1)

40-41: ```shell
#!/bin/bash

브래킷 표기법으로 ADMIN_ROUTE.tags 참조 검색

rg -n "ADMIN_ROUTE['tags']"


</details>
<details>
<summary>src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx (1)</summary>

`21-22`: **아이콘 키 추가 후 `IconKey` 타입·사이드바 리스트 동기화 확인**  
`skillTags`, `positionTags`가 `IconKey` 유니온에 자동 반영되었지만, 사이드바를 구성하는 `list` 배열에서도 동일한 키 문자열을 사용해야 타입 오류가 없습니다. 해당 리스트 정의가 모두 업데이트되었는지 확인해 주세요.

</details>
<details>
<summary>src/components/admin/adminTags/skills/AdminSkillTags.tsx (1)</summary>

`3-5`: **단순 래퍼 컴포넌트 구현 적절**  
별도 로직 없이 `AdminTagsBasic`을 감싸는 구조는 명확하며, 추후 확장을 위한 분리로도 적합해 보입니다.

</details>
<details>
<summary>src/components/admin/adminTags/positions/AdminPositionTags.tsx (1)</summary>

`1-5`: **AdminTagsBasic에 태그 타입 전달 검증 필요**  
`AdminTagsBasic`가 skill/position 구분 없이 내부 로직으로 타입을 처리하는지 확인이 필요합니다. 만약 props로 tagType(예: `'position'`)을 받도록 설계되었다면, 해당 prop 전달을 누락한 것으로 보입니다. 컴포넌트가 예상대로 동작하는지 검증해 주세요.

</details>
<details>
<summary>src/pages/admin/adminTags/skill/AdminSkillTagsPage.tsx (1)</summary>

`1-5`: **스킬 태그 관리 페이지 래퍼 구현 적절**  
`CommonAdminPage`에 `title='스킬 태그 관리'`를 전달하는 단순 래퍼로, 설계 의도에 부합합니다. 추가 로직이나 prop 필요 없이 현재 구조가 명확해 보입니다.

</details>
<details>
<summary>src/pages/admin/adminTags/position/AdminPositionTagsPage.tsx (1)</summary>

`1-5`: **포지션 태그 관리 페이지 래퍼 구현 적절**  
`CommonAdminPage`에 `title='포지션 태그 관리'`를 전달하는 단순 래퍼로, 일관성 있고 명료합니다.

</details>
<details>
<summary>src/hooks/user/useSearchFilteringSkillTag.ts (1)</summary>

`17-36`: **중앙화된 Query Key 사용 확인**  
`useQueries`의 `queryKey`에 inline 배열 대신 `Tag` 상수를 적용하여 캐시 일관성을 개선한 점이 좋습니다. 모든 곳에서 동일한 `Tag` 상수를 사용해 데이터 불일치를 방지하는지 확인하세요.

</details>
<details>
<summary>src/constants/admin/sidebar.ts (1)</summary>

`33-41`: **스킬/포지션 태그 항목 분리 👍**

사이드바 항목을 두 개로 나누어 사용자가 각 태그 타입을 명확히 인지할 수 있게 된 점이 좋습니다. 라우트 상수와도 일관되게 연결되어 있어 문제가 없어 보입니다.

</details>
<details>
<summary>src/routes/AdminRoutes.tsx (1)</summary>

`122-130`: **새 라우트 상수 확인 필요**

`ADMIN_ROUTE.skillTags`·`ADMIN_ROUTE.positionTags` 가 constants 파일에 실제로 정의되어 있는지, 사이드바 메뉴와 URL 패턴이 일치하는지 확인해 주세요. 누락 시 빌드 타임 오류 또는 라우팅 실패가 발생합니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

} from '../../api/projectSearchFiltering.api';
import { Tag } from '../queries/keys';

export const useSearchFilteringSkillTag = () => {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

훅 이름이 반환 데이터 범위와 불일치
현재 useSearchFilteringSkillTag는 skill, position, method 태그를 모두 가져오므로 이름이 과도하게 한정적입니다. 전반적인 태그 필터링을 의미하도록 useSearchFilteringTags 등으로 리네이밍을 권장합니다.

-export const useSearchFilteringSkillTag = () => {
+export const useSearchFilteringTags = () => {
🤖 Prompt for AI Agents
In src/hooks/user/useSearchFilteringSkillTag.ts at line 11, the hook name
useSearchFilteringSkillTag is misleading because it returns skill, position, and
method tags, not just skill tags. Rename the hook to useSearchFilteringTags or a
similar name that reflects the broader scope of returned tags to improve clarity
and maintainability.

Comment on lines 38 to 45
$select={
(isMain &&
selectedTag?.includes(skillTagData.id) ||
((isMain &&
searchFiltersSkillTag?.includes(skillTagData.id)) ||
(isCreate && selectedTag?.includes(skillTagData.id))
? true
: false
: false)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

불필요한 삼항 연산자 제거 및 가독성 개선

$select 계산식에 중복된 selectedTag?.includes(...) 호출과 삼항 연산자가 있어 복잡도가 증가했습니다. 불필요한 ? true : false 를 없애고 조건을 평탄화하면 코드가 간결해집니다.

-                  $select={
-                    selectedTag?.includes(skillTagData.id) ||
-                    ((isMain &&
-                      searchFiltersSkillTag?.includes(skillTagData.id)) ||
-                    (isCreate && selectedTag?.includes(skillTagData.id))
-                      ? true
-                      : false)
-                  }
+                  $select={
+                    selectedTag?.includes(skillTagData.id) ||
+                    (isMain && searchFiltersSkillTag?.includes(skillTagData.id)) ||
+                    (isCreate && selectedTag?.includes(skillTagData.id))
+                  }
📝 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
$select={
(isMain &&
selectedTag?.includes(skillTagData.id) ||
((isMain &&
searchFiltersSkillTag?.includes(skillTagData.id)) ||
(isCreate && selectedTag?.includes(skillTagData.id))
? true
: false
: false)
}
$select={
selectedTag?.includes(skillTagData.id) ||
(isMain && searchFiltersSkillTag?.includes(skillTagData.id)) ||
(isCreate && selectedTag?.includes(skillTagData.id))
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 40-44: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🤖 Prompt for AI Agents
In src/components/common/skillTagBox/SkillTagBox.tsx around lines 38 to 45, the
$select expression uses redundant ternary operators and repeated calls to
selectedTag?.includes(skillTagData.id), which complicates the logic. Simplify
the condition by removing the unnecessary '? true : false' and avoid duplicate
includes calls, flattening the condition to improve readability and reduce
complexity.

Comment on lines +55 to +59
const data: CRUDDataType =
state.type === 'skill'
? skillTagsData.filter((list) => list.id === itemId)[0]
: positionTagsData.filter((list) => list.id === itemId)[0];

Copy link

@coderabbitai coderabbitai bot Jun 17, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

data 획득 시 안전성 부족

filter(...)[0] 는 일치 항목이 없을 때 undefined 를 반환합니다. 이후 useEffect(167번째 줄)에서 data.name 접근 시 런타임 오류가 발생할 수 있습니다.
find 로 변경하거나 null-check 를 추가해 주세요.

🤖 Prompt for AI Agents
In src/components/admin/adminTags/AdminTagCRUD.tsx around lines 55 to 59, the
code uses filter(...)[0] to get the data item, which returns undefined if no
match is found, causing runtime errors when accessing data.name later. Replace
filter(...)[0] with find(...) to safely get the first matching item or
undefined, and add a null-check before accessing properties of data to prevent
runtime errors.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

조건문에 data가 있을때 name에 접근하는건데 무슨 문제가 있어?

Copy link

Choose a reason for hiding this comment

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

@YouD0313 맞습니다! 죄송합니다. 코드를 다시 확인해보니 167번째 줄의 useEffect에서 if (data) 조건문으로 data가 존재할 때만 data.name에 접근하고 있네요.

useEffect(() => {
  if (data) {  // 여기서 이미 null check를 하고 있음
    // ... data.name 접근
  }
}, [data, state]);

제가 조건문을 놓치고 분석했습니다. 현재 코드는 이미 안전하게 처리되어 있어서 런타임 오류가 발생하지 않을 것 같습니다. 불필요한 제안이었네요.

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: 2

♻️ Duplicate comments (1)
src/components/common/skillTagBox/SkillTagBox.tsx (1)

39-44: 불필요한 삼항 연산자 및 중복 조건 제거 필요
같은 이슈가 이전 커밋에서도 지적되었습니다.

기존 코드에서는 selectedTag?.includes(...) 조건을 두 번 평가하고, ? true : false 삼항 연산자가 불필요하게 쓰이고 있습니다. 아래와 같이 간결하게 리팩터링하면 가독성과 성능이 모두 개선됩니다.

-                  $select={
-                    selectedTag?.includes(skillTagData.id) ||
-                    ((isMain &&
-                      searchFiltersSkillTag?.includes(skillTagData.id)) ||
-                    (isCreate && selectedTag?.includes(skillTagData.id))
-                      ? true
-                      : false)
-                  }
+                  $select={
+                    selectedTag?.includes(skillTagData.id) ||
+                    (isMain && searchFiltersSkillTag?.includes(skillTagData.id))
+                  }
🧹 Nitpick comments (5)
src/hooks/user/useSearchFilteringTags.ts (1)

19-35: queryKey를 배열 형태로 통일해 캐싱 일관성을 확보하세요

React-Query 문서에서는 queryKey를 배열로 통일해 쓰는 것을 권장합니다.
단일 문자열도 동작은 하지만, 배열(또는 튜플)로 두면 추후 파라미터 추가나 캐시 무효화 전략 변경 시 실수를 줄일 수 있습니다.

-        queryKey: Tag.skillTag,
+        queryKey: [Tag.skillTag],
...
-        queryKey: Tag.positionTag,
+        queryKey: [Tag.positionTag],
...
-        queryKey: Tag.method,
+        queryKey: [Tag.method],
src/components/admin/adminTags/AdminTagCRUD.tsx (4)

55-59: filter(...)[0] 대신 find 사용이 간결·성능 모두에 유리

filter로 전체 배열을 만든 뒤 [0]로 첫 요소를 꺼내면 불필요한 배열이 생성됩니다. find를 쓰면 매칭되는 첫 요소를 즉시 반환하므로 읽기도 쉽고 성능 낭비도 없습니다.

-  ? skillTagsData.filter((list) => list.id === itemId)[0]
-  : positionTagsData.filter((list) => list.id === itemId)[0];
+  ? skillTagsData.find((list) => list.id === itemId)
+  : positionTagsData.find((list) => list.id === itemId);

82-88: 중복 검사 시 some으로 빠른 탈출 가능

duplication.length > 0을 확인하려고 전체 배열을 생성하고 있습니다.
Array.some을 사용하면 매칭되는 순간 루프를 종료해 더 효율적입니다.

-  const duplication =
-    state.type === 'skill'
-      ? skillTagsData.filter((data) => data.name === name)
-      : positionTagsData.filter((data) => data.name === name);
-  if (duplication.length > 0) {
+  const isDuplicated =
+    state.type === 'skill'
+      ? skillTagsData.some((data) => data.name === name)
+      : positionTagsData.some((data) => data.name === name);
+  if (isDuplicated) {

101-106: 수정 시 자기 자신 제외 로직은 👍 그러나 체이닝 중복

두 번의 filter 체이닝보다, 한 번에 조건을 합쳐 가독성을 높일 수 있습니다.

-  .filter((data) => data.id !== itemId)
-  .filter((data) => data.name === name)
+  .filter((data) => data.id !== itemId && data.name === name)

155-161: setTimeout 1 초 지연은 불필요·불안정

파일·텍스트 입력 초기화는 동기적으로 수행해도 무방합니다. 지연을 두면 사용자가 빠르게 연속 조작 시 상태 불일치가 발생할 수 있습니다.

-  setTimeout(() => {
-    if (fileInputRef.current) {
-      fileInputRef.current.value = '';
-    }
-    if (textInputRef.current) {
-      textInputRef.current.value = '';
-    }
-  }, 1000);
+  if (fileInputRef.current) fileInputRef.current.value = '';
+  if (textInputRef.current) textInputRef.current.value = '';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 902ce49 and 086edcc.

📒 Files selected for processing (12)
  • src/api/admin/tag.api.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagCRUD.tsx (1 hunks)
  • src/components/admin/adminTags/AdminTagsBasic.tsx (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionItems.tsx (1 hunks)
  • src/components/common/skillTagBox/SkillTagBox.tsx (2 hunks)
  • src/components/common/skillTagBox/skillTag/skillTagImg/SkillTagImg.tsx (1 hunks)
  • src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx (1 hunks)
  • src/components/user/mypage/myProfile/editProfile/EditProfile.tsx (2 hunks)
  • src/components/user/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx (2 hunks)
  • src/hooks/admin/useAdminTag.ts (1 hunks)
  • src/hooks/queries/keys.ts (1 hunks)
  • src/hooks/user/useSearchFilteringTags.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/components/user/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx
  • src/components/user/mypage/myProfile/editProfile/EditProfile.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/hooks/admin/useAdminTag.ts
  • src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx
  • src/components/admin/adminTags/positions/AdminPositionItems.tsx
  • src/hooks/queries/keys.ts
  • src/components/admin/adminTags/AdminTagsBasic.tsx
  • src/api/admin/tag.api.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/common/skillTagBox/SkillTagBox.tsx (1)
src/hooks/user/useSearchFilteringTags.ts (1)
  • useSearchFilteringTags (11-49)
🪛 Biome (1.9.4)
src/components/common/skillTagBox/SkillTagBox.tsx

[error] 40-44: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test

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: 1

🧹 Nitpick comments (3)
src/components/common/skillTagBox/skillTag/SkillTag.tsx (1)

17-18: updatedAt 미존재 시 캐시 무효화 실패 가능성

updatedAt 값이 undefined(기존 레코드) 일 경우, 쿼리 파라미터 t 가 빠져 이미지 캐싱이 계속 유지될 수 있습니다.
백엔드가 모든 태그에 updatedAt 을 보장하지 않는다면, 기본값을 넣어 안전하게 처리해 주세요.

-        updatedAt={skillTagData.updatedAt}
+        updatedAt={skillTagData.updatedAt ?? ''}
src/components/admin/adminTags/AdminTagCRUD.tsx (2)

53-57: filter(...)[0] 대신 find 사용 권장

첫 번째 일치 항목만 필요하므로 find 가 의도에 더 적합하며, 불필요한 배열 생성을 피할 수 있습니다.

-    state.type === 'skill'
-      ? skillTagsData.filter((list) => list.id === itemId)[0]
-      : positionTagsData.filter((list) => list.id === itemId)[0];
+    state.type === 'skill'
+      ? skillTagsData.find((list) => list.id === itemId)
+      : positionTagsData.find((list) => list.id === itemId);

144-159: 입력 초기화에 setTimeout 사용 불필요

DOM ref 값 초기화를 1 초 지연시킬 이유가 명확하지 않습니다. 즉시 초기화하면 코드 간결성과 사용자 경험이 개선됩니다.

-    onGetItemId(null);
-    setTimeout(() => {
-      if (fileInputRef.current) {
-        fileInputRef.current.value = '';
-      }
-      if (textInputRef.current) {
-        textInputRef.current.value = '';
-      }
-    }, 1000);
+    onGetItemId(null);
+    if (fileInputRef.current) fileInputRef.current.value = '';
+    if (textInputRef.current) textInputRef.current.value = '';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 086edcc and 7d3d928.

📒 Files selected for processing (4)
  • src/components/admin/adminTags/AdminTagCRUD.tsx (1 hunks)
  • src/components/common/skillTagBox/skillTag/SkillTag.tsx (1 hunks)
  • src/components/common/skillTagBox/skillTag/skillTagImg/SkillTagImg.tsx (1 hunks)
  • src/models/tags.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/models/tags.ts
  • src/components/common/skillTagBox/skillTag/skillTagImg/SkillTagImg.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test

Comment on lines 30 to 32
type Skill = Omit<SkillTag, 'createdAt'>;

type CRUDDataType = Skill | PositionTag;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Omit<SkillTag, 'createdAt'> 타입 오류

SkillTag 인터페이스에서 createdAt 필드가 제거되었으므로, 해당 키를 Omit 대상으로 지정하면 컴파일 에러가 발생합니다.

-type Skill = Omit<SkillTag, 'createdAt'>;
+type Skill = SkillTag; // 필요 시 다른 필드로 대체
📝 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
type Skill = Omit<SkillTag, 'createdAt'>;
type CRUDDataType = Skill | PositionTag;
type Skill = SkillTag; // 필요 시 다른 필드로 대체
type CRUDDataType = Skill | PositionTag;
🤖 Prompt for AI Agents
In src/components/admin/adminTags/AdminTagCRUD.tsx around lines 30 to 32, the
use of Omit<SkillTag, 'createdAt'> causes a type error because the SkillTag
interface no longer includes the 'createdAt' field. To fix this, remove the Omit
utility and directly use SkillTag or adjust the type definition to exclude only
existing keys. Ensure the type definition matches the current structure of
SkillTag without referencing non-existent keys.

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

♻️ Duplicate comments (1)
src/components/admin/adminTags/AdminTagCRUD.tsx (1)

53-57: filter(...)[0] 대신 find 사용으로 가독성과 성능 모두 개선

filter(...)[0] 는 조건을 만족하는 모든 요소를 끝까지 순회한 뒤 첫 번째 값만 사용합니다. Array.prototype.find 를 사용하면 일치하는 항목을 만나자마자 순회를 멈추므로 불필요한 반복을 줄일 수 있고 의도도 더 명확하게 드러납니다.

- const data: CRUDDataType =
-   state.type === 'skill'
-     ? skillTagsData.filter((list) => list.id === itemId)[0]
-     : positionTagsData.filter((list) => list.id === itemId)[0];
+ const data: CRUDDataType | undefined =
+   state.type === 'skill'
+     ? skillTagsData.find((list) => list.id === itemId)
+     : positionTagsData.find((list) => list.id === itemId);
🧹 Nitpick comments (2)
src/components/admin/adminTags/AdminTagCRUD.tsx (2)

168-180: useEffect 의존성에 전체 state 객체를 두면 불필요한 리렌더 유발

state 는 부모 컴포넌트에서 매 렌더마다 새로운 객체로 넘어올 가능성이 높습니다. 이 경우 실제 데이터에 변화가 없더라도 effect 가 매번 실행되어 성능 저하가 발생할 수 있습니다. state.type 과 같이 실제로 참조하는 필드만 의존성으로 지정해 주세요.

- }, [data, state]);
+ }, [data, state.type]);

144-159: setTimeout 으로 입력 필드 초기화 → 즉시 처리로 간단화 가능

1 초 딜레이를 둘 명확한 이유가 없다면 타이머를 제거하고 바로 값을 초기화하는 편이 단순‧안정적입니다. 타임아웃은 테스트 플래키(flaky) 원인이 될 수도 있습니다.

- setTimeout(() => {
-   if (fileInputRef.current) {
-     fileInputRef.current.value = '';
-   }
-   if (textInputRef.current) {
-     textInputRef.current.value = '';
-   }
- }, 1000);
+ if (fileInputRef.current) fileInputRef.current.value = '';
+ if (textInputRef.current) textInputRef.current.value = '';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d3d928 and 8d2477a.

📒 Files selected for processing (1)
  • src/components/admin/adminTags/AdminTagCRUD.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test

@YouD0313 YouD0313 merged commit f4ea25e into develop Jun 18, 2025
4 checks passed
@YouD0313 YouD0313 deleted the feat/#336 branch June 18, 2025 15:31
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.

태그 관리 페이지 구현

3 participants