save dulu

This commit is contained in:
ghozahimma65 2026-02-25 18:08:09 +07:00
parent a1d2dd1990
commit 9f467b7dab
14 changed files with 537 additions and 4 deletions

View File

@ -32,7 +32,10 @@ public function store(Request $request)
'tanggal_lahir' => 'required|date',
'jenis_kelamin' => 'required|in:L,P',
'wali_murid_id' => 'required|exists:wali_murids,id',
// Alamat dihapus, karena ikut Wali Murid
// --- TAMBAHAN BARU: Validasi Titik Koordinat Peta ---
'latitude' => 'nullable|string',
'longitude' => 'nullable|string',
]);
Siswa::create([
@ -43,6 +46,10 @@ public function store(Request $request)
'tanggal_lahir' => $request->tanggal_lahir,
'jenis_kelamin' => $request->jenis_kelamin,
'wali_murid_id' => $request->wali_murid_id,
// --- TAMBAHAN BARU: Simpan ke Database ---
'latitude' => $request->latitude,
'longitude' => $request->longitude,
]);
return redirect()->route('siswa.index')->with('success', 'Data Siswa berhasil ditambahkan.');
@ -67,6 +74,10 @@ public function update(Request $request, $id)
'tanggal_lahir' => 'required|date',
'jenis_kelamin' => 'required|in:L,P',
'wali_murid_id' => 'required|exists:wali_murids,id',
// --- TAMBAHAN BARU: Validasi Titik Koordinat Peta ---
'latitude' => 'nullable|string',
'longitude' => 'nullable|string',
]);
$siswa->update([
@ -77,6 +88,10 @@ public function update(Request $request, $id)
'tanggal_lahir' => $request->tanggal_lahir,
'jenis_kelamin' => $request->jenis_kelamin,
'wali_murid_id' => $request->wali_murid_id,
// --- TAMBAHAN BARU: Update ke Database ---
'latitude' => $request->latitude,
'longitude' => $request->longitude,
]);
return redirect()->route('siswa.index')->with('success', 'Data Siswa berhasil diperbarui!');

View File

