add files
This commit is contained in:
73
.gitignore
vendored
Normal file
73
.gitignore
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# Временные файлы редакторов и ОС
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Логи приложения
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Файлы окружения (не коммитим чувствительные данные)
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Зависимости Composer (можно коммитить, но обычно нет)
|
||||
/vendor/
|
||||
/composer.lock
|
||||
|
||||
# Кэш и скомпилированные файлы
|
||||
/cache/
|
||||
/tmp/
|
||||
/var/cache/
|
||||
/var/tmp/
|
||||
|
||||
# Файлы IDE
|
||||
.idea/
|
||||
.phpstorm.meta.php
|
||||
*.buildpath
|
||||
*.project
|
||||
*.settings/
|
||||
.vscode/
|
||||
|
||||
# Unit тесты и покрытие кода
|
||||
/coverage/
|
||||
.phpunit.result.cache
|
||||
|
||||
# Деплой и билд
|
||||
/build/
|
||||
/dist/
|
||||
|
||||
# Файлы операционной системы
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
[Tt]humbs.db
|
||||
|
||||
# Apache/Nginx
|
||||
.htaccess
|
||||
nginx.conf.local
|
||||
|
||||
# Сессии PHP
|
||||
/sessions/
|
||||
|
||||
# Загружаемые файлы (если они не должны быть в репозитории)
|
||||
/uploads/
|
||||
/storage/app/public/
|
||||
/public/storage/
|
||||
|
||||
# Файлы пакетов
|
||||
*.tar.gz
|
||||
*.zip
|
||||
|
||||
# SQLite базы данных (если используются)
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# Xdebug
|
||||
xdebug.log
|
||||
20
composer.json
Normal file
20
composer.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
|
||||
"require": {
|
||||
"php":">=5.3.0",
|
||||
"masterforweb/kuri":"dev-master",
|
||||
"masterforweb/db_lite":"dev-master"
|
||||
},
|
||||
"repositories":[
|
||||
|
||||
{
|
||||
"type":"git",
|
||||
"url":"https://github.com/masterforweb/kuri"
|
||||
},
|
||||
{
|
||||
"type":"git",
|
||||
"url":"https://github.com/masterforweb/db_lite"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
5
composer.sh
Normal file
5
composer.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker run --rm --interactive --tty \
|
||||
--volume $PWD:/app \
|
||||
composer update
|
||||
8
config.php
Normal file
8
config.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
db_config('argumentiru', 'mysql:host=87.249.36.139;dbname=argumentiru', 'argumentiru', 'hjYu78kl*90-Uio23');
|
||||
|
||||
define('SITE', 'https://exdv.argumenti.ru/');
|
||||
76
exdv.js
Normal file
76
exdv.js
Normal file
@@ -0,0 +1,76 @@
|
||||
function trackAdvViews() {
|
||||
// Находим все изображения с data-picture="ex_1109"
|
||||
const images = document.querySelectorAll('img[data-picture^="ex_"]');
|
||||
|
||||
// Получаем данные пользователя
|
||||
const userData = {
|
||||
page_url: window.location.href,
|
||||
user_agent: navigator.userAgent,
|
||||
viewed_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Отслеживаем только уникальные adv_id на этой странице
|
||||
const trackedIds = new Set();
|
||||
|
||||
images.forEach(img => {
|
||||
const pictureData = img.getAttribute('data-picture');
|
||||
const advId = pictureData.replace('ex_', '');
|
||||
|
||||
// Проверяем, не отслеживали ли мы уже этот adv_id на этой странице
|
||||
if (!trackedIds.has(advId)) {
|
||||
trackedIds.add(advId);
|
||||
|
||||
const data = {
|
||||
adv_id: parseInt(advId),
|
||||
...userData
|
||||
};
|
||||
|
||||
// Отправляем запрос на сервер
|
||||
sendViewData(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function sendViewData(data) {
|
||||
const url = 'https://exdv.argumenti.ru';
|
||||
|
||||
console.log('📤 Отправка JSON данных:', data);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
mode: 'no-cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // Важно для JSON!
|
||||
},
|
||||
body: JSON.stringify(data) // Отправляем как JSON
|
||||
})
|
||||
.then(response => {
|
||||
console.log('📥 Статус ответа:', response.status);
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
console.log('✅ Успешный ответ:', result);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Ошибка:', error.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Дебаунсинг для избежания множественных вызовов
|
||||
let trackTimeout = null;
|
||||
function debouncedTrackAdvViews() {
|
||||
if (trackTimeout) {
|
||||
clearTimeout(trackTimeout);
|
||||
}
|
||||
trackTimeout = setTimeout(trackAdvViews, 100);
|
||||
}
|
||||
|
||||
// Запускаем отслеживание при загрузке страницы
|
||||
document.addEventListener('DOMContentLoaded', debouncedTrackAdvViews);
|
||||
96
index.php
Normal file
96
index.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
// exdv/add endpoint
|
||||
//$pdo = new PDO('mysql:host=87.249.36.139;dbname=argumentiru;charset=utf8mb4', 'argumentiru', 'hjYu78kl*90-Uio23');
|
||||
|
||||
// exdv/add endpoint
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
// ОБРАБОТКА OPTIONS ЗАПРОСА (preflight)
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
// Получаем сырые JSON данные
|
||||
$jsonInput = file_get_contents('php://input');
|
||||
$input = json_decode($jsonInput, true);
|
||||
|
||||
// Логируем для отладки
|
||||
error_log("Raw JSON input: " . $jsonInput);
|
||||
error_log("Decoded data: " . print_r($input, true));
|
||||
|
||||
// Проверяем что JSON распарсился
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid JSON: ' . json_last_error_msg()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Валидация данных
|
||||
if (!isset($input['adv_id']) || !is_numeric($input['adv_id'])) {
|
||||
error_log("Ошибка: invalid adv_id");
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid adv_id', 'received_data' => $input]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Подготовка данных
|
||||
$advId = (int)$input['adv_id'];
|
||||
$ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
$userAgent = $input['user_agent'] ?? '';
|
||||
$pageUrl = $input['page_url'] ?? '';
|
||||
$viewedAt = date('Y-m-d H:i:s');
|
||||
|
||||
// Создаем хэш на основе adv_id, ip, user_agent и текущей минуты
|
||||
$minuteWindow = date('Y-m-d H:i');
|
||||
$hashData = $advId . $ipAddress . $userAgent . $minuteWindow;
|
||||
$viewHash = md5($hashData);
|
||||
|
||||
try {
|
||||
$pdo = new PDO('mysql:host=87.249.36.139;dbname=argumentiru;charset=utf8mb4', 'argumentiru', 'hjYu78kl*90-Uio23');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Проверяем существующий просмотр по хэшу
|
||||
$checkStmt = $pdo->prepare("SELECT view_id FROM `adv_views` WHERE view_hash = ? LIMIT 1");
|
||||
$checkStmt->execute([$viewHash]);
|
||||
|
||||
if ($checkStmt->fetch()) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'duplicate' => true,
|
||||
'message' => 'View already recorded in this minute'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Сохраняем новый просмотр с хэшем
|
||||
$stmt = $pdo->prepare("INSERT INTO `adv_views` (`view_hash`, `adv_id`, `ip_address`, `user_agent`, `viewed_at`, `page_url`) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$viewHash, $advId, $ipAddress, $userAgent, $viewedAt, $pageUrl]);
|
||||
|
||||
$viewId = $pdo->lastInsertId();
|
||||
|
||||
http_response_code(200);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'view_id' => $viewId,
|
||||
'view_hash' => $viewHash,
|
||||
'message' => 'View recorded successfully'
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
error_log("Database error: " . $e->getMessage());
|
||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
?>
|
||||
428
report.php
Normal file
428
report.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
// report.php
|
||||
session_start();
|
||||
|
||||
// Проверка пароля
|
||||
$correct_password = 'sdf7-jKl89w'; // Замените на ваш пароль
|
||||
|
||||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||||
if (isset($_POST['password'])) {
|
||||
if ($_POST['password'] === $correct_password) {
|
||||
$_SESSION['authenticated'] = true;
|
||||
} else {
|
||||
$error = "Неверный пароль";
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Доступ к отчету</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #f4f4f4; display: flex; justify-content: center; align-items: center; height: 100vh; }
|
||||
.login-form { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; }
|
||||
input[type="password"] { padding: 10px; margin: 10px 0; width: 200px; border: 1px solid #ddd; border-radius: 4px; }
|
||||
button { background: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }
|
||||
.error { color: #e74c3c; margin-top: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-form">
|
||||
<h2>🔒 Доступ к отчету</h2>
|
||||
<form method="POST">
|
||||
<input type="password" name="password" placeholder="Введите пароль" required>
|
||||
<br>
|
||||
<button type="submit">Войти</button>
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="error"><?= $error ?></div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Основной код отчета
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
// Подключение к базе данных
|
||||
try {
|
||||
|
||||
$pdo = new PDO('mysql:host=87.249.36.139;dbname=argumentiru;charset=utf8mb4', 'argumentiru', 'hjYu78kl*90-Uio23');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
die('Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Обработка параметров периода
|
||||
$start_date = $_GET['start_date'] ?? date('Y-m-01');
|
||||
$end_date = $_GET['end_date'] ?? date('Y-m-d');
|
||||
$adv_id = $_GET['adv_id'] ?? null;
|
||||
$space_id = $_GET['space_id'] ?? null;
|
||||
|
||||
// Валидация дат
|
||||
if (!strtotime($start_date) || !strtotime($end_date)) {
|
||||
$start_date = date('Y-m-01');
|
||||
$end_date = date('Y-m-d');
|
||||
}
|
||||
|
||||
// Получение статистики - ТОЛЬКО баннеры с показами за период
|
||||
function getAdvStats($pdo, $start_date, $end_date, $adv_id = null, $space_id = null) {
|
||||
$sql = "
|
||||
SELECT
|
||||
ai.item_id,
|
||||
ai.itemname,
|
||||
ai.erid,
|
||||
ai.adv_link,
|
||||
ai.space_id,
|
||||
ai.adv_file,
|
||||
ai.desktop,
|
||||
ai.phone,
|
||||
ai.tablet,
|
||||
ai.android,
|
||||
ai.adv_active,
|
||||
COUNT(av.view_id) as views_count,
|
||||
COUNT(DISTINCT av.ip_address) as unique_views,
|
||||
MIN(av.viewed_at) as first_view,
|
||||
MAX(av.viewed_at) as last_view
|
||||
FROM adv_views av
|
||||
INNER JOIN adv_items2 ai ON av.adv_id = ai.item_id
|
||||
WHERE av.viewed_at BETWEEN :start_date AND DATE_ADD(:end_date, INTERVAL 1 DAY)
|
||||
";
|
||||
|
||||
$params = [
|
||||
'start_date' => $start_date,
|
||||
'end_date' => $end_date
|
||||
];
|
||||
|
||||
// Фильтр по ID баннера
|
||||
if ($adv_id) {
|
||||
$sql .= " AND ai.item_id = :adv_id";
|
||||
$params['adv_id'] = $adv_id;
|
||||
}
|
||||
|
||||
// Фильтр по space_id
|
||||
if ($space_id) {
|
||||
$sql .= " AND ai.space_id = :space_id";
|
||||
$params['space_id'] = $space_id;
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY ai.item_id, ai.itemname, ai.erid
|
||||
HAVING views_count > 0
|
||||
ORDER BY views_count DESC, ai.itemname ASC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// Получение списка баннеров для фильтра - ТОЛЬКО те, у которых есть показы за период
|
||||
function getAdvList($pdo, $start_date, $end_date) {
|
||||
$sql = "
|
||||
SELECT DISTINCT
|
||||
ai.item_id,
|
||||
ai.itemname,
|
||||
ai.erid,
|
||||
ai.space_id
|
||||
FROM adv_views av
|
||||
INNER JOIN adv_items2 ai ON av.adv_id = ai.item_id
|
||||
WHERE av.viewed_at BETWEEN :start_date AND DATE_ADD(:end_date, INTERVAL 1 DAY)
|
||||
ORDER BY ai.itemname
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'start_date' => $start_date,
|
||||
'end_date' => $end_date
|
||||
]);
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// Получение списка space_id для фильтра - ТОЛЬКО те, у которых есть показы за период
|
||||
function getSpaceList($pdo, $start_date, $end_date) {
|
||||
$sql = "
|
||||
SELECT DISTINCT ai.space_id
|
||||
FROM adv_views av
|
||||
INNER JOIN adv_items2 ai ON av.adv_id = ai.item_id
|
||||
WHERE av.viewed_at BETWEEN :start_date AND DATE_ADD(:end_date, INTERVAL 1 DAY)
|
||||
ORDER BY ai.space_id
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'start_date' => $start_date,
|
||||
'end_date' => $end_date
|
||||
]);
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
$stats = getAdvStats($pdo, $start_date, $end_date, $adv_id, $space_id);
|
||||
$adv_list = getAdvList($pdo, $start_date, $end_date);
|
||||
$space_list = getSpaceList($pdo, $start_date, $end_date);
|
||||
|
||||
// Общая статистика
|
||||
$total_views = array_sum(array_column($stats, 'views_count'));
|
||||
$total_unique = array_sum(array_column($stats, 'unique_views'));
|
||||
$total_banners = count($stats);
|
||||
|
||||
// Топ баннеры
|
||||
$top_banners = array_slice($stats, 0, 5);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Отчет по показам баннеров</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; background: #f4f4f4; padding: 20px; }
|
||||
.container { max-width: 1400px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
h1 { color: #2c3e50; margin-bottom: 20px; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
||||
.filters { background: #f8f9fa; padding: 20px; border-radius: 6px; margin-bottom: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; font-weight: bold; color: #555; }
|
||||
input, select, button { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
|
||||
button { background: #3498db; color: white; border: none; cursor: pointer; transition: background 0.3s; }
|
||||
button:hover { background: #2980b9; }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px; }
|
||||
.stat-card { background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); text-align: center; border-left: 4px solid #3498db; }
|
||||
.top-banners { background: #fff3cd; padding: 15px; border-radius: 6px; margin-bottom: 20px; border-left: 4px solid #ffc107; }
|
||||
.top-banner-item { padding: 5px 0; border-bottom: 1px solid #ffeaa7; }
|
||||
.stat-number { font-size: 24px; font-weight: bold; color: #2c3e50; }
|
||||
.stat-label { font-size: 14px; color: #7f8c8d; }
|
||||
.table-container { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
th { background: #34495e; color: white; position: sticky; top: 0; }
|
||||
tr:hover { background: #f5f5f5; }
|
||||
.status-active { color: #27ae60; font-weight: bold; }
|
||||
.status-inactive { color: #e74c3c; font-weight: bold; }
|
||||
.device-icon { display: inline-block; width: 20px; text-align: center; }
|
||||
.no-data { text-align: center; padding: 40px; color: #7f8c8d; font-style: italic; }
|
||||
.export-btn { background: #27ae60; margin-left: 10px; }
|
||||
.export-btn:hover { background: #219a52; }
|
||||
.logout-btn { background: #e74c3c; margin-left: 10px; }
|
||||
.logout-btn:hover { background: #c0392b; }
|
||||
.filter-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; }
|
||||
.header-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.erid-column { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header-actions">
|
||||
<h1>📊 Отчет по показам баннеров</h1>
|
||||
<div>
|
||||
<button type="button" class="export-btn" onclick="exportToCSV()">Экспорт в CSV</button>
|
||||
<button type="button" class="logout-btn" onclick="logout()">Выйти</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Фильтры -->
|
||||
<div class="filters">
|
||||
<form method="GET" action="">
|
||||
<div class="filter-row">
|
||||
<div class="form-group">
|
||||
<label for="start_date">Дата с:</label>
|
||||
<input type="date" id="start_date" name="start_date" value="<?= htmlspecialchars($start_date) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="end_date">Дата по:</label>
|
||||
<input type="date" id="end_date" name="end_date" value="<?= htmlspecialchars($end_date) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="adv_id">Баннер:</label>
|
||||
<select id="adv_id" name="adv_id">
|
||||
<option value="">Все баннеры</option>
|
||||
<?php foreach ($adv_list as $adv): ?>
|
||||
<option value="<?= $adv['item_id'] ?>" <?= $adv_id == $adv['item_id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($adv['item_id'] . ' - ' . $adv['itemname'] . ($adv['erid'] ? ' (' . $adv['erid'] . ')' : '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="space_id">ID пространства:</label>
|
||||
<select id="space_id" name="space_id">
|
||||
<option value="">Все пространства</option>
|
||||
<?php foreach ($space_list as $space): ?>
|
||||
<option value="<?= $space['space_id'] ?>" <?= $space_id == $space['space_id'] ? 'selected' : '' ?>>
|
||||
<?= $space['space_id'] ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px;">
|
||||
<button type="submit">Применить фильтры</button>
|
||||
<button type="button" onclick="resetFilters()">Сбросить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Общая статистика -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?= number_format($total_banners) ?></div>
|
||||
<div class="stat-label">Баннеров с показами</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?= number_format($total_views) ?></div>
|
||||
<div class="stat-label">Всего показов</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?= number_format($total_unique) ?></div>
|
||||
<div class="stat-label">Уникальные показы</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">
|
||||
<?= $total_banners > 0 ? number_format($total_views / $total_banners, 1) : 0 ?>
|
||||
</div>
|
||||
<div class="stat-label">Среднее на баннер</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Топ баннеры -->
|
||||
<?php if (!empty($top_banners)): ?>
|
||||
<div class="top-banners">
|
||||
<h3>🏆 Топ-5 баннеров по показам</h3>
|
||||
<?php foreach ($top_banners as $index => $banner): ?>
|
||||
<div class="top-banner-item">
|
||||
<strong>#<?= $index + 1 ?></strong>
|
||||
ID <?= $banner['item_id'] ?> - <?= htmlspecialchars($banner['itemname']) ?>
|
||||
<?php if ($banner['erid']): ?>
|
||||
(<?= htmlspecialchars($banner['erid']) ?>)
|
||||
<?php endif; ?>
|
||||
<span style="float: right; color: #e74c3c; font-weight: bold;">
|
||||
<?= number_format($banner['views_count']) ?> показов
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Таблица с данными -->
|
||||
<div class="table-container">
|
||||
<?php if (!empty($stats)): ?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>ERID</th>
|
||||
<th>Ссылка</th>
|
||||
<th>ID пространства</th>
|
||||
<th>Устройства</th>
|
||||
<th>Статус</th>
|
||||
<th>Показы</th>
|
||||
<th>Уникальные</th>
|
||||
<th>Первый показ</th>
|
||||
<th>Последний показ</th>
|
||||
<th>CTR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$days_diff = max(1, (strtotime($end_date) - strtotime($start_date)) / (60 * 60 * 24) + 1);
|
||||
foreach ($stats as $row):
|
||||
$avg_per_day = $row['views_count'] / $days_diff;
|
||||
$ctr = $row['views_count'] > 0 ? ($row['unique_views'] / $row['views_count'] * 100) : 0;
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $row['item_id'] ?></td>
|
||||
<td><strong><?= htmlspecialchars($row['itemname']) ?></strong></td>
|
||||
<td class="erid-column"><?= htmlspecialchars($row['erid'] ?: '—') ?></td>
|
||||
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<?= htmlspecialchars($row['adv_link']) ?>
|
||||
</td>
|
||||
<td><?= $row['space_id'] ?></td>
|
||||
<td>
|
||||
<?php if ($row['desktop']): ?><span class="device-icon" title="Desktop">🖥️</span><?php endif; ?>
|
||||
<?php if ($row['phone']): ?><span class="device-icon" title="Phone">📱</span><?php endif; ?>
|
||||
<?php if ($row['tablet']): ?><span class="device-icon" title="Tablet">📟</span><?php endif; ?>
|
||||
<?php if ($row['android']): ?><span class="device-icon" title="Android">🤖</span><?php endif; ?>
|
||||
</td>
|
||||
<td class="<?= $row['adv_active'] ? 'status-active' : 'status-inactive' ?>">
|
||||
<?= $row['adv_active'] ? 'Активен' : 'Неактивен' ?>
|
||||
</td>
|
||||
<td><span style="color: #e74c3c; font-weight: bold;"><?= number_format($row['views_count']) ?></span></td>
|
||||
<td><?= number_format($row['unique_views']) ?></td>
|
||||
<td><?= $row['first_view'] ? date('d.m.Y H:i', strtotime($row['first_view'])) : '—' ?></td>
|
||||
<td><?= $row['last_view'] ? date('d.m.Y H:i', strtotime($row['last_view'])) : '—' ?></td>
|
||||
<td><?= number_format($ctr, 1) ?>%</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="no-data">
|
||||
<h3>📭 Нет данных за выбранный период</h3>
|
||||
<p>Попробуйте изменить параметры фильтрации</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Экспорт в CSV
|
||||
function exportToCSV() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('export', 'csv');
|
||||
window.location.href = 'report_export.php?' + params.toString();
|
||||
}
|
||||
|
||||
// Сброс фильтров
|
||||
function resetFilters() {
|
||||
document.getElementById('start_date').value = '<?= date('Y-m-01') ?>';
|
||||
document.getElementById('end_date').value = '<?= date('Y-m-d') ?>';
|
||||
document.getElementById('adv_id').value = '';
|
||||
document.getElementById('space_id').value = '';
|
||||
}
|
||||
|
||||
// Выход из системы
|
||||
function logout() {
|
||||
if (confirm('Вы уверены, что хотите выйти?')) {
|
||||
window.location.href = '?logout=1';
|
||||
}
|
||||
}
|
||||
|
||||
// Автоматическое ограничение дат
|
||||
document.getElementById('start_date').addEventListener('change', function() {
|
||||
const endDate = document.getElementById('end_date');
|
||||
if (this.value > endDate.value) {
|
||||
endDate.value = this.value;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('end_date').addEventListener('change', function() {
|
||||
const startDate = document.getElementById('start_date');
|
||||
if (this.value < startDate.value) {
|
||||
startDate.value = this.value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
// Обработка выхода
|
||||
if (isset($_GET['logout'])) {
|
||||
session_destroy();
|
||||
header('Location: report.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user