From 37757715473f58928edaf21ce3bc2e165f841580 Mon Sep 17 00:00:00 2001 From: Andika Chamberlin Date: Tue, 23 Sep 2025 18:26:25 +0700 Subject: [PATCH 1/3] sarana: add sarana details per desa (migration, model, controller, view) -- closes #939 --- app/Exports/ExportDataSarana.php | 82 +++++++++ .../Controllers/Data/DataSaranaController.php | 120 +++++++++++++ .../Controllers/Data/DataUmumController.php | 8 +- app/Models/DataDesa.php | 5 + app/Models/DataSarana.php | 20 +++ database/factories/DataSaranaFactory.php | 27 +++ ...2025_09_23_115608_create_saranas_table.php | 36 ++++ database/seeders/DasDataSaranaTableSeeder.php | 60 +++++++ .../views/data/data_sarana/create.blade.php | 99 +++++++++++ .../views/data/data_sarana/edit.blade.php | 103 ++++++++++++ .../views/data/data_sarana/index.blade.php | 158 ++++++++++++++++++ .../views/data/data_umum/form_edit.blade.php | 39 +++-- .../views/layouts/fragments/sidebar.blade.php | 5 +- routes/web.php | 11 ++ tests/Feature/DataSaranaControllerTest.php | 102 +++++++++++ 15 files changed, 860 insertions(+), 15 deletions(-) create mode 100644 app/Exports/ExportDataSarana.php create mode 100644 app/Http/Controllers/Data/DataSaranaController.php create mode 100644 app/Models/DataSarana.php create mode 100644 database/factories/DataSaranaFactory.php create mode 100644 database/migrations/2025_09_23_115608_create_saranas_table.php create mode 100644 database/seeders/DasDataSaranaTableSeeder.php create mode 100644 resources/views/data/data_sarana/create.blade.php create mode 100644 resources/views/data/data_sarana/edit.blade.php create mode 100644 resources/views/data/data_sarana/index.blade.php create mode 100644 tests/Feature/DataSaranaControllerTest.php diff --git a/app/Exports/ExportDataSarana.php b/app/Exports/ExportDataSarana.php new file mode 100644 index 0000000000..5a2cb2b517 --- /dev/null +++ b/app/Exports/ExportDataSarana.php @@ -0,0 +1,82 @@ +data = $data; + $this->author = $author; + } + + public function collection() + { + return $this->data->map(function ($item) { + return [ + $item->id, + $item->desa->nama ?? '-', + $item->nama, + $item->jumlah, + $item->kategori, + $item->keterangan, + ]; + }); + } + + public function headings(): array + { + return [ + ['Laporan Data Sarana'], + [''], + ['ID', 'Desa', 'Nama Sarana', 'Jumlah', 'Kategori', 'Keterangan'], + ]; + } + + public function styles(Worksheet $sheet) + { + return [ + 1 => ['font' => ['bold' => true, 'size' => 14]], + 3 => ['font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']]], + ]; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + $sheet = $event->sheet->getDelegate(); + + $sheet->mergeCells('A1:F1'); + $sheet->getStyle('A1')->getAlignment()->setHorizontal('center'); + + $sheet->setCellValue('A2', 'Nama: ' . $this->author); + $sheet->setCellValue('F2', 'Tanggal: ' . date('d-m-Y')); + + $sheet->getStyle('A2')->getAlignment()->setHorizontal('left'); + $sheet->getStyle('F2')->getAlignment()->setHorizontal('right'); + + $sheet->getStyle('A3:F3')->getFill() + ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) + ->getStartColor()->setARGB('4CAF50'); + + $lastRow = $sheet->getHighestRow(); + $sheet->getStyle("A3:F{$lastRow}") + ->getBorders() + ->getAllBorders() + ->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN); + }, + ]; + } +} diff --git a/app/Http/Controllers/Data/DataSaranaController.php b/app/Http/Controllers/Data/DataSaranaController.php new file mode 100644 index 0000000000..3d8d164b1f --- /dev/null +++ b/app/Http/Controllers/Data/DataSaranaController.php @@ -0,0 +1,120 @@ + function ($query) use ($search, $kategori, $startDate, $endDate) { + $query + ->when($search, fn($q) => $q->where('nama', 'like', "%$search%")) + ->when($kategori, fn($q) => $q->where('kategori', $kategori)) + ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + ->latest('id'); + }]) + ->paginate(10); + + $rekapKategori = DB::table('das_data_sarana') + ->select('kategori', DB::raw('SUM(jumlah) as total')) + ->groupBy('kategori') + ->pluck('total', 'kategori'); + + return view('data.data_sarana.index', compact('page_title', 'page_description', 'desas', 'rekapKategori')); + } + + public function create() + { + $page_title = "Tambah Sarana"; + $page_description = "Form tambah data sarana"; + + $desas = DataDesa::all(); + return view('data.data_sarana.create', compact('page_title', 'page_description', 'desas')); + } + + public function store(Request $request) + { + $request->validate([ + 'desa_id' => 'required|integer:desa_id', + 'nama' => 'required|string|max:255', + 'jumlah' => 'required|integer|min:0', + 'kategori' => 'required|string|max:100', + 'keterangan' => 'required|string:max:255', + ]); + + DataSarana::create($request->all()); + + return redirect()->route('data.data-sarana.index')->with('success', 'Data sarana berhasil ditambahkan.'); + } + + public function edit($id) + { + $page_title = 'Edit Data Sarana'; + $page_description = 'Ubah informasi sarana desa'; + + $sarana = DataSarana::findOrFail($id); + $desas = DataDesa::all(); + + return view('data.data_sarana.edit', compact('page_title', 'page_description', 'sarana', 'desas')); + } + + public function update(Request $request, $id) + { + $request->validate([ + 'desa_id' => 'required|integer:desa_id', + 'nama' => 'required|string|max:255', + 'jumlah' => 'required|integer|min:0', + 'kategori' => 'required|string|max:100', + 'keterangan' => 'required|string:max:255', + ]); + + $sarana = DataSarana::findOrFail($id); + $sarana->update($request->all()); + + return redirect()->route('data.data-sarana.index')->with('success', 'Data sarana berhasil diperbarui.'); + } + + public function destroy($id) + { + $sarana = DataSarana::findOrFail($id); + $sarana->delete(); + + return redirect()->route('data.data-sarana.index')->with('success', 'Data sarana berhasil dihapus.'); + } + + public function export() + { + $search = request('search'); + $kategori = request('kategori'); + $startDate = request('start_date'); + $endDate = request('end_date'); + + $data = \App\Models\DataSarana::with('desa') + ->when($search, fn($q) => $q->where('nama', 'like', "%$search%")) + ->when($kategori, fn($q) => $q->where('kategori', $kategori)) + ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + ->latest('id') + ->get(); + + return Excel::download(new ExportDataSarana($data, 'Admin Desa'), 'data_sarana.xlsx'); + } + +} diff --git a/app/Http/Controllers/Data/DataUmumController.php b/app/Http/Controllers/Data/DataUmumController.php index be9f7d6c40..c916a65bc7 100644 --- a/app/Http/Controllers/Data/DataUmumController.php +++ b/app/Http/Controllers/Data/DataUmumController.php @@ -36,6 +36,7 @@ use App\Models\DataUmum; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\DB; class DataUmumController extends Controller { @@ -51,7 +52,12 @@ public function index() $page_title = 'Data Umum'; $page_description = 'Ubah Data Umum'; - return view('data.data_umum.edit', compact('page_title', 'page_description', 'data_umum', 'luas_wilayah')); + $rekapKategori = DB::table('das_data_sarana') + ->select('kategori', DB::raw('SUM(jumlah) as total')) + ->groupBy('kategori') + ->pluck('total', 'kategori'); + + return view('data.data_umum.edit', compact('page_title', 'page_description', 'data_umum', 'luas_wilayah', 'rekapKategori')); } /** diff --git a/app/Models/DataDesa.php b/app/Models/DataDesa.php index 832b2695e7..81fe0fb206 100644 --- a/app/Models/DataDesa.php +++ b/app/Models/DataDesa.php @@ -181,4 +181,9 @@ public function pembangunan() { return $this->hasMany(Pembangunan::class, 'desa_id', 'desa_id'); } + + public function saranas() + { + return $this->hasMany(DataSarana::class, 'desa_id', 'id'); + } } diff --git a/app/Models/DataSarana.php b/app/Models/DataSarana.php new file mode 100644 index 0000000000..73604472e6 --- /dev/null +++ b/app/Models/DataSarana.php @@ -0,0 +1,20 @@ +belongsTo(DataDesa::class, 'desa_id', 'id'); + } +} diff --git a/database/factories/DataSaranaFactory.php b/database/factories/DataSaranaFactory.php new file mode 100644 index 0000000000..110edf4630 --- /dev/null +++ b/database/factories/DataSaranaFactory.php @@ -0,0 +1,27 @@ + DataDesa::factory(), // otomatis buat desa baru + 'nama' => $this->faker->word, + 'jumlah' => $this->faker->numberBetween(1, 100), + 'kategori' => $this->faker->randomElement([ + 'puskesmas','puskesmas_pembantu','posyandu','pondok_bersalin', + 'paud','sd','smp','sma', + 'masjid_besar','mushola','gereja','pasar','balai_pertemuan' + ]), + 'keterangan' => $this->faker->sentence, + ]; + } +} diff --git a/database/migrations/2025_09_23_115608_create_saranas_table.php b/database/migrations/2025_09_23_115608_create_saranas_table.php new file mode 100644 index 0000000000..eecb7a36b1 --- /dev/null +++ b/database/migrations/2025_09_23_115608_create_saranas_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + + $table->unsignedInteger('desa_id'); + $table->foreign('desa_id')->references('id')->on('das_data_desa')->onDelete('cascade'); + + $table->string('kategori', 191); + $table->string('nama', 191)->nullable(); + $table->integer('jumlah')->default(0); + $table->text('keterangan')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('das_data_sarana'); + } +}; diff --git a/database/seeders/DasDataSaranaTableSeeder.php b/database/seeders/DasDataSaranaTableSeeder.php new file mode 100644 index 0000000000..fcdc0c90c8 --- /dev/null +++ b/database/seeders/DasDataSaranaTableSeeder.php @@ -0,0 +1,60 @@ +delete(); + DB::statement('ALTER TABLE das_data_desa AUTO_INCREMENT = 1'); // reset id + + DB::table('das_data_desa')->insert([ + 'id' => 1, + 'profil_id' => 1, + 'desa_id' => 1333222, + 'nama' => 'Desa Contoh', + 'sebutan_desa' => 'Desa', + 'website' => 'https://desa-contoh.id', + 'path' => json_encode(['desa-contoh']), // ✅ JSON valid + 'luas_wilayah' => 1234, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + // Untuk das_data_sarana bisa truncate karena hanya referensi ke desa_id + DB::table('das_data_sarana')->truncate(); + + DB::table('das_data_sarana')->insert([ + [ + 'id' => 2, + 'desa_id' => 1, + 'kategori' => 'puskesmas', + 'nama' => 'Puskesmas', + 'jumlah' => 1, + 'keterangan' => 'Puskesmas induk di pusat desa', + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'id' => 3, + 'desa_id' => 1, + 'kategori' => 'masjid_besar', + 'nama' => 'Masjid', + 'jumlah' => 7, + 'keterangan' => null, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } +} diff --git a/resources/views/data/data_sarana/create.blade.php b/resources/views/data/data_sarana/create.blade.php new file mode 100644 index 0000000000..1b436eaa2d --- /dev/null +++ b/resources/views/data/data_sarana/create.blade.php @@ -0,0 +1,99 @@ +@extends('layouts.dashboard_template') + +@section('content') +
+

