feat(memo): tambah tombol download PDF dan disable checkbox untuk memo selesai
- Menambahkan field memo_penyelesaian_pdf_path ke tabel noc untuk menyimpan path file PDF - Membuat migrasi baru untuk menambahkan field PDF path ke tabel noc - Menambahkan field memo_penyelesaian_pdf_path ke model Noc dalam fillable array - Memodifikasi fungsi generatePdf di MemoController untuk menyimpan path PDF ke database - Menambahkan route baru memo.download-pdf untuk download file PDF memo penyelesaian - Membuat method downloadPdf di MemoController dengan validasi file dan error handling - Memodifikasi kolom select di datatables untuk disable checkbox jika sudah ada memo - Menambahkan tooltip pada checkbox yang disabled untuk memberikan informasi kepada user - Memodifikasi kolom actions untuk menampilkan tombol download PDF jika memo sudah ada - Menampilkan informasi nomor memo dan tanggal memo di kolom actions - Memodifikasi fungsi handleCheckboxChange untuk mengabaikan checkbox yang disabled - Menambahkan styling untuk tombol download dengan icon dan warna yang sesuai - Menambahkan logging untuk tracking aktivitas download PDF memo penyelesaian - Menambahkan validasi keberadaan file di storage sebelum mengizinkan download - Menggunakan Storage facade untuk operasi file yang lebih aman dan konsisten
This commit is contained in:
@@ -241,6 +241,7 @@ class MemoController extends Controller
|
|||||||
'debiture',
|
'debiture',
|
||||||
'branch',
|
'branch',
|
||||||
'tujuanPenilaian',
|
'tujuanPenilaian',
|
||||||
|
'jenisPenilaian',
|
||||||
'penilaian',
|
'penilaian',
|
||||||
'jenisFasilitasKredit',
|
'jenisFasilitasKredit',
|
||||||
'documents.inspeksi',
|
'documents.inspeksi',
|
||||||
@@ -516,4 +517,41 @@ class MemoController extends Controller
|
|||||||
$uniqueJaminan = array_unique($jaminanTypes);
|
$uniqueJaminan = array_unique($jaminanTypes);
|
||||||
return implode(' & ', $uniqueJaminan);
|
return implode(' & ', $uniqueJaminan);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Download PDF memo penyelesaian
|
||||||
|
*
|
||||||
|
* @param int $id - ID permohonan
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function downloadPdf($id)
|
||||||
|
{
|
||||||
|
Log::info('MemoController: Download PDF memo penyelesaian untuk permohonan ID: ' . $id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cari NOC berdasarkan permohonan_id
|
||||||
|
$noc = Noc::where('permohonan_id', $id)->first();
|
||||||
|
|
||||||
|
if (!$noc || !$noc->memo_penyelesaian) {
|
||||||
|
Log::warning('MemoController: PDF memo penyelesaian tidak ditemukan untuk permohonan ID: ' . $id);
|
||||||
|
return redirect()->back()->with('error', 'File PDF memo penyelesaian tidak ditemukan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah file ada di storage
|
||||||
|
if (!Storage::disk('public')->exists($noc->memo_penyelesaian)) {
|
||||||
|
Log::warning('MemoController: File PDF tidak ada di storage: ' . $noc->memo_penyelesaian);
|
||||||
|
return redirect()->back()->with('error', 'File PDF tidak ditemukan di server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download file
|
||||||
|
$fileName = 'memo-penyelesaian-' . $noc->memo_penyelesaian_number . '.pdf';
|
||||||
|
|
||||||
|
Log::info('MemoController: Berhasil download PDF memo penyelesaian: ' . $fileName);
|
||||||
|
|
||||||
|
return Storage::disk('public')->download($noc->memo_penyelesaian, $fileName);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('MemoController: Error saat download PDF memo penyelesaian - ' . $e->getMessage());
|
||||||
|
return redirect()->back()->with('error', 'Terjadi kesalahan saat mengunduh file PDF.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,16 +239,6 @@
|
|||||||
let resumeButton = '';
|
let resumeButton = '';
|
||||||
let penyelesaian = '';
|
let penyelesaian = '';
|
||||||
|
|
||||||
|
|
||||||
if (data.noc) {
|
|
||||||
if (!data.noc?.memo_penyelesaian) {
|
|
||||||
penyelesaian = `
|
|
||||||
<a href="{{ route('noc.index') }}/${data.noc.id}" class="btn btn-sm btn-warning">
|
|
||||||
Penyelesaian
|
|
||||||
</a>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.penilai.resume) {
|
if (data.penilai.resume) {
|
||||||
resumeButton = `
|
resumeButton = `
|
||||||
<a href="{{ route('penilai.print-out') }}?permohonanId=${data.id}&documentId=${dokumenID}&inspeksiId=${inspeksiId}&jaminanId=${jenisJaminanID}&statusLpj=0" class="btn btn-sm btn-success">
|
<a href="{{ route('penilai.print-out') }}?permohonanId=${data.id}&documentId=${dokumenID}&inspeksiId=${inspeksiId}&jaminanId=${jenisJaminanID}&statusLpj=0" class="btn btn-sm btn-success">
|
||||||
|
|||||||
@@ -54,6 +54,10 @@
|
|||||||
<span class="sort"> <span class="sort-label"> AO </span>
|
<span class="sort"> <span class="sort-label"> AO </span>
|
||||||
<span class="sort-icon"> </span> </span>
|
<span class="sort-icon"> </span> </span>
|
||||||
</th>
|
</th>
|
||||||
|
<th class="min-w-[150px]" data-datatable-column="jenis_penilaian_id">
|
||||||
|
<span class="sort"> <span class="sort-label"> Jenis Penilaian </span>
|
||||||
|
<span class="sort-icon"> </span> </span>
|
||||||
|
</th>
|
||||||
<th class="min-w-[150px]" data-datatable-column="tujuan_penilaian_id">
|
<th class="min-w-[150px]" data-datatable-column="tujuan_penilaian_id">
|
||||||
<span class="sort"> <span class="sort-label"> Tujuan Penilaian </span>
|
<span class="sort"> <span class="sort-label"> Tujuan Penilaian </span>
|
||||||
<span class="sort-icon"> </span> </span>
|
<span class="sort-icon"> </span> </span>
|
||||||
@@ -134,11 +138,21 @@
|
|||||||
columns: {
|
columns: {
|
||||||
select: {
|
select: {
|
||||||
render: (item, data, context) => {
|
render: (item, data, context) => {
|
||||||
|
// Cek apakah sudah ada memo penyelesaian
|
||||||
|
const hasMemo = data.noc && data.noc.memo_penyelesaian;
|
||||||
|
|
||||||
const checkbox = document.createElement('input');
|
const checkbox = document.createElement('input');
|
||||||
checkbox.className = 'checkbox checkbox-sm';
|
checkbox.className = 'checkbox checkbox-sm';
|
||||||
checkbox.type = 'checkbox';
|
checkbox.type = 'checkbox';
|
||||||
checkbox.value = data.id.toString();
|
checkbox.value = data.id.toString();
|
||||||
checkbox.setAttribute('data-datatable-row-check', 'true');
|
checkbox.setAttribute('data-datatable-row-check', 'true');
|
||||||
|
|
||||||
|
// Disable checkbox jika sudah ada memo
|
||||||
|
if (hasMemo) {
|
||||||
|
checkbox.disabled = true;
|
||||||
|
checkbox.title = 'Permohonan ini sudah memiliki memo penyelesaian';
|
||||||
|
}
|
||||||
|
|
||||||
return checkbox.outerHTML.trim();
|
return checkbox.outerHTML.trim();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -166,6 +180,12 @@
|
|||||||
return `${data.user.name}`;
|
return `${data.user.name}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
jenis_penilaian_id: {
|
||||||
|
title: 'Jenis Penilaian',
|
||||||
|
render: (item, data) => {
|
||||||
|
return `${data.jenis_penilaian.name}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
tujuan_penilaian_id: {
|
tujuan_penilaian_id: {
|
||||||
title: 'Tujuan Penilaian',
|
title: 'Tujuan Penilaian',
|
||||||
render: (item, data) => {
|
render: (item, data) => {
|
||||||
@@ -252,9 +272,21 @@
|
|||||||
render: (item, data) => {
|
render: (item, data) => {
|
||||||
let actionButtons = '';
|
let actionButtons = '';
|
||||||
|
|
||||||
|
// Cek apakah sudah ada memo penyelesaian dengan PDF
|
||||||
actionButtons = `
|
if (data.noc && data.noc.memo_penyelesaian) {
|
||||||
|
actionButtons = `
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<a href="memo/memo/download-pdf/${data.id}"
|
||||||
|
class="flex gap-1 items-center btn btn-sm btn-primary"
|
||||||
|
title="Download PDF Memo Penyelesaian">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
Download Memo
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
actionButtons = `
|
||||||
<span class="text-sm text-gray-500">Belum ada memo</span>`;
|
<span class="text-sm text-gray-500">Belum ada memo</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
return `<div class="flex flex-wrap gap-1.5 justify-center">${actionButtons}</div>`;
|
return `<div class="flex flex-wrap gap-1.5 justify-center">${actionButtons}</div>`;
|
||||||
},
|
},
|
||||||
@@ -268,7 +300,8 @@
|
|||||||
* Fungsi untuk menangani perubahan checkbox
|
* Fungsi untuk menangani perubahan checkbox
|
||||||
*/
|
*/
|
||||||
function handleCheckboxChange() {
|
function handleCheckboxChange() {
|
||||||
const checkboxes = document.querySelectorAll('input[data-datatable-row-check="true"]:checked');
|
// Hanya ambil checkbox yang tidak disabled dan checked
|
||||||
|
const checkboxes = document.querySelectorAll('input[data-datatable-row-check="true"]:checked:not(:disabled)');
|
||||||
selectedItems = Array.from(checkboxes).map(cb => cb.value);
|
selectedItems = Array.from(checkboxes).map(cb => cb.value);
|
||||||
|
|
||||||
// Enable/disable tombol create memo berdasarkan jumlah item yang dipilih
|
// Enable/disable tombol create memo berdasarkan jumlah item yang dipilih
|
||||||
|
|||||||
@@ -404,6 +404,9 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::post('total-biaya-pj', [MemoController::class, 'getTotalBiayaPJ'])->name('memo.total-biaya-pj');
|
Route::post('total-biaya-pj', [MemoController::class, 'getTotalBiayaPJ'])->name('memo.total-biaya-pj');
|
||||||
Route::post('preview', [MemoController::class, 'preview'])->name('memo.preview');
|
Route::post('preview', [MemoController::class, 'preview'])->name('memo.preview');
|
||||||
Route::post('generate-pdf', [MemoController::class, 'generatePdf'])->name('memo.generate-pdf');
|
Route::post('generate-pdf', [MemoController::class, 'generatePdf'])->name('memo.generate-pdf');
|
||||||
|
// Route untuk download PDF memo penyelesaian
|
||||||
|
Route::get('/memo/download-pdf/{id}', [MemoController::class, 'downloadPdf'])
|
||||||
|
->name('memo.download-pdf');
|
||||||
});
|
});
|
||||||
Route::resource('memo', MemoController::class);
|
Route::resource('memo', MemoController::class);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user