Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions src/entities/chat/model/mock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ChatMessage } from "../ui/MessageRow/MessageRow";
export const chats = [
{
chatId: 501,
Expand Down Expand Up @@ -60,3 +61,118 @@ export const chats = [
otherImage: "",
},
];

export const mockMessages: ChatMessage[] = [
// 🔹 [1] 첫 메시지 — 상대방 (프로필 보임, 시간 표시)
{
id: 1,
type: "text",
content: "안녕하세요! 이 물건 아직 있나요?",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:00:00Z",
},

// 🔹 [2] 내 메시지 (시간 차 있음 → 시간 표시)
{
id: 2,
type: "text",
content: "네, 아직 있습니다 🙂",
isMine: true,
sendAt: "2025-10-31T10:01:00Z",
},

// 🔹 [3] 상대방 이미지 메시지 (같은 유저, 같은 분 안 → 프로필 X, 시간 X)
{
id: 3,
type: "image",
content:
"https://chalddackimage.blob.core.windows.net/chalddackimage/150100000286_03.webp",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:01:30Z",
},

// 🔹 [4] 상대방 텍스트 메시지 (같은 유저, 같은 분 → 프로필 X, 마지막 → 시간 표시)
{
id: 4,
type: "text",
content: "좋아요, 거래 원해요!",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:02:00Z",
},

// 🔹 [5] 내 메시지 여러 개 (같은 분 → 앞은 showTime=false, 마지막만 true)
{
id: 5,
type: "text",
content: "좋아요. 어디서 거래할까요?",
isMine: true,
sendAt: "2025-10-31T10:03:10Z",
},
{
id: 6,
type: "text",
content: "저는 강남역 근처 가능합니다.",
isMine: true,
sendAt: "2025-10-31T10:03:40Z",
},
{
id: 7,
type: "text",
content: "시간은 언제쯤 괜찮으세요?",
isMine: true,
sendAt: "2025-10-31T10:04:00Z",
},

// 🔹 [8] 상대방 메시지 (새로운 유저 → 프로필 보임, 시간 표시)
{
id: 8,
type: "text",
content: "오후 3시쯤 어떠세요?",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:05:00Z",
},

// 🔹 [9] 내 메시지 (시간 간격 큼 → 시간 표시)
{
id: 9,
type: "text",
content: "좋습니다. 그때 뵐게요!",
isMine: true,
sendAt: "2025-10-31T10:10:00Z",
},

// 🔹 [10] 날짜 변경 (새로운 날짜 → 날짜 구분선 확인용)
{
id: 10,
type: "text",
content: "안녕하세요, 어제 말씀드린 거래건입니다.",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-11-01T09:55:00Z",
},
{
id: 11,
type: "text",
content: "오늘 일정 그대로죠?",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-11-01T09:56:00Z",
},
{
id: 12,
type: "text",
content: "네, 맞아요! 오후 3시 강남역에서 봬요.",
isMine: true,
sendAt: "2025-11-01T09:57:00Z",
},
];
144 changes: 106 additions & 38 deletions src/entities/chat/ui/ChattingRoom/ChattingRoom.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,74 @@
"use client";
import React, { useState, useRef, useEffect } from "react";
import { MessageRow, MessageRowProps } from "../MessageRow/MessageRow";
import { Message } from "../Message/Message";
import { mockMessages } from "../../model/mock";
import Button from "@/shared/ui/Button/Button";
import { TextField } from "@/shared/ui/TextField/TextField";
import ImageSelectIcon from "@/shared/images/image-select.svg";
import DeleteIcon from "@/shared/images/delete.svg";

interface ChatMessage {
id: number;
type: "text" | "image";
content: string;
isMine: boolean;
}

export const ChattingRoom = () => {
const number = 50000;
const [messages, setMessages] = useState<ChatMessage[]>([
{
id: 1,
type: "text",
content: "안녕하세요! 이 물건 아직 있나요?",
isMine: false,
},
{ id: 2, type: "text", content: "네, 아직 있습니다 🙂", isMine: true },
{
id: 3,
type: "image",
content:
"https://chalddackimage.blob.core.windows.net/chalddackimage/150100000286_03.webp",
isMine: false,
},
{ id: 4, type: "text", content: "좋아요, 거래 원해요!", isMine: false },
]);
const [messages, setMessages] = useState<MessageRowProps[]>([]);

const [text, setText] = useState("");
const [image, setImage] = useState<File | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);

const formatTime = (time: string) =>
new Date(time).toLocaleTimeString("ko-KR", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
});

