Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit f73a186

Browse files
authored
fix: 카테고리 관련 수정 (0405) (#67) #patch
* 카테고리칩 클릭가능하면 마우스 포인터 되도록 * 카테고리 바텀시트 불필요하게 열리는 현상 방지 * 포커스되는 경우 Outline 생성되는 것 제거 시도 * 서버에서 받은 에러 메시지 표시 * 카테고리 칩에는 아예 텍스트 선택 안되게 * 카테고리 너무 긴경우 말줄임 처리 * 삭제하기 버튼 색상 변경
1 parent f9c0ba0 commit f73a186

File tree

7 files changed

+92
-57
lines changed

7 files changed

+92
-57
lines changed

src/renderer/features/category/ui/category-chip.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Category } from '@/entities/category';
22
import { Icon } from '@/shared/ui';
3-
import { getCategoryIconName } from '@/shared/utils';
3+
import { cn, getCategoryIconName } from '@/shared/utils';
44

55
type CategoryChipProps = {
66
category: Category;
@@ -10,7 +10,10 @@ type CategoryChipProps = {
1010
export const CategoryChip = ({ category, onClick }: CategoryChipProps) => {
1111
return (
1212
<div
13-
className="subBody-sb flex min-w-[80px] gap-sm rounded-xs bg-background-secondary p-md text-text-tertiary"
13+
className={cn(
14+
'subBody-sb flex min-w-[80px] select-none gap-sm rounded-xs bg-background-secondary p-md text-text-tertiary',
15+
onClick && 'cursor-pointer',
16+
)}
1417
onClick={onClick}
1518
>
1619
<Icon name={getCategoryIconName(category.iconType)} size="sm" />

src/renderer/features/category/ui/change-category-drawer.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,23 @@ import {
1717
Button,
1818
DrawerFooter,
1919
Dialog,
20-
// useToast,
20+
useToast,
2121
} from '@/shared/ui';
2222
import { cn, getCategoryIconName } from '@/shared/utils';
23+
import { isErrorResponse } from '@/shared/utils/error';
2324

2425
type ChangeCategoryDrawerProps = {
2526
open: boolean;
2627
onOpenChange: (isOpen: boolean) => void;
28+
onClose: () => void;
2729
};
2830
type ChangeCategoryDrawerMode = 'select' | 'edit' | 'delete';
2931

30-
export const ChangeCategoryDrawer = ({ open, onOpenChange }: ChangeCategoryDrawerProps) => {
32+
export const ChangeCategoryDrawer = ({
33+
open,
34+
onOpenChange,
35+
onClose,
36+
}: ChangeCategoryDrawerProps) => {
3137
const [mode, setMode] = useState<ChangeCategoryDrawerMode>('select');
3238

3339
useEffect(() => {
@@ -37,7 +43,7 @@ export const ChangeCategoryDrawer = ({ open, onOpenChange }: ChangeCategoryDrawe
3743
}, [open]);
3844

3945
return (
40-
<Drawer open={open} onOpenChange={onOpenChange}>
46+
<Drawer open={open} onOpenChange={onOpenChange} onClose={onClose}>
4147
<DrawerContent>
4248
{mode === 'select' && (
4349
<SelectModeDrawerContent setMode={setMode} onClose={() => onOpenChange(false)} />
@@ -144,7 +150,7 @@ const SelectModeDrawerContent = ({ setMode, onClose }: SelectModeDrawerContentPr
144150
className="flex w-full flex-row items-center justify-start gap-2 p-5"
145151
>
146152
<Icon name={getCategoryIconName(category.iconType)} size="sm" />
147-
<span className="body-sb text-text-primary">{category.title}</span>
153+
<span className="body-sb truncate text-text-primary">{category.title}</span>
148154
</SelectGroupItem>
149155
))}
150156
</SelectGroup>
@@ -201,7 +207,10 @@ const EditModeDrawerContent = ({ setMode }: EditModeDrawerContentProps) => {
201207
>
202208
<Icon name={disabled ? 'lock' : getCategoryIconName(category.iconType)} size="sm" />
203209
<span
204-
className={cn('body-sb', disabled ? 'text-text-disabled' : 'text-text-primary')}
210+
className={cn(
211+
'body-sb truncate',
212+
disabled ? 'text-text-disabled' : 'text-text-primary',
213+
)}
205214
>
206215
{category.title}
207216
</span>
@@ -225,6 +234,7 @@ const DeleteModeDrawerContent = ({ setMode }: DeleteModeDrawerContentProps) => {
225234

226235
const { mutateAsync: deleteCategories } = useDeleteCategories();
227236
const confirmDeleteDialogProps = useDisclosure();
237+
const { toast } = useToast();
228238

229239
const [selectedCategoryIds, setSelectedCategoryIds] = useState<string[]>([]);
230240
const isDisabledCompleteButton = selectedCategoryIds.length === 0;
@@ -233,9 +243,16 @@ const DeleteModeDrawerContent = ({ setMode }: DeleteModeDrawerContentProps) => {
233243
confirmDeleteDialogProps.setIsOpen(true);
234244
};
235245
const handleDeleteCategories = async () => {
236-
// TODO: api 호출 실패시 에러 처리
237-
await deleteCategories({ body: { no: selectedCategoryIds.map(Number) } });
238-
setMode('select');
246+
try {
247+
await deleteCategories({ body: { no: selectedCategoryIds.map(Number) } });
248+
setMode('select');
249+
} catch (error) {
250+
const errorMessage = isErrorResponse(error)
251+
? error.message
252+
: '알 수 없는 오류가 발생했습니다.';
253+
// TODO: 토스트 아이콘 적용?
254+
toast({ message: errorMessage });
255+
}
239256
};
240257

241258
return (
@@ -280,7 +297,10 @@ const DeleteModeDrawerContent = ({ setMode }: DeleteModeDrawerContentProps) => {
280297
size="md"
281298
/>
282299
<span
283-
className={cn('body-sb', disabled ? 'text-text-disabled' : 'text-text-primary')}
300+
className={cn(
301+
'body-sb truncate',
302+
disabled ? 'text-text-disabled' : 'text-text-primary',
303+
)}
284304
>
285305
{category.title}
286306
</span>
@@ -292,6 +312,7 @@ const DeleteModeDrawerContent = ({ setMode }: DeleteModeDrawerContentProps) => {
292312
<DrawerFooter>
293313
<Button
294314
className="w-full"
315+
variant="secondary"
295316
disabled={isDisabledCompleteButton}
296317
onClick={handleClickDeleteButton}
297318
>

src/renderer/pages/category.tsx

Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@ import { useEffect, useState } from 'react';
33
import { useNavigate, useParams } from 'react-router-dom';
44

55
import { CategoryIconType } from '@/entities/category';
6-
import {
7-
useUpdateCategory,
8-
useCategory,
9-
useCreateCategory,
10-
useCategories,
11-
} from '@/features/category';
6+
import { useUpdateCategory, useCategory, useCreateCategory } from '@/features/category';
127
import { PATH } from '@/shared/constants';
138
import { useDisclosure } from '@/shared/hooks';
149
import {
@@ -21,6 +16,7 @@ import {
2116
SimpleLayout,
2217
} from '@/shared/ui';
2318
import { CategoryIconTypeMap, cn } from '@/shared/utils';
19+
import { isErrorResponse } from '@/shared/utils/error';
2420

2521
const CATEGORY_NAME_MAX_LENGTH = 10;
2622

@@ -32,7 +28,6 @@ const CategoryPage = () => {
3228
const drawerProps = useDisclosure();
3329

3430
const { data: category } = useCategory(categoryNo);
35-
const { data: categories } = useCategories();
3631
const { mutateAsync: createCategory, isPending: isCreating } = useCreateCategory();
3732
const { mutateAsync: updateCategory, isPending: isUpdating } = useUpdateCategory();
3833
const isPending = isCreating || isUpdating;
@@ -59,52 +54,46 @@ const CategoryPage = () => {
5954
}
6055
}, [category]);
6156

62-
useEffect(() => {
63-
const isTooLong = trimmedCategoryName.length > CATEGORY_NAME_MAX_LENGTH;
64-
const isDuplicated = isPending
65-
? // @note: 낙관적 업데이트를 하기 때문에 카테고리 생성 중에 중복 에러 메시지 보이지 않도록 함
66-
false
67-
: categories?.some(
68-
(category) => category.title === trimmedCategoryName && category.no !== categoryNo,
69-
);
70-
if (isTooLong) {
71-
return setErrorMessage(`최대 ${CATEGORY_NAME_MAX_LENGTH}글자까지 입력할 수 있어요.`);
72-
}
73-
if (isDuplicated) {
74-
return setErrorMessage('이미 존재하는 카테고리예요.');
57+
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
58+
setTypedCategoryName(e.target.value);
59+
if (e.target.value.length > CATEGORY_NAME_MAX_LENGTH) {
60+
setErrorMessage(`카테고리 이름은 ${CATEGORY_NAME_MAX_LENGTH}자 이내로 입력해주세요.`);
61+
} else {
62+
setErrorMessage(null);
7563
}
76-
setErrorMessage(null);
77-
}, [trimmedCategoryName, categories, categoryNo, isPending]);
78-
64+
};
7965
const handleClickChangeIconButton = () => {
8066
drawerProps.setIsOpen(true);
8167
};
8268
const handleClickBackButton = () => {
8369
navigate(PATH.POMODORO, { state: { openChangeCategoryDrawer: true } });
8470
};
8571
const handleClickCompleteButton = async () => {
86-
// TODO:
87-
// - 카테고리 api 호출 에러 처리
88-
// - 이미 존재하는 카테고리인 경우
89-
// - 카테고리 이름이 너무 긴 경우
90-
91-
if (isEditMode) {
92-
await updateCategory({
93-
no: categoryNo,
94-
body: {
95-
title: trimmedCategoryName,
96-
iconType: selectedCategoryIconType,
97-
},
98-
});
99-
} else {
100-
await createCategory({
101-
body: {
102-
title: trimmedCategoryName,
103-
iconType: selectedCategoryIconType,
104-
},
105-
});
72+
try {
73+
if (isEditMode) {
74+
await updateCategory({
75+
no: categoryNo,
76+
body: {
77+
title: trimmedCategoryName,
78+
iconType: selectedCategoryIconType,
79+
},
80+
});
81+
} else {
82+
await createCategory({
83+
body: {
84+
title: trimmedCategoryName,
85+
iconType: selectedCategoryIconType,
86+
},
87+
});
88+
}
89+
navigate(PATH.POMODORO, { state: { openChangeCategoryDrawer: true } });
90+
} catch (error) {
91+
const errorMessage = isErrorResponse(error)
92+
? error.message
93+
: '알 수 없는 오류가 발생했습니다.';
94+
95+
setErrorMessage(errorMessage);
10696
}
107-
navigate(PATH.POMODORO, { state: { openChangeCategoryDrawer: true } });
10897
};
10998

11099
return (
@@ -130,7 +119,7 @@ const CategoryPage = () => {
130119

131120
<input
132121
value={typedCategoryName}
133-
onChange={(e) => setTypedCategoryName(e.target.value)}
122+
onChange={handleInput}
134123
type="text"
135124
placeholder="카테고리 이름"
136125
className="body-sb mt-6 w-full rounded-sm bg-white p-lg text-text-primary caret-text-accent-1 placeholder:text-text-disabled"

src/renderer/shared/api/httpClient.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const __fetch = async <T = unknown, D = unknown>(
4242
});
4343

4444
if (!response.ok) {
45+
if (response.headers.get('Content-type')?.includes('application/json')) {
46+
throw await response.json();
47+
}
4548
const error = new Error();
4649
const text = await response.text();
4750
error.message = text;

src/renderer/shared/ui/drawer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const DrawerContent = React.forwardRef<
3939
<DrawerPrimitive.Content
4040
ref={ref}
4141
className={cn(
42-
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[24px] border bg-background-primary',
42+
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[24px] border bg-background-primary outline-none',
4343
className,
4444
)}
4545
{...props}

src/renderer/shared/utils/error.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type ErrorResponse = {
2+
type: string;
3+
message: string;
4+
errorTraceId: string;
5+
};
6+
7+
export const isErrorResponse = (error: unknown): error is ErrorResponse => {
8+
if (typeof error !== 'object' || error == null) {
9+
return false;
10+
}
11+
return 'type' in error && 'message' in error && 'errorTraceId' in error;
12+
};

src/renderer/widgets/pomodoro/ui/home-screen.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ export const HomeScreen = ({
7373
}
7474
}, [openChangeCategoryDrawer]);
7575

76+
const handleCloseDrawer = () => {
77+
if (openChangeCategoryDrawer) {
78+
location.state = { openChangeCategoryDrawer: false };
79+
}
80+
};
81+
7682
const showRandomMessage = () => {
7783
const messages = getTooltipMessages(user?.cat?.type);
7884
const randomIndex = Math.floor(Math.random() * messages.length);
@@ -146,6 +152,7 @@ export const HomeScreen = ({
146152
<ChangeCategoryDrawer
147153
open={changeCategoryDrawerProps.isOpen}
148154
onOpenChange={changeCategoryDrawerProps.setIsOpen}
155+
onClose={handleCloseDrawer}
149156
/>
150157

151158
{showGuide && (

0 commit comments

Comments
 (0)