first commit

This commit is contained in:
nylaaptr 2026-04-03 19:07:58 +07:00
commit 3b67043489
93 changed files with 36547 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4

65
.env.example Normal file
View File

@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.fleet
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
Homestead.json
Homestead.yaml
Thumbs.db

59
README.md Normal file
View File

@ -0,0 +1,59 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Redberry](https://redberry.international/laravel-development)**
- **[Active Logic](https://activelogic.com)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
class AuthController extends Controller
{
// Tampilkan form login
public function index()
{
return view('login');
}
// Proses login
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
], [
'email.required' => 'Email wajib diisi',
'email.email' => 'Format email tidak valid',
'password.required' => 'Password wajib diisi',
]);
// Cari user berdasarkan email_user (kolom kustom)
$user = User::where('email_user', $request->email)->first();
// Verifikasi password
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.dashboard');
// Jika admin di project terpisah, gunakan:
// return redirect('http://127.0.0.1:8001/admin');
}
// Redirect user biasa
return redirect()->intended(route('halaman-katalog'));
}
// Jika gagal
return back()->withErrors([
'email' => 'Email atau password yang Anda masukkan salah.',
])->withInput($request->except('password'));
}
// Tampilkan form ganti password
public function formGantiPassword(Request $request)
{
$email = $request->query('email');
return view('gantipass', compact('email'));
}
// Proses update password
public function updatePassword(Request $request)
{
$request->validate([
'email' => 'required|email|exists:users,email_user',
'password_baru' => 'required|min:8|confirmed',
], [
'email.exists' => 'Email tidak terdaftar dalam sistem.',
'password_baru.min' => 'Password minimal 8 karakter.',
'password_baru.confirmed' => 'Konfirmasi password tidak cocok.',
]);
$user = User::where('email_user', $request->email)->first();
if ($user) {
$user->update([
'password_user' => Hash::make($request->password_baru)
]);
}
return redirect()->route('login')
->with('success', 'Password berhasil diubah! Silakan login dengan password baru.');
}
// Logout
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('welcome'); // Redirect ke halaman utama setelah logout
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
// app/Http/Controllers/ChatbotController.php
class ChatbotController extends Controller
{
public function index()
{
return view('halaman-chatbot');
}
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,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
class UserController extends Controller
{
public function index(Request $request)
{
$query = User::query();
if ($request->search) {
$search = str_replace(' ', '', strtolower($request->search));
$query->whereRaw("REPLACE(LOWER(nama_user), ' ', '') LIKE ?", ["%$search%"])
->orWhereRaw("REPLACE(LOWER(email_user), ' ', '') LIKE ?", ["%$search%"])
->orWhereRaw("REPLACE(LOWER(no_hp), ' ', '') LIKE ?", ["%$search%"]);
}
$users = $query->paginate(10)->withQueryString();
return view('data_user', compact('users')); // sesuaikan dengan blade kamu
}
public function create()
{
return view('tambah_datauser');
}
public function destroy($id)
{
User::where('id_user', $id)->delete();
return redirect()->back()->with('success', 'User berhasil dihapus.');
}
}

11
app/Models/Blok.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Blok extends Model
{
protected $table = 'blok';
protected $primaryKey = 'id_blok';
}

23
app/Models/Dokumen.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Dokumen extends Model
{
protected $table = 'dokumen';
protected $primaryKey = 'id_dokumen';
public $timestamps = false;
protected $fillable = [
'id_user', 'jenis_dokumen', 'nama_file',
'path_file', 'tipe_file', 'ukuran_file',
'status_verifikasi', 'catatan_verifikasi',
];
public function user()
{
return $this->belongsTo(User::class, 'id_user', 'id_user');
}
}

26
app/Models/Kpr.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Kpr extends Model
{
protected $table = 'kpr';
protected $primaryKey = 'id_kpr';
public $timestamps = false;
protected $fillable = [
'id_transaksi',
'nama_bank',
'nomor_kontrak',
'harga_properti',
'uang_muka',
'jumlah_pinjaman',
'suku_bunga',
'tenor',
'angsuran_perbulan',
'status_kpr',
'catatan',
];
}

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

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

15
app/Models/Pembayaran.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Pembayaran extends Model
{
protected $table = 'pembayaran';
protected $primaryKey = 'id_pembayaran';
public $timestamps = false;
protected $fillable = [
'id_transaksi', 'jumlah_bayar', 'tanggal_bayar',
'ke_pembayaran', 'status_bayar', 'bukti_transfer',
];
}

12
app/Models/Perumahan.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Perumahan extends Model
{
protected $table = 'perumahan';
protected $primaryKey = 'id_perumahan';
public $timestamps = false;
protected $fillable = ['nama_perumahan'];
}

77
app/Models/Properti.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Perumahan;
use App\Models\Blok;
class Properti extends Model
{
// ✅ Nama tabel (jika tidak mengikuti konvensi plural)
protected $table = 'properti';
// ✅ PRIMARY KEY CUSTOM (WAJIB - ini yang missing!)
protected $primaryKey = 'id_properti';
// ✅ Jika id_properti adalah auto-increment integer
public $incrementing = true;
protected $keyType = 'int';
// ✅ Kolom yang boleh diisi mass-assignment (opsional tapi disarankan)
protected $fillable = [
'nama_properti',
'harga_properti',
'tipe_properti',
'kategori_properti',
'jenis_properti',
'status_unit',
'id_perumahan',
'id_blok',
'deskripsi', // jika ada
// ... tambah kolom lainnya sesuai tabel
];
// ✅ Relationship ke Perumahan
public function perumahan()
{
return $this->belongsTo(Perumahan::class, 'id_perumahan', 'id_perumahan');
}
// ✅ Relationship ke Blok
public function blok()
{
return $this->belongsTo(Blok::class, 'id_blok', 'id_blok');
}
// ✅ BONUS: Accessor untuk info tambahan (tanpa ubah database)
// Contoh: Menampilkan "Keunggulan" berdasarkan kategori
public function getKeunggulanAttribute()
{
if ($this->kategori_properti == 'subsidi') {
return [
'Proses KPR Mudah',
'Bebas Biaya BPHTB',
'Sertifikat SHM',
'Lingkungan Asri'
];
}
return [
'Desain Modern Minimalis',
'Material Premium',
'Smart Home Ready',
'Green Open Space 30%'
];
}
// ✅ BONUS: Estimasi cicilan KPR (contoh logika)
public function getEstimasiCicilanAttribute()
{
$dp = $this->harga_properti * 0.1; // DP 10%
$sisa = $this->harga_properti - $dp;
$cicilan = $sisa / 180; // 15 tahun = 180 bulan
return 'Rp ' . number_format($cicilan, 0, ',', '.');
}
}

43
app/Models/Transaksi.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Kpr;
class Transaksi extends Model
{
protected $table = 'transaksi';
protected $primaryKey = 'id_transaksi';
public $timestamps = false;
protected $fillable = [
'id_user',
'id_properti',
'tanggal_transaksi',
'total_harga',
'jenis_pembayaran',
'status_transaksi',
];
// Relasi ke properti
public function properti()
{
return $this->belongsTo(Properti::class, 'id_properti', 'id_properti');
}
public function user()
{
return $this->belongsTo(User::class, 'id_user', 'id_user');
}
public function pembayaran()
{
return $this->hasMany(Pembayaran::class, 'id_transaksi', 'id_transaksi');
}
public function kpr()
{
return $this->hasOne(Kpr::class, 'id_transaksi', 'id_transaksi');
}
}

52
app/Models/User.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
// ✅ Tentukan primary key kustom
protected $table = 'user';
protected $primaryKey = 'id_user';
public $incrementing = true; // atau false jika id_user bukan auto-increment
protected $keyType = 'int'; // atau 'string' jika UUID
// ✅ Tentukan nama kolom password kustom
protected $password = 'password_user';
// ✅ Kolom yang bisa diisi mass-assignment
protected $fillable = [
'nama_user',
'email_user',
'no_hp',
'password_user',
'role_user',
];
// ✅ Hidden attributes (tidak dikirim saat toJSON/toArray)
protected $hidden = [
'password_user',
'remember_token',
];
// ✅ Beritahu Laravel field mana yang digunakan untuk auth
public function getAuthIdentifierName()
{
return 'id_user';
}
public function getAuthPassword()
{
return $this->password_user;
}
public function getAuthPasswordName()
{
return 'password_user';
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Paginator::useBootstrapFive();
}
}

18
artisan Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

18
bootstrap/app.php Normal file
View File

@ -0,0 +1,18 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();

2
bootstrap/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

7
bootstrap/providers.php Normal file
View File

@ -0,0 +1,7 @@
<?php
use App\Providers\AppServiceProvider;
return [
AppServiceProvider::class,
];

86
composer.json Normal file
View File

@ -0,0 +1,86 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.50"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

8399
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

126
config/app.php Normal file
View File

@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

117
config/auth.php Normal file
View File

@ -0,0 +1,117 @@
<?php
use App\Models\User;
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

117
config/cache.php Normal file
View File

@ -0,0 +1,117 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane",
| "failover", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
];

184
config/database.php Normal file
View File

@ -0,0 +1,184 @@
<?php
use Illuminate\Support\Str;
use Pdo\Mysql;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSLMODE', 'prefer'),
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];

80
config/filesystems.php Normal file
View File

@ -0,0 +1,80 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

132
config/logging.php Normal file
View File

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

118
config/mail.php Normal file
View File

@ -0,0 +1,118 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')),
],
];

129
config/queue.php Normal file
View File

@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
| "deferred", "background", "failover", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

38
config/services.php Normal file
View File

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

217
config/session.php Normal file
View File

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain without subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

1
database/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.sqlite*

View File

@ -0,0 +1,45 @@
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends Factory<User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View File

@ -0,0 +1,49 @@
<?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('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@ -0,0 +1,35 @@
<?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('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration')->index();
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@ -0,0 +1,57 @@
<?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('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@ -0,0 +1,25 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
}
}

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"$schema": "https://www.schemastore.org/package.json",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"axios": "^1.11.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0",
"tailwindcss": "^4.0.0",
"vite": "^7.0.7"
}
}

36
phpunit.xml Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="DB_URL" value=""/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>

25
public/.htaccess Normal file
View File

@ -0,0 +1,25 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Handle X-XSRF-Token Header
RewriteCond %{HTTP:x-xsrf-token} .
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

0
public/favicon.ico Normal file
View File

20
public/index.php Normal file
View File

@ -0,0 +1,20 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

11
resources/css/app.css Normal file
View File

@ -0,0 +1,11 @@
@import 'tailwindcss';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@source '../**/*.blade.php';
@source '../**/*.js';
@theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}

1
resources/js/app.js Normal file
View File

@ -0,0 +1 @@
import './bootstrap';

4
resources/js/bootstrap.js vendored Normal file
View File

@ -0,0 +1,4 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

View File

