Logo Universitas

Universitas ASA Indonesia

Program Studi: Teknologi Informasi

Mata Kuliah: Pengembangan Aplikasi Web Lanjut

SKS: 3 (2 teori, 1 praktikum)

Dosen Pengampu: Istiqomah Sumadikarta, S.T., M.Kom.

Beranda Mundur Maju

Pertemuan 11: Implementasi Halaman Pengguna #2

Menampilkan User di Navbar


Di materi sebelum-sebelumnya, kita telah berhasil menampilkan data categories di dalam navbar. Sekarang kita akan lanjutkan lagi belajar bagaimana cara menampilkan data user yang sedang login di dalam navbar.

Konsepnya, jika user belum login kita akan menampilkan button LOGIN, tapi jika user sudah login kita akan tampilkan nama user tersebut. Jadi kita cukup bermain menggunakan kondisi if dan else untuk problem seperti ini.

Langlah 1 - Edit Component Header Web

Sekarang kita akan melakukan penambahan beberapa kode di dalam component Header Web untuk menampilkan data user yang sedang login.

Silahkan buka file src/components/web/Header.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import react and hook
import React, { useState, useEffect } from "react";

//import component react bootstrap
import {
    Navbar,
    Container,
    Nav,
    NavDropdown,
} from 'react-bootstrap';

//import react router dom
import { Link } from "react-router-dom";

//import BASE URL API
import Api from "../../api";

//import js cookie
import Cookies from "js-cookie";

