2026-02-05 09:39:23 +07:00

636 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

document.addEventListener('DOMContentLoaded', () => {
const tableState = { data: [], page: 1, pageSize: 10, search: '', lastPage: 1, total: 0, startDate: '', endDate: '', mode: 'pengajuan' };
const historyState = { data: [], page: 1, pageSize: 10, search: '', lastPage: 1, total: 0, startDate: '', endDate: '', mode: 'history' };
const tbodyPending = document.getElementById('tablePendingFile');
const tbodyHistory = document.getElementById('tableHistoryFile');
const paginationPendingEl = document.getElementById('paginationControls');
const paginationHistoryEl = document.getElementById('paginationHistory');
const summaryEl = document.getElementById('tableSummary');
const pageSizeSelect = document.getElementById('tablePageSize');
const startDateInput = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate');
const selectAllCheckbox = document.getElementById('selectAllPending');
const bulkApproveBtn = document.getElementById('bulkApproveBtn');
const clearSelectionBtn = document.getElementById('clearSelectionBtn');
const selectedCountEl = document.getElementById('selectedCount');
const pendingBulkActionsEl = document.getElementById('pendingBulkActions');
const tabsEl = document.getElementById('pengajuanTabs');
const titleEl = document.getElementById('pendingTitle');
const tabPendingEl = document.getElementById('tabPengajuan');
const tabHistoryEl = document.getElementById('tabHistory');
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const selectedIds = new Set();
if (pageSizeSelect) {
const initialSize = parseInt(pageSizeSelect.value);
if (!isNaN(initialSize)) {
tableState.pageSize = initialSize;
historyState.pageSize = initialSize;
}
pageSizeSelect.addEventListener('change', (e) => {
const val = parseInt(e.target.value);
if (!isNaN(val) && val > 0) {
tableState.pageSize = val;
historyState.pageSize = val;
tableState.page = 1;
historyState.page = 1;
fetchData();
}
});
}
window.applyDateFilter = function(){
const start = startDateInput.value || '';
const end = endDateInput.value || '';
tableState.startDate = start;
tableState.endDate = end;
historyState.startDate = start;
historyState.endDate = end;
tableState.page = 1;
historyState.page = 1;
fetchData();
}
function formatTanggal(dateString) {
if (!dateString) return '-';
const d = new Date(dateString);
return d.toLocaleDateString('id-ID', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
}
function statusBadge(status){
if (status === 'rejected') return '<span class="badge bg-danger">Rejected</span>';
if (status === 'revised') return '<span class="badge bg-info">Revised</span>';
return '<span class="badge bg-warning text-dark">Pending</span>';
}
function aksesBadge(akses){
if (akses){
return '<span class="badge bg-success">Umum</span>';
}
return '<span class="badge bg-primary">Internal Unit</span>';
}
function safeText(val){
return val ? String(val) : '-';
}
function isRejected(item){
return item?.status_action === 'rejected';
}
function getSelectableIdsOnPage(){
return (tableState.data || [])
.filter((item) => !isRejected(item))
.map((item) => String(item.file_directory_id));
}
function updateSelectAllState(){
if (!selectAllCheckbox) return;
if (tableState.mode === 'history') {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
selectAllCheckbox.disabled = true;
return;
}
const selectableIds = getSelectableIdsOnPage();
if (selectableIds.length === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
selectAllCheckbox.disabled = true;
return;
}
selectAllCheckbox.disabled = false;
const selectedOnPage = selectableIds.filter((id) => selectedIds.has(id)).length;
selectAllCheckbox.checked = selectedOnPage > 0 && selectedOnPage === selectableIds.length;
selectAllCheckbox.indeterminate = selectedOnPage > 0 && selectedOnPage < selectableIds.length;
}
function updateSelectionUI(){
const count = selectedIds.size;
if (selectedCountEl) selectedCountEl.textContent = String(count);
if (bulkApproveBtn) bulkApproveBtn.disabled = count === 0 || tableState.mode === 'history';
if (clearSelectionBtn) clearSelectionBtn.disabled = count === 0 || tableState.mode === 'history';
updateSelectAllState();
}
function buildPendingRow(item){
const tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-';
const tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-';
const tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-';
const id = String(item.file_directory_id);
const rejected = isRejected(item);
const checked = selectedIds.has(id);
const aksi = `
<div class="d-flex gap-1">
<button class="btn btn-sm btn-primary" onclick="infoDok(this)"
data-file="${item.file}"
data-fileName="${item.nama_dokumen}"
data-id="${item.file_directory_id}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">
<i class="fa-solid fa-file-pdf"></i>
</button>
<button class="btn btn-sm btn-success" onclick="approvePending('${item.file_directory_id}', '${item.nama_dokumen || ''}')">
<i class="fa-solid fa-check"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="rejectPending('${item.file_directory_id}', '${item.nama_dokumen || ''}')">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
`;
return `
<tr class="${checked ? 'table-active' : ''}">
<td class="text-center">
<input type="checkbox"
class="form-check-input row-select"
data-id="${id}"
${checked ? 'checked' : ''}
${rejected ? 'disabled' : ''}>
</td>
<td class="text-center">${rejected ? '' : aksi}</td>
<td>${safeText(item.no_dokumen)}</td>
<td>${statusBadge(item?.status_action)}</td>
<td>${aksesBadge(item?.permission_file)}</td>
<td><a href="#" class="file-link"
data-file="${item.file}"
data-fileName="${item.nama_dokumen}"
data-id="${item.file_directory_id}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">${item.nama_dokumen}</a></td>
<td class="col-kategori"><div class="cell-wrap">${safeText(item.folder)}</div></td>
<td class="col-unit"><div class="cell-wrap">${safeText(item.part)}</div></td>
<td class="text-nowrap">${tanggalTerbit}</td>
<td class="text-nowrap">${tanggalExp}</td>
<td class="text-nowrap">${tanggal}</td>
<td class="col-uploader"><div class="cell-wrap">${safeText(item.pegawai_nama_entry)}</div></td>
</tr>
`;
}
function buildHistoryRow(item){
const tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-';
const tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-';
const tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-';
const actionType = item?.action_type || '-';
return `
<tr>
<td>${safeText(item.no_dokumen)}</td>
<td>${aksesBadge(item?.permission_file)}</td>
<td><a href="#" class="file-link"
data-file="${item.file || ''}"
data-fileName="${item.nama_dokumen || '-'}"
data-id="${item.file_directory_id || ''}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">${safeText(item.nama_dokumen)}</a></td>
<td>${safeText(item.folder)}</td>
<td>${safeText(item.part)}</td>
<td><span class="badge bg-info">${actionType}</span></td>
<td class="text-nowrap">${tanggalTerbit}</td>
<td class="text-nowrap">${tanggalExp}</td>
<td class="text-nowrap">${tanggal}</td>
</tr>
`;
}
function renderPagination(totalPages){
const activeState = tableState.mode === 'history' ? historyState : tableState;
const paginationEl = tableState.mode === 'history' ? paginationHistoryEl : paginationPendingEl;
if (!paginationEl) return;
if (totalPages <= 1) {
paginationEl.innerHTML = '';
return;
}
const maxButtons = 5;
let start = Math.max(1, activeState.page - Math.floor(maxButtons/2));
let end = Math.min(totalPages, start + maxButtons - 1);
start = Math.max(1, end - maxButtons + 1);
let buttons = '';
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="prev" ${activeState.page === 1 ? 'disabled' : ''}></button>`;
for (let i = start; i <= end; i++) {
buttons += `<button class="btn btn-sm ${i === activeState.page ? 'btn-primary' : 'btn-outline-secondary'}" data-page="${i}">${i}</button>`;
}
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="next" ${activeState.page === totalPages ? 'disabled' : ''}></button>`;
paginationEl.innerHTML = `
<div class="d-flex align-items-center gap-2 flex-wrap">
<div class="btn-group" role="group">${buttons}</div>
<span class="small text-muted">Halaman ${activeState.page} dari ${totalPages}</span>
</div>
`;
}
function bindPagination(paginationEl, state){
if (!paginationEl) return;
paginationEl.addEventListener('click', (e) => {
const page = e.target.getAttribute('data-page');
if (!page) return;
if (page === 'prev' && state.page > 1) state.page--;
else if (page === 'next') {
if (state.page < state.lastPage) state.page++;
} else {
state.page = parseInt(page);
}
fetchData();
});
}
bindPagination(paginationPendingEl, tableState);
bindPagination(paginationHistoryEl, historyState);
function renderTable(){
const isHistoryMode = tableState.mode === 'history';
const activeState = isHistoryMode ? historyState : tableState;
const pageData = activeState.data || [];
const targetBody = isHistoryMode ? tbodyHistory : tbodyPending;
const rowBuilder = isHistoryMode ? buildHistoryRow : buildPendingRow;
const colSpan = isHistoryMode ? 9 : 12;
if (!targetBody) return;
if (pageData.length === 0) {
targetBody.innerHTML = `
<tr>
<td colspan="${colSpan}" class="text-center text-muted py-4">
Tidak ada data
</td>
</tr>
`;
} else {
targetBody.innerHTML = pageData.map(rowBuilder).join('');
}
const from = activeState.total === 0 ? 0 : ((activeState.page - 1) * activeState.pageSize) + 1;
const to = Math.min(((activeState.page - 1) * activeState.pageSize) + pageData.length, activeState.total);
if (summaryEl) {
summaryEl.textContent = activeState.total ? `Menampilkan ${from} - ${to} dari ${activeState.total} data` : 'Tidak ada data';
}
renderPagination(activeState.lastPage || 1);
updateSelectionUI();
}
let searchDebounce;
window.debouncedTableSearch = function(value){
clearTimeout(searchDebounce);
searchDebounce = setTimeout(() => {
const keyword = value.trim();
tableState.search = keyword;
historyState.search = keyword;
tableState.page = 1;
historyState.page = 1;
fetchData();
}, 250);
}
window.refreshLog = function(){
tableState.search = '';
tableState.startDate = '';
tableState.endDate = '';
historyState.search = '';
historyState.startDate = '';
historyState.endDate = '';
const tableSearch = document.getElementById('tableSearch');
if (tableSearch) tableSearch.value = '';
if (startDateInput) startDateInput.value = '';
if (endDateInput) endDateInput.value = '';
selectedIds.clear();
tableState.page = 1;
historyState.page = 1;
fetchData();
}
function updateTabUI(){
if (tabsEl) {
tabsEl.querySelectorAll('.nav-link').forEach((btn) => {
btn.classList.toggle('active', btn.getAttribute('data-mode') === tableState.mode);
});
}
if (titleEl) {
titleEl.textContent = tableState.mode === 'history' ? 'Log History' : 'Data Pending';
}
if (tabPendingEl && tabHistoryEl) {
tabPendingEl.classList.toggle('d-none', tableState.mode === 'history');
tabHistoryEl.classList.toggle('d-none', tableState.mode !== 'history');
}
if (pendingBulkActionsEl) {
pendingBulkActionsEl.classList.toggle('d-none', tableState.mode === 'history');
}
updateSelectionUI();
}
if (tabsEl) {
tabsEl.addEventListener('click', (e) => {
const btn = e.target.closest('[data-mode]');
if (!btn) return;
const mode = btn.getAttribute('data-mode');
if (!mode || mode === tableState.mode) return;
tableState.mode = mode;
if (mode === 'history') {
historyState.page = 1;
} else {
tableState.page = 1;
}
updateTabUI();
fetchData();
});
}
function fetchData(){
if (summaryEl) summaryEl.textContent = 'Memuat data...';
const activeState = tableState.mode === 'history' ? historyState : tableState;
const params = new URLSearchParams({
page: activeState.page,
per_page: activeState.pageSize,
keyword: activeState.search || '',
start_date: activeState.startDate || '',
end_date: activeState.endDate || ''
});
const endpoint = tableState.mode === 'history'
? `/data/log-dokumen?${params.toString()}`
: `/datatable/pending-file?${params.toString()}`;
fetch(endpoint)
.then(res => res.json())
.then(data => {
activeState.data = data?.data || [];
activeState.lastPage = data?.pagination?.last_page || 1;
activeState.total = data?.pagination?.total || 0;
renderTable();
})
.catch(err => {
console.error(err);
if (summaryEl) summaryEl.textContent = 'Gagal memuat data';
});
}
window.approvePending = function(id, fileName){
Swal.fire({
title: 'Approve dokumen?',
text: fileName || 'Dokumen akan disetujui.',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Approve',
cancelButtonText: 'Batal',
}).then((result) => {
if (!result.isConfirmed) return;
fetch(`/pending-file/${id}/approve`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal approve.');
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen disetujui.',
timer: 1500,
showConfirmButton: false
});
selectedIds.delete(String(id));
countData();
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
}
window.rejectPending = function(id, fileName){
Swal.fire({
title: 'Reject dokumen?',
text: fileName || 'Dokumen akan ditolak.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Reject',
cancelButtonText: 'Batal',
input: 'textarea',
inputLabel: 'Catatan',
inputPlaceholder: 'Tulis alasan atau catatan revisi...',
inputAttributes: {
'aria-label': 'Catatan'
},
preConfirm: (value) => {
const revision = (value || '').trim();
if (!revision) {
Swal.showValidationMessage('Catatan revisi wajib diisi.');
}
return revision;
}
}).then((result) => {
if (!result.isConfirmed) return;
fetch(`/pending-file/${id}/reject`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
revision: result.value
})
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal reject.');
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen ditolak.',
timer: 1500,
showConfirmButton: false
});
selectedIds.delete(String(id));
countData();
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
}
window.infoDok = function(e){
let fileUrl =$(e).data('file');
let noDokumen = $(e).data('no_dokumen');
let tanggalTerbit = $(e).data('tanggal_terbit');
let permissionFile = $(e).data('permission_file');
let fileName = $(e).data('fileName');
currentFile = fileUrl;
idDirectory = $(e).data('id');
const titleEl = document.getElementById('confirm_preview_file');
if (titleEl) titleEl.textContent = fileName;
const noEl = document.getElementById('confirm-upload-dokumen');
if (noEl) noEl.textContent = noDokumen;
const tglEl = document.getElementById('confirm-time-dokumen');
if (tglEl) tglEl.textContent = tanggalTerbit;
const permEl = document.getElementById('confirm-permission');
if (permEl) {
const publicDoc = isPublic(permissionFile);
permEl.textContent = publicDoc ? 'Umum' : 'Internal Unit';
permEl.className = 'badge ' + (publicDoc ? 'bg-success' : 'bg-secondary');
}
let previewBox = document.getElementById('file-preview');
previewBox.innerHTML = `<div id="pdfWrap" style="height:500px; overflow:auto; background:#f7f7f7; padding:8px;">
<div id="pdfPages"></div>
</div>`;
openPreview(idDirectory);
$("#previewModal").modal('show');
}
if (tbodyPending) {
tbodyPending.addEventListener('change', (e) => {
const target = e.target;
if (!target.classList.contains('row-select')) return;
const id = target.getAttribute('data-id');
if (!id) return;
if (target.checked) selectedIds.add(id);
else selectedIds.delete(id);
const row = target.closest('tr');
if (row) row.classList.toggle('table-active', target.checked);
updateSelectionUI();
});
}
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', () => {
const selectableIds = getSelectableIdsOnPage();
if (selectableIds.length === 0) return;
if (selectAllCheckbox.checked) {
selectableIds.forEach((id) => selectedIds.add(id));
} else {
selectableIds.forEach((id) => selectedIds.delete(id));
}
renderTable();
});
}
if (clearSelectionBtn) {
clearSelectionBtn.addEventListener('click', () => {
selectedIds.clear();
renderTable();
});
}
if (bulkApproveBtn) {
bulkApproveBtn.addEventListener('click', () => {
if (selectedIds.size === 0) return;
const ids = Array.from(selectedIds);
Swal.fire({
title: 'Approve dokumen terpilih?',
text: `Total ${ids.length} dokumen akan disetujui.`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Approve',
cancelButtonText: 'Batal',
}).then((result) => {
if (!result.isConfirmed) return;
fetch(`/pending-file/approve-multiple`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ids })
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal approve dokumen.');
}
ids.forEach((id) => selectedIds.delete(String(id)));
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen disetujui.',
timer: 1500,
showConfirmButton: false
});
countData();
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
});
}
updateTabUI();
fetchData();
});
document.addEventListener('click', function(e){
if(e.target.matches('.file-link')){
e.preventDefault();
let fileUrl = e.target.getAttribute('data-file');
let noDokumen = e.target.getAttribute('data-no_dokumen');
let tanggalTerbit = e.target.getAttribute('data-tanggal_terbit');
let permissionFile = e.target.getAttribute('data-permission_file');
let fileName = e.target.getAttribute('data-fileName');
currentFile = fileUrl;
idDirectory = e.target.getAttribute('data-id');
const titleEl = document.getElementById('confirm_preview_file');
if (titleEl) titleEl.textContent = fileName;
const noEl = document.getElementById('confirm-upload-dokumen');
if (noEl) noEl.textContent = noDokumen;
const tglEl = document.getElementById('confirm-time-dokumen');
if (tglEl) tglEl.textContent = tanggalTerbit;
const permEl = document.getElementById('confirm-permission');
if (permEl) {
const publicDoc = isPublic(permissionFile);
permEl.textContent = publicDoc ? 'Umum' : 'Internal Unit';
permEl.className = 'badge ' + (publicDoc ? 'bg-success' : 'bg-secondary');
}
let previewBox = document.getElementById('file-preview');
previewBox.innerHTML = `<div id="pdfWrap" style="height:500px; overflow:auto; background:#f7f7f7; padding:8px;">
<div id="pdfPages"></div>
</div>`;
openPreview(idDirectory);
$("#previewModal").modal('show');
}
if(e.target.matches('#btn-view-full')){
window.open(`/full-preview/${idDirectory}`, '_blank');
}
});
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}