-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] 회의실 관리 페이지 #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "119-fe-feat-\uD68C\uC758\uC2E4-\uAD00\uB9AC-\uD398\uC774\uC9C0-\uB370\uC774\uD130-\uB85C\uB529"
[Feat] 회의실 관리 페이지 #162
Changes from all commits
ff8198b
fc7a4bf
dae58fc
beb3c72
d4e0cda
6ed3604
366660e
b304cad
26e31eb
abccec1
49dfaef
0c56ff7
ab50a1f
2fc1a3e
c94af4f
6f91768
86b8fba
830864b
23ccf77
0f4cfb8
1e6c183
e0df074
be51efc
6cddbb9
86ebd6e
5e56199
1cfc374
16a34d1
b20b5fd
289c27b
7230f8b
cd7e374
31e142c
a2df06c
d4559d5
1d23d73
098c8c4
4349fa2
7080e60
a08eb38
6101cb7
5b10aa5
96c025b
dbcde82
c8f93b9
028c023
14b86ab
af5a6b5
c83703e
3556e60
36d99c1
9138564
3e14bc1
39fecd6
73915f1
d19f344
b478133
0a20509
402c5a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,105 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { API_ENDPOINTS } from "@repo/constants"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { type IRoom, type ICategory, type TItemType, type IEquipment } from "@repo/types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { axiosRequester } from "@/lib/axios"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const getAllCategories = async (): Promise<ICategory[]> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<ICategory[]>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "GET", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.CATEGORIES.GET_ALL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const getAllRooms = async (): Promise<IRoom[]> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<IRoom[]>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "GET", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.ITEMS.GET_ALL("room"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const postNewRoom = async (itemType: TItemType, body: Record<string, string>): Promise<IRoom | IEquipment> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<IRoom | IEquipment>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.ITEMS.CREATE_ITEM(itemType), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
AdamSeungheonShin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const patchRoom = async (itemId: string, body: Record<string, string>): Promise<IRoom | IEquipment> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<IRoom | IEquipment>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "PATCH", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.ITEMS.UPDATE_ITEM(itemId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const deleteRoom = async (itemId: string): Promise<string> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<string>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "DELETE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.ITEMS.DELETE_ITEM(itemId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const postNewCategory = async (body: Record<string, string>): Promise<ICategory> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<ICategory>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.CATEGORIES.CREATE_CATEGORY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 에러 처리 및 입력값 검증 추가 필요 카테고리 생성 시 입력값 검증과 구체적인 에러 처리가 필요합니다. export const postNewCategory = async (body: Record<string, string>): Promise<ICategory> => {
+ if (!body.name) {
+ throw new Error('카테고리 이름은 필수입니다');
+ }
const { data } = await axiosRequester<ICategory>({
options: {
method: "POST",
url: API_ENDPOINTS.CATEGORIES.CREATE_CATEGORY,
- headers: {
- "Content-Type": "application/json",
- },
data: body,
},
});
+ if (!data._id || !data.name) {
+ throw new Error('서버 응답이 올바른 카테고리 형식이 아닙니다');
+ }
return data;
};📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const patchCategory = async (categoryId: string, body: Record<string, string>): Promise<ICategory> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<ICategory>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "PATCH", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.CATEGORIES.UPDATE_CATEGORY(categoryId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const deleteCategory = async (categoryId: string): Promise<string> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await axiosRequester<string>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "DELETE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: API_ENDPOINTS.CATEGORIES.DELETE_CATEGORY(categoryId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button, Input } from "@ui/index"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useForm } from "react-hook-form"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { AxiosError } from "axios"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { postNewCategory } from "@/api/meetings"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { notify } from "@/app/store/useToastStore"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { QUERY_KEYS } from "@/lib/queryKey"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function AddCategoryForm(): JSX.Element { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| register, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleSubmit, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reset, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formState: { errors, isSubmitting }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = useForm({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defaultValues: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const queryClient = useQueryClient(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { mutate: addCategory } = useMutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mutationFn: async (payload: Record<string, string>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await postNewCategory(payload); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSuccess: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| notify("success", "카테고리가 추가되었습니다!"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void queryClient.invalidateQueries({ queryKey: QUERY_KEYS.CATEGORIES }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reset(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onError: (error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error instanceof AxiosError && error.response) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| notify("error", String(error.response.data.message)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| notify("error", "알 수 없는 오류가 발생했습니다. 다시 시도해주세요."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 뮤테이션 에러 처리 개선 필요 에러 처리 로직이 너무 일반적입니다. 구체적인 에러 케이스(예: 중복된 카테고리명)에 대한 처리가 필요합니다. 다음과 같이 개선하는 것을 제안합니다: onError: (error) => {
if (error instanceof AxiosError && error.response) {
- notify("error", String(error.response.data.message));
+ switch (error.response.status) {
+ case 409:
+ notify("error", "이미 존재하는 카테고리입니다.");
+ break;
+ case 400:
+ notify("error", "잘못된 카테고리명입니다.");
+ break;
+ default:
+ notify("error", String(error.response.data.message));
+ }
} else {
notify("error", "알 수 없는 오류가 발생했습니다. 다시 시도해주세요.");
}
},📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleSubmitForm = handleSubmit((data) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const payload = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...data, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| itemType: "room", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addCategory(payload); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <form onSubmit={handleSubmitForm} className="flex h-full flex-col justify-between"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h1 className="my-24">카테고리 추가</h1> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="카테고리명" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...register("name", { required: true })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error={errors.name} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={isSubmitting} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+55
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 입력 필드 유효성 검사 강화 필요 현재 required 검사만 수행하고 있습니다. 카테고리명에 대한 추가적인 유효성 검사가 필요합니다. 다음과 같이 개선하는 것을 제안합니다: <Input
placeholder="카테고리명"
- {...register("name", { required: true })}
+ {...register("name", {
+ required: "카테고리명은 필수입니다",
+ minLength: { value: 2, message: "최소 2자 이상 입력해주세요" },
+ maxLength: { value: 20, message: "최대 20자까지 입력 가능합니다" },
+ pattern: {
+ value: /^[가-힣a-zA-Z0-9\s]+$/,
+ message: "특수문자는 사용할 수 없습니다"
+ }
+ })}
error={errors.name}
disabled={isSubmitting}
/>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button variant="Action" type="submit" disabled={isSubmitting} isPending={isSubmitting}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 카테고리 추가 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,19 @@ | ||||||
| "use client"; | ||||||
|
|
||||||
| import { PlusIcon } from "@ui/public"; | ||||||
|
|
||||||
| interface AddItemButtonProps { | ||||||
| onClick: () => void; | ||||||
| } | ||||||
|
|
||||||
| export default function AddItemButton({ onClick }: AddItemButtonProps): JSX.Element { | ||||||
| return ( | ||||||
| <button | ||||||
| className="hover:bg-custom-black/5 flex size-32 cursor-pointer justify-center rounded-full transition-colors duration-300 ease-in-out" | ||||||
| type="button" | ||||||
| onClick={onClick} | ||||||
| > | ||||||
| <PlusIcon width={20} fill="true" /> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
다음과 같이 수정하십시오: - <PlusIcon width={20} fill="true" />
+ <PlusIcon width={20} fill />📝 Committable suggestion
Suggested change
|
||||||
| </button> | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Modal } from "@ui/index"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Dropdown from "@ui/src/components/common/Dropdown"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface CategoryEditDropdownProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isEditing?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClickEdit: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function CategoryEditDropdown({ isEditing, onClickEdit }: CategoryEditDropdownProps): JSX.Element { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Dropdown | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selectedValue={isEditing} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSelect={(value: string | boolean) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value === "수정") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClickEdit(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 타입 안전성 개선 필요
+ type DropdownAction = "수정" | "삭제";
onSelect={(value: string | boolean) => {
- if (value === "수정") {
+ if (value === "수정" as DropdownAction) {
onClickEdit();
}
}}
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Dropdown.Toggle iconType="kebab" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Dropdown.Wrapper className="-left-30 top-56"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Dropdown.Item hoverStyle="purple" value="수정"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 수정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Dropdown.Item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Modal.Trigger> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Dropdown.Item hoverStyle="purple" value="삭제"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 삭제 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Dropdown.Item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Modal.Trigger> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 접근성 및 키보드 네비게이션 개선 필요 드롭다운 메뉴의 접근성이 부족합니다. 키보드 네비게이션과 ARIA 속성을 추가하면 좋을 것 같습니다. - <Dropdown.Toggle iconType="kebab" />
+ <Dropdown.Toggle
+ iconType="kebab"
+ aria-label="카테고리 수정 메뉴"
+ aria-haspopup="true"
+ />
<Dropdown.Wrapper className="-left-30 top-56">
- <Dropdown.Item hoverStyle="purple" value="수정">
+ <Dropdown.Item
+ hoverStyle="purple"
+ value="수정"
+ role="menuitem"
+ tabIndex={0}
+ >
수정
</Dropdown.Item>
<Modal.Trigger>
- <Dropdown.Item hoverStyle="purple" value="삭제">
+ <Dropdown.Item
+ hoverStyle="purple"
+ value="삭제"
+ role="menuitem"
+ tabIndex={0}
+ >
삭제
</Dropdown.Item>
</Modal.Trigger>
</Dropdown.Wrapper>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Dropdown.Wrapper> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Dropdown> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,65 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect } from "react"; | ||
| import { useQuery } from "@tanstack/react-query"; | ||
| import EmptyState from "@ui/src/components/common/EmptyState"; | ||
| import { getAllCategories, getAllRooms } from "@/api/meetings"; | ||
| import LoadingBar from "@/components/common/Skeleton/LoadingBar"; | ||
| import { notify } from "@/app/store/useToastStore"; | ||
| import useMeetingsStore from "../_store/useMeetingsStore"; | ||
| import SidePanel from "./SidePanel"; | ||
| import CategoryListItem from "./CategoryListItem"; | ||
|
|
||
| export default function CategoryList(): JSX.Element { | ||
| const { categories, setCategories, rooms, setRooms } = useMeetingsStore(); | ||
|
|
||
| const { | ||
| data: fetchedCategories, | ||
| isLoading: isCategoriesLoading, | ||
| error: categoriesError, | ||
| } = useQuery({ | ||
| queryKey: ["categories"], | ||
| queryFn: getAllCategories, | ||
| }); | ||
|
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 쿼리 키 상수화 필요 쿼리 키가 문자열 리터럴로 하드코딩되어 있습니다.
- queryKey: ["categories"],
+ queryKey: QUERY_KEYS.CATEGORIES,
|
||
| const { | ||
| data: fetchedRooms, | ||
| isLoading: isRoomsLoading, | ||
| error: roomsError, | ||
| } = useQuery({ queryKey: ["rooms"], queryFn: getAllRooms }); | ||
|
|
||
| useEffect(() => { | ||
| if (fetchedCategories) { | ||
| const roomCategories = fetchedCategories.filter((category) => category.itemType === "room"); | ||
| setCategories(roomCategories); | ||
| } | ||
| }, [fetchedCategories, setCategories]); | ||
|
|
||
| useEffect(() => { | ||
| if (fetchedRooms) { | ||
| setRooms(fetchedRooms); | ||
| } | ||
| }, [fetchedRooms, setRooms]); | ||
|
|
||
| if (categoriesError ?? roomsError) { | ||
| notify("error", "데이터를 불러오는데 실패했습니다."); | ||
| return <div>데이터를 불러오는데 실패했습니다.</div>; | ||
| } | ||
|
|
||
| const categoriesWithRooms = categories.map((category) => ({ | ||
| ...category, | ||
| rooms: rooms.filter((room) => room.category._id === category._id), | ||
| })); | ||
|
|
||
| return ( | ||
| <div> | ||
| <CategoryListItem /> | ||
| </div> | ||
| <> | ||
| {isCategoriesLoading || (isRoomsLoading && <LoadingBar classNames="w-full h-72" />)} | ||
| {!isCategoriesLoading && categories.length === 0 && ( | ||
| <EmptyState message={{ title: "", description: "등록된 카테고리가 없습니다." }} /> | ||
| )} | ||
| {categoriesWithRooms.map((category) => { | ||
| return <CategoryListItem key={category._id} category={category} rooms={category.rooms} />; | ||
| })} | ||
| <SidePanel /> | ||
| </> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.