const formatDate = (time: string) => {
const date = new Date(time);
return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`;
};

useEffect(() => {
const computedMessages: MessageRowProps[] = [];
let lastDate: string | null = null;

mockMessages.forEach((msg, i) => {
const prev = mockMessages[i - 1];
const next = mockMessages[i + 1];

const showProfile = !msg.isMine && (!prev || prev.isMine);

const currentTime = formatTime(msg.sendAt);
const nextTime = next ? formatTime(next.sendAt) : null;
const showTime =
!next || next.isMine !== msg.isMine || nextTime !== currentTime;

const currentDate = formatDate(msg.sendAt);
if (lastDate !== currentDate) {
computedMessages.push({
message: {
id: Date.now(),
type: "system",
content: currentDate,
isMine: false,
sendAt: msg.sendAt,
},
showProfile: false,
showTime: false,
});
lastDate = currentDate;
}

computedMessages.push({
message: { ...msg },
showProfile,
showTime,
});
});

setMessages(computedMessages);
}, []);

useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
Expand All @@ -57,13 +89,54 @@ export const ChattingRoom = () => {
};

const sendMessage = (type: "text" | "image", content: string) => {
const newMessage: ChatMessage = {
id: Date.now(),
type,
content,
isMine: true,
};
setMessages((prev) => [...prev, newMessage]);
const now = new Date();
const sendAt = now.toISOString();
const currentDate = formatDate(sendAt);

setMessages((prev) => {
const updatedPrev = [...prev];

const last = prev[prev.length - 1].message;
const lastDate = last ? formatDate(last.sendAt) : null;
const newMsgTime = formatTime(sendAt);
const lastMsgTime = last ? formatTime(last.sendAt) : null;

if (lastDate !== currentDate) {
updatedPrev.push({
message: {
id: Date.now() + new Date(last?.sendAt).getTime(),
type: "system",
content: currentDate,
isMine: false,
sendAt,
},
showProfile: false,
showTime: false,
});
}

const newMessageRow: MessageRowProps = {
message: {
id: Date.now(),
type,
content,
isMine: true,
sendAt,
},
showProfile: false,
showTime: true,
};

//직전 메시지가 같은 시간이라면 showTime false로 수정
if (last?.isMine === true && lastMsgTime === newMsgTime) {
updatedPrev[updatedPrev.length - 1] = {
...updatedPrev[updatedPrev.length - 1],
showTime: false,
};
}

return [...updatedPrev, newMessageRow];
});
};

const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -97,13 +170,8 @@ export const ChattingRoom = () => {
{/* 메시지 리스트 영역 */}
<div className="absolute top-[100px] bottom-[100px] left-0 w-full overflow-y-auto p-4">
<div className="flex flex-col gap-4">
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.isMine ? "justify-end" : "justify-start"} gap-4`}
>
<Message {...message} />
</div>
{messages.map((row) => (
<MessageRow key={row.message.id} {...row} />
))}
</div>
<div ref={messagesEndRef} />
Expand Down
16 changes: 7 additions & 9 deletions src/entities/chat/ui/Message/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import cn from "@/shared/lib/cn";
import Image from "next/image";

export interface MessageProps {
type: "text" | "image";
Expand All @@ -10,26 +9,25 @@ export interface MessageProps {

export const Message = ({ type, content, isMine }: MessageProps) => {
const messageBase = cn(
"max-w-[80%] break-words rounded-2xl px-4 py-2 text-white",
"text-[14px] xl:text-[16px]",
"break-words rounded-2xl px-4 py-2 text-white text-[14px]",
isMine ? "bg-gradient-to-r from-[#5097fa] to-[#5363ff]" : "bg-[#9fa6b2]",
);

return (
<>
{type === "image" ? (
<Image
<img
src={content}
width={200}
height={200}
alt="chat image"
className="h-auto max-w-[50%] rounded-2xl object-cover"
className={cn(
"block rounded-2xl object-cover",
"h-auto w-auto max-w-[250px]", // 비율 유지
isMine ? "ml-auto" : "mr-auto",
)}
/>
) : (
<div className={messageBase}>{content}</div>
)}
</>
);
};

export default Message;
Loading