This commit is contained in:
Faradina 2025-07-14 10:24:47 +07:00
parent 42ad713f36
commit 8fb5e4426d
169 changed files with 44037 additions and 2717 deletions

13
.gitattributes vendored
View File

@ -1,2 +1,11 @@
# Auto detect text files and perform LF normalization * text=auto eol=lf
* text=auto
*.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

View File

@ -1,2 +1,61 @@
# RECAJE <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 may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
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

@ -1,89 +1,89 @@
<?php <?php
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Category; use App\Models\Category;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class CategoryController extends Controller class CategoryController extends Controller
{ {
/** /**
* Display a listing of the resource. * Display a listing of the resource.
*/ */
public function index() public function index()
{ {
$categories = Category::with('subcategories')->get(); $categories = Category::with('subcategories')->get();
return view('admin.categories.index', compact('categories')); return view('admin.categories.index', compact('categories'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
*/ */
public function create() public function create()
{ {
return view('admin.categories.create'); return view('admin.categories.create');
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
]); ]);
$category = Category::create([ $category = Category::create([
'name' => $request->name, 'name' => $request->name,
]); ]);
return redirect()->route('admin.subcategories.create', ['category_id' => $category->id]) return redirect()->route('admin.subcategories.create', ['category_id' => $category->id])
->with('success', 'Kategori berhasil ditambahkan! Silakan tambahkan sub-kategori.'); ->with('success', 'Kategori berhasil ditambahkan! Silakan tambahkan sub-kategori.');
} }
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function show(Category $category) public function show(Category $category)
{ {
return view('admin.categories.show', compact('category')); return view('admin.categories.show', compact('category'));
} }
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
*/ */
public function edit(Category $category) public function edit(Category $category)
{ {
return view('admin.categories.edit', compact('category')); return view('admin.categories.edit', compact('category'));
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
*/ */
public function update(Request $request, Category $category) public function update(Request $request, Category $category)
{ {
$request->validate([ $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
]); ]);
$category->update([ $category->update([
'name' => $request->name, 'name' => $request->name,
]); ]);
return redirect()->route('admin.categories.index') return redirect()->route('admin.categories.index')
->with('success', 'Kategori berhasil diperbarui!'); ->with('success', 'Kategori berhasil diperbarui!');
} }
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
*/ */
public function destroy(Category $category) public function destroy(Category $category)
{ {
$category->delete(); $category->delete();
return redirect()->route('admin.categories.index') return redirect()->route('admin.categories.index')
->with('success', 'Kategori berhasil dihapus!'); ->with('success', 'Kategori berhasil dihapus!');
} }
} }

View File

