39564-vm/admin.php
2026-04-11 19:06:55 +00:00

293 lines
17 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/app/bootstrap.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'login') {
$username = trim((string)($_POST['username'] ?? ''));
$password = (string)($_POST['password'] ?? '');
if (diagnostic_admin_login($username, $password)) {
diagnostic_flash_set('success', 'Sesja administratora została rozpoczęta.');
header('Location: /admin.php');
exit;
}
diagnostic_flash_set('danger', 'Nieprawidłowy login lub hasło administratora.');
header('Location: /admin.php');
exit;
}
if ($action === 'logout') {
diagnostic_admin_logout();
diagnostic_flash_set('info', 'Sesja administratora została zakończona.');
header('Location: /admin.php');
exit;
}
}
if (diagnostic_admin_is_authenticated() && ($_GET['export'] ?? '') === 'csv') {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="wyniki-diagnozy-' . date('Ymd-His') . '.csv"');
echo diagnostic_admin_export_csv();
exit;
}
$flash = diagnostic_flash_get();
$meta = diagnostic_meta('Panel administratora', 'Zabezpieczony panel do przeglądu prób diagnozy i wyników dojrzałości procesowej.');
$definition = diagnostic_quiz_definition();
$attempts = diagnostic_admin_is_authenticated() ? diagnostic_admin_attempts() : [];
$stats = diagnostic_admin_is_authenticated() ? diagnostic_admin_stats() : [];
$selectedAttempt = null;
if (diagnostic_admin_is_authenticated() && isset($_GET['attempt'])) {
$selectedAttempt = diagnostic_get_attempt((int)$_GET['attempt']);
}
if (!$selectedAttempt && !empty($attempts)) {
$selectedAttempt = diagnostic_get_attempt((int)$attempts[0]['id']);
}
$questionMap = diagnostic_question_map();
$credentials = diagnostic_admin_credentials();
?>
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($meta['title']) ?></title>
<meta name="description" content="<?= htmlspecialchars($meta['description']) ?>">
<meta property="og:title" content="<?= htmlspecialchars($meta['title']) ?>">
<meta property="og:description" content="<?= htmlspecialchars($meta['description']) ?>">
<meta property="twitter:title" content="<?= htmlspecialchars($meta['title']) ?>">
<meta property="twitter:description" content="<?= htmlspecialchars($meta['description']) ?>">
<?php if ($meta['image'] !== ''): ?>
<meta property="og:image" content="<?= htmlspecialchars($meta['image']) ?>">
<meta property="twitter:image" content="<?= htmlspecialchars($meta['image']) ?>">
<?php endif; ?>
<meta name="robots" content="noindex, nofollow">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/assets/css/custom.css?v=<?= urlencode((string)@filemtime(__DIR__ . '/assets/css/custom.css')) ?>">
</head>
<body class="app-shell">
<header class="site-header border-bottom bg-white sticky-top">
<nav class="navbar navbar-expand-lg navbar-light py-3">
<div class="container">
<a class="navbar-brand fw-semibold text-dark" href="/">Przegląd procesów</a>
<div class="d-flex align-items-center gap-2 ms-auto">
<a class="btn btn-sm btn-outline-dark" href="/diagnostic.php">Diagnoza</a>
<?php if (diagnostic_admin_is_authenticated()): ?>
<form method="post" class="m-0">
<input type="hidden" name="action" value="logout">
<button type="submit" class="btn btn-sm btn-dark">Wyloguj</button>
</form>
<?php endif; ?>
</div>
</div>
</nav>
</header>
<main class="py-4 py-lg-5">
<div class="<?= diagnostic_admin_is_authenticated() ? 'container-fluid admin-workspace' : 'container' ?>">
<?php if ($flash): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> mb-4" role="alert">
<?= htmlspecialchars($flash['message']) ?>
</div>
<?php endif; ?>
<?php if (!diagnostic_admin_is_authenticated()): ?>
<section class="row justify-content-center">
<div class="col-lg-5">
<div class="surface-card p-4 p-lg-5">
<div class="eyebrow mb-3">Strefa zabezpieczona</div>
<h1 class="h3 mb-3">Logowanie administratora</h1>
<p class="text-secondary mb-4">Panel służy do przeglądu prób, analizy wyników oraz eksportu danych z diagnozy dojrzałości procesowej.</p>
<form method="post" novalidate>
<input type="hidden" name="action" value="login">
<div class="mb-3">
<label for="username" class="form-label">Login</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-4">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-dark w-100">Wejdź do panelu</button>
</form>
<?php if ($credentials['using_default']): ?>
<div class="alert alert-warning mt-4 mb-0" role="alert">
Uwaga: aktywne są domyślne dane logowania administratora (<strong>admin / admin123</strong>). Przed użyciem produkcyjnym ustaw własne wartości <code>DIAGNOSTIC_ADMIN_USER</code> i <code>DIAGNOSTIC_ADMIN_PASS</code>.
</div>
<?php endif; ?>
</div>
</div>
</section>
<?php else: ?>
<section class="surface-card p-4 p-lg-5 mb-4">
<div class="d-flex flex-column flex-lg-row justify-content-between gap-3 align-items-lg-center">
<div>
<div class="eyebrow mb-2">Panel administratora</div>
<h1 class="page-title mb-1">Operacje i wyniki diagnozy</h1>
<p class="text-secondary mb-0">Przeglądaj próby respondentów, śledź skuteczność ukończenia i analizuj wyniki w poszczególnych obszarach.</p>
</div>
<a class="btn btn-outline-dark" href="/admin.php?export=csv">Eksportuj CSV</a>
</div>
</section>
<section class="row g-3 mb-4">
<div class="col-sm-6 col-lg-3"><div class="metric-card"><strong><?= (int)$stats['total_attempts'] ?></strong><span>Wszystkie próby</span></div></div>
<div class="col-sm-6 col-lg-3"><div class="metric-card"><strong><?= (int)$stats['completed_attempts'] ?></strong><span>Ukończone</span></div></div>
<div class="col-sm-6 col-lg-3"><div class="metric-card"><strong><?= (int)$stats['completion_rate'] ?>%</strong><span>Wskaźnik ukończenia</span></div></div>
<div class="col-sm-6 col-lg-3"><div class="metric-card"><strong><?= (int)$stats['average_score'] ?>%</strong><span>Średni wynik</span></div></div>
</section>
<section class="row g-4">
<div class="col-xl-8 col-xxl-9">
<div class="surface-card p-4 h-100">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<div class="eyebrow mb-2">Lista prób</div>
<h2 class="h4 mb-0">Ostatnie odpowiedzi</h2>
</div>
<span class="pill-badge subdued"><?= count($attempts) ?> rekordów</span>
</div>
<?php if (empty($attempts)): ?>
<div class="empty-state text-center py-5 px-4">
<h3 class="h5 mb-2">Brak zapisanych prób</h3>
<p class="text-secondary mb-0">Po pierwszym wypełnieniu diagnozy przez użytkownika wyniki pojawią się tutaj automatycznie.</p>
</div>
<?php else: ?>
<div class="table-responsive admin-table-wrap">
<table class="table admin-table align-middle mb-0">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">E-mail</th>
<th scope="col">Status</th>
<th scope="col">Telefon</th>
<th scope="col">Wynik</th>
<th scope="col">Segment</th>
<th scope="col">Raport</th>
<th scope="col">Szczegóły</th>
</tr>
</thead>
<tbody>
<?php foreach ($attempts as $row): ?>
<tr>
<td>#<?= (int)$row['id'] ?></td>
<td><?= htmlspecialchars((string)$row['email']) ?></td>
<td><span class="pill-badge<?= ($row['status'] ?? '') === 'completed' ? '' : ' subdued' ?>"><?= ($row['status'] ?? '') === 'completed' ? 'ukończona' : 'w trakcie' ?></span></td>
<td><?= !empty($row['contact_phone']) ? htmlspecialchars((string)$row['contact_phone']) : '—' ?></td>
<td><?= isset($row['result']['percentage_score']) ? (int)$row['result']['percentage_score'] . '%' : '—' ?></td>
<td><?= htmlspecialchars((string)($row['result']['segment_label'] ?? '—')) ?></td>
<td><?= htmlspecialchars((string)$row['email_report_status']) ?></td>
<td><a class="btn btn-sm btn-outline-dark" href="/admin.php?attempt=<?= (int)$row['id'] ?>">Zobacz</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<div class="col-xl-4 col-xxl-3">
<div class="surface-card p-4 h-100">
<?php if (!$selectedAttempt): ?>
<div class="empty-state text-center py-5 px-4">
<h3 class="h5 mb-2">Brak wybranej próby</h3>
<p class="text-secondary mb-0">Wybierz odpowiedź z listy, aby zobaczyć szczegóły, rozkład sekcji i pełny zestaw odpowiedzi.</p>
</div>
<?php else: ?>
<?php $result = $selectedAttempt['result'] ?? []; ?>
<div class="d-flex justify-content-between gap-3 mb-4 align-items-start">
<div>
<div class="eyebrow mb-2">Szczegóły próby #<?= (int)$selectedAttempt['id'] ?></div>
<h2 class="h4 mb-1"><?= htmlspecialchars((string)$selectedAttempt['email']) ?></h2>
<p class="text-secondary mb-0">Rozpoczęto: <?= htmlspecialchars((string)$selectedAttempt['started_at']) ?></p>
</div>
<div class="score-chip text-end">
<span>Wynik</span>
<strong><?= isset($result['percentage_score']) ? (int)$result['percentage_score'] . '%' : '—' ?></strong>
</div>
</div>
<div class="mb-4">
<div class="row g-3">
<div class="col-sm-6"><div class="compact-card"><strong>Status</strong><span><?= ($selectedAttempt['status'] ?? '') === 'completed' ? 'Ukończona' : 'W trakcie' ?></span></div></div>
<div class="col-sm-6"><div class="compact-card"><strong>Raport e-mail</strong><span><?= htmlspecialchars((string)$selectedAttempt['email_report_status']) ?></span></div></div>
<div class="col-sm-6"><div class="compact-card"><strong>Zgoda marketingowa</strong><span><?= ((int)$selectedAttempt['marketing_consent']) === 1 ? 'Tak' : 'Nie' ?></span></div></div>
<div class="col-sm-6"><div class="compact-card"><strong>Telefon kontaktowy</strong><span><?= !empty($selectedAttempt['contact_phone']) ? htmlspecialchars((string)$selectedAttempt['contact_phone']) : '—' ?></span></div></div>
<div class="col-sm-6"><div class="compact-card"><strong>Prośba o kontakt</strong><span><?= htmlspecialchars((string)($selectedAttempt['consultation_requested_at'] ?? '—')) ?></span></div></div>
<div class="col-sm-6"><div class="compact-card"><strong>Zakończono</strong><span><?= htmlspecialchars((string)($selectedAttempt['completed_at'] ?? '—')) ?></span></div></div>
</div>
</div>
<?php if (!empty($result)): ?>
<div class="mb-4">
<h3 class="h5 mb-3">Podsumowanie wyniku</h3>
<p class="text-secondary mb-3"><strong><?= htmlspecialchars((string)($result['segment_label'] ?? '')) ?></strong> — <?= htmlspecialchars((string)($result['segment_summary'] ?? '')) ?></p>
<div class="row g-3">
<?php foreach (($result['section_scores'] ?? []) as $section): ?>
<div class="col-12">
<div class="section-score-card">
<span class="small text-secondary d-block mb-2"><?= htmlspecialchars($section['section_name']) ?></span>
<strong class="d-block mb-2"><?= (int)$section['percentage'] ?>%</strong>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-dark" role="progressbar" style="width: <?= (int)$section['percentage'] ?>%;" aria-valuenow="<?= (int)$section['percentage'] ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div>
<h3 class="h5 mb-3">Odpowiedzi respondenta</h3>
<ul class="answer-review-list mb-0">
<?php foreach ($questionMap as $questionId => $question): ?>
<?php $rawAnswer = $selectedAttempt['answers'][$questionId] ?? null; ?>
<li>
<em><?= htmlspecialchars($question['section_name']) ?></em>
<strong><?= htmlspecialchars($question['text']) ?></strong>
<span class="text-secondary"><?= $rawAnswer === null ? 'Brak odpowiedzi' : htmlspecialchars(diagnostic_answer_label((int)$rawAnswer)) ?></span>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
</div>
</section>
<section class="surface-card p-4 mt-4">
<div class="section-intro mb-3">
<div class="eyebrow">Zakres diagnozy</div>
<h2 class="h4 mb-0">Obszary oceniane w narzędziu</h2>
</div>
<div class="row g-3">
<?php foreach ($definition['sections'] as $section): ?>
<div class="col-md-6 col-xl-4">
<div class="compact-card h-100">
<strong><?= htmlspecialchars($section['name']) ?></strong>
<span><?= count($section['questions']) ?> pytań w tej sekcji.</span>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
</div>
</main>
<footer class="border-top py-4 bg-white">
<div class="container d-flex flex-column flex-md-row justify-content-between gap-2">
<span class="text-secondary small">Panel administracyjny diagnozy dojrzałości procesowej.</span>
<div class="d-flex gap-3 small">
<a class="text-decoration-none text-secondary" href="/">Strona główna</a>
<a class="text-decoration-none text-secondary" href="/diagnostic.php">Diagnoza</a>
<a class="text-decoration-none text-secondary" href="/healthz.php">Status</a>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
<script src="/assets/js/main.js?v=<?= urlencode((string)@filemtime(__DIR__ . '/assets/js/main.js')) ?>" defer></script>
</body>
</html>