Files
lpj/resources/views/memo/create.blade.php
Daeng Deni Mardaeni 0c2c0c9e20 feat(memo): Tambah halaman preview dan PDF memo penyelesaian
Menambahkan fitur preview memo penyelesaian sebelum data disimpan ke database, untuk memastikan user dapat memeriksa kembali detail permohonan dan biaya terkait.

Perubahan pada Controller:
- Menambahkan method `preview()` di MemoController untuk menampilkan halaman preview memo penyelesaian.
- Menambahkan method `generatePdf()` untuk menghasilkan PDF resmi memo dengan format sesuai standar bank.
- Mengimplementasikan validasi input lengkap sebelum proses preview dan PDF generation.
- Mengintegrasikan helper fungsi terbilang untuk konversi total biaya ke dalam format huruf.

Perubahan pada View:
- Menambahkan view `preview.blade.php` sebagai template resmi memo penyelesaian dengan layout AGI header, informasi memo, daftar permohonan, total biaya PJ, dan tanda tangan.
- Menampilkan tabel daftar permohonan terlampir dengan informasi debitur, cabang, AO, dan nominal biaya PJ.
- Menyediakan fitur print preview dengan styling khusus untuk pencetakan dokumen resmi.
- Menambahkan UI interaktif untuk mempermudah navigasi sebelum menyimpan data.

Perubahan pada Form Create:
- Mengubah alur form `create.blade.php` agar mengarahkan ke halaman preview terlebih dahulu sebelum penyimpanan.
- Menambahkan tombol untuk melanjutkan proses ke simpan atau kembali ke pengisian form.

Routing dan Navigasi:
- Menambahkan route baru `memo.preview` untuk preview dan `memo.generate-pdf` untuk generate PDF memo penyelesaian.
- Menambahkan breadcrumb untuk halaman preview agar navigasi lebih jelas.

Fitur Tambahan:
- Implementasi JavaScript untuk interaksi form preview, print action, dan konfirmasi user.
- Penambahan fitur terbilang untuk memudahkan verifikasi nominal dalam format teks.
- Penanganan error secara komprehensif dengan logging dan rollback pada transaksi jika terjadi kesalahan.
- Menjamin data tetap konsisten meskipun user hanya melakukan preview tanpa menyimpan.

Tujuan Perubahan:
- Memberikan fasilitas preview agar user dapat memverifikasi data sebelum menyimpan memo penyelesaian.
- Memastikan output memo dalam format PDF resmi yang sesuai dengan template bank.
- Meningkatkan user experience dan mengurangi potensi kesalahan input sebelum proses finalisasi.
- Mempermudah proses cetak memo dengan fitur print-friendly dan PDF yang siap dikirim atau diarsipkan.
2025-07-17 09:55:06 +07:00

305 lines
14 KiB
PHP