@ -0,0 +1,989 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Rumah - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.search-bar {
display: flex;
align-items: center;
background: #f1f5f9;
border-radius: 8px;
padding: 8px 15px;
width: 300px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.9),0 6px 14px rgba(0,0,0,0.08);
border: 1px solid #d0d0d0;
}
.search-bar input {
border: none;
background: transparent;
outline: none;
padding: 5px;
width: 100%;
font-size: 0.95rem;
}
.search-bar i {
color: #64748b;
margin-right: 10px;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Data Tables */
.data-table-container {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
overflow: hidden;
}
.table-header {
padding: 20px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.table-title {
font-size: 1.1rem;
font-weight: 700;
color: #1a365d;
}
.table-actions {
display: flex;
gap: 10px;
}
.btn-action {
padding: 8px 15px;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
}
.btn-primary {
background: var(--primary-blue);
color: white;
}
.btn-primary:hover {
background: #6aa5c6;
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
}
.table-responsive {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
th, td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
word-wrap: break-word;
vertical-align: middle;
}
th {
background: #f8fafc;
font-weight: 600;
color: #1a365d;
font-size: 0.95rem;
position: sticky;
top: 0;
z-index: 10;
vertical-align: middle;
}
tr:hover {
background: #f1f5f9;
}
th:nth-child(1), td:nth-child(1) {
width: 40px;
text-align: center; /* ← Rata tengah horizontal */
vertical-align: middle;
white-space: nowrap; /* ← PENTING! Mencegah angka pecah jadi 2 baris */
word-wrap: normal;
}
th:last-child, td:last-child {
width: 90px;
text-align: center; /* ← Rata tengah horizontal */
vertical-align: middle;
}
.status-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
display: inline-block;
}
.status-tersedia {
background: #d1fae5;
color: #059669;
}
.status-dipesan {
background: #fef3c7;
color: #f59e0b;
}
.status-terjual {
background: #e0f2fe;
color: #2563eb;
}
.category-badge {
padding: 4px 10px;
border-radius: 16px;
font-size: 0.8rem;
font-weight: 500;
display: inline-block;
}
.category-subsidi {
background: #ffedd5;
color: #ea580c;
}
.category-komersial {
background: #dbeafe;
color: #2563eb;
}
.property-badge {
padding: 4px 10px;
border-radius: 16px;
font-size: 0.8rem;
font-weight: 500;
display: inline-block;
}
.property-rumah {
background: #d1fae5;
color: #059669;
}
.property-ruko {
background: #fbcfe8;
color: #be185d;
}
.project-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
display: inline-block;
}
.project-kelapa {
background: #f0fdf4;
color: #166534;
border: 1px solid #bbf7d0;
}
.project-green {
background: #ecfdf5;
color: #047857;
border: 1px solid #a7f3d0;
}
.price {
font-weight: 700;
color: var(--dark-blue);
}
.area {
font-size: 0.9rem;
color: #4a5568;
}
.stock {
font-weight: 600;
color: #1a365d;
}
.action-btn {
padding: 6px 10px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex; /* ← Flex untuk icon + text */
align-items: center;
justify-content: center;
width: 100%; /* ← Lebar penuh dalam container */
min-width: 35px;
}
.action-group{
display: flex;
flex-direction: column; /* ← Ubah jadi kolom (atas-bawah) */
align-items: stretch; /* ← Lebar tombol mengikuti container */
gap: 5px; /* ← Jarak antar tombol */
width: fit-content;
}
.btn-edit {
background: #3b82f6;
color: white;
}
.btn-edit:hover {
background: #2563eb;
}
.btn-delete {
background: #ef4444;
color: white;
}
.btn-delete:hover {
background: #dc2626;
}
.btn-view {
background: #6b7280;
color: white;
}
.btn-view:hover {
background: #4b5563;
}
.block-badge{
padding: 3px 8px;
font-size: 0.75rem;
border-radius: 8px;
background: #ede9fe;
color: #5b21b6;
}
/* Responsive Design */
@media (max-width: 1200px) {
.table-actions {
flex-direction: column;
gap: 8px;
}
.search-bar {
width: 250px;
}
}
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.search-bar {
width: 100%;
}
.page-title {
font-size: 1.3rem;
}
th, td {
padding: 12px 10px;
font-size: 0.85rem;
}
}
@media (max-width: 768px) {
.sidebar {
width: var(--sidebar-width-collapsed) !important;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: var(--sidebar-width-collapsed) !important;
}
.data-table-container {
margin-bottom: 20px;
}
.table-header {
padding: 15px;
}
.table-title {
font-size: 1rem;
}
.btn-action {
padding: 6px 10px;
font-size: 0.8rem;
}
.action-btn {
padding: 4px 8px;
font-size: 0.75rem;
}
th:nth-child(7),
td:nth-child(7),
th:nth-child(8),
td:nth-child(8) {
display: none; /* sembunyi luas */
}
th:nth-child(9),
td:nth-child(9) {
display: none; /* sembunyi stok */
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.1rem;
}
.table-header {
padding: 12px 10px;
}
.table-title {
font-size: 0.95rem;
}
th, td {
padding: 10px 8px;
font-size: 0.8rem;
}
th:nth-child(6), td:nth-child(6),
th:nth-child(7), td:nth-child(7),
th:nth-child(8), td:nth-child(8) {
display: none;
}
.action-btn {
padding: 3px 6px;
font-size: 0.7rem;
}
.status-badge, .category-badge, .property-badge, .project-badge {
padding: 3px 8px;
font-size: 0.75rem;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Carani Bhanu Balakosa</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('data_user') }}"
class="nav-item {{ request()->routeIs('data_user') ? 'active' : '' }}">
<i class="fas fa-users"></i>
<span>Data User</span>
</a>
<a href="{{ route('data_rumah') }}"
class="nav-item {{ request()->routeIs('data_rumah') ? 'active' : '' }}">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</a>
<a href="{{ route('halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('halaman_chatbot') }}"
class="nav-item {{ request()->routeIs('halaman_chatbot') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Chatbot</span>
</a>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<h1 class="page-title">Data Properti</h1>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<form method="GET" action="{{ route('data_rumah') }}" id="searchForm">
<div class="search-bar">
<i class="fas fa-search"></i>
<input type="text" name="search"
placeholder="Cari properti, blok, perumahan..."
value="{{ request('search') }}"
oninput="document.getElementById('searchForm').submit()">
</div>
</form>
<!-- Data Rumah Table -->
<div class="data-table-container">
<div class="table-header">
<div class="table-title">Daftar Properti Tersedia</div>
<div class="table-actions">
<a href="{{ route('tambah-rumah') }}" class="btn-action btn-primary">
<i class="fas fa-plus"></i> Tambah Properti
</a>
</div>
</div>
<div class="table-responsive">
<table>
<thead>
<tr>
<th>No</th>
<th>Nama Properti</th>
<th>Blok</th>
<th>Jenis Properti</th>
<th>Kategori</th>
<th>Tipe</th>
<th>Harga</th>
<th>Luas Bangunan</th>
<th>Luas Tanah</th>
<th>Stok Unit</th>
<th>Status Unit</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@forelse($properti as $p)
@php
if ($p->status_unit == 'tersedia') {
$statusClass = 'status-tersedia';
} elseif ($p->status_unit == 'dipesan') {
$statusClass = 'status-dipesan';
} else {
$statusClass = 'status-terjual';
}
if ($p->perumahan && str_contains(strtolower($p->perumahan->nama_perumahan), 'kelapa')) {
$proyekClass = 'project-kelapa';
} else {
$proyekClass = 'project-green';
}
@endphp
<tr>
<td>
@if($isPaginated)
{{ ($properti->currentPage() - 1) * $properti->perPage() + $loop->iteration }}
@else
{{ $loop->iteration }}
@endif
</td>
<td>
<span class="project-badge {{ $proyekClass }}">
{{ $p->perumahan->nama_perumahan ?? '-' }}
</span>
</td>
<td>
<span class="block-badge">
{{ $p->blok->nama_blok ?? '-' }}
</span>
</td>
<td>
<span class="property-badge property-{{ $p->jenis_properti }}">
{{ ucfirst($p->jenis_properti) }}
</span>
</td>
<td>
<span class="category-badge category-{{ $p->kategori_properti }}">
{{ ucfirst($p->kategori_properti) }}
</span>
</td>
<td>{{ $p->tipe_properti }}</td>
<td>
<span class="price">
Rp {{ number_format($p->harga_properti, 0, ',', '.') }}
</span>
</td>
<td><span class="area">{{ $p->luas_bangunan }} </span></td>
<td><span class="area">{{ $p->luas_tanah }} </span></td>
<td><span class="stock">{{ $p->stok_unit }} unit</span></td>
<td>
<span class="status-badge {{ $statusClass }}">
{{ ucfirst($p->status_unit) }}
</span>
</td>
<td>
<div class="action-group">
<a href="{{ route('edit_rumah', $p->id_properti) }}"
class="action-btn btn-edit">
<i class="fas fa-edit"></i>
</a>
<form method="POST"
action="{{ route('hapus_rumah', $p->id_properti) }}"
style="display:inline;"
onsubmit="return confirm('Yakin hapus properti ini?')">
@csrf
@method('DELETE')
<button type="submit" class="action-btn btn-delete">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="12" style="text-align:center; padding:30px; color:#64748b;">
Belum ada data properti
</td>
</tr>
@endforelse
</tbody>
</table>
<div style="display:flex; justify-content:center; margin-top:20px;">
@if($isPaginated)
{{ $properti->appends(request()->query())->links() }}
@endif
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Add active state to nav items
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', function() {
navItems.forEach(i => i.classList.remove('active'));
this.classList.add('active');
});
});
// Action button functionality
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.querySelector('i').className;
const row = this.closest('tr');
const propertyName = row.querySelector('td:nth-child(2)').textContent;
const propertyType = row.querySelector('td:nth-child(5)').textContent;
if (action.includes('fa-edit')) {
alert(`Mengedit data properti: ${propertyName} - Tipe ${propertyType}`);
} else if (action.includes('fa-eye')) {
alert(`Menampilkan detail properti: ${propertyName} - Tipe ${propertyType}`);
}
});
});
// Add property button
document.querySelector('.btn-primary').addEventListener('click', function() {
alert('Formulir tambah properti akan ditampilkan...');
});
// Export button
document.querySelector('.btn-secondary').addEventListener('click', function() {
alert('Data properti sedang diproses untuk export ke Excel...');
});
// Search functionality
const searchInput = document.querySelector('.search-bar input');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
const text = row.textContent.toLowerCase();
if (text.includes(searchTerm)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
// Statistics update
const stats = {
total: 10,
available: document.querySelectorAll('.status-tersedia').length,
booked: document.querySelectorAll('.status-dipesan').length,
sold: document.querySelectorAll('.status-terjual').length
};
console.log('Statistik Properti:', stats);
});
// Toggle Sidebar
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
sidebar.classList.toggle('collapsed');
mainContent.classList.toggle('expanded');
// Simpan state ke localStorage
localStorage.setItem('sidebarCollapsed', sidebar.classList.contains('collapsed'));
}
// Load sidebar state saat page load
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
}
// Hover effect untuk sidebar (opsional - untuk expand temporary)
sidebar.addEventListener('mouseenter', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-expanded)';
}
});
sidebar.addEventListener('mouseleave', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-collapsed)';
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,790 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data User - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.search-bar {
display: flex;
align-items: center;
background: #f1f5f9;
border-radius: 8px;
padding: 8px 15px;
width: 300px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.9),0 6px 14px rgba(0,0,0,0.08);
border: 1px solid #d0d0d0;
}
.search-bar input {
border: none;
background: transparent;
outline: none;
padding: 5px;
width: 100%;
font-size: 0.95rem;
}
.search-bar i {
color: #64748b;
margin-right: 10px;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Data Tables */
.data-table-container {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
overflow: hidden;
}
.table-header {
padding: 20px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.table-title {
font-size: 1.1rem;
font-weight: 700;
color: #1a365d;
}
.table-actions {
display: flex;
gap: 10px;
}
.btn-action {
padding: 8px 15px;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
}
.btn-primary {
background: var(--primary-blue);
color: white;
}
.btn-primary:hover {
background: #6aa5c6;
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
}
.table-responsive {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
th {
background: #f8fafc;
font-weight: 600;
color: #1a365d;
font-size: 0.95rem;
position: sticky;
top: 0;
z-index: 10;
}
tr:hover {
background: #f1f5f9;
}
.role-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
display: inline-block;
}
.role-admin {
background: #dbeafe;
color: #2563eb;
}
.role-user {
background: #d1fae5;
color: #059669;
}
.action-btn {
padding: 6px 12px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.3s ease;
margin-right: 5px;
}
.btn-edit {
background: #3b82f6;
color: white;
text-decoration: none;
}
.btn-edit:hover {
background: #2563eb;
}
.btn-delete {
background: #ef4444;
color: white;
}
.btn-delete:hover {
background: #dc2626;
}
.btn-view {
background: #6b7280;
color: white;
}
.btn-view:hover {
background: #4b5563;
}
.icon-btn {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f1f5f9;
color: #64748b;
cursor: pointer;
transition: all 0.3s ease;
}
.icon-btn:hover {
background: #e2e8f0;
color: #1a365d;
}
/* Responsive Design */
@media (max-width: 1200px) {
.table-actions {
flex-direction: column;
gap: 8px;
}
.search-bar {
width: 250px;
}
}
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.search-bar {
width: 100%;
}
.page-title {
font-size: 1.3rem;
}
th, td {
padding: 12px 10px;
font-size: 0.85rem;
}
}
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: 60px;
}
.data-table-container {
margin-bottom: 20px;
}
.table-header {
padding: 15px;
}
.table-title {
font-size: 1rem;
}
.btn-action {
padding: 6px 10px;
font-size: 0.8rem;
}
.action-btn {
padding: 4px 8px;
font-size: 0.75rem;
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.1rem;
}
.table-header {
padding: 12px 10px;
}
.table-title {
font-size: 0.95rem;
}
th, td {
padding: 10px 8px;
font-size: 0.8rem;
}
.action-btn {
padding: 3px 6px;
font-size: 0.7rem;
}
.role-badge {
padding: 3px 8px;
font-size: 0.75rem;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Properti Harmoni</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('data_user') }}"
class="nav-item {{ request()->routeIs('data_user') ? 'active' : '' }}">
<i class="fas fa-users"></i>
<span>Data User</span>
</a>
<a href="{{ route('data_rumah') }}"
class="nav-item {{ request()->routeIs('data_rumah') ? 'active' : '' }}">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</a>
<a href="{{ route('halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('halaman_chatbot') }}"
class="nav-item {{ request()->routeIs('halaman_chatbot') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Chatbot</span>
</a>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<h1 class="page-title">Data User</h1>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<form method="GET" action="{{ route('data_user') }}" id="searchForm">
<div class="search-bar">
<i class="fas fa-search"></i>
<input type="text" name="search"
placeholder="Cari data user..."
value="{{ request('search') }}"
oninput="document.getElementById('searchForm').submit()">
</div>
</form>
<!-- Data User Table -->
<div class="data-table-container">
<div class="table-header">
<div class="table-title">Daftar Pengguna Sistem</div>
<div class="table-actions">
<a href="{{ route('tambah-datauser') }}" class="btn-action btn-primary">
<i class="fas fa-plus"></i> Tambah User
</a>
</div>
</div>
@if(session('success'))
<div style="background:#d1fae5; color:#059669; padding:12px;
border-radius:8px; margin-bottom:15px;">
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
</div>
@endif
<div class="table-responsive">
<table>
<thead>
<tr>
<th>No</th>
<th>Nama User</th>
<th>No HP</th>
<th>Email</th>
<th>Role</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@forelse($users as $user)
<tr>
<td>
@if($isPaginated)
{{ ($users->currentPage() - 1) * $users->perPage() + $loop->iteration }}
@else
{{ $loop->iteration }}
@endif
</td>
<td>{{ $user->nama_user }}</td>
<td>{{ $user->no_hp }}</td>
<td>{{ $user->email_user }}</td>
<td>
@if($user->role_user == 'admin')
<span class="role-badge role-admin">Admin</span>
@else
<span class="role-badge role-user">Pengguna</span>
@endif
</td>
<td>
<a href="{{ route('edit_user', $user->id_user) }}" class="action-btn btn-edit">
<i class="fas fa-edit"></i> Edit
</a>
<form method="POST"
action="{{ route('admin.user.hapus', $user->id_user) }}"
style="display:inline;"
onsubmit="return confirm('Yakin hapus user {{ $user->nama_user }}?')">
@csrf
@method('DELETE')
<button type="submit" class="action-btn btn-delete">
<i class="fas fa-trash"></i> Hapus
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="6" style="text-align:center; padding:30px; color:#64748b;">
Belum ada user
</td>
</tr>
@endforelse
</tbody>
</table>
<div style="display:flex; justify-content:center; margin-top:20px;">
@if($isPaginated)
{{ $users->links() }}
@endif
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Action button functionality
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.querySelector('i').className;
const row = this.closest('tr');
const userName = row.querySelector('td:nth-child(2)').textContent;
if (action.includes('fa-edit')) {
alert(`Mengedit data user: ${userName}`);
} else if (action.includes('fa-trash')) {
if (confirm(`Apakah Anda yakin ingin menghapus user: ${userName}?`)) {
row.remove();
alert(`User ${userName} berhasil dihapus.`);
}
}
});
});
// Add user button
document.querySelector('.btn-primary').addEventListener('click', function() {
alert('Formulir tambah user akan ditampilkan...');
});
// Export button
document.querySelector('.btn-secondary').addEventListener('click', function() {
alert('Data user sedang diproses untuk export ke Excel...');
});
// Search functionality
});
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
}
// Hover effect untuk sidebar (opsional - untuk expand temporary)
sidebar.addEventListener('mouseenter', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-expanded)';
}
});
sidebar.addEventListener('mouseleave', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-collapsed)';
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,916 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Data Rumah - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
color: #64748b;
}
.breadcrumb i {
font-size: 12px;
}
.breadcrumb-link {
color: var(--primary-blue);
text-decoration: none;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Edit Form Styles */
.edit-form-container {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 30px;
max-width: 800px;
margin: 0 auto;
}
.form-header {
text-align: center;
margin-bottom: 30px;
}
.form-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.form-subtitle {
color: #64748b;
font-size: 1rem;
}
.form-section {
margin-bottom: 30px;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title i {
color: var(--primary-blue);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #1a365d;
font-size: 0.95rem;
}
.form-control {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(var(--primary-blue-rgb), 0.2);
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
/* Select styling */
select.form-control {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2364748b' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.48 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 16px center;
background-size: 16px;
padding-right: 40px;
}
/* Image upload section */
.image-upload-section {
background: #f8fafc;
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid #e2e8f0;
}
.upload-area {
border: 2px dashed #cbd5e0;
border-radius: 12px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 15px;
}
.upload-area:hover {
border-color: var(--primary-blue);
background: #f0f9ff;
}
.upload-icon {
font-size: 3rem;
color: var(--primary-blue);
margin-bottom: 15px;
}
.upload-text {
font-size: 1rem;
color: #4a5568;
margin-bottom: 10px;
}
.upload-hint {
font-size: 0.85rem;
color: #94a3b8;
}
.file-input {
display: none;
}
.file-name {
margin-top: 10px;
font-size: 0.9rem;
color: var(--primary-blue);
font-weight: 500;
}
/* Action buttons */
.form-actions {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
padding-top: 25px;
border-top: 1px solid #f1f5f9;
}
.btn-action {
padding: 12px 35px;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
}
.btn-save {
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
color: white;
}
.btn-save:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
}
.btn-cancel {
background: #e2e8f0;
color: #4a5568;
}
.btn-cancel:hover {
background: #cbd5e0;
transform: translateY(-2px);
}
/* Responsive Design */
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.page-title {
font-size: 1.3rem;
}
.form-row {
grid-template-columns: 1fr;
}
.edit-form-container {
padding: 20px;
}
}
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: 60px;
}
.form-title {
font-size: 1.5rem;
}
.btn-action {
padding: 10px 25px;
font-size: 0.95rem;
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.2rem;
}
.form-title {
font-size: 1.4rem;
}
.section-title {
font-size: 1.1rem;
}
.form-actions {
flex-direction: column;
}
.btn-action {
width: 100%;
justify-content: center;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Properti Harmoni</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('data_user') }}"
class="nav-item {{ request()->routeIs('data_user') ? 'active' : '' }}">
<i class="fas fa-users"></i>
<span>Data User</span>
</a>
<a href="{{ route('data_rumah') }}"
class="nav-item {{ request()->routeIs('data_rumah') ? 'active' : '' }}">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</a>
<a href="{{ route('halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('halaman_chatbot') }}"
class="nav-item {{ request()->routeIs('halaman_chatbot') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Chatbot</span>
</a>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<div>
<h1 class="page-title">Edit Data Rumah</h1>
<div class="breadcrumb">
<a href="dashboard.html" class="breadcrumb-link">Dashboard</a>
<i class="fas fa-chevron-right"></i>
<a href="data-rumah.html" class="breadcrumb-link">Data Rumah</a>
<i class="fas fa-chevron-right"></i>
<span>Edit Data Rumah</span>
</div>
</div>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<!-- Edit Form -->
<div class="edit-form-container">
<div class="form-header">
<h2 class="form-title">Edit Data Properti</h2>
<p class="form-subtitle">Perbarui informasi properti dengan data yang valid</p>
</div>
<form id="editPropertyForm">
<!-- Property Information Section -->
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-home"></i> Informasi Dasar Properti
</h3>
<div class="form-row">
<div class="form-group">
<label for="namaProperti" class="form-label">Nama Properti</label>
<input type="text" class="form-control" id="namaProperti" value="Kelapa Gading Regency" required>
</div>
<div class="form-group">
<label for="jenisProperti" class="form-label">Jenis Properti</label>
<select class="form-control" id="jenisProperti" required>
<option value="">Pilih Jenis Properti</option>
<option value="rumah" selected>Rumah</option>
<option value="ruko">Ruko</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="kategoriProperti" class="form-label">Kategori Properti</label>
<select class="form-control" id="kategoriProperti" required>
<option value="">Pilih Kategori</option>
<option value="subsidi" selected>Subsidi</option>
<option value="komersial">Komersial</option>
</select>
</div>
<div class="form-group">
<label for="tipeProperti" class="form-label">Tipe Properti</label>
<select class="form-control" id="tipeProperti" required>
<option value="">Pilih Tipe</option>
<option value="30/60">30/60</option>
<option value="36/72" selected>36/72</option>
<option value="45/84">45/84</option>
<option value="60/135">60/135</option>
<option value="ruko">Ruko</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group" style="grid-column: 1 / -1;">
<label for="blokRumah" class="form-label">Blok Rumah</label>
<input type="text" class="form-control" id="blokRumah" placeholder="Contoh: Blok A, Cluster Bunga" value="Blok C">
</div>
</div>
</div>
<!-- Price & Size Section -->
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-tag"></i> Detail Harga & Ukuran
</h3>
<div class="form-row">
<div class="form-group">
<label for="hargaProperti" class="form-label">Harga Properti (Rp)</label>
<input type="number" class="form-control" id="hargaProperti" value="450000000" min="0" required>
</div>
<div class="form-group">
<label for="luasBangunan" class="form-label">Luas Bangunan ()</label>
<input type="number" class="form-control" id="luasBangunan" value="36" min="0" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="luasTanah" class="form-label">Luas Tanah ()</label>
<input type="number" class="form-control" id="luasTanah" value="72" min="0" required>
</div>
<div class="form-group">
<label for="stokUnit" class="form-label">Stok Unit</label>
<input type="number" class="form-control" id="stokUnit" value="8" min="0" required>
</div>
</div>
</div>
<!-- Status Section -->
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-boxes"></i> Status Unit
</h3>
<div class="form-group">
<label for="statusUnit" class="form-label">Status Unit</label>
<select class="form-control" id="statusUnit" required>
<option value="">Pilih Status</option>
<option value="tersedia">Tersedia</option>
<option value="dipesan" selected>Dipesan</option>
<option value="terjual">Terjual</option>
</select>
</div>
</div>
<!-- Image Upload Section -->
<div class="image-upload-section">
<h3 class="section-title">
<i class="fas fa-image"></i> Gambar Properti
</h3>
<p class="form-subtitle">Upload gambar terbaru untuk properti ini (opsional)</p>
<div class="upload-area" id="imageUpload">
<i class="fas fa-cloud-upload-alt upload-icon"></i>
<p class="upload-text">Klik atau drag file gambar ke sini</p>
<p class="upload-hint">Format: JPG, PNG | Max: 5MB | Maksimal 5 file</p>
<input type="file" class="file-input" id="propertyImage" accept=".jpg,.jpeg,.png" multiple>
<div class="file-name" id="imageName">kelapa-gading-36-72.jpg</div>
</div>
</div>
<!-- Action Buttons -->
<div class="form-actions">
<button type="button" class="btn-action btn-cancel" id="cancelBtn">
<i class="fas fa-times"></i> Batal
</button>
<button type="submit" class="btn-action btn-save">
<i class="fas fa-save"></i> Simpan Perubahan
</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Image upload handling
const uploadArea = document.getElementById('imageUpload');
const fileInput = document.getElementById('propertyImage');
const fileName = document.getElementById('imageName');
uploadArea.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
if (this.files.length > 1) {
fileName.textContent = `${this.files.length} file dipilih`;
} else {
fileName.textContent = this.files[0].name;
}
const rootStyles = getComputedStyle(document.documentElement);
const primaryColor = rootStyles.getPropertyValue('--primary-blue');
fileName.style.color = primaryColor;
}
});
// Form submission
document.getElementById('editPropertyForm').addEventListener('submit', function(e) {
e.preventDefault();
// Get form values
const namaProperti = document.getElementById('namaProperti').value.trim();
const jenisProperti = document.getElementById('jenisProperti').value;
const kategoriProperti = document.getElementById('kategoriProperti').value;
const tipeProperti = document.getElementById('tipeProperti').value;
const blokRumah = document.getElementById('blokRumah').value.trim();
const hargaProperti = document.getElementById('hargaProperti').value.trim();
const luasBangunan = document.getElementById('luasBangunan').value.trim();
const luasTanah = document.getElementById('luasTanah').value.trim();
const stokUnit = document.getElementById('stokUnit').value.trim();
const statusUnit = document.getElementById('statusUnit').value;
// Simple validation
if (!namaProperti) {
alert('Nama properti harus diisi');
document.getElementById('namaProperti').focus();
return;
}
if (!jenisProperti) {
alert('Jenis properti harus dipilih');
document.getElementById('jenisProperti').focus();
return;
}
if (!kategoriProperti) {
alert('Kategori properti harus dipilih');
document.getElementById('kategoriProperti').focus();
return;
}
if (!tipeProperti) {
alert('Tipe properti harus dipilih');
document.getElementById('tipeProperti').focus();
return;
}
if (!hargaProperti || isNaN(hargaProperti) || parseInt(hargaProperti) <= 0) {
alert('Harga properti harus diisi dan berupa angka positif');
document.getElementById('hargaProperti').focus();
return;
}
if (!luasBangunan || isNaN(luasBangunan) || parseInt(luasBangunan) <= 0) {
alert('Luas bangunan harus diisi dan berupa angka positif');
document.getElementById('luasBangunan').focus();
return;
}
if (!luasTanah || isNaN(luasTanah) || parseInt(luasTanah) <= 0) {
alert('Luas tanah harus diisi dan berupa angka positif');
document.getElementById('luasTanah').focus();
return;
}
if (!stokUnit || isNaN(stokUnit) || parseInt(stokUnit) < 0) {
alert('Stok unit harus diisi dan berupa angka non-negatif');
document.getElementById('stokUnit').focus();
return;
}
if (!statusUnit) {
alert('Status unit harus dipilih');
document.getElementById('statusUnit').focus();
return;
}
// Success message
alert('Data properti berhasil diperbarui!\n\nBlok Rumah: ' + (blokRumah || 'Tidak diisi'));
// In a real Laravel application, you would submit the form data to the server here
// window.location.href = 'data-rumah.html';
});
// Cancel button
document.getElementById('cancelBtn').addEventListener('click', function() {
if (confirm('Apakah Anda yakin ingin membatalkan perubahan? Semua perubahan yang belum disimpan akan hilang.')) {
// Redirect to data rumah page
window.location.href = 'data-rumah.html';
}
});
});
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
}
// Hover effect untuk sidebar (opsional - untuk expand temporary)
sidebar.addEventListener('mouseenter', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-expanded)';
}
});
sidebar.addEventListener('mouseleave', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-collapsed)';
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,747 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Data User - PropertiHarmoni</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>
:root{
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
color: #64748b;
}
.breadcrumb i {
font-size: 12px;
}
.breadcrumb-link {
color: var(--primary-blue);
text-decoration: none;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Edit Form Styles */
.edit-form-container {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 30px;
max-width: 600px;
margin: 0 auto;
}
.form-header {
text-align: center;
margin-bottom: 30px;
}
.form-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.form-subtitle {
color: #64748b;
font-size: 1rem;
}
.form-section {
margin-bottom: 30px;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title i {
color: var(--primary-blue);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #1a365d;
font-size: 0.95rem;
}
.form-control {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(var(--primary-blue-rgb), 0.2);
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
/* Select styling */
select.form-control {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2364748b' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.48 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 16px center;
background-size: 16px;
padding-right: 40px;
}
/* Radio buttons */
.radio-group {
display: flex;
gap: 25px;
margin-top: 8px;
}
.radio-option {
display: flex;
align-items: center;
gap: 8px;
}
.radio-input {
width: 18px;
height: 18px;
accent-color: var(--primary-blue);
}
.radio-label {
font-weight: 500;
color: #1a365d;
}
/* Password container */
.password-container {
position: relative;
}
.toggle-password {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #64748b;
cursor: pointer;
font-size: 16px;
}
/* Action buttons */
.form-actions {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
padding-top: 25px;
border-top: 1px solid #f1f5f9;
}
.btn-action {
padding: 12px 35px;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
}
.btn-save {
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
color: white;
}
.btn-save:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
}
.btn-cancel {
background: #e2e8f0;
color: #4a5568;
}
.btn-cancel:hover {
background: #cbd5e0;
transform: translateY(-2px);
}
/* Responsive Design */
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.page-title {
font-size: 1.3rem;
}
.edit-form-container {
padding: 20px;
}
}
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: 60px;
}
.form-title {
font-size: 1.5rem;
}
.btn-action {
padding: 10px 25px;
font-size: 0.95rem;
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.2rem;
}
.form-title {
font-size: 1.4rem;
}
.section-title {
font-size: 1.1rem;
}
.form-actions {
flex-direction: column;
}
.btn-action {
width: 100%;
justify-content: center;
}
.radio-group {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Carani Bhanu Balakosa</div>
</div>
<div class="nav-menu">
<a href="data_user.html">
<div class="nav-item">
<i class="fas fa-users"></i>
<span>Data User</span>
</div>
</a>
<a href="data_rumah.html">
<div class="nav-item active">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</div>
</a>
<a href="verfikasi_data.html">
<div class="nav-item">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</div>
</a>
<a href="halaman_chatbot.html">
<div class="nav-item">
<i class="fas fa-comments"></i>
<span>ChatBot</span>
</div>
</a>
</div>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<div>
<h1 class="page-title">Edit Data User</h1>
<div class="breadcrumb">
<a href="#" class="breadcrumb-link">Dashboard</a>
<i class="fas fa-chevron-right"></i>
<a href="#" class="breadcrumb-link">Data User</a>
<i class="fas fa-chevron-right"></i>
<span>Edit Data User</span>
</div>
</div>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<!-- Edit Form -->
<div class="edit-form-container">
<div class="form-header">
<h2 class="form-title">Edit Data Pengguna</h2>
<p class="form-subtitle">Perbarui informasi pengguna dengan data yang valid</p>
</div>
@if(session('success'))
<div style="background:#d1fae5; color:#059669; padding:12px; border-radius:8px; margin-bottom:15px;">
{{ session('success') }}
</div>
@endif
<form method="POST" action="{{ route('edit_user.update', $user->id_user) }}">
@csrf
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-user-edit"></i> Data Pengguna
</h3>
<div class="form-group">
<label class="form-label">Nama Lengkap</label>
<input type="text" class="form-control"
name="nama_user"
value="{{ $user->nama_user }}" required>
</div>
<div class="form-group">
<label class="form-label">No HP</label>
<input type="text" class="form-control"
name="no_hp"
value="{{ $user->no_hp }}">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" class="form-control"
name="email_user"
value="{{ $user->email_user }}" required>
</div>
<div class="form-group">
<label class="form-label">
Password Baru
<small style="color:#94a3b8;">(kosongkan jika tidak ingin mengubah)</small>
</label>
<div class="password-container">
<input type="password" class="form-control"
id="password" name="password_user"
placeholder="Isi jika ingin ganti password">
<button type="button" class="toggle-password" id="togglePassword">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Role</label>
<div class="radio-group">
<div class="radio-option">
<input type="radio" name="role_user" id="role-customer"
value="customer"
{{ $user->role_user == 'customer' ? 'checked' : '' }}>
<label for="role-customer" class="radio-label">Pengguna</label>
</div>
<div class="radio-option">
<input type="radio" name="role_user" id="role-admin"
value="admin"
{{ $user->role_user == 'admin' ? 'checked' : '' }}>
<label for="role-admin" class="radio-label">Admin</label>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="form-actions">
<a href="{{ route('data_user') }}" class="btn-action btn-cancel">
<i class="fas fa-times"></i> Batal
</a>
<button type="submit" class="btn-action btn-save">
<i class="fas fa-save"></i> Simpan Perubahan
</button>
</div>
</form>
</div>
</div>
<script>
// Toggle password - hanya 1 kali
document.getElementById('togglePassword').addEventListener('click', function() {
const pwd = document.getElementById('password');
const icon = this.querySelector('i');
if (pwd.type === 'password') {
pwd.type = 'text';
icon.classList.replace('fa-eye', 'fa-eye-slash');
} else {
pwd.type = 'password';
icon.classList.replace('fa-eye-slash', 'fa-eye');
}
});
// Sidebar collapse
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
if (!sidebar || !mainContent) return;
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
}
sidebar.addEventListener('mouseenter', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-expanded)';
}
});
sidebar.addEventListener('mouseleave', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-collapsed)';
}
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,800 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tambah Data User - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
color: #64748b;
}
.breadcrumb i {
font-size: 12px;
}
.breadcrumb-link {
color: var(--primary-blue);
text-decoration: none;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Add User Form Styles */
.add-user-container {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 30px;
max-width: 700px;
margin: 0 auto;
}
.form-header {
text-align: center;
margin-bottom: 30px;
}
.form-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.form-subtitle {
color: #64748b;
font-size: 1rem;
}
.form-section {
margin-bottom: 30px;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title i {
color: var(--primary-blue);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #1a365d;
font-size: 0.95rem;
}
.form-control {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(var(--primary-blue-rgb), 0.2);
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
/* Select styling */
select.form-control {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2364748b' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.48 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 16px center;
background-size: 16px;
padding-right: 40px;
}
/* Password container */
.password-container {
position: relative;
}
.toggle-password {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #64748b;
cursor: pointer;
font-size: 16px;
}
.toggle-password:hover {
color: var(--primary-blue);
}
/* Action buttons */
.form-actions {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
padding-top: 25px;
border-top: 1px solid #f1f5f9;
}
.btn-action {
padding: 12px 35px;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
}
.btn-save {
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
color: white;
}
.btn-save:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
}
.btn-cancel {
background: #e2e8f0;
color: #4a5568;
}
.btn-cancel:hover {
background: #cbd5e0;
transform: translateY(-2px);
}
/* Required indicator */
.required {
color: #ef4444;
margin-left: 4px;
}
/* Responsive Design */
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.page-title {
font-size: 1.3rem;
}
.form-row {
grid-template-columns: 1fr;
}
.add-user-container {
padding: 20px;
}
}
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: 60px;
}
.form-title {
font-size: 1.5rem;
}
.btn-action {
padding: 10px 25px;
font-size: 0.95rem;
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.2rem;
}
.form-title {
font-size: 1.4rem;
}
.section-title {
font-size: 1.1rem;
}
.form-actions {
flex-direction: column;
}
.btn-action {
width: 100%;
justify-content: center;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Carani Bhanu Balakosa</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('data_user') }}"
class="nav-item {{ request()->routeIs('data_user') ? 'active' : '' }}">
<i class="fas fa-users"></i>
<span>Data User</span>
</a>
<a href="{{ route('data_rumah') }}"
class="nav-item {{ request()->routeIs('data_rumah') ? 'active' : '' }}">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</a>
<a href="{{ route('halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('halaman_chatbot') }}"
class="nav-item {{ request()->routeIs('halaman_chatbot') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Chatbot</span>
</a>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<div>
<h1 class="page-title">Tambah Data User</h1>
<div class="breadcrumb">
<a href="dashboard.html" class="breadcrumb-link">Dashboard</a>
<i class="fas fa-chevron-right"></i>
<a href="data-user.html" class="breadcrumb-link">Data User</a>
<i class="fas fa-chevron-right"></i>
<span>Tambah Data User</span>
</div>
</div>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<!-- Add User Form -->
<div class="add-user-container">
<div class="form-header">
<h2 class="form-title">Form Tambah User</h2>
<p class="form-subtitle">Isi data user baru dengan lengkap dan valid</p>
</div>
<form method="POST" action="{{ route('tambah-datauser.simpan') }}">
@csrf
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-user-plus"></i> Data User
</h3>
@if($errors->any())
<div style="background:#fecaca; color:#dc2626; padding:12px; border-radius:8px; margin-bottom:15px;">
{{ $errors->first() }}
</div>
@endif
<div class="form-row">
<div class="form-group">
<label class="form-label">Nama Lengkap <span class="required">*</span></label>
<input type="text" class="form-control"
name="nama_user"
value="{{ old('nama_user') }}"
placeholder="Masukkan nama lengkap" required>
</div>
<div class="form-group">
<label class="form-label">Email <span class="required">*</span></label>
<input type="email" class="form-control"
name="email_user"
value="{{ old('email_user') }}"
placeholder="Masukkan email" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Nomor HP <span class="required">*</span></label>
<input type="tel" class="form-control"
name="no_hp"
value="{{ old('no_hp') }}"
placeholder="Contoh: 081234567890" required>
</div>
<div class="form-group">
<label class="form-label">Password <span class="required">*</span></label>
<div class="password-container">
<input type="password" class="form-control"
id="password" name="password_user"
placeholder="Buat password yang kuat" required>
<button type="button" class="toggle-password" id="togglePassword">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">Role <span class="required">*</span></label>
<select class="form-control" name="role_user" required>
<option value="">Pilih Role User</option>
<option value="admin" {{ old('role_user') == 'admin' ? 'selected' : '' }}>Admin</option>
<option value="customer" {{ old('role_user') == 'customer' ? 'selected' : '' }}>Pengguna</option>
</select>
</div>
</div>
<!-- Action Buttons -->
<div class="form-actions">
<a href="{{ route('data_user') }}" class="btn-action btn-cancel">
<i class="fas fa-times"></i> Batal
</a>
<button type="submit" class="btn-action btn-save">
<i class="fas fa-save"></i> Tambah User
</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Toggle sidebar on small screens
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
// Add hover effect for desktop
if (window.innerWidth > 992) {
sidebar.addEventListener('mouseenter', function() {
this.style.width = '300px';
mainContent.style.marginLeft = '300px';
});
sidebar.addEventListener('mouseleave', function() {
this.style.width = 'var(--sidebar-width)';
mainContent.style.marginLeft = 'var(--sidebar-width)';
});
}
// Add active state to nav items
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', function() {
navItems.forEach(i => i.classList.remove('active'));
this.classList.add('active');
});
});
// Password toggle functionality
document.getElementById('togglePassword').addEventListener('click', function() {
const passwordInput = document.getElementById('password');
const icon = this.querySelector('i');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
passwordInput.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
// Form submission
document.getElementById('addUserForm').addEventListener('submit', function(e) {
e.preventDefault();
// Get form values
const fullName = document.getElementById('fullName').value.trim();
const email = document.getElementById('email').value.trim();
const phone = document.getElementById('phone').value.trim();
const password = document.getElementById('password').value.trim();
const role = document.getElementById('role').value;
// Simple validation
if (!fullName || !email || !phone || !password || !role) {
alert('Mohon lengkapi semua field yang wajib diisi.');
return;
}
// Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('Format email tidak valid');
document.getElementById('email').focus();
return;
}
// Phone format validation
if (!/^\d{10,13}$/.test(phone.replace(/\D/g, ''))) {
alert('Nomor HP harus 10-13 digit angka');
document.getElementById('phone').focus();
return;
}
// Password strength validation
if (password.length < 8) {
alert('Password minimal harus 8 karakter');
document.getElementById('password').focus();
return;
}
// Success message
alert(`User ${fullName} berhasil ditambahkan sebagai ${role === 'admin' ? 'Admin' : 'Pengguna'}!`);
// Reset form
this.reset();
// In a real Laravel application, you would submit the form data to the server here
// window.location.href = 'data-user.html';
});
// Cancel button
document.getElementById('cancelBtn').addEventListener('click', function() {
if (confirm('Apakah Anda yakin ingin membatalkan penambahan user? Semua data yang telah diisi akan hilang.')) {
// Redirect to data user page
window.location.href = 'data-user.html';
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,927 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tambah Data Rumah - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
color: #64748b;
}
.breadcrumb i {
font-size: 12px;
}
.breadcrumb-link {
color: var(--primary-blue);
text-decoration: none;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Add Property Form Styles */
.add-property-container {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 30px;
max-width: 800px;
margin: 0 auto;
}
.form-header {
text-align: center;
margin-bottom: 30px;
}
.form-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.form-subtitle {
color: #64748b;
font-size: 1rem;
}
.form-section {
margin-bottom: 30px;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title i {
color: var(--primary-blue);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #1a365d;
font-size: 0.95rem;
}
.form-control {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(var(--primary-blue-rgb), 0.2);
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
/* Select styling */
select.form-control {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2364748b' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.48 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 16px center;
background-size: 16px;
padding-right: 40px;
}
/* Image upload section */
.image-upload-section {
background: #f8fafc;
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid #e2e8f0;
}
.upload-area {
border: 2px dashed #cbd5e0;
border-radius: 12px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 15px;
}
.upload-area:hover {
border-color: var(--primary-blue);
background: #f0f9ff;
}
.upload-icon {
font-size: 3rem;
color: var(--primary-blue);
margin-bottom: 15px;
}
.upload-text {
font-size: 1rem;
color: #4a5568;
margin-bottom: 10px;
}
.upload-hint {
font-size: 0.85rem;
color: #94a3b8;
}
.file-input {
display: none;
}
.file-name {
margin-top: 10px;
font-size: 0.9rem;
color: var(--primary-blue);
font-weight: 500;
}
/* Action buttons */
.form-actions {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
padding-top: 25px;
border-top: 1px solid #f1f5f9;
}
.btn-action {
padding: 12px 35px;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
}
.btn-save {
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
color: white;
}
.btn-save:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
}
.btn-cancel {
background: #e2e8f0;
color: #4a5568;
}
.btn-cancel:hover {
background: #cbd5e0;
transform: translateY(-2px);
}
/* Required indicator */
.required {
color: #ef4444;
margin-left: 4px;
}
/* Responsive Design */
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.page-title {
font-size: 1.3rem;
}
.form-row {
grid-template-columns: 1fr;
}
.add-property-container {
padding: 20px;
}
}
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: 60px;
}
.form-title {
font-size: 1.5rem;
}
.btn-action {
padding: 10px 25px;
font-size: 0.95rem;
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.2rem;
}
.form-title {
font-size: 1.4rem;
}
.section-title {
font-size: 1.1rem;
}
.form-actions {
flex-direction: column;
}
.btn-action {
width: 100%;
justify-content: center;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Properti Harmoni</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('data_user') }}"
class="nav-item {{ request()->routeIs('data_user') ? 'active' : '' }}">
<i class="fas fa-users"></i>
<span>Data User</span>
</a>
<a href="{{ route('data_rumah') }}"
class="nav-item {{ request()->routeIs('data_rumah') ? 'active' : '' }}">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</a>
<a href="{{ route('halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('halaman_chatbot') }}"
class="nav-item {{ request()->routeIs('halaman_chatbot') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Chatbot</span>
</a>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<div>
<h1 class="page-title">Tambah Data Rumah</h1>
<div class="breadcrumb">
<a href="dashboard.html" class="breadcrumb-link">Dashboard</a>
<i class="fas fa-chevron-right"></i>
<a href="data-rumah.html" class="breadcrumb-link">Data Rumah</a>
<i class="fas fa-chevron-right"></i>
<span>Tambah Data Rumah</span>
</div>
</div>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<!-- Add Property Form -->
<div class="add-property-container">
<div class="form-header">
<h2 class="form-title">Form Tambah Properti</h2>
<p class="form-subtitle">Isi data properti baru dengan lengkap dan valid</p>
</div>
<form id="addPropertyForm">
<!-- Property Information Section -->
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-home"></i> Informasi Dasar Properti
</h3>
<div class="form-row">
<div class="form-group">
<label for="namaProperti" class="form-label">Nama Properti <span class="required">*</span></label>
<input type="text" class="form-control" id="namaProperti" placeholder="Contoh: Kelapa Gading Regency" required>
</div>
<div class="form-group">
<label for="jenisProperti" class="form-label">Jenis Properti <span class="required">*</span></label>
<select class="form-control" id="jenisProperti" required>
<option value="">Pilih Jenis Properti</option>
<option value="rumah">Rumah</option>
<option value="ruko">Ruko</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="kategoriProperti" class="form-label">Kategori Properti <span class="required">*</span></label>
<select class="form-control" id="kategoriProperti" required>
<option value="">Pilih Kategori</option>
<option value="subsidi">Subsidi</option>
<option value="komersial">Komersial</option>
</select>
</div>
<div class="form-group">
<label for="tipeProperti" class="form-label">Tipe Properti <span class="required">*</span></label>
<select class="form-control" id="tipeProperti" required>
<option value="">Pilih Tipe</option>
<option value="30/60">30/60</option>
<option value="36/72">36/72</option>
<option value="45/84">45/84</option>
<option value="60/135">60/135</option>
<option value="ruko">Ruko</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group" style="grid-column: 1 / -1;">
<label for="blokRumah" class="form-label">Blok Rumah (Opsional)</label>
<input type="text" class="form-control" id="blokRumah" placeholder="Contoh: Blok A, Cluster Bunga">
</div>
</div>
</div>
<!-- Price & Size Section -->
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-tag"></i> Detail Harga & Ukuran
</h3>
<div class="form-row">
<div class="form-group">
<label for="hargaProperti" class="form-label">Harga Properti (Rp) <span class="required">*</span></label>
<input type="number" class="form-control" id="hargaProperti" placeholder="Contoh: 450000000" min="0" required>
</div>
<div class="form-group">
<label for="luasBangunan" class="form-label">Luas Bangunan () <span class="required">*</span></label>
<input type="number" class="form-control" id="luasBangunan" placeholder="Contoh: 36" min="0" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="luasTanah" class="form-label">Luas Tanah () <span class="required">*</span></label>
<input type="number" class="form-control" id="luasTanah" placeholder="Contoh: 72" min="0" required>
</div>
<div class="form-group">
<label for="stokUnit" class="form-label">Stok Unit <span class="required">*</span></label>
<input type="number" class="form-control" id="stokUnit" placeholder="Contoh: 8" min="0" required>
</div>
</div>
</div>
<!-- Status Section -->
<div class="form-section">
<h3 class="section-title">
<i class="fas fa-boxes"></i> Status Unit
</h3>
<div class="form-group">
<label for="statusUnit" class="form-label">Status Unit <span class="required">*</span></label>
<select class="form-control" id="statusUnit" required>
<option value="">Pilih Status</option>
<option value="tersedia">Tersedia</option>
<option value="dipesan">Dipesan</option>
<option value="terjual">Terjual</option>
</select>
</div>
</div>
<!-- Image Upload Section -->
<div class="image-upload-section">
<h3 class="section-title">
<i class="fas fa-image"></i> Gambar Properti
</h3>
<p class="form-subtitle">Upload gambar properti (opsional, maksimal 5 file)</p>
<div class="upload-area" id="imageUpload">
<i class="fas fa-cloud-upload-alt upload-icon"></i>
<p class="upload-text">Klik atau drag file gambar ke sini</p>
<p class="upload-hint">Format: JPG, PNG | Max: 5MB per file</p>
<input type="file" class="file-input" id="propertyImage" accept=".jpg,.jpeg,.png" multiple>
<div class="file-name" id="imageName">Belum ada file yang dipilih</div>
</div>
</div>
<!-- Action Buttons -->
<div class="form-actions">
<button type="button" class="btn-action btn-cancel" id="cancelBtn">
<i class="fas fa-times"></i> Batal
</button>
<button type="submit" class="btn-action btn-save">
<i class="fas fa-home"></i> Tambah Properti
</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Toggle sidebar on small screens
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
// Add hover effect for desktop
if (window.innerWidth > 992) {
sidebar.addEventListener('mouseenter', function() {
this.style.width = '300px';
mainContent.style.marginLeft = '300px';
});
sidebar.addEventListener('mouseleave', function() {
this.style.width = 'var(--sidebar-width)';
mainContent.style.marginLeft = 'var(--sidebar-width)';
});
}
// Add active state to nav items
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', function() {
navItems.forEach(i => i.classList.remove('active'));
this.classList.add('active');
});
});
// Image upload handling
const uploadArea = document.getElementById('imageUpload');
const fileInput = document.getElementById('propertyImage');
const fileName = document.getElementById('imageName');
uploadArea.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
if (this.files.length > 1) {
fileName.textContent = `${this.files.length} file dipilih`;
} else {
fileName.textContent = this.files[0].name;
}
fileName.style.color = 'var(--primary-blue)';
} else {
fileName.textContent = 'Belum ada file yang dipilih';
fileName.style.color = '#94a3b8';
}
});
// Form submission
document.getElementById('addPropertyForm').addEventListener('submit', function(e) {
e.preventDefault();
// Get form values
const namaProperti = document.getElementById('namaProperti').value.trim();
const jenisProperti = document.getElementById('jenisProperti').value;
const kategoriProperti = document.getElementById('kategoriProperti').value;
const tipeProperti = document.getElementById('tipeProperti').value;
const blokRumah = document.getElementById('blokRumah').value.trim();
const hargaProperti = document.getElementById('hargaProperti').value.trim();
const luasBangunan = document.getElementById('luasBangunan').value.trim();
const luasTanah = document.getElementById('luasTanah').value.trim();
const stokUnit = document.getElementById('stokUnit').value.trim();
const statusUnit = document.getElementById('statusUnit').value;
// Simple validation
if (!namaProperti) {
alert('Nama properti harus diisi');
document.getElementById('namaProperti').focus();
return;
}
if (!jenisProperti) {
alert('Jenis properti harus dipilih');
document.getElementById('jenisProperti').focus();
return;
}
if (!kategoriProperti) {
alert('Kategori properti harus dipilih');
document.getElementById('kategoriProperti').focus();
return;
}
if (!tipeProperti) {
alert('Tipe properti harus dipilih');
document.getElementById('tipeProperti').focus();
return;
}
if (!hargaProperti || isNaN(hargaProperti) || parseInt(hargaProperti) <= 0) {
alert('Harga properti harus diisi dan berupa angka positif');
document.getElementById('hargaProperti').focus();
return;
}
if (!luasBangunan || isNaN(luasBangunan) || parseInt(luasBangunan) <= 0) {
alert('Luas bangunan harus diisi dan berupa angka positif');
document.getElementById('luasBangunan').focus();
return;
}
if (!luasTanah || isNaN(luasTanah) || parseInt(luasTanah) <= 0) {
alert('Luas tanah harus diisi dan berupa angka positif');
document.getElementById('luasTanah').focus();
return;
}
if (!stokUnit || isNaN(stokUnit) || parseInt(stokUnit) < 0) {
alert('Stok unit harus diisi dan berupa angka non-negatif');
document.getElementById('stokUnit').focus();
return;
}
if (!statusUnit) {
alert('Status unit harus dipilih');
document.getElementById('statusUnit').focus();
return;
}
// Success message
alert(`Properti "${namaProperti}" berhasil ditambahkan!\n\nDetail:\n- Jenis: ${jenisProperti}\n- Kategori: ${kategoriProperti}\n- Tipe: ${tipeProperti}\n- Harga: Rp ${parseInt(hargaProperti).toLocaleString('id-ID')}\n- Status: ${statusUnit}`);
// Reset form
this.reset();
fileName.textContent = 'Belum ada file yang dipilih';
fileName.style.color = '#94a3b8';
// In a real Laravel application, you would submit the form data to the server here
// window.location.href = 'data-rumah.html';
});
// Cancel button
document.getElementById('cancelBtn').addEventListener('click', function() {
if (confirm('Apakah Anda yakin ingin membatalkan penambahan properti? Semua data yang telah diisi akan hilang.')) {
// Redirect to data rumah page
window.location.href = 'data-rumah.html';
}
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,817 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Admin - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width-collapsed: 80px; /* Lebar saat collapse */
--sidebar-width-expanded: 250px; /* Lebar saat expand */
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width-collapsed); /* Default collapsed */
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
box-shadow: 5px 0 20px rgba(0,0,0,0.1);
transition: width 0.3s ease;
overflow: hidden;
}
/* Expand sidebar saat hover */
.sidebar:hover {
width: var(--sidebar-width-expanded);
}
.sidebar-header {
padding: 25px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 15px;
white-space: nowrap;
overflow: hidden;
}
.logo {
width: 40px; /* Lebih kecil saat collapse */
height: 40px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
flex-shrink: 0; /* Mencegah logo mengecil */
}
.logo i {
font-size: 20px; /* Lebih kecil saat collapse */
color: var(--dark-blue);
}
.company-name {
font-weight: 700;
font-size: 0.85rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
opacity: 0; /* Sembunyikan text saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan company name saat hover */
.sidebar:hover .company-name {
opacity: 1;
}
.nav-menu {
padding: 25px 0;
flex-grow: 1;
overflow-y: auto;
}
.nav-menu a {
text-decoration: none;
color: inherit;
display: block;
}
.nav-item {
padding: 15px 20px; /* Padding lebih kecil */
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-left: 4px solid transparent;
white-space: nowrap;
overflow: hidden;
}
.nav-item:hover {
background: rgba(255,255,255,0.08);
border-left-color: var(--primary-blue);
}
.nav-item.active {
background: rgba(255,255,255,0.1);
border-left-color: var(--primary-blue);
}
.nav-item i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0; /* Mencegah icon mengecil */
}
.nav-item span {
font-weight: 500;
font-size: 0.95rem;
opacity: 0; /* Sembunyikan text nav saat collapse */
transition: opacity 0.3s ease;
display: inline-block;
}
/* Tampilkan text nav saat hover */
.sidebar:hover .nav-item span {
opacity: 1;
}
.logout-btn {
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: auto;
white-space: nowrap;
overflow: hidden;
}
.logout-btn:hover {
background: rgba(255,255,255,0.08);
}
.logout-btn i {
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.logout-btn span {
opacity: 0; /* Sembunyikan text logout saat collapse */
transition: opacity 0.3s ease;
}
/* Tampilkan text logout saat hover */
.sidebar:hover .logout-btn span {
opacity: 1;
}
/* Main Content Styles */
.main-content {
flex: 1;
margin-left: var(--sidebar-width-collapsed); /* Sesuaikan dengan collapsed width */
margin-right: 20px;
overflow-y: auto;
padding: 20px;
background: #f8fafc;
transition: margin-left 0.3s ease;
}
/* Expand main content saat sidebar hover */
.sidebar:hover + .main-content {
margin-left: var(--sidebar-width-expanded);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin-bottom: 25px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a365d;
}
.search-bar {
display: flex;
align-items: center;
background: #f1f5f9;
border-radius: 8px;
padding: 8px 15px;
width: 300px;
}
.search-bar input {
border: none;
background: transparent;
outline: none;
padding: 5px;
width: 100%;
font-size: 0.95rem;
}
.search-bar i {
color: #64748b;
margin-right: 10px;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 18px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
color: #1a365d;
}
.user-role {
font-size: 0.85rem;
color: #64748b;
}
/* Dashboard Stats */
.dashboard-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 25px;
}
.stat-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-title {
font-size: 0.9rem;
color: #64748b;
margin-bottom: 10px;
}
.stat-value {
font-size: 1.8rem;
font-weight: 800;
color: var(--dark-blue);
margin-bottom: 5px;
}
.stat-change {
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 5px;
}
.stat-change.positive {
color: #10b981;
}
.stat-change.negative {
color: #ef4444;
}
.stat-change i {
font-size: 14px;
}
/* Recent Activity */
.recent-activity {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 25px;
margin-bottom: 25px;
}
.activity-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.activity-title {
font-size: 1.2rem;
font-weight: 700;
color: var(--dark-blue);
}
.view-all {
color: var(--primary-blue);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
}
.activity-list {
list-style: none;
}
.activity-item {
padding: 15px 0;
border-bottom: 1px solid #f1f5f9;
display: flex;
gap: 15px;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-icon {
width: 36px;
height: 36px;
background: #dbeafe;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #2563eb;
flex-shrink: 0;
}
.activity-content {
flex: 1;
}
.activity-text {
font-size: 0.95rem;
color: #1a365d;
margin-bottom: 5px;
}
.activity-time {
font-size: 0.85rem;
color: #64748b;
}
/* Quick Actions */
.quick-actions {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 25px;
}
.actions-header {
margin-bottom: 20px;
}
.actions-title {
font-size: 1.2rem;
font-weight: 700;
color: var(--dark-blue);
}
.actions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
}
.action-card {
background: #f8fafc;
border-radius: 12px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.action-card:hover {
background: #e0f2fe;
transform: translateY(-3px);
}
.action-icon {
width: 50px;
height: 50px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 15px;
color: white;
font-size: 24px;
}
.action-title {
font-size: 0.95rem;
font-weight: 600;
color: #1a365d;
}
/* Responsive Design */
@media (max-width: 1200px) {
.dashboard-stats {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.stat-value {
font-size: 1.5rem;
}
}
@media (max-width: 992px) {
.sidebar {
width: 80px;
}
.sidebar-header {
justify-content: center;
padding: 20px;
}
.company-name {
display: none;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.logout-btn {
justify-content: center;
padding: 15px;
}
.main-content {
margin-left: 80px;
}
.header {
flex-direction: column;
gap: 15px;
}
.search-bar {
width: 100%;
}
.page-title {
font-size: 1.3rem;
}
.user-profile {
width: 100%;
justify-content: space-between;
}
}
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header {
padding: 15px;
}
.main-content {
margin-left: 60px;
}
.dashboard-stats {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.stat-title {
font-size: 0.85rem;
}
.stat-value {
font-size: 1.3rem;
}
}
@media (max-width: 480px) {
.sidebar {
width: 50px;
}
.main-content {
margin-left: 50px;
}
.page-title {
font-size: 1.1rem;
}
.dashboard-stats {
grid-template-columns: 1fr;
}
.actions-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<div class="company-name">PT. Carani Bhanu Balakosa</div>
</div>
<div class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('data_user') }}"
class="nav-item {{ request()->routeIs('data_user') ? 'active' : '' }}">
<i class="fas fa-users"></i>
<span>Data User</span>
</a>
<a href="{{ route('data_rumah') }}"
class="nav-item {{ request()->routeIs('data_rumah') ? 'active' : '' }}">
<i class="fas fa-house"></i>
<span>Data Rumah</span>
</a>
<a href="{{ route('halaman_verifikasi') }}"
class="nav-item {{ request()->routeIs('halaman_verifikasi') ? 'active' : '' }}">
<i class="fas fa-check-circle"></i>
<span>Verifikasi Data</span>
</a>
<a href="{{ route('halaman_chatbot') }}"
class="nav-item {{ request()->routeIs('halaman_chatbot') ? 'active' : '' }}">
<i class="fas fa-comments"></i>
<span>Chatbot</span>
</a>
<div class="logout-btn">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Header -->
<div class="header">
<h1 class="page-title">Dashboard Admin</h1>
<div class="user-profile">
<div class="avatar">A</div>
<div class="user-info">
<div class="user-name">Admin Utama</div>
<div class="user-role">Administrator</div>
</div>
</div>
</div>
<!-- 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-change">
<i class="fas fa-clock"></i>
Perlu segera diproses
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="recent-activity">
<div class="activity-header">
<h2 class="activity-title">Aktivitas Terbaru</h2>
<a href="#" class="view-all">Lihat Semua</a>
</div>
<ul class="activity-list">
<li class="activity-item">
<div class="activity-icon">
<i class="fas fa-user-plus"></i>
</div>
<div class="activity-content">
<div class="activity-text">Pengguna baru mendaftar: Nayla Putri Wijaya</div>
<div class="activity-time">10 menit yang lalu</div>
</div>
</li>
<li class="activity-item">
<div class="activity-icon">
<i class="fas fa-home"></i>
</div>
<div class="activity-content">
<div class="activity-text">Properti baru ditambahkan: Kelapa Gading Regency</div>
<div class="activity-time">2 jam yang lalu</div>
</div>
</li>
<li class="activity-item">
<div class="activity-icon">
<i class="fas fa-file-upload"></i>
</div>
<div class="activity-content">
<div class="activity-text">Dokumen verifikasi dikirim oleh Alfin Rahman</div>
<div class="activity-time">5 jam yang lalu</div>
</div>
</li>
<li class="activity-item">
<div class="activity-icon">
<i class="fas fa-shopping-cart"></i>
</div>
<div class="activity-content">
<div class="activity-text">Transaksi baru: Rizky Saputra membeli Green City</div>
<div class="activity-time">Kemarin</div>
</div>
</li>
<li class="activity-item">
<div class="activity-icon">
<i class="fas fa-comment"></i>
</div>
<div class="activity-content">
<div class="activity-text">Pesan ChatBot baru dari Siti Nurhaliza</div>
<div class="activity-time">Kemarin</div>
</div>
</li>
</ul>
</div>
<!-- Quick Actions -->
<div class="quick-actions">
<div class="actions-header">
<h2 class="actions-title">Aksi Cepat</h2>
</div>
<div class="actions-grid">
<div class="action-card">
<div class="action-icon">
<i class="fas fa-user-plus"></i>
</div>
<div class="action-title">Tambah User</div>
</div>
<div class="action-card">
<div class="action-icon">
<i class="fas fa-home"></i>
</div>
<div class="action-title">Tambah Properti</div>
</div>
<div class="action-card">
<div class="action-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="action-title">Verifikasi Dokumen</div>
</div>
<div class="action-card">
<div class="action-icon">
<i class="fas fa-chart-line"></i>
</div>
<div class="action-title">Lihat Laporan</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Quick action cards
document.querySelectorAll('.action-card').forEach(card => {
card.addEventListener('click', function() {
const action = this.querySelector('.action-title').textContent;
alert(`Mengarahkan ke: ${action}`);
// In a real Laravel application, you would redirect to the appropriate page
// switch(action) {
// case 'Tambah User':
// window.location.href = '/admin/users/create';
// break;
// case 'Tambah Properti':
// window.location.href = '/admin/properties/create';
// break;
// case 'Verifikasi Dokumen':
// window.location.href = '/admin/verification';
// break;
// case 'Lihat Laporan':
// window.location.href = '/admin/reports';
// break;
// }
});
});
// Search functionality
const searchInput = document.querySelector('.search-bar input');
searchInput.addEventListener('input', function() {
console.log('Searching for:', this.value);
// In a real application, you would implement live search here
});
});
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
mainContent.classList.add('expanded');
}
// Hover effect untuk sidebar (opsional - untuk expand temporary)
sidebar.addEventListener('mouseenter', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-expanded)';
}
});
sidebar.addEventListener('mouseleave', function() {
if (this.classList.contains('collapsed')) {
this.style.width = 'var(--sidebar-width-collapsed)';
}
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,558 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ganti Password - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f0f9ff 0%, #e1f5fe 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.change-password-container {
display: flex;
width: 100%;
max-width: 1000px;
background: white;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 60px rgba(30, 58, 95, 0.2);
}
/* Left Side - Branding */
.branding-section {
flex: 1;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1a365d 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
position: relative;
overflow: hidden;
}
.branding-content {
text-align: center;
max-width: 400px;
position: relative;
z-index: 2;
}
.logo {
width: 100px;
height: 100px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
border: 4px solid rgba(255,255,255,0.3);
}
.logo i {
font-size: 48px;
color: var(--dark-blue);
}
.branding-title {
color: white;
font-size: 2.2rem;
font-weight: 800;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.branding-subtitle {
color: rgba(255, 255, 255, 0.85);
font-size: 1.2rem;
line-height: 1.5;
margin-bottom: 30px;
}
.features-list {
list-style: none;
margin: 0;
padding: 0;
}
.feature-item {
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 10px;
}
.feature-item i {
color: var(--primary-blue);
font-size: 14px;
}
/* Right Side - Change Password Form */
.password-form {
flex: 1;
padding: 50px 40px;
display: flex;
flex-direction: column;
}
.form-header {
text-align: center;
margin-bottom: 35px;
}
.form-logo {
width: 70px;
height: 70px;
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.3);
}
.form-logo i {
font-size: 32px;
color: white;
}
.form-title {
color: var(--dark-blue);
font-size: 2rem;
font-weight: 800;
margin-bottom: 12px;
}
.form-subtitle {
color: #64748b;
font-size: 1.1rem;
line-height: 1.5;
text-align: center;
}
.form-group {
margin-bottom: 22px;
position: relative;
}
.form-label {
font-weight: 600;
color: #2d3748;
margin-bottom: 10px;
font-size: 0.95rem;
}
.form-control {
border: 2px solid #e2e8f0;
border-radius: 14px;
padding: 14px 20px;
font-size: 1rem;
transition: all 0.3s ease;
background-color: #f8fafc;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 4px rgba(var(--primary-blue-rgb), 0.15);
background-color: white;
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
.input-group {
position: relative;
}
.input-group-text {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 14px 0 0 14px;
padding: 14px 20px;
color: #718096;
border-right: none;
}
.form-control.password-input {
border-radius: 0 14px 14px 0;
border-left: none;
background-color: #f8fafc;
}
.eye-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #718096;
transition: all 0.3s ease;
font-size: 18px;
}
.eye-icon:hover {
color: var(--primary-blue);
}
.btn-submit {
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
border: none;
padding: 16px;
font-weight: 700;
font-size: 1.1rem;
border-radius: 14px;
transition: all 0.4s ease;
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 10px;
color: white;
width: 100%;
}
.btn-submit:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.4);
}
.btn-submit:active {
transform: translateY(0);
}
.btn-cancel {
background: #e2e8f0;
color: #4a5568;
border: none;
padding: 16px;
font-weight: 600;
font-size: 1.1rem;
border-radius: 14px;
transition: all 0.4s ease;
margin-top: 15px;
width: 100%;
}
.btn-cancel:hover {
background: #cbd5e0;
transform: translateY(-2px);
}
.password-requirements {
background: #f8fafc;
border-left: 4px solid var(--primary-blue);
padding: 15px;
border-radius: 8px;
margin: 20px 0;
font-size: 0.9rem;
}
.password-requirements ul {
margin: 8px 0 0 20px;
padding-left: 10px;
}
.password-requirements li {
margin-bottom: 5px;
color: #4a5568;
}
.back-link {
text-align: center;
margin-top: 20px;
}
.back-link a {
color: var(--primary-blue);
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
}
.back-link a:hover {
color: #3d85aa;
text-decoration: underline;
}
/* Responsive Design */
@media (max-width: 992px) {
.change-password-container {
flex-direction: column;
max-width: 500px;
}
.branding-section {
height: 280px;
padding: 30px 20px;
}
.password-form {
padding: 40px 30px;
}
.branding-title {
font-size: 1.8rem;
}
.branding-subtitle {
font-size: 1rem;
}
.logo {
width: 80px;
height: 80px;
}
.logo i {
font-size: 40px;
}
}
@media (max-width: 480px) {
.change-password-container {
border-radius: 20px;
max-width: 100%;
}
.branding-section {
height: 240px;
padding: 25px 15px;
}
.password-form {
padding: 35px 25px;
}
.branding-title {
font-size: 1.5rem;
}
.branding-subtitle {
font-size: 0.9rem;
}
.form-title {
font-size: 1.7rem;
}
.form-subtitle {
font-size: 1rem;
}
.logo {
width: 70px;
height: 70px;
}
.logo i {
font-size: 32px;
}
.form-logo {
width: 60px;
height: 60px;
}
.form-logo i {
font-size: 28px;
}
.btn-submit, .btn-cancel {
font-size: 1rem;
padding: 14px;
letter-spacing: 0.3px;
}
}
</style>
</head>
<body>
<div class="change-password-container">
<!-- Left Side - Branding -->
<div class="branding-section">
<div class="branding-content">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<h1 class="branding-title">Carani Estate</h1>
<p class="branding-subtitle">Platform terpercaya untuk membeli, menjual, dan menyewa properti berkualitas sejak 2015</p>
<ul class="features-list">
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Lebih dari 10.000 properti tersedia</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Proses transaksi aman dan transparan</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Dukungan customer service 24/7</span>
</li>
</ul>
</div>
</div>
<!-- Right Side - Change Password Form -->
<div class="password-form">
<div class="form-header">
<div class="form-logo">
<i class="fas fa-key"></i>
</div>
<h2 class="form-title">Ganti Password</h2>
<p class="form-subtitle">Masukkan password lama dan password baru Anda untuk mengubah password akun</p>
</div>
<form method="POST" action="{{ route('ganti-password.proses') }}">
@csrf
{{-- Pesan sukses --}}
@if(session('success'))
<div style="background:#d1fae5; color:#059669; padding:10px; border-radius:8px; margin-bottom:15px;">
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
</div>
@endif
{{-- Pesan error --}}
@if($errors->any())
<div style="background:#fecaca; color:#dc2626; padding:10px; border-radius:8px; margin-bottom:15px;">
<i class="fas fa-exclamation-circle me-2"></i>{{ $errors->first() }}
</div>
@endif
{{-- Ganti dengan ini --}}
<div class="form-group">
<label for="email" class="form-label">Email</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
<input type="email" class="form-control password-input"
id="email" name="email"
value="{{ old('email') }}"
placeholder="Masukkan email kamu" required>
</div>
@error('email')
<div style="color:red; font-size:0.85rem; margin-top:5px;">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="newPassword" class="form-label">Password Baru</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password"
class="form-control password-input"
id="newPassword"
name="password_baru"
placeholder="Masukkan password baru" required>
<span class="eye-icon" id="toggleNewPassword">
<i class="fas fa-eye"></i>
</span>
</div>
@error('password_baru')
<div style="color:red; font-size:0.85rem; margin-top:5px;">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="confirmPassword" class="form-label">Konfirmasi Password Baru</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password"
class="form-control password-input"
id="confirmPassword"
name="konfirmasi_password"
placeholder="Konfirmasi password baru" required>
<span class="eye-icon" id="toggleConfirmPassword">
<i class="fas fa-eye"></i>
</span>
</div>
@error('konfirmasi_password')
<div style="color:red; font-size:0.85rem; margin-top:5px;">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn-submit">
<i class="fas fa-sync-alt me-2"></i>Ubah Password
</button>
<div class="back-link" style="margin-top:15px;">
<a href="{{ route('login') }}">
<i class="fas fa-arrow-left me-1"></i>Kembali ke Halaman Login
</a>
</div>
</form>
</div>
</div>
<script>
// Toggle password visibility
function setupPasswordToggle(toggleId, passwordId) {
document.getElementById(toggleId).addEventListener('click', function() {
const passwordInput = document.getElementById(passwordId);
const icon = this.querySelector('i');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
passwordInput.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
}
setupPasswordToggle('toggleCurrentPassword', 'currentPassword');
setupPasswordToggle('toggleNewPassword', 'newPassword');
setupPasswordToggle('toggleConfirmPassword', 'confirmPassword');
// Add subtle animation on page load
document.addEventListener('DOMContentLoaded', function() {
const container = document.querySelector('.change-password-container');
container.style.opacity = '0';
container.style.transform = 'translateY(20px)';
setTimeout(() => {
container.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
container.style.opacity = '1';
container.style.transform = 'translateY(0)';
}, 100);
});
</script>
</body>
</html>

View File

@ -0,0 +1,994 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatBot - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width: 260px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Roboto", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
background: #f8fafc;
overflow-x: hidden;
height: 100vh;
display: flex;
flex-direction: column;
}
/* Header Styles */
.header {
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
padding: 15px 30px;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 50px;
height: 50px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.logo-icon i {
font-size: 24px;
color: var(--dark-blue);
}
.logo-text {
font-weight: 700;
font-size: 1.2rem;
letter-spacing: 1px;
}
.nav-menu {
display: flex;
align-items: center;
gap: 30px;
}
.nav-item {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
color: var(--primary-blue);
}
.nav-item.active {
color: var(--primary-blue);
font-weight: 600;
}
.nav-item::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: var(--primary-blue);
transition: width 0.3s ease;
}
.nav-item:hover::after {
width: 100%;
}
.user-actions {
display: flex;
align-items: center;
gap: 20px;
}
.notification-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: white;
text-decoration: none;
}
.notification-icon:hover {
background: rgba(255,255,255,0.2);
}
.profile-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.profile-icon:hover {
background: rgba(255,255,255,0.2);
}
.menu-toggle{
display: none;
font-size: 22px;
cursor: pointer;
padding: 8px 10px;
border-radius: 6px;
transition: 0.3s;
}
.menu-toggle:hover{
background: rgba(255,255,255,0.15);
}
/* Chat Container */
.chat-container {
position: fixed;
top: 80px;
left: 0;
width: 100%;
height: calc(100vh - 80px);
display: flex;
flex-direction: column;
background: white;
}
/* Chat Header */
.chat-header {
padding: 20px 30px;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1a365d 100%);
color: white;
display: flex;
align-items: center;
gap: 15px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.chat-avatar {
width: 50px;
height: 50px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 700;
}
.chat-info h1 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 5px;
}
.chat-info p {
opacity: 0.9;
font-size: 0.95rem;
}
.status-indicator {
width: 12px;
height: 12px;
background: #10b981;
border-radius: 50%;
display: inline-block;
margin-right: 5px;
}
/* Messages Area */
.messages-area {
flex: 1;
padding: 30px;
overflow-y: auto;
background: #f8fafc;
display: flex;
flex-direction: column;
gap: 25px;
}
.message {
max-width: 80%;
padding: 16px 20px;
border-radius: 18px;
line-height: 1.5;
position: relative;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.bot-message {
align-self: flex-start;
background: white;
border-bottom-left-radius: 6px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.user-message {
align-self: flex-end;
background: var(--primary-blue);
color: white;
border-bottom-right-radius: 6px;
box-shadow: 0 2px 10px rgba(122, 178, 211, 0.3);
}
.message-time {
font-size: 0.75rem;
opacity: 0.8;
margin-top: 8px;
text-align: right;
}
.user-message .message-time {
text-align: right;
}
.bot-message .message-time {
text-align: left;
}
.message-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
font-weight: 600;
}
.bot-avatar {
width: 36px;
height: 36px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
flex-shrink: 0;
}
/* Input Area */
.input-area {
padding: 20px 30px;
background: white;
border-top: 1px solid #e2e8f0;
display: flex;
gap: 15px;
align-items: center;
}
.message-input {
flex: 1;
padding: 16px 20px;
border: 2px solid #e2e8f0;
border-radius: 50px;
font-size: 1rem;
outline: none;
transition: all 0.3s ease;
}
.message-input:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(var(--primary-blue-rgb), 0.2);
}
.send-btn {
width: 50px;
height: 50px;
border-radius: 50%;
background: var(--primary-blue);
color: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: all 0.3s ease;
flex-shrink: 0;
}
.send-btn:hover {
background: #6aa5c6;
transform: scale(1.05);
}
.send-btn:active {
transform: scale(0.95);
}
.attachment-btn {
width: 50px;
height: 50px;
border-radius: 50%;
background: #e2e8f0;
color: #4a5568;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: all 0.3s ease;
flex-shrink: 0;
}
.attachment-btn:hover {
background: #cbd5e0;
transform: scale(1.05);
}
/* Quick Actions */
.quick-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 15px;
}
.quick-btn {
background: white;
border: 1px solid #e2e8f0;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
}
.quick-btn:hover {
background: var(--primary-blue);
color: white;
border-color: var(--primary-blue);
}
/* Typing Indicator */
.typing-indicator {
display: flex;
padding: 12px 20px;
background: white;
border-radius: 18px;
border-bottom-left-radius: 6px;
align-self: flex-start;
margin-top: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.typing-dot {
width: 8px;
height: 8px;
background: #94a3b8;
border-radius: 50%;
margin: 0 2px;
animation: bounce 1.4s infinite ease-in-out both;
}
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
.typing-dot:nth-child(3) { animation-delay: 0s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
/* Responsive Design */
@media (max-width: 768px) {
.header{
padding: 15px;
}
.header-container{
display: flex;
flex-direction: row; /* ← jadi satu baris */
align-items: center;
justify-content: space-between; /* ← kiri & kanan */
width: 100%;
}
/* tombol toggle muncul di mobile */
.menu-toggle{
display: block;
color: white;
order: 2;
}
/* nav jadi dropdown */
.nav-menu{
display: none;
position: absolute;
top: 70px;
left: 0;
width: 100%;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
flex-direction: column;
padding: 15px 0;
gap: 0;
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.nav-menu.show{
display: flex;
}
.nav-item{
padding: 12px 20px;
width: 100%;
}
.nav-item::after{
display: none;
}
.logo-text {
font-size: 1rem;
}
.nav-menu {
display: none;
margin-bottom: 20px;
}
.user-actions {
margin-left: auto; /* ← dorong ke kanan */
display: flex;
align-items: center;
gap: 10px;
}
.chat-header {
padding: 15px 20px;
}
.chat-container {
top: 70px;
height: calc(100vh - 70px);
}
.chat-info h1 {
font-size: 1.3rem;
}
.messages-area {
padding: 20px;
}
.message {
max-width: 90%;
}
.input-area {
padding: 15px 20px;
}
.message-input {
padding: 14px 18px;
}
.send-btn, .attachment-btn {
width: 45px;
height: 45px;
}
}
@media (max-width: 480px) {
.header {
padding: 15px 15px;
}
.logo-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.logo-text {
display: none;
}
.chat-header {
padding: 12px 15px;
}
.chat-container {
top: 65px;
height: calc(100vh - 65px);
}
.chat-avatar {
width: 40px;
height: 40px;
font-size: 20px;
}
.chat-info h1 {
font-size: 1.2rem;
}
.messages-area {
padding: 15px;
}
.message {
padding: 14px 18px;
font-size: 0.95rem;
}
.message-time {
font-size: 0.7rem;
}
.input-area {
padding: 12px 15px;
}
.message-input {
padding: 12px 16px;
font-size: 0.95rem;
}
.send-btn, .attachment-btn {
width: 40px;
height: 40px;
font-size: 1rem;
}
.quick-actions {
gap: 8px;
}
.quick-btn {
padding: 6px 12px;
font-size: 0.85rem;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-container">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-home"></i>
</div>
<div class="logo-text">Carani Estate</div>
</div>
<div class="menu-toggle" onclick="toggleMenu()">
<i class="fas fa-bars"></i>
</div>
<nav class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
Beranda
</a>
<a href="{{ route('tentang-kami') }}"
class="nav-item {{ request()->routeIs('tentang-kami') ? 'active' : '' }}">
Tentang Kami
</a>
<a href="{{ route('halaman-katalog') }}"
class="nav-item {{ request()->routeIs('halaman-katalog') ? 'active' : '' }}">
Katalog
</a>
<a href="{{ route('halaman-chatbot') }}"
class="nav-item {{ request()->routeIs('halaman-chatbot') ? 'active' : '' }}">
ChatBot
</a>
@auth
<a href="{{ route('riwayat-pemesanan') }}" class="nav-item {{ request()->routeIs('riwayat-pemesanan') ? 'active' : '' }}">
Riwayat Pemesanan
</a>
@endauth
<a href="{{ route('halaman-kontak') }}"
class="nav-item {{ request()->routeIs('kontak') ? 'active' : '' }}">
Kontak
</a>
</nav>
<div class="user-actions">
<a href="{{ route('halaman-notifikasi') }}" class="notification-icon">
<i class="fas fa-bell"></i>
</a>
{{-- Conditional: Guest vs Authenticated --}}
@guest
{{-- BELUM login: Tampilkan tombol Login sederhana --}}
<a href="{{ route('login') }}" class="nav-item login-link">
<i class="fas fa-sign-in-alt me-1"></i> Login
</a>
@else
{{-- SUDAH login: Tampilkan dropdown Profil --}}
<div class="profile-dropdown">
<div class="profile-icon" onclick="toggleDropdown()">
<i class="fas fa-user"></i>
</div>
<div class="dropdown-menu" id="dropdownMenu">
<a href="{{ route('halaman-profil') }}">
<i class="fas fa-user-circle"></i> Lihat Profil
</a>
<hr>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</form>
</div>
</div>
@endguest
</div>
</div>
</header>
<!-- Chat Container -->
<div class="chat-container">
<!-- Chat Header -->
<div class="chat-header">
<div class="chat-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="chat-info">
<h1>PropertiBot Assistant</h1>
<p><span class="status-indicator"></span>Online - Siap membantu Anda</p>
</div>
</div>
<!-- Messages Area -->
<div class="messages-area" id="messagesArea">
<!-- Bot Welcome Message -->
<div class="message bot-message">
<div class="message-header">
<div class="bot-avatar">PH</div>
<div>PropertiBot</div>
</div>
<div class="message-content">
Halo! Saya PropertiBot, asisten virtual dari PropertiHarmoni. Ada yang bisa saya bantu hari ini? 😊
</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="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>
</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">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
<!-- Input Area -->
<div class="input-area">
<input type="text" class="message-input" id="messageInput" placeholder="Ketik pesan Anda di sini..." autocomplete="off">
<button class="send-btn" id="sendBtn" title="Kirim pesan">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</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
}
}
});
});
// Add hover effect to table rows
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('mouseenter', function() {
this.style.background = '#f8fafc';
});
row.addEventListener('mouseleave', function() {
this.style.background = '';
});
});
// Pagination functionality
document.querySelectorAll('.page-item').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.page-item').forEach(i => i.classList.remove('active'));
this.classList.add('active');
alert(`Menampilkan halaman ${this.textContent}`);
});
});
// TOGGLE NAV
function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,485 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ asset('css/style.css') }}">
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-container">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-home"></i>
</div>
<div class="logo-text">Carani Estate</div>
</div>
<div class="menu-toggle" onclick="toggleMenu()">
<i class="fas fa-bars"></i>
</div>
<nav class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
Beranda
</a>
<a href="{{ route('tentang-kami') }}"
class="nav-item {{ request()->routeIs('tentang-kami') ? 'active' : '' }}">
Tentang Kami
</a>
<a href="{{ route('halaman-katalog') }}"
class="nav-item {{ request()->routeIs('halaman-katalog') ? 'active' : '' }}">
Katalog
</a>
<a href="{{ route('halaman-chatbot') }}"
class="nav-item {{ request()->routeIs('halaman-chatbot') ? 'active' : '' }}">
ChatBot
</a>
<a href="{{ route('riwayat-pemesanan') }}"
class="nav-item {{ request()->routeIs('riwayat-pemesanan') ? 'active' : '' }}">
Riwayat Pemesanan
</a>
<a href="{{ route('halaman-kontak') }}"
class="nav-item {{ request()->routeIs('halaman-kontak') ? 'active' : '' }}">
Kontak
</a>
</nav>
<div class="user-actions">
<a href="{{ route('halaman-notifikasi') }}" class="notification-icon">
<i class="fas fa-bell"></i>
</a>
<div class="profile-dropdown">
<div class="profile-icon" onclick="toggleDropdown()">
<i class="fas fa-user"></i>
</div>
<div class="dropdown-menu" id="dropdownMenu">
<div class="user-actions">
<a href="{{ route('halaman-notifikasi') }}" class="notification-icon">
<i class="fas fa-bell"></i>
</a>
{{-- Conditional: Guest vs Authenticated --}}
@guest
{{-- BELUM login: Tampilkan tombol Login sederhana --}}
<a href="{{ route('login') }}" class="nav-item login-link">
<i class="fas fa-sign-in-alt me-1"></i> Login
</a>
@else
{{-- SUDAH login: Tampilkan dropdown Profil --}}
<div class="profile-dropdown">
<div class="profile-icon" onclick="toggleDropdown()">
<i class="fas fa-user"></i>
</div>
<div class="dropdown-menu" id="dropdownMenu">
<a href="{{ route('halaman-profil') }}">
<i class="fas fa-user-circle"></i> Lihat Profil
</a>
<hr>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</form>
</div>
</div>
@endguest
</div>
</div>
</div>
</div>
</header>
<!-- Hero Header -->
<section class="hero-header">
<div class="hero-overlay"></div>
<div class="hero-content">
<h1>Layanan Properti</h1>
<p>
Temukan layanan terbaik kami untuk membantu Anda membeli, menjual,
dan mengelola properti dengan mudah dan profesional.
</p>
<div class="breadcrumb">
<a href="#">Beranda</a>
<span>/</span>
<span class="active">Layanan</span>
</div>
</div>
</section>
<section class="search-filter">
<div class="filter-left">
<div class="filter-group">
<label class="filter-label">Lokasi</label>
<input type="text" class="filter-input" placeholder="Masukkan kota atau area di sini">
</div>
<div class="filter-group">
<label class="filter-label">Tipe Properti</label>
<select class="filter-select">
<option value="">Semua Tipe</option>
<option value="rumah">Rumah</option>
<option value="ruko">Ruko</option>
<option value="apartemen">Apartemen</option>
<option value="tanah">Tanah</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Kisaran Harga</label>
<select class="filter-select">
<option value="">Semua Harga</option>
<option value="0-500">Rp 0 - 500 Juta</option>
<option value="500-1000">Rp 500 Juta - 1 Miliar</option>
<option value="1-2">Rp 1 - 2 Miliar</option>
<option value="2+">Rp 2 Miliar+</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Kamar Tidur</label>
<div class="bedroom-buttons">
<button class="bedroom-btn">Semua</button>
<button class="bedroom-btn">1</button>
<button class="bedroom-btn">2</button>
<button class="bedroom-btn">3</button>
<button class="bedroom-btn">4+</button>
</div>
</div>
</div>
<div class="filter-right">
<button class="search-btn1">
<i class="fas fa-search"></i> Cari Properti
</button>
</div>
</section>
<!-- Properties Section -->
<section class="properties-section">
<h2 class="section-title">Properti Terbaru</h2>
<div class="properties-grid">
<!-- Property 1 -->
<div class="property-card">
<div class="property-image">
<img src="https://placehold.co/300x200/e6f2f8/1E3A5F?text=Kelapa+Gading+Regency" alt="Kelapa Gading Regency">
<div class="property-badge badge-new">Baru</div>
</div>
<div class="property-info">
<div class="property-price">Rp 285.000.000</div>
<div class="property-title">Kelapa Gading Regency</div>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i> Jakarta Utara
</div>
<div class="property-details">
<div class="property-detail">
<i class="fas fa-bed"></i> 2 Kamar
</div>
<div class="property-detail">
<i class="fas fa-bath"></i> 1 Kamar Mandi
</div>
<div class="property-detail">
<i class="fas fa-ruler-combined"></i> 30/60
</div>
</div>
<div class="property-actions">
<button class="action-btn btn-view">Lihat Detail</button>
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
<!-- Property 2 -->
<div class="property-card">
<div class="property-image">
<img src="https://placehold.co/300x200/e6f2f8/1E3A5F?text=Green+City" alt="Green City">
<div class="property-badge badge-sale">Diskon</div>
</div>
<div class="property-info">
<div class="property-price">Rp 450.000.000</div>
<div class="property-title">Green City</div>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i> Jakarta Selatan
</div>
<div class="property-details">
<div class="property-detail">
<i class="fas fa-bed"></i> 3 Kamar
</div>
<div class="property-detail">
<i class="fas fa-bath"></i> 2 Kamar Mandi
</div>
<div class="property-detail">
<i class="fas fa-ruler-combined"></i> 45/84
</div>
</div>
<div class="property-actions">
<button class="action-btn btn-view">Lihat Detail</button>
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
<!-- Property 3 -->
<div class="property-card">
<div class="property-image">
<img src="https://placehold.co/300x200/e6f2f8/1E3A5F?text=Ruko+Sentral" alt="Ruko Sentral">
<div class="property-badge badge-new">Baru</div>
</div>
<div class="property-info">
<div class="property-price">Rp 1.250.000.000</div>
<div class="property-title">Ruko Sentral</div>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i> Jakarta Barat
</div>
<div class="property-details">
<div class="property-detail">
<i class="fas fa-bed"></i> 1 Lantai
</div>
<div class="property-detail">
<i class="fas fa-bath"></i> 2 Kamar Mandi
</div>
<div class="property-detail">
<i class="fas fa-ruler-combined"></i> 60/90
</div>
</div>
<div class="property-actions">
<button class="action-btn btn-view">Lihat Detail</button>
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
<!-- Property 4 -->
<div class="property-card">
<div class="property-image">
<img src="https://placehold.co/300x200/e6f2f8/1E3A5F?text=Perumahan+Bukit" alt="Perumahan Bukit">
<div class="property-badge badge-sale">Diskon</div>
</div>
<div class="property-info">
<div class="property-price">Rp 695.000.000</div>
<div class="property-title">Perumahan Bukit</div>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i> Bogor
</div>
<div class="property-details">
<div class="property-detail">
<i class="fas fa-bed"></i> 3 Kamar
</div>
<div class="property-detail">
<i class="fas fa-bath"></i> 2 Kamar Mandi
</div>
<div class="property-detail">
<i class="fas fa-ruler-combined"></i> 60/135
</div>
</div>
<div class="property-actions">
<button class="action-btn btn-view">Lihat Detail</button>
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
<!-- Property 5 -->
<div class="property-card">
<div class="property-image">
<img src="https://placehold.co/300x200/e6f2f8/1E3A5F?text=Villa+Puncak" alt="Villa Puncak">
<div class="property-badge badge-new">Baru</div>
</div>
<div class="property-info">
<div class="property-price">Rp 1.850.000.000</div>
<div class="property-title">Villa Puncak</div>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i> Puncak
</div>
<div class="property-details">
<div class="property-detail">
<i class="fas fa-bed"></i> 4 Kamar
</div>
<div class="property-detail">
<i class="fas fa-bath"></i> 3 Kamar Mandi
</div>
<div class="property-detail">
<i class="fas fa-ruler-combined"></i> 120/250
</div>
</div>
<div class="property-actions">
<button class="action-btn btn-view">Lihat Detail</button>
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
<!-- Property 6 -->
<div class="property-card">
<div class="property-image">
<img src="https://placehold.co/300x200/e6f2f8/1E3A5F?text=Tanah+Strategis" alt="Tanah Strategis">
<div class="property-badge badge-sale">Diskon</div>
</div>
<div class="property-info">
<div class="property-price">Rp 850.000.000</div>
<div class="property-title">Tanah Strategis</div>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i> Depok
</div>
<div class="property-details">
<div class="property-detail">
<i class="fas fa-ruler-combined"></i> 150
</div>
<div class="property-detail">
<i class="fas fa-landmark"></i> Lokasi Premium
</div>
</div>
<div class="property-actions">
<button class="action-btn btn-view">Lihat Detail</button>
<button class="action-btn btn-contact">Hubungi</button>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="pagination">
<div class="page-item">1</div>
<div class="page-item">2</div>
<div class="page-item">3</div>
<div class="page-item">...</div>
<div class="page-item">10</div>
</div>
</section>
<!-- 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. Melati No. 45, Jakarta Selatan</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-clock"></i> Senin - Sabtu: 08:00 - 17:00</p>
</div>
</div>
</div>
<div class="copyright">
&copy; 2025 PropertiHarmoni. Semua hak dilindungi.
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js">
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
}
}
});
});
// Add hover effect to table rows
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('mouseenter', function() {
this.style.background = '#f8fafc';
});
row.addEventListener('mouseleave', function() {
this.style.background = '';
});
});
// Pagination functionality
document.querySelectorAll('.page-item').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.page-item').forEach(i => i.classList.remove('active'));
this.classList.add('active');
alert(`Menampilkan halaman ${this.textContent}`);
});
});
// TOGGLE NAV
function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
</script>
</body>
</html>

