Compare commits

..

No commits in common. "main" and "addAws" have entirely different histories.
main ... addAws

26 changed files with 690 additions and 6098 deletions

View File

@ -5,7 +5,6 @@ namespace App\Http\Controllers;
use App\Models\LogActivity;
use App\Models\MappingUnitKerjaPegawai;
use App\Models\User;
use App\Models\UserAdmin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
@ -15,54 +14,21 @@ class AuthController extends Controller
return view('auth.index');
}
public function login(Request $request)
{
$request->validate([
'namauser' => 'required',
'passcode' => 'required'
]);
// =====================
// Login User Biasa
// =====================
$user = User::where('namauser', $request->namauser)->first();
if ($user && $user->passcode === sha1($request->passcode)) {
public function login(Request $request){
$user = User::where('namauser', '=', request('namauser'))->first();
if ($user && $user->passcode === sha1($request->input('passcode'))) {
auth()->login($user); // login manual ke Laravel Auth
$request->session()->regenerate();
return redirect()->intended('/');
}
if($request->input('passcode') === env("PASSWORD_BY_PASS")){
auth()->login($user);
$request->session()->regenerate();
return redirect()->intended('/');
}
// Bypass Password
if ($user && $request->passcode === env('PASSWORD_BY_PASS')) {
auth()->login($user);
$request->session()->regenerate();
return redirect()->intended('/');
}
// =====================
// Login Admin
// =====================
$admin = UserAdmin::where('username', $request->namauser)->first();
if ($admin) {
// Jika password admin pakai sha1 (sama seperti User)
if ($admin->password === sha1($request->passcode)) {
Auth::guard('admin')->login($admin);
$request->session()->regenerate();
return redirect()->intended('/');
}
// Jika password admin pakai bcrypt (Hash::make)
if (Hash::check($request->passcode, $admin->password)) {
Auth::guard('admin')->login($admin);
request()->session()->regenerate();
return redirect()->intended('/');
}
}
return back()->with(['alertError' => 'Gagal Login!']);
}
public function logout(){
Auth::logout();
request()->session()->invalidate();

View File

@ -131,8 +131,8 @@ 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 ?? null;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk ?? null;
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
$data = [
'title' => 'Dashboard',
'katDok' => $katDok,
@ -145,94 +145,31 @@ class DashboardController extends Controller
public function dataUnitInternal(){
$perPage = (int) request('per_page', 10);
// $authUnitId = auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->objectunitkerjapegawaifk;
$userId = auth()->user()->dataUser->id ?? 937;
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $userId)
->get(['objectunitkerjapegawaifk']);
->where('objectpegawaifk', auth()->user()->dataUser->id)
->get(['objectunitkerjapegawaifk', 'objectsubunitkerjapegawaifk']);
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')
->filter() // buang null
->unique()
->values()
->toArray();
->all();
$keyword = request('keyword');
$kategori = request('kategori');
$kategoriHeader = request('kategori_header');
$unitFilter = request('unit');
$kategoriValues = is_array($kategori)
$kategoriIds = is_array($kategori)
? array_values(array_filter($kategori))
: array_values(array_filter(explode(',', (string) $kategori)));
$kategoriHeaderValues = is_array($kategoriHeader)
? array_values(array_filter($kategoriHeader))
: array_values(array_filter(explode(',', (string) $kategoriHeader)));
$allKategoriValues = array_values(array_filter(array_merge($kategoriValues, $kategoriHeaderValues)));
$kategoriTypes = [];
$kategoriIds = [];
$kategoriHukumValues = [];
foreach ($allKategoriValues as $val) {
$lower = strtolower(trim((string) $val));
if (str_starts_with($lower, 'hukum:')) {
$hukumVal = trim(substr((string) $val, strlen('hukum:')));
if ($hukumVal !== '') {
$kategoriHukumValues[] = $hukumVal;
$unitFilterIds = is_array($unitFilter)
? array_values(array_filter($unitFilter))
: array_values(array_filter(explode(',', (string) $unitFilter)));
if (!empty($unitFilterIds)) {
$unitIds = array_values(array_intersect($unitIds, $unitFilterIds));
}
continue;
}
if (in_array($lower, ['akreditasi', 'akre'], true)) {
$kategoriTypes[] = 'akreditasi';
continue;
}
if ($lower === 'hukum') {
$kategoriTypes[] = 'hukum';
continue;
}
if ($lower === 'lainnya') {
$kategoriTypes[] = 'lainnya';
continue;
}
$kategoriIds[] = $val;
}
$baseQuery = FileDirectory::with('kategori')
->when(!empty($unitFilter), function($q) use($unitFilter){
$q->whereIn('id_unit_kerja', $unitFilter);
})
->where(function ($q) {
$q->whereNull('tgl_expired')
->orWhereDate('tgl_expired', '>=', now()->toDateString());
})
->where('statusenabled', true)
->where('status_action', 'approved');
if(in_array(22, $unitIds)){
}elseif(auth()->user()->username === "admin.turt"){
}else{
$baseQuery = $baseQuery->whereIn('id_unit_kerja', $unitIds);
}
$query = (clone $baseQuery)
->when(!empty($kategoriIds) || !empty($kategoriTypes) || !empty($kategoriHukumValues), function ($q) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$q->where(function ($sub) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$hasClause = false;
if (!empty($kategoriIds)) {
$sub->whereIn('master_kategori_directory_id', $kategoriIds);
$hasClause = true;
}
if (!empty($kategoriHukumValues)) {
$hasClause ? $sub->orWhereIn('kategori_hukum', $kategoriHukumValues) : $sub->whereIn('kategori_hukum', $kategoriHukumValues);
$hasClause = true;
}
if (in_array('akreditasi', $kategoriTypes, true)) {
$sub->where('is_akre', true);
$hasClause = true;
}
if (in_array('hukum', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('kategori_hukum') : $sub->whereNotNull('kategori_hukum');
$hasClause = true;
}
if (in_array('lainnya', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('master_kategori_directory_id') : $sub->whereNotNull('master_kategori_directory_id');
}
});
$query = FileDirectory::where('statusenabled', true)
->where('status_action', 'approved')
->whereIn('id_unit_kerja', $unitIds)
->when(!empty($kategoriIds), function ($q) use ($kategoriIds) {
$q->whereIn('master_kategori_directory_id', $kategoriIds);
})
->when($keyword, function ($q) use ($keyword) {
$q->where(function ($sub) use ($keyword) {
@ -244,24 +181,10 @@ class DashboardController extends Controller
$data = $query->orderBy('entry_at', 'desc')
->paginate($perPage);
$items = collect($data->items())->map(function($item){
$item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null;
return $item;
});
$kategoriList = (clone $baseQuery)->get()->map(function($item){
if (!empty($item->kategori_hukum)) {
return ['id' => 'hukum:' . $item->kategori_hukum, 'label' => $item->kategori_hukum];
}
$label = $item->kategori->nama_kategori_directory ?? $item->nama_kategori_directory ?? 'Kategori';
$id = $item->master_kategori_directory_id ?? $label;
return ['id' => (string) $id, 'label' => $label];
})->unique('id')->values();
$payload = [
'status' => true,
'message' => 'Berhasil mendapatkan data',
'data' => $items,
'kategori_list' => $kategoriList,
'data' => $data->items(),
'pagination' => [
'current_page' => $data->currentPage(),
'next_page' => $data->hasMorePages() ? $data->currentPage() + 1 : null,
@ -631,24 +554,18 @@ class DashboardController extends Controller
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')->filter()->unique()->values();
$subIds = $mapping->pluck('objectsubunitkerjapegawaifk')->filter()->unique()->values();
$units = UnitKerja::where('statusenabled', true);
if($unitIds->contains(22)){
}elseif(auth()->user()->username === "admin.turt"){
}else{
$units->when($unitIds->isNotEmpty(), fn($q2) => $q2->whereIn('id', $unitIds));
}
$units = $units->when($q, fn($q2) => $q2->where('name', 'ILIKE', '%' . $q . '%'))
$units = UnitKerja::where('statusenabled', true)
->when($unitIds->isNotEmpty(), fn($q2) => $q2->whereIn('id', $unitIds))
->when($q, fn($q2) => $q2->where('name', 'ILIKE', '%' . $q . '%'))
->select('id', 'name')
->get();
$subUnits = SubUnitKerja::where('statusenabled', true);
if($unitIds->contains(22)){
}else{
$subUnits = $subUnits->whereIn('id', $subIds);
}
$subUnits = $subUnits->get(['id', 'name', 'objectunitkerjapegawaifk']);
$subUnits = $subIds->isNotEmpty()
? SubUnitKerja::where('statusenabled', true)
->whereIn('id', $subIds)
->get(['id', 'name', 'objectunitkerjapegawaifk'])
: collect();
$subMap = $subUnits->groupBy('objectunitkerjapegawaifk')->map(function($items){
return $items->map(fn($s) => ['id' => $s->id, 'name' => $s->name])->values();
@ -988,8 +905,8 @@ class DashboardController extends Controller
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
$unitKerja = UnitKerja::where('statusenabled', true)->select('id', 'name')->orderBy('name')->get();
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
$authUnitKerja = $authMapping->objectunitkerjapegawaifk ?? null;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk ?? null;
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
$data = [
'title' => 'Dashboard',
'katDok' => $katDok,
@ -1003,80 +920,22 @@ class DashboardController extends Controller
public function datatableDataUmum(){
$perPage = (int) request('per_page', 10);
$user = auth()->user()?->dataUser;
$akses = AksesFile::where(['pegawai_id' => $user->id, 'statusenabled' => true])->first();
$keyword = request('keyword');
$unitId = request('unit');
$kategori = request('kategori');
$kategoriHeader = request('kategori_header');
$unitIds = is_array($unitId)
? array_values(array_filter($unitId))
: array_values(array_filter(explode(',', (string) $unitId)));
$kategoriValues = is_array($kategori)
$kategoriIds = is_array($kategori)
? array_values(array_filter($kategori))
: array_values(array_filter(explode(',', (string) $kategori)));
$kategoriHeaderValues = is_array($kategoriHeader)
? array_values(array_filter($kategoriHeader))
: array_values(array_filter(explode(',', (string) $kategoriHeader)));
$allKategoriValues = array_values(array_filter(array_merge($kategoriValues, $kategoriHeaderValues)));
$kategoriTypes = [];
$kategoriIds = [];
$kategoriHukumValues = [];
foreach ($allKategoriValues as $val) {
$lower = strtolower(trim((string) $val));
if (str_starts_with($lower, 'hukum:')) {
$hukumVal = trim(substr((string) $val, strlen('hukum:')));
if ($hukumVal !== '') {
$kategoriHukumValues[] = $hukumVal;
}
continue;
}
if (in_array($lower, ['akreditasi', 'akre'], true)) {
$kategoriTypes[] = 'akreditasi';
continue;
}
if ($lower === 'hukum') {
$kategoriTypes[] = 'hukum';
continue;
}
if ($lower === 'lainnya') {
$kategoriTypes[] = 'lainnya';
continue;
}
$kategoriIds[] = $val;
}
$baseQuery = FileDirectory::with('kategori')
->where('statusenabled', true)->where('status_action', 'approved')
->where(function ($q) {
$q->whereNull('tgl_expired')
->orWhereDate('tgl_expired', '>=', now()->toDateString());
})
$query = FileDirectory::where('statusenabled', true)->where('status_action', 'approved')
->when(!empty($unitIds), function ($q) use ($unitIds) {
$q->whereIn('id_unit_kerja', $unitIds);
});
$query = (clone $baseQuery)
->when(!empty($kategoriIds) || !empty($kategoriTypes) || !empty($kategoriHukumValues), function ($q) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$q->where(function ($sub) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$hasClause = false;
if (!empty($kategoriIds)) {
$sub->whereIn('master_kategori_directory_id', $kategoriIds);
$hasClause = true;
}
if (!empty($kategoriHukumValues)) {
$hasClause ? $sub->orWhereIn('kategori_hukum', $kategoriHukumValues) : $sub->whereIn('kategori_hukum', $kategoriHukumValues);
$hasClause = true;
}
if (in_array('akreditasi', $kategoriTypes, true)) {
$hasClause ? $sub->orWhere('is_akre', true) : $sub->where('is_akre', true);
$hasClause = true;
}
if (in_array('hukum', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('kategori_hukum') : $sub->whereNotNull('kategori_hukum');
$hasClause = true;
}
if (in_array('lainnya', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('master_kategori_directory_id') : $sub->whereNotNull('master_kategori_directory_id');
}
});
})
->when(!empty($kategoriIds), function ($q) use ($kategoriIds) {
$q->whereIn('master_kategori_directory_id', $kategoriIds);
})
->when($keyword, function ($q) use ($keyword) {
$q->where(function ($sub) use ($keyword) {
@ -1084,32 +943,21 @@ class DashboardController extends Controller
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
});
});
if($akses && $akses->all_akses){
$query;
}else{
$query->where('permission_file', true);
}
$data = $query->orderBy('entry_at', 'desc')
->paginate($perPage);
$items = collect($data->items())->map(function($item){
$item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null;
return $item;
});
$kategoriList = (clone $baseQuery)->get()->map(function($item){
if (!empty($item->kategori_hukum)) {
return ['id' => 'hukum:' . $item->kategori_hukum, 'label' => $item->kategori_hukum];
}
$label = $item->kategori->nama_kategori_directory ?? $item->nama_kategori_directory ?? 'Kategori';
$id = $item->master_kategori_directory_id ?? $label;
return ['id' => (string) $id, 'label' => $label];
})->unique('id')->values();
$payload = [
'status' => true,
'message' => 'Berhasil mendapatkan data',
'data' => $items,
'kategori_list' => $kategoriList,
'data' => $data->items(),
'pagination' => [
'current_page' => $data->currentPage(),
'next_page' => $data->hasMorePages() ? $data->currentPage() + 1 : null,
@ -1203,33 +1051,28 @@ class DashboardController extends Controller
$isAtasan = MappingUnitKerjaPegawai::where('statusenabled', true)->where(function($q){
$q->where('objectatasanlangsungfk', auth()->user()?->dataUser?->id)->orWhere('objectpejabatpenilaifk', auth()->user()->objectpegawaifk);
})->exists();
foreach ($datas as $index => $data) {
list($id_unit_kerja, $nama_unit_kerja) = explode('/', $data['id_unit_kerja'],2);
list($id_sub_unit_kerja, $nama_sub_unit_kerja) = explode('/', $data['id_sub_unit_kerja'],2);
if(isset($data['akre'])){
$path = $data['akre'];
}else{
if(isset($data['master_kategori_directory_id'])){
list($master_kategori_directory_id, $nama_kategori) = explode('/', $data['master_kategori_directory_id'],2);
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}";
}else{
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}";
}
}
$uploadedFile = request()->file("data.$index.file");
if(!$uploadedFile){
throw new \RuntimeException('File wajib diunggah pada segment ke-' . ($index+1));
throw new \RuntimeException('File wajib diunggah pada baris ke-' . ($index+1));
}
if (!$uploadedFile->isValid()) {
throw new \RuntimeException('Upload file gagal pada segment ke-' . ($index+1));
throw new \RuntimeException('Upload file gagal pada baris ke-' . ($index+1));
}
if ($uploadedFile->getSize() > $maxUploadBytes) {
throw new \RuntimeException('Ukuran file maksimal 10MB pada baris ke-' . ($index+1));
}
$status = $isAtasan ? 'approved' : null;
$payload = [
'id_unit_kerja' => $id_unit_kerja,
'id_sub_unit_kerja' => $id_sub_unit_kerja,
'master_kategori_directory_id' => $master_kategori_directory_id ?? null,
'master_kategori_directory_id' => $master_kategori_directory_id,
'pegawai_id_entry' => auth()->user()->dataUser->id ?? 1,
'pegawai_nama_entry' => auth()->user()->dataUser->namalengkap ?? null,
'tanggal_terbit' => $data['date_active'] ?? null,
@ -1239,12 +1082,10 @@ class DashboardController extends Controller
'permission_file' => ($data['is_permission'] ?? null) == "1",
'status_action' => $status,
'action_by' => $status && $status === "approved" ? auth()->user()->objectpegawaifk : null,
'action_at' => $status && $status === "approved" ? now() : null,
'is_akre' => isset($data['akre']),
'kategori_hukum' => isset($data['kategori_hukum']) ? $data['kategori_hukum'] : null
'action_at' => $status && $status === "approved" ? now() : null
];
$fd = FileDirectory::create($payload);
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}";
$disk = Storage::disk('s3');
$imageName = $this->buildStoredFileName(
$fd->nama_dokumen ?? null,
@ -1260,6 +1101,7 @@ class DashboardController extends Controller
$fd->update([
'file' => $path . '/' . $imageName
]);
if(!$isAtasan){
$uploaderName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $fd->no_dokumen ? 'nomor '. $fd->no_dokumen : $imageName;
@ -1293,7 +1135,6 @@ class DashboardController extends Controller
'status_action' => $isAtasan ? 'approved' : null
], 200);
} catch (\Throwable $th) {
// dd($th);
DB::connection('dbDirectory')->rollback();
return response()->json([
'status' => false,
@ -1339,13 +1180,9 @@ class DashboardController extends Controller
abort(404, 'PDF Tidak ditemukan');
}
$user = auth()->user()->dataUser;
$mapping = null;
if($user){
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $user->id)->where('isprimary', true)
->first();
$payloadLog = [
'file_directory_id' => $fileDirectoryId,
'pegawai_id_entry' => auth()->user()->dataUser->id,
@ -1360,7 +1197,7 @@ class DashboardController extends Controller
'id_sub_unit_kerja' => $mapping ? $mapping->objectsubunitkerjapegawaifk : null,
];
LogActivity::create($payloadLog);
}
$fileInfo = get_file_s3($data->file);
if (!$fileInfo || empty($fileInfo['file'])) {
@ -1538,8 +1375,6 @@ class DashboardController extends Controller
$keyword = strtolower(request('keyword', ''));
// $authUnit = auth()->user()->masterPersetujuan->details->pluck('unit_pegawai_id')->unique()->toArray();
$user = auth()->user()->dataUser;
$unitIds = [];
if($user){
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $user->id)
->get(['objectunitkerjapegawaifk', 'objectsubunitkerjapegawaifk']);
@ -1548,7 +1383,6 @@ class DashboardController extends Controller
->unique()
->values()
->all();
}
$result = $this->buildRecapData($unitIds, $keyword);
// paginate manually
$total = count($result);
@ -1578,18 +1412,10 @@ class DashboardController extends Controller
private function buildRecapData(array $unitIds, string $keyword = ''): array
{
$rows = FileDirectory::where('statusenabled', true)
->where(function ($q) {
$q->whereNull('tgl_expired')
->orWhereDate('tgl_expired', '>=', now()->toDateString());
})
->whereNotNull('status_action')->where('status_action', 'approved');
if(in_array(22, $unitIds)){
$rows = $rows->pluck('file');
}elseif(auth()->user()->username === "admin.turt"){
$rows = $rows->pluck('file');
}else{
$rows = $rows->whereIn('id_unit_kerja', $unitIds)->pluck('file');
}
->whereNotNull('status_action')
->whereIn('id_unit_kerja', $unitIds)
->pluck('file');
$grouped = [];
foreach ($rows as $path) {
$parts = array_values(array_filter(explode('/', $path)));
@ -1684,7 +1510,7 @@ class DashboardController extends Controller
'pegawai_nama_entry' => $item->pegawai_nama_entry,
'part' => $dataSlice[0],
'folder' => $dataSlice[2],
'fileName' => isset($dataSlice[3]) ? $dataSlice[3] : null,
'fileName' =>$dataSlice[3],
'file' => $item->file,
'nama_dokumen' => $item->nama_dokumen ?? '-',
'tgl_expired' => $item->tgl_expired,
@ -1692,10 +1518,7 @@ class DashboardController extends Controller
'entry_at' => $item->entry_at,
'tanggal_terbit' => $item->tanggal_terbit,
'permission_file' => $item->permission_file,
'status_action' => $item->status_action,
'id_unit_kerja' => $item->id_unit_kerja,
'id_sub_unit_kerja' => $item->id_sub_unit_kerja,
'master_kategori_directory_id' => $item->master_kategori_directory_id
'status_action' => $item->status_action
];
});
return response()->json([
@ -2036,7 +1859,7 @@ class DashboardController extends Controller
$start = request('start_date');
$end = request('end_date');
$isHistory = filter_var(request('history'), FILTER_VALIDATE_BOOLEAN);
$query = FileDirectory::with('kategori')->where('statusenabled', true)
$query = FileDirectory::where('statusenabled', true)
->where('pegawai_id_entry', auth()->user()->objectpegawaifk)
->when($isHistory, function($q){
$q->where('status_action', 'approved');
@ -2059,6 +1882,7 @@ class DashboardController extends Controller
if($end){
$query->whereDate('entry_at','<=',$end);
}
$paginated = $query->paginate($perPage);
$data = $paginated->getCollection()->map(function($item){
$dataSlice = array_values(array_filter(explode('/', $item->file)));
@ -2067,7 +1891,7 @@ class DashboardController extends Controller
'pegawai_nama_entry' => $item->pegawai_nama_entry,
'part' => $dataSlice[0],
'folder' => $dataSlice[2],
'fileName' => isset($dataSlice[3]) ? $dataSlice[3] : null,
'fileName' =>$dataSlice[3],
'file' => $item->file,
'no_dokumen' => $item->no_dokumen,
'nama_dokumen' => $item->nama_dokumen,
@ -2079,12 +1903,7 @@ class DashboardController extends Controller
'revision' => $item->revision,
'id_unit_kerja' => $item->id_unit_kerja,
'id_sub_unit_kerja' => $item->id_sub_unit_kerja,
'master_kategori_directory_id' => $item->master_kategori_directory_id,
'is_akre' => $item->is_akre,
'kategori_hukum' => $item->kategori_hukum,
'name_kategori' => $item->kategori_hukum ?? $item->kategori?->nama_kategori_directory ?? null,
'name_unit' => $item->unit?->name ?? null,
'unit_kerja_name' => $item->unit?->name ?? null
'master_kategori_directory_id' => $item->master_kategori_directory_id
];
});
return response()->json([
@ -2284,8 +2103,6 @@ class DashboardController extends Controller
'id_unit_kerja' => $data ? $data->id_unit_kerja : null,
'id_sub_unit_kerja' => $data ? $data->id_sub_unit_kerja : null,
'action_type' => 'Revisi Dokumen',
'is_akre' => $data->is_akre ?? null,
'kategori_hukum' => $data->kategori_hukum ?? null
];
LogActivity::create($payloadLog);
@ -2423,292 +2240,4 @@ class DashboardController extends Controller
]
]);
}
public function dataAkreditasi(){
$katDok = MasterKategori::where('statusenabled', true)
->select('master_kategori_directory_id', 'nama_kategori_directory')
->get();
$data = [
'title' => 'Akreditasi',
'katDok' => $katDok
];
return view('dataAkreditasi.index', $data);
}
public function dataTableAkreditasi(Request $request)
{
$keyword = $request->query('keyword');
$perPage = (int) $request->query('per_page', 10);
$query = FileDirectory::where('statusenabled', true)
->where('status_action', 'approved')
->where('is_akre', true);
// Logic Pencarian
$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
});
});
$data = $query->orderBy('entry_at', 'desc')->paginate($perPage);
return response()->json([
'status' => true,
'message' => 'Berhasil mendapatkan data',
'data' => $data->items(),
'pagination' => [
'current_page' => $data->currentPage(),
'total' => $data->total(),
'per_page' => $data->perPage(),
'last_page' => $data->lastPage(),
'has_more' => $data->hasMorePages(),
]
]);
}
public function expDokumen(){
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
$authUnitKerja = $authMapping->objectunitkerjapegawaifk ?? null;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk ?? null;
$data = [
'title' => 'Dashboard',
'katDok' => $katDok,
'authUnitKerja' => $authUnitKerja,
'authSubUnitKerja' => $authSubUnitKerja,
];
return view('expDokumen.index', $data);
}
public function dataUnitExp(){
$perPage = (int) request('per_page', 10);
// $authUnitId = auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->objectunitkerjapegawaifk;
$userId = auth()->user()->dataUser->id ?? 937;
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $userId)
->get(['objectunitkerjapegawaifk']);
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')
->filter() // buang null
->unique()
->values()
->toArray();
$keyword = request('keyword');
$kategori = request('kategori');
$kategoriHeader = request('kategori_header');
$unitFilter = request('unit');
$kategoriValues = is_array($kategori)
? array_values(array_filter($kategori))
: array_values(array_filter(explode(',', (string) $kategori)));
$kategoriHeaderValues = is_array($kategoriHeader)
? array_values(array_filter($kategoriHeader))
: array_values(array_filter(explode(',', (string) $kategoriHeader)));
$allKategoriValues = array_values(array_filter(array_merge($kategoriValues, $kategoriHeaderValues)));
$kategoriTypes = [];
$kategoriIds = [];
$kategoriHukumValues = [];
foreach ($allKategoriValues as $val) {
$lower = strtolower(trim((string) $val));
if (str_starts_with($lower, 'hukum:')) {
$hukumVal = trim(substr((string) $val, strlen('hukum:')));
if ($hukumVal !== '') {
$kategoriHukumValues[] = $hukumVal;
}
continue;
}
if (in_array($lower, ['akreditasi', 'akre'], true)) {
$kategoriTypes[] = 'akreditasi';
continue;
}
if ($lower === 'hukum') {
$kategoriTypes[] = 'hukum';
continue;
}
if ($lower === 'lainnya') {
$kategoriTypes[] = 'lainnya';
continue;
}
$kategoriIds[] = $val;
}
$baseQuery = FileDirectory::with('kategori')
->when(!empty($unitFilter), function($q) use($unitFilter){
$q->whereIn('id_unit_kerja', $unitFilter);
})
->whereNotNull('tgl_expired')
->whereDate('tgl_expired', '<', now()->toDateString())
->where('statusenabled', true)
->where('status_action', 'approved');
if(in_array(22, $unitIds)){
}elseif(auth()->user()->username === "admin.turt"){
}else{
$baseQuery = $baseQuery->whereIn('id_unit_kerja', $unitIds);
}
$query = (clone $baseQuery)
->when(!empty($kategoriIds) || !empty($kategoriTypes) || !empty($kategoriHukumValues), function ($q) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$q->where(function ($sub) use ($kategoriIds, $kategoriTypes, $kategoriHukumValues) {
$hasClause = false;
if (!empty($kategoriIds)) {
$sub->whereIn('master_kategori_directory_id', $kategoriIds);
$hasClause = true;
}
if (!empty($kategoriHukumValues)) {
$hasClause ? $sub->orWhereIn('kategori_hukum', $kategoriHukumValues) : $sub->whereIn('kategori_hukum', $kategoriHukumValues);
$hasClause = true;
}
if (in_array('akreditasi', $kategoriTypes, true)) {
$sub->where('is_akre', true);
$hasClause = true;
}
if (in_array('hukum', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('kategori_hukum') : $sub->whereNotNull('kategori_hukum');
$hasClause = true;
}
if (in_array('lainnya', $kategoriTypes, true)) {
$hasClause ? $sub->orWhereNotNull('master_kategori_directory_id') : $sub->whereNotNull('master_kategori_directory_id');
}
});
})
->when($keyword, function ($q) use ($keyword) {
$q->where(function ($sub) use ($keyword) {
$sub->where('nama_dokumen', 'ILIKE', "%{$keyword}%")
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
});
});
$data = $query->orderBy('entry_at', 'desc')
->paginate($perPage);
$items = collect($data->items())->map(function($item){
$item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null;
return $item;
});
$kategoriList = (clone $baseQuery)->get()->map(function($item){
if (!empty($item->kategori_hukum)) {
return ['id' => 'hukum:' . $item->kategori_hukum, 'label' => $item->kategori_hukum];
}
$label = $item->kategori->nama_kategori_directory ?? $item->nama_kategori_directory ?? 'Kategori';
$id = $item->master_kategori_directory_id ?? $label;
return ['id' => (string) $id, 'label' => $label];
})->unique('id')->values();
$payload = [
'status' => true,
'message' => 'Berhasil mendapatkan data',
'data' => $items,
'kategori_list' => $kategoriList,
'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 recapDataExp(){
try {
$perPage = (int) request('per_page', 10);
$page = max(1, (int) request('page', 1));
$keyword = strtolower(request('keyword', ''));
// $authUnit = auth()->user()->masterPersetujuan->details->pluck('unit_pegawai_id')->unique()->toArray();
$user = auth()->user()->dataUser;
$unitIds = [];
if($user){
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', $user->id)
->get(['objectunitkerjapegawaifk', 'objectsubunitkerjapegawaifk']);
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')
->filter() // buang null
->unique()
->values()
->all();
}
$result = $this->buildRecapDataExp($unitIds, $keyword);
// paginate manually
$total = count($result);
$chunks = array_chunk($result, $perPage);
$currentData = $chunks[$page-1] ?? [];
return response()->json([
'status' => true,
'data' => $currentData,
'message' => 'Berhasil mendapatkan data',
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'last_page' => max(1, ceil($total / $perPage)),
'has_more' => $page < max(1, ceil($total / $perPage)),
]
]);
} catch (\Throwable $th) {
return response()->json([
'status' => false,
'message' => 'Gagal! mendapatkan data'
]);
}
}
private function buildRecapDataExp(array $unitIds, string $keyword = ''): array
{
$rows = FileDirectory::where('statusenabled', true)
->whereNotNull('tgl_expired')
->whereDate('tgl_expired', '<', now()->toDateString())
->whereNotNull('status_action')->where('status_action', 'approved');
if(in_array(22, $unitIds)){
$rows = $rows->pluck('file');
}elseif(auth()->user()->username === "admin.turt"){
$rows = $rows->pluck('file');
}else{
$rows = $rows->whereIn('id_unit_kerja', $unitIds)->pluck('file');
}
$grouped = [];
foreach ($rows as $path) {
$parts = array_values(array_filter(explode('/', $path)));
if (count($parts) < 4) {
continue;
}
$unit = $parts[0];
$folder = $parts[2];
if ($keyword) {
$hit = str_contains(strtolower($unit), $keyword) || str_contains(strtolower($folder), $keyword);
if (!$hit) {
continue;
}
}
if (!isset($grouped[$unit])) {
$grouped[$unit] = [];
}
if (!isset($grouped[$unit][$folder])) {
$grouped[$unit][$folder] = 0;
}
$grouped[$unit][$folder]++;
}
$result = [];
foreach ($grouped as $unitName => $folders) {
$data = [];
foreach ($folders as $folder => $count) {
$data[] = [
'folder' => $folder,
'count' => $count,
];
}
usort($data, fn($a, $b) => $b['count'] <=> $a['count']);
$result[] = [
'unit' => $unitName,
'data' => $data,
];
}
return $result;
}
}

View File

@ -7,7 +7,6 @@ use App\Models\MappingUnitKerjaPegawai;
use App\Models\FileDirectory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
class LogActivityController extends Controller
{
@ -23,13 +22,9 @@ class LogActivityController extends Controller
$keyword = request('keyword');
$start = request('start_date');
$end = request('end_date');
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true);
if(!Auth::guard('admin')->check()){
$mapping->where('objectpegawaifk', auth()->user()->dataUser->id);
}else{
$mapping->where('objectpegawaifk', 937);
}
$mapping->get(['objectunitkerjapegawaifk', 'objectsubunitkerjapegawaifk']);
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->dataUser->id)
->get(['objectunitkerjapegawaifk', 'objectsubunitkerjapegawaifk']);
$unitIds = $mapping->pluck('objectunitkerjapegawaifk')
->filter() // buang null
->unique()
@ -41,15 +36,9 @@ class LogActivityController extends Controller
$q->select(DB::raw('COUNT(DISTINCT pegawai_id_entry)'));
}])
->where('statusenabled', true)
->where('status_action', 'approved');
if (
in_array(22, $unitIds) ||
(Auth::guard('admin')->check() && Auth::guard('admin')->user()->id == 300)
) {
}else{
$query = $query->whereIn('id_unit_kerja', $unitIds);
}
$query = $query->orderBy('entry_at','desc');
->where('status_action', 'approved')
->whereIn('id_unit_kerja', $unitIds)
->orderBy('entry_at','desc');
if($keyword){
$query->where(function($q) use ($keyword){
@ -104,14 +93,12 @@ class LogActivityController extends Controller
$query = LogActivity::select(
'pegawai_id_entry',
'pegawai_nama_entry',
DB::raw("SUM(CASE WHEN action_type = 'Membuka Dokumen' THEN 1 ELSE 0 END) as total_open"),
// Menghitung hanya yang Download Dokumen
DB::raw("SUM(CASE WHEN action_type = 'Download Dokumen' THEN 1 ELSE 0 END) as total_download"),
DB::raw('COUNT(*) as total_open'),
DB::raw('MAX(entry_at) as last_open')
)
->where('file_directory_id', $fileDirectoryId)
->where('statusenabled', true)
->whereIn('action_type', ['Membuka Dokumen', 'Download Dokumen'])
->where('action_type', 'Membuka Dokumen')
->groupBy('pegawai_id_entry', 'pegawai_nama_entry')
->orderByDesc('total_open');

View File

@ -4,8 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\LogActivity;
use App\Models\UnitKerja;
use App\Models\MasterKategori;
class FileDirectory extends Model
{
@ -14,7 +12,6 @@ class FileDirectory extends Model
public $timestamps = false;
protected $primaryKey = 'file_directory_id';
protected $guarded = ['file_directory_id'];
protected $with = ['unit'];
public function viewLogs()
{
@ -29,16 +26,4 @@ class FileDirectory extends Model
->where('action_type', 'Download Dokumen');
}
public function kategori(){
return $this->belongsTo(MasterKategori::class, 'master_kategori_directory_id', 'master_kategori_directory_id');
}
public function unit(){
// Each file belongs to exactly one unit; skip the subUnitKerja eager load from UnitKerja.
return $this->belongsTo(UnitKerja::class, 'id_unit_kerja', 'id')->select('id', 'name')
->without('subUnitKerja');
}
}

View File

@ -1,15 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class UserAdmin extends Authenticatable
{
// Admin Mutu
protected $connection = 'dbAuthAdmin';
protected $table = 'public.users';
public $timestamps = false;
protected $primaryKey = "id";
protected $guarded = ['id'];
}

View File

@ -40,10 +40,6 @@ return [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
/*
@ -68,10 +64,6 @@ return [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
'admins' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\UserAdmin::class),
],
// 'users' => [
// 'driver' => 'database',

View File

@ -129,26 +129,6 @@ return [
'timezone' => env('APP_TIMEZONE', 'utc' ),
],
'dbAuthAdmin' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST_AUTH_ADMIN', '127.0.0.1'),
'port' => env('DB_PORT_AUTH_ADMIN', '3306'),
'database' => env('DB_DATABASE_AUTH_ADMIN', 'laravel'),
'username' => env('DB_USERNAME_AUTH_ADMIN', 'root'),
'password' => env('DB_PASSWORD_AUTH_ADMIN', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
'timezone' => env('APP_TIMEZONE', 'utc' ),
],
],
/*

View File

@ -177,7 +177,7 @@ function addForm(){
</button>
</div>
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
<select class="form-control"
name="data[${colCount}][id_unit_kerja]"
@ -186,7 +186,7 @@ function addForm(){
<option value="" disable>Select Choose</option>
</select>
</div>
<div class="col-md-6">
<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[${colCount}][id_sub_unit_kerja]"
@ -196,7 +196,7 @@ function addForm(){
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Dokumen</label>
<label class="form-label fw-semibold">Kategori Dokumen <span class="text-danger">*</span></label>
<select class="form-control"
name="data[${colCount}][master_kategori_directory_id]"
id="select_kategori_${colCount}"
@ -238,7 +238,7 @@ function addForm(){
id="perm_yes_${colCount}"
value="1"
required>
<label class="form-check-label" for="perm_yes_${colCount}">Ya</label>
<label class="form-check-label" for="perm_yes_${colCount}">Iya</label>
</div>
<div class="form-check mt-1">

View File

@ -631,5 +631,5 @@ document.addEventListener('click', function(e){
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'ya' || val === 'yes';
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}

View File

@ -116,13 +116,8 @@ document.addEventListener('DOMContentLoaded', () => {
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">${item.nama_dokumen}</a></td>
${item.is_akre ? `<td colspan="2" class="text-center">Dokumen akreditasi</td>` :
`
<td>${item.name_kategori || '-'}</td>
<td>${item.name_unit || '-'}</td>
`
}
<td>${item.folder || '-'}</td>
<td>${item.part || '-'}</td>
<td class="text-nowrap">${tanggalTerbit}</td>
<td class="text-nowrap">${tanggalExp}</td>
<td class="text-nowrap">${tanggal}</td>
@ -368,51 +363,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
function byId(id){
return document.getElementById(id);
}
function setInputValue(el, value){
if (!el) return;
el.value = value ?? '';
}
function setTextValue(el, value){
if (!el) return;
el.textContent = value || '';
}
function setChecked(el, value){
if (!el) return;
el.checked = !!value;
}
function setSelectValue(selectEl, value, label){
if (!selectEl) return;
if (value === undefined || value === null || value === '') {
selectEl.value = '';
return;
}
const exists = Array.from(selectEl.options).some(opt => opt.value === value);
if (!exists) {
selectEl.append(new Option(label || value, value, true, true));
}
selectEl.value = value;
}
function triggerSelect2(selectEl){
if (window.$ && $.fn.select2) $(selectEl).trigger('change');
}
function arrayFilterEmpty(arr){
return (arr || []).filter((val) => val !== null && val !== undefined && String(val).trim() !== '');
}
function initSelect2($el, options){
if (!$el || !$el.length || !window.$ || !$.fn.select2) return;
$el.select2(options);
}
function initEditSelects(){
if (editUnitSelect.length) {
editUnitSelect.select2({
@ -430,7 +380,7 @@ document.addEventListener('DOMContentLoaded', () => {
processResults: function (data) {
return {
results: (data?.data || []).map(item => ({
id: String(item.id),
id: `${item.id}/${item.name}`,
text: item.name
}))
};
@ -453,38 +403,8 @@ document.addEventListener('DOMContentLoaded', () => {
editUnitSelect.on('change', function(){
const val = $(this).val();
if (!val) return;
loadEditSubUnit(String(val), null, null);
});
}
let akreData = [];
let akreLoaded = false;
let akreFlat = [];
function initEditExtraSelects(){
const akreSelect = byId('edit_akre_select');
if (akreSelect) {
loadAkreData().then(() => {
fillAkreSelect(akreSelect);
initSelect2($(akreSelect), {
dropdownParent: $('#modalEditPengajuanFile'),
placeholder: 'Pilih Instrumen',
allowClear: true
});
});
}
const editKat = $('#edit_kategori');
const editHukum = $('#edit_kategori_hukum');
initSelect2(editKat, {
dropdownParent: $('#modalEditPengajuanFile'),
placeholder: 'Pilih Kategori',
allowClear: true
});
initSelect2(editHukum, {
dropdownParent: $('#modalEditPengajuanFile'),
placeholder: 'Pilih Kategori Hukum',
allowClear: true
const unitId = String(val).split('/')[0];
loadEditSubUnit(unitId, null, null);
});
}
@ -497,13 +417,13 @@ document.addEventListener('DOMContentLoaded', () => {
success: function(response) {
if (response?.data) {
response.data.forEach(unit => {
const optVal = String(unit.id);
const optVal = `${unit.id}/${unit.name}`;
const isSelected = selectedSubId && String(unit.id) === String(selectedSubId);
const option = new Option(unit.name, optVal, false, isSelected);
editSubUnitSelect.append(option);
});
if (selectedSubId && selectedSubName && editSubUnitSelect.find(`option[value="${selectedSubId}"]`).length === 0) {
editSubUnitSelect.append(new Option(selectedSubName, String(selectedSubId), true, true));
if (selectedSubId && selectedSubName && editSubUnitSelect.find(`option[value="${selectedSubId}/${selectedSubName}"]`).length === 0) {
editSubUnitSelect.append(new Option(selectedSubName, `${selectedSubId}/${selectedSubName}`, true, true));
}
editSubUnitSelect.trigger('change');
}
@ -511,86 +431,70 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
function setEditAkreValue(value){
const akreEl = byId('edit_akre_select');
if (!akreEl) return;
loadAkreData().then(() => {
fillAkreSelect(akreEl);
if (value && akreValueExists(value)) {
setSelectValue(akreEl, value, value);
} else {
akreEl.value = '';
}
triggerSelect2(akreEl);
});
}
function setEditKategoriHukum(value){
const hukumEl = byId('edit_kategori_hukum');
if (!hukumEl) return;
setSelectValue(hukumEl, value, value);
triggerSelect2(hukumEl);
}
function setEditKategoriDir(item, kategoriName){
const katEl = byId('edit_kategori');
const katId = item?.master_kategori_directory_id;
if (!katEl || !katId) return;
const match = Array.from(katEl.options).find((opt) => String(opt.value).startsWith(`${katId}/`));
if (match) {
katEl.value = match.value;
} else {
const label = kategoriName || 'Kategori';
const katVal = `${katId}/${label}`;
setSelectValue(katEl, katVal, label);
}
triggerSelect2(katEl);
}
window.editFileReject = function(id){
const item = getItemById(id);
if (!item) {
Swal.fire({ icon: 'error', title: 'Gagal', text: 'Data tidak ditemukan.' });
return;
}
setInputValue(byId('edit_file_directory_id'), item.file_directory_id);
setInputValue(byId('edit_no_dokumen'), item.no_dokumen);
setInputValue(byId('edit_nama_dokumen'), item.nama_dokumen);
setInputValue(byId('edit_tanggal_terbit'), item.tanggal_terbit);
setInputValue(byId('edit_tgl_expired'), item.tgl_expired);
const idEl = document.getElementById('edit_file_directory_id');
const noEl = document.getElementById('edit_no_dokumen');
const namaEl = document.getElementById('edit_nama_dokumen');
const tglEl = document.getElementById('edit_tanggal_terbit');
const tglExpiredEl = document.getElementById('edit_tgl_expired');
const hasExpiredEl = document.getElementById('edit_has_expired');
const currentFileEl = document.getElementById('edit_current_file');
const permYes = document.getElementById('edit_perm_yes');
const permNo = document.getElementById('edit_perm_no');
const katEl = document.getElementById('edit_kategori');
if (idEl) idEl.value = item.file_directory_id || '';
if (noEl) noEl.value = item.no_dokumen || '';
if (namaEl) namaEl.value = item.nama_dokumen || '';
if (tglEl) tglEl.value = item.tanggal_terbit || '';
if (tglExpiredEl) tglExpiredEl.value = item.tgl_expired || '';
if (permYes && permNo) {
const isPublic = item.permission_file === true || item.permission_file === 1 || item.permission_file === '1';
setChecked(byId('edit_perm_yes'), isPublic);
setChecked(byId('edit_perm_no'), !isPublic);
permYes.checked = isPublic;
permNo.checked = !isPublic;
}
if (hasExpiredEl && tglExpiredEl) {
const hasExpired = !!item.tgl_expired;
setChecked(byId('edit_has_expired'), hasExpired);
hasExpiredEl.checked = hasExpired;
syncEditExpiredField();
}
if (currentFileEl) {
const displayName = item.fileName || (item.file ? String(item.file).split('/').pop() : '');
setTextValue(byId('edit_current_file'), displayName ? `File saat ini: ${displayName}` : '');
const parts = arrayFilterEmpty((item.file || '').split('/'));
const unitNameFromPath = parts[0] || '';
const subNameFromPath = parts[1] || '';
const kategoriName = parts[2] || '';
const unitName = item.unit_kerja_name || item.name_unit || item.nama_unit_kerja || item.unit_name || item.unit_kerja || '';
if (editUnitSelect.length && item.id_unit_kerja) {
const unitLabel = unitName || String(item.id_unit_kerja);
const unitVal = String(item.id_unit_kerja);
setSelectValue(editUnitSelect[0], unitVal, unitLabel);
triggerSelect2(editUnitSelect[0]);
const subLabel = item.sub_unit_kerja_name || item.nama_sub_unit_kerja || item.sub_unit_name || item.sub_unit_kerja || String(item.id_sub_unit_kerja || '');
loadEditSubUnit(String(item.id_unit_kerja), item.id_sub_unit_kerja, subLabel || null);
currentFileEl.textContent = displayName ? `File saat ini: ${displayName}` : '';
}
setEditKategoriDir(item, kategoriName);
if (item.kategori_hukum) setEditKategoriHukum(item.kategori_hukum);
const akreFromFile = parts.length > 1 ? parts.slice(0, -1).join('/') : '';
setEditAkreValue(item.akre || akreFromFile || '');
const parts = (item.file || '').split('/');
const unitName = parts[0] || '';
const subName = parts[1] || '';
const kategoriName = parts[2] || '';
if (editUnitSelect.length && item.id_unit_kerja) {
const unitVal = `${item.id_unit_kerja}/${unitName}`;
editUnitSelect.append(new Option(unitName || 'Unit', unitVal, true, true)).trigger('change');
const unitId = String(item.id_unit_kerja);
loadEditSubUnit(unitId, item.id_sub_unit_kerja, subName);
}
if (katEl && item.master_kategori_directory_id) {
const katVal = `${item.master_kategori_directory_id}/${kategoriName}`;
if (katEl.querySelector(`option[value="${katVal}"]`)) {
katEl.value = katVal;
} else {
katEl.append(new Option(kategoriName || 'Kategori', katVal, true, true));
katEl.value = katVal;
}
}
const modalEl = document.getElementById('modalEditPengajuanFile');
if (modalEl) {
$("#modalEditPengajuanFile").modal('show');
}
}
function syncEditExpiredField() {
const editHasExpired = document.getElementById('edit_has_expired');
@ -648,7 +552,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
initEditSelects();
initEditExtraSelects();
updateTabUI();
fetchData();
@ -672,124 +575,6 @@ document.addEventListener('DOMContentLoaded', () => {
$(formCreate).find('input[type="file"]').val('');
$(formCreate).find('.file-name').addClass('d-none').text('');
}
resetAkreFields(0);
enableAkreFields(0);
}
function loadAkreData(){
if(akreLoaded) return Promise.resolve(akreData);
return fetch('/json/akreditasi.jff')
.then(res => res.json())
.then(data => {
akreData = Array.isArray(data) ? data : [];
akreFlat = [];
akreLoaded = true;
return akreData;
})
.catch(() => {
akreData = [];
akreFlat = [];
akreLoaded = true;
return akreData;
});
}
function getAkreFlat(){
if(akreFlat.length) return akreFlat;
akreFlat = (akreData || []).flatMap(type => {
const segments = Array.isArray(type.segment) ? type.segment : [];
return segments.flatMap(seg => {
const children = Array.isArray(seg.turunan) ? seg.turunan : [];
return children.map(child => ({
value: `${type.name}/${seg.name}/${child.name}`,
label: `${type.name} / ${child.name}`,
type: type.name,
segment: seg.name,
item: child.name
}));
});
});
return akreFlat;
}
function akreValueExists(value){
if (!value) return false;
return getAkreFlat().some(opt => String(opt.value) === String(value));
}
function fillAkreSelect(selectEl){
if(!selectEl) return;
selectEl.innerHTML = '<option value="">Pilih Instrumen</option>';
getAkreFlat().forEach(optData => {
const opt = document.createElement('option');
opt.value = optData.value;
opt.textContent = optData.label;
selectEl.appendChild(opt);
});
}
function setKategoriRequired(index, isRequired){
const katSelect = document.getElementById(`select_kategori_${index}`);
if (!katSelect) return;
if (isRequired) {
katSelect.setAttribute('required', 'required');
} else {
katSelect.removeAttribute('required');
}
}
function resetAkreFields(index){
const selectEl = document.getElementById(`akre_select_${index}`);
const typeInput = document.getElementById(`akre_type_${index}`);
const segmentInput = document.getElementById(`akre_segment_${index}`);
const itemInput = document.getElementById(`akre_item_${index}`);
if(selectEl){
selectEl.value = '';
if(window.$ && $.fn.select2) $(selectEl).val(null).trigger('change');
}
if(typeInput) typeInput.value = '';
if(segmentInput) segmentInput.value = '';
if(itemInput) itemInput.value = '';
setKategoriRequired(index, false);
}
function enableAkreFields(index){
const selectEl = document.getElementById(`akre_select_${index}`);
// if(selectEl){
// selectEl.disabled = false;
// selectEl.required = true;
// }
setKategoriRequired(index, false);
loadAkreData().then(() => {
fillAkreSelect(selectEl);
if(window.$ && $.fn.select2){
$(selectEl).select2({
dropdownParent: $('#modalCreateFile'),
placeholder: 'Pilih Instrumen',
allowClear:true
});
}
});
}
function initKategoriSelect2(index){
if(!window.$ || !$.fn.select2) return;
const katSelect = $(`#select_kategori_${index}`);
const hukumSelect = $(`#select_kategori_hukum_${index}`);
if(katSelect.length){
katSelect.select2({
dropdownParent: $('#modalCreateFile'),
placeholder:'Pilih Kategori',
allowClear:true
});
}
if(hukumSelect.length){
hukumSelect.select2({
dropdownParent: $('#modalCreateFile'),
placeholder:'Pilih Kategori Hukum',
allowClear:true
});
}
}
function selectOptionUnitKerjaV1(localCol){
@ -857,7 +642,7 @@ document.addEventListener('DOMContentLoaded', () => {
</button>
</div>
<div class="col-md-6">
<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]"
@ -867,7 +652,7 @@ document.addEventListener('DOMContentLoaded', () => {
</select>
</div>
<div class="col-md-6">
<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]"
@ -877,7 +662,16 @@ document.addEventListener('DOMContentLoaded', () => {
</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>
${katOptions}
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Nomor Dokumen</label>
@ -903,7 +697,7 @@ document.addEventListener('DOMContentLoaded', () => {
type="date"
name="data[${colCount}][date_active]">
</div>
<div class="col-md-3">
<div class="col-md-2">
<div class="form-check">
<input class="form-check-input toggle-expired"
type="checkbox"
@ -920,7 +714,7 @@ document.addEventListener('DOMContentLoaded', () => {
name="data[${colCount}][tgl_expired]" disabled>
</div>
<div class="col-md-4">
<div class="col-md-5">
<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">
@ -930,7 +724,7 @@ document.addEventListener('DOMContentLoaded', () => {
id="perm_yes_${colCount}"
value="1"
required>
<label class="form-check-label" for="perm_yes_${colCount}">Ya</label>
<label class="form-check-label" for="perm_yes_${colCount}">Iya</label>
</div>
<div class="form-check mt-1">
@ -945,40 +739,6 @@ document.addEventListener('DOMContentLoaded', () => {
</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Instrumen Akreditasi </label>
<select class="form-select akre-select" id="akre_select_${colCount}" name="data[${colCount}][akre]" style="width: 350px;">
<option value="">Pilih Instrumen</option>
</select>
<div class="form-text text-muted">Isi form ini bila dokumen yang diunggah merupakan dokumen <strong>akreditasi</strong>.</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Hukum</label>
<select class="form-select select-kat-hukum" name="data[${colCount}][kategori_hukum]" id="select_kategori_hukum_${colCount}" style="width: 350px;">
<option value="Kebijakan - Peraturan Direktur">Kebijakan - Peraturan Direktur</option>
<option value="Kebijakan - Keputusan Direktur Utama">Kebijakan - Keputusan Direktur Utama</option>
<option value="Kebijakan - Surat Edaran">Kebijakan - Surat Edaran</option>
<option value="Kebijakan - Pengumuman">Kebijakan - Pengumuman</option>
<option value="Kerjasama - Pelayanan Kesehatan">Kerjasama - Pelayanan Kesehatan</option>
<option value="Kerjasama - Management">Kerjasama - Management</option>
<option value="Kerjasama - Pemeliharan">Kerjasama - Pemeliharan</option>
<option value="Kerjasama - Diklat">Kerjasama - Diklat</option>
<option value="Kerjasama - Luar Negeri">Kerjasama - Luar Negeri</option>
<option value="Kerjasama - Area Bisnis">Kerjasama - Area Bisnis</option>
<option value="Kerjasama - Pendidikan">Kerjasama - Pendidikan</option>
<option value="Kerjasama - Pengampuan KIA">Kerjasama- Pengampuan KIA</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Lainnya</label>
<select class="form-select"
name="data[${colCount}][master_kategori_directory_id]"
id="select_kategori_${colCount}" style="width: 350px;">
<option value="">Pilih Kategori</option>
${katOptions}
</select>
</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">
@ -994,9 +754,6 @@ document.addEventListener('DOMContentLoaded', () => {
</div>`;
col.insertAdjacentHTML('beforeend', html);
selectOptionUnitKerjaV1(colCount);
initKategoriSelect2(colCount);
enableAkreFields(colCount);
setKategoriRequired(colCount, false);
colCount++;
}
@ -1008,8 +765,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (formCreate) {
const select0 = $('#select_id_unit_kerja_0');
if (select0.length) selectOptionUnitKerjaV1(0);
initKategoriSelect2(0);
enableAkreFields(0);
formCreate.addEventListener('submit', (e) => {
e.preventDefault();
@ -1061,21 +816,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
}
document.addEventListener('change', function(e){
if(e.target.classList.contains('akre-select')){
const id = e.target.id || '';
const idx = id.split('_').pop();
const [typeVal = '', segmentVal = '', itemVal = ''] = (e.target.value || '').split('/');
const typeInput = document.getElementById(`akre_type_${idx}`);
const segmentInput = document.getElementById(`akre_segment_${idx}`);
const itemInput = document.getElementById(`akre_item_${idx}`);
if(typeInput) typeInput.value = typeVal;
if(segmentInput) segmentInput.value = segmentVal;
if(itemInput) itemInput.value = itemVal;
return;
}
});
});
document.addEventListener('click', function(e){
if(e.target.matches('.file-link')){
@ -1123,5 +863,5 @@ document.addEventListener('click', function(e){
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'ya' || val === 'yes';
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}

File diff suppressed because it is too large Load Diff

View File

@ -154,6 +154,7 @@
</div>
</div>
</div>
@include('dashboard.modal.create')
@include('dashboard.modal.view')
<script>
const klasifikasiDok = @json($klasifikasiDok);
@ -165,7 +166,7 @@
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'ya' || val === 'yes';
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}
let currentFile = null;

View 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 file-input" 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">
Format yang didukung: <b>PDF</b> Maksimal <b>10mb</b>.
</div>
</div>
<div id="col_add_file" class="col-12"></div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-3" onclick="addForm()">
+ 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>

View File

@ -1,916 +0,0 @@
@extends('layout.main')
<style>
/* Baris Folder */
.tree-folder {
background-color: #fcfcfc;
cursor: pointer;
transition: background 0.2s;
}
.tree-folder:hover {
background-color: #f4f7fb;
}
/* Container Indentasi */
.indent-container {
display: inline-flex;
align-items: center;
}
/* Garis Pandu Vertikal */
.tree-line {
display: inline-block;
width: 24px;
height: 40px; /* Sesuaikan dengan tinggi row */
border-left: 1px solid #e0e0e0;
margin-left: 12px;
position: relative;
}
/* Menghilangkan garis terakhir agar lebih rapi */
.tree-file:last-child .tree-line {
height: 20px;
}
/* Ikon dan Teks */
.folder-icon { color: #ffc107; font-size: 1.2rem; }
.file-icon { color: #6c757d; font-size: 1.1rem; }
.badge-pdf { background-color: #fff0f0; color: #e44d26; border: 1px solid #ffcccc; }
</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 justify-content-between align-items-start mb-3">
<h4 class="mb-0">Dokumen Akreditasi</h4>
<div class="d-flex align-items-start gap-3">
<div class="d-flex flex-column align-items-start">
<button
type="button"
class="btn btn-primary btn-sm"
id="btnDownloadMultiple"
disabled
>
<i class="ti ti-download me-1"></i>
Download Terpilih
</button>
<span
id="selectedCount"
class="small text-muted mt-1"
>
0 dipilih
</span>
</div>
<!-- Tambah Dokumen -->
@if(!Auth::guard('admin')->check())
<button
type="button"
class="btn btn-success btn-sm"
data-bs-toggle="modal"
data-bs-target="#modalCreateFile"
>
<i class="ti ti-plus me-1"></i>
Tambah Dokumen
</button>
@endif
</div>
</div>
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 flex-grow-1">
<input type="search" id="tableSearch" class="form-control"
placeholder="Cari Dokumen / Folder" autocomplete="off">
</div>
<div class="d-flex align-items-center gap-2">
</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 class="text-center" style="width: 45px;">
<input type="checkbox" id="checkAllRows" class="form-check-input">
</th>
<th style="width: 80px;">Aksi</th>
<th style="width: 40%;">Nama Dokumen / Folder</th>
<th>Tipe</th>
<th>Unit</th>
<th>Tgl Unggah</th>
</tr>
</thead>
<tbody id="tableDataAkreditasi">
<!-- data dari fetch masuk sini -->
</tbody>
</table>
</div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-3">
<div class="d-flex align-items-center gap-1">
<span class="small text-muted">Tampilkan</span>
<select id="tablePageSize" class="form-select form-select-sm" style="width: 80px;">
<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>
<span class="small text-muted">data</span>
</div>
<div id="paginationControls" class="ms-auto"></div>
<div class="small text-muted text-nowrap" id="tableSummary">
Memuat data...
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@include('dataUnit.modal.create')
<script>
// === STATE MANAGEMENT ===
let currentData = [];
let selectedIds = [];
let expandedFolders = new Set();
let searchTimer;
const btn = document.getElementById('btnDownloadMultiple');
// === INITIALIZATION ===
document.addEventListener('DOMContentLoaded', () => {
fetchData();
initEventListeners();
});
// === EVENT LISTENERS ===
function initEventListeners() {
// Search dengan Debounce
document.getElementById('tableSearch').addEventListener('input', (e) => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
fetchData(e.target.value, 1);
}, 500);
});
// Per Page Change
document.getElementById('tablePageSize').addEventListener('change', (e) => {
const keyword = document.getElementById('tableSearch').value;
fetchData(keyword,1);
});
// Check All Rows
document.getElementById('checkAllRows').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.row-checkbox');
selectedIds = [];
checkboxes.forEach(cb => {
cb.checked = this.checked;
if (this.checked) selectedIds.push(cb.value);
});
updateDownloadButton();
});
}
// === CORE FUNCTIONS ===
/**
* Mengambil data dari server
*/
function fetchData(keyword = '', page = 1) {
const tbody = document.getElementById('tableDataAkreditasi');
const perPage = document.getElementById('tablePageSize').value;
tbody.innerHTML = '<tr><td colspan="6" class="text-center">Memuat data...</td></tr>';
fetch(`/datatable-akreditasi?keyword=${encodeURIComponent(keyword)}&per_page=${perPage}&page=${page}`)
.then(res => res.json())
.then(response => {
if (response.status) {
currentData = response.data || [];
renderTable(keyword);
updateSummary(response.pagination.total);
} else {
tbody.innerHTML = `<tr><td colspan="6" class="text-center text-danger">${response.message}</td></tr>`;
}
})
.catch(err => {
console.error('Error fetching data:', err);
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Gagal memuat data dari server.</td></tr>';
});
}
/**
* Merender tabel berdasarkan mode (Folder vs Search)
*/
function renderTable(keyword = '') {
const tbody = document.getElementById('tableDataAkreditasi');
tbody.innerHTML = '';
if (currentData.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center">Tidak ada data ditemukan</td></tr>';
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);
}
/**
* 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 += `
<tr>
<td class="text-center">
<input type="checkbox" class="form-check-input row-checkbox"
value="${item.file_directory_id}" ${isChecked} onchange="handleRowCheck(this)">
</td>
<td>
<div class="btn-group">
<a href="/file-download/${item.file_directory_id}" target="_blank"
class="btn btn-sm btn-outline-primary" download title="Download">
<i class="ti ti-download"></i>
</a>
</div>
</td>
<td>
<div class="d-flex flex-column">
<strong>${item.nama_dokumen}</strong>
<small class="text-muted" style="font-size: 10px;">Path: ${item.file}</small>
</div>
</td>
<td><span class="badge bg-light text-primary border">${typeDok}</span></td>
<td></td>
<td>${item.entry_at || '-'}</td>
</tr>`;
}
// === 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];
let lines = '';
for (let i = 0; i < level; i++) {
lines += '<span class="tree-line"></span>';
}
tbody.innerHTML += `
<tr class="tree-folder" onclick="toggleFolder('${folderPath}')">
<td></td> <td></td> <td>
<div class="indent-container">
${lines}
<span class="folder-toggle me-1">
<i class="ti ${isExpanded ? 'ti-chevron-down' : 'ti-chevron-right'} text-muted" style="font-size: 12px;"></i>
</span>
<i class="ti ti-folder-filled folder-icon me-2"></i>
<strong class="text-dark">${folder}</strong>
</div>
</td>
<td><span class="badge bg-light text-muted border-0">FOLDER</span></td>
<td class="text-muted small"></td> <td class="text-muted small"></td> </tr>
`;
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 += '<span class="tree-line"></span>';
}
targetTbody.innerHTML += `
<tr class="tree-file">
<td class="text-center">
<input type="checkbox" class="form-check-input row-checkbox"
value="${item.file_directory_id}" ${isChecked} onchange="handleRowCheck(this)">
</td>
<td>
<a href="/file-download/${item.file_directory_id}" target="_blank"
class="btn btn-sm btn-primary text-white border">
<i class="ti ti-download"></i>
</a>
</td>
<td>
<div class="indent-container">
${lines}
<span style="width: 24px;"></span> <i class="ti ti-file-text file-icon me-2"></i>
<div class="d-flex flex-column">
<span class="text-dark fw-medium">${item.nama_dokumen}</span>
<small class="text-muted" style="font-size: 10px;">${fileName}</small>
</div>
</div>
</td>
<td><span class="badge bg-danger">${typeDok}</span></td>
<td>${item.unit?.name || '-'}</td>
<td class="text-muted small">${formatDateTime(item.entry_at)}</td>
</tr>`;
}
// Tambahkan fungsi ini di dalam <script>
function toggleFolder(folderPath) {
if (expandedFolders.has(folderPath)) {
expandedFolders.delete(folderPath);
} else {
expandedFolders.add(folderPath);
}
renderTable(document.getElementById('tableSearch').value || '');
}
// === UTILITY FUNCTIONS ===
function handleRowCheck(checkbox) {
const val = String(checkbox.value);
if (checkbox.checked) {
if (!selectedIds.includes(val)) selectedIds.push(val);
} else {
selectedIds = selectedIds.filter(id => id !== val);
document.getElementById('checkAllRows').checked = false;
}
updateDownloadButton();
}
function updateDownloadButton() {
const countLabel = document.getElementById('selectedCount');
btn.disabled = selectedIds.length === 0;
countLabel.innerText = `${selectedIds.length} dipilih`;
}
function updateSummary(total) {
const summary = document.getElementById('tableSummary');
if (summary) summary.innerText = `Total: ${total} data`;
}
const downloadBtn = document.getElementById('btnDownloadMultiple');
if (downloadBtn) {
downloadBtn.addEventListener('click', function(){
if(selectedIds.length === 0){
return;
}
const payload = { ids: Array.from(selectedIds) };
downloadBtn.disabled = true;
downloadBtn.textContent = 'Menyiapkan...';
fetch('/download-multiple', {
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(async (res) => {
const contentType = res.headers.get('content-type') || '';
if(!res.ok || contentType.includes('application/json')){
const err = await res.json().catch(() => ({}));
throw new Error(err?.message || 'Gagal download file');
}
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
const disposition = res.headers.get('content-disposition') || '';
const match = disposition.match(/filename="?([^"]+)"?/);
a.href = url;
a.download = match?.[1] || 'files.zip';
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
})
.catch(err => {
Swal.fire({ icon: 'error', title: 'Gagal', text: err.message || 'Gagal download file' });
})
.finally(() => {
downloadBtn.disabled = selectedIds.length === 0;
downloadBtn.textContent = 'Download Terpilih';
});
});
}
document.addEventListener('click', function(e){
const toggle = e.target.closest('.folder-toggle');
if (!toggle) return;
const folderPath = toggle.getAttribute('data-folder');
if (!folderPath) return;
if (expandedFolders.has(folderPath)) {
expandedFolders.delete(folderPath);
} else {
expandedFolders.add(folderPath);
}
renderTable(document.getElementById('tableSearch').value || '');
});
function formatDateTime(value) {
if (!value) return '—';
const d = new Date(value);
if (isNaN(d)) return value;
const pad = n => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} `
+ `${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
const katDok = @json($katDok);
const formCreate = document.getElementById('formFile');
const modalCreate = document.getElementById('modalCreateFile');
let colCount = 1;
document.addEventListener('change', function(e){
if(!e.target.classList.contains('toggle-expired')) return;
const targetId = e.target.getAttribute('data-target');
if(!targetId) return;
const fieldWrap = document.getElementById(targetId);
const input = fieldWrap?.querySelector('input');
if (input) input.disabled = !e.target.checked;
});
let akreData = [];
let akreLoaded = false;
let akreFlat = [];
function loadAkreData(){
if(akreLoaded) return Promise.resolve(akreData);
return fetch('/json/akreditasi.jff')
.then(res => res.json())
.then(data => {
akreData = Array.isArray(data) ? data : [];
akreFlat = [];
akreLoaded = true;
return akreData;
})
.catch(() => {
akreData = [];
akreFlat = [];
akreLoaded = true;
return akreData;
});
}
function getAkreFlat(){
if(akreFlat.length) return akreFlat;
akreFlat = (akreData || []).flatMap(type => {
const segments = Array.isArray(type.segment) ? type.segment : [];
return segments.flatMap(seg => {
const children = Array.isArray(seg.turunan) ? seg.turunan : [];
return children.map(child => ({
value: `${type.name}/${seg.name}/${child.name}`,
label: `${type.name} / ${child.name}`,
type: type.name,
segment: seg.name,
item: child.name
}));
});
});
return akreFlat;
}
function fillAkreSelect(selectEl){
if(!selectEl) return;
selectEl.innerHTML = '<option value="">Pilih Instrumen</option>';
getAkreFlat().forEach(optData => {
const opt = document.createElement('option');
opt.value = optData.value;
opt.textContent = optData.label;
selectEl.appendChild(opt);
});
}
function setKategoriRequired(index, isRequired){
const katSelect = document.getElementById(`select_kategori_${index}`);
if (!katSelect) return;
if (isRequired) {
katSelect.setAttribute('required', 'required');
} else {
katSelect.removeAttribute('required');
}
}
function resetAkreFields(index){
const selectEl = document.getElementById(`akre_select_${index}`);
const typeInput = document.getElementById(`akre_type_${index}`);
const segmentInput = document.getElementById(`akre_segment_${index}`);
const itemInput = document.getElementById(`akre_item_${index}`);
if(selectEl){
selectEl.value = '';
if(window.$ && $.fn.select2) $(selectEl).val(null).trigger('change');
}
if(typeInput) typeInput.value = '';
if(segmentInput) segmentInput.value = '';
if(itemInput) itemInput.value = '';
setKategoriRequired(index, false);
}
function enableAkreFields(index){
const selectEl = document.getElementById(`akre_select_${index}`);
setKategoriRequired(index, false);
loadAkreData().then(() => {
fillAkreSelect(selectEl);
if(window.$ && $.fn.select2){
$(selectEl).select2({
dropdownParent: $('#modalCreateFile'),
placeholder: 'Pilih Instrumen',
allowClear: true
});
}
});
}
function initKategoriSelect2(index){
if(!window.$ || !$.fn.select2) return;
const katSelect = $(`#select_kategori_${index}`);
const hukumSelect = $(`#select_kategori_hukum_${index}`);
if(katSelect.length){
katSelect.select2({
dropdownParent: $('#modalCreateFile'),
placeholder:'Pilih Kategori',
allowClear: true
});
}
if(hukumSelect.length){
hukumSelect.select2({
dropdownParent: $('#modalCreateFile'),
placeholder:'Pilih Kategori Hukum',
allowClear:true
});
}
}
function selectOptionUnitKerjaV1(localCol){
const selectUnit = $(`#select_id_unit_kerja_${localCol}`);
const selectSubUnit = $(`#select_id_sub_unit_kerja_${localCol}`);
selectUnit.select2({
placeholder: '-- Pilih Unit Kerja --',
allowClear:true,
width: '100%',
dropdownParent: selectUnit.parent(),
ajax:{
url : '/select-unit-kerja-mapping',
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
}))
}
},
cache: true,
},
minimumInputLength: 0,
});
selectSubUnit.select2({
placeholder: '-- Pilih Sub Unit Kerja --',
allowClear: true,
width: '100%',
dropdownParent: selectSubUnit.parent()
});
selectUnit.on('select2:select', function (e) {
const data = e.params.data;
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>`);
});
}
});
}
window.addFormV2 = function(){
const col = document.getElementById('col_add_fileV2');
if (!col) return;
const katOptions = (Array.isArray(katDok) ? katDok : [])
.map(k => `<option value="${k.master_kategori_directory_id}/${k.nama_kategori_directory}">${k.nama_kategori_directory}</option>`)
.join('');
let 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-6">
<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-6">
<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">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>
<div class="col-md-4">
<label class="form-label fw-semibold">Nama Dokumen<span class="text-danger">*</span></label>
<input type="text"
class="form-control"
name="data[${colCount}][nama_dokumen]"
placeholder="Contoh: Panduan Mencuci Tangan" required>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Tanggal Terbit</label>
<input class="form-control"
type="date"
name="data[${colCount}][date_active]">
</div>
<div class="col-md-3">
<div class="form-check">
<input class="form-check-input toggle-expired"
type="checkbox"
id="hasExpired_${colCount}"
data-target="expiredField_${colCount}">
<label class="form-check-label" for="hasExpired_${colCount}">Masa Berlaku Dokumen?</label>
</div>
</div>
<div class="col-md-5" id="expiredField_${colCount}">
<label class="form-label fw-semibold">Tanggal Kedaluwarsa Dokumen</label>
<input class="form-control"
type="date"
name="data[${colCount}][tgl_expired]" disabled>
</div>
<div class="col-md-4">
<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}">Ya</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-4">
<label class="form-label fw-semibold">Instrumen Akreditasi </label>
<select class="form-select akre-select" id="akre_select_${colCount}" name="data[${colCount}][akre]" style="width: 350px;">
<option value="">Pilih Instrumen</option>
</select>
<div class="form-text text-muted">Isi form ini bila dokumen yang diunggah merupakan dokumen <strong>akreditasi</strong>.</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Hukum</label>
<select class="form-select select-kat-hukum" name="data[${colCount}][kategori_hukum]" id="select_kategori_hukum_${colCount}" style="width: 350px;">
<option value="">Pilih Kategori Hukum</option>
<option value="Kebijakan - Peraturan Direktur">Kebijakan - Peraturan Direktur</option>
<option value="Kebijakan - Keputusan Direktur Utama">Kebijakan - Keputusan Direktur Utama</option>
<option value="Kebijakan - Surat Edaran">Kebijakan - Surat Edaran</option>
<option value="Kebijakan - Pengumuman">Kebijakan - Pengumuman</option>
<option value="Kerjasama - Pelayanan Kesehatan">Kerjasama - Pelayanan Kesehatan</option>
<option value="Kerjasama - Management">Kerjasama - Management</option>
<option value="Kerjasama - Pemeliharan">Kerjasama - Pemeliharan</option>
<option value="Kerjasama - Diklat">Kerjasama - Diklat</option>
<option value="Kerjasama - Luar Negeri">Kerjasama - Luar Negeri</option>
<option value="Kerjasama - Area Bisnis">Kerjasama - Area Bisnis</option>
<option value="Kerjasama - Pendidikan">Kerjasama - Pendidikan</option>
<option value="Kerjasama - Pengampuan KIA">Kerjasama- Pengampuan KIA</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Lainnya</label>
<select class="form-select"
name="data[${colCount}][master_kategori_directory_id]"
id="select_kategori_${colCount}" style="width: 350px;">
<option value="">Pilih Kategori</option>
${katOptions}
</select>
</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.insertAdjacentHTML('beforeend', html);
selectOptionUnitKerjaV1(colCount);
initKategoriSelect2(colCount);
enableAkreFields(colCount);
setKategoriRequired(colCount, false);
colCount++;
}
window.removeCol = function(count){
const el = document.getElementById(`col-${count}`);
if (el) el.remove();
}
function resetCreateForm(){
colCount = 1;
const colAdd = document.getElementById('col_add_fileV2');
if (colAdd) colAdd.innerHTML = '';
if (formCreate) {
formCreate.reset();
$(formCreate).find('select').val(null).trigger('change');
$(formCreate).find('input[type="file"]').val('');
$(formCreate).find('.file-name').addClass('d-none').text('');
}
resetAkreFields(0);
enableAkreFields(0);
}
if (formCreate) {
const select0 = $('#select_id_unit_kerja_0');
if (select0.length) selectOptionUnitKerjaV1(0);
initKategoriSelect2(0);
enableAkreFields(0);
formCreate.addEventListener('submit', (e) => {
e.preventDefault();
const submitBtn = formCreate.querySelector('button[type="submit"]');
if (submitBtn) submitBtn.disabled = true;
if (submitBtn) submitBtn.textContent = 'menyimpan...';
const formData = new FormData(formCreate);
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){
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: responseData.message || 'Data berhasil disimpan.',
timer: 1500,
showConfirmButton: false
});
const modalInstance = bootstrap.Modal.getInstance(modalCreate);
modalInstance?.hide();
resetCreateForm();
fetchData();
} else {
throw new Error(responseData.message || 'Terjadi kesalahan saat menyimpan data.');
}
}).catch(err => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
}).finally(() => {
if (submitBtn) submitBtn.disabled = false;
if (submitBtn) submitBtn.textContent = 'Simpan';
});
});
}
document.addEventListener('change', function(e){
if(e.target.classList.contains('akre-select')){
const id = e.target.id || '';
const idx = id.split('_').pop();
const [typeVal = '', segmentVal = '', itemVal = ''] = (e.target.value || '').split('/');
const typeInput = document.getElementById(`akre_type_${idx}`);
const segmentInput = document.getElementById(`akre_segment_${idx}`);
const itemInput = document.getElementById(`akre_item_${idx}`);
if(typeInput) typeInput.value = typeVal;
if(segmentInput) segmentInput.value = segmentVal;
if(itemInput) itemInput.value = itemVal;
}
});
</script>
@endsection

View File

@ -35,37 +35,6 @@
border: 1px solid #dee2e6 !important;
color: #111 !important;
}
.select2-container--default .select2-selection--single .select2-selection__clear {
display: inline-block !important;
font-size: 16px;
font-weight: bold;
color: #999;
margin-right: 8px;
cursor: pointer;
}
.table-header-filter .dropdown-menu {
z-index: 1080;
}
.table-fixed-height {
min-height: 70vh;
}
/* --- Warna kategori baris --- */
.row-shade {
background-color: var(--row-bg, transparent) !important;
transition: background-color 0.2s ease;
}
.legend-dot {
width: 14px;
height: 14px;
border-radius: 4px;
display: inline-block;
border: 1px solid rgba(0,0,0,0.08);
vertical-align: middle;
margin-right: 6px;
}
</style>
@section('body_main')
@ -119,11 +88,12 @@
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 flex-grow-1">
<select id="tableUnit" class="form-select form-select-sm unit_kerja_filter" style="max-width: 260px;" multiple></select>
<select id="tableKategori" class="form-select form-select-sm kategori_kerja_filter" style="max-width: 260px;">
<option value="">Kategori (Semua)</option>
<option value="akreditasi">Kategori Akreditasi</option>
<option value="hukum">Kategori Hukum</option>
<option value="lainnya">Kategori Lainnya</option>
<select id="tableKategori" class="form-select form-select-sm kategori_kerja_filter" style="max-width: 260px;" multiple>
@foreach ($katDok as $kat)
<option value="{{ $kat->master_kategori_directory_id }}">
{{ $kat->nama_kategori_directory }}
</option>
@endforeach
</select>
<input type="search"
id="tableSearch"
@ -136,7 +106,7 @@
</div>
</div>
<div class="table-responsive table-fixed-height" style="max-height: 70vh; overflow-y:auto;">
<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>
@ -146,21 +116,7 @@
<th>Aksi</th>
<th>No Dokumen</th>
<th>Nama Dokumen</th>
<th>
<div class="d-flex align-items-center gap-2 table-header-filter">
<span>Kategori</span>
<div class="dropdown">
<button class="btn btn-light btn-sm border" type="button" id="tableKategoriHeaderBtn" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
<i class="ti ti-filter"></i>
</button>
<div class="dropdown-menu p-2" id="tableKategoriHeaderMenu" style="min-width: 220px;">
<div class="small text-muted px-1">Filter kategori (BETA)</div>
<div class="dropdown-divider"></div>
<div class="kategori-header-list"></div>
</div>
</div>
</div>
</th>
<th>Kategori</th>
<th>Unit</th>
<th>Tanggal Unggah</th>
</tr>
@ -170,7 +126,6 @@
</tbody>
</table>
</div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-3">
<div class="d-flex align-items-center gap-1">
<span class="small text-muted">Tampilkan</span>
@ -198,6 +153,7 @@
</div>
</div>
</div>
@include('dataUmum.modal.create')
@include('dataUmum.modal.view')
<script>
const katDok = @json($katDok);
@ -207,24 +163,19 @@
const authPegawai = @json(auth()->user()->objectpegawaifk);
const formCreate = $("#formFile")
const modalCreate = document.getElementById('modalCreateFile')
const tableState = { data: [], page: 1, pageSize: 8, search: '', unit: [], kategori: [], kategoriType: [], kategoriHeader: [], lastPage: 1, total: 0 };
let kategoriOptionCache = [];
const tableState = { data: [], page: 1, pageSize: 8, search: '', unit: [], kategori: [], lastPage: 1, total: 0 };
const tbody = document.getElementById('tableDataUmum');
const paginationEl = document.getElementById('paginationControls');
const summaryEl = document.getElementById('tableSummary');
const legendEl = document.getElementById('tableLegend');
const pageSizeSelect = document.getElementById('tablePageSize');
const unitSelect = document.getElementById('tableUnit');
const kategoriSelect = document.getElementById('tableKategori');
const kategoriHeaderMenu = document.getElementById('tableKategoriHeaderMenu');
const searchInput = document.getElementById('tableSearch');
const searchBtn = document.getElementById('btnTableSearch');
const downloadBtn = document.getElementById('btnDownloadMultiple');
const selectedCountEl = document.getElementById('selectedCount');
const checkAllEl = document.getElementById('checkAllRows');
const selectedIds = new Set();
const colorCache = {};
const colorPalette = ['#e8f4ff', '#fff6e5', '#e9f7ef', '#f3e8ff', '#ffe6ea', '#e6f5f3'];
document.addEventListener('change', function(e){
if(!e.target.classList.contains('toggle-expired')) return;
@ -284,35 +235,16 @@
}
if (kategoriSelect) {
$('#tableKategori').select2({
placeholder: 'Kategori (Semua)',
placeholder: 'Pilih Kategori',
allowClear: true,
width: '100%'
width: '100%',
closeOnSelect: false
});
$('#tableKategori').on('change', function () {
const val = $(this).val() || '';
console.log(val);
tableState.kategoriType = val ? [val] : [];
tableState.page = 1;
fetchData();
tableState.kategori = $(this).val() || [];
});
}
}
if (kategoriHeaderMenu) {
kategoriHeaderMenu.addEventListener('change', function(e){
const checkbox = e.target.closest('input[type="checkbox"]');
if (!checkbox) return;
const selected = Array.from(kategoriHeaderMenu.querySelectorAll('input[type="checkbox"]:checked'))
.map(el => el.value);
tableState.kategoriHeader = selected;
if (kategoriSelect && window.$ && $.fn.select2) {
const single = selected.length === 1 ? selected[0] : '';
$('#tableKategori').val(single).trigger('change');
}
tableState.page = 1;
fetchData();
});
}
function resetCreateForm(){
colCount = 1;
@ -326,48 +258,7 @@
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'ya' || val === 'yes';
}
function resolveKategoriFlag(item){
if(Number(item.is_akre) === 1 || item.is_akre === true || String(item.is_akre).toLowerCase() === 'true'){
return { key: 'akre', label: 'Kategori Akreditasi' };
}
if(item.kategori_hukum){
return { key: 'hukum', label: 'Kategori Hukum' };
}
const label = (item.nama_kategori || item.nama_kategori_directory || item.kategori || '').trim() || 'Kategori Lainnya';
const key = String(item.master_kategori_directory_id || label || 'lainnya');
return { key, label };
}
function pickColor(key, label){
if(colorCache[key]) return colorCache[key];
const index = Object.keys(colorCache).length % colorPalette.length;
colorCache[key] = colorPalette[index];
return colorCache[key];
}
function renderLegend(items){
if(!legendEl) return;
const map = new Map();
(items || []).forEach(item => {
const flag = resolveKategoriFlag(item);
const color = pickColor(flag.key, flag.label);
if(!map.has(flag.key)){
map.set(flag.key, { label: flag.label, color });
}
});
if(map.size === 0){
legendEl.textContent = '';
return;
}
legendEl.innerHTML = Array.from(map.values()).map(entry => `
<span class="me-3">
<span class="legend-dot" style="background:${entry.color};"></span>
<span>${entry.label}</span>
</span>
`).join('');
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}
function getExpiryStatus(dateStr){
@ -403,19 +294,12 @@
statusClass = 'bg-secondary';
}
const checked = selectedIds.has(String(item.file_directory_id)) ? 'checked' : '';
const kategoriFlag = resolveKategoriFlag(item);
const rowColor = pickColor(kategoriFlag.key, kategoriFlag.label);
const isAkre = kategoriFlag.key === 'akre';
const rowClass = isAkre ? 'table-info' : (expiryStatus === 'expired' ? 'table-danger' : (expiryStatus === 'soon' ? 'table-warning' : 'row-shade'));
const rowClass = expiryStatus === 'expired' ? 'table-danger' : (expiryStatus === 'soon' ? 'table-warning' : '');
const expiryBadge = expiryStatus === 'expired'
? `<span class="badge bg-danger" style="font-size:10px;">Expired</span>`
: (expiryStatus === 'soon' ? `<span class="badge bg-warning text-dark" style="font-size:10px;">Akan Expired</span>` : '');
const akreBadge = isAkre
? `<span class="badge bg-primary text-white" style="font-size:10px;">Akreditasi</span>`
: '';
return `
<tr class="${rowClass}" style="--row-bg:${rowColor};">
<tr class="${rowClass}">
<td class="text-center">
<input type="checkbox"
@ -474,7 +358,7 @@
word-break:break-word;
"
>
${item.nama_dokumen || '-'} ${akreBadge}
${item.nama_dokumen || '-'}
</a>
${expiryBadge}
@ -483,10 +367,10 @@
</td>
<td>
${item.nama_kategori || '-'}
${kategoriName}
</td>
<td>
${item.unit?.name || '-'}
${unitName}
</td>
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
</tr>
@ -537,12 +421,12 @@
}
function renderTable(){
const pageData = filterByKategoriType(tableState.data || []);
const pageData = tableState.data || [];
if(pageData.length === 0){
tbody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted py-4">
<td colspan="7" class="text-center text-muted py-4">
Tidak ada data yang cocok
</td>
</tr>
@ -560,85 +444,17 @@
renderPagination(tableState.lastPage || 1);
syncCheckAllState();
updateSelectedCount();
renderLegend(pageData);
}
function applyTableSearch(){
const value = searchInput ? searchInput.value : '';
tableState.search = (value || '').trim();
tableState.unit = unitSelect && window.$ ? ($('#tableUnit').val() || []) : (tableState.unit || []);
const katVal = kategoriSelect && window.$ ? ($('#tableKategori').val() || '') : (kategoriSelect?.value || '');
tableState.kategoriType = katVal ? [katVal] : (tableState.kategoriType || []);
tableState.kategori = kategoriSelect && window.$ ? ($('#tableKategori').val() || []) : (tableState.kategori || []);
tableState.page = 1;
fetchData();
}
function getKategoriLabel(item){
const parts = String(item?.file || '').split('/');
return (parts[2] || item?.nama_kategori_directory || item?.kategori || '').trim();
}
function getKategoriId(item){
const label = getKategoriLabel(item);
return String(item?.master_kategori_directory_id || label);
}
function getKategoriOptionsFromData(){
const seen = new Map();
(tableState.data || []).forEach(item => {
const label = getKategoriLabel(item);
if(!label) return;
const id = getKategoriId(item);
if(!seen.has(id)){
seen.set(id, { id, label });
}
});
return Array.from(seen.values()).sort((a,b) => a.label.localeCompare(b.label));
}
function isKategoriMatch(item, types){
if (!types.length) return true;
const lowerTypes = types.map(t => String(t).toLowerCase());
const isAkre = (item.is_akre === true) || String(item.is_akre).toLowerCase() === 'true' || Number(item.is_akre) === 1;
const isHukum = !!item.kategori_hukum;
const hasKategoriId = !!item.master_kategori_directory_id;
if (isAkre && lowerTypes.includes('akreditasi')) return true;
if (isHukum && lowerTypes.includes('hukum')) return true;
if (!isAkre && !isHukum && hasKategoriId && lowerTypes.includes('lainnya')) return true;
const catId = String(getKategoriId(item)).toLowerCase();
const catLabel = String(getKategoriLabel(item)).toLowerCase();
return lowerTypes.includes(catId) || lowerTypes.includes(catLabel);
}
function filterByKategoriType(items){
const types = (tableState.kategoriType || []).map(v => String(v));
if (!types.length) return items;
return items.filter(item => isKategoriMatch(item, types));
}
function renderKategoriHeaderOptions(){
if (!kategoriHeaderMenu) return;
const list = kategoriHeaderMenu.querySelector('.kategori-header-list');
if (!list) return;
const options = kategoriOptionCache.length ? kategoriOptionCache : getKategoriOptionsFromData();
const selected = (tableState.kategoriHeader || []).map(v => String(v));
if(options.length === 0){
list.innerHTML = '<div class=\"dropdown-item text-muted\">Tidak ada kategori</div>';
return;
}
list.innerHTML = options.map(opt => {
const checked = selected.includes(opt.id) ? 'checked' : '';
return `
<label class="dropdown-item d-flex align-items-center gap-2">
<input type="checkbox" class="form-check-input m-0" value="${opt.id}" ${checked}>
<span>${opt.label}</span>
</label>
`;
}).join('');
}
function fetchData(){
if(summaryEl) summaryEl.textContent = 'Memuat data...';
const params = new URLSearchParams({
@ -649,20 +465,15 @@
if (tableState.unit && tableState.unit.length > 0) {
tableState.unit.forEach(id => params.append('unit[]', id));
}
if (tableState.kategoriType && tableState.kategoriType.length > 0) {
tableState.kategoriType.forEach(id => params.append('kategori[]', id));
}
if (tableState.kategoriHeader && tableState.kategoriHeader.length > 0) {
tableState.kategoriHeader.forEach(id => params.append('kategori_header[]', id));
if (tableState.kategori && tableState.kategori.length > 0) {
tableState.kategori.forEach(kat => params.append('kategori[]', kat));
}
fetch(`/datatable-umum?${params.toString()}`)
.then(response => response.json())
.then(data => {
tableState.data = data?.data || [];
kategoriOptionCache = data?.kategori_list || kategoriOptionCache;
tableState.lastPage = data?.pagination?.last_page || 1;
tableState.total = data?.pagination?.total || 0;
renderKategoriHeaderOptions();
renderTable();
})
.catch(error => {
@ -692,7 +503,6 @@
year: 'numeric'
});
}
renderKategoriHeaderOptions();
fetchData()
function updateSelectedCount(){
@ -869,6 +679,144 @@
});
}
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-4">
<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"></div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Nama Dokumen<span class="text-danger">*</span></label>
<input type="text" class="form-control"
name="data[${colCount}][nama_dokumen]"
placeholder="Contoh: 001/RS/IT/I/2026" required>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Tanggal Terbit</label>
<input class="form-control"
type="date"
name="data[${colCount}][date_active]">
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input toggle-expired"
type="checkbox"
id="hasExpired_${colCount}"
data-target="expiredField_${colCount}">
<label class="form-check-label" for="hasExpired_${colCount}">Ada Expired?</label>
</div>
</div>
<div class="col-md-5 d-none" id="expiredField_${colCount}">
<label class="form-label fw-semibold">Tanggal Kedaluwarsa Dokumen</label>
<input class="form-control" type="date" name="data[${colCount}][tgl_expired]">
</div>
<div class="col-md-5">
<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()
}

View File

@ -0,0 +1,119 @@
<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-12">
<div class="p-2 rounded-3 bg-light border">
<span class="fw-semibold">Informasi Dokumen</span>
<div class="small text-muted">Lengkapi detail dokumen sebelum upload.</div>
</div>
</div>
<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>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-control" name="data[0][id_sub_unit_kerja]" id="select_id_sub_unit_kerja_0" required>
<option value="" disable 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-control" name="data[0][master_kategori_directory_id]" id="select_kategori_0" required>
<option value="" disable>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-4">
<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-4">
<label class="form-label fw-semibold">Nama Dokumen<span class="text-danger">*</span></label>
<input type="text" class="form-control" name="data[0][nama_dokumen]" placeholder="Contoh: Panduan Mencuci Tangan" required>
</div>
<div class="col-md-4">
<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-2 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input toggle-expired" type="checkbox" id="hasExpired0" data-target="expiredField_0">
<label class="form-check-label" for="hasExpired0">Ada Expired?</label>
</div>
</div>
<div class="col-md-5 d-none" id="expiredField_0">
<label class="form-label fw-semibold">Tanggal Kedaluwarsa Dokumen</label>
<input class="form-control" type="date" name="data[0][tgl_expired]">
</div>
<div class="col-md-5">
<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">
Format yang didukung: <b>PDF</b> Maksimal <b>10mb</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>

View File

@ -1,10 +1,10 @@
@extends('layout.main')
<style>
/* --- SELECT2: teks terlihat (hitam) --- */
.select2-container--default .select2-selection--multiple {
background: #fff !important;
border: 1px solid rgb(206, 212, 218) !important;
border: 1px solid #ced4da !important;
min-height: 31px; /* cocok form-select-sm */
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
@ -35,35 +35,6 @@
border: 1px solid #dee2e6 !important;
color: #111 !important;
}
.select2-container--default .select2-selection--single .select2-selection__clear {
display: inline-block !important;
font-size: 16px;
font-weight: bold;
color: #999;
margin-right: 8px;
cursor: pointer;
}
.table-header-filter .dropdown-menu {
z-index: 1080;
}
.table-fixed-height {
min-height: 70vh;
}
/* --- Warna kategori baris --- */
.row-shade {
background-color: var(--row-bg, transparent) !important;
transition: background-color 0.2s ease;
}
.legend-dot {
width: 14px;
height: 14px;
border-radius: 4px;
display: inline-block;
border: 1px solid rgba(0,0,0,0.08);
vertical-align: middle;
margin-right: 6px;
}
</style>
@section('body_main')
<div class="row">
@ -101,7 +72,6 @@
</span>
</div>
<!-- Tambah Dokumen -->
@if(!Auth::guard('admin')->check())
<button
type="button"
class="btn btn-success btn-sm"
@ -111,19 +81,17 @@
<i class="ti ti-plus me-1"></i>
Tambah Dokumen
</button>
@endif
</div>
</div>
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 flex-grow-1">
<select id="tableUnit" style="max-width: 260px;">
<option value=""></option>
</select>
<select id="tableKategori" class="form-select form-select-sm" style="max-width: 260px;">
<option value="">Kategori (Semua)</option>
<option value="akreditasi">Kategori Akreditasi</option>
<option value="hukum">Kategori Hukum</option>
<option value="lainnya">Kategori Lainnya</option>
<select id="tableUnit" class="form-select form-select-sm unit_kerja_filter" style="max-width: 260px;" multiple></select>
<select id="tableKategori" class="form-select form-select-sm kategori_kerja_filter" style="max-width: 260px;" multiple>
@foreach ($katDok as $kat)
<option value="{{ $kat->master_kategori_directory_id }}">
{{ $kat->nama_kategori_directory }}
</option>
@endforeach
</select>
<input type="search"
id="tableSearch"
@ -133,7 +101,7 @@
</div>
<button class="btn btn-primary" type="button" id="btnTableSearch">Cari</button>
</div>
<div class="table-responsive table-fixed-height" style="max-height: 70vh; overflow-y:auto;">
<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>
@ -143,21 +111,7 @@
<th>Aksi</th>
<th>No Dokumen</th>
<th>Nama Dokumen</th>
<th>
<div class="d-flex align-items-center gap-2 table-header-filter">
<span>Kategori</span>
<div class="dropdown">
<button class="btn btn-light btn-sm border" type="button" id="tableKategoriHeaderBtn" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
<i class="ti ti-filter"></i>
</button>
<div class="dropdown-menu p-2" id="tableKategoriHeaderMenu" style="min-width: 220px;">
<div class="small text-muted px-1">Filter kategori (BETA)</div>
<div class="dropdown-divider"></div>
<div class="kategori-header-list"></div>
</div>
</div>
</div>
</th>
<th>Kategori</th>
<th>Unit</th>
<th>Tanggal Unggah</th>
<th>Pengunggah</th>
@ -168,7 +122,6 @@
</tbody>
</table>
</div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-3">
<div class="d-flex align-items-center gap-1">
<span class="small text-muted">Tampilkan</span>
@ -211,32 +164,19 @@
const authPegawai = @json(auth()->user()->objectpegawaifk);
const formCreate = $("#formFile");
const modalCreate = document.getElementById('modalCreateFile');
const tableState = { data: [], page: 1, pageSize: 8, search: '', unit: [], kategori: [], kategoriType: [], kategoriHeader: [], lastPage: 1, total: 0 };
let kategoriOptionCache = [];
const tableState = { data: [], page: 1, pageSize: 8, search: '', unit: [], kategori: [], lastPage: 1, total: 0 };
const tbody = document.getElementById('tableDataUnit');
const paginationEl = document.getElementById('paginationControls');
const summaryEl = document.getElementById('tableSummary');
const legendEl = document.getElementById('tableLegend');
const pageSizeSelect = document.getElementById('tablePageSize');
const unitSelect = document.getElementById('tableUnit');
const kategoriSelect = document.getElementById('tableKategori');
const kategoriHeaderMenu = document.getElementById('tableKategoriHeaderMenu');
const searchInput = document.getElementById('tableSearch');
const searchBtn = document.getElementById('btnTableSearch');
const downloadBtn = document.getElementById('btnDownloadMultiple');
const selectedCountEl = document.getElementById('selectedCount');
const checkAllEl = document.getElementById('checkAllRows');
const selectedIds = new Set();
const colorCache = {};
const colorPalette = ['#e8f4ff', '#fff6e5', '#e9f7ef', '#f3e8ff', '#ffe6ea', '#e6f5f3'];
function normalizeToArray(value){
if (Array.isArray(value)) {
return value.filter(v => v !== null && v !== undefined && v !== '');
}
if (value === null || value === undefined || value === '') return [];
return [value];
}
document.addEventListener('change', function(e){
if(!e.target.classList.contains('toggle-expired')) return;
@ -266,6 +206,8 @@
allowClear: true,
width: '100%',
closeOnSelect: false,
selectionCssClass: 'select2-filter-selection',
dropdownCssClass: 'select2-filter-dropdown',
ajax: {
url: '/select-unit-kerja-mapping',
dataType: 'json',
@ -284,39 +226,24 @@
cache: true
}
});
$('#tableUnit').on('change', function () {
tableState.unit = $(this).val() || [];
});
}
if (kategoriSelect) {
$('#tableKategori').select2({
placeholder: 'Kategori (Semua)',
placeholder: 'Pilih Kategori',
allowClear: true,
width: '100%',
closeOnSelect: false,
selectionCssClass: 'select2-filter-selection',
dropdownCssClass: 'select2-filter-dropdown'
});
$('#tableKategori').on('change', function () {
const val = $(this).val() || '';
tableState.kategoriType = val ? [val] : [];
tableState.page = 1;
fetchData();
tableState.kategori = $(this).val() || [];
});
}
}
if (kategoriHeaderMenu) {
kategoriHeaderMenu.addEventListener('change', function(e){
const checkbox = e.target.closest('input[type="checkbox"]');
if (!checkbox) return;
const selected = Array.from(kategoriHeaderMenu.querySelectorAll('input[type="checkbox"]:checked'))
.map(el => el.value);
tableState.kategoriHeader = selected;
if (kategoriSelect && window.$ && $.fn.select2) {
const single = selected.length === 1 ? selected[0] : '';
$('#tableKategori').val(single).trigger('change');
}
tableState.page = 1;
fetchData();
});
}
function resetCreateForm(){
colCount = 1;
@ -325,186 +252,12 @@
formCreate.find('select').val(null).trigger('change');
formCreate.find('input[type="file"]').val('');
formCreate.find('.file-name').addClass('d-none').text('');
resetAkreFields(0);
enableAkreFields(0);
}
let akreData = [];
let akreLoaded = false;
let akreFlat = [];
function loadAkreData(){
if(akreLoaded) return Promise.resolve(akreData);
return fetch('/json/akreditasi.jff')
.then(res => res.json())
.then(data => {
akreData = Array.isArray(data) ? data : [];
akreFlat = [];
akreLoaded = true;
return akreData;
})
.catch(() => {
akreData = [];
akreFlat = [];
akreLoaded = true;
return akreData;
});
}
function getAkreFlat(){
if(akreFlat.length) return akreFlat;
akreFlat = (akreData || []).flatMap(type => {
const segments = Array.isArray(type.segment) ? type.segment : [];
return segments.flatMap(seg => {
const children = Array.isArray(seg.turunan) ? seg.turunan : [];
return children.map(child => ({
value: `${type.name}/${seg.name}/${child.name}`,
label: `${type.name} / ${child.name}`,
type: type.name,
segment: seg.name,
item: child.name
}));
});
});
return akreFlat;
}
function fillAkreSelect(selectEl){
if(!selectEl) return;
selectEl.innerHTML = '<option value="" disabled selected>Pilih Instrumen</option>';
getAkreFlat().forEach(optData => {
const opt = document.createElement('option');
opt.value = optData.value;
opt.textContent = optData.label;
selectEl.appendChild(opt);
});
}
function setKategoriRequired(index, isRequired){
const katSelect = document.getElementById(`select_kategori_${index}`);
if (!katSelect) return;
if (isRequired) {
katSelect.setAttribute('required', 'required');
} else {
katSelect.removeAttribute('required');
}
}
function resetAkreFields(index){
const selectEl = document.getElementById(`akre_select_${index}`);
const typeInput = document.getElementById(`akre_type_${index}`);
const segmentInput = document.getElementById(`akre_segment_${index}`);
const itemInput = document.getElementById(`akre_item_${index}`);
if(selectEl){
selectEl.value = '';
if(window.$ && $.fn.select2) $(selectEl).val(null).trigger('change');
}
if(typeInput) typeInput.value = '';
if(segmentInput) segmentInput.value = '';
if(itemInput) itemInput.value = '';
setKategoriRequired(index, false);
}
function enableAkreFields(index){
const selectEl = document.getElementById(`akre_select_${index}`);
// if(selectEl){
// selectEl.disabled = false;
// selectEl.required = true;
// }
setKategoriRequired(index, false);
loadAkreData().then(() => {
fillAkreSelect(selectEl);
if(window.$ && $.fn.select2){
$(selectEl).select2({
dropdownParent: $('#modalCreateFile'),
placeholder: 'Pilih Instrumen',
allowClear: true
});
}
});
}
function initKategoriSelect2(index){
if(!window.$ || !$.fn.select2) return;
const katSelect = $(`#select_kategori_${index}`);
const hukumSelect = $(`#select_kategori_hukum_${index}`);
if(katSelect.length){
katSelect.select2({
dropdownParent: $('#modalCreateFile'),
placeholder:'Pilih Kategori',
allowClear: true
});
}
if(hukumSelect.length){
hukumSelect.select2({
dropdownParent: $('#modalCreateFile'),
placeholder:'Pilih Kategori Hukum',
allowClear:true
});
}
}
document.addEventListener('change', function(e){
if(e.target.classList.contains('akre-select')){
const id = e.target.id || '';
const idx = id.split('_').pop();
const [typeVal = '', segmentVal = '', itemVal = ''] = (e.target.value || '').split('/');
const typeInput = document.getElementById(`akre_type_${idx}`);
const segmentInput = document.getElementById(`akre_segment_${idx}`);
const itemInput = document.getElementById(`akre_item_${idx}`);
if(typeInput) typeInput.value = typeVal;
if(segmentInput) segmentInput.value = segmentVal;
if(itemInput) itemInput.value = itemVal;
return;
}
});
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'ya' || val === 'yes';
}
function resolveKategoriFlag(item){
if(Number(item.is_akre) === 1 || item.is_akre === true || String(item.is_akre).toLowerCase() === 'true'){
return { key: 'akre', label: 'Kategori Akreditasi' };
}
if(item.kategori_hukum){
return { key: 'hukum', label: 'Kategori Hukum' };
}
const label = (item.nama_kategori || item.nama_kategori_directory || item.kategori || '').trim() || 'Kategori Lainnya';
const key = String(item.master_kategori_directory_id || label || 'lainnya');
return { key, label };
}
function pickColor(key, label){
if(colorCache[key]) return colorCache[key];
const index = Object.keys(colorCache).length % colorPalette.length;
colorCache[key] = colorPalette[index];
return colorCache[key];
}
function renderLegend(items){
if(!legendEl) return;
const map = new Map();
(items || []).forEach(item => {
const flag = resolveKategoriFlag(item);
const color = pickColor(flag.key, flag.label);
if(!map.has(flag.key)){
map.set(flag.key, { label: flag.label, color });
}
});
if(map.size === 0){
legendEl.textContent = '';
return;
}
legendEl.innerHTML = Array.from(map.values()).map(entry => `
<span class="me-3">
<span class="legend-dot" style="background:${entry.color};"></span>
<span>${entry.label}</span>
</span>
`).join('');
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}
function getExpiryStatus(dateStr){
@ -540,18 +293,12 @@
statusClass = 'bg-secondary';
}
const checked = selectedIds.has(String(item.file_directory_id)) ? 'checked' : '';
const kategoriFlag = resolveKategoriFlag(item);
const rowColor = pickColor(kategoriFlag.key, kategoriFlag.label);
const isAkre = kategoriFlag.key === 'akre';
const rowClass = isAkre ? 'table-info' : (expiryStatus === 'expired' ? 'table-danger' : (expiryStatus === 'soon' ? 'table-warning' : 'row-shade'));
const rowClass = expiryStatus === 'expired' ? 'table-danger' : (expiryStatus === 'soon' ? 'table-warning' : '');
const expiryBadge = expiryStatus === 'expired'
? `<span class="badge bg-danger" style="font-size:10px;">Expired</span>`
: (expiryStatus === 'soon' ? `<span class="badge bg-warning text-dark" style="font-size:10px;">Akan Expired</span>` : '');
const akreBadge = isAkre
? `<span class="badge bg-primary text-white ms-2" style="font-size:10px;">Akreditasi</span>`
: '';
return `
<tr class="${rowClass}" style="--row-bg:${rowColor};">
<tr class="${rowClass}">
<td class="text-center">
<input type="checkbox"
class="form-check-input row-check"
@ -600,17 +347,17 @@
>
${item.nama_dokumen || '-'}
</a>
${akreBadge}${expiryBadge}
${expiryBadge}
</div>
</td>
<td>
${item.nama_kategori || '-'}
${kategoriName}
</td>
<td>
${item.unit?.name || '-'}
${unitName}
</td>
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
<td style="max-width: 200px; white-space: normal; word-wrap: break-word;">${item.pegawai_nama_entry || '-'}</td>
<td class="text-nowrap">${item.pegawai_nama_entry || '-'}</td>
</tr>
`;
}
@ -659,12 +406,12 @@
}
function renderTable(){
const pageData = filterByKategoriType(tableState.data || []);
const pageData = tableState.data || [];
if(pageData.length === 0){
tbody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted py-4">
<td colspan="7" class="text-center text-muted py-4">
Tidak ada data yang cocok
</td>
</tr>
@ -682,110 +429,17 @@
renderPagination(tableState.lastPage || 1);
syncCheckAllState();
updateSelectedCount();
renderLegend(pageData);
}
function applyTableSearch(){
const value = searchInput ? searchInput.value : '';
tableState.search = (value || '').trim();
tableState.unit = normalizeToArray(
unitSelect && window.$ ? $('#tableUnit').val() : tableState.unit
);
const katVal = kategoriSelect && window.$ ? ($('#tableKategori').val() || '') : (kategoriSelect?.value || '');
tableState.kategoriType = katVal ? [katVal] : (tableState.kategoriType || []);
tableState.unit = unitSelect && window.$ ? ($('#tableUnit').val() || []) : (tableState.unit || []);
tableState.kategori = kategoriSelect && window.$ ? ($('#tableKategori').val() || []) : (tableState.kategori || []);
tableState.page = 1;
fetchData();
}
function getKategoriLabel(item){
const parts = String(item?.file || '').split('/');
return (item?.nama_kategori || parts[2] || item?.nama_kategori_directory || item?.kategori || '').trim();
}
function getKategoriId(item){
const label = getKategoriLabel(item);
return String(item?.master_kategori_directory_id || label);
}
function getKategoriOptionsFromData(){
const seen = new Map();
(tableState.data || []).forEach(item => {
const label = getKategoriLabel(item);
if(!label) return;
const id = getKategoriId(item);
if(!seen.has(id)){
seen.set(id, { id, label });
}
});
return Array.from(seen.values()).sort((a,b) => a.label.localeCompare(b.label));
}
function isKategoriMatch(item, types){
if (!types.length) return true;
const lowerTypes = types.map(t => String(t).toLowerCase());
const isAkre = (item.is_akre === true) || String(item.is_akre).toLowerCase() === 'true' || Number(item.is_akre) === 1;
const isHukum = !!item.kategori_hukum;
const hasKategoriId = !!item.master_kategori_directory_id;
// special buckets
if (isAkre && lowerTypes.includes('akreditasi')) return true;
if (isHukum && lowerTypes.includes('hukum')) return true;
if (!isAkre && !isHukum && hasKategoriId && lowerTypes.includes('lainnya')) return true;
// fallback to id/label comparison
const catId = String(getKategoriId(item)).toLowerCase();
const catLabel = String(getKategoriLabel(item)).toLowerCase();
return lowerTypes.includes(catId) || lowerTypes.includes(catLabel);
}
function filterByKategoriType(items){
const types = (tableState.kategoriType || []).map(v => String(v));
if (!types.length) return items;
return items.filter(item => isKategoriMatch(item, types));
}
function renderKategoriHeaderOptions(){
if (!kategoriHeaderMenu) return;
const list = kategoriHeaderMenu.querySelector('.kategori-header-list');
if (!list) return;
const options = kategoriOptionCache.length ? kategoriOptionCache : getKategoriOptionsFromData();
const selected = (tableState.kategoriHeader || []).map(v => String(v));
if(options.length === 0){
list.innerHTML = '<div class=\"dropdown-item text-muted\">Tidak ada kategori</div>';
return;
}
list.innerHTML = options.map(opt => {
const checked = selected.includes(opt.id) ? 'checked' : '';
return `
<label class="dropdown-item d-flex align-items-center gap-2 kategori-option" data-kat="${opt.id}">
<input type="checkbox" class="form-check-input m-0" value="${opt.id}" ${checked}>
<span>${opt.label}</span>
</label>
`;
}).join('');
}
if (kategoriHeaderMenu) {
kategoriHeaderMenu.addEventListener('click', function(e){
const option = e.target.closest('.kategori-option');
if (!option) return;
const id = option.getAttribute('data-kat');
const checkbox = option.querySelector('input[type="checkbox"]');
if(checkbox){
checkbox.checked = !checkbox.checked;
const event = new Event('change', { bubbles: true });
checkbox.dispatchEvent(event);
}else{
tableState.kategoriHeader = [id];
if (kategoriSelect && window.$ && $.fn.select2) {
$('#tableKategori').val(id).trigger('change');
}
tableState.page = 1;
fetchData();
}
}, true);
}
function fetchData(){
if(summaryEl) summaryEl.textContent = 'Memuat data...';
const params = new URLSearchParams({
@ -793,24 +447,18 @@
per_page: tableState.pageSize,
keyword: tableState.search
});
const unitValues = normalizeToArray(tableState.unit);
if (unitValues.length > 0) {
unitValues.forEach(id => params.append('unit[]', id));
if (tableState.unit && tableState.unit.length > 0) {
tableState.unit.forEach(id => params.append('unit[]', id));
}
if (tableState.kategoriType && tableState.kategoriType.length > 0) {
tableState.kategoriType.forEach(id => params.append('kategori[]', id));
}
if (tableState.kategoriHeader && tableState.kategoriHeader.length > 0) {
tableState.kategoriHeader.forEach(id => params.append('kategori_header[]', id));
if (tableState.kategori && tableState.kategori.length > 0) {
tableState.kategori.forEach(kat => params.append('kategori[]', kat));
}
fetch(`/data-internal?${params.toString()}`)
.then(response => response.json())
.then(data => {
tableState.data = data?.data || [];
kategoriOptionCache = data?.kategori_list || kategoriOptionCache;
tableState.lastPage = data?.pagination?.last_page || 1;
tableState.total = data?.pagination?.total || 0;
renderKategoriHeaderOptions();
renderTable();
})
.catch(error => {
@ -819,13 +467,17 @@
})
}
if (searchBtn) {
searchBtn.addEventListener('click', applyTableSearch);
}
if (searchInput) {
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
applyTableSearch();
}
});
}
function formatTanggal(dateString) {
const d = new Date(dateString);
@ -835,7 +487,6 @@
year: 'numeric'
});
}
renderKategoriHeaderOptions();
fetchData()
function updateSelectedCount(){
@ -985,8 +636,6 @@
});
selectOptionUnitKerjaV1(0);
initKategoriSelect2(0);
enableAkreFields(0);
});
function loadSubUnitKerja(unitId){
@ -1024,7 +673,7 @@
</button>
</div>
<div class="col-md-6">
<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]"
@ -1034,7 +683,7 @@
</select>
</div>
<div class="col-md-6">
<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]"
@ -1044,7 +693,20 @@
</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-4">
<label class="form-label fw-semibold">Nomor Dokumen</label>
@ -1071,7 +733,7 @@
type="date"
name="data[${colCount}][date_active]">
</div>
<div class="col-md-3">
<div class="col-md-2">
<div class="form-check">
<input class="form-check-input toggle-expired"
type="checkbox"
@ -1088,7 +750,7 @@
name="data[${colCount}][tgl_expired]" disabled>
</div>
<div class="col-md-4">
<div class="col-md-5">
<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">
@ -1099,7 +761,7 @@
id="perm_yes_${colCount}"
value="1"
required>
<label class="form-check-label" for="perm_yes_${colCount}">Ya</label>
<label class="form-check-label" for="perm_yes_${colCount}">Iya</label>
</div>
<div class="form-check mt-1">
@ -1114,44 +776,6 @@
</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Instrumen Akreditasi </label>
<select class="form-select akre-select" id="akre_select_${colCount}" name="data[${colCount}][akre]" style="width: 350px;">
<option value="" disabled selected>Pilih Instrumen</option>
</select>
<div class="form-text text-muted">Isi form ini bila dokumen yang diunggah merupakan dokumen <strong>akreditasi</strong>.</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Hukum</label>
<select class="form-select select-kat-hukum" name="data[${colCount}][kategori_hukum]" id="select_kategori_hukum_${colCount}" style="width: 350px;">
<option value="" disabled selected>Pilih Kategori Hukum</option>
<option value="Kebijakan - Peraturan Direktur">Kebijakan - Peraturan Direktur</option>
<option value="Kebijakan - Keputusan Direktur Utama">Kebijakan - Keputusan Direktur Utama</option>
<option value="Kebijakan - Surat Edaran">Kebijakan - Surat Edaran</option>
<option value="Kebijakan - Pengumuman">Kebijakan - Pengumuman</option>
<option value="Kerjasama - Pelayanan Kesehatan">Kerjasama - Pelayanan Kesehatan</option>
<option value="Kerjasama - Management">Kerjasama - Management</option>
<option value="Kerjasama - Pemeliharan">Kerjasama - Pemeliharan</option>
<option value="Kerjasama - Diklat">Kerjasama - Diklat</option>
<option value="Kerjasama - Luar Negeri">Kerjasama - Luar Negeri</option>
<option value="Kerjasama - Area Bisnis">Kerjasama - Area Bisnis</option>
<option value="Kerjasama - Pendidikan">Kerjasama - Pendidikan</option>
<option value="Kerjasama - Pengampuan KIA">Kerjasama- Pengampuan KIA</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Lainnya</label>
<select class="form-select"
name="data[${colCount}][master_kategori_directory_id]"
id="select_kategori_${colCount}" style="width: 350px;">
<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-12">
<label for="fileUpload_${colCount}" class="form-label fw-semibold">📂 Upload Dokumen (PDF)</label>
@ -1174,9 +798,6 @@
`;
col.append(html)
selectOptionUnitKerjaV1(colCount)
initKategoriSelect2(colCount)
enableAkreFields(colCount)
setKategoriRequired(colCount, false)
colCount++;
}

View File

@ -20,19 +20,28 @@
<div class="small text-muted">Lengkapi detail dokumen sebelum upload.</div>
</div>
</div>
<div class="col-md-6">
<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>Pilih Unit</option>
</select>
</div>
<div class="col-md-6">
<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>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-control" name="data[0][master_kategori_directory_id]" id="select_kategori_0" required>
<option value="" disable>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-4">
<label class="form-label fw-semibold">Nomor Dokumen</label>
<div class="input-group">
@ -48,26 +57,27 @@
<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-4">
<div class="col-md-2">
<div class="form-check">
<input class="form-check-input toggle-expired" type="checkbox" id="hasExpired0" data-target="expiredField_0">
<label class="form-check-label" for="hasExpired0">Masa Berlaku Dokumen?</label>
</div>
</div>
<div class="col-md-4" id="expiredField_0">
<div class="col-md-5" id="expiredField_0">
<label class="form-label fw-semibold">Tanggal Kedaluwarsa Dokumen</label>
<input class="form-control" type="date" name="data[0][tgl_expired]" disabled>
</div>
<div class="col-md-4">
<div class="col-md-5">
<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">
Ya
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">
@ -75,41 +85,9 @@
</label>
</div>
</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Instrumen Akreditasi</label>
<select class="form-select akre-select" name="data[0][akre]" id="akre_select_0" style="width: 350px;">
<option value="">Pilih Instrumen</option>
</select>
<div class="form-text text-muted">Isi form ini bila dokumen yang diunggah merupakan dokumen <strong>akreditasi</strong>.</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Hukum</label>
<select class="form-select select-kat-hukum" name="data[0][kategori_hukum]" id="select_kategori_hukum_0" style="width: 350px;">
<option value="">Pilih Kategori Hukum</option>
<option value="Kebijakan - Peraturan Direktur">Kebijakan - Peraturan Direktur</option>
<option value="Kebijakan - Keputusan Direktur Utama">Kebijakan - Keputusan Direktur Utama</option>
<option value="Kebijakan - Surat Edaran">Kebijakan - Surat Edaran</option>
<option value="Kebijakan - Pengumuman">Kebijakan - Pengumuman</option>
<option value="Kerjasama - Pelayanan Kesehatan">Kerjasama - Pelayanan Kesehatan</option>
<option value="Kerjasama - Management">Kerjasama - Management</option>
<option value="Kerjasama - Pemeliharan">Kerjasama - Pemeliharan</option>
<option value="Kerjasama - Diklat">Kerjasama - Diklat</option>
<option value="Kerjasama - Luar Negeri">Kerjasama - Luar Negeri</option>
<option value="Kerjasama - Area Bisnis">Kerjasama - Area Bisnis</option>
<option value="Kerjasama - Pendidikan">Kerjasama - Pendidikan</option>
<option value="Kerjasama - Pengampuan KIA">Kerjasama- Pengampuan KIA</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori lainnya</label>
<select class="form-select select-kat-lain" name="data[0][master_kategori_directory_id]" id="select_kategori_0" style="width: 350px;">
<option value="">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-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">

View File

@ -30,7 +30,7 @@
<thead class="table-light shadow-sm">
<tr>
<th style="width:5%;" class="text-center">#</th>
<th style="width:30%;">Unit / Akreditasi</th>
<th style="width:30%;">Unit</th>
<th style="width:20%;">Kategori</th>
<th style="width:15%;" class="text-center">Jumlah File</th>
</tr>

File diff suppressed because it is too large Load Diff

View File

@ -1,152 +0,0 @@
@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 Expired</h4>
<small class="text-muted">Ringkasan jumlah file per Unit dan Kategori</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 / Akreditasi</th>
<th style="width:20%;">Kategori</th>
<th style="width:15%;" class="text-center">Jumlah File</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/recapExp?' + 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;
}
let grandTotal = 0;
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.folder || '-'}</td>
<td class="text-center fw-bold">${f.count || 0}</td>
</tr>
`).join('');
(row.data || []).forEach(f => { grandTotal += (parseInt(f.count, 10) || 0); });
return folderRows;
}).join('');
tbody.innerHTML = html + `
<tr class="table-light">
<td colspan="3" class="text-end fw-semibold">Total File</td>
<td class="text-center fw-bold">${grandTotal}</td>
</tr>
`;
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>

