359 lines
12 KiB
HTML
359 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Play Life Backend - Admin Panel</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
h1 {
|
||
color: white;
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
font-size: 2.5em;
|
||
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.card {
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.card h2 {
|
||
color: #667eea;
|
||
margin-bottom: 20px;
|
||
font-size: 1.3em;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.card textarea {
|
||
width: 100%;
|
||
min-height: 150px;
|
||
padding: 12px;
|
||
border: 2px solid #e0e0e0;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
font-family: monospace;
|
||
resize: vertical;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.card textarea:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
}
|
||
|
||
.card button {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
border-radius: 5px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
width: 100%;
|
||
}
|
||
|
||
.card button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.card button:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.card button:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
.result {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: #f5f5f5;
|
||
border-radius: 5px;
|
||
border-left: 4px solid #667eea;
|
||
}
|
||
|
||
.result h3 {
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
.result pre {
|
||
background: #2d2d2d;
|
||
color: #f8f8f2;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
overflow-x: auto;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.result.success {
|
||
border-left-color: #4caf50;
|
||
}
|
||
|
||
.result.error {
|
||
border-left-color: #f44336;
|
||
}
|
||
|
||
.result.loading {
|
||
border-left-color: #ff9800;
|
||
}
|
||
|
||
.status {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.status.success {
|
||
background: #4caf50;
|
||
color: white;
|
||
}
|
||
|
||
.status.error {
|
||
background: #f44336;
|
||
color: white;
|
||
}
|
||
|
||
.status.loading {
|
||
background: #ff9800;
|
||
color: white;
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>🎯 Play Life Backend - Admin Panel</h1>
|
||
|
||
<div class="grid">
|
||
<!-- Message Post Card -->
|
||
<div class="card">
|
||
<h2>
|
||
📨 Message Post
|
||
<span class="status" id="messageStatus" style="display: none;"></span>
|
||
</h2>
|
||
<textarea id="messageText" placeholder="Введите сообщение с паттернами **Project+10.5** или **Project-5.0**...
|
||
|
||
Пример:
|
||
Сегодня работал над проектами:
|
||
**Frontend+15.5**
|
||
**Backend+8.0**
|
||
**Design-2.5**"></textarea>
|
||
<button onclick="sendMessage()">Отправить сообщение</button>
|
||
<div id="messageResult"></div>
|
||
</div>
|
||
|
||
<!-- Daily Report Trigger Card -->
|
||
<div class="card">
|
||
<h2>
|
||
📈 Daily Report Trigger
|
||
<span class="status" id="dailyReportStatus" style="display: none;"></span>
|
||
</h2>
|
||
<p style="margin-bottom: 15px; color: #666;">
|
||
Нажмите кнопку для отправки ежедневного отчёта по Score и Целям в Telegram (обычно отправляется автоматически в 23:59).
|
||
</p>
|
||
<button onclick="triggerDailyReport()">Отправить отчёт</button>
|
||
<div id="dailyReportResult"></div>
|
||
</div>
|
||
|
||
<!-- Weekly Goals Setup Card -->
|
||
<div class="card">
|
||
<h2>
|
||
🎯 Weekly Goals Setup
|
||
<span class="status" id="goalsStatus" style="display: none;"></span>
|
||
</h2>
|
||
<p style="margin-bottom: 15px; color: #666;">
|
||
Нажмите кнопку для установки целей на текущую неделю на основе медианы за последние 3 месяца (с отправкой в чат). Обычно срабатывает автоматически в начале недели.
|
||
</p>
|
||
<button onclick="setupWeeklyGoals()">Обновить цели</button>
|
||
<div id="goalsResult"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function getApiUrl() {
|
||
// Автоматически определяем URL текущего хоста
|
||
// Админка обслуживается тем же бекендом, поэтому используем текущий origin
|
||
return window.location.origin;
|
||
}
|
||
|
||
function showStatus(elementId, status, text) {
|
||
const statusEl = document.getElementById(elementId);
|
||
statusEl.textContent = text;
|
||
statusEl.className = `status ${status}`;
|
||
statusEl.style.display = 'inline-block';
|
||
}
|
||
|
||
function hideStatus(elementId) {
|
||
document.getElementById(elementId).style.display = 'none';
|
||
}
|
||
|
||
function showResult(elementId, data, isError = false, isLoading = false) {
|
||
const resultEl = document.getElementById(elementId);
|
||
resultEl.innerHTML = '';
|
||
|
||
if (isLoading) {
|
||
resultEl.innerHTML = '<div class="result loading"><h3>⏳ Загрузка...</h3></div>';
|
||
return;
|
||
}
|
||
|
||
const div = document.createElement('div');
|
||
div.className = `result ${isError ? 'error' : 'success'}`;
|
||
|
||
const h3 = document.createElement('h3');
|
||
h3.textContent = isError ? '❌ Ошибка' : '✅ Успешно';
|
||
div.appendChild(h3);
|
||
|
||
const pre = document.createElement('pre');
|
||
pre.textContent = JSON.stringify(data, null, 2);
|
||
div.appendChild(pre);
|
||
|
||
resultEl.appendChild(div);
|
||
}
|
||
|
||
async function sendMessage() {
|
||
const text = document.getElementById('messageText').value.trim();
|
||
if (!text) {
|
||
alert('Пожалуйста, введите сообщение');
|
||
return;
|
||
}
|
||
|
||
showStatus('messageStatus', 'loading', 'Отправка...');
|
||
showResult('messageResult', null, false, true);
|
||
|
||
try {
|
||
const response = await fetch(`${getApiUrl()}/message/post`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
body: {
|
||
text: text
|
||
}
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (response.ok) {
|
||
showStatus('messageStatus', 'success', 'Успешно');
|
||
showResult('messageResult', data, false);
|
||
} else {
|
||
showStatus('messageStatus', 'error', 'Ошибка');
|
||
showResult('messageResult', data, true);
|
||
}
|
||
} catch (error) {
|
||
showStatus('messageStatus', 'error', 'Ошибка');
|
||
showResult('messageResult', { error: error.message }, true);
|
||
}
|
||
}
|
||
|
||
async function setupWeeklyGoals() {
|
||
showStatus('goalsStatus', 'loading', 'Обновление...');
|
||
showResult('goalsResult', null, false, true);
|
||
|
||
try {
|
||
const response = await fetch(`${getApiUrl()}/weekly_goals/setup`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (response.ok) {
|
||
showStatus('goalsStatus', 'success', 'Успешно');
|
||
showResult('goalsResult', data, false);
|
||
} else {
|
||
showStatus('goalsStatus', 'error', 'Ошибка');
|
||
showResult('goalsResult', data, true);
|
||
}
|
||
} catch (error) {
|
||
showStatus('goalsStatus', 'error', 'Ошибка');
|
||
showResult('goalsResult', { error: error.message }, true);
|
||
}
|
||
}
|
||
|
||
async function triggerDailyReport() {
|
||
showStatus('dailyReportStatus', 'loading', 'Отправка...');
|
||
showResult('dailyReportResult', null, false, true);
|
||
|
||
try {
|
||
const response = await fetch(`${getApiUrl()}/daily-report/trigger`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (response.ok) {
|
||
showStatus('dailyReportStatus', 'success', 'Успешно');
|
||
showResult('dailyReportResult', data, false);
|
||
} else {
|
||
showStatus('dailyReportStatus', 'error', 'Ошибка');
|
||
showResult('dailyReportResult', data, true);
|
||
}
|
||
} catch (error) {
|
||
showStatus('dailyReportStatus', 'error', 'Ошибка');
|
||
showResult('dailyReportResult', { error: error.message }, true);
|
||
}
|
||
}
|
||
|
||
// Разрешаем отправку формы по Enter (Ctrl+Enter для textarea)
|
||
document.getElementById('messageText').addEventListener('keydown', function(e) {
|
||
if (e.ctrlKey && e.key === 'Enter') {
|
||
sendMessage();
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|