fixing scroll bar

This commit is contained in:
JokoPrasetio 2026-06-11 11:37:28 +07:00
parent 7bbc87d6a7
commit 422ec731dd
4 changed files with 137 additions and 10 deletions

View File

@ -1608,19 +1608,38 @@ class DashboardController extends Controller
$result = $this->buildRecapData($unitIds, $keyword);
// paginate manually
$total = count($result);
$lastPage = max(1, (int) ceil($total / max(1, $perPage)));
$page = min($page, $lastPage);
$chunks = array_chunk($result, $perPage);
$currentData = $chunks[$page-1] ?? [];
$grandTotalFiles = 0;
foreach ($result as $unitRecap) {
foreach (($unitRecap['data'] ?? []) as $folderRecap) {
$grandTotalFiles += (int) ($folderRecap['count'] ?? 0);
}
}
$currentPageTotalFiles = 0;
foreach ($currentData as $unitRecap) {
foreach (($unitRecap['data'] ?? []) as $folderRecap) {
$currentPageTotalFiles += (int) ($folderRecap['count'] ?? 0);
}
}
return response()->json([
'status' => true,
'data' => $currentData,
'message' => 'Berhasil mendapatkan data',
'summary' => [
'total_units' => $total,
'grand_total_files' => $grandTotalFiles,
'current_page_total_files' => $currentPageTotalFiles,
],
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'last_page' => max(1, ceil($total / $perPage)),
'has_more' => $page < max(1, ceil($total / $perPage)),
'last_page' => $lastPage,
'has_more' => $page < $lastPage,
]
]);
} catch (\Throwable $th) {

View File

@ -51,6 +51,30 @@
.table-fixed-height {
min-height: 70vh;
}
.table-responsive.table-fixed-height {
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: #b8c7db transparent;
}
.table-responsive.table-fixed-height::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.table-responsive.table-fixed-height::-webkit-scrollbar-track {
background: transparent;
border-radius: 999px;
}
.table-responsive.table-fixed-height::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.72);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
.table-responsive.table-fixed-height::-webkit-scrollbar-thumb:hover {
background: rgba(100, 116, 139, 0.86);
border: 2px solid transparent;
background-clip: padding-box;
}
/* --- Warna kategori baris --- */
.row-shade {

View File

@ -49,6 +49,30 @@
.table-fixed-height {
min-height: 70vh;
}
.table-responsive.table-fixed-height {
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: #b8c7db transparent;
}
.table-responsive.table-fixed-height::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.table-responsive.table-fixed-height::-webkit-scrollbar-track {
background: transparent;
border-radius: 999px;
}
.table-responsive.table-fixed-height::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.72);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
.table-responsive.table-fixed-height::-webkit-scrollbar-thumb:hover {
background: rgba(100, 116, 139, 0.86);
border: 2px solid transparent;
background-clip: padding-box;
}
/* --- Warna kategori baris --- */
.row-shade {

View File

@ -1,4 +1,32 @@
@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>
@ -25,7 +53,7 @@
</button>
</div>
</div>
<div class="table-responsive" style="max-height: 55vh; overflow-y:auto;">
<div class="table-responsive recap-scroll-area">
<table class="table table-sm table-hover align-middle">
<thead class="table-light shadow-sm">
<tr>
@ -42,13 +70,16 @@
</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 };
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);
@ -68,48 +99,77 @@ function changePerPage(val){
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())
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;
}
let grandTotal = 0;
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">${idx+1}</td>` : ''}
${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('');
(row.data || []).forEach(f => { grandTotal += (parseInt(f.count, 10) || 0); });
return folderRows;
}).join('');
tbody.innerHTML = html + `
<tr class="table-light">
<td colspan="3" class="text-end fw-semibold">Total File</td>
<td class="text-center fw-bold">${grandTotal}</td>
<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;
}
});
}