Skip to content

Commit 7156201

Browse files
authored
Merge pull request #122 from hyeonjiroh/feat/#112/mydashboard-page
[fix] #115/할 일 생성 모달 API 연결
2 parents f10cd07 + 8baa685 commit 7156201

4 files changed

Lines changed: 250 additions & 22 deletions

File tree

src/app/(after-login)/dashboard/[dashboardid]/_components/AddTaskButton.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import { useModalStore } from "@/lib/store/useModalStore";
2+
import { useColumnStore } from "@/lib/store/useColumnStore";
23
import Button from "@/components/common/button/Button";
34
import Image from "next/image";
45
import AddIcon from "../../../../../../public/icon/add_icon.svg";
56

6-
export default function AddTaskButton() {
7+
export default function AddTaskButton({ columnId }: { columnId: number }) {
78
const { openModal } = useModalStore();
9+
const { setSelectedColumnId } = useColumnStore();
10+
11+
const openCreateTaskModal = () => {
12+
setSelectedColumnId(columnId);
13+
openModal("createTask");
14+
};
815

916
return (
1017
<Button
1118
variant="whiteGray"
12-
onClick={() => openModal("createTask")}
19+
onClick={openCreateTaskModal}
1320
className="flex gap-3 w-full h-[32px] rounded-md tablet:h-[40px] pc:w-[314px]"
1421
>
1522
<Image

src/app/(after-login)/dashboard/[dashboardid]/_components/Column.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default function Column({ id, title }: DashboardColumn) {
7676
</div>
7777
<div className="flex flex-col gap-[10px] flex-grow min-h-0 tablet:gap-4">
7878
<div>
79-
<AddTaskButton />
79+
<AddTaskButton columnId={id} />
8080
</div>
8181
<div className="flex flex-col gap-[10px] flex-grow min-h-0 overflow-y-auto whitespace-nowrap scrollbar-hide tablet:gap-4">
8282
{items.map((item, index) => (

src/components/modal/create-task/CreateTaskModal.tsx

Lines changed: 181 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,151 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
22
import Image from "next/image";
33
import Modal from "@/components/common/modal/Modal";
44
import TagInput from "@/components/common/input/TagInput";
55
import Input from "@/components/common/input/Input";
6+
import DateInput from "@/components/common/input/DateInput";
67
import ImageInput from "@/components/common/input/ImageInput";
78
import Textarea from "@/components/common/textarea/Textarea";
9+
import UserIcon from "@/components/common/user-icon/UserIcon";
810
import dropdownIcon from "../../../../public/icon/dropdown_icon.svg";
11+
import { fetchDashboardMember } from "@/lib/apis/membersApi";
12+
import { DashboardMember } from "@/lib/types";
13+
import { useDashboardStore } from "@/lib/store/useDashboardStore";
14+
import { useColumnStore } from "@/lib/store/useColumnStore";
15+
import checkItem from "../../../../public/icon/check_icon.svg";
16+
import { createCard } from "@/lib/apis/cardsApi";
917

1018
export default function CreateDashboardModal() {
19+
const { dashboardId } = useDashboardStore();
20+
const { selectedColumnId } = useColumnStore();
21+
const [assignees, setAssignees] = useState<DashboardMember | null>(null);
22+
const [form, setForm] = useState<{ title: string; description: string }>({
23+
title: "",
24+
description: "",
25+
});
26+
const [dueDate, setDueDate] = useState<string>("");
27+
const [tags, setTags] = useState<string[]>([]);
28+
const [imageUrl, setImageUrl] = useState<string | null>(null);
29+
const [items, setItems] = useState<DashboardMember[]>([]);
30+
1131
// 해당 폼이 유효성 검사 후 제출 가능해질 때 해당 state 값이 true가 되도록 하기
1232
const [isFormValid, setIsFormValid] = useState(false);
13-
14-
// TagInput 컴포넌트에 전달할 state
15-
const [tags, setTags] = useState<string[]>([]);
16-
const [selectedManager, setSelectedManager] = useState<string | null>(null);
33+
// const [selectedColumn, setSelectedColumn] = useState(0);
1734
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
1835

19-
const managers = ["김경민", "노현지", "이아름", "이재혁", "임지혜"];
36+
const accessToken = localStorage.getItem("accessToken") ?? "";
37+
38+
const fetchMembers = async () => {
39+
if (!dashboardId) return;
40+
41+
try {
42+
const res = await fetchDashboardMember({
43+
token: accessToken,
44+
id: dashboardId,
45+
page: 1,
46+
size: 20,
47+
});
48+
console.log("Fetched members:", res.members); // 데이터 확인용
49+
50+
setItems(res.members);
51+
} catch (error) {
52+
console.error("Failed to load members:", error);
53+
}
54+
};
55+
56+
useEffect(() => {
57+
if (dashboardId !== undefined && dashboardId !== null) {
58+
fetchMembers();
59+
}
60+
}, [dashboardId]);
61+
62+
// useEffect(() => {}, [items]);
63+
64+
const handleAssigneeSelect = (selectedAssignee: DashboardMember) => {
65+
if (!selectedAssignee) {
66+
console.error("선택된 담당자가 없습니다.");
67+
return;
68+
}
69+
setAssignees(selectedAssignee); // 선택된 담당자 정보를 assignee 상태로 업데이트
70+
setIsDropdownOpen(false);
71+
};
72+
73+
const handleInputChange = (
74+
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
75+
) => {
76+
const { name, value } = e.target;
77+
setForm((prev) => ({
78+
...prev,
79+
[name]: value, // 변경된 값만 업데이트
80+
}));
81+
};
82+
83+
const selected = items.find(
84+
(assignee) => assignees?.userId === assignee.userId
85+
);
86+
87+
useEffect(() => {
88+
const isNotEmpty =
89+
form.title.trim() !== "" &&
90+
form.description.trim() !== "" &&
91+
assignees !== null &&
92+
dueDate.trim() !== "" &&
93+
tags.join() !== "";
94+
setIsFormValid(isNotEmpty);
95+
}, [form.title, form.description, assignees, dueDate, tags]);
2096

2197
// 활성화된 모달 버튼 클릭 시 실행할 함수
22-
const buttonClick = () => {
23-
alert("Hi"); // 이 부분 바꿔주시면 됩니다
24-
setIsFormValid(false); // 이 코드는 배포할 때 문제가 있어서 임시로 넣어놓은 코드라 삭제하시면 됩니다
98+
const buttonClick = async () => {
99+
if (!dashboardId) return;
100+
if (!selectedColumnId) return;
101+
102+
// 생성된 데이터 값 확인용 입니당
103+
console.log("카드 생성 데이터:", {
104+
dashboardId,
105+
selectedColumnId,
106+
assigneeUserId: assignees?.userId,
107+
title: form.title,
108+
description: form.description,
109+
dueDate,
110+
tags,
111+
imageUrl,
112+
});
113+
114+
try {
115+
await createCard({
116+
token: accessToken,
117+
assigneeUserId: Number(assignees?.userId),
118+
dashboardId: Number(dashboardId),
119+
columnId: selectedColumnId,
120+
title: form.title,
121+
description: form.description,
122+
dueDate,
123+
tags,
124+
imageUrl,
125+
});
126+
} catch (error) {
127+
console.error("카드 생성 실패:", error);
128+
}
129+
130+
window.location.reload();
25131
};
26132

133+
// dashboardId가 number로 잘나오는지 확인하려고 작성한 코드입니당
134+
useEffect(() => {
135+
console.log("🔍 dashboardId 값:", dashboardId);
136+
console.log("🔍 dashboardId 타입:", typeof dashboardId);
137+
}, [dashboardId]);
138+
139+
if (!selectedColumnId) return;
140+
27141
return (
28142
<Modal
29143
button={{
30144
onConfirm: buttonClick,
31145
disabled: !isFormValid,
32146
}}
33147
>
34-
<div className="flex flex-col w-[271px] gap-6 tablet:w-[520px] tablet:gap-8">
148+
<div className="relative flex flex-col w-[271px] gap-6 tablet:w-[520px] tablet:gap-8">
35149
<div className="relative flex flex-col">
36150
<label className="block mb-2.5 text-lg font-medium text-gray-800 tablet:mb-2 tablet:text-2lg">
37151
담당자
@@ -41,40 +155,88 @@ export default function CreateDashboardModal() {
41155
onClick={() => setIsDropdownOpen((prev) => !prev)}
42156
>
43157
<div className="flex justify-between">
44-
<span>{selectedManager || "이름을 입력해 주세요"}</span>
158+
{selected ? (
159+
<div className="flex items-center gap-[6px]">
160+
<UserIcon
161+
name={selected.nickname}
162+
img={selected.profileImageUrl}
163+
size="sm"
164+
/>
165+
<div className="font-normal text-lg text-gray-800">
166+
{selected.nickname}
167+
</div>
168+
</div>
169+
) : (
170+
<span className="text-gray-500">담당자 선택</span>
171+
)}
45172
<Image src={dropdownIcon} width={8} height={8} alt="" />
46173
</div>
47174
</button>
48175
</div>
49176

50177
{isDropdownOpen && (
51-
<ul className="border border-gray-400 rounded-md">
52-
{managers.map((manager) => (
178+
<ul className="border border-gray-400 rounded-md w-full tablet:w-[520px] absolute top-[89px] z-10 bg-white">
179+
{items.map((assignee) => (
53180
<li
54-
key={manager}
181+
key={assignee.userId}
55182
className="px-4 py-2 hover:text-violet hover:bg-violet-8 cursor-pointer"
56183
onClick={() => {
57-
setSelectedManager(manager);
58-
setIsDropdownOpen(false);
184+
handleAssigneeSelect(assignee);
59185
}}
60186
>
61-
{manager}
187+
<div className="flex gap-2 items-center">
188+
<div className="relative invert brightness-75 w-4 h-3">
189+
{assignees?.userId === assignee.userId && (
190+
<Image
191+
src={checkItem}
192+
fill
193+
alt="checkIcon"
194+
className="mr-2"
195+
/>
196+
)}
197+
</div>
198+
<div className="flex items-center gap-1.5">
199+
<UserIcon
200+
name={assignee.nickname}
201+
img={assignee.profileImageUrl}
202+
size="sm"
203+
/>
204+
{assignee.nickname}
205+
</div>
206+
</div>
62207
</li>
63208
))}
64209
</ul>
65210
)}
66-
<Input label="제목" placeholder="제목을 입력해 주세요" required />
211+
<Input
212+
label="제목"
213+
name="title"
214+
value={form.title}
215+
onChange={handleInputChange}
216+
placeholder="제목을 입력해 주세요"
217+
required
218+
/>
67219
<Textarea
68220
label="설명"
221+
name="description"
222+
value={form.description}
223+
onChange={handleInputChange}
69224
placeholder="설명을 입력해 주세요"
70225
required
71226
spanClassName="ml-0.5"
72227
containerClassName="gap-2.5 tablet:gap-2"
73228
labelClassName="font-medium text-lg tablet:text-2lg"
74229
textareaClassName="font-normal placeholder:text-gray-500 rounded-md text-md h-[84px] px-4 py-[13px] tablet:rounded-lg tablet:h-[126px] tablet:py-[15px] tablet:text-lg"
75230
/>
231+
<DateInput value={dueDate} onChange={setDueDate} />
76232
<TagInput label="태그" tags={tags} setTags={setTags} />
77-
<ImageInput label="이미지" variant="task" columnId={46355} />
233+
<ImageInput
234+
label="이미지"
235+
variant="task"
236+
columnId={selectedColumnId}
237+
token={accessToken}
238+
onImageUrlChange={(url) => setImageUrl(url)}
239+
/>
78240
</div>
79241
</Modal>
80242
);

src/lib/apis/cardsApi.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,62 @@ export async function deleteCard({
104104

105105
return null;
106106
}
107+
108+
export async function createCard({
109+
token,
110+
assigneeUserId,
111+
dashboardId,
112+
columnId,
113+
title,
114+
description,
115+
dueDate,
116+
tags,
117+
imageUrl,
118+
}: {
119+
token: string;
120+
assigneeUserId: number;
121+
dashboardId: number;
122+
columnId: number;
123+
title: string;
124+
description: string;
125+
dueDate: string;
126+
tags: string[];
127+
imageUrl: string | null;
128+
}) {
129+
type CreateCardPayload = {
130+
assigneeUserId: number;
131+
dashboardId: number;
132+
columnId: number;
133+
title: string;
134+
description: string;
135+
dueDate: string;
136+
tags: string[];
137+
imageUrl?: string;
138+
};
139+
140+
const payload: CreateCardPayload = {
141+
assigneeUserId,
142+
dashboardId,
143+
columnId,
144+
title,
145+
description,
146+
dueDate,
147+
tags,
148+
};
149+
150+
if (imageUrl) {
151+
payload.imageUrl = imageUrl;
152+
}
153+
154+
const res = await fetch(`${BASE_URL}/cards`, {
155+
method: "POST",
156+
headers: {
157+
accept: "application/json",
158+
Authorization: `Bearer ${token}`,
159+
"Content-Type": "application/json",
160+
},
161+
body: JSON.stringify(payload),
162+
});
163+
164+
return await res.json();
165+
}

0 commit comments

Comments
 (0)