status emel
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
<div>
|
||||
<div class="text-muted small">Soalselidik Dijawab</div>
|
||||
<div class="fs-3 fw-bold">{{ $stats['total_responses'] }}</div>
|
||||
@if($stats['pending_emails'] > 0)
|
||||
<div class="text-warning small"><i class="bi bi-envelope-fill me-1"></i>{{ $stats['pending_emails'] }} emel tertunda</div>
|
||||
@if($stats['emails_pending'] > 0)
|
||||
<div class="text-warning small"><i class="bi bi-envelope-fill me-1"></i>{{ $stats['emails_pending'] }} emel tertunda</div>
|
||||
@else
|
||||
<div class="text-muted small">Tiada emel tertunda</div>
|
||||
@endif
|
||||
@@ -72,6 +72,69 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Email & Download Status --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white border-bottom py-3">
|
||||
<span class="fw-semibold"><i class="bi bi-envelope-fill me-2 text-primary"></i>Status Penghantaran E-Sijil</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="d-flex align-items-center gap-3 p-3 rounded-3 bg-secondary bg-opacity-10">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="bi bi-hourglass-split text-secondary fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small">Dalam Antrian</div>
|
||||
<div class="fs-3 fw-bold lh-1">{{ $stats['emails_pending'] }}</div>
|
||||
<div class="text-muted" style="font-size:.7rem;">menunggu / sedang cuba</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="d-flex align-items-center gap-3 p-3 rounded-3 bg-success bg-opacity-10">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="bi bi-envelope-check-fill text-success fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small">Berjaya Dihantar</div>
|
||||
<div class="fs-3 fw-bold lh-1">{{ $stats['emails_sent'] }}</div>
|
||||
<div class="text-muted" style="font-size:.7rem;">emel e-sijil</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="d-flex align-items-center gap-3 p-3 rounded-3 bg-danger bg-opacity-10">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="bi bi-envelope-x-fill text-danger fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small">Gagal Dihantar</div>
|
||||
<div class="fs-3 fw-bold lh-1">{{ $stats['emails_failed'] }}</div>
|
||||
<div class="text-muted" style="font-size:.7rem;">semua percubaan gagal</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="d-flex align-items-center gap-3 p-3 rounded-3 bg-warning bg-opacity-10">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="bi bi-download text-warning fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small">Sijil Dimuat Turun</div>
|
||||
<div class="fs-3 fw-bold lh-1">{{ $stats['downloaded_certs'] }}</div>
|
||||
<div class="text-muted" style="font-size:.7rem;">{{ $stats['total_download_count'] }} kali klik pautan</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Recent Programs --}}
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-8">
|
||||
|
||||
@@ -100,12 +100,17 @@
|
||||
<th>Sesi</th>
|
||||
<th>Sumber</th>
|
||||
<th>Status</th>
|
||||
<th>E-Sijil</th>
|
||||
<th class="text-end">Tindakan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($programParticipants as $i => $pp)
|
||||
@php $p = $pp->participant; @endphp
|
||||
@php
|
||||
$p = $pp->participant;
|
||||
$cert = $certificates[$pp->participant_id] ?? null;
|
||||
$emailLog = $cert ? ($emailLogs[$cert->id] ?? null) : null;
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="text-muted small">{{ $programParticipants->firstItem() + $i }}</td>
|
||||
<td>
|
||||
@@ -143,6 +148,54 @@
|
||||
<span class="badge bg-light text-dark border">Berdaftar</span>
|
||||
@endif
|
||||
</td>
|
||||
<td style="min-width:130px;">
|
||||
@if(! $cert)
|
||||
<span class="text-muted small">—</span>
|
||||
@else
|
||||
{{-- Sijil --}}
|
||||
@if(in_array($cert->status, ['generated','emailed','downloaded']))
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle">
|
||||
<i class="bi bi-award-fill me-1"></i>Jana ✓
|
||||
</span>
|
||||
@elseif($cert->status === 'pending')
|
||||
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">
|
||||
<i class="bi bi-hourglass-split me-1"></i>Menjana...
|
||||
</span>
|
||||
@elseif($cert->status === 'failed')
|
||||
<span class="badge bg-danger-subtle text-danger border border-danger-subtle">
|
||||
<i class="bi bi-x-circle me-1"></i>Gagal Jana
|
||||
</span>
|
||||
@endif
|
||||
|
||||
{{-- Emel --}}
|
||||
@if($emailLog)
|
||||
@if($emailLog->status === 'sent')
|
||||
<div class="text-success mt-1" style="font-size:.7rem;">
|
||||
<i class="bi bi-envelope-check me-1"></i>Emel Dihantar
|
||||
</div>
|
||||
@elseif($emailLog->status === 'failed')
|
||||
<div class="text-danger mt-1" style="font-size:.7rem;">
|
||||
<i class="bi bi-envelope-x me-1"></i>Emel Gagal
|
||||
</div>
|
||||
@elseif($emailLog->status === 'pending')
|
||||
<div class="text-warning mt-1" style="font-size:.7rem;">
|
||||
<i class="bi bi-hourglass-split me-1"></i>Dalam Antrian
|
||||
</div>
|
||||
@endif
|
||||
@elseif($cert->isGenerated())
|
||||
<div class="text-muted mt-1" style="font-size:.7rem;">
|
||||
<i class="bi bi-envelope me-1"></i>Belum Dihantar
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Muat turun --}}
|
||||
@if($cert->download_count > 0)
|
||||
<div class="text-primary mt-1" style="font-size:.7rem;">
|
||||
<i class="bi bi-download me-1"></i>{{ $cert->download_count }}× Muat Turun
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@if($pp->status !== 'checked_in')
|
||||
<form method="POST"
|
||||
|
||||
Reference in New Issue
Block a user