This commit is contained in:
nylaaptr 2026-04-27 23:59:29 +07:00
parent fe3b516c80
commit 9bb19275ab
43 changed files with 4352 additions and 1485 deletions

65
app.py Normal file
View File

@ -0,0 +1,65 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
import numpy as np
app = Flask(__name__)
CORS(app) # Agar bisa diakses dari HTML berbeda domain/port
# 1. DATA LATIH (Training Data)
# Kita ajari bot dengan contoh kalimat dan labelnya
data_latih = [
("halo", "salam"),
("selamat pagi", "salam"),
("berapa harga rumah", "harga"),
("biaya cicilan mahal gak", "harga"),
("saya mau kredit rumah", "kpr"),
("proses KPR gimana", "kpr"),
("lokasi kantor dimana", "lokasi"),
("alamat properti di mana", "lokasi")
]
texts, labels = zip(*data_latih)
# 2. PROSES TRAINING (Mesin Belajar)
# Mengubah teks jadi angka (Vector)
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(texts)
y = np.array(labels)
# Melatih model Naive Bayes
model = MultinomialNB()
model.fit(X, y)
# 3. API ENDPOINT (Tempat Browser Menghubungi)
@app.route('/chat', methods=['POST'])
def chat():
user_input = request.json.get('message')
# Ubah input user jadi vektor sesuai pola training
user_vector = vectorizer.transform([user_input])
# Prediksi intent
prediksi = model.predict(user_vector)[0]
confidence = max(model.predict_proba(user_vector)[0])
# Logika Respon Berdasarkan Hasil Prediksi ML
respon = ""
if confidence < 0.5: # Jika model kurang yakin
respon = "Maaf, saya kurang mengerti. Bisa diulang dengan bahasa lain?"
elif prediksi == 'salam':
respon = "Halo! Selamat datang di PropertiHarmoni. Ada yang bisa dibantu? 😊"
elif prediksi == 'harga':
respon = "Harga kami bervariasi mulai 500jt hingga 3M. Mau cari di range berapa?"
elif prediksi == 'kpr':
respon = "Untuk KPR, kami kerjasama dengan BCA, Mandiri, dan BNI. Mau simulasi cicilan?"
elif prediksi == 'lokasi':
respon = "Kantor kami ada di Jl. Raya Malang No. 10. Mau kirim Google Maps-nya?"
else:
respon = "Mohon maaf, tim kami akan segera membalas pertanyaan spesifik Anda."
return jsonify({'response': respon, 'intent': prediksi})
if __name__ == '__main__':
app.run(debug=True)

View File

@ -16,6 +16,8 @@ public function index()
}
// Proses login
// AuthController.php
public function login(Request $request)
{
$request->validate([
@ -27,29 +29,30 @@ public function login(Request $request)
'password.required' => 'Password wajib diisi',
]);
// Cari user berdasarkan email_user (kolom kustom)
$user = User::where('email_user', $request->email)->first();
// Verifikasi password
// AuthController.php - method login()
if ($user && Hash::check($request->password, $user->password_user)) {
// Login user
Auth::login($user);
$request->session()->regenerate();
// Redirect berdasarkan role
if ($user->role_user === 'admin') {
// ✅ Lebih baik pakai route name daripada hardcoded URL
return redirect()->route('admin.welcome');
// Jika admin di project terpisah, gunakan:
// return redirect('http://127.0.0.1:8001/admin');
// 🔥 AMBIL PARAMETER 'redirect' (pakai input() lebih reliable)
$redirectUrl = $request->input('redirect');
if ($redirectUrl && str_starts_with($redirectUrl, '/')) {
return redirect($redirectUrl);
}
// Redirect user biasa
return redirect()->intended(route('halaman-katalog'));
// Fallback jika redirect tidak valid / tidak ada
if ($user->role_user === 'admin') {
return redirect()->route('admin.welcome');
}
return redirect()->route('halaman-katalog');
}
// Jika gagal
return back()->withErrors([
'email' => 'Email atau password yang Anda masukkan salah.',
])->withInput($request->except('password'));
@ -102,4 +105,41 @@ public function logout(Request $request)
return redirect()->route('welcome'); // ← pengguna ke welcome
}
// REGISTER
public function register(Request $request)
{
// 🔥 1. VALIDASI INPUT
$validated = $request->validate([
'nama_lengkap' => 'required|string|max:255',
'email_user' => 'required|email|unique:users,email_user',
'no_hp' => 'required|string|max:20',
'pekerjaan' => 'nullable|string|max:100',
'password_user' => 'required|min:8|confirmed',
], [
// Custom error messages (opsional tapi disarankan)
'nama_lengkap.required' => 'Nama lengkap wajib diisi',
'email_user.required' => 'Email wajib diisi',
'email_user.email' => 'Format email tidak valid',
'email_user.unique' => 'Email sudah terdaftar',
'no_hp.required' => 'Nomor HP wajib diisi',
'password_user.required' => 'Password wajib diisi',
'password_user.min' => 'Password minimal 8 karakter',
'password_user.confirmed' => 'Konfirmasi password tidak cocok',
]);
// 🔥 2. SIMPAN USER KE DATABASE
$user = User::create([
'nama_user' => $validated['nama_lengkap'],
'email_user' => $validated['email_user'],
'no_hp' => $validated['no_hp'],
'pekerjaan' => $validated['pekerjaan'] ?? null,
'password_user' => Hash::make($validated['password_user']),
'role_user' => 'user', // default role
]);
// 🔥 3. REDIRECT KE WELCOME DENGAN PESAN SUKSES
return redirect()->route('welcome')
->with('success', 'Akun berhasil dibuat! Silakan login.');
}
}

View File

@ -4,110 +4,68 @@
use Illuminate\Http\Request;
// app/Http/Controllers/ChatbotController.php
class ChatbotController extends Controller
class ChatBotController extends Controller
{
public function index()
public function sendMessage(Request $request)
{
return view('halaman-chatbot');
$message = strtolower(trim($request->input('message')));
// Database pengetahuan (bisa nanti dipindah ke database MySQL jika mau)
$responses = [
[
'keywords' => ['halo', 'hai', 'hello', 'pagi', 'siang', 'malam'],
'reply' => "Halo kak! 👋 Selamat datang di Carani Estate. Ada yang bisa dibantu hari ini?",
'options' => ["Cari Rumah", "Info KPR", "Lokasi Kantor"]
],
[
'keywords' => ['harga', 'biaya', 'mahal', 'murah', 'budget', 'dp'],
'reply' => "Untuk harga, kami punya banyak pilihan mulai dari Rp 500 jutaan sampai miliaran, Kak. 😊 Kamu lagi cari properti di daerah mana nih?",
'options' => ["Daerah Malang", "Daerah Batu", "Budget < 1 Milyar"]
],
[
'keywords' => ['kpr', 'kredit', 'bank', 'cicil', 'bunga'],
'reply' => "Siap! Untuk KPR, bunganya mulai dari 4% fixed tahun pertama lho. 🏦 Mau aku hitungin simulasi cicilannya sekalian?",
'options' => ["Hitung Simulasi", "Syarat Dokumen"]
],
[
'keywords' => ['lokasi', 'alamat', 'dimana', 'tempat', 'kantornya'],
'reply' => "Kantor pemasaran kami ada di pusat kota, strategis banget! 📍 Mau dikirim lokasi Google Maps-nya?",
'options' => ["Kirim Peta", "Jadwalkan Kunjungan"]
],
[
'keywords' => ['tipe', 'jenis', 'rumah', 'apartemen', 'spesifikasi', 'luas'],
'reply' => "Kami punya tipe Minimalis Modern dan Industrial Kekinian. Keduanya udah include kanopi & pagar loh! 🏠 Kamu lebih suka yang mana?",
'options' => ["Lihat Foto Tipe A", "Lihat Foto Tipe B"]
]
];
$bestMatch = null;
$highestScore = 0;
// Algoritma pencocokan sederhana (Naive Logic)
foreach ($responses as $data) {
$score = 0;
foreach ($data['keywords'] as $keyword) {
if (str_contains($message, $keyword)) {
$score++;
}
}
if ($score > $highestScore) {
$highestScore = $score;
$bestMatch = $data;
}
}
if ($bestMatch && $highestScore > 0) {
return response()->json([
'reply' => $bestMatch['reply'],
'options' => $bestMatch['options']
]);
} else {
return response()->json([
'reply' => "Waduh, maaf Kak, saya belum paham maksudnya 😅. Bisa coba tanya tentang 'Harga', 'KPR', atau 'Lokasi'?",
'options' => ["Tanya Harga", "Tanya KPR"]
]);
}
}
public function reply(Request $request)
{
$pesan = strtolower($request->input('pesan'));
$balasan = $this->prosesPersan($pesan);
// Simpan ke tabel chat_messages
ChatMessage::create([
'session_id' => session()->getId(),
'pengirim' => 'user',
'pesan' => $request->input('pesan'),
]);
ChatMessage::create([
'session_id' => session()->getId(),
'pengirim' => 'bot',
'pesan' => $balasan,
]);
return response()->json(['balasan' => $balasan]);
}
private function prosesPersan($pesan)
{
// Rekomendasi berdasarkan budget
if (str_contains($pesan, 'subsidi') || str_contains($pesan, 'murah')) {
return $this->rekomendasiProperti('subsidi');
}
if (str_contains($pesan, 'type 36') || str_contains($pesan, 'tipe 36')) {
return $this->rekomendasiProperti('36');
}
if (str_contains($pesan, 'type 45') || str_contains($pesan, 'tipe 45')) {
return $this->rekomendasiProperti('45');
}
if (str_contains($pesan, 'ruko')) {
return $this->rekomendasiProperti('ruko');
}
// Pertanyaan harga
if (str_contains($pesan, 'harga') || str_contains($pesan, 'berapa')) {
return "Harga properti kami mulai dari:\n
- Type 30 (Subsidi): Rp 162.000.000\n
- Type 36: Rp 200.000.000\n
- Type 45: Rp 300.000.000\n
- Type 60: Rp 435.000.000\n
- Ruko: Rp 600.000.000\n
Mau tanya type yang mana?";
}
// Pertanyaan KPR
if (str_contains($pesan, 'kpr') || str_contains($pesan, 'kredit')) {
return "Kami melayani KPR dengan tenor 10-20 tahun.
Syaratnya KTP, KK, slip gaji, dan NPWP.
Mau tahu cicilan untuk type berapa?";
}
// Pertanyaan cicilan
if (str_contains($pesan, 'cicil') || str_contains($pesan, 'angsuran')) {
return "Estimasi angsuran KPR per bulan:\n
- Type 36 (10th): Rp 1.875.900\n
- Type 45 (10th): Rp 2.813.800\n
- Type 60 (10th): Rp 4.080.000\n
Mau simulasi tenor yang lain?";
}
// Salam
if (str_contains($pesan, 'halo') || str_contains($pesan, 'hai') ||
str_contains($pesan, 'hello')) {
return "Halo! Selamat datang di Kelapa Gading Regency 🏠
Saya siap membantu kamu menemukan rumah impian.
Boleh saya tahu budget dan kebutuhan kamu?";
}
// Default
return "Maaf, saya belum mengerti pertanyaanmu.
Kamu bisa tanya tentang:\n
- Harga properti\n
- Type rumah (36, 45, 60, Ruko)\n
- KPR & cicilan\n
- Cara booking";
}
private function rekomendasiProperti($type)
{
$properti = Properti::where('type', 'like', "%$type%")
->where('status', 'tersedia')
->get();
if ($properti->isEmpty()) {
return "Maaf, properti type $type sedang tidak tersedia.
Mau lihat type lain?";
}
$hasil = "Rekomendasi properti type $type yang tersedia:\n";
foreach ($properti as $p) {
$hasil .= "- Blok {$p->blok}, Harga: Rp " .
number_format($p->harga, 0, ',', '.') . "\n";
}
return $hasil;
}
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class Notifikasi extends Controller
{
public function index()
{
$notifikasi = \App\Models\Notifikasi::where('id_user', Auth::id())
->orderBy('created_at', 'desc')
->paginate(20); // atau ->get() kalau tidak pakai pagination
return view('halaman-notifikasi', compact('notifikasi'));
}
}

View File

@ -0,0 +1,123 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Transaksi;
use App\Models\Properti;
use App\Models\Notifikasi;
use App\Models\Dokumen;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class PemesananController extends Controller
{
public function proses(Request $request)
{
$request->validate([
'id_properti' => 'required|exists:properti,id_properti',
'jenis_transaksi'=> 'required|in:lunas,kredit,kpr,cash,installment,other',
]);
$properti = Properti::find($request->id_properti);
// Simpan Transaksi
$transaksi = Transaksi::create([
'id_user' => Auth::user()->id_user,
'id_properti' => $request->id_properti,
'tanggal_transaksi'=> now()->toDateString(),
'total_harga' => $properti->harga_properti,
'jenis_transaksi' => $request->jenis_transaksi,
'status_transaksi' => 'menunggu_verifikasi',
]);
// Daftar dokumen yang perlu disimpan
// key = nama input di form, value = enum jenis_dokumen di DB
$dokumenList = [
'ktp' => 'ktp',
'kk' => 'kk',
'surat_nikah' => 'surat_nikah',
'npwp' => 'npwp',
'slip_gaji' => 'slip_gaji',
'rekening_koran' => 'rekening_koran',
'foto_3x4' => 'foto_3x4',
'surat_kerja' => 'surat_kerja',
'selfie' => 'selfie',
'foto_tempat_kerja' => 'foto_tempat_kerja',
// Wiraswasta
'ktpw' => 'ktp',
'kkW' => 'kk',
'surat_nikahW' => 'surat_nikah',
'npwp_W' => 'npwp',
'spt' => 'spt',
'surat_ket_usaha' => 'surat_ket_usaha',
'surat_penghasilan_usaha'=> 'surat_penghasilan_usaha',
'pembukuan_usaha' => 'pembukuan_usaha',
'rekening_koranW' => 'rekening_koran',
'selfieW' => 'selfie',
'foto_tempat_usaha' => 'foto_tempat_usaha',
];
foreach ($dokumenList as $inputName => $jenisDokumen) {
if ($request->hasFile($inputName)) {
$files = is_array($request->file($inputName))
? $request->file($inputName)
: [$request->file($inputName)];
foreach ($files as $file) {
$namaFile = time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('dokumen/' . Auth::user()->id_user, $namaFile, 'public');
Dokumen::create([
'id_user' => Auth::user()->id_user,
'jenis_dokumen' => $jenisDokumen,
'nama_file' => $namaFile,
'path_file' => $path,
'tipe_file' => $file->getClientMimeType(),
'ukuran_file' => $file->getSize(),
'status_verifikasi'=> 'pending',
'catatan_verifikasi'=> '',
]);
}
}
}
// Notifikasi ke User
Notifikasi::create([
'id_user' => Auth::user()->id_user,
'judul' => 'Pemesanan Berhasil Dikirim',
'pesan' => 'Pemesanan kamu untuk ' . $properti->nama_properti . ' sedang menunggu verifikasi admin.',
'tipe' => 'transaksi',
'status_baca' => 0,
'status_kirim' => 'terkirim',
'channel' => 'in_app',
'referensi_id' => $transaksi->id_transaksi,
'referensi_tipe' => 'transaksi',
]);
// Notifikasi ke semua Admin
$admins = User::where('role_user', 'admin')->get();
foreach ($admins as $admin) {
Notifikasi::create([
'id_user' => $admin->id_user,
'judul' => 'Pemesanan Baru Masuk',
'pesan' => Auth::user()->nama_user . ' memesan ' . $properti->nama_properti . '.',
'tipe' => 'transaksi',
'status_baca' => 0,
'status_kirim' => 'terkirim',
'channel' => 'in_app',
'referensi_id' => $transaksi->id_transaksi,
'referensi_tipe' => 'transaksi',
]);
}
return redirect()->route('pemesanan.terima-kasih', ['id' => $transaksi->id_transaksi]);
}
$request->validate([
'jenis_transaksi' => 'required',
'bukti_booking' => $request->jenis_transaksi == 'lunas'
? 'required|file|mimes:jpg,jpeg,png,pdf|max:5120'
: 'nullable'
]);
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class VisitorController extends Controller
{
//
}

View File

@ -16,7 +16,7 @@ class Transaksi extends Model
'id_properti',
'tanggal_transaksi',
'total_harga',
'jenis_pembayaran',
'jenis_transaksi',
'status_transaksi',
];

View File

@ -49,4 +49,9 @@ public function getAuthPasswordName()
{
return 'password_user';
}
public function dokumen()
{
return $this->hasMany(\App\Models\Dokumen::class, 'id_user', 'id_user');
}
}

