-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-bundle.html
More file actions
50 lines (45 loc) · 96.1 KB
/
Copy pathtest-bundle.html
File metadata and controls
50 lines (45 loc) · 96.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html>
<head>
<title>Test Bundle</title>
<style>
body { font-family: monospace; padding: 20px; background: #f5f5f5; }
#result { background: white; padding: 20px; border-radius: 8px; white-space: pre-wrap; word-break: break-all; max-height: 60vh; overflow: auto; }
.error { color: red; }
.success { color: green; }
</style>
</head>
<body>
<h2>Bundle Test</h2>
<div id="result">Testing...</div>
<script>
window.addEventListener('DOMContentLoaded', async function() {
const result = document.getElementById('result');
try {
const templateEl = document.querySelector('script[type="__bundler/template"]');
if (!templateEl) {
result.innerHTML = '<span class="error">Template element not found</span>';
return;
}
const content = templateEl.textContent;
result.innerHTML += `\nTemplate content length: ${content.length}\n`;
result.innerHTML += `First 100 chars: ${content.substring(0, 100)}\n`;
result.innerHTML += `Last 100 chars: ${content.substring(content.length - 100)}\n\n`;
// Try to parse
const parsed = JSON.parse(content);
result.innerHTML += `<span class="success">✓ JSON parsed successfully!</span>\n`;
result.innerHTML += `Decoded string length: ${parsed.length}\n`;
result.innerHTML += `First 100 chars of decoded:\n${parsed.substring(0, 100)}\n`;
} catch (err) {
result.innerHTML = `<span class="error">ERROR: ${err.message}</span>\n`;
if (err.name === 'SyntaxError') {
result.innerHTML += `Syntax position: ${err.message.match(/\d+/)}\n`;
}
}
});
</script>
<script type="__bundler/template">
"<!DOCTYPE html>\n<html lang=\"id\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Sistem Rincian SPD \u2014 Imigrasi 2026</title>\n<style>\n /* ============================================================\n TOKENS\n ============================================================ */\n :root{\n --bg: #0A1628;\n --panel: #0D1F3C;\n --card: #112240;\n --card-2: #0F1E3A;\n --border: #1E3A5F;\n --border-2: #2A4A6F;\n --input: #1A2E4A;\n --gold: #C8A951;\n --gold-2: #A8892F;\n --gold-soft: #E2C77A;\n --blue: #1A6EBD;\n --blue-2: #1558A0;\n --text: #E8F0FE;\n --text-2: #8BA7C7;\n --text-3: #5E789C;\n --danger: #E5484D;\n --ok: #2BB673;\n --radius: 8px;\n --radius-sm: 5px;\n --shadow: 0 6px 20px rgba(0,0,0,.35);\n --font: 'Segoe UI', system-ui, -apple-system, Arial, sans-serif;\n }\n\n *{ box-sizing: border-box; }\n html, body{ height: 100%; }\n body{\n margin:0; background: var(--bg); color: var(--text);\n font-family: var(--font); font-size: 14px; line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n }\n button, input, select, textarea{ font-family: inherit; font-size: inherit; color: inherit; }\n\n /* ============================================================\n APP SHELL\n ============================================================ */\n .app{ min-height: 100vh; display: flex; flex-direction: column; }\n\n .topbar{\n background: linear-gradient(180deg, #0E2245 0%, #0A1B36 100%);\n border-bottom: 1px solid var(--border);\n padding: 14px 28px;\n display: flex; align-items: center; gap: 18px;\n position: sticky; top: 0; z-index: 20;\n }\n .topbar .crest{\n width: 52px; height: 52px; flex: 0 0 52px;\n border-radius: 50%; object-fit: contain;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,.4));\n }\n .topbar .titles{ flex: 1; min-width: 0; }\n .topbar h1{\n margin: 0; font-size: 18px; letter-spacing: .14em;\n color: var(--text); font-weight: 700;\n }\n .topbar h1 .accent{ color: var(--gold); }\n .topbar .sub{\n margin-top: 2px; font-size: 11px; letter-spacing: .18em;\n color: var(--text-2); text-transform: uppercase;\n }\n .topbar .meta{\n text-align: right; font-size: 11px; color: var(--text-3);\n letter-spacing: .1em;\n }\n .topbar .meta strong{ color: var(--gold-soft); font-weight: 600; }\n\n /* Tabs */\n .tabs{\n background: var(--panel);\n border-bottom: 1px solid var(--border);\n padding: 0 28px;\n display: flex; gap: 6px;\n position: sticky; top: 76px; z-index: 19;\n }\n .tab{\n appearance: none; background: transparent; border: 0;\n padding: 14px 22px; cursor: pointer;\n color: var(--text-2); font-size: 13px; letter-spacing: .14em;\n text-transform: uppercase; font-weight: 600;\n border-bottom: 2px solid transparent;\n transition: color .15s, border-color .15s;\n }\n .tab:hover{ color: var(--text); }\n .tab.active{\n color: var(--text);\n border-bottom-color: var(--gold);\n }\n .tab .dot{\n display: inline-block; width: 6px; height: 6px; border-radius: 50%;\n background: var(--gold); margin-right: 8px; vertical-align: middle;\n opacity: .35;\n }\n .tab.active .dot{ opacity: 1; box-shadow: 0 0 8px var(--gold); }\n\n /* Main */\n .main{ padding: 24px 28px 48px; max-width: 1380px; width: 100%; margin: 0 auto; }\n .view{ display: none; }\n .view.active{ display: block; }\n\n /* ============================================================\n CARDS / SECTIONS\n ============================================================ */\n .section{\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n margin-bottom: 20px;\n box-shadow: var(--shadow);\n overflow: hidden;\n }\n .section-head{\n display: flex; align-items: center; gap: 12px;\n padding: 14px 20px;\n background: linear-gradient(180deg, rgba(200,169,81,.06), transparent);\n border-bottom: 1px solid var(--border);\n }\n .section-head .num{\n width: 26px; height: 26px; border-radius: 4px;\n background: var(--gold); color: #1A1305;\n display: grid; place-items: center; font-weight: 700; font-size: 12px;\n }\n .section-head h2{\n margin: 0; font-size: 13px; letter-spacing: .16em;\n text-transform: uppercase; font-weight: 700;\n }\n .section-head .hint{\n margin-left: auto; font-size: 11px; color: var(--text-3);\n letter-spacing: .08em;\n }\n .section-body{ padding: 18px 20px 20px; }\n\n /* ============================================================\n FORM FIELDS\n ============================================================ */\n .grid{ display: grid; gap: 14px; }\n .g-2{ grid-template-columns: repeat(2, minmax(0,1fr)); }\n .g-3{ grid-template-columns: repeat(3, minmax(0,1fr)); }\n .g-4{ grid-template-columns: repeat(4, minmax(0,1fr)); }\n .g-6{ grid-template-columns: repeat(6, minmax(0,1fr)); }\n .col-2{ grid-column: span 2; }\n .col-3{ grid-column: span 3; }\n .col-4{ grid-column: span 4; }\n .col-6{ grid-column: span 6; }\n .col-all{ grid-column: 1 / -1; }\n\n .field label{\n display: block; font-size: 11px; letter-spacing: .12em;\n text-transform: uppercase; color: var(--text-2);\n margin-bottom: 6px; font-weight: 600;\n }\n .field label .req{ color: var(--gold); margin-left: 3px; }\n .field input, .field select, .field textarea{\n width: 100%; padding: 10px 12px;\n background: var(--input);\n border: 1px solid var(--border-2);\n color: var(--text);\n border-radius: var(--radius-sm);\n outline: none;\n transition: border-color .15s, box-shadow .15s, background .15s;\n }\n .field input:focus, .field select:focus, .field textarea:focus{\n border-color: var(--gold);\n box-shadow: 0 0 0 3px rgba(200,169,81,.18);\n background: #1F375C;\n }\n .field textarea{ resize: vertical; min-height: 64px; }\n .field input[type=\"number\"]{ font-variant-numeric: tabular-nums; }\n .field.invalid input, .field.invalid select, .field.invalid textarea{\n border-color: var(--danger);\n box-shadow: 0 0 0 3px rgba(229,72,77,.15);\n }\n .field .hint{ font-size: 11px; color: var(--text-3); margin-top: 4px; }\n\n /* Pegawai card */\n .pegawai-card{\n background: var(--card-2);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n margin-bottom: 14px;\n overflow: hidden;\n }\n .pegawai-head{\n display: flex; align-items: center; gap: 12px;\n padding: 10px 16px;\n background: linear-gradient(90deg, rgba(26,110,189,.18), transparent 60%);\n border-bottom: 1px solid var(--border);\n }\n .pegawai-head .badge{\n width: 26px; height: 26px; border-radius: 50%;\n background: var(--blue); color: #fff;\n display: grid; place-items: center; font-weight: 700; font-size: 12px;\n }\n .pegawai-head .name{ font-weight: 600; color: var(--text); }\n .pegawai-head .name .placeholder{ color: var(--text-3); font-weight: 500; }\n .pegawai-head .actions{ margin-left: auto; display: flex; gap: 8px; }\n .pegawai-body{ padding: 14px 16px; }\n\n .pegawai-totals{\n margin-top: 14px; padding-top: 14px;\n border-top: 1px dashed var(--border-2);\n display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;\n }\n .stat{\n background: rgba(255,255,255,.02);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n padding: 10px 12px;\n }\n .stat .k{ font-size: 10px; color: var(--text-3); letter-spacing: .14em; text-transform: uppercase; }\n .stat .v{ margin-top: 4px; font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }\n .stat.total{\n background: linear-gradient(135deg, rgba(200,169,81,.18), rgba(200,169,81,.04));\n border-color: rgba(200,169,81,.5);\n }\n .stat.total .v{ color: var(--gold-soft); font-size: 16px; }\n\n /* Bukti block */\n .bukti-block{\n margin-top: 14px; padding: 14px 16px;\n background: rgba(26,110,189,.06);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n }\n .bukti-title{\n font-size: 11px; letter-spacing: .14em; text-transform: uppercase;\n color: var(--text-2); font-weight: 600; margin-bottom: 12px;\n display: flex; align-items: center; gap: 8px;\n }\n .bukti-title::before{\n content: \"\ud83d\udcce\"; font-size: 13px;\n }\n .bukti-block select{ font-size: 13px; }\n\n /* DPR line items */\n .dpr-item{\n background: var(--card-2);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n padding: 12px 14px; margin-bottom: 10px;\n }\n .dpr-item-head{\n display: flex; align-items: center; gap: 10px; margin-bottom: 10px;\n }\n .dpr-item-head .ix{\n width: 22px; height: 22px; border-radius: 4px; flex: 0 0 22px;\n background: var(--blue); color: #fff; display: grid; place-items: center;\n font-size: 11px; font-weight: 700;\n }\n .dpr-item-head .calc{\n margin-left: auto; font-size: 12px; color: var(--gold-soft);\n font-variant-numeric: tabular-nums; font-weight: 600;\n }\n .dpr-calc-row{\n display: grid; gap: 8px; align-items: end;\n grid-template-columns: 1fr 70px 80px 70px 80px 1.2fr;\n }\n .dpr-calc-row .op{\n text-align: center; color: var(--text-3); font-weight: 700; padding-bottom: 10px;\n }\n .grp-total{\n margin-top: 14px; padding: 12px 16px;\n background: linear-gradient(135deg, rgba(200,169,81,.16), rgba(200,169,81,.04));\n border: 1px solid rgba(200,169,81,.5); border-radius: var(--radius-sm);\n display: flex; align-items: center; gap: 14px;\n }\n .grp-total .lbl{ font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-2); }\n .grp-total .amt{ margin-left: auto; font-size: 18px; font-weight: 700; color: var(--gold-soft); font-variant-numeric: tabular-nums; }\n .grp-total .terb{ flex-basis: 100%; font-size: 12px; color: var(--text-3); font-style: italic; }\n @media (max-width: 900px){\n .dpr-calc-row{ grid-template-columns: 1fr 1fr; }\n .dpr-calc-row .op{ display: none; }\n }\n\n /* ============================================================\n BUTTONS\n ============================================================ */\n .btn{\n appearance: none; border: 1px solid transparent;\n padding: 10px 16px; border-radius: var(--radius-sm);\n cursor: pointer; font-weight: 600; font-size: 13px;\n letter-spacing: .04em;\n display: inline-flex; align-items: center; gap: 8px;\n transition: transform .05s, background .15s, border-color .15s, box-shadow .15s;\n white-space: nowrap;\n }\n .btn:active{ transform: translateY(1px); }\n .btn-primary{\n background: linear-gradient(180deg, var(--gold) 0%, var(--gold-2) 100%);\n color: #1A1305;\n box-shadow: 0 2px 0 rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.3);\n }\n .btn-primary:hover{ filter: brightness(1.05); }\n .btn-print{\n background: var(--blue); color: #fff;\n border-color: var(--blue-2);\n }\n .btn-print:hover{ background: var(--blue-2); }\n .btn-ghost{\n background: transparent; color: var(--text);\n border-color: var(--border-2);\n }\n .btn-ghost:hover{ background: rgba(255,255,255,.04); border-color: var(--gold); color: var(--gold-soft); }\n .btn-danger{\n background: transparent; color: var(--danger);\n border-color: rgba(229,72,77,.4);\n }\n .btn-danger:hover{ background: rgba(229,72,77,.1); }\n .btn-sm{ padding: 6px 10px; font-size: 12px; }\n .btn-icon{ padding: 6px 8px; }\n\n .actions-bar{\n position: sticky; bottom: 0;\n margin: 0 -28px -48px;\n padding: 14px 28px;\n background: linear-gradient(180deg, transparent, rgba(10,22,40,.92) 40%);\n backdrop-filter: blur(8px);\n border-top: 1px solid var(--border);\n display: flex; gap: 10px; flex-wrap: wrap;\n z-index: 5;\n }\n .actions-bar .spacer{ flex: 1; }\n\n .add-btn{\n width: 100%; padding: 14px;\n border: 1.5px dashed var(--border-2);\n background: transparent;\n color: var(--text-2);\n border-radius: var(--radius);\n cursor: pointer; font-weight: 600;\n letter-spacing: .1em; text-transform: uppercase; font-size: 12px;\n transition: color .15s, border-color .15s, background .15s;\n }\n .add-btn:hover{ color: var(--gold); border-color: var(--gold); background: rgba(200,169,81,.05); }\n .add-btn:disabled{ opacity: .4; cursor: not-allowed; }\n\n /* DPR PIC preview pane */\n .preview-shell{\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 24px;\n box-shadow: var(--shadow);\n }\n .preview-shell .preview-bar{\n display: flex; align-items: center; gap: 10px;\n margin-bottom: 18px; padding-bottom: 14px;\n border-bottom: 1px solid var(--border);\n }\n .preview-shell .preview-bar h2{\n margin: 0; font-size: 13px; letter-spacing: .16em; text-transform: uppercase;\n }\n .preview-shell .preview-bar .spacer{ flex: 1; }\n .paper{\n background: #fff; color: #111;\n padding: 22mm 18mm;\n margin: 0 auto;\n width: 210mm; min-height: 297mm;\n font-family: 'Times New Roman', Georgia, serif;\n font-size: 11pt; line-height: 1.4;\n box-shadow: 0 8px 30px rgba(0,0,0,.45);\n }\n .paper-stack{\n display: flex; flex-direction: column;\n gap: 28px; align-items: center;\n }\n .paper-stack .paper{ margin: 0; }\n .paper-page-label{\n align-self: center; margin: 0 auto;\n font-size: 11px; letter-spacing: .16em; text-transform: uppercase;\n color: var(--text-3); font-weight: 600;\n display: flex; align-items: center; gap: 10px;\n }\n .paper-page-label::before, .paper-page-label::after{\n content: \"\"; width: 40px; height: 1px; background: var(--border-2);\n }\n\n /* Toast */\n .toast{\n position: fixed; bottom: 24px; left: 50%;\n transform: translateX(-50%) translateY(20px);\n background: var(--card); color: var(--text);\n border: 1px solid var(--gold);\n padding: 10px 18px; border-radius: 999px;\n box-shadow: var(--shadow);\n opacity: 0; pointer-events: none;\n transition: opacity .2s, transform .2s;\n z-index: 100; font-size: 13px;\n }\n .toast.show{ opacity: 1; transform: translateX(-50%) translateY(0); }\n\n /* Modal */\n .modal-overlay{\n position: fixed; inset: 0; background: rgba(0,0,0,.6);\n display: none; align-items: center; justify-content: center; z-index: 200;\n }\n .modal-overlay.show{ display: flex; }\n .modal{\n background: var(--card); border: 1px solid var(--border);\n border-radius: var(--radius); padding: 24px;\n width: min(440px, 90vw);\n box-shadow: var(--shadow);\n }\n .modal h3{ margin: 0 0 8px; }\n .modal p{ color: var(--text-2); margin: 0 0 18px; }\n .modal .modal-actions{ display: flex; justify-content: flex-end; gap: 10px; }\n\n /* ============================================================\n PRINT SHEETS (hidden on screen, shown on print)\n ============================================================ */\n .print-root{ display: none; }\n\n /* ============================================================\n PRINT STYLES\n ============================================================ */\n @page{ size: A4 portrait; margin: 18mm 16mm; }\n @media print{\n html, body {\n height: auto !important;\n overflow: visible !important;\n }\n body{ background: #fff; color: #000; font-family: 'Times New Roman', Georgia, serif; font-size: 11pt; }\n .app{ display: none !important; }\n .print-root{ display: block !important; }\n .toast, .modal-overlay{ display: none !important; }\n }\n\n /* Mobile Print Preview styles */\n #print-preview-modal-body .sheet {\n background: #fff;\n color: #111;\n padding: 20mm 15mm;\n margin: 10px auto;\n width: 210mm;\n min-height: 297mm;\n font-family: 'Times New Roman', Georgia, serif;\n font-size: 11pt;\n line-height: 1.4;\n box-shadow: 0 4px 15px rgba(0,0,0,.45);\n box-sizing: border-box;\n page-break-after: always;\n }\n #print-preview-modal-body .sheet.landscape {\n width: 297mm;\n min-height: 210mm;\n }\n #print-preview-modal-body .sheet:last-child {\n page-break-after: auto;\n }\n \n @media (max-width: 768px) {\n #print-preview-modal-body {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 15px;\n }\n #print-preview-modal-body .sheet {\n margin: 0;\n }\n }\n\n /* ============================================================\n PRINT SHEET LAYOUT (used in screen preview AND print)\n ============================================================ */\n .sheet{\n font-family: 'Times New Roman', Georgia, serif;\n color: #000;\n page-break-after: always;\n }\n .sheet:last-child{ page-break-after: auto; }\n .kop{\n text-align: center;\n border-bottom: 3px double #000;\n padding-bottom: 8px; margin-bottom: 14px;\n display: flex; align-items: center; gap: 14px;\n }\n .kop .kop-logo{\n width: 84px; height: 84px; flex: 0 0 84px;\n object-fit: contain;\n }\n .kop .kop-text{ flex: 1; text-align: center; }\n .kop .kop-text .l1{ font-size: 11pt; font-weight: 700; }\n .kop .kop-text .l2{ font-size: 11pt; font-weight: 700; }\n .kop .kop-text .l3{ font-size: 11pt; font-weight: 700; }\n .kop .kop-text .l4{ font-size: 13pt; font-weight: 700; letter-spacing: .02em; }\n .kop .kop-text .l5{ font-size: 10pt; margin-top: 2px; }\n .kop .kop-text .l6{ font-size: 9pt; }\n .kop-pad{ width: 84px; flex: 0 0 84px; } /* balance */\n\n .doc-title{\n text-align: center; font-weight: 700;\n text-decoration: underline;\n margin: 12px 0 4px;\n font-size: 12pt;\n }\n .doc-subtitle{\n text-align: center; font-size: 11pt; margin-bottom: 12px;\n }\n\n .doc-table{\n width: 100%; border-collapse: collapse; font-size: 10pt;\n }\n .doc-table th, .doc-table td{\n border: 1px solid #000; padding: 4px 6px; vertical-align: top;\n }\n .doc-table th{ background: #EEE; font-weight: 700; text-align: center; }\n .num{ text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }\n .ctr{ text-align: center; }\n .ttd-cell{ text-align: left; vertical-align: top; padding: 4px 6px; height: 56px; }\n\n .ttd-grid{\n display: grid; grid-template-columns: 1fr 1fr; gap: 28px;\n margin-top: 28px; page-break-inside: avoid;\n }\n .ttd{ text-align: center; }\n .ttd .role{ font-weight: 400; margin-bottom: 64px; }\n .ttd .name{ font-weight: 700; text-decoration: underline; }\n .ttd .nip{ font-weight: 400; }\n\n .terbilang{ font-style: italic; }\n\n /* Landscape sheet (Nominatif) */\n .sheet.landscape{ /* handled via page rule below */ }\n @media print{\n .sheet.landscape{ page: landscape; }\n }\n @page landscape{ size: A4 landscape; margin: 12mm 10mm; }\n\n /* Footer responsiveness */\n @media (max-width: 900px){\n .g-3, .g-4, .g-6{ grid-template-columns: repeat(2, minmax(0,1fr)); }\n .col-3, .col-4, .col-6{ grid-column: span 2; }\n .pegawai-totals{ grid-template-columns: repeat(2, 1fr); }\n }\n</style>\n</head>\n<body>\n\n<!-- ============================================================\n SCREEN APP\n ============================================================ -->\n<div class=\"app\" id=\"app\">\n\n <header class=\"topbar\">\n <img class=\"crest\" src=\"4e9b82d6-5ca9-4f6b-bdcc-22ba73f2ca1b\" alt=\"Logo Imigrasi\">\n <div class=\"titles\">\n <h1>SISTEM <span class=\"accent\">RINCIAN SPD</span></h1>\n <div class=\"sub\">Imigrasi 2026 \u00b7 Kantor Imigrasi Kelas I TPI Pontianak</div>\n </div>\n <div class=\"meta\">\n <div>TAHUN ANGGARAN <strong>2026</strong></div>\n <div>v 1.0</div>\n </div>\n </header>\n\n <nav class=\"tabs\" role=\"tablist\">\n <button class=\"tab active\" data-tab=\"pengisian\" role=\"tab\"><span class=\"dot\"></span>Pengisian</button>\n <button class=\"tab\" data-tab=\"dpr\" role=\"tab\"><span class=\"dot\"></span>DPR PIC</button>\n </nav>\n\n <main class=\"main\">\n\n <!-- ========== VIEW: PENGISIAN ========== -->\n <section class=\"view active\" id=\"view-pengisian\">\n\n <!-- Section A: Data Kegiatan -->\n <div class=\"section\">\n <div class=\"section-head\">\n <div class=\"num\">A</div>\n <h2>Data Kegiatan \u00b7 Header SPD</h2>\n <div class=\"hint\">Wajib dilengkapi sebelum cetak</div>\n </div>\n <div class=\"section-body\">\n <div class=\"grid g-6\">\n <div class=\"field col-6\">\n <label>Judul Kegiatan <span class=\"req\">*</span></label>\n <textarea data-h=\"judulKegiatan\" rows=\"2\" placeholder=\"MELAKUKAN KEGIATAN KOORDINASI TERKAIT\u2026\"></textarea>\n </div>\n\n <div class=\"field col-2\">\n <label>Nomor SPT <span class=\"req\">*</span></label>\n <input data-h=\"nomorSPT\" type=\"text\" placeholder=\"WIM.16.IMI.1-TI.04.01-\u2026\">\n </div>\n <div class=\"field col-2\">\n <label>Tanggal SPT</label>\n <input data-h=\"tanggalSPT\" type=\"date\">\n </div>\n <div class=\"field col-2\">\n <label>Tujuan Kota</label>\n <input data-h=\"tujuanKota\" type=\"text\" placeholder=\"Jakarta\">\n </div>\n\n <div class=\"field col-3\">\n <label>Nama Program</label>\n <input data-h=\"namaProgram\" type=\"text\" placeholder=\"Pengelolaan Teknologi Informasi Keimigrasian\">\n </div>\n <div class=\"field col-3\">\n <label>Tanggal Kegiatan (teks bebas)</label>\n <input data-h=\"tanggalKegiatan\" type=\"text\" placeholder=\"6 sd 8 Mei 2026\">\n </div>\n\n <div class=\"field col-3\">\n <label>Pejabat Pembuat Komitmen (PPK)</label>\n <input data-h=\"namaPPK\" type=\"text\" list=\"dl-pegawai-nama\" placeholder=\"Nama lengkap PPK\">\n </div>\n <div class=\"field col-2\">\n <label>NIP PPK</label>\n <input data-h=\"nipPPK\" type=\"text\">\n </div>\n <div class=\"field col-1\" style=\"grid-column: span 1;\">\n <label>Kota Terbit</label>\n <input data-h=\"kotaTerbit\" type=\"text\" placeholder=\"Pontianak\" value=\"Pontianak\">\n </div>\n\n <div class=\"field col-6\">\n <label>Jabatan PPK</label>\n <input data-h=\"jabatanPPK\" type=\"text\">\n </div>\n\n <div class=\"field col-3\">\n <label>Bendahara Pengeluaran</label>\n <input data-h=\"namaBendahara\" type=\"text\" list=\"dl-pegawai-nama\">\n </div>\n <div class=\"field col-2\">\n <label>NIP Bendahara</label>\n <input data-h=\"nipBendahara\" type=\"text\">\n </div>\n <div class=\"field\" style=\"grid-column: span 1;\">\n <label>Kode Anggaran</label>\n <input data-h=\"kodeAnggaran\" type=\"text\" placeholder=\"opsional\">\n </div>\n </div>\n </div>\n </div>\n\n <!-- Section B: Daftar Pegawai -->\n <div class=\"section\">\n <div class=\"section-head\">\n <div class=\"num\">B</div>\n <h2>Daftar Pegawai SPD</h2>\n <div class=\"hint\"><span id=\"pegawai-count\">0</span> / 10 pegawai</div>\n </div>\n <div class=\"section-body\">\n <div id=\"pegawai-list\"></div>\n <button class=\"add-btn\" id=\"btn-add-pegawai\">+ Tambah Pegawai</button>\n </div>\n </div>\n\n <!-- Action bar -->\n <div class=\"actions-bar\">\n <button class=\"btn btn-primary\" id=\"btn-save\">\ud83d\udcbe Simpan Data</button>\n <button class=\"btn btn-print\" id=\"btn-print-nominatif\">\ud83d\udda8\ufe0f Cetak Nominatif</button>\n <button class=\"btn btn-print\" id=\"btn-print-dpr\">\ud83d\udda8\ufe0f Cetak DPR PIC</button>\n <button class=\"btn btn-print\" id=\"btn-print-all\">\ud83d\udda8\ufe0f Cetak Rincian SPD Semua</button>\n <div class=\"spacer\"></div>\n <button class=\"btn btn-ghost\" id=\"btn-loadexample\">\u2934 Muat Contoh</button>\n <button class=\"btn btn-danger\" id=\"btn-reset\">\ud83d\uddd1\ufe0f Reset Form</button>\n </div>\n </section>\n\n <!-- ========== VIEW: DPR PIC ========== -->\n <section class=\"view\" id=\"view-dpr\">\n\n <!-- Penandatangan -->\n <div class=\"section\">\n <div class=\"section-head\">\n <div class=\"num\">P</div>\n <h2>Penandatangan & Dasar Surat</h2>\n <div class=\"hint\">Default mengikuti data PPK</div>\n </div>\n <div class=\"section-body\">\n <div class=\"grid g-6\">\n <div class=\"field col-3\">\n <label>Nama Penandatangan <span class=\"req\">*</span></label>\n <input data-d=\"namaTtd\" type=\"text\" list=\"dl-pegawai-nama\">\n </div>\n <div class=\"field col-2\">\n <label>NIP</label>\n <input data-d=\"nipTtd\" type=\"text\">\n </div>\n <div class=\"field\" style=\"grid-column: span 1;\">\n <label>Kota Terbit</label>\n <input data-d=\"kotaTtd\" type=\"text\" value=\"Pontianak\">\n </div>\n <div class=\"field col-3\">\n <label>Jabatan</label>\n <input data-d=\"jabatanTtd\" type=\"text\">\n </div>\n <div class=\"field col-2\">\n <label>Tanggal Surat Perintah</label>\n <input data-d=\"tanggalSurat\" type=\"date\">\n </div>\n <div class=\"field\" style=\"grid-column: span 1;\">\n <label>Tanggal Cetak</label>\n <input data-d=\"tanggalCetak\" type=\"date\">\n </div>\n <div class=\"field col-3\">\n <label>Nomor Surat Perintah</label>\n <input data-d=\"nomorSurat\" type=\"text\" placeholder=\"WIM.16.IMI.1-TI.04.01-2438\">\n </div>\n <div class=\"field col-3\">\n <label>Nama Penerima (kolom kanan TTD)</label>\n <input data-d=\"namaPenerima\" type=\"text\" list=\"dl-pegawai-nama\">\n </div>\n </div>\n </div>\n </div>\n\n <!-- FORM 1: Uang Harian -->\n <div class=\"section\" data-grp=\"g1\">\n <div class=\"section-head\">\n <div class=\"num\" style=\"background:var(--blue); color:#fff;\">1</div>\n <h2>Form 1 \u00b7 Uang Harian</h2>\n <div class=\"hint\">Belanja Barang Non Operasional</div>\n </div>\n <div class=\"section-body\">\n <div class=\"grid g-6\" style=\"margin-bottom:16px;\">\n <div class=\"field col-3\">\n <label>Baris Program 1</label>\n <input data-g=\"g1\" data-gk=\"baris1\" type=\"text\" placeholder=\"OPERASI INTELIJEN DI WILAYAH\">\n </div>\n <div class=\"field col-3\">\n <label>Baris Program 2</label>\n <input data-g=\"g1\" data-gk=\"baris2\" type=\"text\" placeholder=\"PENGUMPULAN DATA DAN INFORMASI\">\n </div>\n <div class=\"field col-4\">\n <label>Kategori Belanja</label>\n <input data-g=\"g1\" data-gk=\"kategori\" type=\"text\" placeholder=\"BELANJA BARANG NON OPERASIONAL LAINNYA\">\n </div>\n <div class=\"field col-2\">\n <label>Kode Anggaran</label>\n <input data-g=\"g1\" data-gk=\"kode\" type=\"text\" placeholder=\"(6161.BHB.U12.051.A.521219)\">\n </div>\n </div>\n <div class=\"dpr-items\" data-items=\"g1\"></div>\n <button class=\"add-btn\" data-additem=\"g1\">+ Tambah Rincian Biaya</button>\n <div class=\"grp-total\" data-grptotal=\"g1\"></div>\n </div>\n </div>\n\n <!-- FORM 2: Pengeluaran Lainnya -->\n <div class=\"section\" data-grp=\"g2\">\n <div class=\"section-head\">\n <div class=\"num\" style=\"background:var(--blue); color:#fff;\">2</div>\n <h2>Form 2 \u00b7 Pengeluaran Lainnya</h2>\n <div class=\"hint\">Belanja Sewa / lainnya</div>\n </div>\n <div class=\"section-body\">\n <div class=\"grid g-6\" style=\"margin-bottom:16px;\">\n <div class=\"field col-3\">\n <label>Baris Program 1</label>\n <input data-g=\"g2\" data-gk=\"baris1\" type=\"text\" placeholder=\"OPERASI INTELIJEN DI WILAYAH\">\n </div>\n <div class=\"field col-3\">\n <label>Baris Program 2</label>\n <input data-g=\"g2\" data-gk=\"baris2\" type=\"text\" placeholder=\"PENGUMPULAN DATA DAN INFORMASI\">\n </div>\n <div class=\"field col-4\">\n <label>Kategori Belanja</label>\n <input data-g=\"g2\" data-gk=\"kategori\" type=\"text\" placeholder=\"BELANJA SEWA\">\n </div>\n <div class=\"field col-2\">\n <label>Kode Anggaran</label>\n <input data-g=\"g2\" data-gk=\"kode\" type=\"text\" placeholder=\"(6161.BHB.U12.051.A.522141)\">\n </div>\n </div>\n <div class=\"dpr-items\" data-items=\"g2\"></div>\n <button class=\"add-btn\" data-additem=\"g2\">+ Tambah Rincian Biaya</button>\n <div class=\"grp-total\" data-grptotal=\"g2\"></div>\n </div>\n </div>\n\n <!-- DPR action bar -->\n <div class=\"actions-bar\">\n <button class=\"btn btn-primary\" id=\"btn-dpr-save\">\ud83d\udcbe Simpan Data</button>\n <button class=\"btn btn-print\" id=\"btn-dpr-print1\">\ud83d\udda8\ufe0f Cetak Form 1 (Uang Harian)</button>\n <button class=\"btn btn-print\" id=\"btn-dpr-print2\">\ud83d\udda8\ufe0f Cetak Form 2 (Lainnya)</button>\n <button class=\"btn btn-print\" id=\"btn-dpr-printall\">\ud83d\udda8\ufe0f Cetak Keduanya</button>\n <div class=\"spacer\"></div>\n <button class=\"btn btn-ghost\" id=\"btn-dpr-preview\">\ud83d\udc41\ufe0f Preview</button>\n <button class=\"btn btn-ghost\" id=\"btn-dpr-fromppk\">\u2934 Isi dari PPK</button>\n </div>\n\n <!-- Preview pane (toggle) -->\n <div class=\"preview-shell\" id=\"dpr-preview-shell\" style=\"display:none; margin-top:20px;\">\n <div class=\"preview-bar\">\n <h2>Preview Cetak</h2>\n <div class=\"spacer\"></div>\n <button class=\"btn btn-ghost\" id=\"btn-dpr-preview-close\">\u2715 Tutup Preview</button>\n </div>\n <div class=\"paper-stack\" id=\"dpr-paper\"></div>\n </div>\n </section>\n\n </main>\n</div>\n\n<!-- ============================================================\n PRINT-ONLY ROOT\n ============================================================ -->\n<div class=\"print-root\" id=\"print-root\"></div>\n\n<!-- Toast & Modal -->\n<div class=\"toast\" id=\"toast\"></div>\n<div class=\"modal-overlay\" id=\"modal\">\n <div class=\"modal\">\n <h3 id=\"modal-title\">Konfirmasi</h3>\n <p id=\"modal-body\">Anda yakin?</p>\n <div class=\"modal-actions\">\n <button class=\"btn btn-ghost\" id=\"modal-cancel\">Batal</button>\n <button class=\"btn btn-danger\" id=\"modal-ok\">Hapus</button>\n </div>\n </div>\n</div>\n\n<!-- Print Preview Modal (mobile-friendly) -->\n<style>\n #print-preview-overlay {\n position: fixed; inset: 0; z-index: 9000;\n background: rgba(5, 12, 26, 0.92);\n backdrop-filter: blur(8px);\n display: none; flex-direction: column;\n overflow: hidden;\n }\n #print-preview-overlay.open { display: flex; }\n #print-preview-header {\n flex: 0 0 auto;\n display: flex; align-items: center; gap: 14px;\n padding: 14px 20px;\n background: linear-gradient(180deg, #0E2245 0%, #0A1B36 100%);\n border-bottom: 1px solid #1E3A5F;\n }\n #print-preview-header h2 {\n margin: 0; font-size: 14px; letter-spacing: .12em;\n text-transform: uppercase; color: #E8F0FE;\n flex: 1;\n }\n #print-preview-header .pphint {\n font-size: 11px; color: #8BA7C7; letter-spacing: .06em;\n }\n #print-preview-body-wrap {\n flex: 1; overflow-y: auto;\n padding: 24px 16px 48px;\n display: flex; flex-direction: column; align-items: center; gap: 20px;\n background: #07111f;\n }\n #print-preview-body-wrap .sheet {\n background: #fff;\n color: #111;\n padding: 18mm 14mm;\n width: 210mm;\n min-height: 297mm;\n font-family: 'Times New Roman', Georgia, serif;\n font-size: 11pt;\n line-height: 1.4;\n box-shadow: 0 8px 32px rgba(0,0,0,.6);\n box-sizing: border-box;\n page-break-after: always;\n flex: 0 0 auto;\n }\n #print-preview-body-wrap .sheet.landscape {\n width: 297mm;\n min-height: 210mm;\n }\n #print-preview-body-wrap .sheet:last-child {\n page-break-after: auto;\n }\n #print-preview-footer {\n flex: 0 0 auto;\n display: flex; align-items: center; gap: 12px;\n padding: 14px 20px;\n background: linear-gradient(0deg, #0E2245 0%, #0A1B36 100%);\n border-top: 1px solid #1E3A5F;\n }\n #print-preview-footer .spacer { flex: 1; }\n #btn-pp-close {\n background: transparent; color: #8BA7C7;\n border: 1px solid #1E3A5F; border-radius: 6px;\n padding: 10px 16px; font-size: 13px; font-weight: 600;\n cursor: pointer; transition: color .15s, border-color .15s;\n display: inline-flex; align-items: center; gap: 8px;\n }\n #btn-pp-close:hover { color: #E8F0FE; border-color: #2A4A6F; }\n #btn-pp-print {\n background: linear-gradient(180deg, #1A6EBD 0%, #1558A0 100%);\n color: #fff; border: 1px solid #1558A0;\n border-radius: 6px; padding: 11px 22px;\n font-size: 13px; font-weight: 700;\n cursor: pointer; letter-spacing: .06em;\n display: inline-flex; align-items: center; gap: 8px;\n box-shadow: 0 2px 0 rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.15);\n transition: filter .15s;\n }\n #btn-pp-print:hover { filter: brightness(1.1); }\n #btn-pp-print:active { transform: translateY(1px); }\n @media print {\n #print-preview-overlay { display: none !important; }\n }\n</style>\n<div id=\"print-preview-overlay\">\n <div id=\"print-preview-header\">\n <h2>\ud83d\udda8\ufe0f Preview Cetak</h2>\n <span class=\"pphint\">Gulir ke bawah untuk melihat semua halaman</span>\n </div>\n <div id=\"print-preview-body-wrap\"></div>\n <div id=\"print-preview-footer\">\n <button id=\"btn-pp-close\">\u2715 Tutup</button>\n <div class=\"spacer\"></div>\n <button id=\"btn-pp-print\">\ud83d\udda8\ufe0f Cetak / Simpan PDF</button>\n </div>\n</div>\n\n<!-- Datalist sumber autocomplete -->\n<datalist id=\"dl-pegawai-nama\"></datalist>\n\n<script>\n/* ================================================================\n DATA SUMBER \u2014 Daftar Pegawai (hardcoded)\n ================================================================ */\nconst daftarPegawai = [\n { nama:\"MOKHAMAD REZA SULAEMAN, A.Md.Im., S.IP.\", nip:\"19800921 200112 1 001\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"PLT. KEPALA RUDENIM PONTIANAK\" },\n { nama:\"ITA ANGGRIANI, S.H.\", nip:\"19721013 199203 2 001\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"KEPALA SUB BAGIAN TATA USAHA\" },\n { nama:\"FAJAR DWI PRASETYO, A.Md.,S.Kom\", nip:\"19800913 200901 1 008\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"KASI KEAMANAN DAN KETERTIBAN\" },\n { nama:\"MUHAMMAD REZA, S.H.\", nip:\"19840124 200212 1 002\", pangkat:\"PENATA / (III/c)\", jabatan:\"KASI PERAWATAN DAN KESEHATAN\" },\n { nama:\"SYECH MUKHAMAR REZA KHARAMI, S.H.\", nip:\"19840831 200312 1 001\", pangkat:\"PENATA / (III/c)\", jabatan:\"KASI REG, ADMINISTRASI & PELAPORAN\" },\n { nama:\"FREDERICK A. RUHUPATTY, S.S.\", nip:\"19770401 200912 1 002\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"KEPALA URUSAN UMUM\" },\n { nama:\"SAULINA SIMORANGKIR, S.S.\", nip:\"19800803 200912 2 001\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"KASUBSI REGISTRASI\" },\n { nama:\"YAKUB SAPUTRA SIMANJUNTAK, S.P.\", nip:\"19830521 200604 1 001\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"KASUBSI KETERTIBAN\" },\n { nama:\"FITRIADI TULUS\", nip:\"19680516 199003 1 003\", pangkat:\"PENATA MUDA Tk. I / (III/b)\", jabatan:\"KASUBSI ADMINISTRASI DAN PELAPORAN\" },\n { nama:\"TARMUJI\", nip:\"19680727 199103 1 002\", pangkat:\"PENATA MUDA Tk. I / (III/b)\", jabatan:\"KASUBSI KESEHATAN\" },\n { nama:\"HARYANTO\", nip:\"19711104 199103 1 001\", pangkat:\"PENATA MUDA Tk. I / (III/b)\", jabatan:\"KASUBSI PERAWATAN\" },\n { nama:\"TEDY SUMPONO\", nip:\"19810907 200501 1 001\", pangkat:\"PENATA MUDA TK. I / (III/b)\", jabatan:\"KASUBSI KEAMANAN\" },\n { nama:\"HARDIAN RAMADHANA SESTYANTO, S.H.\", nip:\"19880425 201212 1 003\", pangkat:\"PENATA MUDA TK. I / (III/b)\", jabatan:\"KAUR KEUANGAN\" },\n { nama:\"GORGA ARISANDY SIREGAR, S.Kom.\", nip:\"19920703 201712 1 001\", pangkat:\"PENATA MUDA TK. I / (III/b)\", jabatan:\"KAUR KEPEGAWAIAN\" },\n { nama:\"IRWANSYAH, S.H.\", nip:\"19701017 199703 1 001\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"PENYUSUN LAPORAN DAN HASIL EVALUASI\" },\n { nama:\"MUHAMMAD IQBAL, A.Md.Im., S.H., M.Si.\", nip:\"19840930 200312 1 002\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"ANALIS PENGELOLAAN KEUANGAN APBN AHLI MUDA\" },\n { nama:\"AGUS DINARIA, S.H.\", nip:\"19870806 200604 2 001\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"PENYUSUN RENCANA KERJA & ANGGARAN\" },\n { nama:\"WIRA PRATAMA ELVIRA PUTRI, S.ST.\", nip:\"19871207 200903 2 003\", pangkat:\"PENATA TK. I / (III/d)\", jabatan:\"PENGOLAH DATA KESEHATAN\" },\n { nama:\"MUHAMMAD FAJAR WAHYUDI, A.Md.\", nip:\"19831005 200901 1 015\", pangkat:\"PENATA / (III/c)\", jabatan:\"PENGELOLA DATA KEAMANAN DAN KETERTIBAN\" },\n { nama:\"HILMAN FIRDAUS, S.Kom.\", nip:\"19870306 200901 1 002\", pangkat:\"PENATA / (III/c)\", jabatan:\"PENYUSUN LAPORAN DAN HASIL EVALUASI\" },\n { nama:\"M. IRWANTO RAHMAWAN\", nip:\"19760108 200112 1 001\", pangkat:\"PENATA MUDA Tk. I / (III/b)\", jabatan:\"PENGELOLA KEAMANAN DAN KETERTIBAN\" },\n { nama:\"YUSDIAWAN\", nip:\"19770530 200212 1 001\", pangkat:\"PENATA MUDA Tk. I / (III/b)\", jabatan:\"PENGOLAH DATA LAPORAN KEAMANAN DAN KETERTIBAN\" },\n { nama:\"GALIH NOFRIO NANDA, S.IP.\", nip:\"19841109 201712 1 001\", pangkat:\"PENATA MUDA Tk. I / (III/b)\", jabatan:\"ANALIS KEIMIGRASIAN PERTAMA\" },\n { nama:\"CHANDRA HADINATA, S.E.\", nip:\"19910117 201503 1 004\", pangkat:\"PENATA MUDA TK. I / (III/b)\", jabatan:\"PENGELOLA DATA\" },\n { nama:\"ARIANDI, S.H.\", nip:\"19880119 200801 1 001\", pangkat:\"PENATA MUDA / (III/a)\", jabatan:\"KOMANDAN JAGA PEMASYARAKATAN/KEIMIGRASIAN\" },\n { nama:\"AGUNG SUPRABOWO, A.Md.Kep.\", nip:\"19900214 201503 1 006\", pangkat:\"PENATA MUDA / (III/a)\", jabatan:\"KOMANDAN JAGA PEMASYARAKATAN/KEIMIGRASIAN\" },\n { nama:\"WAN RHOMA DONNY, A.Md.\", nip:\"19900420 201503 1 002\", pangkat:\"PENATA MUDA / (III/a)\", jabatan:\"PENGELOLA DATA KEPEGAWAIAN\" },\n { nama:\"MOCHAMAD HENDRO TRIYONO, A.Md.\", nip:\"19900816 201503 1 003\", pangkat:\"PENATA MUDA / (III/a)\", jabatan:\"PENGOLAH DATA LAPORAN\" },\n { nama:\"DEWA GEDE GUNA SUWASTAWA, A.Md.\", nip:\"19930822 201712 1 001\", pangkat:\"PENATA MUDA / (III/a)\", jabatan:\"PEMERIKSA KEIMIGRASIAN MAHIR\" },\n { nama:\"JANUARTI TRI NINGSIH, S.H.\", nip:\"19940115 201212 2 001\", pangkat:\"PENATA MUDA / (III/a)\", jabatan:\"PENGOLAH BAHAN EVALUASI DAN PELAPORAN\" }\n];\n\n/* ================================================================\n STATE\n ================================================================ */\nconst STORAGE_KEY = \"spd-imigrasi-2026\";\n\nconst defaultHeader = {\n judulKegiatan: \"\",\n nomorSPT: \"\",\n tanggalSPT: \"\",\n namaProgram: \"\",\n tanggalKegiatan: \"\",\n tujuanKota: \"\",\n namaPPK: \"\",\n nipPPK: \"\",\n jabatanPPK: \"\",\n namaBendahara: \"\",\n nipBendahara: \"\",\n kotaTerbit: \"Pontianak\",\n kodeAnggaran: \"\"\n};\n\nconst blankPegawai = () => ({\n nama:\"\", nip:\"\", golongan:\"III\", jabatan:\"\", tujuan:\"\",\n noSPD:\"\", tanggalSPD:\"\", tanggalPelaksanaan:\"\",\n tglMulai:\"\", tglSelesai:\"\",\n lamaHari:1, uangHarian:0,\n maskapaiPergi:\"\", tiketPergi:0,\n maskapaiPulang:\"\", tiketPulang:0,\n taksi:0, taksiDaerah:0, transportKab:0,\n penginapan:0, representasi:0,\n rekening:\"\",\n buktiTiket:\"Bukti Terlampir\",\n buktiUangHarian:\"Tidak Ada Bukti\",\n buktiPenginapan:\"Bukti Terlampir\",\n buktiTaksi:\"Bukti Terlampir\",\n buktiTaksiDaerah:\"Bukti Terlampir\",\n buktiTransportKab:\"Tidak Ada Bukti\",\n buktiRepresentasi:\"Tidak Ada Bukti\"\n});\n\nconst defaultDPR = {\n namaTtd:\"\", nipTtd:\"\", jabatanTtd:\"\", kotaTtd:\"Pontianak\",\n tanggalSurat:\"\", tanggalCetak:\"\", nomorSurat:\"\", namaPenerima:\"\",\n g1: {\n baris1:\"\", baris2:\"\", kategori:\"BELANJA BARANG NON OPERASIONAL LAINNYA\", kode:\"\",\n items:[ blankDPRItem() ]\n },\n g2: {\n baris1:\"\", baris2:\"\", kategori:\"BELANJA SEWA\", kode:\"\",\n items:[ blankDPRItem() ]\n }\n};\nfunction blankDPRItem(){\n return { uraian:\"\", qty1:1, sat1:\"Org\", qty2:1, sat2:\"Hari\", tarif:0, ket:\"\" };\n}\n\nconst state = {\n header: { ...defaultHeader },\n pegawai: [ blankPegawai() ],\n dpr: JSON.parse(JSON.stringify(defaultDPR))\n};\n\n/* ================================================================\n UTILITIES\n ================================================================ */\nfunction formatRupiah(n){\n n = Number(n)||0;\n return new Intl.NumberFormat('id-ID',{style:'currency',currency:'IDR',minimumFractionDigits:0}).format(n);\n}\n\nfunction num(v){ const n = Number(v); return isFinite(n)?n:0; }\n\nconst angkaSatuan = [\"\",\"SATU\",\"DUA\",\"TIGA\",\"EMPAT\",\"LIMA\",\"ENAM\",\"TUJUH\",\"DELAPAN\",\"SEMBILAN\",\"SEPULUH\",\"SEBELAS\"];\nfunction terbilangCore(n){\n n = Math.floor(Math.abs(n));\n if (n < 12) return angkaSatuan[n];\n if (n < 20) return terbilangCore(n-10) + \" BELAS\";\n if (n < 100) return terbilangCore(Math.floor(n/10)) + \" PULUH\" + (n%10? \" \"+terbilangCore(n%10):\"\");\n if (n < 200) return \"SERATUS\" + (n-100? \" \"+terbilangCore(n-100):\"\");\n if (n < 1000) return terbilangCore(Math.floor(n/100)) + \" RATUS\" + (n%100? \" \"+terbilangCore(n%100):\"\");\n if (n < 2000) return \"SERIBU\" + (n-1000? \" \"+terbilangCore(n-1000):\"\");\n if (n < 1000000) return terbilangCore(Math.floor(n/1000)) + \" RIBU\" + (n%1000? \" \"+terbilangCore(n%1000):\"\");\n if (n < 1000000000) return terbilangCore(Math.floor(n/1000000)) + \" JUTA\" + (n%1000000? \" \"+terbilangCore(n%1000000):\"\");\n if (n < 1000000000000) return terbilangCore(Math.floor(n/1000000000)) + \" MILIAR\" + (n%1000000000? \" \"+terbilangCore(n%1000000000):\"\");\n return terbilangCore(Math.floor(n/1000000000000)) + \" TRILIUN\" + (n%1000000000000? \" \"+terbilangCore(n%1000000000000):\"\");\n}\nfunction terbilang(n){\n n = Number(n)||0;\n if (n === 0) return \"NOL RUPIAH\";\n return (terbilangCore(n).replace(/\\s+/g,\" \").trim()) + \" RUPIAH\";\n}\n\nfunction lamaHariTeks(n){\n n = Math.max(0, Math.floor(Number(n)||0));\n const huruf = [\"nol\",\"satu\",\"dua\",\"tiga\",\"empat\",\"lima\",\"enam\",\"tujuh\",\"delapan\",\"sembilan\",\"sepuluh\",\"sebelas\",\"dua belas\",\"tiga belas\",\"empat belas\",\"lima belas\"];\n const t = huruf[n] || String(n);\n return `${n} (${t}) hari`;\n}\n\nconst bulanID = [\"Januari\",\"Februari\",\"Maret\",\"April\",\"Mei\",\"Juni\",\"Juli\",\"Agustus\",\"September\",\"Oktober\",\"November\",\"Desember\"];\nfunction formatTanggalID(tgl){\n if (!tgl) return \"\";\n // accept yyyy-mm-dd\n const m = String(tgl).match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!m) return tgl;\n const y = +m[1], mo = +m[2], d = +m[3];\n return `${d} ${bulanID[mo-1]} ${y}`;\n}\nfunction parseTgl(tgl){\n const m = String(tgl||\"\").match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!m) return null;\n return { y:+m[1], mo:+m[2], d:+m[3] };\n}\nfunction formatRangeID(mulai, selesai){\n const a = parseTgl(mulai), b = parseTgl(selesai);\n if (!a && !b) return \"\";\n if (a && !b) return formatTanggalID(mulai);\n if (!a && b) return formatTanggalID(selesai);\n if (a.y===b.y && a.mo===b.mo) return `${a.d} sd ${b.d} ${bulanID[a.mo-1]} ${a.y}`;\n if (a.y===b.y) return `${a.d} ${bulanID[a.mo-1]} sd ${b.d} ${bulanID[b.mo-1]} ${a.y}`;\n return `${formatTanggalID(mulai)} sd ${formatTanggalID(selesai)}`;\n}\nfunction hitungLamaHari(mulai, selesai){\n const a = parseTgl(mulai), b = parseTgl(selesai);\n if (!a || !b) return null;\n const da = new Date(a.y, a.mo-1, a.d), db = new Date(b.y, b.mo-1, b.d);\n const diff = Math.round((db - da) / 86400000) + 1;\n return diff > 0 ? diff : 1;\n}\nfunction tglPelaksanaanText(p){\n const r = formatRangeID(p.tglMulai, p.tglSelesai);\n return r || p.tanggalPelaksanaan || \"\";\n}\n\nfunction totalsForPegawai(p){\n const tiketPP = num(p.tiketPergi) + num(p.tiketPulang);\n const uangHarianTotal = num(p.uangHarian) * num(p.lamaHari);\n const grand = tiketPP + num(p.taksi) + num(p.taksiDaerah) + num(p.transportKab) + uangHarianTotal + num(p.penginapan) + num(p.representasi);\n return { tiketPP, uangHarianTotal, grand };\n}\n\nfunction totalsAll(){\n let tiket=0, taksi=0, taksiDaerah=0, transport=0, uh=0, penginapan=0, repr=0, grand=0;\n state.pegawai.forEach(p=>{\n const t = totalsForPegawai(p);\n tiket += t.tiketPP; taksi += num(p.taksi); taksiDaerah += num(p.taksiDaerah);\n transport += num(p.transportKab); uh += t.uangHarianTotal;\n penginapan += num(p.penginapan); repr += num(p.representasi); grand += t.grand;\n });\n return { tiket, taksi, taksiDaerah, transport, uh, penginapan, repr, grand };\n}\n\n/* ================================================================\n STORAGE\n ================================================================ */\nfunction save(){\n try{ localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); }catch(e){}\n}\nfunction load(){\n try{\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return false;\n const obj = JSON.parse(raw);\n if (obj && obj.header && Array.isArray(obj.pegawai)){\n state.header = { ...defaultHeader, ...obj.header };\n state.pegawai = obj.pegawai.length? obj.pegawai.map(p=>({...blankPegawai(),...p})) : [blankPegawai()];\n if (obj.dpr){\n const d = obj.dpr;\n state.dpr = {\n ...JSON.parse(JSON.stringify(defaultDPR)),\n ...d,\n g1: { ...defaultDPR.g1, ...(d.g1||{}), items: (d.g1&&d.g1.items&&d.g1.items.length)? d.g1.items.map(x=>({...blankDPRItem(),...x})) : [blankDPRItem()] },\n g2: { ...defaultDPR.g2, ...(d.g2||{}), items: (d.g2&&d.g2.items&&d.g2.items.length)? d.g2.items.map(x=>({...blankDPRItem(),...x})) : [blankDPRItem()] }\n };\n }\n return true;\n }\n }catch(e){}\n return false;\n}\n\n/* ================================================================\n TOAST + MODAL\n ================================================================ */\nconst toastEl = document.getElementById('toast');\nlet toastTimer;\nfunction toast(msg){\n toastEl.textContent = msg;\n toastEl.classList.add('show');\n clearTimeout(toastTimer);\n toastTimer = setTimeout(()=>toastEl.classList.remove('show'), 2200);\n}\n\nconst modalEl = document.getElementById('modal');\nfunction confirmModal({title, body, okText=\"Hapus\", cancelText=\"Batal\", danger=true}){\n return new Promise(resolve=>{\n document.getElementById('modal-title').textContent = title;\n document.getElementById('modal-body').textContent = body;\n const ok = document.getElementById('modal-ok'); const cancel = document.getElementById('modal-cancel');\n ok.textContent = okText; cancel.textContent = cancelText;\n ok.className = \"btn \" + (danger?\"btn-danger\":\"btn-primary\");\n modalEl.classList.add('show');\n const cleanup = (val)=>{ modalEl.classList.remove('show'); ok.onclick=null; cancel.onclick=null; resolve(val); };\n ok.onclick = ()=>cleanup(true);\n cancel.onclick = ()=>cleanup(false);\n modalEl.onclick = (e)=>{ if(e.target===modalEl) cleanup(false); };\n });\n}\n\n/* ================================================================\n TABS\n ================================================================ */\ndocument.querySelectorAll('.tab').forEach(t=>{\n t.addEventListener('click', ()=>{\n document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));\n document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));\n t.classList.add('active');\n document.getElementById('view-'+t.dataset.tab).classList.add('active');\n if (t.dataset.tab === 'dpr') renderDPR();\n });\n});\n\n/* ================================================================\n HEADER FORM BINDING\n ================================================================ */\nfunction bindHeader(){\n document.querySelectorAll('[data-h]').forEach(el=>{\n const key = el.dataset.h;\n el.value = state.header[key] ?? \"\";\n el.addEventListener('input', ()=>{\n state.header[key] = el.value;\n // If PPK name matches a daftarPegawai entry, autofill NIP & Jabatan\n if (key === 'namaPPK') autoFillPPK(el.value);\n if (key === 'namaBendahara') autoFillBendahara(el.value);\n save();\n // refresh tujuan default for empty pegawai rows\n if (key === 'tujuanKota'){ refreshPegawaiTujuanPlaceholders(); }\n });\n });\n}\nfunction autoFillPPK(name){\n const m = daftarPegawai.find(p=>p.nama===name);\n if (m){\n state.header.nipPPK = m.nip;\n state.header.jabatanPPK = m.jabatan;\n document.querySelector('[data-h=\"nipPPK\"]').value = m.nip;\n document.querySelector('[data-h=\"jabatanPPK\"]').value = m.jabatan;\n save();\n }\n}\nfunction autoFillBendahara(name){\n const m = daftarPegawai.find(p=>p.nama===name);\n if (m){\n state.header.nipBendahara = m.nip;\n document.querySelector('[data-h=\"nipBendahara\"]').value = m.nip;\n save();\n }\n}\nfunction refreshPegawaiTujuanPlaceholders(){\n document.querySelectorAll('[data-p-field=\"tujuan\"]').forEach(inp=>{\n inp.placeholder = state.header.tujuanKota || \"Tujuan kota\";\n });\n}\n\n/* ================================================================\n PEGAWAI LIST RENDER\n ================================================================ */\nconst peListEl = document.getElementById('pegawai-list');\nconst peCountEl = document.getElementById('pegawai-count');\n\nfunction pegawaiRowHTML(i){\n return `\n <div class=\"pegawai-card\" data-i=\"${i}\">\n <div class=\"pegawai-head\">\n <div class=\"badge\">${i+1}</div>\n <div class=\"name\" data-role=\"name-display\"><span class=\"placeholder\">Pegawai baru \u2014 pilih nama</span></div>\n <div class=\"actions\">\n <button class=\"btn btn-ghost btn-sm\" data-act=\"print-one\">\ud83d\udda8\ufe0f Cetak Rincian</button>\n <button class=\"btn btn-danger btn-sm\" data-act=\"remove\">\u2715 Hapus</button>\n </div>\n </div>\n <div class=\"pegawai-body\">\n <div class=\"grid g-6\">\n <div class=\"field col-3\">\n <label>Nama Lengkap <span class=\"req\">*</span></label>\n <input data-p-field=\"nama\" type=\"text\" list=\"dl-pegawai-nama\" placeholder=\"Ketik atau pilih dari daftar\" />\n </div>\n <div class=\"field col-2\">\n <label>NIP <span class=\"req\">*</span></label>\n <input data-p-field=\"nip\" type=\"text\" placeholder=\"auto-isi dari daftar\" />\n </div>\n <div class=\"field\">\n <label>Golongan</label>\n <select data-p-field=\"golongan\">\n <option value=\"II\">II</option>\n <option value=\"III\" selected>III</option>\n <option value=\"IV\">IV</option>\n </select>\n </div>\n\n <div class=\"field col-6\">\n <label>Jabatan</label>\n <input data-p-field=\"jabatan\" type=\"text\" />\n </div>\n\n <div class=\"field col-2\">\n <label>Tujuan</label>\n <input data-p-field=\"tujuan\" type=\"text\" />\n </div>\n <div class=\"field col-2\">\n <label>Nomor SPD <span class=\"req\">*</span></label>\n <input data-p-field=\"noSPD\" type=\"text\" placeholder=\"WIM.16.IMI.1-KU.03.01-\u2026\" />\n </div>\n <div class=\"field col-2\">\n <label>Tanggal SPD <span class=\"req\">*</span></label>\n <input data-p-field=\"tanggalSPD\" type=\"date\" />\n </div>\n\n <div class=\"field col-2\">\n <label>Tanggal Mulai</label>\n <input data-p-field=\"tglMulai\" type=\"date\" />\n </div>\n <div class=\"field col-2\">\n <label>Tanggal Selesai</label>\n <input data-p-field=\"tglSelesai\" type=\"date\" />\n </div>\n <div class=\"field\">\n <label>Lama Hari <span style=\"color:var(--text-3)\">(auto)</span></label>\n <input data-p-field=\"lamaHari\" type=\"number\" min=\"0\" value=\"1\" />\n </div>\n <div class=\"field col-2\">\n <label>Uang Harian (Rp/hari)</label>\n <input data-p-field=\"uangHarian\" type=\"number\" min=\"0\" step=\"1000\" placeholder=\"530000\" />\n </div>\n\n <div class=\"field col-3\">\n <label>Maskapai Pergi</label>\n <input data-p-field=\"maskapaiPergi\" type=\"text\" placeholder=\"Batik Air (PNK - CGK)\" />\n </div>\n <div class=\"field col-3\">\n <label>Harga Tiket Pergi (Rp)</label>\n <input data-p-field=\"tiketPergi\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n\n <div class=\"field col-3\">\n <label>Maskapai Pulang</label>\n <input data-p-field=\"maskapaiPulang\" type=\"text\" placeholder=\"Citilink (CGK - PNK)\" />\n </div>\n <div class=\"field col-3\">\n <label>Harga Tiket Pulang (Rp)</label>\n <input data-p-field=\"tiketPulang\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n\n <div class=\"field col-2\">\n <label>Taksi (Rp)</label>\n <input data-p-field=\"taksi\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n <div class=\"field col-2\">\n <label>Taksi Daerah (Rp)</label>\n <input data-p-field=\"taksiDaerah\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n <div class=\"field col-2\">\n <label>Transport Antar Kab/Kota (Rp)</label>\n <input data-p-field=\"transportKab\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n\n <div class=\"field col-2\">\n <label>Biaya Penginapan (Rp)</label>\n <input data-p-field=\"penginapan\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n <div class=\"field col-2\">\n <label>Uang Representasi (Rp)</label>\n <input data-p-field=\"representasi\" type=\"number\" min=\"0\" step=\"1000\" />\n </div>\n <div class=\"field col-2\">\n <label>No. Rekening</label>\n <input data-p-field=\"rekening\" type=\"text\" placeholder=\"BPG 042 \u2026\" />\n </div>\n </div>\n\n <div class=\"bukti-block\">\n <div class=\"bukti-title\">Status Bukti Pengeluaran</div>\n <div class=\"grid g-4\">\n <div class=\"field\">\n <label>Tiket Pesawat</label>\n <select data-p-field=\"buktiTiket\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n <div class=\"field\">\n <label>Uang Harian</label>\n <select data-p-field=\"buktiUangHarian\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n <div class=\"field\">\n <label>Penginapan</label>\n <select data-p-field=\"buktiPenginapan\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n <div class=\"field\">\n <label>Taksi</label>\n <select data-p-field=\"buktiTaksi\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n <div class=\"field\">\n <label>Taksi Daerah</label>\n <select data-p-field=\"buktiTaksiDaerah\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n <div class=\"field\">\n <label>Transport Antar Kab/Kota</label>\n <select data-p-field=\"buktiTransportKab\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n <div class=\"field\">\n <label>Uang Representasi</label>\n <select data-p-field=\"buktiRepresentasi\" data-bukti>\n <option>Bukti Terlampir</option><option>Tidak Ada Bukti</option>\n </select>\n </div>\n </div>\n </div>\n\n <div class=\"pegawai-totals\" data-role=\"totals\">\n <div class=\"stat\"><div class=\"k\">Total Tiket PP</div><div class=\"v\" data-t=\"tiket\">Rp 0</div></div>\n <div class=\"stat\"><div class=\"k\">Uang Harian \u00d7 Lama</div><div class=\"v\" data-t=\"uh\">Rp 0</div></div>\n <div class=\"stat\"><div class=\"k\">Lama Hari</div><div class=\"v\" data-t=\"hari\">0 hari</div></div>\n <div class=\"stat total\"><div class=\"k\">Total Per Pegawai</div><div class=\"v\" data-t=\"grand\">Rp 0</div></div>\n </div>\n </div>\n </div>\n `;\n}\n\nfunction bindPegawaiCard(card, i){\n const p = state.pegawai[i];\n // populate values\n card.querySelectorAll('[data-p-field]').forEach(inp=>{\n const key = inp.dataset.pField;\n inp.value = p[key] ?? \"\";\n });\n // tujuan placeholder/default\n refreshPegawaiTujuanPlaceholders();\n\n // input listeners\n card.querySelectorAll('[data-p-field]').forEach(inp=>{\n const key = inp.dataset.pField;\n inp.addEventListener('input', ()=>{\n let v = inp.value;\n if (inp.type === 'number') v = inp.value === \"\"? 0 : Number(inp.value);\n state.pegawai[i][key] = v;\n\n // autofill from daftarPegawai\n if (key === 'nama'){\n const m = daftarPegawai.find(x=>x.nama===v);\n if (m){\n state.pegawai[i].nip = m.nip;\n state.pegawai[i].jabatan = m.jabatan;\n // golongan dari pangkat (III/d -> III, etc.)\n const gMatch = m.pangkat.match(/\\(([^)]+)\\)/);\n if (gMatch){\n const g = gMatch[1].split('/')[0].trim();\n state.pegawai[i].golongan = g;\n card.querySelector('[data-p-field=\"golongan\"]').value = g;\n }\n card.querySelector('[data-p-field=\"nip\"]').value = m.nip;\n card.querySelector('[data-p-field=\"jabatan\"]').value = m.jabatan;\n }\n }\n // default tujuan from header\n if (key === 'tujuan' && v === \"\" && state.header.tujuanKota){\n // leave placeholder\n }\n // auto-hitung lama hari dari rentang tanggal\n if (key === 'tglMulai' || key === 'tglSelesai'){\n const lh = hitungLamaHari(state.pegawai[i].tglMulai, state.pegawai[i].tglSelesai);\n if (lh != null){\n state.pegawai[i].lamaHari = lh;\n const lhInput = card.querySelector('[data-p-field=\"lamaHari\"]');\n if (lhInput) lhInput.value = lh;\n }\n }\n updateTotals(card, i);\n updateNameDisplay(card, i);\n save();\n });\n });\n\n // action buttons\n card.querySelector('[data-act=\"remove\"]').addEventListener('click', async ()=>{\n if (state.pegawai.length === 1){\n toast(\"Minimal satu pegawai\");\n return;\n }\n const ok = await confirmModal({title:\"Hapus pegawai?\", body:`Pegawai #${i+1} akan dihapus.`, okText:\"Hapus\"});\n if (ok){\n state.pegawai.splice(i,1);\n save();\n renderAllPegawai();\n }\n });\n card.querySelector('[data-act=\"print-one\"]').addEventListener('click', ()=>printRincianOne(i));\n\n updateTotals(card, i);\n updateNameDisplay(card, i);\n}\n\nfunction updateTotals(card, i){\n const p = state.pegawai[i];\n const t = totalsForPegawai(p);\n card.querySelector('[data-t=\"tiket\"]').textContent = formatRupiah(t.tiketPP);\n card.querySelector('[data-t=\"uh\"]').textContent = formatRupiah(t.uangHarianTotal);\n card.querySelector('[data-t=\"hari\"]').textContent = lamaHariTeks(p.lamaHari);\n card.querySelector('[data-t=\"grand\"]').textContent= formatRupiah(t.grand);\n}\nfunction updateNameDisplay(card, i){\n const p = state.pegawai[i];\n const el = card.querySelector('[data-role=\"name-display\"]');\n if (p.nama){\n el.innerHTML = `${p.nama} <span style=\"color:var(--text-3); font-weight:500; margin-left:8px;\">${p.nip||\"\"}</span>`;\n } else {\n el.innerHTML = `<span class=\"placeholder\">Pegawai baru \u2014 pilih nama</span>`;\n }\n}\n\nfunction renderAllPegawai(){\n peListEl.innerHTML = state.pegawai.map((_,i)=>pegawaiRowHTML(i)).join(\"\");\n peListEl.querySelectorAll('.pegawai-card').forEach((card,i)=>bindPegawaiCard(card,i));\n peCountEl.textContent = state.pegawai.length;\n document.getElementById('btn-add-pegawai').disabled = state.pegawai.length >= 10;\n}\n\ndocument.getElementById('btn-add-pegawai').addEventListener('click', ()=>{\n if (state.pegawai.length >= 10){ toast(\"Maksimal 10 pegawai\"); return; }\n const p = blankPegawai();\n // inherit defaults from header\n if (state.header.tujuanKota) p.tujuan = state.header.tujuanKota;\n state.pegawai.push(p);\n save();\n renderAllPegawai();\n});\n\n/* Populate datalist */\nfunction fillDatalist(){\n const dl = document.getElementById('dl-pegawai-nama');\n dl.innerHTML = daftarPegawai.map(p=>`<option value=\"${p.nama}\">${p.jabatan}</option>`).join(\"\");\n}\n\n/* ================================================================\n ACTIONS BAR\n ================================================================ */\ndocument.getElementById('btn-save').addEventListener('click', ()=>{\n save();\n toast(\"\u2713 Data tersimpan\");\n});\ndocument.getElementById('btn-reset').addEventListener('click', async ()=>{\n const ok = await confirmModal({title:\"Reset form?\", body:\"Semua data form akan dikosongkan. Tindakan ini tidak bisa dibatalkan.\", okText:\"Reset\"});\n if (ok){\n state.header = { ...defaultHeader };\n state.pegawai = [ blankPegawai() ];\n state.dpr = JSON.parse(JSON.stringify(defaultDPR));\n save();\n bindHeader();\n renderAllPegawai();\n renderDPR();\n toast(\"Form direset\");\n }\n});\ndocument.getElementById('btn-loadexample').addEventListener('click', ()=>{\n Object.assign(state.header, contohData.header);\n state.pegawai = contohData.pegawai.map(p=>({...blankPegawai(),...p}));\n if (contohData.dpr) state.dpr = JSON.parse(JSON.stringify(contohData.dpr));\n save();\n bindHeader();\n renderAllPegawai();\n renderDPR();\n toast(\"Contoh data dimuat\");\n});\n\ndocument.getElementById('btn-print-nominatif').addEventListener('click', ()=>printNominatif());\ndocument.getElementById('btn-print-dpr').addEventListener('click', ()=>{\n // switch to DPR view + print both groups\n document.querySelector('[data-tab=\"dpr\"]').click();\n setTimeout(printDPRBoth, 80);\n});\ndocument.getElementById('btn-print-all').addEventListener('click', ()=>printAllRincian());\n\n/* DPR PIC tab buttons */\ndocument.getElementById('btn-dpr-save').addEventListener('click', ()=>{ save(); toast(\"\u2713 Data DPR tersimpan\"); });\ndocument.getElementById('btn-dpr-print1').addEventListener('click', ()=>printDPRGroup('g1'));\ndocument.getElementById('btn-dpr-print2').addEventListener('click', ()=>printDPRGroup('g2'));\ndocument.getElementById('btn-dpr-printall').addEventListener('click', printDPRBoth);\ndocument.getElementById('btn-dpr-fromppk').addEventListener('click', ()=>{\n state.dpr.namaTtd = state.header.namaPPK;\n state.dpr.nipTtd = state.header.nipPPK;\n state.dpr.jabatanTtd = state.header.jabatanPPK;\n state.dpr.kotaTtd = state.header.kotaTerbit || \"Pontianak\";\n state.dpr.tanggalSurat = state.header.tanggalSPT;\n state.dpr.nomorSurat = state.header.nomorSPT;\n if (!state.dpr.namaPenerima) state.dpr.namaPenerima = state.header.namaPPK;\n save(); bindDPRHeader(); toast(\"Data PPK disalin\");\n});\nconst dprPreviewShell = document.getElementById('dpr-preview-shell');\ndocument.getElementById('btn-dpr-preview').addEventListener('click', ()=>{\n dprPreviewShell.style.display = dprPreviewShell.style.display==='none'? 'block':'none';\n if (dprPreviewShell.style.display==='block') renderDPRPreview();\n});\ndocument.getElementById('btn-dpr-preview-close').addEventListener('click', ()=>{ dprPreviewShell.style.display='none'; });\n\n/* ================================================================\n VALIDATION (mark required empty fields red)\n ================================================================ */\nfunction validateBeforePrint(){\n let bad = 0;\n document.querySelectorAll('.field').forEach(f=>f.classList.remove('invalid'));\n // header required\n const reqHeader = ['judulKegiatan','nomorSPT','namaPPK','nipPPK'];\n reqHeader.forEach(k=>{\n if (!state.header[k]){\n const el = document.querySelector(`[data-h=\"${k}\"]`);\n if (el) el.closest('.field').classList.add('invalid');\n bad++;\n }\n });\n // pegawai required\n state.pegawai.forEach((p,i)=>{\n ['nama','nip','noSPD','tanggalSPD'].forEach(k=>{\n if (!p[k]){\n const card = document.querySelector(`.pegawai-card[data-i=\"${i}\"]`);\n if (card){\n const el = card.querySelector(`[data-p-field=\"${k}\"]`);\n if (el) el.closest('.field').classList.add('invalid');\n }\n bad++;\n }\n });\n });\n if (bad>0){\n toast(`\u2717 ${bad} field wajib belum lengkap`);\n return false;\n }\n return true;\n}\n\n/* ================================================================\n PRINT \u2014 KOP & SHEETS\n ================================================================ */\nfunction getLogoUrl(){\n const crest = document.querySelector('.topbar .crest');\n return crest && crest.src ? crest.src : '';\n}\n\nfunction kopHTML(){\n const logoSrc = getLogoUrl();\n return `\n <div class=\"kop\">\n ${logoSrc ? `<img class=\"kop-logo\" src=\"${logoSrc}\" alt=\"Logo\" />` : `<div class=\"kop-pad\"></div>`}\n <div class=\"kop-text\">\n <div class=\"l1\">KEMENTERIAN IMIGRASI DAN PEMASYARAKATAN REPUBLIK INDONESIA</div>\n <div class=\"l2\">DIREKTORAT JENDERAL IMIGRASI</div>\n <div class=\"l3\">KANTOR WILAYAH KALIMANTAN BARAT</div>\n <div class=\"l4\">KANTOR IMIGRASI KELAS I TPI PONTIANAK</div>\n <div class=\"l5\">Jalan Letjen. Sutoyo No. 122 Pontianak 78121</div>\n <div class=\"l6\">Laman: www.pontianak.imigrasi.go.id | Surel: keuangan.kanimptk@gmail.com</div>\n </div>\n <div class=\"kop-pad\"></div>\n </div>\n `;\n}\n\nfunction sheetNominatifHTML(){\n const h = state.header;\n const tot = totalsAll();\n const rows = state.pegawai.map((p,i)=>{\n const t = totalsForPegawai(p);\n return `\n <tr>\n <td class=\"ctr\">${i+1}</td>\n <td><b>${p.nama||\"\u2014\"}</b><br/>NIP. ${p.nip||\"\u2014\"}</td>\n <td class=\"ctr\">${p.golongan||\"\"}</td>\n <td>${p.jabatan||\"\"}</td>\n <td>${p.tujuan||h.tujuanKota||\"\"}</td>\n <td>${tglPelaksanaanText(p)}</td>\n <td class=\"num\">${t.tiketPP? formatRupiah(t.tiketPP):\"-\"}</td>\n <td class=\"num\">${num(p.taksi)? formatRupiah(p.taksi):\"-\"}</td>\n <td class=\"num\">${num(p.taksiDaerah)? formatRupiah(p.taksiDaerah):\"-\"}</td>\n <td class=\"num\">${t.uangHarianTotal? formatRupiah(t.uangHarianTotal):\"-\"}</td>\n <td class=\"num\">${num(p.penginapan)? formatRupiah(p.penginapan):\"-\"}</td>\n <td class=\"num\"><b>${formatRupiah(t.grand)}</b></td>\n <td style=\"font-size:9pt\">${p.rekening||\"\"}</td>\n <td class=\"ttd-cell\">${i+1}.</td>\n </tr>\n `;\n }).join(\"\");\n\n return `\n <div class=\"sheet landscape\">\n ${kopHTML()}\n <div class=\"doc-title\">DAFTAR NOMINATIF PEMBAYARAN PERJALANAN DINAS</div>\n <div class=\"doc-subtitle\">\n <div>${h.judulKegiatan||\"\u2014\"}</div>\n <div style=\"margin-top:4px\">Sesuai Surat Perintah Tugas Nomor: <b>${h.nomorSPT||\"\u2014\"}</b> Tanggal: <b>${formatTanggalID(h.tanggalSPT)||\"\u2014\"}</b></div>\n </div>\n\n <table class=\"doc-table\">\n <thead>\n <tr>\n <th rowspan=\"2\" style=\"width:3%\">No</th>\n <th rowspan=\"2\" style=\"width:13%\">Nama / NIP</th>\n <th rowspan=\"2\" style=\"width:4%\">Gol</th>\n <th rowspan=\"2\" style=\"width:11%\">Jabatan</th>\n <th rowspan=\"2\" style=\"width:7%\">Tujuan</th>\n <th rowspan=\"2\" style=\"width:7%\">Tgl Pelaksanaan</th>\n <th colspan=\"5\">Biaya Perjalanan (Rp)</th>\n <th rowspan=\"2\" style=\"width:9%\">Jumlah</th>\n <th rowspan=\"2\" style=\"width:8%\">Rekening</th>\n <th rowspan=\"2\" style=\"width:10%\">TTD</th>\n </tr>\n <tr>\n <th>Tiket PP</th>\n <th>Taksi</th>\n <th>Taksi Daerah</th>\n <th>Uang Harian</th>\n <th>Penginapan</th>\n </tr>\n </thead>\n <tbody>\n ${rows}\n <tr>\n <td colspan=\"6\" class=\"ctr\"><b>JUMLAH</b></td>\n <td class=\"num\"><b>${formatRupiah(tot.tiket)}</b></td>\n <td class=\"num\"><b>${formatRupiah(tot.taksi)}</b></td>\n <td class=\"num\"><b>${formatRupiah(tot.taksiDaerah)}</b></td>\n <td class=\"num\"><b>${formatRupiah(tot.uh)}</b></td>\n <td class=\"num\"><b>${formatRupiah(tot.penginapan)}</b></td>\n <td class=\"num\"><b>${formatRupiah(tot.grand)}</b></td>\n <td colspan=\"2\"></td>\n </tr>\n </tbody>\n </table>\n\n <div style=\"margin-top:8px; font-size:10pt;\"><i>Terbilang: ${terbilang(tot.grand)}</i></div>\n\n <div class=\"ttd-grid\">\n <div class=\"ttd\">\n <div>Mengetahui,</div>\n <div class=\"role\">Pejabat Pembuat Komitmen,</div>\n <div class=\"name\">${h.namaPPK||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${h.nipPPK||\"\u2014\"}</div>\n </div>\n <div class=\"ttd\">\n <div>${h.kotaTerbit||\"Pontianak\"}, ${formatTanggalID(h.tanggalSPT)||\"\u2014\"}</div>\n <div class=\"role\">Bendahara Pengeluaran,</div>\n <div class=\"name\">${h.namaBendahara||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${h.nipBendahara||\"\u2014\"}</div>\n </div>\n </div>\n </div>\n `;\n}\n\nfunction dprGroupTotal(g){\n return (g.items||[]).reduce((s,it)=>s + (num(it.qty1)||1)*(num(it.qty2)||1)*num(it.tarif), 0);\n}\nfunction dprItemJumlah(it){\n return (num(it.qty1)||1)*(num(it.qty2)||1)*num(it.tarif);\n}\n\nfunction sheetDPRGroupHTML(groupKey){\n const d = state.dpr;\n const g = d[groupKey];\n const total = dprGroupTotal(g);\n const tglSurat = formatTanggalID(d.tanggalSurat) || \"\u2014\";\n const tglCetak = formatTanggalID(d.tanggalCetak || d.tanggalSurat) || \"\u2014\";\n const noLabel = (i)=> String(i+1).padStart(2,\"0\");\n\n const rows = (g.items||[]).map((it,i)=>{\n const j = dprItemJumlah(it);\n const calc = `${num(it.qty1)||1} ${it.sat1||\"Org\"} \u00d7 ${num(it.qty2)||1} ${it.sat2||\"Hari\"} \u00d7 ${formatRupiah(it.tarif)}`;\n return `\n <tr>\n <td class=\"ctr\">${noLabel(i)}</td>\n <td>\n ${it.uraian||\"\u2014\"}<br/>\n <span style=\"color:#555; font-size:9pt\">${calc}</span>\n </td>\n <td class=\"num\">${formatRupiah(j)}</td>\n <td style=\"font-size:9pt\">${it.ket||\"\"}</td>\n </tr>\n `;\n }).join(\"\");\n\n const subtitleLines = [g.baris1, g.baris2, g.kategori].filter(Boolean)\n .map(x=>`<div>${x}</div>`).join(\"\");\n\n return `\n <div class=\"sheet\">\n ${kopHTML()}\n <div class=\"doc-title\">DAFTAR PENGELUARAN RIIL</div>\n <div class=\"doc-subtitle\">\n ${subtitleLines}\n ${g.kode?`<div>${g.kode}</div>`:\"\"}\n </div>\n\n <div style=\"margin-top:14px\">Yang bertandatangan dibawah ini :</div>\n <table style=\"margin-top:6px; font-size:11pt;\">\n <tr><td style=\"width:90px\">Nama</td><td>:</td><td><b>${d.namaTtd||\"\u2014\"}</b></td></tr>\n <tr><td>NIP</td><td>:</td><td>${d.nipTtd||\"\u2014\"}</td></tr>\n <tr><td>Jabatan</td><td>:</td><td>${d.jabatanTtd||\"\u2014\"}</td></tr>\n </table>\n\n <div style=\"margin-top:10px\">\n Berdasarkan Surat Perintah Tanggal <b>${tglSurat}</b> Nomor: <b>${d.nomorSurat||\"\u2014\"}</b>,\n dengan ini menyatakan dengan sesungguhnya bahwa:\n </div>\n\n <ol style=\"padding-left:20px; margin-top:8px;\">\n <li>Biaya ${(g.kategori||\"belanja\").toLowerCase()} yang dikeluarkan, meliputi :</li>\n </ol>\n\n <table class=\"doc-table\" style=\"margin-top:6px;\">\n <thead>\n <tr>\n <th style=\"width:5%\">No</th>\n <th>Rincian Biaya</th>\n <th style=\"width:22%\">Jumlah</th>\n <th style=\"width:22%\">Keterangan</th>\n </tr>\n </thead>\n <tbody>\n ${rows}\n <tr>\n <td colspan=\"2\" class=\"ctr\"><b>JUMLAH</b></td>\n <td class=\"num\"><b>${formatRupiah(total)}</b></td>\n <td></td>\n </tr>\n <tr>\n <td colspan=\"4\"><i class=\"terbilang\">Terbilang: ${terbilang(total)}</i></td>\n </tr>\n </tbody>\n </table>\n\n <ol start=\"2\" style=\"padding-left:20px; margin-top:10px;\">\n <li>Jumlah uang tersebut pada angka 1 di atas benar-benar dikeluarkan untuk pelaksanaan kegiatan dimaksud, dan apabila di kemudian hari terdapat kelebihan atas pembayaran, kami bersedia untuk menyetorkan kelebihan tersebut ke Kas Negara.</li>\n </ol>\n\n <div style=\"margin-top:8px\">\n Demikian pernyataan ini kami buat dengan sebenarnya, untuk dipergunakan sebagaimana mestinya.\n </div>\n\n <div class=\"ttd-grid\">\n <div class=\"ttd\">\n <div>Mengetahui,</div>\n <div class=\"role\">Pejabat Pembuat Komitmen,</div>\n <div class=\"name\">${d.namaTtd||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${d.nipTtd||\"\u2014\"}</div>\n </div>\n <div class=\"ttd\">\n <div>${d.kotaTtd||\"Pontianak\"}, ${tglCetak}</div>\n <div class=\"role\">Penerima,</div>\n <div class=\"name\">${d.namaPenerima||d.namaTtd||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${d.nipTtd||\"\u2014\"}</div>\n </div>\n </div>\n </div>\n `;\n}\n\nfunction sheetRincianHTML(idx){\n const h = state.header;\n const p = state.pegawai[idx];\n const t = totalsForPegawai(p);\n const grand = t.grand;\n const tanggalCetak = h.tanggalSPT? formatTanggalID(h.tanggalSPT) : \"\";\n const pelaksanaan = tglPelaksanaanText(p);\n\n // Bangun baris-baris rincian biaya (1 baris = 1 <tr> agar kolom presisi)\n const rincianLines = [];\n rincianLines.push({ desc:`<b>${h.namaProgram||\"\u2014\"}</b><br/>An. <b>${p.nama||\"\u2014\"}</b>${pelaksanaan?`<br/>${pelaksanaan}`:\"\"}`, amt:\"\", ket:\"\" });\n rincianLines.push({ desc:`<i>Belanja Perjalanan Biasa</i>`, amt:\"\", ket:\"\" });\n rincianLines.push({ desc:`- Tiket Pesawat`, amt: formatRupiah(t.tiketPP), ket:\"\" });\n rincianLines.push({ desc:`<span style=\"display:inline-block;padding-left:18px\">${p.maskapaiPergi||\"\u2014\"}</span>`, amt:`<span style=\"color:#666\">${formatRupiah(p.tiketPergi)}</span>`, ket: p.buktiTiket||\"Bukti Terlampir\" });\n rincianLines.push({ desc:`<span style=\"display:inline-block;padding-left:18px\">${p.maskapaiPulang||\"\u2014\"}</span>`, amt:`<span style=\"color:#666\">${formatRupiah(p.tiketPulang)}</span>`, ket: p.buktiTiket||\"Bukti Terlampir\" });\n rincianLines.push({ desc:`- Uang Harian ${num(p.lamaHari)} \u00d7 ${formatRupiah(p.uangHarian)}`, amt: formatRupiah(t.uangHarianTotal), ket: p.buktiUangHarian||\"Tidak Ada Bukti\" });\n if (num(p.penginapan)) rincianLines.push({ desc:`- Penginapan`, amt: formatRupiah(p.penginapan), ket: p.buktiPenginapan||\"Bukti Terlampir\" });\n if (num(p.taksi)) rincianLines.push({ desc:`- Taksi`, amt: formatRupiah(p.taksi), ket: p.buktiTaksi||\"Bukti Terlampir\" });\n if (num(p.taksiDaerah)) rincianLines.push({ desc:`- Taksi Daerah`, amt: formatRupiah(p.taksiDaerah), ket: p.buktiTaksiDaerah||\"Bukti Terlampir\" });\n if (num(p.transportKab))rincianLines.push({ desc:`- Transport Antar Kab/Kota`, amt: formatRupiah(p.transportKab), ket: p.buktiTransportKab||\"Tidak Ada Bukti\" });\n if (num(p.representasi))rincianLines.push({ desc:`- Uang Representasi`, amt: formatRupiah(p.representasi), ket: p.buktiRepresentasi||\"Tidak Ada Bukti\" });\n const rincianRows = rincianLines.map((r,ri)=>{\n const noCell = ri===0? `<td class=\"ctr\" rowspan=\"${rincianLines.length}\">1</td>` : \"\";\n return `<tr>${noCell}<td>${r.desc}</td><td class=\"num\">${r.amt}</td><td style=\"font-size:10pt\" class=\"ctr\">${r.ket}</td></tr>`;\n }).join(\"\");\n\n return `\n <div class=\"sheet\">\n ${kopHTML()}\n <div class=\"doc-title\">RINCIAN BIAYA PERJALANAN DINAS</div>\n <table style=\"margin: 6px auto 10px; font-size:11pt\">\n <tr><td>Lampiran SPD Nomor</td><td>:</td><td><b>${p.noSPD||\"\u2014\"}</b></td></tr>\n <tr><td>Tanggal</td><td>:</td><td>${formatTanggalID(p.tanggalSPD)||\"\u2014\"}</td></tr>\n </table>\n\n <table class=\"doc-table\">\n <thead>\n <tr>\n <th style=\"width:5%\">No</th>\n <th>Perincian Biaya</th>\n <th style=\"width:20%\">Jumlah (Rp)</th>\n <th style=\"width:18%\">Keterangan</th>\n </tr>\n </thead>\n <tbody>\n ${rincianRows}\n <tr>\n <td colspan=\"2\" class=\"ctr\"><b>JUMLAH</b></td>\n <td class=\"num\"><b>${formatRupiah(grand)}</b></td>\n <td></td>\n </tr>\n <tr>\n <td colspan=\"4\"><i class=\"terbilang\">Terbilang: ${terbilang(grand)}</i></td>\n </tr>\n </tbody>\n </table>\n\n <div class=\"ttd-grid\">\n <div class=\"ttd\">\n <div>Telah Dibayar Sejumlah</div>\n <div><b>${formatRupiah(grand)}</b></div>\n <div style=\"margin-top:8px\" class=\"role\">Bendahara Pengeluaran,</div>\n <div class=\"name\">${h.namaBendahara||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${h.nipBendahara||\"\u2014\"}</div>\n </div>\n <div class=\"ttd\">\n <div>${h.kotaTerbit||\"Pontianak\"}, ${tanggalCetak||\"\u2014\"}</div>\n <div>Telah Menerima Jumlah Uang Sebesar</div>\n <div><b>${formatRupiah(grand)}</b></div>\n <div style=\"margin-top:8px\" class=\"role\">Yang Menerima,</div>\n <div class=\"name\">${p.nama||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${p.nip||\"\u2014\"}</div>\n </div>\n </div>\n\n <div style=\"margin-top:20px; border-top:1px solid #000; padding-top:10px;\">\n <div style=\"text-align:center; font-weight:700; text-decoration:underline; margin-bottom:8px;\">PERHITUNGAN SPD RAMPUNG</div>\n <table style=\"margin:0 auto; font-size:11pt\">\n <tr><td style=\"width:200px\">Ditetapkan sejumlah</td><td>:</td><td><b>${formatRupiah(grand)}</b></td></tr>\n <tr><td>Yang telah dibayar semula</td><td>:</td><td>${formatRupiah(grand)}</td></tr>\n <tr><td>Sisa Kurang / Lebih dibayar</td><td>:</td><td>${formatRupiah(0)}</td></tr>\n </table>\n <div style=\"text-align:right; margin-top:24px\">\n <div class=\"role\">Pejabat Pembuat Komitmen,</div>\n <div style=\"height:60px\"></div>\n <div class=\"name\" style=\"display:inline-block\">${h.namaPPK||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${h.nipPPK||\"\u2014\"}</div>\n </div>\n </div>\n </div>\n\n <div class=\"sheet\">\n ${kopHTML()}\n <div class=\"doc-title\">DAFTAR PENGELUARAN RILL</div>\n\n <div style=\"margin-top:10px\">Yang bertandatangan dibawah ini:</div>\n <table style=\"margin-top:6px; font-size:11pt;\">\n <tr><td style=\"width:90px\">Nama</td><td>:</td><td><b>${p.nama||\"\u2014\"}</b></td></tr>\n <tr><td>NIP</td><td>:</td><td>${p.nip||\"\u2014\"}</td></tr>\n <tr><td>Jabatan</td><td>:</td><td>${p.jabatan||\"\u2014\"}</td></tr>\n </table>\n\n <div style=\"margin-top:10px\">\n Berdasarkan Surat Perjalanan Dinas (SPD) Nomor: <b>${p.noSPD||\"\u2014\"}</b> tanggal <b>${formatTanggalID(p.tanggalSPD)||\"\u2014\"}</b>, dengan ini menyatakan dengan sesungguhnya bahwa:\n </div>\n\n <ol style=\"padding-left:20px; margin-top:8px;\">\n <li>Biaya transportasi pegawai dan/atau biaya penginapan di bawah ini yang tidak dapat diperoleh bukti-bukti pengeluarannya, meliputi:</li>\n </ol>\n\n <table class=\"doc-table\" style=\"margin-top:6px\">\n <thead>\n <tr>\n <th style=\"width:5%\">No</th>\n <th>Uraian</th>\n <th style=\"width:25%\">Jumlah</th>\n </tr>\n </thead>\n <tbody>\n <tr><td class=\"ctr\">1</td><td>Taksi</td><td class=\"num\">${formatRupiah(p.taksi)}</td></tr>\n <tr><td class=\"ctr\">2</td><td>Taksi Daerah</td><td class=\"num\">${formatRupiah(p.taksiDaerah)}</td></tr>\n <tr><td class=\"ctr\">3</td><td>Transport Antar Kab/Kota</td><td class=\"num\">${formatRupiah(p.transportKab)}</td></tr>\n <tr>\n <td colspan=\"2\" class=\"ctr\"><b>JUMLAH</b></td>\n <td class=\"num\"><b>${formatRupiah(num(p.taksi)+num(p.taksiDaerah)+num(p.transportKab))}</b></td>\n </tr>\n <tr><td colspan=\"3\"><i class=\"terbilang\">Terbilang: ${terbilang(num(p.taksi)+num(p.taksiDaerah)+num(p.transportKab))}</i></td></tr>\n </tbody>\n </table>\n\n <ol start=\"2\" style=\"padding-left:20px; margin-top:10px\">\n <li>Jumlah uang tersebut pada angka 1 di atas benar-benar dikeluarkan untuk pelaksanaan perjalanan dinas dimaksud, dan apabila dikemudian hari terdapat kelebihan atas pembayaran, kami bersedia menyetorkan kelebihan tersebut ke Kas Negara.</li>\n </ol>\n\n <div style=\"margin-top:8px\">\n Demikian pernyataan ini dibuat dengan sebenarnya, untuk dipergunakan sebagaimana mestinya.\n </div>\n\n <div class=\"ttd-grid\">\n <div class=\"ttd\">\n <div>Mengetahui / Menyetujui,</div>\n <div class=\"role\">Pejabat Pembuat Komitmen,</div>\n <div class=\"name\">${h.namaPPK||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${h.nipPPK||\"\u2014\"}</div>\n </div>\n <div class=\"ttd\">\n <div>${h.kotaTerbit||\"Pontianak\"}, ${tanggalCetak||\"\u2014\"}</div>\n <div class=\"role\">Pelaksana Perjalanan Dinas,</div>\n <div class=\"name\">${p.nama||\"\u2014\"}</div>\n <div class=\"nip\">NIP. ${p.nip||\"\u2014\"}</div>\n </div>\n </div>\n </div>\n `;\n}\n\n/* ================================================================\n PRINT TRIGGERS\n ================================================================ */\n\n// Detect mobile/tablet (touch devices or narrow viewport)\nfunction isMobile(){\n return ('ontouchstart' in window || navigator.maxTouchPoints > 0) ||\n window.matchMedia('(max-width: 900px)').matches;\n}\n\n// Print preview modal elements\nconst ppOverlay = document.getElementById('print-preview-overlay');\nconst ppBodyWrap = document.getElementById('print-preview-body-wrap');\nconst btnPPClose = document.getElementById('btn-pp-close');\nconst btnPPPrint = document.getElementById('btn-pp-print');\n\nbtnPPClose.addEventListener('click', ()=>{ ppOverlay.classList.remove('open'); });\n\nbtnPPPrint.addEventListener('click', ()=>{\n // Close the preview overlay first so it doesn't appear on print output\n ppOverlay.classList.remove('open');\n // window.print() is called synchronously inside user gesture \u2014 works on mobile\n window.print();\n});\n\n// Tap outside the preview body to close\nppOverlay.addEventListener('click', (e)=>{\n if (e.target === ppOverlay) ppOverlay.classList.remove('open');\n});\n\nfunction injectPrintAndPrint(html){\n // Always inject into print-root for @media print rendering\n const root = document.getElementById('print-root');\n root.innerHTML = html;\n\n if (isMobile()) {\n // On mobile: show the elegant print preview modal instead of blind print()\n // This lets the user see the document and tap \"Cetak\" to trigger print synchronously\n ppBodyWrap.innerHTML = html;\n ppOverlay.classList.add('open');\n ppBodyWrap.scrollTop = 0;\n } else {\n // On desktop: use the original async trigger (safe because user gesture is not lost)\n setTimeout(()=>window.print(), 80);\n }\n}\n\nfunction printNominatif(){\n if (!validateBeforePrint()) return;\n injectPrintAndPrint(sheetNominatifHTML());\n}\nfunction validateDPR(){\n let bad = 0;\n document.querySelectorAll('#view-dpr .field').forEach(f=>f.classList.remove('invalid'));\n if (!state.dpr.namaTtd){\n const el = document.querySelector('[data-d=\"namaTtd\"]');\n if (el) el.closest('.field').classList.add('invalid');\n bad++;\n }\n if (bad>0){ toast(\"\u2717 Nama penandatangan wajib diisi\"); return false; }\n return true;\n}\nfunction printDPRGroup(groupKey){\n if (!validateDPR()) return;\n injectPrintAndPrint(sheetDPRGroupHTML(groupKey));\n}\nfunction printDPRBoth(){\n if (!validateDPR()) return;\n injectPrintAndPrint(sheetDPRGroupHTML('g1') + sheetDPRGroupHTML('g2'));\n}\nfunction printRincianOne(i){\n if (!validateBeforePrint()) return;\n injectPrintAndPrint(sheetRincianHTML(i));\n}\nfunction printAllRincian(){\n if (!validateBeforePrint()) return;\n const all = state.pegawai.map((_,i)=>sheetRincianHTML(i)).join(\"\");\n injectPrintAndPrint(all);\n}\n\nwindow.addEventListener('afterprint', ()=>{\n // clear print root so it doesn't bloat DOM\n document.getElementById('print-root').innerHTML = \"\";\n // Also clear preview body to free memory\n ppBodyWrap.innerHTML = \"\";\n});\n\n/* ================================================================\n DPR PIC FORM RENDER / BIND / PREVIEW\n ================================================================ */\nfunction bindDPRHeader(){\n document.querySelectorAll('[data-d]').forEach(el=>{\n const key = el.dataset.d;\n el.value = state.dpr[key] ?? \"\";\n el.oninput = ()=>{\n state.dpr[key] = el.value;\n if (key === 'namaTtd'){\n const m = daftarPegawai.find(x=>x.nama===el.value);\n if (m){\n state.dpr.nipTtd = m.nip;\n state.dpr.jabatanTtd = m.jabatan;\n document.querySelector('[data-d=\"nipTtd\"]').value = m.nip;\n document.querySelector('[data-d=\"jabatanTtd\"]').value = m.jabatan;\n }\n }\n save();\n };\n });\n // group header fields\n document.querySelectorAll('[data-g]').forEach(el=>{\n const gk = el.dataset.g, key = el.dataset.gk;\n el.value = state.dpr[gk][key] ?? \"\";\n el.oninput = ()=>{ state.dpr[gk][key] = el.value; save(); };\n });\n}\n\nfunction dprItemHTML(gk, it, i){\n return `\n <div class=\"dpr-item\" data-di=\"${gk}-${i}\">\n <div class=\"dpr-item-head\">\n <div class=\"ix\">${String(i+1).padStart(2,'0')}</div>\n <div style=\"font-size:12px; color:var(--text-2); letter-spacing:.08em;\">RINCIAN BIAYA</div>\n <div class=\"calc\" data-dcalc>Rp 0</div>\n <button class=\"btn btn-danger btn-sm\" data-drem=\"${gk}-${i}\">\u2715</button>\n </div>\n <div class=\"field\" style=\"margin-bottom:10px;\">\n <label>Uraian</label>\n <input data-df=\"uraian\" type=\"text\" placeholder=\"Biaya Uang Harian untuk 5 (lima) orang petugas selama 3 (tiga) hari\" />\n </div>\n <div class=\"dpr-calc-row\">\n <div class=\"field\"><label>Qty 1</label><input data-df=\"qty1\" type=\"number\" min=\"0\" step=\"1\" /></div>\n <div class=\"field\"><label>Satuan</label><input data-df=\"sat1\" type=\"text\" placeholder=\"Org\" /></div>\n <div class=\"field\"><label>Qty 2</label><input data-df=\"qty2\" type=\"number\" min=\"0\" step=\"1\" /></div>\n <div class=\"field\"><label>Satuan</label><input data-df=\"sat2\" type=\"text\" placeholder=\"Hari\" /></div>\n <div class=\"field\"><label>Tarif (Rp)</label><input data-df=\"tarif\" type=\"number\" min=\"0\" step=\"1000\" /></div>\n <div class=\"field\"><label>Keterangan</label><input data-df=\"ket\" type=\"text\" /></div>\n </div>\n </div>\n `;\n}\n\nfunction renderDPRItems(gk){\n const wrap = document.querySelector(`[data-items=\"${gk}\"]`);\n const items = state.dpr[gk].items;\n wrap.innerHTML = items.map((it,i)=>dprItemHTML(gk,it,i)).join(\"\");\n wrap.querySelectorAll('.dpr-item').forEach((card,i)=>{\n const it = items[i];\n card.querySelectorAll('[data-df]').forEach(inp=>{\n const key = inp.dataset.df;\n inp.value = it[key] ?? \"\";\n inp.addEventListener('input', ()=>{\n let v = inp.value;\n if (inp.type==='number') v = inp.value===\"\"?0:Number(inp.value);\n it[key] = v;\n card.querySelector('[data-dcalc]').textContent = formatRupiah(dprItemJumlah(it));\n updateGroupTotal(gk);\n save();\n });\n });\n card.querySelector('[data-dcalc]').textContent = formatRupiah(dprItemJumlah(it));\n card.querySelector(`[data-drem=\"${gk}-${i}\"]`).addEventListener('click', ()=>{\n if (items.length===1){ toast(\"Minimal satu rincian\"); return; }\n items.splice(i,1); save(); renderDPRItems(gk); updateGroupTotal(gk);\n });\n });\n updateGroupTotal(gk);\n}\n\nfunction updateGroupTotal(gk){\n const total = dprGroupTotal(state.dpr[gk]);\n const el = document.querySelector(`[data-grptotal=\"${gk}\"]`);\n if (el){\n el.innerHTML = `\n <div class=\"lbl\">Jumlah ${gk==='g1'?'Form 1':'Form 2'}</div>\n <div class=\"amt\">${formatRupiah(total)}</div>\n <div class=\"terb\">Terbilang: ${terbilang(total)}</div>\n `;\n }\n}\n\ndocument.querySelectorAll('[data-additem]').forEach(btn=>{\n btn.addEventListener('click', ()=>{\n const gk = btn.dataset.additem;\n state.dpr[gk].items.push(blankDPRItem());\n save(); renderDPRItems(gk);\n });\n});\n\nfunction renderDPR(){\n bindDPRHeader();\n renderDPRItems('g1');\n renderDPRItems('g2');\n}\n\nfunction renderDPRPreview(){\n const wrap = (label, html)=>`\n <div class=\"paper-page-label\">${label}</div>\n <div class=\"paper\">${html}</div>\n `;\n document.getElementById('dpr-paper').innerHTML =\n wrap(\"Halaman 1 \u00b7 Form Uang Harian\", sheetDPRGroupHTML('g1')) +\n wrap(\"Halaman 2 \u00b7 Form Pengeluaran Lainnya\", sheetDPRGroupHTML('g2'));\n}\n\n/* ================================================================\n CONTOH DATA\n ================================================================ */\nconst contohData = {\n header: {\n judulKegiatan: \"MELAKUKAN KEGIATAN KOORDINASI TERKAIT CONTACT CENTRE DAN PENANGANAN PENGADUAN PADA KANTOR IMIGRASI KELAS I TPI PONTIANAK KE DIREKTORAT JENDERAL IMIGRASI JAKARTA\",\n nomorSPT: \"WIM.16.IMI.1-TI.04.01-2438\",\n tanggalSPT: \"2026-05-05\",\n namaProgram: \"Pengelolaan Teknologi Informasi Keimigrasian\",\n tanggalKegiatan: \"6 sd 8 Mei 2026\",\n tujuanKota: \"Jakarta\",\n namaPPK: \"SYECH MUKHAMAR REZA KHARAMI, S.H.\",\n nipPPK: \"19840831 200312 1 001\",\n jabatanPPK: \"KASI REG, ADMINISTRASI & PELAPORAN\",\n namaBendahara: \"MUHAMMAD REZA, S.H.\",\n nipBendahara: \"19840124 200212 1 002\",\n kotaTerbit: \"Pontianak\",\n kodeAnggaran: \"\"\n },\n pegawai: [\n {\n nama:\"SYECH MUKHAMAR REZA KHARAMI, S.H.\", nip:\"19840831 200312 1 001\",\n golongan:\"III\", jabatan:\"KASI REG, ADMINISTRASI & PELAPORAN\",\n tujuan:\"Jakarta\", noSPD:\"WIM.16.IMI.1-KU.03.01-2439\", tanggalSPD:\"2026-05-25\",\n tanggalPelaksanaan:\"28 sd 28 Mei 2026\", tglMulai:\"2026-05-28\", tglSelesai:\"2026-05-28\", lamaHari:1, uangHarian:530000,\n maskapaiPergi:\"Batik Air (PNK - CGK)\", tiketPergi:1550263,\n maskapaiPulang:\"Citilink (CGK - PNK)\", tiketPulang:1481947,\n taksi:250000, taksiDaerah:96200, transportKab:0,\n penginapan:1460000, representasi:0,\n rekening:\"BPG 042 655216928301000\"\n },\n {\n nama:\"SAULINA SIMORANGKIR, S.S.\", nip:\"19800803 200912 2 001\",\n golongan:\"III\", jabatan:\"KASUBSI REGISTRASI\",\n tujuan:\"Jakarta\", noSPD:\"WIM.16.IMI.1-KU.03.01-2440\", tanggalSPD:\"2026-05-27\",\n tanggalPelaksanaan:\"28 sd 28 Mei 2026\", tglMulai:\"2026-05-28\", tglSelesai:\"2026-05-28\", lamaHari:1, uangHarian:530000,\n maskapaiPergi:\"Batik Air (PNK - CGK)\", tiketPergi:1550263,\n maskapaiPulang:\"Citilink (CGK - PNK)\", tiketPulang:1481947,\n taksi:500000, taksiDaerah:311500, transportKab:0,\n penginapan:1460000, representasi:0,\n rekening:\"BPG 042 655216928301000\"\n }\n ],\n dpr: {\n namaTtd:\"SYECH MUKHAMAR REZA KHARAMI, S.H.\", nipTtd:\"19840831 200312 1 001\",\n jabatanTtd:\"KASI REG, ADMINISTRASI & PELAPORAN\", kotaTtd:\"Pontianak\",\n tanggalSurat:\"2026-05-05\", tanggalCetak:\"2026-05-08\",\n nomorSurat:\"WIM.16.IMI.1-TI.04.01-2438\",\n namaPenerima:\"SYECH MUKHAMAR REZA KHARAMI, S.H.\",\n g1: {\n baris1:\"OPERASI INTELIJEN DI WILAYAH\", baris2:\"PENGUMPULAN DATA DAN INFORMASI\",\n kategori:\"BELANJA BARANG NON OPERASIONAL LAINNYA\", kode:\"(6161.BHB.U12.051.A.521219)\",\n items:[\n { uraian:\"Biaya Uang Harian untuk 5 (lima) orang petugas selama 3 (tiga) hari pada Tanggal 6 sd 8 Mei 2026\", qty1:5, sat1:\"Org\", qty2:3, sat2:\"Hari\", tarif:530000, ket:\"Biaya Pengamatan/Penyusupan/Penelitian/Penjejakan\" },\n { uraian:\"Biaya Informan 1 (satu) orang\", qty1:1, sat1:\"Org\", qty2:1, sat2:\"Keg\", tarif:1700000, ket:\"\" }\n ]\n },\n g2: {\n baris1:\"OPERASI INTELIJEN DI WILAYAH\", baris2:\"PENGUMPULAN DATA DAN INFORMASI\",\n kategori:\"BELANJA SEWA\", kode:\"(6161.BHB.U12.051.A.522141)\",\n items:[\n { uraian:\"Biaya sewa Safehouse 1 (satu) unit selama 3 (tiga) hari pada Tanggal 6 sd 8 Mei 2026\", qty1:1, sat1:\"unit\", qty2:1, sat2:\"Keg\", tarif:3000000, ket:\"\" }\n ]\n }\n }\n};\n\n/* ================================================================\n INIT\n ================================================================ */\nfunction init(){\n fillDatalist();\n const hadSaved = load();\n if (!hadSaved){\n // start empty but pre-fill kota terbit\n state.header.kotaTerbit = \"Pontianak\";\n }\n bindHeader();\n renderAllPegawai();\n renderDPR();\n}\ninit();\n</script>\n\n\n\n</body></html>\n"
</script>
</body>
</html>