@ -0,0 +1,142 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AStarController extends Controller
{
public function cariRute(Request $request)
{
$id_awal = 1; // ID PAUD Aisyiyah Kartoharjo (Start)
$awal = DB::table('titik_jalans')->where('id', $id_awal)->first();
// 1. Ambil kordinat dari HP Flutter
$latTujuan = $request->query('lat');
$lngTujuan = $request->query('lng');
if (!$latTujuan || !$lngTujuan) {
return response()->json(['success' => false, 'message' => 'Koordinat tidak valid'], 400);
}
$semua_titik = DB::table('titik_jalans')->get()->keyBy('id');
// 2. CARI TITIK TUJUAN YANG PALING COCOK BERDASARKAN KOORDINAT
$tujuan = null;
$jarakTerdekat = INF;
foreach ($semua_titik as $titik) {
$jarak = $this->hitungJarak($latTujuan, $lngTujuan, $titik->latitude, $titik->longitude);
if ($jarak < $jarakTerdekat) {
$jarakTerdekat = $jarak;
$tujuan = $titik;
}
}
if (!$tujuan) {
return response()->json(['success' => false, 'message' => 'Titik tidak ditemukan'], 404);
}
$id_tujuan = $tujuan->id; // Dapatkan ID aslinya di tabel titik_jalans
// 3. Ambil jalur jembatannya
$edges = DB::table('jalur_jalans')->get();
$graph = [];
foreach ($edges as $edge) {
$graph[$edge->titik_awal_id][] = [
'tujuan' => $edge->titik_tujuan_id,
'jarak' => $edge->jarak
];
}
// ==========================================
// PROSES A-STAR (A*)
// ==========================================
$openList = [$id_awal];
$closedList = [];
$cameFrom = [];
$gCost = [];
$fCost = [];
foreach ($semua_titik as $id => $titik) {
$gCost[$id] = INF;
$fCost[$id] = INF;
}
$gCost[$id_awal] = 0;
$fCost[$id_awal] = $this->hitungHeuristic($awal, $tujuan);
while (!empty($openList)) {
$current = null;
$lowestF = INF;
foreach ($openList as $nodeId) {
if ($fCost[$nodeId] < $lowestF) {
$lowestF = $fCost[$nodeId];
$current = $nodeId;
}
}
if ($current == $id_tujuan) {
return $this->rekonstruksiRute($cameFrom, $current, $semua_titik, $gCost[$current]);
}
$openList = array_diff($openList, [$current]);
$closedList[] = $current;
if (isset($graph[$current])) {
foreach ($graph[$current] as $neighbor) {
$neighborId = $neighbor['tujuan'];
if (in_array($neighborId, $closedList)) continue;
$tentativeGCost = $gCost[$current] + $neighbor['jarak'];
if ($tentativeGCost < $gCost[$neighborId]) {
$cameFrom[$neighborId] = $current;
$gCost[$neighborId] = $tentativeGCost;
$hCost = $this->hitungHeuristic($semua_titik[$neighborId], $tujuan);
$fCost[$neighborId] = $tentativeGCost + $hCost;
if (!in_array($neighborId, $openList)) {
$openList[] = $neighborId;
}
}
}
}
}
return response()->json(['success' => false, 'message' => 'Rute tidak ditemukan (Buntu)'], 404);
}
// --- RUMUS BANTUAN ---
private function hitungJarak($lat1, $lon1, $lat2, $lon2) {
$earthRadius = 6371000;
$latFrom = deg2rad((float)$lat1);
$lonFrom = deg2rad((float)$lon1);
$latTo = deg2rad((float)$lat2);
$lonTo = deg2rad((float)$lon2);
$latDelta = $latTo - $latFrom;
$lonDelta = $lonTo - $lonFrom;
$angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) + cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));
return round($angle * $earthRadius);
}
private function hitungHeuristic($titikA, $titikB) {
return $this->hitungJarak($titikA->latitude, $titikA->longitude, $titikB->latitude, $titikB->longitude);
}
private function rekonstruksiRute($cameFrom, $current, $semua_titik, $jarakTotal) {
$rute = [];
while (isset($cameFrom[$current])) {
array_unshift($rute, $semua_titik[$current]);
$current = $cameFrom[$current];
}
array_unshift($rute, $semua_titik[$current]);
return response()->json(['success' => true, 'jarak_total_meter' => $jarakTotal, 'titik_rute' => $rute]);
}
}

10
app/Models/JalurJalan.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class JalurJalan extends Model
{
//
}

10
app/Models/Kunjungan.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Kunjungan extends Model
{
//
}

10
app/Models/TitikJalan.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TitikJalan extends Model
{
//
}

View File

@ -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
{
public function up()
{
Schema::table('siswas', function (Blueprint $table) {
// Menambahkan kolom untuk titik koordinat GPS
$table->string('latitude')->nullable()->after('alamat');
$table->string('longitude')->nullable()->after('latitude');
});
}
public function down()
{
Schema::table('siswas', function (Blueprint $table) {
$table->dropColumn(['latitude', 'longitude']);
});
}
};

View File

