Skip to content

Commit c2970f7

Browse files
authored
Refactor: 개발 2차 QA 작업 (#119)
* feat: 대시보드 카테고리 수정 및 삭제 api * feat: 토글 버튼 zindex 제어 * feat: 아티클 수정 patch api 연결 * feat: 카테고리 생성하기 * fix: lint 수정 * fix: lint dpfj * feat: 저장 시 모달 닫기 * fix: err lint 수정 * feat: 시간 포맷팅 수정 * feat: 모달 닫기 로직
1 parent eab8501 commit c2970f7

5 files changed

Lines changed: 224 additions & 25 deletions

File tree

apps/client/src/pages/dashboard/apis/axios/category.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,51 @@ export const getModalCategories = async () => {
1818
const { data } = await apiRequest.get('/api/v1/categories/extension');
1919
return data;
2020
};
21+
export const patchCategories = async ({
22+
categoryId,
23+
categoryName,
24+
}: {
25+
categoryId: number;
26+
categoryName: string;
27+
}) => {
28+
const response = await apiRequest.patch(`/api/v1/categories/${categoryId}`, {
29+
categoryName,
30+
});
31+
return response.data;
32+
};
33+
34+
export const deleteCategories = async ({
35+
categoryId,
36+
}: {
37+
categoryId: number;
38+
}) => {
39+
const response = await apiRequest.delete(
40+
`/api/v1/categories/${categoryId}`,
41+
{}
42+
);
43+
return response.data;
44+
};
45+
46+
export const patchArticles = async ({
47+
articleId,
48+
categoryId,
49+
memo,
50+
remindTime,
51+
}: {
52+
articleId: number;
53+
categoryId: number;
54+
memo: string;
55+
remindTime: string;
56+
}) => {
57+
const response = await apiRequest.patch(`/api/v1/articles/${articleId}`, {
58+
categoryId,
59+
memo,
60+
remindTime,
61+
});
62+
return response.data;
63+
};
64+
65+
export const postCategories = async (payload: { categoryName: string }) => {
66+
const response = await apiRequest.post('/api/v1/categories', payload);
67+
return response.data;
68+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {
2+
patchCategories,
3+
deleteCategories,
4+
patchArticles,
5+
postCategories,
6+
} from '../axios/category';
7+
import { useMutation } from '@tanstack/react-query';
8+
9+
export const usePatchCategories = () => {
10+
return useMutation({
11+
mutationFn: patchCategories,
12+
});
13+
};
14+
15+
export const useDeleteCategories = () => {
16+
return useMutation({
17+
mutationFn: deleteCategories,
18+
});
19+
};
20+
export const usePatchArticles = () => {
21+
return useMutation({
22+
mutationFn: patchArticles,
23+
});
24+
};
25+
export const usePostCategories = () => {
26+
return useMutation({
27+
mutationFn: postCategories,
28+
});
29+
};

apps/client/src/shared/components/ui/modalPop/ModalPop.tsx

Lines changed: 140 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import {
22
useGetArticleDetail,
33
useGetModalCategories,
44
} from '@/pages/dashboard/apis/query/article';
5+
import {
6+
usePatchCategories,
7+
useDeleteCategories,
8+
usePatchArticles,
9+
usePostCategories,
10+
} from '@/pages/dashboard/apis/query/category';
511
import { fetchOGData } from '@/shared/utils/ogImage';
612
import { POP_TEXTAREA_MAX_LENGTH } from '@constants/index';
713
import {
@@ -39,16 +45,25 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
3945
const [categoryPopupMode, setCategoryPopupMode] = useState<
4046
'edit' | 'add' | ''
4147
>('');
48+
const [memo, setMemo] = useState('');
4249
const [categories, setCategories] = useState<string[]>([]);
50+
4351
const [selectedCategory, setSelectedCategory] = useState('');
4452
const { data: modalCategories } = useGetModalCategories(); // 모달 카테고리 전체 조회
4553
const { data: articleDetail } = useGetArticleDetail(selectedArticleId); // 아티클 상세 조회
54+
const { mutate: patchCategories } = usePatchCategories(); // 카테고리 수정
55+
const { mutate: deleteCategories } = useDeleteCategories(); // 카테고리 삭제
56+
const { mutate: postCategories } = usePostCategories();
4657
const [matchId, setMatchedId] = useState<number | null>(null);
58+
const [editCategoryIndex, setEditCategoryIndex] = useState<number | null>(
59+
null
60+
);
61+
const { mutate: patchArticle } = usePatchArticles();
62+
const [url, setUrl] = useState('');
4763
const [title, setTitle] = useState('');
4864
const [description, setDescription] = useState('');
4965
const [image, setImage] = useState('');
50-
51-
console.log('matchId', matchId); // TODO: 일단 안 쓰는데 재림쓰 필요할 거 같아서 남김ㅎ
66+
const [articleId, setArticleId] = useState(0);
5267

5368
const handleFieldChange = (
5469
field: 'date' | 'time',
@@ -62,8 +77,10 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
6277
}));
6378
};
6479

