diff --git a/app/Http/Controllers/Ppid/JenisDokumenPpidController.php b/app/Http/Controllers/Ppid/JenisDokumenPpidController.php new file mode 100644 index 000000000..f48a37750 --- /dev/null +++ b/app/Http/Controllers/Ppid/JenisDokumenPpidController.php @@ -0,0 +1,403 @@ +share('page_title', $this->page_title); + view()->share('icons', $this->icons); + } + + public function index() + { + $page_description = 'Daftar Jenis Dokumen'; + + return view('ppid.jenis_dokumen.index', compact('page_description')); + } + + public function getData(Request $request) + { + $query = PpidJenisDokumen::query(); + + if ($request->filled('jenis_dokumen_id')) { + $query->where('id', $request->jenis_dokumen_id); + } + + if ($request->filled('status')) { + $query->where('status', $request->status); + } + + $searchValue = $request->input('search.value'); + if (!empty($searchValue)) { + $query->where(function ($q) use ($searchValue) { + $q->where('nama', 'like', "%{$searchValue}%") + ->orWhere('slug', 'like', "%{$searchValue}%") + ->orWhere('deskripsi', 'like', "%{$searchValue}%"); + }); + } + + return DataTables::of($query) + ->addIndexColumn() + ->order(function ($query) use ($request) { + + $allowedColumns = [ + 'id', + 'nama', + 'slug', + 'deskripsi', + 'kode', + 'icon', + 'urut', + 'status', + 'created_at', + 'updated_at' + ]; + + if ($request->has('order')) { + + $columns = $request->get('columns'); + $order = $request->get('order')[0]; + + $columnName = $columns[$order['column']]['data'] ?? null; + $dir = $order['dir'] === 'desc' ? 'desc' : 'asc'; + + if ($columnName && in_array($columnName, $allowedColumns)) { + $query->orderBy($columnName, $dir); + return; + } + } + + // default paling aman + $query->orderBy('urut', 'asc'); + }) + ->addColumn('checkbox', function ($row) { + $protectedSlugs = ['secara-berkala', 'serta-merta', 'tersedia-setiap-saat']; + $disabled = in_array($row->slug, $protectedSlugs) ? 'disabled' : ''; + return ''; + }) + ->addColumn('drag_handle', function ($row) { + return ''; + }) + ->addColumn('aksi', function ($row) { + $data = [ + 'id' => $row->id, + 'drag' => true, + ]; + + if (!auth()->guest()) { + $data['edit_url'] = route('ppid.jenis-dokumen.edit', $row->id); + + // Logic Protected Slugs + $protectedSlugs = ['secara-berkala', 'serta-merta', 'tersedia-setiap-saat']; + $data['delete_url'] = !in_array($row->slug, $protectedSlugs) + ? route('ppid.jenis-dokumen.destroy', $row->id) + : null; + + // Status URL Logic + $statusUrl = route('ppid.jenis-dokumen.status', $row->id); + if ($row->status == 0) { + $data['lock_url'] = $statusUrl; + $data['unlock_url'] = null; + } else { + $data['lock_url'] = null; + $data['unlock_url'] = $statusUrl; + } + } + + return view('forms.aksi', $data); + }) + ->editColumn('icon', function ($row) { + $bgColor = !empty($row->kode) ? $row->kode : '#ffffff'; + $iconColor = 'white'; + + return '
+ +
'; + }) + ->editColumn('status', function ($row) { + return $row->getStatusLabelAttribute(); + }) + ->rawColumns(['checkbox', 'drag_handle', 'aksi', 'icon', 'status']) + ->make(true); + } + + public function create() + { + $page_description = 'Tambah Jenis Dokumen'; + + return view('ppid.jenis_dokumen.create', compact('page_description')); + } + + public function store(Request $request) + { + $request->merge(['slug' => \Str::slug($request->nama)]); + $request->validate([ + 'nama' => 'required|string|max:150', + 'slug' => 'required|unique:ppid_jenis_dokumen,slug', + ], [ + 'slug.unique' => 'Nama Jenis Dokumen sudah ada.' + ]); + try { + PpidJenisDokumen::create([ + 'nama' => $request->nama, + 'slug' => Str::slug($request->nama), + 'deskripsi' => $request->deskripsi, + 'kode' => $request->kode ?? '#ffffff', + 'icon' => $request->icon ?? 'fa fa-file', + 'status' => '1', + 'urut' => (PpidJenisDokumen::max('urut') ?? 0) + 1, + ]); + + return redirect()->route('ppid.jenis-dokumen.index')->with('success', 'Jenis Dokumen berhasil ditambahkan'); + } catch (\Exception $e) { + report($e); + return back()->withInput()->with('error', 'Jenis Dokumen gagal disimpan!'); + } + } + + public function edit($id) + { + $jenis = PpidJenisDokumen::findOrFail($id); + $page_description = 'Edit Jenis Dokumen'; + + return view('ppid.jenis_dokumen.edit', compact('jenis', 'page_description')); + } + + public function update(Request $request, $id) + { + $slug = Str::slug($request->nama); + $request->merge(['slug' => $slug]); + $request->validate([ + 'nama' => 'required|string|max:150', + 'slug' => 'required|unique:ppid_jenis_dokumen,slug,' . $id, + ], [ + 'nama.required' => 'Nama Jenis Dokumen wajib diisi.', + 'slug.unique' => 'Nama ini sudah digunakan oleh dokumen lain.' + ]); + + try { + $jenis = PpidJenisDokumen::findOrFail($id); + $dataUpdate = $request->only(['nama', 'deskripsi', 'kode', 'icon', 'status']); + + $jenis->update($dataUpdate); + return redirect()->route('ppid.jenis-dokumen.index')->with('success', 'Jenis Dokumen berhasil diubah'); + } catch (\Exception $e) { + report($e); + return back()->withInput()->with('error', 'Jenis Dokumen gagal diubah!'); + } + } + + public function destroy($id) + { + try { + $jenis = PpidJenisDokumen::findOrFail($id); + $protectedSlugs = ['secara-berkala', 'serta-merta', 'tersedia-setiap-saat']; + + if (in_array($jenis->slug, $protectedSlugs)) { + return back()->with('error', 'Jenis Dokumen tidak boleh dihapus!'); + } + + $jenis->delete(); + return back()->with('success', 'Jenis Dokumen berhasil dihapus'); + } catch (\Exception $e) { + report($e); + return back()->with('error', 'Jenis Dokumen gagal dihapus!'); + } + } + + /** + * Bulk Delete - Hapus Multiple Records + */ + public function bulkDelete(Request $request) + { + $request->validate([ + 'ids' => 'required|array|min:1', + 'ids.*' => 'required|integer|exists:ppid_jenis_dokumen,id' + ], [ + 'ids.required' => 'Tidak ada data yang dipilih', + 'ids.array' => 'Format data tidak valid', + 'ids.min' => 'Pilih minimal 1 data untuk dihapus', + 'ids.*.exists' => 'Data tidak ditemukan' + ]); + + try { + $ids = $request->ids; + $protectedSlugs = ['secara-berkala', 'serta-merta', 'tersedia-setiap-saat']; + + $protectedItems = PpidJenisDokumen::whereIn('id', $ids) + ->whereIn('slug', $protectedSlugs) + ->count(); + + if ($protectedItems > 0) { + return response()->json([ + 'success' => false, + 'message' => 'Beberapa data tidak dapat dihapus karena dilindungi sistem!' + ], 422); + } + + DB::beginTransaction(); + + $deletedCount = PpidJenisDokumen::whereIn('id', $ids) + ->whereNotIn('slug', $protectedSlugs) + ->delete(); + + DB::commit(); + + if ($deletedCount > 0) { + return response()->json([ + 'success' => true, + 'message' => "Berhasil menghapus {$deletedCount} data" + ]); + } else { + return response()->json([ + 'success' => false, + 'message' => 'Tidak ada data yang dapat dihapus' + ], 422); + } + } catch (\Exception $e) { + DB::rollBack(); + report($e); + + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat menghapus data: ' . $e->getMessage() + ], 500); + } + } + + /** + * Fitur Drag & Drop untuk menyimpan urutan baru + */ + public function updateOrder(Request $request) + { + // Validasi request + $request->validate([ + 'order' => 'required|array|min:1', + 'order.*.id' => 'required|integer|exists:ppid_jenis_dokumen,id', + 'order.*.position' => 'required|integer|min:1' + ], [ + 'order.required' => 'Data urutan tidak ditemukan', + 'order.array' => 'Format data tidak valid', + 'order.*.id.exists' => 'Data tidak ditemukan' + ]); + + try { + DB::beginTransaction(); + + foreach ($request->order as $item) { + PpidJenisDokumen::where('id', $item['id']) + ->update(['urut' => $item['position']]); + } + + DB::commit(); + + if ($request->ajax()) { + + return response()->json([ + 'success' => true, + 'message' => 'Urutan Jenis Dokumen berhasil diperbarui' + ]); + } + + return redirect()->route('ppid.jenis-dokumen.index') + ->with('success', 'Urutan Jenis Dokumen berhasil diperbarui'); + } catch (\Exception $e) { + DB::rollBack(); + report($e); + + if ($request->ajax()) { + + return response()->json([ + 'success' => false, + 'message' => 'Gagal memperbarui urutan: ' . $e->getMessage() + ], 500); + } + + return back()->with('error', 'Gagal memperbarui urutan!'); + } + } + + /** + * Update status + */ + public function status($id) + { + try { + $jenis = PpidJenisDokumen::findOrFail($id); + $jenis->status = ($jenis->status == '1') ? '0' : '1'; + $jenis->save(); + + return redirect()->route('ppid.jenis-dokumen.index') + ->with('success', 'Status Jenis Dokumen berhasil diubah!'); + } catch (\Exception $e) { + report($e); + return back()->with('error', 'Status Jenis Dokumen gagal diubah!'); + } + } +} diff --git a/app/Models/PpidJenisDokumen.php b/app/Models/PpidJenisDokumen.php new file mode 100644 index 000000000..ab8ae82c6 --- /dev/null +++ b/app/Models/PpidJenisDokumen.php @@ -0,0 +1,60 @@ +slug)) { + $model->slug = \Str::slug($model->nama); + } + }); + + // PROTEKSI: Mencegah perubahan slug untuk 3 data utama saat update + static::updating(function ($model) { + $protectedSlugs = ['secara-berkala', 'serta-merta', 'tersedia-setiap-saat']; + + // Jika slug lama masuk daftar proteksi, kembalikan slug ke nilai originalnya + if (in_array($model->getOriginal('slug'), $protectedSlugs)) { + $model->slug = $model->getOriginal('slug'); + } else { + // Untuk data non-proteksi, slug boleh berubah mengikuti nama + if ($model->isDirty('nama')) { + $model->slug = \Str::slug($model->nama); + } + } + }); + } + + public function scopeAktif($query) + { + return $query->where('status', '1'); + } + + public function getStatusLabelAttribute() + { + return $this->status == '1' + ? 'Aktif' + : 'Tidak-Aktif'; + } +} diff --git a/database/migrations/2026_01_29_152221_create_ppid_jenis_dokumen_table.php b/database/migrations/2026_01_29_152221_create_ppid_jenis_dokumen_table.php new file mode 100644 index 000000000..c03447765 --- /dev/null +++ b/database/migrations/2026_01_29_152221_create_ppid_jenis_dokumen_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('nama', 150); + $table->string('slug', 150)->unique(); + $table->text('deskripsi')->nullable(); + $table->string('kode', 50)->nullable(); + $table->string('icon', 100)->nullable(); + $table->integer('urut')->default(0); + $table->enum('status', ['0', '1'])->default('1')->comment('0=tidak aktif, 1=aktif'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('ppid_jenis_dokumen'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 484a6adec..b23794a13 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -74,6 +74,7 @@ public function run() $this->call(DasProfilTableSeeder::class); $this->call(DasDataUmumTableSeeder::class); $this->call(PendudukSexSeeder::class); + $this->call(PpidJenisDokumenSeeder::class); DB::statement('SET FOREIGN_KEY_CHECKS=1;'); } diff --git a/database/seeders/PpidJenisDokumenSeeder.php b/database/seeders/PpidJenisDokumenSeeder.php new file mode 100644 index 000000000..5118613e6 --- /dev/null +++ b/database/seeders/PpidJenisDokumenSeeder.php @@ -0,0 +1,58 @@ + 'Secara Berkala', + 'slug' => 'secara-berkala', + 'deskripsi' => 'Informasi yang rutin diterbitkan dan diperbaharui untuk publik.', + 'kode' => '#0e15d8', + 'icon' => 'fa fa-calendar', + 'urut' => 1, + 'status' => '1', + ], + [ + 'nama' => 'Serta Merta', + 'slug' => 'serta-merta', + 'deskripsi' => 'Informasi yang wajib diumumkan segera karena penting bagi masyarakat.', + 'kode' => '#059e17', + 'icon' => 'fa fa-bolt', + 'urut' => 2, + 'status' => '1', + ], + [ + 'nama' => 'Tersedia Setiap Saat', + 'slug' => 'tersedia-setiap-saat', + 'deskripsi' => 'Informasi yang tersedia dan dapat diakses setiap saat.', + 'kode' => '#ff9900', + 'icon' => 'fa fa-globe', + 'urut' => 3, + 'status' => '1', + ], + ]; + + foreach ($data as $item) { + PpidJenisDokumen::updateOrCreate( + ['slug' => $item['slug']], + array_merge($item, [ + 'created_at' => now(), + 'updated_at' => now(), + ]) + ); + } + } +} diff --git a/resources/views/layouts/fragments/sidebar.blade.php b/resources/views/layouts/fragments/sidebar.blade.php index 7a790cfb0..0b2586ca3 100644 --- a/resources/views/layouts/fragments/sidebar.blade.php +++ b/resources/views/layouts/fragments/sidebar.blade.php @@ -421,6 +421,24 @@ class="fa fa-files-o">Permohonan @endif + @if ($user->hasrole(['super-admin', 'admin-kecamatan'])) +
  • + PPID + + + + + + +
  • + @endif + @if ($user->hasrole(['super-admin', 'administrator-website']))
  • diff --git a/resources/views/ppid/jenis_dokumen/_form.blade.php b/resources/views/ppid/jenis_dokumen/_form.blade.php new file mode 100644 index 000000000..39ec15278 --- /dev/null +++ b/resources/views/ppid/jenis_dokumen/_form.blade.php @@ -0,0 +1,91 @@ +
    +
    +
    + +
    +
    + + {!! html()->text('nama', old('nama', $jenis->nama ?? null))->class('form-control')->placeholder('Masukkan nama dokumen...')->id('nama') !!} + @if ($errors->has('nama')) + {{ $errors->first('nama') }} + @endif + @if ($errors->has('slug')) + {{ $errors->first('slug') }} + @endif +
    + +
    + + {!! html()->textarea('deskripsi', old('deskripsi', $jenis->deskripsi ?? null))->class('form-control')->rows(3)->placeholder('Penjelasan singkat...')->id('deskripsi') !!} +
    +
    +
    +
    + +
    +
    +
    +
    + + {!! html()->input('color', 'kode', old('kode', $jenis->kode ?? '#000000'))->class('form-control')->style('height: 38px;') !!} +
    + +
    + +
    + + + + + +
    + +
    +
    +
    + +
    +
    + @foreach ($icons as $icon) +
    + +
    + @endforeach +
    +
    + +
    + + {!! html()->select('status', ['1' => 'Aktif', '0' => 'Tidak Aktif'], old('status', $jenis->status ?? '1'))->class('form-control')->id('status') !!} +
    +
    + +
    +
    +
    + +@push('scripts') + +@endpush diff --git a/resources/views/ppid/jenis_dokumen/create.blade.php b/resources/views/ppid/jenis_dokumen/create.blade.php new file mode 100644 index 000000000..0b5259bbe --- /dev/null +++ b/resources/views/ppid/jenis_dokumen/create.blade.php @@ -0,0 +1,18 @@ +@extends('layouts.dashboard_template') + +@section('content') +
    +

    {{ $page_title }} {{ $page_description }}

    + +
    + +
    + {!! html()->form('POST', route('ppid.jenis-dokumen.store'))->id('form-ppid')->open() !!} + @include('ppid.jenis_dokumen._form') + {!! html()->form()->close() !!} +
    +@endsection diff --git a/resources/views/ppid/jenis_dokumen/edit.blade.php b/resources/views/ppid/jenis_dokumen/edit.blade.php new file mode 100644 index 000000000..cf734c6db --- /dev/null +++ b/resources/views/ppid/jenis_dokumen/edit.blade.php @@ -0,0 +1,20 @@ +@extends('layouts.dashboard_template') + +@section('content') +
    +

    + {{ $page_title }} + {{ $page_description }} +

    + +
    +
    + {!! html()->form('PUT', route('ppid.jenis-dokumen.update', $jenis->id))->attribute('enctype', 'multipart/form-data')->open() !!} + @include('ppid.jenis_dokumen._form') + {!! html()->form()->close() !!} +
    +@endsection diff --git a/resources/views/ppid/jenis_dokumen/index.blade.php b/resources/views/ppid/jenis_dokumen/index.blade.php new file mode 100644 index 000000000..d7867805f --- /dev/null +++ b/resources/views/ppid/jenis_dokumen/index.blade.php @@ -0,0 +1,381 @@ +@extends('layouts.dashboard_template') + +@section('content') +
    +

    {{ $page_title }} {{ $page_description }}

    + +
    + +
    + @include('partials.flash_message') + +
    +
    + +  Tambah + + +
    +
    + +
    +
    +
    +
    + {!! html()->select('status')->options([ + 1 => 'Aktif', + 0 => 'Tidak Aktif', + ])->class('form-control filter-input')->placeholder('Semua')->id('filter_status') !!} +
    +
    +
    +
    + +
    + + + + + + + + + + + + + +
    + + No#AksiNama Jenis DokumenDeskripsiIconStatus
    +
    +
    +
    +
    + + + +@endsection + +@include('partials.asset_datatables') +@push('scripts') + + + + + + + + @include('forms.datatable-vertical') + @include('forms.delete-modal') + @include('forms.lock-modal') + @include('forms.unlock-modal') +@endpush diff --git a/routes/web.php b/routes/web.php index 70fbbe3af..c166b6afa 100644 --- a/routes/web.php +++ b/routes/web.php @@ -216,17 +216,17 @@ Route::group(['prefix' => 'statistik'], function () { Route::get('kependudukan', 'KependudukanController@showKependudukan')->name('statistik.kependudukan'); - Route::get('show-kependudukan', 'KependudukanController@showKependudukanPartial')->name('statistik.show-kependudukan'); + Route::get('show-kependudukan', 'KependudukanController@showKependudukanPartial')->name('statistik.show-kependudukan'); - Route::get('pendidikan', 'PendidikanController@showPendidikan')->name('statistik.pendidikan'); + Route::get('pendidikan', 'PendidikanController@showPendidikan')->name('statistik.pendidikan'); - Route::get('program-dan-bantuan', 'ProgramBantuanController@showProgramBantuan')->name('statistik.program-bantuan'); + Route::get('program-dan-bantuan', 'ProgramBantuanController@showProgramBantuan')->name('statistik.program-bantuan'); - Route::get('anggaran-dan-realisasi', 'AnggaranRealisasiController@showAnggaranDanRealisasi')->name('statistik.anggaran-dan-realisasi'); + Route::get('anggaran-dan-realisasi', 'AnggaranRealisasiController@showAnggaranDanRealisasi')->name('statistik.anggaran-dan-realisasi'); - Route::get('anggaran-desa', 'AnggaranDesaController@showAnggaranDesa')->name('statistik.anggaran-desa'); + Route::get('anggaran-desa', 'AnggaranDesaController@showAnggaranDesa')->name('statistik.anggaran-desa'); - Route::get('kesehatan', 'KesehatanController@showKesehatan')->name('statistik.kesehatan'); + Route::get('kesehatan', 'KesehatanController@showKesehatan')->name('statistik.kesehatan'); }); Route::group(['prefix' => 'unduhan'], function () { @@ -234,7 +234,7 @@ Route::group(['prefix' => 'prosedur'], function () { Route::permanentRedirect('/', '/'); - Route::get('/', 'DownloadController@indexProsedur')->name('unduhan.prosedur'); + Route::get('/', 'DownloadController@indexProsedur')->name('unduhan.prosedur'); Route::get('{file}/download', 'DownloadController@downloadProsedur')->name('unduhan.prosedur.download'); }); @@ -835,6 +835,27 @@ }); }); + /** + * Group Routing for PPID + */ + Route::namespace('\App\Http\Controllers\Ppid')->group(function () { + + Route::group(['prefix' => 'ppid', 'middleware' => ['role:super-admin|admin-kecamatan']], function () { + Route::group(['prefix' => 'jenis-dokumen'], function () { + Route::get('/', ['as' => 'ppid.jenis-dokumen.index', 'uses' => 'JenisDokumenPpidController@index']); + Route::get('create', ['as' => 'ppid.jenis-dokumen.create', 'uses' => 'JenisDokumenPpidController@create']); + Route::get('getdata', ['as' => 'ppid.jenis-dokumen.getdata', 'uses' => 'JenisDokumenPpidController@getData']); + Route::post('store', ['as' => 'ppid.jenis-dokumen.store', 'uses' => 'JenisDokumenPpidController@store']); + Route::get('edit/{jenis_dokumen}', ['as' => 'ppid.jenis-dokumen.edit', 'uses' => 'JenisDokumenPpidController@edit']); + Route::put('update/{jenis_dokumen}', ['as' => 'ppid.jenis-dokumen.update', 'uses' => 'JenisDokumenPpidController@update']); + Route::delete('destroy/{jenis_dokumen}', ['as' => 'ppid.jenis-dokumen.destroy', 'uses' => 'JenisDokumenPpidController@destroy']); + Route::post('/bulk-delete', ['as' => 'ppid.jenis-dokumen.bulk-delete', 'uses' => 'JenisDokumenPpidController@bulkDelete']); + Route::put('status/{jenis_dokumen}', ['as' => 'ppid.jenis-dokumen.status', 'uses' => 'JenisDokumenPpidController@status']); + Route::post('update-order', ['as' => 'ppid.jenis-dokumen.update-order', 'uses' => 'JenisDokumenPpidController@updateOrder']); + }); + }); + }); + /** * Group Routing for Setting */ @@ -844,16 +865,16 @@ Route::get('/', 'index')->name('setting.user.index'); Route::get('getdata', 'getDataUser')->name('setting.user.getdata'); Route::get('create', 'create')->name('setting.user.create'); - Route::post('store', 'store')->name('setting.user.store'); + Route::post('store', 'store')->name('setting.user.store'); Route::put('updatePassword/{id}', 'updatePassword')->name('setting.user.updatePassword'); Route::put('password/{id}', 'password')->name('setting.user.password'); Route::post('destroy/{id}', 'destroy')->name('setting.user.destroy'); - Route::post('active/{id}', 'active')->name('setting.user.active'); + Route::post('active/{id}', 'active')->name('setting.user.active'); }); - Route::group(['prefix' => 'user', 'controller' => UserController::class], function () { + Route::group(['prefix' => 'user', 'controller' => UserController::class], function () { Route::get('edit/{id}', 'edit')->name('setting.user.edit'); - Route::put('update/{id}', 'update')->name('setting.user.update'); + Route::put('update/{id}', 'update')->name('setting.user.update'); Route::get('photo-profil/{id}', 'photo')->name('setting.user.photo'); Route::put('update-photo/{id}', 'updatePhoto')->name('setting.user.uphoto'); }); @@ -939,7 +960,7 @@ Route::get('/', 'index')->name('setting.themes.index'); Route::get('activate/{themes}', 'activate')->name('setting.themes.activate'); Route::get('rescan', 'rescan')->name('setting.themes.rescan'); - Route::post('clear-cache', 'clearCache')->name('setting.themes.clear-cache'); + Route::post('clear-cache', 'clearCache')->name('setting.themes.clear-cache'); // post to-upload Route::post('upload', 'upload')->name('setting.themes.upload'); Route::delete('destroy/{themes}', 'destroy')->name('setting.themes.destroy'); diff --git a/tests/Feature/PpidJenisDokumenTest.php b/tests/Feature/PpidJenisDokumenTest.php new file mode 100644 index 000000000..01c5f6855 --- /dev/null +++ b/tests/Feature/PpidJenisDokumenTest.php @@ -0,0 +1,555 @@ +withoutMiddleware([ + Authenticate::class, + RoleMiddleware::class, + PermissionMiddleware::class, + CompleteProfile::class, + GlobalShareMiddleware::class, + ]); + + // Sesuaikan dengan kolom tabel profil Anda + $profil = Profil::create([ + 'provinsi_id' => '73', + 'nama_provinsi' => 'SULAWESI SELATAN', + 'kabupaten_id' => '73.08', + 'nama_kabupaten' => 'BONE', + 'kecamatan_id' => '73.08.02', + 'nama_kecamatan' => 'Kahu', + 'alamat' => 'Jl. Test No. 1', + 'kode_pos' => '92767', + 'telepon' => '082290050133', + 'email' => 'admin@mail.com', + 'tahun_pembentukan' => '2000', + 'dasar_pembentukan' => 'xxx', + // Tambahkan bts_wil jika memang kolom ini ada di migrasi Anda + 'bts_wil_utara' => '0', + 'bts_wil_timur' => '0', + 'bts_wil_selatan' => '0', + 'bts_wil_barat' => '0', + ]); + + view()->share([ + 'profil' => $profil, + 'browser_title' => 'Testing OpenDK', + 'page_title' => 'PPID', + 'page_description' => 'Daftar Jenis Dokumen', + // OpenDK sering butuh icons di view ppid + 'icons' => ['fa fa-file', 'fa fa-book'] + ]); + + $this->user = User::factory()->create(); + $this->tableName = (new PpidJenisDokumen())->getTable(); +}); + +/* =================================================================== + INDEX & VIEW + =================================================================== */ + +test('halaman indeks jenis dokumen ppid dapat diakses', function () { + $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.index')) + ->assertStatus(200) + ->assertViewIs('ppid.jenis_dokumen.index') + ->assertViewHas('page_description', 'Daftar Jenis Dokumen'); +}); + +test('halaman tambah jenis dokumen dapat diakses', function () { + $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.create')) + ->assertStatus(200) + ->assertViewIs('ppid.jenis_dokumen.create') + ->assertViewHas('page_description', 'Tambah Jenis Dokumen'); +}); + +test('halaman edit jenis dokumen dapat diakses', function () { + $doc = PpidJenisDokumen::create([ + 'nama' => 'Test Document', + 'slug' => 'test-document', + 'status' => 1, + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.edit', $doc->id)) + ->assertStatus(200) + ->assertViewIs('ppid.jenis_dokumen.edit') + ->assertViewHas('jenis') + ->assertViewHas('page_description', 'Edit Jenis Dokumen'); +}); + +/* =================================================================== + CREATE - Validasi & Simpan Data + =================================================================== */ + +test('admin dapat menambah jenis dokumen baru dengan data lengkap', function () { + $data = [ + 'nama' => 'Dokumen Transparansi Anggaran', + 'deskripsi' => 'Rincian anggaran tahunan', + 'kode' => '#3498db', + 'icon' => 'fa fa-money', + ]; + + $this->actingAs($this->user) + ->post(route('ppid.jenis-dokumen.store'), $data) + ->assertRedirect(route('ppid.jenis-dokumen.index')) + ->assertSessionHas('success', 'Jenis Dokumen berhasil ditambahkan'); + + $this->assertDatabaseHas($this->tableName, [ + 'nama' => 'Dokumen Transparansi Anggaran', + 'slug' => 'dokumen-transparansi-anggaran', + 'deskripsi' => 'Rincian anggaran tahunan', + 'kode' => '#3498db', + 'icon' => 'fa fa-money', + 'status' => '1', + ]); +}); + +test('sistem otomatis mengisi nilai default saat data tidak lengkap', function () { + $data = [ + 'nama' => 'Dokumen Minimal' + ]; + + $this->actingAs($this->user) + ->post(route('ppid.jenis-dokumen.store'), $data) + ->assertRedirect(route('ppid.jenis-dokumen.index')) + ->assertSessionHas('success'); + + $this->assertDatabaseHas($this->tableName, [ + 'nama' => 'Dokumen Minimal', + 'slug' => 'dokumen-minimal', + 'kode' => '#ffffff', + 'icon' => 'fa fa-file', + 'status' => '1', + ]); +}); + +test('nama jenis dokumen wajib diisi', function () { + $this->actingAs($this->user) + ->post(route('ppid.jenis-dokumen.store'), ['nama' => '']) + ->assertSessionHasErrors('nama'); +}); + +test('nama tidak boleh lebih dari 150 karakter', function () { + $this->actingAs($this->user) + ->post(route('ppid.jenis-dokumen.store'), [ + 'nama' => str_repeat('a', 151) + ]) + ->assertSessionHasErrors('nama'); +}); + +test('sistem mencegah duplikasi slug saat create', function () { + PpidJenisDokumen::create([ + 'nama' => 'Existing Document', + 'slug' => 'existing-document', + 'status' => 1, + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->post(route('ppid.jenis-dokumen.store'), [ + 'nama' => 'Existing Document' + ]) + ->assertSessionHasErrors('slug'); +}); + +test('urutan otomatis diset ke posisi terakhir saat create', function () { + PpidJenisDokumen::create(['nama' => 'First', 'slug' => 'first', 'status' => 1, 'urut' => 5]); + PpidJenisDokumen::create(['nama' => 'Second', 'slug' => 'second', 'status' => 1, 'urut' => 10]); + + $this->actingAs($this->user) + ->post(route('ppid.jenis-dokumen.store'), ['nama' => 'New Document']) + ->assertSessionHas('success'); + + $this->assertDatabaseHas($this->tableName, [ + 'nama' => 'New Document', + 'urut' => 11 + ]); +}); + +/* =================================================================== + UPDATE - Edit & Validasi + =================================================================== */ + +test('admin dapat mengedit jenis dokumen', function () { + $doc = PpidJenisDokumen::create([ + 'nama' => 'Original Name', + 'slug' => 'original-name', + 'deskripsi' => 'Original Description', + 'kode' => '#000000', + 'icon' => 'fa fa-file', + 'status' => 1, + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->put(route('ppid.jenis-dokumen.update', $doc->id), [ + 'nama' => 'Updated Name', + 'deskripsi' => 'New description', + 'kode' => '#ff5733', + 'icon' => 'fa fa-folder', + 'status' => 1 + ]) + ->assertRedirect(route('ppid.jenis-dokumen.index')) + ->assertSessionHas('success', 'Jenis Dokumen berhasil diubah'); + + $this->assertDatabaseHas($this->tableName, [ + 'id' => $doc->id, + 'nama' => 'Updated Name', + 'slug' => 'updated-name', + 'deskripsi' => 'New description', + 'kode' => '#ff5733', + 'icon' => 'fa fa-folder' + ]); +}); + +test('sistem mencegah duplikasi slug saat update', function () { + $doc1 = PpidJenisDokumen::create(['nama' => 'First', 'slug' => 'first', 'status' => 1, 'urut' => 1]); + $doc2 = PpidJenisDokumen::create(['nama' => 'Second', 'slug' => 'second', 'status' => 1, 'urut' => 2]); + + $this->actingAs($this->user) + ->put(route('ppid.jenis-dokumen.update', $doc2->id), [ + 'nama' => 'First' + ]) + ->assertSessionHasErrors('slug'); +}); + +test('update mengizinkan slug yang sama dengan data sendiri', function () { + $doc = PpidJenisDokumen::create([ + 'nama' => 'Original', + 'slug' => 'original', + 'status' => 1, + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->put(route('ppid.jenis-dokumen.update', $doc->id), [ + 'nama' => 'Original', + 'deskripsi' => 'Updated description only' + ]) + ->assertRedirect(route('ppid.jenis-dokumen.index')) + ->assertSessionHas('success'); + + $this->assertDatabaseHas($this->tableName, [ + 'id' => $doc->id, + 'nama' => 'Original', + 'slug' => 'original' + ]); +}); + +/* =================================================================== + DELETE - Hapus Data + =================================================================== */ + +test('admin dapat menghapus jenis dokumen yang tidak diproteksi dengan soft delete', function () { + $doc = PpidJenisDokumen::create([ + 'nama' => 'Deleteable Document', + 'slug' => 'deleteable-document', + 'status' => 1, + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->delete(route('ppid.jenis-dokumen.destroy', $doc->id)) + ->assertRedirect() + ->assertSessionHas('success', 'Jenis Dokumen berhasil dihapus'); + + // Soft delete: data masih ada tapi deleted_at terisi + $this->assertSoftDeleted($this->tableName, ['id' => $doc->id]); +}); + +test('sistem mencegah penghapusan jenis dokumen yang diproteksi', function () { + $protectedSlugs = ['secara-berkala', 'serta-merta', 'tersedia-setiap-saat']; + + foreach ($protectedSlugs as $slug) { + $doc = PpidJenisDokumen::create([ + 'nama' => ucwords(str_replace('-', ' ', $slug)), + 'slug' => $slug, + 'status' => 1, + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->delete(route('ppid.jenis-dokumen.destroy', $doc->id)) + ->assertSessionHas('error', 'Jenis Dokumen tidak boleh dihapus!'); + + $this->assertDatabaseHas($this->tableName, ['id' => $doc->id]); + } +}); + +/* =================================================================== + BULK DELETE + =================================================================== */ + +test('admin dapat menghapus beberapa jenis dokumen sekaligus dengan soft delete', function () { + $doc1 = PpidJenisDokumen::create(['nama' => 'Delete 1', 'slug' => 'delete-1', 'status' => 1, 'urut' => 1]); + $doc2 = PpidJenisDokumen::create(['nama' => 'Delete 2', 'slug' => 'delete-2', 'status' => 1, 'urut' => 2]); + $doc3 = PpidJenisDokumen::create(['nama' => 'Delete 3', 'slug' => 'delete-3', 'status' => 1, 'urut' => 3]); + + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.bulk-delete'), [ + 'ids' => [$doc1->id, $doc2->id, $doc3->id] + ]) + ->assertStatus(200) + ->assertJson([ + 'success' => true, + 'message' => 'Berhasil menghapus 3 data' + ]); + + // Soft delete: data masih ada tapi deleted_at terisi + $this->assertSoftDeleted($this->tableName, ['id' => $doc1->id]); + $this->assertSoftDeleted($this->tableName, ['id' => $doc2->id]); + $this->assertSoftDeleted($this->tableName, ['id' => $doc3->id]); +}); + +test('bulk delete menolak jika ada data terproteksi dalam daftar', function () { + $regular = PpidJenisDokumen::create(['nama' => 'Regular', 'slug' => 'regular', 'status' => 1, 'urut' => 1]); + $protected = PpidJenisDokumen::create(['nama' => 'Serta Merta', 'slug' => 'serta-merta', 'status' => 1, 'urut' => 2]); + + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.bulk-delete'), [ + 'ids' => [$regular->id, $protected->id] + ]) + ->assertStatus(422) + ->assertJson([ + 'success' => false, + 'message' => 'Beberapa data tidak dapat dihapus karena dilindungi sistem!' + ]); + + $this->assertDatabaseHas($this->tableName, ['id' => $regular->id]); + $this->assertDatabaseHas($this->tableName, ['id' => $protected->id]); +}); + +test('bulk delete validasi array tidak boleh kosong', function () { + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.bulk-delete'), ['ids' => []]) + ->assertStatus(422) + ->assertJsonValidationErrors('ids'); +}); + +test('bulk delete validasi id harus exists di database', function () { + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.bulk-delete'), [ + 'ids' => [99999] + ]) + ->assertStatus(422) + ->assertJsonValidationErrors('ids.0'); +}); + +/* =================================================================== + STATUS TOGGLE + =================================================================== */ + +test('admin dapat mengubah status dari aktif ke nonaktif', function () { + $doc = PpidJenisDokumen::create([ + 'nama' => 'Active Document', + 'slug' => 'active-document', + 'status' => '1', + 'urut' => 1 + ]); + + $response = $this->actingAs($this->user) + ->put(route('ppid.jenis-dokumen.status', $doc->id)); + + $response->assertStatus(302) + ->assertRedirect(route('ppid.jenis-dokumen.index')) + ->assertSessionHas('success', 'Status Jenis Dokumen berhasil diubah!'); + + $doc->refresh(); + + $this->assertDatabaseHas($this->tableName, [ + 'id' => $doc->id, + 'status' => '0' + ]); + + // Atau menggunakan expect untuk lebih presisi + expect($doc->status)->toEqual('0'); +}); + +test('admin dapat mengubah status dari nonaktif ke aktif', function () { + $doc = PpidJenisDokumen::create([ + 'nama' => 'Inactive Document', + 'slug' => 'inactive-document', + 'status' => '0', + 'urut' => 1 + ]); + + $this->actingAs($this->user) + ->put(route('ppid.jenis-dokumen.status', $doc->id)) + ->assertRedirect(route('ppid.jenis-dokumen.index')) + ->assertSessionHas('success'); + + $this->assertDatabaseHas($this->tableName, [ + 'id' => $doc->id, + 'status' => '1' + ]); +}); + +/* =================================================================== + ORDERING - Drag & Drop + =================================================================== */ + +test('update order validasi array tidak boleh kosong', function () { + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.update-order'), ['order' => []]) + ->assertStatus(422) + ->assertJsonValidationErrors('order'); +}); + +test('update order validasi id harus exists', function () { + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.update-order'), [ + 'order' => [ + ['id' => 99999, 'position' => 1] + ] + ]) + ->assertStatus(422) + ->assertJsonValidationErrors('order.0.id'); +}); + +test('update order validasi position harus integer minimal 1', function () { + $doc = PpidJenisDokumen::create(['nama' => 'Test', 'slug' => 'test', 'status' => 1, 'urut' => 1]); + + $this->actingAs($this->user) + ->postJson(route('ppid.jenis-dokumen.update-order'), [ + 'order' => [ + ['id' => $doc->id, 'position' => 0] + ] + ]) + ->assertStatus(422) + ->assertJsonValidationErrors('order.0.position'); +}); + +/* =================================================================== + DATATABLES + =================================================================== */ + +test('dapat mengambil data jenis dokumen melalui ajax datatables', function () { + PpidJenisDokumen::create(['nama' => 'Document A', 'slug' => 'document-a', 'status' => '1', 'urut' => 1]); + PpidJenisDokumen::create(['nama' => 'Document B', 'slug' => 'document-b', 'status' => '0', 'urut' => 2]); + + $response = $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.getdata')); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'data', + 'recordsTotal', + 'recordsFiltered', + 'draw' + ]); +}); + +test('datatables dapat filter berdasarkan jenis_dokumen_id', function () { + $doc1 = PpidJenisDokumen::create(['nama' => 'First', 'slug' => 'first', 'status' => 1, 'urut' => 1]); + $doc2 = PpidJenisDokumen::create(['nama' => 'Second', 'slug' => 'second', 'status' => 1, 'urut' => 2]); + + $response = $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.getdata', ['jenis_dokumen_id' => $doc1->id])); + + $response->assertStatus(200); + + $data = $response->json(); + expect($data['recordsFiltered'])->toBe(1); +}); + +test('datatables dapat filter berdasarkan status', function () { + PpidJenisDokumen::create(['nama' => 'Active', 'slug' => 'active', 'status' => '1', 'urut' => 1]); + PpidJenisDokumen::create(['nama' => 'Inactive', 'slug' => 'inactive', 'status' => '0', 'urut' => 2]); + + $response = $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.getdata', ['status' => 1])); + + $response->assertStatus(200); + + $data = $response->json(); + expect($data['recordsFiltered'])->toBe(1); +}); + +test('datatables default sorting berdasarkan urut ascending', function () { + $doc1 = PpidJenisDokumen::create(['nama' => 'Third', 'slug' => 'third', 'status' => 1, 'urut' => 3]); + $doc2 = PpidJenisDokumen::create(['nama' => 'First', 'slug' => 'first', 'status' => 1, 'urut' => 1]); + $doc3 = PpidJenisDokumen::create(['nama' => 'Second', 'slug' => 'second', 'status' => 1, 'urut' => 2]); + + $response = $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.getdata')); + + $response->assertStatus(200); + + $data = $response->json('data'); + expect($data[0]['nama'])->toBe('First'); + expect($data[1]['nama'])->toBe('Second'); + expect($data[2]['nama'])->toBe('Third'); +}); + +/* =================================================================== + STATUS - Enum & Label + =================================================================== */ + +test('status menggunakan enum dengan nilai 0 dan 1', function () { + $inactive = PpidJenisDokumen::create(['nama' => 'Inactive', 'slug' => 'inactive', 'status' => '0', 'urut' => 1]); + $active = PpidJenisDokumen::create(['nama' => 'Active', 'slug' => 'active', 'status' => '1', 'urut' => 2]); + + expect($inactive->status)->toEqual(0); + expect($active->status)->toEqual(1); +}); + +test('status menampilkan label html di datatables', function () { + $doc = PpidJenisDokumen::create(['nama' => 'Test', 'slug' => 'test', 'status' => 1, 'urut' => 1]); + + $response = $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.getdata')); + + $response->assertStatus(200); + + $data = $response->json('data'); + // Memastikan status di-render sebagai HTML label (bukan plain text) + expect($data[0]['status'])->toContain('label'); +}); + +test('datatables dapat melakukan global search berdasarkan nama', function () { + + PpidJenisDokumen::create([ + 'nama' => 'Dokumen Keuangan', + 'slug' => 'dokumen-keuangan', + 'status' => 1, + 'urut' => 1 + ]); + + PpidJenisDokumen::create([ + 'nama' => 'Dokumen SDM', + 'slug' => 'dokumen-sdm', + 'status' => 1, + 'urut' => 2 + ]); + + $response = $this->actingAs($this->user) + ->get(route('ppid.jenis-dokumen.getdata', [ + 'search' => [ + 'value' => 'Keuangan' + ] + ])); + + $response->assertStatus(200); + + $data = $response->json(); + + expect($data['recordsFiltered'])->toBe(1); + expect($data['data'][0]['nama'])->toBe('Dokumen Keuangan'); +});