1596 lines
68 KiB
PHP
1596 lines
68 KiB
PHP
@extends('layouts.admin')
|
||
|
||
@section('title', 'Edit Kontrakan')
|
||
|
||
@section('content')
|
||
<div class="container-fluid px-4">
|
||
<style>
|
||
.breadcrumb {
|
||
background: transparent;
|
||
border-radius: 8px;
|
||
padding: 0;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.breadcrumb-item a {
|
||
color: #667eea;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.breadcrumb-item.active {
|
||
color: #764ba2;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.page-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 2rem;
|
||
border-radius: 12px;
|
||
margin-bottom: 2rem;
|
||
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.15);
|
||
}
|
||
|
||
.page-header h2 {
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.form-card {
|
||
border: none;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.section-header {
|
||
color: #667eea;
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
padding-bottom: 1rem;
|
||
border-bottom: 2px solid #667eea;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.form-label {
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.input-group-text {
|
||
border: 1px solid #dee2e6;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.form-control:focus {
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||
}
|
||
|
||
.btn-submit {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border: none;
|
||
color: white;
|
||
font-weight: 600;
|
||
padding: 0.75rem 1.75rem;
|
||
border-radius: 8px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-submit:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
|
||
color: white;
|
||
}
|
||
|
||
.info-card {
|
||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
|
||
border: 1px solid #667eea;
|
||
border-radius: 10px;
|
||
padding: 1.5rem;
|
||
margin-top: 2rem;
|
||
}
|
||
</style>
|
||
|
||
<!-- Breadcrumb -->
|
||
<nav aria-label="breadcrumb" class="mb-4">
|
||
<ol class="breadcrumb">
|
||
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
|
||
<li class="breadcrumb-item"><a href="{{ route('kontrakan.index') }}">Kontrakan</a></li>
|
||
<li class="breadcrumb-item active">Edit Data</li>
|
||
</ol>
|
||
</nav>
|
||
|
||
<!-- Page Header -->
|
||
<div class="page-header mb-4">
|
||
<h2 class="mb-2">
|
||
<i class="bi bi-pencil-square me-2"></i>Edit Kontrakan
|
||
</h2>
|
||
<p class="mb-0 fs-6">Update informasi kontrakan <strong>{{ $kontrakan->nama }}</strong></p>
|
||
</div>
|
||
|
||
<!-- Form Card -->
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="card form-card">
|
||
<div class="card-body p-4">
|
||
<form action="{{ route('kontrakan.update', $kontrakan->id) }}" method="POST" enctype="multipart/form-data" id="kontrakanForm">
|
||
@csrf
|
||
@method('PUT')
|
||
|
||
<!-- Informasi Dasar Section -->
|
||
<div class="mb-4">
|
||
<h5 class="section-header">
|
||
<i class="bi bi-info-circle me-2"></i>Informasi Dasar
|
||
</h5>
|
||
|
||
<div class="row g-3">
|
||
<!-- Nama Kontrakan -->
|
||
<div class="col-md-12">
|
||
<label for="nama" class="form-label fw-semibold">
|
||
Nama Kontrakan <span class="text-danger">*</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0">
|
||
<i class="bi bi-building text-primary"></i>
|
||
</span>
|
||
<input
|
||
type="text"
|
||
name="nama"
|
||
class="form-control border-start-0 @error('nama') is-invalid @enderror"
|
||
id="nama"
|
||
placeholder="Contoh: Kontrakan Melati Residence"
|
||
value="{{ old('nama', $kontrakan->nama) }}"
|
||
required
|
||
>
|
||
@error('nama')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Masukkan nama kontrakan yang mudah dikenali</small>
|
||
</div>
|
||
|
||
<!-- Alamat -->
|
||
<div class="col-md-12">
|
||
<label for="alamat" class="form-label fw-semibold">
|
||
Alamat Lengkap <span class="text-danger">*</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0 align-items-start pt-2">
|
||
<i class="bi bi-geo-alt text-danger"></i>
|
||
</span>
|
||
<textarea
|
||
name="alamat"
|
||
class="form-control border-start-0 @error('alamat') is-invalid @enderror"
|
||
id="alamat"
|
||
rows="3"
|
||
placeholder="Contoh: Jl. Kaliurang KM 5, Sleman, Yogyakarta"
|
||
required
|
||
>{{ old('alamat', $kontrakan->alamat) }}</textarea>
|
||
@error('alamat')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Alamat lengkap beserta RT/RW jika ada. Alamat akan auto-update saat klik peta.</small>
|
||
</div>
|
||
|
||
<!-- WhatsApp -->
|
||
<div class="col-md-12">
|
||
<label for="no_whatsapp" class="form-label fw-semibold">
|
||
<i class="bi bi-whatsapp text-success"></i> Nomor WhatsApp Pemilik <span class="text-muted">(Opsional)</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0">
|
||
<i class="bi bi-telephone text-success"></i>
|
||
</span>
|
||
<input
|
||
type="text"
|
||
name="no_whatsapp"
|
||
class="form-control border-start-0 @error('no_whatsapp') is-invalid @enderror"
|
||
id="no_whatsapp"
|
||
placeholder="Contoh: 081234567890"
|
||
value="{{ old('no_whatsapp', $kontrakan->no_whatsapp) }}"
|
||
maxlength="20"
|
||
>
|
||
@error('no_whatsapp')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
Format: 08xxx atau 628xxx (untuk kemudahan calon penyewa menghubungi)
|
||
</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== LOKASI & KOORDINAT SECTION - UPDATED! ========== -->
|
||
<div class="mb-4">
|
||
<h5 class="section-header">
|
||
<i class="bi bi-pin-map-fill me-2"></i>Lokasi & Koordinat
|
||
</h5>
|
||
|
||
<!-- Alert Info - UPDATED -->
|
||
<div class="alert alert-info border-info">
|
||
<div class="d-flex">
|
||
<i class="bi bi-info-circle me-2 mt-1"></i>
|
||
<div>
|
||
<strong>⭐ Cara Menentukan Lokasi (PILIH SALAH SATU):</strong>
|
||
<ul class="mb-0 mt-2 small">
|
||
<li class="mb-2">
|
||
<strong class="text-success">🎯 REKOMENDASI #1: Klik di Peta</strong>
|
||
<div class="text-muted" style="font-size: 0.85rem;">
|
||
Klik pada peta di lokasi kontrakan → Alamat & koordinat <strong>otomatis terisi</strong><br>
|
||
<span class="badge bg-success bg-opacity-10 text-success mt-1">Paling Akurat & Mudah!</span>
|
||
</div>
|
||
</li>
|
||
<li class="mb-2">
|
||
<strong class="text-primary">📍 Deteksi GPS</strong>
|
||
<div class="text-muted" style="font-size: 0.85rem;">
|
||
Klik "Deteksi Lokasi Saya" <strong>HANYA jika Anda sedang berada di lokasi kontrakan</strong>
|
||
</div>
|
||
</li>
|
||
<li class="mb-2">
|
||
<strong class="text-warning">⌨️ Input Manual Koordinat</strong>
|
||
<div class="text-muted" style="font-size: 0.85rem;">
|
||
Ketik latitude & longitude dari Google Maps → Marker otomatis pindah
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<strong class="text-secondary">🔍 Cari dari Alamat</strong>
|
||
<div class="text-muted" style="font-size: 0.85rem;">
|
||
<span class="text-danger">⚠️ Hanya untuk alamat umum</span> (contoh: "Sumbersari, Jember")<br>
|
||
<span class="text-danger">❌ TIDAK untuk nama perumahan/kontrakan spesifik</span>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
<div class="alert alert-warning bg-opacity-10 border-warning mt-2 mb-0 py-2 px-3">
|
||
<small class="mb-0">
|
||
<strong>💡 Tips:</strong> Jika alamat berisi nama perumahan/gang (contoh: "Perumahan Melati, Gang Mawar"),
|
||
<strong class="text-danger">gunakan cara KLIK PETA atau INPUT KOORDINAT manual</strong>,
|
||
jangan gunakan tombol "Cari dari Alamat"
|
||
</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tombol Geocoding & Deteksi Lokasi -->
|
||
<div class="row g-2 mb-3">
|
||
<div class="col-12 col-md-6">
|
||
<button type="button" id="geocodeBtn" class="btn btn-secondary w-100">
|
||
<i class="bi bi-search me-2"></i>Cari dari Alamat Umum
|
||
</button>
|
||
<small class="text-danger d-block mt-1">
|
||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||
Hanya untuk alamat umum (Kota/Kecamatan), bukan nama perumahan!
|
||
</small>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<button type="button" id="detectLocation" class="btn btn-outline-primary w-100">
|
||
<i class="bi bi-crosshair me-2"></i>Deteksi Lokasi Saya
|
||
</button>
|
||
<small class="text-muted d-block mt-1">
|
||
<i class="bi bi-geo-alt me-1"></i>Gunakan GPS Anda (harus di lokasi)
|
||
</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
<!-- Latitude - UPDATED: Bisa Input Manual -->
|
||
<div class="col-md-6">
|
||
<label for="latitude" class="form-label fw-semibold">
|
||
Latitude <span class="text-danger">*</span>
|
||
<span class="badge bg-warning bg-opacity-10 text-warning ms-2 small">
|
||
<i class="bi bi-pencil me-1"></i>Bisa input manual
|
||
</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light">
|
||
<i class="bi bi-geo text-danger"></i>
|
||
</span>
|
||
<input
|
||
type="number"
|
||
name="latitude"
|
||
class="form-control @error('latitude') is-invalid @enderror"
|
||
id="latitude"
|
||
placeholder="-6.966667"
|
||
value="{{ old('latitude', $kontrakan->latitude) }}"
|
||
step="0.00000001"
|
||
min="-90"
|
||
max="90"
|
||
required
|
||
>
|
||
@error('latitude')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Range: -90 sampai 90</small>
|
||
</div>
|
||
|
||
<!-- Longitude - UPDATED: Bisa Input Manual -->
|
||
<div class="col-md-6">
|
||
<label for="longitude" class="form-label fw-semibold">
|
||
Longitude <span class="text-danger">*</span>
|
||
<span class="badge bg-warning bg-opacity-10 text-warning ms-2 small">
|
||
<i class="bi bi-pencil me-1"></i>Bisa input manual
|
||
</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light">
|
||
<i class="bi bi-geo text-danger"></i>
|
||
</span>
|
||
<input
|
||
type="number"
|
||
name="longitude"
|
||
class="form-control @error('longitude') is-invalid @enderror"
|
||
id="longitude"
|
||
placeholder="110.416664"
|
||
value="{{ old('longitude', $kontrakan->longitude) }}"
|
||
step="0.00000001"
|
||
min="-180"
|
||
max="180"
|
||
required
|
||
>
|
||
@error('longitude')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Range: -180 sampai 180</small>
|
||
</div>
|
||
|
||
<!-- Map Container -->
|
||
<div class="col-12">
|
||
<div id="map" style="height: 400px; border-radius: 8px; border: 2px solid #dee2e6;"></div>
|
||
<small class="text-muted d-block mt-2">
|
||
<i class="bi bi-hand-index me-1"></i>Klik pada peta → alamat & koordinat otomatis terisi | Drag marker untuk ubah posisi
|
||
</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Detail Properti Section -->
|
||
<div class="mb-4">
|
||
<h5 class="section-header">
|
||
<i class="bi bi-house me-2"></i>Detail Properti
|
||
</h5>
|
||
|
||
<div class="row g-3">
|
||
<!-- Harga -->
|
||
<div class="col-md-6">
|
||
<label for="harga" class="form-label fw-semibold">
|
||
Harga Sewa/Tahun <span class="text-danger">*</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0">
|
||
<i class="bi bi-currency-dollar text-success"></i>
|
||
</span>
|
||
<input
|
||
type="number"
|
||
name="harga"
|
||
class="form-control border-start-0 @error('harga') is-invalid @enderror"
|
||
id="harga"
|
||
placeholder="500000"
|
||
value="{{ old('harga', $kontrakan->harga) }}"
|
||
min="0"
|
||
required
|
||
>
|
||
@error('harga')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Harga dalam Rupiah per tahun</small>
|
||
<div id="harga_preview" class="mt-1" style="display:none;">
|
||
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size:0.82rem; padding: 4px 10px;">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
<span id="harga_preview_text"></span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- jumlah_kamar -->
|
||
<div class="col-md-6">
|
||
<label for="jumlah_kamar" class="form-label fw-semibold">
|
||
Jumlah Kamar Tidur <span class="text-danger">*</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0">
|
||
<i class="bi bi-door-closed text-primary"></i>
|
||
</span>
|
||
<input
|
||
type="number"
|
||
name="jumlah_kamar"
|
||
class="form-control border-start-0 @error('jumlah_kamar') is-invalid @enderror"
|
||
id="jumlah_kamar"
|
||
placeholder="Contoh: 3"
|
||
value="{{ old('jumlah_kamar', $kontrakan->jumlah_kamar) }}"
|
||
min="1"
|
||
step="1"
|
||
required
|
||
>
|
||
<span class="input-group-text bg-light">kamar</span>
|
||
@error('jumlah_kamar')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Jumlah kamar tidur yang tersedia</small>
|
||
</div>
|
||
|
||
<!-- bathroom_count -->
|
||
<div class="col-md-6">
|
||
<label for="bathroom_count" class="form-label fw-semibold">
|
||
Jumlah Kamar Mandi
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0">
|
||
<i class="bi bi-droplet text-info"></i>
|
||
</span>
|
||
<input
|
||
type="number"
|
||
name="bathroom_count"
|
||
class="form-control border-start-0 @error('bathroom_count') is-invalid @enderror"
|
||
id="bathroom_count"
|
||
placeholder="Contoh: 1"
|
||
value="{{ old('bathroom_count', $kontrakan->bathroom_count ?? 1) }}"
|
||
min="0"
|
||
step="1"
|
||
>
|
||
<span class="input-group-text bg-light">kamar</span>
|
||
@error('bathroom_count')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Informasi kamar mandi untuk detail kontrakan</small>
|
||
</div>
|
||
|
||
<!-- Jarak - AUTO CALCULATED! -->
|
||
<div class="col-md-6">
|
||
<label for="jarak" class="form-label fw-semibold">
|
||
Jarak ke Kampus <span class="text-danger">*</span>
|
||
<span class="badge bg-info bg-opacity-10 text-info ms-2">
|
||
<i class="bi bi-calculator me-1"></i>Auto-calculated
|
||
</span>
|
||
</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-end-0">
|
||
<i class="bi bi-pin-map text-info"></i>
|
||
</span>
|
||
<input
|
||
type="number"
|
||
name="jarak"
|
||
class="form-control border-start-0 @error('jarak') is-invalid @enderror"
|
||
id="jarak"
|
||
placeholder="Otomatis dihitung"
|
||
value="{{ old('jarak', $kontrakan->jarak) }}"
|
||
min="0"
|
||
readonly
|
||
required
|
||
>
|
||
<span class="input-group-text bg-light">meter</span>
|
||
@error('jarak')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
Jarak otomatis dihitung dari koordinat kontrakan ke kampus
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Fasilitas -->
|
||
<div class="col-md-6">
|
||
@php
|
||
$facilityOptions = [
|
||
'WiFi',
|
||
'Kasur',
|
||
'Lemari',
|
||
'Meja Belajar',
|
||
'Kamar Mandi Dalam',
|
||
'Kamar Mandi Luar',
|
||
'Closet Duduk',
|
||
'Dapur',
|
||
'Dapur Bersama',
|
||
'Tempat Cuci Piring',
|
||
'Kompor',
|
||
'Ruang Tamu',
|
||
'Jemuran',
|
||
'Parkir Motor',
|
||
'Parkir Mobil',
|
||
'Garasi',
|
||
'Listrik Termasuk',
|
||
'Air PDAM/Sumur',
|
||
'Keamanan 24 Jam',
|
||
'CCTV',
|
||
'Akses 24 Jam',
|
||
'Musholla',
|
||
'Perlengkapan Tidur',
|
||
];
|
||
$selectedFacilities = collect(explode(',', old('fasilitas', $kontrakan->fasilitas ?? '')))
|
||
->map(fn($item) => strtolower(trim($item)))
|
||
->filter()
|
||
->values()
|
||
->all();
|
||
@endphp
|
||
<label for="fasilitas" class="form-label fw-semibold">
|
||
Fasilitas
|
||
</label>
|
||
<input type="hidden" name="fasilitas" id="fasilitas" value="{{ old('fasilitas', $kontrakan->fasilitas) }}">
|
||
<div class="border rounded p-2 bg-light @error('fasilitas') border-danger @enderror">
|
||
<div class="row g-2">
|
||
@foreach($facilityOptions as $facility)
|
||
<div class="col-6">
|
||
<div class="form-check">
|
||
<input
|
||
class="form-check-input fasilitas-checkbox"
|
||
type="checkbox"
|
||
value="{{ $facility }}"
|
||
id="fasilitas_{{ $loop->index }}"
|
||
{{ in_array(strtolower($facility), $selectedFacilities) ? 'checked' : '' }}
|
||
>
|
||
<label class="form-check-label small" for="fasilitas_{{ $loop->index }}">
|
||
{{ $facility }}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@error('fasilitas')
|
||
<div class="text-danger small mt-2">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
<small class="text-muted">Checklist satu atau lebih fasilitas sesuai data kontrakan</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Upload Foto Section -->
|
||
<div class="mb-4">
|
||
<h5 class="section-header">
|
||
<i class="bi bi-camera me-2"></i>Foto Kontrakan
|
||
</h5>
|
||
|
||
<div class="row g-3">
|
||
<div class="col-md-12">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="row align-items-center">
|
||
<!-- Foto Preview (Kiri) -->
|
||
<div class="col-md-4 text-center mb-3 mb-md-0">
|
||
@if($kontrakan->foto)
|
||
<img
|
||
src="{{ asset('uploads/kontrakan/' . $kontrakan->foto) }}"
|
||
alt="{{ $kontrakan->nama }}"
|
||
class="img-fluid rounded"
|
||
style="max-height: 150px; cursor: pointer;"
|
||
onclick="openImageModal(this.src)"
|
||
id="current-foto"
|
||
>
|
||
@else
|
||
<div class="py-3">
|
||
<i class="bi bi-image text-muted" style="font-size: 3rem;"></i>
|
||
<p class="text-muted mb-0 small">Belum ada foto</p>
|
||
</div>
|
||
@endif
|
||
|
||
<div id="preview-foto-container" style="display: none;">
|
||
<img id="preview-foto" src="" alt="Preview" class="img-fluid rounded" style="max-height: 150px;">
|
||
<p class="text-success mb-0 small mt-1">
|
||
<i class="bi bi-check-circle me-1"></i>Foto baru
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Input & Actions (Kanan) -->
|
||
<div class="col-md-8">
|
||
<label for="foto" class="form-label fw-semibold mb-2">
|
||
<i class="bi bi-upload me-1"></i>Upload / Ganti Foto
|
||
</label>
|
||
<input
|
||
type="file"
|
||
name="foto"
|
||
class="form-control form-control-sm @error('foto') is-invalid @enderror"
|
||
id="foto"
|
||
accept="image/jpeg,image/png,image/jpg"
|
||
>
|
||
<small class="text-muted d-block mt-1">Format: JPG, PNG, JPEG (Max 2MB)</small>
|
||
@error('foto')
|
||
<div class="invalid-feedback">{{ $message }}</div>
|
||
@enderror
|
||
|
||
@if($kontrakan->foto)
|
||
<div class="form-check mt-3">
|
||
<input
|
||
class="form-check-input border-danger"
|
||
type="checkbox"
|
||
name="hapus_foto"
|
||
value="1"
|
||
id="hapus_foto"
|
||
>
|
||
<label class="form-check-label text-danger fw-semibold" for="hapus_foto">
|
||
<i class="bi bi-trash me-1"></i>Hapus foto ini
|
||
</label>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Info Perubahan -->
|
||
<div class="alert alert-info border-info border-2 mb-4">
|
||
<h6 class="fw-bold mb-2">
|
||
<i class="bi bi-info-circle me-2"></i>Info Perubahan
|
||
</h6>
|
||
<p class="mb-0 small">
|
||
Anda sedang mengedit data kontrakan <strong>{{ $kontrakan->nama }}</strong>.
|
||
Pastikan semua perubahan sudah benar sebelum menyimpan.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div class="d-flex gap-2 justify-content-end pt-3 border-top">
|
||
<a href="{{ route('kontrakan.index') }}" class="btn btn-outline-secondary px-4">
|
||
<i class="bi bi-arrow-left me-2"></i>Batal
|
||
</a>
|
||
<button type="submit" class="btn btn-submit px-4">
|
||
<i class="bi bi-save me-2"></i>Simpan Perubahan
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Data History Card -->
|
||
<div class="info-card">
|
||
<div class="d-flex">
|
||
<div class="me-3">
|
||
<i class="bi bi-clock-history text-primary" style="font-size: 1.5rem;"></i>
|
||
</div>
|
||
<div>
|
||
<h6 class="fw-bold mb-2">Riwayat Data:</h6>
|
||
<p class="mb-1 small">
|
||
<strong>Dibuat:</strong> {{ $kontrakan->created_at->format('d M Y, H:i') }} WIB
|
||
</p>
|
||
@if($kontrakan->updated_at != $kontrakan->created_at)
|
||
<p class="mb-0 small">
|
||
<strong>Terakhir diupdate:</strong> {{ $kontrakan->updated_at->format('d M Y, H:i') }} WIB
|
||
</p>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Leaflet CSS -->
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||
<!-- Leaflet JS -->
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<!-- JavaScript -->
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// ========== KOORDINAT KAMPUS POLIJE ==========
|
||
const KAMPUS_LAT = -8.15981;
|
||
const KAMPUS_LNG = 113.72312;
|
||
|
||
// ========== GET EXISTING DATA ==========
|
||
const existingLat = {{ $kontrakan->latitude ?? 'null' }};
|
||
const existingLng = {{ $kontrakan->longitude ?? 'null' }};
|
||
|
||
// ========== MAP INITIALIZATION ==========
|
||
const initialLat = existingLat || KAMPUS_LAT;
|
||
const initialLng = existingLng || KAMPUS_LNG;
|
||
|
||
const map = L.map('map').setView([initialLat, initialLng], existingLat ? 15 : 13);
|
||
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© OpenStreetMap contributors',
|
||
maxZoom: 19
|
||
}).addTo(map);
|
||
|
||
// Marker kampus (optional - untuk referensi)
|
||
const kampusIcon = L.divIcon({
|
||
className: 'custom-marker',
|
||
html: '<div style="background: #dc3545; color: white; padding: 5px 10px; border-radius: 5px; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3);">🏫 KAMPUS</div>',
|
||
iconSize: [80, 30],
|
||
iconAnchor: [40, 15]
|
||
});
|
||
L.marker([KAMPUS_LAT, KAMPUS_LNG], { icon: kampusIcon }).addTo(map);
|
||
|
||
let marker;
|
||
let isUpdatingFromInput = false; // Flag untuk prevent loop
|
||
|
||
// ========== FUNCTION: REVERSE GEOCODING (KOORDINAT → ALAMAT) ==========
|
||
function reverseGeocode(lat, lng) {
|
||
const reverseGeocodeURL = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`;
|
||
|
||
fetch(reverseGeocodeURL)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data && data.display_name) {
|
||
document.getElementById('alamat').value = data.display_name;
|
||
console.log('✅ Alamat berhasil di-update:', data.display_name);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('⚠️ Reverse geocoding error:', error);
|
||
});
|
||
}
|
||
|
||
// ========== FUNCTION: ADD/UPDATE MARKER ==========
|
||
function addMarker(lat, lng, shouldReverseGeocode = true) {
|
||
if (marker) {
|
||
marker.setLatLng([lat, lng]);
|
||
} else {
|
||
marker = L.marker([lat, lng], {
|
||
draggable: true
|
||
}).addTo(map);
|
||
|
||
// Event: Drag marker
|
||
marker.on('dragend', function(e) {
|
||
const position = marker.getLatLng();
|
||
updateCoordinates(position.lat, position.lng);
|
||
calculateDistance(position.lat, position.lng);
|
||
reverseGeocode(position.lat, position.lng); // Auto update alamat
|
||
});
|
||
}
|
||
|
||
map.setView([lat, lng], 15);
|
||
updateCoordinates(lat, lng);
|
||
calculateDistance(lat, lng);
|
||
|
||
// Reverse geocoding (koordinat → alamat)
|
||
if (shouldReverseGeocode) {
|
||
reverseGeocode(lat, lng);
|
||
}
|
||
}
|
||
|
||
// ========== INITIALIZE MARKER WITH EXISTING DATA ==========
|
||
if (existingLat && existingLng) {
|
||
addMarker(existingLat, existingLng, false); // false = don't reverse geocode on load
|
||
}
|
||
|
||
// ========== FUNCTION: UPDATE COORDINATES INPUT ==========
|
||
function updateCoordinates(lat, lng) {
|
||
if (!isUpdatingFromInput) {
|
||
document.getElementById('latitude').value = lat.toFixed(8);
|
||
document.getElementById('longitude').value = lng.toFixed(8);
|
||
}
|
||
}
|
||
|
||
// ========== FUNCTION: VALIDATE COORDINATES ==========
|
||
function validateCoordinates(lat, lng) {
|
||
lat = parseFloat(lat);
|
||
lng = parseFloat(lng);
|
||
|
||
if (isNaN(lat) || isNaN(lng)) {
|
||
return { valid: false, message: '❌ Koordinat harus berupa angka!' };
|
||
}
|
||
|
||
if (lat < -90 || lat > 90) {
|
||
return { valid: false, message: '❌ Latitude harus antara -90 sampai 90!' };
|
||
}
|
||
|
||
if (lng < -180 || lng > 180) {
|
||
return { valid: false, message: '❌ Longitude harus antara -180 sampai 180!' };
|
||
}
|
||
|
||
return { valid: true, lat: lat, lng: lng };
|
||
}
|
||
|
||
// ========== FUNCTION: CALCULATE DISTANCE (HAVERSINE FORMULA) ==========
|
||
function calculateDistance(lat, lng) {
|
||
const R = 6371e3; // Earth radius in meters
|
||
const φ1 = KAMPUS_LAT * Math.PI / 180;
|
||
const φ2 = lat * Math.PI / 180;
|
||
const Δφ = (lat - KAMPUS_LAT) * Math.PI / 180;
|
||
const Δλ = (lng - KAMPUS_LNG) * Math.PI / 180;
|
||
|
||
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
|
||
Math.cos(φ1) * Math.cos(φ2) *
|
||
Math.sin(Δλ/2) * Math.sin(Δλ/2);
|
||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||
|
||
const distance = Math.round(R * c); // Distance in meters
|
||
document.getElementById('jarak').value = distance;
|
||
|
||
console.log('📏 Jarak ke kampus:', distance, 'meter');
|
||
}
|
||
|
||
// ========== INPUT MANUAL KOORDINAT → UPDATE PETA ==========
|
||
const latInput = document.getElementById('latitude');
|
||
const lngInput = document.getElementById('longitude');
|
||
|
||
let inputTimeout;
|
||
|
||
function handleCoordinateInput() {
|
||
clearTimeout(inputTimeout);
|
||
|
||
inputTimeout = setTimeout(() => {
|
||
const lat = latInput.value;
|
||
const lng = lngInput.value;
|
||
|
||
if (lat && lng) {
|
||
const validation = validateCoordinates(lat, lng);
|
||
|
||
if (validation.valid) {
|
||
isUpdatingFromInput = true;
|
||
addMarker(validation.lat, validation.lng, true); // true = reverse geocode
|
||
isUpdatingFromInput = false;
|
||
|
||
console.log('✅ Marker dipindahkan ke:', validation.lat, validation.lng);
|
||
} else {
|
||
alert(validation.message);
|
||
}
|
||
}
|
||
}, 800); // Delay 800ms untuk avoid spam
|
||
}
|
||
|
||
latInput.addEventListener('input', handleCoordinateInput);
|
||
lngInput.addEventListener('input', handleCoordinateInput);
|
||
|
||
// ========== GEOCODING: ALAMAT → KOORDINAT ==========
|
||
document.getElementById('geocodeBtn').addEventListener('click', function() {
|
||
const alamat = document.getElementById('alamat').value.trim();
|
||
|
||
if (!alamat) {
|
||
alert('⚠️ Mohon isi alamat lengkap terlebih dahulu!');
|
||
document.getElementById('alamat').focus();
|
||
return;
|
||
}
|
||
|
||
const button = this;
|
||
const originalHTML = button.innerHTML;
|
||
|
||
button.disabled = true;
|
||
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Mencari koordinat...';
|
||
|
||
// Fungsi untuk mencari koordinat
|
||
function tryGeocode(searchQuery) {
|
||
const geocodeURL = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchQuery)}&limit=5&countrycodes=id`;
|
||
|
||
return fetch(geocodeURL)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data && data.length > 0) {
|
||
// Filter hasil yang paling relevan (prioritaskan yang ada "jember")
|
||
let bestMatch = data[0];
|
||
const jemberMatch = data.find(item =>
|
||
item.display_name.toLowerCase().includes('jember')
|
||
);
|
||
if (jemberMatch) bestMatch = jemberMatch;
|
||
|
||
return bestMatch;
|
||
}
|
||
return null;
|
||
});
|
||
}
|
||
|
||
// STRATEGI 1: Cari dengan alamat lengkap
|
||
tryGeocode(alamat)
|
||
.then(result => {
|
||
if (result) {
|
||
return result;
|
||
}
|
||
|
||
// STRATEGI 2: Ambil kata kunci penting (kecamatan + kota)
|
||
const parts = alamat.split(',').map(s => s.trim());
|
||
if (parts.length >= 2) {
|
||
// Coba: Kecamatan, Kota
|
||
const simplifiedQuery = `${parts[parts.length - 2]}, ${parts[parts.length - 1]}`;
|
||
return tryGeocode(simplifiedQuery);
|
||
}
|
||
return null;
|
||
})
|
||
.then(result => {
|
||
if (result) {
|
||
return result;
|
||
}
|
||
|
||
// STRATEGI 3: Cari hanya nama kota/kabupaten
|
||
const cityMatch = alamat.match(/jember|sumbersari/i);
|
||
if (cityMatch) {
|
||
return tryGeocode('Jember, Jawa Timur, Indonesia');
|
||
}
|
||
return null;
|
||
})
|
||
.then(result => {
|
||
button.disabled = false;
|
||
button.innerHTML = originalHTML;
|
||
|
||
if (result) {
|
||
const lat = parseFloat(result.lat);
|
||
const lng = parseFloat(result.lon);
|
||
|
||
addMarker(lat, lng, false);
|
||
|
||
// Success notification
|
||
alert('✅ Koordinat berhasil ditemukan!\n\n' +
|
||
'📍 Lokasi ditemukan: ' + result.display_name + '\n' +
|
||
'🌍 Koordinat: ' + lat.toFixed(6) + ', ' + lng.toFixed(6) + '\n' +
|
||
'📏 Jarak ke kampus: ' + document.getElementById('jarak').value + ' meter\n\n' +
|
||
'💡 Anda bisa drag marker untuk penyesuaian lokasi yang lebih tepat!');
|
||
} else {
|
||
// Gagal semua strategi
|
||
alert('❌ Alamat tidak ditemukan di peta!\n\n' +
|
||
'💡 Solusi Alternatif:\n\n' +
|
||
'1️⃣ CARA TERMUDAH: Klik langsung pada PETA di lokasi yang Anda inginkan\n' +
|
||
' → Alamat akan otomatis terisi!\n\n' +
|
||
'2️⃣ Sederhanakan alamat:\n' +
|
||
' • Dari: "' + alamat + '"\n' +
|
||
' • Jadi: "Sumbersari, Jember, Jawa Timur"\n' +
|
||
' • Atau: "Jember, Jawa Timur"\n\n' +
|
||
'3️⃣ Gunakan GPS:\n' +
|
||
' • Klik "Deteksi Lokasi Saya" jika Anda di lokasi\n\n' +
|
||
'4️⃣ Input Manual:\n' +
|
||
' • Ketik koordinat latitude & longitude langsung\n' +
|
||
' • Contoh: Latitude: -8.159917, Longitude: 113.722750');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
button.disabled = false;
|
||
button.innerHTML = originalHTML;
|
||
|
||
console.error('Geocoding error:', error);
|
||
alert('⚠️ Terjadi kesalahan saat mencari koordinat.\n\n' +
|
||
'🎯 SOLUSI CEPAT:\n' +
|
||
'• Klik langsung pada PETA (paling mudah!)\n' +
|
||
'• Atau gunakan "Deteksi Lokasi Saya"\n' +
|
||
'• Atau ketik koordinat manual');
|
||
});
|
||
});
|
||
|
||
// ========== DETECT USER LOCATION (GPS) ==========
|
||
document.getElementById('detectLocation').addEventListener('click', function() {
|
||
const button = this;
|
||
const originalHTML = button.innerHTML;
|
||
|
||
button.disabled = true;
|
||
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Mendeteksi lokasi...';
|
||
|
||
if (navigator.geolocation) {
|
||
navigator.geolocation.getCurrentPosition(
|
||
function(position) {
|
||
const lat = position.coords.latitude;
|
||
const lng = position.coords.longitude;
|
||
|
||
addMarker(lat, lng, true); // true = reverse geocode
|
||
|
||
button.disabled = false;
|
||
button.innerHTML = originalHTML;
|
||
|
||
alert('✅ Lokasi GPS berhasil terdeteksi!\n\n' +
|
||
'🌍 Koordinat: ' + lat.toFixed(6) + ', ' + lng.toFixed(6) + '\n' +
|
||
'📏 Jarak ke kampus: ' + document.getElementById('jarak').value + ' meter\n' +
|
||
'📍 Alamat otomatis di-update!');
|
||
},
|
||
function(error) {
|
||
button.disabled = false;
|
||
button.innerHTML = originalHTML;
|
||
|
||
let errorMsg = '❌ Gagal mendeteksi lokasi GPS.\n\n';
|
||
switch(error.code) {
|
||
case error.PERMISSION_DENIED:
|
||
errorMsg += '🚫 Izin lokasi ditolak.\n\n' +
|
||
'💡 Solusi:\n' +
|
||
'1. Klik ikon gembok/info di address bar\n' +
|
||
'2. Izinkan akses lokasi\n' +
|
||
'3. Refresh halaman dan coba lagi';
|
||
break;
|
||
case error.POSITION_UNAVAILABLE:
|
||
errorMsg += '📡 Informasi lokasi tidak tersedia.\n\n' +
|
||
'💡 Coba gunakan "Cari dari Alamat" sebagai gantinya.';
|
||
break;
|
||
case error.TIMEOUT:
|
||
errorMsg += '⏱️ Waktu permintaan habis.\n\n' +
|
||
'💡 Coba lagi atau gunakan cara lain.';
|
||
break;
|
||
default:
|
||
errorMsg += '⚠️ Terjadi kesalahan yang tidak diketahui.';
|
||
}
|
||
alert(errorMsg);
|
||
}
|
||
);
|
||
} else {
|
||
button.disabled = false;
|
||
button.innerHTML = originalHTML;
|
||
alert('❌ Browser Anda tidak mendukung Geolocation.\n\n' +
|
||
'💡 Gunakan "Cari dari Alamat" atau klik pada peta.');
|
||
}
|
||
});
|
||
|
||
// ========== CLICK ON MAP → AUTO UPDATE ALAMAT ==========
|
||
map.on('click', function(e) {
|
||
addMarker(e.latlng.lat, e.latlng.lng, true); // true = reverse geocode
|
||
});
|
||
|
||
// ========== WHATSAPP VALIDATION ==========
|
||
const whatsappInput = document.getElementById('no_whatsapp');
|
||
|
||
whatsappInput.addEventListener('input', function(e) {
|
||
this.value = this.value.replace(/[^0-9]/g, '');
|
||
if (this.value.length > 20) {
|
||
this.value = this.value.substring(0, 20);
|
||
}
|
||
});
|
||
|
||
whatsappInput.addEventListener('blur', function() {
|
||
const value = this.value.trim();
|
||
if (value) {
|
||
if (!value.startsWith('08') && !value.startsWith('628') && !value.startsWith('62')) {
|
||
alert('⚠️ Format nomor WhatsApp tidak valid.\n\nGunakan format: 08xxx atau 628xxx');
|
||
this.focus();
|
||
}
|
||
if (value.length < 10) {
|
||
alert('⚠️ Nomor WhatsApp terlalu pendek.\n\nMinimal 10 digit.');
|
||
this.focus();
|
||
}
|
||
}
|
||
});
|
||
|
||
// ========== FASILITAS CHECKLIST ==========
|
||
const fasilitasHiddenInput = document.getElementById('fasilitas');
|
||
const fasilitasCheckboxes = document.querySelectorAll('.fasilitas-checkbox');
|
||
let fasilitasTouched = false;
|
||
|
||
function syncFasilitasChecklist() {
|
||
if (!fasilitasHiddenInput || !fasilitasCheckboxes.length) return;
|
||
const selectedFacilities = Array.from(fasilitasCheckboxes)
|
||
.filter(checkbox => checkbox.checked)
|
||
.map(checkbox => checkbox.value.trim())
|
||
.filter(Boolean);
|
||
fasilitasHiddenInput.value = selectedFacilities.join(', ');
|
||
}
|
||
|
||
if (fasilitasCheckboxes.length) {
|
||
fasilitasCheckboxes.forEach(function(checkbox) {
|
||
checkbox.addEventListener('change', function() {
|
||
fasilitasTouched = true;
|
||
syncFasilitasChecklist();
|
||
});
|
||
});
|
||
}
|
||
|
||
if (fasilitasHiddenInput && !fasilitasHiddenInput.value.trim()) {
|
||
syncFasilitasChecklist();
|
||
}
|
||
|
||
// ========== FOTO PREVIEW ==========
|
||
const fotoInput = document.getElementById('foto');
|
||
const previewFoto = document.getElementById('preview-foto');
|
||
const previewContainer = document.getElementById('preview-foto-container');
|
||
|
||
fotoInput.addEventListener('change', function(e) {
|
||
const file = e.target.files[0];
|
||
const currentFoto = document.getElementById('current-foto');
|
||
|
||
if (file) {
|
||
if (file.size > 2097152) {
|
||
alert('❌ Ukuran file maksimal 2MB!');
|
||
this.value = '';
|
||
previewContainer.style.display = 'none';
|
||
if (currentFoto) currentFoto.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
|
||
if (!allowedTypes.includes(file.type)) {
|
||
alert('❌ Hanya file JPG, PNG, dan JPEG yang diperbolehkan!');
|
||
this.value = '';
|
||
previewContainer.style.display = 'none';
|
||
if (currentFoto) currentFoto.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
if (currentFoto) currentFoto.style.display = 'none';
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
previewFoto.src = e.target.result;
|
||
previewContainer.style.display = 'block';
|
||
}
|
||
reader.readAsDataURL(file);
|
||
} else {
|
||
previewContainer.style.display = 'none';
|
||
if (currentFoto) currentFoto.style.display = 'block';
|
||
}
|
||
});
|
||
|
||
// ========== FORM VALIDATION ==========
|
||
const form = document.getElementById('kontrakanForm');
|
||
|
||
form.addEventListener('submit', function(e) {
|
||
if (fasilitasHiddenInput && (fasilitasTouched || !fasilitasHiddenInput.value.trim())) {
|
||
syncFasilitasChecklist();
|
||
}
|
||
|
||
const lat = parseFloat(latInput.value);
|
||
const lng = parseFloat(lngInput.value);
|
||
|
||
// Validasi koordinat
|
||
const validation = validateCoordinates(lat, lng);
|
||
if (!validation.valid) {
|
||
e.preventDefault();
|
||
alert(validation.message);
|
||
return;
|
||
}
|
||
|
||
// Validasi WhatsApp
|
||
const waValue = whatsappInput.value.trim();
|
||
if (waValue) {
|
||
if (waValue.length < 10) {
|
||
e.preventDefault();
|
||
alert('❌ Nomor WhatsApp minimal 10 digit!');
|
||
whatsappInput.focus();
|
||
return;
|
||
}
|
||
if (!waValue.startsWith('08') && !waValue.startsWith('628') && !waValue.startsWith('62')) {
|
||
e.preventDefault();
|
||
alert('❌ Format nomor WhatsApp tidak valid.\n\nGunakan: 08xxx atau 628xxx');
|
||
whatsappInput.focus();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (!form.checkValidity()) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
form.classList.add('was-validated');
|
||
});
|
||
|
||
// ========== LIVE PREVIEW HARGA ==========
|
||
const hargaInput = document.getElementById('harga');
|
||
const hargaPreview = document.getElementById('harga_preview');
|
||
const hargaPreviewText = document.getElementById('harga_preview_text');
|
||
|
||
function terbilangSingkat(angka) {
|
||
if (!angka || angka <= 0) return '';
|
||
const m = Math.floor(angka / 1000000);
|
||
const rb = Math.floor((angka % 1000000) / 1000);
|
||
const s = angka % 1000;
|
||
let parts = [];
|
||
if (m > 0) parts.push(m + ' Juta');
|
||
if (rb > 0) parts.push(rb + ' Ribu');
|
||
if (s > 0) parts.push(s + ' Rupiah');
|
||
return parts.join(' ') || '0';
|
||
}
|
||
|
||
function formatRibu(angka) {
|
||
return parseInt(angka).toLocaleString('id-ID');
|
||
}
|
||
|
||
function updateHargaPreview() {
|
||
const val = parseInt(hargaInput.value);
|
||
if (val > 0) {
|
||
hargaPreviewText.textContent = 'Rp ' + formatRibu(val) + ' — ' + terbilangSingkat(val);
|
||
hargaPreview.style.display = 'block';
|
||
} else {
|
||
hargaPreview.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
if (hargaInput) hargaInput.addEventListener('input', updateHargaPreview);
|
||
// Tampilkan saat load jika sudah ada nilai
|
||
if (hargaInput && hargaInput.value) updateHargaPreview();
|
||
});
|
||
|
||
// ========== MODAL FUNCTIONS ==========
|
||
function openImageModal(src) {
|
||
const modal = document.getElementById('imageModal');
|
||
const modalImg = document.getElementById('modalImage');
|
||
|
||
modal.style.display = 'block';
|
||
modalImg.src = src;
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeImageModal() {
|
||
const modal = document.getElementById('imageModal');
|
||
modal.style.display = 'none';
|
||
document.body.style.overflow = 'auto';
|
||
}
|
||
|
||
document.getElementById('imageModal').addEventListener('click', function(e) {
|
||
if (e.target === this) {
|
||
closeImageModal();
|
||
}
|
||
});
|
||
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
closeImageModal();
|
||
}
|
||
});
|
||
</script>
|
||
<style>
|
||
.form-control:focus,
|
||
.form-select:focus {
|
||
border-color: #86b7fe;
|
||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
|
||
}
|
||
|
||
.input-group-text {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.form-control:focus + .input-group-text,
|
||
.input-group:focus-within .input-group-text {
|
||
border-color: #86b7fe;
|
||
background-color: #e7f1ff;
|
||
}
|
||
|
||
.card {
|
||
transition: box-shadow 0.3s ease;
|
||
}
|
||
|
||
.was-validated .form-control:invalid {
|
||
border-color: #dc3545;
|
||
}
|
||
|
||
.was-validated .form-control:valid {
|
||
border-color: #198754;
|
||
}
|
||
|
||
/* Modal Styles */
|
||
.modal-fullscreen {
|
||
position: fixed;
|
||
z-index: 9999;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.95);
|
||
animation: fadeIn 0.3s;
|
||
}
|
||
|
||
.modal-close {
|
||
position: absolute;
|
||
top: 20px;
|
||
right: 40px;
|
||
color: #fff;
|
||
font-size: 40px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
z-index: 10000;
|
||
}
|
||
|
||
.modal-close:hover {
|
||
color: #bbb;
|
||
}
|
||
|
||
.modal-content-img {
|
||
max-width: 90%;
|
||
max-height: 90%;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
animation: zoomIn 0.3s;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {opacity: 0;}
|
||
to {opacity: 1;}
|
||
}
|
||
|
||
@keyframes zoomIn {
|
||
from {transform: translate(-50%, -50%) scale(0.5);}
|
||
to {transform: translate(-50%, -50%) scale(1);}
|
||
}
|
||
|
||
/* Leaflet Map Custom Styles */
|
||
.leaflet-container {
|
||
font-family: inherit;
|
||
}
|
||
|
||
.leaflet-popup-content-wrapper {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* WhatsApp input styling */
|
||
#no_whatsapp:focus {
|
||
border-color: #25D366 !important;
|
||
box-shadow: 0 0 0 0.25rem rgba(37, 211, 102, 0.15) !important;
|
||
}
|
||
|
||
#no_whatsapp.is-valid {
|
||
border-color: #25D366;
|
||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2325D366' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
|
||
}
|
||
|
||
/* Loading animation */
|
||
.spinner-border {
|
||
width: 1rem;
|
||
height: 1rem;
|
||
border-width: 0.15em;
|
||
}
|
||
|
||
/* Custom marker kampus */
|
||
.custom-marker {
|
||
background: none;
|
||
border: none;
|
||
}
|
||
|
||
/* Input koordinat style */
|
||
#latitude:focus,
|
||
#longitude:focus {
|
||
border-color: #ffc107 !important;
|
||
box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.15) !important;
|
||
}
|
||
|
||
/* Highlight saat input manual aktif */
|
||
#latitude:not([readonly]):hover,
|
||
#longitude:not([readonly]):hover {
|
||
background-color: #fffbf0;
|
||
cursor: text;
|
||
}
|
||
|
||
/* Badge animation */
|
||
.badge {
|
||
animation: pulse 2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.7; }
|
||
}
|
||
|
||
/* Smooth transitions */
|
||
.form-control,
|
||
.btn {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
/* Alert styling */
|
||
.alert-info {
|
||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||
border-left: 4px solid #2196f3;
|
||
}
|
||
|
||
/* Map container styling */
|
||
#map {
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
transition: box-shadow 0.3s ease;
|
||
}
|
||
|
||
#map:hover {
|
||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
/* Button hover effects */
|
||
#geocodeBtn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3);
|
||
}
|
||
|
||
#detectLocation:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(13, 110, 253, 0.3);
|
||
}
|
||
|
||
/* Responsive adjustments */
|
||
@media (max-width: 768px) {
|
||
#map {
|
||
height: 300px !important;
|
||
}
|
||
|
||
.alert ul {
|
||
padding-left: 1.2rem;
|
||
}
|
||
|
||
.badge {
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
|
||
/* Input number remove spinner arrows (optional) */
|
||
input[type="number"]::-webkit-inner-spin-button,
|
||
input[type="number"]::-webkit-outer-spin-button {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
input[type="number"]:hover::-webkit-inner-spin-button,
|
||
input[type="number"]:hover::-webkit-outer-spin-button {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Success state for coordinates */
|
||
#latitude.is-valid,
|
||
#longitude.is-valid {
|
||
border-color: #198754;
|
||
background-color: #f0fff4;
|
||
}
|
||
|
||
/* Warning state for manual input */
|
||
#latitude:not([readonly]),
|
||
#longitude:not([readonly]) {
|
||
border-left: 3px solid #ffc107;
|
||
}
|
||
|
||
/* Tooltip style */
|
||
small.text-muted {
|
||
font-size: 0.8rem;
|
||
display: block;
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
/* Button loading state */
|
||
.btn:disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* Card info styling */
|
||
.card.bg-light {
|
||
border-left: 4px solid #0d6efd;
|
||
}
|
||
|
||
/* Input group icon colors */
|
||
.input-group-text i.bi-building {
|
||
color: #0d6efd;
|
||
}
|
||
|
||
.input-group-text i.bi-geo-alt {
|
||
color: #dc3545;
|
||
}
|
||
|
||
.input-group-text i.bi-geo {
|
||
color: #dc3545;
|
||
}
|
||
|
||
.input-group-text i.bi-telephone {
|
||
color: #25D366;
|
||
}
|
||
|
||
.input-group-text i.bi-currency-dollar {
|
||
color: #198754;
|
||
}
|
||
|
||
.input-group-text i.bi-rulers {
|
||
color: #ffc107;
|
||
}
|
||
|
||
.input-group-text i.bi-pin-map {
|
||
color: #0dcaf0;
|
||
}
|
||
|
||
.input-group-text i.bi-star {
|
||
color: #ffc107;
|
||
}
|
||
|
||
.input-group-text i.bi-image {
|
||
color: #0d6efd;
|
||
}
|
||
|
||
/* Smooth scroll behavior */
|
||
html {
|
||
scroll-behavior: smooth;
|
||
}
|
||
|
||
/* Focus visible for accessibility */
|
||
*:focus-visible {
|
||
outline: 2px solid #0d6efd;
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
/* Print styles */
|
||
@media print {
|
||
.btn,
|
||
.breadcrumb,
|
||
nav,
|
||
#map {
|
||
display: none !important;
|
||
}
|
||
|
||
.card {
|
||
box-shadow: none !important;
|
||
border: 1px solid #dee2e6 !important;
|
||
}
|
||
}
|
||
|
||
/* Animation for loading */
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.spinner-border {
|
||
animation: spin 0.75s linear infinite;
|
||
}
|
||
|
||
/* Hover effect for current foto */
|
||
#current-foto {
|
||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||
}
|
||
|
||
#current-foto:hover {
|
||
transform: scale(1.05);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* Preview foto animation */
|
||
#preview-foto-container {
|
||
animation: fadeIn 0.3s ease-in;
|
||
}
|
||
|
||
/* Breadcrumb styling */
|
||
.breadcrumb-item + .breadcrumb-item::before {
|
||
color: #6c757d;
|
||
}
|
||
|
||
.breadcrumb-item.active {
|
||
color: #0d6efd;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Form section headers */
|
||
.border-bottom {
|
||
border-bottom: 2px solid #e9ecef !important;
|
||
}
|
||
|
||
h5.fw-bold {
|
||
color: #2c3e50;
|
||
}
|
||
|
||
/* Action buttons hover */
|
||
.btn-light:hover {
|
||
background-color: #e9ecef;
|
||
border-color: #dee2e6;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: #0b5ed7;
|
||
border-color: #0a58ca;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 8px rgba(13, 110, 253, 0.3);
|
||
}
|
||
|
||
/* Alert animations */
|
||
.alert {
|
||
animation: slideInDown 0.4s ease-out;
|
||
}
|
||
|
||
@keyframes slideInDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
/* Checkbox styling for "Hapus foto" */
|
||
.form-check-input:checked[type="checkbox"] {
|
||
background-color: #dc3545;
|
||
border-color: #dc3545;
|
||
}
|
||
|
||
.form-check-input:focus {
|
||
border-color: #dc3545;
|
||
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
|
||
}
|
||
|
||
/* Timeline styling for history */
|
||
.bi-clock-history {
|
||
color: #0d6efd;
|
||
}
|
||
|
||
/* Improved map marker */
|
||
.leaflet-marker-icon {
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.leaflet-marker-icon:hover {
|
||
transform: scale(1.1);
|
||
}
|
||
</style>
|
||
|
||
@endsection
|