Files
eCert-MBIP/app/Http/Controllers/Admin/StatisticsController.php
2026-05-19 09:53:36 +08:00

137 lines
5.4 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Program;
use App\Models\Attendance;
use App\Models\Certificate;
use App\Models\QuestionnaireAnswer;
use App\Models\QuestionnaireQuestion;
use App\Models\QuestionnaireResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\View\View;
class StatisticsController extends Controller
{
public function show(Program $program): View
{
$program->load(['questionnaire.questionnaireSet.questions']);
// Attendance by session
$bySession = $program->attendances()
->selectRaw('attendance_session, COUNT(*) as total')
->groupBy('attendance_session')
->pluck('total', 'attendance_session')
->toArray();
// Attendance by source
$bySource = $program->attendances()
->selectRaw('attendance_source, COUNT(*) as total')
->groupBy('attendance_source')
->pluck('total', 'attendance_source')
->toArray();
// Certificate status breakdown
$certStats = $program->certificates()
->selectRaw('status, COUNT(*) as total')
->groupBy('status')
->pluck('total', 'status')
->toArray();
// Response rate + question stats
$pq = $program->questionnaire;
$responseRate = null;
$questionStats = [];
$totalResponses = 0;
if ($pq && $pq->is_confirmed) {
$totalAttended = array_sum($bySession); // reuse already-fetched data
$totalResponses = QuestionnaireResponse::where('program_id', $program->id)->count();
$responseRate = $totalAttended > 0 ? round($totalResponses / $totalAttended * 100) : 0;
$questions = $pq->questionnaireSet->questions ?? collect();
// Load ALL answers in one query, group by question — avoids N+1
$allAnswers = QuestionnaireAnswer::whereIn('questionnaire_question_id', $questions->pluck('id'))
->get()
->groupBy('questionnaire_question_id');
foreach ($questions as $q) {
$answers = $allAnswers->get($q->id, collect());
if ($q->question_type === 'rating') {
$values = $answers->map(fn($a) => is_array($a->answer_value) ? (int) ($a->answer_value[0] ?? 0) : (int) $a->answer_value);
$questionStats[] = [
'id' => $q->id,
'text' => $q->question_text,
'type' => 'rating',
'average' => $values->count() > 0 ? round($values->avg(), 2) : null,
'count' => $values->count(),
];
} elseif (in_array($q->question_type, ['single_choice', 'multiple_choice'])) {
$counts = [];
foreach ($answers as $row) {
$items = is_array($row->answer_value) ? $row->answer_value : [$row->answer_value];
foreach ($items as $item) {
$counts[$item] = ($counts[$item] ?? 0) + 1;
}
}
$questionStats[] = [
'id' => $q->id,
'text' => $q->question_text,
'type' => $q->question_type,
'options' => $q->options_json ?? [],
'counts' => $counts,
'total' => $answers->count(),
];
}
}
}
// Reuse data already computed above — no extra queries
$summary = [
'total_attendances' => array_sum($bySession),
'pre_registered' => $bySource['pre_registered_staff'] ?? 0,
'walk_in' => $bySource['walk_in_external'] ?? 0,
'total_certificates' => array_sum($certStats),
'generated_certs' => ($certStats['generated'] ?? 0) + ($certStats['emailed'] ?? 0) + ($certStats['downloaded'] ?? 0),
'downloaded_certs' => $certStats['downloaded'] ?? 0,
'total_responses' => $totalResponses,
];
return view('admin.programs.statistics.show', compact(
'program', 'summary', 'bySession', 'bySource',
'certStats', 'responseRate', 'questionStats'
));
}
public function export(Program $program): Response
{
$rows = $program->attendances()
->with('participant')
->get()
->map(fn($a) => [
$a->participant->name,
$a->participant->agency ?: '',
$a->attendance_session,
$a->attendance_source,
$a->checked_in_at->format('d/m/Y H:i'),
]);
$csv = "\xEF\xBB\xBF";
$csv .= implode(',', ['Nama', 'Agensi', 'Sesi', 'Sumber', 'Masa Check-In']) . "\n";
foreach ($rows as $row) {
$csv .= implode(',', array_map(fn($v) => '"' . str_replace('"', '""', $v) . '"', $row)) . "\n";
}
$filename = 'statistik-' . str($program->title)->slug() . '-' . now()->format('Ymd') . '.csv';
return response($csv, 200, [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}
}