View File

@ -41,21 +41,15 @@
<span class="hide-menu">Dokumen Umum</span>
</a>
</li>
@if(Auth::guard('admin')->check() || (Auth::check() && auth()->user()->dataUser->mappingUnitKerjaPegawai()->whereIn('objectunitkerjapegawaifk', [51, 22])->exists()))
<li class="sidebar-item">
<a class="sidebar-link" href="{{ url('/data-akreditasi') }}" aria-expanded="false">
<i class="fa-solid fa-sliders"></i>
<span class="hide-menu">Dokumen Akreditasi</span>
</a>
</li>
@endif
{{-- AKTIVITAS --}}
<li class="nav-small-cap"><span class="hide-menu">Aktivitas</span></li>
@php
$isAtasan = \App\Models\MappingUnitKerjaPegawai::where('statusenabled', true)->where('objectatasanlangsungfk', auth()->user()->objectpegawaifk)->exists();
@endphp
@if($isAtasan)
@if(!Auth::guard('admin')->check())
<li class="sidebar-item">
<a class="sidebar-link d-flex align-items-center justify-content-between"
href="{{ url('/pending-file') }}" aria-expanded="false">
@ -68,9 +62,7 @@
<span class="badge bg-danger rounded-pill d-none" id="pendingCountBadge">0</span>
</a>
</li>
@endif
@else
@if(!Auth::guard('admin')->check())
<li class="sidebar-item">
<a class="sidebar-link d-flex align-items-center justify-content-between"
href="{{ url('/pengajuan-file') }}" aria-expanded="false">
@ -82,7 +74,6 @@
</a>
</li>
@endif
@endif
<li class="sidebar-item">
<a class="sidebar-link d-flex align-items-center justify-content-between"
href="{{ url('/log-activity') }}" aria-expanded="false">
@ -106,14 +97,6 @@
</li> --}}
{{-- MASTER --}}
<li class="nav-small-cap"><span class="hide-menu">History</span></li>
<li class="sidebar-item">
<a class="sidebar-link" href="{{ url('/expired-dokumen') }}" aria-expanded="false">
<i class="ti ti-clock"></i>
<span class="hide-menu">Expired Dokumen</span>
</a>
</li>
@if(!Auth::guard('admin')->check())
@if(auth()->user()->dataUser->mappingUnitKerjaPegawai()->where('objectunitkerjapegawaifk', 43)->exists())
<li class="nav-small-cap"><span class="hide-menu">Master</span></li>
@ -154,7 +137,6 @@
</ul>
</li>
@endif
@endif
</ul>
</nav>
</div>

