Skip to content

Commit d560779

Browse files
authored
Merge pull request #213 from part3-4team-Taskify/minji
[Fix, Refactor, Style] FormModal: 공통 모달 컴포넌트 생성 / Column: 수정 시 중복 네이밍 버그 fix & 제목 20자 제한 추가
2 parents 0fca393 + 7d58a83 commit d560779

File tree

12 files changed

+230
-215
lines changed

12 files changed

+230
-215
lines changed

src/components/card/Profile.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import useUserStore from "@/store/useUserStore";
33
import Image from "next/image";
44
import { getUserInfo, updateProfile, uploadProfileImage } from "@/api/users";
55
import Input from "@/components/input/Input";
6-
import { toast } from "react-toastify";
76
import { useUserPermission } from "@/hooks/useUserPermission";
7+
import { toast } from "react-toastify";
88

99
export const ProfileCard = () => {
1010
const { user, updateNickname, updateProfileImage } = useUserStore();
@@ -23,6 +23,7 @@ export const ProfileCard = () => {
2323
setEmail(data.email);
2424
} catch (err) {
2525
console.error("유저 정보 불러오기 실패:", err);
26+
toast.error("유저 정보를 불러올 수 없습니다.");
2627
}
2728
};
2829

src/components/columnCard/AddColumnModal.tsx

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/components/columnCard/Column.tsx

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Image from "next/image";
44
import { CardType } from "@/types/task";
55
import TaskModal from "@/components/modalInput/TaskModal";
66
import { TodoButton, ShortTodoButton } from "@/components/button/TodoButton";
7-
import ColumnManageModal from "@/components/columnCard/ColumnManageModal";
87
import ColumnDeleteModal from "@/components/columnCard/ColumnDeleteModal";
98
import { updateColumn, deleteColumn } from "@/api/columns";
109
import { getDashboardMembers, getCardDetail } from "@/api/card";
@@ -15,6 +14,7 @@ import CardDetailModal from "@/components/modalDashboard/CardDetailModal";
1514
import { CardDetailType } from "@/types/cards";
1615
import { toast } from "react-toastify";
1716
import { useDashboardPermission } from "@/hooks/useDashboardPermission";
17+
import FormModal from "@/components/modal/FormModal";
1818

