Skip to content

Commit f6cdd70

Browse files
authored
Merge pull request #84 from codeit-sprint-part3-6team/#81_모달_대시보드-생성
#81 모달 대시보드 생성
2 parents 9bdc834 + 775ab18 commit f6cdd70

File tree

5 files changed

+246
-15
lines changed

5 files changed

+246
-15
lines changed

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"date-fns": "^2.30.0",
2121
"dayjs": "^1.11.13",
2222
"next": "15.0.3",
23+
"overlay-kit": "^1.4.1",
2324
"react": "^18.3.1",
2425
"react-dom": "^18.3.1",
2526
"react-redux": "^9.2.0",

src/components/product/mydashboard/DashboardList.module.css

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,74 @@
3939
color: var(--black-medium);
4040
}
4141

42+
.modal {
43+
background-color: var(--white);
44+
width: 584px;
45+
height: 344px;
46+
padding: 32px;
47+
border-radius: 10px;
48+
}
49+
50+
.modal h2 {
51+
font-size: 24px;
52+
font-weight: 700;
53+
color: var(--black-medium);
54+
}
55+
56+
.modal h3 {
57+
margin-top: 24px;
58+
font-size: 18px;
59+
font-weight: 500;
60+
}
61+
62+
.modal input {
63+
margin-top: 8px;
64+
border: 1px solid var(--gray-medium);
65+
width: 100%;
66+
padding: 15px 16px;
67+
border-radius: 8px;
68+
}
69+
70+
.modal input::placeholder {
71+
font-size: 16px;
72+
font-weight: 400;
73+
color: var(--black-medium);
74+
}
75+
76+
.color-picker {
77+
margin-top: 16px;
78+
margin-bottom: 40px;
79+
display: flex;
80+
gap: 8px;
81+
}
82+
83+
.color-circle:hover {
84+
cursor: pointer;
85+
}
86+
87+
.color-picker .color-circle {
88+
width: 30px;
89+
height: 30px;
90+
border: none;
91+
margin: 0;
92+
position: relative;
93+
}
94+
95+
.color-selected::after {
96+
content: '✓';
97+
font-size: 18px;
98+
color: white;
99+
position: absolute;
100+
top: 50%;
101+
left: 50%;
102+
transform: translate(-50%, -50%);
103+
}
104+
105+
.modal-buttons {
106+
display: flex;
107+
gap: 8px;
108+
}
109+
42110
@media screen and (max-width: 1199px) {
43111
.dashboard-container {
44112
margin-left: 200px;
@@ -64,4 +132,21 @@
64132
.pagination {
65133
max-width: 260px;
66134
}
135+
136+
.modal {
137+
width: 327px;
138+
height: 312px;
139+
}
140+
141+
.modal h2 {
142+
font-size: 20px;
143+
}
144+
145+
.modal h3 {
146+
font-size: 16px;
147+
}
148+
149+
.modal input::placeholder {
150+
font-size: 14px;
151+
}
67152
}

src/components/product/mydashboard/DashboardList.tsx

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,82 @@
11
import { useEffect, useState } from 'react';
22
import { useRouter } from 'next/router';
33
import getDashboards from '@/lib/mydashboard/getDashboard';
4+
import postDashboards from '@/lib/mydashboard/postDashboard';
45
import CDSButton from '@/components/common/button/CDSButton';
6+
import OverlayContainer from '@/components/common/modal/overlay-container/OverlayContainer';
57
import styles from './DashboardList.module.css';
68

79
export default function DashboardList() {
810
const [dashboards, setDashboards] = useState([]);
911
const [currentPage, setCurrentPage] = useState(1);
1012
const [totalPages, setTotalPages] = useState(1);
1113

14+
const [showModal, setShowModal] = useState(false);
15+
const [newDashboardName, setNewDashboardName] = useState('');
16+
const [selectedColor, setSelectedColor] = useState('#7ac555');
17+
const [isLoading, setIsLoading] = useState(false);
18+
1219
const router = useRouter();
1320

14-
// 대시보드 클릭 이벤트
21+
const colors = ['#7ac555', '#760dde', '#ffa500', '#76a5ea', '#e876ea'];
22+
1523
const handleClick = (dashboardId) => {
16-
router.push(`/dashboards/${dashboardId}`);
24+
router.push(`/dashboard/${dashboardId}`);
1725
};
1826

19-
// 페이지네이션 변경 이벤트
2027
const handlePageChange = (newPage) => {
2128
if (newPage > 0 && newPage <= totalPages) {
2229
setCurrentPage(newPage);
2330
}
2431
};
2532

26-
useEffect(() => {
27-
// 대시보드 데이터 불러오기
28-
const fetchDashboards = async () => {
29-
const response = await getDashboards({
30-
page: currentPage,
31-
size: 5,
32-
navigationMethod: 'pagination',
33+
const openModal = () => setShowModal(true);
34+
const closeModal = () => {
35+
setShowModal(false);
36+
setSelectedColor('#7ac555');
37+
setNewDashboardName('');
38+
};
39+
40+
const fetchDashboards = async () => {
41+
const response = await getDashboards({
42+
page: currentPage,
43+
size: 5,
44+
navigationMethod: 'pagination',
45+
});
46+
setDashboards(response.dashboards);
47+
setTotalPages(Math.ceil(response.totalCount / 5));
48+
};
49+
50+
const handleNewDashboard = async () => {
51+
if (!newDashboardName.trim()) {
52+
alert('대시보드 이름을 입력해주세요.');
53+
return;
54+
}
55+
56+
setIsLoading(true);
57+
58+
try {
59+
await postDashboards({
60+
title: newDashboardName,
61+
color: selectedColor,
3362
});
34-
setDashboards(response.dashboards);
35-
setTotalPages(Math.ceil(response.totalCount / 5));
36-
};
63+
closeModal();
64+
fetchDashboards();
65+
} catch (error) {
66+
alert(error.message || '대시보드 생성에 실패했습니다.');
67+
} finally {
68+
setIsLoading(false);
69+
}
70+
};
3771

72+
useEffect(() => {
3873
fetchDashboards();
3974
}, [currentPage]);
75+
4076
return (
4177
<div className={styles['dashboard-container']}>
42-
{/* 대시보드 목록 */}
4378
<ul className={styles['dashboard-list']}>
44-
<CDSButton btnType="dashboard_add" onClick={() => handleClick('new')}>
79+
<CDSButton btnType="dashboard_add" onClick={openModal}>
4580
새로운 대시보드
4681
</CDSButton>
4782
{dashboards.map((item) => (
@@ -76,6 +111,57 @@ export default function DashboardList() {
76111
/>
77112
</div>
78113
</div>
114+
115+
{/* 모달창 */}
116+
{showModal && (
117+
<OverlayContainer>
118+
<div className={styles.modal}>
119+
<h2>새로운 대시보드</h2>
120+
<h3>대시보드 이름</h3>
121+
<input
122+
type="text"
123+
placeholder="대시보드 이름을 입력해주세요"
124+
value={newDashboardName}
125+
onChange={(e) => setNewDashboardName(e.target.value)}
126+
className={styles['modal-input']}
127+
disabled={isLoading}
128+
/>
129+
130+
{/* 색상 선택 UI */}
131+
<div className={styles['color-picker']}>
132+
{colors.map((color) => (
133+
<button
134+
type="button"
135+
key={color}
136+
className={`${styles['color-circle']} ${
137+
color === selectedColor ? styles['color-selected'] : ''
138+
}`}
139+
style={{ backgroundColor: color }}
140+
onClick={() => setSelectedColor(color)}
141+
aria-label={`색상 선택: ${color}`}
142+
/>
143+
))}
144+
</div>
145+
146+
<div className={styles['modal-buttons']}>
147+
<CDSButton
148+
btnType="modal"
149+
onClick={closeModal}
150+
disabled={isLoading}
151+
>
152+
취소
153+
</CDSButton>
154+
<CDSButton
155+
btnType="modal_colored"
156+
onClick={handleNewDashboard}
157+
disabled={isLoading}
158+
>
159+
{isLoading ? '생성 중...' : '생성'}
160+
</CDSButton>
161+
</div>
162+
</div>
163+
</OverlayContainer>
164+
)}
79165
</div>
80166
);
81167
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import instance from '../instance';
2+
3+
interface PostDashboardsParams {
4+
title: string;
5+
color: string;
6+
}
7+
8+
interface PostDashboardsResponse {
9+
id: number;
10+
title: string;
11+
color: string;
12+
createdAt: string;
13+
updatedAt: string;
14+
createdByMe: boolean;
15+
userId: number;
16+
}
17+
18+
export default async function postDashboards(
19+
params: PostDashboardsParams,
20+
): Promise<PostDashboardsResponse> {
21+
try {
22+
const { title, color } = params;
23+
24+
const { data } = await instance.post<PostDashboardsResponse>(
25+
`/11-6/dashboards`,
26+
{
27+
title,
28+
color,
29+
},
30+
{
31+
headers: {
32+
'Content-Type': 'application/json',
33+
},
34+
},
35+
);
36+
return data;
37+
} catch (error) {
38+
if (error.response) {
39+
throw new Error(
40+
error.response.data.message || '대시보드 등록에 실패했습니다.',
41+
);
42+
}
43+
throw new Error('대시보드 등록 요청 중 문제가 발생했습니다.');
44+
}
45+
}

0 commit comments

Comments
 (0)