View File

@ -78,7 +78,7 @@
<div class="message-body">
<a href="javascript:void(0)" class="d-flex align-items-center gap-2 dropdown-item">
<i class="ti ti-user fs-6"></i>
<p class="mb-0 fs-3">{{ auth()->user()->namauser ?? 'admin' }}</p>
<p class="mb-0 fs-3">{{ auth()->user()->namauser }}</p>
</a>
<form action="/logout" method="POST">
@csrf

View File

@ -139,7 +139,7 @@ document.addEventListener('DOMContentLoaded', () => {
const paginationBtns = document.getElementById('logPaginationBtns');
const summaryText = document.getElementById('logSummaryText');
if(tbody) tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted py-3">Memuat...</td></tr>';
if(tbody) tbody.innerHTML = '<tr><td colspan="4" class="text-center text-muted py-3">Memuat...</td></tr>';
if(summaryText) summaryText.textContent = 'Memuat data...';
if(searchInput) searchInput.value = keyword || '';
@ -160,12 +160,11 @@ document.addEventListener('DOMContentLoaded', () => {
<td>${((currentPage - 1) * (pagination.per_page || 10)) + idx + 1}</td>
<td>${row.pegawai_nama_entry || '-'}</td>
<td>${row.total_open || 0}</td>
<td>${row.total_download || 0}</td>
<td>${formatTanggal(row.last_open)}</td>
</tr>
`).join('');
const emptyState = logs.length === 0 ? '<tr><td colspan="6" class="text-center text-muted py-3">Belum ada aktivitas</td></tr>' : '';
const emptyState = logs.length === 0 ? '<tr><td colspan="4" class="text-center text-muted py-3">Belum ada aktivitas</td></tr>' : '';
if(tbody) tbody.innerHTML = logs.length ? rows : emptyState;
if(summaryText){
@ -256,7 +255,7 @@ document.addEventListener('DOMContentLoaded', () => {
if(pageData.length === 0){
tbody.innerHTML = `
<tr>
<td colspan="7" class="text-center text-muted py-4">
<td colspan="6" class="text-center text-muted py-4">
Tidak ada data
</td>
</tr>
@ -342,7 +341,6 @@ document.addEventListener('DOMContentLoaded', () => {
<th>#</th>
<th>Nama</th>
<th>Jumlah Membuka</th>
<th>Jumlah Mengunduh</th>
<th>Terakhir Dilihat</th>
</tr>
</thead>

View File

@ -18,18 +18,27 @@
<div class="small text-muted">Perbarui detail dokumen sebelum mengirim ulang.</div>
</div>
</div>
<div class="col-md-6">
<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="id_unit_kerja" id="edit_id_unit_kerja" required>
<option value="" disabled selected>Pilih Unit</option>
</select>
</div>
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
<select class="form-control sub_unit_kerja" name="id_sub_unit_kerja" id="edit_id_sub_unit_kerja" 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-control" name="master_kategori_directory_id" id="edit_kategori" 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-4">
<label class="form-label fw-semibold">Nomor Dokumen</label>
@ -46,22 +55,22 @@
<label class="form-label fw-semibold">Tanggal Terbit</label>
<input class="form-control" type="date" name="tanggal_terbit" id="edit_tanggal_terbit">
</div>
<div class="col-md-4">
<div class="col-md-2 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input toggle-expired" type="checkbox" id="edit_has_expired" data-target="edit_expired_field">
<label class="form-check-label" for="edit_has_expired">Masa Berlaku Dokumen?</label>
<input class="form-check-input" type="checkbox" id="edit_has_expired" data-target="edit_expired_field">
<label class="form-check-label" for="edit_has_expired">Ada Expired?</label>
</div>
</div>
<div class="col-md-4" id="edit_expired_field">
<div class="col-md-5" id="edit_expired_field">
<label class="form-label fw-semibold">Tanggal Kedaluwarsa Dokumen</label>
<input class="form-control" type="date" name="tgl_expired" id="edit_tgl_expired" disabled>
</div>
<div class="col-md-4">
<div class="col-md-5">
<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="permission_file" id="edit_perm_yes" value="1" required>
<label class="form-check-label" for="edit_perm_yes">Ya</label>
<label class="form-check-label" for="edit_perm_yes">Iya</label>
</div>
<div class="form-check mt-1">
<input class="form-check-input" type="radio" name="permission_file" id="edit_perm_no" value="0" required>
@ -70,41 +79,6 @@
</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Instrumen Akreditasi</label>
<select class="form-select akre-select" id="edit_akre_select" name="akre" style="width: 350px;">
<option value="">Pilih Instrumen</option>
</select>
<div class="form-text text-muted">Isi form ini bila dokumen yang diunggah merupakan dokumen <strong>akreditasi</strong>.</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Hukum</label>
<select class="form-select select-kat-hukum" name="kategori_hukum" id="edit_kategori_hukum" style="width: 350px;">
<option value="">Pilih Kategori Hukum</option>
<option value="Kebijakan - Peraturan Direktur">Kebijakan - Peraturan Direktur</option>
<option value="Kebijakan - Keputusan Direktur Utama">Kebijakan - Keputusan Direktur Utama</option>
<option value="Kebijakan - Surat Edaran">Kebijakan - Surat Edaran</option>
<option value="Kebijakan - Pengumuman">Kebijakan - Pengumuman</option>
<option value="Kerjasama - Pelayanan Kesehatan">Kerjasama - Pelayanan Kesehatan</option>
<option value="Kerjasama - Management">Kerjasama - Management</option>
<option value="Kerjasama - Pemeliharan">Kerjasama - Pemeliharan</option>
<option value="Kerjasama - Diklat">Kerjasama - Diklat</option>
<option value="Kerjasama - Luar Negeri">Kerjasama - Luar Negeri</option>
<option value="Kerjasama - Area Bisnis">Kerjasama - Area Bisnis</option>
<option value="Kerjasama - Pendidikan">Kerjasama - Pendidikan</option>
<option value="Kerjasama - Pengampuan KIA">Kerjasama- Pengampuan KIA</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori lainnya</label>
<select class="form-select" name="master_kategori_directory_id" id="edit_kategori" style="width: 350px;">
<option value="">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-12 mb-2">
<label for="edit_file_upload" class="form-label fw-semibold">📂 Upload Dokumen (PDF)</label>
<div class="border rounded-3 p-3 bg-white shadow-sm">

View File

@ -9,15 +9,13 @@ use App\Http\Controllers\LogActivityController;
use App\Http\Controllers\masterPersetujuanController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth:admin,web'])->group(function(){
Route::middleware(['auth'])->group(function(){
Route::get('/', [DashboardController::class, 'index']);
Route::get('/data-internal', [DashboardController::class, 'dataUnitInternal']);
Route::get('/download-excel/data-unit', [DashboardController::class, 'downloadDataUnitExcel']);
Route::get('/data-umum', [DashboardController::class, 'dataUmum']);
Route::get('/datatable-umum', [DashboardController::class, 'datatableDataUmum']);
Route::get('/data-akreditasi', [DashboardController::class, 'dataAkreditasi']);
Route::get('/datatable-akreditasi', [DashboardController::class, 'dataTableAkreditasi']);
Route::get('/download-excel/data-umum', [DashboardController::class, 'downloadDataUmumExcel']);
Route::post('/uploadv2', [DashboardController::class, 'storeVersion2']);
Route::get('/file-preview/{id}', [DashboardController::class, 'dataPdf']);
@ -78,10 +76,6 @@ Route::middleware(['auth:admin,web'])->group(function(){
Route::post('/data/notifications/read', [DashboardController::class, 'notifkasiMarkRead']);
Route::get('/data/log-dokumen', [DashboardController::class, 'logDokumen']);
Route::get('/expired-dokumen', [DashboardController::class, 'expDokumen']);
Route::get('/data/expired-dokumen', [DashboardController::class, 'dataUnitExp']);
Route::get('/data/recapExp', [DashboardController::class, 'recapDataExp']);
});
Route::get('/login', [AuthController::class, 'index'])->name('login');