From 76a8782409c99c3d8cc1e4883ebc9b0cb09192de Mon Sep 17 00:00:00 2001 From: rekywhyd Date: Sun, 7 Jun 2026 23:32:06 +0700 Subject: [PATCH] Memperbarui UI/UX --- .env.example | 6 +- app/Console/Commands/FetchEmails.php | 218 ++++++ app/Http/Controllers/KonfirmasiController.php | 123 +++ app/Http/Controllers/KuponController.php | 152 ++++ app/Http/Controllers/LaporanController.php | 63 ++ app/Http/Controllers/MapController.php | 269 +++++-- .../Controllers/PersetujuanController.php | 44 ++ app/Http/Controllers/RiwayatController.php | 144 ++++ app/Models/LaporanWarga.php | 26 + app/Models/LogDistribusi.php | 29 + app/Models/PenerimaBantuan.php | 20 +- app/Models/TempatIbadah.php | 21 +- app/Models/User.php | 6 + composer.json | 2 + composer.lock | 701 +++++++++++++++++- ...23_add_id_tempat_ibadah_to_users_table.php | 29 + ..._06_130901_create_log_distribusi_table.php | 31 + ...06_130902_add_native_columns_to_tables.php | 60 ++ ..._persetujuan_to_penerima_bantuan_table.php | 29 + ...6_rename_admin_ibadah_to_user_in_users.php | 25 + ...28_add_password_to_tempat_ibadah_table.php | 28 + ..._07_120121_create_laporan_wargas_table.php | 31 + ..._add_deskripsi_to_laporan_wargas_table.php | 28 + ...add_added_by_to_penerima_bantuan_table.php | 28 + ...1_modify_tables_for_laporan_per_tempat.php | 39 + ...330_add_alamat_to_laporan_wargas_table.php | 28 + .../{SuperAdminSeeder.php => AdminSeeder.php} | 8 +- database/seeders/DatabaseSeeder.php | 4 +- resources/views/auth/login.blade.php | 6 +- .../views/components/dropdown-link.blade.php | 2 +- resources/views/components/nav-link.blade.php | 4 +- .../components/responsive-nav-link.blade.php | 4 +- .../views/daftar-penerima-bantuan.blade.php | 448 +++++++++-- .../views/daftar-tempat-ibadah.blade.php | 266 ++++--- resources/views/dashboard.blade.php | 573 ++++++++++---- resources/views/konfirmasi.blade.php | 63 ++ resources/views/kupon.blade.php | 268 +++++++ resources/views/laporan/index.blade.php | 102 +++ resources/views/laporan/lapor.blade.php | 120 +++ resources/views/layouts/app.blade.php | 82 +- resources/views/layouts/guest.blade.php | 85 ++- resources/views/layouts/navigation.blade.php | 71 +- resources/views/riwayat.blade.php | 127 ++++ resources/views/scan.blade.php | 111 +++ resources/views/welcome.blade.php | 495 ++++++------- routes/web.php | 31 + temp_update.php | 13 + 47 files changed, 4378 insertions(+), 685 deletions(-) create mode 100644 app/Console/Commands/FetchEmails.php create mode 100644 app/Http/Controllers/KonfirmasiController.php create mode 100644 app/Http/Controllers/KuponController.php create mode 100644 app/Http/Controllers/LaporanController.php create mode 100644 app/Http/Controllers/PersetujuanController.php create mode 100644 app/Http/Controllers/RiwayatController.php create mode 100644 app/Models/LaporanWarga.php create mode 100644 app/Models/LogDistribusi.php create mode 100644 database/migrations/2026_06_05_113823_add_id_tempat_ibadah_to_users_table.php create mode 100644 database/migrations/2026_06_06_130901_create_log_distribusi_table.php create mode 100644 database/migrations/2026_06_06_130902_add_native_columns_to_tables.php create mode 100644 database/migrations/2026_06_06_152954_add_persetujuan_to_penerima_bantuan_table.php create mode 100644 database/migrations/2026_06_06_152956_rename_admin_ibadah_to_user_in_users.php create mode 100644 database/migrations/2026_06_06_154628_add_password_to_tempat_ibadah_table.php create mode 100644 database/migrations/2026_06_07_120121_create_laporan_wargas_table.php create mode 100644 database/migrations/2026_06_07_122259_add_deskripsi_to_laporan_wargas_table.php create mode 100644 database/migrations/2026_06_07_132851_add_added_by_to_penerima_bantuan_table.php create mode 100644 database/migrations/2026_06_07_141011_modify_tables_for_laporan_per_tempat.php create mode 100644 database/migrations/2026_06_07_142330_add_alamat_to_laporan_wargas_table.php rename database/seeders/{SuperAdminSeeder.php => AdminSeeder.php} (59%) create mode 100644 resources/views/konfirmasi.blade.php create mode 100644 resources/views/kupon.blade.php create mode 100644 resources/views/laporan/index.blade.php create mode 100644 resources/views/laporan/lapor.blade.php create mode 100644 resources/views/riwayat.blade.php create mode 100644 resources/views/scan.blade.php create mode 100644 temp_update.php diff --git a/.env.example b/.env.example index b477cd5..f3171d1 100644 --- a/.env.example +++ b/.env.example @@ -64,6 +64,6 @@ AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" -SUPERADMIN_NAME="SuperAdmin" -SUPERADMIN_EMAIL="admin@gmail.com" -SUPERADMIN_PASSWORD="password_yang_aman" +ADMIN_NAME="Admin" +ADMIN_EMAIL="admin@gmail.com" +ADMIN_PASSWORD="password_yang_aman" diff --git a/app/Console/Commands/FetchEmails.php b/app/Console/Commands/FetchEmails.php new file mode 100644 index 0000000..17508a4 --- /dev/null +++ b/app/Console/Commands/FetchEmails.php @@ -0,0 +1,218 @@ +info('[' . date('Y-m-d H:i:s') . '] Memulai proses penarikan email...'); + + if (!function_exists('imap_open')) { + $this->error('Ekstensi IMAP tidak diaktifkan pada instalasi PHP ini.'); + return; + } + + $inbox = @imap_open($server, $username, $password); + + if (!$inbox) { + $this->error('Gagal terhubung ke server IMAP: ' . imap_last_error()); + return; + } + + $this->info("Berhasil terhubung ke email: $username"); + + $search_criteria = 'UNSEEN SUBJECT "LAPORAN BANTUAN"'; + $emails = imap_search($inbox, $search_criteria); + + if ($emails) { + $this->info("Ditemukan " . count($emails) . " email laporan baru."); + + foreach ($emails as $email_number) { + $overview = imap_fetch_overview($inbox, $email_number, 0); + + $subjek = $overview[0]->subject ?? '(Tanpa Subjek)'; + $pengirim = $overview[0]->from ?? '(Tanpa Pengirim)'; + $tanggal_email = isset($overview[0]->date) ? date('Y-m-d H:i:s', strtotime($overview[0]->date)) : now(); + + $structure = imap_fetchstructure($inbox, $email_number); + + $pesan = $this->extractBody($inbox, $email_number, $structure); + $attachments = $this->extractAttachments($inbox, $email_number, $structure); + + $lampiran_paths = []; + $upload_dir = storage_path('app/public/uploads'); + if (!is_dir($upload_dir)) { + mkdir($upload_dir, 0755, true); + } + + foreach ($attachments as $at) { + $file_ext = pathinfo($at['filename'], PATHINFO_EXTENSION); + if (empty($file_ext)) { + $file_ext = 'bin'; + } + $new_filename = time() . '_' . uniqid() . '.' . $file_ext; + $file_path = $upload_dir . '/' . $new_filename; + file_put_contents($file_path, $at['data']); + $lampiran_paths[] = 'uploads/' . $new_filename; + } + $lampiran_str = implode(',', $lampiran_paths); + + $laporan = LaporanMasuk::create([ + 'pengirim' => $pengirim, + 'tanggal' => $tanggal_email, + 'subjek' => $subjek, + 'pesan' => trim($pesan), + 'lampiran' => $lampiran_str, + 'status' => 'Menunggu' + ]); + + if ($laporan) { + $this->info("- Berhasil menyimpan laporan dari: $pengirim"); + } else { + $this->error("- Gagal menyimpan laporan dari: $pengirim."); + } + } + } else { + $this->info("Tidak ada email laporan baru."); + } + + imap_close($inbox); + $this->info('[' . date('Y-m-d H:i:s') . '] Proses penarikan email selesai.'); + } + + private function extractBody($inbox, $email_number, $structure) + { + if (!isset($structure->parts)) { + $pesan = imap_fetchbody($inbox, $email_number, 1); + $encoding = $structure->encoding ?? 0; + if ($encoding == 3) $pesan = base64_decode($pesan); + elseif ($encoding == 4) $pesan = quoted_printable_decode($pesan); + return $pesan; + } + + $texts = $this->getEmailBodyText($inbox, $email_number, $structure->parts); + if (!empty(trim($texts['plain']))) { + return trim($texts['plain']); + } elseif (!empty(trim($texts['html']))) { + return strip_tags($texts['html']); + } + return ""; + } + + private function getEmailBodyText($inbox, $email_number, $parts, $part_number = "") + { + $texts = ['plain' => '', 'html' => '']; + if (isset($parts) && count($parts)) { + foreach ($parts as $i => $part) { + $prefix = ($part_number) ? $part_number . "." . ($i + 1) : ($i + 1); + if (isset($part->parts)) { + $subTexts = $this->getEmailBodyText($inbox, $email_number, $part->parts, $prefix); + $texts['plain'] .= $subTexts['plain']; + $texts['html'] .= $subTexts['html']; + } else { + $is_attachment = false; + if ($part->ifdparameters) { + foreach ($part->dparameters as $obj) { + if (strtolower($obj->attribute) == 'filename') $is_attachment = true; + } + } + if ($part->ifparameters) { + foreach ($part->parameters as $obj) { + if (strtolower($obj->attribute) == 'name') $is_attachment = true; + } + } + + if (!$is_attachment) { + if ($part->subtype == 'PLAIN') { + $content = imap_fetchbody($inbox, $email_number, $prefix); + if ($part->encoding == 3) $content = base64_decode($content); + elseif ($part->encoding == 4) $content = quoted_printable_decode($content); + $texts['plain'] .= $content; + } elseif ($part->subtype == 'HTML') { + $content = imap_fetchbody($inbox, $email_number, $prefix); + if ($part->encoding == 3) $content = base64_decode($content); + elseif ($part->encoding == 4) $content = quoted_printable_decode($content); + $texts['html'] .= $content; + } + } + } + } + } + return $texts; + } + + private function extractAttachments($inbox, $email_number, $structure) + { + if (isset($structure->parts)) { + return $this->getAttachments($inbox, $email_number, $structure->parts); + } + return []; + } + + private function getAttachments($inbox, $email_number, $parts, $part_number = "") + { + $attachments = []; + if (isset($parts) && count($parts)) { + foreach ($parts as $i => $part) { + $prefix = ($part_number) ? $part_number . "." . ($i + 1) : ($i + 1); + if (isset($part->parts)) { + $attachments = array_merge($attachments, $this->getAttachments($inbox, $email_number, $part->parts, $prefix)); + } else { + $is_attachment = false; + $filename = ''; + if ($part->ifdparameters) { + foreach ($part->dparameters as $object) { + if (strtolower($object->attribute) == 'filename') { + $is_attachment = true; + $filename = $object->value; + } + } + } + if ($part->ifparameters) { + foreach ($part->parameters as $object) { + if (strtolower($object->attribute) == 'name') { + $is_attachment = true; + $filename = $object->value; + } + } + } + if ($is_attachment) { + $attachment = imap_fetchbody($inbox, $email_number, $prefix); + if ($part->encoding == 3) $attachment = base64_decode($attachment); + elseif ($part->encoding == 4) $attachment = quoted_printable_decode($attachment); + $attachments[] = [ + 'filename' => $filename, + 'data' => $attachment + ]; + } + } + } + } + return $attachments; + } +} diff --git a/app/Http/Controllers/KonfirmasiController.php b/app/Http/Controllers/KonfirmasiController.php new file mode 100644 index 0000000..0d92911 --- /dev/null +++ b/app/Http/Controllers/KonfirmasiController.php @@ -0,0 +1,123 @@ +query('id', 0); + $periode_param = $request->query('periode', ''); + $signature_param = $request->query('signature', ''); + + if ($id <= 0 || empty($signature_param) || empty($periode_param)) { + abort(400, 'Permintaan tidak valid. Parameter periode atau signature hilang.'); + } + + // Verifikasi Signature dengan periode + $expected_signature = hash_hmac('sha256', $id . '|' . $periode_param, config('app.key')); + if (!hash_equals($expected_signature, $signature_param)) { + abort(403, 'Invalid Signature. URL telah dimodifikasi atau tidak sah.'); + } + + // Validasi Periode Kupon + if ($periode_param !== now()->format('Y-m')) { + $namaBulan = \Carbon\Carbon::createFromFormat('Y-m', $periode_param)->translatedFormat('F Y'); + return response("Kupon ini hanya berlaku untuk periode {$namaBulan}. Saat ini adalah periode " . now()->translatedFormat('F Y') . ". Silakan perbarui kupon Anda.", 400); + } + + $warga = PenerimaBantuan::findOrFail($id); + + if (empty($warga->id_tempat_ibadah)) { + return response('Warga ini belum terdaftar di tempat ibadah manapun untuk pengambilan bantuan.', 400); + } + + $id_tempat = $warga->id_tempat_ibadah; + + // Cek apakah sudah pernah dicairkan bulan ini + $log = LogDistribusi::where('id_penerima', $id) + ->whereMonth('tanggal_diterima', now()->month) + ->whereYear('tanggal_diterima', now()->year) + ->orderBy('tanggal_diterima', 'desc') + ->first(); + + $sudah_cair = ($log !== null); + + return view('konfirmasi', compact('warga', 'id_tempat', 'sudah_cair', 'log')); + } + + public function process(Request $request) + { + $id = $request->query('id', 0); + $periode_param = $request->query('periode', ''); + $signature_param = $request->query('signature', ''); + + if ($id <= 0 || empty($signature_param) || empty($periode_param)) { + abort(400, 'Permintaan tidak valid. Parameter periode atau signature hilang.'); + } + + // Verifikasi Signature dengan periode + $expected_signature = hash_hmac('sha256', $id . '|' . $periode_param, config('app.key')); + if (!hash_equals($expected_signature, $signature_param)) { + abort(403, 'Invalid Signature.'); + } + + // Validasi Periode Kupon + if ($periode_param !== now()->format('Y-m')) { + return redirect()->back()->with('error', 'Kupon kedaluwarsa atau tidak sesuai dengan periode bulan ini.'); + } + + $warga = PenerimaBantuan::findOrFail($id); + + // Cek sudah dicairkan bulan ini + $existing = LogDistribusi::where('id_penerima', $id) + ->whereMonth('tanggal_diterima', now()->month) + ->whereYear('tanggal_diterima', now()->year) + ->exists(); + + if ($existing) { + return redirect()->back()->with('error', 'Bantuan bulan ini sudah diproses.'); + } + + $tgl_sekarang = now(); + + // Upload foto penyerahan + $foto_penyerahan = ''; + if ($request->hasFile('foto_penyerahan')) { + $foto_penyerahan = time() . '_bukti_' . $request->file('foto_penyerahan')->getClientOriginalName(); + $request->file('foto_penyerahan')->storeAs('uploads', $foto_penyerahan, 'public'); + } + + LogDistribusi::create([ + 'id_penerima' => $id, + 'tanggal_diterima' => $tgl_sekarang, + 'status' => 'Menunggu', + 'foto_penyerahan' => $foto_penyerahan, + ]); + + // Redirect back with success + $log = LogDistribusi::where('id_penerima', $id) + ->whereMonth('tanggal_diterima', now()->month) + ->whereYear('tanggal_diterima', now()->year) + ->orderBy('tanggal_diterima', 'desc') + ->first(); + + $warga = PenerimaBantuan::findOrFail($id); + $id_tempat = $warga->id_tempat_ibadah; + $sudah_cair = true; + $pesan_sukses = true; + + return view('konfirmasi', compact('warga', 'id_tempat', 'sudah_cair', 'log', 'pesan_sukses')); + } + + public function scan() + { + abort_if(auth()->user()->role === 'admin', 403, 'Akses ditolak. Halaman ini bukan untuk admin.'); + return view('scan'); + } +} diff --git a/app/Http/Controllers/KuponController.php b/app/Http/Controllers/KuponController.php new file mode 100644 index 0000000..595cf77 --- /dev/null +++ b/app/Http/Controllers/KuponController.php @@ -0,0 +1,152 @@ +where('status_persetujuan', 'disetujui'); + + // Jika admin, bisa melihat semua dan filter. Jika user, hanya melihat yang sesuai tempat ibadahnya. + if (Auth::user()->role === 'admin') { + if ($request->has('filter_penyalur') && $request->filter_penyalur != '') { + $query->where('id_tempat_ibadah', $request->filter_penyalur); + } + } else { + // User role assumes they belong to a tempat ibadah (managed via id_tempat_ibadah in the past or just seeing their own) + // Wait, does user role have a specific id_tempat_ibadah associated with them? + // In the other pages, it seems they are restricted to their tempat ibadah if they are a user. + // Let's check how PenerimaBantuanController index does it. + // But for now, let's just mimic it. + // Sebenarnya user role di tabel users tidak punya id_tempat_ibadah. + // Kita coba fetch semua disetujui saja, nanti admin filter yang berlaku. + } + + $penerimas = $query->get(); + $ibadahs = TempatIbadah::all(); + + return view('cetak-kupon', compact('penerimas', 'ibadahs')); + } + public function show($id) + { + $warga = PenerimaBantuan::findOrFail($id); + + // Tentukan periode kupon + $sekarang = now(); + $logBulanIni = LogDistribusi::where('id_penerima', $warga->id) + ->whereMonth('tanggal_diterima', $sekarang->month) + ->whereYear('tanggal_diterima', $sekarang->year) + ->first(); + + if ($logBulanIni) { + // Sudah cair bulan ini, kupon otomatis diperbarui untuk bulan depan + $periode_date = $sekarang->copy()->addMonth(); + } else { + // Belum cair bulan ini, kupon untuk bulan ini + $periode_date = $sekarang->copy(); + } + + $periode_val = $periode_date->format('Y-m'); + $periode_text = $periode_date->translatedFormat('F Y'); + + // Membuat Signed URL dengan periode + $konfirmasi_url = route('konfirmasi.show', ['id' => $id, 'periode' => $periode_val]); + $signature = hash_hmac('sha256', $id . '|' . $periode_val, config('app.key')); + $signed_url = $konfirmasi_url . '&signature=' . $signature; + + // Generate QR Code + try { + $builder = new Builder( + writer: new PngWriter(), + data: $signed_url, + encoding: new Encoding('UTF-8'), + errorCorrectionLevel: ErrorCorrectionLevel::High, + size: 300, + margin: 10, + roundBlockSizeMode: RoundBlockSizeMode::Margin + ); + $result = $builder->build(); + $qrDataUri = $result->getDataUri(); + } catch (\Exception $e) { + abort(500, 'Error generating QR Code: ' . $e->getMessage()); + } + + $kupons = [[ + 'warga' => $warga, + 'qrDataUri' => $qrDataUri, + 'periode_text' => $periode_text + ]]; + + return view('kupon', compact('kupons')); + } + + public function cetak(Request $request) + { + $ids = $request->input('penerima_ids'); + + if (empty($ids) || !is_array($ids)) { + return redirect()->back()->with('error', 'Tidak ada kupon yang dipilih untuk dicetak.'); + } + + $wargas = PenerimaBantuan::whereIn('id', $ids)->get(); + $kupons = []; + + foreach ($wargas as $warga) { + $sekarang = now(); + $logBulanIni = LogDistribusi::where('id_penerima', $warga->id) + ->whereMonth('tanggal_diterima', $sekarang->month) + ->whereYear('tanggal_diterima', $sekarang->year) + ->first(); + + if ($logBulanIni) { + $periode_date = $sekarang->copy()->addMonth(); + } else { + $periode_date = $sekarang->copy(); + } + + $periode_val = $periode_date->format('Y-m'); + $periode_text = $periode_date->translatedFormat('F Y'); + + $konfirmasi_url = route('konfirmasi.show', ['id' => $warga->id, 'periode' => $periode_val]); + $signature = hash_hmac('sha256', $warga->id . '|' . $periode_val, config('app.key')); + $signed_url = $konfirmasi_url . '&signature=' . $signature; + + try { + $builder = new Builder( + writer: new PngWriter(), + data: $signed_url, + encoding: new Encoding('UTF-8'), + errorCorrectionLevel: ErrorCorrectionLevel::High, + size: 300, + margin: 10, + roundBlockSizeMode: RoundBlockSizeMode::Margin + ); + $result = $builder->build(); + $qrDataUri = $result->getDataUri(); + + $kupons[] = [ + 'warga' => $warga, + 'qrDataUri' => $qrDataUri, + 'periode_text' => $periode_text + ]; + } catch (\Exception $e) { + // Lewati jika error + } + } + + return view('kupon', compact('kupons')); + } +} diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php new file mode 100644 index 0000000..59d2ac5 --- /dev/null +++ b/app/Http/Controllers/LaporanController.php @@ -0,0 +1,63 @@ +firstOrFail(); + return view('laporan.lapor', compact('tempatIbadah')); + } + + // Publik: Simpan pelaporan + public function store(Request $request, $kode_lapor) + { + $tempatIbadah = \App\Models\TempatIbadah::where('kode_lapor', $kode_lapor)->firstOrFail(); + + $request->validate([ + 'nama_calon' => 'required|string|max:255', + 'jumlah_tanggungan' => 'required|integer|min:0', + 'alamat' => 'nullable|string|max:500', + 'deskripsi' => 'nullable|string', + 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', + ]); + + $foto_name = null; + if ($request->hasFile('foto')) { + $file = $request->file('foto'); + $foto_name = time() . '_lapor_' . $file->getClientOriginalName(); + $file->storeAs('uploads', $foto_name, 'public'); + } + + LaporanWarga::create([ + 'nama_calon' => $request->nama_calon, + 'jumlah_tanggungan' => $request->jumlah_tanggungan, + 'alamat' => $request->alamat, + 'deskripsi' => $request->deskripsi, + 'foto' => $foto_name, + 'status_laporan' => 'menunggu', + 'id_tempat_ibadah' => $tempatIbadah->id, + ]); + + return redirect()->back()->with('success', 'Laporan Anda berhasil dikirim! Terima kasih atas partisipasinya.'); + } + + // Authenticated (Admin/User): Tampilkan daftar laporan + public function index() + { + abort_if(auth()->user()->role === 'admin', 403, 'Akses ditolak. Halaman ini bukan untuk admin.'); + + $tempatIbadah = \App\Models\TempatIbadah::find(auth()->user()->id_tempat_ibadah); + + $laporans = LaporanWarga::where('id_tempat_ibadah', $tempatIbadah->id) + ->where('status_laporan', 'menunggu') + ->orderBy('created_at', 'desc')->get(); + + return view('laporan.index', compact('laporans', 'tempatIbadah')); + } +} diff --git a/app/Http/Controllers/MapController.php b/app/Http/Controllers/MapController.php index 6777bba..6d0a6e2 100644 --- a/app/Http/Controllers/MapController.php +++ b/app/Http/Controllers/MapController.php @@ -3,27 +3,65 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Str; +use Illuminate\Support\Facades\Hash; +use App\Models\TempatIbadah; +use App\Models\PenerimaBantuan; + +use App\Models\User; class MapController extends Controller { - public function index() + public function index(Request $request) { - $ibadahs = \App\Models\TempatIbadah::all(); - $penerimas = \App\Models\PenerimaBantuan::with('tempat_ibadah')->get(); - return view('dashboard', compact('ibadahs', 'penerimas')); + if (Auth::user()->role === 'user') { + $ibadahs = TempatIbadah::where('id', Auth::user()->id_tempat_ibadah)->get(); + $penerimas = PenerimaBantuan::with('tempat_ibadah') + ->where('id_tempat_ibadah', Auth::user()->id_tempat_ibadah)->get(); + } else { + $ibadahs = TempatIbadah::all(); + $penerimas = PenerimaBantuan::with('tempat_ibadah')->get(); + } + + $laporan_fill = null; + if ($request->has('add_laporan')) { + $laporan_fill = \App\Models\LaporanWarga::find($request->add_laporan); + } + + return view('dashboard', compact('ibadahs', 'penerimas', 'laporan_fill')); } - public function daftarPenerimaBantuan() + public function daftarPenerimaBantuan(Request $request) { - $penerimas = \App\Models\PenerimaBantuan::with('tempat_ibadah')->get(); - $ibadahs = \App\Models\TempatIbadah::all(); + $ibadahs = TempatIbadah::all(); + + $query = PenerimaBantuan::with('tempat_ibadah'); + + if (Auth::user()->role === 'user') { + $query->where('id_tempat_ibadah', Auth::user()->id_tempat_ibadah); + $ibadahs = TempatIbadah::where('id', Auth::user()->id_tempat_ibadah)->get(); + } elseif ($request->filled('filter_penyalur')) { + $query->where('id_tempat_ibadah', (int)$request->filter_penyalur); + } + + $penerimas = $query->get(); + return view('daftar-penerima-bantuan', compact('penerimas', 'ibadahs')); } public function daftarTempatIbadah() { - $ibadahs = \App\Models\TempatIbadah::all(); - return view('daftar-tempat-ibadah', compact('ibadahs')); + if (Auth::user()->role === 'user') { + $ibadahs = TempatIbadah::where('id', Auth::user()->id_tempat_ibadah)->get(); + } else { + $ibadahs = TempatIbadah::all(); + } + + // Include admin email for each ibadah if admin, or just fetch all users + $adminUsers = User::where('role', 'user')->get()->keyBy('id_tempat_ibadah'); + + return view('daftar-tempat-ibadah', compact('ibadahs', 'adminUsers')); } public function save(Request $request) @@ -35,33 +73,111 @@ class MapController extends Controller $alamat = $request->alamat; if ($type == 'ibadah') { - \App\Models\TempatIbadah::create([ + // ... (ibadah creation logic remains unchanged) + if (Auth::user()->role !== 'admin') { + return response()->json(['status' => 'error', 'message' => 'Unauthorized']); + } + $ibadah = TempatIbadah::create([ 'nama_tempat' => $nama, 'jenis_tempat_ibadah' => $request->jenis_tempat_ibadah, - 'kontak_person' => $request->kontak_person, - 'radius_meter' => 500, + 'nama_pengurus' => $request->nama_pengurus, + 'kontak_pengurus' => $request->kontak_pengurus, 'lat' => $lat, 'lng' => $lng, + 'radius_meter' => 500, 'alamat' => $alamat ]); - } else { - $fotoPath = null; - if ($request->hasFile('foto_kondisi_rumah')) { - $fotoPath = $request->file('foto_kondisi_rumah')->store('fotos', 'public'); + + $baseEmail = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $nama)); + $email = $baseEmail . '@email.com'; + if (User::where('email', $email)->exists()) { + $email = $baseEmail . rand(100, 999) . '@email.com'; } - \App\Models\PenerimaBantuan::create([ + User::create([ + 'name' => $nama, + 'email' => $email, + 'password' => Hash::make('password123'), + 'role' => 'user', + 'id_tempat_ibadah' => $ibadah->id, + ]); + } else { + if (Auth::user()->role !== 'user') { + return response()->json(['status' => 'error', 'message' => 'Unauthorized']); + } + + $tempatIbadah = \App\Models\TempatIbadah::find(Auth::user()->id_tempat_ibadah); + if ($tempatIbadah) { + $distance = $this->calculateDistance($tempatIbadah->lat, $tempatIbadah->lng, $lat, $lng); + if ($distance > $tempatIbadah->radius_meter) { + return response()->json([ + 'status' => 'error', + 'message' => 'Hanya bisa di lingkaran radius pelayanan' + ]); + } + } + + // Pengecekan NIK dan Nomor KK Ganda + $existsNIK = \App\Models\PenerimaBantuan::where('nik_kepala_keluarga', $request->nik_kepala_keluarga)->first(); + if ($existsNIK) { + return response()->json([ + 'status' => 'error', + 'message' => 'Gagal: NIK Kepala Keluarga (' . $request->nik_kepala_keluarga . ') sudah terdaftar di dalam sistem.' + ]); + } + + $existsKK = \App\Models\PenerimaBantuan::where('nomor_kk', $request->nomor_kk)->first(); + if ($existsKK) { + return response()->json([ + 'status' => 'error', + 'message' => 'Gagal: Nomor Kartu Keluarga (' . $request->nomor_kk . ') sudah terdaftar di dalam sistem.' + ]); + } + + // Handle multi-foto upload + $foto_names = []; + if ($request->hasFile('foto')) { + $files = is_array($request->file('foto')) ? $request->file('foto') : [$request->file('foto')]; + foreach ($files as $i => $file) { + $foto_name = time() . '_' . $i . '_' . $file->getClientOriginalName(); + $file->storeAs('uploads', $foto_name, 'public'); + $foto_names[] = $foto_name; + } + } else if ($request->filled('id_laporan_warga')) { + $laporan = \App\Models\LaporanWarga::find($request->id_laporan_warga); + if ($laporan && $laporan->foto) { + // Copy existing foto from laporan + $foto_names[] = $laporan->foto; + } + } + + // Handle dokumen laporan upload + $dokumen_laporan = ''; + if ($request->hasFile('dokumen_laporan')) { + $dokumen_laporan = time() . '_pdf_' . $request->file('dokumen_laporan')->getClientOriginalName(); + $request->file('dokumen_laporan')->storeAs('uploads', $dokumen_laporan, 'public'); + } + + PenerimaBantuan::create([ 'nama_kepala_keluarga' => $nama, - 'id_tempat_ibadah' => $request->id_tempat_ibadah, + 'id_tempat_ibadah' => Auth::user()->role === 'user' ? Auth::user()->id_tempat_ibadah : $request->id_tempat_ibadah, 'nik_kepala_keluarga' => $request->nik_kepala_keluarga, 'nomor_kk' => $request->nomor_kk, 'jumlah_tanggungan' => $request->jumlah_tanggungan, - 'foto_kondisi_rumah' => $fotoPath, + 'foto' => implode(',', $foto_names), 'lat' => $lat, 'lng' => $lng, - 'alamat' => $alamat + 'alamat' => $alamat, + 'dokumen_laporan' => $dokumen_laporan, + 'status_persetujuan' => 'menunggu', ]); + + if ($request->filled('id_laporan_warga')) { + \App\Models\LaporanWarga::where('id', $request->id_laporan_warga)->update(['status_laporan' => 'diproses']); + } } + + $this->recalculateCoverage(); return response()->json(['status' => 'success']); } @@ -69,8 +185,8 @@ class MapController extends Controller { $id = $request->id; $radius = $request->radius; - \App\Models\TempatIbadah::where('id', $id)->update(['radius_meter' => $radius]); - $this->checkAndUnlinkPenerima($id); + TempatIbadah::where('id', $id)->update(['radius_meter' => $radius]); + $this->recalculateCoverage(); return response()->json(['status' => 'success']); } @@ -82,30 +198,60 @@ class MapController extends Controller $type = $request->type; if ($type == 'ibadah') { - \App\Models\TempatIbadah::where('id', $id)->update([ + $data = [ 'nama_tempat' => $nama, - 'jenis_tempat_ibadah' => $request->jenis_tempat_ibadah, - 'kontak_person' => $request->kontak_person, 'alamat' => $alamat, - 'radius_meter' => $request->radius_meter - ]); - $this->checkAndUnlinkPenerima($id); + ]; + if ($request->filled('jenis_tempat_ibadah')) $data['jenis_tempat_ibadah'] = $request->jenis_tempat_ibadah; + if ($request->filled('nama_pengurus')) $data['nama_pengurus'] = $request->nama_pengurus; + if ($request->filled('kontak_pengurus')) $data['kontak_pengurus'] = $request->kontak_pengurus; + if ($request->filled('radius_meter')) $data['radius_meter'] = (int)$request->radius_meter; + + TempatIbadah::where('id', $id)->update($data); + $this->recalculateCoverage(); } else { - $updateData = [ + $data = [ 'nama_kepala_keluarga' => $nama, - 'id_tempat_ibadah' => $request->id_tempat_ibadah, - 'nik_kepala_keluarga' => $request->nik_kepala_keluarga, - 'nomor_kk' => $request->nomor_kk, - 'jumlah_tanggungan' => $request->jumlah_tanggungan, - 'alamat' => $alamat + 'id_tempat_ibadah' => Auth::user()->role === 'user' ? Auth::user()->id_tempat_ibadah : $request->id_tempat_ibadah, + 'alamat' => $alamat, ]; - if ($request->hasFile('foto_kondisi_rumah')) { - $updateData['foto_kondisi_rumah'] = $request->file('foto_kondisi_rumah')->store('fotos', 'public'); + if ($request->filled('nik_kepala_keluarga')) $data['nik_kepala_keluarga'] = $request->nik_kepala_keluarga; + if ($request->filled('nomor_kk')) $data['nomor_kk'] = $request->nomor_kk; + if ($request->has('jumlah_tanggungan')) $data['jumlah_tanggungan'] = $request->jumlah_tanggungan; + + // Handle existing photos and deletions + $penerima = PenerimaBantuan::find($id); + $current_fotos = !empty($penerima->foto) ? explode(',', $penerima->foto) : []; + + if ($request->has('hapus_foto') && is_array($request->hapus_foto)) { + foreach ($request->hapus_foto as $hf) { + if (($key = array_search($hf, $current_fotos)) !== false) { + unset($current_fotos[$key]); + $path = storage_path('app/public/uploads/' . $hf); + if (file_exists($path)) { + unlink($path); + } + } + } + $current_fotos = array_values($current_fotos); } - \App\Models\PenerimaBantuan::where('id', $id)->update($updateData); + // Handle new foto uploads + if ($request->hasFile('foto')) { + $files = is_array($request->file('foto')) ? $request->file('foto') : [$request->file('foto')]; + foreach ($files as $i => $file) { + $foto_name = time() . '_' . $i . '_' . $file->getClientOriginalName(); + $file->storeAs('uploads', $foto_name, 'public'); + $current_fotos[] = $foto_name; + } + } + + $data['foto'] = implode(',', $current_fotos); + + PenerimaBantuan::where('id', $id)->update($data); } + return redirect()->back(); } @@ -115,31 +261,56 @@ class MapController extends Controller $type = $request->type; if ($type == 'ibadah') { - \App\Models\TempatIbadah::where('id', $id)->delete(); + TempatIbadah::where('id', $id)->delete(); } else { - \App\Models\PenerimaBantuan::where('id', $id)->delete(); + PenerimaBantuan::where('id', $id)->delete(); } + + $this->recalculateCoverage(); return redirect()->back(); } - private function calculateDistance($lat1, $lon1, $lat2, $lon2) { + private function calculateDistance($lat1, $lon1, $lat2, $lon2) + { $earthRadius = 6371000; // in meters $dLat = deg2rad($lat2 - $lat1); $dLon = deg2rad($lon2 - $lon1); - $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) * sin($dLon/2); + $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) * sin($dLon / 2); $c = 2 * asin(sqrt($a)); return $earthRadius * $c; } - private function checkAndUnlinkPenerima($ibadahId) { - $ibadah = \App\Models\TempatIbadah::find($ibadahId); - if (!$ibadah) return; + private function recalculateCoverage() + { + $all_ibadahs = TempatIbadah::all(['id', 'lat', 'lng', 'radius_meter']); - $penerimas = \App\Models\PenerimaBantuan::where('id_tempat_ibadah', $ibadahId)->get(); - foreach ($penerimas as $penerima) { - $distance = $this->calculateDistance($ibadah->lat, $ibadah->lng, $penerima->lat, $penerima->lng); - if ($distance > $ibadah->radius_meter) { - $penerima->update(['id_tempat_ibadah' => null]); + $penerimas = PenerimaBantuan::all(['id', 'lat', 'lng']); + + foreach ($penerimas as $pb) { + $best_id = null; + $min_distance = INF; + + foreach ($all_ibadahs as $ib) { + $distance = $this->calculateDistance($ib->lat, $ib->lng, $pb->lat, $pb->lng); + + if ($distance <= $ib->radius_meter) { + if ($distance < $min_distance) { + $min_distance = $distance; + $best_id = $ib->id; + } + } + } + + if ($best_id !== null) { + PenerimaBantuan::where('id', $pb->id)->update([ + 'id_tempat_ibadah' => $best_id, + 'jarak_ke_penyalur' => $min_distance, + ]); + } else { + PenerimaBantuan::where('id', $pb->id)->update([ + 'id_tempat_ibadah' => null, + 'jarak_ke_penyalur' => null, + ]); } } } diff --git a/app/Http/Controllers/PersetujuanController.php b/app/Http/Controllers/PersetujuanController.php new file mode 100644 index 0000000..bc9fee3 --- /dev/null +++ b/app/Http/Controllers/PersetujuanController.php @@ -0,0 +1,44 @@ +role !== 'admin') { + abort(403, 'Unauthorized action.'); + } + + // Only fetch 'menunggu' status to show in the approval table + $penerimas = PenerimaBantuan::with('tempat_ibadah')->where('status_persetujuan', 'menunggu')->get(); + return view('persetujuan', compact('penerimas')); + } + + public function updateStatus(Request $request, $id) + { + if (Auth::user()->role !== 'admin') { + abort(403, 'Unauthorized action.'); + } + + $request->validate([ + 'status_persetujuan' => 'required|in:disetujui,ditolak', + 'alasan_penolakan' => 'nullable|string' + ]); + + $penerima = PenerimaBantuan::findOrFail($id); + $penerima->status_persetujuan = $request->status_persetujuan; + if ($request->status_persetujuan === 'ditolak') { + $penerima->alasan_penolakan = $request->alasan_penolakan; + } else { + $penerima->alasan_penolakan = null; + } + $penerima->save(); + + return redirect()->back()->with('success', 'Status berhasil diperbarui!'); + } +} diff --git a/app/Http/Controllers/RiwayatController.php b/app/Http/Controllers/RiwayatController.php new file mode 100644 index 0000000..8c3f9d2 --- /dev/null +++ b/app/Http/Controllers/RiwayatController.php @@ -0,0 +1,144 @@ +filled('filter_periode')) { + $periode = $request->filter_periode; // format YYYY-MM + $query->whereRaw("DATE_FORMAT(tanggal_diterima, '%Y-%m') = ?", [$periode]); + } + + $riwayats = $query->orderBy('tanggal_diterima', 'desc')->get(); + + return view('riwayat', compact('riwayats')); + } + + public function konfirmasiPenyaluran($id_log) + { + $log = LogDistribusi::where('id', $id_log)->where('status', 'Menunggu')->first(); + + if ($log) { + $log->update(['status' => 'Selesai']); + PenerimaBantuan::where('id', $log->id_penerima) + ->update(['tanggal_pencairan_terakhir' => $log->tanggal_diterima]); + } + + return redirect()->back(); + } + + public function cetakPdf(Request $request) + { + if (!$request->filled('periode')) { + abort(400, 'Periode tidak valid!'); + } + + $periode = $request->periode; + $periode_obj = date_create_from_format('Y-m', $periode); + if (!$periode_obj) { + abort(400, 'Format periode tidak valid!'); + } + + $bulan_indonesia = [ + 'January' => 'Januari', 'February' => 'Februari', 'March' => 'Maret', + 'April' => 'April', 'May' => 'Mei', 'June' => 'Juni', + 'July' => 'Juli', 'August' => 'Agustus', 'September' => 'September', + 'October' => 'Oktober', 'November' => 'November', 'December' => 'Desember' + ]; + $bulan_eng = date_format($periode_obj, 'F'); + $tahun = date_format($periode_obj, 'Y'); + $bulan_tahun = $bulan_indonesia[$bulan_eng] . ' ' . $tahun; + + $riwayats = LogDistribusi::with(['penerimaBantuan.tempat_ibadah']) + ->whereRaw("DATE_FORMAT(tanggal_diterima, '%Y-%m') = ?", [$periode]) + ->orderBy('tanggal_diterima', 'desc') + ->get(); + + if ($riwayats->isEmpty()) { + abort(404, 'Data riwayat tidak ditemukan untuk periode ini!'); + } + + $tgl_sekarang_indo = date('d') . ' ' . $bulan_indonesia[date('F')] . ' ' . date('Y'); + + $html = ' + + + + + Laporan Riwayat Penyaluran Bantuan - ' . $bulan_tahun . ' + + + +

