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')
+
+
+
+ {!! 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')
+
+
+ {!! 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')
+
+
+
+ @include('partials.flash_message')
+
+
+
+
+
+
+
+
+
+
+
Apakah Anda yakin ingin menghapus 0 data yang dipilih?
+
Tindakan ini tidak dapat dibatalkan!
+
+
+
+
+
+@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');
+});