@ -1,123 +1,123 @@
<?php <?php
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Category; use App\Models\Category;
use App\Models\Subcategory; use App\Models\Subcategory;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class SubcategoryController extends Controller class SubcategoryController extends Controller
{ {
/** /**
* Display a listing of the resource. * Display a listing of the resource.
*/ */
public function index() public function index()
{ {
$subcategories = Subcategory::paginate(10); $subcategories = Subcategory::paginate(10);
return view('admin.subcategories.index', compact('subcategories')); return view('admin.subcategories.index', compact('subcategories'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
*/ */
public function create() public function create()
{ {
$categories = Category::all(); $categories = Category::all();
$selectedCategoryId = request('category_id'); $selectedCategoryId = request('category_id');
return view('admin.subcategories.create', compact('categories', 'selectedCategoryId')); return view('admin.subcategories.create', compact('categories', 'selectedCategoryId'));
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
*/ */
public function store(Request $request) public function store(Request $request)
{ {
\Illuminate\Support\Facades\Log::info('Request data:', $request->all()); \Illuminate\Support\Facades\Log::info('Request data:', $request->all());
$request->validate([ $request->validate([
'category_id' => 'required|exists:categories,id', 'category_id' => 'required|exists:categories,id',
'subcategories' => 'required|array|min:1', 'subcategories' => 'required|array|min:1',
'subcategories.*.name' => 'required|string|max:255', 'subcategories.*.name' => 'required|string|max:255',
'subcategories.*.value' => 'required|integer|min:1|max:5' 'subcategories.*.value' => 'required|integer|min:1|max:5'
]); ]);
\Illuminate\Support\Facades\Log::info('Validation passed, processing subcategories'); \Illuminate\Support\Facades\Log::info('Validation passed, processing subcategories');
try { try {
DB::beginTransaction(); DB::beginTransaction();
foreach ($request->subcategories as $subcategory) { foreach ($request->subcategories as $subcategory) {
\Illuminate\Support\Facades\Log::info('Creating subcategory:', $subcategory); \Illuminate\Support\Facades\Log::info('Creating subcategory:', $subcategory);
Subcategory::create([ Subcategory::create([
'category_id' => $request->category_id, 'category_id' => $request->category_id,
'name' => $subcategory['name'], 'name' => $subcategory['name'],
'value' => $subcategory['value'] 'value' => $subcategory['value']
]); ]);
} }
DB::commit(); DB::commit();
\Illuminate\Support\Facades\Log::info('All subcategories created successfully'); \Illuminate\Support\Facades\Log::info('All subcategories created successfully');
return redirect()->route('admin.subcategories.index') return redirect()->route('admin.subcategories.index')
->with('success', 'Sub-kategori berhasil ditambahkan!'); ->with('success', 'Sub-kategori berhasil ditambahkan!');
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack(); DB::rollBack();
\Illuminate\Support\Facades\Log::error('Error creating subcategories: ' . $e->getMessage()); \Illuminate\Support\Facades\Log::error('Error creating subcategories: ' . $e->getMessage());
return back()->with('error', 'Terjadi kesalahan saat menambahkan sub-kategori: ' . $e->getMessage()); return back()->with('error', 'Terjadi kesalahan saat menambahkan sub-kategori: ' . $e->getMessage());
} }
} }
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function show(Subcategory $subcategory) public function show(Subcategory $subcategory)
{ {
return view('admin.subcategories.show', compact('subcategory')); return view('admin.subcategories.show', compact('subcategory'));
} }
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
*/ */
public function edit(Subcategory $subcategory) public function edit(Subcategory $subcategory)
{ {
$categories = Category::all(); $categories = Category::all();
return view('admin.subcategories.edit', compact('subcategory', 'categories')); return view('admin.subcategories.edit', compact('subcategory', 'categories'));
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
*/ */
public function update(Request $request, Subcategory $subcategory) public function update(Request $request, Subcategory $subcategory)
{ {
$request->validate([ $request->validate([
'category_id' => 'required|exists:categories,id', 'category_id' => 'required|exists:categories,id',
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
]); ]);
$subcategory->update([ $subcategory->update([
'category_id' => $request->category_id, 'category_id' => $request->category_id,
'name' => $request->name, 'name' => $request->name,
]); ]);
return redirect()->route('admin.subcategories.index') return redirect()->route('admin.subcategories.index')
->with('success', 'Sub-kategori berhasil diperbarui!'); ->with('success', 'Sub-kategori berhasil diperbarui!');
} }
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
*/ */
public function destroy(Subcategory $subcategory) public function destroy(Subcategory $subcategory)
{ {
$subcategory->delete(); $subcategory->delete();
return redirect()->route('admin.subcategories.index') return redirect()->route('admin.subcategories.index')
->with('success', 'Sub-kategori berhasil dihapus!'); ->with('success', 'Sub-kategori berhasil dihapus!');
} }
} }

View File

@ -1,182 +1,182 @@
<?php <?php
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class UserController extends Controller class UserController extends Controller
{ {
/** /**
* Create a new controller instance. * Create a new controller instance.
* *
* @return void * @return void
*/ */
public function __construct() public function __construct()
{ {
// Middleware auth diterapkan di routes/web.php // Middleware auth diterapkan di routes/web.php
} }
/** /**
* Menampilkan daftar pengguna. * Menampilkan daftar pengguna.
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function index() public function index()
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
$users = User::orderBy('id', 'asc')->paginate(10); $users = User::orderBy('id', 'asc')->paginate(10);
// Format nama setiap user // Format nama setiap user
$users->getCollection()->transform(function ($user) { $users->getCollection()->transform(function ($user) {
$user->name = ucwords(strtolower($user->name)); $user->name = ucwords(strtolower($user->name));
return $user; return $user;
}); });
return view('admin.users.index', compact('users')); return view('admin.users.index', compact('users'));
} }
/** /**
* Menampilkan detail pengguna. * Menampilkan detail pengguna.
* *
* @param \App\Models\User $user * @param \App\Models\User $user
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function show(User $user) public function show(User $user)
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
$user->name = ucwords(strtolower($user->name)); $user->name = ucwords(strtolower($user->name));
return view('admin.users.show', compact('user')); return view('admin.users.show', compact('user'));
} }
/** /**
* Menghapus pengguna. * Menghapus pengguna.
* *
* @param \App\Models\User $user * @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(User $user) public function destroy(User $user)
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
// Tidak bisa menghapus diri sendiri // Tidak bisa menghapus diri sendiri
if (Auth::id() === $user->id) { if (Auth::id() === $user->id) {
return back()->with('error', 'Anda tidak dapat menghapus akun Anda sendiri.'); return back()->with('error', 'Anda tidak dapat menghapus akun Anda sendiri.');
} }
$user->delete(); $user->delete();
return redirect()->route('admin.users.index') return redirect()->route('admin.users.index')
->with('success', 'Pengguna berhasil dihapus!'); ->with('success', 'Pengguna berhasil dihapus!');
} }
public function create() public function create()
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
return view('admin.users.create'); return view('admin.users.create');
} }
/** /**
* Menyimpan user baru. * Menyimpan user baru.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(Request $request) public function store(Request $request)
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
$request->validate([ $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users', 'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed', 'password' => 'required|string|min:8|confirmed',
'role' => 'required|in:admin,user', 'role' => 'required|in:admin,user',
]); ]);
$user = User::create([ $user = User::create([
'name' => ucwords(strtolower($request->name)), 'name' => ucwords(strtolower($request->name)),
'email' => $request->email, 'email' => $request->email,
'password' => Hash::make($request->password), 'password' => Hash::make($request->password),
'role' => $request->role, 'role' => $request->role,
]); ]);
return redirect()->route('admin.users.index') return redirect()->route('admin.users.index')
->with('success', 'Pengguna berhasil ditambahkan!'); ->with('success', 'Pengguna berhasil ditambahkan!');
} }
/** /**
* Menampilkan form edit user. * Menampilkan form edit user.
* *
* @param \App\Models\User $user * @param \App\Models\User $user
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function edit(User $user) public function edit(User $user)
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
$user->name = ucwords(strtolower($user->name)); $user->name = ucwords(strtolower($user->name));
return view('admin.users.edit', compact('user')); return view('admin.users.edit', compact('user'));
} }
/** /**
* Memperbarui data user. * Memperbarui data user.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \App\Models\User $user * @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function update(Request $request, User $user) public function update(Request $request, User $user)
{ {
// Cek apakah pengguna adalah admin // Cek apakah pengguna adalah admin
if (Auth::user()->role !== 'admin') { if (Auth::user()->role !== 'admin') {
return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.');
} }
$request->validate([ $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id, 'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
'password' => 'nullable|string|min:8|confirmed', 'password' => 'nullable|string|min:8|confirmed',
'role' => 'required|in:admin,user', 'role' => 'required|in:admin,user',
]); ]);
$data = [ $data = [
'name' => ucwords(strtolower($request->name)), 'name' => ucwords(strtolower($request->name)),
'email' => $request->email, 'email' => $request->email,
'role' => $request->role, 'role' => $request->role,
]; ];
// Update password hanya jika diisi // Update password hanya jika diisi
if ($request->filled('password')) { if ($request->filled('password')) {
$data['password'] = Hash::make($request->password); $data['password'] = Hash::make($request->password);
} }
$user->update($data); $user->update($data);
return redirect()->route('admin.users.index') return redirect()->route('admin.users.index')
->with('success', 'Pengguna berhasil diperbarui!'); ->with('success', 'Pengguna berhasil diperbarui!');
} }
} }

View File

@ -1,41 +1,41 @@
<?php <?php
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller class ForgotPasswordController extends Controller
{ {
/** /**
* Menampilkan formulir reset password * Menampilkan formulir reset password
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function showLinkRequestForm() public function showLinkRequestForm()
{ {
return view('auth.passwords.email'); return view('auth.passwords.email');
} }
/** /**
* Mengirim link reset password * Mengirim link reset password
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function sendResetLinkEmail(Request $request) public function sendResetLinkEmail(Request $request)
{ {
$request->validate([ $request->validate([
'email' => ['required', 'email'], 'email' => ['required', 'email'],
]); ]);
$status = Password::sendResetLink( $status = Password::sendResetLink(
$request->only('email') $request->only('email')
); );
return $status === Password::RESET_LINK_SENT return $status === Password::RESET_LINK_SENT
? back()->with('status', 'Link reset password telah dikirim ke email Anda!') ? back()->with('status', 'Link reset password telah dikirim ke email Anda!')
: back()->withErrors(['email' => __($status)]); : back()->withErrors(['email' => __($status)]);
} }
} }

View File

@ -1,151 +1,151 @@
<?php <?php
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite; use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class LoginController extends Controller class LoginController extends Controller
{ {
/** /**
* Menampilkan form login * Menampilkan form login
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function showLoginForm() public function showLoginForm()
{ {
return view('auth.login'); return view('auth.login');
} }
/** /**
* Where to redirect users after login. * Where to redirect users after login.
* *
* @var string * @var string
*/ */
protected function redirectTo() protected function redirectTo()
{ {
if (Auth::check() && Auth::user()->role === 'admin') { if (Auth::check() && Auth::user()->role === 'admin') {
return route('admin.dashboard'); return route('admin.dashboard');
} }
return '/'; return '/';
} }
/** /**
* Memproses request login * Memproses request login
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function login(Request $request) public function login(Request $request)
{ {
try { try {
$credentials = $request->validate([ $credentials = $request->validate([
'email' => ['required', 'email'], 'email' => ['required', 'email'],
'password' => ['required'], 'password' => ['required'],
], [ ], [
'email.required' => 'Email harus diisi', 'email.required' => 'Email harus diisi',
'email.email' => 'Format email tidak valid', 'email.email' => 'Format email tidak valid',
'password.required' => 'Password harus diisi', 'password.required' => 'Password harus diisi',
]); ]);
if (Auth::attempt($credentials, $request->boolean('remember'))) { if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate(); $request->session()->regenerate();
if (Auth::user()->role === 'admin') { if (Auth::user()->role === 'admin') {
return redirect()->route('admin.dashboard'); return redirect()->route('admin.dashboard');
} }
return redirect()->intended('/'); return redirect()->intended('/');
} }
return back() return back()
->withInput($request->only('email')) ->withInput($request->only('email'))
->withErrors([ ->withErrors([
'email' => 'Email atau password yang diberikan tidak cocok dengan data kami.', 'email' => 'Email atau password yang diberikan tidak cocok dengan data kami.',
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return back() return back()
->withInput($request->only('email')) ->withInput($request->only('email'))
->withErrors([ ->withErrors([
'error' => 'Terjadi kesalahan saat mencoba login. Silakan coba lagi.', 'error' => 'Terjadi kesalahan saat mencoba login. Silakan coba lagi.',
]); ]);
} }
} }
/** /**
* Logout pengguna * Logout pengguna
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function logout(Request $request) public function logout(Request $request)
{ {
Auth::logout(); Auth::logout();
$request->session()->invalidate(); $request->session()->invalidate();
$request->session()->regenerateToken(); $request->session()->regenerateToken();
return redirect('/'); return redirect('/');
} }
/** /**
* Redirect ke halaman login Google. * Redirect ke halaman login Google.
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function redirectToGoogle() public function redirectToGoogle()
{ {
return Socialite::driver('google')->redirect(); return Socialite::driver('google')->redirect();
} }
/** /**
* Handle callback dari Google. * Handle callback dari Google.
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function handleGoogleCallback() public function handleGoogleCallback()
{ {
try { try {
$googleUser = Socialite::driver('google')->user(); $googleUser = Socialite::driver('google')->user();
// Cari user berdasarkan google id, jika tidak ada cari berdasarkan email // Cari user berdasarkan google id, jika tidak ada cari berdasarkan email
$user = User::where('google_id', $googleUser->id)->first(); $user = User::where('google_id', $googleUser->id)->first();
if (!$user) { if (!$user) {
// Cek apakah email sudah terdaftar // Cek apakah email sudah terdaftar
$user = User::where('email', $googleUser->email)->first(); $user = User::where('email', $googleUser->email)->first();
if (!$user) { if (!$user) {
// Jika user belum terdaftar, buat user baru // Jika user belum terdaftar, buat user baru
$user = User::create([ $user = User::create([
'name' => $googleUser->name, 'name' => $googleUser->name,
'email' => $googleUser->email, 'email' => $googleUser->email,
'google_id' => $googleUser->id, 'google_id' => $googleUser->id,
'password' => Hash::make(rand(1,10000)), 'password' => Hash::make(rand(1,10000)),
'email_verified_at' => now(), // Email otomatis terverifikasi 'email_verified_at' => now(), // Email otomatis terverifikasi
]); ]);
} else { } else {
// Update google_id untuk user yang sudah ada // Update google_id untuk user yang sudah ada
$user->update([ $user->update([
'google_id' => $googleUser->id, 'google_id' => $googleUser->id,
'email_verified_at' => now(), // Email otomatis terverifikasi 'email_verified_at' => now(), // Email otomatis terverifikasi
]); ]);
} }
} }
// Login user // Login user
Auth::login($user); Auth::login($user);
return redirect('/'); return redirect('/');
} catch (\Exception $e) { } catch (\Exception $e) {
return redirect('/login')->with('error', 'Terjadi kesalahan saat login dengan Google: ' . $e->getMessage()); return redirect('/login')->with('error', 'Terjadi kesalahan saat login dengan Google: ' . $e->getMessage());
} }
} }
} }

View File

@ -1,51 +1,51 @@
<?php <?php
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
class RegisterController extends Controller class RegisterController extends Controller
{ {
/** /**
* Menampilkan form registrasi * Menampilkan form registrasi
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function showRegistrationForm() public function showRegistrationForm()
{ {
return view('auth.register'); return view('auth.register');
} }
/** /**
* Memproses request registrasi * Memproses request registrasi
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function register(Request $request) public function register(Request $request)
{ {
$request->validate([ $request->validate([
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'], 'password' => ['required', 'string', 'min:8', 'confirmed'],
'terms' => ['required'], 'terms' => ['required'],
]); ]);
$user = User::create([ $user = User::create([
'name' => $request->name, 'name' => $request->name,
'email' => $request->email, 'email' => $request->email,
'password' => Hash::make($request->password), 'password' => Hash::make($request->password),
]); ]);
event(new Registered($user)); event(new Registered($user));
Auth::login($user); Auth::login($user);
return redirect()->route('verification.notice'); return redirect()->route('verification.notice');
} }
} }

View File

@ -1,58 +1,58 @@
<?php <?php
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class ResetPasswordController extends Controller class ResetPasswordController extends Controller
{ {
/** /**
* Menampilkan formulir reset password dengan token * Menampilkan formulir reset password dengan token
* *
* @param string $token * @param string $token
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {
return view('auth.passwords.reset')->with( return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email] ['token' => $token, 'email' => $request->email]
); );
} }
/** /**
* Memproses reset password * Memproses reset password
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function reset(Request $request) public function reset(Request $request)
{ {
$request->validate([ $request->validate([
'token' => ['required'], 'token' => ['required'],
'email' => ['required', 'email'], 'email' => ['required', 'email'],
'password' => ['required', 'confirmed', 'min:8'], 'password' => ['required', 'confirmed', 'min:8'],
]); ]);
$status = Password::reset( $status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'), $request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password) { function ($user, $password) {
$user->forceFill([ $user->forceFill([
'password' => Hash::make($password) 'password' => Hash::make($password)
])->setRememberToken(Str::random(60)); ])->setRememberToken(Str::random(60));
$user->save(); $user->save();
event(new PasswordReset($user)); event(new PasswordReset($user));
} }
); );
return $status === Password::PASSWORD_RESET return $status === Password::PASSWORD_RESET
? redirect()->route('login')->with('status', 'Password Anda telah direset!') ? redirect()->route('login')->with('status', 'Password Anda telah direset!')
: back()->withErrors(['email' => [__($status)]]); : back()->withErrors(['email' => [__($status)]]);
} }
} }

View File

@ -1,91 +1,91 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Cafe; use App\Models\Cafe;
use App\Models\Category; use App\Models\Category;
use App\Models\Subcategory; use App\Models\Subcategory;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class CafeController extends Controller class CafeController extends Controller
{ {
/** /**
* Tampilkan halaman pencarian cafe dengan stepper * Tampilkan halaman pencarian cafe dengan stepper
*/ */
public function search(Request $request) public function search(Request $request)
{ {
// Tentukan langkah saat ini berdasarkan parameter URL // Tentukan langkah saat ini berdasarkan parameter URL
$currentStep = 1; $currentStep = 1;
if ($request->has('weight')) { if ($request->has('weight')) {
$currentStep = 3; // Hasil $currentStep = 3; // Hasil
} elseif ($request->has('criteria')) { } elseif ($request->has('criteria')) {
$currentStep = 2; // Kriteria $currentStep = 2; // Kriteria
} }
return view('cafe.search-fixed', compact('currentStep')); return view('cafe.search-fixed', compact('currentStep'));
} }
/** /**
* Tampilkan halaman rekomendasi cafe * Tampilkan halaman rekomendasi cafe
*/ */
public function recommend() public function recommend()
{ {
return view('cafe.recommend'); return view('cafe.recommend');
} }
/** /**
* Tampilkan semua cafe dengan fitur fuzzy search toleran terhadap typo * Tampilkan semua cafe dengan fitur fuzzy search toleran terhadap typo
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$query = Cafe::with(['ratings.subcategory', 'ratings.category']); $query = Cafe::with(['ratings.subcategory', 'ratings.category']);
// Filter berdasarkan pencarian jika ada // Filter berdasarkan pencarian jika ada
if ($request->has('search') && !empty($request->search)) { if ($request->has('search') && !empty($request->search)) {
$searchTerm = $request->search; $searchTerm = $request->search;
// Memecah kata kunci pencarian menjadi beberapa kata // Memecah kata kunci pencarian menjadi beberapa kata
$keywords = explode(' ', $searchTerm); $keywords = explode(' ', $searchTerm);
$query->where(function($q) use ($searchTerm, $keywords) { $query->where(function($q) use ($searchTerm, $keywords) {
// Exact match dengan prioritas tertinggi // Exact match dengan prioritas tertinggi
$q->where('nama', 'LIKE', "%{$searchTerm}%"); $q->where('nama', 'LIKE', "%{$searchTerm}%");
// Pencarian fuzzy untuk masing-masing kata kunci // Pencarian fuzzy untuk masing-masing kata kunci
foreach ($keywords as $keyword) { foreach ($keywords as $keyword) {
if (strlen($keyword) >= 3) { if (strlen($keyword) >= 3) {
// Membuat variasi kata kunci untuk toleransi typo // Membuat variasi kata kunci untuk toleransi typo
$q->orWhere('nama', 'LIKE', "%{$keyword}%"); $q->orWhere('nama', 'LIKE', "%{$keyword}%");
// Tambahkan wildcard di tengah untuk toleransi kesalahan ketik 1 karakter // Tambahkan wildcard di tengah untuk toleransi kesalahan ketik 1 karakter
for ($i = 0; $i < strlen($keyword) - 1; $i++) { for ($i = 0; $i < strlen($keyword) - 1; $i++) {
$fuzzyWord = substr($keyword, 0, $i) . '_' . substr($keyword, $i + 1); $fuzzyWord = substr($keyword, 0, $i) . '_' . substr($keyword, $i + 1);
$q->orWhere('nama', 'LIKE', "%{$fuzzyWord}%"); $q->orWhere('nama', 'LIKE', "%{$fuzzyWord}%");
} }
} }
} }
// Mencoba juga pencarian dengan operator SOUNDS LIKE jika tersedia // Mencoba juga pencarian dengan operator SOUNDS LIKE jika tersedia
try { try {
$q->orWhereRaw("SOUNDEX(nama) = SOUNDEX(?)", [$searchTerm]); $q->orWhereRaw("SOUNDEX(nama) = SOUNDEX(?)", [$searchTerm]);
} catch (\Exception $e) { } catch (\Exception $e) {
// SOUNDEX mungkin tidak tersedia, tidak perlu error handling // SOUNDEX mungkin tidak tersedia, tidak perlu error handling
} }
}); });
} }
$cafes = $query->latest()->paginate(12)->withQueryString(); $cafes = $query->latest()->paginate(12)->withQueryString();
return view('cafe.index', compact('cafes')); return view('cafe.index', compact('cafes'));
} }
/** /**
* Tampilkan detail cafe * Tampilkan detail cafe
*/ */
public function show($id) public function show($id)
{ {
$cafe = Cafe::with(['ratings.subcategory', 'ratings.category'])->findOrFail($id); $cafe = Cafe::with(['ratings.subcategory', 'ratings.category'])->findOrFail($id);
return view('cafe.show', compact('cafe')); return view('cafe.show', compact('cafe'));
} }
} }

View File

@ -1,71 +1,71 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\ContactMessage; use App\Models\ContactMessage;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ContactController extends Controller class ContactController extends Controller
{ {
/** /**
* Store a newly created contact message in storage. * Store a newly created contact message in storage.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255', 'email' => 'required|string|email|max:255',
'message' => 'required|string', 'message' => 'required|string',
]); ]);
ContactMessage::create($validated); ContactMessage::create($validated);
return redirect()->back()->with('success', 'Pesan Anda telah terkirim! Terima kasih telah menghubungi kami.'); return redirect()->back()->with('success', 'Pesan Anda telah terkirim! Terima kasih telah menghubungi kami.');
} }
/** /**
* Display a listing of contact messages for admin. * Display a listing of contact messages for admin.
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index() public function index()
{ {
$messages = ContactMessage::orderBy('created_at', 'desc')->paginate(10); $messages = ContactMessage::orderBy('created_at', 'desc')->paginate(10);
return view('admin.contact-messages.index', compact('messages')); return view('admin.contact-messages.index', compact('messages'));
} }
/** /**
* Display the specified contact message. * Display the specified contact message.
* *
* @param \App\Models\ContactMessage $contactMessage * @param \App\Models\ContactMessage $contactMessage
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function show(ContactMessage $contactMessage) public function show(ContactMessage $contactMessage)
{ {
// Mark as read // Mark as read
if (!$contactMessage->is_read) { if (!$contactMessage->is_read) {
$contactMessage->is_read = true; $contactMessage->is_read = true;
$contactMessage->save(); $contactMessage->save();
} }
return view('admin.contact-messages.show', compact('contactMessage')); return view('admin.contact-messages.show', compact('contactMessage'));
} }
/** /**
* Remove the specified contact message from storage. * Remove the specified contact message from storage.
* *
* @param \App\Models\ContactMessage $contactMessage * @param \App\Models\ContactMessage $contactMessage
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function destroy(ContactMessage $contactMessage) public function destroy(ContactMessage $contactMessage)
{ {
$contactMessage->delete(); $contactMessage->delete();
return redirect()->route('admin.contact-messages.index') return redirect()->route('admin.contact-messages.index')
->with('success', 'Pesan kontak berhasil dihapus.'); ->with('success', 'Pesan kontak berhasil dihapus.');
} }
} }

View File

@ -1,111 +1,111 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Cafe; use App\Models\Cafe;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Exception; use Exception;
class FavoriteController extends Controller class FavoriteController extends Controller
{ {
// Konstruktor tidak diperlukan karena middleware auth sudah diterapkan di route // Konstruktor tidak diperlukan karena middleware auth sudah diterapkan di route
/** /**
* Toggle status favorit cafe * Toggle status favorit cafe
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $cafeId * @param int $cafeId
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function toggleFavorite(Request $request, $cafeId) public function toggleFavorite(Request $request, $cafeId)
{ {
try { try {
// Validasi input // Validasi input
if (!$cafeId || !is_numeric($cafeId)) { if (!$cafeId || !is_numeric($cafeId)) {
Log::error('Invalid cafe ID provided: ' . $cafeId); Log::error('Invalid cafe ID provided: ' . $cafeId);
return response()->json(['success' => false, 'message' => 'ID cafe tidak valid'], 400); return response()->json(['success' => false, 'message' => 'ID cafe tidak valid'], 400);
} }
// Cek apakah cafe dengan ID tersebut ada // Cek apakah cafe dengan ID tersebut ada
$cafe = Cafe::find($cafeId); $cafe = Cafe::find($cafeId);
if (!$cafe) { if (!$cafe) {
Log::error('Cafe not found with ID: ' . $cafeId); Log::error('Cafe not found with ID: ' . $cafeId);
return response()->json(['success' => false, 'message' => 'Cafe tidak ditemukan'], 404); return response()->json(['success' => false, 'message' => 'Cafe tidak ditemukan'], 404);
} }
$user = Auth::user(); $user = Auth::user();
if (!$user) { if (!$user) {
Log::error('User not authenticated for favorite toggle'); Log::error('User not authenticated for favorite toggle');
return response()->json(['success' => false, 'message' => 'Pengguna tidak terautentikasi'], 401); return response()->json(['success' => false, 'message' => 'Pengguna tidak terautentikasi'], 401);
} }
Log::info('Toggling favorite for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); Log::info('Toggling favorite for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId);
// Pemeriksaan apakah sudah favorit secara langsung menggunakan query // Pemeriksaan apakah sudah favorit secara langsung menggunakan query
$existingFavorite = DB::table('favorites') $existingFavorite = DB::table('favorites')
->where('user_id', $user->id) ->where('user_id', $user->id)
->where('cafe_id', $cafeId) ->where('cafe_id', $cafeId)
->first(); ->first();
if ($existingFavorite) { if ($existingFavorite) {
// Hapus favorit // Hapus favorit
DB::table('favorites') DB::table('favorites')
->where('user_id', $user->id) ->where('user_id', $user->id)
->where('cafe_id', $cafeId) ->where('cafe_id', $cafeId)
->delete(); ->delete();
$isFavorited = false; $isFavorited = false;
Log::info('Favorite removed for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); Log::info('Favorite removed for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId);
} else { } else {
// Tambahkan favorit // Tambahkan favorit
DB::table('favorites')->insert([ DB::table('favorites')->insert([
'user_id' => $user->id, 'user_id' => $user->id,
'cafe_id' => $cafeId, 'cafe_id' => $cafeId,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now() 'updated_at' => now()
]); ]);
$isFavorited = true; $isFavorited = true;
Log::info('Favorite added for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); Log::info('Favorite added for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId);
} }
if ($request->wantsJson() || $request->ajax()) { if ($request->wantsJson() || $request->ajax()) {
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'isFavorited' => $isFavorited 'isFavorited' => $isFavorited
]); ]);
} }
return back()->with('success', $isFavorited ? 'Cafe ditambahkan ke favorit' : 'Cafe dihapus dari favorit'); return back()->with('success', $isFavorited ? 'Cafe ditambahkan ke favorit' : 'Cafe dihapus dari favorit');
} catch (Exception $e) { } catch (Exception $e) {
Log::error('Error toggling favorite: ' . $e->getMessage() . "\n" . $e->getTraceAsString()); Log::error('Error toggling favorite: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
if ($request->wantsJson() || $request->ajax()) { if ($request->wantsJson() || $request->ajax()) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage() 'message' => 'Terjadi kesalahan: ' . $e->getMessage()
], 500); ], 500);
} }
return back()->with('error', 'Terjadi kesalahan saat mengubah status favorit.'); return back()->with('error', 'Terjadi kesalahan saat mengubah status favorit.');
} }
} }
/** /**
* Menampilkan daftar cafe favorit user * Menampilkan daftar cafe favorit user
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index() public function index()
{ {
$user = Auth::user(); $user = Auth::user();
$favorites = $user->favorites()->with('cafe')->get(); $favorites = $user->favorites()->with('cafe')->get();
return view('favorites.index', [ return view('favorites.index', [
'favorites' => $favorites 'favorites' => $favorites
]); ]);
} }
} }

View File

@ -1,54 +1,54 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
class ProfileController extends Controller class ProfileController extends Controller
{ {
/** /**
* Menampilkan halaman profil * Menampilkan halaman profil
*/ */
public function index() public function index()
{ {
$user = Auth::user(); $user = Auth::user();
return view('profile.index', compact('user')); return view('profile.index', compact('user'));
} }
/** /**
* Update informasi profil pengguna * Update informasi profil pengguna
*/ */
public function update(Request $request) public function update(Request $request)
{ {
$user = Auth::user(); $user = Auth::user();
$validated = $request->validate([ $validated = $request->validate([
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,'.$user->id], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,'.$user->id],
]); ]);
$user->update($validated); $user->update($validated);
return redirect()->route('profile.index')->with('success', 'Profil berhasil diperbarui!'); return redirect()->route('profile.index')->with('success', 'Profil berhasil diperbarui!');
} }
/** /**
* Update password pengguna * Update password pengguna
*/ */
public function updatePassword(Request $request) public function updatePassword(Request $request)
{ {
$validated = $request->validate([ $validated = $request->validate([
'current_password' => ['required', 'current_password'], 'current_password' => ['required', 'current_password'],
'password' => ['required', 'confirmed', Password::defaults()], 'password' => ['required', 'confirmed', Password::defaults()],
]); ]);
$user = Auth::user(); $user = Auth::user();
$user->password = Hash::make($validated['password']); $user->password = Hash::make($validated['password']);
$user->save(); $user->save();
return redirect()->route('profile.index')->with('success', 'Password berhasil diperbarui!'); return redirect()->route('profile.index')->with('success', 'Password berhasil diperbarui!');
} }
} }

View File

@ -1,199 +1,199 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Cafe; use App\Models\Cafe;
use App\Models\Category; use App\Models\Category;
use App\Models\Subcategory; use App\Models\Subcategory;
use App\Models\SearchHistory; use App\Models\SearchHistory;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class SmartSearchController extends Controller class SmartSearchController extends Controller
{ {
/** /**
* Display the search form. * Display the search form.
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function index() public function index()
{ {
$currentStep = 1; $currentStep = 1;
return view('cafe.search-fixed', compact('currentStep')); return view('cafe.search-fixed', compact('currentStep'));
} }
/** /**
* Process the search form and display results. * Process the search form and display results.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function search(Request $request) public function search(Request $request)
{ {
$currentStep = 3; $currentStep = 3;
$weights = $request->input('weight', []); $weights = $request->input('weight', []);
$criterias = $request->input('criteria', []); $criterias = $request->input('criteria', []);
$lokasi = $request->input('lokasi'); $lokasi = $request->input('lokasi');
// Get cafes with ratings // Get cafes with ratings
$cafes = Cafe::with(['ratings.subcategory', 'ratings.category'])->get(); $cafes = Cafe::with(['ratings.subcategory', 'ratings.category'])->get();
// Filter berdasarkan lokasi jika ada // Filter berdasarkan lokasi jika ada
if ($lokasi) { if ($lokasi) {
$cafes = $cafes->filter(function($cafe) use ($lokasi) { $cafes = $cafes->filter(function($cafe) use ($lokasi) {
return stripos($cafe->lokasi, $lokasi) !== false; return stripos($cafe->lokasi, $lokasi) !== false;
}); });
} }
// Hitung total semua bobot untuk normalisasi // Hitung total semua bobot untuk normalisasi
$totalWeight = array_sum($weights); $totalWeight = array_sum($weights);
if ($totalWeight == 0) { if ($totalWeight == 0) {
// Jika tidak ada bobot, buat bobot merata // Jika tidak ada bobot, buat bobot merata
$categories = Category::all(); $categories = Category::all();
foreach ($categories as $category) { foreach ($categories as $category) {
$weights[$category->id] = 100 / $categories->count(); $weights[$category->id] = 100 / $categories->count();
} }
$totalWeight = 100; $totalWeight = 100;
} }
// Hitung skor SMART untuk setiap cafe // Hitung skor SMART untuk setiap cafe
$cafeScores = []; $cafeScores = [];
foreach ($cafes as $cafe) { foreach ($cafes as $cafe) {
$score = 0; $score = 0;
$debugInfo = []; $debugInfo = [];
$ratings = \Illuminate\Support\Facades\DB::table('cafe_ratings') $ratings = \Illuminate\Support\Facades\DB::table('cafe_ratings')
->join('subcategories', 'cafe_ratings.subcategory_id', '=', 'subcategories.id') ->join('subcategories', 'cafe_ratings.subcategory_id', '=', 'subcategories.id')
->join('categories', 'cafe_ratings.category_id', '=', 'categories.id') ->join('categories', 'cafe_ratings.category_id', '=', 'categories.id')
->where('cafe_ratings.cafe_id', $cafe->id) ->where('cafe_ratings.cafe_id', $cafe->id)
->select( ->select(
'cafe_ratings.category_id', 'cafe_ratings.category_id',
'categories.name as category_name', 'categories.name as category_name',
'cafe_ratings.subcategory_id', 'cafe_ratings.subcategory_id',
'subcategories.name as subcategory_name', 'subcategories.name as subcategory_name',
'subcategories.value' 'subcategories.value'
) )
->get(); ->get();
foreach ($ratings as $rating) { foreach ($ratings as $rating) {
// Jika ada bobot untuk kategori ini // Jika ada bobot untuk kategori ini
if (isset($weights[$rating->category_id])) { if (isset($weights[$rating->category_id])) {
$weight = floatval($weights[$rating->category_id]); $weight = floatval($weights[$rating->category_id]);
$normalizedWeight = $totalWeight > 0 ? $weight / $totalWeight : 0; $normalizedWeight = $totalWeight > 0 ? $weight / $totalWeight : 0;
// Pastikan nilai valid // Pastikan nilai valid
$value = max(1, intval($rating->value)); $value = max(1, intval($rating->value));
// Bonus nilai jika kriteria dipilih // Bonus nilai jika kriteria dipilih
if (isset($criterias[$rating->category_id]) && if (isset($criterias[$rating->category_id]) &&
$criterias[$rating->category_id] == $rating->subcategory_id) { $criterias[$rating->category_id] == $rating->subcategory_id) {
$value = 5; // Nilai maksimal $value = 5; // Nilai maksimal
} }
// Hitung utilitas - menerapkan konsep yang sama dengan view // Hitung utilitas - menerapkan konsep yang sama dengan view
$utilityValue = ($value - 1) / 4; // Normalisasi ke rentang 0-1 (jika nilai antara 1-5) $utilityValue = ($value - 1) / 4; // Normalisasi ke rentang 0-1 (jika nilai antara 1-5)
$ratingScore = $normalizedWeight * $utilityValue; $ratingScore = $normalizedWeight * $utilityValue;
$score += $ratingScore; $score += $ratingScore;
// Simpan untuk debug // Simpan untuk debug
$debugInfo[$rating->category_name] = [ $debugInfo[$rating->category_name] = [
'bobot' => $weight, 'bobot' => $weight,
'bobot_normal' => $normalizedWeight, 'bobot_normal' => $normalizedWeight,
'nilai' => $value, 'nilai' => $value,
'nilai_asli' => $rating->value, 'nilai_asli' => $rating->value,
'subcategory' => $rating->subcategory_name, 'subcategory' => $rating->subcategory_name,
'skor_kriteria' => $ratingScore 'skor_kriteria' => $ratingScore
]; ];
} }
} }
$cafeScores[$cafe->id] = [ $cafeScores[$cafe->id] = [
'score' => $score, 'score' => $score,
'debug' => $debugInfo 'debug' => $debugInfo
]; ];
} }
// Urutkan cafe berdasarkan skor tertinggi // Urutkan cafe berdasarkan skor tertinggi
uasort($cafeScores, function($a, $b) { uasort($cafeScores, function($a, $b) {
return $b['score'] <=> $a['score']; return $b['score'] <=> $a['score'];
}); });
// Ambil id cafe yang sudah diurutkan // Ambil id cafe yang sudah diurutkan
$rankedCafeIds = array_keys($cafeScores); $rankedCafeIds = array_keys($cafeScores);
// Urutkan koleksi cafe sesuai skor // Urutkan koleksi cafe sesuai skor
$rankedCafes = $cafes->sortBy(function($cafe) use ($rankedCafeIds) { $rankedCafes = $cafes->sortBy(function($cafe) use ($rankedCafeIds) {
return array_search($cafe->id, $rankedCafeIds); return array_search($cafe->id, $rankedCafeIds);
}); });
// Ambil hanya 10 cafe teratas // Ambil hanya 10 cafe teratas
$rankedCafes = $rankedCafes->take(10); $rankedCafes = $rankedCafes->take(10);
// Persiapkan data hasil untuk disimpan // Persiapkan data hasil untuk disimpan
$resultsData = []; $resultsData = [];
foreach ($rankedCafes as $cafe) { foreach ($rankedCafes as $cafe) {
if (isset($cafeScores[$cafe->id])) { if (isset($cafeScores[$cafe->id])) {
$resultsData[$cafe->id] = [ $resultsData[$cafe->id] = [
'name' => $cafe->nama, 'name' => $cafe->nama,
'score' => $cafeScores[$cafe->id]['score'], 'score' => $cafeScores[$cafe->id]['score'],
'details' => $cafeScores[$cafe->id]['debug'] 'details' => $cafeScores[$cafe->id]['debug']
]; ];
} }
} }
// Simpan hasil pencarian ke database jika user sudah login // Simpan hasil pencarian ke database jika user sudah login
if (Auth::check()) { if (Auth::check()) {
SearchHistory::create([ SearchHistory::create([
'user_id' => Auth::id(), 'user_id' => Auth::id(),
'weights' => $weights, 'weights' => $weights,
'criteria' => $criterias, 'criteria' => $criterias,
'location' => $lokasi, 'location' => $lokasi,
'results' => $resultsData 'results' => $resultsData
]); ]);
} }
return view('cafe.search-fixed', compact('currentStep', 'rankedCafes', 'cafeScores')); return view('cafe.search-fixed', compact('currentStep', 'rankedCafes', 'cafeScores'));
} }
/** /**
* Display search history for the authenticated user. * Display search history for the authenticated user.
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function history() public function history()
{ {
$histories = SearchHistory::where('user_id', Auth::id()) $histories = SearchHistory::where('user_id', Auth::id())
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->paginate(10); ->paginate(10);
return view('cafe.search-history', compact('histories')); return view('cafe.search-history', compact('histories'));
} }
/** /**
* Display search history detail. * Display search history detail.
* *
* @param int $id * @param int $id
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function detail($id) public function detail($id)
{ {
$history = SearchHistory::where('user_id', Auth::id()) $history = SearchHistory::where('user_id', Auth::id())
->findOrFail($id); ->findOrFail($id);
// Retrieve cafes from the stored results // Retrieve cafes from the stored results
$cafeIds = array_keys($history->results ?? []); $cafeIds = array_keys($history->results ?? []);
$cafes = Cafe::whereIn('id', $cafeIds)->get(); $cafes = Cafe::whereIn('id', $cafeIds)->get();
// Map cafes to include their scores from history // Map cafes to include their scores from history
$rankedCafes = $cafes->map(function($cafe) use ($history) { $rankedCafes = $cafes->map(function($cafe) use ($history) {
$cafe->smart_score = $history->results[$cafe->id]['score'] ?? 0; $cafe->smart_score = $history->results[$cafe->id]['score'] ?? 0;
$cafe->smart_details = $history->results[$cafe->id]['details'] ?? []; $cafe->smart_details = $history->results[$cafe->id]['details'] ?? [];
return $cafe; return $cafe;
})->sortByDesc('smart_score'); })->sortByDesc('smart_score');
$weights = $history->weights; $weights = $history->weights;
return view('cafe.search-detail', compact('history', 'rankedCafes', 'weights')); return view('cafe.search-detail', compact('history', 'rankedCafes', 'weights'));
} }
} }

View File

@ -1,67 +1,69 @@
<?php <?php
namespace App\Http; namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel class Kernel extends HttpKernel
{ {
/** /**
* The application's global HTTP middleware stack. * The application's global HTTP middleware stack.
* *
* These middleware are run during every request to your application. * These middleware are run during every request to your application.
* *
* @var array<int, class-string|string> * @var array<int, class-string|string>
*/ */
protected $middleware = [ protected $middleware = [
// // \App\Http\Middleware\TrustHosts::class,
]; \App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
/** \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
* The application's route middleware groups. \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
* \App\Http\Middleware\TrimStrings::class,
* @var array<string, array<int, class-string|string>> \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
*/ ];
protected $middlewareGroups = [
'web' => [ /**
// Web middleware group * The application's route middleware groups.
], *
* @var array<string, array<int, class-string|string>>
'api' => [ */
// API middleware group protected $middlewareGroups = [
], 'web' => [
]; \App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
/** \Illuminate\Session\Middleware\StartSession::class,
* The application's route middleware aliases. \Illuminate\View\Middleware\ShareErrorsFromSession::class,
* \App\Http\Middleware\VerifyCsrfToken::class,
* Aliases may be used instead of class names to assign middleware to routes and groups. \Illuminate\Routing\Middleware\SubstituteBindings::class,
* ],
* @var array<string, class-string|string>
*/ 'api' => [
protected $middlewareAliases = [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'auth' => \App\Http\Middleware\Authenticate::class, 'throttle:api',
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, ],
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, ];
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, /**
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, * The application's route middleware.
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, *
'signed' => \App\Http\Middleware\ValidateSignature::class, * These middleware may be assigned to groups or used individually.
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, *
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, * @var array<string, class-string|string>
'admin' => \App\Http\Middleware\AdminMiddleware::class, */
]; protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
/** 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
* Register the application's route middleware. 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
* 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
* These middleware may be assigned to groups or used individually. 'can' => \Illuminate\Auth\Middleware\Authorize::class,
* 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
* @var array<string, class-string|string> 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
*/ 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
protected $routeMiddleware = [ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
// 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
]; 'role' => \App\Http\Middleware\CheckRole::class,
'is_admin' => \App\Http\Middleware\AdminMiddleware::class,
];
} }

View File

@ -1,26 +1,26 @@
<?php <?php
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class AdminMiddleware class AdminMiddleware
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (Auth::check() && Auth::user()->role === 'admin') { if (Auth::check() && Auth::user()->role === 'admin') {
return $next($request); return $next($request);
} }
return redirect()->route('home')->with('error', 'Anda tidak memiliki akses untuk halaman ini.'); return redirect()->route('home')->with('error', 'Anda tidak memiliki akses untuk halaman ini.');
} }
} }

View File

@ -1,38 +1,38 @@
<?php <?php
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
class RedirectIfAuthenticated class RedirectIfAuthenticated
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @param string|null ...$guards * @param string|null ...$guards
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next, ...$guards) public function handle(Request $request, Closure $next, ...$guards)
{ {
$guards = empty($guards) ? [null] : $guards; $guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) { foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) { if (Auth::guard($guard)->check()) {
$user = Auth::guard($guard)->user(); $user = Auth::guard($guard)->user();
if ($user && $user->role === 'admin') { if ($user && $user->role === 'admin') {
return redirect()->route('admin.dashboard'); return redirect()->route('admin.dashboard');
} }
return redirect(RouteServiceProvider::HOME); return redirect(RouteServiceProvider::HOME);
} }
} }
return $next($request); return $next($request);
} }
} }

