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)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$query = PbgTask::query()->orderBy('id', 'desc');
|
$query = PbgTask::with(['attachments' => function ($q) {
|
||||||
if($request->has('search') && !empty($request->get("search"))){
|
$q->whereIn('pbg_type', ['berita_acara', 'bukti_bayar']);
|
||||||
$query->where('name', 'LIKE', '%'.$request->get('search').'%')
|
}])->orderBy('id', 'desc');
|
||||||
->orWhere('registration_number', 'LIKE', '%'.$request->get('search').'%')
|
|
||||||
->orWhere('document_number', 'LIKE', '%'.$request->get('search').'%');
|
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());
|
return RequestAssignmentResouce::collection($query->paginate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ class RequestAssignmentResouce extends JsonResource
|
|||||||
'due_date' => $this->due_date,
|
'due_date' => $this->due_date,
|
||||||
'land_certificate_phase' => $this->land_certificate_phase,
|
'land_certificate_phase' => $this->land_certificate_phase,
|
||||||
'task_created_at' => $this->task_created_at,
|
'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');
|
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 { Grid, html } from "gridjs/dist/gridjs.umd.js";
|
||||||
import "gridjs/dist/gridjs.umd.js";
|
|
||||||
import gridjs from "gridjs/dist/gridjs.umd.js";
|
|
||||||
import GlobalConfig from "../global-config";
|
import GlobalConfig from "../global-config";
|
||||||
import { Dropzone } from "dropzone";
|
import { Dropzone } from "dropzone";
|
||||||
Dropzone.autoDiscover = false;
|
Dropzone.autoDiscover = false;
|
||||||
|
|
||||||
class PbgTasks {
|
class PbgTasks {
|
||||||
|
constructor() {
|
||||||
|
this.table = null;
|
||||||
|
this.toastMessage = document.getElementById("toast-message");
|
||||||
|
this.toastElement = document.getElementById("toastNotification");
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.initTableRequestAssignment();
|
this.initTableRequestAssignment();
|
||||||
this.handleSendNotification();
|
this.handleSendNotification();
|
||||||
this.handleUpload();
|
this.handleUploadBuktiBayar();
|
||||||
this.handleUploadBeritaAcara();
|
this.handleUploadBeritaAcara();
|
||||||
}
|
}
|
||||||
|
|
||||||
initTableRequestAssignment() {
|
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
|
const config = {
|
||||||
tableContainer.innerHTML = "";
|
|
||||||
let canUpdate = tableContainer.getAttribute("data-updater") === "1";
|
|
||||||
new Grid({
|
|
||||||
columns: [
|
columns: [
|
||||||
"ID",
|
"ID",
|
||||||
{ name: "Name", width: "15%" },
|
{ name: "Name" },
|
||||||
{ name: "Condition", width: "7%" },
|
{ name: "Condition" },
|
||||||
"Registration Number",
|
"Registration Number",
|
||||||
"Document Number",
|
"Document Number",
|
||||||
{ name: "Address", width: "30%" },
|
{ name: "Address" },
|
||||||
"Status",
|
"Status",
|
||||||
"Function Type",
|
"Function Type",
|
||||||
"Consultation Type",
|
"Consultation Type",
|
||||||
{ name: "Due Date", width: "10%" },
|
{ name: "Due Date" },
|
||||||
{
|
{
|
||||||
name: "Action",
|
name: "Action",
|
||||||
formatter: (cell) => {
|
formatter: (cell) => {
|
||||||
let tableContainer =
|
|
||||||
document.getElementById("table-pbg-tasks");
|
|
||||||
let canUpdate =
|
let canUpdate =
|
||||||
tableContainer.getAttribute("data-updater") === "1";
|
tableContainer.getAttribute("data-updater") === "1";
|
||||||
|
|
||||||
if (!canUpdate) {
|
if (!canUpdate) {
|
||||||
return gridjs.html(
|
return html(
|
||||||
`<span class="text-muted">No Privilege</span>`
|
`<span class="text-muted">No Privilege</span>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return gridjs.html(`
|
return html(`
|
||||||
<div class="d-flex justify-content-center align-items-center gap-2">
|
<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">
|
<a href="/pbg-task/${cell.id}"
|
||||||
Detail
|
class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"
|
||||||
</a>
|
style="white-space: nowrap; line-height: 1;">
|
||||||
<button class="btn btn-sm btn-info upload-btn" data-id="${cell}">
|
<iconify-icon icon="mingcute:eye-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
|
||||||
Upload Bukti Bayar
|
</a>
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-info upload-btn-berita-acara" data-id="${cell}">
|
${
|
||||||
Buat Berita Acara
|
cell.attachment_berita_acara
|
||||||
</button>
|
? `
|
||||||
</div>
|
<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`,
|
url: `${GlobalConfig.apiHost}/api/request-assignments`,
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${document
|
Authorization: `Bearer ${token}`,
|
||||||
.querySelector('meta[name="api-token"]')
|
|
||||||
.getAttribute("content")}`,
|
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
then: (data) =>
|
then: (data) =>
|
||||||
@@ -98,11 +134,20 @@ class PbgTasks {
|
|||||||
item.function_type,
|
item.function_type,
|
||||||
item.consultation_type,
|
item.consultation_type,
|
||||||
item.due_date,
|
item.due_date,
|
||||||
item.id,
|
item,
|
||||||
]),
|
]),
|
||||||
total: (data) => data.meta.total,
|
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() {
|
handleSendNotification() {
|
||||||
@@ -128,71 +173,320 @@ class PbgTasks {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpload() {
|
handleUploadBuktiBayar() {
|
||||||
// Handle button click to show modal
|
const modalEl = document.getElementById("modalBuktiBayar");
|
||||||
document.addEventListener("click", function (event) {
|
const modalInstance = new bootstrap.Modal(modalEl);
|
||||||
if (event.target.classList.contains("upload-btn")) {
|
const modalTaskIdSpan = modalEl.querySelector("#modal-task-id span");
|
||||||
// Show modal
|
|
||||||
let uploadModal = new bootstrap.Modal(
|
modalEl.addEventListener("hide.bs.modal", () => {
|
||||||
document.getElementById("uploadModal")
|
if (
|
||||||
);
|
document.activeElement &&
|
||||||
uploadModal.show();
|
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
|
// Only bind once
|
||||||
dz.on("addedfile", function () {
|
if (!window.uploadHandlerBoundBuktiBayar) {
|
||||||
if (dz.files.length > 1) {
|
document.addEventListener("click", (event) => {
|
||||||
dz.removeFile(dz.files[0]);
|
const uploadBtn = event.target.closest(
|
||||||
}
|
".upload-btn-bukti-bayar"
|
||||||
});
|
);
|
||||||
|
|
||||||
// Handle upload button click
|
if (uploadBtn) {
|
||||||
document
|
const taskId = uploadBtn.getAttribute("data-id");
|
||||||
.getElementById("uploadBtn")
|
modalTaskIdSpan.textContent = taskId;
|
||||||
.addEventListener("click", function () {
|
|
||||||
if (dz.getQueuedFiles().length > 0) {
|
// Set task ID on the form element so Dropzone can read it
|
||||||
dz.processQueue(); // Manually process upload
|
document.getElementById(
|
||||||
} else {
|
"dropzoneBuktiBayar"
|
||||||
alert("Please select a file to upload.");
|
).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("removedfile", function () {
|
||||||
dz.on("success", function (file, response) {
|
document
|
||||||
alert("File uploaded successfully!");
|
.getElementById("fileInfoBuktiBayar")
|
||||||
dz.removeAllFiles(); // Clear after upload
|
.classList.add("d-none");
|
||||||
});
|
document.getElementById(
|
||||||
|
"uploadedFileNameBuktiBayar"
|
||||||
|
).textContent = "";
|
||||||
|
document
|
||||||
|
.querySelector(".dz-message")
|
||||||
|
.classList.remove("d-none");
|
||||||
|
});
|
||||||
|
|
||||||
// Error callback
|
document
|
||||||
dz.on("error", function (file, errorMessage) {
|
.getElementById("removeFileBtnBuktiBayar")
|
||||||
alert("Upload failed: " + errorMessage);
|
.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() {
|
handleUploadBeritaAcara() {
|
||||||
// Handle button click to show modal
|
const modalEl = document.getElementById("modalBeritaAcara");
|
||||||
document.addEventListener("click", function (event) {
|
const modalInstance = new bootstrap.Modal(modalEl);
|
||||||
if (event.target.classList.contains("upload-btn-berita-acara")) {
|
const modalTaskIdSpan = modalEl.querySelector("#modal-task-id span");
|
||||||
// Show modal
|
|
||||||
let uploadModal = new bootstrap.Modal(
|
modalEl.addEventListener("hide.bs.modal", () => {
|
||||||
document.getElementById("uploadBeritaAcara")
|
if (
|
||||||
);
|
document.activeElement &&
|
||||||
uploadModal.show();
|
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')
|
@section('css')
|
||||||
@vite(['node_modules/gridjs/dist/theme/mermaid.min.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
|
@endsection
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@@ -71,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- 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-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -79,21 +99,27 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<p id="modal-task-id">Task ID: <span></span></p>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<form action="/upload-bukti-bayar" method="POST" class="dropzone" id="singleFileDropzone">
|
<form action="/upload-bukti-bayar" method="POST" class="dropzone" id="dropzoneBuktiBayar">
|
||||||
<div class="dz-message needsclick">
|
<div class="dz-message needsclick">
|
||||||
<i class="h1 bx bx-cloud-upload"></i>
|
<i class="h1 bx bx-cloud-upload"></i>
|
||||||
<h3>Drop file here or click to upload.</h3>
|
<h3>Drop file here or click to upload.</h3>
|
||||||
<span class="text-muted fs-13">
|
<span class="text-muted fs-13">
|
||||||
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<div class="d-flex justify-content-end">
|
<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
|
<i class="bx bx-upload"></i> Upload
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,31 +129,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- 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-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Upload Berita Acara</h5>
|
<h5 class="modal-title">Upload Berita Acara</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<p id="modal-task-id">Task ID: <span></span></p>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<form action="/upload-berita-acara" method="POST" class="dropzone" id="singleFileDropzone">
|
<form action="/upload-berita-acara" method="POST" class="dropzone" id="dropzoneBeritaAcara">
|
||||||
<div class="dz-message needsclick">
|
<div class="dz-message needsclick">
|
||||||
<i class="h1 bx bx-cloud-upload"></i>
|
<i class="h1 bx bx-cloud-upload"></i>
|
||||||
<h3>Drop file here or click to upload.</h3>
|
<h3>Drop file here or click to upload.</h3>
|
||||||
<span class="text-muted fs-13">
|
<span class="text-muted fs-13">
|
||||||
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
(Only one file allowed. Selected file will be uploaded upon clicking submit.)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<!-- 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>
|
</div>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<button type="button" id="uploadBeritaAcara" class="btn btn-success">
|
<button type="button" id="submitBeritaAcara" class="btn btn-success">
|
||||||
<i class="bx bx-upload"></i> Upload
|
<i class="bx bx-upload"></i> Upload
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Http\Controllers\Api\GoogleSheetController;
|
|||||||
use App\Http\Controllers\Api\ImportDatasourceController;
|
use App\Http\Controllers\Api\ImportDatasourceController;
|
||||||
use App\Http\Controllers\Api\LackOfPotentialController;
|
use App\Http\Controllers\Api\LackOfPotentialController;
|
||||||
use App\Http\Controllers\Api\MenusController;
|
use App\Http\Controllers\Api\MenusController;
|
||||||
|
use App\Http\Controllers\Api\PbgTaskAttachmentsController;
|
||||||
use App\Http\Controllers\Api\PbgTaskController;
|
use App\Http\Controllers\Api\PbgTaskController;
|
||||||
use App\Http\Controllers\Api\PbgTaskGoogleSheetsController;
|
use App\Http\Controllers\Api\PbgTaskGoogleSheetsController;
|
||||||
use App\Http\Controllers\Api\ReportPbgPtspController;
|
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/excel', 'export_excel')->name('api.report-ptsp.excel');
|
||||||
Route::get('/report-ptsp/pdf', 'export_pdf')->name('api.report-ptsp.pdf');
|
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