Update besar apk finance

This commit is contained in:
Wian Drs
2026-09-11 16:03:00 +07:00
parent d42483b350
commit 4ef15c22ae
897 changed files with 98056 additions and 15979 deletions
+75
View File
@@ -0,0 +1,75 @@
<?php
$monthNames=array(1=>'Januari',2=>'Februari',3=>'Maret',4=>'April',5=>'Mei',6=>'Juni',7=>'Juli',8=>'Agustus',9=>'September',10=>'Oktober',11=>'November',12=>'Desember');
$accountTypes=array('asset'=>'Aset','liability'=>'Liabilitas','equity'=>'Ekuitas','revenue'=>'Pendapatan','expense'=>'Beban');
$periodLabel=function($period)use($monthNames){$parts=explode('-',(string)$period);return isset($parts[1])?($monthNames[(int)$parts[1]].' '.$parts[0]):$period;};
?>
<div class="container-fluid py-3 budget-page">
<header class="budget-hero">
<div><small>PERENCANAAN KEUANGAN</small><h4>Budgeting</h4><p>Kelola nominal tetap per akun dan pantau akumulasinya untuk periode yang dipilih.</p></div>
<?php if($can_create):?><button type="button" class="btn btn-primary add-budget"><i class="bi bi-plus-lg me-1"></i> Tambah Budget</button><?php endif?>
</header>
<?php if($this->session->flashdata('success')):?><div class="alert alert-success"><?=html_escape($this->session->flashdata('success'))?></div><?php endif?>
<?php if($this->session->flashdata('error')):?><div class="alert alert-danger"><?=html_escape($this->session->flashdata('error'))?></div><?php endif?>
<nav class="budget-section-nav" aria-label="Bagian Budgeting">
<a class="<?=$active_tab==='list'?'active':''?>" href="<?=base_url('budgets?tab=list')?>"><i class="bi bi-card-checklist"></i><span>Daftar Budget</span><b><?=count($entries)?></b></a>
<a class="<?=$active_tab==='accounts'?'active':''?>" href="<?=base_url('budgets?tab=accounts')?>"><i class="bi bi-pie-chart"></i><span>Budget Per Akun</span><b><?=$budget_account_count?></b></a>
</nav>
<?php if($reminders):?><section class="budget-reminder-alert"><div class="reminder-icon"><i class="bi bi-bell"></i></div><div class="flex-grow-1"><strong><?=count($reminders)?> pengingat pembayaran perlu diperhatikan</strong><div class="reminder-items"><?php foreach(array_slice($reminders,0,4)as$reminder):?><span class="<?=$reminder->reminder_state==='overdue'?'overdue':''?>"><?=html_escape($reminder->title)?> · tanggal <?=$reminder->reminder_day?></span><?php endforeach?><?php if(count($reminders)>4):?><span>+<?=count($reminders)-4?> lainnya</span><?php endif?></div></div></section><?php endif?>
<?php if($active_tab==='list'):?>
<section class="budget-card">
<div class="budget-card-head"><div><h5>Daftar Budget</h5><p>Setiap kebutuhan dibuat sebagai jadwal tersendiri. Beberapa jadwal boleh menggunakan akun yang sama dan semuanya dijumlahkan.</p></div><?php if($can_export):?><div class="btn-group"><button type="button" class="btn btn-outline-danger budget-pdf" data-title="Daftar Budget" data-url="<?=base_url('budgets/export_list/pdf?'.http_build_query(array('list_account_id'=>$list_account_id)))?>"><i class="bi bi-file-earmark-pdf me-1"></i>PDF</button><a class="btn btn-outline-success" href="<?=base_url('budgets/export_list/excel?'.http_build_query(array('list_account_id'=>$list_account_id)))?>"><i class="bi bi-file-earmark-excel me-1"></i>Excel</a></div><?php endif?></div>
<form class="budget-filter" method="get"><input type="hidden" name="tab" value="list"><div><label class="form-label">Filter Akun</label><select class="form-select" name="list_account_id"><option value="">Semua akun</option><?php foreach($accounts as$a):?><option value="<?=$a->id?>" <?=$list_account_id==$a->id?'selected':''?>><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div><button class="btn btn-outline-primary"><i class="bi bi-funnel me-1"></i>Tampilkan</button><?php if($list_account_id):?><a href="<?=base_url('budgets?tab=list')?>" class="btn btn-light">Reset</a><?php endif?></form>
<div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Budget / Akun</th><th>Pola</th><th>Mulai / Bulan</th><th class="text-end">Nominal Budget</th><th>Pengingat</th><th>Keterangan</th><th>Diperbarui</th><th>Aksi</th></tr></thead><tbody>
<?php foreach($entries as$entry):?><tr>
<td><strong><?=html_escape($entry->title)?></strong><small class="d-block text-muted"><?=html_escape($entry->kode_akun.' - '.$entry->nama_akun.' · '.($accountTypes[$entry->tipe]??ucfirst($entry->tipe)))?></small></td>
<td><?php if($entry->entry_type==='recurring'):?><span class="badge bg-primary-subtle text-primary">Berulang</span><small class="d-block text-muted mt-1">Ditambahkan setiap bulan</small><?php else:?><span class="badge bg-warning-subtle text-warning-emphasis">Satu Bulan</span><small class="d-block text-muted mt-1">Hanya bulan yang dipilih</small><?php endif?></td>
<td><?=html_escape($periodLabel($entry->period))?></td><td class="text-end fw-semibold">Rp <?=number_format($entry->amount,2,',','.')?></td>
<td><?php if(!(int)$entry->reminder_enabled):?><span class="text-muted">Tidak ada</span><?php else:?><strong><?=$entry->entry_type==='recurring'?'Setiap tanggal ':'Tanggal '?><?=$entry->reminder_day?></strong><?php $reminderLabels=array('completed'=>array('success','Selesai bulan ini'),'overdue'=>array('danger','Terlambat'),'due'=>array('danger','Jatuh tempo hari ini'),'upcoming'=>array('warning text-dark','Segera jatuh tempo'),'scheduled'=>array('secondary','Terjadwal'),'future'=>array('info text-dark','Belum berlaku'),'inactive_period'=>array('secondary','Bukan bulan berjalan'));$rl=$reminderLabels[$entry->reminder_state]??array('secondary','Terjadwal');?><small class="d-block mt-1"><span class="badge bg-<?=$rl[0]?>"><?=$rl[1]?></span></small><?php endif?></td>
<td><?=html_escape($entry->notes?:'-')?></td><td><small><?=html_escape($entry->updated_at?:$entry->created_at)?></small><small class="d-block text-muted"><?=html_escape($entry->updater_name?:$entry->creator_name?:'-')?></small></td>
<td class="text-nowrap"><?php if($can_update&&in_array($entry->reminder_state,array('scheduled','upcoming','due','overdue'),true)):?><button type="button" class="btn btn-sm btn-outline-success complete-reminder" data-id="<?=$entry->id?>" data-label="<?=html_escape($entry->title)?>"><i class="bi bi-check2"></i> Selesai Bulan Ini</button><?php endif?> <?php if($can_update):?><button type="button" class="btn btn-sm btn-outline-primary edit-budget" data-id="<?=$entry->id?>" data-title="<?=html_escape($entry->title)?>" data-account="<?=$entry->account_id?>" data-period="<?=html_escape($entry->period)?>" data-type="<?=html_escape($entry->entry_type)?>" data-amount="<?=html_escape($entry->amount)?>" data-reminder="<?=(int)$entry->reminder_enabled?>" data-day="<?=html_escape($entry->reminder_day)?>" data-notes="<?=html_escape($entry->notes)?>"><i class="bi bi-pencil"></i> Edit</button><?php endif?> <?php if($can_delete):?><button type="button" class="btn btn-sm btn-outline-danger delete-budget" data-id="<?=$entry->id?>" data-label="<?=html_escape($entry->title)?>"><i class="bi bi-x-circle"></i> Nonaktifkan</button><?php endif?></td>
</tr><?php endforeach?>
<?php if(!$entries):?><tr><td colspan="8" class="empty-state"><i class="bi bi-wallet2"></i><strong>Belum ada budget</strong><span>Tambahkan budget tetap untuk akun yang diperlukan.</span></td></tr><?php endif?>
</tbody></table></div>
</section>
<?php else:?>
<section class="budget-card">
<div class="budget-card-head"><div><h5>Budget Per Akun</h5><p>Hanya akun yang mempunyai budget yang ditampilkan. Budget berulang dihitung otomatis untuk setiap bulan dalam rentang.</p></div><?php if($can_export):?><div class="btn-group"><button type="button" class="btn btn-outline-danger budget-pdf" data-title="Budget Per Akun" data-url="<?=base_url('budgets/export_accounts/pdf?'.http_build_query($filters))?>"><i class="bi bi-file-earmark-pdf me-1"></i>PDF</button><a class="btn btn-outline-success" href="<?=base_url('budgets/export_accounts/excel?'.http_build_query($filters))?>"><i class="bi bi-file-earmark-excel me-1"></i>Excel</a></div><?php endif?></div>
<form class="budget-filter budget-period-filter" method="get"><input type="hidden" name="tab" value="accounts"><div><label class="form-label">Dari Bulan</label><input type="month" class="form-control" name="from" value="<?=html_escape($filters['from'])?>" required></div><div><label class="form-label">Sampai Bulan</label><input type="month" class="form-control" name="to" value="<?=html_escape($filters['to'])?>" required></div><div><label class="form-label">Akun</label><select class="form-select" name="account_id"><option value="">Semua akun yang memiliki budget</option><?php foreach($budget_accounts as$a):?><option value="<?=$a->id?>" <?=$filters['account_id']==$a->id?'selected':''?>><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div><button class="btn btn-primary"><i class="bi bi-search me-1"></i>Tampilkan</button></form>
<div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Akun</th><th class="text-end">Akumulasi Budget</th><th class="text-end">Realisasi</th><th class="text-end">Komitmen PO</th><th class="text-end">Total Pemakaian</th><th class="text-end">Sisa</th><th class="text-end">%</th><th>Kondisi</th></tr></thead><tbody>
<?php foreach($report_rows as$row):?><tr><td><strong><?=html_escape($row['kode_akun'].' - '.$row['nama_akun'])?></strong><small class="d-block text-muted"><?=html_escape($accountTypes[$row['tipe']]??ucfirst($row['tipe']))?></small></td><?php foreach(array('budget','actual','commitment','usage','remaining')as$key):?><td class="text-end <?=$key==='budget'?'fw-semibold':''?>">Rp <?=number_format($row[$key],2,',','.')?></td><?php endforeach?><td class="text-end"><?=number_format($row['percentage'],2,',','.')?>%</td><td><span class="badge bg-<?=$row['indicator']==='over'?'danger':($row['indicator']==='warning'?'warning text-dark':'success')?>"><?=$row['indicator']==='over'?'Melebihi':($row['indicator']==='warning'?'Mendekati Batas':'Aman')?></span></td></tr><?php endforeach?>
<?php if(!$report_rows):?><tr><td colspan="8" class="empty-state"><i class="bi bi-bar-chart"></i><strong>Belum ada budget pada periode ini</strong><span>Buat budget berulang atau budget khusus bulan terlebih dahulu.</span></td></tr><?php endif?>
</tbody></table></div>
</section>
<?php endif?>
</div>
<div class="modal fade" id="budgetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false"><div class="modal-dialog modal-dialog-centered modal-dialog-scrollable"><form class="modal-content" id="budgetForm" action="<?=base_url('budgets/save_entry')?>" method="post"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="id" id="budgetId"><div class="modal-header"><div><small class="text-muted">BUDGET PER AKUN</small><h5 class="modal-title">Tambah Budget</h5></div><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body">
<label class="form-label">Nama Budget <span class="text-danger">*</span></label><input type="text" class="form-control mb-3" name="title" id="budgetTitle" maxlength="150" placeholder="Contoh: Pembayaran Listrik Kantor 1" required>
<label class="form-label">Akun <span class="text-danger">*</span></label><select class="form-select mb-3" name="account_id" id="budgetAccount" required><option value="">Pilih akun</option><?php foreach($accounts as$a):?><option value="<?=$a->id?>"><?=html_escape($a->kode_akun.' - '.$a->nama_akun.' · '.($accountTypes[$a->tipe]??ucfirst($a->tipe)))?></option><?php endforeach?></select>
<label class="form-label">Pola Budget <span class="text-danger">*</span></label><div class="budget-type-options mb-3"><label><input type="radio" name="entry_type" value="recurring" checked><span><b>Budget Berulang</b><small>Nominal ditambahkan setiap bulan mulai dari bulan yang dipilih.</small></span></label><label><input type="radio" name="entry_type" value="override"><span><b>Budget Satu Bulan</b><small>Nominal hanya ditambahkan pada satu bulan yang dipilih.</small></span></label></div>
<label class="form-label" id="periodLabel">Berlaku Mulai Bulan <span class="text-danger">*</span></label><input type="month" class="form-control mb-3" name="period" id="budgetPeriod" value="<?=date('Y-m')?>" required>
<label class="form-label">Nominal Budget per Bulan <span class="text-danger">*</span></label><div class="input-group mb-3"><span class="input-group-text">Rp</span><input type="text" class="form-control format-rupiah" inputmode="decimal" name="amount" id="budgetAmount" placeholder="0,00" required></div>
<div class="budget-reminder-option mb-3"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" name="reminder_enabled" value="1" id="budgetReminder"><label class="form-check-label fw-semibold" for="budgetReminder">Aktifkan pengingat pembayaran bulanan</label></div><small class="text-muted">Pengingat akan muncul tujuh hari sebelum tanggal pembayaran.</small><div class="mt-3 d-none" id="reminderDayWrap"><label class="form-label">Tanggal Pembayaran <span class="text-danger">*</span></label><input type="number" class="form-control" name="reminder_day" id="budgetReminderDay" min="1" max="31" inputmode="numeric" placeholder="1–31"><small class="text-muted">Jika bulan lebih pendek, tanggal otomatis memakai hari terakhir bulan tersebut.</small></div></div>
<label class="form-label">Keterangan</label><textarea class="form-control" name="notes" id="budgetNotes" rows="3" placeholder="Contoh: Biaya sewa kantor tetap"></textarea>
</div><div class="modal-footer"><button type="button" class="btn btn-light" data-bs-dismiss="modal">Batal</button><button class="btn btn-primary" id="saveBudget"><i class="bi bi-check2-circle me-1"></i>Simpan Budget</button></div></form></div></div>
<div class="modal fade" id="budgetPdfModal" tabindex="-1"><div class="modal-dialog modal-xl modal-dialog-centered"><div class="modal-content budget-pdf-content"><div class="modal-header"><h5 class="modal-title">Pratinjau PDF Budget</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body p-0"><iframe title="Pratinjau PDF Budget"></iframe></div></div></div></div>
<style>
.budget-page{--budget-blue:#176bff}.budget-hero{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}.budget-hero small{font-size:.7rem;letter-spacing:.15em;color:#758199}.budget-hero h4{margin:2px 0 3px}.budget-hero p,.budget-card-head p{margin:0;color:#69758a}.budget-section-nav{display:grid;grid-template-columns:repeat(2,minmax(180px,1fr));gap:8px;padding:8px;margin-bottom:16px;background:#fff;border:1px solid #e4e9f1;border-radius:14px}.budget-section-nav a{display:flex;align-items:center;justify-content:center;gap:9px;min-height:48px;padding:10px 14px;border-radius:9px;color:#526079;text-decoration:none;font-weight:600}.budget-section-nav a:hover{background:#f3f7ff;color:var(--budget-blue)}.budget-section-nav a.active{background:var(--budget-blue);color:#fff;box-shadow:0 5px 14px rgba(23,107,255,.2)}.budget-section-nav b{min-width:22px;padding:2px 6px;border-radius:20px;background:rgba(255,255,255,.22);font-size:.72rem;text-align:center}.budget-section-nav a:not(.active) b{background:#eef2f8}.budget-card{overflow:hidden;background:#fff;border:1px solid #e4e9f1;border-radius:14px;box-shadow:0 6px 22px rgba(22,34,55,.05)}.budget-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:17px 18px;border-bottom:1px solid #e8ecf2}.budget-card-head h5{margin:0 0 3px}.budget-filter{display:flex;align-items:end;gap:10px;padding:14px 18px;background:#fafbfd;border-bottom:1px solid #e8ecf2}.budget-filter>div{min-width:min(360px,100%)}.budget-period-filter{display:grid;grid-template-columns:180px 180px minmax(240px,1fr) auto}.budget-card .table th{padding:12px 14px;background:#f6f8fb;color:#59657a;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}.budget-card .table td{padding:13px 14px}.empty-state{text-align:center!important;padding:54px 20px!important;color:#768196}.empty-state i,.empty-state strong,.empty-state span{display:block}.empty-state i{margin-bottom:8px;font-size:1.8rem}.empty-state strong{color:#344054}.budget-type-options{display:grid;gap:8px}.budget-type-options label{display:flex;gap:10px;padding:12px;border:1px solid #dfe5ee;border-radius:10px;cursor:pointer}.budget-type-options label:has(input:checked){border-color:var(--budget-blue);background:#f2f7ff}.budget-type-options input{margin-top:3px}.budget-type-options span,.budget-type-options small{display:block}.budget-type-options small{margin-top:2px;color:#6f7b8f;font-weight:400}@media(max-width:767.98px){.budget-hero{align-items:flex-start}.budget-section-nav{display:flex;overflow:auto}.budget-section-nav a{flex:1;white-space:nowrap}.budget-filter,.budget-period-filter{display:grid;grid-template-columns:1fr}.budget-filter>div{min-width:0}.budget-card-head{align-items:flex-start}.budget-card .table td,.budget-card .table th{white-space:nowrap}.modal-dialog{margin:.5rem}}
.budget-reminder-alert{display:flex;align-items:flex-start;gap:12px;margin-bottom:16px;padding:14px 16px;border:1px solid #ffd89a;border-radius:13px;background:#fff8e8;color:#6c4a09}.reminder-icon{display:grid;place-items:center;width:38px;height:38px;flex:0 0 38px;border-radius:10px;background:#ffedc4;color:#e48300;font-size:1.1rem}.reminder-items{display:flex;flex-wrap:wrap;gap:6px;margin-top:7px}.reminder-items span{padding:4px 9px;border-radius:20px;background:#fff;border:1px solid #f2d79c;font-size:.78rem}.reminder-items span.overdue{border-color:#f2b8b8;background:#fff0f0;color:#b42318}.budget-reminder-option{padding:13px 14px;border:1px solid #dfe5ee;border-radius:10px;background:#fafbfd}
.budget-pdf-content{height:min(90vh,900px)}.budget-pdf-content .modal-body{min-height:0}.budget-pdf-content iframe{width:100%;height:100%;border:0}.budget-card-head .btn-group{flex:0 0 auto}@media(max-width:575.98px){.budget-card-head{flex-direction:column}.budget-card-head .btn-group{width:100%}.budget-card-head .btn-group>*{flex:1}}
</style>
<script>
document.addEventListener('DOMContentLoaded',function(){
const modalEl=document.getElementById('budgetModal'),modal=modalEl?bootstrap.Modal.getOrCreateInstance(modalEl):null,form=document.getElementById('budgetForm'),money=n=>Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});
function reset(){form.reset();document.getElementById('budgetId').value='';document.getElementById('budgetPeriod').value='<?=date('Y-m')?>';document.querySelector('#budgetModal .modal-title').textContent='Tambah Budget';updatePeriodLabel();updateReminder()}
function updatePeriodLabel(){const recurring=form.querySelector('[name="entry_type"]:checked')?.value==='recurring';document.getElementById('periodLabel').innerHTML=(recurring?'Berlaku Mulai Bulan':'Bulan Budget')+' <span class="text-danger">*</span>'}
function updateReminder(){const enabled=document.getElementById('budgetReminder').checked,wrap=document.getElementById('reminderDayWrap'),day=document.getElementById('budgetReminderDay');wrap.classList.toggle('d-none',!enabled);day.required=enabled;if(!enabled)day.value=''}
document.querySelectorAll('[name="entry_type"]').forEach(x=>x.addEventListener('change',updatePeriodLabel));
document.getElementById('budgetReminder')?.addEventListener('change',updateReminder);
document.querySelector('.add-budget')?.addEventListener('click',()=>{reset();modal.show()});
document.querySelectorAll('.edit-budget').forEach(button=>button.addEventListener('click',()=>{reset();document.getElementById('budgetId').value=button.dataset.id;document.getElementById('budgetTitle').value=button.dataset.title||'';document.getElementById('budgetAccount').value=button.dataset.account;document.getElementById('budgetPeriod').value=button.dataset.period;document.getElementById('budgetAmount').value=money(button.dataset.amount);document.getElementById('budgetNotes').value=button.dataset.notes||'';document.getElementById('budgetReminder').checked=button.dataset.reminder==='1';document.getElementById('budgetReminderDay').value=button.dataset.day||'';const type=form.querySelector('[name="entry_type"][value="'+button.dataset.type+'"]');if(type)type.checked=true;document.querySelector('#budgetModal .modal-title').textContent='Edit Budget';updatePeriodLabel();updateReminder();modal.show()}));
form?.addEventListener('submit',async function(event){event.preventDefault();const button=document.getElementById('saveBudget');button.disabled=true;try{const response=await fetch(form.action,{method:'POST',headers:{'X-Requested-With':'XMLHttpRequest'},body:new FormData(form)}),result=await response.json().catch(()=>({status:false,message:'Respons server tidak valid.'}));if(!response.ok||!result.status)throw Error(result.message||'Budget gagal disimpan.');modal.hide();if(window.AppAlert)AppAlert.toast(result.message,'success');setTimeout(()=>location.href='<?=base_url('budgets?tab=list')?>',500)}catch(error){if(window.AppAlert)AppAlert.toast(error.message,'error');else Swal.fire({icon:'error',title:'Gagal',text:error.message})}finally{button.disabled=false}});
document.querySelectorAll('.delete-budget').forEach(button=>button.addEventListener('click',async()=>{const ask=window.AppAlert?await AppAlert.confirm({title:'Nonaktifkan budget?',text:button.dataset.label+' tidak lagi dipakai dalam perhitungan.',confirmButtonText:'Ya, Nonaktifkan'}):await Swal.fire({title:'Nonaktifkan budget?',text:button.dataset.label,icon:'warning',showCancelButton:true});if(!ask.isConfirmed)return;try{const body=new FormData();body.append('<?=$this->security->get_csrf_token_name()?>','<?=$this->security->get_csrf_hash()?>');const response=await fetch('<?=base_url('budgets/delete_entry/')?>'+button.dataset.id,{method:'POST',headers:{'X-Requested-With':'XMLHttpRequest'},body:body}),result=await response.json().catch(()=>({status:false,message:'Respons server tidak valid.'}));if(!response.ok||!result.status)throw Error(result.message||'Budget gagal dinonaktifkan.');if(window.AppAlert)AppAlert.toast(result.message,'success');setTimeout(()=>location.reload(),500)}catch(error){if(window.AppAlert)AppAlert.toast(error.message,'error');else Swal.fire({icon:'error',title:'Gagal',text:error.message})}}));
document.querySelectorAll('.complete-reminder').forEach(button=>button.addEventListener('click',async()=>{const ask=window.AppAlert?await AppAlert.confirm({title:'Tandai pembayaran selesai?',text:button.dataset.label+' tidak akan muncul lagi sebagai pengingat pada bulan ini.',confirmButtonText:'Ya, Sudah Selesai'}):await Swal.fire({title:'Tandai pembayaran selesai?',text:button.dataset.label,icon:'question',showCancelButton:true});if(!ask.isConfirmed)return;try{const body=new FormData();body.append('<?=$this->security->get_csrf_token_name()?>','<?=$this->security->get_csrf_hash()?>');const response=await fetch('<?=base_url('budgets/complete_reminder/')?>'+button.dataset.id,{method:'POST',headers:{'X-Requested-With':'XMLHttpRequest'},body:body}),result=await response.json().catch(()=>({status:false,message:'Respons server tidak valid.'}));if(!response.ok||!result.status)throw Error(result.message||'Pengingat gagal diperbarui.');if(window.AppAlert)AppAlert.toast(result.message,'success');setTimeout(()=>location.reload(),500)}catch(error){if(window.AppAlert)AppAlert.toast(error.message,'error');else Swal.fire({icon:'error',title:'Gagal',text:error.message})}}));
document.querySelectorAll('.budget-pdf').forEach(button=>button.addEventListener('click',()=>{const pdfModal=document.getElementById('budgetPdfModal');pdfModal.querySelector('.modal-title').textContent=button.dataset.title||'Pratinjau PDF Budget';pdfModal.querySelector('iframe').src=button.dataset.url;bootstrap.Modal.getOrCreateInstance(pdfModal).show()}));document.getElementById('budgetPdfModal')?.addEventListener('hidden.bs.modal',function(){this.querySelector('iframe').src='about:blank'});
});
</script>