done project directory -> update ke bu panca

This commit is contained in:
JokoPrasetio 2025-09-15 14:10:24 +07:00
parent ed5695b791
commit 14216d5784
9 changed files with 312 additions and 189 deletions

View File

@ -62,7 +62,12 @@ class DashboardController extends Controller
->when($subArray, fn($q) => $q->whereIn('id_sub_unit_kerja', $subArray))
->when($katArray, fn($q) => $q->whereIn('master_kategori_directory_id', $katArray))
->when($klaArray, fn($q) => $q->whereIn('master_klasifikasi_directory_id', $klaArray))
->when($keyword, fn($q) => $q->where('file', 'ilike', "%{$keyword}%")->orWhere('pegawai_nama_entry', 'ilike', "%{$keyword}%"))
->when($keyword, fn($q) =>
$q->where(function($query) use ($keyword) {
$query->where('file', 'ilike', "%{$keyword}%")
->orWhere('pegawai_nama_entry', 'ilike', "%{$keyword}%");
})
)
])
])
->select('id', 'name')
@ -87,8 +92,10 @@ class DashboardController extends Controller
->with([ // muat relasi
'subUnitKerja' => fn($q) => $q->with([ // sub-unit
'fileDirectory' => fn($f) => $f->when($keyword, fn($q) =>
$q->where('file', 'ilike', "%{$keyword}%")
->orWhere('pegawai_nama_entry', 'ilike', "%{$keyword}%")
$q->where(function($query) use ($keyword) {
$query->where('file', 'ilike', "%{$keyword}%")
->orWhere('pegawai_nama_entry', 'ilike', "%{$keyword}%");
})
)
])
])
@ -106,7 +113,10 @@ class DashboardController extends Controller
'subUnitKerja' => fn($q) => $q->where('id', $authSub)
->with([ // 2. file-directory + filter keyword
'fileDirectory' => fn($f) => $f->when($keyword, fn($q) =>
$q->where('file', 'like', "%{$keyword}%")->orWhere('pegawai_nama_entry', 'ilike', "%{$keyword}%")
$q->where(function($query) use ($keyword) {
$query->where('file', 'ilike', "%{$keyword}%")
->orWhere('pegawai_nama_entry', 'ilike', "%{$keyword}%");
})
)
])
])
@ -130,26 +140,30 @@ class DashboardController extends Controller
$datas = request()->input('data');
$result = [];
foreach($datas as $index => $value){
$file = request()->file("data.$index.file");
list($id_unit_kerja, $nama_unit_kerja) = explode('/', $value['id_unit_kerja'],2);
list($id_sub_unit_kerja, $nama_sub_unit_kerja) = explode('/', $value['id_sub_unit_kerja'],2);
list($master_kategori_directory_id, $nama_kategori) = explode('/', $value['master_kategori_directory_id'],2);
list($klasifikasi, $nama_klasifikasi) = explode('/', $value['klasifikasi'],2);
$payload = [
'master_klasifikasi_directory_id' => $klasifikasi,
'id_unit_kerja' => $id_unit_kerja,
'id_sub_unit_kerja' => $id_sub_unit_kerja,
'master_kategori_directory_id' => $master_kategori_directory_id,
'pegawai_id_entry' => auth()->user()->id ?? 1,
'pegawai_nama_entry' => auth()->user()->namalengkap ?? 'tes',
];
if($file){
$imageName = $file->getClientOriginalName();
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}/{$nama_klasifikasi}";
$file->storeAs($path, $imageName, 'file_directory');
$payload['file'] =$path .'/' .$imageName;
$files = request()->file("data.fileUpload$index");
if(!empty($files)){
foreach ($files['file'] as $file) {
list($id_unit_kerja, $nama_unit_kerja) = explode('/', $value['id_unit_kerja'],2);
list($id_sub_unit_kerja, $nama_sub_unit_kerja) = explode('/', $value['id_sub_unit_kerja'],2);
list($master_kategori_directory_id, $nama_kategori) = explode('/', $value['master_kategori_directory_id'],2);
list($klasifikasi, $nama_klasifikasi) = explode('/', $value['klasifikasi'],2);
$payload = [
'master_klasifikasi_directory_id' => $klasifikasi,
'id_unit_kerja' => $id_unit_kerja,
'id_sub_unit_kerja' => $id_sub_unit_kerja,
'master_kategori_directory_id' => $master_kategori_directory_id,
'pegawai_id_entry' => auth()->user()->dataUser->id ?? 1,
'pegawai_nama_entry' => auth()->user()->dataUser->namalengkap ?? 'tes',
];
if($file){
$imageName = $file->getClientOriginalName();
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}/{$nama_klasifikasi}";
$file->storeAs($path, $imageName, 'file_directory');
$payload['file'] =$path .'/' .$imageName;
}
FileDirectory::create($payload);
}
}
FileDirectory::create($payload);
}
DB::connection('dbDirectory')->commit();
return response()->json([

View File

@ -1,57 +1,133 @@
let allFiles = [];
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".file-input").forEach(input => {
bindFileUpload(input);
});
});
formCreate.on('submit', function(e){
e.preventDefault();
function bindFileUpload(input) {
const dropArea = input.closest(".file-drop-area");
const fileNameBox = dropArea.querySelector(".file-name");
const inputId = input?.id;
allFiles[inputId] = [];
input.addEventListener("change", function () {
for (let i = 0; i < this.files.length; i++) {
allFiles[inputId].push(this.files[i]);
}
renderFileList(inputId, fileNameBox);
this.value = ""; // reset agar bisa pilih file lagi
});
}
const form = this;
const formData = new FormData(form);
fetch(`/upload`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value,
},
body: formData
}).then(async(res) => {
const responseData = await res.json();
if (responseData.status) {
const handler = function () {
Toastify({
text: responseData.message || 'Berhasil melakukan aksi!',
duration: 3000,
gravity: "top", // bisa "bottom"
position: "right", // bisa "left"
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)", // hijau gradasi
color: "#fff",
}
}).showToast();
$("#col_add_file").html('');
colCount = 1; // reset counter
formCreate.find('input[type="text"], input[type="file"]').val('');
formCreate.find('select').val(null).trigger('change');
if($("#klasifikasi_dok").val().length === 0 || $("#kategori_dok").val().length === 0 ){
index()
}else{
searchData()
}
modalCreate.removeEventListener('hidden.bs.modal', handler);
};
modalCreate.addEventListener('hidden.bs.modal', handler);
bootstrap.Modal.getInstance(modalCreate).hide();
} else {
throw new Error(responseData.message || 'Terjadi kesalahan saat menyimpan data.');
function renderFileList(inputId, container) {
const files = allFiles[inputId];
if (!files || files.length === 0) {
container.classList.add("d-none");
container.innerHTML = "";
return;
}
}).catch(err => {
if (err.message) {
let list = "<ul class='list-unstyled mb-0'>";
files.forEach((file, index) => {
list += `
<li class="d-flex justify-content-between align-items-center">
<span> ${file.name}</span>
<button type="button" class="btn btn-sm btn-danger ms-2"
onclick="removeFile('${inputId}', ${index})"></button>
</li>`;
});
list += "</ul>";
container.innerHTML = list;
container.classList.remove("d-none");
}
function removeFile(inputId, index) {
allFiles[inputId].splice(index, 1);
const container = document.querySelector(`#${inputId}`).closest(".file-drop-area").querySelector(".file-name");
renderFileList(inputId, container);
}
formCreate.on('submit', function(e){
e.preventDefault();
let hasFile = false;
for (const inputId in allFiles) {
if(allFiles[inputId] && allFiles[inputId].length > 0){
hasFile = true;
break;
}
}
if(!hasFile){
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message
icon: 'warning',
title: 'Perhatian',
text: 'Silahkan upload minimal 1 file sebelum submit'
});
return;
}
const formData = new FormData(this);
for (const inputId in allFiles) {
allFiles[inputId].forEach((file, index) => {
formData.append(`data[${inputId}][file][]`, file); // gunakan inputId = name input file di HTML, misal "files[]"
});
}
fetch(`/upload`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value,
},
body: formData
}).then(async(res) => {
const responseData = await res.json();
if (responseData.status) {
const handler = function () {
Toastify({
text: responseData.message || 'Berhasil melakukan aksi!',
duration: 3000,
gravity: "top", // bisa "bottom"
position: "right", // bisa "left"
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)", // hijau gradasi
color: "#fff",
}
}).showToast();
$("#col_add_file").html('');
colCount = 1; // reset counter
formCreate.find('input[type="file"]').each(function () {
const newInput = $(this).clone(); // clone dengan attribute multiple
$(this).replaceWith(newInput); // ganti input lama dengan baru
bindFileUpload(newInput[0])
});
formCreate.find('select').val(null).trigger('change');
document.querySelectorAll(".file-name").forEach(el => {
el.classList.add("d-none");
el.innerHTML = "";
});
if($("#klasifikasi_dok").val().length === 0 || $("#kategori_dok").val().length === 0 ){
index()
}else{
searchData()
}
modalCreate.removeEventListener('hidden.bs.modal', handler);
};
modalCreate.addEventListener('hidden.bs.modal', handler);
bootstrap.Modal.getInstance(modalCreate).hide();
} else {
throw new Error(responseData.message || 'Terjadi kesalahan saat menyimpan data.');
}
}).catch(err => {
if (err.message) {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message
});
}
});
});
});

