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 |
+ Tanggal Terima |
+ Nama Kepala Keluarga |
+ NIK |
+ Alamat |
+ Penyalur |
+ Status |
+
+
+ ';
+
+ $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 . ' |
+ ' . $tgl . ' |
+ ' . e($nama_kk) . ' |
+ ' . $nik . ' |
+ ' . e($alamat) . ' |
+ ' . e($penyalur) . ' |
+ ' . $r->status . ' |
+
';
+ $no++;
+ }
+
+ $html .= '
+
+
+
+ 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
-
+
+
+
+
+
+
+
+
+
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 @@
-