fixing download admin
This commit is contained in:
parent
8c0aecd4aa
commit
5786369973
@ -39,7 +39,6 @@ class AuthController extends Controller
|
|||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
return redirect()->intended('/');
|
return redirect()->intended('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// Login Admin
|
// Login Admin
|
||||||
// =====================
|
// =====================
|
||||||
|
|||||||
@ -147,7 +147,7 @@ class DashboardController extends Controller
|
|||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
$isAdmin = auth('admin')->check();
|
$isAdmin = auth('admin')->check();
|
||||||
$pegawaiId = $user?->dataUser?->id;
|
$pegawaiId = $user?->dataUser?->id;
|
||||||
|
|
||||||
$unitIds = [];
|
$unitIds = [];
|
||||||
if (!$isAdmin) {
|
if (!$isAdmin) {
|
||||||
if (!$pegawaiId) {
|
if (!$pegawaiId) {
|
||||||
@ -226,7 +226,6 @@ class DashboardController extends Controller
|
|||||||
->where('statusenabled', true)
|
->where('statusenabled', true)
|
||||||
->where('status_action', 'approved');
|
->where('status_action', 'approved');
|
||||||
|
|
||||||
if (!$isAdmin) {
|
|
||||||
if (in_array(22, $unitIds, true)) {
|
if (in_array(22, $unitIds, true)) {
|
||||||
// akses semua unit
|
// akses semua unit
|
||||||
} elseif (($user?->username === 'admin.turt')) {
|
} elseif (($user?->username === 'admin.turt')) {
|
||||||
@ -234,7 +233,6 @@ class DashboardController extends Controller
|
|||||||
} else {
|
} else {
|
||||||
$baseQuery = $baseQuery->whereIn('id_unit_kerja', $unitIds);
|
$baseQuery = $baseQuery->whereIn('id_unit_kerja', $unitIds);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$query = (clone $baseQuery)
|
$query = (clone $baseQuery)
|
||||||
->when(!empty($kategoriIds) || !empty($kategoriTypes) || !empty($kategoriHukumValues), function ($q) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
|
->when(!empty($kategoriIds) || !empty($kategoriTypes) || !empty($kategoriHukumValues), function ($q) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
|
||||||
@ -963,22 +961,25 @@ class DashboardController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$user = auth()->user()->dataUser;
|
$user = auth()->user()->dataUser;
|
||||||
|
if($user){
|
||||||
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
|
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
|
||||||
->where('objectpegawaifk', $user->id)->where('isprimary', true)
|
->where('objectpegawaifk', $user->id)->where('isprimary', true)
|
||||||
->first();
|
->first();
|
||||||
LogActivity::create([
|
LogActivity::create([
|
||||||
'file_directory_id' => $id,
|
'file_directory_id' => $id,
|
||||||
'pegawai_id_entry' => $user->id,
|
'pegawai_id_entry' => $user->id,
|
||||||
'pegawai_nama_entry' => $user->namalengkap,
|
'pegawai_nama_entry' => $user->namalengkap,
|
||||||
'entry_at' => now(),
|
'entry_at' => now(),
|
||||||
'action_type' => 'Download Dokumen',
|
'action_type' => 'Download Dokumen',
|
||||||
'no_dokumen' => $data->no_dokumen,
|
'no_dokumen' => $data->no_dokumen,
|
||||||
'file' => $data->file,
|
'file' => $data->file,
|
||||||
'statusenabled' => true,
|
'statusenabled' => true,
|
||||||
'mod_change' => null,
|
'mod_change' => null,
|
||||||
'id_unit_kerja' => $mapping ? $mapping->objectunitkerjapegawaifk : null,
|
'id_unit_kerja' => $mapping ? $mapping->objectunitkerjapegawaifk : null,
|
||||||
'id_sub_unit_kerja' => $mapping ? $mapping->objectsubunitkerjapegawaifk : null,
|
'id_sub_unit_kerja' => $mapping ? $mapping->objectsubunitkerjapegawaifk : null,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$tempDir = storage_path('app/temp');
|
$tempDir = storage_path('app/temp');
|
||||||
if (!is_dir($tempDir)) {
|
if (!is_dir($tempDir)) {
|
||||||
@ -2711,7 +2712,20 @@ class DashboardController extends Controller
|
|||||||
{
|
{
|
||||||
$keyword = $request->query('keyword');
|
$keyword = $request->query('keyword');
|
||||||
$perPage = (int) $request->query('per_page', 10);
|
$perPage = (int) $request->query('per_page', 10);
|
||||||
$query = FileDirectory::where('statusenabled', true)
|
$perPage = max(1, min(100, $perPage));
|
||||||
|
|
||||||
|
$query = FileDirectory::query()
|
||||||
|
->select([
|
||||||
|
'file_directory_id',
|
||||||
|
'nama_dokumen',
|
||||||
|
'no_dokumen',
|
||||||
|
'file',
|
||||||
|
'entry_at',
|
||||||
|
'id_unit_kerja',
|
||||||
|
'is_akre',
|
||||||
|
])
|
||||||
|
->with(['unit:id,name'])
|
||||||
|
->where('statusenabled', true)
|
||||||
->where('status_action', 'approved')
|
->where('status_action', 'approved')
|
||||||
->where('is_akre', true);
|
->where('is_akre', true);
|
||||||
|
|
||||||
@ -2723,7 +2737,9 @@ class DashboardController extends Controller
|
|||||||
->orWhere('file', 'ILIKE', "%{$keyword}%"); // Cari juga berdasarkan nama file di path
|
->orWhere('file', 'ILIKE', "%{$keyword}%"); // Cari juga berdasarkan nama file di path
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$data = $query->orderBy('entry_at', 'desc')->paginate($perPage);
|
$data = $query
|
||||||
|
->orderBy('entry_at', 'desc')
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => true,
|
'status' => true,
|
||||||
@ -2731,10 +2747,14 @@ class DashboardController extends Controller
|
|||||||
'data' => $data->items(),
|
'data' => $data->items(),
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'current_page' => $data->currentPage(),
|
'current_page' => $data->currentPage(),
|
||||||
|
'next_page' => $data->hasMorePages() ? $data->currentPage() + 1 : null,
|
||||||
|
'prev_page' => $data->currentPage() > 1 ? $data->currentPage() - 1 : null,
|
||||||
'total' => $data->total(),
|
'total' => $data->total(),
|
||||||
'per_page' => $data->perPage(),
|
'per_page' => $data->perPage(),
|
||||||
'last_page' => $data->lastPage(),
|
'last_page' => $data->lastPage(),
|
||||||
'has_more' => $data->hasMorePages(),
|
'has_more' => $data->hasMorePages(),
|
||||||
|
'from' => $data->firstItem(),
|
||||||
|
'to' => $data->lastItem(),
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -144,6 +144,8 @@
|
|||||||
let expandedFolders = new Set();
|
let expandedFolders = new Set();
|
||||||
let searchTimer;
|
let searchTimer;
|
||||||
const btn = document.getElementById('btnDownloadMultiple');
|
const btn = document.getElementById('btnDownloadMultiple');
|
||||||
|
const tableState = { page: 1, pageSize: 10, lastPage: 1, total: 0, from: 0, to: 0, search: '' };
|
||||||
|
const paginationEl = document.getElementById('paginationControls');
|
||||||
// === INITIALIZATION ===
|
// === INITIALIZATION ===
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
fetchData();
|
fetchData();
|
||||||
@ -156,6 +158,7 @@
|
|||||||
document.getElementById('tableSearch').addEventListener('input', (e) => {
|
document.getElementById('tableSearch').addEventListener('input', (e) => {
|
||||||
clearTimeout(searchTimer);
|
clearTimeout(searchTimer);
|
||||||
searchTimer = setTimeout(() => {
|
searchTimer = setTimeout(() => {
|
||||||
|
tableState.page = 1;
|
||||||
fetchData(e.target.value, 1);
|
fetchData(e.target.value, 1);
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
@ -163,7 +166,8 @@
|
|||||||
// Per Page Change
|
// Per Page Change
|
||||||
document.getElementById('tablePageSize').addEventListener('change', (e) => {
|
document.getElementById('tablePageSize').addEventListener('change', (e) => {
|
||||||
const keyword = document.getElementById('tableSearch').value;
|
const keyword = document.getElementById('tableSearch').value;
|
||||||
fetchData(keyword,1);
|
tableState.page = 1;
|
||||||
|
fetchData(keyword, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check All Rows
|
// Check All Rows
|
||||||
@ -185,7 +189,11 @@
|
|||||||
*/
|
*/
|
||||||
function fetchData(keyword = '', page = 1) {
|
function fetchData(keyword = '', page = 1) {
|
||||||
const tbody = document.getElementById('tableDataAkreditasi');
|
const tbody = document.getElementById('tableDataAkreditasi');
|
||||||
const perPage = document.getElementById('tablePageSize').value;
|
const perPage = parseInt(document.getElementById('tablePageSize').value, 10) || 10;
|
||||||
|
|
||||||
|
tableState.search = keyword;
|
||||||
|
tableState.pageSize = perPage;
|
||||||
|
tableState.page = page;
|
||||||
|
|
||||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center">Memuat data...</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center">Memuat data...</td></tr>';
|
||||||
|
|
||||||
@ -195,7 +203,13 @@
|
|||||||
if (response.status) {
|
if (response.status) {
|
||||||
currentData = response.data || [];
|
currentData = response.data || [];
|
||||||
renderTable(keyword);
|
renderTable(keyword);
|
||||||
updateSummary(response.pagination.total);
|
const pag = response.pagination || {};
|
||||||
|
tableState.total = pag.total || 0;
|
||||||
|
tableState.lastPage = pag.last_page || 1;
|
||||||
|
tableState.from = pag.from || 0;
|
||||||
|
tableState.to = pag.to || 0;
|
||||||
|
renderPagination(tableState.lastPage);
|
||||||
|
updateSummary();
|
||||||
} else {
|
} else {
|
||||||
tbody.innerHTML = `<tr><td colspan="6" class="text-center text-danger">${response.message}</td></tr>`;
|
tbody.innerHTML = `<tr><td colspan="6" class="text-center text-danger">${response.message}</td></tr>`;
|
||||||
}
|
}
|
||||||
@ -427,9 +441,63 @@
|
|||||||
countLabel.innerText = `${selectedIds.length} dipilih`;
|
countLabel.innerText = `${selectedIds.length} dipilih`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSummary(total) {
|
function updateSummary() {
|
||||||
const summary = document.getElementById('tableSummary');
|
const summary = document.getElementById('tableSummary');
|
||||||
if (summary) summary.innerText = `Total: ${total} data`;
|
if (!summary) return;
|
||||||
|
|
||||||
|
if (!tableState.total) {
|
||||||
|
summary.innerText = 'Tidak ada data';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = tableState.from || 0;
|
||||||
|
const to = tableState.to || 0;
|
||||||
|
summary.innerText = `Menampilkan ${from} - ${to} dari ${tableState.total} data`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(totalPages){
|
||||||
|
if(!paginationEl) return;
|
||||||
|
if(totalPages <= 1){
|
||||||
|
paginationEl.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxButtons = 5;
|
||||||
|
let start = Math.max(1, tableState.page - Math.floor(maxButtons/2));
|
||||||
|
let end = Math.min(totalPages, start + maxButtons - 1);
|
||||||
|
start = Math.max(1, end - maxButtons + 1);
|
||||||
|
|
||||||
|
let buttons = '';
|
||||||
|
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="prev" ${tableState.page === 1 ? 'disabled' : ''}>‹ Sebelumnya</button>`;
|
||||||
|
|
||||||
|
for(let i=start; i<=end; i++){
|
||||||
|
buttons += `<button class="btn btn-sm ${i === tableState.page ? 'btn-primary' : 'btn-outline-secondary'}" data-page="${i}">${i}</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="next" ${tableState.page === totalPages ? 'disabled' : ''}>Berikutnya ›</button>`;
|
||||||
|
|
||||||
|
paginationEl.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 ${tableState.page} dari ${totalPages}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(paginationEl){
|
||||||
|
paginationEl.addEventListener('click', (e) => {
|
||||||
|
const page = e.target.getAttribute('data-page');
|
||||||
|
if(!page) return;
|
||||||
|
|
||||||
|
if(page === 'prev' && tableState.page > 1) tableState.page--;
|
||||||
|
else if(page === 'next'){
|
||||||
|
if(tableState.page < tableState.lastPage) tableState.page++;
|
||||||
|
}else{
|
||||||
|
tableState.page = parseInt(page, 10) || 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData(tableState.search || '', tableState.page);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadBtn = document.getElementById('btnDownloadMultiple');
|
const downloadBtn = document.getElementById('btnDownloadMultiple');
|
||||||
|
|||||||
@ -177,132 +177,358 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
||||||
|
|
||||||
function setList(listEl, items, itemClass = '') {
|
/*
|
||||||
if (!listEl) return;
|
|--------------------------------------------------------------------------
|
||||||
|
| HELPER
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
if (!items.length) {
|
function escapeHtml(value) {
|
||||||
listEl.innerHTML = '<div class="dropdown-item text-muted small">Tidak ada notifikasi</div>';
|
return String(value ?? '')
|
||||||
return;
|
.replaceAll('&', '&')
|
||||||
}
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
listEl.innerHTML = items.map(item => `
|
.replaceAll('"', '"')
|
||||||
<a href="${item.url || '#'}" class="dropdown-item d-flex align-items-start gap-2 ${itemClass}" data-notif-id="${item.id ?? ''}">
|
.replaceAll("'", ''');
|
||||||
<span class="badge ${item.is_read ? 'bg-secondary' : 'bg-primary'} rounded-circle"
|
|
||||||
style="width:10px;height:10px;margin-top:6px;"></span>
|
|
||||||
<div>
|
|
||||||
<div class="fw-semibold">${item.text_notifikasi || '-'}</div>
|
|
||||||
<div class="small text-muted">${item.created_at || ''}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
`).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydrateNotification(endpoint, els, errorText) {
|
function ajaxGet(url) {
|
||||||
fetch(endpoint)
|
return fetch(url, {
|
||||||
.then(r => r.json())
|
method: 'GET',
|
||||||
.then(res => {
|
headers: {
|
||||||
const unread = res?.status ? Number(res.unread || 0) : 0;
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
const items = res?.status ? (res.data || []) : [];
|
'Accept': 'application/json'
|
||||||
|
|
||||||
// 🔴 HANDLE DOT
|
|
||||||
if (els.dotEl) {
|
|
||||||
if (unread > 0) {
|
|
||||||
els.dotEl.classList.remove('d-none');
|
|
||||||
} else {
|
|
||||||
els.dotEl.classList.add('d-none');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setList(els.listEl, items, els.itemClass || '');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (els.listEl) {
|
|
||||||
els.listEl.innerHTML = `<div class="dropdown-item text-muted small">${errorText}</div>`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachMarkRead(toggleEl, els, endpoint) {
|
function ajaxPost(url) {
|
||||||
if (!toggleEl) return;
|
return fetch(url, {
|
||||||
|
|
||||||
toggleEl.addEventListener('show.bs.dropdown', () => {
|
|
||||||
fetch(endpoint, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': csrfToken,
|
'X-CSRF-TOKEN': csrfToken,
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
}).then(() => {
|
|
||||||
if (els.dotEl) els.dotEl.classList.add('d-none');
|
|
||||||
}).catch(() => {});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔴 EXPIRED
|
/*
|
||||||
const expiredEls = {
|
|--------------------------------------------------------------------------
|
||||||
listEl: document.getElementById('expiredNotifList'),
|
| NOTIFIKASI BIASA
|
||||||
dotEl: document.getElementById('expiredDot'),
|
|--------------------------------------------------------------------------
|
||||||
itemClass: 'js-expired-notif-item'
|
*/
|
||||||
};
|
|
||||||
|
|
||||||
hydrateNotification('/data/expired-notifications', expiredEls, 'Gagal memuat notifikasi expired');
|
const notifListEl = document.getElementById('notifList');
|
||||||
attachMarkRead(
|
const notifCountTextEl = document.getElementById('notifCountText');
|
||||||
document.getElementById('dropExpired'),
|
const notifBadgeEl = document.getElementById('notifCountBadge');
|
||||||
expiredEls,
|
const notifToggle = document.getElementById('dropNotif') || document.getElementById('drop1');
|
||||||
'/data/expired-notifications/read'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🔔 NOTIF
|
function setNotifList(items) {
|
||||||
const notifEls = {
|
if (!notifListEl) return;
|
||||||
listEl: document.getElementById('notifList'),
|
|
||||||
dotEl: document.getElementById('notifDot')
|
|
||||||
};
|
|
||||||
|
|
||||||
hydrateNotification('/data/notifications', notifEls, 'Gagal memuat notifikasi');
|
if (!items || !items.length) {
|
||||||
attachMarkRead(
|
notifListEl.innerHTML = `
|
||||||
document.getElementById('dropNotif'),
|
<div class="dropdown-item text-muted small">
|
||||||
notifEls,
|
Tidak ada notifikasi
|
||||||
'/data/notifications/read'
|
</div>
|
||||||
);
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifListEl.innerHTML = items.map(item => {
|
||||||
|
const url = item.url || '#';
|
||||||
|
const text = escapeHtml(item.text_notifikasi || '-');
|
||||||
|
const createdAt = escapeHtml(item.created_at || '');
|
||||||
|
const isRead = item.is_read ? true : false;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<a href="${url}" class="dropdown-item d-flex align-items-start gap-2">
|
||||||
|
<span class="badge ${isRead ? 'bg-secondary' : 'bg-primary'} rounded-circle"
|
||||||
|
style="width:10px;height:10px;margin-top:6px;"></span>
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold">${text}</div>
|
||||||
|
<div class="small text-muted">${createdAt}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNotifBadge(unread) {
|
||||||
|
unread = Number(unread || 0);
|
||||||
|
|
||||||
|
if (notifCountTextEl) {
|
||||||
|
notifCountTextEl.textContent = `${unread} baru`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notifBadgeEl) return;
|
||||||
|
|
||||||
|
if (unread > 0) {
|
||||||
|
notifBadgeEl.classList.remove('d-none');
|
||||||
|
notifBadgeEl.textContent = unread > 99 ? '99+' : unread;
|
||||||
|
} else {
|
||||||
|
notifBadgeEl.classList.add('d-none');
|
||||||
|
notifBadgeEl.textContent = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadNotifications() {
|
||||||
|
ajaxGet('/data/notifications')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(res => {
|
||||||
|
const unread = res?.status ? Number(res.unread || 0) : 0;
|
||||||
|
const items = res?.status ? (res.data || []) : [];
|
||||||
|
|
||||||
|
setNotifBadge(unread);
|
||||||
|
setNotifList(items);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (notifListEl) {
|
||||||
|
notifListEl.innerHTML = `
|
||||||
|
<div class="dropdown-item text-muted small">
|
||||||
|
Gagal memuat notifikasi
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotifBadge(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function markNotificationsAsRead() {
|
||||||
|
ajaxPost('/data/notifications/read')
|
||||||
|
.then(() => {
|
||||||
|
setNotifBadge(0);
|
||||||
|
|
||||||
|
if (notifListEl) {
|
||||||
|
notifListEl.querySelectorAll('.badge').forEach(badge => {
|
||||||
|
badge.classList.remove('bg-primary');
|
||||||
|
badge.classList.add('bg-secondary');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifToggle) {
|
||||||
|
notifToggle.addEventListener('show.bs.dropdown', function() {
|
||||||
|
markNotificationsAsRead();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| NOTIFIKASI EXPIRED
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
const expiredListEl = document.getElementById('expiredNotifList');
|
||||||
|
const expiredDotEl = document.getElementById('expiredDot');
|
||||||
|
const expiredToggle = document.getElementById('dropExpired');
|
||||||
|
|
||||||
|
function setExpiredList(items) {
|
||||||
|
if (!expiredListEl) return;
|
||||||
|
|
||||||
|
if (!items || !items.length) {
|
||||||
|
expiredListEl.innerHTML = `
|
||||||
|
<div class="dropdown-item text-muted small">
|
||||||
|
Tidak ada notifikasi expired
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expiredListEl.innerHTML = items.map(item => {
|
||||||
|
const url = item.url || '#';
|
||||||
|
const text = escapeHtml(item.text_notifikasi || '-');
|
||||||
|
const createdAt = escapeHtml(item.created_at || '');
|
||||||
|
const isRead = item.is_read ? true : false;
|
||||||
|
|
||||||
|
const docId = item.doc_id || item.document_id || item.dokumen_id || item.id || '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<a href="${url}"
|
||||||
|
class="dropdown-item d-flex align-items-start gap-2 js-expired-notif-item"
|
||||||
|
data-doc-id="${docId}">
|
||||||
|
<span class="badge ${isRead ? 'bg-secondary' : 'bg-warning'} rounded-circle"
|
||||||
|
style="width:10px;height:10px;margin-top:6px;"></span>
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold">${text}</div>
|
||||||
|
<div class="small text-muted">${createdAt}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setExpiredDot(unread) {
|
||||||
|
unread = Number(unread || 0);
|
||||||
|
|
||||||
|
if (!expiredDotEl) return;
|
||||||
|
|
||||||
|
if (unread > 0) {
|
||||||
|
expiredDotEl.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
expiredDotEl.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExpiredNotifications() {
|
||||||
|
ajaxGet('/data/expired-notifications')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(res => {
|
||||||
|
const unread = res?.status ? Number(res.unread || 0) : 0;
|
||||||
|
const items = res?.status ? (res.data || []) : [];
|
||||||
|
|
||||||
|
setExpiredDot(unread);
|
||||||
|
setExpiredList(items);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (expiredListEl) {
|
||||||
|
expiredListEl.innerHTML = `
|
||||||
|
<div class="dropdown-item text-muted small">
|
||||||
|
Gagal memuat notifikasi expired
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExpiredDot(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function markExpiredNotificationsAsRead() {
|
||||||
|
ajaxPost('/data/expired-notifications/read')
|
||||||
|
.then(() => {
|
||||||
|
setExpiredDot(0);
|
||||||
|
|
||||||
|
if (expiredListEl) {
|
||||||
|
expiredListEl.querySelectorAll('.badge').forEach(badge => {
|
||||||
|
badge.classList.remove('bg-warning');
|
||||||
|
badge.classList.add('bg-secondary');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredToggle) {
|
||||||
|
expiredToggle.addEventListener('show.bs.dropdown', function() {
|
||||||
|
markExpiredNotificationsAsRead();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| MODAL DETAIL EXPIRED
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
// 📋 DETAIL MODAL (EXPIRED)
|
|
||||||
const expiredDetailModalEl = document.getElementById('expiredDetailModal');
|
const expiredDetailModalEl = document.getElementById('expiredDetailModal');
|
||||||
const expiredDetailModal = (expiredDetailModalEl && window.bootstrap?.Modal)
|
|
||||||
? new bootstrap.Modal(expiredDetailModalEl)
|
let expiredDetailModal = null;
|
||||||
: null;
|
|
||||||
|
if (expiredDetailModalEl && window.bootstrap && window.bootstrap.Modal) {
|
||||||
|
expiredDetailModal = new bootstrap.Modal(expiredDetailModalEl);
|
||||||
|
}
|
||||||
|
|
||||||
const expiredState = {
|
const expiredState = {
|
||||||
docId: null,
|
docId: null,
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 10,
|
perPage: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
const expiredDetailTbody = document.getElementById('expiredDetailTbody');
|
const expiredDetailTbody = document.getElementById('expiredDetailTbody');
|
||||||
const expiredDetailInfo = document.getElementById('expiredDetailInfo');
|
const expiredDetailInfo = document.getElementById('expiredDetailInfo');
|
||||||
const expiredDetailPageText = document.getElementById('expiredDetailPageText');
|
const expiredDetailPageText = document.getElementById('expiredDetailPageText');
|
||||||
|
|
||||||
function setExpiredLoading(text = 'Memuat...') {
|
const expiredOpenDetailBtn = document.getElementById('expiredOpenDetailBtn');
|
||||||
|
const expiredFilterDaysMax = document.getElementById('expiredFilterDaysMax');
|
||||||
|
const expiredApplyFilterBtn = document.getElementById('expiredApplyFilterBtn');
|
||||||
|
const expiredResetFilterBtn = document.getElementById('expiredResetFilterBtn');
|
||||||
|
const expiredDetailPrevBtn = document.getElementById('expiredDetailPrevBtn');
|
||||||
|
const expiredDetailNextBtn = document.getElementById('expiredDetailNextBtn');
|
||||||
|
|
||||||
|
function showExpiredModal() {
|
||||||
|
if (expiredDetailModal) {
|
||||||
|
expiredDetailModal.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback kalau bootstrap.Modal tidak terbaca.
|
||||||
|
* Ini berguna kalau Bootstrap JS belum include.
|
||||||
|
*/
|
||||||
|
if (expiredDetailModalEl) {
|
||||||
|
expiredDetailModalEl.classList.add('show');
|
||||||
|
expiredDetailModalEl.style.display = 'block';
|
||||||
|
expiredDetailModalEl.removeAttribute('aria-hidden');
|
||||||
|
expiredDetailModalEl.setAttribute('aria-modal', 'true');
|
||||||
|
|
||||||
|
document.body.classList.add('modal-open');
|
||||||
|
|
||||||
|
let backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'modal-backdrop fade show';
|
||||||
|
backdrop.id = 'manualExpiredBackdrop';
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
} else {
|
||||||
|
alert('Modal expiredDetailModal tidak ditemukan di HTML.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideExpiredModalFallback() {
|
||||||
|
if (!expiredDetailModalEl) return;
|
||||||
|
|
||||||
|
expiredDetailModalEl.classList.remove('show');
|
||||||
|
expiredDetailModalEl.style.display = 'none';
|
||||||
|
expiredDetailModalEl.setAttribute('aria-hidden', 'true');
|
||||||
|
expiredDetailModalEl.removeAttribute('aria-modal');
|
||||||
|
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
|
||||||
|
const backdrop = document.getElementById('manualExpiredBackdrop');
|
||||||
|
if (backdrop) {
|
||||||
|
backdrop.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expiredDetailModalEl?.querySelectorAll('[data-bs-dismiss="modal"]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
if (!expiredDetailModal) {
|
||||||
|
hideExpiredModalFallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setExpiredDetailLoading(text = 'Memuat...') {
|
||||||
if (!expiredDetailTbody) return;
|
if (!expiredDetailTbody) return;
|
||||||
|
|
||||||
expiredDetailTbody.innerHTML = `
|
expiredDetailTbody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="text-muted small">${text}</td>
|
<td colspan="7" class="text-muted small">${escapeHtml(text)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildExpiredDetailParams() {
|
function buildExpiredDetailParams() {
|
||||||
const daysMax = document.getElementById('expiredFilterDaysMax')?.value ?? '';
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
params.set('page', String(expiredState.page));
|
params.set('page', String(expiredState.page));
|
||||||
params.set('per_page', String(expiredState.perPage));
|
params.set('per_page', String(expiredState.perPage));
|
||||||
|
|
||||||
if (expiredState.docId) params.set('doc_id', String(expiredState.docId));
|
if (expiredState.docId) {
|
||||||
if (daysMax !== '' && daysMax !== null) params.set('days_left_max', String(daysMax));
|
params.set('doc_id', String(expiredState.docId));
|
||||||
|
}
|
||||||
|
|
||||||
|
const daysMax = expiredFilterDaysMax?.value ?? '';
|
||||||
|
|
||||||
|
if (daysMax !== '' && daysMax !== null) {
|
||||||
|
params.set('days_left_max', String(daysMax));
|
||||||
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
@ -313,20 +539,30 @@
|
|||||||
const page = Number(meta?.page || expiredState.page);
|
const page = Number(meta?.page || expiredState.page);
|
||||||
const maxPage = total ? Math.ceil(total / perPage) : 1;
|
const maxPage = total ? Math.ceil(total / perPage) : 1;
|
||||||
|
|
||||||
if (expiredDetailPageText) expiredDetailPageText.textContent = String(page);
|
if (expiredDetailPageText) {
|
||||||
|
expiredDetailPageText.textContent = String(page);
|
||||||
|
}
|
||||||
|
|
||||||
if (expiredDetailInfo) {
|
if (expiredDetailInfo) {
|
||||||
const start = total ? ((page - 1) * perPage + 1) : 0;
|
const start = total ? ((page - 1) * perPage + 1) : 0;
|
||||||
const end = Math.min(page * perPage, total);
|
const end = Math.min(page * perPage, total);
|
||||||
expiredDetailInfo.textContent = total ? `Menampilkan ${start}-${end} dari ${total} data` : 'Tidak ada data';
|
|
||||||
|
expiredDetailInfo.textContent = total
|
||||||
|
? `Menampilkan ${start}-${end} dari ${total} data`
|
||||||
|
: 'Tidak ada data';
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevBtn = document.getElementById('expiredDetailPrevBtn');
|
if (expiredDetailPrevBtn) {
|
||||||
const nextBtn = document.getElementById('expiredDetailNextBtn');
|
expiredDetailPrevBtn.disabled = page <= 1;
|
||||||
if (prevBtn) prevBtn.disabled = page <= 1;
|
}
|
||||||
if (nextBtn) nextBtn.disabled = page >= maxPage;
|
|
||||||
|
if (expiredDetailNextBtn) {
|
||||||
|
expiredDetailNextBtn.disabled = page >= maxPage;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expiredDetailTbody) return;
|
if (!expiredDetailTbody) return;
|
||||||
if (!rows?.length) {
|
|
||||||
|
if (!rows || !rows.length) {
|
||||||
expiredDetailTbody.innerHTML = `
|
expiredDetailTbody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="text-muted small">Tidak ada data</td>
|
<td colspan="7" class="text-muted small">Tidak ada data</td>
|
||||||
@ -337,12 +573,18 @@
|
|||||||
|
|
||||||
expiredDetailTbody.innerHTML = rows.map((row, idx) => {
|
expiredDetailTbody.innerHTML = rows.map((row, idx) => {
|
||||||
const no = (page - 1) * perPage + (idx + 1);
|
const no = (page - 1) * perPage + (idx + 1);
|
||||||
const unit = row.unit_name || '-';
|
|
||||||
const noDok = row.no_dokumen || '-';
|
const unit = escapeHtml(row.unit_name || row.nama_unit || '-');
|
||||||
const nama = row.nama_dokumen || '-';
|
const noDok = escapeHtml(row.no_dokumen || row.nomor_dokumen || '-');
|
||||||
const tgl = row.tgl_expired_label || '-';
|
const nama = escapeHtml(row.nama_dokumen || row.nama_document || row.nama || '-');
|
||||||
const daysLeft = (row.days_left === null || row.days_left === undefined) ? '-' : String(row.days_left);
|
const tgl = escapeHtml(row.tgl_expired_label || row.tgl_expired || row.expired_date || '-');
|
||||||
const previewUrl = row.preview_url || '#';
|
|
||||||
|
const daysLeft = row.days_left === null || row.days_left === undefined
|
||||||
|
? '-'
|
||||||
|
: escapeHtml(row.days_left);
|
||||||
|
|
||||||
|
const previewUrl = row.preview_url || row.url || '#';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${no}</td>
|
<td>${no}</td>
|
||||||
@ -352,76 +594,145 @@
|
|||||||
<td>${tgl}</td>
|
<td>${tgl}</td>
|
||||||
<td>${daysLeft}</td>
|
<td>${daysLeft}</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm btn-outline-primary" href="${previewUrl}" target="_blank" rel="noopener">Preview</a>
|
<a class="btn btn-sm btn-outline-primary"
|
||||||
|
href="${previewUrl}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener">
|
||||||
|
Preview
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadExpiredDetail() {
|
function loadExpiredDetail() {
|
||||||
setExpiredLoading();
|
setExpiredDetailLoading();
|
||||||
|
|
||||||
const params = buildExpiredDetailParams();
|
const params = buildExpiredDetailParams();
|
||||||
|
|
||||||
try {
|
ajaxGet(`/data/expired-notifications/detail?${params.toString()}`)
|
||||||
const r = await fetch(`/data/expired-notifications/detail?${params.toString()}`, {
|
.then(r => r.json())
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
.then(res => {
|
||||||
|
if (!res?.status) {
|
||||||
|
setExpiredDetailLoading(res?.message || 'Gagal memuat data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExpiredDetail(res.data || [], res.meta || {});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setExpiredDetailLoading('Gagal memuat data detail expired');
|
||||||
});
|
});
|
||||||
const res = await r.json();
|
|
||||||
if (!res?.status) {
|
|
||||||
setExpiredLoading(res?.message || 'Gagal memuat data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
renderExpiredDetail(res.data || [], res.meta || {});
|
|
||||||
} catch (e) {
|
|
||||||
setExpiredLoading('Gagal memuat data');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExpiredDetail(docId = null) {
|
function openExpiredDetail(docId = null) {
|
||||||
expiredState.docId = docId ? Number(docId) : null;
|
expiredState.docId = docId ? Number(docId) : null;
|
||||||
expiredState.page = 1;
|
expiredState.page = 1;
|
||||||
|
|
||||||
if (expiredState.docId) {
|
/**
|
||||||
const daysMaxEl = document.getElementById('expiredFilterDaysMax');
|
* Kalau klik dari item expired tertentu,
|
||||||
if (daysMaxEl) daysMaxEl.value = '';
|
* filter hari dikosongkan supaya data berdasarkan doc_id tetap muncul.
|
||||||
|
*/
|
||||||
|
if (expiredState.docId && expiredFilterDaysMax) {
|
||||||
|
expiredFilterDaysMax.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expiredDetailModal) expiredDetailModal.show();
|
showExpiredModal();
|
||||||
loadExpiredDetail();
|
loadExpiredDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('expiredOpenDetailBtn')?.addEventListener('click', () => openExpiredDetail(null));
|
/**
|
||||||
|
* Tombol "Detail" di header expired.
|
||||||
|
*/
|
||||||
|
if (expiredOpenDetailBtn) {
|
||||||
|
expiredOpenDetailBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
document.getElementById('expiredNotifList')?.addEventListener('click', (e) => {
|
expiredState.docId = null;
|
||||||
const item = e.target.closest('a.js-expired-notif-item');
|
expiredState.page = 1;
|
||||||
if (!item) return;
|
|
||||||
e.preventDefault();
|
|
||||||
openExpiredDetail(item.dataset.notifId || null);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('expiredApplyFilterBtn')?.addEventListener('click', () => {
|
if (expiredFilterDaysMax) {
|
||||||
expiredState.page = 1;
|
expiredFilterDaysMax.value = '30';
|
||||||
loadExpiredDetail();
|
}
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('expiredResetFilterBtn')?.addEventListener('click', () => {
|
openExpiredDetail(null);
|
||||||
const daysMaxEl = document.getElementById('expiredFilterDaysMax');
|
});
|
||||||
if (daysMaxEl) daysMaxEl.value = '30';
|
}
|
||||||
expiredState.docId = null;
|
|
||||||
expiredState.page = 1;
|
|
||||||
loadExpiredDetail();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('expiredDetailPrevBtn')?.addEventListener('click', () => {
|
/**
|
||||||
if (expiredState.page <= 1) return;
|
* Klik salah satu item expired dari dropdown.
|
||||||
expiredState.page -= 1;
|
* Nanti langsung buka modal detail sesuai dokumen.
|
||||||
loadExpiredDetail();
|
*/
|
||||||
});
|
if (expiredListEl) {
|
||||||
|
expiredListEl.addEventListener('click', function(e) {
|
||||||
|
const item = e.target.closest('a.js-expired-notif-item');
|
||||||
|
|
||||||
document.getElementById('expiredDetailNextBtn')?.addEventListener('click', () => {
|
if (!item) return;
|
||||||
expiredState.page += 1;
|
|
||||||
loadExpiredDetail();
|
e.preventDefault();
|
||||||
});
|
|
||||||
});
|
const docId = item.dataset.docId || null;
|
||||||
</script>
|
|
||||||
|
openExpiredDetail(docId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredApplyFilterBtn) {
|
||||||
|
expiredApplyFilterBtn.addEventListener('click', function() {
|
||||||
|
expiredState.docId = null;
|
||||||
|
expiredState.page = 1;
|
||||||
|
loadExpiredDetail();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredResetFilterBtn) {
|
||||||
|
expiredResetFilterBtn.addEventListener('click', function() {
|
||||||
|
expiredState.docId = null;
|
||||||
|
expiredState.page = 1;
|
||||||
|
|
||||||
|
if (expiredFilterDaysMax) {
|
||||||
|
expiredFilterDaysMax.value = '30';
|
||||||
|
}
|
||||||
|
|
||||||
|
loadExpiredDetail();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredDetailPrevBtn) {
|
||||||
|
expiredDetailPrevBtn.addEventListener('click', function() {
|
||||||
|
if (expiredState.page <= 1) return;
|
||||||
|
|
||||||
|
expiredState.page -= 1;
|
||||||
|
loadExpiredDetail();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiredDetailNextBtn) {
|
||||||
|
expiredDetailNextBtn.addEventListener('click', function() {
|
||||||
|
expiredState.page += 1;
|
||||||
|
loadExpiredDetail();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| LOAD PERTAMA
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
loadNotifications();
|
||||||
|
loadExpiredNotifications();
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| REFRESH OTOMATIS
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
loadNotifications();
|
||||||
|
loadExpiredNotifications();
|
||||||
|
}, 60000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@ -55,7 +55,7 @@ Route::middleware(['auth:admin,web'])->group(function(){
|
|||||||
Route::get('/log-activity', [LogActivityController::class, 'index']);
|
Route::get('/log-activity', [LogActivityController::class, 'index']);
|
||||||
Route::get('/datatable/log-activity', [LogActivityController::class, 'datatable']);
|
Route::get('/datatable/log-activity', [LogActivityController::class, 'datatable']);
|
||||||
Route::get('/datatable/log-activity/{fileDirectoryId}', [LogActivityController::class, 'detailByFile']);
|
Route::get('/datatable/log-activity/{fileDirectoryId}', [LogActivityController::class, 'detailByFile']);
|
||||||
Route::get('/datatable/log-activity-pengajuan', [LogActivityController::class, 'datatableHistoryPengajuan']);
|
Route::get('/datatable/log-activity-pengajuapn', [LogActivityController::class, 'datatableHistoryPengajuan']);
|
||||||
|
|
||||||
Route::get('/recap', [DashboardController::class, 'recapView']);
|
Route::get('/recap', [DashboardController::class, 'recapView']);
|
||||||
Route::get('/data/recap', [DashboardController::class, 'recapData']);
|
Route::get('/data/recap', [DashboardController::class, 'recapData']);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user