fixing scroll bar
This commit is contained in:
parent
7bbc87d6a7
commit
422ec731dd
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user