435 lines
26 KiB
TypeScript
435 lines
26 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useRef, useEffect } from 'react'
|
||
import { FileDown, FolderOpen, X, Loader2, CheckCircle2, ChevronDown, Calendar, Printer } from 'lucide-react'
|
||
import { supabase } from '@/lib/supabase'
|
||
import { showSwal } from '@/lib/swal'
|
||
import { getPetugasLokalByPosyandu } from './action-jadwal'
|
||
|
||
// ─── TYPES ────────────────────────────────────────────────────────
|
||
interface JadwalItem {
|
||
id: string
|
||
tanggal: string
|
||
jam_mulai: string
|
||
jam_selesai: string
|
||
diedit_oleh: string
|
||
posyandu_id: string
|
||
detail_posyandu: {
|
||
nama_posyandu: string
|
||
alamat: string
|
||
}
|
||
}
|
||
|
||
interface PetugasLokal {
|
||
nama_petugas: string
|
||
nomor_hp: string
|
||
jabatan: string
|
||
}
|
||
|
||
const MONTHS = [
|
||
{ num: 1, name: 'Januari' }, { num: 2, name: 'Februari' },
|
||
{ num: 3, name: 'Maret' }, { num: 4, name: 'April' },
|
||
{ num: 5, name: 'Mei' }, { num: 6, name: 'Juni' },
|
||
{ num: 7, name: 'Juli' }, { num: 8, name: 'Agustus' },
|
||
{ num: 9, name: 'September' }, { num: 10, name: 'Oktober' },
|
||
{ num: 11, name: 'November' }, { num: 12, name: 'Desember' },
|
||
]
|
||
|
||
const START_YEAR = 2026
|
||
const currentYear = new Date().getFullYear()
|
||
const YEARS = Array.from(
|
||
{ length: Math.max(currentYear, START_YEAR) - START_YEAR + 1 },
|
||
(_, i) => START_YEAR + i,
|
||
)
|
||
|
||
type Step = 'config' | 'generating' | 'done' | 'error'
|
||
|
||
interface Props {
|
||
adminName: string
|
||
}
|
||
|
||
export function CetakBatchJadwalModal({ adminName }: Props) {
|
||
const [open, setOpen] = useState(false)
|
||
const [year, setYear] = useState(START_YEAR)
|
||
const [month, setMonth] = useState(new Date().getMonth() + 1)
|
||
const [dirHandle, setDirHandle] = useState<FileSystemDirectoryHandle | null>(null)
|
||
const [step, setStep] = useState<Step>('config')
|
||
const [progress, setProgress] = useState({ current: 0, total: 0, name: '' })
|
||
const [elapsed, setElapsed] = useState(0)
|
||
const [errorMsg, setErrorMsg] = useState('')
|
||
|
||
// Template state for batch processing
|
||
const [activeJadwal, setActiveJadwal] = useState<JadwalItem | null>(null)
|
||
const [activePetugas, setActivePetugas] = useState<PetugasLokal[]>([])
|
||
|
||
const templateRef = useRef<HTMLDivElement>(null)
|
||
const monthName = MONTHS.find(m => m.num === month)?.name ?? 'Bulan'
|
||
const folderName = `jadwal_${monthName.toLowerCase()}_${year}`
|
||
const supportsFileSys = typeof window !== 'undefined' && 'showDirectoryPicker' in window
|
||
|
||
const pickDir = async () => {
|
||
try {
|
||
const handle = await (window as any).showDirectoryPicker({ mode: 'readwrite' })
|
||
setDirHandle(handle)
|
||
} catch { /* user cancelled */ }
|
||
}
|
||
|
||
const reset = () => {
|
||
setStep('config')
|
||
setProgress({ current: 0, total: 0, name: '' })
|
||
setElapsed(0)
|
||
setErrorMsg('')
|
||
setActiveJadwal(null)
|
||
setActivePetugas([])
|
||
}
|
||
|
||
const handleClose = () => {
|
||
setOpen(false)
|
||
setTimeout(reset, 300)
|
||
}
|
||
|
||
const handleGenerate = async () => {
|
||
setStep('generating')
|
||
const startTime = Date.now()
|
||
const timer = setInterval(() => setElapsed(Math.floor((Date.now() - startTime) / 1000)), 500)
|
||
|
||
try {
|
||
const { default: html2canvas } = await import('html2canvas')
|
||
const { default: jsPDF } = await import('jspdf')
|
||
|
||
// 1. Fetch schedules for the period
|
||
const startOfMonth = `${year}-${String(month).padStart(2, '0')}-01`
|
||
const endOfMonth = new Date(year, month, 0).toISOString().split('T')[0]
|
||
|
||
const { data: schedules, error: eJ } = await supabase
|
||
.from('jadwal_posyandu')
|
||
.select(`
|
||
id, tanggal, jam_mulai, jam_selesai, diedit_oleh, posyandu_id,
|
||
detail_posyandu ( nama_posyandu, alamat )
|
||
`)
|
||
.gte('tanggal', startOfMonth)
|
||
.lte('tanggal', endOfMonth)
|
||
.order('tanggal', { ascending: true })
|
||
|
||
if (eJ) throw new Error(eJ.message)
|
||
if (!schedules || schedules.length === 0) {
|
||
setErrorMsg(`Tidak ada jadwal ditemukan untuk ${monthName} ${year}`)
|
||
setStep('error')
|
||
clearInterval(timer)
|
||
return
|
||
}
|
||
|
||
setProgress({ current: 0, total: schedules.length, name: '' })
|
||
|
||
// 2. Prepare folder
|
||
let folderHandle: FileSystemDirectoryHandle | null = null
|
||
if (dirHandle) {
|
||
folderHandle = await dirHandle.getDirectoryHandle(folderName, { create: true })
|
||
}
|
||
|
||
// 3. Loop and Generate
|
||
for (let i = 0; i < schedules.length; i++) {
|
||
const j = schedules[i] as any as JadwalItem
|
||
setProgress({ current: i + 1, total: schedules.length, name: j.detail_posyandu.nama_posyandu })
|
||
|
||
// Fetch local officers for this Posyandu
|
||
const resP = await getPetugasLokalByPosyandu(j.posyandu_id)
|
||
const petugas = resP.success ? resP.petugas : []
|
||
|
||
// Update template and wait for render
|
||
setActiveJadwal(j)
|
||
setActivePetugas(petugas || [])
|
||
await new Promise(r => setTimeout(r, 400)) // Stable render buffer
|
||
|
||
if (!templateRef.current) continue
|
||
|
||
// Capture
|
||
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)
|
||
|
||
const fileName = `Jadwal_${j.detail_posyandu.nama_posyandu.replace(/\s+/g, '_')}_${j.tanggal}.pdf`
|
||
const blob = pdf.output('blob')
|
||
|
||
if (folderHandle) {
|
||
const fh = await folderHandle.getFileHandle(fileName, { create: true })
|
||
const writable = await fh.createWritable()
|
||
await writable.write(blob)
|
||
await writable.close()
|
||
} else {
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = fileName
|
||
a.click()
|
||
URL.revokeObjectURL(url)
|
||
await new Promise(r => setTimeout(r, 200))
|
||
}
|
||
}
|
||
|
||
clearInterval(timer)
|
||
setStep('done')
|
||
await showSwal.success('Selesai!', `Berhasil mencetak ${schedules.length} file PDF jadwal.`)
|
||
handleClose()
|
||
|
||
} catch (err: any) {
|
||
clearInterval(timer)
|
||
setErrorMsg(err?.message ?? 'Gagal memproses batch PDF.')
|
||
setStep('error')
|
||
} finally {
|
||
setActiveJadwal(null)
|
||
setActivePetugas([])
|
||
}
|
||
}
|
||
|
||
const pct = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0
|
||
const estRemaining = progress.current > 0
|
||
? Math.round((elapsed / progress.current) * (progress.total - progress.current))
|
||
: null
|
||
|
||
// ── Template helper variables ──────────────────────────────────
|
||
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' })
|
||
|
||
return (
|
||
<>
|
||
<button
|
||
onClick={() => { setOpen(true); reset() }}
|
||
className="flex items-center justify-center gap-2 px-5 py-2.5 bg-white text-gray-700 border-2 border-gray-100 font-black rounded-xl hover:border-black hover:text-black transition-all text-xs"
|
||
>
|
||
<Printer className="w-4 h-4" />
|
||
Cetak Semua Jadwal
|
||
</button>
|
||
|
||
{open && (
|
||
<div className="fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" onClick={step === 'config' ? handleClose : undefined}>
|
||
<div className="bg-white rounded-2xl border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] w-full max-w-md overflow-hidden" onClick={e => e.stopPropagation()}>
|
||
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between px-6 py-5 border-b-2 border-black bg-black text-white">
|
||
<div className="flex items-center gap-3">
|
||
<Printer className="w-5 h-5 text-red-500" />
|
||
<div>
|
||
<p className="font-black text-lg leading-none">Cetak Batch Jadwal</p>
|
||
<p className="text-[10px] text-gray-400 mt-0.5 uppercase tracking-widest">Generate PDF Kolektif Per Periode</p>
|
||
</div>
|
||
</div>
|
||
{step !== 'generating' && (
|
||
<button onClick={handleClose} className="p-1.5 rounded-full hover:bg-white/10 transition-colors">
|
||
<X className="w-4 h-4" />
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{step === 'config' && (
|
||
<div className="p-6 flex flex-col gap-6">
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="relative">
|
||
<label className="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1 block">Tahun</label>
|
||
<select value={year} onChange={e => setYear(Number(e.target.value))} className="w-full appearance-none border-2 border-gray-200 focus:border-black rounded-lg pl-3 pr-8 py-2.5 text-sm font-bold bg-white focus:outline-none transition-colors">
|
||
{YEARS.map(y => <option key={y} value={y}>{y}</option>)}
|
||
</select>
|
||
<ChevronDown className="absolute right-3 bottom-2.5 w-4 h-4 text-gray-400 pointer-events-none" />
|
||
</div>
|
||
<div className="relative">
|
||
<label className="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1 block">Bulan</label>
|
||
<select value={month} onChange={e => setMonth(Number(e.target.value))} className="w-full appearance-none border-2 border-gray-200 focus:border-black rounded-lg pl-3 pr-8 py-2.5 text-sm font-bold bg-white focus:outline-none transition-colors">
|
||
{MONTHS.map(m => <option key={m.num} value={m.num}>{m.name}</option>)}
|
||
</select>
|
||
<ChevronDown className="absolute right-3 bottom-2.5 w-4 h-4 text-gray-400 pointer-events-none" />
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-2">Lokasi Penyimpanan</p>
|
||
{supportsFileSys ? (
|
||
<button onClick={pickDir} className="w-full flex items-center justify-between px-4 py-3 border-2 border-dashed border-gray-200 hover:border-black rounded-xl transition-all group">
|
||
<div className="flex items-center gap-3">
|
||
<FolderOpen className="w-5 h-5 text-gray-400 group-hover:text-black" />
|
||
<span className="text-sm font-bold text-gray-500 group-hover:text-black">{dirHandle ? dirHandle.name : 'Pilih Folder Tujuan'}</span>
|
||
</div>
|
||
{dirHandle && <CheckCircle2 className="w-4 h-4 text-emerald-500" />}
|
||
</button>
|
||
) : (
|
||
<div className="p-3 bg-amber-50 border border-amber-100 rounded-xl text-[10px] font-bold text-amber-700 leading-relaxed uppercase">
|
||
⚠ Browser Anda tidak mendukung akses folder. File akan diunduh satu per satu secara otomatis.
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<button onClick={handleGenerate} className="w-full py-4 bg-red-600 text-white font-black text-xs uppercase tracking-widest rounded-xl hover:bg-red-700 transition-all shadow-[4px_4px_0px_0px_rgba(0,0,0,0.2)] hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px] flex items-center justify-center gap-2">
|
||
<FileDown className="w-4 h-4" />
|
||
Mulai Cetak Batch
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{step === 'generating' && (
|
||
<div className="p-6 flex flex-col gap-5">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 bg-red-50 rounded-lg flex items-center justify-center flex-shrink-0 animate-pulse">
|
||
<Loader2 className="w-5 h-5 text-red-600 animate-spin" />
|
||
</div>
|
||
<div>
|
||
<p className="font-black text-base leading-tight">Sedang Memproses PDF...</p>
|
||
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mt-1">Jangan tutup halaman ini</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="flex justify-between items-end mb-1.5">
|
||
<span className="text-[10px] font-black uppercase text-gray-400">{progress.current} / {progress.total} POSYANDU</span>
|
||
<span className="text-lg font-black">{pct}%</span>
|
||
</div>
|
||
<div className="w-full h-3 bg-gray-100 rounded-full overflow-hidden border border-gray-100">
|
||
<div className="h-full bg-red-600 rounded-full transition-all duration-300" style={{ width: `${pct}%` }} />
|
||
</div>
|
||
</div>
|
||
|
||
{progress.name && (
|
||
<div className="p-3 bg-gray-50 border border-gray-100 rounded-xl">
|
||
<p className="text-[10px] font-black text-gray-400 uppercase mb-0.5">Memproses:</p>
|
||
<p className="text-sm font-black uppercase truncate">{progress.name}</p>
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="p-3 bg-gray-50 border border-gray-100 rounded-xl text-center">
|
||
<p className="text-[9px] font-black text-gray-400 uppercase mb-1">Berjalan</p>
|
||
<p className="text-xl font-black font-mono">{String(Math.floor(elapsed / 60)).padStart(2, '0')}:{String(elapsed % 60).padStart(2, '0')}</p>
|
||
</div>
|
||
<div className="p-3 bg-gray-50 border border-gray-100 rounded-xl text-center">
|
||
<p className="text-[9px] font-black text-gray-400 uppercase mb-1">Estimasi</p>
|
||
<p className="text-xl font-black font-mono text-emerald-600">{estRemaining !== null ? `${String(Math.floor(estRemaining / 60)).padStart(2, '0')}:${String(estRemaining % 60).padStart(2, '0')}` : '--:--'}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{step === 'error' && (
|
||
<div className="p-8 flex flex-col items-center text-center gap-4">
|
||
<div className="w-16 h-16 bg-red-50 rounded-full flex items-center justify-center">
|
||
<X className="w-8 h-8 text-red-500" />
|
||
</div>
|
||
<div>
|
||
<p className="font-black text-xl mb-1 uppercase">Gagal Memproses</p>
|
||
<p className="text-xs text-gray-500 font-semibold">{errorMsg}</p>
|
||
</div>
|
||
<button onClick={reset} className="w-full py-3 bg-black text-white font-black text-xs uppercase tracking-widest rounded-xl">Coba Lagi</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* ─── Hidden PDF Template (Same as CetakPDFJadwal) ─── */}
|
||
{activeJadwal && (
|
||
<div style={{ position: 'fixed', top: '-9999px', left: '-9999px', zIndex: -1 }}>
|
||
<div ref={templateRef} style={{ width: '794px', backgroundColor: '#ffffff', fontFamily: "'Inter', 'Arial', sans-serif", color: '#111111', padding: '60px 70px', boxSizing: 'border-box' }}>
|
||
|
||
<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-{activeJadwal.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>
|
||
|
||
<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' }}>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' }}>{activeJadwal.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' }}>{activeJadwal.detail_posyandu.alamat}</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: '9px', fontWeight: 800, textTransform: 'uppercase', color: '#888', letterSpacing: '1px', marginBottom: '15px' }}>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' }}>{new Date(activeJadwal.tanggal).toLocaleDateString('id-ID', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}</div>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: '10px', color: '#888', fontWeight: 600, marginBottom: '4px' }}>Sesi Operasional</div>
|
||
<div style={{ fontSize: '18px', fontWeight: 900, color: '#e11d48' }}>{activeJadwal.jam_mulai.slice(0, 5)} - {activeJadwal.jam_selesai.slice(0, 5)} WIB</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '40px' }}>
|
||
<div style={{ fontSize: '9px', fontWeight: 800, textTransform: 'uppercase', color: '#888', letterSpacing: '1px', marginBottom: '15px' }}>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>
|
||
{activePetugas.length > 0 ? activePetugas.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.</td></tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<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' }}>⚠️ 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>
|
||
|
||
<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' }}>{activeJadwal.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' }}>{adminName}</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>
|
||
)}
|
||
</>
|
||
)
|
||
}
|