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