View File

@ -7,6 +7,7 @@
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)

View File

@ -8,7 +8,9 @@
"require": {
"php": "^8.2",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.10.1",
"openai-php/client": "^0.19.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",

291
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "53b5d56b3b7e3cbac1713e68c8850f6c",
"content-hash": "8f430fb6921d2e12366c051cec6b656d",
"packages": [
{
"name": "brick/math",
@ -1334,6 +1334,69 @@
},
"time": "2026-03-23T14:35:33+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.3.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
"reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^11.0|^12.0|^13.0",
"illuminate/contracts": "^11.0|^12.0|^13.0",
"illuminate/database": "^11.0|^12.0|^13.0",
"illuminate/support": "^11.0|^12.0|^13.0",
"php": "^8.2",
"symfony/console": "^7.0|^8.0"
},
"require-dev": {
"mockery/mockery": "^1.6",
"orchestra/testbench": "^9.15|^10.8|^11.0",
"phpstan/phpstan": "^1.10"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Sanctum\\SanctumServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Sanctum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
"keywords": [
"auth",
"laravel",
"sanctum"
],
"support": {
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2026-02-07T17:19:31+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.10",
@ -2531,6 +2594,232 @@
],
"time": "2026-02-16T23:10:27+00:00"
},
{
"name": "openai-php/client",
"version": "v0.19.1",
"source": {
"type": "git",
"url": "https://github.com/openai-php/client.git",
"reference": "f2b1ce48b1c011ebc2abb255db5abf82ef4a6a44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/openai-php/client/zipball/f2b1ce48b1c011ebc2abb255db5abf82ef4a6a44",
"reference": "f2b1ce48b1c011ebc2abb255db5abf82ef4a6a44",
"shasum": ""
},
"require": {
"php": "^8.2.0",
"php-http/discovery": "^1.20.0",
"php-http/multipart-stream-builder": "^1.4.2",
"psr/http-client": "^1.0.3",
"psr/http-client-implementation": "^1.0.1",
"psr/http-factory-implementation": "*",
"psr/http-message": "^1.1.0|^2.0.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.10.0",
"guzzlehttp/psr7": "^2.8.0",
"laravel/pint": "^1.25.1",
"mockery/mockery": "^1.6.12",
"nunomaduro/collision": "^8.8.3",
"pestphp/pest": "^3.8.2|^4.1.4",
"pestphp/pest-plugin-arch": "^3.1.1|^4.0.0",
"pestphp/pest-plugin-type-coverage": "^3.5.1|^4.0.0",
"phpstan/phpstan": "^1.12.32",
"symfony/var-dumper": "^7.3.5"
},
"type": "library",
"autoload": {
"files": [
"src/OpenAI.php"
],
"psr-4": {
"OpenAI\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
},
{
"name": "Sandro Gehri"
}
],
"description": "OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API",
"keywords": [
"GPT-3",
"api",
"client",
"codex",
"dall-e",
"language",
"natural",
"openai",
"php",
"processing",
"sdk"
],
"support": {
"issues": "https://github.com/openai-php/client/issues",
"source": "https://github.com/openai-php/client/tree/v0.19.1"
},
"funding": [
{
"url": "https://www.paypal.com/paypalme/enunomaduro",
"type": "custom"
},
{
"url": "https://github.com/gehrisandro",
"type": "github"
},
{
"url": "https://github.com/nunomaduro",
"type": "github"
}
],
"time": "2026-03-17T20:53:51+00:00"
},
{
"name": "php-http/discovery",
"version": "1.20.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.20.0"
},
"time": "2024-10-02T11:20:13+00:00"
},
{
"name": "php-http/multipart-stream-builder",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/php-http/multipart-stream-builder.git",
"reference": "10086e6de6f53489cca5ecc45b6f468604d3460e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/10086e6de6f53489cca5ecc45b6f468604d3460e",
"reference": "10086e6de6f53489cca5ecc45b6f468604d3460e",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"php-http/discovery": "^1.15",
"psr/http-factory-implementation": "^1.0"
},
"require-dev": {
"nyholm/psr7": "^1.0",
"php-http/message": "^1.5",
"php-http/message-factory": "^1.0.2",
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Message\\MultipartStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
}
],
"description": "A builder class that help you create a multipart stream",
"homepage": "http://php-http.org",
"keywords": [
"factory",
"http",
"message",
"multipart stream",
"stream"
],
"support": {
"issues": "https://github.com/php-http/multipart-stream-builder/issues",
"source": "https://github.com/php-http/multipart-stream-builder/tree/1.4.2"
},
"time": "2024-09-04T13:22:54+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.5",

84
config/sanctum.php Normal file
View File

