diff --git a/src/app/Http/Controllers/Admin/DashboardController.php b/src/app/Http/Controllers/Admin/DashboardController.php index b80727b..f1c83c1 100644 --- a/src/app/Http/Controllers/Admin/DashboardController.php +++ b/src/app/Http/Controllers/Admin/DashboardController.php @@ -22,8 +22,11 @@ class DashboardController extends Controller 'total_certificates' => Certificate::count(), 'generated_certs' => Certificate::whereIn('status', ['generated', 'emailed', 'downloaded'])->count(), 'downloaded_certs' => Certificate::where('status', 'downloaded')->count(), + 'total_download_count'=> (int) Certificate::sum('download_count'), 'total_responses' => QuestionnaireResponse::count(), - 'pending_emails' => EmailLog::where('status', 'pending')->count(), + 'emails_pending' => EmailLog::where('status', 'pending')->count(), + 'emails_sent' => EmailLog::where('status', 'sent')->count(), + 'emails_failed' => EmailLog::where('status', 'failed')->count(), ]; $recentPrograms = Program::with('creator') diff --git a/src/app/Http/Controllers/Admin/ParticipantController.php b/src/app/Http/Controllers/Admin/ParticipantController.php index c2f3ba5..17602d4 100644 --- a/src/app/Http/Controllers/Admin/ParticipantController.php +++ b/src/app/Http/Controllers/Admin/ParticipantController.php @@ -3,6 +3,8 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Models\Certificate; +use App\Models\EmailLog; use App\Models\Participant; use App\Models\Program; use App\Models\ProgramParticipant; @@ -40,6 +42,20 @@ class ParticipantController extends Controller $programParticipants = $query->paginate(20)->withQueryString(); + // Load certificates and latest email logs for displayed participants + $participantIds = $programParticipants->pluck('participant_id'); + $certificates = Certificate::where('program_id', $program->id) + ->whereIn('participant_id', $participantIds) + ->get() + ->keyBy('participant_id'); + + $certIds = $certificates->pluck('id'); + $emailLogs = EmailLog::whereIn('certificate_id', $certIds) + ->orderByDesc('id') + ->get() + ->groupBy('certificate_id') + ->map->first(); + $countRow = DB::table('program_participants') ->where('program_id', $program->id) ->selectRaw("COUNT(*) as total, SUM(is_pre_registered) as pre_registered, SUM(registration_source = 'walk_in') as walk_in, SUM(status = 'checked_in') as checked_in") @@ -52,7 +68,7 @@ class ParticipantController extends Controller 'checked_in' => (int) ($countRow->checked_in ?? 0), ]; - return view('admin.programs.participants.index', compact('program', 'programParticipants', 'counts')); + return view('admin.programs.participants.index', compact('program', 'programParticipants', 'counts', 'certificates', 'emailLogs')); } public function create(Program $program): View diff --git a/src/app/Http/Controllers/Public/AttendanceCheckController.php b/src/app/Http/Controllers/Public/AttendanceCheckController.php index b362976..e936a1d 100644 --- a/src/app/Http/Controllers/Public/AttendanceCheckController.php +++ b/src/app/Http/Controllers/Public/AttendanceCheckController.php @@ -82,7 +82,7 @@ class AttendanceCheckController extends Controller // Masukkan queue hantar e-sijil jika sijil sudah dijana dan belum dihantar if ($certificate && $certificate->status === 'generated' && ! $certificate->emailed_at) { - SendCertificateEmailJob::dispatch($certificate); + SendCertificateEmailJob::dispatchForCert($certificate); } return view('public.semak.result', compact('program', 'qrCode', 'participant', 'attendance', 'certificate')) diff --git a/src/app/Jobs/SendCertificateEmailJob.php b/src/app/Jobs/SendCertificateEmailJob.php index b77e99a..fde625a 100644 --- a/src/app/Jobs/SendCertificateEmailJob.php +++ b/src/app/Jobs/SendCertificateEmailJob.php @@ -30,9 +30,15 @@ class SendCertificateEmailJob implements ShouldQueue $email = $cert->participant->email; if (! $email) { + EmailLog::where('certificate_id', $cert->id)->where('status', 'pending')->delete(); return; } + $log = EmailLog::where('certificate_id', $cert->id) + ->where('status', 'pending') + ->latest() + ->first(); + try { Mail::to($email)->send(new CertificateReadyMail($cert)); @@ -41,41 +47,70 @@ class SendCertificateEmailJob implements ShouldQueue 'emailed_at' => now(), ]); - EmailLog::create([ - 'program_id' => $cert->program_id, - 'participant_id' => $cert->participant_id, - 'certificate_id' => $cert->id, - 'recipient_email'=> $email, - 'subject' => 'Sijil Digital Program — ' . $cert->program->title, - 'email_type' => 'certificate_ready', - 'status' => 'sent', - 'sent_at' => now(), - ]); + if ($log) { + $log->update(['status' => 'sent', 'sent_at' => now()]); + } else { + EmailLog::create([ + 'program_id' => $cert->program_id, + 'participant_id' => $cert->participant_id, + 'certificate_id' => $cert->id, + 'recipient_email' => $email, + 'subject' => 'Sijil Digital Program — ' . $cert->program->title, + 'email_type' => 'certificate_ready', + 'status' => 'sent', + 'sent_at' => now(), + ]); + } } catch (\Throwable $e) { - EmailLog::create([ - 'program_id' => $cert->program_id, - 'participant_id' => $cert->participant_id, - 'certificate_id' => $cert->id, - 'recipient_email'=> $email, - 'subject' => 'Sijil Digital Program — ' . $cert->program->title, - 'email_type' => 'certificate_ready', - 'status' => 'failed', - 'error_message' => $e->getMessage(), - ]); + if ($log) { + $log->update(['status' => 'failed', 'error_message' => $e->getMessage()]); + } else { + EmailLog::create([ + 'program_id' => $cert->program_id, + 'participant_id' => $cert->participant_id, + 'certificate_id' => $cert->id, + 'recipient_email' => $email, + 'subject' => 'Sijil Digital Program — ' . $cert->program->title, + 'email_type' => 'certificate_ready', + 'status' => 'failed', + 'error_message' => $e->getMessage(), + ]); + } throw $e; } } + /** + * Cipta pending EmailLog dahulu, kemudian dispatch job. + * Ini membolehkan dashboard tunjuk status "dalam antrian" sebelum job diproses. + */ + public static function dispatchForCert(Certificate $cert): void + { + $cert->loadMissing(['participant', 'program']); + + EmailLog::create([ + 'program_id' => $cert->program_id, + 'participant_id' => $cert->participant_id, + 'certificate_id' => $cert->id, + 'recipient_email' => $cert->participant->email, + 'subject' => 'Sijil Digital Program — ' . $cert->program->title, + 'email_type' => 'certificate_ready', + 'status' => 'pending', + ]); + + static::dispatch($cert); + } + public static function dispatchBatch(Program $program): void { $program->certificates() ->whereIn('status', ['generated']) ->whereNull('emailed_at') - ->with('participant') + ->with(['participant', 'program']) ->each(function (Certificate $cert) { if ($cert->participant->email) { - static::dispatch($cert); + static::dispatchForCert($cert); } }); } diff --git a/src/resources/views/admin/dashboard.blade.php b/src/resources/views/admin/dashboard.blade.php index edd54e1..6118134 100644 --- a/src/resources/views/admin/dashboard.blade.php +++ b/src/resources/views/admin/dashboard.blade.php @@ -61,8 +61,8 @@