Skip to content

Commit 23394fd

Browse files
authored
Merge pull request #204 from part3-4team-Taskify/minji
[Feat, Rafactor] CardDetailModal: 이미지 클릭 시 팝업 오픈 / ModalInput: tag 개수 4개 제한 & 백스페이스 삭제 기능 추가
2 parents aee7cd8 + ffd4e56 commit 23394fd

File tree

13 files changed

+144
-48
lines changed

13 files changed

+144
-48
lines changed

src/components/columnCard/Card.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { AssigneeType, CardType } from "@/types/task";
22
import Image from "next/image";
3+
import { getTagColor } from "../modalInput/chips/ColorTagChip";
4+
import RandomProfile from "../table/member/RandomProfile";
35

46
type CardProps = CardType & {
57
imageUrl?: string | null;
@@ -61,28 +63,26 @@ export default function Card({
6163
{/* 태그 + 날짜 + 닉네임 */}
6264
<div
6365
className={`
64-
flex flex-col gap-2 mt-2
66+
flex flex-col gap-2 mt-2 whitespace-nowrap
67+
sm:max-w-none max-w-[192px]
6568
md:flex-row md:items-center md:justify-between md:mt-1
6669
lg:flex-col lg:items-start lg:mt-2
6770
text-sm md:text-xs
6871
`}
6972
>
7073
{/* 태그들 */}
7174
<div className="flex gap-1 flex-wrap">
72-
{tags.map((tag, idx) => (
73-
<span
74-
key={idx}
75-
className={`px-2 py-0.5 rounded-md text-xs font-medium ${
76-
idx % 3 === 0
77-
? "bg-[#F9EEE3] text-[#D58D49]"
78-
: idx % 3 === 1
79-
? "bg-[#F7DBF0] text-[#D549B6]"
80-
: "bg-[#DBE6F7] text-[#4981D5]"
81-
}`}
82-
>
83-
{tag}
84-
</span>
85-
))}
75+
{tags.map((tag, idx) => {
76+
const { textColor, bgColor } = getTagColor(idx);
77+
return (
78+
<span
79+
key={idx}
80+
className={`px-2 py-0.5 rounded-md text-xs font-medium ${textColor} ${bgColor}`}
81+
>
82+
{tag}
83+
</span>
84+
);
85+
})}
8686
</div>
8787

8888
{/* 날짜 + 닉네임 */}
@@ -100,13 +100,16 @@ export default function Card({
100100
<Image
101101
src={assignee.profileImageUrl}
102102
alt="프로필 이미지"
103-
width={24}
104-
height={24}
105-
className="w-6 h-6 rounded-full object-cover"
103+
width={22}
104+
height={22}
105+
className="sm:w-[24px] sm:h-[24px] rounded-full object-cover"
106106
/>
107107
) : (
108-
<div className="w-6 h-6 flex items-center justify-center bg-[#A3C4A2] text-white font-medium rounded-full text-xs">
109-
{assignee.nickname[0]}
108+
<div
109+
className="sm:w-[24px] sm:h-[24px] w-[22px] h-[22px] rounded-full
110+
overflow-hidden flex items-center justify-center"
111+
>
112+
<RandomProfile name={assignee.nickname} />
110113
</div>
111114
)}
112115
</div>

src/components/columnCard/Column.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export default function Column({
160160
</div>
161161

162162
{/* 스크롤바 컨테이너 */}
163-
<div className="flex flex-col lg:pl-[21px] overflow-y-auto w-full lg:w-[357px] rounded-[12px] bg-[#F5F2FC]">
163+
<div className="flex flex-col lg:pl-[21px] overflow-y-auto w-full lg:w-[357px] rounded-md bg-[#F5F2FC]">
164164
{/* 카드 리스트 */}
165165
<div
166166
className="flex-1 overflow-y-auto overflow-x-hidden"

src/components/modal/DeleteMemberModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default function DeleteDashboardModal({
3838
onClose={onClose}
3939
width="w-[327px] sm:w-[568px]"
4040
height="h-[160px] sm:h-[174px]"
41-
backgroundClassName="bg-black/30 z-50"
41+
backgroundClassName="bg-black/35 z-50"
4242
>
4343
<div className="flex flex-col sm:gap-10 gap-6 text-center ">
4444
<p className="text-xl mt-1.5">멤버를 삭제하시겠습니까?</p>

src/components/modalDashboard/CardDetail.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1+
import { useState } from "react";
12
import Image from "next/image";
23
import { CardDetailType } from "@/types/cards";
34
import { ColumnNameTag } from "../modalInput/chips/ColumnNameTag";
45
import ColorTagChip, {
56
getTagColor,
67
} from "@/components/modalInput/chips/ColorTagChip";
8+
import { PopupImageModal } from "./PopupImageModal";
79

810
interface CardDetailProps {
911
card: CardDetailType;
1012
columnName: string;
1113
}
1214

1315
export default function CardDetail({ card, columnName }: CardDetailProps) {
16+
const [isImageModalOpen, setIsImageModalOpen] = useState(false);
17+
1418
return (
1519
<div className="flex flex-col gap-5 w-full">
1620
<div className="flex flex-wrap items-center gap-5">
@@ -34,7 +38,7 @@ export default function CardDetail({ card, columnName }: CardDetailProps) {
3438
{/* 내용 */}
3539
<p
3640
className="
37-
text-black font-normal sm:text-[14px] text-[12px] overflow-auto pr-1
41+
text-black font-normal sm:text-[16px] text-[14px] overflow-auto pr-1
3842
w-full lg:max-w-[445px] sm:max-w-[420px] max-w-[295px]
3943
min-h-0 sm:max-h-[100px] max-h-[80px]
4044
whitespace-pre-wrap word-break break-words
@@ -45,17 +49,28 @@ export default function CardDetail({ card, columnName }: CardDetailProps) {
4549

4650
{/* 이미지 */}
4751
{card.imageUrl && (
48-
<div className="md:w-[420px] lg:w-[445px]">
52+
<div
53+
className="md:w-[420px] lg:w-[445px] cursor-pointer"
54+
onClick={() => setIsImageModalOpen(true)}
55+
>
4956
<Image
5057
src={card.imageUrl}
5158
alt="카드 이미지"
5259
width={290}
5360
height={168}
5461
className="rounded-lg object-cover
55-
lg:w-[445px] md:w-[420px]"
62+
lg:w-[445px] md:w-[420px]
63+
lg:h-[280px] md:h-[240px]
64+
sm:max-h-none max-h-[180px]"
5665
/>
5766
</div>
5867
)}
68+
{isImageModalOpen && (
69+
<PopupImageModal
70+
imageUrl={card.imageUrl!}
71+
onClose={() => setIsImageModalOpen(false)}
72+
/>
73+
)}
5974
</div>
6075
);
6176
}

src/components/modalDashboard/CardDetailModal.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ export default function CardDetailPage({
9898
<>
9999
{/* 모달 고정 div */}
100100
<div
101-
className="fixed inset-0 bg-black/30 z-50
101+
className="fixed inset-0 bg-black/35 z-50
102102
flex items-center justify-center px-4 sm:px-6"
103103
>
104-
{/* 모달 컨테이너 */}
104+
{/* 모달창 */}
105105
<div
106106
className="relative flex flex-col
107107
overflow-y-auto
@@ -115,7 +115,7 @@ export default function CardDetailPage({
115115
{/* 헤더 */}
116116
<div className="flex justify-between sm:mb-4 mb-2">
117117
{/* 카드 제목 */}
118-
<h2 className="text-black3 font-bold sm:text-[24px] text-[20px]">
118+
<h2 className="text-black3 font-bold sm:text-[20px] text-[16px]">
119119
{cardData.title}
120120
</h2>
121121
{/* 버튼 컨테이너 */}
@@ -133,16 +133,18 @@ export default function CardDetailPage({
133133
>
134134
<MoreVertical className="w-8 h-8 text-black3 cursor-pointer" />
135135
</button>
136+
{/* 카드 편집 드롭다운 메뉴 */}
136137
{showMenu && (
137138
<div
138139
className="absolute right-0 top-9.5 p-2 z-40
139-
sm:w-28 w-20
140+
flex flex-col items-center justify-center sm:gap-[6px] gap-[11px]
141+
sm:w-28 w-20 sm:h-24
140142
bg-white border border-[#D9D9D9] rounded-lg"
141143
>
142144
<button
143-
className="w-full rounded-sm
145+
className="w-full h-full rounded-sm
144146
font-normal sm:text-[14px] text-[12px] text-black3
145-
hover:bg-[#F1EFFD] hover:text-[#5534DA]
147+
hover:bg-[var(--color-violet8)] hover:text-[var(--primary)]
146148
cursor-pointer"
147149
type="button"
148150
onClick={() => {
@@ -153,9 +155,9 @@ export default function CardDetailPage({
153155
수정하기
154156
</button>
155157
<button
156-
className="w-full rounded-sm
158+
className="w-full h-full rounded-sm
157159
font-normal sm:text-[14px] text-[12px] text-black3
158-
hover:bg-[#F1EFFD] hover:text-[#5534DA]
160+
hover:bg-[var(--color-violet8)] hover:text-[var(--primary)]
159161
cursor-pointer"
160162
type="button"
161163
onClick={() => deleteCardMutate()}
@@ -189,7 +191,6 @@ export default function CardDetailPage({
189191
</p>
190192
<CardInput
191193
hasButton
192-
small
193194
value={commentText}
194195
onTextChange={setCommentText}
195196
onButtonClick={handleCommentSubmit}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// 이미지 클릭 시 확대 모달 오픈
2+
import { useRef } from "react";
3+
import Image from "next/image";
4+
import { useClosePopup } from "@/hooks/useClosePopup";
5+
6+
interface PopupImageModalProps {
7+
imageUrl: string;
8+
onClose: () => void;
9+
}
10+
11+
export const PopupImageModal = ({
12+
imageUrl,
13+
onClose,
14+
}: PopupImageModalProps) => {
15+
const ref = useRef<HTMLDivElement>(null);
16+
useClosePopup(ref, onClose);
17+
18+
return (
19+
<div
20+
className="fixed inset-0 z-50 bg-black/35 flex items-center justify-center"
21+
onClick={onClose}
22+
>
23+
<div
24+
ref={ref}
25+
className="relative bg-white rounded-md p-2 cursor-default
26+
min-h-0 min-w-0"
27+
// onClick={(e) => e.stopPropagation()}
28+
>
29+
<Image
30+
src={imageUrl}
31+
alt="확대 이미지"
32+
width={600}
33+
height={600}
34+
objectFit="contain"
35+
className="object-contain rounded-md
36+
w-full h-auto
37+
max-w-[80vw]
38+
max-h-[80vh]
39+
lg:max-w-[800px] sm:max-w-[400px]"
40+
unoptimized // 외부 이미지면 성능 최적화 해제
41+
/>
42+
</div>
43+
</div>
44+
);
45+
};

src/components/modalDashboard/ProfileIcon.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ export const ProfileIcon: React.FC<ProfileIconProps> = ({
2020
<Image
2121
src={profileImageUrl}
2222
alt="유저 프로필 아이콘"
23-
width={34}
24-
height={34}
25-
className="object-cover w-[26px] h-[26px] rounded-full"
23+
width={26}
24+
height={26}
25+
className="object-cover sm:w-[34px] sm:h-[34px] rounded-full"
2626
/>
2727
) : (
28-
<RandomProfile name={nickname} />
28+
<div
29+
className="sm:w-[34px] sm:h-[34px] w-[26px] h-[26px] rounded-full
30+
overflow-hidden flex items-center justify-center"
31+
>
32+
<RandomProfile name={nickname} />
33+
</div>
2934
)}
3035
</>
3136
);

src/components/modalDashboard/Representative.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const Representative = ({ card }: RepresentativeProps) => {
1717
<div className="flex sm:flex-col sm:gap-4 gap-17">
1818
{/* 담당자 컨테이너 */}
1919
<div>
20-
<p className="font-12sb text-black3 mb-1">담당자</p>
20+
<p className="font-12sb text-black3 mb-[4px]">담당자</p>
2121
<div className="flex items-center gap-2">
2222
<ProfileIcon
2323
userId={card.assignee.id}
@@ -35,7 +35,7 @@ export const Representative = ({ card }: RepresentativeProps) => {
3535

3636
{/* 마감일 컨테이너 */}
3737
<div>
38-
<p className="font-12sb text-black3 sm:mb-1 mb-[8px]">마감일</p>
38+
<p className="font-12sb text-black3 sm:mb-1 mb-[4px]">마감일</p>
3939
<p className="font-normal text-black3 sm:text-[14px] text-[12px]">
4040
{new Date(card.dueDate).toLocaleString("ko-KR", {
4141
year: "numeric",

src/components/modalInput/ModalInput.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ColorTagChip, { getTagColor } from "./chips/ColorTagChip";
66
import { inputClassNames } from "./InputClassNames";
77
import clsx from "clsx";
88
import { format } from "date-fns";
9+
import { toast } from "react-toastify";
910

1011
type ModalInputType = "제목" | "마감일" | "태그";
1112

@@ -63,6 +64,18 @@ export default function ModalInput({
6364

6465
const handleAddTag = (event: KeyboardEvent<HTMLInputElement>) => {
6566
if (event.key === "Enter" && tagInput.trim() !== "") {
67+
if (tagInput.trim().length > 10) {
68+
toast.error("10자 이하로 입력해 주세요.");
69+
return;
70+
}
71+
72+
// 태그 개수 제한
73+
if (tags.length >= 4) {
74+
setTagInput("");
75+
toast.error("태그는 4개까지 입력할 수 있습니다.");
76+
return;
77+
}
78+
6679
if (tags.some((tag) => tag.text === tagInput.trim())) {
6780
setTagInput("");
6881
return;
@@ -86,6 +99,17 @@ export default function ModalInput({
8699
onValueChange(updatedTags.map((tag) => tag.text));
87100
};
88101

102+
const handleKeydown = (e: KeyboardEvent<HTMLInputElement>) => {
103+
handleAddTag(e);
104+
105+
if (e.key === "Backspace" && tagInput === "") {
106+
const lastTag = tags[tags.length - 1];
107+
if (lastTag) {
108+
handleDeleteTag(lastTag.text);
109+
}
110+
}
111+
};
112+
89113
const handleDateChange = (date: Date | null) => {
90114
setSelectedDate(date);
91115
if (date) {
@@ -174,7 +198,7 @@ export default function ModalInput({
174198
value={tagInput}
175199
placeholder="입력 후 Enter"
176200
onChange={handleTagInputChange}
177-
onKeyDown={handleAddTag}
201+
onKeyDown={handleKeydown}
178202
className="flex-grow min-w-[100px] h-full
179203
border-none outline-none
180204
font-normal text-[14px] sm:text-[16px]

src/components/modalInput/chips/ColorTagChip.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import clsx from "clsx";
22

33
const tagColorSet = [
44
{
5-
textColor: "text-[var(--sortTextGreen)]",
6-
bgColor: "bg-[var(--sortTextBgGreen)]",
5+
textColor: "text-[var(--sortTextBlue)]",
6+
bgColor: "bg-[var(--sortTextBgBlue)]",
77
},
88
{
99
textColor: "text-[var(--sortTextPink)]",
1010
bgColor: "bg-[var(--sortTextBgPink)]",
1111
},
1212
{
13-
textColor: "text-[var(--sortTextBlue)]",
14-
bgColor: "bg-[var(--sortTextBgBlue)]",
13+
textColor: "text-[var(--sortTextGreen)]",
14+
bgColor: "bg-[var(--sortTextBgGreen)]",
1515
},
1616
{
1717
textColor: "text-[var(--sortTextOrange)]",

0 commit comments

Comments
 (0)