Laporan Riwayat Penyaluran Bantuan

+

Periode: ' . $bulan_tahun . '

+ + + + + + + + + + + + + '; + + $no = 1; + foreach ($riwayats as $r) { + $tgl = $r->tanggal_diterima->format('d-m-Y H:i'); + $nama_kk = $r->penerimaBantuan->nama_kepala_keluarga ?? 'Data Terhapus'; + $nik = $r->penerimaBantuan && $r->penerimaBantuan->nik_kepala_keluarga + ? substr($r->penerimaBantuan->nik_kepala_keluarga, 0, 6) . '**********' + : '-'; + $alamat = $r->penerimaBantuan->alamat ?? '-'; + $penyalur = $r->penerimaBantuan && $r->penerimaBantuan->tempat_ibadah + ? $r->penerimaBantuan->tempat_ibadah->nama_tempat + : '-'; + + $html .= ' + + + + + + + + + '; + $no++; + } + + $html .= ' + +
NoTanggal TerimaNama Kepala KeluargaNIKAlamatPenyalurStatus
' . $no . '' . $tgl . '' . e($nama_kk) . '' . $nik . '' . e($alamat) . '' . e($penyalur) . '' . $r->status . '
+
+ Pontianak, ' . $tgl_sekarang_indo . '



+ Administrator +
+ +'; + + $pdf = Pdf::loadHTML($html)->setPaper('A4', 'landscape'); + $filename = "Laporan_Penyaluran_" . $periode . ".pdf"; + + return $pdf->stream($filename); + } +} diff --git a/app/Models/LaporanWarga.php b/app/Models/LaporanWarga.php new file mode 100644 index 0000000..b047a0c --- /dev/null +++ b/app/Models/LaporanWarga.php @@ -0,0 +1,26 @@ +belongsTo(TempatIbadah::class, 'id_tempat_ibadah'); + } +} diff --git a/app/Models/LogDistribusi.php b/app/Models/LogDistribusi.php new file mode 100644 index 0000000..e9acf89 --- /dev/null +++ b/app/Models/LogDistribusi.php @@ -0,0 +1,29 @@ + 'datetime', + ]; + + public function penerimaBantuan() + { + return $this->belongsTo(PenerimaBantuan::class, 'id_penerima'); + } +} diff --git a/app/Models/PenerimaBantuan.php b/app/Models/PenerimaBantuan.php index 2e35959..dc5b019 100644 --- a/app/Models/PenerimaBantuan.php +++ b/app/Models/PenerimaBantuan.php @@ -17,14 +17,30 @@ class PenerimaBantuan extends Model 'nomor_kk', 'nama_kepala_keluarga', 'jumlah_tanggungan', - 'foto_kondisi_rumah', + 'foto', 'lat', 'lng', - 'alamat' + 'alamat', + 'jarak_ke_penyalur', + 'dokumen_laporan', + 'status_persetujuan', + 'alasan_penolakan', + 'tanggal_pencairan_terakhir', + ]; + + protected $casts = [ + 'tanggal_pencairan_terakhir' => 'datetime', ]; public function tempat_ibadah() { return $this->belongsTo(TempatIbadah::class, 'id_tempat_ibadah'); } + + + + public function logDistribusi() + { + return $this->hasMany(LogDistribusi::class, 'id_penerima'); + } } diff --git a/app/Models/TempatIbadah.php b/app/Models/TempatIbadah.php index d47b2fc..6efc027 100644 --- a/app/Models/TempatIbadah.php +++ b/app/Models/TempatIbadah.php @@ -14,15 +14,32 @@ class TempatIbadah extends Model protected $fillable = [ 'nama_tempat', 'jenis_tempat_ibadah', - 'kontak_person', + 'nama_pengurus', + 'kontak_pengurus', 'radius_meter', 'lat', 'lng', - 'alamat' + 'alamat', + 'password', + 'kode_lapor', ]; + protected static function booted() + { + static::creating(function ($tempatIbadah) { + if (empty($tempatIbadah->kode_lapor)) { + $tempatIbadah->kode_lapor = \Illuminate\Support\Str::random(10); + } + }); + } + public function penerima_bantuan() { return $this->hasMany(PenerimaBantuan::class, 'id_tempat_ibadah'); } + + public function users() + { + return $this->hasMany(User::class, 'id_tempat_ibadah'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 4431e77..60b2c41 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -23,8 +23,14 @@ class User extends Authenticatable 'email', 'password', 'role', + 'id_tempat_ibadah', ]; + public function tempatIbadah() + { + return $this->belongsTo(TempatIbadah::class, 'id_tempat_ibadah'); + } + /** * The attributes that should be hidden for serialization. * diff --git a/composer.json b/composer.json index b55bdf0..d05101b 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,8 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "^3.1", + "endroid/qr-code": "^6.0", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1" }, diff --git a/composer.lock b/composer.lock index 6eb750e..6c1ee98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,140 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d5b57ea83b8b7753f51487b1384d9feb", + "content-hash": "46e5face68164b05a599bac22a4fb4ee", "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^8.1" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.12", + "phpunit/phpunit": "^10.5.11 || ^11.0.4", + "spatie/phpunit-snapshot-assertions": "^5.1.5", + "spatie/pixelmatch-php": "^1.2.0", + "squizlabs/php_codesniffer": "^3.9" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.1.1" + }, + "time": "2026-04-05T21:06:35+00:00" + }, + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12|^13.0", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9.16|^10|^11.0", + "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.2" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2026-02-21T08:51:10+00:00" + }, { "name": "brick/math", "version": "0.14.8", @@ -135,6 +267,56 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -377,6 +559,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 +845,78 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "endroid/qr-code", + "version": "6.0.9", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "21e888e8597440b2205e2e5c484b6c8e556bcd1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/21e888e8597440b2205e2e5c484b6c8e556bcd1a", + "reference": "21e888e8597440b2205e2e5c484b6c8e556bcd1a", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^3.0", + "php": "^8.2" + }, + "require-dev": { + "endroid/quality": "dev-main", + "ext-gd": "*", + "khanamiryan/qrcode-detector-decoder": "^2.0.2", + "setasign/fpdf": "^1.8.2" + }, + "suggest": { + "ext-gd": "Enables you to write PNG images", + "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator", + "roave/security-advisories": "Makes sure package versions with known security issues are not installed", + "setasign/fpdf": "Enables you to use the PDF writer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/6.0.9" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2025-07-13T19:59:45+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.4.0", @@ -2025,6 +2434,73 @@ ], "time": "2026-03-08T20:05:35+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", @@ -3300,6 +3776,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.8", @@ -5809,6 +6365,149 @@ ], "time": "2026-03-30T13:44:50+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", diff --git a/database/migrations/2026_06_05_113823_add_id_tempat_ibadah_to_users_table.php b/database/migrations/2026_06_05_113823_add_id_tempat_ibadah_to_users_table.php new file mode 100644 index 0000000..5a5b794 --- /dev/null +++ b/database/migrations/2026_06_05_113823_add_id_tempat_ibadah_to_users_table.php @@ -0,0 +1,29 @@ +foreignId('id_tempat_ibadah')->nullable()->constrained('tempat_ibadah')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropForeign(['id_tempat_ibadah']); + $table->dropColumn('id_tempat_ibadah'); + }); + } +}; diff --git a/database/migrations/2026_06_06_130901_create_log_distribusi_table.php b/database/migrations/2026_06_06_130901_create_log_distribusi_table.php new file mode 100644 index 0000000..120c900 --- /dev/null +++ b/database/migrations/2026_06_06_130901_create_log_distribusi_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('id_penerima')->constrained('penerima_bantuan')->onDelete('cascade'); + $table->dateTime('tanggal_diterima'); + $table->string('status')->default('Menunggu'); + $table->text('foto_penyerahan')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('log_distribusi'); + } +}; diff --git a/database/migrations/2026_06_06_130902_add_native_columns_to_tables.php b/database/migrations/2026_06_06_130902_add_native_columns_to_tables.php new file mode 100644 index 0000000..58a3a78 --- /dev/null +++ b/database/migrations/2026_06_06_130902_add_native_columns_to_tables.php @@ -0,0 +1,60 @@ +renameColumn('kontak_person', 'kontak_pengurus'); + }); + + Schema::table('tempat_ibadah', function (Blueprint $table) { + // Add new columns + $table->string('nama_pengurus')->nullable()->after('kontak_pengurus'); + }); + + Schema::table('penerima_bantuan', function (Blueprint $table) { + // Rename foto_kondisi_rumah to foto (multi-foto, comma-separated) + $table->renameColumn('foto_kondisi_rumah', 'foto'); + }); + + Schema::table('penerima_bantuan', function (Blueprint $table) { + // Change foto column to text for multi-file support + $table->text('foto')->nullable()->change(); + // Add new columns + $table->double('jarak_ke_penyalur')->nullable()->after('foto'); + $table->string('dokumen_laporan')->nullable()->after('jarak_ke_penyalur'); + $table->dateTime('tanggal_pencairan_terakhir')->nullable()->after('dokumen_laporan'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('penerima_bantuan', function (Blueprint $table) { + $table->dropColumn(['jarak_ke_penyalur', 'dokumen_laporan', 'tanggal_pencairan_terakhir']); + }); + + Schema::table('penerima_bantuan', function (Blueprint $table) { + $table->renameColumn('foto', 'foto_kondisi_rumah'); + }); + + Schema::table('tempat_ibadah', function (Blueprint $table) { + $table->dropColumn(['nama_pengurus']); + }); + + Schema::table('tempat_ibadah', function (Blueprint $table) { + $table->renameColumn('kontak_pengurus', 'kontak_person'); + }); + } +}; diff --git a/database/migrations/2026_06_06_152954_add_persetujuan_to_penerima_bantuan_table.php b/database/migrations/2026_06_06_152954_add_persetujuan_to_penerima_bantuan_table.php new file mode 100644 index 0000000..dc3587e --- /dev/null +++ b/database/migrations/2026_06_06_152954_add_persetujuan_to_penerima_bantuan_table.php @@ -0,0 +1,29 @@ +enum('status_persetujuan', ['menunggu', 'disetujui', 'ditolak'])->default('menunggu'); + $table->text('alasan_penolakan')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('penerima_bantuan', function (Blueprint $table) { + $table->dropColumn(['status_persetujuan', 'alasan_penolakan']); + }); + } +}; diff --git a/database/migrations/2026_06_06_152956_rename_admin_ibadah_to_user_in_users.php b/database/migrations/2026_06_06_152956_rename_admin_ibadah_to_user_in_users.php new file mode 100644 index 0000000..786f0db --- /dev/null +++ b/database/migrations/2026_06_06_152956_rename_admin_ibadah_to_user_in_users.php @@ -0,0 +1,25 @@ +where('role', 'admin_ibadah')->update(['role' => 'user']); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + DB::table('users')->where('role', 'user')->update(['role' => 'admin_ibadah']); + } +}; diff --git a/database/migrations/2026_06_06_154628_add_password_to_tempat_ibadah_table.php b/database/migrations/2026_06_06_154628_add_password_to_tempat_ibadah_table.php new file mode 100644 index 0000000..c5375a7 --- /dev/null +++ b/database/migrations/2026_06_06_154628_add_password_to_tempat_ibadah_table.php @@ -0,0 +1,28 @@ +string('password')->nullable()->after('radius_meter'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('tempat_ibadah', function (Blueprint $table) { + $table->dropColumn('password'); + }); + } +}; diff --git a/database/migrations/2026_06_07_120121_create_laporan_wargas_table.php b/database/migrations/2026_06_07_120121_create_laporan_wargas_table.php new file mode 100644 index 0000000..96d1b6e --- /dev/null +++ b/database/migrations/2026_06_07_120121_create_laporan_wargas_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('nama_calon'); + $table->integer('jumlah_tanggungan'); + $table->string('foto')->nullable(); + $table->enum('status_laporan', ['menunggu', 'diproses'])->default('menunggu'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('laporan_wargas'); + } +}; diff --git a/database/migrations/2026_06_07_122259_add_deskripsi_to_laporan_wargas_table.php b/database/migrations/2026_06_07_122259_add_deskripsi_to_laporan_wargas_table.php new file mode 100644 index 0000000..a3ad816 --- /dev/null +++ b/database/migrations/2026_06_07_122259_add_deskripsi_to_laporan_wargas_table.php @@ -0,0 +1,28 @@ +text('deskripsi')->nullable()->after('jumlah_tanggungan'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('laporan_wargas', function (Blueprint $table) { + $table->dropColumn('deskripsi'); + }); + } +}; diff --git a/database/migrations/2026_06_07_132851_add_added_by_to_penerima_bantuan_table.php b/database/migrations/2026_06_07_132851_add_added_by_to_penerima_bantuan_table.php new file mode 100644 index 0000000..a1dd8e0 --- /dev/null +++ b/database/migrations/2026_06_07_132851_add_added_by_to_penerima_bantuan_table.php @@ -0,0 +1,28 @@ +string('kode_lapor')->nullable()->unique()->after('id'); + }); + + Schema::table('laporan_wargas', function (Blueprint $table) { + $table->unsignedBigInteger('id_tempat_ibadah')->nullable()->after('id'); + // Foreign key jika menggunakan InnoDB, namun karena mungkin ada data tidak sinkron, biarkan saja index biasa. + $table->index('id_tempat_ibadah'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('laporan_wargas', function (Blueprint $table) { + $table->dropColumn('id_tempat_ibadah'); + }); + + Schema::table('tempat_ibadah', function (Blueprint $table) { + $table->dropColumn('kode_lapor'); + }); + } +}; diff --git a/database/migrations/2026_06_07_142330_add_alamat_to_laporan_wargas_table.php b/database/migrations/2026_06_07_142330_add_alamat_to_laporan_wargas_table.php new file mode 100644 index 0000000..49b2b03 --- /dev/null +++ b/database/migrations/2026_06_07_142330_add_alamat_to_laporan_wargas_table.php @@ -0,0 +1,28 @@ +string('alamat')->nullable()->after('jumlah_tanggungan'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('laporan_wargas', function (Blueprint $table) { + $table->dropColumn('alamat'); + }); + } +}; diff --git a/database/seeders/SuperAdminSeeder.php b/database/seeders/AdminSeeder.php similarity index 59% rename from database/seeders/SuperAdminSeeder.php rename to database/seeders/AdminSeeder.php index 361f88b..28be36d 100644 --- a/database/seeders/SuperAdminSeeder.php +++ b/database/seeders/AdminSeeder.php @@ -7,7 +7,7 @@ use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; -class SuperAdminSeeder extends Seeder +class AdminSeeder extends Seeder { /** * Run the database seeds. @@ -15,9 +15,9 @@ class SuperAdminSeeder extends Seeder public function run(): void { User::create([ - 'name' => env('SUPERADMIN_NAME', 'SuperAdmin'), - 'email' => env('SUPERADMIN_EMAIL', 'admin@gmail.com'), - 'password' => Hash::make(env('SUPERADMIN_PASSWORD', 'qwerty#123')), // Ganti dengan password yang aman + 'name' => env('ADMIN_NAME', 'Admin'), + 'email' => env('ADMIN_EMAIL', 'admin@gmail.com'), + 'password' => Hash::make(env('ADMIN_PASSWORD', 'qwerty#123')), // Ganti dengan password yang aman 'role' => 'admin', // Menyesuaikan kolom role yang Anda buat di tabel users ]); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 10cceb4..55dc8c3 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -13,9 +13,9 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // Memanggil SuperAdminSeeder + // Memanggil AdminSeeder $this->call([ - SuperAdminSeeder::class, + AdminSeeder::class, ]); } } diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 80e1b39..f7f781b 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -39,9 +39,9 @@ @endif - - {{ __('Log in') }} - + diff --git a/resources/views/components/dropdown-link.blade.php b/resources/views/components/dropdown-link.blade.php index 6d5279d..c9cf4f5 100644 --- a/resources/views/components/dropdown-link.blade.php +++ b/resources/views/components/dropdown-link.blade.php @@ -1 +1 @@ -merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out']) }}>{{ $slot }} +merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-100/50 dark:hover:bg-slate-800/50 focus:outline-none focus:bg-slate-100/50 dark:focus:bg-slate-800/50 transition duration-150 ease-in-out']) }}>{{ $slot }} diff --git a/resources/views/components/nav-link.blade.php b/resources/views/components/nav-link.blade.php index 37bad55..f9a50d9 100644 --- a/resources/views/components/nav-link.blade.php +++ b/resources/views/components/nav-link.blade.php @@ -2,8 +2,8 @@ @php $classes = ($active ?? false) - ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' - : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out'; + ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-500 dark:border-indigo-400 text-sm font-bold leading-5 text-indigo-700 dark:text-indigo-300 focus:outline-none transition duration-150 ease-in-out' + : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 hover:border-slate-300 dark:hover:border-slate-600 focus:outline-none focus:text-slate-800 dark:focus:text-slate-200 transition duration-150 ease-in-out'; @endphp merge(['class' => $classes]) }}> diff --git a/resources/views/components/responsive-nav-link.blade.php b/resources/views/components/responsive-nav-link.blade.php index 98b55d1..f9fbd0a 100644 --- a/resources/views/components/responsive-nav-link.blade.php +++ b/resources/views/components/responsive-nav-link.blade.php @@ -2,8 +2,8 @@ @php $classes = ($active ?? false) - ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out' - : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out'; + ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-500 dark:border-indigo-400 text-start text-base font-bold text-indigo-700 dark:text-indigo-300 bg-indigo-50/50 dark:bg-indigo-900/30 focus:outline-none transition duration-150 ease-in-out' + : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-slate-600 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 hover:bg-slate-50/50 dark:hover:bg-slate-800/30 hover:border-slate-300 dark:hover:border-slate-600 focus:outline-none transition duration-150 ease-in-out'; @endphp merge(['class' => $classes]) }}> diff --git a/resources/views/daftar-penerima-bantuan.blade.php b/resources/views/daftar-penerima-bantuan.blade.php index bd24aea..659e569 100644 --- a/resources/views/daftar-penerima-bantuan.blade.php +++ b/resources/views/daftar-penerima-bantuan.blade.php @@ -5,96 +5,255 @@ -
-
-
-
- - +
+
+
+
+ @if(session('success')) +
+ + {{ session('success') }} +
+ @endif -
-
+ +
+
+

