2026-06-11 11:37:28 +07:00

213 lines
8.9 KiB
PHP
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.

@php($showRecapTitle = $showRecapTitle ?? true)
<style>
.recap-scroll-area {
max-height: 55vh;
overflow-y: auto;
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: #b8c7db transparent;
}
.recap-scroll-area::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.recap-scroll-area::-webkit-scrollbar-track {
background: transparent;
border-radius: 999px;
}
.recap-scroll-area::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.72);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
.recap-scroll-area::-webkit-scrollbar-thumb:hover {
background: rgba(100, 116, 139, 0.86);
border: 2px solid transparent;
background-clip: padding-box;
}
</style>
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
@if ($showRecapTitle)
<div>
<h4 class="mb-0">Rekap Dokumen</h4>
<small class="text-muted">Ringkasan jumlah file per Unit dan Kategori</small>
</div>
@endif
<div class="{{ $showRecapTitle ? 'ms-md-auto' : '' }} d-flex gap-2 align-items-center">
<div class="input-group input-group-sm" style="max-width:320px;">
<span class="input-group-text bg-white border-end-0">
<i class="fa fa-search text-muted"></i>
</span>
<input type="search" id="recapSearch" class="form-control border-start-0" placeholder="Cari unit atau folder" oninput="debouncedRecapSearch(this.value)">
</div>
<select id="recapPerPage" class="form-select form-select-sm" style="width:auto;" onchange="changePerPage(this.value)">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
<button class="btn btn-outline-secondary btn-sm d-flex align-items-center gap-1" onclick="fetchRecap()">
<i class="fa fa-rotate"></i>
<span>Refresh</span>
</button>
</div>
</div>
<div class="table-responsive recap-scroll-area">
<table class="table table-sm table-hover align-middle">
<thead class="table-light shadow-sm">
<tr>
<th style="width:5%;" class="text-center">#</th>
<th style="width:30%;">Unit / Akreditasi</th>
<th style="width:20%;">Kategori</th>
<th style="width:15%;" class="text-center">Jumlah File</th>
</tr>
</thead>
<tbody id="recapBody">
<tr>
<td colspan="4" class="text-center text-muted py-4">Memuat data...</td>
</tr>
</tbody>
</table>
</div>
<div class="small text-muted mt-2" id="recapSummary">Memuat ringkasan rekap...</div>
<div class="d-flex flex-column flex-md-row align-items-center justify-content-between gap-2 mt-3" id="recapPagination"></div>
<script>
document.addEventListener('DOMContentLoaded', () => fetchRecap());
let recapDebounce;
const recapState = { page:1, perPage:10, keyword:'', lastPage:1, totalUnits:0, grandTotalFiles:0, currentPageTotalFiles:0 };
let recapRequestController = null;
let recapRequestToken = 0;
function debouncedRecapSearch(val){
clearTimeout(recapDebounce);
recapDebounce = setTimeout(() => {
recapState.keyword = val;
recapState.page = 1;
fetchRecap();
}, 250);
}
function changePerPage(val){
recapState.perPage = parseInt(val) || 10;
recapState.page = 1;
fetchRecap();
}
function fetchRecap(){
const tbody = document.getElementById('recapBody');
const pager = document.getElementById('recapPagination');
const summaryEl = document.getElementById('recapSummary');
if(!tbody) return;
const requestToken = ++recapRequestToken;
if (recapRequestController) {
recapRequestController.abort();
}
recapRequestController = new AbortController();
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Memuat data...</td></tr>`;
if(pager) pager.innerHTML = '';
if(summaryEl) summaryEl.textContent = 'Memuat ringkasan rekap...';
const params = new URLSearchParams({
page: recapState.page,
per_page: recapState.perPage,
keyword: recapState.keyword || ''
});
fetch('/data/recap?' + params.toString(), {
signal: recapRequestController.signal,
})
.then(res => res.json())
.then(json => {
if (requestToken !== recapRequestToken) return;
const rows = json?.data || [];
recapState.page = json?.pagination?.current_page || recapState.page;
recapState.lastPage = json?.pagination?.last_page || 1;
recapState.totalUnits = json?.summary?.total_units || 0;
recapState.grandTotalFiles = json?.summary?.grand_total_files || 0;
recapState.currentPageTotalFiles = json?.summary?.current_page_total_files || 0;
if(!rows.length){
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Tidak ada data</td></tr>`;
if(summaryEl) summaryEl.textContent = recapState.grandTotalFiles
? `Total file hasil filter: ${recapState.grandTotalFiles}`
: 'Tidak ada data rekap';
return;
}
const html = rows.map((row, idx) => {
const rowNumber = ((recapState.page - 1) * recapState.perPage) + idx + 1;
const folderRows = (row.data || []).map((f, i) => `
<tr>
${i === 0 ? `<td rowspan="${row.data.length}" class="text-center align-middle fw-semibold">${rowNumber}</td>` : ''}
${i === 0 ? `<td rowspan="${row.data.length}" class="fw-semibold">${row.unit || '-'}</td>` : ''}
<td>${f.folder || '-'}</td>
<td class="text-center fw-bold">${f.count || 0}</td>
</tr>
`).join('');
return folderRows;
}).join('');
tbody.innerHTML = html + `
<tr class="table-light">
<td colspan="3" class="text-end fw-semibold">Subtotal Halaman Ini</td>
<td class="text-center fw-bold">${recapState.currentPageTotalFiles}</td>
</tr>
`;
if(summaryEl) {
const from = recapState.totalUnits === 0 ? 0 : ((recapState.page - 1) * recapState.perPage) + 1;
const to = Math.min(((recapState.page - 1) * recapState.perPage) + rows.length, recapState.totalUnits);
summaryEl.textContent = `Menampilkan ${from}-${to} dari ${recapState.totalUnits} unit rekap. Total file hasil filter: ${recapState.grandTotalFiles}.`;
}
renderRecapPagination();
})
.catch(err => {
if (err?.name === 'AbortError') return;
console.error(err);
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-danger py-4">Gagal memuat data</td></tr>`;
const summaryEl = document.getElementById('recapSummary');
if(summaryEl) summaryEl.textContent = 'Gagal memuat ringkasan rekap';
})
.finally(() => {
if (requestToken === recapRequestToken) {
recapRequestController = null;
}
});
}
function renderRecapPagination(){
const pager = document.getElementById('recapPagination');
if(!pager) return;
if(recapState.lastPage <= 1){
pager.innerHTML = '';
return;
}
const maxButtons = 5;
let start = Math.max(1, recapState.page - Math.floor(maxButtons/2));
let end = Math.min(recapState.lastPage, start + maxButtons - 1);
start = Math.max(1, end - maxButtons + 1);
let buttons = '';
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="prev" ${recapState.page === 1 ? 'disabled' : ''}></button>`;
for(let i=start; i<=end; i++){
buttons += `<button class="btn btn-sm ${i === recapState.page ? 'btn-primary' : 'btn-outline-secondary'}" data-page="${i}">${i}</button>`;
}
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="next" ${recapState.page === recapState.lastPage ? 'disabled' : ''}></button>`;
pager.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 ${recapState.page} dari ${recapState.lastPage}</span>
</div>
`;
pager.querySelectorAll('button[data-page]').forEach(btn => {
btn.addEventListener('click', () => {
const page = btn.getAttribute('data-page');
if(page === 'prev' && recapState.page > 1) recapState.page--;
else if(page === 'next' && recapState.page < recapState.lastPage) recapState.page++;
else if(!isNaN(parseInt(page))) recapState.page = parseInt(page);
fetchRecap();
});
});
}
</script>