Skip to content

Commit 51a0194

Browse files
authored
Merge pull request #112 from part3-4team-Taskify/fix01
[Feat Fix] CSS 수정 및 사이드 메뉴 기능추가
2 parents 1db035b + d5b065a commit 51a0194

File tree

8 files changed

+126
-35
lines changed

8 files changed

+126
-35
lines changed

src/api/dashboards.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,19 @@ export const updateColumn = async ({
113113
});
114114
return res.data;
115115
};
116+
117+
export const createDashboard = async ({
118+
teamId,
119+
title,
120+
color,
121+
}: {
122+
teamId: string;
123+
title: string;
124+
color: string;
125+
}) => {
126+
const res = await axiosInstance.post(`/${teamId}/dashboards`, {
127+
title,
128+
color,
129+
});
130+
return res.data;
131+
};

src/components/modal/NewDashboard.tsx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,28 @@ import { useState } from "react";
22
import Input from "../input/Input";
33
import Image from "next/image";
44
import axios from "axios";
5+
interface Dashboard {
6+
id: number;
7+
title: string;
8+
color: string;
9+
userId: number;
10+
createdAt: string;
11+
updatedAt: string;
12+
createdByMe: boolean;
13+
}
514

6-
export default function NewDashboard({ onClose }: { onClose?: () => void }) {
15+
export default function NewDashboard({
16+
onClose,
17+
onCreate,
18+
}: {
19+
onClose?: () => void;
20+
onCreate?: (newDashboard: Dashboard) => void;
21+
}) {
722
const [title, setTitle] = useState("");
823
const [selected, setSelected] = useState<number | null>(null);
924
const [loading, setLoading] = useState(false);
1025

1126
const colors = ["#7ac555", "#760DDE", "#FF9800", "#76A5EA", "#E876EA"];
12-
1327
const token = localStorage.getItem("accessToken");
1428

1529
const handleSubmit = async () => {
@@ -30,13 +44,11 @@ export default function NewDashboard({ onClose }: { onClose?: () => void }) {
3044
},
3145
}
3246
);
33-
console.log("대시보드 생성 성공:", response.data);
34-
alert("대시보드가 성공적으로 생성되었습니다.");
35-
console.log(loading);
3647

37-
onClose?.(); // 모달 닫기
48+
onCreate?.(response.data);
49+
onClose?.();
3850
} catch (error) {
39-
console.error("대시보드 생성 실패:", error);
51+
alert("대시보드 생성에 실패했습니다.");
4052
} finally {
4153
setLoading(false);
4254
}
@@ -87,10 +99,10 @@ export default function NewDashboard({ onClose }: { onClose?: () => void }) {
8799
onClick={handleSubmit}
88100
disabled={!title || selected === null}
89101
className={`cursor-pointer sm:w-[256px] sm:h-[54px] w-[295px] h-[54px] rounded-[8px]
90-
border border-[var(--color-gray3)] text-[var(--color-white)]
91-
${!title || selected === null ? "bg-gray-300 cursor-not-allowed" : "bg-[var(--primary)]"}`}
102+
border border-[var(--color-gray3)] text-[var(--color-white)]
103+
${!title || selected === null ? "bg-gray-300 cursor-not-allowed" : "bg-[var(--primary)]"}`}
92104
>
93-
생성
105+
{loading ? "생성 중..." : "생성"}
94106
</button>
95107
</div>
96108
</div>

src/components/modalInput/AssigneeSelect.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
2+
3+
const colors = ["bg-[#C4B1A2]", "bg-[#9DD7ED]", "bg-[#FDD446]", "bg-[#FFC85A]"];
24

35
interface AssigneeSelectProps {
46
value: string;
57
onChange: (value: string) => void;
6-
users: string[]; // users는 string[]이어야 합니다.
8+
users: string[];
79
label?: string;
810
required?: boolean;
911
}
@@ -17,11 +19,27 @@ export default function AssigneeSelect({
1719
}: AssigneeSelectProps) {
1820
const [isOpen, setIsOpen] = useState(false);
1921
const [filter, setFilter] = useState("");
22+
const [userColors, setUserColors] = useState<Record<string, string>>({});
2023

24+
// 유저 필터링
2125
const filtered = users.filter((name) =>
2226
name.toLowerCase().includes(filter.toLowerCase() || "")
2327
);
2428

29+
// 유저별 색상 매핑 (한 번만 부여)
30+
useEffect(() => {
31+
setUserColors((prev) => {
32+
const updated = { ...prev };
33+
users.forEach((user) => {
34+
if (!updated[user]) {
35+
const color = colors[Math.floor(Math.random() * colors.length)];
36+
updated[user] = color;
37+
}
38+
});
39+
return updated;
40+
});
41+
}, [users]);
42+
2543
return (
2644
<div className="inline-flex flex-col items-start gap-2.5 w-full max-w-[520px]">
2745
{label && (
@@ -32,14 +50,19 @@ export default function AssigneeSelect({
3250
)}
3351

3452
<div className="relative w-full">
53+
{/* 선택된 담당자 */}
3554
<div
3655
className="flex items-center justify-between h-[48px] px-4 border border-[var(--color-gray3)] rounded-md cursor-pointer focus-within:border-[var(--primary)]"
3756
onClick={() => setIsOpen(!isOpen)}
3857
>
3958
<div className="flex items-center gap-2">
4059
{value ? (
4160
<>
42-
<span className="w-6 h-6 rounded-full text-xs text-white flex items-center justify-center bg-[#A0E6FF]">
61+
<span
62+
className={`w-6 h-6 rounded-full text-xs text-white flex items-center justify-center ${
63+
userColors[value] || "bg-[#A0E6FF]"
64+
}`}
65+
>
4366
{value.charAt(0).toUpperCase()}
4467
</span>
4568
<span className="font-18r">{value}</span>
@@ -52,8 +75,9 @@ export default function AssigneeSelect({
5275
</div>
5376
</div>
5477

78+
{/* 드롭다운 */}
5579
{isOpen && (
56-
<ul className="absolute top-full left-0 mt-1 w-full bg-white border border-[var(--color-gray3)] rounded-md shadow-lg z-10">
80+
<ul className="absolute top-full left-0 mt-1 w-full bg-white border border-[var(--color-gray3)] rounded-md shadow-lg z-10 max-h-[200px] overflow-y-auto">
5781
{filtered.map((name, idx) => (
5882
<li
5983
key={idx}
@@ -64,7 +88,16 @@ export default function AssigneeSelect({
6488
}}
6589
className="px-4 py-2 cursor-pointer hover:bg-[var(--color-gray1)] flex items-center justify-between"
6690
>
67-
<span className="text-sm">{name}</span>
91+
<div className="flex items-center gap-2">
92+
<span
93+
className={`w-6 h-6 rounded-full text-xs text-white flex items-center justify-center ${
94+
userColors[name] || "bg-[#A0E6FF]"
95+
}`}
96+
>
97+
{name.charAt(0).toUpperCase()}
98+
</span>
99+
<span className="text-sm">{name}</span>
100+
</div>
68101
{value === name && (
69102
<span className="text-[var(--primary)]"></span>
70103
)}

src/components/modalInput/StatusSelect.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ interface StatusSelectProps {
1111

1212
const STATUS_OPTIONS = [
1313
{ label: "To Do", color: "bg-[#9D8CFC]" },
14-
{ label: "On Progress", color: "bg-[#A0E6FF]" },
15-
{ label: "Done", color: "bg-[#B8F986]" },
14+
{ label: "On Progress", color: "bg-[#9D8CFC]" },
15+
{ label: "Done", color: "bg-[#9D8CFC]" },
1616
];
1717

1818
export default function StatusSelect({
@@ -23,29 +23,36 @@ export default function StatusSelect({
2323
}: StatusSelectProps) {
2424
const [isOpen, setIsOpen] = useState(false);
2525

26+
const selectedStatus = STATUS_OPTIONS.find((opt) => opt.label === value);
27+
2628
return (
2729
<div className="inline-flex flex-col items-start gap-2.5 w-full max-w-[520px]">
2830
{label && (
2931
<p className="font-18m text-[var(--color-black)]">
30-
{label}{" "}
32+
{label}
3133
{required && <span className="text-[var(--color-purple)]">*</span>}
3234
</p>
3335
)}
3436

3537
<div className="relative w-full">
38+
{/* 현재 선택된 값 */}
3639
<div
3740
className="flex items-center justify-between h-[48px] px-4 border border-[var(--color-gray3)] rounded-md cursor-pointer focus-within:border-[var(--primary)]"
3841
onClick={() => setIsOpen(!isOpen)}
3942
>
4043
<div className="flex items-center gap-2">
41-
<span
42-
className={clsx(
43-
"w-2 h-2 rounded-full",
44-
STATUS_OPTIONS.find((opt) => opt.label === value)?.color ||
45-
"bg-gray-300"
46-
)}
47-
></span>
48-
<span className="font-18r">{value || "상태를 선택해주세요"}</span>
44+
{selectedStatus ? (
45+
<>
46+
<span
47+
className={clsx("w-2 h-2 rounded-full", selectedStatus.color)}
48+
></span>
49+
<span className="font-18r">{selectedStatus.label}</span>
50+
</>
51+
) : (
52+
<span className="font-18r text-[var(--color-gray2)]">
53+
상태를 선택해주세요
54+
</span>
55+
)}
4956
</div>
5057
<Image
5158
src="/svgs/arrow-down.svg"
@@ -55,6 +62,7 @@ export default function StatusSelect({
5562
/>
5663
</div>
5764

65+
{/* 드롭다운 리스트 */}
5866
{isOpen && (
5967
<ul className="absolute top-full left-0 mt-1 w-full bg-white border border-[var(--color-gray3)] rounded-md shadow-lg z-10">
6068
{STATUS_OPTIONS.map((status) => (

src/components/modalInput/TaskModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export default function TaskModal({
123123
color="third"
124124
buttonSize="md"
125125
onClick={onClose}
126-
className="w-full sm:w-[256px] h-[54px] border border-gray bg-white text-black rounded-lg"
126+
className="w-full sm:w-[256px] h-[54px] border border-[var(--color-gray3)] bg-white text-black rounded-lg"
127127
>
128128
취소
129129
</TextButton>

src/components/modalInput/ToDoModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export default function TaskModal({
138138
color="third"
139139
buttonSize="md"
140140
onClick={onClose}
141-
className="w-full sm:w-[256px] h-[54px] border border-gray bg-white text-black rounded-lg"
141+
className="w-full sm:w-[256px] h-[54px] border border-[var(--color-gray3)] bg-white text-black rounded-lg"
142142
>
143143
취소
144144
</TextButton>

src/components/sideMenu/SideMenu.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Link from "next/link";
44
import { useRouter } from "next/router";
55
import { useState } from "react";
66
import { PaginationButton } from "@/components/button/PaginationButton";
7+
import NewDashboard from "@/components/modal/NewDashboard";
78

89
interface Dashboard {
910
id: number;
@@ -18,16 +19,21 @@ interface Dashboard {
1819
interface SideMenuProps {
1920
teamId: string;
2021
dashboardList: Dashboard[];
22+
onCreateDashboard?: (dashboard: Dashboard) => void; // ✅ 부모 상태 갱신용 콜백
2123
}
2224

23-
export default function SideMenu({ teamId, dashboardList }: SideMenuProps) {
25+
export default function SideMenu({
26+
dashboardList,
27+
onCreateDashboard,
28+
}: SideMenuProps) {
2429
const router = useRouter();
2530
const { boardid } = router.query;
2631
const boardId = parseInt(boardid as string);
2732

28-
const itemsPerPage = 18;
2933
const [currentPage, setCurrentPage] = useState(1);
34+
const [isModalOpen, setIsModalOpen] = useState(false);
3035

36+
const itemsPerPage = 18;
3137
const totalPages = Math.ceil(dashboardList.length / itemsPerPage);
3238
const startIndex = (currentPage - 1) * itemsPerPage;
3339
const endIndex = startIndex + itemsPerPage;
@@ -46,7 +52,7 @@ export default function SideMenu({ teamId, dashboardList }: SideMenuProps) {
4652
{/* 로고 */}
4753
<div className="mb-14 px-3 sm:mb-9 sm:px-0">
4854
<Link
49-
href={"/"}
55+
href={"/mydashboard"}
5056
className="flex lg:justify-start md:justify-start sm:justify-center"
5157
>
5258
<Image
@@ -78,7 +84,7 @@ export default function SideMenu({ teamId, dashboardList }: SideMenuProps) {
7884
<span className="hidden md:block font-12sb text-[var(--color-black)]">
7985
Dash Boards
8086
</span>
81-
<button className="ml-auto">
87+
<button className="ml-auto" onClick={() => setIsModalOpen(true)}>
8288
<Image
8389
src="/svgs/icon-add-box.svg"
8490
width={20}
@@ -151,6 +157,17 @@ export default function SideMenu({ teamId, dashboardList }: SideMenuProps) {
151157
</div>
152158
)}
153159
</nav>
160+
161+
{/* 대시보드 생성 모달 */}
162+
{isModalOpen && (
163+
<NewDashboard
164+
onClose={() => setIsModalOpen(false)}
165+
onCreate={(newDashboard) => {
166+
onCreateDashboard?.(newDashboard);
167+
setIsModalOpen(false);
168+
}}
169+
/>
170+
)}
154171
</aside>
155172
);
156173
}

src/pages/mydashboard.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,13 @@ export default function MyDashboardPage() {
7575

7676
return (
7777
<div className="flex h-screen overflow-hidden">
78-
<SideMenu teamId={teamId} dashboardList={dashboardList} />
78+
<SideMenu
79+
teamId={teamId}
80+
dashboardList={dashboardList}
81+
onCreateDashboard={(newDashboard) =>
82+
setDashboardList((prev) => [newDashboard, ...prev])
83+
}
84+
/>
7985

8086
<div className="flex flex-col flex-1 overflow-hidden">
8187
<HeaderDashboard variant="mydashboard" />
@@ -112,12 +118,11 @@ export default function MyDashboardPage() {
112118
</main>
113119
</div>
114120

115-
{/* 새로운 대시보드 모달 */}
116121
{isModalOpen && (
117122
<NewDashboard
118123
onClose={() => {
119124
setIsModalOpen(false);
120-
fetchDashboards(); // 생성 후 목록 새로고침
125+
fetchDashboards();
121126
}}
122127
/>
123128
)}

0 commit comments

Comments
 (0)