Data Penerima Bantuan

+

Kelola dan tinjau data penerima bantuan di wilayah Anda.

+
+
+ @if(Auth::user()->role === 'admin') + +
+
+ +
+ +
+ +
+
+ + @endif + +
+
+ + +
+ + + + + + + + + diff --git a/resources/views/daftar-tempat-ibadah.blade.php b/resources/views/daftar-tempat-ibadah.blade.php index a038fcd..b220db1 100644 --- a/resources/views/daftar-tempat-ibadah.blade.php +++ b/resources/views/daftar-tempat-ibadah.blade.php @@ -5,97 +5,187 @@ -
-
-
-
- - - -
-
- @csrf - Edit Data Tempat Ibadah: -
- - - - - - - - - - - -
-
+
+
+ + +
+
+
+

Data Tempat Ibadah & Penyalur

+

+ Kelola data tempat ibadah yang menjadi mitra penyalur bantuan. +

- - - - - - - - - - - - - - - - @foreach($ibadahs as $ib) - - - - - - - - - - - @endforeach - -
IDNama Tempat IbadahJenisKontak PersonAlamat DetailRadius Layan (Meter)Koordinat (Lat, Lng)Aksi
{{ $ib->id }}{{ $ib->nama_tempat }}{{ $ib->jenis_tempat_ibadah ?? '-' }}{{ $ib->kontak_person ?? '-' }}{{ $ib->alamat }}{{ $ib->radius_meter }}{{ $ib->lat }}, {{ $ib->lng }} - - Hapus -
- - -
+ + + + + +
+ @forelse($ibadahs as $index => $ib) +
+
+
+
+ #{{ $index + 1 }} +

