|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import { useState } from 'react'; |
4 | | -import BotMessage from './BotMessage'; |
5 | | -import UserMessage from './UserMessage'; |
6 | | -import MessageInput from './MessageInput'; |
| 3 | +import { useEffect, useRef, useState } from 'react'; |
| 4 | +import BotMessage from './bot/BotMessage'; |
| 5 | +import UserMessage from './user/UserMessage'; |
| 6 | +import NewMessageAlert from './bot/NewMessageAlert'; |
| 7 | +import MessageInput from './user/MessageInput'; |
| 8 | + |
| 9 | +// TODOS : 아직 api 몰라서 임시 type |
| 10 | +interface ChatMessage { |
| 11 | + id: number; |
| 12 | + message: string; |
| 13 | + sender: 'user' | 'bot'; |
| 14 | +} |
7 | 15 |
|
8 | 16 | function ChatSection() { |
9 | | - const [messages, setMessages] = useState<string[]>([]); |
| 17 | + const [messages, setMessages] = useState<ChatMessage[]>([]); |
| 18 | + const chatEndRef = useRef<HTMLDivElement>(null); |
| 19 | + const chatListRef = useRef<HTMLDivElement>(null); |
| 20 | + const isScrollBottom = useRef(true); |
| 21 | + const [showNewMessageAlert, setShowNewMessageAlert] = useState(false); |
10 | 22 |
|
11 | 23 | const handleSubmit = (message: string) => { |
12 | | - setMessages((prev) => [...prev, message]); |
| 24 | + // 사용자 메시지 |
| 25 | + setMessages((prev) => [...prev, { id: prev.length + 1, message, sender: 'user' }]); |
| 26 | + }; |
| 27 | + |
| 28 | + // 쑤리 임시 메시지 |
| 29 | + // useEffect(() => { |
| 30 | + // const interval = setInterval(() => { |
| 31 | + // setMessages((prev) => [ |
| 32 | + // ...prev, |
| 33 | + // { id: prev.length + 1, message: `새 메시지 ${prev.length + 1}`, sender: 'bot' }, |
| 34 | + // ]); |
| 35 | + // }, 1000); |
| 36 | + |
| 37 | + // return () => clearInterval(interval); |
| 38 | + // }, []); |
| 39 | + |
| 40 | + // 스크롤 제일 아래인지 체크 |
| 41 | + const handleCheckBottom = (e: React.UIEvent<HTMLDivElement>) => { |
| 42 | + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; |
| 43 | + |
| 44 | + isScrollBottom.current = scrollTop + clientHeight >= scrollHeight - 10; |
| 45 | + |
| 46 | + if (isScrollBottom.current) setShowNewMessageAlert(false); |
| 47 | + }; |
| 48 | + |
| 49 | + // 새 메시지가 들어오면 자동 스크롤 |
| 50 | + useEffect(() => { |
| 51 | + if (isScrollBottom.current) { |
| 52 | + chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
| 53 | + setShowNewMessageAlert(false); // 새메세지 숨김 |
| 54 | + } else { |
| 55 | + setShowNewMessageAlert(true); // 새메세지 보여줌 |
| 56 | + } |
| 57 | + }, [messages]); |
| 58 | + |
| 59 | + // 스크롤 제일 아래로 |
| 60 | + const handleScrollToBottom = () => { |
| 61 | + if (chatListRef.current) { |
| 62 | + chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
| 63 | + isScrollBottom.current = true; |
| 64 | + } |
13 | 65 | }; |
14 | 66 |
|
15 | 67 | return ( |
16 | | - <section className="page-layout max-w-1024 py-12 "> |
| 68 | + <section className="mx-auto w-full flex-1"> |
17 | 69 | <h2 className="sr-only">대화 목록 및 입력 창</h2> |
18 | | - <div className="flex flex-col gap-10 pb-20"> |
19 | | - <BotMessage /> |
| 70 | + <div |
| 71 | + ref={chatListRef} |
| 72 | + onScroll={handleCheckBottom} |
| 73 | + className="flex flex-col gap-10 pt-12 px-3 overflow-y-auto max-h-[calc(100vh-116px)] md:max-h-[calc(100vh-144px)]" |
| 74 | + > |
| 75 | + {messages.map(({ id, message, sender }) => |
| 76 | + sender === 'user' ? ( |
| 77 | + <UserMessage key={id} message={message} /> |
| 78 | + ) : ( |
| 79 | + <BotMessage key={id} messages={[{ id, type: 'text', message }]} /> |
| 80 | + ) |
| 81 | + )} |
20 | 82 |
|
21 | | - {messages.map((msg, i) => ( |
22 | | - <UserMessage key={i} message={msg} /> |
23 | | - ))} |
| 83 | + <div ref={chatEndRef}></div> |
| 84 | + {showNewMessageAlert && <NewMessageAlert onClick={handleScrollToBottom} />} |
24 | 85 | </div> |
25 | 86 | <MessageInput onSubmit={handleSubmit} /> |
26 | 87 | </section> |
|
0 commit comments