Files
play-life/play-life-backend/admin.html
2025-12-30 18:27:12 +03:00

359 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>