Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
W
WebFront
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
websystem
WebFront
Commits
57de5d9a
Commit
57de5d9a
authored
5 months ago
by
석찬 윤
Browse files
Options
Downloads
Plain Diff
Merge branch 'develop' into 'main'
fix: 모바일 뷰 오류 해결 See merge request
!58
parents
e5fa519e
a99513d7
No related branches found
No related tags found
1 merge request
!58
fix: 모바일 뷰 오류 해결
Pipeline
#11060
passed
5 months ago
Stage: build
Stage: deploy
Changes
2
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/components/Card.jsx
+3
-1
3 additions, 1 deletion
src/components/Card.jsx
src/components/ChattingDetail.jsx
+206
-147
206 additions, 147 deletions
src/components/ChattingDetail.jsx
with
209 additions
and
148 deletions
src/components/Card.jsx
+
3
−
1
View file @
57de5d9a
...
...
@@ -56,7 +56,9 @@ export default function Card({
return
(
<
div
className
=
{
cn
(
variantClass
)
}
onClick
=
{
onDetail
}
>
<
h3
className
=
"mb-2 text-xl font-bold"
>
{
title
}
</
h3
>
<
h3
className
=
"mb-2 text-xl font-bold"
onClick
=
{
onDetail
}
>
{
title
}
</
h3
>
<
Label
size
=
"sm"
theme
=
"black"
>
장소:
{
location
}
</
Label
>
...
...
This diff is collapsed.
Click to expand it.
src/components/ChattingDetail.jsx
+
206
−
147
View file @
57de5d9a
import
{
useParams
,
useLocation
}
from
'
react-router-dom
'
;
import
{
useEffect
,
useState
,
useRef
,
useCallback
,
useMemo
}
from
'
react
'
;
import
'
./ChattingDetail.css
'
;
import
styled
,
{
keyframes
}
from
'
styled-components
'
;
import
{
FaSearch
,
FaArrowUp
,
FaArrowDown
}
from
'
react-icons/fa
'
;
import
{
useParams
,
useLocation
}
from
"
react-router-dom
"
;
import
{
useEffect
,
useState
,
useRef
,
useCallback
,
useMemo
}
from
"
react
"
;
import
"
./ChattingDetail.css
"
;
import
styled
,
{
keyframes
}
from
"
styled-components
"
;
import
{
FaSearch
,
FaArrowUp
,
FaArrowDown
}
from
"
react-icons/fa
"
;
import
Button
from
"
../components/Button
"
;
import
ChattingNoticeDetailModal
from
'
../components/ChattingNoticeDetailModal
'
;
import
ChattingNoticeListModal
from
'
../components/ChattingNoticeListModal
'
;
import
ChattingNoticeDetailModal
from
"
../components/ChattingNoticeDetailModal
"
;
import
ChattingNoticeListModal
from
"
../components/ChattingNoticeListModal
"
;
// 웹소켓 서버 연결 URL
const
WS_URL
=
process
.
env
.
REACT_APP_WS_URL
;
...
...
@@ -69,26 +69,28 @@ const ChatRoomMessages = styled.div`
const
MessageContainer
=
styled
.
div
`
display: flex;
justify-content:
${(
props
)
=>
(
props
.
isMine
?
'
flex-end
'
:
'
flex-start
'
)}
;
justify-content:
${(
props
)
=>
(
props
.
isMine
?
"
flex-end
"
:
"
flex-start
"
)}
;
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
border:
${(
props
)
=>
(
props
.
highlighted
?
'
2px solid blue
'
:
'
none
'
)}
;
border:
${(
props
)
=>
(
props
.
highlighted
?
"
2px solid blue
"
:
"
none
"
)}
;
transition: background-color 0.3s ease, border 0.3s ease;
animation:
${(
props
)
=>
(
props
.
highlighted
?
shakeAnimation
:
'
none
'
)}
0.5s ease;
animation:
${(
props
)
=>
(
props
.
highlighted
?
shakeAnimation
:
"
none
"
)}
0.5s
ease;
`
;
const
MessageBubble
=
styled
.
div
`
max-width: 60%;
padding: 10px;
border-radius: 10px;
background-color:
${(
props
)
=>
(
props
.
isMine
?
'
#dcf8c6
'
:
'
#f9f9f9
'
)}
;
text-align:
${(
props
)
=>
(
props
.
isMine
?
'
right
'
:
'
left
'
)}
;
border:
${(
props
)
=>
(
props
.
highlighted
?
'
2px solid blue
'
:
'
none
'
)}
;
background-color:
${(
props
)
=>
(
props
.
isMine
?
"
#dcf8c6
"
:
"
#f9f9f9
"
)}
;
text-align:
${(
props
)
=>
(
props
.
isMine
?
"
right
"
:
"
left
"
)}
;
border:
${(
props
)
=>
(
props
.
highlighted
?
"
2px solid blue
"
:
"
none
"
)}
;
word-wrap: break-word;
transition: background-color 0.3s ease, border 0.3s ease;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
animation:
${(
props
)
=>
(
props
.
highlighted
?
shakeAnimation
:
'
none
'
)}
0.5s ease;
animation:
${(
props
)
=>
(
props
.
highlighted
?
shakeAnimation
:
"
none
"
)}
0.5s
ease;
`
;
const
CenteredMessage
=
styled
.
div
`
...
...
@@ -107,7 +109,7 @@ const CenteredMessage = styled.div`
const
MessageTimestamp
=
styled
.
div
`
font-size: 0.8em;
color: #888;
text-align:
${(
props
)
=>
(
props
.
isMine
?
'
right
'
:
'
left
'
)}
;
text-align:
${(
props
)
=>
(
props
.
isMine
?
"
right
"
:
"
left
"
)}
;
`
;
const
ChatRoomInput
=
styled
.
div
`
...
...
@@ -146,9 +148,11 @@ const FixedSearchBar = styled(SearchBar)`
`
;
const
NoticeContainer
=
styled
.
div
`
background-color:
${({
isCollapsed
})
=>
(
isCollapsed
?
"
transparent
"
:
"
#f8f9fa
"
)}
;
background-color:
${({
isCollapsed
})
=>
isCollapsed
?
"
transparent
"
:
"
#f8f9fa
"
}
;
padding:
${({
isCollapsed
})
=>
(
isCollapsed
?
"
0
"
:
"
10px
"
)}
;
border-bottom:
${({
isCollapsed
})
=>
(
isCollapsed
?
"
none
"
:
"
1px solid #ddd
"
)}
;
border-bottom:
${({
isCollapsed
})
=>
isCollapsed
?
"
none
"
:
"
1px solid #ddd
"
}
;
position: relative; /* 확성기 위치 조정을 위해 사용 */
`
;
...
...
@@ -198,8 +202,8 @@ function ChattingDetail() {
// const [notice, setNotice] = useState(null); // 공지 메시지
const
[
messages
,
setMessages
]
=
useState
([]);
const
[
input
,
setInput
]
=
useState
(
''
);
const
[
searchTerm
,
setSearchTerm
]
=
useState
(
''
);
const
[
input
,
setInput
]
=
useState
(
""
);
const
[
searchTerm
,
setSearchTerm
]
=
useState
(
""
);
const
[
searchResults
,
setSearchResults
]
=
useState
([]);
const
[
currentSearchIndex
,
setCurrentSearchIndex
]
=
useState
(
0
);
const
[
isSearching
,
setIsSearching
]
=
useState
(
false
);
...
...
@@ -272,12 +276,16 @@ function ChattingDetail() {
};
const
handleDismissNotice
=
()
=>
{
const
dismissedNotices
=
JSON
.
parse
(
localStorage
.
getItem
(
'
dismissedNotices
'
))
||
[];
const
dismissedNotices
=
JSON
.
parse
(
localStorage
.
getItem
(
"
dismissedNotices
"
))
||
[];
// 현재 공지사항이 로컬 스토리지에 추가되도록 처리
if
(
notice
&&
!
dismissedNotices
.
includes
(
notice
.
message
))
{
dismissedNotices
.
push
(
notice
.
message
);
localStorage
.
setItem
(
'
dismissedNotices
'
,
JSON
.
stringify
(
dismissedNotices
));
localStorage
.
setItem
(
"
dismissedNotices
"
,
JSON
.
stringify
(
dismissedNotices
)
);
}
setIsNoticeVisible
(
false
);
// 공지를 숨김
...
...
@@ -285,21 +293,24 @@ function ChattingDetail() {
const
fetchUnreadCounts
=
async
()
=>
{
try
{
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/unread-count/
${
chatRoomId
}
`
,
{
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/unread-count/
${
chatRoomId
}
`
,
{
method
:
"
GET
"
,
credentials
:
"
include
"
,
headers
:
{
"
Content-Type
"
:
"
application/json
"
,
},
});
}
);
if
(
response
.
ok
)
{
const
data
=
await
response
.
json
();
setChatUnread
(
data
);
}
else
{
console
.
error
(
'
Failed to fetch unread counts
'
);
console
.
error
(
"
Failed to fetch unread counts
"
);
}
}
catch
(
error
)
{
console
.
error
(
'
Error fetching unread counts:
'
,
error
);
console
.
error
(
"
Error fetching unread counts:
"
,
error
);
}
};
...
...
@@ -317,10 +328,12 @@ function ChattingDetail() {
}
try
{
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/update-status-and-logid`
,
{
method
:
'
POST
'
,
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/update-status-and-logid`
,
{
method
:
"
POST
"
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
"
Content-Type
"
:
"
application/json
"
,
},
body
:
JSON
.
stringify
({
chatRoomId
,
...
...
@@ -328,15 +341,18 @@ function ChattingDetail() {
isOnline
:
status
,
logId
:
logId
,
// 상태에 따라 logId도 함께 보냄
}),
});
}
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
'
Failed to update status and logId
'
);
throw
new
Error
(
"
Failed to update status and logId
"
);
}
console
.
log
(
`
${
nickname
}
: 상태업데이트 to
${
status
}
, logId updated to
${
logId
}
for `
);
console
.
log
(
`
${
nickname
}
: 상태업데이트 to
${
status
}
, logId updated to
${
logId
}
for `
);
}
catch
(
err
)
{
console
.
error
(
'
Error updating status and logId:
'
,
err
);
console
.
error
(
"
Error updating status and logId:
"
,
err
);
}
};
...
...
@@ -344,9 +360,8 @@ function ChattingDetail() {
e
.
preventDefault
();
if
(
window
.
confirm
(
"
이 메시지를 공지로 설정하시겠습니까?
"
))
{
try
{
const
noticeMessage
=
{
type
:
'
notice
'
,
// 메시지 유형: 공지사항 설정
type
:
"
notice
"
,
// 메시지 유형: 공지사항 설정
chatRoomId
:
chatRoomId
,
// 현재 채팅방 ID
nickname
:
loggedInUser
,
// 공지 설정한 사용자 닉네임
text
:
messageData
.
message
,
// 공지 내용
...
...
@@ -354,15 +369,14 @@ function ChattingDetail() {
// WebSocket을 통해 서버로 메시지 전송
ws
.
current
.
send
(
JSON
.
stringify
(
noticeMessage
));
console
.
log
(
'
공지사항 설정 메시지 전송:
'
,
noticeMessage
);
console
.
log
(
"
공지사항 설정 메시지 전송:
"
,
noticeMessage
);
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/
${
chatRoomId
}
/notices`
,
{
method
:
'
POST
'
,
method
:
"
POST
"
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
"
Content-Type
"
:
"
application/json
"
,
},
body
:
JSON
.
stringify
({
sender
:
messageData
.
sender
,
...
...
@@ -375,10 +389,10 @@ function ChattingDetail() {
const
newNotice
=
await
response
.
json
();
setNotice
(
newNotice
);
// 공지를 로컬 상태로 업데이트
}
else
{
console
.
error
(
'
Failed to set notice
'
);
console
.
error
(
"
Failed to set notice
"
);
}
}
catch
(
error
)
{
console
.
error
(
'
Error setting notice:
'
,
error
);
console
.
error
(
"
Error setting notice:
"
,
error
);
}
}
};
...
...
@@ -402,11 +416,14 @@ function ChattingDetail() {
const
fetchLatestNotice
=
async
()
=>
{
try
{
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/
${
chatRoomId
}
/notices/latest`
);
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/
${
chatRoomId
}
/notices/latest`
);
if
(
response
.
ok
)
{
const
latestNotice
=
await
response
.
json
();
const
dismissedNotices
=
JSON
.
parse
(
localStorage
.
getItem
(
'
dismissedNotices
'
))
||
[];
const
dismissedNotices
=
JSON
.
parse
(
localStorage
.
getItem
(
"
dismissedNotices
"
))
||
[];
const
isDismissed
=
dismissedNotices
.
includes
(
latestNotice
.
message
);
// 새로운 공지라면 표시하고, 숨겨진 공지가 아니라면 표시
...
...
@@ -417,18 +434,18 @@ function ChattingDetail() {
setNotice
(
null
);
setIsNoticeVisible
(
false
);
}
else
{
console
.
error
(
'
Failed to fetch latest notice
'
);
console
.
error
(
"
Failed to fetch latest notice
"
);
}
}
catch
(
error
)
{
console
.
error
(
'
Error fetching latest notice:
'
,
error
);
console
.
error
(
"
Error fetching latest notice:
"
,
error
);
}
};
const
scrollToHighlightedMessage
=
()
=>
{
if
(
highlightedMessageRef
.
current
)
{
highlightedMessageRef
.
current
.
scrollIntoView
({
behavior
:
'
smooth
'
,
block
:
'
center
'
behavior
:
"
smooth
"
,
block
:
"
center
"
,
});
}
};
...
...
@@ -448,19 +465,21 @@ function ChattingDetail() {
reconnectAttempts
=
0
;
// 재연결 성공 시 시도 횟수 초기화
const
joinMessage
=
JSON
.
stringify
({
type
:
'
join
'
,
type
:
"
join
"
,
chatRoomId
,
nickname
,
});
ws
.
current
.
send
(
joinMessage
);
console
.
log
(
`(클라이언트) WebSocket 연결 완료 - 채팅방:
${
chatRoomId
}
, 닉네임:
${
nickname
}
`
);
console
.
log
(
`(클라이언트) WebSocket 연결 완료 - 채팅방:
${
chatRoomId
}
, 닉네임:
${
nickname
}
`
);
};
ws
.
current
.
onmessage
=
(
event
)
=>
{
const
messageData
=
JSON
.
parse
(
event
.
data
);
console
.
log
(
"
받은 메시지
"
,
messageData
);
if
(
messageData
.
type
===
'
status
'
)
{
if
(
messageData
.
type
===
"
status
"
)
{
const
{
nickname
,
isOnline
}
=
messageData
;
if
(
!
isOnline
)
{
console
.
log
(
`
${
nickname
}
님이 오프라인 상태로 전환되었습니다.`
);
...
...
@@ -469,27 +488,30 @@ function ChattingDetail() {
return
;
}
if
(
messageData
.
type
===
'
notice
'
)
{
if
(
messageData
.
type
===
"
notice
"
)
{
// 공지사항 변경 이벤트 처리
console
.
log
(
'
새 공지사항 알림 수신:
'
,
messageData
);
console
.
log
(
"
새 공지사항 알림 수신:
"
,
messageData
);
setNotice
({
sender
:
messageData
.
sender
,
message
:
messageData
.
message
,
});
setIsNoticeVisible
(
true
);
// 공지를 표시하도록 설정
fetchLatestNotice
();
// 최신 공지사항 업데이트
}
else
if
(
messageData
.
type
===
'
previousMessages
'
)
{
}
else
if
(
messageData
.
type
===
"
previousMessages
"
)
{
// setMessages((prevMessages) => [...prevMessages, ...messageData.messages]);
setMessages
((
prevMessages
)
=>
{
const
messageIds
=
new
Set
(
prevMessages
.
map
((
msg
)
=>
msg
.
_id
));
// 기존 메시지의 ID 저장
const
newMessages
=
messageData
.
messages
.
filter
((
msg
)
=>
!
messageIds
.
has
(
msg
.
_id
));
// 중복 제거
const
newMessages
=
messageData
.
messages
.
filter
(
(
msg
)
=>
!
messageIds
.
has
(
msg
.
_id
)
);
// 중복 제거
return
[...
prevMessages
,
...
newMessages
];
});
}
else
{
// setMessages((prevMessages) => [...prevMessages, messageData]);
setMessages
((
prevMessages
)
=>
{
const
messageIds
=
new
Set
(
prevMessages
.
map
((
msg
)
=>
msg
.
_id
));
// 기존 메시지의 ID 저장
if
(
!
messageIds
.
has
(
messageData
.
_id
))
{
// 새 메시지가 중복되지 않으면 추가
if
(
!
messageIds
.
has
(
messageData
.
_id
))
{
// 새 메시지가 중복되지 않으면 추가
return
[...
prevMessages
,
messageData
];
}
return
prevMessages
;
...
...
@@ -500,8 +522,12 @@ function ChattingDetail() {
const
lastMessage
=
messageData
;
// 새로 받은 메시지
lastReadLogIdRef
.
current
=
lastMessage
.
_id
;
console
.
log
(
'
Received message logId:
'
,
lastReadLogIdRef
.
current
);
if
(
!
chatUnreadSortArray
.
length
||
chatUnreadSortArray
[
chatUnreadSortArray
.
length
-
1
][
1
]
<=
messageData
.
_id
)
{
console
.
log
(
"
Received message logId:
"
,
lastReadLogIdRef
.
current
);
if
(
!
chatUnreadSortArray
.
length
||
chatUnreadSortArray
[
chatUnreadSortArray
.
length
-
1
][
1
]
<=
messageData
.
_id
)
{
fetchUnreadCounts
(
chatRoomId
);
}
}
...
...
@@ -509,46 +535,57 @@ function ChattingDetail() {
};
ws
.
current
.
onclose
=
async
(
event
)
=>
{
console
.
warn
(
'
(클라이언트)WebSocket 연결이 종료되었습니다.
'
,
event
);
console
.
warn
(
"
(클라이언트)WebSocket 연결이 종료되었습니다.
"
,
event
);
// 탭이 활성화된 상태에서만 재연결 시도
if
(
isTabActiveRef
.
current
&&
reconnectAttempts
<
MAX_RECONNECT_ATTEMPTS
)
{
if
(
isTabActiveRef
.
current
&&
reconnectAttempts
<
MAX_RECONNECT_ATTEMPTS
)
{
setTimeout
(()
=>
{
console
.
log
(
`WebSocket 재연결 시도 (
${
reconnectAttempts
+
1
}
/
${
MAX_RECONNECT_ATTEMPTS
}
)`
);
console
.
log
(
`WebSocket 재연결 시도 (
${
reconnectAttempts
+
1
}
/
${
MAX_RECONNECT_ATTEMPTS
}
)`
);
reconnectAttempts
++
;
joinRoom
();
// 재연결 시도
},
2000
);
// 2초 후 재연결
}
else
if
(
!
isTabActiveRef
.
current
)
{
console
.
log
(
'
브라우저 탭이 비활성화 상태입니다. WebSocket 재연결을 시도하지 않습니다.
'
);
console
.
log
(
"
브라우저 탭이 비활성화 상태입니다. WebSocket 재연결을 시도하지 않습니다.
"
);
}
else
{
console
.
error
(
'
WebSocket 재연결 실패: 최대 시도 횟수를 초과했습니다.
'
);
console
.
error
(
"
WebSocket 재연결 실패: 최대 시도 횟수를 초과했습니다.
"
);
}
};
ws
.
current
.
onerror
=
(
error
)
=>
{
console
.
error
(
'
WebSocket 오류:
'
,
error
);
console
.
error
(
"
WebSocket 오류:
"
,
error
);
};
};
const
updateLastReadAt
=
async
()
=>
{
try
{
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/update-read-status`
,
{
method
:
'
POST
'
,
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/update-read-status`
,
{
method
:
"
POST
"
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
"
Content-Type
"
:
"
application/json
"
,
},
body
:
JSON
.
stringify
({
chatRoomId
,
nickname
}),
});
}
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
'
Failed to update lastReadAt
'
);
throw
new
Error
(
"
Failed to update lastReadAt
"
);
}
console
.
log
(
'
lastReadAt updated for
'
,
nickname
);
console
.
log
(
"
lastReadAt updated for
"
,
nickname
);
}
catch
(
err
)
{
console
.
error
(
'
Error updating lastReadAt:
'
,
err
);
console
.
error
(
"
Error updating lastReadAt:
"
,
err
);
}
};
// // 채팅방에서 퇴장
...
...
@@ -568,8 +605,10 @@ function ChattingDetail() {
// };
const
handleSearch
=
(
e
)
=>
{
if
(
e
.
key
===
'
Enter
'
)
{
const
results
=
messages
.
filter
((
message
)
=>
message
.
message
.
includes
(
searchTerm
));
if
(
e
.
key
===
"
Enter
"
)
{
const
results
=
messages
.
filter
((
message
)
=>
message
.
message
.
includes
(
searchTerm
)
);
setSearchResults
(
results
);
setCurrentSearchIndex
(
results
.
length
>
0
?
results
.
length
-
1
:
-
1
);
setIsSearching
(
true
);
...
...
@@ -580,13 +619,16 @@ function ChattingDetail() {
};
const
handleArrowDown
=
()
=>
{
if
(
searchResults
.
length
>
0
&&
currentSearchIndex
<
searchResults
.
length
-
1
)
{
if
(
searchResults
.
length
>
0
&&
currentSearchIndex
<
searchResults
.
length
-
1
)
{
setCurrentSearchIndex
((
prevIndex
)
=>
prevIndex
+
1
);
setTimeout
(()
=>
{
scrollToHighlightedMessage
();
},
0
);
}
else
{
alert
(
'
더 이상 검색결과가 없습니다.
'
);
alert
(
"
더 이상 검색결과가 없습니다.
"
);
}
};
...
...
@@ -597,14 +639,14 @@ function ChattingDetail() {
scrollToHighlightedMessage
();
},
0
);
}
else
{
alert
(
'
더 이상 검색결과가 없습니다.
'
);
alert
(
"
더 이상 검색결과가 없습니다.
"
);
}
};
const
sendMessage
=
()
=>
{
if
(
input
.
trim
())
{
const
message
=
JSON
.
stringify
({
type
:
'
message
'
,
type
:
"
message
"
,
chatRoomId
,
sender
:
loggedInUser
,
nickname
,
...
...
@@ -613,15 +655,15 @@ function ChattingDetail() {
try
{
ws
.
current
.
send
(
message
);
console
.
log
(
'
전송한 메시지:
'
,
message
);
console
.
log
(
"
전송한 메시지:
"
,
message
);
updateLastReadAt
();
}
catch
(
error
)
{
console
.
error
(
'
메시지 전송 오류:
'
,
error
);
console
.
error
(
"
메시지 전송 오류:
"
,
error
);
}
setInput
(
''
);
setInput
(
""
);
}
else
{
alert
(
'
메시지를 입력하세요.
'
);
alert
(
"
메시지를 입력하세요.
"
);
}
};
...
...
@@ -637,7 +679,7 @@ function ChattingDetail() {
const
sendHeartbeat
=
()
=>
{
if
(
ws
.
current
&&
ws
.
current
.
readyState
===
WebSocket
.
OPEN
)
{
const
heartbeatMessage
=
JSON
.
stringify
({
type
:
'
heartbeat
'
,
type
:
"
heartbeat
"
,
chatRoomId
,
nickname
,
});
...
...
@@ -662,10 +704,10 @@ function ChattingDetail() {
updateLastReadAt
();
// 탭 상태가 변경되면 Heartbeat 중단/재개
document
.
addEventListener
(
'
visibilitychange
'
,
()
=>
{
if
(
document
.
visibilityState
===
'
hidden
'
)
{
document
.
addEventListener
(
"
visibilitychange
"
,
()
=>
{
if
(
document
.
visibilityState
===
"
hidden
"
)
{
stopHeartbeat
();
}
else
if
(
document
.
visibilityState
===
'
visible
'
)
{
}
else
if
(
document
.
visibilityState
===
"
visible
"
)
{
startHeartbeat
();
}
});
...
...
@@ -679,20 +721,22 @@ function ChattingDetail() {
useEffect
(()
=>
{
// eslint-disable-next-line react-hooks/exhaustive-deps
const
handleVisibilityChange
=
()
=>
{
if
(
document
.
visibilityState
===
'
hidden
'
)
{
// 브라우저 탭이 비활성화되었을 때
if
(
document
.
visibilityState
===
"
hidden
"
)
{
// 브라우저 탭이 비활성화되었을 때
// isTabActive = false; // 탭 비활성화 상태로 설정
isTabActiveRef
.
current
=
false
;
// 탭 비활성화 상태로 설정
updateUserStatusAndLogId
(
false
);
if
(
ws
.
current
)
{
ws
.
current
.
close
();
// WebSocket 연결 종료
console
.
log
(
'
브라우저 탭 비활성화: WebSocket 연결 종료
'
);
console
.
log
(
"
브라우저 탭 비활성화: WebSocket 연결 종료
"
);
}
}
else
if
(
document
.
visibilityState
===
'
visible
'
)
{
// 브라우저 탭이 다시 활성화되었을 때
}
else
if
(
document
.
visibilityState
===
"
visible
"
)
{
// 브라우저 탭이 다시 활성화되었을 때
// isTabActive = true; // 탭 활성화 상태로 설정
isTabActiveRef
.
current
=
false
;
// 탭 비활성화 상태로 설정
updateUserStatusAndLogId
(
true
);
if
(
!
ws
.
current
||
ws
.
current
.
readyState
===
WebSocket
.
CLOSED
)
{
console
.
log
(
'
브라우저 탭 활성화: WebSocket 연결 재시작
'
);
console
.
log
(
"
브라우저 탭 활성화: WebSocket 연결 재시작
"
);
joinRoom
();
// WebSocket 재연결 시도
}
}
...
...
@@ -713,8 +757,8 @@ function ChattingDetail() {
},
[
currentSearchIndex
,
searchResults
.
length
]);
useEffect
(()
=>
{
console
.
log
(
'
chatRoomId:
'
,
chatRoomId
);
console
.
log
(
'
nickname:
'
,
nickname
);
console
.
log
(
"
chatRoomId:
"
,
chatRoomId
);
console
.
log
(
"
nickname:
"
,
nickname
);
setLoggedInUser
(
nickname
);
},
[
chatRoomId
,
nickname
]);
...
...
@@ -727,15 +771,17 @@ function ChattingDetail() {
// 공지사항 목록 가져오기
const
fetchNotices
=
async
()
=>
{
try
{
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/
${
chatRoomId
}
/notices`
);
const
response
=
await
fetch
(
`
${
process
.
env
.
REACT_APP_BASE_URL
}
/api/chat/
${
chatRoomId
}
/notices`
);
if
(
response
.
ok
)
{
const
data
=
await
response
.
json
();
setNotices
(
data
);
// 공지사항 목록 업데이트
}
else
{
console
.
error
(
'
Failed to fetch notices
'
);
console
.
error
(
"
Failed to fetch notices
"
);
}
}
catch
(
error
)
{
console
.
error
(
'
Error fetching notices:
'
,
error
);
console
.
error
(
"
Error fetching notices:
"
,
error
);
}
};
fetchNotices
();
...
...
@@ -743,7 +789,7 @@ function ChattingDetail() {
useEffect
(()
=>
{
// 공지가 변경되면 렌더링
console
.
log
(
'
공지사항 업데이트:
'
,
notice
);
console
.
log
(
"
공지사항 업데이트:
"
,
notice
);
},
[
notice
]);
useEffect
(()
=>
{
...
...
@@ -754,12 +800,12 @@ function ChattingDetail() {
}
};
window
.
addEventListener
(
'
beforeunload
'
,
handleBeforeUnload
);
// 브라우저 종료/새로고침 이벤트 리스너 추가
window
.
addEventListener
(
"
beforeunload
"
,
handleBeforeUnload
);
// 브라우저 종료/새로고침 이벤트 리스너 추가
return
()
=>
{
console
.
log
(
"
페이지 이동시 상태 업데이트
"
);
updateUserStatusAndLogId
(
false
);
// 페이지 이동 시 상태 업데이트
window
.
removeEventListener
(
'
beforeunload
'
,
handleBeforeUnload
);
// 컴포넌트 언마운트 시 이벤트 리스너 제거
window
.
removeEventListener
(
"
beforeunload
"
,
handleBeforeUnload
);
// 컴포넌트 언마운트 시 이벤트 리스너 제거
if
(
ws
.
current
)
{
ws
.
current
.
close
();
// WebSocket 연결 종료
}
...
...
@@ -770,21 +816,26 @@ function ChattingDetail() {
useEffect
(()
=>
{
const
lastMessage
=
messages
[
messages
.
length
-
1
];
localStorage
.
setItem
(
`loadedPreviousMessages-
${
chatRoomId
}
`
,
"
true
"
);
localStorage
.
setItem
(
`loadedPreviousMessages-
${
chatRoomId
}
`
,
"
true
"
);
const
hasLoadedPreviousMessages
=
localStorage
.
getItem
(
`loadedPreviousMessages-
${
chatRoomId
}
`
);
if
(
!
hasLoadedPreviousMessages
&&
lastMessage
&&
lastMessage
.
type
===
"
previousMessages
"
)
{
if
(
!
hasLoadedPreviousMessages
&&
lastMessage
&&
lastMessage
.
type
===
"
previousMessages
"
)
{
return
;
// 이전 메시지일 경우 자동 스크롤 방지
}
// 새 메시지 수신 시 자동으로 아래로 스크롤
if
(
lastMessage
&&
lastMessage
.
type
===
"
message
"
&&
messagesEndRef
.
current
)
{
if
(
lastMessage
&&
lastMessage
.
type
===
"
message
"
&&
messagesEndRef
.
current
)
{
messagesEndRef
.
current
.
scrollIntoView
({
behavior
:
"
smooth
"
});
}
},
[
messages
,
chatRoomId
]);
...
...
@@ -792,12 +843,15 @@ function ChattingDetail() {
const
highlightSearchTerm
=
(
message
)
=>
{
if
(
!
searchTerm
)
return
message
;
const
regex
=
new
RegExp
(
`(
${
searchTerm
}
)`
,
'
gi
'
);
const
regex
=
new
RegExp
(
`(
${
searchTerm
}
)`
,
"
gi
"
);
const
parts
=
message
.
split
(
regex
);
return
parts
.
map
((
part
,
index
)
=>
part
.
toLowerCase
()
===
searchTerm
.
toLowerCase
()
?
(
<
span
key
=
{
index
}
style
=
{
{
backgroundColor
:
'
yellow
'
,
fontWeight
:
'
bold
'
}
}
>
<
span
key
=
{
index
}
style
=
{
{
backgroundColor
:
"
yellow
"
,
fontWeight
:
"
bold
"
}
}
>
{
part
}
</
span
>
)
:
(
...
...
@@ -809,7 +863,7 @@ function ChattingDetail() {
return
(
<
ChatRoomContainer
>
<
ChatRoomHeader
>
<
h1
className
=
"
heading-1
text-gray-800"
>
{
chatRoomName
}
</
h1
>
<
h1
className
=
"text-gray-800
heading-1
"
>
{
chatRoomName
}
</
h1
>
{
/* 검색 버튼 */
}
<
Button
size
=
"lg"
...
...
@@ -937,13 +991,12 @@ function ChattingDetail() {
const
sameSenderAsNext
=
nextMessage
&&
nextMessage
.
sender
===
messageData
.
sender
;
const
messageTime
=
new
Date
(
messageData
.
timestamp
).
toLocaleTimeString
(
[],
{
const
messageTime
=
new
Date
(
messageData
.
timestamp
).
toLocaleTimeString
([],
{
hour
:
"
2-digit
"
,
minute
:
"
2-digit
"
,
}
);
});
const
prevMessageTime
=
prevMessage
&&
...
...
@@ -959,7 +1012,9 @@ function ChattingDetail() {
if
(
messageData
.
type
===
"
join
"
||
messageData
.
type
===
"
leave
"
)
{
return
(
<
CenteredMessage
key
=
{
index
}
>
{
messageData
.
message
}
</
CenteredMessage
>
<
CenteredMessage
key
=
{
index
}
>
{
messageData
.
message
}
</
CenteredMessage
>
);
}
...
...
@@ -983,7 +1038,9 @@ function ChattingDetail() {
<
MessageContainer
isMine
=
{
isMine
}
highlighted
=
{
searchResults
[
currentSearchIndex
]
===
messageData
}
highlighted
=
{
searchResults
[
currentSearchIndex
]
===
messageData
}
ref
=
{
searchResults
[
currentSearchIndex
]
===
messageData
?
highlightedMessageRef
...
...
@@ -1019,7 +1076,9 @@ function ChattingDetail() {
<
MessageBubble
isMine
=
{
isMine
}
highlighted
=
{
searchResults
[
currentSearchIndex
]
===
messageData
}
highlighted
=
{
searchResults
[
currentSearchIndex
]
===
messageData
}
onContextMenu
=
{
(
e
)
=>
handleRightClick
(
e
,
messageData
)
}
style
=
{
{
textAlign
:
"
left
"
,
...
...
@@ -1087,7 +1146,7 @@ function ChattingDetail() {
theme
=
"pink"
state
=
{
input
.
trim
()
?
"
default
"
:
"
disable
"
}
onClick
=
{
sendMessage
}
className
=
"
ml-3
flex items-center justify-center whitespace-nowrap
w-16
"
// w-32로 버튼 너비 설정
className
=
"flex items-center justify-center
flex-1 w-16 ml-3 tablet:flex-initial
whitespace-nowrap"
// w-32로 버튼 너비 설정
>
전송
</
Button
>
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment