Compare commits

...

4 Commits
main ... akrev2

8 changed files with 1989 additions and 550 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Models\FileDirectory;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -149,6 +150,90 @@ class AkreditasiInstrumenController extends Controller
return true;
}
private function hasFilesInPath(string $folderPath): bool
{
$escapedPath = addcslashes($folderPath, '\\%_');
return FileDirectory::query()
->where('statusenabled', true)
->where('is_akre', true)
->where('file', 'ILIKE', $escapedPath . '/%')
->exists();
}
private function removeNodeByPath(array &$data, string $folderPath): bool
{
$parts = array_values(array_filter(explode('/', $folderPath), fn ($value) => trim((string) $value) !== ''));
$depth = count($parts);
if ($depth === 0) {
return false;
}
if ($depth === 1) {
foreach ($data as $index => $type) {
if (($type['name'] ?? null) !== $parts[0]) {
continue;
}
$segments = $type['segment'] ?? [];
if (is_array($segments) && count($segments) > 0) {
return false;
}
array_splice($data, $index, 1);
return true;
}
return false;
}
foreach ($data as $typeIndex => $type) {
if (($type['name'] ?? null) !== $parts[0]) {
continue;
}
$segments = $data[$typeIndex]['segment'] ?? [];
if (!is_array($segments)) {
return false;
}
foreach ($segments as $segmentIndex => $segment) {
if (($segment['name'] ?? null) !== $parts[1]) {
continue;
}
if ($depth === 2) {
$children = $segment['turunan'] ?? [];
if (is_array($children) && count($children) > 0) {
return false;
}
array_splice($segments, $segmentIndex, 1);
$data[$typeIndex]['segment'] = array_values($segments);
return true;
}
$children = $segments[$segmentIndex]['turunan'] ?? [];
if (!is_array($children)) {
return false;
}
foreach ($children as $childIndex => $child) {
if (($child['name'] ?? null) !== $parts[2]) {
continue;
}
array_splice($children, $childIndex, 1);
$segments[$segmentIndex]['turunan'] = array_values($children);
$data[$typeIndex]['segment'] = array_values($segments);
return true;
}
}
}
return false;
}
/**
* Read akreditasi.jff (array)
*/
@ -313,4 +398,97 @@ class AkreditasiInstrumenController extends Controller
@fclose($lockFp);
}
}
public function destroy(Request $request)
{
$validated = $request->validate([
'path' => ['required', 'string', 'max:2000'],
]);
$folderPath = trim((string) ($validated['path'] ?? ''));
if ($folderPath === '') {
return response()->json([
'status' => false,
'message' => 'Path folder wajib diisi.',
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
if ($this->hasFilesInPath($folderPath)) {
return response()->json([
'status' => false,
'message' => 'Folder tidak bisa dihapus karena masih memiliki file di dalamnya.',
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$this->ensureJsonDirExists();
$path = $this->akreditasiJsonPath();
$lockPath = $this->akreditasiLockPath();
$lockFp = @fopen($lockPath, 'c+');
if (!$lockFp) {
return response()->json([
'status' => false,
'message' => 'Gagal membuka file lock untuk akreditasi.jff.',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
try {
if (!flock($lockFp, LOCK_EX)) {
return response()->json([
'status' => false,
'message' => 'Gagal mengunci file akreditasi.jff.',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$raw = @file_get_contents($path);
if ($raw === false) {
$data = [];
} else {
$data = $this->decodeJsonArray($raw, true);
if ($data === null) {
$repaired = $this->tryRepairJsonArray($raw);
if ($repaired === null) {
return response()->json([
'status' => false,
'message' => 'File public/json/akreditasi.jff tidak valid (JSON rusak).',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$data = $repaired;
}
}
$removed = $this->removeNodeByPath($data, $folderPath);
if (!$removed) {
return response()->json([
'status' => false,
'message' => 'Folder tidak ditemukan atau masih memiliki turunan.',
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return response()->json([
'status' => false,
'message' => 'Gagal mengubah data menjadi JSON.',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$json .= "\n";
if (!$this->atomicWrite($path, $json)) {
return response()->json([
'status' => false,
'message' => 'Gagal menulis file akreditasi.jff (atomic write).',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
return response()->json([
'status' => true,
'message' => 'Folder akreditasi berhasil dihapus.',
'data' => $data,
]);
} finally {
@flock($lockFp, LOCK_UN);
@fclose($lockFp);
}
}
}

View File

@ -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;
@ -87,27 +88,6 @@ class PdfFpdiWithAlpha extends Fpdi
class DashboardController extends Controller
{
// public function index(){
// $katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
// $klasifikasiDok = MasterKlasifikasi::where('statusenabled', true)->select('master_klasifikasi_directory_id', 'nama_klasifikasi_directory')->get();
// $prefillFilter = session()->pull('dashboard_prefill');
// $authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
// $authUnitKerja = $authMapping->objectunitkerjapegawaifk;
// $authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
// $allAkses = AksesFile::where(['statusenabled' => true, 'pegawai_id' => auth()->user()?->dataUser->id])->first();
// $payload = [
// 'title' => 'Dashboard',
// 'katDok' => $katDok,
// 'klasifikasiDok' => $klasifikasiDok,
// 'authUnitKerja' => $authUnitKerja,
// 'authSubUnitKerja' => $authSubUnitKerja,
// 'allAkses' => $allAkses ?? null,
// 'prefillFilter' => $prefillFilter
// ];
// return view('dashboard.index', $payload);
// }
public function setDashboardPrefill(Request $request)
{
@ -2761,16 +2741,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));
@ -2789,12 +2787,28 @@ 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, $keyword) {
$escapedPath = addcslashes($folderPath, '\\%_');
$slashCount = substr_count($folderPath, '/') + 1;
$q->where('file', 'ILIKE', $escapedPath . '/%');
if (!$keyword) {
$q->whereRaw(
"(length(file) - length(replace(file, '/', ''))) = ?",
[$slashCount]
);
}
});
// 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
@ -2819,6 +2833,57 @@ class DashboardController extends Controller
]);
}
public function dataAkreditasiRecap()
{
$rows = FileDirectory::query()
->select(['file'])
->where('statusenabled', true)
->where('status_action', 'approved')
->where('is_akre', true)
->get();
$folderCounts = [];
$totalFiles = 0;
foreach ($rows as $row) {
$filePath = trim((string) ($row->file ?? ''));
if ($filePath === '' || !str_contains($filePath, '/')) {
continue;
}
$folderPath = preg_replace('~/[^/]+$~', '', $filePath);
$folderPath = trim((string) $folderPath, '/');
if ($folderPath === '') {
continue;
}
$folderCounts[$folderPath] = ($folderCounts[$folderPath] ?? 0) + 1;
$totalFiles++;
}
$detailRows = collect($folderCounts)
->map(fn ($count, $path) => [
'path' => $path,
'total' => $count,
])
->sortBy([
['total', 'desc'],
['path', 'asc'],
])
->values()
->all();
return response()->json([
'status' => true,
'message' => 'Berhasil mendapatkan rekap akreditasi',
'summary' => [
'total_files' => $totalFiles,
'total_folders' => count($folderCounts),
],
'data' => $detailRows,
]);
}
public function expDokumen(){
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];

78
composer.lock generated
View File

@ -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"
}

View File

@ -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"
}
]
}
]
},

View File

@ -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"
}
]
}
]
},
@ -1298,7 +1480,7 @@
},
{
"name": "PPI 8.c"
},
}
]
},
{
@ -3178,4 +3360,8 @@
}
]
},
{
"name": "tst",
"segment": []
}
]

