- Исправлено 'плавающее' меню (дублирование кнопок при навигации)
- Создан base.html - единый базовый шаблон для всех страниц
- Добавлена кнопка '📅 Запланированные посты' во все 11 страниц
- Sticky sidebar с улучшенным дизайном
- Все страницы теперь имеют одинаковое меню (11 кнопок)
- Улучшенная подсветка активного пункта (border-left)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
213 lines
12 KiB
HTML
213 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Телефоны и службы - Домовой Бот</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
|
<style>
|
|
.sidebar { min-height: 100vh; background: linear-gradient(180deg, #2c3e50 0%, #1a252f 100%); }
|
|
.sidebar .nav-link { color: rgba(255,255,255,0.8); padding: 1rem; }
|
|
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: white; background-color: rgba(255,255,255,0.1); }
|
|
.content { padding: 2rem; }
|
|
.service-image-preview { max-width: 200px; max-height: 200px; object-fit: contain; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-md-2 sidebar p-0">
|
|
<div class="p-3 text-white text-center"><h4><i class="bi bi-house-door"></i> Домовой Бот</h4></div>
|
|
<nav class="nav flex-column">
|
|
<a class="nav-link" href="/"><i class="bi bi-speedometer2"></i> Дашборд</a>
|
|
<a class="nav-link" href="/users"><i class="bi bi-people"></i> Пользователи</a>
|
|
<a class="nav-link" href="/verification"><i class="bi bi-shield-check"></i> Верификация</a>
|
|
<a class="nav-link active" href="/phones"><i class="bi bi-telephone"></i> Телефоны</a>
|
|
<a class="nav-link" href="/ads"><i class="bi bi-megaphone"></i> Объявления</a>
|
|
<a class="nav-link" href="/broadcast"><i class="bi bi-broadcast"></i> Рассылки</a>
|
|
<a class="nav-link" href="/scheduled_posts"><i class="bi bi-calendar2-plus"></i> Запланированные посты</a>
|
|
<a class="nav-link" href="/polls"><i class="bi bi-bar-chart"></i> Опросы</a>
|
|
<a class="nav-link" href="/events"><i class="bi bi-calendar-event"></i> События</a>
|
|
<a class="nav-link" href="/schedules"><i class="bi bi-clock"></i> Расписания</a>
|
|
<a class="nav-link" href="/export"><i class="bi bi-download"></i> Экспорт</a>
|
|
</nav>
|
|
</div>
|
|
<div class="col-md-10 content">
|
|
<h2 class="mb-4"><i class="bi bi-telephone"></i> Телефоны и службы</h2>
|
|
|
|
<!-- Добавить службу -->
|
|
<div class="card mb-4">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0"><i class="bi bi-plus-lg"></i> Добавить службу</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="addServiceForm" class="row g-3">
|
|
<div class="col-md-3">
|
|
<input type="text" class="form-control" id="serviceName" placeholder="Название" required>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<input type="text" class="form-control" id="servicePhone" placeholder="Телефон" required>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="serviceCategory">
|
|
<option value="emergency">🚨 Экстренные</option>
|
|
<option value="utility">🔧 ЖКХ</option>
|
|
<option value="masters">👷 Мастера</option>
|
|
<option value="police">👮 Полиция</option>
|
|
<option value="other">📋 Другое</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input type="text" class="form-control" id="serviceDesc" placeholder="Описание">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="submit" class="btn btn-primary w-100">Добавить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Загрузить картинку -->
|
|
<div class="card mb-4">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0"><i class="bi bi-image"></i> Загрузить картинку для службы</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="uploadImageForm" class="row g-3" enctype="multipart/form-data">
|
|
<div class="col-md-3">
|
|
<select class="form-select" id="imageCategory" required>
|
|
<option value="emergency">🚨 Экстренные</option>
|
|
<option value="utility">🔧 ЖКХ</option>
|
|
<option value="masters">👷 Мастера</option>
|
|
<option value="police">👮 Полиция</option>
|
|
<option value="other">📋 Другое</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<input type="file" class="form-control" id="imageFile" accept="image/*" required>
|
|
<small class="text-muted">JPG, PNG до 5MB</small>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="submit" class="btn btn-success w-100">Загрузить</button>
|
|
</div>
|
|
</form>
|
|
<div id="uploadResult" class="mt-3"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Список служб -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5>Существующие службы:</h5>
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Категория</th>
|
|
<th>Название</th>
|
|
<th>Телефон</th>
|
|
<th>Описание</th>
|
|
<th>Картинка</th>
|
|
<th>Действия</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for service in services %}
|
|
<tr>
|
|
<td>
|
|
{% if service.category == 'emergency' %}
|
|
<span class="badge bg-danger">🚨 Экстренные</span>
|
|
{% elif service.category == 'utility' %}
|
|
<span class="badge bg-info">🔧 ЖКХ</span>
|
|
{% elif service.category == 'masters' %}
|
|
<span class="badge bg-success">👷 Мастера</span>
|
|
{% elif service.category == 'police' %}
|
|
<span class="badge bg-warning text-dark">👮 Полиция</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Другое</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ service.name }}</td>
|
|
<td><a href="tel:{{ service.phone }}">{{ service.phone }}</a></td>
|
|
<td>{{ service.description or '—' }}</td>
|
|
<td>
|
|
{% if service.image_path %}
|
|
<img src="/static/service_images/{{ service.image_path.split('/')[-1] }}"
|
|
class="service-image-preview" alt="Картинка">
|
|
<br><small class="text-muted">✅</small>
|
|
{% else %}
|
|
<span class="text-muted">❌ Нет</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteService({{ service.service_id }})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% if not services %}
|
|
<p class="text-center text-muted">Служб нет</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
// Добавление службы
|
|
document.getElementById('addServiceForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const data = {
|
|
name: document.getElementById('serviceName').value,
|
|
phone: document.getElementById('servicePhone').value,
|
|
category: document.getElementById('serviceCategory').value,
|
|
description: document.getElementById('serviceDesc').value
|
|
};
|
|
const r = await fetch('/api/service/add', {
|
|
method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(data)
|
|
});
|
|
const result = await r.json();
|
|
if (result.success) { alert('✅ Служба добавлена'); location.reload(); }
|
|
else { alert('❌ Ошибка: ' + (result.error || 'Неизвестная')); }
|
|
});
|
|
|
|
// Загрузка картинки
|
|
document.getElementById('uploadImageForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData();
|
|
formData.append('category', document.getElementById('imageCategory').value);
|
|
formData.append('file', document.getElementById('imageFile').files[0]);
|
|
|
|
const resultDiv = document.getElementById('uploadResult');
|
|
resultDiv.innerHTML = '<div class="alert alert-info">Загрузка...</div>';
|
|
|
|
try {
|
|
const r = await fetch('/api/service/upload_image', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const result = await r.json();
|
|
|
|
if (result.success) {
|
|
resultDiv.innerHTML = `<div class="alert alert-success">✅ ${result.message}<br>Файл: ${result.file_path}</div>`;
|
|
} else {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${result.error}</div>`;
|
|
}
|
|
} catch (err) {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${err.message}</div>`;
|
|
}
|
|
});
|
|
|
|
async function deleteService(id) {
|
|
if (!confirm('Удалить службу?')) return;
|
|
const r = await fetch(`/api/service/${id}/delete`, { method: 'POST' });
|
|
const result = await r.json();
|
|
if (result.success) { alert('✅ Удалено'); location.reload(); }
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|