add expired menu

This commit is contained in:
JokoPrasetio 2026-04-06 10:10:29 +07:00
parent a0b32672b4
commit 1b07699476
7 changed files with 1882 additions and 13 deletions

View File

@ -195,6 +195,10 @@ class DashboardController extends Controller
->when(!empty($unitFilter), function($q) use($unitFilter){
$q->whereIn('id_unit_kerja', $unitFilter);
})
->where(function ($q) {
$q->whereNull('tgl_expired')
->orWhereDate('tgl_expired', '>=', now()->toDateString());
})
->where('statusenabled', true)
->where('status_action', 'approved');
@ -1041,6 +1045,10 @@ class DashboardController extends Controller
}
$baseQuery = FileDirectory::with('kategori')
->where('statusenabled', true)->where('status_action', 'approved')
->where(function ($q) {
$q->whereNull('tgl_expired')
->orWhereDate('tgl_expired', '>=', now()->toDateString());
})
->when(!empty($unitIds), function ($q) use ($unitIds) {
$q->whereIn('id_unit_kerja', $unitIds);
});
@ -1336,8 +1344,8 @@ class DashboardController extends Controller
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $user->id)->where('isprimary', true)
->first();
$payloadLog = [
'file_directory_id' => $fileDirectoryId,
'pegawai_id_entry' => auth()->user()->dataUser->id,
@ -1560,7 +1568,6 @@ class DashboardController extends Controller
]
]);
} catch (\Throwable $th) {
dd($th);
return response()->json([
'status' => false,
'message' => 'Gagal! mendapatkan data'
@ -1571,7 +1578,11 @@ class DashboardController extends Controller
private function buildRecapData(array $unitIds, string $keyword = ''): array
{
$rows = FileDirectory::where('statusenabled', true)
->whereNotNull('status_action')->where('status_action', 'approved');
->where(function ($q) {
$q->whereNull('tgl_expired')
->orWhereDate('tgl_expired', '>=', now()->toDateString());
})
->whereNotNull('status_action')->where('status_action', 'approved');
if(in_array(22, $unitIds)){
$rows = $rows->pluck('file');
}elseif(auth()->user()->username === "admin.turt"){
@ -2455,4 +2466,249 @@ class DashboardController extends Controller
]
]);
}
public function expDokumen(){
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
$authUnitKerja = $authMapping->objectunitkerjapegawaifk ?? null;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk ?? null;
$data = [
'title' => 'Dashboard',
'katDok' => $katDok,
'authUnitKerja' => $authUnitKerja,
'authSubUnitKerja' => $authSubUnitKerja,
];
return view('expDokumen.index', $data);
}
public function dataUnitExp(){
$perPage = (int) request('per_page', 10);
// $authUnitId = auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->objectunitkerjapegawaifk;
$userId = auth()->user()->dataUser->id ?? 937;
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $userId)
->get(['objectunitkerjapegawaifk']);
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')
->filter() // buang null
->unique()
->values()
->toArray();
$keyword = request('keyword');
$kategori = request('kategori');
$kategoriHeader = request('kategori_header');
$unitFilter = request('unit');
$kategoriValues = is_array($kategori)
? array_values(array_filter($kategori))
: array_values(array_filter(explode(',', (string) $kategori)));
$kategoriHeaderValues = is_array($kategoriHeader)
? array_values(array_filter($kategoriHeader))
: array_values(array_filter(explode(',', (string) $kategoriHeader)));
$allKategoriValues = array_values(array_filter(array_merge($kategoriValues, $kategoriHeaderValues)));
$kategoriTypes = [];
$kategoriIds = [];
$kategoriHukumValues = [];
foreach ($allKategoriValues as $val) {
$lower = strtolower(trim((string) $val));
if (str_starts_with($lower, 'hukum:')) {
$hukumVal = trim(substr((string) $val, strlen('hukum:')));
if ($hukumVal !== '') {
$kategoriHukumValues[] = $hukumVal;
}
continue;
}
if (in_array($lower, ['akreditasi', 'akre'], true)) {
$kategoriTypes[] = 'akreditasi';
continue;
}
if ($lower === 'hukum') {
$kategoriTypes[] = 'hukum';
continue;
}
if ($lower === 'lainnya') {
$kategoriTypes[] = 'lainnya';
continue;
}
$kategoriIds[] = $val;
}
$baseQuery = FileDirectory::with('kategori')
->when(!empty($unitFilter), function($q) use($unitFilter){
$q->whereIn('id_unit_kerja', $unitFilter);
})
->whereNotNull('tgl_expired')
->whereDate('tgl_expired', '<', now()->toDateString())
->where('statusenabled', true)
->where('status_action', 'approved');
if(in_array(22, $unitIds)){
}elseif(auth()->user()->username === "admin.turt"){
}else{
$baseQuery = $baseQuery->whereIn('id_unit_kerja', $unitIds);
}
$query = (clone $baseQuery)
->when(!empty($kategoriIds) || !empty($kategoriTypes) || !empty($kategoriHukumValues), function ($q) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$q->where(function ($sub) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$hasClause = false;
if (!empty($kategoriIds)) {
$sub->whereIn('master_kategori_directory_id', $kategoriIds);
$hasClause = true;
}
if (!empty($kategoriHukumValues)) {
$hasClause ? $sub->orWhereIn('kategori_hukum', $kategoriHukumValues) : $sub->whereIn('kategori_hukum', $kategoriHukumValues);
$hasClause = true;
}
if (in_array('akreditasi', $kategoriTypes, true)) {
$sub->where('is_akre', true);
$hasClause = true;
}
if (in_array('hukum', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('kategori_hukum') : $sub->whereNotNull('kategori_hukum');
$hasClause = true;
}
if (in_array('lainnya', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('master_kategori_directory_id') : $sub->whereNotNull('master_kategori_directory_id');
}
});
})
->when($keyword, function ($q) use ($keyword) {
$q->where(function ($sub) use ($keyword) {
$sub->where('nama_dokumen', 'ILIKE', "%{$keyword}%")
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
});
});
$data = $query->orderBy('entry_at', 'desc')
->paginate($perPage);
$items = collect($data->items())->map(function($item){
$item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null;
return $item;
});
$kategoriList = (clone $baseQuery)->get()->map(function($item){
if (!empty($item->kategori_hukum)) {
return ['id' => 'hukum:' . $item->kategori_hukum, 'label' => $item->kategori_hukum];
}
$label = $item->kategori->nama_kategori_directory ?? $item->nama_kategori_directory ?? 'Kategori';
$id = $item->master_kategori_directory_id ?? $label;
return ['id' => (string) $id, 'label' => $label];
})->unique('id')->values();
$payload = [
'status' => true,
'message' => 'Berhasil mendapatkan data',
'data' => $items,
'kategori_list' => $kategoriList,
'pagination' => [
'current_page' => $data->currentPage(),
'next_page' => $data->hasMorePages() ? $data->currentPage() + 1 : null,
'has_more' => $data->hasMorePages(),
'last_page' => $data->lastPage(),
'per_page' => $data->perPage(),
'total' => $data->total(),
]
];
return response()->json($payload);
}
public function recapDataExp(){
try {
$perPage = (int) request('per_page', 10);
$page = max(1, (int) request('page', 1));
$keyword = strtolower(request('keyword', ''));
// $authUnit = auth()->user()->masterPersetujuan->details->pluck('unit_pegawai_id')->unique()->toArray();
$user = auth()->user()->dataUser;
$unitIds = [];
if($user){
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $user->id)
->get(['objectunitkerjapegawaifk', 'objectsubunitkerjapegawaifk']);
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')
->filter() // buang null
->unique()
->values()
->all();
}
$result = $this->buildRecapDataExp($unitIds, $keyword);
// paginate manually
$total = count($result);
$chunks = array_chunk($result, $perPage);
$currentData = $chunks[$page-1] ?? [];
return response()->json([
'status' => true,
'data' => $currentData,
'message' => 'Berhasil mendapatkan data',
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'last_page' => max(1, ceil($total / $perPage)),
'has_more' => $page < max(1, ceil($total / $perPage)),
]
]);
} catch (\Throwable $th) {
return response()->json([
'status' => false,
'message' => 'Gagal! mendapatkan data'
]);
}
}
private function buildRecapDataExp(array $unitIds, string $keyword = ''): array
{
$rows = FileDirectory::where('statusenabled', true)
->whereNotNull('tgl_expired')
->whereDate('tgl_expired', '<', now()->toDateString())
->whereNotNull('status_action')->where('status_action', 'approved');
if(in_array(22, $unitIds)){
$rows = $rows->pluck('file');
}elseif(auth()->user()->username === "admin.turt"){
$rows = $rows->pluck('file');
}else{
$rows = $rows->whereIn('id_unit_kerja', $unitIds)->pluck('file');
}
$grouped = [];
foreach ($rows as $path) {
$parts = array_values(array_filter(explode('/', $path)));
if (count($parts) < 4) {
continue;
}
$unit = $parts[0];
$folder = $parts[2];
if ($keyword) {
$hit = str_contains(strtolower($unit), $keyword) || str_contains(strtolower($folder), $keyword);
if (!$hit) {
continue;
}
}
if (!isset($grouped[$unit])) {
$grouped[$unit] = [];
}
if (!isset($grouped[$unit][$folder])) {
$grouped[$unit][$folder] = 0;
}
$grouped[$unit][$folder]++;
}
$result = [];
foreach ($grouped as $unitName => $folders) {
$data = [];
foreach ($folders as $folder => $count) {
$data[] = [
'folder' => $folder,
'count' => $count,
];
}
usort($data, fn($a, $b) => $b['count'] <=> $a['count']);
$result[] = [
'unit' => $unitName,
'data' => $data,
];
}
return $result;
}
}

