- StatisticsController: attendance by session/source, cert status, response rate, question averages - Statistics export as CSV - Chart.js visualisations: bar (session), doughnut (source), progress bars (cert status, ratings) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
5.3 KiB
PHP
132 lines
5.3 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(['attendances.participant', '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('source, COUNT(*) as total')
|
|
->groupBy('source')
|
|
->pluck('total', 'source')
|
|
->toArray();
|
|
|
|
// Certificate status breakdown
|
|
$certStats = $program->certificates()
|
|
->selectRaw('status, COUNT(*) as total')
|
|
->groupBy('status')
|
|
->pluck('total', 'status')
|
|
->toArray();
|
|
|
|
// Response rate
|
|
$pq = $program->questionnaire;
|
|
$responseRate = null;
|
|
$questionStats = [];
|
|
|
|
if ($pq && $pq->is_confirmed) {
|
|
$totalAttended = $program->attendances()->count();
|
|
$totalResponses = QuestionnaireResponse::where('program_id', $program->id)->count();
|
|
$responseRate = $totalAttended > 0 ? round($totalResponses / $totalAttended * 100) : 0;
|
|
|
|
// Rating question averages
|
|
$questions = $pq->questionnaireSet->questions ?? collect();
|
|
foreach ($questions as $q) {
|
|
if ($q->question_type === 'rating') {
|
|
$answers = QuestionnaireAnswer::where('questionnaire_question_id', $q->id)
|
|
->pluck('answer_value');
|
|
$values = $answers->map(fn($v) => is_array($v) ? (int)($v[0] ?? 0) : (int)$v);
|
|
$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'])) {
|
|
$answers = QuestionnaireAnswer::where('questionnaire_question_id', $q->id)
|
|
->pluck('answer_value');
|
|
$counts = [];
|
|
foreach ($answers as $val) {
|
|
$items = is_array($val) ? $val : [$val];
|
|
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(),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$summary = [
|
|
'total_attendances' => $program->attendances()->count(),
|
|
'pre_registered' => $program->attendances()->where('source', 'pre_registered_staff')->count(),
|
|
'walk_in' => $program->attendances()->where('source', 'walk_in_external')->count(),
|
|
'total_certificates' => $program->certificates()->count(),
|
|
'generated_certs' => $program->certificates()->whereIn('status', ['generated', 'emailed', 'downloaded'])->count(),
|
|
'downloaded_certs' => $program->certificates()->where('status', 'downloaded')->count(),
|
|
'total_responses' => QuestionnaireResponse::where('program_id', $program->id)->count(),
|
|
];
|
|
|
|
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->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 . '"',
|
|
]);
|
|
}
|
|
}
|