feat: certificate template management and generation (Fasa 7)
- CertificateService: Intervention Image v3 text overlay on template - GenerateCertificateJob: queued generation with retry logic - SendCertificateEmailJob: stub (implemented in Fasa 8) - CertificateTemplateController: upload, config editor, preview, test generate - Admin/CertificateController: list, generate-all, email-all - Public/CertificateController: show with questionnaire gate, download - DejaVuSans fonts bundled under resources/fonts - Views: admin template/certificate management, public certificate download Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
135
resources/views/admin/programs/certificates/index.blade.php
Normal file
135
resources/views/admin/programs/certificates/index.blade.php
Normal file
@@ -0,0 +1,135 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Sijil — ' . $program->title)
|
||||
@section('header', 'Pengurusan Sijil')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.programs.index') }}">Program</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.programs.show', $program) }}">{{ Str::limit($program->title, 30) }}</a></li>
|
||||
<li class="breadcrumb-item active">Sijil</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- Stats --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 bg-secondary bg-opacity-10 text-center p-3">
|
||||
<div class="fs-3 fw-bold">{{ $stats['total'] }}</div>
|
||||
<div class="small text-muted">Jumlah</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 bg-success bg-opacity-10 text-center p-3">
|
||||
<div class="fs-3 fw-bold text-success">{{ $stats['generated'] }}</div>
|
||||
<div class="small text-muted">Dijana</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 bg-warning bg-opacity-10 text-center p-3">
|
||||
<div class="fs-3 fw-bold text-warning">{{ $stats['pending'] }}</div>
|
||||
<div class="small text-muted">Menunggu</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 bg-danger bg-opacity-10 text-center p-3">
|
||||
<div class="fs-3 fw-bold text-danger">{{ $stats['failed'] }}</div>
|
||||
<div class="small text-muted">Gagal</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Actions --}}
|
||||
<div class="d-flex gap-2 mb-4 flex-wrap">
|
||||
<form method="POST" action="{{ route('admin.programs.certificates.generate-all', $program) }}">
|
||||
@csrf
|
||||
<button class="btn btn-primary"
|
||||
onclick="return confirm('Jana sijil untuk semua peserta hadir?')">
|
||||
<i class="bi bi-gear me-1"></i> Jana Semua Sijil
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if($stats['generated'] > 0)
|
||||
<form method="POST" action="{{ route('admin.programs.certificates.email-all', $program) }}">
|
||||
@csrf
|
||||
<button class="btn btn-outline-success"
|
||||
onclick="return confirm('Hantar emel sijil kepada semua peserta yang sijilnya sudah sedia?')">
|
||||
<i class="bi bi-envelope me-1"></i> Hantar Emel Sijil
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if(! $program->certificateTemplate)
|
||||
<div class="alert alert-warning mb-0 py-2 px-3 small">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
<a href="{{ route('admin.programs.template.show', $program) }}">Upload template sijil</a> dahulu sebelum jana sijil.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Certificate List --}}
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Peserta</th>
|
||||
<th>No. Sijil</th>
|
||||
<th>Status</th>
|
||||
<th>Dijana</th>
|
||||
<th>Muat Turun</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($certificates as $cert)
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-medium small">{{ $cert->participant->name }}</div>
|
||||
<div class="text-muted" style="font-size:0.75rem;">{{ $cert->participant->agency ?: '—' }}</div>
|
||||
</td>
|
||||
<td class="small text-muted">{{ $cert->certificate_no ?? '—' }}</td>
|
||||
<td>
|
||||
@if($cert->status === 'generated' || $cert->status === 'downloaded')
|
||||
<span class="badge bg-success">Sedia</span>
|
||||
@elseif($cert->status === 'emailed')
|
||||
<span class="badge bg-info">Diemailkan</span>
|
||||
@elseif($cert->status === 'pending')
|
||||
<span class="badge bg-warning text-dark">Menunggu</span>
|
||||
@elseif($cert->status === 'generating')
|
||||
<span class="badge bg-secondary">Menjana...</span>
|
||||
@elseif($cert->status === 'failed')
|
||||
<span class="badge bg-danger" title="{{ $cert->error_message }}">Gagal</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small text-muted">{{ $cert->generated_at?->format('d/m H:i') ?? '—' }}</td>
|
||||
<td class="small text-muted text-center">{{ $cert->download_count ?: '—' }}</td>
|
||||
<td>
|
||||
@if($cert->isGenerated())
|
||||
<a href="{{ route('public.certificate.show', $cert->token) }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-award d-block fs-1 mb-3 opacity-25"></i>
|
||||
Belum ada sijil dijana. Klik "Jana Semua Sijil" untuk mula.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($certificates->hasPages())
|
||||
<div class="card-footer bg-white">
|
||||
{{ $certificates->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
Reference in New Issue
Block a user