65-
const handleCategoryChange = (value: string) => {
80+
const handleCategoryChange = (value: string, index?: number) => {
6681
if (value === 'edit') {
82+
setEditCategoryIndex(index ?? null);
83+
setSelectedCategory(value);
6784
setCategoryPopupMode('edit');
6885
} else if (value === 'create') {
6986
setCategoryPopupMode('add');
@@ -82,8 +99,19 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
8299
return;
83100
}
84101
if (categoryPopupMode === 'add') {
85-
setCategories((prev) => [...prev, newCategory]);
86-
setSelectedCategory(newCategory);
102+
postCategories(
103+
{ categoryName: newCategory },
104+
{
105+
onSuccess: () => {
106+
setCategories((prev) => [...prev, newCategory]);
107+
setSelectedCategory(newCategory);
108+
handlePopupClose();
109+
},
110+
onError: (err: any) => {
111+
console.log(err);
112+
},
113+
}
114+
);
87115
} else if (categoryPopupMode === 'edit') {
88116
setCategories((prev) =>
89117
prev.map((cat) => (cat === selectedCategory ? newCategory : cat))
@@ -93,6 +121,100 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
93121
handlePopupClose();
94122
};
95123

124+
const handlePopupEdit = (newCategory?: string) => {
125+
if (!newCategory) {
126+
return;
127+
}
128+
if (categoryPopupMode === 'edit' && editCategoryIndex !== null) {
129+
setSelectedCategory(newCategory);
130+
handlePopupClose();
131+
132+
patchCategories(
133+
{
134+
categoryId:
135+
modalCategories.data.categories[editCategoryIndex].categoryId,
136+
categoryName: newCategory,
137+
},
138+
{
139+
onSuccess: () => {
140+
setCategories((prev) => [...prev, newCategory]);
141+
setSelectedCategory(newCategory);
142+
location.reload();
143+
handlePopupClose();
144+
},
145+
onError: (err: Error) => {
146+
console.log(err);
147+
},
148+
}
149+
);
150+
}
151+
};
152+
const handlePopupDelete = () => {
153+
setSelectedCategory('');
154+
if (categoryPopupMode === 'edit' && editCategoryIndex !== null) {
155+
handlePopupClose();
156+
deleteCategories(
157+
{
158+
categoryId:
159+
modalCategories.data.categories[editCategoryIndex].categoryId,
160+
},
161+
{
162+
onSuccess: () => {
163+
location.reload();
164+
handlePopupClose();
165+
},
166+
onError: (err: any) => {
167+
console.error(err);
168+
},
169+
}
170+
);
171+
}
172+
};
173+
const handleSave = () => {
174+
const defaultCategoryId =
175+
modalCategories?.data?.categories?.[0]?.categoryId ?? null;
176+
const categoryIdToUse = matchId ?? defaultCategoryId;
177+
const formatTime24 = (raw: string): string => {
178+
const [period, time] = raw.split(' ');
179+
const [hourStr, minuteStr] = time.split(':');
180+
let hour = Number(hourStr);
181+
const minute = Number(minuteStr);
182+
183+
if (period === '오후' && hour !== 12) {
184+
hour += 12;
185+
}
186+
if (period === '오전' && hour === 12) {
187+
hour = 0;
188+
}
189+
190+
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
191+
};
192+
193+
const formattedTime = formatTime24(formState.time);
194+
const formattedDate = formState.date.replace(/\./g, '-');
195+
const remindTime = `${formattedDate}T${formattedTime}:00`;
196+
const remindTimeFormatted = remindTime;
197+
198+
patchArticle(
199+
{
200+
articleId: articleId,
201+
categoryId: categoryIdToUse,
202+
memo: memo,
203+
remindTime: remindTimeFormatted,
204+
},
205+
{
206+
onSuccess: (data) => {
207+
console.log('✅ 저장 성공:', data);
208+
// window.close(); // 최종 배포 시 주석 해제
209+
},
210+
onError: (error: any) => {
211+
const message = error?.response?.data?.message;
212+
alert('❌ 저장 실패:' + message);
213+
},
214+
}
215+
);
216+
};
217+
96218
useEffect(() => {
97219
if (modalCategories) {
98220
const categoryNames: string[] = modalCategories.data.categories.map(
@@ -126,14 +248,18 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
126248
time: formattedTime,
127249
}));
128250
const og = await fetchOGData(articleDetail.url);
251+
const actualUrl = articleDetail?.url ?? '';
252+
const actualArticeId = articleDetail?.articleId ?? '';
253+
setArticleId(actualArticeId);
254+
setUrl(actualUrl || '');
129255
setTitle(og.title || '');
130256
setDescription(og.siteName || '');
131257
setImage(og.image || '');
132258
}
133259
};
134260

135261
test();
136-
}, [articleDetail]);
262+
}, [articleDetail, url]);
137263

