From d95676d477b154780fd9ec3828e00a0a1462cae0 Mon Sep 17 00:00:00 2001 From: arifal Date: Thu, 10 Apr 2025 15:44:51 +0700 Subject: [PATCH] fix show page document pbg --- .../Api/PbgTaskAttachmentsController.php | 37 +- .../PbgTaskAttachmentsController.php | 21 + resources/js/pbg-task/index.js | 368 +++++++----------- .../views/pbg-task-attachment/show.blade.php | 50 +++ resources/views/pbg_task/index.blade.php | 2 - routes/api.php | 1 + routes/web.php | 2 + 7 files changed, 246 insertions(+), 235 deletions(-) create mode 100644 app/Http/Controllers/PbgTaskAttachmentsController.php create mode 100644 resources/views/pbg-task-attachment/show.blade.php diff --git a/app/Http/Controllers/Api/PbgTaskAttachmentsController.php b/app/Http/Controllers/Api/PbgTaskAttachmentsController.php index aa01c4b..c8316a6 100644 --- a/app/Http/Controllers/Api/PbgTaskAttachmentsController.php +++ b/app/Http/Controllers/Api/PbgTaskAttachmentsController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\PbgTaskAttachment; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; +use Symfony\Component\HttpFoundation\Response; class PbgTaskAttachmentsController extends Controller { @@ -28,14 +29,18 @@ class PbgTaskAttachmentsController extends Controller 'pbg_type' => 'string' ]); - $file = $request->file('file'); - $path = $file->store("uploads/pbg-tasks/{$pbg_task_id}", "public"); - $attachment = PbgTaskAttachment::create([ 'pbg_task_id' => $pbg_task_id, - 'file_name' => $file->getClientOriginalName(), + 'file_name' => $request->file('file')->getClientOriginalName(), + 'file_path' => '', // empty path initially + 'pbg_type' => $request->pbg_type == 'bukti_bayar' ? 'bukti_bayar' : 'berita_acara' + ]); + + $file = $request->file('file'); + $path = $file->store("uploads/pbg-tasks/{$pbg_task_id}/{$attachment->id}", "public"); + + $attachment->update([ 'file_path' => $path, - 'pbg_type' => $request->pbg_type ]); return response()->json([ @@ -79,4 +84,26 @@ class PbgTaskAttachmentsController extends Controller { // } + + public function download(string $id) + { + try { + $data = PbgTaskAttachment::findOrFail($id); + $filePath = $data->file_path; // already relative to 'public' disk + + if (!Storage::disk('public')->exists($filePath)) { + return response()->json([ + "success" => false, + "message" => "File not found on server" + ], Response::HTTP_NOT_FOUND); + } + + return Storage::disk('public')->download($filePath, $data->file_name); + } catch (\Exception $e) { + return response()->json([ + "success" => false, + "message" => $e->getMessage() + ]); + } + } } diff --git a/app/Http/Controllers/PbgTaskAttachmentsController.php b/app/Http/Controllers/PbgTaskAttachmentsController.php new file mode 100644 index 0000000..9dd4296 --- /dev/null +++ b/app/Http/Controllers/PbgTaskAttachmentsController.php @@ -0,0 +1,21 @@ +get('type') == "berita-acara" ? "Berita Acara" : "Bukti Bayar"; + $data = PbgTaskAttachment::findOrFail($id); + $pbg = PbgTask::findOrFail($data->pbg_task_id); + return view('pbg-task-attachment.show', compact('data', 'pbg', 'title')); + }catch(\Exception $e){ + return view('pages.404'); + } + } +} diff --git a/resources/js/pbg-task/index.js b/resources/js/pbg-task/index.js index 0c22e58..2e741aa 100644 --- a/resources/js/pbg-task/index.js +++ b/resources/js/pbg-task/index.js @@ -11,10 +11,31 @@ class PbgTasks { } init() { + this.setupFileUploadModal({ + modalId: "modalBuktiBayar", + dropzoneId: "dropzoneBuktiBayar", + uploadBtnClass: "upload-btn-bukti-bayar", + removeBtnId: "removeFileBtnBuktiBayar", + submitBtnId: "submitBuktiBayar", + fileNameSpanId: "uploadedFileNameBuktiBayar", + fileInfoId: "fileInfoBuktiBayar", + pbgType: "bukti_bayar", + bindFlag: "uploadHandlerBoundBuktiBayar", + }); + + this.setupFileUploadModal({ + modalId: "modalBeritaAcara", + dropzoneId: "dropzoneBeritaAcara", + uploadBtnClass: "upload-btn-berita-acara", + removeBtnId: "removeFileBtnBeritaAcara", + submitBtnId: "submitBeritaAcara", + fileNameSpanId: "uploadedFileNameBeritaAcara", + fileInfoId: "fileInfoBeritaAcara", + pbgType: "berita_acara", + bindFlag: "uploadHandlerBoundBeritaAcara", + }); this.initTableRequestAssignment(); this.handleSendNotification(); - this.handleUploadBuktiBayar(); - this.handleUploadBeritaAcara(); } initTableRequestAssignment() { @@ -58,10 +79,11 @@ class PbgTasks { ${ cell.attachment_berita_acara ? ` - - + style="white-space: nowrap; line-height: 1;" + target="_blank"> + Berita Acara ` @@ -78,10 +100,11 @@ class PbgTasks { ${ cell.attachment_bukti_bayar ? ` - - + style="white-space: nowrap; line-height: 1;" + target="_blank"> + Bukti Bayar ` @@ -173,95 +196,77 @@ class PbgTasks { }); } - handleUploadBuktiBayar() { - const modalEl = document.getElementById("modalBuktiBayar"); + setupFileUploadModal({ + modalId, + dropzoneId, + uploadBtnClass, + removeBtnId, + submitBtnId, + fileNameSpanId, + fileInfoId, + pbgType, + bindFlag, + }) { + const modalEl = document.getElementById(modalId); const modalInstance = new bootstrap.Modal(modalEl); - const modalTaskIdSpan = modalEl.querySelector("#modal-task-id span"); + let taskId; + // Blur-fix for modal modalEl.addEventListener("hide.bs.modal", () => { if ( document.activeElement && modalEl.contains(document.activeElement) ) { document.activeElement.blur(); - setTimeout(() => { - document.body.focus(); - }, 10); + setTimeout(() => document.body.focus(), 10); } }); - // Only bind once - if (!window.uploadHandlerBoundBuktiBayar) { - document.addEventListener("click", (event) => { - const uploadBtn = event.target.closest( - ".upload-btn-bukti-bayar" - ); - - if (uploadBtn) { - const taskId = uploadBtn.getAttribute("data-id"); - modalTaskIdSpan.textContent = taskId; - - // Set task ID on the form element so Dropzone can read it - document.getElementById( - "dropzoneBuktiBayar" - ).dataset.taskId = taskId; - + // Bind click listener only once + if (!window[bindFlag]) { + document.addEventListener("click", (e) => { + const btn = e.target.closest(`.${uploadBtnClass}`); + if (btn) { + taskId = btn.getAttribute("data-id"); modalInstance.show(); } }); - - window.uploadHandlerBoundBuktiBayar = true; + window[bindFlag] = true; } - // Prevent multiple Dropzone instances - if ( - !Dropzone.instances.some( - (dz) => dz.element.id === "dropzoneBuktiBayar" - ) - ) { + // Avoid reinitializing Dropzone + if (!Dropzone.instances.some((dz) => dz.element.id === dropzoneId)) { const self = this; - new Dropzone("#dropzoneBuktiBayar", { - url: function () { - const taskId = - document.getElementById("dropzoneBuktiBayar").dataset - .taskId; - return `/api/pbg-task-attachment/${taskId}`; - }, + + new Dropzone(`#${dropzoneId}`, { + url: () => `/api/pbg-task-attachment/${taskId}`, maxFiles: 1, - maxFilesize: 5, + maxFilesize: 5, // MB acceptedFiles: ".jpg,.png,.pdf", autoProcessQueue: false, - addRemoveLinks: false, - priviewsContainer: null, paramName: "file", headers: { "X-CSRF-TOKEN": document.querySelector( 'meta[name="csrf-token"]' ).content, - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, + Authorization: `Bearer ${ + document.querySelector('meta[name="api-token"]').content + }`, Accept: "application/json", }, - params: { - pbg_type: "bukti_bayar", - }, + params: { pbg_type: pbgType }, dictDefaultMessage: "Drop your file here or click to upload.", init: function () { const dz = this; - dz.on("addedfile", function (file) { - // Always ensure only one file - if (dz.files.length > 1) { - dz.removeFile(dz.files[0]); - } - // Small delay helps ensure UI updates even with same file + dz.on("addedfile", (file) => { + if (dz.files.length > 1) dz.removeFile(dz.files[0]); setTimeout(() => { document.getElementById( - "uploadedFileNameBuktiBayar" + fileNameSpanId ).textContent = file.name; document - .getElementById("fileInfoBuktiBayar") + .getElementById(fileInfoId) .classList.remove("d-none"); document .querySelector(".dz-message") @@ -269,26 +274,23 @@ class PbgTasks { }, 10); }); - dz.on("removedfile", function () { + dz.on("removedfile", () => { document - .getElementById("fileInfoBuktiBayar") + .getElementById(fileInfoId) .classList.add("d-none"); - document.getElementById( - "uploadedFileNameBuktiBayar" - ).textContent = ""; + document.getElementById(fileNameSpanId).textContent = + ""; document .querySelector(".dz-message") .classList.remove("d-none"); }); document - .getElementById("removeFileBtnBuktiBayar") - .addEventListener("click", function () { - dz.removeAllFiles(); - }); + .getElementById(removeBtnId) + .addEventListener("click", () => dz.removeAllFiles()); document - .getElementById("submitBuktiBayar") + .getElementById(submitBtnId) .addEventListener("click", () => { if (dz.getQueuedFiles().length > 0) { dz.processQueue(); @@ -301,20 +303,18 @@ class PbgTasks { dz.on("success", () => { dz.removeAllFiles(true); - // Reset UI document - .getElementById("fileInfoBuktiBayar") + .getElementById(fileInfoId) .classList.add("d-none"); - document.getElementById( - "uploadedFileNameBuktiBayar" - ).textContent = ""; + document.getElementById(fileNameSpanId).textContent = + ""; document.querySelector(".dz-message").style.display = "block"; - document.activeElement.blur(); // Lepas fokus dari tombol + document.activeElement.blur(); setTimeout(() => { - document.body.focus(); // Atau fokus ke tombol lain kalau mau - modalInstance.hide(); // Tutup modal SETELAH fokus dipindah - }, 50); // Delay singkat biar aman + document.body.focus(); + modalInstance.hide(); + }, 50); self.toastMessage.innerText = "File uploaded successfully!"; self.toast.show(); @@ -331,162 +331,74 @@ class PbgTasks { } } - handleUploadBeritaAcara() { - const modalEl = document.getElementById("modalBeritaAcara"); - const modalInstance = new bootstrap.Modal(modalEl); - const modalTaskIdSpan = modalEl.querySelector("#modal-task-id span"); + handleDownloadButtons(buttonClass) { + const buttons = document.querySelectorAll(`.${buttonClass}`); - modalEl.addEventListener("hide.bs.modal", () => { - if ( - document.activeElement && - modalEl.contains(document.activeElement) - ) { - document.activeElement.blur(); - setTimeout(() => { - document.body.focus(); - }, 10); - } - }); + buttons.forEach((button) => { + button.addEventListener("click", () => { + const attachmentId = button.getAttribute("data-id"); + const originalContent = button.innerHTML; - // Only bind once - if (!window.uploadHandlerBoundBeritaAcara) { - document.addEventListener("click", (event) => { - const uploadBtn = event.target.closest( - ".upload-btn-berita-acara" - ); + // Disable button & show loading + button.disabled = true; + button.innerHTML = ` + + Loading... + `; - if (uploadBtn) { - const taskId = uploadBtn.getAttribute("data-id"); - modalTaskIdSpan.textContent = taskId; + fetch(`/api/pbg-task-attachment/${attachmentId}/download`, { + method: "GET", + headers: { + "X-CSRF-TOKEN": document.querySelector( + 'meta[name="csrf-token"]' + ).content, + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + Accept: "application/json", + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error("File not found or server error."); + } + return response + .blob() + .then((blob) => ({ blob, response })); + }) + .then(({ blob, response }) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; - // Set task ID on the form element so Dropzone can read it - document.getElementById( - "dropzoneBeritaAcara" - ).dataset.taskId = taskId; + const contentDisposition = response.headers.get( + "Content-Disposition" + ); + let fileName = "downloaded-file"; - modalInstance.show(); - } - }); - - window.uploadHandlerBoundBeritaAcara = true; - } - - // Prevent multiple Dropzone instances - if ( - !Dropzone.instances.some( - (dz) => dz.element.id === "dropzoneBeritaAcara" - ) - ) { - const self = this; - new Dropzone("#dropzoneBeritaAcara", { - url: function () { - const taskId = document.getElementById( - "dropzoneBeritaAcara" - ).dataset.taskId; - return `/api/pbg-task-attachment/${taskId}`; - }, - maxFiles: 1, - maxFilesize: 5, - acceptedFiles: ".jpg,.png,.pdf", - autoProcessQueue: false, - addRemoveLinks: false, - priviewsContainer: null, - paramName: "file", - headers: { - "X-CSRF-TOKEN": document.querySelector( - 'meta[name="csrf-token"]' - ).content, - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, - Accept: "application/json", - }, - params: { - pbg_type: "berita_acara", - }, - dictDefaultMessage: "Drop your file here or click to upload.", - init: function () { - const dz = this; - dz.on("addedfile", function (file) { - // Always ensure only one file - if (dz.files.length > 1) { - dz.removeFile(dz.files[0]); + if (contentDisposition?.includes("filename=")) { + fileName = contentDisposition + .split("filename=")[1] + .replace(/"/g, "") + .trim(); } - // Small delay helps ensure UI updates even with same file - setTimeout(() => { - document.getElementById( - "uploadedFileNameBeritaAcara" - ).textContent = file.name; - document - .getElementById("fileInfoBeritaAcara") - .classList.remove("d-none"); - document - .querySelector(".dz-message") - .classList.add("d-none"); - }, 10); + a.download = fileName; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }) + .catch((error) => { + console.error("Download failed:", error); + alert("Failed to download file."); + }) + .finally(() => { + button.disabled = false; + button.innerHTML = originalContent; }); - - dz.on("removedfile", function () { - document - .getElementById("fileInfoBeritaAcara") - .classList.add("d-none"); - document.getElementById( - "uploadedFileNameBeritaAcara" - ).textContent = ""; - document - .querySelector(".dz-message") - .classList.remove("d-none"); - }); - - document - .getElementById("removeFileBtnBeritaAcara") - .addEventListener("click", function () { - dz.removeAllFiles(); - }); - - document - .getElementById("submitBeritaAcara") - .addEventListener("click", () => { - if (dz.getQueuedFiles().length > 0) { - dz.processQueue(); - } else { - self.toastMessage.innerText = - "Please select a file to upload."; - self.toast.show(); - } - }); - - dz.on("success", () => { - dz.removeAllFiles(true); - // Reset UI - document - .getElementById("fileInfoBeritaAcara") - .classList.add("d-none"); - document.getElementById( - "uploadedFileNameBeritaAcara" - ).textContent = ""; - document.querySelector(".dz-message").style.display = - "block"; - document.activeElement.blur(); // Lepas fokus dari tombol - setTimeout(() => { - document.body.focus(); // Atau fokus ke tombol lain kalau mau - modalInstance.hide(); // Tutup modal SETELAH fokus dipindah - }, 50); // Delay singkat biar aman - self.toastMessage.innerText = - "File uploaded successfully!"; - self.toast.show(); - self.initTableRequestAssignment(); - }); - - dz.on("error", (file, message) => { - self.toastMessage.innerText = - message || "Upload failed!"; - self.toast.show(); - }); - }, }); - } + }); } } diff --git a/resources/views/pbg-task-attachment/show.blade.php b/resources/views/pbg-task-attachment/show.blade.php new file mode 100644 index 0000000..ce45d58 --- /dev/null +++ b/resources/views/pbg-task-attachment/show.blade.php @@ -0,0 +1,50 @@ +@extends('layouts.vertical', ['subtitle' => $title]) + +@section('content') +@include('layouts.partials.page-title', ['title' => 'Data', 'subtitle' => 'PBG']) + +
+
+
+
+
{{ $title }}
+

Document Number: {{ $pbg->document_number }}

+
+
+
+
+ +
+
+
+
+ @php + $extension = strtolower(pathinfo($data->file_name, PATHINFO_EXTENSION)); + @endphp + + @if (in_array($extension, ['jpg', 'jpeg', 'png'])) +
+ {{ $data->file_name }} +
+ @elseif ($extension === 'pdf') + + @else +
+ Unsupported file type: {{ $extension }} +
+ @endif +
+
+
+
+@endsection diff --git a/resources/views/pbg_task/index.blade.php b/resources/views/pbg_task/index.blade.php index c8042ac..6ca54bc 100644 --- a/resources/views/pbg_task/index.blade.php +++ b/resources/views/pbg_task/index.blade.php @@ -99,7 +99,6 @@