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);
|
$result = $this->buildRecapData($unitIds, $keyword);
|
||||||
// paginate manually
|
// paginate manually
|
||||||
$total = count($result);
|
$total = count($result);
|
||||||
|
$lastPage = max(1, (int) ceil($total / max(1, $perPage)));
|
||||||
|
$page = min($page, $lastPage);
|
||||||
$chunks = array_chunk($result, $perPage);
|
$chunks = array_chunk($result, $perPage);
|
||||||
$currentData = $chunks[$page-1] ?? [];
|
$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([
|
return response()->json([
|
||||||
'status' => true,
|
'status' => true,
|
||||||
'data' => $currentData,
|
'data' => $currentData,
|
||||||
'message' => 'Berhasil mendapatkan data',
|
'message' => 'Berhasil mendapatkan data',
|
||||||
|
'summary' => [
|
||||||
|
'total_units' => $total,
|
||||||
|
'grand_total_files' => $grandTotalFiles,
|
||||||
|
'current_page_total_files' => $currentPageTotalFiles,
|
||||||
|
],
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'current_page' => $page,
|
'current_page' => $page,
|
||||||
'per_page' => $perPage,
|
'per_page' => $perPage,
|
||||||
'total' => $total,
|
'total' => $total,
|
||||||
'last_page' => max(1, ceil($total / $perPage)),
|
'last_page' => $lastPage,
|
||||||
'has_more' => $page < max(1, ceil($total / $perPage)),
|
'has_more' => $page < $lastPage,
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
|
|||||||
@ -51,6 +51,30 @@
|
|||||||
.table-fixed-height {
|
.table-fixed-height {
|
||||||
min-height: 70vh;
|
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 --- */
|
/* --- Warna kategori baris --- */
|
||||||
.row-shade {
|
.row-shade {
|
||||||
|
|||||||
@ -49,6 +49,30 @@
|
|||||||
.table-fixed-height {
|
.table-fixed-height {
|
||||||
min-height: 70vh;
|
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 --- */
|
/* --- Warna kategori baris --- */
|
||||||
.row-shade {
|
.row-shade {
|
||||||
|
|||||||
@ -1,4 +1,32 @@
|
|||||||
@php($showRecapTitle = $showRecapTitle ?? true)
|
@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">
|
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||||
@if ($showRecapTitle)
|
@if ($showRecapTitle)
|
||||||
<div>
|
<div>
|
||||||
@ -25,7 +53,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<table class="table table-sm table-hover align-middle">
|
||||||
<thead class="table-light shadow-sm">
|
<thead class="table-light shadow-sm">
|
||||||
<tr>
|
<tr>
|
||||||
@ -42,13 +70,16 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
<div class="d-flex flex-column flex-md-row align-items-center justify-content-between gap-2 mt-3" id="recapPagination"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => fetchRecap());
|
document.addEventListener('DOMContentLoaded', () => fetchRecap());
|
||||||
|
|
||||||
let recapDebounce;
|
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){
|
function debouncedRecapSearch(val){
|
||||||
clearTimeout(recapDebounce);
|
clearTimeout(recapDebounce);
|
||||||
@ -68,48 +99,77 @@ function changePerPage(val){
|
|||||||
function fetchRecap(){
|
function fetchRecap(){
|
||||||
const tbody = document.getElementById('recapBody');
|
const tbody = document.getElementById('recapBody');
|
||||||
const pager = document.getElementById('recapPagination');
|
const pager = document.getElementById('recapPagination');
|
||||||
|
const summaryEl = document.getElementById('recapSummary');
|
||||||
if(!tbody) return;
|
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>`;
|
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Memuat data...</td></tr>`;
|
||||||
if(pager) pager.innerHTML = '';
|
if(pager) pager.innerHTML = '';
|
||||||
|
if(summaryEl) summaryEl.textContent = 'Memuat ringkasan rekap...';
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
page: recapState.page,
|
page: recapState.page,
|
||||||
per_page: recapState.perPage,
|
per_page: recapState.perPage,
|
||||||
keyword: recapState.keyword || ''
|
keyword: recapState.keyword || ''
|
||||||
});
|
});
|
||||||
fetch('/data/recap?' + params.toString())
|
fetch('/data/recap?' + params.toString(), {
|
||||||
|
signal: recapRequestController.signal,
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
|
if (requestToken !== recapRequestToken) return;
|
||||||
const rows = json?.data || [];
|
const rows = json?.data || [];
|
||||||
|
recapState.page = json?.pagination?.current_page || recapState.page;
|
||||||
recapState.lastPage = json?.pagination?.last_page || 1;
|
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){
|
if(!rows.length){
|
||||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Tidak ada data</td></tr>`;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let grandTotal = 0;
|
|
||||||
const html = rows.map((row, idx) => {
|
const html = rows.map((row, idx) => {
|
||||||
|
const rowNumber = ((recapState.page - 1) * recapState.perPage) + idx + 1;
|
||||||
const folderRows = (row.data || []).map((f, i) => `
|
const folderRows = (row.data || []).map((f, i) => `
|
||||||
<tr>
|
<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>` : ''}
|
${i === 0 ? `<td rowspan="${row.data.length}" class="fw-semibold">${row.unit || '-'}</td>` : ''}
|
||||||
<td>${f.folder || '-'}</td>
|
<td>${f.folder || '-'}</td>
|
||||||
<td class="text-center fw-bold">${f.count || 0}</td>
|
<td class="text-center fw-bold">${f.count || 0}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
(row.data || []).forEach(f => { grandTotal += (parseInt(f.count, 10) || 0); });
|
|
||||||
return folderRows;
|
return folderRows;
|
||||||
}).join('');
|
}).join('');
|
||||||
tbody.innerHTML = html + `
|
tbody.innerHTML = html + `
|
||||||
<tr class="table-light">
|
<tr class="table-light">
|
||||||
<td colspan="3" class="text-end fw-semibold">Total File</td>
|
<td colspan="3" class="text-end fw-semibold">Subtotal Halaman Ini</td>
|
||||||
<td class="text-center fw-bold">${grandTotal}</td>
|
<td class="text-center fw-bold">${recapState.currentPageTotalFiles}</td>
|
||||||
</tr>
|
</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();
|
renderRecapPagination();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
if (err?.name === 'AbortError') return;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-danger py-4">Gagal memuat data</td></tr>`;
|
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