function WebHeader() {

    //state categories
    const [categories, setCategories] = useState([]);

    //state user logged in
    const [user, setUser] = useState({});

    //token
    const token = Cookies.get("token");

    //function "fetchDataCategories"
    const fetchDataCategories = async () => {

        //fetching Rest API "categories"
        await Api.get('/api/web/categories')
        .then((response) => {

            //set data to state
            setCategories(response.data.data);
        });
    }

    //function "fetchDataUser"
    const fetchDataUser = async () => {

        //fetching Rest API "user"
        await Api.get('/api/admin/user', {
            headers: {
                //header Bearer + Token
                Authorization: `Bearer ${token}`,
            }
        })
        .then((response) => {

            //set data to state
            setUser(response.data);
        });
    }

    //hook
    useEffect(() => {

        //call function "fetchDataCategories"
        fetchDataCategories();

        //if token already exists
        if(token) {

            //call function "fetchDataUser"
            fetchDataUser();
        }

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
        <React.Fragment>
            <Navbar collapseOnSelect expand="lg" className="navbar-custom shadow-sm" fixed="top">
                <Container>
                    <Navbar.Brand as={Link} to="/" className="fw-bold text-white"><i className="fa fa-map-marked-alt"></i> TRAVEL GIS</Navbar.Brand>
                    <Navbar.Toggle aria-controls="responsive-navbar-nav" />
                    <Navbar.Collapse id="responsive-navbar-nav">
                    <Nav className="me-auto">
                        <NavDropdown title={<span><i className="fa fa-list-ul"></i> CATEGORIES</span> } id="collasible-nav-dropdown" className="fw-bold text-white">
                        {
                            categories.map((category) => (
                                <NavDropdown.Item as={Link} to={`/category/${category.slug}`} key={category.id}><img src={category.image} style={{ width: "35px" }} alt=""/> {category.name.toUpperCase()}</NavDropdown.Item>
                            ))
                        }
                        <NavDropdown.Divider />
                        <NavDropdown.Item as={Link} to="/posts/direction">LIHAT LAINNYA <i className="fa fa-long-arrow-alt-right"></i></NavDropdown.Item>
                        </NavDropdown>
                        <Nav.Link as={Link} to="/places" className="fw-bold text-white"><i className="fa fa-globe-asia"></i> PLACES</Nav.Link>
                        <Nav.Link as={Link} to="/maps" className="fw-bold text-white"><i className="fa fa-map"></i> MAPS</Nav.Link>
                    </Nav>
                    <Nav>
                        <Nav.Link className="fw-bold text-white me-4"><i className="fa fa-search"></i> SEARCH</Nav.Link>
                        {token 
                            ? <Link to="/admin/dashboard" className="btn btn-md btn-light text-uppercase"><i className="fa fa-user-circle"></i> {user.name}</Link>
                            : <Link to="/admin/login" className="btn btn-md btn-light"><i className="fa fa-lock"></i> LOGIN</Link>
                        }
                    </Nav>
                    </Navbar.Collapse>
                </Container>
            </Navbar>
        </React.Fragment>
    );
}

export default WebHeader;

Dari penambahan kode di atas, pertama kita import package Js Cookie, karena kita akan membutuhkan sebuah token yang disimpan di dalam cookies browser.

//import js cookie
import Cookies from "js-cookie";

Setelah itu, di dalam function component WebHeader, kita buat sebuah state baru yang bernama user dengan jenis object. State ini akan kita gunakan untuk menyimpan response data user yang di dapatkan dari Rest API.

//state user logged in
const [user, setUser] = useState({});

Kemudian kita buat variable baru dengan nama token, dimana isinya adalah token yang telah kita simpan di dalam cookies browser saat user melakukan proses otentikasi.

//token
const token = Cookies.get("token");

Dan kita membuat sebuah function baru yang bernama fetchDataUser dengan jenis asynchronus.

//function "fetchDataUser"
const fetchDataUser = async () => {

	//...
	
}

Di dalam function fetchDataUser kita akan melakukan HTTP request ke dalam server menggunakan endpoint /api/admin/user dengan method GET.

//fetching Rest API "user"
await Api.get('/api/admin/user', {
    headers: {
        //header Bearer + Token
        Authorization: `Bearer ${token}`,
    }
})

Jika proses fetching berhasil dilakukan, maka kita akan melakukan assign response data user ke dalam state yang bernama user.

//set data to state
setUser(response.data);

Di dalam hook useEffect, kita menambahakan sebuah kondisi. Jika token bernilai true atau user sudah login, maka kita akan memanggil function yang bernama fetchDataUser.

//hook
useEffect(() => {

    //call function "fetchDataCategories"
    fetchDataCategories();

    //if token already exists
    if (token) {

        //call function "fetchDataUser"
        fetchDataUser();
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Maka secara otomatis function fetchDataUser akan dijalankan pertama kali saat component diload ketika user sudah login.

Kemudian di dalam JSX kita tinggal membuat sebuah kondisi untuk menampilkan data-nya. Jika variable token bernilai true, maka kita akan menampilkan nama user. Jika tidak, maka kita akan menampilkan button LOGIN.

{token 
   ? <Link to="/admin/dashboard" className="btn btn-md btn-light text-uppercase"><i className="fa fa-user-circle"></i> {user.name}</Link>
   : <Link to="/admin/login" className="btn btn-md btn-light"><i className="fa fa-lock"></i> LOGIN</Link>
}

Langkah 2 - Uji Coba

Sekarang jika kita belum melakukan proses otentikasi, maka kita akan mendapatkan hasil yang kurang lebih seperti berikut ini :

Dari gambar di atas, kita menampilkan sebuah button LOGIN, karena memang kita belum melakukan proses otentikasi.

Sekarang silahkan lakukan proses login atau otentikasi dan jika berhasil, maka kita akan mendapatkan nama user yang sedang login di dalam navbar. Kurang lebih seperti berikut ini :

Konfigurasi Route untuk Halaman Category Show


Sekarang kita akan lanjutkan membuat konfigurasi route untuk menampilkan halaman category show. Sebelum itu, kita akan buat sebuah view/component-nya terlebih dahulu.

Langkah 1 - Membuat View/Component Category Show

Silahkan buat folder baru dengan nama categories di dalam folder src/pages/web/ dan di dalam folder categories silahkan buat file baru dengan nama Show.jsx, kemudian masukkan kode berikut ini di dalamnya.

//import react
import React from "react";

//import layout web
import LayoutWeb from "../../../layouts/Web";

function WebCategoryShow() {

    return(
        <React.Fragment>
            <LayoutWeb>
                <div className="container mt-80">
                    Halaman Show Category
                </div>
            </LayoutWeb>
        </React.Fragment>
    )

}

export default WebCategoryShow;

Di atas, kita berikan sample kode untuk halaman category show, karena kita akan ubah lagi dimateri-materi selanjutnya.

Langkah 2 - Konfigurasi Route Category Show

Setelah berhasil membuat component category show, sekarang kita lanjutkan membuat konfigurasi route-nya. Silahkan buka file src/routes/routes.jsx, kemudian ubah kode-nya menjadi seperti berikut ini :

//import react router dom
import { Routes, Route } from "react-router-dom";

//=======================================================================
//ADMIN
//=======================================================================

//import view Login
import Login from '../pages/admin/Login.jsx';

//import component private routes
import PrivateRoute from "./PrivateRoutes";

//import view admin Dashboard
import Dashboard from '../pages/admin/dashboard/Index.jsx';

//import view admin categories Index
import CategoriesIndex from '../pages/admin/categories/Index.jsx';

//import view admin category Create
import CategoryCreate from '../pages/admin/categories/Create.jsx';

//import view admin category Edit
import CategoryEdit from '../pages/admin/categories/Edit.jsx';

//import view admin places Index
import PlacesIndex from '../pages/admin/places/Index.jsx';

//import view admin places Create
import PlaceCreate from '../pages/admin/places/Create.jsx';

//import view admin places Edit
import PlaceEdit from '../pages/admin/places/Edit.jsx';

//import view admin sliders Index
import SlidersIndex from '../pages/admin/sliders/Index.jsx';

//import view admin slider Create
import SliderCreate from '../pages/admin/sliders/Create.jsx';

//import view admin users Index
import UsersIndex from '../pages/admin/users/Index.jsx';

//import view admin user Create
import UserCreate from '../pages/admin/users/Create.jsx';

//import view admin user Edit
import UserEdit from '../pages/admin/users/Edit.jsx';

//=======================================================================
//WEB
//=======================================================================

//import view web Home
import Home from '../pages/web/home/Index.jsx';

//import view web category Show
import WebCategoryShow from '../pages/web/categories/Show.jsx';

function RoutesIndex() {
    return (
        <Routes>

            {/* route "/admin/login" */}
            <Route path="/admin/login" element={<Login />} />

            {/* private route "/admin/dashboard" */}
            <Route
                path="/admin/dashboard"
                element={
                        <PrivateRoute>
                            <Dashboard />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories" */}
            <Route
                path="/admin/categories"
                element={
                        <PrivateRoute>
                            <CategoriesIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories/create" */}
            <Route
                path="/admin/categories/create"
                element={
                        <PrivateRoute>
                            <CategoryCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories/edit/:id" */}
            <Route
                path="/admin/categories/edit/:id"
                element={
                        <PrivateRoute>
                            <CategoryEdit />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places" */}
            <Route
                path="/admin/places"
                element={
                        <PrivateRoute>
                            <PlacesIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places/create" */}
            <Route
                path="/admin/places/create"
                element={
                        <PrivateRoute>
                            <PlaceCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places/edit/:id" */}
            <Route
                path="/admin/places/edit/:id"
                element={
                        <PrivateRoute>
                            <PlaceEdit />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/sliders" */}
            <Route
                path="/admin/sliders"
                element={
                        <PrivateRoute>
                            <SlidersIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/sliders/create" */}
            <Route
                path="/admin/sliders/create"
                element={
                        <PrivateRoute>
                            <SliderCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users" */}
            <Route
                path="/admin/users"
                element={
                        <PrivateRoute>
                            <UsersIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users/create" */}
            <Route
                path="/admin/users/create"
                element={
                        <PrivateRoute>
                            <UserCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users/edit/:id" */}
            <Route
                path="/admin/users/edit/:id"
                element={
                        <PrivateRoute>
                            <UserEdit />
                        </PrivateRoute>
                }
            />

            {/* route "/" */}
            <Route path="/" element={<Home />} />

            {/* route "/category/:slug" */}
            <Route path="/category/:slug" element={<WebCategoryShow />} />

        </Routes>
    )
}

export default RoutesIndex

Dari perubahan kode di atas, pertama kita import view/component category show yang sebelumnya sudah kita buat.

//import view web category Show
import WebCategoryShow from '../pages/web/category/Show.jsx';

Setelah itu, kita buat konfigurasi route-nya dengan path /category/:slug, dimana nilai slug akan bersifat dinamis. Dan jika URL tersebut diakses, maka akan memanggil view/component <WebCategoryShow />.

Sekarang, jika kita klik salah satu data category, maka akan menampilkan halaman yang kurang lebih seperti berikut ini :

Menampilkan Data Places Berdasarkan Category


Pada materi kali ini kita akan belajar menampilkan data place berdasarkan category. Sebelum itu, kita akan membuat component baru untuk card place, tujuannya agar kita bisa gunakan berulang-ulang di dalam component lain atau reusable.

Langkah 1 - Membuat Component Card Place

Silahkan buat file baru dengan nama CardPlace.jsx di dalam folder src/components/utilities, kemudian masukkan kode berikut ini di dalamnya.

//import react router dom
import { Link } from "react-router-dom";

function CardPlace(props) {

    return (
        <div className="col-md-6 mb-4" key={props.id}>
            <Link to={`/places/${props.slug}`} className="text-decoration-none text-dark">
                <div className="card border-0 rounded shadow-sm mb-3" style={{ maxWidth: "540px" }}>
                    <div className="row g-0">
                        <div className="col-md-4">
                            {
                                props.images.slice(0, 1).map((placeImage) => (
                                    <img src={placeImage.image} className="img-fluid rounded-start" alt="..." style={{ height: "100%", objectFit: "cover" }} key={placeImage.id} />
                                ))
                            }
                        </div>
                        <div className="col-md-8">
                            <div className="card-body">
                                <h5 className="card-title">{props.title}</h5>
                                <hr />
                                <p className="card-text"><i className="fa fa-map-marker"></i> <i>{props.address}</i></p>
                            </div>
                        </div>
                    </div>
                </div>
            </Link>
        </div>
    )
}

export default CardPlace;

Dari penambahan kode di atas, pertama kita import provider Link dari React Router DOM.

//import react router dom
import { Link } from "react-router-dom";

Setelah itu, di dalam parameter function component CardPlace kita berikan sebuah props, dimana props tersebut akan menerima data yang dikirimkan oleh component lain.

function CardPlace(props) {

	//...
	
}

Kemudian di dalam JSX, kita akan menampilkan data dari props yang telah dikirimkan. Contohnya seperti berikut ini :

<h5 className="card-title">{props.title}</h5>

Dan karena di dalam data place memiliki gambar yang banyak, maka kita akan ambil 1 gambar yang nanti kita gunakan sebagai thumbnail dari data place.

{
   props.images.slice(0, 1).map((placeImage) => (
       <img src={placeImage.image} className="img-fluid rounded-start" alt="..." style={{ height: "100%", objectFit: "cover" }} key={placeImage.id} />
   ))
}

Di atas, kita ambil 1 gambar dari array images dan kita looping menggunakan method map dan kemudian kita tampilkan sebagai thumbnail.

Langkah 2 - Edit View/Component Category Show

Sekarang kita akan belajar menampilkan data place berdasarkan category yang sedang dibuka dan kita akan mencoba menggunakan component CardPlace untuk menampilkan data-nya.

Silahkan buka file src/pages/web/categories/Show.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import hook react
import React, { useEffect, useState } from "react";

//import hook useParams react router dom
import { useParams } from "react-router-dom";

//import layout web
import LayoutWeb from "../../../layouts/Web";

//import BASE URL API
import Api from "../../../api";

//import card place component
import CardPlace from "../../../components/utilities/CardPlace";

function WebCategoryShow() {

    const [category, setCategory] = useState({});
    const [places, setPlaces] = useState([]);

    //get params from url
    const { slug } = useParams();

    //function "fetchDataCategory"
    const fetchDataCategory = async () => {

        //fetching Rest API
        await Api.get(`/api/web/categories/${slug}`)
            .then((response) => {

                //set data to state "category"
                setCategory(response.data.data);

                //set data to state "places"
                setPlaces(response.data.data.places);

                //set title from state "category"
                document.title = `Category : ${response.data.data.name} - Website Wisata Berbasis GIS (Geographic Information System)`;

            })
    }

    //hook
    useEffect(() => {

        //call function "fetchDataCategory"
        fetchDataCategory();
            
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [slug]);

    return (
        <React.Fragment>
            <LayoutWeb>
                <div className="container mt-80">
                    <div className="row">
                        <div className="col-md-12">
                            <h4>CATEGORY : <strong className="text-uppercase">{category.name}</strong></h4>
                            <hr />
                        </div>
                        {
                            places.length > 0
                                ? places.map((place) => (
                                    <CardPlace
                                        key={place.id}
                                        id={place.id}
                                        slug={place.slug}
                                        title={place.title}
                                        images={place.images}
                                        address={place.address}
                                    />
                                ))
                                : <div className="alert alert-danger border-0 rounded shadow-sm" role="alert">
                                    <strong>Opps...!</strong> Data Belum Tersedia!.
                                  </div>
                        }
                    </div>
                </div>
            </LayoutWeb>
        </React.Fragment>
    )

}

export default WebCategoryShow;

Dari perubahan kode di atas, pertama-tama kita import hook dari react, yaitu useState dan useEffect.

//import hook react
import React, { useEffect, useState } from "react";

Setelah itu, kita juga import hook useParams dari React Router DOM. Ini akan kita gunakan salah satunya untuk mengambil nilai parameter slug dari URL browser.

//import hook useParams react router dom
import { useParams } from "react-router-dom";

Karena akan melakukan HTTP request, maka kita perlu import konfigurasi API yang sebelumnya sudah kita buat.

//import BASE URL API
import Api from "../../../api";

Kemudian kita import component CardPlace, karena akan kita gunakan untuk menampilkan data place-nya.

//import card place component
import CardPlace from "../../../components/utilities/CardPlace";

Di dalam function component WebCategoryShow pertama-tama kita membuat 2 state baru, yaitu category dan places.

const [category, setCategory] = useState({});
const [places, setPlaces] = useState([]);

Untuk state category akan kita gunakan untuk menyimpan object dari detail data category. Sedangkan state places akan kita gunakan untuk menympan array data places berdasarkan category.

Kemudian kita buat object slug dari hook useParams, tujuannya agar mempermudah kita dalam memanggil hook tersebut.

//get params from url
const { slug } = useParams();

Dan kita buat function baru yang bernama fetchDataCategory dengan jenis asynchronus.

//function "fetchDataCategory"
const fetchDataCategory = async () => {

	//...
	
}

Di dalam function tersebut kita melakukan HTTP request ke dalam endpoint dengan menambahkan parameter slug.

//fetching Rest API
await Api.get(`/api/web/categories/${slug}`)

Jika data berhasil didapatkan dari server, maka kita tinggal melakukan assign ke dalam state category dan places.

//set data to state "category"
setCategory(response.data.data);

//set data to state "places"
setPlaces(response.data.data.places);

Kita juga melakukan konfigurasi title halaman dengan data category yang sedang dibuka, dengan tujuan agar title halaman menjadi dinamis sesuai dengan data yang dibuka.

//set title from state "category"
document.title = `Category : ${response.data.data.name} - Website Wisata Berbasis GIS (Geographic Information System)`;

Agar function fetchDataCategory dapat dijalankan, maka kita perlu memanggilnya di dalam hook useEffect.

//hook
useEffect(() => {

    //call function "fetchDataCategory"
    fetchDataCategory();

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, [slug]);

Kita tambahkan [slug], dengan tujuan jika slug mengalami perubahan, maka akan melakukan mounting ulang atau menjalankan hook useEffect lagi dengan data yang baru.

Dan di dalam JSX, untuk menampilkan nama category, kita menggunakan kode seperti berikut ini :

<h4>CATEGORY : <strong className="text-uppercase">{category.name}</strong></h4>

Dan untuk data places kita akan looping menggunakan method map. Sebelum itu, kita akan melakukan sebuah kondisi untuk memastikan apakah data places tersedia atau tidak.

places.length > 0

	? //perulangan data places
	
	: //data tidak tersedia

Untuk perulangan data places, kita arahkan ke dalam component CardPlace dengan mengirimkan beberapa data sebagai props. Seperti key, id, slug, title, image dan address.

<CardPlace
    key={place.id}
    id={place.id}
    slug={place.slug}
    title={place.title}
    images={place.images}
    address={place.address}
/>

Langkah 3 - Uji Coba Menampilkan Data Places Berdasarkan Category

Sekarang, silahkan buka salah satu category yang sudah memiliki data places, maka kurang lebih hasilnya seperti berikut ini :

Tapi, jika data category tersebut belum memiliki data places, maka akan menampilkan pesan seperti berikut ini :

Konfigurasi Route untuk Halaman Places


Setelah berhasil menampilkan data places berdasarkan category, maka sekarang kita akan lanjutkan membuat konfigurasi route untuk menampilkan halaman index data places. Seperti sebelum-sebelumnya, kita akan membuat view/component-nya terlebih dahulu.

Langkah 1 - Membuat View/Component Places Index

Silahkan buat folder baru dengan nama places di dalam folder src/pages/web, kemudian di dalam folder places, silahkan buat file baru dengan nama Index.jsx dan masukkan kode berikut ini di dalamnya.

//import react
import React from "react";

//import layout web
import LayoutWeb from "../../../layouts/Web";

function WebPlacesIndex() {

    return(
        <React.Fragment>
            <LayoutWeb>
                <div className="container mt-80">
                    Halaman Places Index
                </div>
            </LayoutWeb>
        </React.Fragment>
    )

}

export default WebPlacesIndex;

Di atas, kita hanya memberikan sample kode saja, karena nanti kita akan ubah lagi dimateri-materi selanjutnya.

Langkah 2 - Konfigurasi Route Places Index

Setelah berhasil membuat view/component places index, langkah selanjutnya adalah melakukan konfigurasi route untuk menampilkan halamannya.

Silahkan buka file src/routes/routes.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import react router dom
import { Routes, Route } from "react-router-dom";

//=======================================================================
//ADMIN
//=======================================================================

//import view Login
import Login from '../pages/admin/Login.jsx';

//import component private routes
import PrivateRoute from "./PrivateRoutes";

//import view admin Dashboard
import Dashboard from '../pages/admin/dashboard/Index.jsx';

//import view admin categories Index
import CategoriesIndex from '../pages/admin/categories/Index.jsx';

//import view admin category Create
import CategoryCreate from '../pages/admin/categories/Create.jsx';

//import view admin category Edit
import CategoryEdit from '../pages/admin/categories/Edit.jsx';

//import view admin places Index
import PlacesIndex from '../pages/admin/places/Index.jsx';

//import view admin places Create
import PlaceCreate from '../pages/admin/places/Create.jsx';

//import view admin places Edit
import PlaceEdit from '../pages/admin/places/Edit.jsx';

//import view admin sliders Index
import SlidersIndex from '../pages/admin/sliders/Index.jsx';

//import view admin slider Create
import SliderCreate from '../pages/admin/sliders/Create.jsx';

//import view admin users Index
import UsersIndex from '../pages/admin/users/Index.jsx';

//import view admin user Create
import UserCreate from '../pages/admin/users/Create.jsx';

//import view admin user Edit
import UserEdit from '../pages/admin/users/Edit.jsx';

//=======================================================================
//WEB
//=======================================================================

//import view web Home
import Home from '../pages/web/home/Index.jsx';

//import view web category Show
import WebCategoryShow from '../pages/web/categories/Show.jsx';

//import view web place Index
import WebPlacesIndex from '../pages/web/places/Index.jsx';

function RoutesIndex() {
    return (
        <Routes>

            {/* route "/admin/login" */}
            <Route path="/admin/login" element={<Login />} />

            {/* private route "/admin/dashboard" */}
            <Route
                path="/admin/dashboard"
                element={
                        <PrivateRoute>
                            <Dashboard />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories" */}
            <Route
                path="/admin/categories"
                element={
                        <PrivateRoute>
                            <CategoriesIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories/create" */}
            <Route
                path="/admin/categories/create"
                element={
                        <PrivateRoute>
                            <CategoryCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories/edit/:id" */}
            <Route
                path="/admin/categories/edit/:id"
                element={
                        <PrivateRoute>
                            <CategoryEdit />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places" */}
            <Route
                path="/admin/places"
                element={
                        <PrivateRoute>
                            <PlacesIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places/create" */}
            <Route
                path="/admin/places/create"
                element={
                        <PrivateRoute>
                            <PlaceCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places/edit/:id" */}
            <Route
                path="/admin/places/edit/:id"
                element={
                        <PrivateRoute>
                            <PlaceEdit />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/sliders" */}
            <Route
                path="/admin/sliders"
                element={
                        <PrivateRoute>
                            <SlidersIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/sliders/create" */}
            <Route
                path="/admin/sliders/create"
                element={
                        <PrivateRoute>
                            <SliderCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users" */}
            <Route
                path="/admin/users"
                element={
                        <PrivateRoute>
                            <UsersIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users/create" */}
            <Route
                path="/admin/users/create"
                element={
                        <PrivateRoute>
                            <UserCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users/edit/:id" */}
            <Route
                path="/admin/users/edit/:id"
                element={
                        <PrivateRoute>
                            <UserEdit />
                        </PrivateRoute>
                }
            />

            {/* route "/" */}
            <Route path="/" element={<Home />} />

            {/* route "/category/:slug" */}
            <Route path="/category/:slug" element={<WebCategoryShow />} />

            {/* route "/places" */}
            <Route path="/places" element={<WebPlacesIndex />} />

        </Routes>
    )
}

export default RoutesIndex

Dari perubahan kode di atas, pertama kita import view/component places index yang sudah kita buat sebelumnya.

//import view web place Index
import WebPlaceIndex from '../pages/web/places/Index.jsx';

Setelah itu, kita buat konfigurasi route-nya dengan path /places, yang artinya jika URL tersebut diakses, maka akan memanggil view/component yang bernama <WebPlacesIndex />.

Sekarang, jika menu PLACES di navbar kita klik atau ke URL berikut ini http://localhost:5173/places. Maka jika berhasil akan menampilkan halaman seperti berikut ini :

Menampilkan Halaman Places


Setelah berhasil membuat konfigurasi route untuk menampilkan halaman places index, sekarang kita akan lanjutkan belajar bagaimana menampilkan data places di halaman tersebut dan kita juga akan belajar tentang pagination untuk membatasi jumlah data yang ditampilkan setiap halaman.

Langkah 1 - Edit View/Component Places Index

Sekarang kita akan belajar untuk menampilkan data places dari server ke dalam React.js menggunakan Rest API dan kita akan melakukan perubahan di dalam file yang sudah pernah kita buat sebelumnya.

Silahkan buka file src/pages/web/places/Index.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import hook from react
import React, { useEffect, useState } from "react";

//import layout web
import LayoutWeb from "../../../layouts/Web";

//import BASE URL API
import Api from "../../../api";

//import card place component
import CardPlace from "../../../components/utilities/CardPlace";

//import pagination component
import PaginationComponent from "../../../components/utilities/Pagination";

function WebPlacesIndex() {

    //title page
    document.title = "Places - TRAVEL GIS - Website Wisata Berbasis GIS (Geographic Information System)";

    //state places
    const [places, setPlaces] = useState([]);

    //state currentPage
    const [currentPage, setCurrentPage] = useState(1);

    //state perPage
    const [perPage, setPerPage] = useState(0);

    //state total
    const [total, setTotal] = useState(0);

    //function "fetchDataPlaces"
    const fetchDataPlaces = async (pageNumber) => {

        //define variable "page"
        const page = pageNumber ? pageNumber : currentPage;

        //fetching Rest API
        await Api.get(`/api/web/places?page=${page}`)
            .then((response) => {

                //set data to state "places"
                setPlaces(response.data.data.data);

                //set currentPage
                setCurrentPage(response.data.data.current_page);

                //set perPage
                setPerPage(response.data.data.per_page);

                //total
                setTotal(response.data.data.total);
            })
    }

    //hook
    useEffect(() => {

        //call function "fetchDataPlaces"
        fetchDataPlaces();

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
        <React.Fragment>
            <LayoutWeb>
                <div className="container mt-80">
                    <div className="row">
                        {
                            places.length > 0
                                ? places.map((place) => (
                                    <CardPlace
                                        key={place.id}
                                        id={place.id}
                                        slug={place.slug}
                                        title={place.title}
                                        images={place.images}
                                        address={place.address}
                                    />
                                ))
                                : <div className="alert alert-danger border-0 rounded shadow-sm" role="alert">
                                    <strong>Opps...!</strong> Data Belum Tersedia!.
                                  </div>
                        }
                    </div>
                    <PaginationComponent 
                        currentPage={currentPage} 
                        perPage={perPage} 
                        total={total} 
                        onChange={(pageNumber) => fetchDataPlaces(pageNumber)}
                        position="center"
                    />
                </div>
            </LayoutWeb>
        </React.Fragment>
    )

}

export default WebPlacesIndex;

Dari perubahan kode di atas, pertama kita import 2 hook dari react, yaitu useState dan useEffect.

//import hook from react
import React, { useEffect, useState } from "react";

Setelah itu, kita import layout web untuk kita gunakan sebagai induk template dari halaman ini. Dimana kode yang akan kita tulis nanti akan berada di dalam layout tersebut.

//import layout web
import LayoutWeb from "../../../layouts/Web";

Karena akan melakukan HTTP request, maka kita butuh melakukan import konfigurasi API yang sudah pernah kita buat sebelumnya.

//import BASE URL API
import Api from "../../../api";

Dan kita import 2 component yang sudah kita buat sendiri, yaitu CardPlace dan Pagination.

//import card place component
import CardPlace from "../../../components/utilities/CardPlace";

//import pagination component
import PaginationComponent from "../../../components/utilities/Pagination";

Di dalam function component WebPlacesIndex, pertama-tama kita melakukan konfigurasi untuk title dari halaman ini.

//title page
document.title = "Places - TRAVEL GIS - Website Wisata Berbasis GIS (Geographic Information System)";

Setelah itu, kita membuat 4 state baru yang nanti akan kita gunakan untuk menyimpan data yang di dapatkan dari Rest API, seperti data places, currentPage, perPage, dan total.

//state places
const [places, setPlaces] = useState([]);

//state currentPage
const [currentPage, setCurrentPage] = useState(1);

//state perPage
const [perPage, setPerPage] = useState(0);

//state total
const [total, setTotal] = useState(0);

Kemudian kita buat function baru yang bernama fetchDataPlaces dengan jenis asynchronus dan memiliki parameter yang bernama pageNumber.

//function "fetchDataPlaces"
const fetchDataPlaces = async (pageNumber) => {

	//...
	
}

Parameter pageNumber akan kita gunakan untuk menerima nilai page yang nantinya kita kirimkan ke server untuk mendapatkan data berdasarkan page tersebut (pagination).

Di dalam function fetchDataPlaces kita buat variable baru yang bernama page yang isinya adalah sebuah kondisi menggunakan ternary operator. Dimana jika pageNumber memiliki value, maka value tersebut yang digunakan. Tapi jika tidak, maka akan mengambil dari state yang bernama currentPage.

//define variable "page"
const page = pageNumber ? pageNumber : currentPage;

Setelah itu, kita lakukan fetching ke dalam server dengan menambahkan variable page tersebut ke dalam endpoint. Tujuannya agar melakukan fetching berdasarkan page itu sendiri.

//fetching Rest API
await Api.get(`/api/web/places?page=${page}`)

Jika proses fetching berhasil dilakukan, maka akan melakukan assign ke dalam 4 state yang sudah kita buat sebelumnya.

//set data to state "places"
setPlaces(response.data.data.data);

//set currentPage
setCurrentPage(response.data.data.current_page);

//set perPage
setPerPage(response.data.data.per_page);

//total
setTotal(response.data.data.total);

Dan agar function fetchDataPlaces dapat dijalankan secara otomatis ketika halaman diakses, maka kita perlu memanggilnya di dalam hook useEffect.

//hook
useEffect(() => {

    //call function "fetchDataPlaces"
    fetchDataPlaces();

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Setelah data berhasil disimpan di dalam state, langkah selanjutnya adalah menampilkan di dalam JSX menggunakan perulangan map. Sebelum itu, kita akan cek terlebih dahulu apakah datanya ada atau tidak di dalam state.

places.length > 0
	? //data tersedia, tampilkan dengan perulangan map
	
	: //data belum tersedia

Di dalam perulangan, kita panggil component CardPlace dengan mengirimkan beberapa data sebagai props, seperti key, id, slug, title, image dan address.

<CardPlace
    key={place.id}
    id={place.id}
    slug={place.slug}
    title={place.title}
    images={place.images}
    address={place.address}
/>

Dan untuk menampilkan navigasi pagination, kita juga memanggil component Pagination dengan mengirimkan beberapa data sebagai props.

<PaginationComponent 
     currentPage={currentPage} 
     perPage={perPage} 
     total={total} 
     onChange={(pageNumber) => fetchDataPlaces(pageNumber)}
     position="center"
/>

Langkah 2 - Uji Coba Halaman Places Index

Sekarang silahkan klik menu PLACES yang ada di navbar, atau bisa ke URL berikut ini http://localhost:5173/places, jika berhasil maka akan mendapatkan hasil yang kurang lebih seperti ini :

Di atas, kita telah berhasil menampilkan data places beserta navigasi untuk pagination.

Konfigurasi Route untuk Halaman Detail Place


Dimateri sebelumnya kita telah berhasil menampilkan data places beserta pagination-nya. Sekarang kita akan lanjutkan belajar membuat konfigurasi route untuk menampilkan halaman detail place.

Di dalam halaman detail place, nantinya kita akan menampilkan beberapa data, seperti gambar slider, maps dan informasi place itu sendiri.

Sebelum kita membuat konfigurasi route-nya, tentu saja kita akan membuat file view/component detail place terlebih dahulu.

Langkah 1 - Membuat View/Component Place Show

Silahkan buat file baru dengan nama Show.jsx di dalam folder src/pages/web/places, kemudian masukkan kode berikut ini di dalam file tersebut.

//import react
import React from "react";

//import layout web
import LayoutWeb from "../../../layouts/Web";

function WebPlacesShow() {

    return(
        <React.Fragment>
            <LayoutWeb>
                <div className="container mt-80">
                    Halaman Places Show
                </div>
            </LayoutWeb>
        </React.Fragment>
    )

}

export default WebPlacesShow;

Di atas, kita berikan sedikit kode sebagai sample, karena kita akan ubah lagi dimateri-materi berikutnya sesuai dengan kebutuhan kita.

Langkah 2 - Konfigurasi Route Place Show

Setelah berhasil membuat file view/component place show, sekarang kita lanjutkan untuk membuat konfigurasi route-nya. Silahkan buka file src/routes/routes.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import react router dom
import { Routes, Route } from "react-router-dom";

//=======================================================================
//ADMIN
//=======================================================================

//import view Login
import Login from '../pages/admin/Login.jsx';

//import component private routes
import PrivateRoute from "./PrivateRoutes";

//import view admin Dashboard
import Dashboard from '../pages/admin/dashboard/Index.jsx';

//import view admin categories Index
import CategoriesIndex from '../pages/admin/categories/Index.jsx';

//import view admin category Create
import CategoryCreate from '../pages/admin/categories/Create.jsx';

//import view admin category Edit
import CategoryEdit from '../pages/admin/categories/Edit.jsx';

//import view admin places Index
import PlacesIndex from '../pages/admin/places/Index.jsx';

//import view admin places Create
import PlaceCreate from '../pages/admin/places/Create.jsx';

//import view admin places Edit
import PlaceEdit from '../pages/admin/places/Edit.jsx';

//import view admin sliders Index
import SlidersIndex from '../pages/admin/sliders/Index.jsx';

//import view admin slider Create
import SliderCreate from '../pages/admin/sliders/Create.jsx';

//import view admin users Index
import UsersIndex from '../pages/admin/users/Index.jsx';

//import view admin user Create
import UserCreate from '../pages/admin/users/Create.jsx';

//import view admin user Edit
import UserEdit from '../pages/admin/users/Edit.jsx';

//=======================================================================
//WEB
//=======================================================================

//import view web Home
import Home from '../pages/web/home/Index.jsx';

//import view web category Show
import WebCategoryShow from '../pages/web/categories/Show.jsx';

//import view web place Index
import WebPlacesIndex from "../pages/web/places/Index.jsx";

//import view web place Show
import WebPlaceShow from '../pages/web/places/Show.jsx';

function RoutesIndex() {
    return (
        <Routes>

            {/* route "/admin/login" */}
            <Route path="/admin/login" element={<Login />} />

            {/* private route "/admin/dashboard" */}
            <Route
                path="/admin/dashboard"
                element={
                        <PrivateRoute>
                            <Dashboard />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories" */}
            <Route
                path="/admin/categories"
                element={
                        <PrivateRoute>
                            <CategoriesIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories/create" */}
            <Route
                path="/admin/categories/create"
                element={
                        <PrivateRoute>
                            <CategoryCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/categories/edit/:id" */}
            <Route
                path="/admin/categories/edit/:id"
                element={
                        <PrivateRoute>
                            <CategoryEdit />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places" */}
            <Route
                path="/admin/places"
                element={
                        <PrivateRoute>
                            <PlacesIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places/create" */}
            <Route
                path="/admin/places/create"
                element={
                        <PrivateRoute>
                            <PlaceCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/places/edit/:id" */}
            <Route
                path="/admin/places/edit/:id"
                element={
                        <PrivateRoute>
                            <PlaceEdit />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/sliders" */}
            <Route
                path="/admin/sliders"
                element={
                        <PrivateRoute>
                            <SlidersIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/sliders/create" */}
            <Route
                path="/admin/sliders/create"
                element={
                        <PrivateRoute>
                            <SliderCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users" */}
            <Route
                path="/admin/users"
                element={
                        <PrivateRoute>
                            <UsersIndex />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users/create" */}
            <Route
                path="/admin/users/create"
                element={
                        <PrivateRoute>
                            <UserCreate />
                        </PrivateRoute>
                }
            />

            {/* private route "/admin/users/edit/:id" */}
            <Route
                path="/admin/users/edit/:id"
                element={
                        <PrivateRoute>
                            <UserEdit />
                        </PrivateRoute>
                }
            />

            {/* route "/" */}
            <Route path="/" element={<Home />} />

            {/* route "/category/:slug" */}
            <Route path="/category/:slug" element={<WebCategoryShow />} />

            {/* route "/places" */}
            <Route path="/places" element={<WebPlacesIndex />} />

            {/* route "/places/:slug" */}
            <Route path="/places/:slug" element={<WebPlaceShow />} />

        </Routes>
    )
}

export default RoutesIndex

Dari penambahan kode di atas, pertama kita import file view/component place show yang sudah kita buat sebelumnya.

//import view web place Show
import WebPlaceShow from '../pages/web/places/Show.jsx';

Setelah itu, kita buat konfigurasi route-nya dengan path /places/:slug, dimana nilai slug akan dinamis sesuai dengan data yang dibuka.

Dan jika URL /places/:slug diakses, maka view/component <WebPlaceShow /> akan dieksekusi dan ditampilkan ke dalam web browser.

Sekarang, silahkan klik salah satu data place yang ada, jika berhasil maka kita akan diarahkan ke dalam halaman detail place, yang kurang lebih seperti berikut ini :

Menampilkan Halaman Detail Data Place


Setelah berhasil membuat konfigurasi route untuk menampilkan halaman detail place, maka selanjutnya kita akan belajar untuk menampilkan data place di halaman tersebut. Data tersebut seperti title, address, website, description dan lain-lain.

Langkah 1 - Edit View/Component Place Show

Sekarang kita akan melakukan perubahan di dalam file view/component place show. Silahkan buka file src/pages/web/places/Show.jsx dan ubah semua kode-nya menjadi seperti berikut ini :

//import hook react
import React, { useEffect, useState, } from "react";

//import react router dom
import { Link, useParams } from "react-router-dom";

//import layout web
import LayoutWeb from "../../../layouts/Web";

//import BASE URL API
import Api from "../../../api";

function WebPlaceShow() {

    //state place
    const [place, setPlace] = useState({});

    //slug params
    const { slug } = useParams();

    //function "fetchDataPlace"
    const fetchDataPlace = async () => {
        //fetching Rest API
        await Api.get(`/api/web/places/${slug}`)
        .then((response) => {

            //set data to state "places"
            setPlace(response.data.data);

            //set title from state "category"
            document.title = `${response.data.data.title} - Website Wisata Berbasis GIS (Geographic Information System)`;
        });
    };

    //hook
    useEffect(() => {
        //call function "fetchDataPlace"
        fetchDataPlace();

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
        <React.Fragment>
          <LayoutWeb>
            <div className="container mt-80">
              <div className="row">
                <div className="col-md-7 mb-4">
                  <div className="card border-0 rounded shadow-sm">
                    <div className="card-body">
                      <h4>{place.title}</h4>
                      <span className="card-text">
                        <i className="fa fa-map-marker"></i> <i>{place.address}</i>
                      </span>
                      <hr />
                      <div
                        dangerouslySetInnerHTML={{ __html: place.description }}
                      />
                    </div>
                  </div>
                </div>
                <div className="col-md-5 mb-4">
                  <div className="card border-0 rounded shadow-sm">
                    <div className="card-body">
                      <h5>
                        <i className="fa fa-map-marked-alt"></i> MAPS
                      </h5>
                      <hr />
                    </div>
                    <hr />
                    <div className="card-body">
                      <div className="row">
                        <div className="col-md-2 col-2">
                          <div className="icon-info-green">
                            <i className="fa fa-map-marker-alt"></i>
                          </div>
                        </div>
                        <div className="col-md-10 col-10">
                            <div className="capt-info fw-bold">ADDRESS</div>
                            <div className="sub-title-info"><i>{place.address}</i></div>
                        </div>
                        <div className="col-md-2 col-2">
                          <div className="icon-info-green">
                            <i className="fa fa-clock"></i>
                          </div>
                        </div>
                        <div className="col-md-10 col-10">
                          <div className="capt-info fw-bold">OFFICE HOURS</div>
                          <div className="sub-title-info">{place.office_hours}</div>
                        </div>
                        <div className="col-md-2 col-2">
                          <div className="icon-info-green">
                            <i className="fa fa-phone"></i>
                          </div>
                        </div>
                        <div className="col-md-10 col-10">
                          <div className="capt-info fw-bold">PHONE</div>
                          <div className="sub-title-info">{place.phone}</div>
                        </div>
                        <div className="col-md-2 col-2">
                          <div className="icon-info-green">
                            <i className="fa fa-globe-asia"></i>
                          </div>
                        </div>
                        <div className="col-md-10 col-10">
                          <div className="capt-info fw-bold">WEBSITE</div>
                          <div className="sub-title-info">
                            <a href={place.website} className="text-decoration-none">{place.website}</a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </LayoutWeb>
        </React.Fragment>
    );
}

export default WebPlaceShow;

Dari perubahan kode di atas, pertama kita import hook dari react, yaitu useState dan useEffect.

//import hook react
import React, { useEffect, useState, } from "react";

Kemudian kita import provider Link dan hook useParams dari React Router DOM.

//import react router dom
import { Link, useParams } from "react-router-dom";

Dan kita import layout web, karena halaman ini akan menggunakan induk template dari layout tersebut.

//import layout web
import LayoutWeb from "../../../layouts/Web";

Karena akan melakukan HTTP request, maka kita harus import konfigurasi API yang sudah kita buat sebelumnya.

//import BASE URL API
import Api from "../../../api";

Dan di dalam function component WebPlaceShow pertama-tama kita buat state baru yang bernama place. Dimana state tersebut yang nanti akan kita gunakan untuk menyimpan object dari data place yang di dapatkan dari Rest API.

//state place
const [place, setPlace] = useState({});

Kemudian kita buat object slug dari hook useParams, tujuannya agar mempermudah kita dalam memanggil hook tersebut.

//slug params
const { slug } = useParams();

Setelah itu, kita buat function baru yang bernama fetchDataPlace dengan jenis asynchronus.

//function "fetchDataPlace"
const fetchDataPlace = async () => {

	//...
	
}

Di dalam function tersebut, kita melakukan HTTP request ke dalam server dengan endpoint Rest API /api/web/places/:slug. Dimana nilai slug akan dinamis diambil dari parameter di web browser.

//fetching Rest API
await Api.get(`/api/web/places/${slug}`)

Jika proses fetching berhasil dilakukan, maka kita akan melakukan assign response data yang berupa object ke dalam state place.

//set data to state "places"
setPlace(response.data.data);

Dan kita juga atur untuk title halaman menggunakan data yang di dapatkan dari Rest API.

//set title from state "category"
document.title = `${response.data.data.title} - Website Wisata Berbasis GIS (Geographic Information System)`;

Agar function fetchDataPlace dapat dijalankan saat halaman diakses, maka kita perlu memanggilnya di dalam hook usseEffect.

//hook
useEffect(() => {
    //call function "fetchDataPlace"
    fetchDataPlace();

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Dan untuk menampilkan data di dalam JSX, kurang lebih seperti berikut ini :

{place.title}

{place.address}

{place.office_hours}

{place.phone}

{place.website}

Dan karena isi dari description berupa sintaks HTML, maka untuk menampilkannya kurang lebih seperti berikut ini :

div dangerouslySetInnerHTML={{ __html: place.description }} />

Langkah 2 - Uji Coba Menampilkan Detail Place

Silahkan klik salah satu data place yang ada dan jika berhasil maka akan menampilkan informasi detail place yang kurang lebih seperti berikut ini :

Beranda Mundur Maju