View File

@ -1,21 +1,21 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
class CafeImage extends Model class CafeImage extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'cafe_id', 'cafe_id',
'image_path' 'image_path'
]; ];
public function cafe() public function cafe()
{ {
return $this->belongsTo(Cafe::class); return $this->belongsTo(Cafe::class);
} }
} }

View File

@ -1,32 +1,32 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class CafeRating extends Model class CafeRating extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'cafe_id', 'cafe_id',
'category_id', 'category_id',
'subcategory_id', 'subcategory_id',
]; ];
public function cafe() public function cafe()
{ {
return $this->belongsTo(Cafe::class); return $this->belongsTo(Cafe::class);
} }
public function category() public function category()
{ {
return $this->belongsTo(Category::class); return $this->belongsTo(Category::class);
} }
public function subcategory() public function subcategory()
{ {
return $this->belongsTo(Subcategory::class); return $this->belongsTo(Subcategory::class);
} }
} }

View File

@ -1,50 +1,50 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class Category extends Model class Category extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'name', 'name',
'slug', 'slug',
'description' 'description'
]; ];
/** /**
* Boot the model. * Boot the model.
*/ */
protected static function boot() protected static function boot()
{ {
parent::boot(); parent::boot();
static::creating(function ($category) { static::creating(function ($category) {
$category->slug = Str::slug($category->name); $category->slug = Str::slug($category->name);
}); });
static::updating(function ($category) { static::updating(function ($category) {
$category->slug = Str::slug($category->name); $category->slug = Str::slug($category->name);
}); });
} }
/** /**
* Get the subcategories for the category. * Get the subcategories for the category.
*/ */
public function subcategories() public function subcategories()
{ {
return $this->hasMany(Subcategory::class); return $this->hasMany(Subcategory::class);
} }
/** /**
* Get the cafes for the category. * Get the cafes for the category.
*/ */
public function cafes() public function cafes()
{ {
return $this->hasMany(Cafe::class); return $this->hasMany(Cafe::class);
} }
} }

View File

@ -1,23 +1,23 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class ContactMessage extends Model class ContactMessage extends Model
{ {
use HasFactory; use HasFactory;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array<int, string> * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'name', 'name',
'email', 'email',
'message', 'message',
'is_read', 'is_read',
]; ];
} }

View File

@ -1,23 +1,23 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Favorite extends Model class Favorite extends Model
{ {
protected $fillable = [ protected $fillable = [
'user_id', 'user_id',
'cafe_id' 'cafe_id'
]; ];
public function user() public function user()
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);
} }
public function cafe() public function cafe()
{ {
return $this->belongsTo(Cafe::class); return $this->belongsTo(Cafe::class);
} }
} }

View File

@ -1,45 +1,45 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class SearchHistory extends Model class SearchHistory extends Model
{ {
use HasFactory; use HasFactory;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'user_id', 'user_id',
'weights', 'weights',
'criteria', 'criteria',
'location', 'location',
'results' 'results'
]; ];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'weights' => 'array', 'weights' => 'array',
'criteria' => 'array', 'criteria' => 'array',
'results' => 'array', 'results' => 'array',
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
]; ];
/** /**
* Get the user that owns the search history. * Get the user that owns the search history.
*/ */
public function user() public function user()
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);
} }
} }

View File

@ -1,43 +1,43 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class Subcategory extends Model class Subcategory extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'category_id', 'category_id',
'name', 'name',
'slug', 'slug',
'value', 'value',
]; ];
/** /**
* Boot the model. * Boot the model.
*/ */
protected static function boot() protected static function boot()
{ {
parent::boot(); parent::boot();
static::creating(function ($subcategory) { static::creating(function ($subcategory) {
$subcategory->slug = Str::slug($subcategory->name); $subcategory->slug = Str::slug($subcategory->name);
}); });
static::updating(function ($subcategory) { static::updating(function ($subcategory) {
$subcategory->slug = Str::slug($subcategory->name); $subcategory->slug = Str::slug($subcategory->name);
}); });
} }
/** /**
* Get the category that owns the subcategory. * Get the category that owns the subcategory.
*/ */
public function category() public function category()
{ {
return $this->belongsTo(Category::class); return $this->belongsTo(Category::class);
} }
} }

View File

@ -1,116 +1,116 @@
<?php <?php
namespace App\Notifications; namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
class CustomVerifyEmail extends Notification class CustomVerifyEmail extends Notification
{ {
/** /**
* The callback that should be used to create the verify email URL. * The callback that should be used to create the verify email URL.
* *
* @var \Closure|null * @var \Closure|null
*/ */
public static $createUrlCallback; public static $createUrlCallback;
/** /**
* The callback that should be used to build the mail message. * The callback that should be used to build the mail message.
* *
* @var \Closure|null * @var \Closure|null
*/ */
public static $toMailCallback; public static $toMailCallback;
/** /**
* Get the notification's channels. * Get the notification's channels.
* *
* @param mixed $notifiable * @param mixed $notifiable
* @return array|string * @return array|string
*/ */
public function via($notifiable) public function via($notifiable)
{ {
return ['mail']; return ['mail'];
} }
/** /**
* Build the mail representation of the notification. * Build the mail representation of the notification.
* *
* @param mixed $notifiable * @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage * @return \Illuminate\Notifications\Messages\MailMessage
*/ */
public function toMail($notifiable) public function toMail($notifiable)
{ {
$verificationUrl = $this->verificationUrl($notifiable); $verificationUrl = $this->verificationUrl($notifiable);
if (static::$toMailCallback) { if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl); return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
} }
return $this->buildMailMessage($verificationUrl); return $this->buildMailMessage($verificationUrl);
} }
/** /**
* Get the verify email notification mail message for the given URL. * Get the verify email notification mail message for the given URL.
* *
* @param string $url * @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage * @return \Illuminate\Notifications\Messages\MailMessage
*/ */
protected function buildMailMessage($url) protected function buildMailMessage($url)
{ {
return (new MailMessage) return (new MailMessage)
->subject('Verifikasi Alamat Email Anda') ->subject('Verifikasi Alamat Email Anda')
->greeting('Halo!') ->greeting('Halo!')
->line('Terima kasih telah mendaftar di aplikasi Recaje.') ->line('Terima kasih telah mendaftar di aplikasi Recaje.')
->line('Silakan klik tombol di bawah ini untuk memverifikasi alamat email Anda.') ->line('Silakan klik tombol di bawah ini untuk memverifikasi alamat email Anda.')
->action('Verifikasi Email', $url) ->action('Verifikasi Email', $url)
->line('Jika Anda tidak membuat akun, Anda dapat mengabaikan email ini.') ->line('Jika Anda tidak membuat akun, Anda dapat mengabaikan email ini.')
->salutation('Salam, Tim Recaje'); ->salutation('Salam, Tim Recaje');
} }
/** /**
* Get the verification URL for the given notifiable. * Get the verification URL for the given notifiable.
* *
* @param mixed $notifiable * @param mixed $notifiable
* @return string * @return string
*/ */
protected function verificationUrl($notifiable) protected function verificationUrl($notifiable)
{ {
if (static::$createUrlCallback) { if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable); return call_user_func(static::$createUrlCallback, $notifiable);
} }
return URL::temporarySignedRoute( return URL::temporarySignedRoute(
'verification.verify', 'verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)), Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[ [
'id' => $notifiable->getKey(), 'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()), 'hash' => sha1($notifiable->getEmailForVerification()),
] ]
); );
} }
/** /**
* Set a callback that should be used when creating the email verification URL. * Set a callback that should be used when creating the email verification URL.
* *
* @param \Closure $callback * @param \Closure $callback
* @return void * @return void
*/ */
public static function createUrlUsing($callback) public static function createUrlUsing($callback)
{ {
static::$createUrlCallback = $callback; static::$createUrlCallback = $callback;
} }
/** /**
* Set a callback that should be used when building the notification mail message. * Set a callback that should be used when building the notification mail message.
* *
* @param \Closure $callback * @param \Closure $callback
* @return void * @return void
*/ */
public static function toMailUsing($callback) public static function toMailUsing($callback)
{ {
static::$toMailCallback = $callback; static::$toMailCallback = $callback;
} }
} }