View File

@ -542,7 +542,7 @@
if(pageData.length === 0){
tbody.innerHTML = `
<tr>
<td colspan="7" class="text-center text-muted py-4">
<td colspan="8" class="text-center text-muted py-4">
Tidak ada data yang cocok
</td>
</tr>

View File

@ -664,7 +664,7 @@
if(pageData.length === 0){
tbody.innerHTML = `
<tr>
<td colspan="7" class="text-center text-muted py-4">
<td colspan="8" class="text-center text-muted py-4">
Tidak ada data yang cocok
</td>
</tr>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
@php($showRecapTitle = $showRecapTitle ?? true)
<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 Expired</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" style="max-height: 55vh; overflow-y:auto;">
<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="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 };
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');
if(!tbody) return;
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Memuat data...</td></tr>`;
if(pager) pager.innerHTML = '';
const params = new URLSearchParams({
page: recapState.page,
per_page: recapState.perPage,
keyword: recapState.keyword || ''
});
fetch('/data/recapExp?' + params.toString())
.then(res => res.json())
.then(json => {
const rows = json?.data || [];
recapState.lastPage = json?.pagination?.last_page || 1;
if(!rows.length){
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Tidak ada data</td></tr>`;
return;
}
let grandTotal = 0;
const html = rows.map((row, idx) => {
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="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>
</tr>
`;
renderRecapPagination();
})
.catch(err => {
console.error(err);
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-danger py-4">Gagal memuat data</td></tr>`;
});
}
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>