138264
return (
139265
<div className="relative flex h-[64.2rem] w-[38.7rem] flex-col items-center justify-between rounded-[1rem] bg-white px-[3rem] py-[3rem]">
@@ -158,8 +284,10 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
158284
<TextArea
159285
defaultValue={articleDetail?.memo}
160286
size="large"
287+
value={memo}
161288
maxLength={POP_TEXTAREA_MAX_LENGTH}
162289
placeholder="메모를 입력하고 도토리를 받아보세요!"
290+
onChange={(e) => setMemo(e.target.value)}
163291
/>
164292
</section>
165293
<section>
@@ -202,25 +330,22 @@ const ModalPop = ({ onClose, onDelete, selectedArticleId }: ModalPopProps) => {
202330
text="저장하기"
203331
size="medium"
204332
type="green"
205-
onClick={onClose}
333+
onClick={() => {
334+
handleSave();
335+
onClose();
336+
}}
206337
/>
207338
</div>
208-
{/* TODO : 하드코딩 데이터 구간 */}
209339

210340
{categoryPopupMode && (
211341
<TextFieldPopup
212342
mode={categoryPopupMode}
213343
value={categoryPopupMode === 'edit' ? selectedCategory : ''}
214344
existingCategories={categories}
215345
onCancel={handlePopupClose}
346+
onEdit={handlePopupEdit}
216347
onConfirm={handlePopupConfirm}
217-
onDelete={() => {
218-
setCategories((prev) =>
219-
prev.filter((cat) => cat !== selectedCategory)
220-
);
221-
setSelectedCategory('');
222-
handlePopupClose();
223-
}}
348+
onDelete={handlePopupDelete}
224349
/>
225350
)}
226351
</div>

apps/extension/src/components/modalPop/ModalPop.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
112112
handlePopupClose();
113113
},
114114
onError: (err: any) => {
115-
const message = err?.response?.data?.message;
116-
alert('❌ 저장 실패:' + err.response?.data.message);
115+
console.log(err);
117116
},
118117
}
119118
);
@@ -139,8 +138,7 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
139138
handlePopupClose();
140139
},
141140
onError: (err: any) => {
142-
const message = err?.response?.data?.message;
143-
alert('❌ 등록 실패:' + err.response?.data.message);
141+
console.log(err);
144142
},
145143
}
146144
);
@@ -161,8 +159,7 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
161159
handlePopupClose();
162160
},
163161
onError: (err: any) => {
164-
const message = err?.response?.data?.message;
165-
alert('❌ 삭제 실패:' + err.response?.data.message);
162+
console.log(err);
166163
},
167164
}
168165
);
@@ -208,11 +205,11 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
208205
{
209206
onSuccess: (data) => {
210207
console.log('✅ 저장 성공:', data);
211-
// window.close(); // 최종 배포 시 주석 해제
208+
window.close();
212209
},
213210
onError: (error: any) => {
214-
const message = error?.response?.data?.message;
215-
alert('❌ 저장 실패:' + message);
211+
console.log(error);
212+
alert('이미 저장된 북마크입니다');
216213
},
217214
}
218215
);

packages/design-system/src/components/category_dropdown/CategoryDropDown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const CategoryDropDown = ({
7272
</div>
7373
{isDropDownOpen && (
7474
<div
75-
className={`absolute mt-[1rem] h-[21.9rem] ${categoryDropdownVariants({ size })} rounded-[1rem] bg-white py-[2rem] pl-[1.4rem] pr-[1.4rem] shadow-[6px_10px_16px_8px_rgba(0,0,0,0.03)]`}
75+
className={`absolute z-10 mt-[1rem] h-[21.9rem] ${categoryDropdownVariants({ size })} rounded-[1rem] bg-white py-[2rem] pl-[1.4rem] pr-[1.4rem] shadow-[6px_10px_16px_8px_rgba(0,0,0,0.03)]`}
7676
>
7777
<div className="flex h-[14.8rem] flex-col gap-[0.5rem] overflow-y-auto overflow-x-hidden">
7878
{categories.map((category, index) => (

0 commit comments

Comments
 (0)