diff --git a/src/entities/chat/model/mock.ts b/src/entities/chat/model/mock.ts index 04f3a9fc..73a6d108 100644 --- a/src/entities/chat/model/mock.ts +++ b/src/entities/chat/model/mock.ts @@ -1,3 +1,4 @@ +import { ChatMessage } from "../ui/MessageRow/MessageRow"; export const chats = [ { chatId: 501, @@ -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", + }, +]; diff --git a/src/entities/chat/ui/ChattingRoom/ChattingRoom.tsx b/src/entities/chat/ui/ChattingRoom/ChattingRoom.tsx index e2543815..5ede7c05 100644 --- a/src/entities/chat/ui/ChattingRoom/ChattingRoom.tsx +++ b/src/entities/chat/ui/ChattingRoom/ChattingRoom.tsx @@ -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([ - { - 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([]); const [text, setText] = useState(""); const [image, setImage] = useState(null); const messagesEndRef = useRef(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]); @@ -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) => { @@ -97,13 +170,8 @@ export const ChattingRoom = () => { {/* ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ ์˜์—ญ */}
- {messages.map((message) => ( -
- -
+ {messages.map((row) => ( + ))}
diff --git a/src/entities/chat/ui/Message/Message.tsx b/src/entities/chat/ui/Message/Message.tsx index 7e7eebe5..0e3620b3 100644 --- a/src/entities/chat/ui/Message/Message.tsx +++ b/src/entities/chat/ui/Message/Message.tsx @@ -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"; @@ -10,20 +9,21 @@ 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" ? ( - chat image ) : (
{content}
@@ -31,5 +31,3 @@ export const Message = ({ type, content, isMine }: MessageProps) => { ); }; - -export default Message; diff --git a/src/entities/chat/ui/MessageRow/MessageRow.tsx b/src/entities/chat/ui/MessageRow/MessageRow.tsx new file mode 100644 index 00000000..2238f5e5 --- /dev/null +++ b/src/entities/chat/ui/MessageRow/MessageRow.tsx @@ -0,0 +1,72 @@ +import { Message } from "../Message/Message"; + +export interface ChatMessage { + id: number; + type: "text" | "image" | "system"; + content: string; + isMine: boolean; + profileImage?: string; + sendAt: string; +} + +export interface MessageRowProps { + message: ChatMessage; + showProfile: boolean; + showTime: boolean; +} + +export const MessageRow = ({ + message, + showProfile, + showTime, +}: MessageRowProps) => { + const timeText = new Date(message.sendAt).toLocaleTimeString("ko-KR", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); + + if (message.type === "system") { + return ( +
+
+ {message.content} +
+
+ ); + } + + return ( +
+
+ {!message.isMine && showProfile && ( + ํ”„๋กœํ•„ + )} + + + + {showTime && ( + + {timeText} + + )} +
+
+ ); +};