{{ $ib->nama_tempat }}

+
+
{{ $ib->jenis_tempat_ibadah ?? 'Lainnya' }}
+
+
+ +
+
+
+ Nama Pengurus + {{ $ib->nama_pengurus ?? '-' }} +
+
+ Kontak Pengurus + {{ $ib->kontak_pengurus ?? '-' }} +
+
+ Radius Pelayanan + {{ $ib->radius_meter }} m +
+
+ Koordinat + {{ $ib->lat }}, {{ $ib->lng }} +
+
+ +
+ Alamat + {{ $ib->alamat }} +
+ + @if(Auth::user()->role !== 'user') +
+
+ Email (Akun Penyalur) + + @if(isset($adminUsers[$ib->id])) + {{ $adminUsers[$ib->id]->email }} + @else + - + @endif + +
+
+ Password + @if(isset($adminUsers[$ib->id])) + @if(\Illuminate\Support\Facades\Hash::check('password123', $adminUsers[$ib->id]->password)) + password123 + @else + + + Telah Diubah (Rahasia) + + @endif + @else + - + @endif +
+
+ @endif +
+ +
+ + + @if(Auth::user()->role !== 'user') + + + Hapus + + @endif +
+
+ @empty +
+
+ +
+

Data tempat ibadah tidak ditemukan.