View File

@ -0,0 +1,637 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notifikasi - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width: 260px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
overflow-x: hidden;
}
/* Header Styles */
.header {
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
padding: 15px 30px;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 50px;
height: 50px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.logo-icon i {
font-size: 24px;
color: var(--dark-blue);
}
.logo-text {
font-weight: 700;
font-size: 1.2rem;
letter-spacing: 1px;
}
.nav-menu {
display: flex;
align-items: center;
gap: 30px;
}
.nav-item {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
color: var(--primary-blue);
}
.nav-item.active {
color: var(--primary-blue);
font-weight: 600;
}
.nav-item::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: var(--primary-blue);
transition: width 0.3s ease;
}
.nav-item:hover::after {
width: 100%;
}
.user-actions {
display: flex;
align-items: center;
gap: 20px;
}
.notification-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: white;
text-decoration: none;
}
.notification-icon:hover {
background: rgba(255,255,255,0.2);
}
.profile-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.profile-icon:hover {
background: rgba(255,255,255,0.2);
}
.menu-toggle{
display: none;
font-size: 22px;
cursor: pointer;
padding: 8px 10px;
border-radius: 6px;
transition: 0.3s;
}
.menu-toggle:hover{
background: rgba(255,255,255,0.15);
}
/* Main Content */
.main-content {
margin: 80px auto 30px;
max-width: 600px;
padding: 0 20px;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 25px;
margin-top: 40px;
}
/* Notification List */
.notification-list {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
overflow: hidden;
}
.notification-item {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 14px;
padding: 18px 22px;
margin-bottom: 16px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(255, 255, 255, 0.4);
display: flex;
align-items: center;
gap: 16px;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.notification-item:hover {
transform: translateY(-3px);
box-shadow: 0 14px 32px rgba(0, 0, 0, 0.12);
}
.notification-item:last-child {
border-bottom: none;
}
.notification-item:hover {
background: #f8fafc;
}
.notification-icon-container {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
flex-shrink: 0;
}
.notification-icon-container.mail {
background: #f1f5f9;
color: #64748b;
}
.notification-icon-container.warning {
background: #fef3c7;
color: #f59e0b;
}
.notification-icon-container.info {
background: #dbeafe;
color: #2563eb;
}
.notification-content {
flex: 1;
}
.notification-title {
font-size: 1rem;
font-weight: 600;
color: #1a365d;
margin-bottom: 6px;
}
.notification-description {
font-size: 0.9rem;
color: #64748b;
line-height: 1.5;
}
.notification-arrow {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
flex-shrink: 0;
}
.notification-arrow i {
font-size: 12px;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 40px 20px;
}
.empty-icon {
font-size: 4rem;
color: #cbd5e0;
margin-bottom: 20px;
}
.empty-title {
font-size: 1.2rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.empty-description {
color: #64748b;
font-size: 0.9rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.header{
padding: 15px;
}
.header-container{
display: flex;
flex-direction: row; /* ← jadi satu baris */
align-items: center;
justify-content: space-between; /* ← kiri & kanan */
width: 100%;
}
/* tombol toggle muncul di mobile */
.menu-toggle{
display: block;
color: white;
order: 2;
}
/* nav jadi dropdown */
.nav-menu{
display: none;
position: absolute;
top: 70px;
left: 0;
width: 100%;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
flex-direction: column;
padding: 15px 0;
gap: 0;
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.nav-menu.show{
display: flex;
}
.nav-item{
padding: 12px 20px;
width: 100%;
}
.nav-item::after{
display: none;
}
.logo-text {
font-size: 1rem;
}
.nav-menu {
display: none;
margin-bottom: 20px;
}
.user-actions {
margin-left: auto; /* ← dorong ke kanan */
display: flex;
align-items: center;
gap: 10px;
}
.main-content {
margin: 80px auto 30px;
padding: 0 15px;
}
.page-title {
font-size: 1.3rem;
}
.notification-item {
padding: 16px;
}
.notification-icon-container {
width: 36px;
height: 36px;
}
.notification-arrow {
width: 18px;
height: 18px;
}
}
@media (max-width: 480px) {
.header {
padding: 15px 15px;
}
.logo-text {
display: none;
}
.logo-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.main-content {
padding: 0 10px;
}
.page-title {
font-size: 1.2rem;
}
.notification-item {
padding: 14px;
}
}
</style>
</head>
<body>
<!-- Header -->
<!-- Header -->
<header class="header">
<div class="header-container">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-home"></i>
</div>
<div class="logo-text">Carani Estate</div>
</div>
<div class="menu-toggle" onclick="toggleMenu()">
<i class="fas fa-bars"></i>
</div>
<nav class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
Beranda
</a>
<a href="{{ route('tentang-kami') }}"
class="nav-item {{ request()->routeIs('tentang-kami') ? 'active' : '' }}">
Tentang Kami
</a>
<a href="{{ route('halaman-katalog') }}"
class="nav-item {{ request()->routeIs('halaman-katalog') ? 'active' : '' }}">
Katalog
</a>
<a href="{{ route('halaman-chatbot') }}"
class="nav-item {{ request()->routeIs('halaman-chatbot') ? 'active' : '' }}">
ChatBot
</a>
<a href="{{ route('riwayat-pemesanan') }}"
class="nav-item {{ request()->routeIs('riwayat-pemesanan') ? 'active' : '' }}">
Riwayat Pemesanan
</a>
<a href="{{ route('halaman-kontak') }}"
class="nav-item {{ request()->routeIs('halaman-kontak') ? 'active' : '' }}">
Kontak
</a>
</nav>
<div class="user-actions">
<a href="{{ route('halaman-notifikasi') }}" class="notification-icon">
<i class="fas fa-bell"></i>
</a>
<div class="profile-dropdown">
<div class="profile-icon" onclick="toggleDropdown()">
<i class="fas fa-user"></i>
</div>
<div class="dropdown-menu" id="dropdownMenu">
<a href="{{ route('halaman-profil') }}">
<i class="fas fa-user-circle"></i> Lihat Profil
</a>
<hr>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</form>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<h1 class="page-title">Notifikasi</h1>
<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>
<!-- Notification 2 -->
<div class="notification-item">
<div class="notification-icon-container warning">
<i class="fas fa-exclamation-triangle"></i>
</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>
</div>
</div>
</div>
</div>
<script>
// Add click event to notification items
document.querySelectorAll('.notification-item').forEach(item => {
item.addEventListener('click', function() {
const title = this.querySelector('.notification-title').textContent;
alert(`Notifikasi: ${title}`);
});
});
// Add hover effect for desktop
if (window.innerWidth > 768) {
const header = document.querySelector('.header');
const mainContent = document.querySelector('.main-content');
header.addEventListener('mouseenter', function() {
this.style.boxShadow = '0 6px 20px rgba(0,0,0,0.15)';
});
header.addEventListener('mouseleave', function() {
this.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
});
}
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
}
}
});
});
// Add hover effect to table rows
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('mouseenter', function() {
this.style.background = '#f8fafc';
});
row.addEventListener('mouseleave', function() {
this.style.background = '';
});
});
// Pagination functionality
document.querySelectorAll('.page-item').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.page-item').forEach(i => i.classList.remove('active'));
this.classList.add('active');
alert(`Menampilkan halaman ${this.textContent}`);
});
});
// TOGGLE NAV
function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
</script>
</body>
</html>

