add feat upload pbg task
This commit is contained in:
82
app/Http/Controllers/Api/PbgTaskAttachmentsController.php
Normal file
82
app/Http/Controllers/Api/PbgTaskAttachmentsController.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PbgTaskAttachment;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class PbgTaskAttachmentsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request, $pbg_task_id)
|
||||
{
|
||||
try{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:jpg,png,pdf|max:5120',
|
||||
'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_path' => $path,
|
||||
'pbg_type' => $request->pbg_type
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'File uploaded successfully.',
|
||||
'attachment' => [
|
||||
'id' => $attachment->id,
|
||||
'file_name' => $attachment->file_name,
|
||||
'file_url' => Storage::url($attachment->file_path),
|
||||
'pbg_type' => $attachment->pbg_type
|
||||
]
|
||||
]);
|
||||
}catch(\Exception $e){
|
||||
\Log::error($e->getMessage());
|
||||
return response()->json([
|
||||
"success" => false,
|
||||
"message" => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,18 @@ class RequestAssignmentController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = PbgTask::query()->orderBy('id', 'desc');
|
||||
if($request->has('search') && !empty($request->get("search"))){
|
||||
$query->where('name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('registration_number', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('document_number', 'LIKE', '%'.$request->get('search').'%');
|
||||
$query = PbgTask::with(['attachments' => function ($q) {
|
||||
$q->whereIn('pbg_type', ['berita_acara', 'bukti_bayar']);
|
||||
}])->orderBy('id', 'desc');
|
||||
|
||||
if ($request->has('search') && !empty($request->get("search"))) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'LIKE', '%' . $request->get('search') . '%')
|
||||
->orWhere('registration_number', 'LIKE', '%' . $request->get('search') . '%')
|
||||
->orWhere('document_number', 'LIKE', '%' . $request->get('search') . '%');
|
||||
});
|
||||
}
|
||||
|
||||
return RequestAssignmentResouce::collection($query->paginate());
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ class RequestAssignmentResouce extends JsonResource
|
||||
'due_date' => $this->due_date,
|
||||
'land_certificate_phase' => $this->land_certificate_phase,
|
||||
'task_created_at' => $this->task_created_at,
|
||||
'attachment_berita_acara' => $this->attachments
|
||||
->where('pbg_type', 'berita_acara')
|
||||
->sortByDesc('created_at')
|
||||
->first(),
|
||||
'attachment_bukti_bayar' => $this->attachments
|
||||
->where('pbg_type', 'bukti_bayar')
|
||||
->sortByDesc('created_at')
|
||||
->first(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,8 @@ class PbgTask extends Model
|
||||
{
|
||||
return $this->hasMany(TaskAssignment::class, 'pbg_task_uid', 'uuid');
|
||||
}
|
||||
|
||||
public function attachments(){
|
||||
return $this->hasMany(PbgTaskAttachment::class, 'pbg_task_id', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
14
app/Models/PbgTaskAttachment.php
Normal file
14
app/Models/PbgTaskAttachment.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PbgTaskAttachment extends Model
|
||||
{
|
||||
protected $fillable = ['pbg_task_id', 'file_name', 'file_path', 'pbg_type'];
|
||||
|
||||
public function task(){
|
||||
return $this->belongsTo(PbgTask::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::dropIfExists('pbg_task_attachments');
|
||||
Schema::create('pbg_task_attachments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('pbg_task_id')->constrained('pbg_task')->onDelete('cascade');
|
||||
$table->string('file_name');
|
||||
$table->string('file_path');
|
||||
$table->string('pbg_type');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pbg_task_attachments');
|
||||
}
|
||||
};
|
||||
@@ -1,63 +1,101 @@
|
||||
import { Grid } from "gridjs/dist/gridjs.umd.js";
|
||||
import "gridjs/dist/gridjs.umd.js";
|
||||
import gridjs from "gridjs/dist/gridjs.umd.js";
|
||||
import { Grid, html } from "gridjs/dist/gridjs.umd.js";
|
||||
import GlobalConfig from "../global-config";
|
||||
import { Dropzone } from "dropzone";
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
class PbgTasks {
|
||||
constructor() {
|
||||
this.table = null;
|
||||
this.toastMessage = document.getElementById("toast-message");
|
||||
this.toastElement = document.getElementById("toastNotification");
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initTableRequestAssignment();
|
||||
this.handleSendNotification();
|
||||
this.handleUpload();
|
||||
this.handleUploadBuktiBayar();
|
||||
this.handleUploadBeritaAcara();
|
||||
}
|
||||
|
||||
initTableRequestAssignment() {
|
||||
let tableContainer = document.getElementById("table-pbg-tasks");
|
||||
// Ambil token
|
||||
const token = document
|
||||
.querySelector('meta[name="api-token"]')
|
||||
.getAttribute("content");
|
||||
|
||||
// Pastikan kontainer kosong sebelum merender ulang Grid.js
|
||||
tableContainer.innerHTML = "";
|
||||
let canUpdate = tableContainer.getAttribute("data-updater") === "1";
|
||||
new Grid({
|
||||
const config = {
|
||||
columns: [
|
||||
"ID",
|
||||
{ name: "Name", width: "15%" },
|
||||
{ name: "Condition", width: "7%" },
|
||||
{ name: "Name" },
|
||||
{ name: "Condition" },
|
||||
"Registration Number",
|
||||
"Document Number",
|
||||
{ name: "Address", width: "30%" },
|
||||
{ name: "Address" },
|
||||
"Status",
|
||||
"Function Type",
|
||||
"Consultation Type",
|
||||
{ name: "Due Date", width: "10%" },
|
||||
{ name: "Due Date" },
|
||||
{
|
||||
name: "Action",
|
||||
formatter: (cell) => {
|
||||
let tableContainer =
|
||||
document.getElementById("table-pbg-tasks");
|
||||
let canUpdate =
|
||||
tableContainer.getAttribute("data-updater") === "1";
|
||||
|
||||
if (!canUpdate) {
|
||||
return gridjs.html(
|
||||
return html(
|
||||
`<span class="text-muted">No Privilege</span>`
|
||||
);
|
||||
}
|
||||
|
||||
return gridjs.html(`
|
||||
<div class="d-flex justify-content-center align-items-center gap-2">
|
||||
<a href="/pbg-task/${cell}" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center">
|
||||
Detail
|
||||
</a>
|
||||
<button class="btn btn-sm btn-info upload-btn" data-id="${cell}">
|
||||
Upload Bukti Bayar
|
||||
</button>
|
||||
<button class="btn btn-sm btn-info upload-btn-berita-acara" data-id="${cell}">
|
||||
Buat Berita Acara
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
return html(`
|
||||
<div class="d-flex justify-content-center align-items-center gap-2">
|
||||
<a href="/pbg-task/${cell.id}"
|
||||
class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"
|
||||
style="white-space: nowrap; line-height: 1;">
|
||||
<iconify-icon icon="mingcute:eye-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
|
||||
</a>
|
||||
|
||||
${
|
||||
cell.attachment_berita_acara
|
||||
? `
|
||||
<a href="/storage/${cell.attachment_berita_acara.file_path}" target="_blank"
|
||||
class="btn btn-success btn-sm d-inline-flex align-items-center justify-content-center"
|
||||
style="white-space: nowrap; line-height: 1;">
|
||||
<iconify-icon icon="mingcute:download-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
|
||||
<span class="ms-1">Berita Acara</span>
|
||||
</a>
|
||||
`
|
||||
: `
|
||||
<button class="btn btn-sm btn-info d-inline-flex align-items-center justify-content-center upload-btn-berita-acara"
|
||||
data-id="${cell.id}"
|
||||
style="white-space: nowrap; line-height: 1;">
|
||||
<iconify-icon icon="mingcute:upload-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
|
||||
<span class="ms-1" style="line-height: 1;">Berita Acara</span>
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
${
|
||||
cell.attachment_bukti_bayar
|
||||
? `
|
||||
<a href="/storage/${cell.attachment_bukti_bayar.file_path}" target="_blank"
|
||||
class="btn btn-success btn-sm d-inline-flex align-items-center justify-content-center"
|
||||
style="white-space: nowrap; line-height: 1;">
|
||||
<iconify-icon icon="mingcute:download-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
|
||||
<span class="ms-1">Bukti Bayar</span>
|
||||
</a>
|
||||
`
|
||||
: `
|
||||
<button class="btn btn-sm btn-info d-inline-flex align-items-center justify-content-center upload-btn-bukti-bayar"
|
||||
data-id="${cell.id}"
|
||||
style="white-space: nowrap; line-height: 1;">
|
||||
<iconify-icon icon="mingcute:upload-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
|
||||
<span class="ms-1" style="line-height: 1;">Bukti Bayar</span>
|
||||
</button>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -81,9 +119,7 @@ class PbgTasks {
|
||||
url: `${GlobalConfig.apiHost}/api/request-assignments`,
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Authorization: `Bearer ${document
|
||||
.querySelector('meta[name="api-token"]')
|
||||
.getAttribute("content")}`,
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
then: (data) =>
|
||||
@@ -98,11 +134,20 @@ class PbgTasks {
|
||||
item.function_type,
|
||||
item.consultation_type,
|
||||
item.due_date,
|
||||
item.id,
|
||||
item,
|
||||
]),
|
||||
total: (data) => data.meta.total,
|
||||
},
|
||||
}).render(document.getElementById("table-pbg-tasks"));
|
||||
};
|
||||
|
||||
const tableContainer = document.getElementById("table-pbg-tasks");
|
||||
|
||||
if (this.table) {
|
||||
this.table.updateConfig(config).forceRender();
|
||||
} else {
|
||||
tableContainer.innerHTML = "";
|
||||
this.table = new Grid(config).render(tableContainer);
|
||||
}
|
||||
}
|
||||
|
||||
handleSendNotification() {
|
||||
@@ -128,71 +173,320 @@ class PbgTasks {
|
||||
});
|
||||
}
|
||||
|
||||
handleUpload() {
|
||||
// Handle button click to show modal
|
||||
document.addEventListener("click", function (event) {
|
||||
if (event.target.classList.contains("upload-btn")) {
|
||||
// Show modal
|
||||
let uploadModal = new bootstrap.Modal(
|
||||
document.getElementById("uploadModal")
|
||||
);
|
||||
uploadModal.show();
|
||||
handleUploadBuktiBayar() {
|
||||
const modalEl = document.getElementById("modalBuktiBayar");
|
||||
const modalInstance = new bootstrap.Modal(modalEl);
|
||||
const modalTaskIdSpan = modalEl.querySelector("#modal-task-id span");
|
||||
|
||||
modalEl.addEventListener("hide.bs.modal", () => {
|
||||
if (
|
||||
document.activeElement &&
|
||||
modalEl.contains(document.activeElement)
|
||||
) {
|
||||
document.activeElement.blur();
|
||||
setTimeout(() => {
|
||||
document.body.focus();
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
let dropzone = new Dropzone("#singleFileDropzone", {
|
||||
url: "/upload-bukti-bayar", // Adjust to your backend endpoint
|
||||
maxFiles: 1, // Allow only 1 file
|
||||
maxFilesize: 5, // Limit size to 5MB
|
||||
acceptedFiles: ".jpg,.png,.pdf", // Allowed file types
|
||||
autoProcessQueue: false, // Prevent automatic upload
|
||||
addRemoveLinks: true, // Show remove button
|
||||
dictDefaultMessage: "Drop your file here or click to upload.",
|
||||
init: function () {
|
||||
let dz = this;
|
||||
|
||||
// Remove previous file when a new file is added
|
||||
dz.on("addedfile", function () {
|
||||
if (dz.files.length > 1) {
|
||||
dz.removeFile(dz.files[0]);
|
||||
}
|
||||
});
|
||||
// Only bind once
|
||||
if (!window.uploadHandlerBoundBuktiBayar) {
|
||||
document.addEventListener("click", (event) => {
|
||||
const uploadBtn = event.target.closest(
|
||||
".upload-btn-bukti-bayar"
|
||||
);
|
||||
|
||||
// Handle upload button click
|
||||
document
|
||||
.getElementById("uploadBtn")
|
||||
.addEventListener("click", function () {
|
||||
if (dz.getQueuedFiles().length > 0) {
|
||||
dz.processQueue(); // Manually process upload
|
||||
} else {
|
||||
alert("Please select a file to upload.");
|
||||
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;
|
||||
|
||||
modalInstance.show();
|
||||
}
|
||||
});
|
||||
|
||||
window.uploadHandlerBoundBuktiBayar = true;
|
||||
}
|
||||
|
||||
// Prevent multiple Dropzone instances
|
||||
if (
|
||||
!Dropzone.instances.some(
|
||||
(dz) => dz.element.id === "dropzoneBuktiBayar"
|
||||
)
|
||||
) {
|
||||
const self = this;
|
||||
new Dropzone("#dropzoneBuktiBayar", {
|
||||
url: function () {
|
||||
const taskId =
|
||||
document.getElementById("dropzoneBuktiBayar").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: "bukti_bayar",
|
||||
},
|
||||
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
|
||||
setTimeout(() => {
|
||||
document.getElementById(
|
||||
"uploadedFileNameBuktiBayar"
|
||||
).textContent = file.name;
|
||||
document
|
||||
.getElementById("fileInfoBuktiBayar")
|
||||
.classList.remove("d-none");
|
||||
document
|
||||
.querySelector(".dz-message")
|
||||
.classList.add("d-none");
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// Success callback
|
||||
dz.on("success", function (file, response) {
|
||||
alert("File uploaded successfully!");
|
||||
dz.removeAllFiles(); // Clear after upload
|
||||
});
|
||||
dz.on("removedfile", function () {
|
||||
document
|
||||
.getElementById("fileInfoBuktiBayar")
|
||||
.classList.add("d-none");
|
||||
document.getElementById(
|
||||
"uploadedFileNameBuktiBayar"
|
||||
).textContent = "";
|
||||
document
|
||||
.querySelector(".dz-message")
|
||||
.classList.remove("d-none");
|
||||
});
|
||||
|
||||
// Error callback
|
||||
dz.on("error", function (file, errorMessage) {
|
||||
alert("Upload failed: " + errorMessage);
|
||||
});
|
||||
},
|
||||
});
|
||||
document
|
||||
.getElementById("removeFileBtnBuktiBayar")
|
||||
.addEventListener("click", function () {
|
||||
dz.removeAllFiles();
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("submitBuktiBayar")
|
||||
.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("fileInfoBuktiBayar")
|
||||
.classList.add("d-none");
|
||||
document.getElementById(
|
||||
"uploadedFileNameBuktiBayar"
|
||||
).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();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleUploadBeritaAcara() {
|
||||
// Handle button click to show modal
|
||||
document.addEventListener("click", function (event) {
|
||||
if (event.target.classList.contains("upload-btn-berita-acara")) {
|
||||
// Show modal
|
||||
let uploadModal = new bootstrap.Modal(
|
||||
document.getElementById("uploadBeritaAcara")
|
||||
);
|
||||
uploadModal.show();
|
||||
const modalEl = document.getElementById("modalBeritaAcara");
|
||||
const modalInstance = new bootstrap.Modal(modalEl);
|
||||
const modalTaskIdSpan = modalEl.querySelector("#modal-task-id span");
|
||||
|
||||
modalEl.addEventListener("hide.bs.modal", () => {
|
||||
if (
|
||||
document.activeElement &&
|
||||
modalEl.contains(document.activeElement)
|
||||
) {
|
||||
document.activeElement.blur();
|
||||
setTimeout(() => {
|
||||
document.body.focus();
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
|
||||
// Only bind once
|
||||
if (!window.uploadHandlerBoundBeritaAcara) {
|
||||
document.addEventListener("click", (event) => {
|
||||
const uploadBtn = event.target.closest(
|
||||
".upload-btn-berita-acara"
|
||||
);
|
||||
|
||||
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(
|
||||
"dropzoneBeritaAcara"
|
||||
).dataset.taskId = taskId;
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
|
||||
@section('css')
|
||||
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
|
||||
<style>
|
||||
#dropzoneBuktiBayar .dz-preview{
|
||||
display: none;
|
||||
}
|
||||
#dropzoneBeritaAcara .dz-preview{
|
||||
display: none;
|
||||
}
|
||||
.file-info-overlay {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
@@ -71,7 +91,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="modalBuktiBayar" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -79,21 +99,27 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="modal-task-id">Task ID: <span></span></p>
|
||||
<div class="mb-3">
|
||||
<form action="/upload-bukti-bayar" method="POST" class="dropzone" id="singleFileDropzone">
|
||||
<div class="dz-message needsclick">
|
||||
<i class="h1 bx bx-cloud-upload"></i>
|
||||
<h3>Drop file here or click to upload.</h3>
|
||||
<span class="text-muted fs-13">
|
||||
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
||||
</span>
|
||||
</div>
|
||||
<form action="/upload-bukti-bayar" method="POST" class="dropzone" id="dropzoneBuktiBayar">
|
||||
<div class="dz-message needsclick">
|
||||
<i class="h1 bx bx-cloud-upload"></i>
|
||||
<h3>Drop file here or click to upload.</h3>
|
||||
<span class="text-muted fs-13">
|
||||
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
||||
</span>
|
||||
</div>
|
||||
<!-- File info inside dropzone -->
|
||||
<div id="fileInfoBuktiBayar" class="file-info-overlay d-none">
|
||||
<span id="uploadedFileNameBuktiBayar" class="text-muted me-3"></span>
|
||||
<button type="button" id="removeFileBtnBuktiBayar" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" id="uploadBtn" class="btn btn-success">
|
||||
<button type="button" id="submitBuktiBayar" class="btn btn-success">
|
||||
<i class="bx bx-upload"></i> Upload
|
||||
</button>
|
||||
</div>
|
||||
@@ -103,31 +129,37 @@
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="uploadBeritaAcara" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="modalBeritaAcara" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Upload Berita Acara</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<h5 class="modal-title">Upload Berita Acara</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="modal-task-id">Task ID: <span></span></p>
|
||||
<div class="mb-3">
|
||||
<form action="/upload-berita-acara" method="POST" class="dropzone" id="singleFileDropzone">
|
||||
<div class="dz-message needsclick">
|
||||
<i class="h1 bx bx-cloud-upload"></i>
|
||||
<h3>Drop file here or click to upload.</h3>
|
||||
<span class="text-muted fs-13">
|
||||
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<form action="/upload-berita-acara" method="POST" class="dropzone" id="dropzoneBeritaAcara">
|
||||
<div class="dz-message needsclick">
|
||||
<i class="h1 bx bx-cloud-upload"></i>
|
||||
<h3>Drop file here or click to upload.</h3>
|
||||
<span class="text-muted fs-13">
|
||||
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
||||
</span>
|
||||
</div>
|
||||
<!-- File info inside dropzone -->
|
||||
<div id="fileInfoBeritaAcara" class="file-info-overlay d-none">
|
||||
<span id="uploadedFileNameBeritaAcara" class="text-muted me-3"></span>
|
||||
<button type="button" id="removeFileBtnBeritaAcara" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" id="uploadBeritaAcara" class="btn btn-success">
|
||||
<i class="bx bx-upload"></i> Upload
|
||||
</button>
|
||||
<button type="button" id="submitBeritaAcara" class="btn btn-success">
|
||||
<i class="bx bx-upload"></i> Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Http\Controllers\Api\GoogleSheetController;
|
||||
use App\Http\Controllers\Api\ImportDatasourceController;
|
||||
use App\Http\Controllers\Api\LackOfPotentialController;
|
||||
use App\Http\Controllers\Api\MenusController;
|
||||
use App\Http\Controllers\Api\PbgTaskAttachmentsController;
|
||||
use App\Http\Controllers\Api\PbgTaskController;
|
||||
use App\Http\Controllers\Api\PbgTaskGoogleSheetsController;
|
||||
use App\Http\Controllers\Api\ReportPbgPtspController;
|
||||
@@ -181,4 +182,8 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
|
||||
Route::get('/report-ptsp/excel', 'export_excel')->name('api.report-ptsp.excel');
|
||||
Route::get('/report-ptsp/pdf', 'export_pdf')->name('api.report-ptsp.pdf');
|
||||
});
|
||||
|
||||
Route::controller(PbgTaskAttachmentsController::class)->group(function (){
|
||||
Route::post('/pbg-task-attachment/{pbg_task_id}', 'store')->name('api.pbg-task.upload');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user