View File

@ -106,6 +106,13 @@
</li> --}}
{{-- MASTER --}}
<li class="nav-small-cap"><span class="hide-menu">History</span></li>
<li class="sidebar-item">
<a class="sidebar-link" href="{{ url('/expired-dokumen') }}" aria-expanded="false">
<i class="ti ti-clock"></i>
<span class="hide-menu">Expired Dokumen</span>
</a>
</li>
@if(!Auth::guard('admin')->check())
@if(auth()->user()->dataUser->mappingUnitKerjaPegawai()->where('objectunitkerjapegawaifk', 43)->exists())
<li class="nav-small-cap"><span class="hide-menu">Master</span></li>

View File

@ -66,18 +66,22 @@ Route::middleware(['auth:admin,web'])->group(function(){
Route::post('/pengajuan-file/{id}/update', [DashboardController::class, 'updatePengajuanFile']);
// Route::middleware(['master.persetujuan'])->group(function () {
Route::get('/pending-file', [DashboardController::class, 'pendingFile']);
Route::get('/datatable/pending-file', [DashboardController::class, 'dataPendingFile']);
Route::post('/pending-file/{id}/approve', [DashboardController::class, 'approvePendingFile']);
Route::post('/pending-file/approve-multiple', [DashboardController::class, 'approvePendingFileMultiple']);
Route::post('/pending-file/{id}/reject', [DashboardController::class, 'rejectPendingFile']);
Route::get('/data/count-pending', [DashboardController::class, 'countDataPending']);
Route::get('/data/count-rejected', [DashboardController::class, 'countRejectedPengajuan']);
Route::get('/pending-file', [DashboardController::class, 'pendingFile']);
Route::get('/datatable/pending-file', [DashboardController::class, 'dataPendingFile']);
Route::post('/pending-file/{id}/approve', [DashboardController::class, 'approvePendingFile']);
Route::post('/pending-file/approve-multiple', [DashboardController::class, 'approvePendingFileMultiple']);
Route::post('/pending-file/{id}/reject', [DashboardController::class, 'rejectPendingFile']);
Route::get('/data/count-pending', [DashboardController::class, 'countDataPending']);
Route::get('/data/count-rejected', [DashboardController::class, 'countRejectedPengajuan']);
// });
Route::get('/data/notifications', [DashboardController::class, 'notifkasiList']);
Route::post('/data/notifications/read', [DashboardController::class, 'notifkasiMarkRead']);
Route::get('/data/log-dokumen', [DashboardController::class, 'logDokumen']);
Route::get('/expired-dokumen', [DashboardController::class, 'expDokumen']);
Route::get('/data/expired-dokumen', [DashboardController::class, 'dataUnitExp']);
Route::get('/data/recapExp', [DashboardController::class, 'recapDataExp']);
});
Route::get('/login', [AuthController::class, 'index'])->name('login');