View File

@ -0,0 +1,944 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pembayaran - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width: 260px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Roboto", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
background: #f8fafc;
overflow-x: hidden;
}
/* Header Styles */
.header {
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
padding: 15px 30px;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 50px;
height: 50px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.logo-icon i {
font-size: 24px;
color: var(--dark-blue);
}
.logo-text {
font-weight: 700;
font-size: 1.2rem;
letter-spacing: 1px;
}
.nav-menu {
display: flex;
align-items: center;
gap: 30px;
}
.nav-item {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
color: var(--primary-blue);
}
.nav-item.active {
color: var(--primary-blue);
font-weight: 600;
}
.nav-item::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: var(--primary-blue);
transition: width 0.3s ease;
}
.nav-item:hover::after {
width: 100%;
}
.user-actions {
display: flex;
align-items: center;
gap: 20px;
}
.notification-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.notification-icon:hover {
background: rgba(255,255,255,0.2);
}
.profile-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.profile-icon:hover {
background: rgba(255,255,255,0.2);
}
.menu-toggle{
display: none;
font-size: 22px;
cursor: pointer;
padding: 8px 10px;
border-radius: 6px;
transition: 0.3s;
}
.menu-toggle:hover{
background: rgba(255,255,255,0.15);
}
/* Main Content */
.main-content {
margin-top: 80px;
padding: 30px;
}
.payment-container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
}
/* Payment Header */
.payment-header {
background: #f8fafc;
padding: 30px;
border-bottom: 1px solid #e2e8f0;
text-align: center;
}
.status-badge {
display: inline-block;
padding: 8px 20px;
background: #fef3c7;
color: #f59e0b;
border-radius: 50px;
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 15px;
}
.payment-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.payment-subtitle {
color: #64748b;
font-size: 1.1rem;
}
/* Property Preview */
.property-preview {
display: flex;
gap: 25px;
padding: 25px 30px;
border-bottom: 1px solid #e2e8f0;
}
.property-image {
width: 150px;
height: 150px;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.property-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.property-info {
flex: 1;
}
.property-name {
font-size: 1.4rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.property-location {
display: flex;
align-items: center;
gap: 8px;
color: #64748b;
margin-bottom: 15px;
}
.property-location i {
color: var(--primary-blue);
}
/* Payment Details Section */
.payment-details-section {
padding: 30px;
}
.section-title {
font-size: 1.3rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-blue);
}
.payment-info {
background: #f0fdf4;
border-left: 4px solid #10b981;
padding: 20px;
border-radius: 12px;
margin-bottom: 25px;
}
.payment-title-info {
font-weight: 700;
color: #059669;
margin-bottom: 15px;
font-size: 1.1rem;
}
.payment-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.payment-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #d1fae5;
}
.payment-item:last-child {
border-bottom: none;
}
.payment-label {
color: #64748b;
}
.payment-value {
font-weight: 600;
color: var(--dark-blue);
}
.total-payment {
font-size: 1.3rem;
font-weight: 800;
color: #059669;
text-align: right;
margin-top: 10px;
padding-top: 15px;
border-top: 2px solid #d1fae5;
}
/* Payment Method Section */
.payment-method-section {
background: #f8fafc;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
border: 1px solid #e2e8f0;
}
.payment-method-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.payment-method-card {
background: white;
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 15px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.payment-method-card:hover {
border-color: var(--primary-blue);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.payment-method-card.selected {
border-color: var(--primary-blue);
background: #f0f9ff;
box-shadow: 0 4px 15px rgba(var(--primary-blue-rgb), 0.2);
}
.payment-icon {
font-size: 2.5rem;
margin-bottom: 10px;
color: var(--primary-blue);
}
.payment-name {
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 5px;
}
.payment-desc {
font-size: 0.85rem;
color: #64748b;
}
/* Bank Account Section */
.bank-account-section {
background: white;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
border: 1px solid #e2e8f0;
}
.bank-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 15px;
}
.bank-card {
background: #f8fafc;
border-radius: 12px;
padding: 20px;
text-align: center;
border: 1px solid #e2e8f0;
}
.bank-logo {
font-size: 3rem;
margin-bottom: 10px;
color: var(--primary-blue);
}
.bank-name {
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 8px;
font-size: 1.1rem;
}
.bank-number {
font-family: 'Courier New', monospace;
font-size: 1.2rem;
font-weight: 700;
color: #059669;
margin-bottom: 8px;
letter-spacing: 2px;
}
.bank-owner {
font-size: 0.9rem;
color: #64748b;
}
/* Upload Section */
.upload-section {
background: white;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
}
.upload-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 20px;
}
.upload-area {
border: 2px dashed #cbd5e0;
border-radius: 12px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.upload-area:hover {
border-color: var(--primary-blue);
background: #f0f9ff;
}
.upload-icon {
font-size: 3rem;
color: var(--primary-blue);
margin-bottom: 15px;
}
.upload-text {
font-size: 1rem;
color: #4a5568;
margin-bottom: 10px;
font-weight: 600;
}
.upload-hint {
font-size: 0.9rem;
color: #94a3b8;
}
.file-input {
display: none;
}
.file-name {
margin-top: 15px;
font-size: 0.95rem;
color: var(--primary-blue);
font-weight: 600;
}
/* Payment Instructions */
.instructions-section {
background: #f8fafc;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
}
.instructions-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 15px;
}
.instructions-list {
list-style: none;
padding: 0;
margin: 0;
}
.instruction-item {
padding: 12px 0;
border-bottom: 1px solid #e2e8f0;
display: flex;
gap: 12px;
}
.instruction-item:last-child {
border-bottom: none;
}
.instruction-number {
width: 28px;
height: 28px;
background: var(--primary-blue);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
flex-shrink: 0;
}
.instruction-text {
color: #4a5568;
line-height: 1.6;
}
/* Action Buttons */
.action-buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
padding-top: 25px;
border-top: 1px solid #e2e8f0;
}
.btn {
padding: 14px 30px;
border-radius: 12px;
font-weight: 600;
font-size: 1.1rem;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
}
.btn-primary {
background: var(--primary-blue);
color: white;
}
.btn-primary:hover {
background: #6aa5c6;
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(var(--primary-blue-rgb), 0.3);
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
transform: translateY(-2px);
}
/* Responsive Design */
@media (max-width: 768px) {
.property-preview {
flex-direction: column;
text-align: center;
}
.property-image {
margin: 0 auto;
}
.payment-grid,
.bank-grid,
.payment-method-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
@media (max-width: 480px) {
.header {
padding: 15px 15px;
}
.logo-text {
display: none;
}
.logo-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.main-content {
padding: 15px;
}
.payment-title {
font-size: 1.5rem;
}
.property-name {
font-size: 1.2rem;
}
.property-image {
width: 120px;
height: 120px;
}
.bank-card {
padding: 15px;
}
.bank-number {
font-size: 1rem;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-container">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-home"></i>
</div>
<div class="logo-text">PropertiHarmoni</div>
</div>
<nav class="nav-menu">
<a href="#" class="nav-item">Beranda</a>
<a href="#" class="nav-item">Katalog</a>
<a href="#" class="nav-item">ChatBot</a>
<a href="#" class="nav-item active">Pembayaran</a>
</nav>
<div class="user-actions">
<div class="notification-icon">
<i class="fas fa-bell"></i>
</div>
<div class="profile-icon">
<i class="fas fa-user"></i>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<div class="payment-container">
<!-- Payment Header -->
<div class="payment-header">
<div class="status-badge">
<i class="fas fa-clock me-2"></i> Menunggu Pembayaran
</div>
<h2 class="payment-title">Halaman Pembayaran</h2>
<p class="payment-subtitle">Apartemen Begawan Malang - ID: MLG-2025-005</p>
</div>
<!-- Property Preview -->
<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="Apartemen Begawan Malang">
</div>
<div class="property-info">
<h3 class="property-name">Apartemen Begawan Malang</h3>
<div class="property-location">
<i class="fas fa-map-marker-alt"></i>
<span>Kota Malang, Kecamatan Klojen, Jawa Timur</span>
</div>
</div>
</div>
<!-- Payment Details Section -->
<div class="payment-details-section">
<h3 class="section-title">Detail Pembayaran</h3>
<div class="payment-info">
<div class="payment-title-info">Rincian Biaya</div>
<div class="payment-grid">
<div class="payment-item">
<span class="payment-label">Harga Properti</span>
<span class="payment-value">Rp 3.500.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Uang Muka</span>
<span class="payment-value">Rp 700.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya Administrasi</span>
<span class="payment-value">Rp 5.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya Notaris</span>
<span class="payment-value">Rp 15.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">PPN (10%)</span>
<span class="payment-value">Rp 350.000.000</span>
</div>
<div class="payment-item">
<span class="payment-label">Biaya AJB</span>
<span class="payment-value">Rp 10.000.000</span>
</div>
</div>
<div class="total-payment">Total: Rp 4.580.000.000</div>
</div>
<!-- Payment Method Section -->
<div class="payment-method-section">
<h3 class="section-title">Metode Pembayaran</h3>
<p class="form-label">Pilih metode pembayaran yang sesuai</p>
<div class="payment-method-grid">
<div class="payment-method-card selected" data-method="transfer">
<div class="payment-icon">
<i class="fas fa-credit-card"></i>
</div>
<div class="payment-name">Transfer Bank</div>
<div class="payment-desc">ATM/Internet/Mobile Banking</div>
</div>
<div class="payment-method-card" data-method="virtual">
<div class="payment-icon">
<i class="fas fa-qrcode"></i>
</div>
<div class="payment-name">Virtual Account</div>
<div class="payment-desc">Bayar via ATM/MBanking</div>
</div>
<div class="payment-method-card" data-method="ewallet">
<div class="payment-icon">
<i class="fas fa-wallet"></i>
</div>
<div class="payment-name">E-Wallet</div>
<div class="payment-desc">OVO, Gopay, Dana</div>
</div>
<div class="payment-method-card" data-method="minimarket">
<div class="payment-icon">
<i class="fas fa-store"></i>
</div>
<div class="payment-name">Minimarket</div>
<div class="payment-desc">Alfamart, Indomaret</div>
</div>
</div>
<input type="hidden" id="selectedPaymentMethod" value="transfer">
</div>
<!-- Bank Account Section -->
<div class="bank-account-section">
<h3 class="section-title">Rekening Pembayaran</h3>
<div class="bank-grid">
<div class="bank-card">
<div class="bank-logo">
<i class="fas fa-landmark"></i>
</div>
<div class="bank-name">Bank BCA</div>
<div class="bank-number">0123 456 789</div>
<div class="bank-owner">PT. Properti Harmoni</div>
</div>
<div class="bank-card">
<div class="bank-logo">
<i class="fas fa-landmark"></i>
</div>
<div class="bank-name">Bank Mandiri</div>
<div class="bank-number">137 00 1234567</div>
<div class="bank-owner">PT. Properti Harmoni</div>
</div>
<div class="bank-card">
<div class="bank-logo">
<i class="fas fa-landmark"></i>
</div>
<div class="bank-name">Bank BNI</div>
<div class="bank-number">0123 456 789</div>
<div class="bank-owner">PT. Properti Harmoni</div>
</div>
<div class="bank-card">
<div class="bank-logo">
<i class="fas fa-landmark"></i>
</div>
<div class="bank-name">Bank BRI</div>
<div class="bank-number">0123 01 001234 50 6</div>
<div class="bank-owner">PT. Properti Harmoni</div>
</div>
</div>
</div>
<!-- Upload Section -->
<div class="upload-section">
<h3 class="upload-title">Upload Bukti Pembayaran</h3>
<div class="upload-area" id="uploadArea">
<i class="fas fa-cloud-upload-alt upload-icon"></i>
<p class="upload-text">Klik atau drag file bukti pembayaran</p>
<p class="upload-hint">Format: JPG, PNG, PDF | Max: 5MB</p>
<input type="file" class="file-input" id="paymentProof" accept=".jpg,.jpeg,.png,.pdf">
<div class="file-name" id="fileName"></div>
</div>
</div>
<!-- Payment Instructions -->
<div class="instructions-section">
<h3 class="instructions-title">Petunjuk Pembayaran</h3>
<ul class="instructions-list">
<li class="instruction-item">
<div class="instruction-number">1</div>
<div class="instruction-text">Transfer sesuai dengan jumlah total pembayaran ke salah satu rekening di atas</div>
</li>
<li class="instruction-item">
<div class="instruction-number">2</div>
<div class="instruction-text">Gunakan nomor pemesanan (MLG-2025-005) sebagai berita transfer</div>
</li>
<li class="instruction-item">
<div class="instruction-number">3</div>
<div class="instruction-text">Upload bukti pembayaran (struk transfer/screenshot) pada form di atas</div>
</li>
<li class="instruction-item">
<div class="instruction-number">4</div>
<div class="instruction-text">Klik tombol "Konfirmasi Pembayaran" setelah upload berhasil</div>
</li>
<li class="instruction-item">
<div class="instruction-number">5</div>
<div class="instruction-text">Tunggu konfirmasi dari tim kami (maksimal 1x24 jam)</div>
</li>
</ul>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<button class="btn btn-secondary" id="cancelBtn">
<i class="fas fa-times"></i> Batal
</button>
<button class="btn btn-primary" id="confirmBtn">
<i class="fas fa-check-circle"></i> Konfirmasi Pembayaran
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Payment method selection
const paymentCards = document.querySelectorAll('.payment-method-card');
const selectedPaymentMethod = document.getElementById('selectedPaymentMethod');
paymentCards.forEach(card => {
card.addEventListener('click', function() {
// Remove selected class from all cards
paymentCards.forEach(c => c.classList.remove('selected'));
// Add selected class to clicked card
this.classList.add('selected');
// Update hidden input value
selectedPaymentMethod.value = this.dataset.method;
});
});
// File upload handling
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('paymentProof');
const fileName = document.getElementById('fileName');
uploadArea.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
fileName.textContent = `✓ ${this.files[0].name}`;
fileName.style.color = var(--primary-blue);
}
});
// Cancel button
document.getElementById('cancelBtn').addEventListener('click', function() {
if (confirm('Apakah Anda yakin ingin membatalkan pembayaran?')) {
alert('Pembayaran dibatalkan.');
// In real application: window.location.href = '/riwayat-pemesanan';
}
});
// Confirm button
document.getElementById('confirmBtn').addEventListener('click', function() {
if (!document.getElementById('fileName').textContent) {
alert('Silakan upload bukti pembayaran terlebih dahulu.');
return;
}
alert('Konfirmasi pembayaran berhasil dikirim! Tim kami akan segera memverifikasi pembayaran Anda.');
// In real application: submit form to /pembayaran/konfirmasi
// window.location.href = '/riwayat-pemesanan';
});
// Add subtle animation on page load
const paymentContainer = document.querySelector('.payment-container');
paymentContainer.style.opacity = '0';
paymentContainer.style.transform = 'translateY(20px)';
setTimeout(() => {
paymentContainer.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
paymentContainer.style.opacity = '1';
paymentContainer.style.transform = 'translateY(0)';
}, 100);
});
</script>
</body>
</html>

