|
document.addEventListener("DOMContentLoaded", () => { |
|
let mediaRecorder, audioChunks = [], audioStream, currentChatId = null; |
|
|
|
const recordBtn = document.getElementById("record-btn"); |
|
const stopBtn = document.getElementById("stop-btn"); |
|
const sendBtn = document.getElementById("send-btn"); |
|
const userInput = document.getElementById("user-input"); |
|
const chatBox = document.getElementById("chat-box"); |
|
const audioFileInput = document.getElementById("audio-file"); |
|
const newChatBtn = document.getElementById("new-chat-btn"); |
|
const chatList = document.getElementById("chat-list"); |
|
const currentChatTitle = document.getElementById("current-chat-title"); |
|
const fileInfo = document.getElementById("file-info"); |
|
const fileName = document.getElementById("file-name"); |
|
const clearFileBtn = document.getElementById("clear-file"); |
|
|
|
|
|
const emotionMap = { |
|
'joy': '😊 Радость', |
|
'neutral': '😐 Нейтрально', |
|
'anger': '😠 Злость', |
|
'sadness': '😢 Грусть', |
|
'surprise': '😲 Удивление' |
|
}; |
|
|
|
|
|
initializeChats(); |
|
|
|
function initializeChats() { |
|
const savedChatId = localStorage.getItem('currentChatId'); |
|
fetch("/get_chats") |
|
.then(response => response.json()) |
|
.then(chats => { |
|
renderChatList(chats); |
|
if (savedChatId && chats.some(c => c.chat_id === savedChatId)) { |
|
loadChat(savedChatId); |
|
} else if (chats.length > 0) { |
|
loadChat(chats[0].chat_id); |
|
} else { |
|
showEmptyChatUI(); |
|
} |
|
}) |
|
.catch(error => { |
|
console.error("Ошибка загрузки чатов:", error); |
|
showEmptyChatUI(); |
|
}); |
|
} |
|
|
|
function showEmptyChatUI() { |
|
if (chatBox) chatBox.innerHTML = '<div class="empty-chat">Нет активного чата</div>'; |
|
} |
|
|
|
function renderChatList(chats) { |
|
if (!chatList) return; |
|
chatList.innerHTML = ''; |
|
chats.forEach(chat => { |
|
const chatItem = document.createElement("div"); |
|
chatItem.className = "chat-item"; |
|
chatItem.dataset.chatId = chat.chat_id; |
|
chatItem.innerHTML = ` |
|
<div class="chat-item-main"> |
|
<i class="fas fa-comment chat-icon"></i> |
|
<div class="chat-item-content"> |
|
<span class="chat-title">${chat.title}</span> |
|
<span class="chat-date">${formatDate(chat.created_at)}</span> |
|
</div> |
|
</div> |
|
<button class="delete-chat-btn" title="Удалить чат"> |
|
<i class="fas fa-trash"></i> |
|
</button> |
|
`; |
|
chatItem.querySelector('.chat-item-main').addEventListener('click', () => { |
|
loadChat(chat.chat_id); |
|
localStorage.setItem('currentChatId', chat.chat_id); |
|
}); |
|
|
|
chatItem.querySelector('.delete-chat-btn').addEventListener('click', (e) => { |
|
e.stopPropagation(); |
|
deleteChat(chat.chat_id); |
|
}); |
|
|
|
chatList.appendChild(chatItem); |
|
}); |
|
} |
|
|
|
function formatDate(dateString) { |
|
if (!dateString) return ''; |
|
const date = new Date(dateString); |
|
return date.toLocaleDateString('ru-RU'); |
|
} |
|
|
|
async function deleteChat(chatId) { |
|
if (!confirm('Вы точно хотите удалить этот чат? Это действие нельзя отменить.')) return; |
|
try { |
|
const response = await fetch(`/delete_chat/${chatId}`, { |
|
method: 'DELETE', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
'X-CSRFToken': getCSRFToken() |
|
} |
|
}); |
|
const result = await response.json(); |
|
if (result.success) { |
|
if (currentChatId === chatId) { |
|
chatBox.innerHTML = '<div class="empty-chat">Чат удалён</div>'; |
|
currentChatId = null; |
|
} |
|
initializeChats(); |
|
} else { |
|
throw new Error(result.error || 'Ошибка при удалении чата'); |
|
} |
|
} catch (error) { |
|
console.error('Delete chat error:', error); |
|
appendMessage('bot', `❌ Ошибка при удалении: ${error.message}`); |
|
} |
|
} |
|
|
|
function getCSRFToken() { |
|
const meta = document.querySelector('meta[name="csrf-token"]'); |
|
return meta ? meta.content : ''; |
|
} |
|
|
|
newChatBtn?.addEventListener("click", startNewChat); |
|
|
|
function startNewChat() { |
|
fetch("/start_chat", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
currentChatId = data.chat_id; |
|
if (currentChatTitle) { |
|
currentChatTitle.textContent = data.title; |
|
} |
|
chatBox.innerHTML = '<div class="message bot-message">Привет! Отправьте текст или голосовое сообщение для анализа эмоций.</div>'; |
|
initializeChats(); |
|
localStorage.setItem('currentChatId', data.chat_id); |
|
}) |
|
.catch(console.error); |
|
} |
|
|
|
function loadChat(chatId) { |
|
fetch(`/load_chat/${chatId}`) |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.error) throw new Error(data.error); |
|
currentChatId = chatId; |
|
currentChatTitle.textContent = data.title; |
|
updateActiveChat(chatId); |
|
chatBox.innerHTML = ""; |
|
data.messages.forEach(msg => { |
|
appendMessage(msg.sender, msg.content); |
|
}); |
|
localStorage.setItem('currentChatId', chatId); |
|
}) |
|
.catch(error => { |
|
console.error("Ошибка загрузки чата:", error); |
|
appendMessage("bot", `❌ Ошибка: ${error.message}`); |
|
}); |
|
} |
|
|
|
function updateActiveChat(chatId) { |
|
document.querySelectorAll(".chat-item").forEach(item => { |
|
item.classList.toggle("active", item.dataset.chatId === chatId); |
|
}); |
|
} |
|
|
|
sendBtn?.addEventListener("click", sendMessage); |
|
userInput?.addEventListener("keypress", (e) => { |
|
if (e.key === "Enter") sendMessage(); |
|
}); |
|
|
|
async function sendMessage() { |
|
const text = userInput?.value.trim(); |
|
if (!text || !currentChatId) return; |
|
appendAndSaveMessage("user", text); |
|
userInput.value = ""; |
|
try { |
|
const response = await fetch("/analyze", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify({ text, chat_id: currentChatId }) |
|
}); |
|
const data = await response.json(); |
|
appendAndSaveMessage("bot", `Эмоция: ${data.emotion} (${(data.confidence * 100).toFixed(1)}%)`); |
|
} catch (error) { |
|
console.error("Ошибка:", error); |
|
appendAndSaveMessage("bot", `❌ Ошибка: ${error.message}`); |
|
} |
|
} |
|
|
|
|
|
if (audioFileInput) { |
|
audioFileInput.addEventListener("change", handleAudioUpload); |
|
} |
|
if (clearFileBtn) { |
|
clearFileBtn.addEventListener("click", clearAudioFile); |
|
} |
|
|
|
function handleAudioUpload() { |
|
const file = audioFileInput?.files[0]; |
|
if (file) { |
|
fileName.textContent = file.name; |
|
fileInfo.style.display = 'flex'; |
|
sendAudioFile(file); |
|
} |
|
} |
|
|
|
function clearAudioFile() { |
|
audioFileInput.value = ''; |
|
fileInfo.style.display = 'none'; |
|
} |
|
|
|
async function sendAudioFile(file) { |
|
if (!currentChatId) return; |
|
appendAndSaveMessage("user", "Загружен аудиофайл..."); |
|
try { |
|
const formData = new FormData(); |
|
formData.append("audio", file); |
|
formData.append("chat_id", currentChatId); |
|
const response = await fetch("/analyze_audio", { |
|
method: "POST", |
|
body: formData |
|
}); |
|
const data = await response.json(); |
|
if (data.transcribed_text) { |
|
appendAndSaveMessage("user", `Распознанный текст: ${data.transcribed_text}`); |
|
} |
|
appendAndSaveMessage("bot", `Эмоция: ${data.emotion} (${(data.confidence * 100).toFixed(1)}%)`); |
|
clearAudioFile(); |
|
} catch (error) { |
|
console.error("Ошибка:", error); |
|
appendAndSaveMessage("bot", `❌ Ошибка: ${error.message}`); |
|
} |
|
} |
|
|
|
|
|
if (recordBtn) recordBtn.addEventListener("click", startRecording); |
|
if (stopBtn) stopBtn.addEventListener("click", stopRecording); |
|
|
|
async function startRecording() { |
|
try { |
|
audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
|
mediaRecorder = new MediaRecorder(audioStream); |
|
audioChunks = []; |
|
mediaRecorder.ondataavailable = e => audioChunks.push(e.data); |
|
mediaRecorder.onstop = async () => { |
|
const audioBlob = new Blob(audioChunks, { type: "audio/wav" }); |
|
sendAudioBlob(audioBlob); |
|
}; |
|
mediaRecorder.start(); |
|
if (recordBtn) recordBtn.disabled = true; |
|
if (stopBtn) stopBtn.disabled = false; |
|
appendMessage("user", "Запись начата..."); |
|
} catch (error) { |
|
console.error("Ошибка записи:", error); |
|
appendMessage("bot", "❌ Не удалось получить доступ к микрофону"); |
|
} |
|
} |
|
|
|
function stopRecording() { |
|
if (mediaRecorder?.state === "recording") { |
|
mediaRecorder.stop(); |
|
if (recordBtn) recordBtn.disabled = false; |
|
if (stopBtn) stopBtn.disabled = true; |
|
if (audioStream) audioStream.getTracks().forEach(track => track.stop()); |
|
} |
|
} |
|
|
|
async function sendAudioBlob(audioBlob) { |
|
if (!currentChatId) return; |
|
appendAndSaveMessage("user", "Отправлено голосовое сообщение..."); |
|
try { |
|
const formData = new FormData(); |
|
formData.append("audio", audioBlob, "recording.wav"); |
|
formData.append("chat_id", currentChatId); |
|
const response = await fetch("/analyze_audio", { |
|
method: "POST", |
|
body: formData |
|
}); |
|
const data = await response.json(); |
|
if (data.transcribed_text) { |
|
appendAndSaveMessage("user", `Распознанный текст: ${data.transcribed_text}`); |
|
} |
|
appendAndSaveMessage("bot", `Эмоция: ${data.emotion} (${(data.confidence * 100).toFixed(1)}%)`); |
|
} catch (error) { |
|
console.error("Ошибка:", error); |
|
appendAndSaveMessage("bot", `❌ Ошибка: ${error.message}`); |
|
} |
|
} |
|
|
|
function appendMessage(sender, text) { |
|
const message = document.createElement("div"); |
|
message.className = `message ${sender}-message`; |
|
message.innerHTML = text; |
|
if (chatBox) { |
|
chatBox.appendChild(message); |
|
chatBox.scrollTop = chatBox.scrollHeight; |
|
} |
|
} |
|
|
|
function appendAndSaveMessage(sender, text) { |
|
appendMessage(sender, text); |
|
if (currentChatId) { |
|
fetch("/save_message", { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json", |
|
"X-CSRFToken": getCSRFToken() |
|
}, |
|
body: JSON.stringify({ |
|
chat_id: currentChatId, |
|
sender: sender, |
|
content: text |
|
}) |
|
}).catch(console.error); |
|
} |
|
} |
|
|
|
|
|
document.getElementById('telegram-upload-form')?.addEventListener('submit', async function(e) { |
|
e.preventDefault(); |
|
const fileInput = document.getElementById('telegram-file'); |
|
const file = fileInput.files[0]; |
|
|
|
if (!file) { |
|
alert('Пожалуйста, выберите файл'); |
|
return; |
|
} |
|
|
|
try { |
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
const response = await fetch('/analyze_telegram_chat', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
const result = await response.json(); |
|
|
|
if (result.error) { |
|
throw new Error(result.error); |
|
} |
|
|
|
alert('Анализ завершен успешно!'); |
|
updateTelegramAnalytics(); |
|
} catch (error) { |
|
console.error('Ошибка загрузки файла:', error); |
|
alert(`Ошибка: ${error.message}`); |
|
} |
|
}); |
|
|
|
|
|
|
|
|
|
function movingAverage(data, windowSize) { |
|
const result = []; |
|
for (let i = 0; i < data.length; i++) { |
|
const start = Math.max(0, i - windowSize + 1); |
|
const slice = data.slice(start, i + 1); |
|
const avg = slice.reduce((sum, val) => sum + val, 0) / slice.length; |
|
result.push(avg); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
function getEmotionColor(emotion) { |
|
const colors = { |
|
'😊 Радость': '#00b894', |
|
'😢 Грусть': '#0984e3', |
|
'😠 Злость': '#d63031', |
|
'😲 Удивление': '#fdcb6e', |
|
'😨 Страх': '#a29bfe', |
|
'😐 Нейтрально': '#636e72' |
|
}; |
|
return colors[emotion] || '#4a4ae8'; |
|
} |
|
|
|
|
|
function getEmotionIcon(emotion) { |
|
const icons = { |
|
'😊 Радость': 'fa-smile', |
|
'😢 Грусть': 'fa-sad-tear', |
|
'😠 Злость': 'fa-angry', |
|
'😲 Удивление': 'fa-surprise', |
|
'😨 Страх': 'fa-flushed', |
|
'😐 Нейтрально': 'fa-meh' |
|
}; |
|
return icons[emotion] || 'fa-comment'; |
|
} |
|
|
|
|
|
|
|
async function updateTelegramAnalytics(range = 'month') { |
|
try { |
|
const response = await fetch('/get_telegram_analysis'); |
|
const analyses = await response.json(); |
|
if (!analyses || analyses.length === 0) { |
|
document.getElementById('emotion-timeline').innerHTML = |
|
'<div class="empty-state"><i class="fas fa-comment-slash"></i><p>Нет данных для отображения</p></div>'; |
|
document.getElementById('emotion-distribution').innerHTML = ''; |
|
return; |
|
} |
|
|
|
const allData = analyses.flatMap(a => JSON.parse(a.data)); |
|
const emotionMap = { |
|
'joy': '😊 Радость', |
|
'sadness': '😢 Грусть', |
|
'anger': '😠 Злость', |
|
'surprise': '😲 Удивление', |
|
'fear': '😨 Страх', |
|
'no_emotion': '😐 Нейтрально' |
|
}; |
|
|
|
|
|
const userSelect = document.getElementById('user-select'); |
|
const selectedUser = userSelect?.value; |
|
let filteredData = allData; |
|
if (selectedUser && selectedUser !== 'all') { |
|
filteredData = allData.filter(d => d.from === selectedUser); |
|
} |
|
|
|
const processedData = filteredData.map(d => ({ |
|
emotion: emotionMap[d.emotion] || d.emotion, |
|
from: d.from, |
|
text: d.text, |
|
date: new Date(d.timestamp), |
|
confidence: d.confidence |
|
})); |
|
|
|
|
|
const groupByTime = (date, range) => { |
|
const d = new Date(date); |
|
if (range === 'week') { |
|
d.setHours(0, 0, 0, 0); |
|
d.setDate(d.getDate() - d.getDay()); |
|
return d; |
|
} else if (range === 'month') { |
|
return new Date(d.getFullYear(), d.getMonth(), 1); |
|
} else if (range === 'year') { |
|
return new Date(d.getFullYear(), 0, 1); |
|
} |
|
return new Date(d.getFullYear(), d.getMonth(), d.getDate()); |
|
}; |
|
|
|
const groupedData = {}; |
|
processedData.forEach(d => { |
|
const timeKey = groupByTime(d.date, range).getTime(); |
|
if (!groupedData[timeKey]) { |
|
groupedData[timeKey] = { |
|
date: new Date(timeKey), |
|
emotions: {} |
|
}; |
|
} |
|
if (!groupedData[timeKey].emotions[d.emotion]) { |
|
groupedData[timeKey].emotions[d.emotion] = { |
|
count: 0, |
|
totalConfidence: 0 |
|
}; |
|
} |
|
groupedData[timeKey].emotions[d.emotion].count++; |
|
groupedData[timeKey].emotions[d.emotion].totalConfidence += d.confidence; |
|
}); |
|
|
|
const timeKeys = Object.keys(groupedData).sort(); |
|
const emotions = [...new Set(processedData.map(d => d.emotion))]; |
|
const traces = emotions.map(emotion => { |
|
const x = []; |
|
const y = []; |
|
const customdata = []; |
|
timeKeys.forEach(key => { |
|
const dataPoint = groupedData[key]; |
|
if (dataPoint.emotions[emotion]) { |
|
x.push(dataPoint.date); |
|
y.push(dataPoint.emotions[emotion].count); |
|
customdata.push({ |
|
emotion: emotion, |
|
avgConfidence: (dataPoint.emotions[emotion].totalConfidence / |
|
dataPoint.emotions[emotion].count).toFixed(2) |
|
}); |
|
} else { |
|
x.push(dataPoint.date); |
|
y.push(0); |
|
customdata.push(null); |
|
} |
|
}); |
|
|
|
return { |
|
x: x, |
|
y: y, |
|
name: emotion, |
|
type: 'scatter', |
|
mode: 'lines+markers', |
|
line: { shape: 'spline' }, |
|
marker: { color: getEmotionColor(emotion), size: 6 }, |
|
fill: 'tonexty', |
|
fillcolor: `${getEmotionColor(emotion)}7F`, |
|
customdata: customdata, |
|
hovertemplate: |
|
'<b>%{x|%d %b %Y}</b><br>' + |
|
'Эмоция: %{fullData.name}<br>' + |
|
'Сообщений: %{y}<br>' + |
|
'Средняя уверенность: %{customdata.avgConfidence}<extra></extra>' |
|
}; |
|
}); |
|
|
|
Plotly.newPlot('emotion-timeline', traces, { |
|
title: false, |
|
plot_bgcolor: 'rgba(0,0,0,0)', |
|
paper_bgcolor: 'rgba(0,0,0,0)', |
|
font: { color: 'white' }, |
|
xaxis: { |
|
title: 'Дата', |
|
tickformat: range === 'year' ? '%Y' : range === 'month' ? '%b %Y' : '%d %b', |
|
gridcolor: 'rgba(255,255,255,0.1)' |
|
}, |
|
yaxis: { |
|
title: 'Количество сообщений', |
|
gridcolor: 'rgba(255,255,255,0.1)' |
|
}, |
|
hovermode: 'closest', |
|
legend: { |
|
orientation: 'h', |
|
y: -0.2 |
|
}, |
|
margin: { t: 0, b: 80 } |
|
}); |
|
|
|
|
|
const dates = [...new Set(processedData.map(d => d.date.toDateString()))]; |
|
const emotionLabels = Object.values(emotionMap); |
|
const z = emotionLabels.map(e => dates.map(d => |
|
processedData.filter(msg => msg.date.toDateString() === d && msg.emotion === e).length |
|
)); |
|
|
|
Plotly.newPlot('calendar-heatmap', [{ |
|
type: 'heatmap', |
|
z: z, |
|
x: dates, |
|
y: emotionLabels, |
|
colorscale: [ |
|
[0, '#2d3436'], |
|
[0.5, '#6c5ce7'], |
|
[1, '#00b894'] |
|
], |
|
showscale: true, |
|
colorbar: { |
|
title: 'Частота', |
|
titleside: 'top', |
|
tickmode: 'array', |
|
tickvals: [0, Math.max(...z.flat())], |
|
ticktext: ['Мало', 'Много'], |
|
ticks: 'outside' |
|
} |
|
}], { |
|
title: 'Тепловая карта эмоций по дням', |
|
xaxis: { |
|
title: 'Дата', |
|
tickangle: -45 |
|
}, |
|
yaxis: { |
|
title: 'Эмоции', |
|
automargin: true |
|
}, |
|
margin: { t: 30, r: 30, l: 80, b: 80 } |
|
}); |
|
|
|
|
|
const totalMessages = processedData.length; |
|
const emotionCounts = {}; |
|
processedData.forEach(d => { |
|
emotionCounts[d.emotion] = (emotionCounts[d.emotion] || 0) + 1; |
|
}); |
|
const sorted = Object.entries(emotionCounts).sort((a, b) => b[1] - a[1]); |
|
const dominant = sorted[0]; |
|
|
|
const sadnessPeaks = processedData |
|
.filter(d => d.emotion === '😢 Грусть') |
|
.reduce((acc, d) => { |
|
const key = d.date.toDateString(); |
|
acc[key] = (acc[key] || 0) + 1; |
|
return acc; |
|
}, {}); |
|
const sadPeak = Object.entries(sadnessPeaks).sort((a, b) => b[1] - a[1])[0]; |
|
|
|
document.getElementById('summary-content').innerHTML = ` |
|
<ul style="color: white;"> |
|
<li>💡 Преобладает: ${dominant[0]} (${((dominant[1]/totalMessages)*100).toFixed(1)}%)</li> |
|
<li>📉 Пик грусти: ${sadPeak[0]} (${sadPeak[1]} сообщений)</li> |
|
</ul> |
|
`; |
|
|
|
|
|
const pieLabels = Object.keys(emotionCounts); |
|
const pieValues = Object.values(emotionCounts); |
|
|
|
Plotly.newPlot('emotion-distribution-pie', [{ |
|
labels: pieLabels, |
|
values: pieValues, |
|
type: 'pie', |
|
textinfo: 'label+percent', |
|
hoverinfo: 'label+value+percent', |
|
marker: { |
|
colors: pieLabels.map(e => getEmotionColor(e)) |
|
}, |
|
textfont: { |
|
color: 'white' |
|
}, |
|
hole: 0.4, |
|
rotation: 45 |
|
}], { |
|
title: false, |
|
plot_bgcolor: 'rgba(0,0,0,0)', |
|
paper_bgcolor: 'rgba(0,0,0,0)', |
|
font: { color: 'white' }, |
|
showlegend: false, |
|
margin: { t: 0, b: 0, l: 0, r: 0 } |
|
}); |
|
|
|
|
|
const statsHTML = pieLabels.slice(0, 6).map((emotion, i) => { |
|
const percentage = ((pieValues[i] / totalMessages) * 100).toFixed(1); |
|
return ` |
|
<div class="emotion-stat"> |
|
<div class="emotion-label" style="color: ${getEmotionColor(emotion)}"> |
|
<span>${emotion.replace(/[\u{1F600}-\u{1F64F}]/gu, '')}</span> |
|
</div> |
|
<div class="confidence-bar"> |
|
<div class="confidence-fill" |
|
style="width: ${percentage}%; |
|
background: ${getEmotionColor(emotion)};"></div> |
|
</div> |
|
<div class="confidence-value">${percentage}%</div> |
|
</div>`; |
|
}).join(''); |
|
|
|
document.getElementById('emotion-distribution').innerHTML = statsHTML; |
|
|
|
|
|
const statsContainer = document.getElementById('emotion-distribution'); |
|
if (statsContainer) { |
|
statsContainer.style.display = 'grid'; |
|
statsContainer.style.gridTemplateColumns = 'repeat(3, 1fr)'; |
|
statsContainer.style.gap = '15px'; |
|
} |
|
|
|
|
|
populateUserSelect(processedData); |
|
|
|
} catch (error) { |
|
console.error('Ошибка обновления аналитики:', error); |
|
document.getElementById('emotion-timeline').innerHTML = |
|
`<div class="empty-state"><i class="fas fa-exclamation-triangle"></i><p>Ошибка загрузки данных: ${error.message}</p></div>`; |
|
} |
|
} |
|
|
|
|
|
let telegramUsers = []; |
|
|
|
function populateUserSelect(processedData) { |
|
const userSelect = document.getElementById('user-select'); |
|
if (!userSelect) return; |
|
|
|
const users = [...new Set(processedData.map(d => d.from))]; |
|
|
|
|
|
if (JSON.stringify(users.sort()) === JSON.stringify(telegramUsers.sort())) return; |
|
|
|
telegramUsers = users; |
|
userSelect.innerHTML = '<option value="all">Все участники</option>'; |
|
|
|
users.forEach(user => { |
|
const option = document.createElement('option'); |
|
option.value = user; |
|
option.textContent = user; |
|
userSelect.appendChild(option); |
|
}); |
|
|
|
|
|
if (!userSelect.dataset.listenerAdded) { |
|
userSelect.addEventListener('change', () => { |
|
updateTelegramAnalytics(document.querySelector('.time-btn.active')?.dataset.range || 'month'); |
|
}); |
|
userSelect.dataset.listenerAdded = 'true'; |
|
} |
|
} |
|
document.querySelectorAll('.time-btn').forEach(button => { |
|
button.addEventListener('click', function () { |
|
|
|
document.querySelectorAll('.time-btn').forEach(btn => btn.classList.remove('active')); |
|
|
|
this.classList.add('active'); |
|
|
|
|
|
const range = this.getAttribute('data-range'); |
|
|
|
|
|
updateTelegramAnalytics(range); |
|
}); |
|
}); |
|
|
|
|
|
window.addEventListener('load', () => { |
|
const activeTimeBtn = document.querySelector('.time-btn.active'); |
|
if (activeTimeBtn) { |
|
const range = activeTimeBtn.dataset.range || 'month'; |
|
updateTelegramAnalytics(range); |
|
} else { |
|
updateTelegramAnalytics('month'); |
|
} |
|
}); |
|
|
|
if (window.location.pathname.includes('/profile')) { |
|
updateTelegramAnalytics(); |
|
} |
|
}); |