Tampilan awal

This commit is contained in:
2026-06-05 18:34:30 +07:00
parent dfff1f18e9
commit f7479770e6
12 changed files with 824 additions and 5 deletions
+146
View File
@@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MapController extends Controller
{
public function index()
{
$ibadahs = \App\Models\TempatIbadah::all();
$penerimas = \App\Models\PenerimaBantuan::with('tempat_ibadah')->get();
return view('dashboard', compact('ibadahs', 'penerimas'));
}
public function daftarPenerimaBantuan()
{
$penerimas = \App\Models\PenerimaBantuan::with('tempat_ibadah')->get();
$ibadahs = \App\Models\TempatIbadah::all();
return view('daftar-penerima-bantuan', compact('penerimas', 'ibadahs'));
}
public function daftarTempatIbadah()
{
$ibadahs = \App\Models\TempatIbadah::all();
return view('daftar-tempat-ibadah', compact('ibadahs'));
}
public function save(Request $request)
{
$type = $request->type;
$nama = $request->nama;
$lat = $request->lat;
$lng = $request->lng;
$alamat = $request->alamat;
if ($type == 'ibadah') {
\App\Models\TempatIbadah::create([
'nama_tempat' => $nama,
'jenis_tempat_ibadah' => $request->jenis_tempat_ibadah,
'kontak_person' => $request->kontak_person,
'radius_meter' => 500,
'lat' => $lat,
'lng' => $lng,
'alamat' => $alamat
]);
} else {
$fotoPath = null;
if ($request->hasFile('foto_kondisi_rumah')) {
$fotoPath = $request->file('foto_kondisi_rumah')->store('fotos', 'public');
}
\App\Models\PenerimaBantuan::create([
'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,
'foto_kondisi_rumah' => $fotoPath,
'lat' => $lat,
'lng' => $lng,
'alamat' => $alamat
]);
}
return response()->json(['status' => 'success']);
}
public function updateRadius(Request $request)
{
$id = $request->id;
$radius = $request->radius;
\App\Models\TempatIbadah::where('id', $id)->update(['radius_meter' => $radius]);
$this->checkAndUnlinkPenerima($id);
return response()->json(['status' => 'success']);
}
public function edit(Request $request)
{
$id = $request->id;
$nama = $request->nama;
$alamat = $request->alamat;
$type = $request->type;
if ($type == 'ibadah') {
\App\Models\TempatIbadah::where('id', $id)->update([
'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);
} else {
$updateData = [
'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
];
if ($request->hasFile('foto_kondisi_rumah')) {
$updateData['foto_kondisi_rumah'] = $request->file('foto_kondisi_rumah')->store('fotos', 'public');
}
\App\Models\PenerimaBantuan::where('id', $id)->update($updateData);
}
return redirect()->back();
}
public function delete(Request $request)
{
$id = $request->id;
$type = $request->type;
if ($type == 'ibadah') {
\App\Models\TempatIbadah::where('id', $id)->delete();
} else {
\App\Models\PenerimaBantuan::where('id', $id)->delete();
}
return redirect()->back();
}
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);
$c = 2 * asin(sqrt($a));
return $earthRadius * $c;
}
private function checkAndUnlinkPenerima($ibadahId) {
$ibadah = \App\Models\TempatIbadah::find($ibadahId);
if (!$ibadah) return;
$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]);
}
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PenerimaBantuan extends Model
{
use HasFactory;
protected $table = 'penerima_bantuan';
protected $fillable = [
'id_tempat_ibadah',
'nik_kepala_keluarga',
'nomor_kk',
'nama_kepala_keluarga',
'jumlah_tanggungan',
'foto_kondisi_rumah',
'lat',
'lng',
'alamat'
];
public function tempat_ibadah()
{
return $this->belongsTo(TempatIbadah::class, 'id_tempat_ibadah');
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TempatIbadah extends Model
{
use HasFactory;
protected $table = 'tempat_ibadah';
protected $fillable = [
'nama_tempat',
'jenis_tempat_ibadah',
'kontak_person',
'radius_meter',
'lat',
'lng',
'alamat'
];
public function penerima_bantuan()
{
return $this->hasMany(PenerimaBantuan::class, 'id_tempat_ibadah');
}
}
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tempat_ibadah', function (Blueprint $table) {
$table->id();
$table->string('nama_tempat');
$table->integer('radius_meter')->default(500);
$table->decimal('lat', 10, 7);
$table->decimal('lng', 10, 7);
$table->string('alamat')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tempat_ibadah');
}
};
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('rumah_miskin', function (Blueprint $table) {
$table->id();
$table->string('nama_kepala_keluarga');
$table->decimal('lat', 10, 7);
$table->decimal('lng', 10, 7);
$table->string('alamat')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('rumah_miskin');
}
};
@@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::rename('rumah_miskin', 'penerima_bantuan');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::rename('penerima_bantuan', 'rumah_miskin');
}
};
@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('tempat_ibadah', function (Blueprint $table) {
$table->enum('jenis_tempat_ibadah', ['Masjid', 'Gereja Katolik', 'Gereja Protestan', 'Vihara', 'Pura', 'Klenteng'])->nullable()->after('nama_tempat');
$table->string('kontak_person')->nullable()->after('alamat');
});
Schema::table('penerima_bantuan', function (Blueprint $table) {
$table->foreignId('id_tempat_ibadah')->nullable()->after('id')->constrained('tempat_ibadah')->nullOnDelete();
$table->string('nik_kepala_keluarga')->nullable()->unique()->after('nama_kepala_keluarga');
$table->string('nomor_kk')->nullable()->after('nik_kepala_keluarga');
$table->integer('jumlah_tanggungan')->nullable()->after('alamat');
$table->string('foto_kondisi_rumah')->nullable()->after('jumlah_tanggungan');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('penerima_bantuan', function (Blueprint $table) {
$table->dropForeign(['id_tempat_ibadah']);
$table->dropColumn(['id_tempat_ibadah', 'nik_kepala_keluarga', 'nomor_kk', 'jumlah_tanggungan', 'foto_kondisi_rumah']);
});
Schema::table('tempat_ibadah', function (Blueprint $table) {
$table->dropColumn(['jenis_tempat_ibadah', 'kontak_person']);
});
}
};
@@ -0,0 +1,114 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Daftar Penerima Bantuan') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<style>
.map-table { width: 100%; border-collapse: collapse; background: white; color: black; margin-top: 20px;}
.map-table th, .map-table td { padding: 12px; border: 1px solid #ddd; text-align: left; }
.map-table th { background: #007bff; color: white; }
.btn-edit { background: #ffc107; color: #000; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 13px; }
.btn-delete { background: #dc3545; color: #fff; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 13px; }
.edit-form { background: #fff3cd; padding: 15px; border-radius: 12px; display: none; margin-bottom: 15px; border: 1px solid #ffeeba; color: black; }
</style>
<div id="panel-edit" class="edit-form">
<form action="{{ route('map.edit') }}" method="POST" enctype="multipart/form-data">
@csrf
<strong>Edit Data Penerima Bantuan:</strong>
<div style="display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; align-items: center;">
<input type="hidden" name="id" id="edit-id">
<input type="hidden" name="type" id="edit-type" value="penerima">
<input type="text" name="nama" id="edit-nama" placeholder="Nama KK" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;" required>
<input type="text" name="nik_kepala_keluarga" id="edit-nik" placeholder="NIK" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<input type="text" name="nomor_kk" id="edit-kk" placeholder="No. KK" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<input type="number" name="jumlah_tanggungan" id="edit-tanggungan" placeholder="Jml Tanggungan" style="padding: 8px; width: 150px; border-radius: 4px; border: 1px solid #ccc; color: black;" min="0">
<select name="id_tempat_ibadah" id="edit-ibadah" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<option value="">- Pilih Tempat Ibadah -</option>
@foreach($ibadahs as $ib)
<option value="{{ $ib->id }}">{{ $ib->nama_tempat }}</option>
@endforeach
</select>
<input type="text" name="alamat" id="edit-alamat" placeholder="Alamat" style="padding: 8px; width: 250px; border-radius: 4px; border: 1px solid #ccc; color: black;" required>
<label style="display:flex; align-items:center; gap:5px; color:#333; font-weight:bold; font-size:13px;">
Ganti Foto: <input type="file" name="foto_kondisi_rumah" id="edit-foto" accept="image/*" style="color: black; font-size:12px;">
</label>
<button type="submit" style="padding: 8px 15px; background: #28a745; color: white; border: none; border-radius: 4px;">Simpan</button>
<button type="button" onclick="closeEdit()" style="padding: 8px 15px; background: #6c757d; color: white; border: none; border-radius: 4px;">Batal</button>
</div>
</form>
</div>
<table class="map-table">
<thead>
<tr>
<th>ID</th>
<th>Nama Kepala Keluarga</th>
<th>NIK / KK</th>
<th>Tanggungan</th>
<th>Tempat Ibadah</th>
<th>Alamat Detail</th>
<th>Koordinat (Lat, Lng)</th>
<th>Foto</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@foreach($penerimas as $r)
<tr>
<td>{{ $r->id }}</td>
<td>{{ $r->nama_kepala_keluarga }}</td>
<td>{{ $r->nik_kepala_keluarga ?? '-' }} / {{ $r->nomor_kk ?? '-' }}</td>
<td>{{ $r->jumlah_tanggungan ?? '0' }}</td>
<td>{{ $r->tempat_ibadah->nama_tempat ?? '-' }}</td>
<td>{{ $r->alamat }}</td>
<td>{{ $r->lat }}, {{ $r->lng }}</td>
<td>
@if($r->foto_kondisi_rumah)
<a href="{{ asset('storage/' . $r->foto_kondisi_rumah) }}" target="_blank" style="color: blue; text-decoration: underline;">Lihat Foto</a>
@else
-
@endif
</td>
<td>
<button class='btn-edit' onclick='openEdit({{ $r->id }}, "{{ addslashes($r->nama_kepala_keluarga) }}", "{{ addslashes($r->alamat) }}", "{{ addslashes($r->nik_kepala_keluarga) }}", "{{ addslashes($r->nomor_kk) }}", "{{ $r->jumlah_tanggungan }}", "{{ $r->id_tempat_ibadah }}")'>Edit</button>
<a href='{{ route("map.delete", ["type" => "penerima", "id" => $r->id]) }}' class='btn-delete' onclick='return confirm("Hapus data penerima ini?")'>Hapus</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<script>
function openEdit(id, nama, alamat, nik, kk, tanggungan, ibadah_id) {
document.getElementById('panel-edit').style.display = 'block';
document.getElementById('edit-id').value = id;
document.getElementById('edit-nama').value = nama;
document.getElementById('edit-alamat').value = alamat;
document.getElementById('edit-nik').value = nik || '';
document.getElementById('edit-kk').value = kk || '';
document.getElementById('edit-tanggungan').value = tanggungan || '';
document.getElementById('edit-ibadah').value = ibadah_id || '';
window.scrollTo(0,0);
}
function closeEdit() { document.getElementById('panel-edit').style.display = 'none'; }
</script>
</div>
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,101 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Daftar Tempat Ibadah') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<style>
.map-table { width: 100%; border-collapse: collapse; background: white; color: black; margin-top: 20px;}
.map-table th, .map-table td { padding: 12px; border: 1px solid #ddd; text-align: left; }
.map-table th { background: #007bff; color: white; }
.btn-edit { background: #ffc107; color: #000; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 13px; }
.btn-delete { background: #dc3545; color: #fff; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 13px; }
.edit-form { background: #fff3cd; padding: 15px; border-radius: 12px; display: none; margin-bottom: 15px; border: 1px solid #ffeeba; color: black; }
</style>
<div id="panel-edit" class="edit-form">
<form action="{{ route('map.edit') }}" method="POST">
@csrf
<strong>Edit Data Tempat Ibadah:</strong>
<div style="display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; align-items: center;">
<input type="hidden" name="id" id="edit-id">
<input type="hidden" name="type" id="edit-type" value="ibadah">
<input type="text" name="nama" id="edit-nama" placeholder="Nama Tempat" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;" required>
<select name="jenis_tempat_ibadah" id="edit-jenis" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<option value="">- Pilih Jenis -</option>
<option value="Masjid">Masjid</option>
<option value="Gereja Katolik">Gereja Katolik</option>
<option value="Gereja Protestan">Gereja Protestan</option>
<option value="Vihara">Vihara</option>
<option value="Pura">Pura</option>
<option value="Klenteng">Klenteng</option>
</select>
<input type="text" name="kontak_person" id="edit-kontak" placeholder="Kontak Person" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<input type="text" name="alamat" id="edit-alamat" placeholder="Alamat" style="padding: 8px; width: 350px; border-radius: 4px; border: 1px solid #ccc; color: black;" required>
<input type="number" name="radius_meter" id="edit-radius" placeholder="Radius (Meter)" style="padding: 8px; width: 130px; border-radius: 4px; border: 1px solid #ccc; color: black;" min="0" required>
<button type="submit" style="padding: 8px 15px; background: #28a745; color: white; border: none; border-radius: 4px;">Simpan</button>
<button type="button" onclick="closeEdit()" style="padding: 8px 15px; background: #6c757d; color: white; border: none; border-radius: 4px;">Batal</button>
</div>
</form>
</div>
<table class="map-table">
<thead>
<tr>
<th>ID</th>
<th>Nama Tempat Ibadah</th>
<th>Jenis</th>
<th>Kontak Person</th>
<th>Alamat Detail</th>
<th>Radius Layan (Meter)</th>
<th>Koordinat (Lat, Lng)</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@foreach($ibadahs as $ib)
<tr>
<td>{{ $ib->id }}</td>
<td>{{ $ib->nama_tempat }}</td>
<td>{{ $ib->jenis_tempat_ibadah ?? '-' }}</td>
<td>{{ $ib->kontak_person ?? '-' }}</td>
<td>{{ $ib->alamat }}</td>
<td>{{ $ib->radius_meter }}</td>
<td>{{ $ib->lat }}, {{ $ib->lng }}</td>
<td>
<button class='btn-edit' onclick='openEdit({{ $ib->id }}, "{{ addslashes($ib->nama_tempat) }}", "{{ addslashes($ib->alamat) }}", {{ $ib->radius_meter }}, "{{ addslashes($ib->jenis_tempat_ibadah) }}", "{{ addslashes($ib->kontak_person) }}")'>Edit</button>
<a href='{{ route("map.delete", ["type" => "ibadah", "id" => $ib->id]) }}' class='btn-delete' onclick='return confirm("Hapus data tempat ibadah ini?")'>Hapus</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<script>
function openEdit(id, nama, alamat, radius, jenis, kontak) {
document.getElementById('panel-edit').style.display = 'block';
document.getElementById('edit-id').value = id;
document.getElementById('edit-nama').value = nama;
document.getElementById('edit-alamat').value = alamat;
document.getElementById('edit-radius').value = radius;
document.getElementById('edit-jenis').value = jenis;
document.getElementById('edit-kontak').value = kontak;
window.scrollTo(0,0);
}
function closeEdit() { document.getElementById('panel-edit').style.display = 'none'; }
</script>
</div>
</div>
</div>
</div>
</x-app-layout>
+253 -2
View File
@@ -1,7 +1,7 @@
<x-app-layout> <x-app-layout>
<x-slot name="header"> <x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dashboard') }} {{ __('Dashboard Pemetaan') }}
</h2> </h2>
</x-slot> </x-slot>
@@ -9,7 +9,258 @@
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg"> <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100"> <div class="p-6 text-gray-900 dark:text-gray-100">
{{ __("You're logged in!") }}
<!-- Map Interface Start -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
#map { height: 450px; border-radius: 12px; margin-bottom: 20px; border: 2px solid #333; z-index: 1; }
.panel { background: #f4f7f6; padding: 20px; border-radius: 12px; margin-bottom: 15px; color: black;}
.edit-form { background: #fff3cd; padding: 15px; border-radius: 12px; display: none; margin-bottom: 15px; border: 1px solid #ffeeba; color: black; }
.map-table { width: 100%; border-collapse: collapse; background: white; color: black; }
.map-table th, .map-table td { padding: 12px; border: 1px solid #ddd; text-align: left; }
.map-table th { background: #007bff; color: white; }
.btn-edit { background: #ffc107; color: #000; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 13px; }
.btn-delete { background: #dc3545; color: #fff; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 13px; }
.leaflet-popup-content { text-align: center; color: black; }
.popup-slider { width: 100%; }
/* Ubah kursor peta menjadi crosshair untuk menandakan mode tambah titik */
.mode-tambah .leaflet-container { cursor: crosshair !important; }
</style>
<div class="panel">
<strong style="display: block; margin-bottom: 10px;">Tambah Titik Baru:</strong>
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<input type="text" id="nama" placeholder="Masukkan Nama..." style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black; flex-grow: 1; max-width: 300px;">
<input type="hidden" id="mode" value="">
<button type="button" id="btn-mode-ibadah" onclick="setMode('ibadah')" style="padding: 10px 15px; background: #e2e8f0; color: #333; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; transition: 0.3s;">
🏛️ Tempat Ibadah
</button>
<button type="button" id="btn-mode-penerima" onclick="setMode('penerima')" style="padding: 10px 15px; background: #e2e8f0; color: #333; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; transition: 0.3s;">
🏠 Penerima Bantuan
</button>
</div>
<!-- Extra Fields for Ibadah -->
<div id="fields-ibadah" style="display:none; margin-top:10px; gap: 10px; flex-wrap: wrap;">
<select id="jenis_tempat_ibadah" style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black;">
<option value="">- Pilih Jenis Tempat Ibadah -</option>
<option value="Masjid">Masjid</option>
<option value="Gereja Katolik">Gereja Katolik</option>
<option value="Gereja Protestan">Gereja Protestan</option>
<option value="Vihara">Vihara</option>
<option value="Pura">Pura</option>
<option value="Klenteng">Klenteng</option>
</select>
<input type="text" id="kontak_person" placeholder="Kontak Person..." style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black;">
</div>
<!-- Extra Fields for Penerima -->
<div id="fields-penerima" style="display:none; margin-top:10px; gap: 10px; flex-wrap: wrap;">
<input type="text" id="nik_kepala_keluarga" placeholder="NIK Kepala Keluarga..." style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black;">
<input type="text" id="nomor_kk" placeholder="Nomor KK..." style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black;">
<input type="number" id="jumlah_tanggungan" placeholder="Jml Tanggungan..." style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black; width: 150px;" min="0">
<select id="id_tempat_ibadah" style="padding: 10px; border-radius: 6px; border: 1px solid #ccc; color: black;">
<option value="">- Pilih Tempat Ibadah -</option>
@foreach($ibadahs as $ib)
<option value="{{ $ib->id }}">{{ $ib->nama_tempat }}</option>
@endforeach
</select>
<label style="display:flex; align-items:center; gap:5px; color:#333; font-weight:bold;">
Foto Rumah: <input type="file" id="foto_kondisi_rumah" accept="image/*" style="color: black;">
</label>
</div>
<small style="color: #666; display: block; margin-top: 10px; font-style: italic;">👉 Pilih jenis titik, isi kolom yang tersedia, lalu <b>klik pada peta</b> untuk menyimpan lokasinya.</small>
</div>
<script>
function setMode(mode) {
var currentMode = document.getElementById('mode').value;
var mapEl = document.getElementById('map');
// Toggle off if clicking the same mode
if (currentMode === mode) {
document.getElementById('mode').value = '';
document.getElementById('btn-mode-ibadah').style.background = '#e2e8f0';
document.getElementById('btn-mode-ibadah').style.color = '#333';
document.getElementById('btn-mode-penerima').style.background = '#e2e8f0';
document.getElementById('btn-mode-penerima').style.color = '#333';
document.getElementById('fields-ibadah').style.display = 'none';
document.getElementById('fields-penerima').style.display = 'none';
mapEl.classList.remove('mode-tambah');
return;
}
document.getElementById('mode').value = mode;
mapEl.classList.add('mode-tambah');
if(mode === 'ibadah') {
document.getElementById('btn-mode-ibadah').style.background = '#007bff';
document.getElementById('btn-mode-ibadah').style.color = 'white';
document.getElementById('btn-mode-penerima').style.background = '#e2e8f0';
document.getElementById('btn-mode-penerima').style.color = '#333';
document.getElementById('fields-ibadah').style.display = 'flex';
document.getElementById('fields-penerima').style.display = 'none';
} else {
document.getElementById('btn-mode-penerima').style.background = '#007bff';
document.getElementById('btn-mode-penerima').style.color = 'white';
document.getElementById('btn-mode-ibadah').style.background = '#e2e8f0';
document.getElementById('btn-mode-ibadah').style.color = '#333';
document.getElementById('fields-ibadah').style.display = 'none';
document.getElementById('fields-penerima').style.display = 'flex';
}
}
</script>
<div id="panel-edit" class="edit-form">
<form action="{{ route('map.edit') }}" method="POST">
@csrf
<strong>Edit Data:</strong>
<input type="hidden" name="id" id="edit-id">
<input type="hidden" name="type" id="edit-type">
<input type="text" name="nama" id="edit-nama" placeholder="Nama" style="padding: 8px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<input type="text" name="alamat" id="edit-alamat" placeholder="Alamat" style="padding: 8px; width: 400px; border-radius: 4px; border: 1px solid #ccc; color: black;">
<button type="submit" style="padding: 8px 15px; background: #28a745; color: white; border: none; border-radius: 4px;">Simpan</button>
<button type="button" onclick="closeEdit()" style="padding: 8px 15px; background: #6c757d; color: white; border: none; border-radius: 4px;">Batal</button>
</form>
</div>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
var map = L.map('map').setView([-0.02, 109.34], 14);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
var listIbadah = [];
var listPenerima = [];
var iconIbadah = L.divIcon({
className: '',
html: `<div style="background:#007bff; color:#fff; border-radius:50%; width:30px; height:30px; display:flex; align-items:center; justify-content:center; border:2px solid #fff;">🏛️</div>`,
iconSize: [30, 30]
});
// 1. Fungsi Edit UI
function openEdit(id, type, nama, alamat) {
document.getElementById('panel-edit').style.display = 'block';
document.getElementById('edit-id').value = id;
document.getElementById('edit-type').value = type;
document.getElementById('edit-nama').value = nama;
document.getElementById('edit-alamat').value = alamat;
window.scrollTo(0,0);
}
function closeEdit() { document.getElementById('panel-edit').style.display = 'none'; }
// 2. Reverse Geocoding & Save
map.on('click', async function(e) {
const mode = document.getElementById('mode').value;
if(!mode) return; // Mode biasa, jangan lakukan apa-apa
const nama = document.getElementById('nama').value;
if(!nama) return alert("Isi nama dulu!");
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${e.latlng.lat}&lon=${e.latlng.lng}`);
const data = await res.json();
const addr = data.address || {};
const alamatDetail = [addr.road, addr.village || addr.suburb, addr.city].filter(Boolean).join(", ");
let fd = new FormData();
fd.append('_token', '{{ csrf_token() }}');
fd.append('type', mode);
fd.append('nama', nama);
fd.append('lat', e.latlng.lat);
fd.append('lng', e.latlng.lng);
fd.append('alamat', alamatDetail || data.display_name);
if (mode === 'ibadah') {
fd.append('jenis_tempat_ibadah', document.getElementById('jenis_tempat_ibadah').value);
fd.append('kontak_person', document.getElementById('kontak_person').value);
} else {
fd.append('nik_kepala_keluarga', document.getElementById('nik_kepala_keluarga').value);
fd.append('nomor_kk', document.getElementById('nomor_kk').value);
fd.append('jumlah_tanggungan', document.getElementById('jumlah_tanggungan').value);
fd.append('id_tempat_ibadah', document.getElementById('id_tempat_ibadah').value);
const fotoInput = document.getElementById('foto_kondisi_rumah');
if (fotoInput.files.length > 0) {
fd.append('foto_kondisi_rumah', fotoInput.files[0]);
}
}
fetch('{{ route("map.save") }}', { method: 'POST', body: fd }).then(() => location.reload());
});
// 3. Load Data Ibadah
@foreach($ibadahs as $ib)
(function(){
var rAwal = {{ $ib->radius_meter }};
var circle = L.circle([{{ $ib->lat }}, {{ $ib->lng }}], {
radius: rAwal, color: 'blue', fillOpacity: 0.1
}).addTo(map);
var marker = L.marker([{{ $ib->lat }}, {{ $ib->lng }}], {icon: iconIbadah}).addTo(map);
listIbadah.push({id: {{ $ib->id }}, nama: "{{ addslashes($ib->nama_tempat) }}", circle: circle, latlng: L.latLng({{ $ib->lat }}, {{ $ib->lng }})});
marker.bindPopup(`
<b>{{ addslashes($ib->nama_tempat) }}</b><br>
Radius: <span id="val_{{ $ib->id }}">${rAwal}</span> m<br>
<input type="range" class="popup-slider" min="100" max="3000" value="${rAwal}" oninput="updateRadius(${rAwal}, this.value, {{ $ib->id }})" onchange="saveRadius(this.value, {{ $ib->id }})">
<button onclick="window.location.href='{{ route('map.delete', ['type' => 'ibadah', 'id' => $ib->id]) }}'" style="background:red; color:white; border:none; padding:5px; width:100%; cursor:pointer; margin-top:5px;">Hapus</button>
`);
})();
@endforeach
// 4. Load Data Penerima Bantuan
@foreach($penerimas as $r)
var rm = L.circleMarker([{{ $r->lat }}, {{ $r->lng }}], {radius: 8, color: '#fff', fillOpacity: 1, weight: 2}).addTo(map);
listPenerima.push({marker: rm, latlng: L.latLng({{ $r->lat }}, {{ $r->lng }})});
@endforeach
// 5. Real-time Update Logic
function updateRadius(oldVal, newVal, id) {
document.getElementById('val_' + id).innerText = newVal;
listIbadah.forEach(item => { if(item.id === id) item.circle.setRadius(newVal); });
syncUI();
}
function saveRadius(val, id) {
let fd = new FormData();
fd.append('_token', '{{ csrf_token() }}');
fd.append('id', id);
fd.append('radius', val);
fetch('{{ route("map.updateRadius") }}', { method: 'POST', body: fd });
}
function syncUI() {
var baris = document.querySelectorAll('.baris-penerima');
baris.forEach(row => {
var rLoc = L.latLng(row.getAttribute('data-lat'), row.getAttribute('data-lng'));
var covered = [];
listIbadah.forEach(ib => { if(rLoc.distanceTo(ib.latlng) <= ib.circle.getRadius()) covered.push(ib.nama); });
row.querySelector('.status-cell').innerHTML = covered.length > 0 ?
`<span style='color:green'>✔ ${covered.join(", ")}</span>` :
`<span style='color:red'>✘ Luar Jangkauan</span>`;
});
listPenerima.forEach(penerima => {
let check = false;
listIbadah.forEach(ib => { if(penerima.latlng.distanceTo(ib.latlng) <= ib.circle.getRadius()) check = true; });
penerima.marker.setStyle({fillColor: check ? 'green' : 'red'});
});
}
syncUI();
</script>
<!-- Map Interface End -->
</div> </div>
</div> </div>
</div> </div>
@@ -15,6 +15,12 @@
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')"> <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }} {{ __('Dashboard') }}
</x-nav-link> </x-nav-link>
<x-nav-link :href="route('ibadah.index')" :active="request()->routeIs('ibadah.index')">
{{ __('Daftar Tempat Ibadah') }}
</x-nav-link>
<x-nav-link :href="route('penerima.index')" :active="request()->routeIs('penerima.index')">
{{ __('Daftar Penerima Bantuan') }}
</x-nav-link>
</div> </div>
</div> </div>
@@ -70,6 +76,12 @@
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')"> <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }} {{ __('Dashboard') }}
</x-responsive-nav-link> </x-responsive-nav-link>
<x-responsive-nav-link :href="route('penerima.index')" :active="request()->routeIs('penerima.index')">
{{ __('Daftar Penerima Bantuan') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('ibadah.index')" :active="request()->routeIs('ibadah.index')">
{{ __('Daftar Tempat Ibadah') }}
</x-responsive-nav-link>
</div> </div>
<!-- Responsive Settings Options --> <!-- Responsive Settings Options -->
+11 -3
View File
@@ -3,13 +3,21 @@
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MapController;
Route::get('/', function () { Route::get('/', function () {
return view('welcome'); return view('welcome');
}); });
Route::get('/dashboard', function () { Route::middleware(['auth', 'verified'])->group(function () {
return view('dashboard'); Route::get('/dashboard', [MapController::class, 'index'])->name('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard'); Route::get('/daftar-penerima-bantuan', [MapController::class, 'daftarPenerimaBantuan'])->name('penerima.index');
Route::get('/daftar-tempat-ibadah', [MapController::class, 'daftarTempatIbadah'])->name('ibadah.index');
Route::post('/map/save', [MapController::class, 'save'])->name('map.save');
Route::post('/map/update-radius', [MapController::class, 'updateRadius'])->name('map.updateRadius');
Route::post('/map/edit', [MapController::class, 'edit'])->name('map.edit');
Route::get('/map/delete', [MapController::class, 'delete'])->name('map.delete');
});
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');