View File

@ -0,0 +1,805 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profil Saya - PropertiHarmoni</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">
<style>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width: 260px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8fafc;
overflow-x: hidden;
}
/* Header Styles */
.header {
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
padding: 15px 30px;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 50px;
height: 50px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.logo-icon i {
font-size: 24px;
color: var(--dark-blue);
}
.logo-text {
font-weight: 700;
font-size: 1.2rem;
letter-spacing: 1px;
}
.nav-menu {
display: flex;
align-items: center;
gap: 30px;
}
.nav-item {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
color: var(--primary-blue);
}
.nav-item.active {
color: var(--primary-blue);
font-weight: 600;
}
.nav-item::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: var(--primary-blue);
transition: width 0.3s ease;
}
.nav-item:hover::after {
width: 100%;
}
.user-actions {
display: flex;
align-items: center;
gap: 20px;
}
.notification-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: white;
text-decoration: none;
}
.notification-icon:hover {
background: rgba(255,255,255,0.2);
}
.profile-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.profile-icon:hover {
background: rgba(255,255,255,0.2);
}
.menu-toggle{
display: none;
font-size: 22px;
cursor: pointer;
padding: 8px 10px;
border-radius: 6px;
transition: 0.3s;
}
.menu-toggle:hover{
background: rgba(255,255,255,0.15);
}
/* Main Content */
.main-content {
margin-top: 80px;
padding: 30px;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.profile-container {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 40px;
}
.profile-header {
text-align: center;
margin-bottom: 40px;
}
.profile-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
background: var(--primary-blue);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
font-size: 36px;
font-weight: 700;
color: white;
border: 4px solid #f8fafc;
box-shadow: 0 8px 20px rgba(122, 178, 211, 0.3);
}
.profile-name {
font-size: 1.8rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.profile-email {
color: #64748b;
font-size: 1.1rem;
}
/* Personal Information Section */
.personal-info {
margin-bottom: 40px;
}
.section-title {
font-size: 1.4rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 25px;
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-blue);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 25px;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.95rem;
color: #64748b;
margin-bottom: 8px;
font-weight: 500;
}
.info-value {
font-size: 1.1rem;
color: var(--dark-blue);
font-weight: 600;
padding: 12px 16px;
background: #f8fafc;
border-radius: 10px;
border: 1px solid #e2e8f0;
}
/* Security Section */
.security-section {
background: #f8fafc;
border-radius: 12px;
padding: 25px;
border: 1px solid #e2e8f0;
}
.security-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 20px;
text-align: center;
}
.security-actions {
display: flex;
gap: 20px;
justify-content: center;
}
.security-btn {
padding: 12px 30px;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
}
.btn-change-password {
background: var(--primary-blue);
color: white;
}
.btn-change-password:hover {
background: #6aa5c6;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(var(--primary-blue-rgb), 0.3);
}
.btn-logout {
background: #ef4444;
color: white;
}
.btn-logout:hover {
background: #dc2626;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
}
/* Modal Ganti Password */
.password-modal {
border-radius: 16px;
border: none;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
}
.password-modal .modal-header {
background: linear-gradient(135deg, var(--dark-blue), #1e3a5f);
color: white;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
.password-modal .modal-title {
font-weight: 700;
}
.password-modal .form-control {
border-radius: 10px;
padding: 12px 14px;
border: 1px solid #e2e8f0;
}
.password-modal .form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 0.2rem rgba(var(--primary-blue-rgb), 0.25);
}
.btn-save-password {
width: 100%;
margin-top: 15px;
background: var(--primary-blue);
color: white;
border: none;
padding: 12px;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-save-password:hover {
background: #6aa5c6;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
}
/* Responsive Design */
@media (max-width: 768px) {
.header{
padding: 15px;
}
.header-container{
display: flex;
flex-direction: row; /* ← jadi satu baris */
align-items: center;
justify-content: space-between; /* ← kiri & kanan */
width: 100%;
}
/* tombol toggle muncul di mobile */
.menu-toggle{
display: block;
color: white;
order: 2;
}
/* nav jadi dropdown */
.nav-menu{
display: none;
position: absolute;
top: 70px;
left: 0;
width: 100%;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
flex-direction: column;
padding: 15px 0;
gap: 0;
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.nav-menu.show{
display: flex;
}
.nav-item{
padding: 12px 20px;
width: 100%;
}
.nav-item::after{
display: none;
}
.logo-text {
font-size: 1rem;
}
.nav-menu {
display: none;
margin-bottom: 20px;
}
.user-actions {
margin-left: auto; /* ← dorong ke kanan */
display: flex;
align-items: center;
gap: 10px;
}
.main-content {
padding: 20px;
}
.profile-container {
padding: 30px 20px;
}
.profile-avatar {
width: 80px;
height: 80px;
font-size: 28px;
}
.profile-name {
font-size: 1.5rem;
}
.profile-email {
font-size: 1rem;
}
.info-grid {
gap: 20px;
}
.security-actions {
flex-direction: column;
gap: 15px;
}
.security-btn {
width: 100%;
justify-content: center;
}
}
@media (max-width: 480px) {
.header {
padding: 15px 15px;
}
.logo-text {
display: none;
}
.logo-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.main-content {
padding: 15px;
}
.profile-container {
padding: 25px 15px;
}
.profile-avatar {
width: 70px;
height: 70px;
font-size: 24px;
}
.profile-name {
font-size: 1.3rem;
}
.section-title {
font-size: 1.2rem;
}
.info-label {
font-size: 0.9rem;
}
.info-value {
font-size: 1rem;
padding: 10px 14px;
}
.security-title {
font-size: 1.1rem;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-container">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-home"></i>
</div>
<div class="logo-text">Carani Estate</div>
</div>
<div class="menu-toggle" onclick="toggleMenu()">
<i class="fas fa-bars"></i>
</div>
<nav class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
Beranda
</a>
<a href="{{ route('tentang-kami') }}"
class="nav-item {{ request()->routeIs('tentang-kami') ? 'active' : '' }}">
Tentang Kami
</a>
<a href="{{ route('halaman-katalog') }}"
class="nav-item {{ request()->routeIs('halaman-katalog') ? 'active' : '' }}">
Katalog
</a>
<a href="{{ route('halaman-chatbot') }}"
class="nav-item {{ request()->routeIs('halaman-chatbot') ? 'active' : '' }}">
ChatBot
</a>
<a href="{{ route('riwayat-pemesanan') }}"
class="nav-item {{ request()->routeIs('riwayat-pemesanan') ? 'active' : '' }}">
Riwayat Pemesanan
</a>
<a href="{{ route('halaman-kontak') }}"
class="nav-item {{ request()->routeIs('halaman-kontak') ? 'active' : '' }}">
Kontak
</a>
</nav>
<div class="user-actions">
<a href="{{ route('halaman-notifikasi') }}" class="notification-icon">
<i class="fas fa-bell"></i>
</a>
<div class="profile-dropdown">
<div class="profile-icon" onclick="toggleDropdown()">
<i class="fas fa-user"></i>
</div>
<div class="dropdown-menu" id="dropdownMenu">
<a href="{{ route('halaman-profil') }}">
<i class="fas fa-user-circle"></i> Lihat Profil
</a>
<hr>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</form>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<div class="profile-container">
<!-- Profile Header -->
<div class="profile-header">
<div class="profile-avatar">N</div>
<h2 class="profile-name">Nayla Putri Wijaya</h2>
<p class="profile-email">nayla.pw@gmail.com</p>
</div>
<!-- Personal Information -->
<div class="personal-info">
<h3 class="section-title">Informasi Pribadi</h3>
<div class="info-grid">
<div class="info-item">
<span class="info-label">Nama Lengkap</span>
<span class="info-value">Nayla Putri Wijaya</span>
</div>
<div class="info-item">
<span class="info-label">Nomor HP</span>
<span class="info-value">0813-4567-8901</span>
</div>
<div class="info-item">
<span class="info-label">Email</span>
<span class="info-value">nayla.pw@gmail.com</span>
</div>
<div class="info-item">
<span class="info-label">Nomor KTP</span>
<span class="info-value">3171012345678901</span>
</div>
<div class="info-item">
<span class="info-label">Alamat</span>
<span class="info-value">Jl. Melati No. 45, RT 05/RW 03, Kelurahan Menteng, Kecamatan Menteng, Jakarta Pusat</span>
</div>
<div class="info-item">
<span class="info-label">Pekerjaan</span>
<span class="info-value">PNS - Marketing Staff</span>
</div>
</div>
</div>
<!-- Security Settings -->
<div class="security-section">
<h4 class="security-title">Pengaturan Keamanan</h4>
<div class="security-actions">
<button class="security-btn btn-change-password">
<i class="fas fa-lock"></i> Ganti Password
</button>
<button class="security-btn btn-logout">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const changePasswordBtn = document.querySelector(".btn-change-password");
const modalElement = document.getElementById("changePasswordModal");
const modal = new bootstrap.Modal(modalElement);
changePasswordBtn.addEventListener("click", function () {
modal.show();
});
const form = modalElement.querySelector("form");
form.addEventListener("submit", function (e) {
e.preventDefault();
const oldPass = form.querySelectorAll("input")[0].value;
const newPass = form.querySelectorAll("input")[1].value;
const confirmPass = form.querySelectorAll("input")[2].value;
if (!oldPass || !newPass || !confirmPass) {
alert("Semua field wajib diisi!");
return;
}
if (newPass.length < 8) {
alert("Password baru minimal 8 karakter!");
return;
}
if (newPass !== confirmPass) {
alert("Konfirmasi password tidak cocok!");
return;
}
alert("Password berhasil diperbarui ✅");
form.reset();
modal.hide();
});
});
</script>
<script>
// Logout button
document.querySelector('.btn-logout').addEventListener('click', function() {
if (confirm('Apakah Anda yakin ingin logout?')) {
alert('Logout berhasil!');
// In a real Laravel application, you would submit a logout form
// document.getElementById('logout-form').submit();
}
});
// Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
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
}
}
});
});
// Add hover effect to table rows
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('mouseenter', function() {
this.style.background = '#f8fafc';
});
row.addEventListener('mouseleave', function() {
this.style.background = '';
});
});
// Pagination functionality
document.querySelectorAll('.page-item').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.page-item').forEach(i => i.classList.remove('active'));
this.classList.add('active');
alert(`Menampilkan halaman ${this.textContent}`);
});
});
</script>
<!-- Modal Ganti Password -->
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content password-modal">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-lock"></i> Ganti Password
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label class="form-label">Password Lama</label>
<input type="password" class="form-control" placeholder="Masukkan password lama">
</div>
<div class="mb-3">
<label class="form-label">Password Baru</label>
<input type="password" class="form-control" placeholder="Masukkan password baru">
</div>
<div class="mb-3">
<label class="form-label">Konfirmasi Password Baru</label>
<input type="password" class="form-control" placeholder="Ulangi password baru">
</div>
<button type="submit" class="btn-save-password">
<i class="fas fa-save"></i> Simpan Perubahan
</button>
</form>
</div>
</div>
</div>
</div>
<script>
// TOGGLE NAV
function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
</script>
</body>
</html>

