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;
|
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
|
class DashboardController extends Controller
|
||||||
{
|
{
|
||||||
public function index(){
|
// public function index(){
|
||||||
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
|
// $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();
|
// $klasifikasiDok = MasterKlasifikasi::where('statusenabled', true)->select('master_klasifikasi_directory_id', 'nama_klasifikasi_directory')->get();
|
||||||
$prefillFilter = session()->pull('dashboard_prefill');
|
// $prefillFilter = session()->pull('dashboard_prefill');
|
||||||
|
|
||||||
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
// $authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
||||||
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
// $authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
||||||
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
|
// $authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
|
||||||
|
|
||||||
$allAkses = AksesFile::where(['statusenabled' => true, 'pegawai_id' => auth()->user()?->dataUser->id])->first();
|
// $allAkses = AksesFile::where(['statusenabled' => true, 'pegawai_id' => auth()->user()?->dataUser->id])->first();
|
||||||
$payload = [
|
// $payload = [
|
||||||
'title' => 'Dashboard',
|
// 'title' => 'Dashboard',
|
||||||
'katDok' => $katDok,
|
// 'katDok' => $katDok,
|
||||||
'klasifikasiDok' => $klasifikasiDok,
|
// 'klasifikasiDok' => $klasifikasiDok,
|
||||||
'authUnitKerja' => $authUnitKerja,
|
// 'authUnitKerja' => $authUnitKerja,
|
||||||
'authSubUnitKerja' => $authSubUnitKerja,
|
// 'authSubUnitKerja' => $authSubUnitKerja,
|
||||||
'allAkses' => $allAkses ?? null,
|
// 'allAkses' => $allAkses ?? null,
|
||||||
'prefillFilter' => $prefillFilter
|
// 'prefillFilter' => $prefillFilter
|
||||||
];
|
// ];
|
||||||
return view('dashboard.index', $payload);
|
// return view('dashboard.index', $payload);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public function setDashboardPrefill(Request $request)
|
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(){
|
public function dataUnitKerja(){
|
||||||
$user = auth()->user()?->dataUser;
|
$user = auth()->user()?->dataUser;
|
||||||
$entryPegawaiId = auth()->user()?->objectpegawaifk;
|
$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();
|
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
|
||||||
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
|
||||||
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
|
||||||
@ -470,7 +580,7 @@ class DashboardController extends Controller
|
|||||||
'authUnitKerja' => $authUnitKerja,
|
'authUnitKerja' => $authUnitKerja,
|
||||||
'authSubUnitKerja' => $authSubUnitKerja,
|
'authSubUnitKerja' => $authSubUnitKerja,
|
||||||
];
|
];
|
||||||
return view('dashboardV2.index', $data);
|
return view('dataUmum.index', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataDocumentLast(){
|
public function dataDocumentLast(){
|
||||||
@ -643,8 +753,9 @@ class DashboardController extends Controller
|
|||||||
|
|
||||||
private function watermarkCenterAndStream(string $pdfPath, string $stampFile)
|
private function watermarkCenterAndStream(string $pdfPath, string $stampFile)
|
||||||
{
|
{
|
||||||
$pdf = new Fpdi();
|
$pdf = new PdfFpdiWithAlpha();
|
||||||
$pageCount = $pdf->setSourceFile($pdfPath);
|
$pageCount = $pdf->setSourceFile($pdfPath);
|
||||||
|
$opacity = $this->getWatermarkOpacity();
|
||||||
|
|
||||||
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
||||||
$tplId = $pdf->importPage($pageNo);
|
$tplId = $pdf->importPage($pageNo);
|
||||||
@ -660,7 +771,9 @@ class DashboardController extends Controller
|
|||||||
$x = ($size['width'] - $stampW) / 2;
|
$x = ($size['width'] - $stampW) / 2;
|
||||||
$y = ($size['height'] - $stampH) / 2;
|
$y = ($size['height'] - $stampH) / 2;
|
||||||
|
|
||||||
|
$pdf->SetAlpha($opacity);
|
||||||
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
||||||
|
$pdf->SetAlpha(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = $pdf->Output('S');
|
$output = $pdf->Output('S');
|
||||||
@ -706,8 +819,9 @@ class DashboardController extends Controller
|
|||||||
|
|
||||||
private function watermarkCenterToFile(string $pdfPath, string $stampFile, string $outputPath): void
|
private function watermarkCenterToFile(string $pdfPath, string $stampFile, string $outputPath): void
|
||||||
{
|
{
|
||||||
$pdf = new Fpdi();
|
$pdf = new PdfFpdiWithAlpha();
|
||||||
$pageCount = $pdf->setSourceFile($pdfPath);
|
$pageCount = $pdf->setSourceFile($pdfPath);
|
||||||
|
$opacity = $this->getWatermarkOpacity();
|
||||||
|
|
||||||
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
||||||
$tplId = $pdf->importPage($pageNo);
|
$tplId = $pdf->importPage($pageNo);
|
||||||
@ -722,12 +836,19 @@ class DashboardController extends Controller
|
|||||||
$x = ($size['width'] - $stampW) / 2;
|
$x = ($size['width'] - $stampW) / 2;
|
||||||
$y = ($size['height'] - $stampH) / 2;
|
$y = ($size['height'] - $stampH) / 2;
|
||||||
|
|
||||||
|
$pdf->SetAlpha($opacity);
|
||||||
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
||||||
|
$pdf->SetAlpha(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pdf->Output($outputPath, 'F');
|
$pdf->Output($outputPath, 'F');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getWatermarkOpacity(): float
|
||||||
|
{
|
||||||
|
return 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
private function cleanupTempFiles(array $tempFiles): void
|
private function cleanupTempFiles(array $tempFiles): void
|
||||||
{
|
{
|
||||||
foreach ($tempFiles as $file) {
|
foreach ($tempFiles as $file) {
|
||||||
|
|||||||
@ -280,7 +280,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if(e.target.matches('#btn-view-full')){
|
||||||
|
window.open(`/file-preview/${idDirectory}`, '_blank');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
|
<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>
|
<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"
|
<div id="file-preview"
|
||||||
class="text-center text-muted d-flex justify-content-center align-items-center"
|
class="text-center text-muted d-flex justify-content-center align-items-center"
|
||||||
style="height:100%;">
|
style="height:100%;">
|
||||||
|
|||||||
@ -9,150 +9,12 @@
|
|||||||
<h4 class="mb-0">Rekap Dokumen</h4>
|
<h4 class="mb-0">Rekap Dokumen</h4>
|
||||||
<small class="text-muted">Ringkasan jumlah file per Unit dan Folder</small>
|
<small class="text-muted">Ringkasan jumlah file per Unit dan Folder</small>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<div class="card-body pb-2">
|
<div class="card-body pb-2">
|
||||||
<div class="table-responsive" style="max-height: 55vh; overflow-y:auto;">
|
@include('dataUnit.section.recap', ['showRecapTitle' => false])
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@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 -->
|
<!-- Body -->
|
||||||
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
|
<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>
|
<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"
|
<div id="file-preview"
|
||||||
class="text-center text-muted d-flex justify-content-center align-items-center"
|
class="text-center text-muted d-flex justify-content-center align-items-center"
|
||||||
style="height:100%;">
|
style="height:100%;">
|
||||||
@ -1,96 +1,199 @@
|
|||||||
@extends('layout.main')
|
@extends('layout.main')
|
||||||
<style>
|
<style>
|
||||||
.table-fixed{ table-layout: fixed; width: 100%; }
|
.file-cell {
|
||||||
.table-fixed th:nth-child(1), .table-fixed td:nth-child(1){ width: 140px; }
|
max-width: 420px;
|
||||||
.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{
|
.file-wrapper {
|
||||||
white-space: normal;
|
display: flex;
|
||||||
word-break: break-word;
|
flex-direction: column;
|
||||||
overflow-wrap: anywhere;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-cell{
|
.file-title {
|
||||||
max-width: 220px;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
.folder-badge{
|
.file-title i {
|
||||||
white-space: normal !important;
|
font-size: 18px;
|
||||||
word-break: break-word;
|
color: #6366f1;
|
||||||
overflow-wrap: anywhere;
|
}
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
.file-link {
|
||||||
-webkit-box-orient: vertical;
|
color: #1f2937;
|
||||||
overflow: hidden;
|
text-decoration: none;
|
||||||
max-width: 100%;
|
line-height: 1.3;
|
||||||
text-align: left;
|
}
|
||||||
line-height: 1.3;
|
|
||||||
padding: .35rem .5rem;
|
.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>
|
</style>
|
||||||
@section('body_main')
|
@section('body_main')
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex align-items-center justify-content-between">
|
<div class="card-header d-flex align-items-center justify-content-between">
|
||||||
<h4 class="mb-0">Data Terakhir</h4>
|
<div class="dataunit-tab-wrap">
|
||||||
<button type="button" class="btn btn-success" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
|
<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>
|
||||||
<div class="card-body p-3">
|
<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="tab-content">
|
||||||
<div class="input-group input-group-sm flex-grow-1">
|
<div class="tab-pane fade show active" id="tab-data-unit" role="tabpanel">
|
||||||
<span class="input-group-text bg-white border-end-0">
|
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||||
<i class="fa fa-search text-muted"></i>
|
<h4 class="mb-0">Data Unit</h4>
|
||||||
</span>
|
<button type="button" class="btn btn-success ms-md-auto" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
|
||||||
<input type="search"
|
</div>
|
||||||
id="tableSearch"
|
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||||
class="form-control border-start-0"
|
<div class="input-group input-group-sm flex-grow-1">
|
||||||
placeholder="Cari nama file, No Dokumen atau folder"
|
<span class="input-group-text bg-white border-end-0">
|
||||||
oninput="debouncedTableSearch(this.value)">
|
<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>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="tab-pane fade" id="tab-data-recap" role="tabpanel">
|
||||||
<select id="tablePageSize" class="form-select form-select-sm" style="width: auto;">
|
@include('dataUnit.section.recap')
|
||||||
<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>
|
||||||
<div class="small text-muted ms-md-auto" id="tableSummary">Memuat data...</div>
|
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@include('dashboardV2.modal.create')
|
@include('dataUnit.modal.create')
|
||||||
@include('dashboardV2.modal.view')
|
@include('dataUnit.modal.view')
|
||||||
<script>
|
<script>
|
||||||
const katDok = @json($katDok);
|
const katDok = @json($katDok);
|
||||||
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
||||||
@ -99,7 +202,7 @@
|
|||||||
const formCreate = $("#formFile")
|
const formCreate = $("#formFile")
|
||||||
const modalCreate = document.getElementById('modalCreateFile')
|
const modalCreate = document.getElementById('modalCreateFile')
|
||||||
const tableState = { data: [], page: 1, pageSize: 8, search: '', lastPage: 1, total: 0 };
|
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 paginationEl = document.getElementById('paginationControls');
|
||||||
const summaryEl = document.getElementById('tableSummary');
|
const summaryEl = document.getElementById('tableSummary');
|
||||||
const pageSizeSelect = document.getElementById('tablePageSize');
|
const pageSizeSelect = document.getElementById('tablePageSize');
|
||||||
@ -143,47 +246,40 @@
|
|||||||
let statusLabel = '';
|
let statusLabel = '';
|
||||||
let statusClass = '';
|
let statusClass = '';
|
||||||
|
|
||||||
if (!item.status_action) {
|
if (publicDoc) {
|
||||||
statusLabel = 'Pending';
|
|
||||||
statusClass = 'bg-warning text-dark';
|
|
||||||
} else if (publicDoc) {
|
|
||||||
statusLabel = 'Umum';
|
statusLabel = 'Umum';
|
||||||
statusClass = 'bg-success';
|
statusClass = 'bg-success';
|
||||||
} else {
|
} else {
|
||||||
statusLabel = 'Internal Unit';
|
statusLabel = 'Internal Unit';
|
||||||
statusClass = 'bg-secondary';
|
statusClass = 'bg-secondary';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap">${item.no_dokumen || '-'}</td>
|
<td class="text-nowrap">${item.no_dokumen || '-'}</td>
|
||||||
<td class="file-cell">
|
<td class="file-cell">
|
||||||
<span class="me-1">📄</span>
|
<div class="file-title">
|
||||||
<a href="#" class="file-link ${statusLabel === "Pending" ? 'fst-italic' : ''}"
|
<i class="ti ti-file-text"></i>
|
||||||
data-file="${item.file}"
|
<a href="#"
|
||||||
data-fileName="${fileName}"
|
class="file-link ${statusLabel === "Pending" ? 'file-pending' : ''}"
|
||||||
data-id="${item.file_directory_id}"
|
data-file="${item.file}"
|
||||||
data-no_dokumen="${item.no_dokumen || '-'}"
|
data-fileName="${fileName}"
|
||||||
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
|
data-id="${item.file_directory_id}"
|
||||||
data-permission_file="${item.permission_file || '-'}">${fileName}</a>
|
data-no_dokumen="${item.no_dokumen || '-'}"
|
||||||
</td>
|
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
|
||||||
<td class="folder-cell">
|
data-permission_file="${item.permission_file || '-'}"
|
||||||
<a href="#"
|
>
|
||||||
class="text-dark fw-semibold folder-prefill folder-link-sm ${statusLabel === "Pending" ? 'fst-italic' : ''}"
|
${fileName}
|
||||||
title="${folderPath}"
|
</a>
|
||||||
data-unit_id="${item.id_unit_kerja || ''}"
|
</div>
|
||||||
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>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge ${statusClass}">
|
${kategoriName}
|
||||||
${statusLabel}
|
</td>
|
||||||
</span>
|
<td>
|
||||||
|
${unitName}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
${subName}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
|
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -273,7 +369,7 @@
|
|||||||
per_page: tableState.pageSize,
|
per_page: tableState.pageSize,
|
||||||
keyword: tableState.search
|
keyword: tableState.search
|
||||||
});
|
});
|
||||||
fetch(`/last-document?${params.toString()}`)
|
fetch(`/data-internal?${params.toString()}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
tableState.data = data?.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 --}}
|
{{-- HOME --}}
|
||||||
<li class="nav-small-cap"><span class="hide-menu">Home</span></li>
|
<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">
|
<a class="sidebar-link" href="{{ url('/') }}" aria-expanded="false">
|
||||||
<i class="ti ti-dashboard"></i>
|
<i class="ti ti-dashboard"></i>
|
||||||
<span class="hide-menu">Dashboard</span>
|
<span class="hide-menu">Dashboard</span>
|
||||||
</a>
|
</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>
|
||||||
|
|
||||||
<li class="sidebar-item">
|
<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>
|
<i class="ti ti-layout-dashboard"></i>
|
||||||
<span class="hide-menu">Dashboard V2</span>
|
<span class="hide-menu">Data Umum</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -75,7 +81,7 @@
|
|||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
{{-- RECAP --}}
|
{{-- 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">
|
<li class="sidebar-item">
|
||||||
<a class="sidebar-link d-flex align-items-center justify-content-between"
|
<a class="sidebar-link d-flex align-items-center justify-content-between"
|
||||||
@ -85,7 +91,7 @@
|
|||||||
<span class="hide-menu">Data Rekap</span>
|
<span class="hide-menu">Data Rekap</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li> --}}
|
||||||
|
|
||||||
{{-- MASTER --}}
|
{{-- MASTER --}}
|
||||||
@if(auth()->user()->akses && auth()->user()->akses->master_akses)
|
@if(auth()->user()->akses && auth()->user()->akses->master_akses)
|
||||||
@ -138,6 +144,28 @@
|
|||||||
.sidebar-submenu { padding-left: 2.7rem; }
|
.sidebar-submenu { padding-left: 2.7rem; }
|
||||||
.sidebar-submenu .sidebar-link { padding: 6px 0; font-size: 13px; opacity: .9; }
|
.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 .sidebar-arrow { transition: transform .2s ease; }
|
||||||
.sidebar-item.has-sub.open .sidebar-arrow { transform: rotate(90deg); }
|
.sidebar-item.has-sub.open .sidebar-arrow { transform: rotate(90deg); }
|
||||||
</style>
|
</style>
|
||||||
@ -185,4 +213,3 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,9 @@ use Illuminate\Support\Facades\Route;
|
|||||||
Route::middleware(['auth'])->group(function(){
|
Route::middleware(['auth'])->group(function(){
|
||||||
|
|
||||||
Route::get('/', [DashboardController::class, 'index']);
|
Route::get('/', [DashboardController::class, 'index']);
|
||||||
Route::get('/new', [DashboardController::class, 'dashboardVersion2']);
|
Route::get('/data-internal', [DashboardController::class, 'dataUnitInternal']);
|
||||||
Route::get('/last-document', [DashboardController::class, 'dataDocumentLast']);
|
Route::get('/data-umum', [DashboardController::class, 'dataUmum']);
|
||||||
|
Route::get('/datatable-umum', [DashboardController::class, 'dataDocumentLast']);
|
||||||
Route::post('/uploadv2', [DashboardController::class, 'storeVersion2']);
|
Route::post('/uploadv2', [DashboardController::class, 'storeVersion2']);
|
||||||
Route::get('/file-preview/{id}', [DashboardController::class, 'dataPdf']);
|
Route::get('/file-preview/{id}', [DashboardController::class, 'dataPdf']);
|
||||||
Route::post('/upload', [DashboardController::class, 'store']);
|
Route::post('/upload', [DashboardController::class, 'store']);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user