Skip to content

Comments

Api(client): Dashboard category 전체 조회 & 생성 API 연결#73

Merged
constantly-dev merged 10 commits intodevelopfrom
api/#71/dashboard-category-api
Sep 11, 2025
Merged

Api(client): Dashboard category 전체 조회 & 생성 API 연결#73
constantly-dev merged 10 commits intodevelopfrom
api/#71/dashboard-category-api

Conversation

@constantly-dev
Copy link
Member

@constantly-dev constantly-dev commented Sep 11, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • Dashboard category 전체 조회 API 연결
  • Dashboard category 생성 API 연결

⭐ PR Point (To Reviewer)

큰 이슈와 이해할 때 어려운 점이 없는 것 같아서 간단하게만 남깁니다!
Dashboard category 생성 API는 onSuccessinvalidateQueries를 통해 대시보드 전체 카테고리 캐시를 무효화해서 바로 업데이트 돼서 반영되도록 설정해줬어요!

createCategory(newCategoryName, {
      onSuccess: () => {
        handleCategoryChange('');
        queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
        close();
      },
      onError: (error) => {
        console.error('카테고리 생성 실패:', error);
      },
    });

error 관련 UI처리는 이후에 스켈레톤 UI나 로딩바 등을 디자이너 분들게 요청해서 작업하면 좋겠네요! 하나 하나 하기보다 error boundaryroading boundary 등으로 중앙화해주면 좋을 것 같아요 👍

📷 Screenshot

2025-09-11.9.15.34.mov

Summary by CodeRabbit

  • 신규 기능

    • 사이드바가 서버에서 카테고리 목록을 가져와 실시간 이름·미확인 수를 반영합니다.
    • 카테고리 생성 기능이 서버로 전송되어 새 카테고리를 추가할 수 있습니다.
    • 생성 팝업 입력이 사이드바 상태에 즉시 반영됩니다.
  • 리팩터링

    • 정적 카테고리 소스를 제거하고 API 기반 데이터 렌더링으로 전환해 유지보수성과 확장성 향상.

@coderabbitai
Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

사이드바에서 정적 CATEGORIES를 제거하고 서버의 대시보드 카테고리 API를 사용하도록 변경했으며, 이를 위한 Axios 헬퍼, React Query 훅, 타입 정의를 추가하고 PopupPortal에 입력 변경(onChange) 핸들러를 연결했습니다.

Changes

Cohort / File(s) Summary
Sidebar 컴포넌트
apps/client/src/shared/components/sidebar/Sidebar.tsx
정적 CATEGORIES 제거. useGetDashboardCategories()로 카테고리 조회하여 categories?.categories로 렌더링, getCategoryNameCategoryItem prop/key/active 매핑을 API 필드(id,name)에 맞게 변경. newCategoryName 상태, handleCategoryChange, handleCreateCategory 추가. useQueryClient로 생성 성공 시 쿼리 무효화.
API 타입 정의
apps/client/src/shared/components/sidebar/types/api.ts
CategoryDashboardCategoriesResponse 인터페이스 추가 (id: number, name: string, unreadCount: number, categories: Category[]).
Axios API 계층
apps/client/src/shared/components/sidebar/apis/axios.ts
getDashboardCategories() (GET /api/v1/categories/dashboard) 및 postCategory(categoryName: string) (POST /api/v1/categories) 추가.
React Query 훅
apps/client/src/shared/components/sidebar/apis/queries.ts
useGetDashboardCategories() (queryKey ['dashboardCategories']) 및 usePostCategory() mutation 훅 추가; 내부에서 getDashboardCategories/postCategory 사용.
PopupPortal 변경
apps/client/src/shared/components/sidebar/PopupPortal.tsx
PropsonChange?: (value: string) => void 추가 및 컴포넌트 시그니처에 반영. 생성 팝업 경로에서 내부 Popup의 onInputChangeonChange 연결.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor 사용자
  participant Sidebar as Sidebar.tsx
  participant QHook as useGetDashboardCategories
  participant API as getDashboardCategories
  participant Server as /api/v1/categories/dashboard
  Note over Sidebar,QHook: 초기 렌더링 흐름
  사용자->>Sidebar: 사이드바 진입 / 마운트
  Sidebar->>QHook: useQuery(['dashboardCategories']) 호출
  QHook->>API: getDashboardCategories() 요청 (GET)
  API->>Server: GET /api/v1/categories/dashboard
  Server-->>API: 200 OK (DashboardCategoriesResponse)
  API-->>QHook: 응답 데이터 반환
  QHook-->>Sidebar: data.categories
  Sidebar->>Sidebar: categories 매핑 후 렌더링 (id, name, unreadCount)
  Note over Sidebar: PopupPortal에 onChange/onCreateConfirm 전달
  사용자->>Sidebar: 카테고리 생성 입력 및 확인
  Sidebar->>Sidebar: handleCreateCategory -> usePostCategory.mutate
  Sidebar->>QHook: queryClient.invalidateQueries(['dashboardCategories']) onSuccess
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

Possibly related PRs

Suggested labels

🛠️ Feature, frontend

Suggested reviewers

  • jllee000
  • jjangminii