@ -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
{
public function up()
{
Schema::create('kunjungans', function (Blueprint $table) {
$table->id();
$table->foreignId('guru_id')->constrained('users')->onDelete('cascade'); // Relasi ke Guru (User)
$table->foreignId('siswa_id')->constrained('siswas')->onDelete('cascade'); // Relasi ke Siswa
$table->date('tanggal_visit');
$table->string('status')->default('menunggu'); // Status: menunggu, jalan, selesai
// --- Kolom untuk Penilaian (Dibuat fleksibel) ---
$table->string('materi_belajar')->nullable();
$table->string('nilai')->nullable(); // String, biar bisa diisi "BB", "BSB", atau angka 80
$table->text('catatan')->nullable();
$table->string('foto_kegiatan')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('kunjungans');
}
};

View File

@ -0,0 +1,30 @@
<?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('titik_jalans', function (Blueprint $table) {
$table->id();
$table->string('nama_titik'); // Contoh: "Simpang 3 Diponegoro", "PAUD", "Rumah Achazia"
$table->string('latitude');
$table->string('longitude');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('titik_jalans');
}
};

View File

@ -0,0 +1,33 @@
<?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('jalur_jalans', function (Blueprint $table) {
$table->id();
// Titik awal persimpangan
$table->foreignId('titik_awal_id')->constrained('titik_jalans')->onDelete('cascade');
// Titik tujuan persimpangan
$table->foreignId('titik_tujuan_id')->constrained('titik_jalans')->onDelete('cascade');
// Jarak asli jalan raya (dalam meter / kilometer) -> Ini jadi G-Cost di A*
$table->double('jarak');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jalur_jalans');
}
};

View File

@ -0,0 +1,87 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class JalurJalanSeeder extends Seeder
{
public function run(): void
{
// 1. Bersihkan tabel jalur_jalans sebelum diisi
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
DB::table('jalur_jalans')->truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
// 2. Ambil semua data titik yang sudah Ghoza input
$titik = DB::table('titik_jalans')->get()->keyBy('id');
$jalur = [];
// 3. Hubungkan PAUD (ID 1) dengan Simpang A (2) dan Simpang B (3)
$jalur = array_merge($jalur, $this->buatJalurBolakBalik($titik[1], $titik[2]));
$jalur = array_merge($jalur, $this->buatJalurBolakBalik($titik[1], $titik[3]));
// 4. Hubungkan SETIAP RUMAH SISWA (ID 4 sampai 59) ke titik terdekatnya
for ($i = 4; $i <= 59; $i++) {
if(!isset($titik[$i])) continue; // Lewati kalau ID tidak ada
$rumah = $titik[$i];
// Hitung jarak dari rumah ke PAUD & Persimpangan
$jarakKePaud = $this->hitungJarak($rumah->latitude, $rumah->longitude, $titik[1]->latitude, $titik[1]->longitude);
$jarakKeSimpangA = $this->hitungJarak($rumah->latitude, $rumah->longitude, $titik[2]->latitude, $titik[2]->longitude);
$jarakKeSimpangB = $this->hitungJarak($rumah->latitude, $rumah->longitude, $titik[3]->latitude, $titik[3]->longitude);
// Cari mana yang paling dekat
$terdekat = 1;
$jarakMin = $jarakKePaud;
if ($jarakKeSimpangA < $jarakMin) {
$terdekat = 2;
$jarakMin = $jarakKeSimpangA;
}
if ($jarakKeSimpangB < $jarakMin) {
$terdekat = 3;
$jarakMin = $jarakKeSimpangB;
}
// Buat jembatan bolak-balik dari rumah ke titik terdekat tersebut
$jalur = array_merge($jalur, $this->buatJalurBolakBalik($rumah, $titik[$terdekat]));
}
// 5. Simpan semua data jembatannya ke Database!
DB::table('jalur_jalans')->insert($jalur);
$totalJalur = count($jalur);
$this->command->info("WOW! Berhasil membuat {$totalJalur} jembatan rute secara otomatis pakai Haversine Formula!");
}
// --- RUMUS BANTUAN ---
// Fungsi bikin jalur 2 arah (Pergi - Pulang)
private function buatJalurBolakBalik($titikA, $titikB) {
$jarak = $this->hitungJarak($titikA->latitude, $titikA->longitude, $titikB->latitude, $titikB->longitude);
return [
['titik_awal_id' => $titikA->id, 'titik_tujuan_id' => $titikB->id, 'jarak' => $jarak],
['titik_awal_id' => $titikB->id, 'titik_tujuan_id' => $titikA->id, 'jarak' => $jarak],
];
}
// Rumus Haversine: Mengubah Latitude & Longitude menjadi jarak Meter aslinya
private function hitungJarak($lat1, $lon1, $lat2, $lon2) {
$earthRadius = 6371000; // Radius bumi dalam meter
$latFrom = deg2rad((float)$lat1);
$lonFrom = deg2rad((float)$lon1);
$latTo = deg2rad((float)$lat2);
$lonTo = deg2rad((float)$lon2);
$latDelta = $latTo - $latFrom;
$lonDelta = $lonTo - $lonFrom;
$angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) +
cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));
return round($angle * $earthRadius); // Dibulatkan
}
}

