- Tambah fields[name][ic_font_size] dalam form — baris: Warna | Saiz Font No IC | Align
- Default: 70% daripada saiz font nama (sebelum ini hardcode 50%)
- loadPreview() hantar ic_font_size terkini ke endpoint pratonton
- writeIcBelow() baca ic_font_size dari config, fallback 70% jika tiada
- Validasi updateConfig: ic_font_size nullable|integer|min:8|max:200
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- writeIcBelow(): auto-kira kedudukan Y (nama_y + nama_font * 1.5)
dan saiz font (50% daripada saiz font nama), align sama dengan nama
- generate(): tulis no_kp peserta sebenar di bawah nama
- generatePreview(): tulis contoh '800808-08-8888' di bawah nama sample
- Guna font DejaVuSans.ttf (regular) untuk IC, Bold untuk nama
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- loadPreview() hantar semua nilai field (X, Y, font_size, color, align) ke endpoint
- certificate_no disertakan hanya jika toggle showCertNo aktif
- testGenerate() bina liveFields dari request, gabung dengan config tersimpan
(supaya font_file & valign kekal dari config asal)
- generatePreview() terima overrideFields optional — preview sentiasa refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Storage::put() guna default disk (local/private) menyebabkan fail disimpan
di storage/app/private/public/qrcodes/ tapi URL /storage/qrcodes/... cari
fail di storage/app/public/qrcodes/ melalui symlink — lokasi berbeza.
- QrCodeService: guna disk('public'), path ringkas 'qrcodes/{token}.png'
- View: Storage::disk('public')->url() untuk URL yang betul
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace is_admin boolean with role enum('super_admin','admin') via migration
- ProgramPolicy: admin program can only view/edit/delete own programs
- EnsureIsAdmin: accepts both roles; EnsureSuperAdmin: super_admin only
- UserController + views: super_admin can manage admin accounts
- Sidebar: user management link & role badge gated on isSuperAdmin()
- Fix Controller base class: add AuthorizesRequests trait
- Fix tests: replace nonAdmin() (invalid enum) with adminProgram() against super_admin-only route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EnsureIsAdmin middleware: gates all admin routes on is_admin flag
- Apply admin middleware to entire admin route group
- Fix questionnaire resource route parameter name mismatch ({set})
- Audit log on questionnaire confirmation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CertificateReadyMail: Mailable with Malay HTML email template
- SendCertificateEmailJob: dispatch per-certificate email, log to email_logs
- Email template: HTML with download link, program details, branding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>