View File

@ -0,0 +1,542 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daftar - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f0f9ff 0%, #e1f5fe 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.register-container {
display: flex;
width: 100%;
max-width: 1000px;
background: white;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 60px rgba(30, 58, 95, 0.2);
}
/* Left Side - Branding */
.register-branding {
flex: 1;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1a365d 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
position: relative;
overflow: hidden;
}
.branding-content {
text-align: center;
max-width: 400px;
position: relative;
z-index: 2;
}
.logo {
width: 100px;
height: 100px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
border: 4px solid rgba(255,255,255,0.3);
}
.logo i {
font-size: 48px;
color: var(--dark-blue);
}
.branding-title {
color: white;
font-size: 2.2rem;
font-weight: 800;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.branding-subtitle {
color: rgba(255, 255, 255, 0.85);
font-size: 1.2rem;
line-height: 1.5;
margin-bottom: 30px;
}
.features-list {
list-style: none;
margin: 0;
padding: 0;
}
.feature-item {
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 10px;
}
.feature-item i {
color: var(--primary-blue);
font-size: 14px;
}
/* Right Side - Register Form */
.register-form {
flex: 1;
padding: 50px 40px;
display: flex;
flex-direction: column;
}
.form-header {
text-align: center;
margin-bottom: 25px;
}
.form-logo {
width: 70px;
height: 70px;
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.3);
}
.form-logo i {
font-size: 32px;
color: white;
}
.form-title {
color: var(--dark-blue);
font-size: 2rem;
font-weight: 800;
margin-bottom: 12px;
}
.form-subtitle {
color: #64748b;
font-size: 1rem;
line-height: 1.5;
}
.form-group {
margin-bottom: 16px;
position: relative;
}
.form-label {
font-weight: 600;
color: #2d3748;
margin-bottom: 10px;
font-size: 0.95rem;
}
.form-control {
border: 2px solid #e2e8f0;
border-radius: 14px;
padding: 14px 20px;
font-size: 1rem;
transition: all 0.3s ease;
background-color: #f8fafc;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 4px rgba(var(--primary-blue-rgb), 0.15);
background-color: white;
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
.input-group {
position: relative;
}
.input-group-text {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 14px 0 0 14px;
padding: 14px 20px;
color: #718096;
border-right: none;
}
.form-control.password-input {
border-radius: 0 14px 14px 0;
border-left: none;
background-color: #f8fafc;
}
.eye-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #718096;
transition: all 0.3s ease;
font-size: 18px;
}
.eye-icon:hover {
color: var(--primary-blue);
}
.btn-register {
background: var(--primary-blue);
color: #fff;
border: none;
padding: 16px;
font-weight: 700;
font-size: 1.1rem;
border-radius: 14px;
transition: all 0.4s ease;
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 10px;
}
.btn-register:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.4);
}
.btn-register:active {
transform: translateY(0);
}
.login-link {
text-align: center;
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #edf2f7;
}
.login-link p {
margin: 0;
color: #4a5568;
font-size: 1rem;
}
.login-link a {
color: var(--primary-blue);
text-decoration: none;
font-weight: 700;
transition: all 0.3s ease;
}
.login-link a:hover {
color: #3d85aa;
text-decoration: underline;
}
/* Responsive Design */
@media (max-width: 992px) {
.register-container {
flex-direction: column;
max-width: 500px;
}
.register-branding {
height: 280px;
padding: 30px 20px;
}
.register-form {
padding: 40px 30px;
}
.branding-title {
font-size: 1.8rem;
}
.branding-subtitle {
font-size: 1rem;
}
.logo {
width: 80px;
height: 80px;
}
.logo i {
font-size: 40px;
}
}
@media (max-width: 480px) {
.register-container {
border-radius: 20px;
max-width: 100%;
}
.register-branding {
height: 240px;
padding: 25px 15px;
}
.register-form {
padding: 35px 25px;
}
.branding-title {
font-size: 1.5rem;
}
.branding-subtitle {
font-size: 0.9rem;
}
.form-title {
font-size: 1.7rem;
}
.form-subtitle {
font-size: 1rem;
}
.logo {
width: 70px;
height: 70px;
}
.logo i {
font-size: 32px;
}
.form-logo {
width: 60px;
height: 60px;
}
.form-logo i {
font-size: 28px;
}
.btn-register {
font-size: 1rem;
padding: 14px;
letter-spacing: 0.3px;
}
}
</style>
</head>
<body>
<div class="register-container">
<!-- Left Side - Branding -->
<div class="register-branding">
<div class="branding-content">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<h1 class="branding-title">Carani Estate</h1>
<p class="branding-subtitle">Platform terpercaya untuk membeli, menjual, dan menyewa properti berkualitas sejak 2015</p>
<ul class="features-list">
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Lebih dari 10.000 properti tersedia</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Proses transaksi aman dan transparan</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Dukungan customer service 24/7</span>
</li>
</ul>
</div>
</div>
<!-- Right Side - Register Form -->
<div class="register-form">
<div class="form-header">
<div class="form-logo">
<i class="fas fa-user-plus"></i>
</div>
<h2 class="form-title">Buat Akun Baru</h2>
<p class="form-subtitle">Daftar sekarang untuk mengakses semua fitur layanan kami</p>
</div>
<form id="registerForm">
<div class="form-group">
<label for="fullName" class="form-label">Nama Lengkap</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-user"></i>
</span>
<input type="text" class="form-control" id="fullName" placeholder="Masukkan nama lengkap Anda" required>
</div>
</div>
<div class="form-group">
<label for="email" class="form-label">Email</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-envelope"></i>
</span>
<input type="email" class="form-control" id="email" placeholder="Masukkan email Anda" required>
</div>
</div>
<div class="form-group">
<label for="phone" class="form-label">Nomor HP</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-phone"></i>
</span>
<input type="tel" class="form-control" id="phone" placeholder="Contoh: 081234567890" required>
</div>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password" class="form-control password-input" id="password" placeholder="Buat password yang kuat" required>
<span class="eye-icon" id="togglePassword">
<i class="fas fa-eye"></i>
</span>
</div>
</div>
<div class="form-group">
<label for="confirmPassword" class="form-label">Konfirmasi Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password" class="form-control password-input" id="confirmPassword" placeholder="Ketik ulang password" required>
<span class="eye-icon" id="toggleConfirmPassword">
<i class="fas fa-eye"></i>
</span>
</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>
</form>
</div>
</div>
<script>
// Toggle password visibility
function setupPasswordToggle(toggleId, passwordId) {
document.getElementById(toggleId).addEventListener('click', function() {
const passwordInput = document.getElementById(passwordId);
const icon = this.querySelector('i');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
passwordInput.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
}
// Setup both password toggles
setupPasswordToggle('togglePassword', 'password');
setupPasswordToggle('toggleConfirmPassword', 'confirmPassword');
// Form submission
document.getElementById('registerForm').addEventListener('submit', function(e) {
e.preventDefault();
const fullName = document.getElementById('fullName').value;
const email = document.getElementById('email').value;
const phone = document.getElementById('phone').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
// Simple validation
if (!fullName || !email || !phone || !password || !confirmPassword) {
alert('Mohon lengkapi semua field yang wajib diisi.');
return;
}
if (password !== confirmPassword) {
alert('Password dan konfirmasi password tidak cocok.');
return;
}
if (password.length < 8) {
alert('Password minimal harus 8 karakter.');
return;
}
// In a real Laravel application, you would submit this to your register route
console.log('Register attempt:', { fullName, email, phone, password });
alert('Pendaftaran berhasil! Silakan login dengan akun Anda.');
window.location.href = 'login.html';
});
// Add subtle animation on page load
document.addEventListener('DOMContentLoaded', function() {
const registerContainer = document.querySelector('.register-container');
registerContainer.style.opacity = '0';
registerContainer.style.transform = 'translateY(20px)';
setTimeout(() => {
registerContainer.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
registerContainer.style.opacity = '1';
registerContainer.style.transform = 'translateY(0)';
}, 100);
});
</script>
</body>
</html>

View File

@ -0,0 +1,605 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f0f9ff 0%, #e1f5fe 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
display: flex;
width: 100%;
max-width: 1000px;
background: white;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 60px rgba(30, 58, 95, 0.2);
}
/* Left Side - Branding */
.login-branding {
flex: 1;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1a365d 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
position: relative;
overflow: hidden;
}
.branding-content {
text-align: center;
max-width: 400px;
position: relative;
z-index: 2;
}
.logo {
width: 100px;
height: 100px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
border: 4px solid rgba(255,255,255,0.3);
}
.logo i {
font-size: 48px;
color: var(--dark-blue);
}
.branding-title {
color: white;
font-size: 2.2rem;
font-weight: 800;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.branding-subtitle {
color: rgba(255, 255, 255, 0.85);
font-size: 1.2rem;
line-height: 1.5;
margin-bottom: 30px;
}
.features-list {
list-style: none;
margin: 0;
padding: 0;
}
.feature-item {
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 10px;
}
.feature-item i {
color: var(--primary-blue);
font-size: 14px;
}
/* Right Side - Login Form */
.login-form {
flex: 1;
padding: 50px 40px;
display: flex;
flex-direction: column;
}
.form-header {
text-align: center;
margin-bottom: 35px;
}
.form-logo {
width: 70px;
height: 70px;
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.3);
}
.form-logo i {
font-size: 32px;
color: white;
}
.form-title {
color: var(--dark-blue);
font-size: 2rem;
font-weight: 800;
margin-bottom: 12px;
}
.form-subtitle {
color: #64748b;
font-size: 1.1rem;
line-height: 1.5;
}
.form-group {
margin-bottom: 22px;
position: relative;
}
.form-label {
font-weight: 600;
color: #2d3748;
margin-bottom: 10px;
font-size: 0.95rem;
}
.form-control {
border: 2px solid #e2e8f0;
border-radius: 14px;
padding: 14px 20px;
font-size: 1rem;
transition: all 0.3s ease;
background-color: #f8fafc;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 4px rgba(var(--primary-blue-rgb), 0.15);
background-color: white;
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
.input-group {
position: relative;
}
.input-group-text {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 14px 0 0 14px;
padding: 14px 20px;
color: #718096;
border-right: none;
}
.form-control.password-input {
border-radius: 0 14px 14px 0;
border-left: none;
background-color: #f8fafc;
}
.eye-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #718096;
transition: all 0.3s ease;
font-size: 18px;
}
.eye-icon:hover {
color: var(--primary-blue);
}
.btn-login {
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
border: none;
padding: 16px;
font-weight: 700;
font-size: 1.1rem;
border-radius: 14px;
transition: all 0.4s ease;
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 10px;
color: white;
}
.btn-login:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.4);
}
.btn-login:active {
transform: translateY(0);
}
.forgot-password {
text-align: right;
margin-top: 8px;
}
.forgot-password a {
color: var(--primary-blue);
text-decoration: none;
font-weight: 600;
font-size: 0.95rem;
transition: all 0.3s ease;
}
.forgot-password a:hover {
color: #3d85aa;
text-decoration: underline;
}
.divider {
display: flex;
align-items: center;
text-align: center;
margin: 30px 0;
}
.divider::before,
.divider::after {
content: '';
flex: 1;
border-bottom: 1px solid #e2e8f0;
}
.divider span {
padding: 0 20px;
color: #718096;
font-size: 0.95rem;
font-weight: 500;
background: white;
}
.user-type-selector {
display: flex;
gap: 15px;
margin-bottom: 25px;
}
.user-type-btn {
flex: 1;
padding: 12px;
border: 2px solid #e2e8f0;
border-radius: 12px;
background: white;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.user-type-btn.active {
background: var(--primary-blue);
color: white;
border-color: var(--primary-blue);
}
.user-type-btn:not(.active):hover {
border-color: var(--primary-blue);
background: #f8fafc;
}
.signup-link {
text-align: center;
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #edf2f7;
}
.signup-link p {
margin: 0;
color: #4a5568;
font-size: 1rem;
}
.signup-link a {
color: var(--primary-blue);
text-decoration: none;
font-weight: 700;
transition: all 0.3s ease;
}
.signup-link a:hover {
color: #3d85aa;
text-decoration: underline;
}
/* Responsive Design */
@media (max-width: 992px) {
.login-container {
flex-direction: column;
max-width: 500px;
}
.login-branding {
height: 280px;
padding: 30px 20px;
}
.login-form {
padding: 40px 30px;
}
.branding-title {
font-size: 1.8rem;
}
.branding-subtitle {
font-size: 1rem;
}
.logo {
width: 80px;
height: 80px;
}
.logo i {
font-size: 40px;
}
}
@media (max-width: 480px) {
.login-container {
border-radius: 20px;
max-width: 100%;
}
.login-branding {
height: 240px;
padding: 25px 15px;
}
.login-form {
padding: 35px 25px;
}
.branding-title {
font-size: 1.5rem;
}
.branding-subtitle {
font-size: 0.9rem;
}
.form-title {
font-size: 1.7rem;
}
.form-subtitle {
font-size: 1rem;
}
.logo {
width: 70px;
height: 70px;
}
.logo i {
font-size: 32px;
}
.form-logo {
width: 60px;
height: 60px;
}
.form-logo i {
font-size: 28px;
}
.btn-login {
font-size: 1rem;
padding: 14px;
letter-spacing: 0.3px;
}
.user-type-selector {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body>
<div class="login-container">
<!-- Left Side - Branding -->
<div class="login-branding">
<div class="branding-content">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<h1 class="branding-title">Carani Estate</h1>
<p class="branding-subtitle">Platform terpercaya untuk membeli, menjual, dan menyewa properti berkualitas sejak 2015</p>
<ul class="features-list">
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Lebih dari 10.000 properti tersedia</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Proses transaksi aman dan transparan</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Dukungan customer service 24/7</span>
</li>
</ul>
</div>
</div>
<!-- Right Side - Login Form -->
<div class="login-form">
<div class="form-header">
<div class="form-logo">
<i class="fas fa-user"></i>
</div>
<h2 class="form-title">Login ke Akun Anda</h2>
<p class="form-subtitle">Masuk dengan akun yang sudah terdaftar untuk mengakses layanan kami</p>
</div>
<form method="POST" action="{{ route('login.proses') }}">
@csrf
<div class="form-group">
<label for="email" class="form-label">Email</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-envelope"></i>
</span>
<input type="email"
class="form-control @error('email') is-invalid @enderror"
id="email"
name="email"
value="{{ old('email') }}"
placeholder="Masukkan email Anda" required>
</div>
{{-- Tampilkan error email --}}
@error('email')
<div style="color:red; font-size:0.85rem; margin-top:5px;">
{{ $message }}
</div>
@enderror
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password"
class="form-control password-input @error('password') is-invalid @enderror"
id="password"
name="password"
placeholder="Masukkan password" required>
<span class="eye-icon" id="togglePassword">
<i class="fas fa-eye"></i>
</span>
</div>
{{-- Tampilkan error password --}}
@error('password')
<div style="color:red; font-size:0.85rem; margin-top:5px;">
{{ $message }}
</div>
@enderror
</div>
<div class="forgot-password">
<a href="{{ route('ganti-password') }}">Lupa password?</a>
</div>
<button type="submit" class="btn-login w-100">
<i class="fas fa-sign-in-alt me-2"></i>Masuk ke Akun
</button>
</form>
</div>
</div>
<script>
// Toggle password visibility
document.getElementById('togglePassword').addEventListener('click', function() {
const passwordInput = document.getElementById('password');
const icon = this.querySelector('i');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
passwordInput.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
// User type selection
const userButtons = document.querySelectorAll('.user-type-btn');
userButtons.forEach(button => {
button.addEventListener('click', function() {
userButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
});
});
// Form submission
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const userType = document.querySelector('.user-type-btn.active').dataset.type;
// In a real Laravel application, you would submit this to your login route
console.log('Login attempt:', { email, password, userType });
// Simple validation
if (email && password) {
alert(`Login sebagai ${userType === 'user' ? 'Pengguna' : 'Admin'} berhasil!`);
// Redirect based on user type
if (userType === 'admin') {
window.location.href = 'admin-dashboard.html';
} else {
window.location.href = 'user-dashboard.html';
}
} else {
alert('Mohon lengkapi semua field yang wajib diisi.');
}
});
// Add subtle animation on page load
document.addEventListener('DOMContentLoaded', function() {
const loginContainer = document.querySelector('.login-container');
loginContainer.style.opacity = '0';
loginContainer.style.transform = 'translateY(20px)';
setTimeout(() => {
loginContainer.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
loginContainer.style.opacity = '1';
loginContainer.style.transform = 'translateY(0)';
}, 100);
});
</script>
</body>
</html>

View File

@ -0,0 +1,542 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daftar - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f0f9ff 0%, #e1f5fe 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.register-container {
display: flex;
width: 100%;
max-width: 1000px;
background: white;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 60px rgba(30, 58, 95, 0.2);
}
/* Left Side - Branding */
.register-branding {
flex: 1;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1a365d 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
position: relative;
overflow: hidden;
}
.branding-content {
text-align: center;
max-width: 400px;
position: relative;
z-index: 2;
}
.logo {
width: 100px;
height: 100px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
border: 4px solid rgba(255,255,255,0.3);
}
.logo i {
font-size: 48px;
color: var(--dark-blue);
}
.branding-title {
color: white;
font-size: 2.2rem;
font-weight: 800;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.branding-subtitle {
color: rgba(255, 255, 255, 0.85);
font-size: 1.2rem;
line-height: 1.5;
margin-bottom: 30px;
}
.features-list {
list-style: none;
margin: 0;
padding: 0;
}
.feature-item {
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 10px;
}
.feature-item i {
color: var(--primary-blue);
font-size: 14px;
}
/* Right Side - Register Form */
.register-form {
flex: 1;
padding: 50px 40px;
display: flex;
flex-direction: column;
}
.form-header {
text-align: center;
margin-bottom: 25px;
}
.form-logo {
width: 70px;
height: 70px;
background: linear-gradient(135deg, var(--primary-blue) 0%, #4a90b7 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.3);
}
.form-logo i {
font-size: 32px;
color: white;
}
.form-title {
color: var(--dark-blue);
font-size: 2rem;
font-weight: 800;
margin-bottom: 12px;
}
.form-subtitle {
color: #64748b;
font-size: 1rem;
line-height: 1.5;
}
.form-group {
margin-bottom: 16px;
position: relative;
}
.form-label {
font-weight: 600;
color: #2d3748;
margin-bottom: 10px;
font-size: 0.95rem;
}
.form-control {
border: 2px solid #e2e8f0;
border-radius: 14px;
padding: 14px 20px;
font-size: 1rem;
transition: all 0.3s ease;
background-color: #f8fafc;
}
.form-control:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 4px rgba(var(--primary-blue-rgb), 0.15);
background-color: white;
outline: none;
}
.form-control::placeholder {
color: #a0aec0;
}
.input-group {
position: relative;
}
.input-group-text {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 14px 0 0 14px;
padding: 14px 20px;
color: #718096;
border-right: none;
}
.form-control.password-input {
border-radius: 0 14px 14px 0;
border-left: none;
background-color: #f8fafc;
}
.eye-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #718096;
transition: all 0.3s ease;
font-size: 18px;
}
.eye-icon:hover {
color: var(--primary-blue);
}
.btn-register {
background: var(--primary-blue);
color: #fff;
border: none;
padding: 16px;
font-weight: 700;
font-size: 1.1rem;
border-radius: 14px;
transition: all 0.4s ease;
box-shadow: 0 6px 20px rgba(var(--primary-blue-rgb), 0.3);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 10px;
}
.btn-register:hover {
background: linear-gradient(135deg, #6aa5c6 0%, #3d85aa 100%);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(var(--primary-blue-rgb), 0.4);
}
.btn-register:active {
transform: translateY(0);
}
.login-link {
text-align: center;
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #edf2f7;
}
.login-link p {
margin: 0;
color: #4a5568;
font-size: 1rem;
}
.login-link a {
color: var(--primary-blue);
text-decoration: none;
font-weight: 700;
transition: all 0.3s ease;
}
.login-link a:hover {
color: #3d85aa;
text-decoration: underline;
}
/* Responsive Design */
@media (max-width: 992px) {
.register-container {
flex-direction: column;
max-width: 500px;
}
.register-branding {
height: 280px;
padding: 30px 20px;
}
.register-form {
padding: 40px 30px;
}
.branding-title {
font-size: 1.8rem;
}
.branding-subtitle {
font-size: 1rem;
}
.logo {
width: 80px;
height: 80px;
}
.logo i {
font-size: 40px;
}
}
@media (max-width: 480px) {
.register-container {
border-radius: 20px;
max-width: 100%;
}
.register-branding {
height: 240px;
padding: 25px 15px;
}
.register-form {
padding: 35px 25px;
}
.branding-title {
font-size: 1.5rem;
}
.branding-subtitle {
font-size: 0.9rem;
}
.form-title {
font-size: 1.7rem;
}
.form-subtitle {
font-size: 1rem;
}
.logo {
width: 70px;
height: 70px;
}
.logo i {
font-size: 32px;
}
.form-logo {
width: 60px;
height: 60px;
}
.form-logo i {
font-size: 28px;
}
.btn-register {
font-size: 1rem;
padding: 14px;
letter-spacing: 0.3px;
}
}
</style>
</head>
<body>
<div class="register-container">
<!-- Left Side - Branding -->
<div class="register-branding">
<div class="branding-content">
<div class="logo">
<i class="fas fa-home"></i>
</div>
<h1 class="branding-title">Carani Estate</h1>
<p class="branding-subtitle">Platform terpercaya untuk membeli, menjual, dan menyewa properti berkualitas sejak 2015</p>
<ul class="features-list">
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Lebih dari 10.000 properti tersedia</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Proses transaksi aman dan transparan</span>
</li>
<li class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Dukungan customer service 24/7</span>
</li>
</ul>
</div>
</div>
<!-- Right Side - Register Form -->
<div class="register-form">
<div class="form-header">
<div class="form-logo">
<i class="fas fa-user-plus"></i>
</div>
<h2 class="form-title">Buat Akun Baru</h2>
<p class="form-subtitle">Daftar sekarang untuk mengakses semua fitur layanan kami</p>
</div>
<form id="registerForm">
<div class="form-group">
<label for="fullName" class="form-label">Nama Lengkap</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-user"></i>
</span>
<input type="text" class="form-control" id="fullName" placeholder="Masukkan nama lengkap Anda" required>
</div>
</div>
<div class="form-group">
<label for="email" class="form-label">Email</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-envelope"></i>
</span>
<input type="email" class="form-control" id="email" placeholder="Masukkan email Anda" required>
</div>
</div>
<div class="form-group">
<label for="phone" class="form-label">Nomor HP</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-phone"></i>
</span>
<input type="tel" class="form-control" id="phone" placeholder="Contoh: 081234567890" required>
</div>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password" class="form-control password-input" id="password" placeholder="Buat password yang kuat" required>
<span class="eye-icon" id="togglePassword">
<i class="fas fa-eye"></i>
</span>
</div>
</div>
<div class="form-group">
<label for="confirmPassword" class="form-label">Konfirmasi Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-lock"></i>
</span>
<input type="password" class="form-control password-input" id="confirmPassword" placeholder="Ketik ulang password" required>
<span class="eye-icon" id="toggleConfirmPassword">
<i class="fas fa-eye"></i>
</span>
</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>
</form>
</div>
</div>
<script>
// Toggle password visibility
function setupPasswordToggle(toggleId, passwordId) {
document.getElementById(toggleId).addEventListener('click', function() {
const passwordInput = document.getElementById(passwordId);
const icon = this.querySelector('i');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
passwordInput.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
}
// Setup both password toggles
setupPasswordToggle('togglePassword', 'password');
setupPasswordToggle('toggleConfirmPassword', 'confirmPassword');
// Form submission
document.getElementById('registerForm').addEventListener('submit', function(e) {
e.preventDefault();
const fullName = document.getElementById('fullName').value;
const email = document.getElementById('email').value;
const phone = document.getElementById('phone').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
// Simple validation
if (!fullName || !email || !phone || !password || !confirmPassword) {
alert('Mohon lengkapi semua field yang wajib diisi.');
return;
}
if (password !== confirmPassword) {
alert('Password dan konfirmasi password tidak cocok.');
return;
}
if (password.length < 8) {
alert('Password minimal harus 8 karakter.');
return;
}
// In a real Laravel application, you would submit this to your register route
console.log('Register attempt:', { fullName, email, phone, password });
alert('Pendaftaran berhasil! Silakan login dengan akun Anda.');
window.location.href = 'login.html';
});
// Add subtle animation on page load
document.addEventListener('DOMContentLoaded', function() {
const registerContainer = document.querySelector('.register-container');
registerContainer.style.opacity = '0';
registerContainer.style.transform = 'translateY(20px)';
setTimeout(() => {
registerContainer.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
registerContainer.style.opacity = '1';
registerContainer.style.transform = 'translateY(0)';
}, 100);
});
</script>
</body>
</html>

View File

@ -0,0 +1,953 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Riwayat Pemesanan - PropertiHarmoni</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>
:root {
--primary-blue: #7AB2D3;
--primary-blue-rgb: 122, 178, 211;
--dark-blue: #1E3A5F;
--light-blue: #e6f2f8;
--sidebar-width: 260px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Roboto", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
background: #f8fafc;
overflow-x: hidden;
}
/* Header Styles */
.header {
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
color: white;
padding: 15px 30px;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 50px;
height: 50px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.logo-icon i {
font-size: 24px;
color: var(--dark-blue);
}
.logo-text {
font-weight: 700;
font-size: 1.2rem;
letter-spacing: 1px;
}
.nav-menu {
display: flex;
align-items: center;
gap: 30px;
}
.nav-item {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
color: var(--primary-blue);
}
.nav-item.active {
color: var(--primary-blue);
font-weight: 600;
}
.nav-item::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: var(--primary-blue);
transition: width 0.3s ease;
}
.nav-item:hover::after {
width: 100%;
}
.user-actions {
display: flex;
align-items: center;
gap: 20px;
}
.notification-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: white;
text-decoration: none;
}
.notification-icon:hover {
background: rgba(255,255,255,0.2);
}
.profile-icon {
width: 36px;
height: 36px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.profile-icon:hover {
background: rgba(255,255,255,0.2);
}
.menu-toggle{
display: none;
font-size: 22px;
cursor: pointer;
padding: 8px 10px;
border-radius: 6px;
transition: 0.3s;
}
.menu-toggle:hover{
background: rgba(255,255,255,0.15);
}
/* Main Content */
.main-content {
margin-top: 80px;
padding: 30px;
}
.page-header {
margin-bottom: 30px;
}
.page-title {
font-size: 2rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.page-subtitle {
color: #64748b;
font-size: 1.1rem;
}
.profile-dropdown {
position: relative;
cursor: pointer;
}
.profile-icon {
display: flex;
align-items: center;
gap: 8px;
}
.dropdown-menu {
display: none;
position: absolute;
right: 0;
top: 100%;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
min-width: 180px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
z-index: 999;
}
.dropdown-menu a,
.dropdown-menu button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
color: #333;
text-decoration: none;
width: 100%;
background: none;
border: none;
cursor: pointer;
font-size: 14px;
}
.dropdown-menu a:hover,
.dropdown-menu button:hover {
background: #f5f5f5;
}
.dropdown-menu hr {
margin: 4px 0;
border: none;
border-top: 1px solid #eee;
}
/* Tampilkan dropdown */
.dropdown-menu.show {
display: block;
}
/* Search and Filter */
.search-filter {
background: white;
border-radius: 16px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.filter-row {
display: flex;
gap: 20px;
align-items: end;
flex-wrap: wrap;
}
.filter-group {
flex: 1;
min-width: 200px;
}
.filter-label {
font-weight: 600;
color: #1a365d;
margin-bottom: 8px;
font-size: 0.95rem;
}
.filter-input, .filter-select {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
}
.filter-input:focus, .filter-select:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(var(--primary-blue-rgb), 0.2);
outline: none;
}
.filter-select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2364748b' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.48 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 16px center;
background-size: 16px;
padding-right: 40px;
}
.search-btn {
background: var(--primary-blue);
color: white;
border: none;
padding: 12px 25px;
border-radius: 12px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
}
.search-btn:hover {
background: #6aa5c6;
}
/* Booking History */
.booking-history {
background: white;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
overflow: hidden;
}
.history-header {
padding: 20px 30px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.history-title {
font-size: 1.2rem;
font-weight: 700;
color: var(--dark-blue);
}
.history-stats {
display: flex;
gap: 20px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 1.1rem;
font-weight: 700;
color: var(--primary-blue);
}
.stat-label {
font-size: 0.85rem;
color: #64748b;
}
.history-table {
width: 100%;
border-collapse: collapse;
}
.history-table th {
background: #f8fafc;
padding: 15px 20px;
text-align: left;
font-weight: 600;
color: var(--dark-blue);
font-size: 0.95rem;
border-bottom: 1px solid #e2e8f0;
}
.history-table td {
padding: 15px 20px;
border-bottom: 1px solid #f1f5f9;
vertical-align: middle;
}
.history-table tr:hover {
background: #f8fafc;
}
.property-info {
display: flex;
align-items: center;
gap: 15px;
}
.property-image {
width: 60px;
height: 60px;
border-radius: 8px;
object-fit: cover;
background: #f1f5f9;
}
.property-details h4 {
font-size: 0.95rem;
font-weight: 600;
color: var(--dark-blue);
margin-bottom: 5px;
}
.property-details p {
font-size: 0.85rem;
color: #64748b;
}
.status-badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
display: inline-block;
}
.status-pending {
background: #fef3c7;
color: #f59e0b;
}
.status-approved {
background: #d1fae5;
color: #059669;
}
.status-rejected {
background: #fecaca;
color: #dc2626;
}
.status-completed {
background: #dbeafe;
color: #2563eb;
}
.price {
font-weight: 700;
color: var(--dark-blue);
font-size: 1.1rem;
}
.action-btn {
padding: 8px 15px;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.3s ease;
margin-right: 5px;
}
.btn-view {
background: #e2e8f0;
color: #4a5568;
}
.btn-view:hover {
background: #cbd5e0;
}
.btn-cancel {
background: #fecaca;
color: #dc2626;
}
.btn-cancel:hover {
background: #fca5a5;
}
/* Pagination */
.pagination-container {
padding: 20px 30px;
border-top: 1px solid #e2e8f0;
display: flex;
justify-content: center;
}
.pagination {
display: flex;
gap: 5px;
}
.page-item {
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.page-item:hover {
background: #f8fafc;
border-color: var(--primary-blue);
}
.page-item.active {
background: var(--primary-blue);
color: white;
border-color: var(--primary-blue);
}
/* Empty State */
.empty-state {
text-align: center;
padding: 60px 20px;
}
.empty-icon {
font-size: 4rem;
color: #cbd5e0;
margin-bottom: 20px;
}
.empty-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--dark-blue);
margin-bottom: 10px;
}
.empty-message {
color: #64748b;
margin-bottom: 20px;
}
.btn-primary {
background: var(--primary-blue);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary:hover {
background: #6aa5c6;
}
/* Responsive Design */
@media (max-width: 992px) {
.filter-row {
flex-direction: column;
gap: 15px;
}
.search-btn {
align-self: flex-start;
}
.history-stats {
display: none;
}
}
@media (max-width: 768px) {
.header{
padding: 15px;
}
.header-container{
display: flex;
flex-direction: row; /* ← jadi satu baris */
align-items: center;
justify-content: space-between; /* ← kiri & kanan */
width: 100%;
}
/* tombol toggle muncul di mobile */
.menu-toggle{
display: block;
color: white;
order: 2;
}
/* nav jadi dropdown */
.nav-menu{
display: none;
position: absolute;
top: 70px;
left: 0;
width: 100%;
background: linear-gradient(135deg, var(--dark-blue) 0%, #1E3A5F 100%);
flex-direction: column;
padding: 15px 0;
gap: 0;
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.nav-menu.show{
display: flex;
}
.nav-item{
padding: 12px 20px;
width: 100%;
}
.nav-item::after{
display: none;
}
.logo-text {
font-size: 1rem;
}
.nav-menu {
display: none;
margin-bottom: 20px;
}
.user-actions {
margin-left: auto; /* ← dorong ke kanan */
display: flex;
align-items: center;
gap: 10px;
}
.main-content {
padding: 20px 15px;
}
.page-title {
font-size: 1.5rem;
}
.history-table th,
.history-table td {
padding: 12px 15px;
font-size: 0.9rem;
}
.property-image {
width: 50px;
height: 50px;
}
.property-details h4 {
font-size: 0.9rem;
}
.action-btn {
padding: 6px 12px;
font-size: 0.8rem;
}
}
@media (max-width: 480px) {
.header {
padding: 15px;
}
.logo-text {
display: none;
}
.logo-icon {
width: 40px;
height: 40px;
}
.nav-menu {
display: none;
}
.main-content {
padding: 15px;
}
.page-title {
font-size: 1.3rem;
}
.filter-group {
min-width: 100%;
}
.history-table {
display: block;
overflow-x: auto;
}
.history-table th,
.history-table td {
white-space: nowrap;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-container">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-home"></i>
</div>
<div class="logo-text">Carani Estate</div>
</div>
<div class="menu-toggle" onclick="toggleMenu()">
<i class="fas fa-bars"></i>
</div>
<nav class="nav-menu" id="navMenu">
<a href="{{ route('welcome') }}"
class="nav-item {{ request()->routeIs('welcome') ? 'active' : '' }}">
Beranda
</a>
<a href="{{ route('tentang-kami') }}"
class="nav-item {{ request()->routeIs('tentang-kami') ? 'active' : '' }}">
Tentang Kami
</a>
<a href="{{ route('halaman-katalog') }}"
class="nav-item {{ request()->routeIs('halaman-katalog') ? 'active' : '' }}">
Katalog
</a>
<a href="{{ route('halaman-chatbot') }}"
class="nav-item {{ request()->routeIs('halaman-chatbot') ? 'active' : '' }}">
ChatBot
</a>
<a href="{{ route('riwayat-pemesanan') }}"
class="nav-item {{ request()->routeIs('riwayat-pemesanan') ? 'active' : '' }}">
Riwayat Pemesanan
</a>
<a href="{{ route('halaman-kontak') }}"
class="nav-item {{ request()->routeIs('halaman-kontak') ? 'active' : '' }}">
Kontak
</a>
</nav>
<div class="user-actions">
<a href="{{ route('halaman-notifikasi') }}" class="notification-icon">
<i class="fas fa-bell"></i>
</a>
<div class="profile-dropdown">
<div class="profile-icon" onclick="toggleDropdown()">
<i class="fas fa-user"></i>
</div>
<div class="dropdown-menu" id="dropdownMenu">
<a href="{{ route('halaman-profil') }}">
<i class="fas fa-user-circle"></i> Lihat Profil
</a>
<hr>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</form>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<div class="page-header">
<h1 class="page-title">Riwayat Pemesanan</h1>
<p class="page-subtitle">Kelola dan pantau semua pemesanan properti Anda</p>
</div>
<!-- Search and Filter -->
<div class="search-filter">
<div class="filter-row">
<div class="filter-group">
<label class="filter-label">Cari Properti</label>
<input type="text" class="filter-input" placeholder="Nama properti, lokasi, atau ID pemesanan">
</div>
<div class="filter-group">
<label class="filter-label">Status Pemesanan</label>
<select class="filter-select">
<option value="">Semua Status</option>
<option value="pending">Menunggu Verifikasi</option>
<option value="approved">Disetujui</option>
<option value="rejected">Ditolak</option>
<option value="completed">Selesai</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Rentang Tanggal</label>
<input type="date" class="filter-input">
</div>
<button class="search-btn">
<i class="fas fa-search"></i> Cari
</button>
</div>
</div>
<!-- Booking History -->
<div class="booking-history">
<div class="history-header">
<h2 class="history-title">Daftar Pemesanan</h2>
<div class="history-stats">
<div class="stat-item">
<div class="stat-value">{{ $transaksi->total() }}</div>
<div class="stat-label">Total</div>
</div>
<div class="stat-item">
<div class="stat-value">
{{ $transaksi->where('status_transaksi', 'menunggu_pembayaran')->count() }}
</div>
<div class="stat-label">Aktif</div>
</div>
</div>
</div>
<table class="history-table">
<thead>
<tr>
<th>No</th>
<th>Properti</th>
<th>Tanggal Pemesanan</th>
<th>Harga</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@forelse($transaksi as $t)
@php
$statusClass = match($t->status_transaksi) {
'menunggu_pembayaran' => 'status-pending',
'menunggu_verifikasi' => 'status-pending',
'berhasil' => 'status-approved',
'ditolak' => 'status-rejected',
default => 'status-pending'
};
$statusLabel = match($t->status_transaksi) {
'menunggu_pembayaran' => 'Menunggu Pembayaran',
'menunggu_verifikasi' => 'Menunggu Verifikasi',
'berhasil' => 'Berhasil',
'ditolak' => 'Ditolak',
default => $t->status_transaksi
};
@endphp
<tr>
<td>{{ $loop->iteration }}</td>
<td>
<div class="property-info">
<img src="img/tipe36.jpg" class="property-image">
<div class="property-details">
<h4>{{ $t->properti->nama_properti ?? '-' }}</h4>
<p>Tipe {{ $t->properti->tipe_properti ?? '-' }}</p>
</div>
</div>
</td>
<td>{{ \Carbon\Carbon::parse($t->tanggal_transaksi)->format('d M Y') }}</td>
<td class="price">Rp {{ number_format($t->total_harga, 0, ',', '.') }}</td>
<td>
<span class="status-badge {{ $statusClass }}">
{{ $statusLabel }}
</span>
</td>
<td>
<a href="{{ route('detail-pemesanan', $t->id_transaksi) }}"
class="action-btn btn-view">
<i class="fas fa-eye"></i> Lihat
</a>
@if($t->status_transaksi == 'menunggu_pembayaran')
<button class="action-btn btn-cancel">
<i class="fas fa-times"></i> Batalkan
</button>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" style="text-align:center; padding:40px; color:#64748b;">
Belum ada riwayat pemesanan
</td>
</tr>
@endforelse
</tbody>
</table>
<!-- Pagination -->
<div class="pagination-container">
{{ $transaksi->links() }}
</div>
</div>
</div>
<script>
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
}
}
});
});
// Add hover effect to table rows
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('mouseenter', function() {
this.style.background = '#f8fafc';
});
row.addEventListener('mouseleave', function() {
this.style.background = '';
});
});
// Pagination functionality
document.querySelectorAll('.page-item').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.page-item').forEach(i => i.classList.remove('active'));
this.classList.add('active');
alert(`Menampilkan halaman ${this.textContent}`);
});
});
// TOGGLE NAV
function toggleMenu(){
document.getElementById('navMenu').classList.toggle('show');
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

8
routes/console.php Normal file
View File

@ -0,0 +1,8 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

310
routes/web.php Normal file
View File

@ -0,0 +1,310 @@
<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\ChatbotController;
use App\Http\Controllers\AuthController;
/*
|--------------------------------------------------------------------------
| 🌐 PUBLIC ROUTES (Bisa diakses Guest & User)
|--------------------------------------------------------------------------
*/
// Home & Company Profile
Route::get('/', function () {
$unggulan = \App\Models\Properti::with('blok')
->where('status_unit', '!=', 'terjual')
->limit(3)
->get();
return view('welcome', compact('unggulan'));
})->name('welcome');
Route::get('/tentang-kami', function () {
return view('tentang-kami');
})->name('tentang-kami');
Route::get('/halaman-kontak', function () {
return view('halaman-kontak');
})->name('halaman-kontak');
Route::get('/halaman-chatbot', function () {
return view('halaman-chatbot');
})->name('halaman-chatbot');
// Katalog Properti (PUBLIC - Bisa dilihat tanpa login)
Route::get('/halaman-katalog', function () {
$query = \App\Models\Properti::with('blok')
->where('status_unit', '!=', 'terjual');
// Filter logic
if (request('perumahan')) {
$query->where('id_perumahan', request('perumahan'));
}
if (request('jenis')) {
$query->where('jenis_properti', request('jenis'));
}
if (request('kategori')) {
$query->where('kategori_properti', request('kategori'));
}
if (request('tipe')) {
$query->where('tipe_properti', request('tipe'));
}
if (request('harga')) {
$harga = request('harga');
if ($harga == '0-200') {
$query->whereBetween('harga_properti', [0, 200000000]);
} elseif ($harga == '200-300') {
$query->whereBetween('harga_properti', [200000000, 300000000]);
} elseif ($harga == '300-500') {
$query->whereBetween('harga_properti', [300000000, 500000000]);
} elseif ($harga == '500+') {
$query->where('harga_properti', '>', 500000000);
}
}
$properti = $query->paginate(9);
return view('halaman-katalog', compact('properti'));
})->name('halaman-katalog');
// Detail Properti (PUBLIC - Bisa dilihat tanpa 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');
// Notifikasi (Opsional: bisa public atau protected)
Route::get('/halaman-notifikasi', function () {
return view('halaman-notifikasi');
})->name('halaman-notifikasi');
/*
|--------------------------------------------------------------------------
| 🔐 AUTH ROUTES (Login, Logout, Password)
|--------------------------------------------------------------------------
*/
Route::get('/login', [AuthController::class, 'index'])->name('login');
Route::post('/login', [AuthController::class, 'login'])->name('login.proses');
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
Route::get('/gantipass', [AuthController::class, 'formGantiPassword'])->name('ganti-password');
Route::post('/gantipass', [AuthController::class, 'updatePassword'])->name('ganti-password.proses');
/*
|--------------------------------------------------------------------------
| 🔒 PROTECTED ROUTES (Hanya User Login)
|--------------------------------------------------------------------------
*/
Route::middleware(['auth'])->group(function () {
// --- 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);
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');
// --- RIWAYAT & DETAIL PEMESANAN ---
Route::get('/riwayat-pemesanan', function () {
$transaksi = \App\Models\Transaksi::with('properti')
->where('id_user', Auth::id())
->orderBy('tanggal_transaksi', 'desc')
->paginate(10);
return view('riwayat-pemesanan', compact('transaksi'));
})->name('riwayat-pemesanan');
Route::get('/detail-pemesanan/{id}', function ($id) {
$transaksi = \App\Models\Transaksi::with(['properti', 'user', 'pembayaran', 'kpr'])
->where('id_transaksi', $id)
->where('id_user', Auth::id())
->firstOrFail();
return view('detail-pemesanan', compact('transaksi'));
})->name('detail-pemesanan');
// --- CHATBOT & PROFIL ---
// 💡 Chatbot saya letakkan di sini agar bisa akses data user
Route::get('/halaman-profil', function () {
return view('halaman-profil');
})->name('halaman-profil');
});
/*
|--------------------------------------------------------------------------
| 👨‍💻 ADMIN ROUTES (Hanya Admin)
|--------------------------------------------------------------------------
*/
Route::prefix('admin')->middleware(['auth'])->group(function () {
Route::get('/', function () {
return view('admin.welcome');
})->name('admin.dashboard');
// ========================
// DATA RUMAH
// ========================
Route::get('/data_rumah', function () {
$search = request('search');
if ($search) {
$properti = \App\Models\Properti::with(['blok', 'perumahan'])
->where('nama_properti', 'like', "%$search%")
->orWhere('tipe_properti', 'like', "%$search%")
->orWhere('jenis_properti', 'like', "%$search%")
->orWhere('status_unit', 'like', "%$search%")
->orWhereHas('perumahan', function($q) use ($search) {
$q->where('nama_perumahan', 'like', "%$search%");
})
->orWhereHas('blok', function($q) use ($search) {
$q->where('nama_blok', 'like', "%$search%");
})
->get();
$isPaginated = false;
} else {
$properti = \App\Models\Properti::with(['blok', 'perumahan'])
->paginate(10);
$isPaginated = true;
}
return view('admin.data_rumah', compact('properti', 'isPaginated'));
})->name('admin.data_rumah');
Route::delete('/hapus_rumah/{id}', function ($id) {
\App\Models\Properti::where('id_properti', $id)->delete();
return redirect()->route('admin.data_rumah')->with('success', 'Properti berhasil dihapus.');
})->name('admin.hapus_rumah');
Route::get('/edit_rumah/{id}', function ($id) {
$properti = \App\Models\Properti::with(['blok', 'perumahan'])
->where('id_properti', $id)->firstOrFail();
return view('admin.edit_rumah', compact('properti'));
})->name('admin.edit_rumah');
Route::get('/tambah-rumah', function () {
return view('admin.tambah-rumah');
})->name('admin.tambah-rumah');
// ========================
// DATA USER
// ========================
Route::get('/data_user', function () {
$search = request('search');
if ($search) {
$users = \App\Models\User::where('nama_user', 'like', "%$search%")
->orWhere('email_user', 'like', "%$search%")
->orWhere('no_hp', 'like', "%$search%")
->get();
$isPaginated = false;
} else {
$users = \App\Models\User::paginate(10);
$isPaginated = true;
}
return view('admin.data_user', compact('users', 'isPaginated'));
})->name('admin.data_user');
Route::get('/tambah-datauser', function () {
return view('admin.tambah-datauser');
})->name('admin.tambah-datauser');
Route::post('/tambah-datauser', function () {
\App\Models\User::create([
'nama_user' => request('nama_user'),
'email_user' => request('email_user'),
'no_hp' => request('no_hp'),
'password_user' => bcrypt(request('password_user')),
'role_user' => request('role_user'),
]);
return redirect()->route('admin.data_user')
->with('success', 'User berhasil ditambahkan.');
})->name('admin.tambah-datauser.simpan');
Route::get('/edit_user/{id}', function ($id) {
$user = \App\Models\User::where('id_user', $id)->firstOrFail();
return view('admin.edit_user', compact('user'));
})->name('admin.edit_user');
Route::post('/edit_user/{id}', function ($id) {
$user = \App\Models\User::where('id_user', $id)->firstOrFail();
$user->nama_user = request('nama_user');
$user->email_user = request('email_user');
$user->no_hp = request('no_hp');
$user->role_user = request('role_user');
if (request('password_user')) {
$user->password_user = bcrypt(request('password_user'));
}
$user->save();
return redirect()->route('admin.data_user')->with('success', 'User berhasil diupdate.');
})->name('admin.edit_user.update');
// ========================
// VERIFIKASI
// ========================
Route::get('/halaman_verifikasi', function () {
$status = request('status');
$query = \App\Models\Dokumen::with('user');
if ($status && $status != 'semua') {
$query->where('status_verifikasi', $status);
}
$dokumen = $query->orderBy('uploaded_at', 'desc')->get();
return view('admin.halaman_verifikasi', compact('dokumen'));
})->name('admin.halaman_verifikasi');
Route::post('/verifikasi/{id}/approve', function ($id) {
$dokumen = \App\Models\Dokumen::findOrFail($id);
$dokumen->status_verifikasi = 'diterima';
$dokumen->verified_at = now();
$dokumen->save();
\App\Models\Notifikasi::create([
'id_user' => $dokumen->id_user,
'judul' => 'Dokumen Disetujui',
'pesan' => 'Dokumen ' . $dokumen->jenis_dokumen . ' kamu telah diterima.',
'tipe' => 'dokumen',
'status_baca' => 0,
'status_kirim' => 'terkirim',
'channel' => 'in_app',
'referensi_id' => $dokumen->id_dokumen,
'referensi_tipe' => 'dokumen',
]);
return back()->with('success', 'Dokumen disetujui');
})->name('admin.verifikasi.approve');
Route::post('/verifikasi/{id}/tolak', function ($id) {
$dokumen = \App\Models\Dokumen::findOrFail($id);
$dokumen->status_verifikasi = 'ditolak';
$dokumen->catatan_verifikasi = request('catatan');
$dokumen->verified_at = now();
$dokumen->save();
return back()->with('success', 'Dokumen ditolak');
})->name('admin.verifikasi.tolak');
});

4
storage/app/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*
!private/
!public/
!.gitignore

2
storage/app/private/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/app/public/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

9
storage/framework/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
compiled.php
config.php
down
events.scanned.php
maintenance.php
routes.php
routes.scanned.php
schedule-*
services.json

3
storage/framework/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*
!data/
!.gitignore

View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/sessions/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/testing/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/views/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,19 @@
<?php
namespace Tests\Feature;
// use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}

10
tests/TestCase.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
//
}

View File

@ -0,0 +1,16 @@
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_that_true_is_true(): void
{
$this->assertTrue(true);
}
}

18
vite.config.js Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
tailwindcss(),
],
server: {
watch: {
ignored: ['**/storage/framework/views/**'],
},
},
});