Pre-merge checks (2 passed, 3 warnings)

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning 코드 변경은 [#71] 대시보드 카테고리 전체 조회 및 생성 API 연동 요구사항을 충족합니다(타입 정의, axios 헬퍼, react-query 훅, Sidebar 연동 및 생성 후 쿼리 무효화 구현 확인). 반면 [#25] Progress 컴포넌트 관련 요구사항은 본 PR에서 구현된 파일이나 코드가 없어 해당 이슈의 목적은 미달성 상태입니다. PR이 실제로 다루는 이슈가 [#71]임을 PR 본문에 명확히 표기하고, 만약 [#25]를 닫으려는 의도라면 Progress 컴포넌트 구현을 포함하거나 해당 이슈 링크를 제거해 주세요.
Description Check ⚠️ Warning 현재 PR 설명에는 Related Issues로 'close #71'만 표시되어 있으나 Tasks 섹션에 구현 요약이 비어 있고 PR Point 및 스크린샷 등이 누락되어 템플릿 요구사항을 충분히 충족하지 못합니다. 이로 인해 리뷰어가 변경 범위와 테스트·검증 방법, 리뷰 포인트를 빠르게 파악하기 어렵습니다. Tasks에 파일별·기능별 요약(무엇을 변경했는지, 사용한 훅/엔드포인트, 동작 방식), PR Point에 리뷰 요청사항과 로컬/수동 검증 절차를 추가하고 필요 시 스크린샷을 첨부해 주세요; 또한 PR에 잘못 연결된 이슈(#25 등)가 있으면 링크를 정리하십시오.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 "Api(client): Dashboard category 전체 조회 & 생성 API 연결"은 변경의 핵심인 대시보드 카테고리 조회 및 생성 API 연동을 명확히 요약하고 있으며 Sidebar, apis/axios, apis/queries, types, PopupPortal 등 주요 변경 파일과 일치합니다. 간결하고 스캔하기 쉬운 형식이라 히스토리에서 주요 변경사항을 빠르게 파악할 수 있습니다.
Out of Scope Changes Check ✅ Passed 변경된 파일들은 모두 대시보드 카테고리 API 연동([#71]) 관련으로 보이며 별도의 무관한 기능(예: Progress 컴포넌트)이나 범위를 벗어난 코드 변경은 확인되지 않습니다.

Poem

서버에서 와르르 온 새 카테고리 🥕
토끼가 훌쩍 쿼리 훅에 뛰어들어
이름과 아이디 하나씩 맞아보며
입력을 톡톡, 생성은 삐약삐약
사이드바가 반짝, hop-hop 🎉

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch api/#71/dashboard-category-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 and usage tips.

@github-actions
Copy link

github-actions bot commented Sep 11, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (10)
apps/client/src/shared/components/sidebar/types/api.ts (3)

1-5: 타입 불변성 강화: 필드에 readonly 부여 권장

컴포넌트 트리에서 불필요한 객체 변이를 예방하고 예측 가능성을 높일 수 있습니다.

다음처럼 변경을 제안합니다:

 export interface Category {
-  categoryId: number;
-  categoryName: string;
-  unreadCount: number;
+  readonly categoryId: number;
+  readonly categoryName: string;
+  readonly unreadCount: number;
 }

7-9: 생성 API 연동 대비: 요청/응답 타입 추가 제안

Issue #71 범위(카테고리 생성)까지 고려하면 요청/응답 타입을 함께 정의해두는 편이 좋습니다.

아래 타입을 동일 파일 하단에 추가하는 것을 제안합니다:

 export interface DashboardCategoriesResponse {
   categories: Category[];
 }
+
+export interface CreateDashboardCategoryRequest {
+  categoryName: string;
+}
+
+export interface CreateDashboardCategoryResponse {
+  category: Category;
+}

1-9: 타입 파일 위치 재검토

컴포넌트 하위가 아닌 공유 타입 경로(예: @shared/types/category.ts)로 이동하면 재사용성이 높아집니다.

apps/client/src/shared/components/sidebar/axios.ts (1)

3-6: 응답 타입 명시 + AbortSignal 지원으로 취소 가능 요청 구현

React Query의 signal을 전달하면 탭 전환 시 불필요한 네트워크/상태 업데이트를 줄일 수 있습니다.

적용 예시는 아래와 같습니다:

+import type {
+  DashboardCategoriesResponse,
+  CreateDashboardCategoryRequest,
+  CreateDashboardCategoryResponse,
+} from './types/api';
+
+const DASHBOARD_CATEGORIES_ENDPOINT = '/api/v1/categories/dashboard';
+
-export const getDashboardCategories = async () => {
-  const { data } = await apiRequest.get('/api/v1/categories/dashboard');
-  return data;
-};
+export const getDashboardCategories = async (
+  signal?: AbortSignal
+): Promise<DashboardCategoriesResponse> => {
+  const { data } = await apiRequest.get<DashboardCategoriesResponse>(
+    DASHBOARD_CATEGORIES_ENDPOINT,
+    { signal }
+  );
+  return data;
+};
+
+export const createDashboardCategory = async (
+  body: CreateDashboardCategoryRequest,
+  signal?: AbortSignal
+): Promise<CreateDashboardCategoryResponse> => {
+  const { data } = await apiRequest.post<CreateDashboardCategoryResponse>(
+    DASHBOARD_CATEGORIES_ENDPOINT,
+    body,
+    { signal }
+  );
+  return data;
+};
apps/client/src/shared/components/sidebar/queries.ts (2)

6-13: React Query: abort 신호 전달 및 캐시 파라미터 튜닝

리스트 성격상 신선도 요구가 낮으므로 staleTime/gcTime을 설정하고, signal을 axios로 전달하세요.

아래처럼 변경 제안합니다:

-import { useQuery, UseQueryResult } from '@tanstack/react-query';
-import { getDashboardCategories } from './axios';
+import { useQuery, UseQueryResult, useMutation, useQueryClient } from '@tanstack/react-query';
+import { getDashboardCategories, createDashboardCategory } from './axios';
-import { DashboardCategoriesResponse } from './types/api';
+import { DashboardCategoriesResponse, CreateDashboardCategoryRequest } from './types/api';

 export const useGetDashboardCategories = (): UseQueryResult<
   DashboardCategoriesResponse,
   AxiosError
 > => {
   return useQuery({
     queryKey: ['dashboardCategories'],
-    queryFn: () => getDashboardCategories(),
+    queryFn: ({ signal }) => getDashboardCategories(signal),
+    staleTime: 5 * 60 * 1000,
+    gcTime: 30 * 60 * 1000,
   });
 };
+
+export const useCreateDashboardCategory = () => {
+  const qc = useQueryClient();
+  return useMutation({
+    mutationFn: (body: CreateDashboardCategoryRequest) =>
+      createDashboardCategory(body),
+    onSuccess: () => {
+      qc.invalidateQueries({ queryKey: ['dashboardCategories'] });
+    },
+  });
+};

1-14: Issue #71 범위와의 정합성: 생성 훅 추가 필요

PR 목표에 “카테고리 생성 API 연결”이 포함되어 있습니다. 위 diff처럼 useCreateDashboardCategory 훅을 노출해 Sidebar에서 사용할 수 있게 해주세요.

원하시면 Sidebar까지 연결하는 커밋 패치를 만들어드릴게요.

apps/client/src/shared/components/sidebar/Sidebar.tsx (4)

16-16: 로딩/에러 상태 처리 및 리스트 디폴트값 지정

현재는 data 미존재 시 UI 피드백이 없습니다. UX 보완을 위해 간단한 처리 추가를 권장합니다.

-import { useGetDashboardCategories } from './queries';
+import { useGetDashboardCategories, useCreateDashboardCategory } from './queries';
+import { useMemo } from 'react';

 ...
-  const { data: categories } = useGetDashboardCategories();
+  const { data: categories, isLoading, isError } = useGetDashboardCategories();
+  const list = categories?.categories ?? [];
+  const createCategory = useCreateDashboardCategory();

필요 시 로딩 상태에 스켈레톤/플레이스홀더를 렌더링해 주세요.


39-41: getCategoryName의 선형 탐색 비용 제거

반복 호출 시 O(n) 탐색이 됩니다. 맵 캐싱으로 상수 시간 조회로 바꾸세요.

-  const getCategoryName = (id: number | null) =>
-    categories?.categories.find((category) => category.categoryId === id)
-      ?.categoryName ?? '';
+  const categoryNameMap = useMemo(
+    () => new Map(list.map((c) => [c.categoryId, c.categoryName])),
+    [list]
+  );
+  const getCategoryName = (id: number | null) =>
+    id == null ? '' : categoryNameMap.get(id) ?? '';

79-91: 보호 카테고리(“안 읽은 정보”) 옵션 비활성화

장기 학습에 따르면 첫 기본 카테고리는 삭제 불가여야 합니다. 옵션 버튼을 비활성화하여 실수 삭제를 예방하세요.

-              {categories?.categories?.map((category) => (
+              {list.map((category) => (
                 <CategoryItem
                   key={category.categoryId}
                   id={category.categoryId}
                   label={category.categoryName}
                   active={selectedCategoryId === category.categoryId}
                   onClick={(id) => {
                     closeMenu();
                     selectCategory(id);
                   }}
-                  onOptionsClick={(id, el) => openMenu(id, el)}
+                  onOptionsClick={
+                    category.categoryName === '안 읽은 정보'
+                      ? undefined
+                      : (id, el) => openMenu(id, el)
+                  }
                 />
               ))}

백엔드에서 별도 플래그(예: isDefault/isDeletable)를 내려준다면 해당 플래그 기반으로 분기하도록 개선해 주세요.


122-137: 생성/수정/삭제 TODO 연결

Issue #71 충족을 위해 최소 생성은 본 PR에서 마무리하는 것이 좋아 보입니다.

아래처럼 생성 확인 콜백에서 변이를 호출해 주세요(Modal이 name을 전달한다는 가정):

-        onCreateConfirm={() => {
-          // TODO: 생성 API
-          close();
-        }}
+        onCreateConfirm={(name: string) => {
+          createCategory.mutate(
+            { categoryName: name },
+            { onSuccess: () => close() }
+          );
+        }}

PopupPortal 콜백 시그니처가 다르다면 알려 주세요. 거기에 맞춰 패치를 제공하겠습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dca89e and 4af665c.

📒 Files selected for processing (4)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (3 hunks)
  • apps/client/src/shared/components/sidebar/axios.ts (1 hunks)
  • apps/client/src/shared/components/sidebar/queries.ts (1 hunks)
  • apps/client/src/shared/components/sidebar/types/api.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/shared/components/sidebar/Sidebar.tsx
🧬 Code graph analysis (2)
apps/client/src/shared/components/sidebar/queries.ts (2)
apps/client/src/shared/components/sidebar/types/api.ts (1)
  • DashboardCategoriesResponse (7-9)
apps/client/src/shared/components/sidebar/axios.ts (1)
  • getDashboardCategories (3-6)
apps/client/src/shared/components/sidebar/Sidebar.tsx (2)
apps/client/src/shared/components/sidebar/queries.ts (1)
  • useGetDashboardCategories (6-14)
apps/client/src/shared/components/sidebar/CategoryItem.tsx (1)
  • CategoryItem (13-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook
🔇 Additional comments (1)
apps/client/src/shared/components/sidebar/axios.ts (1)

3-6: 엔드포인트 확인 필요

생성도 동일 엔드포인트(DASHBOARD_CATEGORIES_ENDPOINT)로 POST가 맞는지 백엔드 스펙 확인 부탁드립니다. 경로가 다를 경우 상수만 조정하면 됩니다.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/client/src/shared/components/sidebar/Sidebar.tsx (1)

125-136: 카테고리 생성 API 미연결 — 이슈 #71 목표 미충족

onCreateConfirm에 TODO가 남아 있어 생성 API가 실제로 호출되지 않습니다. 생성 성공 후 목록 무효화도 필요합니다.

아래와 같이 최소 배선 제안드립니다.

  1. API 추가(axios.ts):
export const createDashboardCategory = (name: string) =>
  apiRequest
    .post('/api/v1/categories/dashboard', { name })
    .then((res) => res.data);
  1. 훅 추가(queries.ts):
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createDashboardCategory } from './axios';

export const useCreateDashboardCategory = () => {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: createDashboardCategory,
    onSuccess: () => qc.invalidateQueries({ queryKey: ['dashboardCategories'] }),
  });
};
  1. 본 컴포넌트 적용:
  • 상단: const createCategory = useCreateDashboardCategory();
  • 콜백 수정(이름 인자 전달 필요: PopupPortal의 시그니처를 (name: string)으로 확장):
-        onCreateConfirm={() => {
-          // TODO: 생성 API
-          close();
-        }}
+        onCreateConfirm={(name) => {
+          createCategory.mutate(name, { onSuccess: close });
+        }}

필요 시 PopupPortal의 onCreateConfirm 시그니처 확장을 함께 PR에 포함해 주세요.

🧹 Nitpick comments (5)
apps/client/src/shared/components/sidebar/apis/axios.ts (1)

3-6: 런타임 스키마 검증(zod) 도입 검토

백엔드 응답 스키마 변경 시 조기 탐지에 유용합니다. 과하지 않다면 아래처럼 parse 한 번 거치는 것을 권장합니다.

import { z } from 'zod';

const DashboardCategoriesResponseSchema = z.object({
  categories: z.array(
    z.object({
      categoryId: z.number(),
      categoryName: z.string(),
      unreadCount: z.number(),
    })
  ),
});

// ...
const { data } = await apiRequest.get<DashboardCategoriesResponse>(/* ... */);
return DashboardCategoriesResponseSchema.parse(data);
apps/client/src/shared/components/sidebar/apis/queries.ts (1)

10-13: React Query 취소 신호 전달 + 캐시 전략 기본값 보강

취소 신호를 전달하고, 불필요한 재요청을 줄이기 위해 기본 staleTime을 설정하세요.

-  return useQuery({
-    queryKey: ['dashboardCategories'],
-    queryFn: () => getDashboardCategories(),
-  });
+  return useQuery({
+    queryKey: ['dashboardCategories'],
+    queryFn: ({ signal }) => getDashboardCategories(signal),
+    staleTime: 60_000,
+  });

추가로 queryKey를 상수(예: sidebarKeys.dashboardCategories)로 추출하면 invalidate 시 오타를 줄일 수 있습니다.

apps/client/src/shared/components/sidebar/Sidebar.tsx (3)

16-16: 로딩/에러/빈 상태 처리로 UX 보완

데이터 지연·실패·빈 목록을 구분해 피드백을 주세요.

-  const { data: categories } = useGetDashboardCategories();
+  const { data: categories, isLoading, isError } = useGetDashboardCategories();

예시(추가 코드): isLoading 시 스켈레톤, isError 시 재시도 버튼, 빈 배열 시 안내문구 표시.


38-41: 경계값 빠른 리턴으로 가독성/미세 최적화

null일 때 탐색을 건너뜁니다.

-  const getCategoryName = (id: number | null) =>
-    categories?.categories.find((category) => category.categoryId === id)
-      ?.categoryName ?? '';
+  const getCategoryName = (id: number | null) => {
+    if (id == null) return '';
+    return (
+      categories?.categories.find((c) => c.categoryId === id)?.categoryName ?? ''
+    );
+  };

79-85: 빈 목록/로딩 시 안전 렌더링

map 이전에 안전 가드 또는 대체 UI를 두면 깜빡임을 줄일 수 있습니다. 예: (categories?.categories ?? []).map(...)

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4af665c and 905e7a1.

📒 Files selected for processing (3)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (3 hunks)
  • apps/client/src/shared/components/sidebar/apis/axios.ts (1 hunks)
  • apps/client/src/shared/components/sidebar/apis/queries.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/shared/components/sidebar/Sidebar.tsx
🧬 Code graph analysis (2)
apps/client/src/shared/components/sidebar/apis/queries.ts (2)
apps/client/src/shared/components/sidebar/types/api.ts (1)
  • DashboardCategoriesResponse (7-9)
apps/client/src/shared/components/sidebar/apis/axios.ts (1)
  • getDashboardCategories (3-6)
apps/client/src/shared/components/sidebar/Sidebar.tsx (2)
apps/client/src/shared/components/sidebar/apis/queries.ts (1)
  • useGetDashboardCategories (6-14)
apps/client/src/shared/components/sidebar/CategoryItem.tsx (1)
  • CategoryItem (13-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook
🔇 Additional comments (1)
apps/client/src/shared/components/sidebar/Sidebar.tsx (1)

97-106: 기본 카테고리("안 읽은 정보") 편집/삭제 비활성화 여부 확인 필요

Sidebar에서 OptionsMenuPortal에 전달되는 categoryId가 기본 카테고리인지 판별해 편집/삭제 옵션이 비활성화되도록 구현되어 있는지 확인 필요. (파일: apps/client/src/shared/components/sidebar/Sidebar.tsx:97-106)
자동 스크립트 실행 결과 관련 식별자(안 읽은 정보, UNREAD, DEFAULT_CATEGORY), OptionsMenuPortal 내 비활성화 로직 또는 Category 타입의 기본 구분 필드가 검색되지 않았음 — 레포에서 OptionsMenuPortal.tsx와 Category 타입을 직접 확인할 것.

Comment on lines 1 to 13
import apiRequest from '@shared/apis/axiosInstance';

export const getDashboardCategories = async () => {
const { data } = await apiRequest.get('/api/v1/categories/dashboard');
return data;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

반환 타입 명시 + 요청 취소(AbortSignal) 지원으로 타입 안정성과 UX 개선

응답 타입을 명시하고, React Query의 cancellation 신호를 전달받아 Axios에 전달해주세요.

-import apiRequest from '@shared/apis/axiosInstance';
+import apiRequest from '@shared/apis/axiosInstance';
+import { DashboardCategoriesResponse } from '@shared/components/sidebar/types/api';

-export const getDashboardCategories = async () => {
-  const { data } = await apiRequest.get('/api/v1/categories/dashboard');
-  return data;
-};
+export const getDashboardCategories = async (
+  signal?: AbortSignal
+): Promise<DashboardCategoriesResponse> => {
+  const { data } = await apiRequest.get<DashboardCategoriesResponse>(
+    '/api/v1/categories/dashboard',
+    { signal }
+  );
+  return data;
+};
📝 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
import apiRequest from '@shared/apis/axiosInstance';
export const getDashboardCategories = async () => {
const { data } = await apiRequest.get('/api/v1/categories/dashboard');
return data;
};
import apiRequest from '@shared/apis/axiosInstance';
import { DashboardCategoriesResponse } from '@shared/components/sidebar/types/api';
export const getDashboardCategories = async (
signal?: AbortSignal
): Promise<DashboardCategoriesResponse> => {
const { data } = await apiRequest.get<DashboardCategoriesResponse>(
'/api/v1/categories/dashboard',
{ signal }
);
return data;
};
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/apis/axios.ts around lines 1 to 6,
the exported getDashboardCategories function lacks an explicit return type and
does not accept an AbortSignal for request cancellation; update the signature to
accept an optional AbortSignal parameter and return a typed Promise (e.g.,
Promise<Category[]> or the correct response DTO), pass that signal into the
axios request config (apiRequest.get<YourType>(url, { signal })), and ensure the
function uses the generic type on apiRequest.get and returns the typed data.

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

🧹 Nitpick comments (2)
apps/client/src/shared/components/sidebar/apis/queries.ts (2)

9-17: useQuery 제네릭 지정 + 상수 키 사용

제네릭을 명시하지 않으면 TData/TError가 넓게 추론됩니다. 함수 레퍼런스로 간결화하고 staleTime도 기본값 설정을 제안합니다.

-export const useGetDashboardCategories = (): UseQueryResult<
-  DashboardCategoriesResponse,
-  AxiosError
-> => {
-  return useQuery({
-    queryKey: ['dashboardCategories'],
-    queryFn: () => getDashboardCategories(),
-  });
-};
+export const useGetDashboardCategories = () => {
+  return useQuery<DashboardCategoriesResponse, AxiosError>({
+    queryKey: DASHBOARD_CATEGORIES_QUERY_KEY,
+    queryFn: getDashboardCategories,
+    staleTime: 30_000,
+  });
+};

19-23: API 헬퍼 반환값 일관화(data만 반환)로 DX 개선

GET은 data를 반환하지만 POST는 AxiosResponse 전체를 반환합니다. 일관화를 권장합니다(후속 타입 추론/DevTools 가독성 개선).

apps/client/src/shared/components/sidebar/apis/axios.ts (참고용, 파일 외 수정):

-export const postCategory = async (categoryName: string) => {
-  const response = await apiRequest.post('/api/v1/categories', {
-    categoryName,
-  });
-  return response;
-};
+export const postCategory = async (categoryName: string) => {
+  const { data } = await apiRequest.post(/* <TResponse> 제네릭 지정 가능 */ '/api/v1/categories', {
+    categoryName,
+  });
+  return data;
+};
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 905e7a1 and 5191236.

📒 Files selected for processing (2)
  • apps/client/src/shared/components/sidebar/apis/axios.ts (1 hunks)
  • apps/client/src/shared/components/sidebar/apis/queries.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/client/src/shared/components/sidebar/apis/axios.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🧬 Code graph analysis (1)
apps/client/src/shared/components/sidebar/apis/queries.ts (2)
apps/client/src/shared/components/sidebar/types/api.ts (1)
  • DashboardCategoriesResponse (7-9)
apps/client/src/shared/components/sidebar/apis/axios.ts (2)
  • getDashboardCategories (3-6)
  • postCategory (8-13)

Comment on lines +1 to +7
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
import {
getDashboardCategories,
postCategory,
} from '@shared/components/sidebar/apis/axios';
import { AxiosError } from 'axios';
import { DashboardCategoriesResponse } from '@shared/components/sidebar/types/api';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

쿼리 무효화를 위한 useQueryClient 추가 + 쿼리 키 상수화

생성 후 목록 갱신을 위해 queryClient가 필요합니다. 키를 상수로 노출해 오탈자/중복을 줄이는 것도 권장합니다.

- import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
+ import { useMutation, useQuery, UseQueryResult, useQueryClient } from '@tanstack/react-query';
@@
 import { DashboardCategoriesResponse } from '@shared/components/sidebar/types/api';
+
+export const DASHBOARD_CATEGORIES_QUERY_KEY = ['dashboardCategories'] as const;
📝 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
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
import {
getDashboardCategories,
postCategory,
} from '@shared/components/sidebar/apis/axios';
import { AxiosError } from 'axios';
import { DashboardCategoriesResponse } from '@shared/components/sidebar/types/api';
import { useMutation, useQuery, UseQueryResult, useQueryClient } from '@tanstack/react-query';
import {
getDashboardCategories,
postCategory,
} from '@shared/components/sidebar/apis/axios';
import { AxiosError } from 'axios';
import { DashboardCategoriesResponse } from '@shared/components/sidebar/types/api';
export const DASHBOARD_CATEGORIES_QUERY_KEY = ['dashboardCategories'] as const;
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/apis/queries.ts around lines 1 to
7, add useQueryClient import and expose a constant query key for dashboard
categories; update the category mutation to call
queryClient.invalidateQueries(QUERY_KEYS.dashboardCategories) (or
.refetchQueries) after a successful post so the list refreshes, and export the
QUERY_KEYS object to avoid typos/duplication across code.

Comment on lines 19 to 23
export const usePostCategory = (categoryName: string) => {
return useMutation({
mutationFn: () => postCategory(categoryName),
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

생성 후 목록 자동 반영: invalidateQueries/캐시 업데이트 추가 필요

현재 생성 성공 후 목록이 갱신되지 않습니다. 최소한 invalidateQueries가 필요합니다. 권장안(변수 기반)과 최소 수정안을 함께 드립니다.

권장안(호출부에서 mutate(categoryName)로 전달):

-export const usePostCategory = (categoryName: string) => {
-  return useMutation({
-    mutationFn: () => postCategory(categoryName),
-  });
-};
+export const usePostCategory = () => {
+  const queryClient = useQueryClient();
+  return useMutation<unknown, AxiosError, string>({
+    mutationKey: ['createCategory'],
+    mutationFn: (name) => postCategory(name),
+    onSuccess: () => {
+      queryClient.invalidateQueries({ queryKey: DASHBOARD_CATEGORIES_QUERY_KEY });
+    },
+  });
+};

최소 수정안(현 시그니처 유지):

 export const usePostCategory = (categoryName: string) => {
-  return useMutation({
-    mutationFn: () => postCategory(categoryName),
-  });
+  const queryClient = useQueryClient();
+  return useMutation({
+    mutationFn: () => postCategory(categoryName),
+    onSuccess: () => {
+      queryClient.invalidateQueries({ queryKey: DASHBOARD_CATEGORIES_QUERY_KEY });
+    },
+  });
 };
📝 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
export const usePostCategory = (categoryName: string) => {
return useMutation({
mutationFn: () => postCategory(categoryName),
});
};
export const usePostCategory = (categoryName: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => postCategory(categoryName),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: DASHBOARD_CATEGORIES_QUERY_KEY });
},
});
};
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/apis/queries.ts around lines 19-23,
the mutation does not update the categories list after creating a category; add
cache invalidation: either (recommended) change the mutation to accept the name
at call site (mutationFn: (name: string) => postCategory(name)) and call
queryClient.invalidateQueries with the variable-aware key onSuccess, or
(minimal) keep the current signature and add an onSuccess handler that calls
queryClient.invalidateQueries(['categories']) (or the exact categories query key
used elsewhere) so the list refreshes after creation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/client/src/shared/components/sidebar/PopupPortal.tsx (1)

8-8: 입력 변경 전파 범위 확장 + 편집 팝업 초기값 설정 필요

  • create 경로의 onInputChange={onChange} 연결은 적절합니다. 다만 edit 경로에서도 동일하게 연결하지 않으면 사이드바의 newCategoryName이 갱신되지 않습니다.
  • 편집 시 현재 이름을 placeholder가 아닌 초기값으로 넣는 것이 일반적입니다. 디자인시스템 Popup이 지원하는 prop(defaultValue 또는 value)을 사용해 주세요.

예시(참고용, edit 블록 내부 수정):

<Popup
  type="input"
  title="카테고리 수정하기"
  left="취소"
  right="확인"
  /* 디자인시스템 명세에 따라 defaultValue 또는 value 사용 */
  defaultValue={popup.name}
  onInputChange={onChange}
  onLeftClick={onClose}
  onRightClick={() => onEditConfirm?.(popup.id)}
/>

추가로, prop 명을 onChangeonDraftChange 등으로 구체화하면 의도가 더 분명해집니다(선택).

디자인시스템 Popup의 입력값 제어 prop 명(defaultValue/value)을 확인해 주세요. 지원 prop이 다르면 그에 맞춰 반영이 필요합니다.

Also applies to: 17-17, 34-34

apps/client/src/shared/components/sidebar/Sidebar.tsx (3)

17-19: 임시 콘솔 로그 제거

머지 전 console.log는 제거해 주세요.

적용 diff:

-  console.log(newCategoryName); // 임시 로그

20-20: 가독성: 변수명 명확화(categories.categoriesdashboardCategories.categories)

중첩된 이름이 읽기 어렵습니다. 조회 결과 변수명을 분리하면 명확해집니다.

적용 diff:

-  const { data: categories } = useGetDashboardCategories();
+  const { data: dashboardCategories } = useGetDashboardCategories();
-  const getCategoryName = (id: number | null) =>
-    categories?.categories.find((category) => category.categoryId === id)
-      ?.categoryName ?? '';
+  const getCategoryName = (id: number | null) =>
+    dashboardCategories?.categories.find((category) => category.categoryId === id)
+      ?.categoryName ?? '';
-              {categories?.categories?.map((category) => (
+              {dashboardCategories?.categories?.map((category) => (
                 <CategoryItem
-                  key={category.categoryId}
-                  id={category.categoryId}
-                  label={category.categoryName}
-                  active={selectedCategoryId === category.categoryId}
+                  key={category.categoryId}
+                  id={category.categoryId}
+                  label={category.categoryName}
+                  active={selectedCategoryId === category.categoryId}
                 />
               ))}

Also applies to: 42-45, 87-93


42-45: getCategoryName 선형 탐색 비용 축소(useMemo Map 캐싱)

호출이 잦아질 경우를 대비해 맵으로 캐싱하면 좋습니다.

예시:

const categoryMap = useMemo(() => {
  const map = new Map<number, string>();
  dashboardCategories?.categories.forEach(c => map.set(c.categoryId, c.categoryName));
  return map;
}, [dashboardCategories?.categories]);

const getCategoryName = (id: number | null) => (id == null ? '' : categoryMap.get(id) ?? '');
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5191236 and b8c7630.

📒 Files selected for processing (2)
  • apps/client/src/shared/components/sidebar/PopupPortal.tsx (3 hunks)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/shared/components/sidebar/Sidebar.tsx
🧬 Code graph analysis (1)
apps/client/src/shared/components/sidebar/Sidebar.tsx (2)
apps/client/src/shared/components/sidebar/apis/queries.ts (1)
  • useGetDashboardCategories (9-17)
apps/client/src/shared/components/sidebar/CategoryItem.tsx (1)
  • CategoryItem (13-61)
🔇 Additional comments (2)
apps/client/src/shared/components/sidebar/Sidebar.tsx (2)

87-99: 기본 카테고리(“안 읽은 정보”) 삭제 방지 확인

학습 메모에 따르면 해당 기본 카테고리는 첫번째로 노출되며 삭제가 불가합니다. 메뉴/옵션에서 삭제 항목을 숨기거나 비활성화하는 방어 로직이 필요한지 확인하세요.

옵션:

  • 서버에서 구분 플래그 제공 시 해당 플래그 기준으로 처리
  • 임시로 category.categoryName === '안 읽은 정보' 또는 특정 ID 고정 값으로 처리(서버 확정 전)

13-13: 카테고리 생성 API 즉시 연결 + 입력값 검증(trim 후 빈값 차단)

  • onCreateConfirm에서 newCategoryName.trim() 후 빈값이면 조기 리턴, mutateAsync로 생성 API 호출, 성공 시 상태 초기화 및 close().
  • 서버 페이로드 키(categoryName 등)와 usePostCategory 훅 시그니처(반환값/인자 형태)를 반드시 확인해 페이로드를 맞출 것.

적용 diff:

-import { useGetDashboardCategories } from '@shared/components/sidebar/apis/queries';
+import { useGetDashboardCategories, usePostCategory } from '@shared/components/sidebar/apis/queries';
-        onCreateConfirm={() => {
-          // TODO: 생성 API
-          close();
-        }}
+        onCreateConfirm={async () => {
+          const name = newCategoryName.trim();
+          if (!name) return;
+          await createCategory({ categoryName: name }); // API 스펙에 맞게 payload 키 확인 필요
+          setNewCategoryName('');
+          close();
+        }}

훅 인스턴스(추천 위치: 다른 훅 선언부 인근):

const { mutateAsync: createCategory } = usePostCategory();

@constantly-dev constantly-dev force-pushed the api/#71/dashboard-category-api branch from b8c7630 to 240e16a Compare September 11, 2025 12:06
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 (5)
apps/client/src/shared/components/sidebar/types/api.ts (1)

1-5: 백엔드 스키마와 필드명 동기화 + 불변 속성 권장

API가 unread_count(snake_case)로 내려줄 가능성 등 명명 차이가 있으면 런타임에서 터집니다. 백엔드 스키마와 정확히 일치하는지 확인하고, 클라이언트 모델은 불변으로 두는 걸 권장합니다.

다음처럼 불변 속성으로 두는 것도 좋습니다:

-export interface Category {
-  id: number;
-  name: string;
-  unreadCount: number;
-}
+export interface Category {
+  readonly id: number;
+  readonly name: string;
+  readonly unreadCount: number;
+}

필요 시 zod로 런타임 검증을 붙이면 안전합니다.

apps/client/src/shared/components/sidebar/Sidebar.tsx (4)

13-19: React Query 키 상수화로 오타/중복 방지

'dashboardCategories' 키 문자열을 여기저기 중복 사용 중입니다. queries.ts에 키 상수를 export하고 공용 사용을 권장합니다.

queries.ts 예시(파일 외 변경):

// apps/client/src/shared/components/sidebar/apis/queries.ts
export const QK_DASHBOARD_CATEGORIES = ['dashboardCategories'] as const;

이 파일 적용:

- queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
+ queryClient.invalidateQueries({ queryKey: QK_DASHBOARD_CATEGORIES });

21-26: LGTM + 로딩/에러 UI 처리 제안

상태 관리와 훅 연결은 적절합니다. 다만 isLoading/error 처리로 UX 보강을 추천합니다(스켈레톤/토스트 등).


49-50: 카테고리명 조회 O(1)로 최적화(useMemo Map 캐싱)

매번 find로 O(n)입니다. 맵 캐싱으로 단순/효율 개선을 제안합니다.

추가 import:

-import { useState } from 'react';
+import { useState, useMemo } from 'react';

치환:

-const getCategoryName = (id: number | null) =>
-  categories?.categories.find((category) => category.id === id)?.name ?? '';
+const nameById = useMemo(
+  () => new Map(categories?.categories?.map((c) => [c.id, c.name]) ?? []),
+  [categories]
+);
+const getCategoryName = (id: number | null) =>
+  id == null ? '' : nameById.get(id) ?? '';

151-153: PopupPortal prop 연결은 적절 — 바쁜 상태에서 중복 제출 방지 검토

연결은 좋아 보입니다. 생성 중(disabled 처리 등) 중복 제출 방지도 고려해 주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8c7630 and 382d585.

📒 Files selected for processing (5)
  • apps/client/src/shared/components/sidebar/PopupPortal.tsx (3 hunks)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (5 hunks)
  • apps/client/src/shared/components/sidebar/apis/axios.ts (1 hunks)
  • apps/client/src/shared/components/sidebar/apis/queries.ts (1 hunks)
  • apps/client/src/shared/components/sidebar/types/api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/client/src/shared/components/sidebar/PopupPortal.tsx
  • apps/client/src/shared/components/sidebar/apis/axios.ts
  • apps/client/src/shared/components/sidebar/apis/queries.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/shared/components/sidebar/Sidebar.tsx
🧬 Code graph analysis (1)
apps/client/src/shared/components/sidebar/Sidebar.tsx (3)
apps/client/src/shared/components/sidebar/apis/queries.ts (2)
  • useGetDashboardCategories (9-17)
  • usePostCategory (19-23)
apps/client/src/shared/hooks/useCategoryPopups.ts (1)
  • useCategoryPopups (9-18)
apps/client/src/shared/components/sidebar/CategoryItem.tsx (1)
  • CategoryItem (13-61)
🔇 Additional comments (4)
apps/client/src/shared/components/sidebar/types/api.ts (1)

7-9: 응답 래퍼 타입과 axios 반환값(data / data.data) 일치 여부 확인 필요

  • 현황: apps/client/src/shared/components/sidebar/apis/axios.ts의 getDashboardCategories가 return data.data로 내부 payload를 반환합니다.
  • 조치(짧게): data.data{ categories: Category[] } 형태인지 확인하세요. 아니라면 apps/client/src/shared/components/sidebar/types/api.ts의 타입을 API 응답에 맞게 수정하거나, getDashboardCategories의 반환값(혹은 반환 타입 선언)을 API 구조에 맞게 조정(예: return data 또는 return data.data.categoriesPromise<...> 명시)하세요. 관련 호출: apps/client/src/shared/components/sidebar/apis/queries.ts.
apps/client/src/shared/components/sidebar/Sidebar.tsx (3)

37-39: 팝업 상태 훅 사용 적절

팝업 상태 분리는 깔끔합니다.


51-54: 입력 핸들러는 OK

트림은 제출 시점에서 처리하는 편이 UX에 자연스럽습니다.


105-117: 기본 카테고리(안 읽은 정보) 고정·옵션 비활성화 필요 — API 식별자 확인 요망

장기 학습에 따르면 기본 카테고리는 항상 첫 항목이고 삭제 불가여야 합니다. 현재 apps/client/src/shared/components/sidebar/Sidebar.tsx에서 categories를 그대로 map하고 있어 프런트 보호 로직이 없습니다.

  • 확인 요청: 백엔드가 기본 카테고리를 구분하는 값(name / id / isDefault 중 무엇)을 알려주세요. 코드베이스에서 "안 읽음"은 발견되지만 "안 읽은 정보"는 존재하지 않습니다.
  • 권장 구현(간단): map 전에 기본 카테고리를 맨 앞으로 정렬하고(CategoryItem 렌더링 전), 기본 카테고리의 옵션 버튼은 비활성화 또는 숨기기. 예:
    • 정렬: categories?.categories?.slice().sort((a,b)=> a.isDefault ? -1 : b.isDefault ? 1 : 0).map(...)
    • 옵션 비활성화: onOptionsClick={category.isDefault || category.name === '안 읽은 정보' ? undefined : (id, el) => openMenu(id, el)}

Comment on lines +55 to +66
const handleCreateCategory = () => {
createCategory(newCategoryName, {
onSuccess: () => {
handleCategoryChange('');
queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
close();
},
onError: (error) => {
console.error('카테고리 생성 실패:', error);
},
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

생성 유효성 검증(빈값/중복) + 키 상수화 반영

빈 문자열/공백만 입력, 중복 이름 방지를 최소한으로 막아 주세요. 동시에 앞서 제안한 쿼리 키 상수 사용으로 일관성을 높일 수 있습니다.

-const handleCreateCategory = () => {
-  createCategory(newCategoryName, {
+const handleCreateCategory = () => {
+  const name = newCategoryName.trim();
+  if (!name) return;
+  if (categories?.categories?.some((c) => c.name === name)) {
+    console.warn('중복 카테고리명입니다.');
+    return;
+  }
+  createCategory(name, {
     onSuccess: () => {
-      handleCategoryChange('');
-      queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
+      handleCategoryChange('');
+      // QK_DASHBOARD_CATEGORIES 상수 사용 권장
+      queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
       close();
     },
     onError: (error) => {
       console.error('카테고리 생성 실패:', error);
     },
   });
 };
📝 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
const handleCreateCategory = () => {
createCategory(newCategoryName, {
onSuccess: () => {
handleCategoryChange('');
queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
close();
},
onError: (error) => {
console.error('카테고리 생성 실패:', error);
},
});
};
const handleCreateCategory = () => {
const name = newCategoryName.trim();
if (!name) return;
if (categories?.categories?.some((c) => c.name === name)) {
console.warn('중복 카테고리명입니다.');
return;
}
createCategory(name, {
onSuccess: () => {
handleCategoryChange('');
// QK_DASHBOARD_CATEGORIES 상수 사용 권장
queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
close();
},
onError: (error) => {
console.error('카테고리 생성 실패:', error);
},
});
};
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around lines 55 to 66,
add pre-submit validation and use the shared query key constant: trim
newCategoryName and if the result is empty (''), set a local validation error or
show a user-facing message and return early; check the trimmed name against the
current categories list (case-insensitive) to detect duplicates and similarly
show an error and return; only call createCategory when validation passes; and
replace the literal queryClient.invalidateQueries({ queryKey:
['dashboardCategories'] }) with the centralized constant (e.g.,
QUERY_KEYS.dashboardCategories) for consistency.

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

Labels

api api 연결

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Api] Dashboard 카테고리 전체 조회 & 카테고리 생성 API 연결

1 participant