View File

@ -1,49 +1,49 @@
<?php <?php
namespace App\Providers; namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider class RouteServiceProvider extends ServiceProvider
{ {
/** /**
* The path to your application's "home" route. * The path to your application's "home" route.
* *
* Typically, users are redirected here after authentication. * Typically, users are redirected here after authentication.
* *
* @var string * @var string
*/ */
public const HOME = '/'; public const HOME = '/';
/** /**
* Define your route model bindings, pattern filters, etc. * Define your route model bindings, pattern filters, etc.
* *
* @return void * @return void
*/ */
public function boot() public function boot()
{ {
$this->configureRateLimiting(); $this->configureRateLimiting();
$this->routes(function () { $this->routes(function () {
Route::prefix('api') Route::prefix('api')
->middleware('api') ->middleware('api')
->namespace($this->namespace) ->namespace($this->namespace)
->group(base_path('routes/api.php')); ->group(base_path('routes/api.php'));
Route::middleware('web') Route::middleware('web')
->namespace($this->namespace) ->namespace($this->namespace)
->group(base_path('routes/web.php')); ->group(base_path('routes/web.php'));
}); });
} }
/** /**
* Configure the rate limiters for the application. * Configure the rate limiters for the application.
* *
* @return void * @return void
*/ */
protected function configureRateLimiting() protected function configureRateLimiting()
{ {
// //
} }
} }

View File

@ -47,9 +47,9 @@
'url' => env('DB_URL'), 'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'), 'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'), 'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'oyiwebid_recaje'), 'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'oyiwebid_faradina'), 'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', 'Faradina14*'), 'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''), 'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'), 'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),

View File

@ -1,35 +1,35 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class CreateContactMessagesTable extends Migration class CreateContactMessagesTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
* *
* @return void * @return void
*/ */
public function up() public function up()
{ {
Schema::create('contact_messages', function (Blueprint $table) { Schema::create('contact_messages', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('email'); $table->string('email');
$table->text('message'); $table->text('message');
$table->boolean('is_read')->default(false); $table->boolean('is_read')->default(false);
$table->timestamps(); $table->timestamps();
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
* *
* @return void * @return void
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('contact_messages'); Schema::dropIfExists('contact_messages');
} }
} }

View File

@ -1,30 +1,30 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
Schema::table('cafes', function (Blueprint $table) { Schema::table('cafes', function (Blueprint $table) {
// Menambahkan kolom area setelah kolom lokasi dan sebelum kolom gambar // Menambahkan kolom area setelah kolom lokasi dan sebelum kolom gambar
$table->string('area')->nullable()->after('lokasi'); $table->string('area')->nullable()->after('lokasi');
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::table('cafes', function (Blueprint $table) { Schema::table('cafes', function (Blueprint $table) {
// Menghapus kolom area jika migrasi di-rollback // Menghapus kolom area jika migrasi di-rollback
$table->dropColumn('area'); $table->dropColumn('area');
}); });
} }
}; };

View File

@ -1,36 +1,36 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class CreateSearchHistoriesTable extends Migration class CreateSearchHistoriesTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
* *
* @return void * @return void
*/ */
public function up() public function up()
{ {
Schema::create('search_histories', function (Blueprint $table) { Schema::create('search_histories', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->json('weights')->nullable(); // Menyimpan bobot kriteria $table->json('weights')->nullable(); // Menyimpan bobot kriteria
$table->json('criteria')->nullable(); // Menyimpan kriteria yang dipilih $table->json('criteria')->nullable(); // Menyimpan kriteria yang dipilih
$table->string('location')->nullable(); // Menyimpan lokasi jika ada $table->string('location')->nullable(); // Menyimpan lokasi jika ada
$table->json('results')->nullable(); // Menyimpan hasil cafe dengan skor $table->json('results')->nullable(); // Menyimpan hasil cafe dengan skor
$table->timestamps(); $table->timestamps();
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
* *
* @return void * @return void
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('search_histories'); Schema::dropIfExists('search_histories');
} }
} }

View File

@ -1,32 +1,32 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
public function up() public function up()
{ {
Schema::create('categories', function (Blueprint $table) { Schema::create('categories', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('subcategory1')->nullable(); $table->string('subcategory1')->nullable();
$table->string('subcategory2')->nullable(); $table->string('subcategory2')->nullable();
$table->string('subcategory3')->nullable(); $table->string('subcategory3')->nullable();
$table->string('subcategory4')->nullable(); $table->string('subcategory4')->nullable();
$table->string('subcategory5')->nullable(); $table->string('subcategory5')->nullable();
$table->integer('value1')->nullable(); $table->integer('value1')->nullable();
$table->integer('value2')->nullable(); $table->integer('value2')->nullable();
$table->integer('value3')->nullable(); $table->integer('value3')->nullable();
$table->integer('value4')->nullable(); $table->integer('value4')->nullable();
$table->integer('value5')->nullable(); $table->integer('value5')->nullable();
$table->timestamps(); $table->timestamps();
}); });
} }
public function down() public function down()
{ {
Schema::dropIfExists('categories'); Schema::dropIfExists('categories');
} }
}; };

View File

@ -1,30 +1,30 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
Schema::create('categories', function (Blueprint $table) { Schema::create('categories', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('slug')->unique(); $table->string('slug')->unique();
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->timestamps(); $table->timestamps();
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('categories'); Schema::dropIfExists('categories');
} }
}; };

View File

@ -1,30 +1,30 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
Schema::create('subcategories', function (Blueprint $table) { Schema::create('subcategories', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->string('name'); $table->string('name');
$table->string('slug')->unique(); $table->string('slug')->unique();
$table->timestamps(); $table->timestamps();
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('subcategories'); Schema::dropIfExists('subcategories');
} }
}; };

View File

@ -1,56 +1,56 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
// Hapus tabel subcategories jika ada // Hapus tabel subcategories jika ada
Schema::dropIfExists('subcategories'); Schema::dropIfExists('subcategories');
// Modifikasi tabel categories // Modifikasi tabel categories
Schema::table('categories', function (Blueprint $table) { Schema::table('categories', function (Blueprint $table) {
// Tambahkan kolom untuk subkategori // Tambahkan kolom untuk subkategori
$table->string('subcategory1')->nullable(); $table->string('subcategory1')->nullable();
$table->string('subcategory2')->nullable(); $table->string('subcategory2')->nullable();
$table->string('subcategory3')->nullable(); $table->string('subcategory3')->nullable();
$table->string('subcategory4')->nullable(); $table->string('subcategory4')->nullable();
$table->string('subcategory5')->nullable(); $table->string('subcategory5')->nullable();
// Tambahkan kolom untuk nilai subkategori // Tambahkan kolom untuk nilai subkategori
$table->integer('value1')->default(1); $table->integer('value1')->default(1);
$table->integer('value2')->default(2); $table->integer('value2')->default(2);
$table->integer('value3')->default(3); $table->integer('value3')->default(3);
$table->integer('value4')->default(4); $table->integer('value4')->default(4);
$table->integer('value5')->default(5); $table->integer('value5')->default(5);
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::table('categories', function (Blueprint $table) { Schema::table('categories', function (Blueprint $table) {
// Hapus kolom subkategori // Hapus kolom subkategori
$table->dropColumn([ $table->dropColumn([
'subcategory1', 'subcategory2', 'subcategory3', 'subcategory4', 'subcategory5', 'subcategory1', 'subcategory2', 'subcategory3', 'subcategory4', 'subcategory5',
'value1', 'value2', 'value3', 'value4', 'value5' 'value1', 'value2', 'value3', 'value4', 'value5'
]); ]);
}); });
// Buat ulang tabel subcategories // Buat ulang tabel subcategories
Schema::create('subcategories', function (Blueprint $table) { Schema::create('subcategories', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->string('name'); $table->string('name');
$table->timestamps(); $table->timestamps();
}); });
} }
}; };

View File

@ -1,30 +1,30 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
Schema::create('cafe_ratings', function (Blueprint $table) { Schema::create('cafe_ratings', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('cafe_id')->constrained()->onDelete('cascade'); $table->foreignId('cafe_id')->constrained()->onDelete('cascade');
$table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->foreignId('subcategory_id')->constrained()->onDelete('cascade'); $table->foreignId('subcategory_id')->constrained()->onDelete('cascade');
$table->timestamps(); $table->timestamps();
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('cafe_ratings'); Schema::dropIfExists('cafe_ratings');
} }
}; };

View File

@ -1,57 +1,57 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
// Langkah 1: Drop tabel cafe_ratings dahulu karena memiliki foreign key ke cafes // Langkah 1: Drop tabel cafe_ratings dahulu karena memiliki foreign key ke cafes
Schema::dropIfExists('cafe_ratings'); Schema::dropIfExists('cafe_ratings');
// Langkah 2: Buat tabel cafes_temp dengan struktur baru // Langkah 2: Buat tabel cafes_temp dengan struktur baru
Schema::create('cafes_temp', function (Blueprint $table) { Schema::create('cafes_temp', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('nama'); $table->string('nama');
$table->string('lokasi'); $table->string('lokasi');
$table->string('gambar')->nullable(); $table->string('gambar')->nullable();
$table->decimal('latitude', 10, 7)->nullable(); $table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable(); $table->decimal('longitude', 10, 7)->nullable();
$table->timestamps(); $table->timestamps();
}); });
// Langkah 3: Pindahkan data dari cafes ke cafes_temp // Langkah 3: Pindahkan data dari cafes ke cafes_temp
DB::statement('INSERT INTO cafes_temp (id, nama, lokasi, gambar, latitude, longitude, created_at, updated_at) DB::statement('INSERT INTO cafes_temp (id, nama, lokasi, gambar, latitude, longitude, created_at, updated_at)
SELECT id, nama, lokasi, gambar, latitude, longitude, created_at, updated_at FROM cafes'); SELECT id, nama, lokasi, gambar, latitude, longitude, created_at, updated_at FROM cafes');
// Langkah 4: Drop tabel cafes lama // Langkah 4: Drop tabel cafes lama
Schema::dropIfExists('cafes'); Schema::dropIfExists('cafes');
// Langkah 5: Rename cafes_temp menjadi cafes // Langkah 5: Rename cafes_temp menjadi cafes
Schema::rename('cafes_temp', 'cafes'); Schema::rename('cafes_temp', 'cafes');
// Langkah 6: Buat kembali tabel cafe_ratings dengan struktur baru // Langkah 6: Buat kembali tabel cafe_ratings dengan struktur baru
Schema::create('cafe_ratings', function (Blueprint $table) { Schema::create('cafe_ratings', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('cafe_id')->constrained()->onDelete('cascade'); $table->foreignId('cafe_id')->constrained()->onDelete('cascade');
$table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->foreignId('subcategory_id')->constrained()->onDelete('cascade'); $table->foreignId('subcategory_id')->constrained()->onDelete('cascade');
$table->timestamps(); $table->timestamps();
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
// Tak perlu diimplementasikan karena ini adalah migrasi restrukturisasi // Tak perlu diimplementasikan karena ini adalah migrasi restrukturisasi
// Jika ingin rollback, perlu restore manual dari backup // Jika ingin rollback, perlu restore manual dari backup
} }
}; };

View File

@ -1,32 +1,32 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
Schema::create('favorites', function (Blueprint $table) { Schema::create('favorites', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('cafe_id')->constrained()->onDelete('cascade'); $table->foreignId('cafe_id')->constrained()->onDelete('cascade');
$table->timestamps(); $table->timestamps();
// Pastikan kombinasi user_id dan cafe_id unik // Pastikan kombinasi user_id dan cafe_id unik
$table->unique(['user_id', 'cafe_id']); $table->unique(['user_id', 'cafe_id']);
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('favorites'); Schema::dropIfExists('favorites');
} }
}; };

View File

@ -1,23 +1,23 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
public function up() public function up()
{ {
Schema::create('cafe_images', function (Blueprint $table) { Schema::create('cafe_images', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('cafe_id')->constrained()->onDelete('cascade'); $table->foreignId('cafe_id')->constrained()->onDelete('cascade');
$table->string('image_path'); $table->string('image_path');
$table->timestamps(); $table->timestamps();
}); });
} }
public function down() public function down()
{ {
Schema::dropIfExists('cafe_images'); Schema::dropIfExists('cafe_images');
} }
}; };

View File

@ -1,29 +1,29 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
Schema::table('cafes', function (Blueprint $table) { Schema::table('cafes', function (Blueprint $table) {
$table->decimal('harga_termurah', 10, 2)->nullable()->after('area'); $table->decimal('harga_termurah', 10, 2)->nullable()->after('area');
$table->decimal('harga_termahal', 10, 2)->nullable()->after('harga_termurah'); $table->decimal('harga_termahal', 10, 2)->nullable()->after('harga_termurah');
}); });
} }
/** /**
* Reverse the migrations. * Reverse the migrations.
*/ */
public function down(): void public function down(): void
{ {
Schema::table('cafes', function (Blueprint $table) { Schema::table('cafes', function (Blueprint $table) {
$table->dropColumn(['harga_termurah', 'harga_termahal']); $table->dropColumn(['harga_termurah', 'harga_termahal']);
}); });
} }
}; };

View File