View File

@ -90,8 +90,9 @@ function addForm(){
let html = '';
html += `<div class="row mt-3" id="col-${colCount}">
<hr />
html += `
<div class="row mt-3" id="col-${colCount}">
<hr />
<div class="col-md-6 mb-2">
<label>Unit</label>
<select class="form-control" name="data[${colCount}][id_unit_kerja]" id="select_id_unit_kerja_${colCount}" required>
@ -104,7 +105,7 @@ function addForm(){
<option value="" disable>Select Choose</option>
</select>
</div>
<div class="col-md-4">
<div class="col-md-6 mb-2">
<label>Kategori Dokumen</label>
<select class="form-control" name="data[${colCount}][master_kategori_directory_id]" id="select_kategori_${colCount}" required>
<option value="" disable>Select Choose</option>
@ -113,11 +114,7 @@ function addForm(){
`).join('')}
</select>
</div>
<div class="col-md-4">
<label>File</label>
<input type="file" class="form-control" name="data[${colCount}][file]" placeholder="exp : Juknis" required>
</div>
<div class="col-md-3">
<div class="col-md-6 mb-2">
<label>Klasifikasi Dokumen</label>
<select class="form-select" name="data[${colCount}][klasifikasi]">
<option value="" disable >Select Choose</option>
@ -125,19 +122,30 @@ function addForm(){
<option value="${kla?.master_klasifikasi_directory_id}/${kla?.nama_klasifikasi_directory}">${kla?.nama_klasifikasi_directory}</option>
`).join('')}
</select>
</div>
</div>
<div class="col-md-11 mb-2">
<label for="fileUpload${colCount}" class="form-label fw-semibold">📂 Upload Dokumen</label>
<div class="file-drop-area border rounded-3 p-1 shadow-sm">
<input class="file-input" type="file" id="fileUpload${colCount}" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx" multiple>
<div class="mt-2 text-success fw-semibold d-none file-name"></div>
</div>
<div class="form-text text-muted fw-semibold">Format yang didukung: PDF, JPG, PNG, Excel dan Word</div>
</div>
<div class="col-md-1">
<button type="button" class="btn btn-danger mt-4 me-2" onclick="removeCol(${colCount})"><i class="fa-solid fa-trash"></i></button>
</div>
</div>
`
col.append(html)
if(allAkses){
selectOptionUnitKerjaV1(colCount)
}else{
selectOptionUnitKerjaV2(colCount)
}
colCount++;
col.append(html)
let newInput = document.querySelector(`#fileUpload${colCount}`);
bindFileUpload(newInput);
if(allAkses){
selectOptionUnitKerjaV1(colCount)
}else{
selectOptionUnitKerjaV2(colCount)
}
colCount++;
}