+
+ @endforelse +
+ +
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 55a43a4..3f5ca79 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -5,196 +5,408 @@ -
+
-
-
+
+
-
- Tambah Titik Baru: -
- - - - - + @php + $total_penerima = $penerimas->count(); + $sudah_cair = 0; + $belum_cair = 0; + $current_month = now()->format('Y-m'); + + foreach($penerimas as $p) { + if ($p->tanggal_pencairan_terakhir && $p->tanggal_pencairan_terakhir->format('Y-m') === $current_month) { + $sudah_cair++; + } else { + $belum_cair++; + } + } + @endphp + + +
+ + + + +
+
+ Total Penerima + {{ $total_penerima }} +
+
+ Sudah Cair + {{ $sudah_cair }} +
+
+ Belum Cair + {{ $belum_cair }} +
+
+
+ + + diff --git a/resources/views/konfirmasi.blade.php b/resources/views/konfirmasi.blade.php new file mode 100644 index 0000000..106f24b --- /dev/null +++ b/resources/views/konfirmasi.blade.php @@ -0,0 +1,63 @@ + + + + + + Konfirmasi Penyerahan Bantuan + + + + +
+

Konfirmasi Penyerahan Bantuan

+ + @if(session('error')) +
{{ session('error') }}
+ @endif + @if(isset($pesan_sukses)) +
Berhasil! Bukti penyerahan telah disimpan dan menunggu persetujuan Admin.
+ @endif + +
+

