Skip to content

Commit 09bc3a9

Browse files
committed
feat: [FN-215] 카드셋 수정 폼에 관리자 선택 기능 추가
1 parent ad669df commit 09bc3a9

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

src/features/cardset/components/cardset-update-dialog.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const CardsetUpdateDialog = ({
6767
hashtag: form.hashtag?.map((tag) => tag.name) || [],
6868
category: form.category,
6969
image: form.imageRefId ? String(form.imageRefId) : undefined,
70+
managers: form.managers?.length ? form.managers : undefined,
7071
};
7172

7273
mutate({ groupId, cardsetId, data });
@@ -81,6 +82,7 @@ const CardsetUpdateDialog = ({
8182
? cardset.hashtag.split(",").map((tag) => ({ name: tag.trim() }))
8283
: [],
8384
imageRefId: cardset.imageRefId,
85+
managers: cardset.managers ?? [],
8486
};
8587

8688
return (
@@ -91,6 +93,7 @@ const CardsetUpdateDialog = ({
9193
<DialogTitle>카드셋 수정</DialogTitle>
9294
</DialogHeader>
9395
<CardsetUpdateForm
96+
groupId={groupId}
9497
formId={FORM_ID}
9598
onSubmit={handleSubmit}
9699
defaultValues={defaultValues}

src/features/cardset/components/cardset-update-form.tsx

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,54 @@ import {
1313
import { Input } from "@/shared/components/input";
1414
import { Label } from "@/shared/components/label";
1515
import { uploadImage } from "@/shared/lib/upload-image";
16+
import { useGroupMembers } from "@/domain/members/hooks/use-group-members";
17+
import { MemberSelectDialog } from "@/domain/members/components/member-select-dialog";
18+
import type { GroupMemberInfo } from "@/shared/apis";
1619

17-
import { X } from "lucide-react";
20+
import { UserPlus, X } from "lucide-react";
21+
import { useState } from "react";
1822
import type { ChangeEvent } from "react";
1923
import { useController, useFieldArray, useForm } from "react-hook-form";
2024

25+
const ROLE_LABEL_MAP: Record<GroupMemberInfo["role"], string> = {
26+
OWNER: "소유자",
27+
HEAD_MANAGER: "총괄 매니저",
28+
MANAGER: "매니저",
29+
STAFF: "스태프",
30+
MEMBER: "일반 회원",
31+
};
32+
2133
export type CardsetUpdateFormField = {
2234
name: string;
2335
publicVisible?: boolean;
2436
category: GroupCategory;
2537
hashtag: { name: string }[];
2638
imageRefId?: number;
39+
managers: number[];
2740
};
2841

2942
type Props = {
43+
groupId: number;
3044
onSubmit: (form: CardsetUpdateFormField) => void;
3145
formId?: string;
3246
defaultValues?: Partial<CardsetUpdateFormField>;
3347
};
3448

3549
const CardsetUpdateForm = ({
50+
groupId,
3651
onSubmit,
3752
formId = "cardset-update-form",
3853
defaultValues,
3954
}: Props) => {
55+
const [managerDialogOpen, setManagerDialogOpen] = useState(false);
56+
57+
const { data: members = [] } = useGroupMembers(groupId);
58+
4059
const { control, formState, register, setValue, handleSubmit } =
4160
useForm<CardsetUpdateFormField>({
4261
defaultValues: {
4362
hashtag: [],
63+
managers: [],
4464
...defaultValues,
4565
},
4666
});
@@ -60,11 +80,33 @@ const CardsetUpdateForm = ({
6080
control,
6181
});
6282

83+
const { field: managersField } = useController({
84+
name: "managers",
85+
control,
86+
});
87+
6388
const { fields, append, remove } = useFieldArray<CardsetUpdateFormField>({
6489
name: "hashtag",
6590
control,
6691
});
6792

93+
const selectedManagerIds: number[] = managersField.value ?? [];
94+
const selectedManagers = members.filter((m) =>
95+
selectedManagerIds.includes(m.id)
96+
);
97+
const availableManagers = members
98+
.filter((m) => !selectedManagerIds.includes(m.id))
99+
.map((m) => ({ ...m, subtitle: ROLE_LABEL_MAP[m.role] }));
100+
101+
const addManager = (member: GroupMemberInfo) => {
102+
managersField.onChange([...selectedManagerIds, member.id]);
103+
setManagerDialogOpen(false);
104+
};
105+
106+
const removeManager = (id: number) => {
107+
managersField.onChange(selectedManagerIds.filter((mId) => mId !== id));
108+
};
109+
68110
const handleChangeImage = async (e: ChangeEvent<HTMLInputElement>) => {
69111
const file = e.target.files?.[0];
70112

@@ -157,6 +199,56 @@ const CardsetUpdateForm = ({
157199
</Button>
158200
</div>
159201
</div>
202+
203+
<div>
204+
<Label className="mb-1">카드셋 관리자</Label>
205+
<Description>관리자만 카드셋을 수정할 수 있습니다.</Description>
206+
{selectedManagers.length > 0 && (
207+
<div className="flex flex-wrap gap-2 mb-2">
208+
{selectedManagers.map((m) => (
209+
<div
210+
key={m.id}
211+
className="flex items-center gap-1.5 bg-accent rounded-full pl-1.5 pr-2 py-1 text-sm"
212+
>
213+
<img
214+
src={
215+
m.profile ||
216+
`https://api.dicebear.com/7.x/avataaars/svg?seed=${m.name}`
217+
}
218+
alt={m.name}
219+
className="size-5 rounded-full object-cover"
220+
/>
221+
<span>{m.name}</span>
222+
<button
223+
type="button"
224+
onClick={() => removeManager(m.id)}
225+
className="text-muted-foreground hover:text-foreground transition-colors"
226+
>
227+
<X className="size-3" />
228+
</button>
229+
</div>
230+
))}
231+
</div>
232+
)}
233+
<Button
234+
type="button"
235+
variant="outline"
236+
size="sm"
237+
onClick={() => setManagerDialogOpen(true)}
238+
>
239+
<UserPlus className="size-4 mr-1.5" />
240+
관리자 추가
241+
</Button>
242+
</div>
243+
244+
<MemberSelectDialog
245+
open={managerDialogOpen}
246+
onOpenChange={setManagerDialogOpen}
247+
members={availableManagers}
248+
onSelect={addManager}
249+
title="카드셋 관리자 추가"
250+
description="카드셋을 관리할 멤버를 선택하세요."
251+
/>
160252
</form>
161253
);
162254
};

0 commit comments

Comments
 (0)