{{ $page_title }}

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

Tambah Data Sarana

+
+
+
+ @csrf + + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + Kembali + + +
+
+ +
+
+
+
+@endsection diff --git a/resources/views/data/data_sarana/edit.blade.php b/resources/views/data/data_sarana/edit.blade.php new file mode 100644 index 0000000000..a49b8bc72c --- /dev/null +++ b/resources/views/data/data_sarana/edit.blade.php @@ -0,0 +1,103 @@ +@extends('layouts.dashboard_template') + +@section('content') +
+

{{ $page_title }}

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

Edit Data Sarana

+
+
+
+ @csrf + @method('PUT') + + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + Kembali + + +
+
+
+
+
+
+@endsection diff --git a/resources/views/data/data_sarana/index.blade.php b/resources/views/data/data_sarana/index.blade.php new file mode 100644 index 0000000000..425e267c90 --- /dev/null +++ b/resources/views/data/data_sarana/index.blade.php @@ -0,0 +1,158 @@ +@extends('layouts.dashboard_template') + +@section('content') +
+

+ {{ $page_title ?? 'Page Title' }} + {{ $page_description ?? '' }} +

+ +
+ +
+
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+ +
+ + + + Reset + + + Export + + + Tambah + +
+
+ +
+ @foreach($desas as $desa) +