File diff suppressed because it is too large Load Diff

View File

@ -2,5 +2,5 @@
<p class="mb-0 fs-4"> ©
<script>
document.write(new Date().getFullYear());
</script> made with by <a href="/" target="_blank" >TIM SIMRS</a></p>
</script> made with by <a href="/" target="_blank" >TIM SIRS</a></p>
</div>

View File

@ -19,10 +19,12 @@ Route::middleware(['auth:admin,web'])->group(function(){
Route::get('/datatable-umum', [DashboardController::class, 'datatableDataUmum']);
Route::get('/data-akreditasi', [DashboardController::class, 'dataAkreditasi']);
Route::get('/datatable-akreditasi', [DashboardController::class, 'dataTableAkreditasi']);
Route::get('/data-akreditasi/recap', [DashboardController::class, 'dataAkreditasiRecap']);
// Kelola "folder/instrumen" akreditasi via file: public/json/akreditasi.jff (tanpa database)
Route::get('/akreditasi/instrumen', [AkreditasiInstrumenController::class, 'index']);
Route::post('/akreditasi/instrumen', [AkreditasiInstrumenController::class, 'store']);
Route::delete('/akreditasi/instrumen', [AkreditasiInstrumenController::class, 'destroy']);
Route::get('/download-excel/data-umum', [DashboardController::class, 'downloadDataUmumExcel']);
Route::post('/uploadv2', [DashboardController::class, 'storeVersion2']);
Route::get('/file-preview/{id}', [DashboardController::class, 'dataPdf']);