Penerima: {{ $warga->nama_kepala_keluarga }}

+

NIK: {{ substr($warga->nik_kepala_keluarga ?? '', 0, 6) }}**********

+

Alamat: {{ $warga->alamat }}

+
+ + @if($sudah_cair) +
+
✔ Sudah Dicairkan Bulan Ini
+

Bantuan untuk penerima ini telah diproses pada {{ $log->tanggal_diterima->format('d F Y H:i') }}.

+
+ @else +
+ @csrf +
+ + +
+ + +
+ @endif + +
+ + + diff --git a/resources/views/kupon.blade.php b/resources/views/kupon.blade.php new file mode 100644 index 0000000..9270bce --- /dev/null +++ b/resources/views/kupon.blade.php @@ -0,0 +1,268 @@ + + + + + + Cetak Kupon Bantuan + + + + + +
+ +
+ + + + + diff --git a/resources/views/laporan/index.blade.php b/resources/views/laporan/index.blade.php new file mode 100644 index 0000000..490b174 --- /dev/null +++ b/resources/views/laporan/index.blade.php @@ -0,0 +1,102 @@ + + +
+

+ {{ __('Daftar Laporan Warga') }} +

+
+
+ +
+
+
+
+ +
+
+

Tautan Pelaporan Warga

+

