Skip to content

Commit 6818c20

Browse files
authored
Merge pull request #95 from hhjin1/card-hj
[Feat] Page: 대시보드 페이지 / Modal: 새 칼럼 생성, 칼럼 관리(삭제, 변경)
2 parents eb470ba + c12a832 commit 6818c20

File tree

13 files changed

+345
-102
lines changed

13 files changed

+345
-102
lines changed

public/svgs/dummy-icon.png

636 Bytes
Loading

src/api/dashboards.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ColumnType } from "@/types/task";
12
import axiosInstance from "./axiosInstance";
23

34
export const getCardsByColumn = async ({
@@ -60,3 +61,49 @@ export const getDashboardById = async ({
6061
const res = await axiosInstance.get(`/${teamId}/dashboards/${dashboardId}`);
6162
return res.data;
6263
};
64+
65+
// 칼럼 생성
66+
export const createColumn = async ({
67+
teamId,
68+
title,
69+
dashboardId,
70+
}: {
71+
teamId: string;
72+
title: string;
73+
dashboardId: number;
74+
}): Promise<ColumnType> => {
75+
const res = await axiosInstance.post(`/${teamId}/columns`, {
76+
title,
77+
dashboardId,
78+
});
79+
80+
return res.data;
81+
};
82+
83+
// 칼럼 삭제
84+
export const deleteColumn = async ({
85+
teamId,
86+
columnId,
87+
}: {
88+
teamId: string;
89+
columnId: number;
90+
}) => {
91+
const res = await axiosInstance.delete(`/${teamId}/columns/${columnId}`);
92+
return res;
93+
};
94+
95+
// 칼럼 수정
96+
export const updateColumn = async ({
97+
teamId,
98+
columnId,
99+
title,
100+
}: {
101+
teamId: string;
102+
columnId: number;
103+
title: string;
104+
}) => {
105+
const res = await axiosInstance.put(`/${teamId}/columns/${columnId}`, {
106+
title,
107+
});
108+
return res.data;
109+
};

src/components/button/PaginationBtn.tsx renamed to src/components/button/PaginationButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React from "react";
22

3-
interface PaginationBtnProps
3+
interface PaginationButtonProps
44
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
55
direction?: "left" | "right";
66
disabled?: boolean;
77
}
88

9-
export const PaginationBtn: React.FC<PaginationBtnProps> = ({
9+
export const PaginationButton: React.FC<PaginationButtonProps> = ({
1010
direction = "left",
1111
disabled = false,
1212
...props
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// components/modal/AddColumnModal.tsx
2+
import { Modal } from "@/components/modal/Modal";
3+
import Input from "@/components/input/Input";
4+
import { CustomBtn } from "@/components/button/CustomButton";
5+
6+
type AddColumnModalProps = {
7+
isOpen: boolean;
8+
onClose: () => void;
9+
newColumnTitle: string;
10+
setNewColumnTitle: (value: string) => void;
11+
onSubmit: () => void;
12+
isCreateDisabled: boolean;
13+
invalidMessage: string;
14+
pattern: string;
15+
};
16+
17+
export default function AddColumnModal({
18+
isOpen,
19+
onClose,
20+
newColumnTitle,
21+
setNewColumnTitle,
22+
onSubmit,
23+
isCreateDisabled,
24+
invalidMessage,
25+
pattern,
26+
}: AddColumnModalProps) {
27+
return (
28+
<Modal isOpen={isOpen} onClose={onClose}>
29+
<div className="flex flex-col gap-3">
30+
<h2 className="text-2xl font-bold">새 칼럼 생성</h2>
31+
32+
<label className="font-medium flex flex-col gap-2">
33+
이름
34+
<Input
35+
type="text"
36+
placeholder="새로운 프로젝트"
37+
value={newColumnTitle}
38+
onChange={setNewColumnTitle}
39+
pattern={pattern}
40+
invalidMessage={invalidMessage}
41+
/>
42+
</label>
43+
44+
<div className="flex justify-between">
45+
<CustomBtn variant="outlineDisabled" onClick={onClose}>
46+
취소
47+
</CustomBtn>
48+
<CustomBtn
49+
variant={isCreateDisabled ? "primaryDisabled" : "primary"}
50+
disabled={isCreateDisabled}
51+
onClick={onSubmit}
52+
>
53+
생성
54+
</CustomBtn>
55+
</div>
56+
</div>
57+
</Modal>
58+
);
59+
}
Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,70 @@
1+
// components/column/Column.tsx
12
import { useState } from "react";
23
import Image from "next/image";
34
import { CardType } from "@/types/task";
45
import Card from "./Card";
5-
import { Modal } from "../modal/Modal";
66
import TodoModal from "@/components/modalInput/ToDoModal";
7-
import Input from "../input/Input";
87
import TodoButton from "@/components/button/TodoButton";
9-
import { CustomBtn } from "../button/CustomBtn";
8+
import ColumnManageModal from "@/components/columnCard/ColumnManageModal";
9+
import ColumnDeleteModal from "@/components/columnCard/ColumnDeleteModal";
10+
import { updateColumn, deleteColumn } from "@/api/dashboards";
1011

1112
type ColumnProps = {
13+
columnId: number;
1214
title?: string;
1315
tasks?: CardType[];
16+
teamId: string;
17+
dashboardId: number;
1418
};
1519

1620
export default function Column({
21+
columnId,
1722
title = "new Task",
1823
tasks = [],
24+
teamId,
25+
dashboardId,
1926
}: ColumnProps) {
2027
const [isColumnModalOpen, setIsColumnModalOpen] = useState(false);
2128
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
2229
const [isTodoModalOpen, setIsTodoModalOpen] = useState(false);
30+
const [columnTitle, setColmnTitle] = useState(title);
31+
32+
const handleEditColumn = async (newTitle: string) => {
33+
if (!newTitle.trim()) {
34+
alert("칼럼 이름을 입력해주세요.");
35+
return;
36+
}
37+
38+
try {
39+
const updated = await updateColumn({ teamId, columnId, title: newTitle });
40+
setColmnTitle(updated.title);
41+
setIsColumnModalOpen(false);
42+
alert("칼럼 이름이 변경되었습니다.");
43+
} catch (error) {
44+
console.error("칼럼 이름 수정 실패:", error);
45+
alert("칼럼 이름 수정 중 오류가 발생했습니다.");
46+
}
47+
};
48+
49+
const handleDeleteColumn = async () => {
50+
try {
51+
await deleteColumn({ teamId, columnId });
52+
setIsDeleteModalOpen(false);
53+
alert("칼럼이 삭제되었습니다.");
54+
// 👉 부모에서 상태를 관리 중이라면 삭제 후 다시 데이터를 불러오거나, 상태 업데이트 필요!
55+
} catch (error) {
56+
console.error("칼럼 삭제 실패:", error);
57+
alert("칼럼 삭제에 실패했습니다.");
58+
}
59+
};
2360

2461
return (
2562
<div className="w-[354px] h-[1010px] border-[var(--color-gray4)] flex flex-col rounded-md border border-solid bg-gray-50 p-4">
2663
{/* 칼럼 헤더 */}
2764
<div className="flex items-center justify-between">
2865
<div className="flex items-center gap-2">
2966
<h2 className="text-lg font-bold">
30-
<span className="text-[var(--primary)]"></span> {title}
67+
<span className="text-[var(--primary)]"></span> {columnTitle}
3168
</h2>
3269
<span className="bg-gray-200 text-gray-700 px-2 py-1 rounded-full text-sm">
3370
{tasks.length}
@@ -59,64 +96,34 @@ export default function Column({
5996
/>
6097
))}
6198

62-
{/* Todo 추가 모달 */}
99+
{/* Todo 모달 */}
63100
{isTodoModalOpen && (
64101
<TodoModal
65-
isOpen={isTodoModalOpen}
102+
isOpen={isTodoModalOpen} // todo todomodal에서 타입정의 추가하기 (isOpen, teamId, dashboardId)
66103
onClose={() => setIsTodoModalOpen(false)}
104+
teamId={teamId}
105+
dashboardId={dashboardId}
67106
/>
68107
)}
69108

70109
{/* 칼럼 관리 모달 */}
71-
{isColumnModalOpen && (
72-
<Modal
73-
isOpen={isColumnModalOpen}
74-
onClose={() => setIsColumnModalOpen(false)}
75-
>
76-
<div className="flex flex-col gap-5">
77-
<h2 className="text-2xl font-bold">칼럼 관리</h2>
78-
<label className="font-medium flex flex-col gap-2">
79-
이름
80-
<Input type="text" />
81-
</label>
82-
<div className="flex justify-between mt-1.5">
83-
<CustomBtn
84-
variant="outlineDisabled"
85-
onClick={() => {
86-
setIsColumnModalOpen(false);
87-
setIsDeleteModalOpen(true);
88-
}}
89-
>
90-
삭제
91-
</CustomBtn>
92-
<CustomBtn>변경</CustomBtn>
93-
</div>
94-
</div>
95-
</Modal>
96-
)}
110+
<ColumnManageModal
111+
isOpen={isColumnModalOpen}
112+
onClose={() => setIsColumnModalOpen(false)}
113+
onDeleteClick={() => {
114+
setIsColumnModalOpen(false);
115+
setIsDeleteModalOpen(true);
116+
}}
117+
columnTitle={columnTitle}
118+
onEditSubmit={handleEditColumn}
119+
/>
97120

98121
{/* 칼럼 삭제 확인 모달 */}
99-
{isDeleteModalOpen && (
100-
<Modal
101-
width="w-[568px]"
102-
height="h-[174px]"
103-
isOpen={isDeleteModalOpen}
104-
onClose={() => setIsDeleteModalOpen(false)}
105-
>
106-
<div className="flex flex-col gap-10 text-center">
107-
<p className="text-xl mt-1.5">칼럼의 모든 카드가 삭제됩니다.</p>
108-
<div className="flex justify-between gap-3">
109-
<CustomBtn
110-
variant="outlineDisabled"
111-
onClick={() => setIsDeleteModalOpen(false)}
112-
>
113-
취소
114-
</CustomBtn>
115-
<CustomBtn variant="primary">삭제</CustomBtn>
116-
</div>
117-
</div>
118-
</Modal>
119-
)}
122+
<ColumnDeleteModal
123+
isOpen={isDeleteModalOpen}
124+
onClose={() => setIsDeleteModalOpen(false)}
125+
onDelete={handleDeleteColumn}
126+
/>
120127
</div>
121128
);
122129
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// components/column/ColumnDeleteModal.tsx
2+
import { Modal } from "../modal/Modal";
3+
import { CustomBtn } from "../button/CustomButton";
4+
5+
type ColumnDeleteModalProps = {
6+
isOpen: boolean;
7+
onClose: () => void;
8+
onDelete: () => void;
9+
};
10+
11+
export default function ColumnDeleteModal({
12+
isOpen,
13+
onClose,
14+
onDelete,
15+
}: ColumnDeleteModalProps) {
16+
return (
17+
<Modal
18+
width="w-[568px]"
19+
height="h-[174px]"
20+
isOpen={isOpen}
21+
onClose={onClose}
22+
>
23+
<div className="flex flex-col gap-10 text-center">
24+
<p className="text-xl mt-1.5">칼럼의 모든 카드가 삭제됩니다.</p>
25+
<div className="flex justify-between gap-3">
26+
<CustomBtn variant="outlineDisabled" onClick={onClose}>
27+
취소
28+
</CustomBtn>
29+
<CustomBtn variant="primary" onClick={onDelete}>
30+
삭제
31+
</CustomBtn>
32+
</div>
33+
</div>
34+
</Modal>
35+
);
36+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// components/column/ColumnManageModal.tsx
2+
import { useState } from "react";
3+
import Input from "../input/Input";
4+
import { Modal } from "../modal/Modal";
5+
import { CustomBtn } from "../button/CustomButton";
6+
7+
type ColumnManageModalProps = {
8+
isOpen: boolean;
9+
onClose: () => void;
10+
onDeleteClick: () => void;
11+
columnTitle: string;
12+
onEditSubmit: (newTitle: string) => void;
13+
};
14+
15+
export default function ColumnManageModal({
16+
isOpen,
17+
onClose,
18+
onDeleteClick,
19+
columnTitle,
20+
onEditSubmit,
21+
}: ColumnManageModalProps) {
22+
const [newTitle, setNewTile] = useState(columnTitle);
23+
24+
return (
25+
<Modal isOpen={isOpen} onClose={onClose}>
26+
<div className="flex flex-col gap-5">
27+
<h2 className="text-2xl font-bold">칼럼 관리</h2>
28+
<label className="font-medium flex flex-col gap-2">
29+
이름
30+
<Input
31+
type="text"
32+
value={newTitle}
33+
onChange={(value) => setNewTile(value)}
34+
/>
35+
</label>
36+
<div className="flex justify-between mt-1.5">
37+
<CustomBtn variant="outlineDisabled" onClick={onDeleteClick}>
38+
삭제
39+
</CustomBtn>
40+
<CustomBtn onClick={() => onEditSubmit(newTitle)}>변경</CustomBtn>
41+
</div>
42+
</div>
43+
</Modal>
44+
);
45+
}

src/components/modal/Modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { createPortal } from "react-dom";
4-
import { CustomBtn } from "../button/CustomBtn";
4+
import { CustomBtn } from "../button/CustomButton";
55

66
interface ButtonProps {
77
label: string;

src/pages/dashboard.tsx

Whitespace-only changes.

0 commit comments

Comments
 (0)