@ -1,126 +1,126 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Cafe; use App\Models\Cafe;
use App\Models\CafeRating; use App\Models\CafeRating;
use App\Models\Category; use App\Models\Category;
use App\Models\Subcategory; use App\Models\Subcategory;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class CafeAndRatingsSeeder extends Seeder class CafeAndRatingsSeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
// Hapus data cafe dan rating yang sudah ada // Hapus data cafe dan rating yang sudah ada
DB::statement('SET FOREIGN_KEY_CHECKS=0'); DB::statement('SET FOREIGN_KEY_CHECKS=0');
CafeRating::truncate(); CafeRating::truncate();
Cafe::truncate(); Cafe::truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1'); DB::statement('SET FOREIGN_KEY_CHECKS=1');
// Data cafe dengan rating spesifik untuk setiap kategori // Data cafe dengan rating spesifik untuk setiap kategori
$cafes = [ $cafes = [
['nama' => 'Poppins', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Indoor, Outdoor'], ['nama' => 'Poppins', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Indoor, Outdoor'],
['nama' => 'Teras JTI', 'lokasi' => null, 'harga' => 4, 'wifi' => 5, 'jam' => 3, 'fotogenik' => 3, 'jarak' => 5, 'fasilitas' => 4, 'area' => 'Indoor, Semi Outdoor, Outdoor'], ['nama' => 'Teras JTI', 'lokasi' => null, 'harga' => 4, 'wifi' => 5, 'jam' => 3, 'fotogenik' => 3, 'jarak' => 5, 'fasilitas' => 4, 'area' => 'Indoor, Semi Outdoor, Outdoor'],
['nama' => 'Nugas Jember', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 1, 'jarak' => 4, 'fasilitas' => 1, 'area' => 'Semi Outdoor'], ['nama' => 'Nugas Jember', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 1, 'jarak' => 4, 'fasilitas' => 1, 'area' => 'Semi Outdoor'],
['nama' => 'Kopi Kampus', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 1, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Semi Outdoor'], ['nama' => 'Kopi Kampus', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 1, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Semi Outdoor'],
['nama' => 'Eterno', 'lokasi' => null, 'harga' => 2, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 5, 'area' => 'Indoor, Outdoor'], ['nama' => 'Eterno', 'lokasi' => null, 'harga' => 2, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 5, 'area' => 'Indoor, Outdoor'],
['nama' => 'Kattappa', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 4, 'area' => 'Indoor, Semi Outdoor, Outdoor'], ['nama' => 'Kattappa', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 4, 'area' => 'Indoor, Semi Outdoor, Outdoor'],
['nama' => 'Fifty-Fifty', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 2, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Semi Outdoor'], ['nama' => 'Fifty-Fifty', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 2, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Semi Outdoor'],
['nama' => 'Discuss Space & Coffee', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 4, 'area' => 'Indoor'], ['nama' => 'Discuss Space & Coffee', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 4, 'area' => 'Indoor'],
['nama' => 'Subur', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor'], ['nama' => 'Subur', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor'],
['nama' => 'Nuansa', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 2, 'jarak' => 3, 'fasilitas' => 5, 'area' => 'Indoor, Semi Outdoor'], ['nama' => 'Nuansa', 'lokasi' => null, 'harga' => 4, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 2, 'jarak' => 3, 'fasilitas' => 5, 'area' => 'Indoor, Semi Outdoor'],
['nama' => 'Nol Kilometer', 'lokasi' => null, 'harga' => 5, 'wifi' => 2, 'jam' => 3, 'fotogenik' => 1, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Semi Outdoor'], ['nama' => 'Nol Kilometer', 'lokasi' => null, 'harga' => 5, 'wifi' => 2, 'jam' => 3, 'fotogenik' => 1, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Semi Outdoor'],
['nama' => 'Tanaloka', 'lokasi' => null, 'harga' => 2, 'wifi' => 4, 'jam' => 2, 'fotogenik' => 5, 'jarak' => 3, 'fasilitas' => 5, 'area' => 'Indoor, Semi Outdoor, Outdoor'], ['nama' => 'Tanaloka', 'lokasi' => null, 'harga' => 2, 'wifi' => 4, 'jam' => 2, 'fotogenik' => 5, 'jarak' => 3, 'fasilitas' => 5, 'area' => 'Indoor, Semi Outdoor, Outdoor'],
['nama' => 'Wafa', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 2, 'fasilitas' => 5, 'area' => 'Indoor, Outdoor'], ['nama' => 'Wafa', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 2, 'fasilitas' => 5, 'area' => 'Indoor, Outdoor'],
['nama' => '888', 'lokasi' => null, 'harga' => 2, 'wifi' => 5, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Semi Outdoor'], ['nama' => '888', 'lokasi' => null, 'harga' => 2, 'wifi' => 5, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Semi Outdoor'],
['nama' => 'Sorai', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Indoor, Outdoor'], ['nama' => 'Sorai', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Indoor, Outdoor'],
['nama' => 'Tharuh', 'lokasi' => null, 'harga' => 3, 'wifi' => 2, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Semi Outdoor'], ['nama' => 'Tharuh', 'lokasi' => null, 'harga' => 3, 'wifi' => 2, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Semi Outdoor'],
['nama' => 'Contact', 'lokasi' => null, 'harga' => 3, 'wifi' => 2, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Semi Outdoor, Outdoor'], ['nama' => 'Contact', 'lokasi' => null, 'harga' => 3, 'wifi' => 2, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Semi Outdoor, Outdoor'],
['nama' => 'Cus Cus', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Outdoor'], ['nama' => 'Cus Cus', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 4, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor, Outdoor'],
['nama' => 'Grufi', 'lokasi' => null, 'harga' => 2, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 5, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor'], ['nama' => 'Grufi', 'lokasi' => null, 'harga' => 2, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 5, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor'],
['nama' => 'Tomoro', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 4, 'fasilitas' => 4, 'area' => 'Indoor'], ['nama' => 'Tomoro', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 4, 'fasilitas' => 4, 'area' => 'Indoor'],
['nama' => 'Fore', 'lokasi' => null, 'harga' => 2, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor'], ['nama' => 'Fore', 'lokasi' => null, 'harga' => 2, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Indoor'],
['nama' => 'Fox', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 1, 'area' => 'Indoor'], ['nama' => 'Fox', 'lokasi' => null, 'harga' => 3, 'wifi' => 3, 'jam' => 4, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 1, 'area' => 'Indoor'],
['nama' => 'Perasa', 'lokasi' => null, 'harga' => 2, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Indoor'], ['nama' => 'Perasa', 'lokasi' => null, 'harga' => 2, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 3, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Indoor'],
['nama' => 'Kopi Boss', 'lokasi' => null, 'harga' => 5, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 1, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Semi Outdoor'], ['nama' => 'Kopi Boss', 'lokasi' => null, 'harga' => 5, 'wifi' => 3, 'jam' => 5, 'fotogenik' => 1, 'jarak' => 3, 'fasilitas' => 3, 'area' => 'Semi Outdoor'],
['nama' => 'Navas', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 5, 'fotogenik' => 2, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Semi Outdoor'], ['nama' => 'Navas', 'lokasi' => null, 'harga' => 3, 'wifi' => 4, 'jam' => 5, 'fotogenik' => 2, 'jarak' => 3, 'fasilitas' => 2, 'area' => 'Semi Outdoor'],
]; ];
// Get all categories and their subcategories // Get all categories and their subcategories
$categories = Category::all(); $categories = Category::all();
// Mendapatkan kategori berdasarkan namanya untuk memudahkan penugasan subcategory // Mendapatkan kategori berdasarkan namanya untuk memudahkan penugasan subcategory
$hargaCategory = Category::where('name', 'Harga')->first(); $hargaCategory = Category::where('name', 'Harga')->first();
$wifiCategory = Category::where('name', 'Kecepatan WiFi')->first(); $wifiCategory = Category::where('name', 'Kecepatan WiFi')->first();
$jamOperasionalCategory = Category::where('name', 'Jam Operasional')->first(); $jamOperasionalCategory = Category::where('name', 'Jam Operasional')->first();
$fotogenikCategory = Category::where('name', 'Fotogenik')->first(); $fotogenikCategory = Category::where('name', 'Fotogenik')->first();
$areaCategory = Category::where('name', 'Area (Outdoor/Indoor)')->first(); $areaCategory = Category::where('name', 'Area (Outdoor/Indoor)')->first();
$jarakCategory = Category::where('name', 'Jarak dengan Kampus')->first(); $jarakCategory = Category::where('name', 'Jarak dengan Kampus')->first();
$fasilitasCategory = Category::where('name', 'Fasilitas')->first(); $fasilitasCategory = Category::where('name', 'Fasilitas')->first();
// Membuat cafe dan ratings // Membuat cafe dan ratings
foreach ($cafes as $cafeData) { foreach ($cafes as $cafeData) {
// Buat cafe dengan data dasar // Buat cafe dengan data dasar
$cafe = Cafe::create([ $cafe = Cafe::create([
'nama' => $cafeData['nama'], 'nama' => $cafeData['nama'],
'lokasi' => $cafeData['lokasi'], 'lokasi' => $cafeData['lokasi'],
'area' => $cafeData['area'], 'area' => $cafeData['area'],
]); ]);
// Assign ratings berdasarkan data yang sudah ditentukan // Assign ratings berdasarkan data yang sudah ditentukan
// Harga // Harga
if ($hargaCategory) { if ($hargaCategory) {
$this->createRating($cafe->id, $hargaCategory->id, $cafeData['harga']); $this->createRating($cafe->id, $hargaCategory->id, $cafeData['harga']);
} }
// WiFi // WiFi
if ($wifiCategory) { if ($wifiCategory) {
$this->createRating($cafe->id, $wifiCategory->id, $cafeData['wifi']); $this->createRating($cafe->id, $wifiCategory->id, $cafeData['wifi']);
} }
// Jam Operasional // Jam Operasional
if ($jamOperasionalCategory) { if ($jamOperasionalCategory) {
$this->createRating($cafe->id, $jamOperasionalCategory->id, $cafeData['jam']); $this->createRating($cafe->id, $jamOperasionalCategory->id, $cafeData['jam']);
} }
// Fotogenik // Fotogenik
if ($fotogenikCategory) { if ($fotogenikCategory) {
$this->createRating($cafe->id, $fotogenikCategory->id, $cafeData['fotogenik']); $this->createRating($cafe->id, $fotogenikCategory->id, $cafeData['fotogenik']);
} }
// Jarak // Jarak
if ($jarakCategory) { if ($jarakCategory) {
$this->createRating($cafe->id, $jarakCategory->id, $cafeData['jarak']); $this->createRating($cafe->id, $jarakCategory->id, $cafeData['jarak']);
} }
// Fasilitas // Fasilitas
if ($fasilitasCategory) { if ($fasilitasCategory) {
$this->createRating($cafe->id, $fasilitasCategory->id, $cafeData['fasilitas']); $this->createRating($cafe->id, $fasilitasCategory->id, $cafeData['fasilitas']);
} }
} }
} }
/** /**
* Membuat rating berdasarkan category_id dan nilai * Membuat rating berdasarkan category_id dan nilai
*/ */
private function createRating($cafeId, $categoryId, $value) private function createRating($cafeId, $categoryId, $value)
{ {
// Cari subcategory yang sesuai dengan category_id dan value // Cari subcategory yang sesuai dengan category_id dan value
$subcategory = Subcategory::where('category_id', $categoryId) $subcategory = Subcategory::where('category_id', $categoryId)
->where('value', $value) ->where('value', $value)
->first(); ->first();
if ($subcategory) { if ($subcategory) {
CafeRating::create([ CafeRating::create([
'cafe_id' => $cafeId, 'cafe_id' => $cafeId,
'category_id' => $categoryId, 'category_id' => $categoryId,
'subcategory_id' => $subcategory->id 'subcategory_id' => $subcategory->id
]); ]);
} }
} }
} }

View File

@ -1,61 +1,61 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Cafe; use App\Models\Cafe;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class CafePriceSeeder extends Seeder class CafePriceSeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
// Data harga berdasarkan ID cafe yang sudah ada // Data harga berdasarkan ID cafe yang sudah ada
$priceData = [ $priceData = [
1 => ['harga_termurah' => 18000, 'harga_termahal' => 25000], // Poppins 1 => ['harga_termurah' => 18000, 'harga_termahal' => 25000], // Poppins
2 => ['harga_termurah' => 10000, 'harga_termahal' => 18000], // Teras JTI 2 => ['harga_termurah' => 10000, 'harga_termahal' => 18000], // Teras JTI
3 => ['harga_termurah' => 10000, 'harga_termahal' => 20000], // Nugas Jember 3 => ['harga_termurah' => 10000, 'harga_termahal' => 20000], // Nugas Jember
4 => ['harga_termurah' => 4000, 'harga_termahal' => 18000], // Kopi Kampus 4 => ['harga_termurah' => 4000, 'harga_termahal' => 18000], // Kopi Kampus
5 => ['harga_termurah' => 15000, 'harga_termahal' => 40000], // Eterno 5 => ['harga_termurah' => 15000, 'harga_termahal' => 40000], // Eterno
7 => ['harga_termurah' => 8000, 'harga_termahal' => 20000], // Fifty-Fifty 7 => ['harga_termurah' => 8000, 'harga_termahal' => 20000], // Fifty-Fifty
8 => ['harga_termurah' => 10000, 'harga_termahal' => 35000], // Discuss Space & Coffee 8 => ['harga_termurah' => 10000, 'harga_termahal' => 35000], // Discuss Space & Coffee
9 => ['harga_termurah' => 12000, 'harga_termahal' => 34000], // Subur 9 => ['harga_termurah' => 12000, 'harga_termahal' => 34000], // Subur
10 => ['harga_termurah' => 6000, 'harga_termahal' => 15000], // Nuansa 10 => ['harga_termurah' => 6000, 'harga_termahal' => 15000], // Nuansa
11 => ['harga_termurah' => 6000, 'harga_termahal' => 8000], // Nol Kilometer 11 => ['harga_termurah' => 6000, 'harga_termahal' => 8000], // Nol Kilometer
13 => ['harga_termurah' => 6000, 'harga_termahal' => 25000], // Wafa 13 => ['harga_termurah' => 6000, 'harga_termahal' => 25000], // Wafa
14 => ['harga_termurah' => 15000, 'harga_termahal' => 45000], // 888 14 => ['harga_termurah' => 15000, 'harga_termahal' => 45000], // 888
15 => ['harga_termurah' => 7000, 'harga_termahal' => 25000], // Sorai 15 => ['harga_termurah' => 7000, 'harga_termahal' => 25000], // Sorai
16 => ['harga_termurah' => 8000, 'harga_termahal' => 23000], // Tharuh 16 => ['harga_termurah' => 8000, 'harga_termahal' => 23000], // Tharuh
17 => ['harga_termurah' => 12000, 'harga_termahal' => 38000], // Contact 17 => ['harga_termurah' => 12000, 'harga_termahal' => 38000], // Contact
18 => ['harga_termurah' => 14000, 'harga_termahal' => 20000], // Cus Cus 18 => ['harga_termurah' => 14000, 'harga_termahal' => 20000], // Cus Cus
19 => ['harga_termurah' => 18000, 'harga_termahal' => 34000], // Grufi 19 => ['harga_termurah' => 18000, 'harga_termahal' => 34000], // Grufi
20 => ['harga_termurah' => 15000, 'harga_termahal' => 25000], // Tomoro 20 => ['harga_termurah' => 15000, 'harga_termahal' => 25000], // Tomoro
21 => ['harga_termurah' => 22000, 'harga_termahal' => 40000], // Fore 21 => ['harga_termurah' => 22000, 'harga_termahal' => 40000], // Fore
22 => ['harga_termurah' => 1500, 'harga_termahal' => 37000], // Fox 22 => ['harga_termurah' => 1500, 'harga_termahal' => 37000], // Fox
23 => ['harga_termurah' => 12000, 'harga_termahal' => 45000], // Perasa 23 => ['harga_termurah' => 12000, 'harga_termahal' => 45000], // Perasa
24 => ['harga_termurah' => 5000, 'harga_termahal' => 15000], // Kopi Boss 24 => ['harga_termurah' => 5000, 'harga_termahal' => 15000], // Kopi Boss
25 => ['harga_termurah' => 10000, 'harga_termahal' => 30000], // Navas 25 => ['harga_termurah' => 10000, 'harga_termahal' => 30000], // Navas
26 => ['harga_termurah' => 15000, 'harga_termahal' => 35000], // Kattappa (ID 26) 26 => ['harga_termurah' => 15000, 'harga_termahal' => 35000], // Kattappa (ID 26)
28 => ['harga_termurah' => 17000, 'harga_termahal' => 47000], // Tanaloka (ID 28) 28 => ['harga_termurah' => 17000, 'harga_termahal' => 47000], // Tanaloka (ID 28)
]; ];
// Update harga untuk setiap cafe // Update harga untuk setiap cafe
foreach ($priceData as $cafeId => $prices) { foreach ($priceData as $cafeId => $prices) {
$cafe = Cafe::find($cafeId); $cafe = Cafe::find($cafeId);
if ($cafe) { if ($cafe) {
$cafe->update([ $cafe->update([
'harga_termurah' => $prices['harga_termurah'], 'harga_termurah' => $prices['harga_termurah'],
'harga_termahal' => $prices['harga_termahal'] 'harga_termahal' => $prices['harga_termahal']
]); ]);
echo "Updated cafe ID {$cafeId} ({$cafe->nama}): {$prices['harga_termurah']} - {$prices['harga_termahal']}\n"; echo "Updated cafe ID {$cafeId} ({$cafe->nama}): {$prices['harga_termurah']} - {$prices['harga_termahal']}\n";
} else { } else {
echo "Cafe with ID {$cafeId} not found\n"; echo "Cafe with ID {$cafeId} not found\n";
} }
} }
} }
} }

View File

