234 lines
12 KiB
TypeScript
234 lines
12 KiB
TypeScript
'use client'
|
||
|
||
import { useRef, useState } from 'react'
|
||
import { FileText, Loader2, Printer } from 'lucide-react'
|
||
import { getPetugasLokalByPosyandu } from './action-jadwal'
|
||
import { showSwal } from '@/lib/swal'
|
||
|
||
interface PetugasLokal {
|
||
nama_petugas: string
|
||
nomor_hp: string
|
||
jabatan: string
|
||
}
|
||
|
||
interface JadwalData {
|
||
id: string
|
||
tanggal: string
|
||
jam_mulai: string
|
||
jam_selesai: string
|
||
diedit_oleh: string
|
||
posyandu_id: string
|
||
detail_posyandu: {
|
||
nama_posyandu: string
|
||
alamat: string
|
||
}
|
||
}
|
||
|
||
interface Props {
|
||
jadwal: JadwalData
|
||
currentAdmin: string
|
||
}
|
||
|
||
export function CetakPDFJadwal({ jadwal, currentAdmin }: Props) {
|
||
const [loading, setLoading] = useState(false)
|
||
const templateRef = useRef<HTMLDivElement>(null)
|
||
const [petugas, setPetugas] = useState<PetugasLokal[]>([])
|
||
|
||
const now = new Date()
|
||
const tglCetak = now.toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' })
|
||
const jamCetak = now.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' })
|
||
|
||
const tglJadwal = new Date(jadwal.tanggal).toLocaleDateString('id-ID', {
|
||
weekday: 'long',
|
||
day: 'numeric',
|
||
month: 'long',
|
||
year: 'numeric'
|
||
})
|
||
|
||
const handlePrint = async () => {
|
||
setLoading(true)
|
||
try {
|
||
// 1. Fetch Local Officers first
|
||
const res = await getPetugasLokalByPosyandu(jadwal.posyandu_id)
|
||
if (res.success) {
|
||
setPetugas(res.petugas || [])
|
||
}
|
||
|
||
// Small delay to ensure state update renders in the hidden div
|
||
await new Promise(r => setTimeout(r, 300))
|
||
|
||
const { default: html2canvas } = await import('html2canvas')
|
||
const { default: jsPDF } = await import('jspdf')
|
||
|
||
if (!templateRef.current) throw new Error('Template not found')
|
||
|
||
const canvas = await html2canvas(templateRef.current, {
|
||
scale: 2,
|
||
useCORS: true,
|
||
backgroundColor: '#ffffff',
|
||
logging: false,
|
||
})
|
||
|
||
const imgData = canvas.toDataURL('image/png')
|
||
const pdf = new jsPDF('p', 'mm', 'a4')
|
||
const pageW = pdf.internal.pageSize.getWidth()
|
||
const imgH = (canvas.height * pageW) / canvas.width
|
||
|
||
pdf.addImage(imgData, 'PNG', 0, 0, pageW, imgH)
|
||
pdf.save(`Jadwal_${jadwal.detail_posyandu.nama_posyandu.replace(/ /g, '_')}_${jadwal.tanggal}.pdf`)
|
||
|
||
} catch (err: any) {
|
||
showSwal.error('Gagal!', `Gagal mencetak PDF: ${err.message}`)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<>
|
||
{/* ─── Hidden PDF Template ─── */}
|
||
<div style={{ position: 'fixed', top: '-9999px', left: '-9999px', zIndex: -1 }}>
|
||
<div
|
||
ref={templateRef}
|
||
style={{
|
||
width: '794px', // A4 width at 96 DPI
|
||
backgroundColor: '#ffffff',
|
||
fontFamily: "'Inter', 'Arial', sans-serif",
|
||
color: '#111111',
|
||
padding: '60px 70px',
|
||
boxSizing: 'border-box',
|
||
}}
|
||
>
|
||
{/* Header: Kop Surat Style */}
|
||
<div style={{ borderBottom: '4px solid #000', paddingBottom: '20px', marginBottom: '30px' }}>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: '12px', fontWeight: 800, letterSpacing: '4px', textTransform: 'uppercase', color: '#666', marginBottom: '8px' }}>
|
||
Pemerintah Kabupaten / Kota
|
||
</div>
|
||
<div style={{ fontSize: '26px', fontWeight: 900, letterSpacing: '-1px', color: '#000' }}>
|
||
DINAS KESEHATAN & POSYANDU
|
||
</div>
|
||
</div>
|
||
<div style={{ textAlign: 'right' }}>
|
||
<div style={{ fontSize: '10px', fontWeight: 700, color: '#888', textTransform: 'uppercase' }}>Nomor Dokumen</div>
|
||
<div style={{ fontSize: '14px', fontWeight: 800 }}>JDWL-{jadwal.id.slice(0, 8).toUpperCase()}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ textAlign: 'center', marginBottom: '40px' }}>
|
||
<h1 style={{ fontSize: '20px', fontWeight: 900, textTransform: 'uppercase', margin: 0, letterSpacing: '2px' }}>
|
||
SURAT PEMBERITAHUAN JADWAL PELAKSANAAN
|
||
</h1>
|
||
<div style={{ width: '60px', height: '3px', background: '#000', margin: '15px auto' }}></div>
|
||
</div>
|
||
|
||
{/* Informasi Utama */}
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '40px', marginBottom: '40px' }}>
|
||
<div>
|
||
<div style={{ fontSize: '9px', fontWeight: 800, textTransform: 'uppercase', color: '#888', letterSpacing: '1px', marginBottom: '15px' }}>
|
||
A. DETAIL POSYANDU
|
||
</div>
|
||
<div style={{ marginBottom: '15px' }}>
|
||
<div style={{ fontSize: '10px', color: '#888', fontWeight: 600, marginBottom: '4px' }}>Nama Posyandu</div>
|
||
<div style={{ fontSize: '14px', fontWeight: 800, color: '#000', textTransform: 'uppercase' }}>{jadwal.detail_posyandu.nama_posyandu}</div>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: '10px', color: '#888', fontWeight: 600, marginBottom: '4px' }}>Alamat Lokasi</div>
|
||
<div style={{ fontSize: '12px', fontWeight: 600, lineHeight: '1.5', color: '#333' }}>{jadwal.detail_posyandu.alamat}</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: '9px', fontWeight: 800, textTransform: 'uppercase', color: '#888', letterSpacing: '1px', marginBottom: '15px' }}>
|
||
B. WAKTU PELAKSANAAN
|
||
</div>
|
||
<div style={{ marginBottom: '15px' }}>
|
||
<div style={{ fontSize: '10px', color: '#888', fontWeight: 600, marginBottom: '4px' }}>Hari & Tanggal</div>
|
||
<div style={{ fontSize: '14px', fontWeight: 800, color: '#000' }}>{tglJadwal}</div>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: '10px', color: '#888', fontWeight: 600, marginBottom: '4px' }}>Sesi Operasional</div>
|
||
<div style={{ fontSize: '18px', fontWeight: 900, color: '#e11d48' }}>{jadwal.jam_mulai.slice(0, 5)} - {jadwal.jam_selesai.slice(0, 5)} WIB</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Tabel Petugas */}
|
||
<div style={{ marginBottom: '40px' }}>
|
||
<div style={{ fontSize: '9px', fontWeight: 800, textTransform: 'uppercase', color: '#888', letterSpacing: '1px', marginBottom: '15px' }}>
|
||
C. DAFTAR PETUGAS LOKAL BERTUGAS
|
||
</div>
|
||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||
<thead>
|
||
<tr style={{ borderBottom: '2px solid #000' }}>
|
||
<th style={{ textAlign: 'left', padding: '12px 8px', fontSize: '11px', fontWeight: 800, textTransform: 'uppercase' }}>Nama Petugas</th>
|
||
<th style={{ textAlign: 'left', padding: '12px 8px', fontSize: '11px', fontWeight: 800, textTransform: 'uppercase' }}>Nomor Telepon</th>
|
||
<th style={{ textAlign: 'left', padding: '12px 8px', fontSize: '11px', fontWeight: 800, textTransform: 'uppercase' }}>Jabatan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{petugas.length > 0 ? petugas.map((p, i) => (
|
||
<tr key={i} style={{ borderBottom: '1px solid #eee' }}>
|
||
<td style={{ padding: '12px 8px', fontSize: '12px', fontWeight: 700 }}>{p.nama_petugas}</td>
|
||
<td style={{ padding: '12px 8px', fontSize: '12px', fontWeight: 600, color: '#666' }}>{p.nomor_hp}</td>
|
||
<td style={{ padding: '12px 8px', fontSize: '11px', fontWeight: 700, color: '#e11d48', textTransform: 'uppercase' }}>{p.jabatan}</td>
|
||
</tr>
|
||
)) : (
|
||
<tr>
|
||
<td colSpan={3} style={{ padding: '30px', textAlign: 'center', fontSize: '12px', color: '#888', fontStyle: 'italic', background: '#f9f9f9' }}>
|
||
Belum ada petugas lokal yang terdaftar untuk Posyandu ini.
|
||
</td>
|
||
</tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{/* Catatan Penting */}
|
||
<div style={{ background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: '12px', padding: '20px', marginBottom: '50px' }}>
|
||
<div style={{ fontSize: '10px', fontWeight: 800, color: '#334155', textTransform: 'uppercase', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||
⚠️ CATATAN SISTEM
|
||
</div>
|
||
<p style={{ fontSize: '11px', lineHeight: '1.7', color: '#475569', margin: 0, fontWeight: 500 }}>
|
||
"Dimohon agar aktivitas program pengecekan stunting di setiap Posyandu dijalankan sesuai dengan waktu dan sesi yang tertera dalam jadwal, guna mendukung pemeliharaan sistem yang baik."
|
||
</p>
|
||
</div>
|
||
|
||
{/* Footer / Metadata */}
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '40px', borderTop: '1px solid #eee', paddingTop: '30px' }}>
|
||
<div style={{ fontSize: '10px', color: '#888', lineHeight: '1.6' }}>
|
||
<div style={{ fontWeight: 700, color: '#555', marginBottom: '4px' }}>Metode Penjadwalan:</div>
|
||
Sistem Penjadwalan Otomatis (Algoritma Random Batch)<br />
|
||
Ditentukan oleh Admin: <span style={{ fontWeight: 700, color: '#000' }}>{jadwal.diedit_oleh.replace('[HISTORY] ', '')}</span>
|
||
</div>
|
||
<div style={{ textAlign: 'right', fontSize: '10px', color: '#888', lineHeight: '1.6' }}>
|
||
<div style={{ fontWeight: 700, color: '#555', marginBottom: '4px' }}>Detail Pengunduhan:</div>
|
||
Dicetak oleh Admin: <span style={{ fontWeight: 700, color: '#000' }}>{currentAdmin}</span><br />
|
||
Waktu Cetak: <span style={{ fontWeight: 700, color: '#000' }}>{tglCetak}, {jamCetak} WIB</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginTop: '40px', textAlign: 'center', fontSize: '9px', color: '#ccc', letterSpacing: '1px' }}>
|
||
DOKUMEN INI DIHASILKAN SECARA OTOMATIS OLEH SISTEM CLOUD STUNTING
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ─── Visible Button ─── */}
|
||
<button
|
||
onClick={handlePrint}
|
||
disabled={loading}
|
||
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all group relative"
|
||
title="Cetak PDF Detail Jadwal"
|
||
>
|
||
{loading ? (
|
||
<Loader2 className="w-4 h-4 animate-spin" />
|
||
) : (
|
||
<FileText className="w-4 h-4" />
|
||
)}
|
||
</button>
|
||
</>
|
||
)
|
||
}
|