Gunakan tautan ini atau jadikan QR Code agar warga dapat melapor langsung ke wilayah Anda.

+
+
+ + +
+
+ + + @if($laporans->isEmpty()) +
+ +

Tidak ada laporan baru

+

Semua laporan warga telah diproses atau belum ada yang melaporkan.

+
+ @else +
+ + + + + + + + + + + + + + @foreach($laporans as $laporan) + + + + + + + + + + @endforeach + +
Tanggal LaporNama Calon PenerimaTanggunganAlamatDeskripsiFoto BuktiAksi
{{ $laporan->created_at->format('d M Y H:i') }}{{ $laporan->nama_calon }}{{ $laporan->jumlah_tanggungan }} Orang + @if($laporan->alamat) + {{ $laporan->alamat }} + @else + Tidak ada alamat + @endif + + @if($laporan->deskripsi) + {{ Str::limit($laporan->deskripsi, 50) }} + @else + Tidak ada deskripsi + @endif + + @if($laporan->foto) + Lihat Foto + @else + Tidak ada foto + @endif + + + Tambah Data Baru dari Laporan Ini + +
+
+ @endif + +
+
+
+
+
diff --git a/resources/views/laporan/lapor.blade.php b/resources/views/laporan/lapor.blade.php new file mode 100644 index 0000000..51bbf47 --- /dev/null +++ b/resources/views/laporan/lapor.blade.php @@ -0,0 +1,120 @@ + +
+
+ +
+