View File

@ -1,10 +1,11 @@
function renderTree(units, katDok) {
function renderTree(units, katDok, keyword) {
if (!Array.isArray(units)) return '';
return `
<ul class="file-tree-ul mt-3">
${units.map(el => `
${units.map(el => {
return `
<li class="file-tree-li folder">
<input class="form-check-input" type="checkbox" data-select="unit" data-unit_id="${el?.id}">
<span class="fw-bolder">📂 ${el.name}</span>
@ -79,8 +80,8 @@ function renderTree(units, katDok) {
`).join('')}
</ul>
` : ''}
</li>
`).join('')}
</li>`
}).join('')}
</ul>
`;
}
@ -101,7 +102,7 @@ function index(kategori_dok = [], unitKerja = null, subUnitKerja = [], klasifika
file_tree.innerHTML = '';
if (Array.isArray(data?.data?.unitKerja)) {
file_tree.innerHTML = renderTree(data.data.unitKerja, data.data.katDok);
file_tree.innerHTML = renderTree(data.data.unitKerja, data.data.katDok, keyword);
// Toggle buka/tutup folder
file_tree.querySelectorAll(".folder > span").forEach(span => {
@ -155,13 +156,21 @@ function searchData(){
index(kategori_dok, unitKerja, subUnitKerja, klasifikasi_id);
}
function searchFile(keyword){
let debounceTimer;
function debounceSearch(input) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
searchFile(input.value.trim());
}, 300);
}
function searchFile(keyword){
let klasifikasi_id = $("#klasifikasi_dok").val()
let kategori_dok = $("#kategori_dok").val()
let unitKerja = $("#unit_kerja").val()
let subUnitKerja = $("#sub_unit_kerja").val()
index(kategori_dok, unitKerja, subUnitKerja, klasifikasi_id, keyword.value);
index(kategori_dok, unitKerja, subUnitKerja, klasifikasi_id, keyword);
}

View File

@ -52,6 +52,7 @@
color: blue;
cursor: pointer;
}
</style>
@section('body_main')
<div class="row">
@ -120,48 +121,10 @@
<button type="button" id="btn-download-multi" class="btn btn-sm btn-primary" onclick="downloadMultiple()" disabled>
<i class="fas fa-download"></i> Download Multiple (<span id="multi-count">0</span>)
</button>
<input type="text" id="search_file" oninput="searchFile(this)" class="form-control form-control-sm" placeholder="Search" style="max-width:220px;">
<input type="text" id="search_file" oninput="debounceSearch(this)" class="form-control form-control-sm" placeholder="Search" style="max-width:220px;">
</div>
<div id="file_tree"></div>
</div>
<div id="preview-wrapper" class="col-md-6 d-none">
<div class="card shadow-sm h-100">
<!-- Header -->
<div class="card-header d-flex justify-content-between align-items-center py-2">
<h6 class="mb-0">
📄 Preview <strong id="confirm_preview_file"></strong>
</h6>
<button class="btn btn-sm btn-outline-danger" id="close-preview" title="Tutup Preview"></button>
</div>
<!-- Body -->
<div class="card-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
<div class="d-flex justify-content-end align-items-center" style="position: sticky; top: 0; background: #ffffff; z-index: 10;">
<button type="button" class="btn btn-sm btn-outline-primary" id="download-file"> Download</button>
</div>
<div id="file-preview"
class="text-center text-muted d-flex justify-content-center align-items-center"
style="height:100%;">
<p>📂 Pilih file untuk melihat preview</p>
</div>
</div>
<!-- Footer -->
<div class="card-footer p-2 ">
<div class="text-black">Klasifikasi Dokumen <strong id="confirm-upload-klasifikasi"></strong></div>
<div class="mt-2 text-muted fst-italic small text-black">
Ditambahkan oleh <strong id="confirm-upload-dokumen"></strong> pada <span id="confirm-time-dokumen"></span>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-sm btn-outline-danger" id="delete-file">
🗑 Hapus
</button>
</div>
</div>
</div>
</div>
</div>
@ -171,6 +134,7 @@
</div>
</div>
@include('dashboard.modal.create')
@include('dashboard.modal.view')
<script>
const klasifikasiDok = @json($klasifikasiDok);
const katDok = @json($katDok);
@ -183,41 +147,39 @@
document.addEventListener('click', function(e) {
if (e.target.matches('.file-link')) {
e.preventDefault();
let fileUrl = e.target.getAttribute('data-file');
let fileName = e.target.getAttribute('data-name_file');
let upload = e.target.getAttribute('data-upload');
let time = e.target.getAttribute('data-time');
let klasifikasiView = e.target.getAttribute('data-klasifikasi');
$("#confirm-upload-dokumen").html(upload)
$("#confirm-time-dokumen").html(time)
$("#confirm-upload-klasifikasi").html(klasifikasiView)
currentFile = fileUrl
$("#confirm-upload-dokumen").html(upload);
$("#confirm-time-dokumen").html(time);
$("#confirm-upload-klasifikasi").html(klasifikasiView);
$("#confirm_preview_file").html(fileName);
currentFile = fileUrl;
idDirectory = e.target.getAttribute('data-id');
let ext = fileUrl.split('.').pop().toLowerCase();
$("#confirm_preview_file").html(fileName)
// ubah layout
document.getElementById('tree-wrapper').classList.remove('col-md-12');
document.getElementById('tree-wrapper').classList.add('col-md-6');
document.getElementById('preview-wrapper').classList.remove('d-none');
let previewBox = document.getElementById('file-preview');
if (['jpg','jpeg','png','gif','webp'].includes(ext)) {
previewBox.innerHTML = `
<img src="/file/${fileUrl}" class="img-fluid rounded shadow-sm" width="100%" height="450px" alt="preview">
`;
previewBox.innerHTML = `<img src="/file/${fileUrl}" class="img-fluid rounded shadow-sm" width="100%" alt="preview">`;
} else if (ext === 'pdf') {
previewBox.innerHTML = `
<iframe src="/file/${fileUrl}" width="100%" height="500px" style="border:none;"></iframe>
`;
previewBox.innerHTML = `<iframe src="/file/${fileUrl}" width="100%" height="500px" style="border:none;"></iframe>`;
} else {
previewBox.innerHTML = `
<p class="text-muted">Tidak bisa preview file ini. Silakan download:</p>
<a href="/file/${fileUrl}" target="_blank" class="btn btn-sm btn-primary">⬇️ Download</a>
`;
}
}
// 🔥 Tampilkan modal
$("#previewModal").modal("show");
}
if (e.target.id === 'delete-file') {
if (!currentFile) {
Swal.fire({
@ -238,37 +200,48 @@
confirmButtonText: 'Ya, hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
fetch(`/delete-file/${idDirectory}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ file: currentFile })
})
.then(res => res.json())
.then(data => {
if (data.success) {
if (result.isConfirmed) {
fetch(`/delete-file/${idDirectory}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ file: currentFile })
})
.then(res => res.json())
.then(data => {
if (data.success) {
Swal.fire({
text: 'File berhasil dihapus',
icon: 'success',
timer: 2000,
showConfirmButton: false
});
const fileLink = document.querySelector(`a.file-link[data-id="${idDirectory}"]`);
if (fileLink) {
const li = fileLink.closest('li.file-tree-li');
li?.remove();
}
// reset preview
document.getElementById('file-preview').innerHTML =
`<p>📂 Pilih file untuk melihat preview</p>`;
document.getElementById('preview-wrapper').classList.add('d-none');
document.getElementById('tree-wrapper').classList.remove('col-md-6');
document.getElementById('tree-wrapper').classList.add('col-12');
currentFile = null; // reset
// Hapus link file dari tree
const fileLink = document.querySelector(`a.file-link[data-id="${idDirectory}"]`);
if (fileLink) {
const li = fileLink.closest('li.file-tree-li');
li?.remove();
}
// Reset modal preview
document.getElementById('file-preview').innerHTML =
`<p>📂 Pilih file untuk melihat preview</p>`;
document.getElementById('confirm_preview_file').innerHTML = "";
document.getElementById('confirm-upload-dokumen').innerHTML = "";
document.getElementById('confirm-time-dokumen').innerHTML = "";
document.getElementById('confirm-upload-klasifikasi').innerHTML = "";
// Tutup modal otomatis setelah hapus
let modalEl = document.getElementById('previewModal');
let modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
// Reset variabel
currentFile = null;
idDirectory = null;
} else {
Swal.fire({
text: 'Gagal menghapus file',
@ -302,15 +275,12 @@
}
})
});
// tombol close
document.getElementById('close-preview').addEventListener('click', function() {
document.getElementById('tree-wrapper').classList.remove('col-md-6');
document.getElementById('tree-wrapper').classList.add('col-md-12');
document.getElementById('preview-wrapper').classList.add('d-none');
});
</script>
<script src="{{ ver('/js/dashboard/_init.js') }}"></script>
<script src="{{ ver('/js/dashboard/index.js') }}"></script>
<script src="{{ ver('/js/dashboard/functions.js') }}"></script>

View File

@ -26,7 +26,7 @@
<option value="" disable>Select Choose</option>
</select>
</div>
<div class="col-md-4">
<div class="col-md-6 mb-2">
<label>Kategori Dokumen</label>
<select class="form-control" name="data[0][master_kategori_directory_id]" id="select_kategori_0" required>
<option value="" disable>Select Choose</option>
@ -35,22 +35,27 @@
@endforeach
</select>
</div>
<div class="col-md-4">
<label>File</label>
<input type="file" class="form-control" name="data[0][file]" placeholder="exp : Juknis" required>
{{-- <input type="hidden" class="form-control" name="id_unit_kerja" id="id_unit_kerja" value="{{ $authUnitKerja }}" placeholder="exp : Juknis">
<input type="hidden" class="form-control" name="id_sub_unit_kerja" id="id_sub_unit_kerja" placeholder="exp : Juknis" value="{{ $authSubUnitKerja }}"> --}}
</div>
<div class="col-md-4">
<div class="col-md-6 mb-2">
<label>Klasifikasi File</label>
<select class="form-select" name="data[0][klasifikasi]" required>
<option value="" disable>Select Choose</option>
@foreach ($klasifikasiDok as $klasifikasi)
<option value="{{ $klasifikasi->master_klasifikasi_directory_id }}/{{ $klasifikasi->nama_klasifikasi_directory }}">{{ $klasifikasi->nama_klasifikasi_directory }}</option>
<option value="{{ $klasifikasi->master_klasifikasi_directory_id }}/{{ $klasifikasi->nama_klasifikasi_directory }}">
{{ $klasifikasi->nama_klasifikasi_directory }}
</option>
@endforeach
</select>
</div>
<div class="col-md-12 mb-2">
<label for="fileUpload0" class="form-label fw-semibold">📂 Upload Dokumen</label>
<div class="file-drop-area border rounded-2 p-1 shadow-sm">
<input class="file-input" type="file" id="fileUpload0" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx" multiple>
<div class="mt-2 text-success fw-semibold d-none file-name"></div>
</div>
<div class="form-text text-muted fw-semibold">
Form bersifat multiple dan format yang didukung: JPG, JPEG, PDF, PNG, PPT Excel dan Word
</div>
</div>
<div id="col_add_file"></div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-3" onclick="addForm()">

View File

@ -0,0 +1,41 @@
<!-- Modal Preview -->
<div class="modal fade" id="previewModal" tabindex="-1" aria-labelledby="previewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<!-- Header -->
<div class="modal-header">
<h6 class="modal-title" id="previewModalLabel">
📄 Preview <strong id="confirm_preview_file"></strong>
</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Body -->
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
<div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
<button type="button" class="btn btn-sm btn-outline-danger mb-2 ms-2" id="delete-file">🗑 Hapus</button>
<button type="button" class="btn btn-sm btn-outline-primary mb-2" id="download-file"> Download</button>
</div>
<div id="file-preview"
class="text-center text-muted d-flex justify-content-center align-items-center"
style="height:100%;">
<p>📂 Pilih file untuk melihat preview</p>
</div>
</div>
<!-- Footer -->
<div class="modal-footer d-flex justify-content-between">
<div>
<div class="text-black">Klasifikasi Dokumen <strong id="confirm-upload-klasifikasi"></strong></div>
<div class="mt-2 text-muted fst-italic small text-black">
Ditambahkan oleh <strong id="confirm-upload-dokumen"></strong> pada <span id="confirm-time-dokumen"></span>
</div>
</div>
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Tutup</button>
</div>
</div>
</div>
</div>