Skip to content

Commit dee4f2c

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into card-table
2 parents 2b1f48c + 7e4d3b2 commit dee4f2c

File tree

9 files changed

+155
-75
lines changed

9 files changed

+155
-75
lines changed

src/api/card.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,25 +83,29 @@ export const getDashboardMembers = async ({
8383
};
8484

8585
/** 4. 카드 수정 */
86-
export const updateCard = async ({
87-
cardId,
88-
columnId,
89-
assigneeUserId,
90-
title,
91-
description,
92-
dueDate,
93-
tags,
94-
imageUrl,
95-
}: {
96-
cardId: number;
97-
columnId: number;
98-
assigneeUserId: number;
99-
title: string;
100-
description: string;
101-
dueDate: string;
102-
tags: string[];
103-
imageUrl?: string;
104-
}) => {
86+
export const updateCard = async (
87+
id: number,
88+
data: Partial<CardDetailType>,
89+
{
90+
cardId,
91+
columnId,
92+
assigneeUserId,
93+
title,
94+
description,
95+
dueDate,
96+
tags,
97+
imageUrl,
98+
}: {
99+
cardId: number;
100+
columnId: number;
101+
assigneeUserId: number;
102+
title: string;
103+
description: string;
104+
dueDate: string;
105+
tags: string[];
106+
imageUrl?: string;
107+
}
108+
) => {
105109
const response = await axiosInstance.put(apiRoutes.cardDetail(cardId), {
106110
columnId,
107111
assigneeUserId,
@@ -155,3 +159,11 @@ export const deleteCard = async (cardId: number) => {
155159
const response = await axiosInstance.delete(url);
156160
return response.data;
157161
};
162+
//카드 수정저장
163+
export const EditCard = async (
164+
cardId: number,
165+
data: Partial<CardDetailType>
166+
) => {
167+
const response = await axiosInstance.put(apiRoutes.cardDetail(cardId), data);
168+
return response.data;
169+
};

src/api/columns.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const createColumn = async ({
2121

2222
// 칼럼 목록 조회
2323
export const getColumns = async ({ dashboardId }: { dashboardId: number }) => {
24-
const res = await axiosInstance.get(apiRoutes.columns(), {
24+
const res = await axiosInstance.get(apiRoutes.columns(TEAM_ID), {
2525
params: {
2626
dashboardId,
2727
},
@@ -49,3 +49,20 @@ export const deleteColumn = async ({ columnId }: { columnId: number }) => {
4949
const res = await axiosInstance.delete(apiRoutes.columnDetail(columnId));
5050
return res;
5151
};
52+
53+
export const getColumn = async ({
54+
dashboardId,
55+
}: {
56+
dashboardId: number;
57+
columnId: number;
58+
}) => {
59+
const res = await axiosInstance.get(apiRoutes.columns(TEAM_ID), {
60+
params: {
61+
dashboardId,
62+
},
63+
});
64+
console.log("🟦 서버 응답:", res.data);
65+
console.log("URL:", apiRoutes.columns(TEAM_ID));
66+
console.log("대시보드 ID:", dashboardId);
67+
return res.data.data;
68+
};

src/components/modalDashboard/CardDetail.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface CardDetailProps {
88
columnName: string;
99
}
1010

11-
export default function CardDetail({ card }: CardDetailProps) {
11+
export default function CardDetail({ card, columnName }: CardDetailProps) {
1212
return (
1313
<div className="p-4 ">
1414
<h2 className="text-3xl font-semibold mb-5">{card.title}</h2>
@@ -50,9 +50,9 @@ export default function CardDetail({ card }: CardDetailProps) {
5050
<div className="flex flex-wrap gap-2 mb-2">
5151
<span
5252
className="rounded-full bg-violet-200 px-3 py-1 text-sm text-violet-800"
53-
title={`상태: ${card.status}`}
53+
title={`상태: ${columnName}`}
5454
>
55-
{card.status}
55+
{columnName}
5656
</span>
5757
<span className="text-2xl font-extralight text-[#D9D9D9]">|</span>
5858
{card.tags.map((tag, idx) => (

src/components/modalDashboard/CardDetailModal.tsx

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
// CardDetailModal.tsx
2-
import { useState } from "react";
1+
import { useMemo, useRef, useState } from "react";
32
import { MoreVertical, X } from "lucide-react";
43
import CardDetail from "./CardDetail";
54
import CommentList from "./CommentList";
65
import CardInput from "@/components/modalInput/CardInput";
7-
import { useMutation, useQueryClient } from "@tanstack/react-query";
6+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
87
import { createComment } from "@/api/comment";
9-
import { deleteCard } from "@/api/card";
8+
import { deleteCard, EditCard } from "@/api/card"; // EditCard API 추가
109
import type { CardDetailType } from "@/types/cards";
1110
import TaskModal from "@/components/modalInput/TaskModal";
11+
import { useClosePopup } from "@/hooks/useClosePopup";
12+
import { getColumn } from "@/api/columns";
1213

1314
interface CardDetailModalProps {
1415
card: CardDetailType;
@@ -17,6 +18,12 @@ interface CardDetailModalProps {
1718
onClose: () => void;
1819
}
1920

21+
interface ColumnType {
22+
id: number;
23+
title: string;
24+
status: string;
25+
}
26+
2027
export default function CardDetailPage({
2128
card,
2229
currentUserId,
@@ -28,6 +35,20 @@ export default function CardDetailPage({
2835
const [showMenu, setShowMenu] = useState(false);
2936
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
3037
const queryClient = useQueryClient();
38+
const popupRef = useRef(null);
39+
useClosePopup(popupRef, () => setShowMenu(false));
40+
41+
const { data: columns = [] } = useQuery<ColumnType[]>({
42+
queryKey: ["columns", dashboardId],
43+
queryFn: () => getColumn({ dashboardId, columnId: card.columnId }),
44+
});
45+
46+
const columnName = useMemo(() => {
47+
return (
48+
columns.find((col) => String(col.id) === String(cardData.columnId))
49+
?.title || "알 수 없음"
50+
);
51+
}, [columns, cardData.columnId]);
3152

3253
const { mutate: createCommentMutate } = useMutation({
3354
mutationFn: createComment,
@@ -55,20 +76,27 @@ export default function CardDetailPage({
5576
});
5677
};
5778

79+
const { mutateAsync: updateCardMutate } = useMutation({
80+
mutationFn: (data: Partial<CardDetailType>) => EditCard(cardData.id, data),
81+
onSuccess: (updatedCard) => {
82+
setCardData(updatedCard); // ✅ 서버 응답을 최신 cardData로 설정
83+
queryClient.invalidateQueries({ queryKey: ["cards"] }); // 필요시
84+
},
85+
});
86+
5887
return (
5988
<>
6089
<div className="fixed inset-0 bg-black/30 z-50 flex items-center justify-center">
6190
<div
6291
className="relative bg-white rounded-lg shadow-lg w-[730px] h-[763px] flex flex-col
63-
md:w-[678px] lg:w-[730px]
64-
"
92+
md:w-[678px] lg:w-[730px]"
6593
>
66-
{/* 오른쪽 상단 메뉴 */}
67-
<div className="absolute top-6 right-10 z-30 flex items-center gap-5 mt-3 ">
94+
<div className="absolute top-6 right-8.5 z-30 flex items-center gap-3 mt-1">
95+
{/* 오른쪽 상단 메뉴 */}
6896
<div className="relative">
6997
<button
7098
onClick={() => setShowMenu((prev) => !prev)}
71-
className="hover:cursor-pointer"
99+
className="w-7 h-7 flex items-center justify-center hover:cursor-pointer"
72100
title="수정하기"
73101
type="button"
74102
>
@@ -96,19 +124,20 @@ export default function CardDetailPage({
96124
</div>
97125
)}
98126
</div>
99-
<button onClick={onClose} title="메뉴 열기">
100-
<X className="w-8 h-8 text-black hover:cursor-pointer" />
127+
128+
<button onClick={onClose} title="닫기">
129+
<X className="w-7 h-7 flex items-center justify-center hover:cursor-pointer" />
101130
</button>
102131
</div>
103132

104133
{/* 모달 내부 콘텐츠 */}
105-
<div className="p-6 flex gap-6 overflow-y-auto flex-1">
106-
<CardDetail card={cardData} columnName={""} />
134+
<div className="p-6 flex gap-6 overflow-y-auto flex-1 w-[550px] h-[460px]">
135+
<CardDetail card={cardData} columnName={columnName} />
107136
</div>
108137

109138
{/* 댓글 입력창 */}
110139
<div className="px-10 pt-2 pb-2">
111-
<p className="text-sm font-semibold mb-2">댓글</p>
140+
<p className="ml-1 text-sm font-semibold mb-2">댓글</p>
112141
<div className="w-[450px] h-[110px]">
113142
<CardInput
114143
hasButton
@@ -117,18 +146,19 @@ export default function CardDetailPage({
117146
onTextChange={setCommentText}
118147
onButtonClick={handleCommentSubmit}
119148
placeholder="댓글 작성하기"
120-
// buttonClassName="border border-[#D9D9D9] text-[#5534DA] hover:bg-[#F1EFFD]"
121149
/>
122150
</div>
123151
</div>
124152

125153
{/* 댓글 목록 */}
126-
<div className="px-6 space-y-4 max-h-[200px] overflow-y-auto">
127-
<CommentList
128-
cardId={card.id}
129-
currentUserId={currentUserId}
130-
teamId={""}
131-
/>
154+
<div className=" max-h-[100px] ml-7 text-base overflow-y-auto scrollbar-hidden">
155+
<div className=" max-h-[50vh]">
156+
<CommentList
157+
cardId={card.id}
158+
currentUserId={currentUserId}
159+
teamId={""}
160+
/>
161+
</div>
132162
</div>
133163
</div>
134164
</div>
@@ -137,20 +167,19 @@ export default function CardDetailPage({
137167
{isEditModalOpen && (
138168
<TaskModal
139169
mode="edit"
140-
columnId={card.columnId} // ✅ 여기 추가!
170+
columnId={card.columnId} // ✅ 여기에 columnId 추가!
141171
onClose={() => setIsEditModalOpen(false)}
142-
onSubmit={(data) => {
143-
setCardData((prev) => ({
144-
...prev,
145-
status: data.status as "todo" | "in-progress" | "done",
146-
assignee: { ...prev.assignee, nickname: data.assignee },
172+
onSubmit={async (data) => {
173+
await updateCardMutate({
174+
status: String(cardData.columnId) || cardData.status,
175+
assignee: { ...cardData.assignee, nickname: data.assignee },
147176
title: data.title,
148177
description: data.description,
149178
dueDate: data.deadline,
150179
tags: data.tags,
151180
imageUrl: data.image ?? "",
152-
}));
153-
setIsEditModalOpen(false);
181+
});
182+
setIsEditModalOpen(false); // 수정 후 모달 닫기
154183
}}
155184
initialData={{
156185
status: cardData.status,

src/components/modalDashboard/CommentList.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,17 @@ export default function CommentList({
3737
data?.pages.flatMap((page) => page.comments) ?? [];
3838

3939
return (
40-
<div className="flex flex-col gap-4 mt-4">
40+
<div className="min-h-[80px] p-2 rounded bg-white shadow-sm">
4141
{allComments.map((comment) => (
42-
<UpdateComment
43-
key={comment.id}
44-
comment={comment}
45-
currentUserId={currentUserId}
46-
teamId={""}
47-
/>
42+
<div key={comment.id} className="p-2 last:border-b-0">
43+
<UpdateComment
44+
comment={comment}
45+
currentUserId={currentUserId}
46+
teamId={""}
47+
/>
48+
</div>
4849
))}
49-
<div ref={ref} className="h-6" />
50+
<div ref={ref} />
5051
</div>
5152
);
5253
}

src/components/modalDashboard/UpdateComment.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,33 +40,37 @@ export default function UpdateComment({
4040
};
4141

4242
return (
43-
<div className="flex gap-2 items-start">
43+
<div className="flex gap-3 items-start w-full">
44+
{/* 프로필 */}
4445
<ProfileIcon
4546
userId={comment.author.id}
4647
nickname={comment.author.nickname}
4748
profileImageUrl={comment.author.profileImageUrl}
48-
imgClassName=""
49-
fontClassName=""
49+
imgClassName="w-8 h-8"
50+
fontClassName="text-sm"
5051
id={0}
5152
/>
52-
<div className="flex flex-col gap-1 w-full">
53-
<div className="flex gap-2 items-center">
54-
<span className="text-sm font-semibold">
53+
54+
{/* 댓글 내용 */}
55+
<div className="flex flex-col w-full space-y-1">
56+
{/* 작성자 + 시간 */}
57+
<div className="flex items-center gap-2 text-sm text-gray-600">
58+
<span className="font-semibold text-black">
5559
{comment.author.nickname}
5660
</span>
57-
<span className="text-xs text-gray-500">
58-
{formatDate(comment.createdAt)}
59-
</span>
61+
<span>{formatDate(comment.createdAt)}</span>
6062
</div>
63+
64+
{/* 본문 */}
6165
{isEditing ? (
6266
<>
6367
<textarea
64-
className="w-full border p-2 rounded text-sm"
68+
className="w-full p-2 text-sm"
6569
value={editedContent}
6670
onChange={(e) => setEditedContent(e.target.value)}
6771
aria-label="댓글"
6872
/>
69-
<div className="flex gap-2 mt-1 text-xs text-gray-600">
73+
<div className="flex gap-2 mt-1 text-sm">
7074
<button
7175
onClick={handleSave}
7276
disabled={editedContent === comment.content}
@@ -78,7 +82,9 @@ export default function UpdateComment({
7882
</>
7983
) : (
8084
<>
81-
<p className="text-sm">{comment.content}</p>
85+
<p className="text-sm whitespace-pre-wrap break-words">
86+
{comment.content}
87+
</p>
8288
{currentUserId === comment.author.id && (
8389
<div className="flex gap-2 text-xs text-gray-500 mt-1">
8490
<button onClick={handleEditToggle}>수정</button>

src/components/modalInput/CardInput.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ChangeEvent, useRef } from "react";
22
import TextButton from "./TextButton";
3+
import clsx from "clsx";
34

45
export interface CardInputProps {
56
value: string;
@@ -33,9 +34,13 @@ export default function CardInput({
3334
value={value}
3435
onChange={handleChange}
3536
placeholder={placeholder}
36-
className={`w-full resize-none rounded-md border border-[var(--color-gray3)] focus:border-[var(--primary)] p-2 outline-none bg-white ${small ? "text-sm" : ""}`}
37+
className={clsx(
38+
"p-4 w-full resize-none rounded-md border border-[var(--color-gray3)] focus:border-[var(--primary)] p-1 outline-none bg-white",
39+
small ? "text-sm" : "text-base",
40+
className
41+
)}
3742
style={{
38-
height: small ? "80px" : "120px", // 고정된 높이 설정
43+
height: small ? "110px" : "100px", // 고정된 높이 설정
3944
overflowY: "auto", // 내용이 넘치면 스크롤 생성
4045
wordWrap: "break-word",
4146
whiteSpace: "pre-wrap",
@@ -49,7 +54,7 @@ export default function CardInput({
4954
color="secondary"
5055
buttonSize="xs"
5156
onClick={onButtonClick}
52-
className="absolute bottom-3 right-3 z-10 flex items-center justify-center border-gray-300 text-[#5534DA] cursor-pointer"
57+
className="absolute bottom-3 right-3 z-10 flex items-center justify-center w-[83px] h-[32px] border-gray-300 text-[#5534DA] cursor-pointer whitespace-nowrap rounded-sm"
5358
>
5459
입력
5560
</TextButton>

0 commit comments

Comments
 (0)