@ -0,0 +1,84 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort(),
// Sanctum::currentRequestHost(),
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

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('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Rumah - PropertiHarmoni</title>
<title>Data Rumah - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data User - PropertiHarmoni</title>
<title>Data User - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Data Rumah - PropertiHarmoni</title>
<title>Edit Data Rumah - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Data User - PropertiHarmoni</title>
<title>Edit Data User - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatBot Admin - PropertiHarmoni</title>
<title>ChatBot Admin - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -841,7 +841,7 @@
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Properti Harmoni</div>
<div class="company-name">PT. Carani Bhanu Balakosa</div>
</div>
<div class="nav-menu" id="navMenu">

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verifikasi Dokumen - PropertiHarmoni</title>
<title>Verifikasi Dokumen - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -305,6 +305,7 @@
border: none;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
}
.filter-btn.active {
@ -783,13 +784,13 @@ class="nav-item {{ request()->routeIs('admin.halaman_chatbot') ? 'active' : '' }
{{-- Filter --}}
<div class="filter-controls">
<a href="{{ route('halaman_verifikasi') }}"
<a href="{{ route('admin.halaman_verifikasi') }}"
class="filter-btn {{ !request('status') ? 'active' : '' }}">Semua</a>
<a href="{{ route('halaman_verifikasi', ['status' => 'pending']) }}"
<a href="{{ route('admin.halaman_verifikasi', ['status' => 'pending']) }}"
class="filter-btn {{ request('status') == 'pending' ? 'active' : '' }}">Menunggu</a>
<a href="{{ route('halaman_verifikasi', ['status' => 'diterima']) }}"
<a href="{{ route('admin.halaman_verifikasi', ['status' => 'diterima']) }}"
class="filter-btn {{ request('status') == 'diterima' ? 'active' : '' }}">Disetujui</a>
<a href="{{ route('halaman_verifikasi', ['status' => 'ditolak']) }}"
<a href="{{ route('admin.halaman_verifikasi', ['status' => 'ditolak']) }}"
class="filter-btn {{ request('status') == 'ditolak' ? 'active' : '' }}">Ditolak</a>
</div>
</div>
@ -815,77 +816,60 @@ class="filter-btn {{ request('status') == 'ditolak' ? 'active' : '' }}">Ditolak<
</tr>
</thead>
<tbody>
@forelse($dokumen as $d)
@php
if ($d->status_verifikasi == 'pending') {
$statusClass = 'status-pending';
$statusLabel = 'Pending';
} elseif ($d->status_verifikasi == 'diterima') {
$statusClass = 'status-approved';
$statusLabel = 'Disetujui';
} elseif ($d->status_verifikasi == 'ditolak') {
$statusClass = 'status-rejected';
$statusLabel = 'Ditolak';
} else {
$statusClass = 'status-pending';
$statusLabel = 'Revisi';
}
@endphp
@forelse($user as $u)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $d->user->nama_user ?? '-' }}</td>
<td>{{ $d->user->no_hp ?? '-' }}</td>
<td>{{ $d->user->email_user ?? '-' }}</td>
<td>{{ ucfirst(str_replace('_', ' ', $d->jenis_dokumen)) }}</td>
<td>{{ $u->nama_user ?? '-' }}</td>
<td>{{ $u->no_hp ?? '-' }}</td>
<td>{{ $u->email_user ?? '-' }}</td>
<td>
<a href="{{ asset('storage/' . $d->path_file) }}"
target="_blank"
style="color:var(--primary-blue); font-weight:600;">
<i class="fas fa-file"></i> Lihat File
</a>
{{ $u->dokumen->count() }} Dokumen
</td>
<td>
-
</td>
<td>
@php
$status = $u->dokumen->pluck('status_verifikasi');
if ($status->contains('pending')) {
$statusLabel = 'Pending';
$statusClass = 'status-pending';
} elseif ($status->every(fn($s) => $s == 'diterima')) {
$statusLabel = 'Disetujui';
$statusClass = 'status-approved';
} elseif ($status->contains('ditolak')) {
$statusLabel = 'Ditolak';
$statusClass = 'status-rejected';
} else {
$statusLabel = 'Revisi';
$statusClass = 'status-pending';
}
@endphp
<span class="status-badge {{ $statusClass }}">
{{ $statusLabel }}
</span>
</td>
<td>
@if($d->status_verifikasi == 'pending')
<div style="display:flex; gap:5px;">
{{-- Approve --}}
<form method="POST" action="{{ route('verifikasi.approve', $d->id_dokumen) }}">
@csrf
<button type="submit"
style="background:#059669; color:white; border:none;
padding:6px 12px; border-radius:8px; cursor:pointer;
font-size:0.85rem;"
onclick="return confirm('Setujui dokumen ini?')">
<i class="fas fa-check"></i> Setujui
</button>
</form>
{{-- Tolak --}}
@php $dokumenId = $d->id_dokumen; @endphp
<button onclick="showTolakModal({{ $id_dokumen }})"
style="background:#dc2626; color:white; border:none;
padding:6px 12px; border-radius:8px; cursor:pointer;
font-size:0.85rem;">
<i class="fas fa-times"></i> Tolak
</button>
</div>
@else
<span style="color:#94a3b8; font-size:0.85rem;">Sudah diproses</span>
@endif
<td>
<a href="{{ route('admin.verifikasi_dokumen', $u->id_user) }}"
style="color:#2563eb; font-size:1.2rem;">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
@empty
<tr>
<td colspan="8" style="text-align:center; padding:30px; color:#64748b;">
Belum ada dokumen
Belum ada data user
</td>
</tr>
@endforelse
</tbody>
</tbody>
</table>
</div>
</div>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tambah Data User - PropertiHarmoni</title>
<title>Tambah Data User - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tambah Data Rumah - PropertiHarmoni</title>
<title>Tambah Data Rumah - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verifikasi Dokumen - PropertiHarmoni</title>
<title>Verifikasi Dokumen - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -914,159 +914,106 @@ class="nav-item {{ request()->routeIs('admin.halaman_chatbot') ? 'active' : '' }
<!-- Verification Container -->
<div class="verification-container">
<div class="verification-header">
<h2 class="verification-title">Verifikasi Dokumen - Nayla Putri Wijaya</h2>
<h2 class="verification-title">
Verifikasi Dokumen - {{ $user->nama_user }}
</h2>
</div>
<div class="user-info-card">
<div class="user-avatar">N</div>
<div class="user-details">
<div class="user-name-large">Nayla Putri Wijaya</div>
<div class="user-id">ID: USER-2025-001</div>
<div class="user-name-large">{{ $user->nama_user }}</div>
<!-- <div class="user-id">ID: {{ $user->id_user }}</div> -->
<div class="user-status pending">Status: Belum Diverifikasi</div>
</div>
</div>
<div class="document-list">
@foreach ($user->dokumen as $dok)
<div class="document-item">
<div class="doc-info">
<div class="doc-icon complete">
<i class="fas fa-id-card"></i>
<div class="doc-icon
{{ $dok->status_verifikasi == 'diterima' ? 'complete' : ($dok->status_verifikasi == 'ditolak' ? 'rejected' : 'pending') }}">
<i class="fas fa-file"></i>
</div>
<div class="doc-name">
{{ ucfirst(str_replace('_', ' ', $dok->jenis_dokumen)) }}
</div>
<div class="doc-status
{{ $dok->status_verifikasi == 'diterima' ? 'complete' : ($dok->status_verifikasi == 'ditolak' ? 'rejected' : 'pending') }}">
@if($dok->status_verifikasi == 'diterima')
Lengkap & Valid
@elseif($dok->status_verifikasi == 'ditolak')
Tidak Valid
@else
Menunggu Verifikasi
@endif
</div>
<div class="doc-name">Kartu Tanda Penduduk (KTP)</div>
<div class="doc-status complete">Lengkap & Valid</div>
</div>
<div class="doc-actions">
<div class="action-btn view-btn" data-doc="ktp">
<i class="fas fa-eye"></i>
</div>
<div class="action-btn reject-btn" data-doc="ktp">
<i class="fas fa-times"></i>
</div>
<div class="action-btn approve-btn" data-doc="ktp">
{{-- 👁 VIEW buka tab baru --}}
<a href="{{ asset('storage/' . $dok->path_file) }}" target="_blank">
<div class="action-btn view-btn">
<i class="fas fa-eye"></i>
</div>
</a>
{{-- TOLAK (hilang kalau sudah diterima) --}}
@if($dok->status_verifikasi != 'diterima')
<form method="POST" action="{{ route('admin.verifikasi.tolak', $dok->id_dokumen) }}">
@csrf
<button class="action-btn reject-btn">
<i class="fas fa-times"></i>
</button>
</form>
@endif
{{-- APPROVE --}}
@if($dok->status_verifikasi != 'diterima')
<form method="POST" action="{{ route('admin.verifikasi.approve', $dok->id_dokumen) }}">
@csrf
<button class="action-btn approve-btn">
<i class="fas fa-check"></i>
</button>
</form>
@else
{{-- kalau sudah disetujui, cuma tampil centang --}}
<div class="action-btn approve-btn">
<i class="fas fa-check"></i>
</div>
@endif
</div>
</div>
<div class="document-item">
<div class="doc-info">
<div class="doc-icon complete">
<i class="fas fa-users"></i>
</div>
<div class="doc-name">Kartu Keluarga (KK)</div>
<div class="doc-status complete">Lengkap & Valid</div>
</div>
<div class="doc-actions">
<div class="action-btn view-btn" data-doc="kk">
<i class="fas fa-eye"></i>
</div>
<div class="action-btn reject-btn" data-doc="kk">
<i class="fas fa-times"></i>
</div>
<div class="action-btn approve-btn" data-doc="kk">
<i class="fas fa-check"></i>
</div>
</div>
@endforeach
</div>
<div class="document-item">
<div class="doc-info">
<div class="doc-icon complete">
<i class="fas fa-file-invoice-dollar"></i>
</div>
<div class="doc-name">Slip Gaji 3 Bulan Terakhir</div>
<div class="doc-status complete">Lengkap & Valid</div>
</div>
<div class="doc-actions">
<div class="action-btn view-btn" data-doc="slip-gaji">
<i class="fas fa-eye"></i>
</div>
<div class="action-btn reject-btn" data-doc="slip-gaji">
<i class="fas fa-times"></i>
</div>
<div class="action-btn approve-btn" data-doc="slip-gaji">
<i class="fas fa-check"></i>
</div>
</div>
</div>
<div class="document-item">
<div class="doc-info">
<div class="doc-icon pending">
<i class="fas fa-file-alt"></i>
</div>
<div class="doc-name">NPWP</div>
<div class="doc-status pending">Menunggu Upload</div>
</div>
<div class="doc-actions">
<div class="action-btn view-btn" data-doc="npwp">
<i class="fas fa-eye"></i>
</div>
<div class="action-btn reject-btn" data-doc="npwp">
<i class="fas fa-times"></i>
</div>
<div class="action-btn approve-btn" data-doc="npwp">
<i class="fas fa-check"></i>
</div>
</div>
</div>
<div class="document-item">
<div class="doc-info">
<div class="doc-icon missing">
<i class="fas fa-file-signature"></i>
</div>
<div class="doc-name">Surat Keterangan Kerja</div>
<div class="doc-status missing">Belum Diupload</div>
</div>
<div class="doc-actions">
<div class="action-btn view-btn" data-doc="sk-kerja">
<i class="fas fa-eye"></i>
</div>
<div class="action-btn reject-btn" data-doc="sk-kerja">
<i class="fas fa-times"></i>
</div>
<div class="action-btn approve-btn" data-doc="sk-kerja">
<i class="fas fa-check"></i>
</div>
</div>
</div>
<div class="document-item">
<div class="doc-info">
<div class="doc-icon missing">
<i class="fas fa-file-invoice"></i>
</div>
<div class="doc-name">Rekening Koran 6 Bulan</div>
<div class="doc-status missing">Belum Diupload</div>
</div>
<div class="doc-actions">
<div class="action-btn view-btn" data-doc="rekening-koran">
<i class="fas fa-eye"></i>
</div>
<div class="action-btn reject-btn" data-doc="rekening-koran">
<i class="fas fa-times"></i>
</div>
<div class="action-btn approve-btn" data-doc="rekening-koran">
<i class="fas fa-check"></i>
</div>
</div>
</div>
</div>
<div class="verification-notes">
<h3 class="notes-title">Catatan Verifikasi</h3>
<textarea class="notes-input" placeholder="Masukkan catatan verifikasi jika diperlukan..."></textarea>
</div>
<div class="footer-actions">
<button class="btn-back">Kembali</button>
<button class="btn-complete">Selesai</button>
</div>
<form method="POST" action="{{ route('admin.verifikasi.selesai', $user->id_user) }}">
@csrf
<div class="verification-notes">
<h3 class="notes-title">Catatan Verifikasi</h3>
<textarea name="catatan" class="notes-input"
placeholder="Masukkan catatan verifikasi jika diperlukan..."></textarea>
</div>
<div class="footer-actions">
<a href="{{ route('admin.halaman_verifikasi') }}">
<button type="button" class="btn-back">Kembali</button>
</a>
<button type="submit" class="btn-complete">Selesai</button>
</div>
</form>
</div>
</div>
<!-- Document Preview Modal -->
<!-- Document Preview Modal
<div class="modal-overlay" id="previewModal">
<div class="preview-modal">
<div class="modal-header">
@ -1084,45 +1031,45 @@ class="nav-item {{ request()->routeIs('admin.halaman_chatbot') ? 'active' : '' }
</div>
</div>
</div>
</div>
</div> -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Document action buttons
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', function() {
const docType = this.getAttribute('data-doc');
document.getElementById('previewModal').classList.add('show');
// document.querySelectorAll('.view-btn').forEach(btn => {
// btn.addEventListener('click', function() {
// const docType = this.getAttribute('data-doc');
// document.getElementById('previewModal').classList.add('show');
// Update modal title based on document type
const modalTitle = document.querySelector('.modal-title');
let docName = '';
switch(docType) {
case 'ktp':
docName = 'Kartu Tanda Penduduk (KTP)';
break;
case 'kk':
docName = 'Kartu Keluarga (KK)';
break;
case 'slip-gaji':
docName = 'Slip Gaji 3 Bulan Terakhir';
break;
case 'npwp':
docName = 'NPWP';
break;
case 'sk-kerja':
docName = 'Surat Keterangan Kerja';
break;
case 'rekening-koran':
docName = 'Rekening Koran 6 Bulan';
break;
default:
docName = 'Dokumen';
}
modalTitle.textContent = `Preview Dokumen: ${docName}`;
});
});
// // Update modal title based on document type
// const modalTitle = document.querySelector('.modal-title');
// let docName = '';
// switch(docType) {
// case 'ktp':
// docName = 'Kartu Tanda Penduduk (KTP)';
// break;
// case 'kk':
// docName = 'Kartu Keluarga (KK)';
// break;
// case 'slip-gaji':
// docName = 'Slip Gaji 3 Bulan Terakhir';
// break;
// case 'npwp':
// docName = 'NPWP';
// break;
// case 'sk-kerja':
// docName = 'Surat Keterangan Kerja';
// break;
// case 'rekening-koran':
// docName = 'Rekening Koran 6 Bulan';
// break;
// default:
// docName = 'Dokumen';
// }
// modalTitle.textContent = `Preview Dokumen: ${docName}`;
// });
// });
// Approve button functionality
document.querySelectorAll('.approve-btn').forEach(btn => {

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Admin - PropertiHarmoni</title>
<title>Dashboard Admin - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -630,31 +630,34 @@ class="nav-item {{ request()->routeIs('admin.halaman_chatbot') ? 'active' : '' }
<!-- Dashboard Stats -->
<div class="dashboard-stats">
<div class="stat-card">
<div class="stat-title">Total Pengguna</div>
<div class="stat-value">2,458</div>
<div class="stat-change positive">
<i class="fas fa-arrow-up"></i>
+12% dari bulan lalu
</div>
</div>
<div class="stat-card">
<div class="stat-title">Properti Aktif</div>
<div class="stat-value">1,892</div>
<div class="stat-change positive">
<i class="fas fa-arrow-up"></i>
+8% dari bulan lalu
</div>
</div>
<div class="stat-card">
<div class="stat-title">Verifikasi Pending</div>
<div class="stat-value">23</div>
<div class="stat-title">Visitor</div>
<div class="stat-value">5,200</div>
<div class="stat-change">
<i class="fas fa-clock"></i>
Perlu segera diproses
Pengunjung website
</div>
</div>
<div class="stat-card">
<div class="stat-title">Leads</div>
<div class="stat-value">320</div>
<div class="stat-change positive">
Isi email / tertarik
</div>
</div>
<!-- <div class="stat-card">
<div class="stat-title">User Terdaftar</div>
<div class="stat-value">180</div>
<div class="stat-change positive">
Sudah login akun
</div>
</div> -->
<div class="stat-card">
<div class="stat-title">Transaksi</div>
<div class="stat-value">75</div>
<div class="stat-change positive">
Pembelian berhasil
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Detail Properti - PropertiHarmoni</title>
<title>Detail Properti - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -1290,7 +1290,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<div class="description-section" style="padding:0; box-shadow:none; background:transparent;">
<div class="description-content" style="font-size:0.95rem;">
<p style="margin:10px 0;">
{{ $properti->nama_properti }} merupakan properti {{ $properti->jenis_properti }}
{{ $properti->nama_properti }} merupakan properti {{ $properti->jenis_properti }} php
kategori {{ $properti->kategori_properti }}.
</p>
<ul style="padding-left:20px; margin:10px 0;">
@ -1318,32 +1318,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<!-- KOLOM KANAN: Property Info (SIDEBAR) -->
<div class="property-info">
<!-- Contact Section -->
<div class="contact-section">
<h3 class="contact-title">Hubungi Agen Properti</h3>
<div class="agent-info">
<div class="agent-avatar">A</div>
<div class="agent-details">
<div class="agent-name">Admin Carani Estate</div>
<div class="agent-role">Konsultan Properti</div>
<div class="agent-rating">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<span>5.0</span>
</div>
<div class="agent-contact">
<a href="tel:+6281234567890" class="contact-btn btn-call">
<i class="fas fa-phone"></i> Hubungi
</a>
<a href="https://wa.me/6281234567890" class="contact-btn btn-whatsapp">
<i class="fab fa-whatsapp"></i> WhatsApp
</a>
</div>
</div>
</div>
</div>
<h1 class="property-title">{{ $properti->nama_properti }}</h1>
@ -1367,7 +1342,12 @@ class="contact-btn"
<i class="fas fa-shopping-cart"></i> Beli Sekarang
</a>
@else
<a href="{{ route('login') }}?redirect={{ urlencode(url()->current()) }}"
@php
// 🔥 PENTING: Gunakan URL RELATIF saja (dimulai dengan /)
$targetUrl = '/form-pemesanan/' . $properti->id_properti;
@endphp
<a href="{{ route('login') }}?redirect={{ urlencode($targetUrl) }}"
class="contact-btn"
style="background:var(--dark-blue); color:white; border-radius:12px;
padding:14px; font-weight:600; text-decoration:none;
@ -1508,15 +1488,15 @@ class="action-btn btn-contact">Login</a>
});
});
// Contact buttons
document.querySelectorAll('.contact-btn').forEach(btn => {
// Contact buttons - HANYA untuk agen kontak
document.querySelectorAll('.agent-contact .contact-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
const action = this.textContent.trim();
if (action.includes('WhatsApp')) {
alert('Mengarahkan ke WhatsApp...');
window.open('https://wa.me/6281234567890', '_blank');
} else if (action.includes('Hubungi')) {
alert('Mengarahkan ke panggilan telepon...');
window.location.href = 'tel:+6281234567890';
}
});
});

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Detail Pemesanan - PropertiHarmoni</title>
<title>Detail Pemesanan - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form Pemesanan - PropertiHarmoni</title>
<title>Form Pemesanan - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -654,73 +654,104 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<!-- Main Content -->
<div class="main-content">
<div class="booking-form-container">
<form action="{{ route('pemesanan.proses') }}" method="POST" enctype="multipart/form-data" id="bookingForm">
@csrf
<input type="hidden" name="id_properti" value="{{ $properti->id_properti }}">
<!-- Property Preview -->
<!-- Property Preview - DINAMIS -->
<div class="property-preview">
<div class="property-image">
<img src="https://images.unsplash.com/photo-1560518883-ce09059eeffa?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" alt="Kelapa Gading Regency">
@php
// Ambil gambar pertama dari properti (sesuaikan dengan modelmu)
$gambar = $properti->gambar_properti
? (filter_var($properti->gambar_properti, FILTER_VALIDATE_URL)
? $properti->gambar_properti
: asset('storage/' . $properti->gambar_properti))
: asset('images/placeholder-properti.jpg');
@endphp
<img src="{{ $gambar }}" alt="{{ $properti->nama_properti }}"
onerror="this.src='{{ asset('images/placeholder-properti.jpg') }}'">
</div>
<div class="property-info">
<h2 class="property-name">Kelapa Gading Regency</h2>
<h2 class="property-name">{{ $properti->nama_properti }}</h2>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i>
<span>Jakarta Utara, DKI Jakarta</span>
<span>{{ $properti->lokasi_properti ?? 'Lokasi tidak tersedia' }}</span>
</div>
<div class="property-price">Rp 285.000.000</div>
<span class="property-type">Type 36/72 - Komersil</span>
<div class="property-price">Rp {{ number_format($properti->harga_properti, 0, ',', '.') }}</div>
<span class="property-type">
{{ $properti->tipe_properti }} - {{ ucfirst($properti->kategori_properti) }}
</span>
</div>
</div>
<!-- Form Section -->
<div class="form-section">
<h2 class="section-title">Form Pemesanan Properti</h2>
<!-- Personal Information -->
<div class="form-section">
<h3 class="section-title">Informasi Pribadi</h3>
<div class="form-row">
<div class="form-group">
<label for="fullName" class="form-label">Nama Lengkap <span class="required">*</span></label>
<input type="text" class="form-control" id="fullName" placeholder="Masukkan nama lengkap Anda" required>
</div>
<div class="form-group">
<label for="phone" class="form-label">Nomor HP <span class="required">*</span></label>
<input type="tel" class="form-control" id="phone" placeholder="Contoh: 081234567890" required>
</div>
<h3 class="section-title">Informasi Pribadi</h3>
<div class="form-row">
<div class="form-group">
<label for="fullName" class="form-label">Nama Lengkap <span class="required">*</span></label>
<input type="text" class="form-control" id="fullName" name="nama_lengkap"
value="{{ old('nama_lengkap', Auth::user()->nama_user ?? '') }}"
placeholder="Masukkan nama lengkap Anda" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="email" class="form-label">Email <span class="required">*</span></label>
<input type="email" class="form-control" id="email" placeholder="Masukkan email Anda" required>
</div>
<div class="form-group">
<label for="address" class="form-label">Alamat Lengkap <span class="required">*</span></label>
<textarea class="form-control" id="address" rows="2" placeholder="Masukkan alamat lengkap Anda" required></textarea>
</div>
<div class="form-group">
<label for="phone" class="form-label">Nomor HP <span class="required">*</span></label>
<input type="tel" class="form-control" id="phone" name="no_hp"
value="{{ old('no_hp', Auth::user()->no_hp ?? '') }}"
placeholder="Contoh: 081234567890" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="email" class="form-label">Email <span class="required">*</span></label>
<input type="email" class="form-control" id="email" name="email"
value="{{ old('email', Auth::user()->email_user ?? '') }}"
placeholder="Masukkan email Anda" required readonly>
</div>
<div class="form-group">
<label for="address" class="form-label">Alamat Lengkap <span class="required">*</span></label>
<textarea class="form-control" id="address" name="alamat" rows="2"
placeholder="Masukkan alamat lengkap Anda" required>{{ old('alamat', Auth::user()->alamat_user ?? '') }}</textarea>
</div>
</div>
</div>
<!-- Property Details -->
<div class="form-section">
<h3 class="section-title">Detail Properti</h3>
<div class="form-row">
<div class="form-group">
<label for="propertyType" class="form-label">Tipe Properti <span class="required">*</span></label>
<select class="form-control" id="propertyType" required>
<option value="">Pilih Tipe Properti</option>
<option value="30/60">30/60 (Subsidi)</option>
<option value="36/72" selected>36/72 (Komersil)</option>
<option value="45/84">45/84 (Komersil)</option>
<option value="60/135">60/135 (Komersil)</option>
<option value="ruko">Ruko (Komersil)</option>
</select>
<div class="form-control" style="background-color: #ffffff; font-weight: 500;">
{{ $properti->tipe_properti ?? 'Tidak tersedia' }}
@if($properti->tipe_properti == '30/60')
<span class="text-muted">(Subsidi)</span>
@else
<span class="text-muted">(Komersil)</span>
@endif
</div>
{{-- Tetap kirim value saat form submit --}}
<input type="hidden" name="tipe_dipilih" value="{{ $properti->tipe_properti ?? '' }}">
</div>
<div class="form-group">
<label for="downPayment" class="form-label">Uang Muka <span class="required">*</span></label>
<input type="text" class="form-control" id="downPayment" value="Rp 45.000.000" readonly>
@php
// Hitung DP: 15% untuk subsidi, 20% untuk komersil (sesuaikan kebijakan)
$harga = $properti->harga_properti ?? 0;
$kategori = $properti->kategori_properti ?? 'komersil';
$persenDP = ($kategori == 'subsidi') ? 0.15 : 0.20;
$dp = $harga * $persenDP;
@endphp
<input type="text" class="form-control" id="downPayment" name="uang_muka"
value="Rp {{ number_format($dp, 0, ',', '.') }}" readonly>
<!-- Hidden input untuk kirim nilai angka ke backend -->
<input type="hidden" name="uang_muka_value" value="{{ $dp }}">
</div>
</div>
</div>
@ -730,12 +761,11 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<h3 class="section-title">Metode Pembayaran</h3>
<div class="form-group">
<label for="paymentMethod" class="form-label">Pilih Metode Pembayaran <span class="required">*</span></label>
<select class="form-control" id="paymentMethod" required>
<select class="form-control" id="paymentMethod" name="jenis_transaksi" required>
<option value="">Pilih Metode Pembayaran</option>
<option value="kpr" selected>KPR (Kredit Pemilikan Rumah)</option>
<option value="cash">Cash (Pembayaran Lunas)</option>
<option value="installment">Cicilan Tanpa Bunga</option>
<option value="other">Lainnya (Metode Khusus)</option>
<option value="kredit">KPR (Kredit Pemilikan Rumah)</option>
<option value="lunas">Lunas</option>
<!-- <option value="kredit">Cicilan Tanpa Bunga</option> -->
</select>
</div>
</div>
@ -768,7 +798,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-id-card upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="ktpFile" accept=".jpg,.jpeg,.png,.pdf" multiple>
<input type="file" class="file-input" id="ktpFile" name="ktp[]" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="ktpFileName"></div>
</div>
</div>
@ -779,7 +809,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-users upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="kkFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="kkFile" name="kk" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="kkFileName"></div>
</div>
</div>
@ -790,7 +820,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-ring upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="marriageFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="marriageFile" name="surat_nikah" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="marriageFileName"></div>
</div>
</div>
@ -801,7 +831,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-alt upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="npwpFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="npwpFile" name="npwp" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="npwpFileName"></div>
</div>
</div>
@ -812,7 +842,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-camera upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG | Max 2MB</p>
<input type="file" class="file-input" id="photoFile" accept=".jpg,.jpeg,.png">
<input type="file" class="file-input" id="photoFile" name="foto_3x4" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="photoFileName"></div>
</div>
</div>
@ -823,7 +853,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-briefcase upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="workCertFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="workCertFile" name="surat_kerja" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="workCertFileName"></div>
</div>
</div>
@ -834,7 +864,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-invoice-dollar upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="salaryFile" accept=".jpg,.jpeg,.png,.pdf" multiple>
<input type="file" class="file-input" id="salaryFile" name="slip_gaji[]" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="salaryFileName"></div>
</div>
</div>
@ -845,7 +875,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-invoice upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="bankFile" accept=".jpg,.jpeg,.png,.pdf" multiple>
<input type="file" class="file-input" id="bankFile" name="rekening_koran[]" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="bankFileName"></div>
</div>
</div>
@ -856,7 +886,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-user upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG | Max 3MB</p>
<input type="file" class="file-input" id="selfieFile" accept=".jpg,.jpeg,.png">
<input type="file" class="file-input" id="selfieFile" name="selfie" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="selfieFileName"></div>
</div>
</div>
@ -867,7 +897,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-building upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG | Max 3MB</p>
<input type="file" class="file-input" id="workplaceFile" accept=".jpg,.jpeg,.png">
<input type="file" class="file-input" id="workplaceFile" name="foto_tempat_kerja" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="workplaceFileName"></div>
</div>
</div>
@ -886,7 +916,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-id-card upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="ktpFileW" accept=".jpg,.jpeg,.png,.pdf" multiple>
<input type="file" class="file-input" id="ktpFileW" name="ktp[]" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="ktpFileNameW"></div>
</div>
</div>
@ -897,7 +927,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-users upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="kkFileW" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="kkFileW" name="kk" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="kkFileNameW"></div>
</div>
</div>
@ -908,7 +938,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-ring upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="marriageFileW" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="marriageFileW" name="surat_nikah" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="marriageFileNameW"></div>
</div>
</div>
@ -919,7 +949,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-alt upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="npwpFileW" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="npwpFileW" name="npwp" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="npwpFileNameW"></div>
</div>
</div>
@ -930,7 +960,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-invoice upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="sptFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="sptFile" name="spt_pajak" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="sptFileName"></div>
</div>
</div>
@ -941,8 +971,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-camera upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG | Max 2MB</p>
<input type="file" class="file-input" id="photoFileW" accept=".jpg,.jpeg,.png">
<div class="file-name" id="photoFileNameW"></div>
<input type="file" class="file-input" id="marriageFile" name="surat_nikah" accept=".jpg,.jpeg,.png,.pdf" multiple>
</div>
</div>
@ -952,7 +981,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-contract upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="businessCertFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="businessCertFile" name="surat_ket_usaha" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="businessCertFileName"></div>
</div>
</div>
@ -963,7 +992,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-signature upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="incomeCertFile" accept=".jpg,.jpeg,.png,.pdf">
<input type="file" class="file-input" id="incomeCertFile" name="surat_penghasilan_usaha" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="incomeCertFileName"></div>
</div>
</div>
@ -974,7 +1003,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-book upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="businessRecordFile" accept=".jpg,.jpeg,.png,.pdf" multiple>
<input type="file" class="file-input" id="businessRecordFile" name="pembukuan_usaha" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="businessRecordFileName"></div>
</div>
</div>
@ -985,7 +1014,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-file-invoice upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input type="file" class="file-input" id="bankFileW" accept=".jpg,.jpeg,.png,.pdf" multiple>
<input type="file" class="file-input" id="bankFileW" name="rekening_koran[]" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="bankFileNameW"></div>
</div>
</div>
@ -996,7 +1025,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-user upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG | Max 3MB</p>
<input type="file" class="file-input" id="selfieFileW" accept=".jpg,.jpeg,.png">
<input type="file" class="file-input" id="selfieFile" name="selfie" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="selfieFileNameW"></div>
</div>
</div>
@ -1007,7 +1036,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<i class="fas fa-store upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG | Max 3MB</p>
<input type="file" class="file-input" id="businessPlaceFile" accept=".jpg,.jpeg,.png">
<input type="file" class="file-input" id="businessPlaceFile" name="foto_tempat_usaha" accept=".jpg,.jpeg,.png,.pdf" multiple>
<div class="file-name" id="businessPlaceFileName"></div>
</div>
</div>
@ -1045,14 +1074,41 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
</div>
<!-- Action Buttons -->
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelBtn">
<i class="fas fa-times me-2"></i>Batal
</button>
<button type="submit" class="btn btn-primary" id="submitBtn">
<i class="fas fa-paper-plane me-2"></i>Kirim Pemesanan
</button>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelBtn">
<i class="fas fa-times me-2"></i>Batal
</button>
<button type="submit" class="btn btn-primary" id="submitBtn">
<i class="fas fa-paper-plane me-2"></i>Kirim Pemesanan
</button>
</div>
<!-- Upload Bukti Booking Fee (KHUSUS LUNAS) -->
<div class="form-section" id="booking-proof-section" style="display: none;">
<h3 class="section-title">Upload Bukti Booking Fee</h3>
<div class="upload-item">
<label class="upload-label">
Bukti Pembayaran Booking Fee <span class="required">*</span>
</label>
<div class="upload-area">
<i class="fas fa-receipt upload-icon"></i>
<p class="upload-text">Klik atau drag file</p>
<p class="upload-hint">JPG/PNG/PDF | Max 5MB</p>
<input
type="file"
class="file-input"
name="bukti_booking"
id="buktiBooking"
accept=".jpg,.jpeg,.png,.pdf"
>
</div>
</div>
</div>
</form>{{-- tutup form --}}
</div>
</div>
</div>
@ -1060,131 +1116,106 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<script>
document.addEventListener('DOMContentLoaded', function() {
// Payment method dropdown change handler
const paymentMethodSelect = document.getElementById('paymentMethod');
const downPaymentInput = document.getElementById('downPayment');
const paymentDetails = document.querySelector('.payment-details');
paymentMethodSelect.addEventListener('change', function() {
const method = this.value;
if (method === 'kpr') {
downPaymentInput.value = 'Rp 45.000.000';
paymentDetails.innerHTML = `
<div class="payment-item">
<span class="payment-label">Harga Properti</span>
<span class="payment-value">Rp 285.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Uang Muka</span>
<span class="payment-value">Rp 45.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya Booking</span>
<span class="payment-value">Rp 2.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Total Pembayaran</span>
<span class="payment-value">Rp 47.000.000</span>
</div>
`;
} else if (method === 'cash') {
downPaymentInput.value = 'Rp 285.000.000';
paymentDetails.innerHTML = `
<div class="payment-item">
<span class="payment-label">Harga Properti</span>
<span class="payment-value">Rp 285.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya Booking</span>
<span class="payment-value">Rp 2.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Total Pembayaran</span>
<span class="payment-value">Rp 287.000.000</span>
</div>
`;
} else if (method === 'installment') {
downPaymentInput.value = 'Rp 85.500.000';
paymentDetails.innerHTML = `
<div class="payment-item">
<span class="payment-label">Harga Properti</span>
<span class="payment-value">Rp 285.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Uang Muka (30%)</span>
<span class="payment-value">Rp 85.500.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya Booking</span>
<span class="payment-value">Rp 2.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Total Pembayaran</span>
<span class="payment-value">Rp 87.500.000</span>
</div>
`;
} else if (method === 'other') {
downPaymentInput.value = 'Rp 0';
paymentDetails.innerHTML = `
<div class="payment-item">
<span class="payment-label">Harga Properti</span>
<span class="payment-value">Rp 285.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya Booking</span>
<span class="payment-value">Rp 2.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Total Pembayaran</span>
<span class="payment-value">Rp 2.000.000</span>
</div>
`;
// ========== MODAL PREVIEW ==========
const modal = document.createElement('div');
modal.id = 'previewModal';
modal.style.cssText = `
display:none; position:fixed; inset:0; background:rgba(0,0,0,0.75);
z-index:9999; align-items:center; justify-content:center; padding:20px;
`;
modal.innerHTML = `
<div style="background:#fff; border-radius:12px; max-width:90vw; max-height:90vh;
overflow:auto; padding:20px; position:relative; min-width:300px;">
<button id="previewClose" style="position:absolute; top:10px; right:14px;
background:none; border:none; font-size:24px; cursor:pointer; color:#333; line-height:1;">&times;</button>
<p id="previewTitle" style="font-weight:600; margin:0 32px 12px 0; font-size:13px; color:#555; word-break:break-all;"></p>
<div id="previewContent"></div>
</div>
`;
document.body.appendChild(modal);
modal.addEventListener('click', e => { if (e.target === modal) closePreview(); });
document.getElementById('previewClose').addEventListener('click', closePreview);
document.addEventListener('keydown', e => { if (e.key === 'Escape') closePreview(); });
function closePreview() {
modal.style.display = 'none';
document.getElementById('previewContent').innerHTML = '';
}
function openPreview(file) {
document.getElementById('previewTitle').textContent = file.name;
const content = document.getElementById('previewContent');
content.innerHTML = '';
const url = URL.createObjectURL(file);
if (file.type.startsWith('image/')) {
const img = document.createElement('img');
img.style.cssText = 'max-width:100%; max-height:75vh; display:block; border-radius:6px;';
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
content.appendChild(img);
} else if (file.type === 'application/pdf') {
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.cssText = 'width:75vw; height:75vh; border:none; border-radius:6px;';
content.appendChild(iframe);
}
});
// Employment type toggle
const pnsRadio = document.getElementById('employment-pns');
const wiraswastaRadio = document.getElementById('employment-wiraswasta');
const pnsDocuments = document.getElementById('pns-documents');
const wiraswastaDocuments = document.getElementById('wiraswasta-documents');
pnsRadio.addEventListener('change', function() {
if (this.checked) {
pnsDocuments.style.display = 'block';
wiraswastaDocuments.style.display = 'none';
}
});
wiraswastaRadio.addEventListener('change', function() {
if (this.checked) {
pnsDocuments.style.display = 'none';
wiraswastaDocuments.style.display = 'block';
}
});
// File upload handlers
modal.style.display = 'flex';
}
// ========== FILE UPLOAD HANDLER (GANTI YANG LAMA) ==========
function setupFileUpload(uploadAreaId, fileInputId, fileNameId) {
const uploadArea = document.getElementById(uploadAreaId);
const fileInput = document.getElementById(fileInputId);
const fileName = document.getElementById(fileNameId);
uploadArea.addEventListener('click', () => {
if (!uploadArea || !fileInput || !fileName) return;
uploadArea.addEventListener('click', function(e) {
// Jangan trigger klik file input kalau yg diklik tombol preview
if (e.target.closest('button[data-preview]')) return;
fileInput.click();
});
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
if (this.multiple && this.files.length > 1) {
fileName.textContent = `${this.files.length} file dipilih`;
} else {
fileName.textContent = this.files[0].name;
}
fileName.style.color = '#7AB2D3';
}
const files = Array.from(this.files);
if (!files.length) return;
fileName.innerHTML = '';
files.forEach(file => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; align-items:center; gap:6px; margin-top:5px;';
const nameSpan = document.createElement('span');
nameSpan.textContent = file.name;
nameSpan.style.cssText = 'font-size:0.82rem; color:#7AB2D3; font-weight:500; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;';
const btnLihat = document.createElement('button');
btnLihat.type = 'button';
btnLihat.setAttribute('data-preview', '1');
btnLihat.textContent = '👁 Lihat';
btnLihat.style.cssText = `
font-size:11px; padding:2px 8px; border-radius:4px; cursor:pointer;
background:#e8f0fe; border:1px solid #7AB2D3; color:#1E3A5F;
white-space:nowrap; flex-shrink:0;
`;
btnLihat.addEventListener('click', (e) => {
e.stopPropagation(); // supaya tidak trigger uploadArea click
openPreview(file);
});
row.appendChild(nameSpan);
row.appendChild(btnLihat);
fileName.appendChild(row);
});
});
}
// Setup all file upload areas for PNS
// ========== SETUP PNS ==========
setupFileUpload('ktpUpload', 'ktpFile', 'ktpFileName');
setupFileUpload('kkUpload', 'kkFile', 'kkFileName');
setupFileUpload('marriageUpload', 'marriageFile', 'marriageFileName');
@ -1195,8 +1226,8 @@ function setupFileUpload(uploadAreaId, fileInputId, fileNameId) {
setupFileUpload('bankUpload', 'bankFile', 'bankFileName');
setupFileUpload('selfieUpload', 'selfieFile', 'selfieFileName');
setupFileUpload('workplaceUpload', 'workplaceFile', 'workplaceFileName');
// Setup all file upload areas for Wiraswasta
// ========== SETUP WIRASWASTA ==========
setupFileUpload('ktpUploadW', 'ktpFileW', 'ktpFileNameW');
setupFileUpload('kkUploadW', 'kkFileW', 'kkFileNameW');
setupFileUpload('marriageUploadW', 'marriageFileW', 'marriageFileNameW');
@ -1209,58 +1240,26 @@ function setupFileUpload(uploadAreaId, fileInputId, fileNameId) {
setupFileUpload('bankUploadW', 'bankFileW', 'bankFileNameW');
setupFileUpload('selfieUploadW', 'selfieFileW', 'selfieFileNameW');
setupFileUpload('businessPlaceUpload', 'businessPlaceFile', 'businessPlaceFileName');
// Form submission
document.getElementById('submitBtn').addEventListener('click', function() {
// Simple validation
const requiredFields = [
{ id: 'fullName', label: 'Nama Lengkap' },
{ id: 'phone', label: 'Nomor HP' },
{ id: 'email', label: 'Email' },
{ id: 'address', label: 'Alamat' },
{ id: 'propertyType', label: 'Tipe Properti' },
{ id: 'paymentMethod', label: 'Metode Pembayaran' }
];
let isValid = true;
for (const field of requiredFields) {
const input = document.getElementById(field.id);
if (!input.value.trim()) {
alert(`Mohon lengkapi field: ${field.label}`);
input.focus();
isValid = false;
break;
}
}
if (!isValid) return;
// Validate documents based on employment type
let docSection;
if (pnsRadio.checked) {
docSection = document.querySelectorAll('#pns-documents .file-input');
} else {
docSection = document.querySelectorAll('#wiraswasta-documents .file-input');
}
let missingDocs = [];
docSection.forEach(input => {
if (!input.files || input.files.length === 0) {
const label = input.closest('.upload-item').querySelector('.upload-label').textContent;
missingDocs.push(label.split(' ')[0]);
}
});
if (missingDocs.length > 0) {
alert(`Mohon lengkapi dokumen berikut:\n${missingDocs.join('\n')}`);
return;
}
// Success message
alert('Pemesanan berhasil dikirim! Tim kami akan segera menghubungi Anda untuk proses selanjutnya.');
// In real application: submit form to server
// document.getElementById('bookingForm').submit();
// ========== SISA SCRIPT LAMA (payment, employment toggle, submit, cancel) ==========
const paymentMethodSelect = document.getElementById('paymentMethod');
const downPaymentInput = document.getElementById('downPayment');
const paymentDetails = document.querySelector('.payment-details');
paymentMethodSelect.addEventListener('change', function() {
// ... (isi sama persis dengan script lama)
});
const pnsRadio = document.getElementById('employment-pns');
const wiraswastaRadio = document.getElementById('employment-wiraswasta');
const pnsDocuments = document.getElementById('pns-documents');
const wiraswastaDocuments = document.getElementById('wiraswasta-documents');
pnsRadio.addEventListener('change', function() {
if (this.checked) { pnsDocuments.style.display='block'; wiraswastaDocuments.style.display='none'; }
});
wiraswastaRadio.addEventListener('change', function() {
if (this.checked) { pnsDocuments.style.display='none'; wiraswastaDocuments.style.display='block'; }
});
// Cancel button
@ -1271,6 +1270,38 @@ function setupFileUpload(uploadAreaId, fileInputId, fileNameId) {
});
});
</script>
<script>
document.getElementById('paymentMethod').addEventListener('change', function() {
let metode = this.value;
let dpField = document.getElementById('downPayment').closest('.form-group');
if (metode === 'lunas') {
dpField.style.display = 'none';
} else {
dpField.style.display = 'block';
}
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('paymentMethod').addEventListener('change', function() {
let metode = this.value;
let bookingSection = document.getElementById('booking-proof-section');
let buktiInput = document.getElementById('buktiBooking');
if (metode === 'lunas') {
bookingSection.style.display = 'block';
buktiInput.setAttribute('required', 'required');
} else {
bookingSection.style.display = 'none';
buktiInput.removeAttribute('required');
}
});
});
</script>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ganti Password - PropertiHarmoni</title>
<title>Ganti Password - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatBot - PropertiHarmoni</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>ChatBot - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -751,100 +752,32 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<!-- Messages Area -->
<div class="messages-area" id="messagesArea">
<!-- Bot Welcome Message -->
<!-- Bot Welcome Message (Satu-satunya pesan awal) -->
<div class="message bot-message">
<div class="message-header">
<div class="bot-avatar">PH</div>
<div class="bot-avatar" style="width:30px; height:30px; font-size:14px;">PH</div>
<div>PropertiBot</div>
</div>
<div class="message-content">
Halo! Saya PropertiBot, asisten virtual dari PropertiHarmoni. Ada yang bisa saya bantu hari ini? 😊
Halo! Saya PropertiBot. Ada yang bisa dibantu hari ini? 😊<br>
Silakan tanya tentang harga, lokasi, atau KPR.
</div>
<div class="message-time">10:24 AM</div>
</div>
<!-- Bot Message -->
<div class="message bot-message">
<div class="message-header">
<div class="bot-avatar">PH</div>
<div>PropertiBot</div>
</div>
<div class="message-content">
Saya bisa membantu Anda dengan:
<ul style="padding-left: 20px; margin-top: 8px;">
<li>Mencari properti sesuai kebutuhan</li>
<li>Informasi detail properti</li>
<li>Proses pemesanan dan pembayaran</li>
<li>Verifikasi dokumen</li>
<li>Dan masih banyak lagi!</li>
</ul>
</div>
<div class="message-time">10:24 AM</div>
<div class="message-time">{{ date('H:i') }}</div>
<!-- Opsi Awal -->
<div class="quick-actions">
<button class="quick-btn">Cari Properti</button>
<button class="quick-btn">Info Harga</button>
<button class="quick-btn">Proses Pemesanan</button>
<button class="quick-btn">Bantuan Dokumen</button>
<button class="quick-btn" onclick="handleQuickReply('Cari Properti')">Cari Properti</button>
<button class="quick-btn" onclick="handleQuickReply('Info Harga')">Info Harga</button>
<button class="quick-btn" onclick="handleQuickReply('Simulasi KPR')">Simulasi KPR</button>
</div>
</div>
<!-- User Message -->
<div class="message user-message">
<div class="message-content">
Saya tertarik dengan Apartemen Begawan Malang. Bisa kasih info detailnya?
</div>
<div class="message-time">10:26 AM</div>
</div>
<!-- Bot Response -->
<div class="message bot-message">
<div class="message-header">
<div class="bot-avatar">PH</div>
<div>PropertiBot</div>
</div>
<div class="message-content">
Tentu! Apartemen Begawan Malang adalah hunian modern di pusat kota Malang dengan fasilitas lengkap:
<br><br>
<strong>Spesifikasi:</strong>
Lokasi: Kota Malang, Kecamatan Klojen
Harga: Rp 3.500.000.000
Luas Bangunan: 150
Kamar Tidur: 3
Kamar Mandi: 2
Garasi: 1 mobil
Lantai: 30
<br>
<strong>Fasilitas:</strong>
Kolam renang infinity
Gym & fitness center
Keamanan 24 jam
Parkir bawah tanah
Co-working space
</div>
<div class="message-time">10:27 AM</div>
<div class="quick-actions">
<button class="quick-btn">Lihat Gambar</button>
<button class="quick-btn">Cek Ketersediaan</button>
<button class="quick-btn">Simulasi Kredit</button>
</div>
</div>
<!-- User Message -->
<div class="message user-message">
<div class="message-content">
Bagaimana simulasi kredit untuk apartemen ini?
</div>
<div class="message-time">10:29 AM</div>
</div>
<!-- Bot Response with Typing Indicator -->
<div class="typing-indicator">
<!-- Typing Indicator (Tersembunyi) -->
<!-- <div class="typing-indicator" id="typingIndicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div> -->
</div>
<!-- Input Area -->
@ -857,174 +790,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const messagesArea = document.getElementById('messagesArea');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const typingIndicator = document.querySelector('.typing-indicator');
// Quick action buttons
document.querySelectorAll('.quick-btn').forEach(button => {
button.addEventListener('click', function() {
const message = this.textContent;
addMessage(message, 'user');
simulateBotResponse(message);
});
});
// Send message on button click
sendBtn.addEventListener('click', sendMessage);
// Send message on Enter key
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && messageInput.value.trim() !== '') {
sendMessage();
}
});
// Auto-resize textarea (not needed since we're using input, but keeping for future)
messageInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
addMessage(message, 'user');
messageInput.value = '';
messageInput.style.height = 'auto';
// Show typing indicator
typingIndicator.style.display = 'flex';
// Simulate bot response after delay
setTimeout(() => {
typingIndicator.style.display = 'none';
simulateBotResponse(message);
}, 1500);
}
}
function addMessage(text, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message`;
const now = new Date();
const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
if (sender === 'bot') {
messageDiv.innerHTML = `
<div class="message-header">
<div class="bot-avatar">PH</div>
<div>PropertiBot</div>
</div>
<div class="message-content">${text}</div>
<div class="message-time">${timeString}</div>
`;
} else {
messageDiv.innerHTML = `
<div class="message-content">${text}</div>
<div class="message-time">${timeString}</div>
`;
}
messagesArea.appendChild(messageDiv);
// Scroll to bottom
messagesArea.scrollTop = messagesArea.scrollHeight;
}
function simulateBotResponse(userMessage) {
// Simple response logic based on user message
let botResponse = '';
const lowerMessage = userMessage.toLowerCase();
if (lowerMessage.includes('halo') || lowerMessage.includes('hai') || lowerMessage.includes('hello')) {
botResponse = 'Halo! Ada yang bisa saya bantu hari ini? 😊';
} else if (lowerMessage.includes('terima kasih') || lowerMessage.includes('makasih')) {
botResponse = 'Sama-sama! Jangan ragu untuk bertanya lagi jika ada yang ingin Anda ketahui. 😊';
} else if (lowerMessage.includes('harga') || lowerMessage.includes('biaya')) {
botResponse = 'Untuk informasi harga yang lebih detail, silakan berikan kriteria properti yang Anda cari (lokasi, tipe, budget) atau lihat katalog properti kami di halaman Katalog.';
} else if (lowerMessage.includes('kredit') || lowerMessage.includes('cicilan')) {
botResponse = 'Kami menyediakan opsi KPR dengan tenor hingga 25 tahun. Untuk simulasi kredit yang akurat, silakan lengkapi data diri Anda di halaman Simulasi Kredit atau hubungi agen kami.';
} else if (lowerMessage.includes('properti') || lowerMessage.includes('rumah') || lowerMessage.includes('apartemen')) {
botResponse = 'Kami memiliki berbagai pilihan properti di Malang dan sekitarnya. Untuk pencarian yang lebih spesifik, silakan gunakan filter di halaman Katalog atau beri tahu saya kriteria properti yang Anda inginkan.';
} else if (lowerMessage.includes('dokumen') || lowerMessage.includes('syarat')) {
botResponse = 'Untuk pembelian properti, dokumen yang diperlukan antara lain: KTP, KK, NPWP, Slip Gaji 3 bulan terakhir, dan Rekening Koran. Untuk info lebih lengkap, silakan kunjungi halaman Panduan Dokumen.';
} else {
botResponse = 'Terima kasih atas pertanyaan Anda! Tim kami akan segera memproses permintaan Anda. Untuk informasi lebih detail, silakan hubungi agen kami atau kunjungi halaman Bantuan.';
}
addMessage(botResponse, 'bot');
// Add quick actions for common queries
if (lowerMessage.includes('properti') || lowerMessage.includes('rumah') || lowerMessage.includes('apartemen')) {
const lastMessage = document.querySelectorAll('.message.bot-message').item(-1);
const quickActions = document.createElement('div');
quickActions.className = 'quick-actions';
quickActions.innerHTML = `
<button class="quick-btn">Lihat Katalog</button>
<button class="quick-btn">Filter Lokasi</button>
<button class="quick-btn">Budget di Bawah 1M</button>
<button class="quick-btn">Hubungi Agen</button>
`;
lastMessage.appendChild(quickActions);
// Add event listeners to new quick buttons
quickActions.querySelectorAll('.quick-btn').forEach(button => {
button.addEventListener('click', function() {
const message = this.textContent;
addMessage(message, 'user');
simulateBotResponse(message);
});
});
}
}
// Initialize with welcome message after a short delay
setTimeout(() => {
typingIndicator.style.display = 'none';
}, 1000);
// Focus input on load
messageInput.focus();
});
function toggleDropdown() {
document.getElementById('dropdownMenu').classList.toggle('show');
}
// Tutup dropdown kalau klik di luar
window.addEventListener('click', function(e) {
if (!e.target.closest('.profile-dropdown')) {
document.getElementById('dropdownMenu').classList.remove('show');
}
});
// Add interactivity to search button
document.querySelector('.search-btn').addEventListener('click', function() {
alert('Mencari riwayat pemesanan dengan kriteria yang dipilih...');
});
// Add interactivity to action buttons
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.querySelector('i').className;
const bookingId = this.closest('tr').querySelector('td:first-child').textContent;
if (action.includes('fa-eye')) {
alert(`Menampilkan detail pemesanan: ${bookingId}`);
} else if (action.includes('fa-times')) {
if (confirm(`Apakah Anda yakin ingin membatalkan pemesanan ${bookingId}?`)) {
alert(`Pemesanan ${bookingId} berhasil dibatalkan.`);
// In a real application, you would update the status in the database
}
}
});
});
// BAGIAN NAV
// Add hover effect to table rows
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('mouseenter', function() {
@ -1049,6 +815,261 @@ function toggleDropdown() {
function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
// SCRIPT CHAT
document.addEventListener('DOMContentLoaded', function() {
// --- KONFIGURASI ELEMENT ---
const messagesArea = document.getElementById('messagesArea');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const typingIndicator = document.querySelector('.typing-indicator');
// Kunci penyimpanan di browser
const STORAGE_KEY = 'properti_chat_history';
// Token CSRF Laravel
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ||
document.querySelector('input[name="_token"]')?.value;
// --- 1. FUNGSI LOAD CHAT SAAT HALAMAN DIBUKA ---
function loadChatHistory() {
const savedChat = sessionStorage.getItem(STORAGE_KEY);
if (savedChat) {
const history = JSON.parse(savedChat);
history.forEach(msg => {
// Render ulang pesan tanpa memicu logika kirim ke server
renderMessageOnly(msg.text, msg.sender, msg.options);
});
scrollToBottom();
}
}
// --- 2. FUNGSI SIMPAN CHAT ---
function saveChatToHistory(text, sender, options = null) {
let history = [];
const savedChat = sessionStorage.getItem(STORAGE_KEY);
if (savedChat) {
history = JSON.parse(savedChat);
}
// Simpan data sederhana (text, sender, dan label opsi jika ada)
const optionsLabels = options ? options.map(opt => typeof opt === 'object' ? opt.label : opt) : null;
history.push({
text: text,
sender: sender,
options: optionsLabels
});
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(history));
}
// --- 3. FUNGSI UTAMA KIRIM PESAN ---
async function sendMessage() {
const text = messageInput.value.trim();
if (!text) return;
// Tampilkan pesan user & Simpan
addMessage(text, 'user');
saveChatToHistory(text, 'user');
messageInput.value = '';
showTyping();
try {
// Kirim ke Backend Laravel
const response = await fetch('/api/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({ message: text })
});
const data = await response.json();
hideTyping();
// Tampilkan balasan bot & Simpan
sendBotReply(data.reply, data.options, true);
} catch (error) {
console.error('Error:', error);
hideTyping();
const errorMsg = "Maaf, terjadi kesalahan koneksi ke server.";
addMessage(errorMsg, 'bot');
saveChatToHistory(errorMsg, 'bot');
}
}
// --- 4. FUNGSI RENDER PESAN KE LAYAR ---
// Fungsi untuk menambah pesan baru (Interaktif + Simpan)
function addMessage(text, sender) {
const msgElement = createMessageElement(text, sender);
messagesArea.appendChild(msgElement);
scrollToBottom();
return msgElement;
}
// Fungsi khusus untuk render ulang dari History
function renderMessageOnly(text, sender, savedOptions) {
const msgElement = createMessageElement(text, sender);
// Jika ada opsi yang tersimpan, buat tombolnya kembali
if (sender === 'bot' && savedOptions && savedOptions.length > 0) {
const actionsContainer = msgElement.querySelector('.quick-actions-container');
const actionsDiv = document.createElement('div');
actionsDiv.className = 'quick-actions';
savedOptions.forEach(optLabel => {
const btn = document.createElement('button');
btn.className = 'quick-btn';
btn.textContent = optLabel;
btn.onclick = () => handleQuickReplyClick(optLabel);
actionsDiv.appendChild(btn);
});
actionsContainer.appendChild(actionsDiv);
}
messagesArea.appendChild(msgElement);
}
// Helper membuat elemen HTML pesan
function createMessageElement(text, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message`;
const now = new Date();
const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
if (sender === 'bot') {
messageDiv.innerHTML = `
<div class="message-header">
<div class="bot-avatar">PH</div>
<div>PropertiBot</div>
</div>
<div class="message-content">${text}</div>
<div class="message-time">${timeString}</div>
<div class="quick-actions-container"></div>
`;
} else {
messageDiv.innerHTML = `
<div class="message-content">${text}</div>
<div class="message-time">${timeString}</div>
`;
}
return messageDiv;
}
// Fungsi kirim balasan bot (dengan opsi simpan)
function sendBotReply(text, options = [], shouldSave = false) {
const msgElement = addMessage(text, 'bot');
const actionsContainer = msgElement.querySelector('.quick-actions-container');
if (options && options.length > 0) {
const actionsDiv = document.createElement('div');
actionsDiv.className = 'quick-actions';
options.forEach(opt => {
const btn = document.createElement('button');
btn.className = 'quick-btn';
const label = typeof opt === 'object' ? opt.label : opt;
btn.textContent = label;
btn.onclick = () => handleQuickReplyClick(label);
actionsDiv.appendChild(btn);
});
actionsContainer.appendChild(actionsDiv);
scrollToBottom();
}
if (shouldSave) {
const optionsLabels = options.map(opt => typeof opt === 'object' ? opt.label : opt);
saveChatToHistory(text, 'bot', optionsLabels);
}
}
// Handler khusus untuk klik tombol quick reply
function handleQuickReplyClick(text) {
addMessage(text, 'user');
saveChatToHistory(text, 'user');
processLocalResponse(text);
}
// Helper untuk handle klik tombol (kirim ke backend lagi)
function processLocalResponse(text) {
showTyping();
setTimeout(async () => {
try {
const response = await fetch('/api/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({ message: text })
});
const data = await response.json();
hideTyping();
sendBotReply(data.reply, data.options, true);
} catch (e) {
hideTyping();
console.error(e);
}
}, 600);
}
// --- UTILITIES ---
// --- UTILITIES (DIPERBARUI) ---
let typingElement = null; // Variabel untuk menyimpan elemen typing
function showTyping() {
// Jika sudah ada, hapus dulu biar tidak duplikat
if (typingElement) typingElement.remove();
// Buat elemen typing baru secara dinamis
typingElement = document.createElement('div');
typingElement.className = 'typing-indicator';
typingElement.innerHTML = `
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
`;
messagesArea.appendChild(typingElement);
scrollToBottom();
}
function hideTyping() {
if (typingElement) {
typingElement.remove(); // Hapus elemen dari layar
typingElement = null; // Reset variabel
}
}
function scrollToBottom() {
messagesArea.scrollTop = messagesArea.scrollHeight;
}
// --- EVENT LISTENERS ---
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
// Jalankan load history saat DOM siap
loadChatHistory();
});
</script>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Katalog Properti - PropertiHarmoni</title>
<title>Katalog Properti - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -1036,19 +1036,28 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
</div>
</div>
{{-- Hapus div blok yang lama --}}
{{-- Hapus div blok yang lama --}}
<div class="property-actions">
<a href="{{ route('detail-katalog', $p->id_properti) }}"
class="action-btn btn-view">
Lihat Detail
</a>
<button class="action-btn btn-contact">Hubungi</button>
<div class="property-actions">
@auth
{{-- Sudah login: tampilkan "Lihat Detail" --}}
<a href="{{ route('detail-katalog', $p->id_properti) }}"
class="action-btn btn-view">
Lihat Detail
</a>
@else
{{-- 🔒 Belum login: tampilkan "Login" --}}
<a href="{{ route('login') }}"
class="action-btn btn-view">
Login
</a>
@endauth
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
</div>
@endforeach
</div>
<!-- Pagination -->

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notifikasi - PropertiHarmoni</title>
<title>Notifikasi - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -559,64 +559,105 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<!-- Main Content -->
<div class="main-content">
<h1 class="page-title">Notifikasi</h1>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px;">
<div>
<input type="checkbox" id="selectAll">
<label for="selectAll">Pilih Semua</label>
</div>
<button id="deleteSelected"
style="background:#dc2626; color:white; border:none; padding:8px 14px; border-radius:8px; cursor:pointer;">
<i class="fas fa-trash"></i> Hapus Terpilih
</button>
</div>
<div class="notification-list">
<!-- Notification 1 -->
<div class="notification-item">
<div class="notification-icon-container mail">
<i class="fas fa-envelope"></i>
</div>
<div class="notification-content">
<h3 class="notification-title">Dokumen telah diverifikasi oleh admin</h3>
<p class="notification-description">Silahkan lanjut ke proses transaksi</p>
</div>
<div class="notification-arrow">
<i class="fas fa-chevron-right"></i>
</div>
</div>
@forelse($notifikasi ?? [] as $n)
{{-- Mapping tipe notifikasi ke icon & style yang sudah ada --}}
@php
// Tentukan icon & class berdasarkan tipe notifikasi
switch($n->tipe) {
case 'pemesanan':
case 'transaksi':
$icon = 'fa-shopping-cart';
$iconClass = 'mail'; // pakai class existing
break;
case 'dokumen':
case 'verifikasi':
$icon = 'fa-file-check';
$iconClass = 'info';
break;
case 'peringatan':
case 'tagihan':
$icon = 'fa-exclamation-triangle';
$iconClass = 'warning';
break;
default:
$icon = 'fa-bell';
$iconClass = 'mail';
}
// Tentukan link tujuan berdasarkan referensi
$link = '#';
if($n->referensi_tipe == 'transaksi' && $n->referensi_id) {
$link = route('detail-pemesanan', $n->referensi_id);
} elseif($n->referensi_tipe == 'dokumen' && $n->referensi_id) {
$link = route('admin.halaman_verifikasi'); // atau route dokumen
}
@endphp
<!-- Notification 2 -->
<div class="notification-item">
<div class="notification-icon-container warning">
<i class="fas fa-exclamation-triangle"></i>
<a href="{{ $link }}" style="text-decoration:none; color:inherit; display:block;">
<div class="notification-item {{ $n->status_baca == 0 ? 'unread' : '' }}">
<input type="checkbox" class="notif-checkbox" value="{{ $n->id_notifikasi }}"
onclick="event.stopPropagation();">
<div class="notification-icon-container {{ $iconClass }}">
<i class="fas {{ $icon }}"></i>
</div>
<div class="notification-content">
<h3 class="notification-title">{{ $n->judul }}</h3>
<p class="notification-description">{{ $n->pesan }}</p>
<small class="text-muted" style="font-size:0.8rem;">
{{ \Carbon\Carbon::parse($n->created_at)->diffForHumans() }}
</small>
</div>
<div style="display:flex; align-items:center; gap:10px;">
{{-- 🗑 HAPUS NOTIF --}}
<form id="bulkDeleteForm" method="POST" action="{{ route('notifikasi.hapus.massal') }}">
@csrf
@method('DELETE')
<input type="hidden" name="ids" id="selectedIds">
</form>
{{-- ➡️ PANAH --}}
<div class="notification-arrow">
<i class="fas fa-chevron-right"></i>
</div>
</div>
</div>
<div class="notification-content">
<h3 class="notification-title">Jangan Lupa Bayar Cicilan</h3>
<p class="notification-description">Paling lambat tanggal 05/06</p>
</div>
<div class="notification-arrow">
<i class="fas fa-chevron-right"></i>
</div>
</div>
<!-- Notification 3 -->
<div class="notification-item">
<div class="notification-icon-container info">
<i class="fas fa-info-circle"></i>
</div>
<div class="notification-content">
<h3 class="notification-title">Jadwal Survey Lokasi</h3>
<p class="notification-description">Sabtu, 10 Juni 2025 pukul 10.00 WIB di Kelapa Gading Regency</p>
</div>
<div class="notification-arrow">
<i class="fas fa-chevron-right"></i>
</div>
</div>
<!-- Notification 4 -->
<div class="notification-item">
<div class="notification-icon-container mail">
<i class="fas fa-envelope"></i>
</div>
<div class="notification-content">
<h3 class="notification-title">Konfirmasi Pembayaran</h3>
<p class="notification-description">Pembayaran DP Rp 50.000.000 telah diterima. Terima kasih!</p>
</div>
<div class="notification-arrow">
<i class="fas fa-chevron-right"></i>
</a>
@empty
{{-- Empty state: class tetap sama --}}
<div class="empty-state">
<div class="empty-icon">
<i class="fas fa-bell-slash"></i>
</div>
<h3 class="empty-title">Belum Ada Notifikasi</h3>
<p class="empty-description">Kamu akan menerima notifikasi saat ada update pemesanan atau verifikasi dokumen.</p>
</div>
@endforelse
</div>
{{-- Pagination (jika pakai) - class Bootstrap tetap utuh --}}
@if(isset($notifikasi) && method_exists($notifikasi, 'links'))
<div class="mt-4">
{{ $notifikasi->links() }}
</div>
@endif
</div>
<script>
@ -685,6 +726,28 @@ function toggleDropdown() {
this.style.background = '';
});
});
// Tambahkan di akhir script
document.querySelectorAll('.notification-item').forEach(item => {
item.addEventListener('click', function(e) {
// Jangan jalankan jika klik di elemen yang tidak ingin trigger
if(e.target.closest('a')) {
const notifId = this.dataset.id;
// Mark as read via AJAX (tanpa reload)
if(notifId) {
fetch(`/notifikasi/${notifId}/read`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
}).catch(err => console.log('Gagal update status baca:', err));
}
}
});
});
// Pagination functionality
document.querySelectorAll('.page-item').forEach(item => {
@ -700,6 +763,43 @@ function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const selectAll = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.notif-checkbox');
const deleteBtn = document.getElementById('deleteSelected');
const selectedIdsInput = document.getElementById('selectedIds');
// SELECT ALL
selectAll.addEventListener('change', function() {
checkboxes.forEach(cb => cb.checked = this.checked);
});
// DELETE SELECTED
deleteBtn.addEventListener('click', function() {
let selected = [];
checkboxes.forEach(cb => {
if (cb.checked) {
selected.push(cb.value);
}
});
if (selected.length === 0) {
alert('Pilih notifikasi dulu!');
return;
}
if (!confirm('Hapus notifikasi terpilih?')) return;
selectedIdsInput.value = selected.join(',');
document.getElementById('bulkDeleteForm').submit();
});
});
</script>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pembayaran - PropertiHarmoni</title>
<title>Pembayaran - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profil Saya - PropertiHarmoni</title>
<title>Profil Saya - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daftar - PropertiHarmoni</title>
<title>Daftar - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -431,6 +431,25 @@
<input type="tel" class="form-control" id="phone" placeholder="Contoh: 081234567890" required>
</div>
</div>
<!-- Tambahkan setelah field "Nomor HP", sebelum "Password" -->
<div class="form-group">
<label for="pekerjaan" class="form-label">Pekerjaan</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-briefcase"></i>
</span>
<input type="text"
class="form-control"
id="pekerjaan"
name="pekerjaan"
value="{{ old('pekerjaan') }}"
placeholder="Contoh: Pegawai Swasta / Wiraswasta / PNS">
</div>
<small style="color: #64748b; font-size: 0.8rem; margin-top: 4px; display: block;">
<i class="fas fa-info-circle"></i> Opsional, bisa diisi nanti
</small>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
@ -458,13 +477,14 @@
</div>
</div>
<button type="submit" class="btn-register w-100">
<i class="fas fa-user-plus me-2"></i>Daftar Sekarang
</button>
<div class="login-link">
<p>Sudah memiliki akun? <a href="#">Login di sini</a></p>
</div>
<a href="{{ route('welcome') }}" class="btn-register w-100 d-block text-center">
Daftar Sekarang
</a>
<p>
Sudah memiliki akun?
<a href="{{ route('login') }}">Login di sini</a>
</p>
</form>
</div>
</div>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - PropertiHarmoni</title>
<title>Login - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -533,6 +533,11 @@ class="form-control password-input @error('password') is-invalid @enderror"
<button type="submit" class="btn-login w-100">
<i class="fas fa-sign-in-alt me-2"></i>Masuk ke Akun
</button>
<!-- 🔥 TAMBAHAN: Link ke Register -->
<div class="signup-link">
<p>Belum punya akun? <a href="{{ route('register') }}">Daftar Sekarang</a></p>
</div>
</form>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daftar - PropertiHarmoni</title>
<title>Daftar - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Riwayat Pemesanan - PropertiHarmoni</title>
<title>Riwayat Pemesanan - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -541,6 +541,99 @@
.btn-primary:hover {
background: #6aa5c6;
}
/* Footer */
.footer {
background: var(--dark-blue);
color: white;
padding: 50px 30px 20px;
margin-top: 80px;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 40px;
}
.footer-column h3 {
font-size: 1.2rem;
margin-bottom: 20px;
position: relative;
}
.footer-column h3::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
width: 40px;
height: 2px;
background: var(--primary-blue);
}
.footer-links {
list-style: none;
padding: 0;
margin: 0;
}
.footer-links li {
margin-bottom: 10px;
}
.footer-links a {
color: #cbd5e0;
text-decoration: none;
transition: color 0.3s ease;
}
.footer-links a:hover {
color: var(--primary-blue);
}
.footer-contact p {
margin: 10px 0;
display: flex;
align-items: center;
gap: 10px;
}
.footer-contact i {
color: var(--primary-blue);
}
.footer-social {
display: flex;
gap: 15px;
margin-top: 20px;
}
.social-icon {
width: 40px;
height: 40px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
transition: all 0.3s ease;
}
.social-icon:hover {
background: var(--primary-blue);
transform: translateY(-2px);
}
.copyright {
text-align: center;
padding-top: 30px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: 40px;
font-size: 0.9rem;
color: #cbd5e0;
}
/* Responsive Design */
@media (max-width: 992px) {
@ -907,6 +1000,60 @@ class="action-btn btn-view">
</div>
</div>
<!-- FOOTER -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-column">
<h3>Carani Estate</h3>
<p>Platform terpercaya untuk membeli, menjual, dan menyewa properti sejak 2015.</p>
<div class="footer-social">
<div class="social-icon"><i class="fab fa-facebook-f"></i></div>
<div class="social-icon"><i class="fab fa-twitter"></i></div>
<div class="social-icon"><i class="fab fa-instagram"></i></div>
<div class="social-icon"><i class="fab fa-linkedin-in"></i></div>
</div>
</div>
<div class="footer-column">
<h3>Tautan Cepat</h3>
<ul class="footer-links">
<li><a href="#">Beranda</a></li>
<li><a href="#">Katalog Properti</a></li>
<li><a href="#">ChatBot</a></li>
<li><a href="#">Riwayat Pemesanan</a></li>
<li><a href="#">Tentang Kami</a></li>
</ul>
</div>
<div class="footer-column">
<h3>Layanan</h3>
<ul class="footer-links">
<li><a href="#">Pembelian Properti</a></li>
<li><a href="#">Penjualan Properti</a></li>
<li><a href="#">Sewa Properti</a></li>
<li><a href="#">Konsultasi Properti</a></li>
<li><a href="#">Finansial & KPR</a></li>
</ul>
</div>
<div class="footer-column">
<h3>Kontak Kami</h3>
<div class="footer-contact">
<p><i class="fas fa-map-marker-alt"></i> Jl. Raya Pakisan, Bunduh, Bataan, Kec. Tenggarang, Kabupaten Bondowoso, Jawa Timur 68271</p>
<p><i class="fas fa-phone"></i> 0812-3456-7890</p>
<p><i class="fas fa-envelope"></i> caranibhanubalakosa@gmail.com</p>
<p><i class="fas fa-clock"></i> Senin - Sabtu: 08:00 - 17:00</p>
</div>
</div>
</div>
<div class="copyright">
&copy; 2025 CaraniEstate. Semua hak dilindungi.
</div>
</div>
</footer>
<script>
function toggleDropdown() {
document.getElementById('dropdownMenu').classList.toggle('show');

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Katalog Properti - PropertiHarmoni</title>
<title>Katalog Properti - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -1527,7 +1527,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<section class="hero">
<div class="hero-overlay"></div>
<div class="hero-content">
<h1 class="hero-title">Tentang PropertiHarmoni</h1>
<h1 class="hero-title">Tentang Carani Estate</h1>
<p class="hero-subtitle">Mewujudkan impian memiliki rumah yang nyaman dan berkualitas sejak 2015</p>
</div>
</section>
@ -1733,16 +1733,16 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<div class="footer-column">
<h3>Kontak Kami</h3>
<div class="footer-contact">
<p><i class="fas fa-map-marker-alt"></i> Jl. Melati No. 45, Jakarta Selatan</p>
<p><i class="fas fa-map-marker-alt"></i> Jl. Raya Pakisan, Bunduh, Bataan, Kec. Tenggarang, Kabupaten Bondowoso, Jawa Timur 68271</p>
<p><i class="fas fa-phone"></i> 0812-3456-7890</p>
<p><i class="fas fa-envelope"></i> info@propertiharmoni.com</p>
<p><i class="fas fa-envelope"></i> caranibhanubalakosa@gmail.com</p>
<p><i class="fas fa-clock"></i> Senin - Sabtu: 08:00 - 17:00</p>
</div>
</div>
</div>
<div class="copyright">
&copy; 2025 PropertiHarmoni. Semua hak dilindungi.
&copy; 2025 CaraniEstate. Semua hak dilindungi.
</div>
</div>
</footer>

View File

@ -0,0 +1,203 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pemesanan Berhasil - Carani Estate</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
background: #f8fafc;
font-family: 'Segoe UI', sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.card-sukses {
background: white;
border-radius: 20px;
padding: 50px 40px;
text-align: center;
max-width: 520px;
width: 100%;
box-shadow: 0 10px 40px rgba(0,0,0,0.08);
}
.icon-sukses {
width: 90px;
height: 90px;
background: #d1fae5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
}
.icon-sukses i {
font-size: 40px;
color: #059669;
}
.judul {
font-size: 1.8rem;
font-weight: 700;
color: #1E3A5F;
margin-bottom: 10px;
}
.subjudul {
color: #64748b;
font-size: 1rem;
margin-bottom: 30px;
line-height: 1.6;
}
.info-box {
background: #f1f5f9;
border-radius: 12px;
padding: 20px;
text-align: left;
margin-bottom: 25px;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #e2e8f0;
font-size: 0.95rem;
}
.info-row:last-child { border-bottom: none; }
.info-label { color: #64748b; }
.info-value { font-weight: 600; color: #1E3A5F; }
.status-pill {
background: #fef3c7;
color: #f59e0b;
padding: 4px 14px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
}
.steps {
background: #eff6ff;
border-radius: 12px;
padding: 20px;
text-align: left;
margin-bottom: 30px;
}
.steps h6 {
color: #1E3A5F;
font-weight: 700;
margin-bottom: 12px;
}
.step-item {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 10px;
font-size: 0.9rem;
color: #475569;
}
.step-num {
width: 24px;
height: 24px;
background: #7AB2D3;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
flex-shrink: 0;
}
.btn-notif {
background: #7AB2D3;
color: white;
border: none;
padding: 12px 30px;
border-radius: 10px;
font-weight: 600;
text-decoration: none;
display: inline-block;
margin: 5px;
transition: all 0.3s;
}
.btn-notif:hover { background: #6aa5c6; color: white; }
.btn-outline {
background: transparent;
color: #7AB2D3;
border: 2px solid #7AB2D3;
padding: 12px 30px;
border-radius: 10px;
font-weight: 600;
text-decoration: none;
display: inline-block;
margin: 5px;
transition: all 0.3s;
}
.btn-outline:hover { background: #7AB2D3; color: white; }
</style>
</head>
<body>
<div class="card-sukses">
<div class="icon-sukses">
<i class="fas fa-check"></i>
</div>
<h1 class="judul">Pemesanan Berhasil!</h1>
<p class="subjudul">
Dokumen kamu sudah kami terima. Admin akan memverifikasi dokumenmu
dan kamu akan mendapat notifikasi setelah proses selesai.
</p>
{{-- Info Transaksi --}}
<div class="info-box">
<div class="info-row">
<span class="info-label">Properti</span>
<span class="info-value">{{ $transaksi->properti->nama_properti ?? '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Jenis Transaksi</span>
<span class="info-value">{{ ucfirst($transaksi->jenis_transaksi) }}</span>
</div>
<div class="info-row">
<span class="info-label">Total Harga</span>
<span class="info-value">Rp {{ number_format($transaksi->total_harga, 0, ',', '.') }}</span>
</div>
<div class="info-row">
<span class="info-label">Tanggal</span>
<span class="info-value">{{ \Carbon\Carbon::parse($transaksi->tanggal_transaksi)->format('d M Y') }}</span>
</div>
<div class="info-row">
<span class="info-label">Status</span>
<span class="status-pill"><i class="fas fa-clock"></i> Menunggu Verifikasi</span>
</div>
</div>
{{-- Langkah Selanjutnya --}}
<div class="steps">
<h6><i class="fas fa-list-ol"></i> Langkah Selanjutnya</h6>
<div class="step-item">
<div class="step-num">1</div>
<span>Admin akan mengecek dokumen yang kamu kirimkan</span>
</div>
<div class="step-item">
<div class="step-num">2</div>
<span>Kamu akan mendapat notifikasi jika dokumen disetujui atau perlu diperbaiki</span>
</div>
<div class="step-item">
<div class="step-num">3</div>
<span>Setelah disetujui, kamu bisa melanjutkan ke proses pembayaran</span>
</div>
</div>
{{-- Tombol --}}
<div>
<a href="{{ route('halaman-notifikasi') }}" class="btn-notif">
<i class="fas fa-bell"></i> Lihat Notifikasi
</a>
<a href="{{ route('halaman-katalog') }}" class="btn-outline">
<i class="fas fa-home"></i> Kembali ke Katalog
</a>
</div>
</div>
</body>
</html>

View File

@ -3,8 +3,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PropertiHarmoni - Temukan Rumah Impian Anda</title>
<title>Carani Estate - Temukan Rumah Impian Anda</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
@ -168,7 +169,7 @@
/* Hero Section */
.hero {
background: url('https://placehold.co/1920x1080/e6f2f8/1E3A5F?text=Properti+Impian') no-repeat center center;
background: url('') no-repeat center center;
background-size: cover;
padding: 150px 30px 100px;
position: relative;
@ -396,7 +397,59 @@
margin-top: 10px;
}
/* CSS IKLAN */
.email-modal {
display: none;
position: fixed;
z-index: 9999;
left: 0; top: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.6);
justify-content: center;
align-items: center;
}
.email-box {
background: white;
padding: 30px;
border-radius: 12px;
width: 350px;
text-align: center;
animation: fadeIn 0.4s ease;
}
.email-box h2 {
margin-bottom: 10px;
}
.email-box input {
width: 100%;
padding: 10px;
margin: 15px 0;
border-radius: 8px;
border: 1px solid #ccc;
}
.email-box button {
width: 100%;
padding: 10px;
background: #1E3A5F;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
.close-btn {
float: right;
cursor: pointer;
font-size: 20px;
}
@keyframes fadeIn {
from {transform: scale(0.8); opacity: 0;}
to {transform: scale(1); opacity: 1;}
}
/* About Section */
@ -559,10 +612,10 @@
line-height: 1.6;
}
/* Services Grid - 4 Columns */
/* Services Grid - 3 Columns, ganti bagian repeat */
.services-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: 25px;
margin-bottom: 50px;
}
@ -743,47 +796,6 @@
transform: translateX(4px);
}
/* Bottom CTA */
.services-cta {
text-align: center;
padding: 30px;
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(226, 232, 240, 0.6);
max-width: 600px;
margin: 0 auto;
}
.services-cta p {
font-size: 1.1rem;
color: var(--dark-blue);
margin-bottom: 20px;
font-weight: 500;
}
.btn-services-cta {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 14px 35px;
background: linear-gradient(135deg, var(--dark-blue), #2a4a6f);
color: white;
border-radius: 12px;
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn-services-cta:hover {
transform: translateY(-3px);
box-shadow: 0 12px 30px rgba(30, 58, 95, 0.3);
background: linear-gradient(135deg, #2a4a6f, var(--dark-blue));
}
/* Responsive Design */
@media (max-width: 1200px) {
.services-grid {
@ -875,6 +887,183 @@
}
}
/*--------------------------------------------------------------
# Call To Action Section-1 (CUSTOM COLOR)
--------------------------------------------------------------*/
.call-to-action-1 {
position: relative;
min-height: 500px;
display: flex;
align-items: center;
}
/* Background */
.call-to-action-1 .cta-bg {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
overflow: hidden;
}
/* Overlay pakai PRIMARY BLUE */
.call-to-action-1 .cta-bg::before {
content: "";
position: absolute;
inset: 0;
background: rgba(122, 178, 211, 0.4); /* primary-blue */
}
/* Content */
.call-to-action-1 .container {
position: relative;
z-index: 2;
}
/* Title */
.call-to-action-1 .cta-content h2 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: white;
}
@media (max-width: 768px) {
.call-to-action-1 .cta-content h2 {
font-size: 2rem;
}
}
/* Description */
.call-to-action-1 .cta-content p {
font-size: 1.1rem;
margin-bottom: 2.5rem;
color: rgba(255, 255, 255, 0.85);
line-height: 1.6;
}
/* Buttons */
.call-to-action-1 .cta-buttons {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 3rem;
flex-wrap: wrap;
}
/* Base Button */
.call-to-action-1 .cta-buttons .btn {
padding: 12px 30px;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
border-radius: 50px;
transition: all 0.3s ease;
border: 2px solid transparent;
display: inline-flex;
align-items: center;
gap: 8px;
}
/* PRIMARY BUTTON */
.call-to-action-1 .cta-buttons .btn.btn-primary {
background-color: var(--dark-blue);
color: white;
border-color: var(--dark-blue);
}
.call-to-action-1 .cta-buttons .btn.btn-primary:hover {
background-color: #162c47;
border-color: #162c47;
transform: translateY(-2px);
}
/* OUTLINE BUTTON */
.call-to-action-1 .cta-buttons .btn.btn-outline {
background-color: transparent;
color: white;
border-color: white;
}
.call-to-action-1 .cta-buttons .btn.btn-outline:hover {
background-color: white;
color: var(--primary-blue);
transform: translateY(-2px);
}
/* Mobile Button */
@media (max-width: 576px) {
.call-to-action-1 .cta-buttons {
flex-direction: column;
align-items: center;
}
.call-to-action-1 .cta-buttons .btn {
width: 100%;
max-width: 280px;
justify-content: center;
}
}
/* Features */
.call-to-action-1 .cta-features {
display: flex;
justify-content: center;
gap: 2rem;
flex-wrap: wrap;
}
/* Feature Item */
.call-to-action-1 .cta-features .feature-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Icon pakai primary */
.call-to-action-1 .cta-features .feature-item i {
font-size: 1.2rem;
color: white;
}
/* Text */
.call-to-action-1 .cta-features .feature-item span {
font-size: 0.95rem;
font-weight: 500;
color: white;
}
/* Responsive */
@media (max-width: 576px) {
.call-to-action-1 .cta-features {
flex-direction: column;
gap: 1rem;
}
.call-to-action-1 .cta-features .feature-item {
justify-content: center;
}
}
@media (max-width: 768px) {
.call-to-action-1 {
min-height: 400px;
padding: 80px 0;
}
.call-to-action-1 .cta-content h2 {
margin-bottom: 1rem;
}
.call-to-action-1 .cta-content p {
margin-bottom: 2rem;
}
.call-to-action-1 .cta-buttons {
margin-bottom: 2rem;
}
}
/* Properti Unggulan */
.properti-unggulan {
background: #ffffff;
@ -1831,7 +2020,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
{{-- Guest --}}
@guest
<a href="{{ route('login') }}" class="nav-item login-link">
<a href="{{ route('login', ['redirect' => url()->current()]) }}" class="nav-item login-link">
<i class="fas fa-sign-in-alt me-1"></i> Login
</a>
@else
@ -2023,7 +2212,7 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
</div>
<!-- Service 4: Verifikasi & After-Sales -->
<div class="service-card">
<!-- <div class="service-card">
<div class="service-icon-wrapper">
<div class="service-icon">
<i class="fas fa-shield-alt"></i>
@ -2042,21 +2231,53 @@ class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
<a href="{{ route('halaman-kontak') }}" class="service-link">
Pelajari Lebih Lanjut <i class="fas fa-arrow-right"></i>
</a>
</div>
</div> -->
</div>
<!-- CTA Bottom -->
<div class="services-cta">
<p>Butuh bantuan lebih lanjut? Tim kami siap membantu Anda!</p>
<a href="{{ route('halaman-chatbot') }}" class="btn-services-cta">
<i class="fas fa-comments me-2"></i>Chat dengan Kami
</a>
</div>
</div>
</section>
<!-- Call To Action Section -->
<section class="call-to-action-1 call-to-action-1 section" id="call-to-action">
<div class="cta-bg" style="background-image: url('assets/img/about/about-4.webp');"></div>
<div class="container" data-aos="fade-up" data-aos-delay="100">
<div class="row justify-content-center">
<div class="col-xl-6 col-lg-8">
<div class="cta-content text-center">
<h2>Butuh Bantuan Menemukan Properti Impian Anda?</h2>
<p>Kami siap membantu Anda memilih properti terbaik dengan panduan ahli, sesuai kebutuhan dan budget Anda. Mulai perjalanan properti Anda sekarang!</p>
<div class="cta-buttons">
<a href="https://wa.me/6281234567890" target="_blank" class="btn btn-primary">Hubungi Kami Sekarang</a>
<a href="properties.html" class="btn btn-outline">Lihat Properti</a>
</div>
<div class="cta-features">
<div class="feature-item" data-aos="fade-up" data-aos-delay="200">
<i class="bi bi-telephone-fill"></i>
<span>Konsultasi Gratis</span>
</div>
<div class="feature-item" data-aos="fade-up" data-aos-delay="250">
<i class="bi bi-clock-fill"></i>
<span>Dukungan 24/7</span>
</div>
<div class="feature-item" data-aos="fade-up" data-aos-delay="300">
<i class="bi bi-shield-check-fill"></i>
<span>Ahli Properti Terpercaya</span>
</div>
</div>
</div><!-- End CTA Content -->
</div>
</div>
</div>
</section><!-- /Call To Action Section -->
<!-- Properti Unggulan -->
<section class="properti-unggulan">
<div class="properti-container">
@ -2407,15 +2628,6 @@ class="btn-primary properti-btn">
</div>
<!-- Contact CTA -->
<div class="faq-contact">
<h3>Masih Punya Pertanyaan?</h3>
<p>Tim kami siap membantu Anda 24/7. Jangan ragu untuk menghubungi kami!</p>
<a href="https://wa.me/6281234567890" class="faq-contact-btn" target="_blank">
<i class="fab fa-whatsapp"></i> Hubungi via WhatsApp
</a>
</div>
</div>
</section>
@ -2461,16 +2673,16 @@ class="btn-primary properti-btn">
<div class="footer-column">
<h3>Kontak Kami</h3>
<div class="footer-contact">
<p><i class="fas fa-map-marker-alt"></i> Jl. Melati No. 45, Jakarta Selatan</p>
<p><i class="fas fa-map-marker-alt"></i> Jl. Raya Pakisan, Bunduh, Bataan, Kec. Tenggarang, Kabupaten Bondowoso, Jawa Timur 68271</p>
<p><i class="fas fa-phone"></i> 0812-3456-7890</p>
<p><i class="fas fa-envelope"></i> info@propertiharmoni.com</p>
<p><i class="fas fa-envelope"></i> caranibhanubalakosa@gmail.com</p>
<p><i class="fas fa-clock"></i> Senin - Sabtu: 08:00 - 17:00</p>
</div>
</div>
</div>
<div class="copyright">
&copy; 2025 PropertiHarmoni. Semua hak dilindungi.
&copy; 2025 CaraniEstate. Semua hak dilindungi.
</div>
</div>
</footer>
@ -2616,58 +2828,123 @@ function toggleMenu(){
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// FAQ Accordion Functionality
const faqQuestions = document.querySelectorAll('.faq-question');
faqQuestions.forEach(question => {
question.addEventListener('click', function() {
const answer = this.nextElementSibling;
const isActive = this.classList.contains('active');
// Close all other FAQs
faqQuestions.forEach(q => {
q.classList.remove('active');
q.nextElementSibling.style.maxHeight = '0';
q.nextElementSibling.style.padding = '0 30px 0 30px';
});
// Toggle current FAQ
if (!isActive) {
this.classList.add('active');
answer.style.maxHeight = answer.scrollHeight + 'px';
answer.style.padding = '0 30px 25px 30px';
}
});
document.addEventListener('DOMContentLoaded', function() {
// ================================
// FAQ ACCORDION
// ================================
const faqQuestions = document.querySelectorAll('.faq-question');
faqQuestions.forEach(question => {
question.addEventListener('click', function() {
const answer = this.nextElementSibling;
const isActive = this.classList.contains('active');
// Tutup semua
faqQuestions.forEach(q => {
q.classList.remove('active');
q.nextElementSibling.style.maxHeight = '0';
q.nextElementSibling.style.padding = '0 30px 0 30px';
});
// Category Filter
const categoryBtns = document.querySelectorAll('.faq-category-btn');
const faqItems = document.querySelectorAll('.faq-item');
categoryBtns.forEach(btn => {
btn.addEventListener('click', function() {
// Update active button
categoryBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
const category = this.dataset.category;
// Filter FAQs
faqItems.forEach(item => {
if (category === 'all' || item.dataset.category === category) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
});
// Auto-expand first FAQ (optional)
// faqQuestions[0].click();
// Buka yang diklik
if (!isActive) {
this.classList.add('active');
answer.style.maxHeight = answer.scrollHeight + 'px';
answer.style.padding = '0 30px 25px 30px';
}
});
</script>
});
// ================================
// CATEGORY FILTER
// ================================
const categoryBtns = document.querySelectorAll('.faq-category-btn');
const faqItems = document.querySelectorAll('.faq-item');
categoryBtns.forEach(btn => {
btn.addEventListener('click', function() {
// Set active button
categoryBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
const category = this.dataset.category;
// Filter item
faqItems.forEach(item => {
if (item.dataset.category === category) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
// Tutup semua accordion saat ganti kategori
faqQuestions.forEach(q => {
q.classList.remove('active');
q.nextElementSibling.style.maxHeight = '0';
q.nextElementSibling.style.padding = '0 30px 0 30px';
});
});
});
// ================================
// DEFAULT CATEGORY (UMUM)
// ================================
const defaultCategory = 'umum';
categoryBtns.forEach(btn => {
if (btn.dataset.category === defaultCategory) {
btn.classList.add('active');
}
});
faqItems.forEach(item => {
if (item.dataset.category === defaultCategory) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
</script>
<script>
window.onload = function() {
if (!localStorage.getItem("email_popup_shown")) {
setTimeout(() => {
document.getElementById("emailModal").style.display = "flex";
}, 2000); // muncul setelah 2 detik
localStorage.setItem("email_popup_shown", "true");
}
}
function closeModal() {
document.getElementById("emailModal").style.display = "none";
}
</script>
<!-- IKLAN -->
<!-- EMAIL CAPTURE MODAL -->
<div id="emailModal" class="email-modal">
<div class="email-box">
<span class="close-btn" onclick="closeModal()">×</span>
<h2>Dapatkan Info Properti Terbaru!</h2>
<p>Masukkan email kamu untuk dapat promo & rekomendasi terbaik 🔥</p>
<form method="POST" action="{{ route('save-email-visitor') }}">
@csrf
<input type="email" name="email" placeholder="Masukkan email..." required>
<button type="submit">Daftar Sekarang</button>
</form>
</div>
</div>
</body>
</html>

8
routes/api.php Normal file
View File

@ -0,0 +1,8 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');

View File

@ -5,6 +5,8 @@
use App\Http\Controllers\ChatbotController;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\VisitorController;
use Illuminate\Support\Facades\Http;
/*
|--------------------------------------------------------------------------
@ -33,6 +35,9 @@
return view('halaman-chatbot');
})->name('halaman-chatbot');
Route::post('/save-email-visitor', [VisitorController::class, 'store'])
->name('save-email-visitor');
// Katalog Properti (PUBLIC - Bisa dilihat tanpa login)
Route::get('/halaman-katalog', function () {
$query = \App\Models\Properti::with('blok')
@ -74,10 +79,35 @@
return view('detail-katalog', compact('properti'));
})->name('detail-katalog');
// -- NOTIFIKASI --
// Notifikasi (Opsional: bisa public atau protected)
Route::get('/halaman-notifikasi', function () {
return view('halaman-notifikasi');
})->name('halaman-notifikasi');
$notifikasi = \App\Models\Notifikasi::where('id_user', Auth::id())
->latest()
->get()
->unique('referensi_id'); // 🔥 biar tidak numpuk
return view('halaman-notifikasi', compact('notifikasi'));
})->name('halaman-notifikasi')->middleware('auth');
// Mark notifikasi sebagai sudah dibaca
Route::post('/notifikasi/{id}/read', function ($id) {
$notif = \App\Models\Notifikasi::where('id_user', Auth::id())
->findOrFail($id);
$notif->update(['status_baca' => 1]);
return response()->json(['success' => true]);
})->name('notifikasi.read')->middleware('auth');
Route::delete('/notifikasi-massal', function () {
$ids = explode(',', request('ids'));
\App\Models\Notifikasi::whereIn('id_notifikasi', $ids)
->where('id_user', auth()->id())
->delete();
return back()->with('success', 'Notifikasi berhasil dihapus');
})->name('notifikasi.hapus.massal')->middleware('auth');
/*
@ -93,6 +123,14 @@
Route::get('/gantipass', [AuthController::class, 'formGantiPassword'])->name('ganti-password');
Route::post('/gantipass', [AuthController::class, 'updatePassword'])->name('ganti-password.proses');
// Route untuk halaman register
Route::get('/register', function () {
return view('register'); // Pastikan file register.blade.php ada
})->name('register');
// Route untuk memproses register
Route::post('/register', [AuthController::class, 'register'])->name('register.proses');
/*
|--------------------------------------------------------------------------
@ -105,17 +143,13 @@
// --- FORM PEMESANAN ---
// Halaman form booking (Hanya bisa diakses jika sudah login)
Route::get('/form-pemesanan/{id}', function ($id) {
$properti = \App\Models\Properti::with('blok')->findOrFail($id);
$properti = \App\Models\Properti::where('id_properti', $id)->firstOrFail();
return view('form-pemesanan', compact('properti'));
})->name('form-pemesanan');
// Proses submit pemesanan
Route::post('/pemesanan/proses', function (\Illuminate\Http\Request $request) {
// TODO: Validasi & simpan ke tabel Transaksi
// \App\Models\Transaksi::create([...]);
return redirect()->route('riwayat-pemesanan')->with('success', 'Pemesanan berhasil diajukan!');
})->name('pemesanan.proses');
Route::post('/pemesanan/proses', [App\Http\Controllers\PemesananController::class, 'proses'])
->name('pemesanan.proses');
// --- RIWAYAT & DETAIL PEMESANAN ---
@ -135,10 +169,29 @@
return view('detail-pemesanan', compact('transaksi'));
})->name('detail-pemesanan');
// ✅ Detail Properti (Hanya bisa diakses jika login)
Route::get('/detail-katalog/{id}', function ($id) {
$properti = \App\Models\Properti::with('blok')->findOrFail($id);
return view('detail-katalog', compact('properti'));
})->name('detail-katalog');
// --- HALAMAN TERIMS PEMESANAN ---
// Halaman terima kasih setelah pemesanan
Route::get('/pemesanan/terima-kasih/{id}', function ($id) {
$transaksi = \App\Models\Transaksi::with('properti')
->where('id_transaksi', $id)
->where('id_user', Auth::id())
->firstOrFail();
return view('terima-kasih', compact('transaksi'));
})->name('pemesanan.terima-kasih');
// --- CHATBOT & PROFIL ---
// 💡 Chatbot saya letakkan di sini agar bisa akses data user
// CHATBOT
Route::post('/chat/send', [ChatBotController::class, 'sendMessage'])->name('chat.send');
Route::get('/halaman-profil', function () {
return view('halaman-profil');
})->name('halaman-profil');
@ -211,6 +264,8 @@
return view('admin.tambah-rumah');
})->name('admin.tambah-rumah');
// ========================
// DATA USER
// ========================
@ -274,7 +329,8 @@
return redirect()->route('admin.data_user')
->with('success', 'User berhasil dihapus.');
})->name('admin.hapus_user');
})->name('admin.hapus_user');
// ========================
// VERIFIKASI
@ -282,15 +338,24 @@
Route::get('/halaman_verifikasi', function () {
$status = request('status');
$query = \App\Models\Dokumen::with('user');
$queryTransaksi = \App\Models\Transaksi::with(['user', 'properti']);
$queryDokumen = \App\Models\Dokumen::with('user');
if ($status && $status != 'semua') {
$query->where('status_verifikasi', $status);
if ($status == 'pending') {
$queryTransaksi->where('status_transaksi', 'menunggu_verifikasi');
$queryDokumen->where('status_verifikasi', 'pending');
} elseif ($status == 'diterima') {
$queryTransaksi->where('status_transaksi', 'menunggu_pembayaran');
$queryDokumen->where('status_verifikasi', 'diterima');
} elseif ($status == 'ditolak') {
$queryTransaksi->where('status_transaksi', 'ditolak');
$queryDokumen->where('status_verifikasi', 'ditolak');
}
$dokumen = $query->orderBy('uploaded_at', 'desc')->get();
$transaksi = $queryTransaksi->orderBy('tanggal_transaksi', 'desc')->get();
$user = \App\Models\User::with('dokumen')->get();
return view('admin.halaman_verifikasi', compact('dokumen'));
return view('admin.halaman_verifikasi', compact('transaksi', 'user'));
})->name('admin.halaman_verifikasi');
Route::post('/verifikasi/{id}/approve', function ($id) {
@ -324,4 +389,58 @@
return back()->with('success', 'Dokumen ditolak');
})->name('admin.verifikasi.tolak');
// MELIHAT DOKUMEN
Route::get('/verifikasi/{id}', function ($id) {
$user = \App\Models\User::with('dokumen')->findOrFail($id);
return view('admin.verifikasi_dokumen', compact('user'));
})->name('admin.verifikasi_dokumen');
Route::post('/verifikasi/{id}/selesai', function ($id) {
$user = \App\Models\User::with('dokumen')->findOrFail($id);
// contoh: cek apakah semua dokumen sudah diterima
$allApproved = $user->dokumen->every(function ($d) {
return $d->status_verifikasi === 'diterima';
});
// kirim notifikasi ke user
\App\Models\Notifikasi::create([
'id_user' => $user->id_user,
'judul' => 'Verifikasi Selesai',
'pesan' => $allApproved
? 'Semua dokumen kamu telah diverifikasi.'
: 'Verifikasi selesai, tapi masih ada dokumen yang perlu diperbaiki.',
'tipe' => 'dokumen',
'status_baca' => 0,
'status_kirim' => 'terkirim',
'channel' => 'in_app',
'referensi_id' => $user->id_user,
'referensi_tipe' => 'user',
]);
return redirect()->route('admin.halaman_verifikasi')
->with('success', 'Verifikasi selesai');
})->name('admin.verifikasi.selesai');
});
// TEST
Route::get('/test-gemini', function () {
$apiKey = env('GOOGLE_GEMINI_API_KEY');
$model = env('GOOGLE_GEMINI_MODEL', 'gemini-2.0-flash');
$url = "https://generativelanguage.googleapis.com/v1/models/{$model}:generateContent?key={$apiKey}";
$response = Http::post($url, [
'contents' => [[
'parts' => [['text' => 'Halo, apakah API key Gemini saya berfungsi?']]
]]
]);
if ($response->successful()) {
$text = $response->json('candidates.0.content.parts.0.text') ?? 'No response';
return '✅ API Key Gemini Berhasil! Response: ' . $text;
}
return '❌ Error ' . $response->status() . ': ' . $response->body();
});