Setelah berhasil melakukan setup laravel, inertia.js, react.js dan mapbox, pada tutorial kali ini kita akan belajar bagaimana caranya menampilkan marker pada halaman Master Location.
Berikut demo dari project yang akan kita buat:
Bagi kamu yang belum setup project ini silakan cek tutorial sebelumnya:
Part 1 - Tutorial GIS Interaktif Menggunakan Laravel Inertia & React
Step 1: Tambahkan Controller, Model, Migration & View Master Location
Jalankan command berikut untuk membuat controller dan migration based on model Location:
php artisan make:model Location -mc
php artisan make:model Location -mc
Jika berhasil maka laravel akan menggenerate file berikut:
Navigasi ke LocationController lalu tambahkan code berikut agar dapat merender view master location dan get semua data locations dari database menggunakan inertia:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use App\Models\Location;
class LocationController extends Controller
{
public function index(): Response{
$locations = Location::all();
return Inertia::render('Location/Index', [
'locations' => $locations
]);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use App\Models\Location;
class LocationController extends Controller
{
public function index(): Response{
$locations = Location::all();
return Inertia::render('Location/Index', [
'locations' => $locations
]);
}
}
Tambahkan file Index.jsx pada folder pages:
import React from "react";
import { Head } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
const Location = ({ auth }) => {
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Master Location
</h2>
}
>
<Head title="Dashboard GIS" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div>This is location page</div>
</div>
</div>
</AuthenticatedLayout>
);
};
export default Location;
import React from "react";
import { Head } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
const Location = ({ auth }) => {
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Master Location
</h2>
}
>
<Head title="Dashboard GIS" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div>This is location page</div>
</div>
</div>
</AuthenticatedLayout>
);
};
export default Location;
Step 2: Setup Route & Menu Master Location
Pada web route laravel tambahkan rute master location:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\LocationController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return Inertia::render('Welcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
});
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/location', [LocationController::class, 'index'])->name('location.index');
});
require __DIR__.'/auth.php';
<?php
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\LocationController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return Inertia::render('Welcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
});
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/location', [LocationController::class, 'index'])->name('location.index');
});
require __DIR__.'/auth.php';
Buka file AuthenticatedLayout.jsx lalu tambahkan menu master location seperti berikut:
...
<Dropdown.Content>
<Dropdown.Link href={route("profile.edit")}>Profile</Dropdown.Link>
<Dropdown.Link href={route("profile.edit")}>Master Location</Dropdown.Link>
<Dropdown.Link href={route("location.index")} method="post" as="button">
Log Out
</Dropdown.Link>
</Dropdown.Content>
...
...
<Dropdown.Content>
<Dropdown.Link href={route("profile.edit")}>Profile</Dropdown.Link>
<Dropdown.Link href={route("profile.edit")}>Master Location</Dropdown.Link>
<Dropdown.Link href={route("location.index")} method="post" as="button">
Log Out
</Dropdown.Link>
</Dropdown.Content>
...
Jika sudah selesai maka kamu akan melihat menu berikut
Step 3: Setup Migration dan Model Master Location
Setelah berhasil menampilkan halaman master location, pada step ini kita akan menambahkan table locations pada database menggunakan migration dan setup field mana saja yang dapat diinput dari sisi model laravel.
Pergi ke file migration location kemudian tambahkan code berikut untuk menambahkan field pada table locations:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('locations', function (Blueprint $table) {
$table->id();
$table->string("name");
$table->text("description")->nullable();
$table->string("lat");
$table->string("long");
$table->string("image");
$table->string("rating");
$table->boolean("is_active");
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('locations');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('locations', function (Blueprint $table) {
$table->id();
$table->string("name");
$table->text("description")->nullable();
$table->string("lat");
$table->string("long");
$table->string("image");
$table->string("rating");
$table->boolean("is_active");
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('locations');
}
};
Jalankan command berikut untuk memulai migrasi:
php artisan migrate
php artisan migrate
Selanjutnya, setup model Location seperti berikut:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
use HasFactory;
protected $fillable = ["name", "description", "lat", "long", "image", "rating", "is_active"];
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
use HasFactory;
protected $fillable = ["name", "description", "lat", "long", "image", "rating", "is_active"];
}
Step 4: Tambahkan Seeder Master Location
Selanjutkan buatlah seeder menggunakan command berikut:
php artisan make:seeder LocationSeeder
php artisan make:seeder LocationSeeder
Kemudian tambahkan data dummy berikut pada LocationSeeder:
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Location;
class LocationSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$locations = [
[
'name' => 'Delicious Bites',
'description' => 'A trendy restaurant offering a fusion of Indonesian and Western cuisines.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/00/bf/95/buffet-spread.jpg?w=500&h=-1&s=1',
'lat' => -6.2146,
'long' => 106.8451,
'rating' => 4,
'is_active' => 1
],
[
'name' => 'Spice Garden',
'description' => 'Experience the flavors of India in the heart of Jakarta. Enjoy authentic Indian dishes in a vibrant setting.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/f3/b9/8c/babi-hong.jpg?w=600&h=-1&s=1',
'lat' => -6.2088,
'long' => 106.8456,
'rating' => 2,
'is_active' => 1
],
[
'name' => 'Oceanic Delights',
'description' => 'Savor delectable seafood dishes while enjoying a stunning view of the Jakarta Bay.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/f3/b7/92/kuluyuk-ayam.jpg?w=600&h=400&s=1',
'lat' => -6.2263,
'long' => 106.8308,
'rating' => 3.5,
'is_active' => 1
],
[
'name' => 'Sushi Haven',
'description' => 'A cozy sushi bar offering an extensive menu of fresh sushi and sashimi.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/08/e3/6d/6a/sushi-tei-plaza-indonesia.jpg?w=600&h=400&s=1',
'lat' => -6.2219,
'long' => 106.8059,
'rating' => 5,
'is_active' => 1
],
[
'name' => 'La Patisserie',
'description' => 'Indulge in a wide array of delectable pastries, cakes, and desserts at this charming French-inspired patisserie.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0f/72/01/3b/diponegoro-dining-hall.jpg?w=600&h=400&s=1',
'lat' => -6.1941,
'long' => 106.8239,
'rating' => 3,
'is_active' => 1
],
[
'name' => 'Rooftop Lounge',
'description' => 'Enjoy panoramic views of the city skyline while sipping on handcrafted cocktails and sampling delicious bar bites.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/16/b3/5c/28/img20190307155916-largejpg.jpg?w=600&h=400&s=1',
'lat' => -6.1966,
'long' => 106.8223,
'rating' => 5,
'is_active' => 1
],
[
'name' => 'Mama Mia Pizza',
'description' => 'A family-friendly pizzeria serving traditional wood-fired pizzas with a variety of mouthwatering toppings.',
'image' => 'https://menufyproduction.imgix.net/637865014833715521+765921.png?auto=compress,format&h=1080&w=1920&fit=max',
'lat' => -6.2189,
'long' => 106.7998,
'rating' => 4.5,
'is_active' => 1
],
[
'name' => 'Noodle House',
'description' => 'Step into this cozy noodle house and savor a wide selection of Asian noodle dishes, from ramen to stir-fried noodles.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/12/50/6e/59/teras-dharmawangsa.jpg?w=600&h=-1&s=1',
'lat' => -6.2173,
'long' => 106.7974,
'rating' => 4,
'is_active' => 1
],
[
'name' => 'The Grill House',
'description' => 'A steakhouse known for its perfectly grilled steaks, accompaniedby flavorful sauces and sides.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/16/85/79/0d/la-grillade.jpg?w=600&h=400&s=1',
'lat' => -6.2081,
'long' => 106.7972,
'rating' => 5,
'is_active' => 1
],
[
'name' => 'Cafe Mornings',
'description' => 'Start your day with a delicious breakfast spread and a cup of freshly brewed coffee at this cozy cafe.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1b/f3/24/3c/1f-9.jpg?w=600&h=400&s=1',
'lat' => -6.2150,
'long' => 106.8169,
'rating' => 4.5,
'is_active' => 1
],
];
foreach ($locations as $location) {
Location::create($location);
}
}
}
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Location;
class LocationSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$locations = [
[
'name' => 'Delicious Bites',
'description' => 'A trendy restaurant offering a fusion of Indonesian and Western cuisines.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/00/bf/95/buffet-spread.jpg?w=500&h=-1&s=1',
'lat' => -6.2146,
'long' => 106.8451,
'rating' => 4,
'is_active' => 1
],
[
'name' => 'Spice Garden',
'description' => 'Experience the flavors of India in the heart of Jakarta. Enjoy authentic Indian dishes in a vibrant setting.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/f3/b9/8c/babi-hong.jpg?w=600&h=-1&s=1',
'lat' => -6.2088,
'long' => 106.8456,
'rating' => 2,
'is_active' => 1
],
[
'name' => 'Oceanic Delights',
'description' => 'Savor delectable seafood dishes while enjoying a stunning view of the Jakarta Bay.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/f3/b7/92/kuluyuk-ayam.jpg?w=600&h=400&s=1',
'lat' => -6.2263,
'long' => 106.8308,
'rating' => 3.5,
'is_active' => 1
],
[
'name' => 'Sushi Haven',
'description' => 'A cozy sushi bar offering an extensive menu of fresh sushi and sashimi.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/08/e3/6d/6a/sushi-tei-plaza-indonesia.jpg?w=600&h=400&s=1',
'lat' => -6.2219,
'long' => 106.8059,
'rating' => 5,
'is_active' => 1
],
[
'name' => 'La Patisserie',
'description' => 'Indulge in a wide array of delectable pastries, cakes, and desserts at this charming French-inspired patisserie.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0f/72/01/3b/diponegoro-dining-hall.jpg?w=600&h=400&s=1',
'lat' => -6.1941,
'long' => 106.8239,
'rating' => 3,
'is_active' => 1
],
[
'name' => 'Rooftop Lounge',
'description' => 'Enjoy panoramic views of the city skyline while sipping on handcrafted cocktails and sampling delicious bar bites.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/16/b3/5c/28/img20190307155916-largejpg.jpg?w=600&h=400&s=1',
'lat' => -6.1966,
'long' => 106.8223,
'rating' => 5,
'is_active' => 1
],
[
'name' => 'Mama Mia Pizza',
'description' => 'A family-friendly pizzeria serving traditional wood-fired pizzas with a variety of mouthwatering toppings.',
'image' => 'https://menufyproduction.imgix.net/637865014833715521+765921.png?auto=compress,format&h=1080&w=1920&fit=max',
'lat' => -6.2189,
'long' => 106.7998,
'rating' => 4.5,
'is_active' => 1
],
[
'name' => 'Noodle House',
'description' => 'Step into this cozy noodle house and savor a wide selection of Asian noodle dishes, from ramen to stir-fried noodles.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/12/50/6e/59/teras-dharmawangsa.jpg?w=600&h=-1&s=1',
'lat' => -6.2173,
'long' => 106.7974,
'rating' => 4,
'is_active' => 1
],
[
'name' => 'The Grill House',
'description' => 'A steakhouse known for its perfectly grilled steaks, accompaniedby flavorful sauces and sides.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/16/85/79/0d/la-grillade.jpg?w=600&h=400&s=1',
'lat' => -6.2081,
'long' => 106.7972,
'rating' => 5,
'is_active' => 1
],
[
'name' => 'Cafe Mornings',
'description' => 'Start your day with a delicious breakfast spread and a cup of freshly brewed coffee at this cozy cafe.',
'image' => 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1b/f3/24/3c/1f-9.jpg?w=600&h=400&s=1',
'lat' => -6.2150,
'long' => 106.8169,
'rating' => 4.5,
'is_active' => 1
],
];
foreach ($locations as $location) {
Location::create($location);
}
}
}
Jalankan seeder agar data dummy di insert pada table locations:
php artisan db:seed --class=LocationSeeder
php artisan db:seed --class=LocationSeeder
Step 5: Tambahkan Custom Marker dan Interaksi Map pada Halaman Master Location
Selanjutnya tambahkan library berikut agar map kita punya slider/carausel:
npm install react-slick
npm install react-slick
npm install slick-carousel
npm install slick-carousel
Pada file app.jsx import slick carausel css:
import "./bootstrap";
import "../css/app.css";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { createRoot } from "react-dom/client";
import { createInertiaApp } from "@inertiajs/react";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
const appName = import.meta.env.VITE_APP_NAME || "Laravel";
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.jsx`,
import.meta.glob("./Pages/**/*.jsx")
),
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
},
progress: {
color: "#4B5563",
},
});
import "./bootstrap";
import "../css/app.css";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { createRoot } from "react-dom/client";
import { createInertiaApp } from "@inertiajs/react";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
const appName = import.meta.env.VITE_APP_NAME || "Laravel";
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.jsx`,
import.meta.glob("./Pages/**/*.jsx")
),
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
},
progress: {
color: "#4B5563",
},
});
Modifikasi page Location/Index.jsx menjadi seperti berikut:
import React, { useCallback, useMemo, useRef, useState } from "react";
import { Head, usePage } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import Map, {
FullscreenControl,
GeolocateControl,
Marker,
NavigationControl,
Popup,
ScaleControl,
} from "react-map-gl";
import ItemSlider from "@/Components/ItemSlider";
const Location = ({ auth }) => {
const { locations } = usePage().props;
const sliderRef = useRef();
const mapRef = useRef();
const [popupInfo, setPopupInfo] = useState(null);
// handle scroll image based on clicked pin
const scrollToSlide = useCallback(
(index) => {
sliderRef.current.slickGoTo(index);
},
[sliderRef]
);
// handle jum to long,lat when click on the slider
const handleJumpTo = useCallback(
(long, lat) => {
mapRef.current.easeTo(
{
center: [long, lat],
zoom: 13, // Zoom level of the target location
bearing: 0, // Bearing of the map (optional)
pitch: 0, // Pitch of the map (optional)
},
{
duration: 2000, // Animation duration in milliseconds
easing: (t) => t, // Easing function, default is linear
}
);
},
[mapRef]
);
// map all locations to pin, dont forget to use useMemo to improve perfomance
const pins = useMemo(
() =>
locations.map((location, index) => (
<Marker
key={`marker-${index}`}
longitude={location.long}
latitude={location.lat}
anchor="bottom"
onClick={(e) => {
e.originalEvent.stopPropagation();
setPopupInfo(location);
scrollToSlide(index);
handleJumpTo(location.long, location.lat);
}}
>
<img
src="https://cdn.iconscout.com/icon/free/png-256/free-restaurant-1495593-1267764.png?f=webp"
className="h-8 w-8"
/>
</Marker>
)),
[]
);
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Master Location
</h2>
}
>
<Head name="Dashboard GIS" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="h-max w-3/4">
<Map
reuseMaps
ref={mapRef}
mapboxAccessToken="YOUR MAPBOX TOKEN"
initialViewState={{
longitude: 106.8291201, //Central Jakarta Long
latitude: -6.1836782, //Central Jakarta Lat
zoom: 12,
bearing: 0,
pitch: 0,
}}
style={{ width: "100%", height: 600 }}
mapStyle="mapbox://styles/mapbox/streets-v9"
>
<GeolocateControl position="top-left" />
<FullscreenControl position="top-left" />
<NavigationControl position="top-left" />
<ScaleControl />
{pins}
{/* Handle Pop Up when Pin Clicked */}
{popupInfo && (
<Popup
anchor="top"
longitude={Number(popupInfo.long)}
latitude={Number(popupInfo.lat)}
onClose={() => setPopupInfo(null)}
>
<div className="mb-3">
<h2 className="font-semibold mb-2 text-lg">
{popupInfo.name}
</h2>
<p>{popupInfo.description}</p>
</div>
<img
width="100%"
src={popupInfo.image}
className="object-cover rounded-sm"
/>
</Popup>
)}
</Map>
</div>
{/* Show Slider and navigate to pin when clicked */}
<div className="mt-8 w-3/4">
<ItemSlider
locations={locations}
ref={sliderRef}
handleJumpTo={handleJumpTo}
setPopupInfo={setPopupInfo}
/>
</div>
</div>
</div>
</AuthenticatedLayout>
);
};
export default Location;
import React, { useCallback, useMemo, useRef, useState } from "react";
import { Head, usePage } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import Map, {
FullscreenControl,
GeolocateControl,
Marker,
NavigationControl,
Popup,
ScaleControl,
} from "react-map-gl";
import ItemSlider from "@/Components/ItemSlider";
const Location = ({ auth }) => {
const { locations } = usePage().props;
const sliderRef = useRef();
const mapRef = useRef();
const [popupInfo, setPopupInfo] = useState(null);
// handle scroll image based on clicked pin
const scrollToSlide = useCallback(
(index) => {
sliderRef.current.slickGoTo(index);
},
[sliderRef]
);
// handle jum to long,lat when click on the slider
const handleJumpTo = useCallback(
(long, lat) => {
mapRef.current.easeTo(
{
center: [long, lat],
zoom: 13, // Zoom level of the target location
bearing: 0, // Bearing of the map (optional)
pitch: 0, // Pitch of the map (optional)
},
{
duration: 2000, // Animation duration in milliseconds
easing: (t) => t, // Easing function, default is linear
}
);
},
[mapRef]
);
// map all locations to pin, dont forget to use useMemo to improve perfomance
const pins = useMemo(
() =>
locations.map((location, index) => (
<Marker
key={`marker-${index}`}
longitude={location.long}
latitude={location.lat}
anchor="bottom"
onClick={(e) => {
e.originalEvent.stopPropagation();
setPopupInfo(location);
scrollToSlide(index);
handleJumpTo(location.long, location.lat);
}}
>
<img
src="https://cdn.iconscout.com/icon/free/png-256/free-restaurant-1495593-1267764.png?f=webp"
className="h-8 w-8"
/>
</Marker>
)),
[]
);
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Master Location
</h2>
}
>
<Head name="Dashboard GIS" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="h-max w-3/4">
<Map
reuseMaps
ref={mapRef}
mapboxAccessToken="YOUR MAPBOX TOKEN"
initialViewState={{
longitude: 106.8291201, //Central Jakarta Long
latitude: -6.1836782, //Central Jakarta Lat
zoom: 12,
bearing: 0,
pitch: 0,
}}
style={{ width: "100%", height: 600 }}
mapStyle="mapbox://styles/mapbox/streets-v9"
>
<GeolocateControl position="top-left" />
<FullscreenControl position="top-left" />
<NavigationControl position="top-left" />
<ScaleControl />
{pins}
{/* Handle Pop Up when Pin Clicked */}
{popupInfo && (
<Popup
anchor="top"
longitude={Number(popupInfo.long)}
latitude={Number(popupInfo.lat)}
onClose={() => setPopupInfo(null)}
>
<div className="mb-3">
<h2 className="font-semibold mb-2 text-lg">
{popupInfo.name}
</h2>
<p>{popupInfo.description}</p>
</div>
<img
width="100%"
src={popupInfo.image}
className="object-cover rounded-sm"
/>
</Popup>
)}
</Map>
</div>
{/* Show Slider and navigate to pin when clicked */}
<div className="mt-8 w-3/4">
<ItemSlider
locations={locations}
ref={sliderRef}
handleJumpTo={handleJumpTo}
setPopupInfo={setPopupInfo}
/>
</div>
</div>
</div>
</AuthenticatedLayout>
);
};
export default Location;
Tambahkan ItemSlider pada folder components:
import React, { forwardRef } from "react";
import Slider from "react-slick";
const settings = {
dots: false,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
arrows: false,
autoplay: false,
variableWidth: true,
pauseOnHover: true,
swipeToSlide: true,
};
const ItemSlider = forwardRef(
(
{ locations = [], handleJumpTo = () => {}, setPopupInfo = () => {} },
ref
) => {
return (
<Slider {...settings} ref={ref}>
{locations.map((location) => (
<div
key={location.name}
className={"pr-4 h-32 w-40 hover:cursor-pointer hover:opacity-80"}
onClick={() => {
handleJumpTo(location.long, location.lat);
setPopupInfo(location);
}}
>
<img
src={location.image}
className="object-cover h-full w-full aspect-video rounded-sm"
/>
<p className="text-white/80 mt-2 w-40 text-lg">{location.name}</p>
<p className="text-white/80 mt-1 w-40 text-sm">
{location.description}
</p>
</div>
))}
</Slider>
);
}
);
export default ItemSlider;
import React, { forwardRef } from "react";
import Slider from "react-slick";
const settings = {
dots: false,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
arrows: false,
autoplay: false,
variableWidth: true,
pauseOnHover: true,
swipeToSlide: true,
};
const ItemSlider = forwardRef(
(
{ locations = [], handleJumpTo = () => {}, setPopupInfo = () => {} },
ref
) => {
return (
<Slider {...settings} ref={ref}>
{locations.map((location) => (
<div
key={location.name}
className={"pr-4 h-32 w-40 hover:cursor-pointer hover:opacity-80"}
onClick={() => {
handleJumpTo(location.long, location.lat);
setPopupInfo(location);
}}
>
<img
src={location.image}
className="object-cover h-full w-full aspect-video rounded-sm"
/>
<p className="text-white/80 mt-2 w-40 text-lg">{location.name}</p>
<p className="text-white/80 mt-1 w-40 text-sm">
{location.description}
</p>
</div>
))}
</Slider>
);
}
);
export default ItemSlider;
Jika sudah selesai lakukan save, maka tampilan projectmu akan menjadi seperti berikut, kamu dapat mengklik semua marker yang ada untuk memunculkan pop up atau mengklik carausel dibawah untuk pergi menuju lokasi yang diinginkan, tentunya semua marker disini telah di load secara dinamis berdasarkan data yang ada pada table locations.
Menarik bukan?, selanjutkan kita akan menambahkan fitur CRUD selanjutnya yaitu Create, stay tune dan sampai jumpa kembali!