@ -1,236 +1,236 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Category; use App\Models\Category;
use App\Models\Subcategory; use App\Models\Subcategory;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class CategoryAndSubcategorySeeder extends Seeder class CategoryAndSubcategorySeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
// Hapus data yang sudah ada untuk menghindari duplikasi // Hapus data yang sudah ada untuk menghindari duplikasi
DB::statement('SET FOREIGN_KEY_CHECKS=0'); DB::statement('SET FOREIGN_KEY_CHECKS=0');
Subcategory::truncate(); Subcategory::truncate();
Category::truncate(); Category::truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1'); DB::statement('SET FOREIGN_KEY_CHECKS=1');
// Kategori 1: Harga // Kategori 1: Harga
$harga = Category::create([ $harga = Category::create([
'name' => 'Harga' 'name' => 'Harga'
]); ]);
// Subkategori Harga // Subkategori Harga
Subcategory::create([ Subcategory::create([
'category_id' => $harga->id, 'category_id' => $harga->id,
'name' => 'Sangat Murah (<10k/porsi)', 'name' => 'Sangat Murah (<10k/porsi)',
'value' => 5 'value' => 5
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $harga->id, 'category_id' => $harga->id,
'name' => 'Murah (10k15k)', 'name' => 'Murah (10k15k)',
'value' => 4 'value' => 4
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $harga->id, 'category_id' => $harga->id,
'name' => 'Sedang (15k25k)', 'name' => 'Sedang (15k25k)',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $harga->id, 'category_id' => $harga->id,
'name' => 'Agak Mahal (25k35k)', 'name' => 'Agak Mahal (25k35k)',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $harga->id, 'category_id' => $harga->id,
'name' => 'Mahal (>35k)', 'name' => 'Mahal (>35k)',
'value' => 1 'value' => 1
]); ]);
// Kategori 2: Kecepatan WiFi // Kategori 2: Kecepatan WiFi
$wifi = Category::create([ $wifi = Category::create([
'name' => 'Kecepatan WiFi' 'name' => 'Kecepatan WiFi'
]); ]);
// Subkategori Kecepatan WiFi // Subkategori Kecepatan WiFi
Subcategory::create([ Subcategory::create([
'category_id' => $wifi->id, 'category_id' => $wifi->id,
'name' => 'Sangat Cepat (>50 Mbps)', 'name' => 'Sangat Cepat (>50 Mbps)',
'value' => 5 'value' => 5
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $wifi->id, 'category_id' => $wifi->id,
'name' => 'Cepat (3050 Mbps)', 'name' => 'Cepat (3050 Mbps)',
'value' => 4 'value' => 4
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $wifi->id, 'category_id' => $wifi->id,
'name' => 'Sedang (1530 Mbps)', 'name' => 'Sedang (1530 Mbps)',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $wifi->id, 'category_id' => $wifi->id,
'name' => 'Lambat (515 Mbps)', 'name' => 'Lambat (515 Mbps)',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $wifi->id, 'category_id' => $wifi->id,
'name' => 'Sangat Lambat (<5 Mbps)', 'name' => 'Sangat Lambat (<5 Mbps)',
'value' => 1 'value' => 1
]); ]);
// Kategori 3: Jam Operasional // Kategori 3: Jam Operasional
$jamOperasional = Category::create([ $jamOperasional = Category::create([
'name' => 'Jam Operasional' 'name' => 'Jam Operasional'
]); ]);
// Subkategori Jam Operasional // Subkategori Jam Operasional
Subcategory::create([ Subcategory::create([
'category_id' => $jamOperasional->id, 'category_id' => $jamOperasional->id,
'name' => '24 Jam', 'name' => '24 Jam',
'value' => 5 'value' => 5
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jamOperasional->id, 'category_id' => $jamOperasional->id,
'name' => '08.0000.00 (16 jam)', 'name' => '08.0000.00 (16 jam)',
'value' => 4 'value' => 4
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jamOperasional->id, 'category_id' => $jamOperasional->id,
'name' => '10.0022.00 (12 jam)', 'name' => '10.0022.00 (12 jam)',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jamOperasional->id, 'category_id' => $jamOperasional->id,
'name' => '<10 jam operasional', 'name' => '<10 jam operasional',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jamOperasional->id, 'category_id' => $jamOperasional->id,
'name' => 'Tidak konsisten / Tidak tetap', 'name' => 'Tidak konsisten / Tidak tetap',
'value' => 1 'value' => 1
]); ]);
// Kategori 4: Fotogenik // Kategori 4: Fotogenik
$fotogenik = Category::create([ $fotogenik = Category::create([
'name' => 'Fotogenik' 'name' => 'Fotogenik'
]); ]);
// Subkategori Fotogenik // Subkategori Fotogenik
Subcategory::create([ Subcategory::create([
'category_id' => $fotogenik->id, 'category_id' => $fotogenik->id,
'name' => 'Sangat Fotogenik (Desain unik, banyak spot foto)', 'name' => 'Sangat Fotogenik (Desain unik, banyak spot foto)',
'value' => 5 'value' => 5
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fotogenik->id, 'category_id' => $fotogenik->id,
'name' => 'Fotogenik (Desain bagus, cukup spot foto)', 'name' => 'Fotogenik (Desain bagus, cukup spot foto)',
'value' => 4 'value' => 4
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fotogenik->id, 'category_id' => $fotogenik->id,
'name' => 'Cukup Fotogenik (estetis biasa saja)', 'name' => 'Cukup Fotogenik (estetis biasa saja)',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fotogenik->id, 'category_id' => $fotogenik->id,
'name' => 'Kurang Fotogenik', 'name' => 'Kurang Fotogenik',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fotogenik->id, 'category_id' => $fotogenik->id,
'name' => 'Tidak Fotogenik', 'name' => 'Tidak Fotogenik',
'value' => 1 'value' => 1
]); ]);
// Kategori 5: Area (Luas Tempat) // Kategori 5: Area (Luas Tempat)
$area = Category::create([ $area = Category::create([
'name' => 'Area (Outdoor/Indoor)' 'name' => 'Area (Outdoor/Indoor)'
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $area->id, 'category_id' => $area->id,
'name' => 'Outdoor & Indoor', 'name' => 'Outdoor & Indoor',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $area->id, 'category_id' => $area->id,
'name' => 'Indoor', 'name' => 'Indoor',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $area->id, 'category_id' => $area->id,
'name' => 'Outdoor', 'name' => 'Outdoor',
'value' => 1 'value' => 1
]); ]);
// Kategori 6: Jarak dengan Kampus // Kategori 6: Jarak dengan Kampus
$jarak = Category::create([ $jarak = Category::create([
'name' => 'Jarak dengan Kampus' 'name' => 'Jarak dengan Kampus'
]); ]);
// Subkategori Jarak dengan Kampus // Subkategori Jarak dengan Kampus
Subcategory::create([ Subcategory::create([
'category_id' => $jarak->id, 'category_id' => $jarak->id,
'name' => 'Sangat Dekat (≤1 Km)', 'name' => 'Sangat Dekat (≤1 Km)',
'value' => 5 'value' => 5
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jarak->id, 'category_id' => $jarak->id,
'name' => 'Dekat (12 Km)', 'name' => 'Dekat (12 Km)',
'value' => 4 'value' => 4
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jarak->id, 'category_id' => $jarak->id,
'name' => 'Sedang (25 Km)', 'name' => 'Sedang (25 Km)',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jarak->id, 'category_id' => $jarak->id,
'name' => 'Jauh (510 Km)', 'name' => 'Jauh (510 Km)',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $jarak->id, 'category_id' => $jarak->id,
'name' => 'Sangat Jauh (>10 Km)', 'name' => 'Sangat Jauh (>10 Km)',
'value' => 1 'value' => 1
]); ]);
// Kategori 7: Fasilitas // Kategori 7: Fasilitas
$fasilitas = Category::create([ $fasilitas = Category::create([
'name' => 'Fasilitas' 'name' => 'Fasilitas'
]); ]);
// Subkategori Fasilitas // Subkategori Fasilitas
Subcategory::create([ Subcategory::create([
'category_id' => $fasilitas->id, 'category_id' => $fasilitas->id,
'name' => 'Sangat Lengkap (5 fasilitas)', 'name' => 'Sangat Lengkap (5 fasilitas)',
'value' => 5 'value' => 5
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fasilitas->id, 'category_id' => $fasilitas->id,
'name' => 'Lengkap (4 fasilitas)', 'name' => 'Lengkap (4 fasilitas)',
'value' => 4 'value' => 4
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fasilitas->id, 'category_id' => $fasilitas->id,
'name' => 'Cukup (3 fasilitas)', 'name' => 'Cukup (3 fasilitas)',
'value' => 3 'value' => 3
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fasilitas->id, 'category_id' => $fasilitas->id,
'name' => 'Kurang (2 fasilitas)', 'name' => 'Kurang (2 fasilitas)',
'value' => 2 'value' => 2
]); ]);
Subcategory::create([ Subcategory::create([
'category_id' => $fasilitas->id, 'category_id' => $fasilitas->id,
'name' => 'Minim / Tidak Lengkap (01 fasilitas)', 'name' => 'Minim / Tidak Lengkap (01 fasilitas)',
'value' => 1 'value' => 1
]); ]);
} }
} }

View File

@ -1,87 +1,87 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class CategorySeeder extends Seeder class CategorySeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
// Hapus data yang ada terlebih dahulu // Hapus data yang ada terlebih dahulu
DB::table('categories')->truncate(); DB::table('categories')->truncate();
// Data kategori dengan subkategori // Data kategori dengan subkategori
$categories = [ $categories = [
[ [
'name' => 'Jarak Kampus', 'name' => 'Jarak Kampus',
'subcategory1' => 'Sangat Dekat', 'subcategory1' => 'Sangat Dekat',
'subcategory2' => 'Dekat', 'subcategory2' => 'Dekat',
'subcategory3' => 'Sedang', 'subcategory3' => 'Sedang',
'subcategory4' => 'Jauh', 'subcategory4' => 'Jauh',
'subcategory5' => 'Sangat Jauh', 'subcategory5' => 'Sangat Jauh',
'value1' => 5, 'value1' => 5,
'value2' => 4, 'value2' => 4,
'value3' => 3, 'value3' => 3,
'value4' => 2, 'value4' => 2,
'value5' => 1, 'value5' => 1,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now() 'updated_at' => now()
], ],
[ [
'name' => 'Kisaran Harga', 'name' => 'Kisaran Harga',
'subcategory1' => 'Sangat Murah', 'subcategory1' => 'Sangat Murah',
'subcategory2' => 'Murah', 'subcategory2' => 'Murah',
'subcategory3' => 'Sedang', 'subcategory3' => 'Sedang',
'subcategory4' => 'Mahal', 'subcategory4' => 'Mahal',
'subcategory5' => 'Sangat Mahal', 'subcategory5' => 'Sangat Mahal',
'value1' => 5, 'value1' => 5,
'value2' => 4, 'value2' => 4,
'value3' => 3, 'value3' => 3,
'value4' => 2, 'value4' => 2,
'value5' => 1, 'value5' => 1,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now() 'updated_at' => now()
], ],
[ [
'name' => 'Fasilitas', 'name' => 'Fasilitas',
'subcategory1' => 'Sangat Lengkap', 'subcategory1' => 'Sangat Lengkap',
'subcategory2' => 'Lengkap', 'subcategory2' => 'Lengkap',
'subcategory3' => 'Cukup', 'subcategory3' => 'Cukup',
'subcategory4' => 'Kurang', 'subcategory4' => 'Kurang',
'subcategory5' => 'Sangat Kurang', 'subcategory5' => 'Sangat Kurang',
'value1' => 5, 'value1' => 5,
'value2' => 4, 'value2' => 4,
'value3' => 3, 'value3' => 3,
'value4' => 2, 'value4' => 2,
'value5' => 1, 'value5' => 1,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now() 'updated_at' => now()
], ],
[ [
'name' => 'Kecepatan WiFi', 'name' => 'Kecepatan WiFi',
'subcategory1' => 'Sangat Cepat', 'subcategory1' => 'Sangat Cepat',
'subcategory2' => 'Cepat', 'subcategory2' => 'Cepat',
'subcategory3' => 'Sedang', 'subcategory3' => 'Sedang',
'subcategory4' => 'Lambat', 'subcategory4' => 'Lambat',
'subcategory5' => 'Sangat Lambat', 'subcategory5' => 'Sangat Lambat',
'value1' => 5, 'value1' => 5,
'value2' => 4, 'value2' => 4,
'value3' => 3, 'value3' => 3,
'value4' => 2, 'value4' => 2,
'value5' => 1, 'value5' => 1,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now() 'updated_at' => now()
] ]
]; ];
// Insert data kategori // Insert data kategori
DB::table('categories')->insert($categories); DB::table('categories')->insert($categories);
$this->command->info('Categories seeded successfully!'); $this->command->info('Categories seeded successfully!');
} }
} }

View File

@ -1,66 +1,66 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use App\Models\Subcategory; use App\Models\Subcategory;
use App\Models\Category; use App\Models\Category;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class SubcategorySeeder extends Seeder class SubcategorySeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
// Hapus data yang ada terlebih dahulu // Hapus data yang ada terlebih dahulu
DB::table('subcategories')->truncate(); DB::table('subcategories')->truncate();
// Data subkategori berdasarkan kategori // Data subkategori berdasarkan kategori
$subcategories = [ $subcategories = [
'Jarak Kampus' => [ 'Jarak Kampus' => [
'Sangat Dekat (< 1 km)', 'Sangat Dekat (< 1 km)',
'Dekat (1-2 km)', 'Dekat (1-2 km)',
'Sedang (2-3 km)', 'Sedang (2-3 km)',
'Jauh (> 3 km)' 'Jauh (> 3 km)'
], ],
'Kisaran Harga' => [ 'Kisaran Harga' => [
'Sangat Murah (< Rp 10.000)', 'Sangat Murah (< Rp 10.000)',
'Murah (Rp 10.000 - Rp 20.000)', 'Murah (Rp 10.000 - Rp 20.000)',
'Sedang (Rp 20.000 - Rp 30.000)', 'Sedang (Rp 20.000 - Rp 30.000)',
'Mahal (> Rp 30.000)' 'Mahal (> Rp 30.000)'
], ],
'Fasilitas' => [ 'Fasilitas' => [
'Sangat Lengkap', 'Sangat Lengkap',
'Lengkap', 'Lengkap',
'Cukup', 'Cukup',
'Minimal' 'Minimal'
], ],
'Kecepatan WiFi' => [ 'Kecepatan WiFi' => [
'Sangat Cepat (> 50 Mbps)', 'Sangat Cepat (> 50 Mbps)',
'Cepat (30-50 Mbps)', 'Cepat (30-50 Mbps)',
'Sedang (10-30 Mbps)', 'Sedang (10-30 Mbps)',
'Lambat (< 10 Mbps)' 'Lambat (< 10 Mbps)'
] ]
]; ];
// Insert data subkategori // Insert data subkategori
foreach ($subcategories as $categoryName => $items) { foreach ($subcategories as $categoryName => $items) {
$category = Category::where('name', $categoryName)->first(); $category = Category::where('name', $categoryName)->first();
if ($category) { if ($category) {
foreach ($items as $subcategoryName) { foreach ($items as $subcategoryName) {
Subcategory::create([ Subcategory::create([
'category_id' => $category->id, 'category_id' => $category->id,
'name' => $subcategoryName, 'name' => $subcategoryName,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now(), 'updated_at' => now(),
]); ]);
} }
} }
} }
$this->command->info('Subcategories seeded successfully!'); $this->command->info('Subcategories seeded successfully!');
} }
} }

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" zoomAndPan="magnify" viewBox="0 0 750 749.999995" height="1000" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="b632305565"><path d="M 104.71875 26.34375 L 645.46875 26.34375 L 645.46875 723.84375 L 104.71875 723.84375 Z M 104.71875 26.34375 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#b632305565)"><path fill="#000000" d="M 645.464844 671.160156 C 645.464844 485.035156 524.316406 334.242188 375.09375 334.242188 C 225.867188 334.242188 104.71875 485.035156 104.71875 671.160156 C 104.71875 700.183594 225.867188 723.84375 375.09375 723.84375 C 524.316406 723.84375 645.464844 700.183594 645.464844 671.160156 Z M 375.09375 26.34375 C 456.488281 26.34375 522.425781 92.277344 522.425781 173.667969 C 522.425781 255.058594 456.488281 320.992188 375.09375 320.992188 C 293.695312 320.992188 227.757812 255.058594 227.757812 173.667969 C 227.757812 92.277344 293.695312 26.34375 375.09375 26.34375 Z M 375.09375 26.34375 " fill-opacity="1" fill-rule="evenodd"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,9 @@
/* Ini akan mencegah papan ketikan (keyboard) muncul pada elemen yang bukan input */
body:not(:focus-within) {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

0
public/favicon.ico Normal file
View File

BIN
public/images/layers-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/images/layers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

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());

252
public/js/cafe-search.js Normal file
View File

