diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 668cc8d..03f2855 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -15,6 +15,7 @@ use App\Models\UnitKerja; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -2742,16 +2743,34 @@ class DashboardController extends Controller $katDok = MasterKategori::where('statusenabled', true) ->select('master_kategori_directory_id', 'nama_kategori_directory') ->get(); + $akreditasiTree = $this->loadAkreditasiTree(); $data = [ 'title' => 'Akreditasi', - 'katDok' => $katDok + 'katDok' => $katDok, + 'akreditasiTree' => $akreditasiTree, ]; return view('dataAkreditasi.index', $data); } + private function loadAkreditasiTree(): array + { + $path = public_path('json/akreditasi.jff'); + if (!File::exists($path)) { + return []; + } + + try { + $decoded = json_decode(File::get($path), true, 512, JSON_THROW_ON_ERROR); + return is_array($decoded) ? $decoded : []; + } catch (\Throwable $th) { + return []; + } + } + public function dataTableAkreditasi(Request $request) { $keyword = $request->query('keyword'); + $folderPath = trim((string) $request->query('folder_path', '')); $perPage = (int) $request->query('per_page', 10); $perPage = max(1, min(100, $perPage)); @@ -2770,12 +2789,19 @@ class DashboardController extends Controller ->where('status_action', 'approved') ->where('is_akre', true); - // Logic Pencarian + // Filter spesifik berdasarkan path folder dari tree/explorer. + // Gunakan prefix match agar "KPS 9.c" tidak ikut mengambil "KPS 9.f". + $query->when($folderPath !== '', function ($q) use ($folderPath) { + $escapedPath = addcslashes($folderPath, '\\%_'); + $q->where('file', 'ILIKE', $escapedPath . '/%'); + }); + + // Pencarian nama file/dokumen bisa digabung dengan filter folder. $query->when($keyword, function ($q) use ($keyword) { $q->where(function ($sub) use ($keyword) { $sub->where('nama_dokumen', 'ILIKE', "%{$keyword}%") ->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%") - ->orWhere('file', 'ILIKE', "%{$keyword}%"); // Cari juga berdasarkan nama file di path + ->orWhere('file', 'ILIKE', "%{$keyword}%"); }); }); $data = $query diff --git a/composer.lock b/composer.lock index e5119ea..b3b9dc5 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.383.2", + "version": "3.384.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "11c2de39e4511dc99e44f049c7dfc8087e051867" + "reference": "a6c85b3a1f5f8327076291040329214b3a2caf2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/11c2de39e4511dc99e44f049c7dfc8087e051867", - "reference": "11c2de39e4511dc99e44f049c7dfc8087e051867", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a6c85b3a1f5f8327076291040329214b3a2caf2b", + "reference": "a6c85b3a1f5f8327076291040329214b3a2caf2b", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.383.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.384.2" }, - "time": "2026-06-01T18:08:21+00:00" + "time": "2026-06-03T18:07:33+00:00" }, { "name": "brick/math", @@ -873,25 +873,26 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.6", + "version": "7.11.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "e7412b3180912c01650cc66647f18c1d1cbe9b94" + "reference": "c987f8ce84b8434fa430795eca0f3430663da72b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e7412b3180912c01650cc66647f18c1d1cbe9b94", - "reference": "e7412b3180912c01650cc66647f18c1d1cbe9b94", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c987f8ce84b8434fa430795eca0f3430663da72b", + "reference": "c987f8ce84b8434fa430795eca0f3430663da72b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^2.5", + "guzzlehttp/psr7": "^2.11", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.24" }, "provide": { "psr/http-client-implementation": "1.0" @@ -980,7 +981,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.6" + "source": "https://github.com/guzzle/guzzle/tree/7.11.0" }, "funding": [ { @@ -996,24 +997,25 @@ "type": "tidelift" } ], - "time": "2026-06-01T13:06:22+00:00" + "time": "2026-06-02T12:40:51+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2" + "reference": "4360e982f87f5f258bf872d094647791db2f4c8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2", + "url": "https://api.github.com/repos/guzzle/promises/zipball/4360e982f87f5f258bf872d094647791db2f4c8e", + "reference": "4360e982f87f5f258bf872d094647791db2f4c8e", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -1063,7 +1065,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.4.1" + "source": "https://github.com/guzzle/promises/tree/2.5.0" }, "funding": [ { @@ -1079,27 +1081,29 @@ "type": "tidelift" } ], - "time": "2026-05-20T22:57:30+00:00" + "time": "2026-06-02T12:23:43+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.10.4", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "d2a1a094e396da8957e797489fddaf860c340cfc" + "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/d2a1a094e396da8957e797489fddaf860c340cfc", - "reference": "d2a1a094e396da8957e797489fddaf860c340cfc", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f", + "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.24" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -1180,7 +1184,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.10.4" + "source": "https://github.com/guzzle/psr7/tree/2.11.0" }, "funding": [ { @@ -1196,7 +1200,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T12:59:07+00:00" + "time": "2026-06-02T12:30:48+00:00" }, { "name": "guzzlehttp/uri-template", @@ -7189,16 +7193,16 @@ }, { "name": "laravel/sail", - "version": "v1.61.0", + "version": "v1.62.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "68ef35015630fe510432e63e11e21749006df688" + "reference": "3aaeefc979f8ba6586fbc5b6e0b1b3638058f98e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/68ef35015630fe510432e63e11e21749006df688", - "reference": "68ef35015630fe510432e63e11e21749006df688", + "url": "https://api.github.com/repos/laravel/sail/zipball/3aaeefc979f8ba6586fbc5b6e0b1b3638058f98e", + "reference": "3aaeefc979f8ba6586fbc5b6e0b1b3638058f98e", "shasum": "" }, "require": { @@ -7248,7 +7252,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2026-05-23T23:33:57+00:00" + "time": "2026-05-27T04:02:01+00:00" }, { "name": "mockery/mockery", @@ -9231,12 +9235,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/public/json/akreditasi.jff b/public/json/akreditasi.jff index 52ac783..9ac6c57 100644 --- a/public/json/akreditasi.jff +++ b/public/json/akreditasi.jff @@ -103,6 +103,188 @@ "name": "TKRS 6.f" } ] + }, + { + "name": "TKRS 7", + "turunan": [ + { + "name": "TKRS 7.a" + }, + { + "name": "TKRS 7.b" + }, + { + "name": "TKRS 7.c" + }, + { + "name": "TKRS 7.d" + }, + { + "name": "TKRS 7.e" + }, + { + "name": "TKRS 7.f" + } + ] + }, + { + "name": "TKRS 7.1", + "turunan": [ + { + "name": "TKRS 7.1.a" + }, + { + "name": "TKRS 7.1.b" + }, + { + "name": "TKRS 7.1.c" + }, + { + "name": "TKRS 7.1.d" + } + ] + }, + { + "name": "TKRS 8", + "turunan": [ + { + "name": "TKRS 8.a" + }, + { + "name": "TKRS 8.b" + }, + { + "name": "TKRS 8.c" + } + ] + }, + { + "name": "TKRS 9", + "turunan": [ + { + "name": "TKRS 9.a" + }, + { + "name": "TKRS 9.b" + }, + { + "name": "TKRS 9.c" + }, + { + "name": "TKRS 9.d" + }, + { + "name": "TKRS 9.e" + } + ] + }, + { + "name": "TKRS 10", + "turunan": [ + { + "name": "TKRS 10.a" + }, + { + "name": "TKRS 10.b" + }, + { + "name": "TKRS 10.c" + }, + { + "name": "TKRS 10.d" + } + ] + }, + { + "name": "TKRS 11", + "turunan": [ + { + "name": "TKRS 11.a" + }, + { + "name": "TKRS 11.b" + }, + { + "name": "TKRS 11.c" + } + ] + }, + { + "name": "TKRS 12", + "turunan": [ + { + "name": "TKRS 12.a" + }, + { + "name": "TKRS 12.b" + }, + { + "name": "TKRS 12.c" + }, + { + "name": "TKRS 12.d" + } + ] + }, + { + "name": "TKRS 13", + "turunan": [ + { + "name": "TKRS 13.a" + }, + { + "name": "TKRS 13.b" + }, + { + "name": "TKRS 13.c" + }, + { + "name": "TKRS 13.d" + }, + { + "name": "TKRS 13.e" + }, + { + "name": "TKRS 13.f" + } + ] + }, + { + "name": "TKRS 14", + "turunan": [ + { + "name": "TKRS 14.a" + }, + { + "name": "TKRS 14.b" + } + ] + }, + { + "name": "TKRS 15", + "turunan": [ + { + "name": "TKRS 15.a" + }, + { + "name": "TKRS 15.b" + }, + { + "name": "TKRS 15.c" + }, + { + "name": "TKRS 15.d" + }, + { + "name": "TKRS 15.e" + }, + { + "name": "TKRS 15.f" + }, + { + "name": "TKRS 15.g" + } + ] } ] }, diff --git a/resources/views/dataAkreditasi/index.blade.php b/resources/views/dataAkreditasi/index.blade.php index de1e358..7293dfc 100644 --- a/resources/views/dataAkreditasi/index.blade.php +++ b/resources/views/dataAkreditasi/index.blade.php @@ -1,5 +1,247 @@ @extends('layout.main') @section('body_main') -
-
-
+
+
+
+
+
Struktur Akreditasi
+

Pilih folder di kiri untuk memfilter dokumen di panel kanan.

+
+
+
Memuat instrumen...
+
+
+
+
+
-
-

Dokumen Akreditasi

-
+
+
+

Dokumen Akreditasi

+

Buka struktur di kiri sampai folder tujuan, lalu dokumen akan tampil di kanan.

+
+
-
-
- +
+ Path + Belum ada folder dipilih +
+ +
+
+ Name + Tipe + Struktur
-
- +
+
Pilih folder pada struktur kiri untuk melihat isinya.
-
- - - - - - - - - - - - - - -
- - AksiNama Dokumen / FolderTipeUnitTgl Unggah
-
-
-
- Tampilkan - - data -
- -
- -
- Memuat data... -
-
-
@@ -213,15 +443,15 @@ // === STATE MANAGEMENT === let currentData = []; let selectedIds = []; - let expandedFolders = new Set(); let searchTimer; + let activeFileSearch = ''; const isAdminUser = @json(Auth::guard('admin')->check()); const btn = document.getElementById('btnDownloadMultiple'); - const tableState = { page: 1, pageSize: 10, lastPage: 1, total: 0, from: 0, to: 0, search: '' }; - const paginationEl = document.getElementById('paginationControls'); // === INITIALIZATION === document.addEventListener('DOMContentLoaded', () => { - fetchData(); + loadAkreData().then(() => initAkreTree(true)); + currentData = []; + renderAkreSubtree(); initEventListeners(); initTooltips(); }); @@ -239,24 +469,35 @@ // === EVENT LISTENERS === function initEventListeners() { - // Search dengan Debounce - document.getElementById('tableSearch').addEventListener('input', (e) => { - clearTimeout(searchTimer); - searchTimer = setTimeout(() => { - tableState.page = 1; - fetchData(e.target.value, 1); - }, 500); - }); + const fileNameSearch = document.getElementById('fileNameSearch'); + if (fileNameSearch) { + const runFileSearch = (value) => { + activeFileSearch = String(value || '').trim(); + if (activeAkrePath) { + fetchData(activeAkrePath, 1); + } else { + renderAkreSubtree(); + } + }; - // Per Page Change - document.getElementById('tablePageSize').addEventListener('change', (e) => { - const keyword = document.getElementById('tableSearch').value; - tableState.page = 1; - fetchData(keyword, 1); - }); + fileNameSearch.addEventListener('input', function(e) { + clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + runFileSearch(e.target.value); + }, 150); + }); + + fileNameSearch.addEventListener('keydown', function(e) { + if (e.key !== 'Enter') return; + e.preventDefault(); + clearTimeout(searchTimer); + runFileSearch(e.target.value); + }); + } // Check All Rows - document.getElementById('checkAllRows').addEventListener('change', function() { + const checkAllRows = document.getElementById('checkAllRows'); + if (checkAllRows) checkAllRows.addEventListener('change', function() { const checkboxes = document.querySelectorAll('.row-checkbox'); selectedIds = []; checkboxes.forEach(cb => { @@ -273,253 +514,41 @@ * Mengambil data dari server */ function fetchData(keyword = '', page = 1) { - const tbody = document.getElementById('tableDataAkreditasi'); - const perPage = parseInt(document.getElementById('tablePageSize').value, 10) || 10; + const perPage = 100; + const subtreeEl = document.getElementById('akreSubtreeContainer'); + if (subtreeEl) { + subtreeEl.innerHTML = '
Memuat isi folder...
'; + } - tableState.search = keyword; - tableState.pageSize = perPage; - tableState.page = page; + const params = new URLSearchParams({ + per_page: String(perPage), + page: String(page), + }); - tbody.innerHTML = 'Memuat data...'; + if (keyword) { + params.set('folder_path', keyword); + } + if (activeFileSearch) { + params.set('keyword', activeFileSearch); + } - fetch(`/datatable-akreditasi?keyword=${encodeURIComponent(keyword)}&per_page=${perPage}&page=${page}`) + fetch(`/datatable-akreditasi?${params.toString()}`) .then(res => res.json()) .then(response => { if (response.status) { currentData = response.data || []; - renderTable(keyword); - const pag = response.pagination || {}; - tableState.total = pag.total || 0; - tableState.lastPage = pag.last_page || 1; - tableState.from = pag.from || 0; - tableState.to = pag.to || 0; - renderPagination(tableState.lastPage); - updateSummary(); + renderAkreSubtree(); } else { - tbody.innerHTML = `${response.message}`; + currentData = []; + renderAkreSubtree(); } }) .catch(err => { console.error('Error fetching data:', err); - tbody.innerHTML = 'Gagal memuat data dari server.'; + currentData = []; + renderAkreSubtree(); }); } - - /** - * Merender tabel berdasarkan mode (Folder vs Search) - */ - function renderTable(keyword = '') { - const tbody = document.getElementById('tableDataAkreditasi'); - tbody.innerHTML = ''; - - if (currentData.length === 0) { - tbody.innerHTML = 'Tidak ada data ditemukan'; - return; - } - - // MODE 1: SEARCH VIEW (Flat View - Langsung tampilkan semua file) - if (keyword.length > 0) { - currentData.forEach(item => renderFileRow(tbody, item)); - return; - } - - // MODE 2: TREE VIEW (All folders & files) - const tree = buildTree(currentData); - if (expandedFolders.size === 0) { - expandAllFolders(tree, ''); - } - renderTree(tbody, tree, '', 0); - initTooltips(); - } - - /** - * Render Baris File (Digunakan di kedua mode) - */ - function renderFileRow(targetTbody, item) { - const fileName = item.file ? item.file.split('/').pop() : 'Unknown'; - const typeDok = fileName.split('.').pop().toUpperCase(); - const isChecked = selectedIds.includes(String(item.file_directory_id)) ? 'checked' : ''; - - targetTbody.innerHTML += ` - - - - - -
- - - -
- - -
- ${item.nama_dokumen} - Path: ${item.file} -
- - ${typeDok} - - ${item.entry_at || '-'} - `; - } - - // === TREE FUNCTIONS === - - function buildTree(items) { - const root = { folders: {}, files: [] }; - (items || []).forEach(item => { - const path = (item.file || '').split('/').filter(Boolean); - if (path.length === 0) { - root.files.push(item); - return; - } - const fileName = path.pop(); - let node = root; - path.forEach(part => { - if (!node.folders[part]) { - node.folders[part] = { folders: {}, files: [] }; - } - node = node.folders[part]; - }); - node.files.push({ ...item, __fileName: fileName }); - }); - return root; - } - - function expandAllFolders(node, basePath) { - Object.keys(node.folders || {}).forEach(folder => { - const folderPath = basePath ? `${basePath}/${folder}` : folder; - expandedFolders.add(folderPath); - expandAllFolders(node.folders[folder], folderPath); - }); - } - - function renderTree(tbody, node, basePath, level) { - const folderNames = Object.keys(node.folders || {}).sort(); - - folderNames.forEach(folder => { - const folderPath = basePath ? `${basePath}/${folder}` : folder; - const isExpanded = expandedFolders.has(folderPath); - const folderNode = node.folders[folder]; - const depth = folderPath.split('/').filter(Boolean).length; - const canAddChild = isAdminUser && (depth === 1 || depth === 2); - - let lines = ''; - for (let i = 0; i < level; i++) { - lines += ''; - } - - tbody.innerHTML += ` - - -
- ${lines} - - - - - ${folder} - ${canAddChild ? ` - - - - ` : ''} -
- - FOLDER - - `; - - if (isExpanded) { - renderTree(tbody, folderNode, folderPath, level + 1); - } - }); - - // Bagian ini akan memanggil renderTreeFileRow yang menampilkan tanggal - (node.files || []).forEach(item => { - renderTreeFileRow(tbody, item, level); - }); - } - - function getFolderMaxDate(node) { - let dates = []; - - // Ambil tanggal dari file di level ini - (node.files || []).forEach(f => { - if (f.entry_at) dates.push(new Date(f.entry_at)); - }); - - // Ambil tanggal dari sub-folder (rekursif) - Object.values(node.folders || {}).forEach(subNode => { - const subDate = getFolderMaxDate(subNode); - if (subDate) dates.push(new Date(subDate)); - }); - - if (dates.length === 0) return null; - - // Cari tanggal paling besar (terbaru) - const maxDate = new Date(Math.max(...dates)); - - // Format balik ke YYYY-MM-DD - return maxDate.toISOString().split('T')[0]; - } - - function renderTreeFileRow(targetTbody, item, level){ - const fileName = item.__fileName || (item.file ? item.file.split('/').pop() : 'Unknown'); - const typeDok = fileName.split('.').pop().toUpperCase(); - const isChecked = selectedIds.includes(String(item.file_directory_id)) ? 'checked' : ''; - - let lines = ''; - for (let i = 0; i < level; i++) { - lines += ''; - } - - targetTbody.innerHTML += ` - - - - - - - - - - -
- ${lines} - -
- ${item.nama_dokumen} - ${fileName} -
-
- - ${typeDok} - ${item.unit?.name || '-'} - ${formatDateTime(item.entry_at)} - `; - } - // Tambahkan fungsi ini di dalam