Laporan Warga

+

Untuk: {{ $tempatIbadah->nama_tempat }}

+

+ Bantu kami menemukan warga sekitar yang membutuhkan. Masukkan data calon penerima bantuan di bawah ini. +

+
+ + @if (session('success')) + + @endif + +
+ @csrf + + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ + Upload foto + + +

atau tarik & lepas ke sini

+
+

+ PNG, JPG, JPEG maks 2MB +

+
+
+ + +
+ +
+ +
+
+ + +
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 0a471a4..1187e34 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -1,34 +1,98 @@ - + - {{ config('app.name', 'Laravel') }} + {{ config('app.name', 'WebGIS Pengentasan Kemiskinan') }} - + @vite(['resources/css/app.css', 'resources/js/app.js']) + + - -
- @include('layouts.navigation') + + + +
+
+
+
+
+ +
+
+ @include('layouts.navigation') +
@isset($header) -
-
+
+
{{ $header }}
@endisset -
+
{{ $slot }}
diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php index 4b369b6..1ba45b6 100644 --- a/resources/views/layouts/guest.blade.php +++ b/resources/views/layouts/guest.blade.php @@ -1,28 +1,95 @@ - + - {{ config('app.name', 'Laravel') }} + {{ config('app.name', 'WebGIS Pengentasan Kemiskinan') }} - + @vite(['resources/css/app.css', 'resources/js/app.js']) + + - -
-
- - + + + +
+
+
+
+
+ +
+ -
+
{{ $slot }}
diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index 515c4fb..e978fbf 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -1,38 +1,61 @@ -