39564-vm/diagnostic.php
2026-04-12 03:50:34 +00:00

467 lines
25 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/app/bootstrap.php';
$definition = diagnostic_quiz_definition();
$questions = diagnostic_flat_questions();
$totalQuestions = count($questions);
$totalSections = count($definition['sections'] ?? []);
$flash = diagnostic_flash_get();
if (isset($_GET['reset']) && $_GET['reset'] === '1') {
diagnostic_clear_attempt_session();
diagnostic_flash_set('info', 'Poprzednia, niedokończona diagnoza została wyczyszczona.');
header('Location: /diagnostic.php');
exit;
}
$attempt = diagnostic_current_attempt();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'start') {
$email = trim((string)($_POST['email'] ?? ''));
$marketingConsent = isset($_POST['marketing_consent']) && $_POST['marketing_consent'] === '1';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
diagnostic_flash_set('danger', 'Podaj poprawny adres e-mail, na który mamy wysłać raport.');
header('Location: /diagnostic.php');
exit;
}
diagnostic_create_attempt($email, $marketingConsent);
header('Location: /diagnostic.php?step=1');
exit;
}
if ($action === 'save-answer') {
$attempt = diagnostic_current_attempt();
if (!$attempt) {
diagnostic_flash_set('warning', 'Sesja wygasła. Rozpocznij diagnozę ponownie.');
header('Location: /diagnostic.php');
exit;
}
$step = max(1, (int)($_POST['step'] ?? 1));
$nav = $_POST['nav'] ?? 'next';
$question = $questions[$step - 1] ?? null;
if (!$question) {
header('Location: /diagnostic.php');
exit;
}
if ($nav === 'back') {
if (isset($_POST['answer']) && $_POST['answer'] !== '') {
diagnostic_save_answer((int)$attempt['id'], $question['id'], (int)$_POST['answer'], $step);
}
$targetStep = max(1, $step - 1);
diagnostic_update_step((int)$attempt['id'], $targetStep);
header('Location: /diagnostic.php?step=' . $targetStep);
exit;
}
if (!isset($_POST['answer']) || $_POST['answer'] === '' || !in_array((int)$_POST['answer'], [0, 1, 2, 3], true)) {
diagnostic_flash_set('danger', 'Wybierz jedną odpowiedź, aby przejść dalej.');
header('Location: /diagnostic.php?step=' . $step);
exit;
}
diagnostic_save_answer((int)$attempt['id'], $question['id'], (int)$_POST['answer'], $step);
if ($step >= $totalQuestions) {
diagnostic_finalize_attempt((int)$attempt['id']);
header('Location: /diagnostic.php?view=result');
exit;
}
$targetStep = $step + 1;
diagnostic_update_step((int)$attempt['id'], $targetStep);
header('Location: /diagnostic.php?step=' . $targetStep);
exit;
}
if ($action === 'request-consultation') {
$attempt = diagnostic_current_attempt();
if (!$attempt || ($attempt['status'] ?? '') !== 'completed') {
diagnostic_flash_set('warning', 'Najpierw ukończ diagnozę, aby zostawić numer telefonu do kontaktu.');
header('Location: /diagnostic.php');
exit;
}
$phone = diagnostic_normalize_phone((string)($_POST['phone'] ?? ''));
if (!diagnostic_phone_is_valid($phone)) {
diagnostic_flash_set('danger', 'Podaj poprawny numer telefonu, abyśmy mogli wrócić z propozycją dalszej konsultacji.');
header('Location: /diagnostic.php?view=result&modal=consultation');
exit;
}
diagnostic_save_contact_phone((int)$attempt['id'], $phone);
$updatedAttempt = diagnostic_get_attempt((int)$attempt['id']) ?? $attempt;
$notificationResult = diagnostic_send_consultation_notification($updatedAttempt);
if (!empty($notificationResult['success'])) {
diagnostic_flash_set('success', 'Dziękujemy. Numer telefonu został zapisany, a administrator otrzymał powiadomienie e-mail o prośbie o kontakt.');
} elseif (($notificationResult['reason'] ?? '') === 'missing_recipient') {
diagnostic_flash_set('warning', 'Numer telefonu został zapisany, ale adres powiadomień administratora nie jest jeszcze ustawiony w panelu administracyjnym.');
} else {
error_log('Consultation notification email failed: ' . ($notificationResult['error'] ?? 'unknown'));
diagnostic_flash_set('warning', 'Numer telefonu został zapisany, ale nie udało się wysłać powiadomienia e-mail do administratora.');
}
header('Location: /diagnostic.php?view=result');
exit;
}
}
$attempt = diagnostic_current_attempt();
if ($attempt && ($attempt['status'] ?? '') === 'completed' && (!isset($_GET['step']) && ($_GET['view'] ?? '') !== 'result')) {
header('Location: /diagnostic.php?view=result');
exit;
}
if ($attempt && ($attempt['status'] ?? '') === 'in_progress' && !isset($_GET['step']) && ($_GET['view'] ?? '') !== 'landing') {
header('Location: /diagnostic.php?step=' . (int)$attempt['current_step']);
exit;
}
$requestedView = $_GET['view'] ?? '';
if ($requestedView === 'result') {
$view = 'result';
} elseif (isset($_GET['step']) || ($attempt && ($attempt['status'] ?? '') === 'in_progress')) {
$view = 'quiz';
} else {
$view = 'landing';
}
$currentStep = max(1, min($totalQuestions, (int)($_GET['step'] ?? ($attempt['current_step'] ?? 1))));
$currentQuestion = $questions[$currentStep - 1] ?? null;
$currentAnswer = $attempt['answers'][$currentQuestion['id'] ?? ''] ?? null;
$progressPercent = (int)round(($currentStep / max($totalQuestions, 1)) * 100);
$openConsultationModal = isset($_GET['modal']) && $_GET['modal'] === 'consultation';
$meta = diagnostic_meta('Diagnoza dojrzałości procesowej', 'Profesjonalna diagnoza dojrzałości procesowej z natychmiastowym podsumowaniem i raportem wysyłanym na e-mail.');
$resultOverview = ($view === 'result' && $attempt && ($attempt['status'] ?? '') === 'completed') ? diagnostic_resolve_result_overview($attempt) : '';
?>
<!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; ?>
<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">
<?= diagnostic_brand_logo_anchor('navbar-brand doctor-biznes-logo-link--nav') ?>
<div class="d-flex align-items-center gap-2 ms-auto">
<a class="btn btn-sm btn-dark" href="/diagnostic.php<?= $attempt ? '?reset=1' : '' ?>"><?= $attempt ? 'Rozpocznij od nowa' : 'Rozpocznij diagnozę' ?></a>
</div>
</div>
</nav>
</header>
<main class="py-4 py-lg-5">
<div class="container">
<?php if ($flash): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> mb-4" role="alert">
<?= htmlspecialchars($flash['message']) ?>
</div>
<?php endif; ?>
<?php if ($view === 'landing'): ?>
<section class="surface-card p-4 p-lg-5 mb-4">
<div class="row g-4 align-items-center">
<div class="col-lg-7">
<div class="eyebrow mb-3">Narzędzie diagnostyczne</div>
<h1 class="page-title mb-3"><?= htmlspecialchars($definition['quiz']['title']) ?></h1>
<p class="lead text-secondary mb-4"><?= htmlspecialchars($definition['quiz']['subtitle']) ?></p>
<div class="row g-3 mb-4">
<div class="col-sm-6"><div class="metric-inline"><span>Czas wypełnienia</span><strong><?= htmlspecialchars($definition['quiz']['estimated_time']) ?></strong></div></div>
<div class="col-sm-6"><div class="metric-inline"><span>Format</span><strong><?= $totalQuestions ?> pytań w <?= $totalSections ?> obszarach</strong></div></div>
<div class="col-sm-6"><div class="metric-inline"><span>Rezultat</span><strong>Wynik na ekranie + raport e-mail</strong></div></div>
<div class="col-sm-6"><div class="metric-inline"><span>Odbiorca</span><strong>Właściciele i liderzy operacyjni</strong></div></div>
</div>
<ul class="feature-list mb-0">
<li>Jedno pytanie na ekranie, bez zbędnego rozproszenia.</li>
<li>Ocena w 7 kluczowych obszarach operacyjnych firmy.</li>
<li>Natychmiastowe podsumowanie z najmocniejszymi i najsłabszymi obszarami.</li>
<li>Raport wysyłany na adres e-mail podany przed rozpoczęciem diagnozy.</li>
</ul>
</div>
<div class="col-lg-5">
<div class="surface-card p-4 bg-white border h-100">
<div class="eyebrow mb-3">Krok 1 z 2</div>
<h2 class="h4 mb-3">Podaj adres e-mail</h2>
<p class="text-secondary mb-4">Podsumowanie zobaczysz od razu na ekranie, a rozszerzony raport wyślemy na wskazany adres e-mail. Konto użytkownika nie jest wymagane.</p>
<form method="post" novalidate>
<input type="hidden" name="action" value="start">
<div class="mb-3">
<label for="email" class="form-label">Adres e-mail</label>
<input type="email" class="form-control form-control-lg" id="email" name="email" placeholder="np. zarzad@twojafirma.pl" required>
</div>
<p class="small text-secondary mb-3">Podanie adresu e-mail jest wymagane wyłącznie do dostarczenia raportu z diagnozy. Zgoda marketingowa jest opcjonalna i niezależna od wysyłki raportu.</p>
<div class="form-check mb-4">
<input class="form-check-input" type="checkbox" value="1" id="marketingConsent" name="marketing_consent">
<label class="form-check-label text-secondary" for="marketingConsent">
Wyrażam zgodę na otrzymywanie dodatkowych materiałów dotyczących usprawniania procesów i zarządzania operacyjnego.
</label>
</div>
<button type="submit" class="btn btn-dark btn-lg w-100">Rozpocznij diagnozę</button>
</form>
</div>
</div>
</div>
</section>
<section class="row g-4">
<div class="col-lg-8">
<div class="surface-card p-4 p-lg-5 h-100">
<div class="section-intro mb-3">
<div class="eyebrow">Zakres diagnozy</div>
<h2 class="section-title">Co ocenia to narzędzie</h2>
</div>
<div class="row g-3">
<?php foreach ($definition['sections'] as $section): ?>
<div class="col-md-6">
<div class="compact-card">
<strong><?= htmlspecialchars($section['name']) ?></strong>
<span><?= count($section['questions']) ?> pytań dotyczących dojrzałości operacyjnej w tym obszarze.</span>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="surface-card p-4 p-lg-5 h-100">
<div class="section-intro mb-3">
<div class="eyebrow">Po co to robić</div>
<h2 class="section-title">Dlaczego warto wykonać diagnozę</h2>
</div>
<ul class="feature-list mb-0">
<li>Zobaczysz, które obszary wymagają największej interwencji organizacyjnej.</li>
<li>Łatwiej ustalisz priorytety usprawnień zamiast działać intuicyjnie.</li>
<li>Otrzymasz punkt wyjścia do rozmowy o wdrożeniu zmian lub konsultacji.</li>
</ul>
</div>
</div>
</section>
<?php elseif ($view === 'quiz' && $attempt && ($attempt['status'] ?? '') === 'in_progress' && $currentQuestion): ?>
<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 mb-4">
<div>
<div class="eyebrow mb-2"><?= htmlspecialchars($currentQuestion['section_name']) ?></div>
<h1 class="page-title mb-2"><?= htmlspecialchars($currentQuestion['text']) ?></h1>
<p class="text-secondary mb-0">Pytanie <?= $currentStep ?> z <?= $totalQuestions ?>. Odpowiedz zgodnie z aktualnym stanem organizacji, a nie stanem docelowym.</p>
</div>
<div class="score-chip text-end">
<span>Postęp</span>
<strong><?= $progressPercent ?>%</strong>
</div>
</div>
<div class="progress mb-4" style="height: 10px;">
<div class="progress-bar bg-dark" role="progressbar" style="width: <?= $progressPercent ?>%;" aria-valuenow="<?= $progressPercent ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<form method="post" novalidate>
<input type="hidden" name="action" value="save-answer">
<input type="hidden" name="step" value="<?= $currentStep ?>">
<div class="row g-3 mb-4">
<?php foreach ($definition['answer_scale'] as $value => $label): ?>
<div class="col-md-6">
<label class="answer-card h-100 <?= ((string)$currentAnswer === (string)$value) ? 'is-selected' : '' ?>">
<input class="answer-input" type="radio" name="answer" value="<?= $value ?>" <?= ((string)$currentAnswer === (string)$value) ? 'checked' : '' ?>>
<strong><?= htmlspecialchars($label) ?></strong>
</label>
</div>
<?php endforeach; ?>
</div>
<div class="d-flex flex-column flex-sm-row gap-3 justify-content-between">
<button type="submit" name="nav" value="back" class="btn btn-outline-dark btn-lg<?= $currentStep === 1 ? ' disabled' : '' ?>" <?= $currentStep === 1 ? 'disabled' : '' ?>>Wstecz</button>
<button type="submit" name="nav" value="next" class="btn btn-dark btn-lg ms-sm-auto"><?= $currentStep === $totalQuestions ? 'Zakończ diagnozę' : 'Następne pytanie' ?></button>
</div>
</form>
</section>
<?php elseif ($view === 'result' && $attempt && ($attempt['status'] ?? '') === 'completed'): ?>
<?php $result = $attempt['result'] ?? []; ?>
<section class="surface-card p-4 p-lg-5 mb-4">
<div class="d-flex flex-column flex-lg-row justify-content-between gap-4 align-items-lg-start mb-4">
<div>
<div class="eyebrow mb-3">Podsumowanie diagnozy</div>
<h1 class="page-title mb-2"><?= htmlspecialchars($result['segment_label'] ?? 'Diagnoza zakończona') ?></h1>
<p class="lead text-secondary mb-0"><?= htmlspecialchars($result['segment_summary'] ?? '') ?></p>
</div>
<div class="score-chip text-end">
<span>Łączny wynik</span>
<strong><?= (int)($result['percentage_score'] ?? 0) ?>%</strong>
</div>
</div>
<?php if (($attempt['email_report_status'] ?? '') !== 'sent'): ?>
<div class="alert alert-warning mb-4" role="alert">Podsumowanie na ekranie jest gotowe. Wysyłka raportu e-mail nie została jeszcze potwierdzona, więc przed użyciem produkcyjnym sprawdź konfigurację SMTP.</div>
<?php else: ?>
<div class="alert alert-success mb-4" role="alert">Szczegółowy raport został wysłany na adres <?= htmlspecialchars((string)$attempt['email']) ?>.</div>
<?php endif; ?>
<div class="row g-3 mb-4">
<?php foreach (($result['section_scores'] ?? []) as $section): ?>
<div class="col-md-6 col-xl-4">
<div class="section-score-card h-100">
<span class="small text-secondary d-block mb-2"><?= htmlspecialchars($section['section_name']) ?></span>
<strong class="d-block mb-1"><?= (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>
</section>
<?php if ($resultOverview !== ''): ?>
<section class="surface-card p-4 p-lg-5 mb-4">
<h2 class="section-title mb-3">Co ten wynik mówi o Twojej organizacji</h2>
<p class="text-secondary mb-0"><?= nl2br(htmlspecialchars($resultOverview)) ?></p>
</section>
<?php endif; ?>
<section class="row g-4">
<div class="col-lg-4">
<div class="surface-card p-4 h-100">
<div class="eyebrow mb-3">Najmocniejsze obszary</div>
<ol class="rank-list mb-0">
<?php foreach (($result['strongest_areas'] ?? []) as $area): ?>
<li>
<strong><?= htmlspecialchars($area['section_name']) ?></strong>
<span><?= (int)$area['percentage'] ?>% dojrzałości w tym obszarze.</span>
</li>
<?php endforeach; ?>
</ol>
</div>
</div>
<div class="col-lg-4">
<div class="surface-card p-4 h-100">
<div class="eyebrow mb-3">Priorytety do poprawy</div>
<ol class="rank-list mb-0">
<?php foreach (($result['weakest_areas'] ?? []) as $area): ?>
<li>
<strong><?= htmlspecialchars($area['section_name']) ?></strong>
<span><?= (int)$area['percentage'] ?>% — tutaj warto rozpocząć porządkowanie i standaryzację.</span>
</li>
<?php endforeach; ?>
</ol>
</div>
</div>
<div class="col-lg-4">
<div class="surface-card p-4 h-100">
<div class="eyebrow mb-3">Rekomendowane kolejne kroki</div>
<ul class="feature-list mb-0">
<?php foreach (($result['recommendations'] ?? []) as $recommendation): ?>
<li><?= htmlspecialchars($recommendation) ?></li>
<?php endforeach; ?>
</ul>
</div>
</div>
</section>
<section class="surface-card p-4 p-lg-5 mt-4">
<div class="row g-4 align-items-center">
<div class="col-lg-8">
<div class="eyebrow mb-3">Kolejny krok</div>
<h2 class="section-title mb-3">Wykorzystaj wynik jako podstawę do rozmowy o usprawnieniach</h2>
<p class="text-secondary mb-3">Największą wartość przynosi przełożenie diagnozy na plan działań: uporządkowanie odpowiedzialności, standaryzację procesów i wdrożenie prostych mechanizmów kontroli. To naturalny punkt wyjścia do konsultacji operacyjnej lub warsztatu usprawniającego.</p>
<?php if (!empty($attempt['contact_phone'])): ?>
<div class="alert alert-success mb-0" role="alert">
Numer do kontaktu został już zapisany: <strong><?= htmlspecialchars((string)$attempt['contact_phone']) ?></strong>.
<?php if (!empty($attempt['consultation_requested_at'])): ?>
<span class="d-block small mt-1">Prośba o kontakt została wysłana <?= htmlspecialchars((string)$attempt['consultation_requested_at']) ?>.</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="col-lg-4 d-flex flex-column gap-3">
<button type="button" class="btn btn-dark btn-lg" data-bs-toggle="modal" data-bs-target="#consultationModal"><?= !empty($attempt['contact_phone']) ? 'Zaktualizuj numer kontaktowy' : 'Umów kolejny krok' ?></button>
<a class="btn btn-outline-dark btn-lg" href="/diagnostic.php?reset=1">Wykonaj diagnozę ponownie</a>
</div>
</div>
</section>
<div class="modal fade" id="consultationModal" tabindex="-1" aria-labelledby="consultationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<div>
<div class="eyebrow mb-2">Dalsza konsultacja</div>
<h2 class="modal-title h4 mb-0" id="consultationModalLabel">Podaj numer telefonu</h2>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<form method="post" novalidate>
<div class="modal-body">
<input type="hidden" name="action" value="request-consultation">
<p class="text-secondary mb-4">Oddzwonimy, aby omówić wynik diagnozy i zaproponować dalszy zakres konsultacji. Numer telefonu jest wymagany, żebyśmy mogli się z Tobą skontaktować.</p>
<div class="mb-3">
<label for="contactPhone" class="form-label">Numer telefonu</label>
<input type="tel" class="form-control form-control-lg" id="contactPhone" name="phone" inputmode="tel" autocomplete="tel" placeholder="np. +48 500 600 700" value="<?= htmlspecialchars((string)($attempt['contact_phone'] ?? '')) ?>" required>
</div>
<p class="small text-secondary mb-0">Akceptowane są cyfry oraz znaki <code>+</code>, spacje, myślniki i nawiasy.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" data-bs-dismiss="modal">Anuluj</button>
<button type="submit" class="btn btn-dark">Wyślij numer do kontaktu</button>
</div>
</form>
</div>
</div>
</div>
<?php else: ?>
<section class="empty-state text-center py-5 px-4">
<div class="eyebrow mb-3">Brak aktywnej diagnozy</div>
<h1 class="page-title mb-3">Nie znaleziono aktywnego wyniku</h1>
<p class="text-secondary mb-4">Rozpocznij nowe badanie, aby wygenerować podsumowanie dojrzałości procesowej i raport wysyłany e-mailem.</p>
<a class="btn btn-dark" href="/diagnostic.php">Rozpocznij diagnozę</a>
</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">Profesjonalna diagnoza dojrzałości procesowej dla właścicieli firm i liderów operacyjnych.</span>
<div class="d-flex gap-3 small">
<a class="text-decoration-none text-secondary" href="/">Strona główna</a>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
<?php if ($openConsultationModal): ?>
<script>
window.addEventListener('DOMContentLoaded', function () {
var consultationModal = document.getElementById('consultationModal');
if (consultationModal && window.bootstrap && window.bootstrap.Modal) {
window.bootstrap.Modal.getOrCreateInstance(consultationModal).show();
}
});
</script>
<?php endif; ?>
<script src="/assets/js/main.js?v=<?= urlencode((string)@filemtime(__DIR__ . '/assets/js/main.js')) ?>" defer></script>
</body>
</html>