1919
type ColumnProps = {
2020
columnId: number;
@@ -38,6 +38,8 @@ export default function Column({
3838
const { canEditColumns } = useDashboardPermission(dashboardId, createdByMe);
3939

4040
const [columnTitle, setColumnTitle] = useState(title);
41+
const [editTitle, setEditTitle] = useState(columnTitle);
42+
const [titleLength, setTitleLength] = useState<number>(0);
4143
const [isColumnModalOpen, setIsColumnModalOpen] = useState(false);
4244
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
4345
const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
@@ -47,6 +49,8 @@ export default function Column({
4749
{ id: number; userId: number; nickname: string }[]
4850
>([]);
4951

52+
const maxColumnTitleLength = 20;
53+
5054
// 카드리스트의 스크롤을 칼럼 영역으로 이동
5155
const scrollRef = useRef<HTMLDivElement | null>(null);
5256

@@ -83,6 +87,7 @@ export default function Column({
8387
setColumnTitle(updated.title);
8488
setIsColumnModalOpen(false);
8589
toast.success("칼럼 제목이 변경되었습니다.");
90+
fetchColumnsAndCards();
8691
} catch (error) {
8792
console.error("칼럼 제목 수정 실패:", error);
8893
toast.error("칼럼 제목 변경에 실패했습니다.");
@@ -161,6 +166,8 @@ export default function Column({
161166
toast.error("읽기 전용 대시보드입니다.");
162167
return;
163168
}
169+
setEditTitle(columnTitle);
170+
setTitleLength(columnTitle.length);
164171
setIsColumnModalOpen(true);
165172
}}
166173
/>
@@ -218,16 +225,36 @@ export default function Column({
218225
)}
219226

220227
{/* 칼럼 관리 모달 */}
221-
<ColumnManageModal
222-
isOpen={isColumnModalOpen}
223-
onClose={() => setIsColumnModalOpen(false)}
224-
onDeleteClick={() => {
225-
setIsColumnModalOpen(false);
226-
setIsDeleteModalOpen(true);
227-
}}
228-
columnTitle={columnTitle}
229-
onEditSubmit={handleEditColumn}
230-
/>
228+
{isColumnModalOpen && (
229+
<FormModal
230+
title="칼럼 이름 수정"
231+
inputLabel="이름"
232+
inputPlaceholder="변경할 이름을 입력해 주세요"
233+
inputValue={editTitle}
234+
onInputChange={(value) => {
235+
if (value.length <= maxColumnTitleLength) {
236+
setEditTitle(value);
237+
setTitleLength(value.length);
238+
}
239+
}}
240+
isInputValid={columnTitle.trim().length > 0}
241+
charCount={{
242+
current: titleLength,
243+
max: maxColumnTitleLength,
244+
}}
245+
onSubmit={() => {
246+
handleEditColumn(editTitle);
247+
setIsColumnModalOpen(false);
248+
}}
249+
submitText="변경"
250+
leftButtonText="삭제"
251+
onLeftButtonClick={() => {
252+
setIsColumnModalOpen(false);
253+
setIsDeleteModalOpen(true);
254+
}}
255+
onClose={() => setIsColumnModalOpen(false)}
256+
/>
257+
)}
231258

232259
{/* 칼럼 삭제 확인 모달 */}
233260
<ColumnDeleteModal

src/components/columnCard/ColumnDeleteModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ export default function ColumnDeleteModal({
2121
height="h-[160px] sm:h-[174px]"
2222
>
2323
<div className="flex flex-col sm:gap-10 gap-6 text-center">
24-
<p className="text-xl mt-1.5">칼럼의 모든 카드가 삭제됩니다.</p>
24+
<p className="text-black3 font-medium sm:text-[20px] text-[18px] mt-1.5">
25+
칼럼의 모든 카드가 삭제됩니다.
26+
</p>
2527
<div className="flex justify-between gap-3">
2628
<CustomBtn variant="outlineDisabled" onClick={onClose}>
2729
취소

src/components/columnCard/ColumnManageModal.tsx

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/components/common/CustomToastContainer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import { ToastContainer, Slide } from "react-toastify";
55
const CustomToastContainer = () => {
66
return (
77
<ToastContainer
8+
limit={2}
89
position="top-center"
910
autoClose={3000}
10-
newestOnTop
11+
newestOnTop={false}
1112
closeButton={false}
1213
pauseOnHover={false}
1314
hideProgressBar={true}

src/components/modal/FormModal.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import Image from "next/image";
2+
import Input from "@/components/input/Input";
3+
4+
interface FormModalProps {
5+
title: string;
6+
inputLabel: string;
7+
inputValue: string;
8+
inputPlaceholder: string;
9+
isInputValid: boolean;
10+
onInputChange: (value: string) => void;
11+
onSubmit: () => void;
12+
onClose: () => void;
13+
submitText: string;
14+
leftButtonText?: string;
15+
onLeftButtonClick?: () => void;
16+
errorMessage?: string;
17+
charCount?: {
18+
current: number;
19+
max: number;
20+
};
21+
}
22+
23+
export default function FormModal({
24+
title,
25+
inputLabel,
26+
inputValue,
27+
inputPlaceholder,
28+
isInputValid,
29+
onInputChange,
30+
onSubmit,
31+
onClose,
32+
submitText,
33+
leftButtonText = "취소",
34+
onLeftButtonClick,
35+
errorMessage,
36+
charCount,
37+
}: FormModalProps) {
38+
return (
39+
<div className="fixed inset-0 flex items-center justify-center bg-black/35 z-50">
40+
<div className="bg-white p-6 rounded-lg shadow-lg w-[327px] sm:w-[568px] min-h-[280px]">
41+
<div className="flex justify-between items-center">
42+
<h2 className="text-[20px] sm:text-[24px] font-bold text-black3">
43+
{title}
44+
</h2>
45+
<Image
46+
src="/svgs/close-white.svg"
47+
alt="닫기"
48+
width={25}
49+
height={25}
50+
className="cursor-pointer"
51+
onClick={onClose}
52+
></Image>
53+
</div>
54+
55+
<div className="relative w-full">
56+
<Input
57+
type="text"
58+
value={inputValue}
59+
onChange={onInputChange}
60+
label={inputLabel}
61+
labelClassName="font-medium text-black3 sm:text-[18px] text-[16px] mt-6"
62+
placeholder={inputPlaceholder}
63+
className="max-w-[620px] mb-1 pr-14
64+
text-black3 font-normal sm:text-[16px] text-[14px]
65+
placeholder:sm:text-[16px] placeholder:text-[14px]"
66+
/>
67+
{charCount && (
68+
<span
69+
className="absolute right-3 top-2/5 translate-y-6.5 font-light
70+
text-[12px] sm:text-[14px] text-[var(--color-gray1)] sm:pr-1.5"
71+
>
72+
{charCount.current} / {charCount.max}
73+
</span>
74+
)}
75+
</div>
76+
{errorMessage && (
77+
<p className="font-14r block text-[var(--color-red)] mt-1">
78+
{errorMessage}
79+
</p>
80+
)}
81+
82+
<div className="mt-8 gap-2 flex justify-between">
83+
<button
84+
onClick={onLeftButtonClick || onClose}
85+
className="cursor-pointer
86+
sm:w-[256px] sm:h-[54px] w-[144px] h-[54px] rounded-[8px]
87+
border border-[var(--color-gray3)] text-[var(--color-gray1)]"
88+
>
89+
{leftButtonText}
90+
</button>
91+
<button
92+
onClick={onSubmit}
93+
disabled={!inputValue || !isInputValid}
94+
className={`cursor-pointer sm:w-[256px] sm:h-[54px] w-[144px] h-[54px] rounded-[8px]
95+
border border-[var(--color-gray3)] text-[var(--color-white)]
96+
${!inputValue || !isInputValid ? "bg-gray-300 cursor-not-allowed" : "bg-[var(--primary)]"}`}
97+
>
98+
{submitText}
99+
</button>
100+
</div>
101+
</div>
102+
</div>
103+
);
104+
}

0 commit comments

Comments
 (0)