@ -0,0 +1,252 @@
// Inisialisasi variabel global
let hasSearchResults = false;
let currentStep = 1;
// Global data untuk menyimpan nilai penting setiap kriteria
const importanceValues = {};
// Label untuk tingkat kepentingan
const importanceLabels = {
1: "Sangat tidak penting (1)",
2: "Tidak penting (2)",
3: "Kurang penting (3)",
4: "Agak penting (4)",
5: "Sedang (5)",
6: "Cukup penting (6)",
7: "Penting (7)",
8: "Sangat penting (8)",
9: "Sangat penting sekali (9)",
10: "Paling penting (10)"
};
// Fungsi untuk update nilai slider yang dipanggil langsung dari oninput HTML
function updateSliderValue(id, value) {
// Update nilai penting di object global
importanceValues[id] = parseInt(value);
// Update tampilan label tingkat kepentingan
const importanceDisplay = document.getElementById(`importance_value_${id}`);
if (importanceDisplay) {
importanceDisplay.textContent = importanceLabels[value];
}
// Hitung ulang bobot dan perbarui tampilan
calculateAndUpdateWeights();
}
// Fungsi untuk menghitung bobot berdasarkan tingkat kepentingan
function calculateAndUpdateWeights() {
let totalImportance = 0;
Object.values(importanceValues).forEach(value => {
totalImportance += value;
});
// Hitung bobot berdasarkan proporsi kepentingan
const weights = {};
let totalWeight = 0;
if (totalImportance > 0) {
// Hitung berdasarkan proporsi
Object.keys(importanceValues).forEach(id => {
weights[id] = Math.floor((importanceValues[id] / totalImportance) * 100);
totalWeight += weights[id];
});
// Distribusikan sisa persen
if (totalWeight < 100) {
// Urutkan ID berdasarkan tingkat kepentingan (tertinggi dulu)
const sortedIds = Object.keys(importanceValues).sort((a, b) =>
importanceValues[b] - importanceValues[a]
);
// Distribusikan sisa ke nilai tertinggi
let remainder = 100 - totalWeight;
for (let i = 0; i < remainder; i++) {
weights[sortedIds[i % sortedIds.length]]++;
}
}
} else {
// Distribusi merata jika semua nilai penting adalah 0
const equalWeight = Math.floor(100 / Object.keys(importanceValues).length);
let remainder = 100 - (equalWeight * Object.keys(importanceValues).length);
Object.keys(importanceValues).forEach((id, index) => {
weights[id] = equalWeight + (index < remainder ? 1 : 0);
});
}
// Update tampilan bobot dan input tersembunyi
Object.keys(weights).forEach(id => {
const weightDisplay = document.getElementById(`weight_display_${id}`);
const hiddenInput = document.getElementById(`hidden_weight_${id}`);
if (weightDisplay) {
weightDisplay.textContent = `${weights[id]}%`;
// Terapkan kode warna berdasarkan nilai bobot
if (weights[id] >= 30) {
weightDisplay.className = 'text-sm font-medium text-green-600 dark:text-green-400';
} else if (weights[id] >= 15) {
weightDisplay.className = 'text-sm font-medium text-blue-600 dark:text-blue-400';
} else {
weightDisplay.className = 'text-sm font-medium text-gray-700 dark:text-gray-300';
}
}
if (hiddenInput) {
hiddenInput.value = weights[id];
}
});
// Update indikator total bobot
const totalWeightIndicator = document.getElementById('total_weight_indicator');
if (totalWeightIndicator) {
const calculatedTotal = Object.values(weights).reduce((sum, w) => sum + w, 0);
totalWeightIndicator.textContent = `Total: ${calculatedTotal}%`;
if (calculatedTotal === 100) {
totalWeightIndicator.classList.remove('bg-red-100', 'text-red-800', 'dark:bg-red-900', 'dark:text-red-300');
totalWeightIndicator.classList.add('bg-green-100', 'text-green-800', 'dark:bg-green-900', 'dark:text-green-300');
} else {
totalWeightIndicator.classList.remove('bg-green-100', 'text-green-800', 'dark:bg-green-900', 'dark:text-green-300');
totalWeightIndicator.classList.add('bg-red-100', 'text-red-800', 'dark:bg-red-900', 'dark:text-red-300');
}
}
return Object.values(weights).reduce((sum, w) => sum + w, 0) === 100;
}
// Fungsi untuk cek apakah halaman ini hasil dari submit form
function checkIfSearchResults() {
return hasSearchResults;
}
// Update fungsi showSection untuk memanggil updateStepProgress
function showSection1() {
document.getElementById('section1').style.display = 'block';
document.getElementById('section2').style.display = 'none';
document.getElementById('section3').style.display = 'none';
updateStepperVisually(1);
window.scrollTo(0, 0);
}
function showSection2() {
document.getElementById('section1').style.display = 'none';
document.getElementById('section2').style.display = 'block';
document.getElementById('section3').style.display = 'none';
updateStepperVisually(2);
window.scrollTo(0, 0);
}
function showSection3() {
document.getElementById('section1').style.display = 'none';
document.getElementById('section2').style.display = 'none';
document.getElementById('section3').style.display = 'block';
updateStepperVisually(3);
window.scrollTo(0, 0);
}
// Fungsi untuk memperbarui tampilan stepper
function updateStepperVisually(step) {
// Ambil semua step-item dari stepper
const stepItems = document.querySelectorAll('.step-item');
stepItems.forEach((item, index) => {
const stepNum = index + 1;
const circleDiv = item.querySelector('.w-10');
const textSpan = item.querySelector('.text-sm');
if (stepNum <= step) {
// Langkah aktif atau sudah dilalui
circleDiv.classList.remove('bg-gray-200', 'dark:bg-gray-700', 'text-gray-600', 'dark:text-gray-400');
circleDiv.classList.add('bg-blue-600', 'text-white');
textSpan.classList.remove('text-gray-500', 'dark:text-gray-400');
textSpan.classList.add('text-blue-600', 'dark:text-blue-400');
} else {
// Langkah yang belum aktif
circleDiv.classList.remove('bg-blue-600', 'text-white');
circleDiv.classList.add('bg-gray-200', 'dark:bg-gray-700', 'text-gray-600', 'dark:text-gray-400');
textSpan.classList.remove('text-blue-600', 'dark:text-blue-400');
textSpan.classList.add('text-gray-500', 'dark:text-gray-400');
}
// Update status aktif di class
if (stepNum <= step) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
// Fungsi inisialisasi yang akan dipanggil ketika DOM selesai dimuat
function initializeApp() {
try {
console.log('DOM Content Loaded.');
// Ambil data dari atribut script
const scriptTag = document.querySelector('script[src*="cafe-search.js"]');
if (scriptTag) {
hasSearchResults = scriptTag.getAttribute('data-has-search-results') === 'true';
currentStep = parseInt(scriptTag.getAttribute('data-current-step') || '1');
}
// Inisialisasi nilai penting dan bobot dari URL jika ada
const sliders = document.querySelectorAll('.importance-slider');
sliders.forEach(slider => {
const id = slider.dataset.id;
const requestWeight = slider.dataset.requestWeight;
// Jika ada bobot dari request
if (requestWeight) {
// Cari nilai importance yang sesuai dengan bobot
for (let i = 1; i <= 10; i++) {
importanceValues[id] = i;
calculateAndUpdateWeights();
const weight = document.getElementById(`hidden_weight_${id}`).value;
if (parseInt(weight) === parseInt(requestWeight)) {
slider.value = i;
updateSliderValue(id, i);
break;
}
}
} else {
// Default nilai awal
const defaultValue = slider.value;
importanceValues[id] = parseInt(defaultValue);
}
});
// Update semua bobot
calculateAndUpdateWeights();
// Tampilkan section sesuai langkah
if (hasSearchResults) {
showSection3();
} else if (currentStep === 2) {
showSection2();
} else {
showSection1();
}
// Form submission validation - ensure weights sum to 100%
const smartForm = document.getElementById('smartForm');
if (smartForm) {
smartForm.addEventListener('submit', function(event) {
if (!calculateAndUpdateWeights()) {
event.preventDefault();
alert('Total bobot harus 100%. Silakan sesuaikan tingkat kepentingan.');
showSection1();
}
});
}
} catch (error) {
console.error('Terjadi error saat inisialisasi halaman:', error);
}
}
// Inisialisasi saat DOM selesai dimuat
document.addEventListener('DOMContentLoaded', initializeApp);

View File

@ -0,0 +1,23 @@
// Mencegah papan ketikan (keyboard) muncul saat pengguna menyentuh area yang bukan input/textarea
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('touchstart', function(e) {
if (e.target.tagName !== 'INPUT' &&
e.target.tagName !== 'TEXTAREA' &&
!e.target.isContentEditable) {
e.preventDefault();
}
}, { passive: false });
// Menambahkan atribut readonly ke elemen date input untuk mencegah keyboard muncul
const dateInputs = document.querySelectorAll('input[type="date"]');
dateInputs.forEach(input => {
input.setAttribute('readonly', 'readonly');
// Tambahkan event listener untuk menghapus readonly saat diklik
input.addEventListener('mousedown', function() {
this.removeAttribute('readonly');
});
input.addEventListener('blur', function() {
this.setAttribute('readonly', 'readonly');
});
});
});

80
public/js/leaflet-init.js Normal file
View File

@ -0,0 +1,80 @@
// Inisialisasi Leaflet Map untuk halaman tambah kafe
document.addEventListener('DOMContentLoaded', function() {
// Pastikan elemen peta ada
const mapElement = document.getElementById('map');
if (!mapElement) {
console.error('Elemen peta tidak ditemukan!');
return;
}
console.log('Menginisialisasi peta Leaflet...');
// Koordinat default (Alun-Alun Jember)
const defaultLat = -8.1722;
const defaultLng = 113.6982;
try {
// Inisialisasi peta
const mymap = L.map('map').setView([defaultLat, defaultLng], 13);
// Tambahkan layer peta
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(mymap);
// Inisialisasi marker
let marker;
// Fungsi untuk menambahkan marker
function addMarker(lat, lng) {
// Hapus marker sebelumnya jika ada
if (marker) {
mymap.removeLayer(marker);
}
// Tambahkan marker baru
marker = L.marker([lat, lng]).addTo(mymap);
// Update formulir
document.getElementById('latitude').value = lat;
document.getElementById('longitude').value = lng;
document.getElementById('koordinat').value = lat + ', ' + lng;
}
// Event listener untuk klik pada peta
mymap.on('click', function(e) {
console.log('Peta diklik pada:', e.latlng);
addMarker(e.latlng.lat, e.latlng.lng);
});
// Event listener untuk input koordinat manual
const koordinatInput = document.getElementById('koordinat');
if (koordinatInput) {
koordinatInput.addEventListener('change', function() {
const koordinat = this.value.split(',');
if (koordinat.length === 2) {
const lat = parseFloat(koordinat[0].trim());
const lng = parseFloat(koordinat[1].trim());
if (!isNaN(lat) && !isNaN(lng)) {
console.log('Mengubah view peta ke koordinat:', lat, lng);
mymap.setView([lat, lng], 13);
addMarker(lat, lng);
}
}
});
}
// Perbaiki ukuran peta
setTimeout(function() {
mymap.invalidateSize();
console.log('Ukuran peta diperbaiki');
}, 100);
console.log('Peta berhasil diinisialisasi');
} catch (error) {
console.error('Terjadi kesalahan saat menginisialisasi peta:', error);
mapElement.innerHTML = '<div style="text-align: center; padding: 20px; color: red;">Terjadi kesalahan saat memuat peta: ' + error.message + '</div>';
}
});

View File

@ -0,0 +1 @@
.leaflet-control-geocoder{border-radius:4px;background:#fff;min-width:26px;min-height:26px}.leaflet-touch .leaflet-control-geocoder{min-width:30px;min-height:30px}.leaflet-control-geocoder a,.leaflet-control-geocoder .leaflet-control-geocoder-icon{border-bottom:none;display:inline-block}.leaflet-control-geocoder .leaflet-control-geocoder-alternatives a{width:inherit;height:inherit;line-height:inherit}.leaflet-control-geocoder a:hover,.leaflet-control-geocoder .leaflet-control-geocoder-icon:hover{border-bottom:none;display:inline-block}.leaflet-control-geocoder-form{display:none;vertical-align:middle}.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form{display:inline-block}.leaflet-control-geocoder-form input{font-size:120%;border:0;background-color:transparent;width:246px}.leaflet-control-geocoder-icon{border-radius:4px;width:26px;height:26px;border:none;background-color:#fff;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.2 13l3.4 6.6c.6 1.1 2.5-.4 2-1.2l-4-6.2z'/%3E%3Ccircle cx='10.8' cy='8.9' r='3.9' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:center;cursor:pointer}.leaflet-touch .leaflet-control-geocoder-icon{width:30px;height:30px}.leaflet-control-geocoder-throbber .leaflet-control-geocoder-icon{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23000' stroke-linecap='round' stroke-width='1.6' viewBox='0 0 24 24'%3E%3Cdefs/%3E%3Cg%3E%3Cpath stroke-opacity='.1' d='M14 8.4l3-5'/%3E%3Cpath stroke-opacity='.2' d='M15.6 10l5-3'/%3E%3Cpath stroke-opacity='.3' d='M16.2 12H22'/%3E%3Cpath stroke-opacity='.4' d='M15.6 14l5 3m-6.5-1.4l2.9 5'/%3E%3Cpath stroke-opacity='.5' d='M12 16.2V22m-2-6.4l-3 5'/%3E%3Cpath stroke-opacity='.6' d='M8.4 14l-5 3'/%3E%3Cpath stroke-opacity='.7' d='M7.8 12H2'/%3E%3Cpath stroke-opacity='.8' d='M8.4 10l-5-3'/%3E%3Cpath stroke-opacity='.9' d='M10 8.4l-3-5'/%3E%3Cpath d='M12 7.8V2'/%3E%3CanimateTransform attributeName='transform' calcMode='discrete' dur='1s' repeatCount='indefinite' type='rotate' values='0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12'/%3E%3C/g%3E%3C/svg%3E")}.leaflet-control-geocoder-form-no-error{display:none}.leaflet-control-geocoder-form input:focus{outline:none}.leaflet-control-geocoder-form button{display:none}.leaflet-control-geocoder-error{margin-top:8px;margin-left:8px;display:block;color:#444}.leaflet-control-geocoder-alternatives{display:block;width:272px;list-style:none;padding:0;margin:0}.leaflet-control-geocoder-alternatives-minimized{display:none;height:0}.leaflet-control-geocoder-alternatives li{white-space:nowrap;display:block;overflow:hidden;padding:5px 8px;text-overflow:ellipsis;border-bottom:1px solid #ccc;cursor:pointer}.leaflet-control-geocoder-alternatives li a,.leaflet-control-geocoder-alternatives li a:hover{width:inherit;height:inherit;line-height:inherit;background:inherit;border-radius:inherit;text-align:left}.leaflet-control-geocoder-alternatives li:last-child{border-bottom:none}.leaflet-control-geocoder-alternatives li:hover,.leaflet-control-geocoder-selected{background-color:#f5f5f5}.leaflet-control-geocoder-address-context{color:#666}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,161 @@
import * as L from 'leaflet';
import { IGeocoder, GeocodingResult } from './geocoders/api';
export interface GeocoderControlOptions extends L.ControlOptions {
/**
* Collapse control unless hovered/clicked
*/
collapsed: boolean;
/**
* How to expand a collapsed control: `touch` or `click` or `hover`
*/
expand: 'touch' | 'click' | 'hover';
/**
* Placeholder text for text input
*/
placeholder: string;
/**
* Message when no result found / geocoding error occurs
*/
errorMessage: string;
/**
* Accessibility label for the search icon used by screen readers
*/
iconLabel: string;
/**
* Object to perform the actual geocoding queries
*/
geocoder?: IGeocoder;
/**
* Immediately show the unique result without prompting for alternatives
*/
showUniqueResult: boolean;
/**
* Show icons for geocoding results (if available); supported by Nominatim
*/
showResultIcons: boolean;
/**
* Minimum number characters before suggest functionality is used (if available from geocoder)
*/
suggestMinLength: number;
/**
* Number of milliseconds after typing stopped before suggest functionality is used (if available from geocoder)
*/
suggestTimeout: number;
/**
* Initial query string for text input
*/
query: string;
/**
* Minimum number of characters in search text before performing a query
*/
queryMinLength: number;
/**
* Whether to mark a geocoding result on the map by default
*/
defaultMarkGeocode: boolean;
}
/**
* Event is fired when selecting a geocode result.
* By default, the control will center the map on it and place a marker at its location.
* To remove the control's default handler for marking a result, set {@link GeocoderControlOptions.defaultMarkGeocode} to `false`.
*/
export type MarkGeocodeEvent = {
geocode: GeocodingResult;
};
export type MarkGeocodeEventHandlerFn = (event: MarkGeocodeEvent) => void;
/**
* Event is fired before invoking {@link IGeocoder.geocode} (or {@link IGeocoder.suggest}).
* The event data contains the query string as `input`.
*/
export type StartGeocodeEvent = {
input: string;
};
export type StartGeocodeEventHandlerFn = (event: StartGeocodeEvent) => void;
/**
* Event is fired before after receiving results from {@link IGeocoder.geocode} (or {@link IGeocoder.suggest}).
* The event data contains the query string as `input` and the geocoding `results`.
*/
export type FinishGeocodeEvent = {
input: string;
results: GeocodingResult[];
};
export type FinishGeocodeEventHandlerFn = (event: FinishGeocodeEvent) => void;
declare module 'leaflet' {
interface Evented {
on(type: 'markgeocode', fn: MarkGeocodeEventHandlerFn, context?: any): this;
on(type: 'startgeocode', fn: StartGeocodeEventHandlerFn, context?: any): this;
on(type: 'startsuggest', fn: StartGeocodeEventHandlerFn, context?: any): this;
on(type: 'finishsuggest', fn: FinishGeocodeEventHandlerFn, context?: any): this;
on(type: 'finishgeocode', fn: FinishGeocodeEventHandlerFn, context?: any): this;
}
}
/**
* Leaflet mixins https://leafletjs.com/reference-1.7.1.html#class-includes
* for TypeScript https://www.typescriptlang.org/docs/handbook/mixins.html
* @internal
*/
declare class EventedControl {
constructor(...args: any[]);
}
/**
* @internal
*/
interface EventedControl extends L.Control, L.Evented {
}
/**
* This is the geocoder control. It works like any other [Leaflet control](https://leafletjs.com/reference.html#control), and is added to the map.
*/
export declare class GeocoderControl extends EventedControl {
options: GeocoderControlOptions;
private _alts;
private _container;
private _errorElement;
private _geocodeMarker;
private _input;
private _lastGeocode;
private _map;
private _preventBlurCollapse;
private _requestCount;
private _results;
private _selection;
private _suggestTimeout;
/**
* Instantiates a geocoder control (to be invoked using `new`)
* @param options the options
*/
constructor(options?: Partial<GeocoderControlOptions>);
addThrobberClass(): void;
removeThrobberClass(): void;
/**
* Returns the container DOM element for the control and add listeners on relevant map events.
* @param map the map instance
* @see https://leafletjs.com/reference.html#control-onadd
*/
onAdd(map: L.Map): HTMLDivElement;
/**
* Sets the query string on the text input
* @param string the query string
*/
setQuery(string: string): this;
private _geocodeResult;
/**
* Marks a geocoding result on the map
* @param result the geocoding result
*/
markGeocode(event: MarkGeocodeEvent): this;
private _geocode;
private _geocodeResultSelected;
private _toggle;
private _expand;
private _collapse;
private _clearResults;
private _createAlt;
private _keydown;
private _change;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link GeocoderControl}
* @param options the options
*/
export declare function geocoder(options?: Partial<GeocoderControlOptions>): GeocoderControl;
export {};

View File

@ -0,0 +1,77 @@
import * as L from 'leaflet';
/**
* An object that represents a result from a geocoding query
*/
export interface GeocodingResult {
/**
* Name of found location
*/
name: string;
/**
* The bounds of the location
*/
bbox: L.LatLngBounds;
/**
* The center coordinate of the location
*/
center: L.LatLng;
/**
* URL for icon representing result; optional
*/
icon?: string;
/**
* HTML formatted representation of the name
*/
html?: string;
/**
* Additional properties returned by the geocoder
*/
properties?: any;
}
/**
* An interface implemented to respond to geocoding queries
*/
export interface IGeocoder {
/**
* Performs a geocoding query and returns the results as promise
* @param query the query
*/
geocode(query: string): Promise<GeocodingResult[]>;
/**
* Performs a geocoding query suggestion (this happens while typing) and returns the results as promise
* @param query the query
*/
suggest?(query: string): Promise<GeocodingResult[]>;
/**
* Performs a reverse geocoding query and returns the results as promise
* @param location the coordinate to reverse geocode
* @param scale the map scale possibly used for reverse geocoding
*/
reverse?(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
export interface GeocoderOptions {
/**
* URL of the service
*/
serviceUrl: string;
/**
* Additional URL parameters (strings) that will be added to geocoding requests
*/
geocodingQueryParams?: Record<string, unknown>;
/**
* Additional URL parameters (strings) that will be added to reverse geocoding requests
*/
reverseQueryParams?: Record<string, unknown>;
/**
* API key to use this service
*/
apiKey?: string;
}
/**
* @internal
*/
export declare function geocodingParams(options: GeocoderOptions, params: Record<string, unknown>): Record<string, unknown>;
/**
* @internal
*/
export declare function reverseParams(options: GeocoderOptions, params: Record<string, unknown>): Record<string, unknown>;

View File

@ -0,0 +1,47 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface ArcGisOptions extends GeocoderOptions {
}
/**
* Implementation of the [ArcGIS geocoder](https://developers.arcgis.com/features/geocoding/)
*/
export declare class ArcGis implements IGeocoder {
options: ArcGisOptions;
constructor(options?: Partial<ArcGisOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link ArcGis}
* @param options the options
*/
export declare function arcgis(options?: Partial<ArcGisOptions>): ArcGis;
/**
* @internal
*/
export interface ArcGisResponse {
spatialReference: {
wkid: number;
latestWkid: number;
};
candidates: Candidate[];
}
interface Candidate {
address: string;
location: {
x: number;
y: number;
};
score: number;
attributes: {
Addr_Type: string;
};
extent: {
xmin: number;
ymin: number;
xmax: number;
ymax: number;
};
}
export {};

View File

@ -0,0 +1,84 @@
import * as L from 'leaflet';
import { GeocodingResult, IGeocoder } from './api';
export interface AzureMapsOptions {
apiKey: string;
serviceUrl: string;
}
/**
* Implementation of [Azure Maps Geocoding](https://www.microsoft.com/en-us/maps/azure/location-services/geocoding)
*
* https://learn.microsoft.com/en-us/rest/api/maps/search?view=rest-maps-1.0
*/
export declare class AzureMaps implements IGeocoder {
private options;
constructor(options: Partial<AzureMapsOptions>);
/**
* {@inheritdoc}
* https://learn.microsoft.com/en-us/rest/api/maps/search/get-search-address?view=rest-maps-1.0&tabs=HTTP
*/
geocode(query: string): Promise<GeocodingResult[]>;
/**
* {@inheritdoc}
* https://learn.microsoft.com/en-us/rest/api/maps/search/get-search-address-reverse?view=rest-maps-1.0&tabs=HTTP
*/
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Azure}
* @param options the options
*/
export declare function azure(options: AzureMapsOptions): AzureMaps;
/**
* @internal
*/
export interface AzureMapsResponse {
summary: Summary;
results: Result[];
}
interface Result {
type: string;
id: string;
score: number;
address: Address;
position: Position;
viewport: Viewport;
entryPoints: EntryPoint[];
}
interface Address {
streetNumber: string;
streetName: string;
municipalitySubdivision: string;
municipality: string;
countrySecondarySubdivision: string;
countryTertiarySubdivision: string;
countrySubdivisionCode: string;
postalCode: string;
extendedPostalCode: string;
countryCode: string;
country: string;
countryCodeISO3: string;
freeformAddress: string;
countrySubdivisionName: string;
}
interface EntryPoint {
type: string;
position: Position;
}
interface Position {
lat: number;
lon: number;
}
interface Viewport {
topLeftPoint: Position;
btmRightPoint: Position;
}
interface Summary {
query: string;
queryType: string;
queryTime: number;
numResults: number;
offset: number;
totalResults: number;
fuzzyLevel: number;
}
export {};

View File

@ -0,0 +1,22 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface BingOptions extends GeocoderOptions {
}
/**
* Implementation of the [Bing Locations API](https://docs.microsoft.com/en-us/bingmaps/rest-services/locations/)
*
* Bing Maps for Enterprise is deprecated and will be retired.
* Free (Basic) account customers can continue to use Bing Maps for Enterprise services until June 30th, 2025.
* Enterprise account customers can continue to use Bing Maps for Enterprise services until June 30th, 2028.
*/
export declare class Bing implements IGeocoder {
options: BingOptions;
constructor(options?: Partial<BingOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Bing}
* @param options the options
*/
export declare function bing(options?: Partial<BingOptions>): Bing;

View File

@ -0,0 +1,52 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
/**
* Implementation of the [Google Geocoding API](https://developers.google.com/maps/documentation/geocoding/)
*/
export interface GoogleOptions extends GeocoderOptions {
}
export declare class Google implements IGeocoder {
options: GoogleOptions;
constructor(options?: Partial<GoogleOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Google}
* @param options the options
*/
export declare function google(options?: Partial<GoogleOptions>): Google;
/**
* @internal
*/
export interface GoogleResponse {
results: Result[];
status: string;
}
interface Result {
address_components: AddressComponent[];
formatted_address: string;
geometry: Geometry;
place_id: string;
types: string[];
}
interface AddressComponent {
long_name: string;
short_name: string;
types: string[];
}
interface Geometry {
bounds: Bounds;
location: Location;
location_type: string;
viewport: Bounds;
}
interface Bounds {
northeast: Location;
southwest: Location;
}
interface Location {
lat: number;
lng: number;
}
export {};

View File

@ -0,0 +1,119 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface HereOptions extends GeocoderOptions {
/**
* Use `apiKey` and the new `HEREv2` geocoder
* @deprecated
*/
app_id: string;
/**
* Use `apiKey` and the new `HEREv2` geocoder
* @deprecated
*/
app_code: string;
reverseGeocodeProxRadius?: any;
apiKey: string;
maxResults: number;
}
/**
* Implementation of the [HERE Geocoder API](https://developer.here.com/documentation/geocoder/topics/introduction.html)
*/
export declare class HERE implements IGeocoder {
options: HereOptions;
constructor(options?: Partial<HereOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
getJSON(url: string, params: any): Promise<GeocodingResult[]>;
}
/**
* Implementation of the new [HERE Geocoder API](https://developer.here.com/documentation/geocoding-search-api/api-reference-swagger.html)
*/
export declare class HEREv2 implements IGeocoder {
options: HereOptions;
constructor(options?: Partial<HereOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
getJSON(url: string, params: any): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link HERE}
* @param options the options
*/
export declare function here(options?: Partial<HereOptions>): HERE | HEREv2;
/**
* @internal
*/
export interface HEREv2Response {
items: Item[];
}
interface Item {
title: string;
id: string;
ontologyId: string;
resultType: string;
address: Address;
mapView?: MapView;
position: Position;
access: Position[];
distance: number;
categories: Category[];
references: Reference[];
foodTypes: Category[];
contacts: Contact[];
openingHours: OpeningHour[];
}
interface MapView {
east: number;
north: number;
south: number;
west: number;
}
interface Position {
lat: number;
lng: number;
}
interface Address {
label: string;
countryCode: string;
countryName: string;
stateCode: string;
state: string;
county: string;
city: string;
district: string;
street: string;
postalCode: string;
houseNumber: string;
}
interface Category {
id: string;
name: string;
primary?: boolean;
}
interface Contact {
phone: Email[];
fax: Email[];
www: Email[];
email: Email[];
}
interface Email {
value: string;
}
interface OpeningHour {
text: string[];
isOpen: boolean;
structured: Structured[];
}
interface Structured {
start: string;
duration: string;
recurrence: string;
}
interface Reference {
supplier: Supplier;
id: string;
}
interface Supplier {
id: string;
}
export {};

View File

@ -0,0 +1,16 @@
export * from './api';
export * from './arcgis';
export * from './bing';
export * from './azure';
export * from './google';
export * from './here';
export * from './latlng';
export * from './mapbox';
export * from './mapquest';
export * from './neutrino';
export * from './nominatim';
export * from './open-location-code';
export * from './opencage';
export * from './pelias';
export * from './photon';
export * from './what3words';

View File

@ -0,0 +1,31 @@
import * as L from 'leaflet';
import { IGeocoder, GeocodingResult } from './api';
export interface LatLngOptions {
/**
* The next geocoder to use for non-supported queries
*/
next?: IGeocoder;
/**
* The size in meters used for passing to `LatLng.toBounds`
*/
sizeInMeters: number;
}
/**
* Parses basic latitude/longitude strings such as `'50.06773 14.37742'`, `'N50.06773 W14.37742'`, `'S 50° 04.064 E 014° 22.645'`, or `'S 50° 4 03.828″, W 14° 22 38.712″'`
* @param query the latitude/longitude string to parse
* @returns the parsed latitude/longitude
*/
export declare function parseLatLng(query: string): L.LatLng | undefined;
/**
* Parses basic latitude/longitude strings such as `'50.06773 14.37742'`, `'N50.06773 W14.37742'`, `'S 50° 04.064 E 014° 22.645'`, or `'S 50° 4 03.828″, W 14° 22 38.712″'`
*/
export declare class LatLng implements IGeocoder {
options: LatLngOptions;
constructor(options?: Partial<LatLngOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link LatLng}
* @param options the options
*/
export declare function latLng(options?: Partial<LatLngOptions>): LatLng;

View File

@ -0,0 +1,64 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface MapboxOptions extends GeocoderOptions {
}
/**
* Implementation of the [Mapbox Geocoding](https://www.mapbox.com/api-documentation/#geocoding)
*/
export declare class Mapbox implements IGeocoder {
options: MapboxOptions;
constructor(options?: Partial<MapboxOptions>);
_getProperties(loc: any): {
text: any;
address: any;
};
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
private _parseResults;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Mapbox}
* @param options the options
*/
export declare function mapbox(options?: Partial<MapboxOptions>): Mapbox;
/**
* @internal
*/
export interface MapboxResponse {
type: string;
query: string[];
features: Feature[];
attribution: string;
}
interface Feature {
id: string;
type: string;
place_type: string[];
relevance: number;
properties: Properties;
text: string;
place_name: string;
matching_text: string;
matching_place_name: string;
center: [number, number];
bbox?: [number, number, number, number];
geometry: Geometry;
address: string;
context: Context[];
}
interface Context {
id: string;
text: string;
wikidata?: string;
short_code?: string;
}
interface Geometry {
type: string;
coordinates: number[];
interpolated: boolean;
omitted: boolean;
}
interface Properties {
}
export {};

View File

@ -0,0 +1,20 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface MapQuestOptions extends GeocoderOptions {
}
/**
* Implementation of the [MapQuest Geocoding API](http://developer.mapquest.com/web/products/dev-services/geocoding-ws)
*/
export declare class MapQuest implements IGeocoder {
options: MapQuestOptions;
constructor(options?: Partial<MapQuestOptions>);
_formatName(...parts: string[]): string;
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
private _parseResults;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link MapQuest}
* @param options the options
*/
export declare function mapQuest(options?: Partial<MapQuestOptions>): MapQuest;

View File

@ -0,0 +1,20 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface NeutrinoOptions extends GeocoderOptions {
userId: string;
}
/**
* Implementation of the [Neutrino API](https://www.neutrinoapi.com/api/geocode-address/)
*/
export declare class Neutrino implements IGeocoder {
options: NeutrinoOptions;
constructor(options?: Partial<NeutrinoOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Neutrino}
* @param options the options
*/
export declare function neutrino(options?: Partial<NeutrinoOptions>): Neutrino;

View File

@ -0,0 +1,63 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export type NominatimResponse = NominatimResult[];
export interface NominatimResult {
place_id: number;
licence: string;
osm_type: string;
osm_id: number;
boundingbox: string[];
lat: string;
lon: string;
display_name: string;
class?: string;
type?: string;
importance?: number;
icon?: string;
address: NominatimAddress;
}
export interface NominatimAddress {
building?: string;
city_district?: string;
city?: string;
country_code?: string;
country?: string;
county?: string;
hamlet?: string;
house_number?: string;
neighbourhood?: string;
postcode?: string;
road?: string;
state_district?: string;
state?: string;
suburb?: string;
village?: string;
}
export interface NominatimOptions extends GeocoderOptions {
/**
* Additional URL parameters (strings) that will be added to geocoding requests; can be used to restrict results to a specific country for example, by providing the [`countrycodes`](https://wiki.openstreetmap.org/wiki/Nominatim#Parameters) parameter to Nominatim
*/
geocodingQueryParams?: Record<string, unknown>;
/**
* A function that takes an GeocodingResult as argument and returns an HTML formatted string that represents the result. Default function breaks up address in parts from most to least specific, in attempt to increase readability compared to Nominatim's naming
*/
htmlTemplate: (r: NominatimResult) => string;
}
/**
* Implementation of the [Nominatim](https://wiki.openstreetmap.org/wiki/Nominatim) geocoder.
*
* This is the default geocoding service used by the control, unless otherwise specified in the options.
*
* Unless using your own Nominatim installation, please refer to the [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/).
*/
export declare class Nominatim implements IGeocoder {
options: NominatimOptions;
constructor(options?: Partial<NominatimOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Nominatim}
* @param options the options
*/
export declare function nominatim(options?: Partial<NominatimOptions>): Nominatim;

View File

@ -0,0 +1,37 @@
import * as L from 'leaflet';
import { IGeocoder, GeocodingResult } from './api';
export interface OpenLocationCodeOptions {
OpenLocationCode: OpenLocationCodeApi;
codeLength?: number;
}
export interface OpenLocationCodeApi {
encode(latitude: number, longitude: number, codeLength?: number): string;
decode(code: string): CodeArea;
}
export interface CodeArea {
latitudeLo: number;
longitudeLo: number;
latitudeHi: number;
longitudeHi: number;
latitudeCenter: number;
longitudeCenter: number;
codeLength: number;
}
/**
* Implementation of the [Plus codes](https://plus.codes/) (formerly OpenLocationCode) (requires [open-location-code](https://www.npmjs.com/package/open-location-code))
*/
export declare class OpenLocationCode implements IGeocoder {
options: OpenLocationCodeOptions;
constructor(options?: Partial<OpenLocationCodeOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<{
name: string;
center: L.LatLng;
bbox: L.LatLngBounds;
}[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link OpenLocationCode}
* @param options the options
*/
export declare function openLocationCode(options?: Partial<OpenLocationCodeOptions>): OpenLocationCode;

View File

@ -0,0 +1,16 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface OpenCageOptions extends GeocoderOptions {
}
/**
* Implementation of the [OpenCage Data API](https://opencagedata.com/)
*/
export declare class OpenCage implements IGeocoder {
options: OpenCageOptions;
constructor(options?: Partial<OpenCageOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
private _parseResults;
}
export declare function opencage(options?: Partial<OpenCageOptions>): OpenCage;

View File

@ -0,0 +1,83 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface PeliasOptions extends GeocoderOptions {
}
/**
* Implementation of the [Pelias](https://pelias.io/), [geocode.earth](https://geocode.earth/) geocoder (formerly Mapzen Search)
*/
export declare class Pelias implements IGeocoder {
options: PeliasOptions;
constructor(options?: Partial<PeliasOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
_parseResults(data: any, bboxname: any): GeocodingResult[];
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Pelias}
* @param options the options
*/
export declare function pelias(options?: Partial<PeliasOptions>): Pelias;
export declare const GeocodeEarth: typeof Pelias;
export declare const geocodeEarth: typeof pelias;
/**
* r.i.p.
* @deprecated
*/
export declare const Mapzen: typeof Pelias;
/**
* r.i.p.
* @deprecated
*/
export declare const mapzen: typeof pelias;
/**
* Implementation of the [Openrouteservice](https://openrouteservice.org/dev/#/api-docs/geocode) geocoder
*/
export declare class Openrouteservice extends Pelias {
constructor(options?: Partial<PeliasOptions>);
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Openrouteservice}
* @param options the options
*/
export declare function openrouteservice(options?: Partial<PeliasOptions>): Openrouteservice;
/**
* @internal
*/
export type PeliasResponse = GeoJSON.FeatureCollection<GeoJSON.Geometry, Properties> & {
geocoding: Geocoding;
};
interface Properties {
id: string;
layer: string;
source_id: string;
name: string;
confidence: number;
match_type: string;
accuracy: string;
country: string;
country_a: string;
region: string;
region_a: string;
county: string;
county_a: string;
localadmin: string;
locality: string;
continent: string;
label: string;
}
interface Geocoding {
version: string;
attribution: string;
query: Query;
warnings: string[];
engine: Engine;
}
interface Engine {
name: string;
author: string;
version: string;
}
interface Query {
}
export {};

View File

@ -0,0 +1,46 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface PhotonOptions extends GeocoderOptions {
reverseUrl: string;
nameProperties: string[];
htmlTemplate?: (r: any) => string;
}
/**
* Implementation of the [Photon](http://photon.komoot.de/) geocoder
*/
export declare class Photon implements IGeocoder {
options: PhotonOptions;
constructor(options?: Partial<PhotonOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(latLng: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
_parseResults(data: GeoJSON.FeatureCollection<GeoJSON.Point>): GeocodingResult[];
_decodeFeatureName(f: GeoJSON.Feature): string;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link Photon}
* @param options the options
*/
export declare function photon(options?: Partial<PhotonOptions>): Photon;
/**
* @internal
*/
export type PhotonResponse = GeoJSON.FeatureCollection<GeoJSON.Geometry, PhotonProperties>;
interface PhotonProperties {
osm_id: number;
osm_type: string;
extent?: number[];
country: string;
osm_key: string;
city: string;
countrycode: string;
osm_value: string;
name: string;
state: string;
type: string;
postcode?: string;
housenumber?: string;
street?: string;
district?: string;
}
export {};

View File

@ -0,0 +1,19 @@
import * as L from 'leaflet';
import { IGeocoder, GeocoderOptions, GeocodingResult } from './api';
export interface What3WordsOptions extends GeocoderOptions {
}
/**
* Implementation of the What3Words service
*/
export declare class What3Words implements IGeocoder {
options: What3WordsOptions;
constructor(options: Partial<What3WordsOptions>);
geocode(query: string): Promise<GeocodingResult[]>;
suggest(query: string): Promise<GeocodingResult[]>;
reverse(location: L.LatLngLiteral, scale: number): Promise<GeocodingResult[]>;
}
/**
* [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link What3Words}
* @param options the options
*/
export declare function what3words(options: Partial<What3WordsOptions>): What3Words;

View File

@ -0,0 +1,5 @@
import { GeocoderControl as Geocoder, geocoder } from './control';
import * as geocoders from './geocoders/index';
import './style.css';
export default Geocoder;
export { Geocoder, geocoder, geocoders };

View File

@ -0,0 +1,12 @@
/**
* @internal
*/
export declare function htmlEscape(string?: string): string;
/**
* @internal
*/
export declare function getJSON<T>(url: string, params: Record<string, unknown>): Promise<T>;
/**
* @internal
*/
export declare function template(str: string, data: Record<string, any>): string;

BIN
public/leaflet-images.zip Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

14419
public/leaflet-src.esm.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

14512
public/leaflet-src.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More