done data unit & data umum
This commit is contained in:
parent
8c1663f9c3
commit
313cf64724
@ -20,29 +20,90 @@ use ZipArchive;
|
||||
|
||||
use function PHPUnit\Framework\isEmpty;
|
||||
|
||||
class PdfFpdiWithAlpha extends Fpdi
|
||||
{
|
||||
protected array $extgstates = [];
|
||||
|
||||
public function SetAlpha(float $alpha, string $blendMode = 'Normal'): void
|
||||
{
|
||||
$alpha = max(0.0, min(1.0, $alpha));
|
||||
$gs = $this->AddExtGState([
|
||||
'ca' => $alpha,
|
||||
'CA' => $alpha,
|
||||
'BM' => '/' . $blendMode
|
||||
]);
|
||||
$this->SetExtGState($gs);
|
||||
}
|
||||
|
||||
protected function AddExtGState(array $parms): int
|
||||
{
|
||||
$n = count($this->extgstates) + 1;
|
||||
$this->extgstates[$n]['parms'] = $parms;
|
||||
return $n;
|
||||
}
|
||||
|
||||
protected function SetExtGState(int $gs): void
|
||||
{
|
||||
$this->_out(sprintf('/GS%d gs', $gs));
|
||||
}
|
||||
|
||||
protected function _putextgstates(): void
|
||||
{
|
||||
foreach ($this->extgstates as $i => $extgstate) {
|
||||
$this->_newobj();
|
||||
$this->extgstates[$i]['n'] = $this->n;
|
||||
$this->_put('<</Type /ExtGState');
|
||||
foreach ($extgstate['parms'] as $k => $v) {
|
||||
$this->_put('/' . $k . ' ' . $v);
|
||||
}
|
||||
$this->_put('>>');
|
||||
$this->_put('endobj');
|
||||
}
|
||||
}
|
||||
|
||||
protected function _putresourcedict(): void
|
||||
{
|
||||
parent::_putresourcedict();
|
||||
if (empty($this->extgstates)) {
|
||||
return;
|
||||
}
|
||||
$this->_put('/ExtGState <<');
|
||||
foreach ($this->extgstates as $k => $extgstate) {
|
||||
$this->_put('/GS' . $k . ' ' . $extgstate['n'] . ' 0 R');
|
||||
}
|
||||
$this->_put('>>');
|
||||
}
|
||||
|
||||
protected function _putresources(): void
|
||||
{
|
||||
$this->_putextgstates();
|
||||
parent::_putresources();
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
|
||||
$klasifikasiDok = MasterKlasifikasi::where('statusenabled', true)->select('master_klasifikasi_directory_id', 'nama_klasifikasi_directory')->get();
|
||||
$prefillFilter = session()->pull('dashboard_prefill');
|
||||
// public function index(){
|
||||
// $katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
|
||||
// $klasifikasiDok = MasterKlasifikasi::where('statusenabled', true)->select('master_klasifikasi_directory_id', 'nama_klasifikasi_directory')->get();
|
||||
// $prefillFilter = session()->pull('dashboard_prefill');
|
||||
|
||||
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
||||
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
||||
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
|
||||
// $authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
||||
// $authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
||||
// $authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
|
||||
|
||||
$allAkses = AksesFile::where(['statusenabled' => true, 'pegawai_id' => auth()->user()?->dataUser->id])->first();
|
||||
$payload = [
|
||||
'title' => 'Dashboard',
|
||||
'katDok' => $katDok,
|
||||
'klasifikasiDok' => $klasifikasiDok,
|
||||
'authUnitKerja' => $authUnitKerja,
|
||||
'authSubUnitKerja' => $authSubUnitKerja,
|
||||
'allAkses' => $allAkses ?? null,
|
||||
'prefillFilter' => $prefillFilter
|
||||
];
|
||||
return view('dashboard.index', $payload);
|
||||
}
|
||||
// $allAkses = AksesFile::where(['statusenabled' => true, 'pegawai_id' => auth()->user()?->dataUser->id])->first();
|
||||
// $payload = [
|
||||
// 'title' => 'Dashboard',
|
||||
// 'katDok' => $katDok,
|
||||
// 'klasifikasiDok' => $klasifikasiDok,
|
||||
// 'authUnitKerja' => $authUnitKerja,
|
||||
// 'authSubUnitKerja' => $authSubUnitKerja,
|
||||
// 'allAkses' => $allAkses ?? null,
|
||||
// 'prefillFilter' => $prefillFilter
|
||||
// ];
|
||||
// return view('dashboard.index', $payload);
|
||||
// }
|
||||
|
||||
public function setDashboardPrefill(Request $request)
|
||||
{
|
||||
@ -63,6 +124,55 @@ class DashboardController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(){
|
||||
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
|
||||
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
||||
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
||||
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
|
||||
$data = [
|
||||
'title' => 'Dashboard',
|
||||
'katDok' => $katDok,
|
||||
'authUnitKerja' => $authUnitKerja,
|
||||
'authSubUnitKerja' => $authSubUnitKerja,
|
||||
];
|
||||
return view('dataUnit.index', $data);
|
||||
}
|
||||
|
||||
public function dataUnitInternal(){
|
||||
$perPage = (int) request('per_page', 10);
|
||||
$authUnitId = auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->objectunitkerjapegawaifk;
|
||||
$keyword = request('keyword');
|
||||
$query = FileDirectory::where('statusenabled', true)
|
||||
->where(function($subQuery){
|
||||
$subQuery->where('status_action', '!=', 'rejected')->whereNotNull('status_action');
|
||||
})
|
||||
->where('id_unit_kerja', $authUnitId)->where('permission_file', false)
|
||||
->when($keyword, function ($q) use ($keyword) {
|
||||
$q->where(function ($sub) use ($keyword) {
|
||||
$sub->where('file', 'ILIKE', "%{$keyword}%")
|
||||
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
|
||||
});
|
||||
});
|
||||
|
||||
$data = $query->orderBy('entry_at', 'desc')
|
||||
->paginate($perPage);
|
||||
|
||||
$payload = [
|
||||
'status' => true,
|
||||
'message' => 'Berhasil mendapatkan data',
|
||||
'data' => $data->items(),
|
||||
'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 dataUnitKerja(){
|
||||
$user = auth()->user()?->dataUser;
|
||||
$entryPegawaiId = auth()->user()?->objectpegawaifk;
|
||||
@ -459,7 +569,7 @@ class DashboardController extends Controller
|
||||
|
||||
}
|
||||
|
||||
public function dashboardVersion2(){
|
||||
public function dataUmum(){
|
||||
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
|
||||
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
||||
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
||||
@ -470,7 +580,7 @@ class DashboardController extends Controller
|
||||
'authUnitKerja' => $authUnitKerja,
|
||||
'authSubUnitKerja' => $authSubUnitKerja,
|
||||
];
|
||||
return view('dashboardV2.index', $data);
|
||||
return view('dataUmum.index', $data);
|
||||
}
|
||||
|
||||
public function dataDocumentLast(){
|
||||
@ -643,8 +753,9 @@ class DashboardController extends Controller
|
||||
|
||||
private function watermarkCenterAndStream(string $pdfPath, string $stampFile)
|
||||
{
|
||||
$pdf = new Fpdi();
|
||||
$pdf = new PdfFpdiWithAlpha();
|
||||
$pageCount = $pdf->setSourceFile($pdfPath);
|
||||
$opacity = $this->getWatermarkOpacity();
|
||||
|
||||
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
||||
$tplId = $pdf->importPage($pageNo);
|
||||
@ -660,7 +771,9 @@ class DashboardController extends Controller
|
||||
$x = ($size['width'] - $stampW) / 2;
|
||||
$y = ($size['height'] - $stampH) / 2;
|
||||
|
||||
$pdf->SetAlpha($opacity);
|
||||
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
||||
$pdf->SetAlpha(1);
|
||||
}
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
@ -706,8 +819,9 @@ class DashboardController extends Controller
|
||||
|
||||
private function watermarkCenterToFile(string $pdfPath, string $stampFile, string $outputPath): void
|
||||
{
|
||||
$pdf = new Fpdi();
|
||||
$pdf = new PdfFpdiWithAlpha();
|
||||
$pageCount = $pdf->setSourceFile($pdfPath);
|
||||
$opacity = $this->getWatermarkOpacity();
|
||||
|
||||
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
||||
$tplId = $pdf->importPage($pageNo);
|
||||
@ -722,12 +836,19 @@ class DashboardController extends Controller
|
||||
$x = ($size['width'] - $stampW) / 2;
|
||||
$y = ($size['height'] - $stampH) / 2;
|
||||
|
||||
$pdf->SetAlpha($opacity);
|
||||
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
||||
$pdf->SetAlpha(1);
|
||||
}
|
||||
|
||||
$pdf->Output($outputPath, 'F');
|
||||
}
|
||||
|
||||
private function getWatermarkOpacity(): float
|
||||
{
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
private function cleanupTempFiles(array $tempFiles): void
|
||||
{
|
||||
foreach ($tempFiles as $file) {
|
||||
|
||||
@ -280,7 +280,9 @@
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if(e.target.matches('#btn-view-full')){
|
||||
window.open(`/file-preview/${idDirectory}`, '_blank');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -13,9 +13,9 @@
|
||||
|
||||
<!-- Body -->
|
||||
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
|
||||
<div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
|
||||
{{-- <div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger mb-2 me-2" id="delete-file">🗑 Hapus</button>
|
||||
</div>
|
||||
</div> --}}
|
||||
<div id="file-preview"
|
||||
class="text-center text-muted d-flex justify-content-center align-items-center"
|
||||
style="height:100%;">
|
||||
|
||||
@ -9,150 +9,12 @@
|
||||
<h4 class="mb-0">Rekap Dokumen</h4>
|
||||
<small class="text-muted">Ringkasan jumlah file per Unit dan Folder</small>
|
||||
</div>
|
||||
<div class="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>
|
||||
<div class="card-body pb-2">
|
||||
<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</th>
|
||||
<th style="width:30%;">Sub Unit</th>
|
||||
<th style="width:20%;">Folder</th>
|
||||
<th style="width:15%;" class="text-center">Jumlah</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>
|
||||
@include('dataUnit.section.recap', ['showRecapTitle' => false])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<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');
|
||||
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/recap?' + 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;
|
||||
}
|
||||
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.subUnit || '-'}</td>
|
||||
<td>${f.folder || '-'}</td>
|
||||
<td class="text-center fw-bold">${f.count || 0}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
return folderRows;
|
||||
}).join('');
|
||||
tbody.innerHTML = html;
|
||||
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>
|
||||
|
||||
869
resources/views/dataUmum/index.blade.php
Normal file
869
resources/views/dataUmum/index.blade.php
Normal file
@ -0,0 +1,869 @@
|
||||
@extends('layout.main')
|
||||
<style>
|
||||
.file-cell {
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.file-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.file-title i {
|
||||
font-size: 18px;
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
color: #1f2937;
|
||||
text-decoration: none;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.file-link:hover {
|
||||
color: #4f46e5;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.file-meta span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* status pending */
|
||||
.file-pending {
|
||||
font-style: italic;
|
||||
color: #9ca3af !important;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link {
|
||||
border: 0;
|
||||
color: #475569;
|
||||
font-weight: 600;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 999px;
|
||||
transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link.active {
|
||||
background: color-mix(in srgb, var(--bs-primary) 12%, #ffffff);
|
||||
color: var(--bs-primary);
|
||||
+}
|
||||
|
||||
.dataunit-tabs .nav-link:hover {
|
||||
color: var(--bs-primary);
|
||||
background: color-mix(in srgb, var(--bs-primary) 8%, #ffffff);
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
right: 18px;
|
||||
bottom: 6px;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: var(--bs-primary);
|
||||
transform: scaleX(0);
|
||||
transform-origin: center;
|
||||
transition: transform .18s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link:hover::after {
|
||||
transform: scaleX(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.dataunit-tab-wrap {
|
||||
background: #f8fafc;
|
||||
border-radius: 999px;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dataunit-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-item {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
@section('body_main')
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-3">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||
<h4 class="mb-0">Data Unit</h4>
|
||||
<button type="button" class="btn btn-success ms-md-auto" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||
<div class="input-group input-group-sm flex-grow-1">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="fa fa-search text-muted"></i>
|
||||
</span>
|
||||
<input type="search"
|
||||
id="tableSearch"
|
||||
class="form-control border-start-0"
|
||||
placeholder="Cari nama file, No Dokumen atau folder"
|
||||
oninput="debouncedTableSearch(this.value)">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<select id="tablePageSize" class="form-select form-select-sm" style="width: auto;">
|
||||
<option value="5">5</option>
|
||||
<option value="10"selected>10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="small text-muted ms-md-auto" id="tableSummary">Memuat data...</div>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height: 70vh; overflow-y:auto;">
|
||||
<table class="table table-sm table-hover align-middle mb-0 table-fixed" id="lastUpdatedTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nomor Surat</th>
|
||||
<th>File</th>
|
||||
<th>Kategori</th>
|
||||
<th>Unit</th>
|
||||
<th>Sub Unit </th>
|
||||
<th>Tanggal Upload</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableDataUmum">
|
||||
<!-- data dari fetch masuk sini -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mt-3" id="paginationControls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include('dataUmum.modal.create')
|
||||
@include('dataUmum.modal.view')
|
||||
<script>
|
||||
const katDok = @json($katDok);
|
||||
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
||||
const authSubUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]->sub_unit_kerja);
|
||||
const mappingUnitKerjaPegawai = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]);
|
||||
const formCreate = $("#formFile")
|
||||
const modalCreate = document.getElementById('modalCreateFile')
|
||||
const tableState = { data: [], page: 1, pageSize: 8, search: '', lastPage: 1, total: 0 };
|
||||
const tbody = document.getElementById('tableDataUmum');
|
||||
const paginationEl = document.getElementById('paginationControls');
|
||||
const summaryEl = document.getElementById('tableSummary');
|
||||
const pageSizeSelect = document.getElementById('tablePageSize');
|
||||
|
||||
if(pageSizeSelect){
|
||||
const initialSize = parseInt(pageSizeSelect.value);
|
||||
if(!isNaN(initialSize)) tableState.pageSize = initialSize;
|
||||
pageSizeSelect.addEventListener('change', (e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
if(!isNaN(val) && val > 0){
|
||||
tableState.pageSize = val;
|
||||
tableState.page = 1;
|
||||
fetchData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetCreateForm(){
|
||||
colCount = 1;
|
||||
$("#col_add_fileV2").html('');
|
||||
formCreate[0]?.reset();
|
||||
formCreate.find('select').val(null).trigger('change');
|
||||
formCreate.find('input[type="file"]').val('');
|
||||
formCreate.find('.file-name').addClass('d-none').text('');
|
||||
}
|
||||
|
||||
function isPublic(permissionVal){
|
||||
if(permissionVal === null || permissionVal === undefined) return false;
|
||||
const val = String(permissionVal).toLowerCase();
|
||||
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
|
||||
}
|
||||
|
||||
function buildRow(item){
|
||||
const parts = (item.file || '').split('/');
|
||||
const fileName = parts.pop() || '-';
|
||||
const folderPath = parts.join('/') || '-';
|
||||
const publicDoc = isPublic(item.permission_file);
|
||||
const unitName = parts[0] || '';
|
||||
const subName = parts[1] || '';
|
||||
const kategoriName = parts[2] || '';
|
||||
let statusLabel = '';
|
||||
let statusClass = '';
|
||||
let badge
|
||||
if (publicDoc) {
|
||||
statusLabel = 'Umum';
|
||||
statusClass = 'bg-success';
|
||||
} else {
|
||||
statusLabel = 'Internal Unit';
|
||||
statusClass = 'bg-secondary';
|
||||
}
|
||||
return `
|
||||
<tr>
|
||||
<td class="text-nowrap">${item.no_dokumen || '-'}</td>
|
||||
<td class="file-cell">
|
||||
<div style="display:flex; flex-direction:column; gap:4px;">
|
||||
|
||||
<!-- BARIS ATAS -->
|
||||
<div style="display:flex; align-items:center; gap:6px;">
|
||||
|
||||
<i class="ti ti-file-text" style="font-size:18px; color:#4f46e5;"></i>
|
||||
|
||||
<a href="#"
|
||||
class="file-link ${statusLabel === "Pending" ? 'file-pending' : ''}"
|
||||
data-file="${item.file}"
|
||||
data-fileName="${fileName}"
|
||||
data-id="${item.file_directory_id}"
|
||||
data-no_dokumen="${item.no_dokumen || '-'}"
|
||||
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
|
||||
data-permission_file="${item.permission_file || '-'}"
|
||||
style="
|
||||
color:#1f2937;
|
||||
font-weight:600;
|
||||
text-decoration:none;
|
||||
line-height:1.3;
|
||||
word-break:break-word;
|
||||
"
|
||||
>
|
||||
${fileName}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- BARIS BAWAH (LABEL) -->
|
||||
<div style="padding-left:24px;">
|
||||
|
||||
${
|
||||
item.permission_file
|
||||
? `
|
||||
<span style="
|
||||
display:inline-block;
|
||||
padding:2px 10px;
|
||||
font-size:11px;
|
||||
border-radius:6px;
|
||||
background:#ecfdf5;
|
||||
color:#047857;
|
||||
border:1px solid #a7f3d0;
|
||||
font-weight:500;
|
||||
cursor:default;
|
||||
">
|
||||
Umum
|
||||
</span>
|
||||
`
|
||||
: `
|
||||
<span style="
|
||||
display:inline-block;
|
||||
padding:2px 10px;
|
||||
font-size:11px;
|
||||
border-radius:6px;
|
||||
background:#eff6ff;
|
||||
color:#1d4ed8;
|
||||
border:1px solid #bfdbfe;
|
||||
font-weight:500;
|
||||
cursor:default;
|
||||
">
|
||||
Internal Unit
|
||||
</span>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
${kategoriName}
|
||||
</td>
|
||||
<td>
|
||||
${unitName}
|
||||
</td>
|
||||
<td>
|
||||
${subName}
|
||||
</td>
|
||||
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
fetchData();
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(){
|
||||
const pageData = tableState.data || [];
|
||||
|
||||
if(pageData.length === 0){
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">
|
||||
Tidak ada data yang cocok
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}else{
|
||||
tbody.innerHTML = pageData.map(buildRow).join('');
|
||||
}
|
||||
|
||||
const from = tableState.total === 0 ? 0 : ((tableState.page -1) * tableState.pageSize) + 1;
|
||||
const to = Math.min(((tableState.page -1) * tableState.pageSize) + pageData.length, tableState.total);
|
||||
if(summaryEl){
|
||||
summaryEl.textContent = tableState.total ? `Menampilkan ${from} - ${to} dari ${tableState.total} dokumen` : 'Tidak ada data';
|
||||
}
|
||||
|
||||
renderPagination(tableState.lastPage || 1);
|
||||
}
|
||||
|
||||
function debouncedTableSearch(value){
|
||||
clearTimeout(window.tableSearchTimer);
|
||||
window.tableSearchTimer = setTimeout(() => {
|
||||
tableState.search = value.trim();
|
||||
tableState.page = 1;
|
||||
fetchData();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function fetchData(){
|
||||
if(summaryEl) summaryEl.textContent = 'Memuat data...';
|
||||
const params = new URLSearchParams({
|
||||
page: tableState.page,
|
||||
per_page: tableState.pageSize,
|
||||
keyword: tableState.search
|
||||
});
|
||||
fetch(`/datatable-umum?${params.toString()}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
tableState.data = data?.data || [];
|
||||
tableState.lastPage = data?.pagination?.last_page || 1;
|
||||
tableState.total = data?.pagination?.total || 0;
|
||||
renderTable();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error : ', error);
|
||||
if(summaryEl) summaryEl.textContent = 'Gagal memuat data';
|
||||
})
|
||||
}
|
||||
|
||||
function formatTanggal(dateString) {
|
||||
const d = new Date(dateString);
|
||||
|
||||
return d.toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
fetchData()
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
const btn = e.target.closest('.folder-prefill');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
|
||||
const payload = {
|
||||
unitId: btn.getAttribute('data-unit_id') || '',
|
||||
subId: btn.getAttribute('data-sub_id') || '',
|
||||
kategoriId: btn.getAttribute('data-kategori_id') || '',
|
||||
unitName: btn.getAttribute('data-unit_name') || '',
|
||||
subName: btn.getAttribute('data-sub_name') || '',
|
||||
kategoriName: btn.getAttribute('data-kategori_name') || '',
|
||||
};
|
||||
|
||||
fetch('/dashboard/prefill', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Gagal set filter');
|
||||
window.location.href = '/';
|
||||
})
|
||||
.catch(() => {
|
||||
Swal.fire({ icon: 'error', title: 'Gagal', text: 'Tidak bisa buka folder' });
|
||||
});
|
||||
});
|
||||
|
||||
let colCount = 1;
|
||||
$(document).ready(function() {
|
||||
$('.unit_kerja').select2({
|
||||
placeholder: '--- Pilih Unit Kerja ---',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
ajax: {
|
||||
url : '/select-unit-kerja',
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function(params){
|
||||
let q = '';
|
||||
if(allAkses){
|
||||
q = params.term;
|
||||
}else{
|
||||
q = authUnitKerja?.name ?? '';
|
||||
}
|
||||
|
||||
return { q };
|
||||
},
|
||||
processResults: function(data){
|
||||
let results = data?.data.map(item => ({
|
||||
id: item.id,
|
||||
text: item.name
|
||||
}));
|
||||
return { results };
|
||||
},
|
||||
cache: true,
|
||||
},
|
||||
minimumInputLength: 0,
|
||||
});
|
||||
|
||||
$('.sub_unit_kerja').select2({
|
||||
placeholder: '-- Pilih Sub Unit Kerja --',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
// // --- isi default unit kerja ---
|
||||
// if(authUnitKerja){
|
||||
// let option = new Option(authUnitKerja.name, authUnitKerja.id, true, true);
|
||||
// $('.unit_kerja').append(option).trigger('change');
|
||||
// }
|
||||
|
||||
let initialUnit = $('.unit_kerja').val();
|
||||
if(initialUnit){
|
||||
loadSubUnitKerja(initialUnit);
|
||||
}
|
||||
|
||||
// jalankan setiap kali unit_kerja berubah
|
||||
$('.unit_kerja').on('change', function(){
|
||||
let idUnit = $(this).val();
|
||||
if(idUnit){
|
||||
loadSubUnitKerja(idUnit);
|
||||
}
|
||||
});
|
||||
|
||||
selectOptionUnitKerjaV1(0);
|
||||
});
|
||||
|
||||
function loadSubUnitKerja(unitId){
|
||||
$('.sub_unit_kerja').empty().append('<option value="" disabled>-- Pilih Sub Unit Kerja --</option>');
|
||||
|
||||
$.ajax({
|
||||
url: `/select-sub-unit-kerja/${unitId}`,
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
if (response?.data) {
|
||||
response.data.forEach(unit => {
|
||||
let selected = (authSubUnitKerja && unit.id === authSubUnitKerja.objectsubunitkerjapegawaifk);
|
||||
const option = new Option(unit.name, unit.id, false, selected);
|
||||
$('.sub_unit_kerja').append(option);
|
||||
});
|
||||
$('.sub_unit_kerja').trigger('change');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function addFormV2(){
|
||||
let col = $("#col_add_fileV2");
|
||||
let html = '';
|
||||
|
||||
html += `
|
||||
<div class="row g-3 align-items-start" id="col-${colCount}">
|
||||
<hr class="my-3" />
|
||||
<div class="col-12 d-flex justify-content-end">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger"
|
||||
onclick="removeCol(${colCount})">
|
||||
<i class="fa-solid fa-trash"></i> Hapus
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-select"
|
||||
name="data[${colCount}][id_unit_kerja]"
|
||||
id="select_id_unit_kerja_${colCount}"
|
||||
required>
|
||||
<option value="" disabled selected>Pilih Unit</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-select"
|
||||
name="data[${colCount}][id_sub_unit_kerja]"
|
||||
id="select_id_sub_unit_kerja_${colCount}"
|
||||
required>
|
||||
<option value="" disabled selected>Pilih Sub Unit</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Kategori Dokumen <span class="text-danger">*</span></label>
|
||||
<select class="form-select"
|
||||
name="data[${colCount}][master_kategori_directory_id]"
|
||||
id="select_kategori_${colCount}"
|
||||
required>
|
||||
<option value="" disabled selected>Pilih Kategori</option>
|
||||
@foreach ($katDok as $kat)
|
||||
<option value="{{ $kat->master_kategori_directory_id }}/{{ $kat->nama_kategori_directory }}">
|
||||
{{ $kat->nama_kategori_directory }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Nomor Dokumen</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">#</span>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="data[${colCount}][no_dokumen]"
|
||||
placeholder="Contoh: 001/RS/IT/I/2026">
|
||||
</div>
|
||||
<div class="form-text text-muted">Opsional, isi jika ada nomor resmi dokumen.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">Tanggal Terbit</label>
|
||||
<input class="form-control"
|
||||
type="date"
|
||||
name="data[${colCount}][date_active]"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">Boleh dilihat unit lain? <span class="text-danger">*</span></label>
|
||||
|
||||
<div class="border rounded-3 p-2 bg-light">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
name="data[${colCount}][is_permission]"
|
||||
id="perm_yes_${colCount}"
|
||||
value="1"
|
||||
required>
|
||||
<label class="form-check-label" for="perm_yes_${colCount}">Iya</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
name="data[${colCount}][is_permission]"
|
||||
id="perm_no_${colCount}"
|
||||
value="2"
|
||||
required>
|
||||
<label class="form-check-label" for="perm_no_${colCount}">Tidak</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="fileUpload_${colCount}" class="form-label fw-semibold">📂 Upload Dokumen (PDF)</label>
|
||||
|
||||
<div class="border rounded-3 p-3 bg-white shadow-sm">
|
||||
<input class="form-control"
|
||||
type="file"
|
||||
id="fileUpload_${colCount}"
|
||||
accept=".pdf"
|
||||
name="data[${colCount}][file]">
|
||||
<div class="mt-2 text-success fw-semibold d-none file-name" id="fileName_${colCount}"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-text text-muted">
|
||||
Format yang didukung: <b>PDF</b>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
col.append(html)
|
||||
selectOptionUnitKerjaV1(colCount)
|
||||
colCount++;
|
||||
}
|
||||
|
||||
function removeCol(count){
|
||||
$(`#col-${count}`).remove()
|
||||
}
|
||||
|
||||
function selectOptionUnitKerjaV1(colCount) {
|
||||
let selectUnit = $(`#select_id_unit_kerja_${colCount}`);
|
||||
let selectSubUnit = $(`#select_id_sub_unit_kerja_${colCount}`);
|
||||
|
||||
// inisialisasi select2 untuk Unit Kerja
|
||||
selectUnit.select2({
|
||||
placeholder: '-- Pilih Unit Kerja --',
|
||||
allowClear:true,
|
||||
width: '100%',
|
||||
dropdownParent: selectUnit.parent(),
|
||||
ajax:{
|
||||
url : '/select-unit-kerja',
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function(params){
|
||||
return { q: params.term }
|
||||
},
|
||||
processResults: function(data){
|
||||
return {
|
||||
results : data?.data.map(item => ({
|
||||
id: item.id+'/'+item.name,
|
||||
text: item.name,
|
||||
sub_units: item.sub_unit_kerja // kirim ke front
|
||||
}))
|
||||
}
|
||||
},
|
||||
cache: true,
|
||||
},
|
||||
minimumInputLength: 1,
|
||||
});
|
||||
|
||||
selectSubUnit.select2({
|
||||
placeholder: '-- Pilih Sub Unit Kerja --',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
dropdownParent: selectSubUnit.parent()
|
||||
});
|
||||
|
||||
// event ketika unit kerja dipilih
|
||||
selectUnit.on('select2:select', function (e) {
|
||||
let data = e.params.data; // data unit kerja terpilih
|
||||
selectSubUnit.empty().append('<option value="" disabled selected>-- Pilih Sub Unit Kerja --</option>');
|
||||
|
||||
if (data.sub_units && data.sub_units.length > 0) {
|
||||
data.sub_units.forEach(sub => {
|
||||
selectSubUnit.append(`<option value="${sub.id}/${sub.name}">${sub.name}</option>`);
|
||||
});
|
||||
}
|
||||
|
||||
// aktifkan select2 untuk sub unit
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
let currentFile = null;
|
||||
let idDirectory = null;
|
||||
document.addEventListener('click', function(e){
|
||||
if(e.target.matches('.file-link')){
|
||||
e.preventDefault();
|
||||
let fileUrl = e.target.getAttribute('data-file');
|
||||
let noDokumen = e.target.getAttribute('data-no_dokumen')
|
||||
let tanggalTerbit = e.target.getAttribute('data-tanggal_terbit')
|
||||
let permissionFile = e.target.getAttribute('data-permission_file')
|
||||
let fileName = e.target.getAttribute('data-fileName')
|
||||
currentFile = fileUrl;
|
||||
idDirectory = e.target.getAttribute('data-id');
|
||||
|
||||
|
||||
const titleEl = document.getElementById('confirm_preview_file');
|
||||
if (titleEl) titleEl.textContent = fileName;
|
||||
|
||||
// set footer info
|
||||
const noEl = document.getElementById('confirm-upload-dokumen');
|
||||
if (noEl) noEl.textContent = noDokumen;
|
||||
|
||||
const tglEl = document.getElementById('confirm-time-dokumen');
|
||||
|
||||
if (tglEl) tglEl.textContent = tanggalTerbit;
|
||||
|
||||
const permEl = document.getElementById('confirm-permission');
|
||||
if (permEl) {
|
||||
const publicDoc = isPublic(permissionFile);
|
||||
permEl.textContent = publicDoc ? 'Bisa dilihat unit lain' : 'Hanya unit ini';
|
||||
permEl.className = 'badge ' + (publicDoc ? 'bg-success' : 'bg-secondary');
|
||||
}
|
||||
let previewBox = document.getElementById('file-preview');
|
||||
previewBox.innerHTML = `<iframe src="/file-preview/${idDirectory}" width="100%" height="500px" style="border:none;"></iframe>`;
|
||||
$("#previewModal").modal('show')
|
||||
}
|
||||
|
||||
if(e.target.id === 'delete-file'){
|
||||
if(!currentFile){
|
||||
Swal.fire({
|
||||
text: "Tidak ada file yang dipilih!",
|
||||
icon: "warning",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
return;
|
||||
}
|
||||
Swal.fire({
|
||||
title: 'Yakin ingin menghapus file ini?',
|
||||
text: "File akan dihapus",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Ya, hapus',
|
||||
cancelButtonText: 'Batal'
|
||||
}).then((result) => {
|
||||
if(result.isConfirmed){
|
||||
fetch(`/delete-file/${idDirectory}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type' : 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({ file: currentFile })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.success){
|
||||
Swal.fire({
|
||||
text: 'File berhasil dihapus',
|
||||
icon: 'success',
|
||||
timer: 2000,
|
||||
showConfirmButton: false
|
||||
});
|
||||
$("#previewModal").modal("hide");
|
||||
fetchData()
|
||||
currentFile = null;
|
||||
idDirectory = null;
|
||||
}else{
|
||||
Swal.fire({
|
||||
text: 'Gagal menghapus file',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
Swal.fire({
|
||||
text: 'Terjadi error saat menghapus file ',
|
||||
icon: 'error'
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(e.target.matches('#btn-view-full')){
|
||||
window.open(`/file-preview/${idDirectory}`, '_blank');
|
||||
}
|
||||
})
|
||||
|
||||
formCreate.off('submit').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const submitBtn = $(this).find('button[type="submit"]');
|
||||
submitBtn.prop('disabled', true).text('menyimpan...')
|
||||
const formData = new FormData(this);
|
||||
console.log(formData);
|
||||
|
||||
fetch(`/uploadv2`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value,
|
||||
},
|
||||
body: formData
|
||||
}).then(async(res) => {
|
||||
const responseData = await res.json();
|
||||
if(responseData.status){
|
||||
Toastify({
|
||||
text: responseData.message || 'Berhasil melakukan aksi!',
|
||||
duration: 3000,
|
||||
gravity: "top",
|
||||
position: "right",
|
||||
style: {
|
||||
background: "linear-gradient(to right, #00b09b, #96c93d)",
|
||||
color: "#fff",
|
||||
}
|
||||
}).showToast();
|
||||
resetCreateForm();
|
||||
fetchData()
|
||||
submitBtn.prop('disabled', false).text('Simpan')
|
||||
const modalInstance = bootstrap.Modal.getInstance(modalCreate);
|
||||
modalInstance?.hide();
|
||||
} else {
|
||||
throw new Error(responseData.message || 'Terjadi kesalahan saat menyimpan data.');
|
||||
}
|
||||
}).catch(err => {
|
||||
if (err.message) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
text: err.message
|
||||
});
|
||||
submitBtn.prop('disabled', false).text('Simpan')
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@ -13,9 +13,9 @@
|
||||
|
||||
<!-- Body -->
|
||||
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
|
||||
<div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
|
||||
{{-- <div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger mb-2 me-2" id="delete-file">🗑 Hapus</button>
|
||||
</div>
|
||||
</div> --}}
|
||||
<div id="file-preview"
|
||||
class="text-center text-muted d-flex justify-content-center align-items-center"
|
||||
style="height:100%;">
|
||||
@ -1,96 +1,199 @@
|
||||
@extends('layout.main')
|
||||
<style>
|
||||
.table-fixed{ table-layout: fixed; width: 100%; }
|
||||
.table-fixed th:nth-child(1), .table-fixed td:nth-child(1){ width: 140px; }
|
||||
.table-fixed th:nth-child(2), .table-fixed td:nth-child(2){ width: 260px; }
|
||||
.table-fixed th:nth-child(3), .table-fixed td:nth-child(3){ width: 220px; }
|
||||
.table-fixed th:nth-child(4), .table-fixed td:nth-child(4){ width: 120px; }
|
||||
.table-fixed th:nth-child(5), .table-fixed td:nth-child(5){ width: 150px; }
|
||||
.file-cell {
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.file-cell{
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.file-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.folder-cell{
|
||||
max-width: 220px;
|
||||
}
|
||||
.file-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.folder-badge{
|
||||
white-space: normal !important;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
line-height: 1.3;
|
||||
padding: .35rem .5rem;
|
||||
}
|
||||
.file-title i {
|
||||
font-size: 18px;
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
color: #1f2937;
|
||||
text-decoration: none;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.file-link:hover {
|
||||
color: #4f46e5;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.file-meta span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* status pending */
|
||||
.file-pending {
|
||||
font-style: italic;
|
||||
color: #9ca3af !important;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link {
|
||||
border: 0;
|
||||
color: #475569;
|
||||
font-weight: 600;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 999px;
|
||||
transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link.active {
|
||||
background: color-mix(in srgb, var(--bs-primary) 12%, #ffffff);
|
||||
color: var(--bs-primary);
|
||||
+}
|
||||
|
||||
.dataunit-tabs .nav-link:hover {
|
||||
color: var(--bs-primary);
|
||||
background: color-mix(in srgb, var(--bs-primary) 8%, #ffffff);
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
right: 18px;
|
||||
bottom: 6px;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: var(--bs-primary);
|
||||
transform: scaleX(0);
|
||||
transform-origin: center;
|
||||
transition: transform .18s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link:hover::after {
|
||||
transform: scaleX(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.dataunit-tab-wrap {
|
||||
background: #f8fafc;
|
||||
border-radius: 999px;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dataunit-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-item {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.dataunit-tabs .nav-link {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.folder-link-sm{
|
||||
font-size: 13px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@section('body_main')
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h4 class="mb-0">Data Terakhir</h4>
|
||||
<button type="button" class="btn btn-success" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
|
||||
<div class="dataunit-tab-wrap">
|
||||
<ul class="nav dataunit-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active d-flex align-items-center gap-2" data-bs-toggle="tab" href="#tab-data-unit" role="tab" aria-controls="tab-data-unit" aria-selected="true">
|
||||
<i class="ti ti-folders"></i>
|
||||
<span>Data Unit</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2" data-bs-toggle="tab" href="#tab-data-recap" role="tab" aria-controls="tab-data-recap" aria-selected="false">
|
||||
<i class="ti ti-chart-bar"></i>
|
||||
<span>Data Recap</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||
<div class="input-group input-group-sm flex-grow-1">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="fa fa-search text-muted"></i>
|
||||
</span>
|
||||
<input type="search"
|
||||
id="tableSearch"
|
||||
class="form-control border-start-0"
|
||||
placeholder="Cari nama file, No Dokumen atau folder"
|
||||
oninput="debouncedTableSearch(this.value)">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tab-data-unit" role="tabpanel">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||
<h4 class="mb-0">Data Unit</h4>
|
||||
<button type="button" class="btn btn-success ms-md-auto" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||
<div class="input-group input-group-sm flex-grow-1">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="fa fa-search text-muted"></i>
|
||||
</span>
|
||||
<input type="search"
|
||||
id="tableSearch"
|
||||
class="form-control border-start-0"
|
||||
placeholder="Cari nama file, No Dokumen atau folder"
|
||||
oninput="debouncedTableSearch(this.value)">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<select id="tablePageSize" class="form-select form-select-sm" style="width: auto;">
|
||||
<option value="5">5</option>
|
||||
<option value="10"selected>10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="small text-muted ms-md-auto" id="tableSummary">Memuat data...</div>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height: 70vh; overflow-y:auto;">
|
||||
<table class="table table-sm table-hover align-middle mb-0 table-fixed" id="lastUpdatedTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nomor Surat</th>
|
||||
<th>File</th>
|
||||
<th>Kategori</th>
|
||||
<th>Unit</th>
|
||||
<th>Sub Unit </th>
|
||||
<th>Tanggal Upload</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableDataUnit">
|
||||
<!-- data dari fetch masuk sini -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mt-3" id="paginationControls"></div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<select id="tablePageSize" class="form-select form-select-sm" style="width: auto;">
|
||||
<option value="5">5</option>
|
||||
<option value="10"selected>10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<div class="tab-pane fade" id="tab-data-recap" role="tabpanel">
|
||||
@include('dataUnit.section.recap')
|
||||
</div>
|
||||
<div class="small text-muted ms-md-auto" id="tableSummary">Memuat data...</div>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height: 70vh; overflow-y:auto;">
|
||||
<table class="table table-sm table-hover align-middle mb-0 table-fixed" id="lastUpdatedTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nomor Surat</th>
|
||||
<th>Nama</th>
|
||||
<th>File Folder</th>
|
||||
<th>Akses</th>
|
||||
<th>Tanggal Modifikasi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableFolderLastUpdated">
|
||||
<!-- data dari fetch masuk sini -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mt-3" id="paginationControls"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include('dashboardV2.modal.create')
|
||||
@include('dashboardV2.modal.view')
|
||||
@include('dataUnit.modal.create')
|
||||
@include('dataUnit.modal.view')
|
||||
<script>
|
||||
const katDok = @json($katDok);
|
||||
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
||||
@ -99,7 +202,7 @@
|
||||
const formCreate = $("#formFile")
|
||||
const modalCreate = document.getElementById('modalCreateFile')
|
||||
const tableState = { data: [], page: 1, pageSize: 8, search: '', lastPage: 1, total: 0 };
|
||||
const tbody = document.getElementById('tableFolderLastUpdated');
|
||||
const tbody = document.getElementById('tableDataUnit');
|
||||
const paginationEl = document.getElementById('paginationControls');
|
||||
const summaryEl = document.getElementById('tableSummary');
|
||||
const pageSizeSelect = document.getElementById('tablePageSize');
|
||||
@ -143,47 +246,40 @@
|
||||
let statusLabel = '';
|
||||
let statusClass = '';
|
||||
|
||||
if (!item.status_action) {
|
||||
statusLabel = 'Pending';
|
||||
statusClass = 'bg-warning text-dark';
|
||||
} else if (publicDoc) {
|
||||
if (publicDoc) {
|
||||
statusLabel = 'Umum';
|
||||
statusClass = 'bg-success';
|
||||
} else {
|
||||
statusLabel = 'Internal Unit';
|
||||
statusClass = 'bg-secondary';
|
||||
}
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td class="text-nowrap">${item.no_dokumen || '-'}</td>
|
||||
<td class="file-cell">
|
||||
<span class="me-1">📄</span>
|
||||
<a href="#" class="file-link ${statusLabel === "Pending" ? 'fst-italic' : ''}"
|
||||
data-file="${item.file}"
|
||||
data-fileName="${fileName}"
|
||||
data-id="${item.file_directory_id}"
|
||||
data-no_dokumen="${item.no_dokumen || '-'}"
|
||||
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
|
||||
data-permission_file="${item.permission_file || '-'}">${fileName}</a>
|
||||
</td>
|
||||
<td class="folder-cell">
|
||||
<a href="#"
|
||||
class="text-dark fw-semibold folder-prefill folder-link-sm ${statusLabel === "Pending" ? 'fst-italic' : ''}"
|
||||
title="${folderPath}"
|
||||
data-unit_id="${item.id_unit_kerja || ''}"
|
||||
data-sub_id="${item.id_sub_unit_kerja || ''}"
|
||||
data-kategori_id="${item.master_kategori_directory_id || ''}"
|
||||
data-unit_name="${unitName}"
|
||||
data-sub_name="${subName}"
|
||||
data-kategori_name="${kategoriName}">
|
||||
${folderPath}
|
||||
</a>
|
||||
<div class="file-title">
|
||||
<i class="ti ti-file-text"></i>
|
||||
<a href="#"
|
||||
class="file-link ${statusLabel === "Pending" ? 'file-pending' : ''}"
|
||||
data-file="${item.file}"
|
||||
data-fileName="${fileName}"
|
||||
data-id="${item.file_directory_id}"
|
||||
data-no_dokumen="${item.no_dokumen || '-'}"
|
||||
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
|
||||
data-permission_file="${item.permission_file || '-'}"
|
||||
>
|
||||
${fileName}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge ${statusClass}">
|
||||
${statusLabel}
|
||||
</span>
|
||||
${kategoriName}
|
||||
</td>
|
||||
<td>
|
||||
${unitName}
|
||||
</td>
|
||||
<td>
|
||||
${subName}
|
||||
</td>
|
||||
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
|
||||
</tr>
|
||||
@ -273,7 +369,7 @@
|
||||
per_page: tableState.pageSize,
|
||||
keyword: tableState.search
|
||||
});
|
||||
fetch(`/last-document?${params.toString()}`)
|
||||
fetch(`/data-internal?${params.toString()}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
tableState.data = data?.data || [];
|
||||
96
resources/views/dataUnit/modal/create.blade.php
Normal file
96
resources/views/dataUnit/modal/create.blade.php
Normal file
@ -0,0 +1,96 @@
|
||||
<div class="modal fade" id="modalCreateFile" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Aksi </h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<!-- Modal Form -->
|
||||
<form id="formFile" action="/uploadv2" enctype="multipart/form-data" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="container" style="max-height: 70vh; overflow-y:auto;">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-control unit_kerja" name="data[0][id_unit_kerja]" id="select_id_unit_kerja_0" required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-control" name="data[0][id_sub_unit_kerja]" id="select_id_sub_unit_kerja_0" required>
|
||||
<option value="" disable selected>Select Choose</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Kategori Dokumen <span class="text-danger">*</span></label>
|
||||
<select class="form-control" name="data[0][master_kategori_directory_id]" id="select_kategori_0" required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
@foreach ($katDok as $kat)
|
||||
<option value="{{ $kat->master_kategori_directory_id }}/{{ $kat->nama_kategori_directory }}">{{ $kat->nama_kategori_directory }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Nomor Dokumen</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">#</span>
|
||||
<input type="text" class="form-control" name="data[0][no_dokumen]" placeholder="Contoh: 001/RS/IT/I/2026">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">Tanggal Terbit</label>
|
||||
<input class="form-control" type="date" name="data[0][date_active]">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">Boleh dilihat unit lain? <span class="text-danger">*</span></label>
|
||||
|
||||
<div class="border rounded-3 p-2 bg-light">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="data[0][is_permission]" id="perm_yes" value="1" required>
|
||||
<label class="form-check-label" for="perm_yes">
|
||||
Iya
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input" type="radio" name="data[0][is_permission]" id="perm_no" value="2" required>
|
||||
<label class="form-check-label" for="perm_no">
|
||||
Tidak
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 mb-2">
|
||||
<label for="fileUpload0" class="form-label fw-semibold">📂 Upload Dokumen (PDF)</label>
|
||||
<div class="border rounded-3 p-3 bg-white shadow-sm">
|
||||
<input class="form-control" type="file" id="fileUpload0" accept=".pdf" name="data[0][file]">
|
||||
<div class="mt-2 text-success fw-semibold d-none file-name"></div>
|
||||
</div>
|
||||
<div class="form-text text-muted">
|
||||
Bisa upload lebih dari 1 file. Format yang didukung: <b>PDF</b>.
|
||||
</div>
|
||||
</div>
|
||||
<div id="col_add_fileV2" class="col-12"></div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm mt-3" onclick="addFormV2()">
|
||||
+ Tambah Form
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Footer -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
|
||||
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
61
resources/views/dataUnit/modal/view.blade.php
Normal file
61
resources/views/dataUnit/modal/view.blade.php
Normal file
@ -0,0 +1,61 @@
|
||||
<!-- Modal Preview -->
|
||||
<div class="modal fade" id="previewModal" tabindex="-1" aria-labelledby="previewModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<h6 class="modal-title" id="previewModalLabel">
|
||||
📄 Preview <strong id="confirm_preview_file"></strong>
|
||||
</h6>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
|
||||
{{-- <div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger mb-2 me-2" id="delete-file">🗑 Hapus</button>
|
||||
</div> --}}
|
||||
<div id="file-preview"
|
||||
class="text-center text-muted d-flex justify-content-center align-items-center"
|
||||
style="height:100%;">
|
||||
<p>📂 Pilih file untuk melihat preview</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer d-flex justify-content-between align-items-center">
|
||||
|
||||
<div class="small text-muted">
|
||||
<span class="me-3">
|
||||
Nomor Dokumen:
|
||||
<strong id="confirm-upload-dokumen" class="text-dark">-</strong>
|
||||
</span>
|
||||
|
||||
<span class="me-3">
|
||||
Tanggal Terbit:
|
||||
<strong id="confirm-time-dokumen" class="text-dark">-</strong>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Akses Dokumen:
|
||||
<span id="confirm-permission" class="badge bg-secondary">-</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- Button -->
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="btn-view-full">
|
||||
Lihat Full
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">
|
||||
Tutup
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
147
resources/views/dataUnit/section/recap.blade.php
Normal file
147
resources/views/dataUnit/section/recap.blade.php
Normal file
@ -0,0 +1,147 @@
|
||||
@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</h4>
|
||||
<small class="text-muted">Ringkasan jumlah file per Unit dan Folder</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</th>
|
||||
<th style="width:30%;">Sub Unit</th>
|
||||
<th style="width:20%;">Folder</th>
|
||||
<th style="width:15%;" class="text-center">Jumlah</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/recap?' + 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;
|
||||
}
|
||||
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.subUnit || '-'}</td>
|
||||
<td>${f.folder || '-'}</td>
|
||||
<td class="text-center fw-bold">${f.count || 0}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
return folderRows;
|
||||
}).join('');
|
||||
tbody.innerHTML = html;
|
||||
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>
|
||||
@ -22,17 +22,23 @@
|
||||
{{-- HOME --}}
|
||||
<li class="nav-small-cap"><span class="hide-menu">Home</span></li>
|
||||
|
||||
<li class="sidebar-item">
|
||||
{{-- <li class="sidebar-item">
|
||||
<a class="sidebar-link" href="{{ url('/') }}" aria-expanded="false">
|
||||
<i class="ti ti-dashboard"></i>
|
||||
<span class="hide-menu">Dashboard</span>
|
||||
</a>
|
||||
</li> --}}
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link" href="{{ url('/') }}" aria-expanded="false">
|
||||
<i class="ti ti-dashboard"></i>
|
||||
<span class="hide-menu">Data Unit</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link" href="{{ url('/new') }}" aria-expanded="false">
|
||||
<a class="sidebar-link" href="{{ url('/data-umum') }}" aria-expanded="false">
|
||||
<i class="ti ti-layout-dashboard"></i>
|
||||
<span class="hide-menu">Dashboard V2</span>
|
||||
<span class="hide-menu">Data Umum</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -75,7 +81,7 @@
|
||||
</li>
|
||||
@endif
|
||||
{{-- RECAP --}}
|
||||
<li class="nav-small-cap"><span class="hide-menu">Recap</span></li>
|
||||
{{-- <li class="nav-small-cap"><span class="hide-menu">Recap</span></li>
|
||||
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link d-flex align-items-center justify-content-between"
|
||||
@ -85,7 +91,7 @@
|
||||
<span class="hide-menu">Data Rekap</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</li> --}}
|
||||
|
||||
{{-- MASTER --}}
|
||||
@if(auth()->user()->akses && auth()->user()->akses->master_akses)
|
||||
@ -138,6 +144,28 @@
|
||||
.sidebar-submenu { padding-left: 2.7rem; }
|
||||
.sidebar-submenu .sidebar-link { padding: 6px 0; font-size: 13px; opacity: .9; }
|
||||
|
||||
.sidebar-item .sidebar-link {
|
||||
border-radius: 10px;
|
||||
transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
|
||||
.sidebar-item .sidebar-link:hover,
|
||||
.sidebar-item .sidebar-link:focus {
|
||||
background: #eef2ff;
|
||||
color: #1e40af;
|
||||
box-shadow: 0 8px 18px rgba(30, 64, 175, 0.12);
|
||||
}
|
||||
|
||||
.sidebar-item .sidebar-link:focus-visible {
|
||||
outline: 2px solid rgba(59, 130, 246, 0.6);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.sidebar-item .sidebar-link:hover i,
|
||||
.sidebar-item .sidebar-link:focus i {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.sidebar-item.has-sub .sidebar-arrow { transition: transform .2s ease; }
|
||||
.sidebar-item.has-sub.open .sidebar-arrow { transform: rotate(90deg); }
|
||||
</style>
|
||||
@ -185,4 +213,3 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -12,8 +12,9 @@ use Illuminate\Support\Facades\Route;
|
||||
Route::middleware(['auth'])->group(function(){
|
||||
|
||||
Route::get('/', [DashboardController::class, 'index']);
|
||||
Route::get('/new', [DashboardController::class, 'dashboardVersion2']);
|
||||
Route::get('/last-document', [DashboardController::class, 'dataDocumentLast']);
|
||||
Route::get('/data-internal', [DashboardController::class, 'dataUnitInternal']);
|
||||
Route::get('/data-umum', [DashboardController::class, 'dataUmum']);
|
||||
Route::get('/datatable-umum', [DashboardController::class, 'dataDocumentLast']);
|
||||
Route::post('/uploadv2', [DashboardController::class, 'storeVersion2']);
|
||||
Route::get('/file-preview/{id}', [DashboardController::class, 'dataPdf']);
|
||||
Route::post('/upload', [DashboardController::class, 'store']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user