diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1331cda --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM php:8.2-apache + +RUN apt-get update && apt-get install -y \ + git \ + unzip \ + libpq-dev \ + libzip-dev \ + zip \ + curl \ + python3 \ + python3-pip \ + && docker-php-ext-install pdo pdo_pgsql pgsql zip + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +WORKDIR /var/www/html + +COPY . /var/www/html + +RUN composer install --no-dev --optimize-autoloader + +RUN if [ -f requirements.txt ]; then pip3 install -r requirements.txt; fi + +RUN a2enmod rewrite + +RUN chown -R www-data:www-data /var/www/html \ + && chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache + +COPY render-apache.conf /etc/apache2/sites-available/000-default.conf + +EXPOSE 80 + +CMD php artisan config:clear && \ + php artisan cache:clear && \ + apache2-foreground \ No newline at end of file diff --git a/app/Exports/PenerimaFinalExport.php b/app/Exports/PenerimaFinalExport.php new file mode 100644 index 0000000..e7ae2ac --- /dev/null +++ b/app/Exports/PenerimaFinalExport.php @@ -0,0 +1,319 @@ +where('status_verifikasi', 'disetujui') + ->orderByDesc('tanggal_penetapan') + ->get(); + + $this->rowCount = $items->count(); + + return $items->map(function ($item, $index) { + $prob = $item->probability ?? 0; + + if ($prob <= 1) { + $prob = $prob * 100; + } + + $nama = $item->nama_lengkap ?? $item->nama ?? '-'; + + // Penghasilan: coba dari relasi calonPenerima, fallback ke kolom langsung. + // Jika null atau 0, tampilkan teks supaya cell tidak kosong. + $penghasilanRaw = $item->calonPenerima?->penghasilan + ?? $item->penghasilan + ?? null; + + $penghasilan = ($penghasilanRaw !== null && (int) $penghasilanRaw > 0) + ? (int) $penghasilanRaw + : 'Tidak Berpenghasilan'; + + return [ + 'No' => $index + 1, + 'Nama Lengkap' => $nama, + 'NIK' => "\t" . ($item->nik ?? '-'), + 'No KK' => "\t" . ($item->no_kk ?? '-'), + 'Nomor RT' => $item->nomor_rt ?? '-', + 'Dusun' => $item->nama_dusun ?? '-', + 'Pekerjaan' => $item->pekerjaan ?? '-', + 'Penghasilan' => $penghasilan, + 'Tanggungan' => $item->jumlah_tanggungan ?? 0, + 'Aset' => $item->aset_kepemilikan ?? '-', + 'Bantuan Lain' => ucfirst($item->bantuan_lain ?? 'tidak'), + 'Usia' => $item->usia ?? 0, + 'Probabilitas' => round($prob, 1), + 'Status' => ucfirst($item->status_verifikasi ?? '-'), + 'Tgl Penetapan' => optional($item->tanggal_penetapan)->format('d-m-Y') ?? '-', + 'Periode Bantuan' => $item->periode_bantuan ?? '-', + 'Jumlah Bantuan' => $item->jumlah_bantuan ?? 300000, + ]; + }); + } + + // ── Headings ────────────────────────────────────────────────────────────── + public function headings(): array + { + return [ + 'No', 'Nama Lengkap', 'NIK', 'No KK', 'Nomor RT', 'Dusun', + 'Pekerjaan', 'Penghasilan (Rp)', 'Tanggungan', + 'Aset Kepemilikan', 'Bantuan Lain', 'Usia', + 'Probabilitas (%)', 'Status', + 'Tgl Penetapan', 'Periode Bantuan', 'Jumlah Bantuan (Rp)', + ]; + } + + public function startCell(): string { return 'A5'; } + public function title(): string { return 'Penerima BLT-DD'; } + + // ── Lebar kolom ────────────────────────────────────────────────────────── + public function columnWidths(): array + { + return [ + 'A' => 5, 'B' => 28, 'C' => 20, 'D' => 20, + 'E' => 8, 'F' => 18, 'G' => 20, 'H' => 18, + 'I' => 9, 'J' => 22, 'K' => 13, 'L' => 7, + 'M' => 14, 'N' => 16, 'O' => 15, 'P' => 18, 'Q' => 20, + ]; + } + + // ── Style header tabel (baris 5) ───────────────────────────────────────── + public function styles(Worksheet $sheet) + { + $sheet->getStyle('A5:Q5')->applyFromArray([ + 'font' => [ + 'bold' => true, + 'color' => ['argb' => 'FFFFFFFF'], + 'size' => 9, + 'name' => 'Arial', + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['argb' => 'FF1E3A8A'], + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + 'wrapText' => true, + ], + ]); + + return []; + } + + // ── Events ──────────────────────────────────────────────────────────────── + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + $sheet = $event->sheet->getDelegate(); + $lastRow = 5 + $this->rowCount; + + $this->buildTitleBlock($sheet); + $this->formatDataRows($sheet, $lastRow); + $this->applyTableBorders($sheet, $lastRow); + $this->buildSummaryRow($sheet, $lastRow); + $this->buildFootnote($sheet, $lastRow); + $this->applyPrintSettings($sheet); + + $sheet->freezePane('A6'); + $sheet->setAutoFilter("A5:Q5"); + }, + ]; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + private function buildTitleBlock(Worksheet $sheet): void + { + foreach (['A1:Q1', 'A2:Q2', 'A3:Q3', 'A4:Q4'] as $range) { + $sheet->mergeCells($range); + } + + $sheet->setCellValue('A1', 'LAPORAN PENERIMA BANTUAN LANGSUNG TUNAI DANA DESA (BLT-DD)'); + $sheet->setCellValue('A2', 'Desa Ngerong, Kecamatan Gempol, Kabupaten Pasuruan — Jawa Timur'); + $sheet->setCellValue('A3', 'Status: Disetujui | Dicetak: ' . now()->translatedFormat('d F Y, H:i') . ' WIB'); + $sheet->setCellValue('A4', ''); + + $sheet->getStyle('A1')->applyFromArray([ + 'font' => ['bold' => true, 'size' => 14, 'name' => 'Arial', 'color' => ['argb' => 'FF1E3A8A']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], + ]); + $sheet->getStyle('A2')->applyFromArray([ + 'font' => ['size' => 10, 'name' => 'Arial', 'color' => ['argb' => 'FF334155']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], + ]); + $sheet->getStyle('A3')->applyFromArray([ + 'font' => ['size' => 9, 'italic' => true, 'name' => 'Arial', 'color' => ['argb' => 'FF64748B']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], + ]); + $sheet->getStyle('A3:Q3')->applyFromArray([ + 'borders' => [ + 'bottom' => ['borderStyle' => Border::BORDER_MEDIUM, 'color' => ['argb' => 'FF1E3A8A']], + ], + ]); + + $sheet->getRowDimension(1)->setRowHeight(26); + $sheet->getRowDimension(2)->setRowHeight(16); + $sheet->getRowDimension(3)->setRowHeight(14); + $sheet->getRowDimension(4)->setRowHeight(6); + $sheet->getRowDimension(5)->setRowHeight(30); + } + + private function formatDataRows(Worksheet $sheet, int $lastRow): void + { + for ($row = 6; $row <= $lastRow; $row++) { + $isEven = ($row % 2 === 0); + + $sheet->getStyle("A{$row}:Q{$row}")->applyFromArray([ + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['argb' => $isEven ? 'FFF8FAFC' : 'FFFFFFFF'], + ], + 'font' => ['name' => 'Arial', 'size' => 9], + 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER], + ]); + + $sheet->getRowDimension($row)->setRowHeight(18); + + foreach (['A', 'E', 'I', 'L', 'M', 'N', 'O'] as $col) { + $sheet->getStyle("{$col}{$row}") + ->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + } + + foreach (['H', 'Q'] as $col) { + $sheet->getStyle("{$col}{$row}") + ->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); + } + + // Format Penghasilan — angka pakai Rp, teks (Tidak Berpenghasilan) pakai italic gray + $hVal = $sheet->getCell("H{$row}")->getValue(); + if (is_numeric($hVal)) { + $sheet->getStyle("H{$row}")->applyFromArray([ + 'numberFormat' => ['formatCode' => '"Rp "#,##0'], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT], + ]); + } else { + $sheet->getStyle("H{$row}")->applyFromArray([ + 'font' => ['italic' => true, 'color' => ['argb' => 'FF94A3B8'], 'size' => 8, 'name' => 'Arial'], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], + ]); + } + $sheet->getStyle("Q{$row}")->applyFromArray([ + 'font' => ['bold' => true, 'color' => ['argb' => 'FF166534'], 'name' => 'Arial', 'size' => 9], + 'numberFormat' => ['formatCode' => '"Rp "#,##0'], + ]); + + $prob = (float) ($sheet->getCell("M{$row}")->getValue() ?? 0); + $probColor = $prob >= 70 ? 'FF10B981' : ($prob >= 40 ? 'FFF59E0B' : 'FFF43F5E'); + $sheet->getStyle("M{$row}")->applyFromArray([ + 'font' => ['bold' => true, 'color' => ['argb' => $probColor], 'name' => 'Arial', 'size' => 9], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], + 'numberFormat' => ['formatCode' => '0.0"%"'], + ]); + + $sheet->getStyle("N{$row}")->applyFromArray([ + 'font' => ['bold' => true, 'color' => ['argb' => 'FF166534'], 'name' => 'Arial', 'size' => 9], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], + ]); + } + } + + private function applyTableBorders(Worksheet $sheet, int $lastRow): void + { + $sheet->getStyle("A5:Q{$lastRow}")->applyFromArray([ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['argb' => 'FFE2E8F0'], + ], + 'outline' => [ + 'borderStyle' => Border::BORDER_MEDIUM, + 'color' => ['argb' => 'FF1E3A8A'], + ], + ], + ]); + } + + private function buildSummaryRow(Worksheet $sheet, int $lastRow): void + { + $sumRow = $lastRow + 2; + + $sheet->mergeCells("A{$sumRow}:B{$sumRow}"); + $sheet->setCellValue("A{$sumRow}", 'RINGKASAN'); + $sheet->setCellValue("C{$sumRow}", 'Total Penerima:'); + $sheet->setCellValue("D{$sumRow}", $this->rowCount . ' orang'); + // ← Total Penghasilan dihapus, diganti Total Dana BLT saja + $sheet->setCellValue("P{$sumRow}", 'Total Dana BLT:'); + $sheet->setCellValue("Q{$sumRow}", "=SUM(Q6:Q{$lastRow})"); + + $sheet->getStyle("A{$sumRow}:Q{$sumRow}")->applyFromArray([ + 'font' => ['bold' => true, 'size' => 9, 'name' => 'Arial', 'color' => ['argb' => 'FF1E3A8A']], + 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FFEFF6FF']], + 'borders' => [ + 'outline' => ['borderStyle' => Border::BORDER_MEDIUM, 'color' => ['argb' => 'FF1E3A8A']], + ], + ]); + + $sheet->getStyle("Q{$sumRow}")->getNumberFormat()->setFormatCode('"Rp "#,##0'); + $sheet->getRowDimension($sumRow)->setRowHeight(18); + } + + private function buildFootnote(Worksheet $sheet, int $lastRow): void + { + $noteRow = $lastRow + 4; + $sheet->mergeCells("A{$noteRow}:Q{$noteRow}"); + $sheet->setCellValue( + "A{$noteRow}", + '* Dokumen ini digenerate otomatis oleh SiBantuDes — Sistem Informasi BLT Dana Desa, Desa Ngerong, Kab. Pasuruan.' + ); + $sheet->getStyle("A{$noteRow}")->applyFromArray([ + 'font' => ['italic' => true, 'size' => 8, 'name' => 'Arial', 'color' => ['argb' => 'FF94A3B8']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_LEFT], + ]); + } + + private function applyPrintSettings(Worksheet $sheet): void + { + $sheet->getPageSetup() + ->setOrientation(PageSetup::ORIENTATION_LANDSCAPE) + ->setPaperSize(PageSetup::PAPERSIZE_A4) + ->setFitToWidth(1) + ->setFitToHeight(0); + + $sheet->getPageMargins() + ->setTop(0.75)->setBottom(0.75) + ->setLeft(0.5)->setRight(0.5); + + $sheet->getHeaderFooter() + ->setOddHeader('&C&B Laporan Penerima BLT-DD — Desa Ngerong') + ->setOddFooter('&L&8SiBantuDes&C&8Halaman &P dari &N&R&8' . now()->format('d/m/Y')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/FilterisasiController.php b/app/Http/Controllers/Admin/FilterisasiController.php index 5b9aa1e..3ba7859 100644 --- a/app/Http/Controllers/Admin/FilterisasiController.php +++ b/app/Http/Controllers/Admin/FilterisasiController.php @@ -14,6 +14,7 @@ class FilterisasiController extends Controller public function index(Request $request) { $user = $request->user(); + if (!$user || $user->role !== 'admin') { abort(403, 'Akses ditolak: hanya Admin.'); } @@ -28,9 +29,11 @@ public function index(Request $request) } $candidates = collect(); - $pickedIds = collect(); + $pickedNiks = collect(); if ($dusunId > 0) { + $dusun = Dusun::find($dusunId); + $query = CalonPenerima::query() ->with(['rt.dusun', 'prediksiKelayakan']) ->whereIn('tracking_status', ['sedang_validasi', 'selesai']) @@ -45,16 +48,19 @@ public function index(Request $request) $candidates = $query->paginate(15)->withQueryString(); - $pickedIds = PenerimaFinal::whereIn('calon_penerima_id', $candidates->pluck('id')->all()) - ->pluck('calon_penerima_id'); + $pickedNiks = PenerimaFinal::query() + ->where('status_verifikasi', 'disetujui') + ->where('nama_dusun', $dusun?->nama_dusun) + ->pluck('nik'); } - return view('admin.filterisasi', compact('dusuns', 'dusunId', 'kuota', 'candidates', 'pickedIds')); + return view('admin.filterisasi', compact('dusuns', 'dusunId', 'kuota', 'candidates', 'pickedNiks')); } public function tetapkan(Request $request) { $user = $request->user(); + if (!$user || $user->role !== 'admin') { abort(403, 'Akses ditolak: hanya Admin.'); } @@ -68,14 +74,16 @@ public function tetapkan(Request $request) $kuota = (int) ($data['kuota'] ?? 7); DB::transaction(function () use ($dusunId, $kuota) { - // Ambil semua kandidat dalam dusun yang sedang divalidasi / sudah selesai + $dusun = Dusun::findOrFail($dusunId); + $allCandidates = CalonPenerima::query() + ->with(['rt.dusun', 'prediksiKelayakan']) ->whereIn('tracking_status', ['sedang_validasi', 'selesai']) ->whereHas('rt', function ($r) use ($dusunId) { $r->where('dusun_id', $dusunId); }) ->leftJoin('prediksi_kelayakans', 'prediksi_kelayakans.calon_penerima_id', '=', 'calon_penerimas.id') - ->select('calon_penerimas.id', DB::raw('COALESCE(prediksi_kelayakans.probability, 0) as prob')) + ->select('calon_penerimas.*', DB::raw('COALESCE(prediksi_kelayakans.probability, 0) as prob')) ->orderByDesc('prob') ->orderByDesc('calon_penerimas.created_at') ->get(); @@ -83,39 +91,56 @@ public function tetapkan(Request $request) $topIds = $allCandidates->take($kuota)->pluck('id')->all(); $allIds = $allCandidates->pluck('id')->all(); - // Hapus hasil final lama untuk kandidat pada dusun ini - if (!empty($allIds)) { - PenerimaFinal::whereIn('calon_penerima_id', $allIds)->delete(); - } - - // Simpan hasil final penerima yang masuk kuota - foreach ($topIds as $id) { - PenerimaFinal::updateOrCreate( - ['calon_penerima_id' => $id], - [ - 'tanggal_penetapan' => now()->toDateString(), - 'periode_bantuan' => date('Y') . ' Triwulan 1', - 'jumlah_bantuan' => 0, - 'status_pencairan' => 'belum_cair', - 'tanggal_pencairan' => null, - ] - ); - } - - // Yang masuk kuota = disetujui + // Top kuota = disetujui if (!empty($topIds)) { CalonPenerima::whereIn('id', $topIds)->update([ 'status_verifikasi' => 'disetujui', ]); } - // Yang tidak masuk kuota = ditolak + // Sisanya = ditolak $notPickedIds = array_diff($allIds, $topIds); + if (!empty($notPickedIds)) { CalonPenerima::whereIn('id', $notPickedIds)->update([ 'status_verifikasi' => 'ditolak', ]); } + + // Ambil ulang data final setelah status diperbarui + $finalCandidates = CalonPenerima::query() + ->with(['rt.dusun', 'prediksiKelayakan']) + ->whereIn('id', $allIds) + ->get(); + + foreach ($finalCandidates as $candidate) { + $probability = $candidate->prediksiKelayakan->probability ?? 0; + + PenerimaFinal::updateOrCreate( + [ + 'nik' => $candidate->nik, + 'periode_bantuan' => '2026 Triwulan 1', + ], + [ + 'nama_lengkap' => $candidate->nama_lengkap ?? $candidate->nama ?? '-', + 'no_kk' => $candidate->no_kk ?? '-', + 'rt_id' => $candidate->rt_id, + 'nomor_rt' => $candidate->rt?->nomor_rt ?? null, + 'nama_dusun' => $candidate->rt?->dusun?->nama_dusun ?? $dusun->nama_dusun, + 'pekerjaan' => $candidate->pekerjaan ?? '-', + 'penghasilan' => $candidate->penghasilan ?? 0, + 'jumlah_tanggungan' => $candidate->jumlah_tanggungan ?? 0, + 'aset_kepemilikan' => $candidate->aset_kepemilikan ?? '-', + 'bantuan_lain' => $candidate->bantuan_lain ?? 'tidak', + 'usia' => $candidate->usia ?? 0, + 'probability' => $probability, + 'status_verifikasi' => $candidate->status_verifikasi ?? 'pending', + 'tanggal_penetapan' => now()->toDateString(), + 'periode_bantuan' => '2026 Triwulan 1', + 'jumlah_bantuan' => 300000, + ] + ); + } }); return back()->with('success', "Berhasil menetapkan kuota {$kuota} penerima untuk dusun terpilih."); @@ -124,6 +149,7 @@ public function tetapkan(Request $request) public function resetDusun(Request $request) { $user = $request->user(); + if (!$user || $user->role !== 'admin') { abort(403, 'Akses ditolak: hanya Admin.'); } @@ -132,24 +158,24 @@ public function resetDusun(Request $request) 'dusun_id' => 'required|exists:dusuns,id', ]); - $dusunId = (int) $data['dusun_id']; + $dusun = Dusun::findOrFail($data['dusun_id']); - DB::transaction(function () use ($dusunId) { + DB::transaction(function () use ($dusun) { $candidateIds = CalonPenerima::query() - ->whereHas('rt', function ($r) use ($dusunId) { - $r->where('dusun_id', $dusunId); + ->whereHas('rt', function ($r) use ($dusun) { + $r->where('dusun_id', $dusun->id); }) ->whereIn('tracking_status', ['sedang_validasi', 'selesai']) ->pluck('id') ->all(); if (!empty($candidateIds)) { - PenerimaFinal::whereIn('calon_penerima_id', $candidateIds)->delete(); - CalonPenerima::whereIn('id', $candidateIds)->update([ 'status_verifikasi' => 'pending', ]); } + + PenerimaFinal::where('nama_dusun', $dusun->nama_dusun)->delete(); }); return back()->with('success', 'Hasil filterisasi dusun berhasil di-reset.'); diff --git a/app/Http/Controllers/Admin/LaporanController.php b/app/Http/Controllers/Admin/LaporanController.php new file mode 100644 index 0000000..828a184 --- /dev/null +++ b/app/Http/Controllers/Admin/LaporanController.php @@ -0,0 +1,146 @@ +user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + $laporans = PenerimaFinal::query() + ->where('status_verifikasi', 'disetujui') + ->orderByDesc('tanggal_penetapan') + ->get(); + + $laporanPubliks = LaporanPublik::latest()->get(); + + return view('admin.laporan', compact('laporans', 'laporanPubliks')); + } + + public function exportPdf(Request $request) + { + $user = $request->user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + $laporans = PenerimaFinal::query() + ->where('status_verifikasi', 'disetujui') + ->orderByDesc('tanggal_penetapan') + ->get(); + + $pdf = Pdf::loadView('admin.laporan-pdf', compact('laporans')) + ->setPaper('a4', 'landscape'); + + return $pdf->download('laporan-penerima-blt-dd-kelurahan-ngerong.pdf'); + } + + public function exportExcel(Request $request) + { + $user = $request->user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + return Excel::download(new PenerimaFinalExport, 'laporan-penerima-blt-dd-kelurahan-ngerong.xlsx'); + } + + public function kirimKeRt(Request $request) + { + $user = $request->user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + $candidateIds = CalonPenerima::query() + ->whereIn('tracking_status', ['sedang_validasi']) + ->whereIn('status_verifikasi', ['disetujui', 'ditolak']) + ->pluck('id') + ->all(); + + if (!empty($candidateIds)) { + CalonPenerima::whereIn('id', $candidateIds)->update([ + 'tracking_status' => 'selesai', + ]); + } + + return back()->with('success', 'Hasil final berhasil dikirim ke RT dusun. RT sekarang hanya dapat melihat status diterima atau ditolak.'); + } + + public function uploadPublikPdf(Request $request) + { + $user = $request->user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + $data = $request->validate([ + 'judul' => 'required|string|max:255', + 'file_pdf' => 'required|mimes:pdf|max:5120', + ]); + + $path = $request->file('file_pdf')->store('laporan-publik', 'public'); + + LaporanPublik::create([ + 'judul' => $data['judul'], + 'file_path' => $path, + 'uploaded_by' => $user->id, + 'is_active' => true, + ]); + + return back()->with('success', 'PDF laporan publik berhasil diupload.'); + } + + public function hapusPublikPdf(Request $request, $id) + { + $user = $request->user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + $laporan = LaporanPublik::findOrFail($id); + + if ($laporan->file_path && Storage::disk('public')->exists($laporan->file_path)) { + Storage::disk('public')->delete($laporan->file_path); + } + + $laporan->delete(); + + return back()->with('success', 'PDF laporan publik berhasil dihapus.'); + } + + public function bersihkanRiwayatSelesai(Request $request) + { + $user = $request->user(); + + if (!$user || $user->role !== 'admin') { + abort(403, 'Akses ditolak: hanya Admin.'); + } + + $deleted = \App\Models\CalonPenerima::query() + ->where('tracking_status', 'selesai') + ->delete(); + + return back()->with('success', "Berhasil membersihkan {$deleted} data riwayat selesai. Arsip laporan tetap tersimpan."); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php index 613bcd9..f34afcc 100644 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -28,6 +28,27 @@ public function store(LoginRequest $request): RedirectResponse $request->session()->regenerate(); + $user = Auth::user(); + + if (!$user->is_active) { + Auth::logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return back()->withErrors([ + 'email' => 'Akun Anda belum diaktifkan oleh admin kelurahan.', + ])->onlyInput('email'); + } + + if ($user->role === 'admin') { + return redirect()->route('admin.dashboard'); + } + + if ($user->role === 'rt') { + return redirect()->route('rt.dashboard'); + } + return redirect()->intended(route('dashboard', absolute: false)); } @@ -39,9 +60,8 @@ public function destroy(Request $request): RedirectResponse Auth::guard('web')->logout(); $request->session()->invalidate(); - $request->session()->regenerateToken(); return redirect('/'); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index c028aa1..c643914 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -8,7 +8,6 @@ use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rules; use Illuminate\View\View; @@ -20,7 +19,6 @@ class RegisteredUserController extends Controller */ public function create(): View { - // Ambil semua RT + dusun untuk dropdown di halaman register $rts = RT::with('dusun') ->orderBy('dusun_id') ->orderBy('nomor_rt') @@ -40,8 +38,6 @@ public function store(Request $request): RedirectResponse 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class], 'password' => ['required', 'confirmed', Rules\Password::defaults()], - - // WAJIB pilih RT biar user punya rt_id 'rt_id' => ['required', 'exists:rts,id'], ]); @@ -49,19 +45,13 @@ public function store(Request $request): RedirectResponse 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), - - // Set default akun hasil register sebagai RT 'role' => 'rt', 'rt_id' => $request->rt_id, + 'is_active' => false, ]); event(new Registered($user)); - Auth::login($user); - - // Kamu bisa arahkan ke dashboard RT kalau punya route khusus RT - // return redirect()->route('rt.dashboard'); - - return redirect(route('dashboard', absolute: false)); + return redirect()->route('login')->with('success', 'Pendaftaran berhasil. Akun Anda sedang menunggu aktivasi dari admin kelurahan.'); } } \ No newline at end of file diff --git a/app/Http/Controllers/LandingPageController.php b/app/Http/Controllers/LandingPageController.php new file mode 100644 index 0000000..b130878 --- /dev/null +++ b/app/Http/Controllers/LandingPageController.php @@ -0,0 +1,33 @@ +query('nik','')); + + $statusData = null; + + if($nik !== ''){ + $statusData = CalonPenerima::with(['rt.dusun']) + ->where('nik',$nik) + ->first(); + } + + $laporanPubliks = LaporanPublik::where('is_active',true) + ->latest() + ->get(); + + return view('landing',compact( + 'nik', + 'statusData', + 'laporanPubliks' + )); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Rt/LaporanController.php b/app/Http/Controllers/Rt/LaporanController.php new file mode 100644 index 0000000..ea6d59f --- /dev/null +++ b/app/Http/Controllers/Rt/LaporanController.php @@ -0,0 +1,51 @@ +user(); + + if (!$user || $user->role !== 'rt') { + abort(403, 'Akses ditolak: hanya RT.'); + } + + $rt = Rt::with('dusun')->find($user->rt_id); + + if (!$rt) { + $laporans = collect(); + $stats = [ + 'total' => 0, + 'diterima' => 0, + 'ditolak' => 0, + ]; + + return view('rt.laporan', compact('laporans', 'stats')); + } + + $nomorRt = $rt->nomor_rt; + $namaDusun = $rt->dusun->nama_dusun ?? null; + + $laporans = PenerimaFinal::query() + ->where('nomor_rt', $nomorRt) + ->where('nama_dusun', $namaDusun) + ->whereIn('status_verifikasi', ['disetujui', 'ditolak']) + ->orderByDesc('tanggal_penetapan') + ->get(); + + $stats = [ + 'total' => $laporans->count(), + 'diterima' => $laporans->where('status_verifikasi', 'disetujui')->count(), + 'ditolak' => $laporans->where('status_verifikasi', 'ditolak')->count(), + ]; + + return view('rt.laporan', compact('laporans', 'stats')); + } +} \ No newline at end of file diff --git a/app/Models/LaporanPublik.php b/app/Models/LaporanPublik.php new file mode 100644 index 0000000..297b829 --- /dev/null +++ b/app/Models/LaporanPublik.php @@ -0,0 +1,23 @@ +belongsTo(User::class, 'uploaded_by'); + } +} \ No newline at end of file diff --git a/app/Models/PenerimaFinal.php b/app/Models/PenerimaFinal.php index cc03214..accafd9 100644 --- a/app/Models/PenerimaFinal.php +++ b/app/Models/PenerimaFinal.php @@ -10,18 +10,31 @@ class PenerimaFinal extends Model use HasFactory; protected $fillable = [ - 'calon_penerima_id', 'tanggal_penetapan', 'periode_bantuan', - 'jumlah_bantuan', 'status_pencairan', 'tanggal_pencairan', + 'nama_lengkap', + 'nik', + 'no_kk', + 'rt_id', + 'nomor_rt', + 'nama_dusun', + 'pekerjaan', + 'penghasilan', + 'jumlah_tanggungan', + 'aset_kepemilikan', + 'bantuan_lain', + 'usia', + 'probability', + 'status_verifikasi', + 'tanggal_penetapan', + 'periode_bantuan', + 'jumlah_bantuan', ]; protected $casts = [ 'tanggal_penetapan' => 'date', - 'tanggal_pencairan' => 'date', 'jumlah_bantuan' => 'decimal:2', + 'probability' => 'decimal:4', + 'penghasilan' => 'integer', + 'jumlah_tanggungan' => 'integer', + 'usia' => 'integer', ]; - - public function calonPenerima() - { - return $this->belongsTo(CalonPenerima::class); - } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 37cd131..d93bb06 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,24 +2,34 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { - /** @use HasFactory<\Database\Factories\UserFactory> */ use HasFactory, Notifiable; - - protected $fillable = ['name', 'email', 'password', 'role', 'rt_id']; - protected $hidden = ['password', 'remember_token']; + + protected $fillable = [ + 'name', + 'email', + 'password', + 'role', + 'rt_id', + 'is_active', + ]; + + protected $hidden = [ + 'password', + 'remember_token', + ]; protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'is_active' => 'boolean', ]; } @@ -42,4 +52,4 @@ public function isRT() { return $this->role === 'rt'; } -} +} \ No newline at end of file diff --git a/composer.json b/composer.json index 71dd370..0cf6222 100644 --- a/composer.json +++ b/composer.json @@ -7,8 +7,10 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "^3.1", "laravel/framework": "^12.0", - "laravel/tinker": "^2.10.1" + "laravel/tinker": "^2.10.1", + "maatwebsite/excel": "*" }, "require-dev": { "barryvdh/laravel-ide-helper": "^3.6", diff --git a/composer.lock b/composer.lock index ad2cfc0..5c8d767 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "936c856896cf71d7de94f42fd2c81b4b", + "content-hash": "61fce7afd7bfa4be47ac89062b07f583", "packages": [ + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9|^10", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-02-13T15:07:54+00:00" + }, { "name": "brick/math", "version": "0.14.7", @@ -135,6 +212,162 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -377,6 +610,161 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.6.0", @@ -508,6 +896,67 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.19.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" + }, + "time": "2025-10-17T16:34:55+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.4.0", @@ -2019,6 +2468,339 @@ ], "time": "2026-01-15T06:54:53+00:00" }, + { + "name": "maatwebsite/excel", + "version": "3.1.67", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "ext-json": "*", + "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0", + "php": "^7.0||^8.0", + "phpoffice/phpspreadsheet": "^1.30.0", + "psr/simple-cache": "^1.0||^2.0||^3.0" + }, + "require-dev": { + "laravel/scout": "^7.0||^8.0||^9.0||^10.0", + "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Excel": "Maatwebsite\\Excel\\Facades\\Excel" + }, + "providers": [ + "Maatwebsite\\Excel\\ExcelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Maatwebsite\\Excel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@spartner.nl" + } + ], + "description": "Supercharged Excel exports and imports in Laravel", + "keywords": [ + "PHPExcel", + "batch", + "csv", + "excel", + "export", + "import", + "laravel", + "php", + "phpspreadsheet" + ], + "support": { + "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.67" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2025-08-26T09:13:16+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.86", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-12-10T09:58:31+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, { "name": "monolog/monolog", "version": "3.10.0", @@ -2526,6 +3308,114 @@ ], "time": "2025-11-20T02:34:59+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.30.2", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2", + "reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": ">=7.4.0 <8.5.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "doctrine/instantiator": "^1.5", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + }, + { + "name": "Owen Leibman" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2" + }, + "time": "2026-01-11T05:58:24+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", @@ -3290,6 +4180,86 @@ }, "time": "2025-12-14T04:43:48+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", @@ -5791,6 +6761,149 @@ ], "time": "2026-01-01T22:13:48+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", @@ -6221,85 +7334,6 @@ ], "time": "2025-12-29T13:15:25+00:00" }, - { - "name": "composer/pcre", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, - "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-11-12T16:29:46+00:00" - }, { "name": "fakerphp/faker", "version": "v1.24.1", diff --git a/config/app.php b/config/app.php index 423eed5..3da8c50 100644 --- a/config/app.php +++ b/config/app.php @@ -60,12 +60,11 @@ |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which - | will be used by the PHP date and date-time functions. The timezone - | is set to "UTC" by default as it is suitable for most use cases. + | will be used by the PHP date and date-time functions. | */ - 'timezone' => 'UTC', + 'timezone' => 'Asia/Jakarta', /* |-------------------------------------------------------------------------- @@ -123,4 +122,4 @@ 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], -]; +]; \ No newline at end of file diff --git a/database/migrations/2026_02_07_150000_create_penerima_finals_table.php b/database/migrations/2026_02_07_150000_create_penerima_finals_table.php index a6c1412..cfff88c 100644 --- a/database/migrations/2026_02_07_150000_create_penerima_finals_table.php +++ b/database/migrations/2026_02_07_150000_create_penerima_finals_table.php @@ -14,22 +14,32 @@ public function up(): void Schema::create('penerima_finals', function (Blueprint $table) { $table->id(); - $table->foreignId('calon_penerima_id') - ->constrained('calon_penerimas') - ->onDelete('cascade'); + // Snapshot data warga + $table->string('nama_lengkap'); + $table->string('nik', 20); + $table->string('no_kk', 20); + // Snapshot wilayah + $table->unsignedBigInteger('rt_id')->nullable(); + $table->string('nomor_rt')->nullable(); + $table->string('nama_dusun')->nullable(); + + // Snapshot data penilaian + $table->string('pekerjaan')->nullable(); + $table->bigInteger('penghasilan')->default(0); + $table->integer('jumlah_tanggungan')->default(0); + $table->string('aset_kepemilikan')->nullable(); + $table->string('bantuan_lain')->nullable(); + $table->integer('usia')->default(0); + + // Snapshot hasil prediksi/final + $table->decimal('probability', 8, 4)->default(0); + $table->enum('status_verifikasi', ['pending', 'disetujui', 'ditolak'])->default('pending'); + + // Data arsip bantuan $table->date('tanggal_penetapan'); - - $table->string('periode_bantuan'); // contoh: 2026 Triwulan 1 - - $table->decimal('jumlah_bantuan', 15, 2); - - $table->enum('status_pencairan', [ - 'belum_cair', - 'sudah_cair' - ])->default('belum_cair'); - - $table->date('tanggal_pencairan')->nullable(); + $table->string('periode_bantuan'); + $table->decimal('jumlah_bantuan', 15, 2)->default(300000); $table->timestamps(); }); diff --git a/database/migrations/2026_02_27_133935_add_is_active_to_users_table.php b/database/migrations/2026_02_27_133935_add_is_active_to_users_table.php index dff022f..37e3f09 100644 --- a/database/migrations/2026_02_27_133935_add_is_active_to_users_table.php +++ b/database/migrations/2026_02_27_133935_add_is_active_to_users_table.php @@ -1,27 +1,22 @@ user(); - - // kalau user login tapi nonaktif -> logout + balik ke login - if ($user && (int)($user->is_active ?? 1) === 0) { - Auth::logout(); - $request->session()->invalidate(); - $request->session()->regenerateToken(); - - return redirect()->route('login') - ->with('error', 'Akun kamu sudah dinonaktifkan oleh Admin.'); - } - - return $next($request); + Schema::table('users', function (Blueprint $table) { + $table->boolean('is_active')->default(false)->after('role'); + }); } -} \ No newline at end of file + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('is_active'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_03_13_195602_create_laporan_publiks_table.php b/database/migrations/2026_03_13_195602_create_laporan_publiks_table.php new file mode 100644 index 0000000..9875aa6 --- /dev/null +++ b/database/migrations/2026_03_13_195602_create_laporan_publiks_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('judul'); + $table->string('file_path'); + $table->foreignId('uploaded_by')->nullable()->constrained('users')->nullOnDelete(); + $table->boolean('is_active')->default(true); + $table->timestamps(); + }); +} + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('laporan_publiks'); + } +}; diff --git a/public/favicon.ico b/public/favicon.ico index e69de29..e148811 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/render-apache.conf b/render-apache.conf new file mode 100644 index 0000000..f676abc --- /dev/null +++ b/render-apache.conf @@ -0,0 +1,12 @@ + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html/public + + + AllowOverride All + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + \ No newline at end of file diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 9a5b29a..85af2ad 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -1,20 +1,82 @@ -
+
-

Dashboard Kelurahan

-

Ringkasan data kelayakan penerima BLT-DD seluruh dusun

+

Dashboard Kelurahan

+

Ringkasan data kelayakan penerima BLT-DD seluruh dusun

-
- - - - +
+ + {{-- ── BELL NOTIFIKASI ── --}} +
+ + + +
+ + {{-- Clock --}} +
+ + + + +
+ + -
+
- {{-- GREETING + NOTIFIKASI --}} -
-
-
+ {{-- GREETING + NOTIFIKASI (asli tidak diubah, hanya ganti class ke inline style) --}} +
+
+
{{ strtoupper(substr(Auth::user()->name ?? 'A', 0, 1)) }}
-

Selamat datang, {{ Auth::user()->name ?? 'Admin' }} 👋

-

Panel admin — kelola dan pantau seluruh data warga.

+

Selamat datang, {{ Auth::user()->name ?? 'Admin' }} 👋

+

Panel admin — kelola dan pantau seluruh data warga.

-
-
- +
+
+
-

{{ $totalPending ?? 0 }} data menunggu verifikasi

- Proses sekarang → +

{{ $totalPending ?? 0 }} data menunggu verifikasi

+ Proses sekarang →
- {{-- STAT CARDS --}} -
+ {{-- STAT CARDS (asli tidak diubah, ganti ke inline style) --}} +
-
-
-
-
-

Total Warga Masuk

-

{{ $totalWarga ?? 0 }}

-

Dari semua dusun

+
+
+
+

Total Warga Masuk

+

{{ $totalWarga ?? 0 }}

+

Dari semua dusun

-
- - - +
+
-
-
-
-
-

Pending Verifikasi

-

{{ $totalPending ?? 0 }}

-

Belum diproses

+
+
+
+

Pending Verifikasi

+

{{ $totalPending ?? 0 }}

+

Belum diproses

-
- - - +
+
-
-
-
-
-

Diterima / Ditetapkan

-

{{ $totalPenerima ?? 0 }}

-

Sudah ditetapkan

+
+
+
+

Diterima / Ditetapkan

+

{{ $totalPenerima ?? 0 }}

+

Sudah ditetapkan

-
- - - +
+
-
-
-
-
-

Ditolak

-

{{ $totalDitolak ?? 0 }}

-

Tidak memenuhi syarat

+
+
+
+

Ditolak

+

{{ $totalDitolak ?? 0 }}

+

Tidak memenuhi syarat

-
- - - +
+
- {{-- GRAFIK + DATA PER DUSUN --}} -
+ {{-- GRAFIK + DATA PER DUSUN (logika PHP asli 100% tidak diubah) --}} +
- {{-- ✅ GRAFIK POLIGON LINE PER DUSUN (SVG murni, seperti referensi) --}} -
-
-
-
-

Jumlah Warga per Dusun

+
+
+
+
+

Jumlah Warga per Dusun

- + {{ $wargaPerDusun->sum('total') }} total warga
-
+
@php $dusuns = $wargaPerDusun->values(); $n = max($dusuns->count(), 1); $maxV = max((int)$dusuns->max('total'), 1); - // Canvas $W = 560; $H = 210; $pL = 40; $pR = 20; $pT = 30; $pB = 45; $cW = $W - $pL - $pR; $cH = $H - $pT - $pB; - // Y ticks (0 .. maxV, max 5 steps) $step = max(1, (int)ceil($maxV / 4)); $yTicks = range(0, $maxV + $step, $step); - // Compute point coords $pts = []; foreach ($dusuns as $i => $d) { $x = $pL + ($n === 1 ? $cW / 2 : ($i / ($n - 1)) * $cW); @@ -173,87 +284,53 @@ function updateAdminClock() { - {{-- Y grid + label --}} @foreach($yTicks as $t) @if($t <= $maxV + $step) @php $gy = $pT + $cH - min($t, $maxV) / $maxV * $cH; @endphp - - {{ $t }} + + {{ $t }} @endif @endforeach - {{-- Label sumbu Y --}} - Jumlah Warga - - {{-- Sumbu X --}} - - {{-- Panah X --}} - - - {{-- Sumbu Y --}} - - {{-- Panah Y --}} - - - {{-- Area fill --}} + Jumlah Warga + + + + - - {{-- Garis poligon biru --}} - - - {{-- Titik & label per dusun --}} + @foreach($pts as $p) - {{-- Dot --}} - - {{-- Nilai di atas titik --}} - {{ $p['val'] }} - {{-- Nama dusun di bawah sumbu X --}} - {{ Str::limit($p['name'], 9) }} + + {{ $p['val'] }} + {{ Str::limit($p['name'], 9) }} @endforeach - - {{-- Label "Nama Dusun" sumbu X --}} - Dusun + Dusun
- {{-- DATA PER DUSUN (tidak diubah) --}} -
-
-
-

Data Per Dusun

+
+
+
+

Data Per Dusun

-
+
@php $maxDusun = $wargaPerDusun->max('total') ?: 1; @endphp @foreach($wargaPerDusun as $data) -
-
-
-
- +
+
+
+
+
- {{ $data->dusun }} + {{ $data->dusun }}
- {{ $data->total }} + {{ $data->total }}
-
-
+
+
@endforeach @@ -262,88 +339,141 @@ function updateAdminClock() {
- {{-- TOP 10 (tidak diubah) --}} -
-
-
-
-

Top 10 Probabilitas Tertinggi

+ {{-- TOP 10 — full width, tambah kolom Dusun & RT, logika asli 100% tidak diubah --}} +
+
+
+
+
+

Top 10 Probabilitas Tertinggi

+

Seluruh dusun — diurutkan dari probabilitas terbesar

+
+
+
+
+ ≥70% + 40–70% + <40% +
+ + Lihat Semua + +
- Lihat Semua →
-
- + +
+
- - - - - - - - + + + + + + + + + - + @forelse($topWarga as $index => $warga) - - + {{-- Rank (asli) --}} + - - - - - + + {{-- Dusun (ditambahkan — pakai $warga->dusun yang sudah ada di data asli) --}} + + + {{-- RT (ditambahkan — pakai $warga->rt jika ada) --}} + + + {{-- Penghasilan (asli) --}} + + + {{-- Probabilitas (asli) --}} + - @empty - @endforelse diff --git a/resources/views/admin/data-warga.blade.php b/resources/views/admin/data-warga.blade.php index 1bc0f49..0536a25 100644 --- a/resources/views/admin/data-warga.blade.php +++ b/resources/views/admin/data-warga.blade.php @@ -49,6 +49,8 @@ .btn-detail{background:#f3f4f6;color:#374151;} .btn-validasi{background:#eff6ff;color:#1d4ed8;} .btn-kirim{background:#fffbeb;color:#b45309;} + .btn-reset{display:inline-flex;align-items:center;gap:6px;padding:9px 14px;background:#ef4444;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;box-shadow:0 2px 8px rgba(239,68,68,.22);white-space:nowrap;} + .btn-reset:hover{filter:brightness(.95);} .info-chip{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;font-size:10.5px;font-weight:700;background:#f8fafc;color:#475569;border:1px solid #e2e8f0;} @@ -56,9 +58,10 @@ .prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;} .prob-bar{height:100%;border-radius:99px;} + /* ── MODAL FIX — scroll handled by .m-wrap, not .m-body ── */ .m-backdrop{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,.45);backdrop-filter:blur(3px);} - .m-wrap{position:fixed;inset:0;z-index:50;overflow-y:auto;pointer-events:none;display:flex;align-items:center;justify-content:center;padding:16px;} - .m-box{width:100%;max-width:620px;background:#fff;border-radius:22px;box-shadow:0 24px 64px rgba(0,0,0,.18);overflow:hidden;display:flex;flex-direction:column;max-height:90vh;pointer-events:auto;} + .m-wrap{position:fixed;inset:0;z-index:50;overflow-y:auto;pointer-events:none;display:flex;align-items:flex-start;justify-content:center;padding:24px 16px;} + .m-box{width:100%;max-width:620px;background:#fff;border-radius:22px;box-shadow:0 24px 64px rgba(0,0,0,.18);overflow:hidden;display:flex;flex-direction:column;pointer-events:auto;margin:auto;} .mhero{position:relative;overflow:hidden;padding:18px 22px;background:linear-gradient(135deg,#1e40af 0%,#2563eb 55%,#3b82f6 100%);} .mhero-b1{position:absolute;top:-20px;right:-20px;width:90px;height:90px;background:rgba(255,255,255,.08);border-radius:50%;} .mhero-b2{position:absolute;bottom:-22px;left:35%;width:70px;height:70px;background:rgba(255,255,255,.06);border-radius:50%;} @@ -70,7 +73,7 @@ .m-close:hover{background:rgba(255,255,255,.28);} .m-close svg{width:14px;height:14px;stroke:currentColor;} .m-st-pill{margin-top:10px;position:relative;display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:20px;background:rgba(255,255,255,.18);color:#fff;border:1px solid rgba(255,255,255,.25);font-size:11px;font-weight:700;} - .m-body{overflow-y:auto;flex:1;padding:14px;background:#f8fafc;display:flex;flex-direction:column;gap:10px;} + .m-body{overflow-y:visible;flex:1;padding:14px;background:#f8fafc;display:flex;flex-direction:column;gap:10px;} .msec{background:#fff;border-radius:14px;border:1.5px solid #f1f5f9;overflow:hidden;} .msec-h{padding:8px 14px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:7px;} .msec-h .bar{width:3px;height:13px;border-radius:3px;flex-shrink:0;} @@ -105,21 +108,39 @@

Pencarian

- -
+ + +
+ + @if(request()->filled('q')) @endif + +
+
+ @csrf + @method('DELETE') + + +
{{-- TABLE --}} @@ -448,7 +469,8 @@ function openDetailModal(id){ set('d_nik',d.nik||'-');set('d_no_kk',d.no_kk||'-');set('d_nama',d.nama_lengkap||'-'); set('d_jk',d.jenis_kelamin||'-');set('d_lahir',(d.tempat_lahir||'-')+', '+(d.tanggal_lahir||'-')); set('d_usia',d.usia?d.usia+' tahun':'-');set('d_kawin',d.status_perkawinan||'-'); - set('d_alamat',d.alamat||'-');set('d_dusun',d.desa||'-'); + set('d_alamat', d.alamat || '-'); + set('d_dusun', d.dusun || '-'); set('d_rt','RT '+String(d.rt||'-').padStart(3,'0'));set('d_aset',d.aset_kepemilikan||'-'); set('d_pekerjaan',d.pekerjaan||'-'); set('d_penghasilan','Rp '+Number(d.penghasilan||0).toLocaleString('id-ID')); diff --git a/resources/views/admin/laporan-pdf.blade.php b/resources/views/admin/laporan-pdf.blade.php new file mode 100644 index 0000000..2f6ad5c --- /dev/null +++ b/resources/views/admin/laporan-pdf.blade.php @@ -0,0 +1,192 @@ + + + + + Laporan Penerima BLT-DD — Desa Ngerong + + + + +@php + $totalPenerima = $laporans->count(); + $totalDana = $laporans->sum('jumlah_bantuan') ?: ($totalPenerima * 300000); + $avgProb = $laporans->avg(function ($item) { + $p = $item->probability ?? 0; + return $p <= 1 ? $p * 100 : $p; + }); + $periodeAktif = $laporans->pluck('periode_bantuan')->filter()->unique()->count(); +@endphp + +{{-- HEADER --}} +
+
+
+
Laporan Penerima BLT-DD
+
Desa Ngerong, Kec. Gempol, Kab. Pasuruan — Penerima final BLT Dana Desa
+
+
+
Laporan Resmi
+
Dicetak: {{ now()->translatedFormat('d F Y') }}
+
+
+ +{{-- SUMMARY --}} +
+
+
{{ $totalPenerima }}
+
Total Penerima
+
+
+
Rp {{ number_format($totalDana, 0, ',', '.') }}
+
Total Dana
+
+
+
{{ number_format($avgProb, 1) }}%
+
Rata-rata Probabilitas
+
+
+
{{ $periodeAktif }}
+
Periode Aktif
+
+
+ +{{-- LABEL --}} +
Daftar Penerima Final
+ +{{-- TABLE --}} +
RankNamaNIKDusunPenghasilanProbabilitasStatus
RankNamaNIKDusunRTPenghasilanProbabilitasStatus
+
@if($index == 0) -
1
+
1
@elseif($index == 1) -
2
+
2
@elseif($index == 2) -
3
+
3
@else -
{{ $index + 1 }}
+
{{ $index + 1 }}
@endif
-
-
- {{ strtoupper(substr($warga->nama, 0, 1)) }} + + {{-- Nama (asli) --}} +
+
+
+ {{ strtoupper(substr($warga->nama, 0, 1)) }}
-
{{ $warga->nama }}
-
{{ $warga->pekerjaan }}
+
{{ $warga->nama }}
+
{{ $warga->pekerjaan }}
{{ $warga->nik }}{{ $warga->dusun }}Rp {{ number_format($warga->penghasilan, 0, ',', '.') }} -
-
-
+ + {{-- NIK (asli) --}} +
+ {{ $warga->nik }} + + + {{ $warga->dusun }} + + + RT {{ str_pad($warga->rt ?? '0', 3, '0', STR_PAD_LEFT) }} + + Rp {{ number_format($warga->penghasilan, 0, ',', '.') }} + + @php + $sc = $warga->probabilitas >= 70 ? '#10b981' : ($warga->probabilitas >= 40 ? '#f59e0b' : '#f43f5e'); + $scB = $warga->probabilitas >= 70 ? '#f0fdf4' : ($warga->probabilitas >= 40 ? '#fffbeb' : '#fff1f2'); + @endphp +
+
+
- + {{ number_format($warga->probabilitas, 1) }}%
+ + {{-- Status (asli) --}} + @if($warga->status == 'pending') - Pending + + Pending + @elseif($warga->status == 'diterima') - Diterima + + Diterima + @elseif($warga->status == 'ditolak') - Ditolak + + Ditolak + @else - {{ ucfirst($warga->status) }} + + {{ ucfirst($warga->status) }} + @endif
-
- - - +
+
+
+ +
+

Belum ada data warga

-

Belum ada data warga

+ + + + + + + + + + + + + + + + @forelse($laporans as $index => $item) + @php + $prob = $item->probability ?? 0; + if ($prob <= 1) $prob = $prob * 100; + $nama = $item->nama_lengkap ?? '-'; + $init = strtoupper(substr($nama, 0, 1)); + $sc = $prob >= 70 ? '#10b981' : ($prob >= 40 ? '#f59e0b' : '#f43f5e'); + $jml = $item->jumlah_bantuan ?? 300000; + @endphp + + + + + + + + + + + + + @empty + + + + @endforelse + +
#NamaNIKNo KKRTDusunProbabilitasTgl PenetapanPeriodeJumlah Bantuan
{{ $index + 1 }}{{ $init }}{{ $nama }}{{ $item->nik ?? '-' }}{{ $item->no_kk ?? '-' }}RT {{ str_pad($item->nomor_rt ?? '0', 3, '0', STR_PAD_LEFT) }}{{ $item->nama_dusun ?? '-' }} + + + + {{ number_format($prob, 1) }}% + {{ $item->tanggal_penetapan?->format('d-m-Y') ?? '-' }}{{ $item->periode_bantuan ?? '-' }}Rp {{ number_format($jml, 0, ',', '.') }}
Belum ada data penerima final.
+ +{{-- FOOTER --}} +
+
+
SiBantuDes — Sistem Informasi Bantuan Langsung Tunai Dana Desa
+
Digenerate otomatis pada {{ now()->translatedFormat('d F Y, H:i') }} WIB
+
{{ $totalPenerima }} penerima • Total Rp {{ number_format($totalDana, 0, ',', '.') }}
+
+
+
+
Mengetahui,
+
+
Kepala Desa Ngerong
+
Kec. Gempol, Kab. Pasuruan
+
+
+
+ + + \ No newline at end of file diff --git a/resources/views/admin/laporan.balde.php b/resources/views/admin/laporan.balde.php deleted file mode 100644 index 054a032..0000000 --- a/resources/views/admin/laporan.balde.php +++ /dev/null @@ -1,24 +0,0 @@ - - -@section('content') -
- -
-

Laporan

-

Rekap data penerima & status, siap untuk cetak/ekspor.

-
- -
-

- Placeholder Laporan. Biasanya isi: -

- -
    -
  • Rekap per dusun
  • -
  • Rekap diterima/ditolak
  • -
  • Export PDF/Excel
  • -
-
- -
-
\ No newline at end of file diff --git a/resources/views/admin/laporan.blade.php b/resources/views/admin/laporan.blade.php new file mode 100644 index 0000000..7e810c0 --- /dev/null +++ b/resources/views/admin/laporan.blade.php @@ -0,0 +1,351 @@ + + + + +
+ +
+

+ Laporan Penerima BLT-DD +

+

+ Kelurahan Ngerong — warga yang telah ditetapkan sebagai penerima final BLT Dana Desa +

+
+ + @if(session('success')) +
+ + + + {{ session('success') }} +
+ @endif + + @php + $totalPenerima = $laporans->count(); + $totalBantuan = $laporans->sum('jumlah_bantuan'); + $avgProb = $laporans->avg(function($i){ + $p = $i->probability ?? 0; + return $p <= 1 ? $p * 100 : $p; + }); + $periodes = $laporans->pluck('periode_bantuan')->filter()->unique()->count(); + @endphp + +
+
+
+ + + +
+
{{ $totalPenerima }}
+
Total Penerima
+
+ +
+
+ + + +
+
Rp {{ number_format($totalBantuan, 0, ',', '.') }}
+
Total Dana Tersalurkan
+
+ +
+
+ + + +
+
{{ number_format($avgProb, 1) }}%
+
Rata-rata Probabilitas
+
+ +
+
+ + + +
+
{{ $periodes }}
+
Periode Aktif
+
+
+ +
+
+
+
+ Ekspor & Distribusi Data +
+
+ +
+ Unduh PDF + Unduh Excel + +
+ @csrf + +
+ + {{ $totalPenerima }} penerima terdaftar +
+
+ +
+
+
+
+ Upload PDF Laporan Publik +
+
+ +
+
+ @csrf + + + +
+ + @if(isset($laporanPubliks) && $laporanPubliks->count() > 0) +
+ @foreach($laporanPubliks as $pdf) +
+
+
{{ $pdf->judul }}
+
+ Diupload: {{ $pdf->created_at->format('d-m-Y H:i') }} +
+
+ +
+ + Lihat PDF + + +
+ @csrf + @method('DELETE') + +
+
+
+ @endforeach +
+ @endif +
+
+ +
+
+
+
+ Daftar Penerima Final +
+ {{ $totalPenerima }} data +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + @forelse($laporans as $index => $item) + @php + $nama = $item->nama_lengkap ?? '-'; + $init = strtoupper(substr($nama, 0, 1)); + $prob = $item->probability ?? 0; + if ($prob <= 1) $prob = $prob * 100; + + $sc = $prob >= 70 ? '#10b981' : ($prob >= 40 ? '#f59e0b' : '#f43f5e'); + $scBg = $prob >= 70 ? '#f0fdf4' : ($prob >= 40 ? '#fffbeb' : '#fff1f2'); + $scBd = $prob >= 70 ? '#bbf7d0' : ($prob >= 40 ? '#fde68a' : '#fecdd3'); + + $bantuanLain = strtolower($item->bantuan_lain ?? 'tidak'); + @endphp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @empty + + + + @endforelse + +
#NamaNIKNo KKRTDusunPekerjaanPenghasilanTanggunganAsetBantuan LainUsiaProbabilitasStatusTgl PenetapanPeriodeJumlah Bantuan
{{ $index + 1 }} +
+
{{ $init }}
+ {{ $nama }} +
+
{{ $item->nik ?? '-' }}{{ $item->no_kk ?? '-' }} + + RT {{ str_pad($item->nomor_rt ?? '0', 3, '0', STR_PAD_LEFT) }} + + {{ $item->nama_dusun ?? '-' }}{{ $item->pekerjaan ?? '-' }}Rp {{ number_format($item->penghasilan ?? 0, 0, ',', '.') }}{{ $item->jumlah_tanggungan ?? 0 }}{{ $item->aset_kepemilikan ?? '-' }} + @if($bantuanLain === 'ya') + Ya + @else + Tidak + @endif + {{ $item->usia ?? 0 }} +
+
+
+
+ {{ number_format($prob, 1) }}% +
+
+ + + {{ ucfirst($item->status_verifikasi ?? '-') }} + + {{ $item->tanggal_penetapan?->format('d-m-Y') ?? '-' }}{{ $item->periode_bantuan ?? '-' }}Rp {{ number_format($item->jumlah_bantuan ?? 300000, 0, ',', '.') }}
+
+
+ + + +
+

Belum ada data penerima final

+
+
+
+ +
+ SiBantuDes · Kelurahan Ngerong +
+
+ +
+ +
\ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 7993c73..6eecab2 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -4,125 +4,488 @@ - Login - Sistem BLT-DD + Login — SiBantuDes @vite(['resources/css/app.css', 'resources/js/app.js']) + + + + - -
-
- -