Skip to content
Merged
3 changes: 3 additions & 0 deletions apps/client/src/shared/components/sidebar/PopupPortal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { PopupState } from '@shared/hooks/useCategoryPopups';
interface Props {
popup: PopupState;
onClose: () => void;
onChange?: (value: string) => void;
onCreateConfirm?: () => void;
onEditConfirm?: (id: number, draft?: string) => void;
onDeleteConfirm?: (id: number) => void;
Expand All @@ -13,6 +14,7 @@ interface Props {
export default function PopupPortal({
popup,
onClose,
onChange,
onCreateConfirm,
onEditConfirm,
onDeleteConfirm,
Expand All @@ -29,6 +31,7 @@ export default function PopupPortal({
title="카테고리 추가하기"
left="취소"
right="추가"
onInputChange={onChange}
placeholder="카테고리 제목을 입력해주세요"
onLeftClick={onClose}
onRightClick={() => onCreateConfirm?.()}
Expand Down
66 changes: 40 additions & 26 deletions apps/client/src/shared/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@ import { useSidebarNav } from '@shared/hooks/useSidebarNav';
import { useCategoryPopups } from '@shared/hooks/useCategoryPopups';
import OptionsMenuPortal from './OptionsMenuPortal';
import PopupPortal from './PopupPortal';

const CATEGORIES = [
{ id: 1, label: '일정' },
{ id: 2, label: '공부' },
{ id: 3, label: '운동' },
{ id: 4, label: '취미' },
{ id: 5, label: '기타' },
{ id: 6, label: '기타' },
{ id: 7, label: '기타' },
{ id: 8, label: '기타' },
{ id: 9, label: '기타' },
{ id: 10, label: '기타' },
];
import {
useGetDashboardCategories,
usePostCategory,
} from '@shared/components/sidebar/apis/queries';
import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';

export function Sidebar() {
const [newCategoryName, setNewCategoryName] = useState('');
const queryClient = useQueryClient();

const { data: categories } = useGetDashboardCategories();
const { mutate: createCategory } = usePostCategory();

const {
activeTab,
selectedCategoryId,
Expand All @@ -35,6 +34,9 @@ export function Sidebar() {
setSelectedCategoryId,
} = useSidebarNav();

const { popup, openCreate, openEdit, openDelete, close } =
useCategoryPopups();

const {
state: menu,
open: openMenu,
Expand All @@ -43,11 +45,25 @@ export function Sidebar() {
style,
} = useAnchoredMenu((anchor) => rightOf(anchor, 30));

const { popup, openCreate, openEdit, openDelete, close } =
useCategoryPopups();

const getCategoryName = (id: number | null) =>
CATEGORIES.find((c) => c.id === id)?.label ?? '';
categories?.categories.find((category) => category.id === id)?.name ?? '';

const handleCategoryChange = (name: string) => {
setNewCategoryName(name);
};

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


return (
<aside className="bg-white-bg sticky top-0 h-screen w-[24rem] border-r border-gray-300">
Expand Down Expand Up @@ -86,12 +102,12 @@ export function Sidebar() {
}}
>
<ul className="bg-none">
{CATEGORIES.map((c) => (
{categories?.categories?.map((category) => (
<CategoryItem
key={c.id}
id={c.id}
label={c.label}
active={selectedCategoryId === c.id}
key={category.id}
id={category.id}
label={category.name}
active={selectedCategoryId === category.id}
onClick={(id) => {
closeMenu();
selectCategory(id);
Expand Down Expand Up @@ -132,10 +148,8 @@ export function Sidebar() {
<PopupPortal
popup={popup}
onClose={close}
onCreateConfirm={() => {
// TODO: 생성 API
close();
}}
onChange={handleCategoryChange}
onCreateConfirm={handleCreateCategory}
onEditConfirm={() => {
// TODO: 수정 API
close();
Expand Down
13 changes: 13 additions & 0 deletions apps/client/src/shared/components/sidebar/apis/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import apiRequest from '@shared/apis/axiosInstance';

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

export const postCategory = async (categoryName: string) => {
const response = await apiRequest.post('/api/v1/categories', {
categoryName,
});
return response;
};
23 changes: 23 additions & 0 deletions apps/client/src/shared/components/sidebar/apis/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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';
Comment on lines +1 to +7
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.


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

export const usePostCategory = () => {
return useMutation({
mutationFn: (categoryName: string) => postCategory(categoryName),
});
};
9 changes: 9 additions & 0 deletions apps/client/src/shared/components/sidebar/types/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Category {
id: number;
name: string;
unreadCount: number;
}

export interface DashboardCategoriesResponse {
categories: Category[];
}
Loading