383 lines
14 KiB
JavaScript
383 lines
14 KiB
JavaScript
const socket = io();
|
|
|
|
// UI Elements
|
|
const chatMessages = document.getElementById('chat-messages');
|
|
const messageInput = document.getElementById('message-input');
|
|
const sendBtn = document.getElementById('send-btn');
|
|
const authToggleBtn = document.getElementById('auth-toggle-btn');
|
|
const loginArea = document.getElementById('login-area');
|
|
const chatInputArea = document.getElementById('chat-input-area');
|
|
const loginSubmitBtn = document.getElementById('login-submit-btn');
|
|
const loginCancelBtn = document.getElementById('login-cancel-btn');
|
|
const imageBtn = document.getElementById('image-btn');
|
|
const imageInput = document.getElementById('image-input');
|
|
const replyInfo = document.getElementById('reply-info');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const roomListContainer = document.getElementById('room-list');
|
|
const roomSearchInput = document.getElementById('room-search');
|
|
const headerAvatar = document.getElementById('header-avatar');
|
|
const targetNameLabel = document.getElementById('target-name');
|
|
const targetStatusLabel = document.getElementById('target-status');
|
|
const exitReplyBtn = document.getElementById('exit-reply-btn');
|
|
const signupShowBtn = document.getElementById('signup-show-btn');
|
|
|
|
// Unique Anonymous ID generation/recovery
|
|
function getAnonymousId() {
|
|
let id = localStorage.getItem('chat_anon_id');
|
|
if (!id) {
|
|
id = 'anon_' + Math.random().toString(36).substr(2, 9);
|
|
localStorage.setItem('chat_anon_id', id);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
const isAdminPath = window.location.pathname.includes('/admin');
|
|
|
|
// Load User from LocalStorage or create Anonymous
|
|
let savedUser = localStorage.getItem('chat_user');
|
|
let currentUser;
|
|
|
|
// CI4 Session Bridge: CI4 뷰에서 넘겨준 세션 정보가 있는지 확인
|
|
// CI4 PHP 예시: window.chatConfig = { isLog-in: <?= session()->get('ISLGIN') ?>, auth: <?= json_encode(session()->get('AUTH')) ?> };
|
|
if (window.chatConfig && window.chatConfig.ISLGIN) {
|
|
const auth = window.chatConfig.AUTH;
|
|
currentUser = {
|
|
id: auth.id || auth.uid,
|
|
username: auth.name || auth.id,
|
|
role: auth.role || 'user'
|
|
};
|
|
// 세션 정보가 있으면 로컬 스토리지도 동기화 (선택 사항)
|
|
localStorage.setItem('chat_user', JSON.stringify(currentUser));
|
|
} else if (savedUser) {
|
|
currentUser = JSON.parse(savedUser);
|
|
} else {
|
|
currentUser = { id: getAnonymousId(), username: '익명', role: 'guest' };
|
|
}
|
|
|
|
if (currentUser && currentUser.role !== 'guest') {
|
|
if (authToggleBtn) authToggleBtn.textContent = '로그아웃 (' + currentUser.username + ')';
|
|
}
|
|
|
|
let currentRoom = (isAdminPath && isConsultantRole(currentUser.role))
|
|
? 'consultants_group'
|
|
: 'room_' + currentUser.id;
|
|
let currentConsultant = null;
|
|
|
|
// Initialize
|
|
if (isAdminPath) {
|
|
const headerTitle = document.querySelector('#chat-header span');
|
|
if (headerTitle) headerTitle.innerText = 'IDC 상담원 관리 시스템';
|
|
|
|
// 상담원 채널 접속 시 로그인 영역 자동 표시 및 사이드바 노출 준비
|
|
if (currentUser.role === 'guest') {
|
|
loginArea.style.display = 'flex';
|
|
} else if (isConsultantRole(currentUser.role)) {
|
|
if (sidebar) sidebar.style.display = 'flex';
|
|
}
|
|
} else {
|
|
// 일반 채팅 경로(/chat)
|
|
authToggleBtn.innerText = '상담원 로그인';
|
|
if (sidebar) sidebar.style.display = 'none';
|
|
if (replyInfo) replyInfo.style.display = 'none';
|
|
|
|
// 일반 사용자용 화면은 너비를 컴팩트하게 조정 (기존 1000px의 70% 수준인 700px)
|
|
const chatWidget = document.getElementById('chat-widget');
|
|
if (chatWidget) chatWidget.style.width = '700px';
|
|
}
|
|
|
|
socket.emit('join_room', { room: currentRoom, user_id: currentUser.id, role: currentUser.role });
|
|
|
|
// 룸 목록 업데이트 수신 (상담원 전용)
|
|
socket.on('update_room_list', (roomList) => {
|
|
if (!roomListContainer) return;
|
|
|
|
allRoomsData = roomList; // 검색 필터링을 위해 저장
|
|
renderRoomList(allRoomsData);
|
|
});
|
|
|
|
let allRoomsData = [];
|
|
|
|
function getAvatarColor(name) {
|
|
const colors = ['#0078d4', '#107c10', '#d13438', '#008575', '#8764b8', '#004e8c'];
|
|
let hash = 0;
|
|
for (let i = 0; i < name.length; i++) {
|
|
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
}
|
|
return colors[Math.abs(hash) % colors.length];
|
|
}
|
|
|
|
function renderRoomList(rooms) {
|
|
// 기본 모니터링 룸 추가
|
|
let listHtml = `
|
|
<div class="room-item ${currentRoom === 'consultants_group' ? 'active' : ''}" onclick="exitReplyMode()">
|
|
<div class="avatar" style="background: #605e5c;">📢</div>
|
|
<div class="room-info">
|
|
<span class="name">전체 모니터링</span>
|
|
<span class="preview">모든 실시간 대화 감시</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
rooms.forEach(roomInfo => {
|
|
const isActive = currentRoom === roomInfo.room;
|
|
const initial = roomInfo.username.charAt(0);
|
|
const avatarColor = getAvatarColor(roomInfo.username);
|
|
|
|
listHtml += `
|
|
<div class="room-item ${isActive ? 'active' : ''}" onclick="joinUserRoom('${roomInfo.room}', '${roomInfo.username}')">
|
|
<div class="avatar" style="background: ${avatarColor};">${initial}</div>
|
|
<div class="room-info">
|
|
<span class="name">${roomInfo.username}</span>
|
|
<span class="preview">최근 메시지 확인 중...</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
roomListContainer.innerHTML = listHtml;
|
|
}
|
|
|
|
// 룸 검색 필터링 제거 (사용자 요청)
|
|
// if (roomSearchInput) { ... }
|
|
|
|
// Send Message
|
|
async function sendMessage(text = null, imageUrl = null) {
|
|
const message = text || messageInput.value.trim();
|
|
if (message || imageUrl) {
|
|
const data = {
|
|
room: currentRoom,
|
|
sender: currentUser.username,
|
|
user_code: currentUser.id,
|
|
consultant_code: currentConsultant,
|
|
message: imageUrl || message,
|
|
msg_type: imageUrl ? 'image' : 'text',
|
|
role: currentUser.role, // Add role to metadata
|
|
timestamp: new Date().toLocaleTimeString()
|
|
};
|
|
socket.emit('send_message', data);
|
|
if (!imageUrl) messageInput.value = '';
|
|
}
|
|
}
|
|
|
|
sendBtn.addEventListener('click', () => sendMessage());
|
|
messageInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') sendMessage();
|
|
});
|
|
|
|
// Image Upload Handling
|
|
imageBtn.addEventListener('click', () => imageInput.click());
|
|
imageInput.addEventListener('change', async (e) => {
|
|
const file = e.target.files[0];
|
|
if (file) await uploadAndSendImage(file);
|
|
imageInput.value = '';
|
|
});
|
|
|
|
// Clipboard Paste Handling (Screen Capture)
|
|
messageInput.addEventListener('paste', async (e) => {
|
|
const clipboardData = e.clipboardData || window.clipboardData;
|
|
if (!clipboardData) return;
|
|
|
|
const items = clipboardData.items;
|
|
let imageFound = false;
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
if (items[i].type.indexOf('image') !== -1) {
|
|
const file = items[i].getAsFile();
|
|
if (file) {
|
|
imageFound = true;
|
|
console.log('이미지 붙여넣기 감지됨:', file.name);
|
|
await uploadAndSendImage(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 이미지를 전송했다면 텍스트박스에 텍스트가 중복으로 들어가는 것을 방지하고 싶을 때 사용 (선택 사항)
|
|
// if (imageFound) e.preventDefault();
|
|
});
|
|
|
|
async function uploadAndSendImage(file) {
|
|
const formData = new FormData();
|
|
formData.append('image', file);
|
|
console.log('이미지 업로드 시도 중:', file.name);
|
|
|
|
try {
|
|
const response = await fetch('/api/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`서버 응답 오류: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
console.log('이미지 업로드 성공:', result.imageUrl);
|
|
await sendMessage(null, result.imageUrl);
|
|
} else {
|
|
alert('이미지 업로드 실패: ' + result.message);
|
|
}
|
|
} catch (err) {
|
|
console.error('이미지 업로드 프로세스 오류:', err);
|
|
alert('이미지 전송 중 오류가 발생했습니다. 서버 로그를 확인하거나 다시 시도해 주세요.');
|
|
}
|
|
}
|
|
|
|
// Helper: 상담원 역할 확인
|
|
function isConsultantRole(role) {
|
|
if (!role) return false;
|
|
const consultantKeywords = ['manage', 'manager', 'cloudflare', 'security', 'director', 'master', 'admin', 'consultant'];
|
|
const roles = String(role).toLowerCase();
|
|
return consultantKeywords.some(keyword => roles.includes(keyword));
|
|
}
|
|
|
|
// Receive Message
|
|
socket.on('receive_message', (data) => {
|
|
if (!chatMessages) return;
|
|
|
|
const msgDiv = document.createElement('div');
|
|
msgDiv.classList.add('message');
|
|
|
|
// Align messages: Consultant right, Guest left (Skype style is often left-dominant, but we follow chat standards)
|
|
const isConsultant = isConsultantRole(data.role);
|
|
const isMe = (data.user_code === currentUser.id);
|
|
|
|
msgDiv.classList.add('message');
|
|
msgDiv.classList.add(isMe ? 'right' : 'left');
|
|
|
|
let contentHtml = '';
|
|
if (data.msg_type === 'image') {
|
|
contentHtml += `<img src="${data.message}" style="max-width:100%; border-radius:8px; cursor:pointer;" onclick="window.open(this.src)">`;
|
|
} else {
|
|
contentHtml += `<span>${data.message}</span>`;
|
|
}
|
|
|
|
msgDiv.innerHTML = contentHtml;
|
|
chatMessages.appendChild(msgDiv);
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
|
|
// 만약 상담원이고 전체 모니터링 모드라면 룸 목록의 미리보기 텍스트 업데이트
|
|
if (isAdminPath && isConsultantRole(currentUser.role) && currentRoom === 'consultants_group') {
|
|
const roomItem = document.querySelector(`.room-item[onclick*="${data.room}"] .preview`);
|
|
if (roomItem) {
|
|
roomItem.innerText = data.msg_type === 'image' ? '(이미지 메시지)' : data.message;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 상담원이 특정 유저의 대화방에 참여하는 함수 (답장 기능)
|
|
function joinUserRoom(roomName, userName) {
|
|
if (currentRoom === roomName) return; // 이미 해당 방이면 무시
|
|
|
|
// 기존 대화 내용 비우기
|
|
chatMessages.innerHTML = '';
|
|
|
|
currentRoom = roomName;
|
|
socket.emit('join_room', { room: currentRoom, user_id: currentUser.id, role: currentUser.role });
|
|
|
|
// UI 업데이트
|
|
updateSidebarActive();
|
|
if (targetNameLabel) targetNameLabel.innerText = userName;
|
|
if (targetStatusLabel) targetStatusLabel.innerText = '상담 중';
|
|
if (headerAvatar) {
|
|
headerAvatar.innerText = userName.charAt(0);
|
|
headerAvatar.style.background = getAvatarColor(userName);
|
|
}
|
|
if (exitReplyBtn) exitReplyBtn.style.display = 'block';
|
|
|
|
const sysMsg = document.createElement('div');
|
|
sysMsg.style.cssText = "text-align:center; margin:20px 0; font-size:12px; color:var(--text-sub); width:100%;";
|
|
sysMsg.innerHTML = `--- <b>${userName}</b> 님과의 대화가 시작되었습니다 ---`;
|
|
chatMessages.appendChild(sysMsg);
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
}
|
|
|
|
function updateSidebarActive() {
|
|
const items = document.querySelectorAll('.room-item');
|
|
items.forEach(item => {
|
|
// onclick 속성에서 룸 분석 (간결한 구현을 위해 속성 매칭)
|
|
if (item.getAttribute('onclick').includes(currentRoom)) {
|
|
item.classList.add('active');
|
|
} else {
|
|
item.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 답장 모드 종료 (전체 모니터링으로 복귀)
|
|
function exitReplyMode() {
|
|
if (currentRoom === 'consultants_group') return;
|
|
|
|
chatMessages.innerHTML = '';
|
|
|
|
currentRoom = 'consultants_group';
|
|
socket.emit('join_room', { room: currentRoom, user_id: currentUser.id, role: currentUser.role });
|
|
|
|
updateSidebarActive();
|
|
if (targetNameLabel) targetNameLabel.innerText = '전체 모니터링';
|
|
if (targetStatusLabel) targetStatusLabel.innerText = '실시간 대기 중';
|
|
if (headerAvatar) {
|
|
headerAvatar.innerText = '📢';
|
|
headerAvatar.style.background = '#605e5c';
|
|
}
|
|
if (exitReplyBtn) exitReplyBtn.style.display = 'none';
|
|
|
|
const sysMsg = document.createElement('div');
|
|
sysMsg.style.cssText = "text-align:center; margin:20px 0; font-size:12px; color:var(--text-sub); width:100%;";
|
|
sysMsg.innerHTML = `--- 실시간 전체 대화 모니터링 중입니다 ---`;
|
|
chatMessages.appendChild(sysMsg);
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
}
|
|
|
|
if (exitReplyBtn) {
|
|
exitReplyBtn.addEventListener('click', exitReplyMode);
|
|
}
|
|
|
|
// Auth Toggle
|
|
authToggleBtn.addEventListener('click', () => {
|
|
if (currentUser.role === 'guest') {
|
|
loginArea.style.display = loginArea.style.display === 'flex' ? 'none' : 'flex';
|
|
} else {
|
|
// 로그아웃 처리: 세션 삭제 후 새로고침
|
|
localStorage.removeItem('chat_user');
|
|
location.reload();
|
|
}
|
|
});
|
|
|
|
// Handle Login Cancellation
|
|
loginCancelBtn.addEventListener('click', () => {
|
|
if (isAdminPath) {
|
|
location.href = '/chat';
|
|
} else {
|
|
loginArea.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Handle Login Submit
|
|
loginSubmitBtn.addEventListener('click', async () => {
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
|
|
try {
|
|
const response = await fetch('/api/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
// 로그인 정보 저장 및 새로고침
|
|
localStorage.setItem('chat_user', JSON.stringify(result.user));
|
|
alert(`${result.user.username}님 환영합니다!`);
|
|
location.reload();
|
|
} else {
|
|
alert('로그인 실패: ' + result.message);
|
|
}
|
|
} catch (err) {
|
|
console.error('Login error:', err);
|
|
alert('로그인 처리 중 오류 발생');
|
|
}
|
|
});
|
|
// Signup Show(Page Redirect)는 HTML의 onclick="location.href='/register'"으로 처리됨
|