+ {{ $desa->nama }} +

+ + + + + + + + + + + @forelse($desa->saranas as $sarana) + + + + + + + @empty + + + + @endforelse + +
Nama SaranaJumlahKategoriAksi
{{ $sarana->nama }}{{ $sarana->jumlah }}{{ $sarana->kategori ?? '-' }} + {{-- + Lihat + --}} + + Edit + +
+ @csrf + @method('DELETE') + +
+
Tidak ada sarana
+ @endforeach +
+ + + + {{--
+

Rekap Total Sarana

+ + + + + + + + + + + + + + + + + + + + + + + + +
KategoriTotal
Puskesmas{{ $rekapKategori['puskesmas'] ?? 0 }}
Puskesmas Pembantu{{ $rekapKategori['puskesmas_pembantu'] ?? 0 }}
Posyandu{{ $rekapKategori['posyandu'] ?? 0 }}
Pondok Bersalin{{ $rekapKategori['pondok_bersalin'] ?? 0 }}
PAUD/Sederajat{{ $rekapKategori['paud'] ?? 0 }}
SD/Sederajat{{ $rekapKategori['sd'] ?? 0 }}
SMP/Sederajat{{ $rekapKategori['smp'] ?? 0 }}
SMA/Sederajat{{ $rekapKategori['sma'] ?? 0 }}
Masjid Besar{{ $rekapKategori['masjid_besar'] ?? 0 }}
Mushola{{ $rekapKategori['mushola'] ?? 0 }}
Gereja{{ $rekapKategori['gereja'] ?? 0 }}
Pasar{{ $rekapKategori['pasar'] ?? 0 }}
Balai Pertemuan{{ $rekapKategori['balai_pertemuan'] ?? 0 }}
+
--}} + +
+
+@endsection diff --git a/resources/views/data/data_umum/form_edit.blade.php b/resources/views/data/data_umum/form_edit.blade.php index 73c48b5cdf..bf25944750 100644 --- a/resources/views/data/data_umum/form_edit.blade.php +++ b/resources/views/data/data_umum/form_edit.blade.php @@ -94,7 +94,8 @@
- {!! Form::number('jml_puskesmas', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_puskesmas', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -102,7 +103,8 @@
- {!! Form::number('jml_puskesmas_pembantu', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_puskesmas_pembantu', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -110,7 +112,8 @@
- {!! Form::number('jml_posyandu', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_posyandu', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -118,7 +121,8 @@
- {!! Form::number('jml_pondok_bersalin', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_pondok_bersalin', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -128,7 +132,8 @@
- {!! Form::number('jml_paud', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_paud', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -136,7 +141,8 @@
- {!! Form::number('jml_sd', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_sd', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -144,7 +150,8 @@
- {!! Form::number('jml_smp', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_smp', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -152,7 +159,8 @@
- {!! Form::number('jml_sma', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_sma', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -162,7 +170,8 @@
- {!! Form::number('jml_masjid_besar', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_masjid_besar', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -170,7 +179,8 @@
- {!! Form::number('jml_mushola', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_mushola', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -178,7 +188,8 @@
- {!! Form::number('jml_gereja', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_gereja', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -186,7 +197,8 @@
- {!! Form::number('jml_pasar', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_pasar', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
@@ -194,7 +206,8 @@
- {!! Form::number('jml_balai_pertemuan', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} + {{-- {!! Form::number('jml_balai_pertemuan', null, ['placeholder' => '0', 'class' => 'form-control', 'required', 'style' => 'text-align:right;']) !!} --}} +
diff --git a/resources/views/layouts/fragments/sidebar.blade.php b/resources/views/layouts/fragments/sidebar.blade.php index 22afbc22a7..7dc04a89f9 100644 --- a/resources/views/layouts/fragments/sidebar.blade.php +++ b/resources/views/layouts/fragments/sidebar.blade.php @@ -136,7 +136,7 @@