208 lines
8.6 KiB
PHP
208 lines
8.6 KiB
PHP
<?php
|
||
require_once __DIR__ . '/auth.php';
|
||
$user = requireAuth();
|
||
|
||
// Filters for Helpers and Curators
|
||
$statusFilter = $_GET['status'] ?? '';
|
||
$categoryFilter = $_GET['category'] ?? '';
|
||
$searchQuery = $_GET['search'] ?? '';
|
||
|
||
$params = [];
|
||
$sql = "SELECT t.*, u.username as creator_name FROM tickets t JOIN users u ON t.user_id = u.id";
|
||
$whereClauses = [];
|
||
|
||
if ($user['role'] === 'user') {
|
||
$whereClauses[] = "t.user_id = ?";
|
||
$params[] = $user['id'];
|
||
} else {
|
||
if ($statusFilter) {
|
||
$whereClauses[] = "t.status = ?";
|
||
$params[] = $statusFilter;
|
||
}
|
||
if ($categoryFilter) {
|
||
$whereClauses[] = "t.category = ?";
|
||
$params[] = $categoryFilter;
|
||
}
|
||
if ($searchQuery) {
|
||
$whereClauses[] = "(t.title LIKE ? OR t.description LIKE ?)";
|
||
$params[] = "%$searchQuery%";
|
||
$params[] = "%$searchQuery%";
|
||
}
|
||
}
|
||
|
||
if (!empty($whereClauses)) {
|
||
$sql .= " WHERE " . implode(" AND ", $whereClauses);
|
||
}
|
||
|
||
$sql .= " ORDER BY t.created_at DESC";
|
||
|
||
$stmt = db()->prepare($sql);
|
||
$stmt->execute($params);
|
||
$tickets = $stmt->fetchAll();
|
||
|
||
$openTicketsCount = 0;
|
||
if ($user['role'] === 'user') {
|
||
foreach ($tickets as $t) {
|
||
if ($t['status'] !== 'closed') $openTicketsCount++;
|
||
}
|
||
}
|
||
|
||
// Stats for Curator
|
||
$stats = [];
|
||
if ($user['role'] === 'curator') {
|
||
$stats['total'] = db()->query("SELECT COUNT(*) FROM tickets")->fetchColumn();
|
||
$stats['active'] = db()->query("SELECT COUNT(*) FROM tickets WHERE status != 'closed'")->fetchColumn();
|
||
// Avg response time (simplified: difference between ticket creation and first message from a helper/curator)
|
||
$stats['avg_response'] = db()->query("
|
||
SELECT ROUND(AVG(TIMESTAMPDIFF(HOUR, t.created_at, m.created_at)), 1)
|
||
FROM tickets t
|
||
JOIN messages m ON t.id = m.ticket_id
|
||
JOIN users u ON m.user_id = u.id
|
||
WHERE u.role != 'user'
|
||
AND m.id = (
|
||
SELECT MIN(m2.id)
|
||
FROM messages m2
|
||
JOIN users u2 ON m2.user_id = u2.id
|
||
WHERE m2.ticket_id = t.id AND u2.role != 'user'
|
||
)
|
||
")->fetchColumn() ?: '...';
|
||
}
|
||
|
||
function getStatusLabel($status) {
|
||
$labels = [
|
||
'open' => 'Открыт',
|
||
'in_progress' => 'В работе',
|
||
'awaiting_response' => 'Ожидает ответа',
|
||
'closed' => 'Закрыт'
|
||
];
|
||
return $labels[$status] ?? $status;
|
||
}
|
||
|
||
function getPriorityLabel($priority) {
|
||
$labels = [
|
||
'low' => 'Низкий',
|
||
'medium' => 'Средний',
|
||
'high' => 'Высокий'
|
||
];
|
||
return $labels[$priority] ?? $priority;
|
||
}
|
||
?>
|
||
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Панель управления - Система поддержки</title>
|
||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||
</head>
|
||
<body>
|
||
<div class="bg-animations">
|
||
<div class="blob blob-1"></div>
|
||
<div class="blob blob-2"></div>
|
||
<div class="blob blob-3"></div>
|
||
</div>
|
||
|
||
<div class="main-wrapper">
|
||
<nav class="navbar">
|
||
<div class="logo">SupportSystem</div>
|
||
<div class="user-info">
|
||
<span><?= htmlspecialchars($user['username']) ?> (<?= $user['role'] ?>)</span>
|
||
<a href="logout.php" class="logout-link">Выйти</a>
|
||
</div>
|
||
</nav>
|
||
|
||
<?php if ($user['role'] === 'curator'): ?>
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-value"><?= $stats['total'] ?></div>
|
||
<div class="stat-label">Всего тикетов</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value"><?= $stats['active'] ?></div>
|
||
<div class="stat-label">Активных</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value"><?= $stats['avg_response'] ?> ч</div>
|
||
<div class="stat-label">Ср. время ответа</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<a href="admin.php" style="text-decoration: none;">
|
||
<div class="stat-value">👥</div>
|
||
<div class="stat-label">Управление пользователями</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="dashboard-header">
|
||
<h1>Тикеты</h1>
|
||
<?php if ($user['role'] === 'user'): ?>
|
||
<?php if ($openTicketsCount < 3): ?>
|
||
<a href="create_ticket.php" class="btn-primary" style="padding: 0.5rem 1rem; width: auto; text-decoration: none;">Создать тикет</a>
|
||
<?php else: ?>
|
||
<span class="text-secondary" style="font-size: 0.875rem;">Максимум 3 открытых тикета</span>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if ($user['role'] !== 'user'): ?>
|
||
<form class="filters-area" style="background-color: var(--card-bg); padding: 1rem; border-radius: 0.75rem; border: 1px solid var(--border-color); margin-bottom: 1.5rem; display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-end;">
|
||
<div class="form-group" style="margin-bottom: 0; flex: 1; min-width: 200px;">
|
||
<label>Поиск</label>
|
||
<input type="text" name="search" value="<?= htmlspecialchars($searchQuery) ?>" placeholder="По теме или описанию...">
|
||
</div>
|
||
<div class="form-group" style="margin-bottom: 0;">
|
||
<label>Статус</label>
|
||
<select name="status">
|
||
<option value="">Все</option>
|
||
<option value="open" <?= $statusFilter === 'open' ? 'selected' : '' ?>>Открыт</option>
|
||
<option value="in_progress" <?= $statusFilter === 'in_progress' ? 'selected' : '' ?>>В работе</option>
|
||
<option value="awaiting_response" <?= $statusFilter === 'awaiting_response' ? 'selected' : '' ?>>Ожидает ответа</option>
|
||
<option value="closed" <?= $statusFilter === 'closed' ? 'selected' : '' ?>>Закрыт</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="margin-bottom: 0;">
|
||
<label>Категория</label>
|
||
<select name="category">
|
||
<option value="">Все</option>
|
||
<option value="technical" <?= $categoryFilter === 'technical' ? 'selected' : '' ?>>Техническая</option>
|
||
<option value="question" <?= $categoryFilter === 'question' ? 'selected' : '' ?>>Вопрос</option>
|
||
<option value="complaint" <?= $categoryFilter === 'complaint' ? 'selected' : '' ?>>Жалоба</option>
|
||
<option value="other" <?= $categoryFilter === 'other' ? 'selected' : '' ?>>Другое</option>
|
||
</select>
|
||
</div>
|
||
<button type="submit" class="btn-primary" style="width: auto; height: 42px; padding: 0 1rem;">Найти</button>
|
||
<a href="index.php" style="color: var(--text-secondary); text-decoration: none; padding-bottom: 0.5rem;">Сбросить</a>
|
||
</form>
|
||
<?php endif; ?>
|
||
|
||
<div class="ticket-grid">
|
||
<?php if (empty($tickets)): ?>
|
||
<p class="text-secondary">Тикетов не найдено.</p>
|
||
<?php else: ?>
|
||
<?php foreach ($tickets as $ticket): ?>
|
||
<a href="ticket.php?id=<?= $ticket['id'] ?>" class="ticket-card">
|
||
<div class="ticket-badge badge-<?= $ticket['status'] ?>">
|
||
<?= getStatusLabel($ticket['status']) ?>
|
||
</div>
|
||
<h3><?= htmlspecialchars($ticket['title']) ?></h3>
|
||
<p><?= htmlspecialchars($ticket['description']) ?></p>
|
||
<div class="ticket-meta">
|
||
<span>Приоритет: <?= getPriorityLabel($ticket['priority']) ?></span>
|
||
<span><?= date('d.m.Y H:i', strtotime($ticket['created_at'])) ?></span>
|
||
</div>
|
||
<?php if (isset($ticket['creator_name'])): ?>
|
||
<div class="ticket-meta" style="margin-top: 0.5rem;">
|
||
<span>От: <?= htmlspecialchars($ticket['creator_name']) ?></span>
|
||
</div>
|
||
<?php endif; ?>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||
</body>
|
||
</html>
|