View File

@ -1,7 +1,7 @@
@extends('layouts.app')
@section('content')
<div class="bg-white shadow-md rounded-lg p-6 max-w-xl mx-auto mt-10">
<div class="bg-white shadow-md rounded-lg p-6 max-w-xl mx-auto mt-10 mb-10">
<div class="flex justify-between items-center mb-6">
<h1 class="text-xl font-bold text-gray-700"> Tambah Siswa Baru</h1>
<a href="{{ route('siswa.index') }}" class="text-gray-500 hover:text-gray-700">&larr; Kembali</a>
@ -74,6 +74,24 @@
</div>
</div>
<div class="mb-4 p-4 border border-blue-200 bg-blue-50 rounded-lg">
<label class="block text-gray-800 font-bold mb-2">📍 Titik Lokasi Rumah (Untuk Rute Kunjungan)</label>
<p class="text-xs text-gray-600 mb-2">Geser dan klik pada peta di bawah untuk menandai rumah siswa.</p>
<div id="map" style="height: 300px; width: 100%; border-radius: 8px; z-index: 1;"></div>
<div class="grid grid-cols-2 gap-4 mt-3">
<div>
<label class="block text-xs text-gray-500 mb-1">Latitude</label>
<input type="text" name="latitude" id="latitude" value="{{ old('latitude') }}" class="w-full border border-gray-300 rounded p-2 bg-gray-100 text-sm" readonly placeholder="Otomatis terisi">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">Longitude</label>
<input type="text" name="longitude" id="longitude" value="{{ old('longitude') }}" class="w-full border border-gray-300 rounded p-2 bg-gray-100 text-sm" readonly placeholder="Otomatis terisi">
</div>
</div>
</div>
<div class="flex justify-end mt-6 gap-3">
<button type="submit" class="bg-green-600 text-white font-semibold px-6 py-2 rounded-lg hover:bg-green-700 transition shadow-md">
💾 Simpan Data
@ -81,4 +99,41 @@
</div>
</form>
</div>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Titik tengah default: Alun-alun Jember
var map = L.map('map').setView([-7.628337, 111.525506], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
var marker;
// Kalau ada error validasi dan ada data lama, tampilkan markernya
@if(old('latitude') && old('longitude'))
var oldLat = {{ old('latitude') }};
var oldLng = {{ old('longitude') }};
marker = L.marker([oldLat, oldLng]).addTo(map);
map.setView([oldLat, oldLng], 15);
@endif
// Event saat peta diklik
map.on('click', function(e) {
var lat = e.latlng.lat;
var lng = e.latlng.lng;
document.getElementById('latitude').value = lat;
document.getElementById('longitude').value = lng;
if (marker) {
map.removeLayer(marker);
}
marker = L.marker([lat, lng]).addTo(map);
});
});
</script>
@endsection

View File