@extends('layouts.main')
@section('breadcrumbs')
{{ Breadcrumbs::render('memo.create') }}
@endsection
@section('content')
<div class="grid gap-5 mx-auto w-full lg:gap-7.5">
<!-- Form Memo Penyelesaian -->
<div class="card">
<div class="card-header">
<h3 class="card-title">
Buat Memo Penyelesaian
</h3>
<div class="flex gap-2 items-center">
<a href="{{ route('memo.index') }}" class="btn btn-sm btn-light">
<i class="ki-filled ki-black-left"></i>
Kembali
</a>
</div>
</div>
<div class="card-body">
@if (session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<form action="{{ route('memo.preview') }}" method="POST" id="memo-form">
@csrf
<!-- Form Input Memo -->
<div class="grid grid-cols-1 gap-5 mb-7 lg:grid-cols-2">
<div class="flex flex-col gap-1">
<label class="text-gray-900 form-label">Nomor Memo <span class="text-red-500">*</span></label>
<input type="text" name="memo_number" class="input"
placeholder="Masukkan nomor memo penyelesaian" value="{{ old('memo_number') }}" required>
@error('memo_number')
<span class="text-sm text-red-500">{{ $message }}</span>
@enderror
</div>
<div class="flex flex-col gap-1">
<label class="text-gray-900 form-label">Tanggal Pembayaran <span
class="text-red-500">*</span></label>
<input type="date" name="payment_date" class="input" value="{{ old('payment_date') }}"
required>
@error('payment_date')
<span class="text-sm text-red-500">{{ $message }}</span>
@enderror
</div>
<div class="flex flex-col gap-1 lg:col-span-2">
<label class="text-gray-900 form-label">Total Biaya PJ</label>
<div class="relative">
<input type="text" id="total-biaya-pj" class="bg-gray-50 input"
value="Rp {{ number_format($totalBiayaPJ ?? 0, 0, ',', '.') }}" readonly>
<div class="flex absolute inset-y-0 right-0 items-center pr-3">
<i class="text-gray-400 ki-filled ki-calculator"></i>
</div>
</div>
<span class="text-sm text-gray-500">Total biaya dari nominal bayar NOC untuk permohonan yang
dipilih</span>
</div>
</div>
<!-- Hidden field untuk tanggal memo otomatis -->
<input type="hidden" name="memo_date" value="{{ date('Y-m-d') }}">
<!-- Daftar Permohonan yang Dipilih -->
@if (count($permohonanList) > 0)
<div class="mb-7">
<h4 class="mb-4 text-lg font-semibold">Daftar Permohonan yang Akan Diproses</h4>
<div class="overflow-x-auto">
<table class="table table-border">
<thead>
<tr class="bg-gray-50">
<th class="w-12 text-center">
<input type="checkbox" id="select-all" class="checkbox checkbox-sm" checked>
</th>
<th>Nomor Registrasi</th>
<th>Debitur</th>
<th>Cabang</th>
<th>AO</th>
<th>Tujuan Penilaian</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach ($permohonanList as $permohonan)
<tr>
<td class="text-center">
<input type="checkbox" name="permohonan_ids[]"
value="{{ $permohonan->id }}"
class="checkbox checkbox-sm permohonan-checkbox" checked>
</td>
<td class="font-medium">{{ $permohonan->nomor_registrasi }}</td>
<td>{{ $permohonan->debiture->name ?? '-' }}</td>
<td>{{ $permohonan->branch->name ?? '-' }}</td>
<td>{{ $permohonan->user->name ?? '-' }}</td>
<td>
@if ($permohonan->tujuanPenilaian)
<span class="badge badge-sm badge-primary">
{{ $permohonan->tujuanPenilaian->name }}
</span>
@else
-
@endif
</td>
<td>
<span class="uppercase badge badge-sm badge-warning">
{{ $permohonan->status }}
</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="p-4 mt-4 bg-blue-50 rounded-lg">
<div class="flex gap-2 items-center">
<i class="text-blue-600 ki-filled ki-information"></i>
<span class="font-medium text-blue-800">Informasi:</span>
</div>
<p class="mt-1 text-blue-700">
Total <span id="selected-count">{{ count($permohonanList) }}</span> permohonan akan
diproses untuk memo penyelesaian.
Anda dapat menghapus centang pada permohonan yang tidak ingin diproses.
</p>
</div>
</div>
<!-- Tombol Submit -->
<div class="flex gap-2 justify-end">
<a href="{{ route('memo.index') }}" class="btn btn-light">
Batal
</a>
<button type="submit" class="btn btn-primary" id="submit-btn">
<i class="ki-filled ki-eye"></i>
Preview Memo Penyelesaian
</button>
</div>
@else
<div class="py-10 text-center">
<div class="mb-4">
<i class="text-6xl text-gray-400 ki-filled ki-information-2"></i>
</div>
<h4 class="mb-2 text-lg font-semibold text-gray-600">Tidak Ada Data</h4>
<p class="mb-4 text-gray-500">Tidak ada permohonan yang dipilih untuk diproses.</p>
<a href="{{ route('memo.index') }}" class="btn btn-primary">
Kembali ke Daftar
</a>
</div>
@endif
</form>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const selectAllCheckbox = document.getElementById('select-all');
const permohonanCheckboxes = document.querySelectorAll('.permohonan-checkbox');
const selectedCountSpan = document.getElementById('selected-count');
const submitBtn = document.getElementById('submit-btn');
const memoForm = document.getElementById('memo-form');
const totalBiayaPJInput = document.getElementById('total-biaya-pj');
/**
* Fungsi untuk mengupdate total biaya PJ
*/
function updateTotalBiayaPJ() {
const checkedBoxes = document.querySelectorAll('.permohonan-checkbox:checked');
const permohonanIds = Array.from(checkedBoxes).map(cb => cb.value);
if (permohonanIds.length === 0) {
if (totalBiayaPJInput) {
totalBiayaPJInput.value = 'Rp 0';
}
return;
}
// Kirim AJAX request untuk mendapatkan total biaya PJ
fetch('{{ route('memo.total-biaya-pj') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
'content')
},
body: JSON.stringify({
permohonan_ids: permohonanIds
})
})
.then(response => response.json())
.then(data => {
if (data.success && totalBiayaPJInput) {
totalBiayaPJInput.value = data.total_biaya_pj_formatted;
}
})
.catch(error => {
console.error('Error:', error);
if (totalBiayaPJInput) {
totalBiayaPJInput.value = 'Error menghitung total';
}
});
}
/**
* Fungsi untuk mengupdate jumlah item yang dipilih
*/
function updateSelectedCount() {
const checkedBoxes = document.querySelectorAll('.permohonan-checkbox:checked');
const count = checkedBoxes.length;
if (selectedCountSpan) {
selectedCountSpan.textContent = count;
}
// Disable submit button jika tidak ada yang dipilih
if (submitBtn) {
submitBtn.disabled = count === 0;
if (count === 0) {
submitBtn.innerHTML = '<i class="ki-filled ki-check"></i> Pilih minimal 1 permohonan';
submitBtn.classList.add('btn-disabled');
} else {
submitBtn.innerHTML =
`<i class="ki-filled ki-check"></i> Buat Memo Penyelesaian (${count} item)`;
submitBtn.classList.remove('btn-disabled');
}
}
// Update select all checkbox state
if (selectAllCheckbox) {
selectAllCheckbox.checked = count === permohonanCheckboxes.length;
selectAllCheckbox.indeterminate = count > 0 && count < permohonanCheckboxes.length;
}
// Update total biaya PJ
updateTotalBiayaPJ();
}
/**
* Event listener untuk select all checkbox
*/
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', function() {
const isChecked = this.checked;
permohonanCheckboxes.forEach(checkbox => {
checkbox.checked = isChecked;
});
updateSelectedCount();
});
}
/**
* Event listener untuk individual checkboxes
*/
permohonanCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
updateSelectedCount();
});
});
/**
* Event listener untuk form submission
*/
if (memoForm) {
memoForm.addEventListener('submit', function(e) {
const checkedBoxes = document.querySelectorAll('.permohonan-checkbox:checked');
if (checkedBoxes.length === 0) {
e.preventDefault();
alert('Pilih minimal 1 permohonan untuk diproses!');
return false;
}
// Konfirmasi sebelum submit
const confirmed = confirm(
`Apakah Anda yakin ingin membuat memo penyelesaian untuk ${checkedBoxes.length} permohonan?`
);
if (!confirmed) {
e.preventDefault();
return false;
}
// Disable submit button untuk mencegah double submit
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="ki-filled ki-loading"></i> Memproses...';
}
});
}
// Initialize count dan total biaya PJ
updateSelectedCount();
});
</script>
@endpush