@ -1,7 +1,7 @@
@extends('layouts.app')
@section('content')
<div class="bg-white shadow-md rounded-lg p-6 max-w-xl mx-auto mt-10">
<div class="bg-white shadow-md rounded-lg p-6 max-w-xl mx-auto mt-10 mb-10">
<div class="flex justify-between items-center mb-6">
<h1 class="text-xl font-bold text-gray-700">✏️ Edit Data Siswa</h1>
<a href="{{ route('siswa.index') }}" class="text-gray-500 hover:text-gray-700">&larr; Kembali</a>
@ -78,6 +78,24 @@
</div>
</div>
<div class="mb-4 p-4 border border-blue-200 bg-blue-50 rounded-lg">
<label class="block text-gray-800 font-bold mb-2">📍 Titik Lokasi Rumah (Untuk Rute Kunjungan)</label>
<p class="text-xs text-gray-600 mb-2">Geser dan klik pada peta untuk mengubah lokasi rumah siswa.</p>
<div id="map" style="height: 300px; width: 100%; border-radius: 8px; z-index: 1;"></div>
<div class="grid grid-cols-2 gap-4 mt-3">
<div>
<label class="block text-xs text-gray-500 mb-1">Latitude</label>
<input type="text" name="latitude" id="latitude" value="{{ old('latitude', $siswa->latitude) }}" class="w-full border border-gray-300 rounded p-2 bg-gray-100 text-sm" readonly>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">Longitude</label>
<input type="text" name="longitude" id="longitude" value="{{ old('longitude', $siswa->longitude) }}" class="w-full border border-gray-300 rounded p-2 bg-gray-100 text-sm" readonly>
</div>
</div>
</div>
<div class="flex justify-end mt-6 gap-3">
<button type="submit" class="bg-green-600 text-white font-semibold px-6 py-2 rounded-lg hover:bg-green-700 transition shadow-md">
💾 Simpan Perubahan
@ -85,4 +103,42 @@
</div>
</form>
</div>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Cek apakah siswa sudah punya koordinat
var currentLat = {{ $siswa->latitude ?? '-7.628337' }};
var currentLng = {{ $siswa->longitude ?? '111.525506' }};
var hasLocation = {{ $siswa->latitude ? 'true' : 'false' }};
var map = L.map('map').setView([currentLat, currentLng], hasLocation ? 16 : 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
var marker;
// Jika sudah ada koordinat, pasang marker
if (hasLocation) {
marker = L.marker([currentLat, currentLng]).addTo(map);
}
// Event saat peta diklik
map.on('click', function(e) {
var lat = e.latlng.lat;
var lng = e.latlng.lng;
document.getElementById('latitude').value = lat;
document.getElementById('longitude').value = lng;
if (marker) {
map.removeLayer(marker);
}
marker = L.marker([lat, lng]).addTo(map);
});
});
</script>
@endsection

View File

@ -10,6 +10,7 @@
use App\Http\Controllers\Api\LaporanController;
use App\Http\Controllers\Api\GuruController;
use App\Http\Controllers\Api\PenjemputanController;
use App\Http\Controllers\Api\AStarController;
/*
|--------------------------------------------------------------------------
@ -26,7 +27,6 @@
// --- KHUSUS WALI MURID (Lihat Data) ---
// Wali melihat daftar anaknya
// ==========================================
// 2. AREA TERKUNCI (BUTUH TOKEN)
// ==========================================
@ -55,5 +55,8 @@
// RUTE PENJEMPUTAN BARU
Route::post('/penjemputan', [PenjemputanController::class, 'store']);
Route::get('/rute-astar', [AStarController::class, 'cariRute']);
});

View File

@ -82,4 +82,30 @@
Route::get('/rapot/{id}/print', [App\Http\Controllers\Admin\RapotController::class, 'print'])->name('rapot.print');
});
Route::get('/sinkron-kordinat', function () {
// Ambil data dari ID 4 sampai akhir (karena ID 1-3 itu PAUD dan Simpang)
$titikRumah = DB::table('titik_jalans')->where('id', '>=', 4)->get();
$berhasil = 0;
foreach ($titikRumah as $titik) {
// Hilangkan kata "Rumah " biar sisa nama siswanya aja
$namaSiswa = str_replace('Rumah ', '', $titik->nama_titik);
$namaSiswa = trim($namaSiswa);
// Update latitude & longitude di tabel siswas yang namanya mirip
$update = DB::table('siswas')
->where('nama_siswa', 'LIKE', '%' . $namaSiswa . '%')
->update([
'latitude' => $titik->latitude,
'longitude' => $titik->longitude
]);
if($update) {
$berhasil++;
}
}
return "Selesai Bosku! Berhasil menyinkronkan $berhasil data koordinat siswa!";
});
});