Tutorial Inertia Roles & Permissions - #13 - Membuat Module Role Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah module role dengan inertia react.

Rafi Taufiqurrahman
Dipublish 19/08/2024

Pendahuluan

Pada artikel sebelumnya kita telah berhasil membuat module permissions, dan pada artikel kali ini kita akan lajutkan untuk melakukan pembuatan module roles.

Membuat Controller Roles

Silahkan teman - teman buka terminal-nya, kemudian jalankan perintah berikut ini :

Terminal
php artisan make:controller RoleController -r

Jika perintah diatas berhasil dijalankan, maka kita akan mendapatkan sebuah file yang terletak di app/Http/Controllers dengan nama RoleController.php.

Silahkan buka file tersebut, kemudian ubah semua kodenya sebagai berikut.

RoleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

class RoleController extends Controller implements HasMiddleware
{
    public static function middleware()
    {
        return [
            new Middleware('permission:roles-data', only: ['index']),
            new Middleware('permission:roles-create', only: ['create', 'store']),
            new Middleware('permission:roles-update', only: ['edit', 'update']),
            new Middleware('permission:roles-delete', only: ['destroy']),
        ];
    }

    /**
     * Display a listing of the resource.
     */
    public function index(Request $request)
    {
        // get roles
        $roles = Role::select('id', 'name')
            ->with('permissions:id,name')
            ->when($request->search,fn($search) => $search->where('name', 'like', '%'.$request->search.'%'))
            ->latest()
            ->paginate(6);

        // render view
        return inertia('Roles/Index', ['roles' => $roles]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        // get permissions
        $permissions = Permission::all();

        // render view
        return inertia('Roles/Create', ['permissions' => $permissions]);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        // validate request
        $request->validate([
            'name' => 'required|min:3|max:255|unique:roles',
            'selectedPermissions' => 'required|array|min:1',
        ]);

        // create new role data
        $role = Role::create(['name' => $request->name]);

        // give permissions to role
        $role->givePermissionTo($request->selectedPermissions);

        // render view
        return to_route('roles.index');
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(Role $role)
    {
        // get permissions
        $permissions = Permission::all();

        // load permissions
        $role->load('permissions');

        // render view
        return inertia('Roles/Edit', ['role' => $role, 'permissions' => $permissions]);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Role $role)
    {
        // validate request
        $request->validate([
            'name' => 'required|min:3|max:255|unique:roles,name,'.$role->id,
            'selectedPermissions' => 'required|array|min:1',
        ]);

        // update role data
        $role->update(['name' => $request->name]);

        // give permissions to role
        $role->syncPermissions($request->selectedPermissions);

        // render view
        return to_route('roles.index');
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Role $role)
    {
        // delete role data
        $role->delete();

        // render view
        return back();
    }
}

Didalam class RoleController kita memiliki 7 method yang kita generate diawal dengan perintah.

Terminal
php artisan make:controller RoleController -r

-r disini maksudnya adalah resources , jadi kita membuat RoleController dengan full resource sehingga kita mendapatkan 7 method sekaligus dengan satu perintah artisan, tapi disini kita hanya akan menggunakan 4 method yaitu :

  • index
  • create
  • store
  • edit
  • update
  • destroy

Dari kode diatas, pertama kita lakukan import model role dan permissions.

RoleController.php
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

Kemudian kita juga melakukan implementasi sebuah interface middleware terbaru yang telah disediakan oleh laravel dengan cara menambahkan kata kunci implements HasMiddleware pada controller kita, kemudian kita import agar tidak error.

RoleController.php
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

Selanjutnya kita bisa menggunakan sebuah method dari interface HasMiddleware seperti kode dibawah ini.

RoleController.php
public static function middleware()
{
    return [
        new Middleware('permission:roles-data', only: ['index']),
        new Middleware('permission:roles-create', only: ['create', 'store']),
        new Middleware('permission:roles-update', only: ['edit', 'update']),
        new Middleware('permission:roles-delete', only: ['destroy']),
    ];
}

Dari kode diatas kita menerapkan beberapa middleware yang berfungsi untuk melakukan pembatasan akses method - method yang telah kita generate sesuai dengan hak akses yang dimiliki oleh user.

Method Index

Method ini kita gunakan untuk menampilkan seluruh data roles yang ada didalam database.

RoleController.php
public function index(Request $request)
{
    // get roles
    $roles = Role::select('id', 'name')
        ->with('permissions:id,name')
        ->when($request->search,fn($search) => $search->where('name', 'like', '%'.$request->search.'%'))
        ->latest()
        ->paginate(6);

    // render view
    return inertia('Roles/Index', ['roles' => $roles]);
}

Pertama - tama kita membuat sebuah variabel roles yang kita gunakan untuk menampilkan sebuah data dari tabel roles disini kita juga menambah pemanggilan sebuah relasi ke tabel permissions menggunakan method with, selain menampilkan data, kita juga menambahkan sebuah pencarian data roles ketika method when dieksekusi. Method tersebut akan dieksekusi ketika kita mengirimkan sebuah request dengan nama search dan ketika kita mengirimkan request tersebut maka kita akan melakukan filter data roles menggunakan method where , selain itu kita juga melakukan pembatasan data yang ditampilkan perhalamanya hanya sebanyak 6 data menggunaakn method paginate dan yang terakhir kita urutkan datanya sesuai data yang paling baru menggunakan method latest.

RoleController.php
// get roles
$roles = Role::select('id', 'name')
    ->with('permissions:id,name')
    ->when($request->search,fn($search) => $search->where('name', 'like', '%'.$request->search.'%'))
    ->latest()
    ->paginate(6);

Kemudian data diatas, kita kirimkan ke sebuah view dengan nama Roles/Index.jsx, dalam bentuk props.

RoleController.php
// render view
return inertia('Roles/Index', ['roles' => $roles]);

Method Create

Method ini kita gunakan untuk menampilkan sebuah form untuk melakukan insert data baru kedalam tabel roles.

RoleController.php
public function create()
{
    // get permissions
    $permissions = Permission::all();

    // render view
    return inertia('Roles/Create', ['permissions' => $permissions]);
}

Kemudian kita juga memanggil semua data dari tabel permissions yang nantinya akan kita kirimkan ke view dalam bentuk props.

RoleController.php
$permissions = Permission::all();

Selanjutnya data diatas, kita kirimkan ke sebuah view dengan nama Roles/Create.jsx, dalam bentuk props.

RoleController.php
// render view
return inertia('Roles/Create', ['permissions' => $permissions]);

Method Store

Method ini kita gunakan untuk melakukan proses insert data baru kedalam tabel roles.

RoleController.php
public function store(Request $request)
{
    // validate request
    $request->validate([
        'name' => 'required|min:3|max:255|unique:roles',
        'selectedPermissions' => 'required|array|min:1',
    ]);

    // create new role data
    $role = Role::create(['name' => $request->name]);

    // give permissions to role
    $role->givePermissionTo($request->selectedPermissions);

    // render view
    return to_route('roles.index');
}

Pada kode diatas, pertama - tama kita melakukan validasi data terlebih dahulu sebelum melakukan insert data kedalam database menggunakan method validate.

RoleController.php
// validate request
$request->validate([
    'name' => 'required|min:3|max:255|unique:roles',
    'selectedPermissions' => 'required|array|min:1',
]);

Selanjutnya jika request yang kita kirimkan sudah sesuai dengan kriteria validasi yang kita definisikan, maka kita akan melakukan insert data baru kedalam tabel roles menggunakan method create.

RoleController.php
// create new role data
$role = Role::create(['name' => $request->name]);

Berikutnya ketika data role berhasil di insert, kita berikan dia sebuah permissions menggunakan method givePermissionTo.

RoleController.php
// give permissions to role
$role->givePermissionTo($request->selectedPermissions);

Setelah berhasil menambahkan data baru, kita akan diarahkan ke sebuah route roles.index.

RoleController.php
// render view
return to_route('roles.index');

Method Edit

Method ini kita gunakan untuk menampilkan sebuah form untuk melakukan proses update dataroles kita.

RoleController.php
public function edit(Role $role)
{
    // get permissions
    $permissions = Permission::all();

    // load permissions
    $role->load('permissions');

    // render view
    return inertia('Roles/Edit', ['role' => $role, 'permissions' => $permissions]);
}

Pada kode diatas, method edit kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel role merupakan dirinya sendiri.

RoleController.php
public function edit(Role $role)

Kemudian kita juga memanggil semua data dari tabel permissions yang nantinya akan kita kirimkan ke view dalam bentuk props.

RoleController.php
 $permissions = Permission::all();

Kita juga melakukan pemanggilan relasi menggunakan method load.

RoleController.php
$role->load('permissions');

Selanjutnya data-data diatas, kita kirimkan ke sebuah view dengan nama Roles/Edit, dalam bentuk props.

RoleController.php
return inertia('Roles/Edit', ['role' => $role, 'permissions' => $permissions]);

Method Update

Method ini kita gunakan untuk melakukan proses update data yang ada didalam tabel roles kita.

RoleController.php
public function update(Request $request, Role $role)
{
    // validate request
    $request->validate([
        'name' => 'required|min:3|max:255|unique:roles,name,'.$role->id,
        'selectedPermissions' => 'required|array|min:1',
    ]);

    // update role data
    $role->update(['name' => $request->name]);

    // give permissions to role
    $role->syncPermissions($request->selectedPermissions);

    // render view
    return to_route('roles.index');
}

Pada kode diatas, method update kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel $role merupakan dirinya sendiri.

RoleController.php
public function update(Request $request, Role $role)

Berikutnya kita definisikan sebuah validasi menggunakan method validate.

RoleController.php
// validate request
$request->validate([
    'name' => 'required|unique:roles,name,'. $role->id,
    'permissions' => 'required|array|min:1'
]);

Kemudian jika request yang kita kirimkan sudah sesuai dengan kriteria validasi yang kita definisikan, maka kita akan melakukan update data kedalam tabel roles menggunakan method update.

RoleController.php
// update role data by id
$role->update([
    'name' => $request->name
]);

Setelah data berhasil di update kita lakukan penyesuain permssions yang dimiliki oleh role tersebut menggunakan method syncPermissions.

RoleController.php
// give permissions to role
$role->syncPermissions($request->selectedPermissions);

Setelah berhasil melakukan update data, kita akan diarahkan ke sebuah route dengan nama roles.index.

RoleController.php
// render view
return to_route('roles.index');

Method Destroy

Method ini kita gunakan untuk melakukan proses penghapusan data role yang kita miliki.

RoleController.php
public function destroy(Role $role)
{
    // delete role data
    $role->delete();

    // render view
    return back();
}

Pada kode diatas, method destroy kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel $role merupakan dirinya sendiri.

RoleController.php
public function destroy(Role $role)

Selanjutnya kita lakukan penghapusan data menggunakan method delete.

RoleController.php
// delete role data
$role->delete();

Terakhir setelah data berhasil dihapus, kita akan diarahkan ke sebuah route roles.index.

RoleController.php
// render view
return back();

Membuat Route Roles

Setelah berhasil membuat sebuah controller Role, sekarang kita akan lanjutkan untuk pembuatan route-nya, silahkan teman - teman buka file routes/web.php, kemudian ubah kode-nya menjadi seperti berikut ini.

web.php
<?php

use Inertia\Inertia;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Application;
use App\Http\Controllers\PostController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\PermissionController;

Route::get('/', function () {
    return Inertia::render('Welcome', [
        'canLogin' => Route::has('login'),
        'canRegister' => Route::has('register'),
        'laravelVersion' => Application::VERSION,
        'phpVersion' => PHP_VERSION,
    ]);
});

Route::middleware('auth')->group(function () {
    // dashboard route
    Route::get('/dashboard', DashboardController::class)->name('dashboard');
    // posts route
    Route::resource('/posts', PostController::class)->only('index', 'store', 'update', 'destroy');
    // permissions route
    Route::resource('/permissions', PermissionController::class);
    // roles route
    Route::resource('roles', RoleController::class)->except('show');
    // profile route
    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');
});

require __DIR__.'/auth.php';

Dari perubahan kode diatas, kita menambahkan sebuah route baru dengan nama roles, untuk memastikan route yang kita buat telah berfungsi, teman - teman bisa jalankan.

Terminal
php artisan r:l --name=roles

Setelah perintah artisan diatas dijalankan, maka kita akan mendapatkan output, kurang lebih seperti berikut ini.

Terminal
  GET|HEAD        roles ........................................................................................ roles.index › RoleController@index
  POST            roles ........................................................................................ roles.store › RoleController@store
  GET|HEAD        roles/create ............................................................................... roles.create › RoleController@create
  PUT|PATCH       roles/{role} ............................................................................... roles.update › RoleController@update
  DELETE          roles/{role} ............................................................................. roles.destroy › RoleController@destroy
  GET|HEAD        roles/{role}/edit .............................................................................. roles.edit › RoleController@edit

Membuat Navigasi Roles

Silahkan teman - teman buka file yang bernama AuthenticatedLayout.jsx yang terletak di folder resources/js/layouts, kemudian ubah kodenya menjadi seperti berikut ini :

AuthenticatedLayout.jsx
import { useState } from 'react';
import ApplicationLogo from '@/Components/ApplicationLogo';
import Dropdown from '@/Components/Dropdown';
import NavLink from '@/Components/NavLink';
import ResponsiveNavLink from '@/Components/ResponsiveNavLink';
import { Link } from '@inertiajs/react';
import hasAnyPermission from '@/Utils/Permissions';

export default function Authenticated({ user, header, children }) {
    const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);

    return (
        <div className="min-h-screen bg-gray-100">
            <nav className="bg-white border-b border-gray-100">
                <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                    <div className="flex justify-between h-16">
                        <div className="flex">
                            <div className="shrink-0 flex items-center">
                                <Link href="/">
                                    <ApplicationLogo className="block h-9 w-auto fill-current text-gray-800" />
                                </Link>
                            </div>

                            <div className="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
                                <NavLink href={route('dashboard')} active={route().current('dashboard')}>
                                    Dashboard
                                </NavLink>
                                {hasAnyPermission(['posts-access']) &&
                                    <NavLink href={route('posts.index')} active={route().current('posts*')}>
                                        Posts
                                    </NavLink>
                                }
                                {hasAnyPermission(['permissions-access']) &&
                                    <NavLink href={route('permissions.index')} active={route().current('permissions*')}>
                                        Permissions
                                    </NavLink>
                                }
                                {hasAnyPermission(['roles-access']) &&
                                    <NavLink href={route('roles.index')} active={route().current('roles*')}>
                                        Roles
                                    </NavLink>
                                }
                            </div>
                        </div>

                        <div className="hidden sm:flex sm:items-center sm:ms-6">
                            <div className="ms-3 relative">
                                <Dropdown>
                                    <Dropdown.Trigger>
                                        <span className="inline-flex rounded-md">
                                            <button
                                                type="button"
                                                className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
                                            >
                                                {user.name}

                                                <svg
                                                    className="ms-2 -me-0.5 h-4 w-4"
                                                    xmlns="http://www.w3.org/2000/svg"
                                                    viewBox="0 0 20 20"
                                                    fill="currentColor"
                                                >
                                                    <path
                                                        fillRule="evenodd"
                                                        d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
                                                        clipRule="evenodd"
                                                    />
                                                </svg>
                                            </button>
                                        </span>
                                    </Dropdown.Trigger>

                                    <Dropdown.Content>
                                        <Dropdown.Link href={route('profile.edit')}>Profile</Dropdown.Link>
                                        <Dropdown.Link href={route('logout')} method="post" as="button">
                                            Log Out
                                        </Dropdown.Link>
                                    </Dropdown.Content>
                                </Dropdown>
                            </div>
                        </div>

                        <div className="-me-2 flex items-center sm:hidden">
                            <button
                                onClick={() => setShowingNavigationDropdown((previousState) => !previousState)}
                                className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
                            >
                                <svg className="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                                    <path
                                        className={!showingNavigationDropdown ? 'inline-flex' : 'hidden'}
                                        strokeLinecap="round"
                                        strokeLinejoin="round"
                                        strokeWidth="2"
                                        d="M4 6h16M4 12h16M4 18h16"
                                    />
                                    <path
                                        className={showingNavigationDropdown ? 'inline-flex' : 'hidden'}
                                        strokeLinecap="round"
                                        strokeLinejoin="round"
                                        strokeWidth="2"
                                        d="M6 18L18 6M6 6l12 12"
                                    />
                                </svg>
                            </button>
                        </div>
                    </div>
                </div>

                <div className={(showingNavigationDropdown ? 'block' : 'hidden') + ' sm:hidden'}>
                    <div className="pt-2 pb-3 space-y-1">
                        <ResponsiveNavLink href={route('dashboard')} active={route().current('dashboard')}>
                            Dashboard
                        </ResponsiveNavLink>
                        {hasAnyPermission(['posts-access']) &&
                            <ResponsiveNavLink href={route('posts.index')} active={route().current('posts*')}>
                                Posts
                            </ResponsiveNavLink>
                        }
                        {hasAnyPermission(['permissions-access']) &&
                            <ResponsiveNavLink href={route('permissions.index')} active={route().current('permissions*')}>
                                Permissions
                            </ResponsiveNavLink>
                        }
                        {hasAnyPermission(['roles-access']) &&
                            <ResponsiveNavLink href={route('roles.index')} active={route().current('roles*')}>
                                Roles
                            </ResponsiveNavLink>
                        }
                    </div>

                    <div className="pt-4 pb-1 border-t border-gray-200">
                        <div className="px-4">
                            <div className="font-medium text-base text-gray-800">{user.name}</div>
                            <div className="font-medium text-sm text-gray-500">{user.email}</div>
                        </div>

                        <div className="mt-3 space-y-1">
                            <ResponsiveNavLink href={route('profile.edit')}>Profile</ResponsiveNavLink>
                            <ResponsiveNavLink method="post" href={route('logout')} as="button">
                                Log Out
                            </ResponsiveNavLink>
                        </div>
                    </div>
                </div>
            </nav>

            {header && (
                <header className="bg-white shadow">
                    <div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">{header}</div>
                </header>
            )}

            <main>{children}</main>
        </div>
    );
}

Pada kode diatas, kita memanggil 2 buah component dengan nama NavLink dan ResponsiveNavLink dengan props href yang kita set ke route yang bernama roles.index dan props active kita set ke semua route roles* dan tidak lupa kita tambahan pengecekan sebuah hak akses yang dimiliki oleh user menggunakan utils hasAnyPermissions yang telah kita buat sebelumnya.

AuthenticatedLayout.jsx
{hasAnyPermission(['roles-access']) &&
  <NavLink href={route('roles.index')} active={route().current('roles*')}>
      Roles
  </NavLink>
}

{hasAnyPermission(['roles-access']) &&
  <ResponsiveNavLink href={route('roles.index')} active={route().current('roles*')}>
      Roles
  </ResponsiveNavLink>
}

Membuat View Roles

Setelah berhasil membuat sebuah Route dan Navigasi Role, sekarang kita akan lanjutkan untuk pembuatan view-nya, disini kita akan membuat 3 view sekaligus yaitu index, create, danedit.

Membuat Index Roles

Silahkan teman - teman buat folder baru dengan nama Roles kemudian didalam folder tersebut buat file baru dengan nama Index.jsx yang akan kita letakan di resources/js/Pages, kemudian ubah kodenya menjadi seperti berikut ini.

Index.jsx
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Container from '@/Components/Container';
import Table from '@/Components/Table';
import Button from '@/Components/Button';
import Pagination from '@/Components/Pagination';
import { Head, usePage } from '@inertiajs/react';
import Search from '@/Components/Search';
import hasAnyPermission from '@/Utils/Permissions';
export default function Index({auth}) {

    // destruct permissions props
    const { roles } = usePage().props;

    return (
        <AuthenticatedLayout
            user={auth.user}
            header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Roles</h2>}
        >
            <Head title={'Roles'}/>
            <Container>
                <div className='mb-4 flex items-center justify-between gap-4'>
                    {hasAnyPermission(['roles-create']) &&
                        <Button type={'add'} url={route('roles.create')}/>
                    }
                    <div className='w-full md:w-4/6'>
                        <Search url={route('roles.index')} placeholder={'Search roles data by name...'}/>
                    </div>
                </div>
                <Table.Card title={'Roles'}>
                    <Table>
                        <Table.Thead>
                            <tr>
                                <Table.Th>#</Table.Th>
                                <Table.Th>Role Name</Table.Th>
                                <Table.Th>Permissions</Table.Th>
                                <Table.Th>Action</Table.Th>
                            </tr>
                        </Table.Thead>
                        <Table.Tbody>
                            {roles.data.map((role, i) => (
                                <tr key={i}>
                                    <Table.Td>{++i + (roles.current_page-1) * roles.per_page}</Table.Td>
                                    <Table.Td>{role.name}</Table.Td>
                                    <Table.Td>
                                        <div className='flex items-center gap-2 flex-wrap'>
                                            {role.name == 'super-admin' ?
                                                <span className="inline-flex items-center px-3 py-1 rounded-full text-sm bg-sky-100 text-sky-700">
                                                    all-permissions
                                                </span>
                                            :
                                            role.permissions.map((permission, i) => (
                                                <span
                                                    className="inline-flex items-center px-3 py-1 rounded-full text-sm bg-sky-100 text-sky-700"
                                                    key={i}
                                                >
                                                    {role.name == 'super-admin' ? 'all-permissions' : permission.name}
                                                </span>
                                            ))}
                                        </div>
                                    </Table.Td>
                                    <Table.Td>
                                        <div className='flex items-center gap-2'>
                                            {hasAnyPermission(['roles-update']) &&
                                                <Button type={'edit'} url={route('roles.edit', role.id)}/>
                                            }
                                            {hasAnyPermission(['roles-delete']) &&
                                                <Button type={'delete'} url={route('roles.destroy', role.id)}/>
                                            }
                                        </div>
                                    </Table.Td>
                                </tr>
                            ))}
                        </Table.Tbody>
                    </Table>
                </Table.Card>
                <div className='flex items-center justify-center'>
                    {roles.last_page !== 1 && (<Pagination links={roles.links}/>)}
                </div>
            </Container>
        </AuthenticatedLayout>
    )
}

Pada kode diatas, pertama - tama kita import beberapa file yang kita butuhkan.

Index.jsx
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Container from '@/Components/Container';
import Table from '@/Components/Table';
import Button from '@/Components/Button';
import Pagination from '@/Components/Pagination';
import { Head, usePage } from '@inertiajs/react';
import Search from '@/Components/Search';
import hasAnyPermission from '@/Utils/Permissions';

Selanjutnya kita membuat sebuah React Function Component dengan props auth.

Index.jsx
export default function Index({auth})

Berikutnya kita melakukan destructing sebuah props roles yang kita dapatkan dari RoleController.

Index.jsx
const { roles } = usePage().props;

Selanjutnya kita melakukan return sebuah component layout, dengan nama AuthenticatedLayout dengan props user dan header.

Index.jsx
<AuthenticatedLayout
    user={auth.user}
    header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Roles</h2>}
>

</AuthenticatedLayout>

Berikutnya kita juga memanggil sebuah head component yang telah disedikan oleh inertia untuk mengganti sebuah title dari halaman kita.

Index.jsx
<Head title={'Roles'}/>

Selanjutnya kita memanggil sebuah utils hasAnyPermissions yang telah kita buat sebelumnya untuk melakukan pengecekan apakah user memiliki hak akses untuk melakukan penambahan data roles.

Index.jsx
{hasAnyPermission(['roles-create']) &&
    <Button type={'add'} url={route('roles.create')}/>
}

Kemudian kita memanggil sebuah component dengan nama Search dengan props url dan placeholder.

Index.jsx
<Search url={route('roles.index')} placeholder={'Search roles data by name...'}/>

Pada props url, kita arahkan pencarian data kita ke sebuah route yang bernama roles.index.

Berikutnya kita juga memanggil sebuah component table dengan props title.

Index.jsx
<Table.Card title={'Roles'}>
    <Table>
        <Table.Thead>
            <tr>
                <Table.Th>#</Table.Th>
                <Table.Th>Role Name</Table.Th>
                <Table.Th>Permissions</Table.Th>
                <Table.Th>Action</Table.Th>
            </tr>
        </Table.Thead>
        <Table.Tbody>
            {roles.data.map((role, i) => (
                <tr key={i}>
                    <Table.Td>{++i + (roles.current_page-1) * roles.per_page}</Table.Td>
                    <Table.Td>{role.name}</Table.Td>
                    <Table.Td>
                        <div className='flex items-center gap-2 flex-wrap'>
                            {role.name == 'super-admin' ?
                                <span className="inline-flex items-center px-3 py-1 rounded-full text-sm bg-sky-100 text-sky-700">
                                    all-permissions
                                </span>
                            :
                            role.permissions.map((permission, i) => (
                                <span
                                    className="inline-flex items-center px-3 py-1 rounded-full text-sm bg-sky-100 text-sky-700"
                                    key={i}
                                >
                                    {role.name == 'super-admin' ? 'all-permissions' : permission.name}
                                </span>
                            ))}
                        </div>
                    </Table.Td>
                    <Table.Td>
                        <div className='flex items-center gap-2'>
                            {hasAnyPermission(['roles-update']) &&
                                <Button type={'edit'} url={route('roles.edit', role.id)}/>
                            }
                            {hasAnyPermission(['roles-delete']) &&
                                <Button type={'delete'} url={route('roles.destroy', role.id)}/>
                            }
                        </div>
                    </Table.Td>
                </tr>
            ))}
        </Table.Tbody>
    </Table>
</Table.Card>

Pada kode diatas, kita melakukan perulangan data roles menggunakan method map.

Index.jsx
{roles.data.map((role, i) => (
	.....
))}

Kemudian kita juga melakukan perulangan lagi untuk menampilkan data permissions yang dimiliki oleh tiap role.

Index.jsx
{role.name == 'super-admin' ?
    <span className="inline-flex items-center px-3 py-1 rounded-full text-sm bg-sky-100 text-sky-700">
        all-permissions
    </span>
:
role.permissions.map((permission, i) => (
    <span
        className="inline-flex items-center px-3 py-1 rounded-full text-sm bg-sky-100 text-sky-700"
        key={i}
    >
        {role.name == 'super-admin' ? 'all-permissions' : permission.name}
    </span>
))}

Selanjutnya kita juga melakukan pengecekan sebuah permissions yang dimiliki oleh users dengan menggunakan utils yang telah kita buat sebelumnya.

Index.jsx
{hasAnyPermission(['roles-update']) &&
  <Button type={'edit'} url={route('roles.edit', role.id)}/>
}
{hasAnyPermission(['roles-delete']) &&
  <Button type={'delete'} url={route('roles.destroy', role.id)}/>
}

Terakhir kita juga memanggil sebuah component pagination dengan sebuah props links.

Index.jsx
{roles.last_page !== 1 && (<Pagination links={roles.links}/>)}

roles-view

Membuat Create Roles

Silahkan teman-teman buat file baru dengan nama Create.jsx didalam folder Roles, kemudian ubah kodenya menjadi seperti berikut ini.

Create.jsx
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Container from '@/Components/Container';
import { Head, useForm, usePage } from '@inertiajs/react';
import Input from '@/Components/Input';
import Button from '@/Components/Button';
import Card from '@/Components/Card';
import Checkbox from '@/Components/Checkbox';
import Swal from 'sweetalert2';
export default function Create({auth}) {

    // destruct permissions from usepage props
    const { permissions } = usePage().props;

    // define state with helper inertia
    const { data, setData, post, errors } = useForm({
        name : '',
        selectedPermissions : []
    });

    // define method handleSelectedPermissions
    const handleSelectedPermissions = (e) => {
        let items = data.selectedPermissions;

        items.push(e.target.value);

        setData('selectedPermissions', items);
    }

    // define method handleStoreData
    const handleStoreData = async (e) => {
        e.preventDefault();

        post(route('roles.store'), {
            onSuccess: () => {
                Swal.fire({
                    title: 'Success!',
                    text: 'Data created successfully!',
                    icon: 'success',
                    showConfirmButton: false,
                    timer: 1500
                })
            }
        });
    }

    return (
        <AuthenticatedLayout
            user={auth.user}
            header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Create Role</h2>}
        >
            <Head title={'Create Roles'}/>
            <Container>
                <Card title={'Create new role'}>
                    <form onSubmit={handleStoreData}>
                        <div className='mb-4'>
                            <Input label={'Role Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input role name.."/>
                        </div>
                        <div className='mb-4'>
                        <div className={`p-4 rounded-t-lg border bg-white`}>
                            <div className='flex items-center gap-2 text-sm text-gray-700'>
                                Permissions
                            </div>
                        </div>
                        <div className='p-4 rounded-b-lg border border-t-0 bg-gray-100'>
                            <div className='flex flex-row flex-wrap gap-4'>
                                {permissions.map((permission, i) => (
                                    <Checkbox label={permission.name} value={permission.name} onChange={handleSelectedPermissions} key={i}/>
                                ))}
                            </div>
                            {errors.selectedPermissions && <div className='text-xs text-red-500 mt-4'>{errors.selectedPermissions}</div>}
                        </div>
                        </div>
                        <div className='flex items-center gap-2'>
                            <Button type={'submit'}/>
                            <Button type={'cancel'} url={route('roles.index')}/>
                        </div>
                    </form>
                </Card>
            </Container>
        </AuthenticatedLayout>
    )
}

Pada kode diatas, pertama - tama kita import beberapa file yang kita butuhkan.

Create.jsx
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Container from '@/Components/Container';
import { Head, useForm, usePage } from '@inertiajs/react';
import Input from '@/Components/Input';
import Button from '@/Components/Button';
import Card from '@/Components/Card';
import Checkbox from '@/Components/Checkbox';
import Swal from 'sweetalert2';

Selanjutnya kita membuat sebuah React Function Component dengan props auth.

Create.jsx
export default function Create({auth})

Berikutnya kita melakukan destructing sebuah props permissions yang kita dapatkan dari RoleController.

Create.jsx
const { permissions } = usePage().props;

Kemudian kita membuat sebuah state menggunakan form helper dari inertia.

Create.jsx
const { data, setData, post, errors } = useForm({
    name : '',
    selectedPermissions : []
});

Selanjutnya kita membuat sebuah method baru dengan nama handleSelectedPermissions yang kita gunakan untuk menangkap sebuah value dari sebuah component checkbox.

Create.jsx
{permissions.map((permission, i) => (
    <Checkbox label={permission.name} value={permission.name} onChange={handleSelectedPermissions} key={i}/>
))}

Ketika component checkbox dikilik maka akan memanggil sebuah method handlesSelectedpermissions yang telah kita buat sebelumnya.

Create.jsx
const handleSelectedPermissions = (e) => {
    let items = data.selectedPermissions;

    items.push(e.target.value);

    setData('selectedPermissions', items);
}

Di atas, pertama kita definisikan sebuah variabel dengan nama items yang berisi state selectedPermissions.

Create.jsx
let items = data.selectedPermissions;

Selajutnya kita melakukan push data atau memasukan value dari component checkbox kedalam variabel items.

Create.jsx
items.push(e.target.value);

Dan terakhir, kita masukan variabel items tersebut ke dalam state selectedPermissions.

Create.jsx
setData('selectedPermissions', items);

Selanjutnya kita membuat sebuah method baru dengan nama handleStoreData yang kita gunakan untuk mengirimkan data kita ke server menggunakan form helper yang telah disediakan oleh inertia.

Create.jsx
const handleStoreData = async (e) => {
    e.preventDefault();

    post(route('roles.store'), {
        onSuccess: () => {
            Swal.fire({
                title: 'Success!',
                text: 'Data created successfully!',
                icon: 'success',
                showConfirmButton: false,
                timer: 1500
            })
        }
    });
}

Jika kita perhatikan, pada method post kita arahkan ke sebuah route yang bernama roles.store dan ketika data berhasil dikirimkan, kita memanggil sebuah sweet alert.

Create.jsx
post(route('roles.store'), {
    onSuccess: () => {
        Swal.fire({
            title: 'Success!',
            text: 'Data created successfully!',
            icon: 'success',
            showConfirmButton: false,
            timer: 1500
        })
    }
});

Kemudian kita melakukan return sebuah component layout dengan nama AuthenticatedLayout dengan props user dan header.

Create.jsx
<AuthenticatedLayout
    user={auth.user}
    header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Create Role</h2>}
>

</AuthenticatedLayout>

Berikutnya kita juga memanggil sebuah head component yang telah disedikan oleh inertia untuk mengganti sebuah title dari halaman kita.

Create.jsx
<Head title={'Create Roles'}/>

Kemudian kita memanggil sebuah component card dengan props title.

Create.jsx
<Card title={'Create new role'}>
    <form onSubmit={handleStoreData}>
        <div className='mb-4'>
            <Input label={'Role Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input role name.."/>
        </div>
        <div className='mb-4'>
        <div className={`p-4 rounded-t-lg border bg-white`}>
            <div className='flex items-center gap-2 text-sm text-gray-700'>
                Permissions
            </div>
        </div>
        <div className='p-4 rounded-b-lg border border-t-0 bg-gray-100'>
            <div className='flex flex-row flex-wrap gap-4'>
                {permissions.map((permission, i) => (
                    <Checkbox label={permission.name} value={permission.name} onChange={handleSelectedPermissions} key={i}/>
                ))}
            </div>
            {errors.selectedPermissions && <div className='text-xs text-red-500 mt-4'>{errors.selectedPermissions}</div>}
        </div>
        </div>
        <div className='flex items-center gap-2'>
            <Button type={'submit'}/>
            <Button type={'cancel'} url={route('roles.index')}/>
        </div>
    </form>
</Card>

Didalam component card kita memanggil beberapa component yang telah kita buat seperti :

  • Input
  • Checkbox
  • Button

Pada bagian form didalam component card kita arahkan ke sebuah method handleStoreData yang telah kita buat sebelumnya.

Create.jsx
<form onSubmit={handleStoreData}>

</form>

roles-create

Membuat Edit Roles

Silahkan teman-teman buat file baru dengan nama Edit.jsx didalam folder Permissions, kemudian ubah kodenya menjadi seperti berikut ini.

Edit.jsx
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Container from '@/Components/Container';
import { Head, useForm, usePage } from '@inertiajs/react';
import Input from '@/Components/Input';
import Button from '@/Components/Button';
import Card from '@/Components/Card';
import Checkbox from '@/Components/Checkbox';
import Swal from 'sweetalert2';
export default function Edit({auth}) {

    // destruct permissions from usepage props
    const { permissions, role } = usePage().props;

    // define state with helper inertia
    const { data, setData, post, errors } = useForm({
        name : role.name,
        selectedPermissions : role.permissions.map(permission => permission.name),
        _method: 'put'
    });

    // define method handleSelectedPermissions
    const handleSelectedPermissions = (e) => {
        let items = data.selectedPermissions;

        if(items.includes(e.target.value))
            items.splice(items.indexOf(e.target.value), 1);
        else
            items.push(e.target.value);

        setData('selectedPermissions', items);
    }

    // define method handleUpdateData
    const handleUpdatedata = async (e) => {
        e.preventDefault();

        post(route('roles.update', role.id), {
            onSuccess: () => {
                Swal.fire({
                    title: 'Success!',
                    text: 'Data created successfully!',
                    icon: 'success',
                    showConfirmButton: false,
                    timer: 1500
                })
            }
        });
    }

    return (
        <AuthenticatedLayout
            user={auth.user}
            header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Edit Role</h2>}
        >
            <Head title={'Edit Roles'}/>
            <Container>
                <Card title={'Edit role'}>
                    <form onSubmit={handleUpdatedata}>
                        <div className='mb-4'>
                            <Input label={'Role Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input role name.."/>
                        </div>
                        <div className='mb-4'>
                        <div className={`p-4 rounded-t-lg border bg-white`}>
                            <div className='flex items-center gap-2 text-sm text-gray-600'>
                                Permissions
                            </div>
                        </div>
                        <div className='p-4 rounded-b-lg border border-t-0 bg-gray-100'>
                            <div className='flex flex-row flex-wrap gap-4'>
                                {permissions.map((permission, i) => (
                                    <Checkbox label={permission.name} value={permission.name} onChange={handleSelectedPermissions} defaultChecked={data.selectedPermissions.includes(permission.name)} key={i}/>
                                ))}
                            </div>
                            {errors.selectedPermissions && <div className='text-xs text-red-500 mt-4'>{errors.selectedPermissions}</div>}
                        </div>
                        </div>
                        <div className='flex items-center gap-2'>
                            <Button type={'submit'}/>
                            <Button type={'cancel'} url={route('roles.index')}/>
                        </div>
                    </form>
                </Card>
            </Container>
        </AuthenticatedLayout>
    )
}

Pada kode diatas, pertama - tama kita import beberapa file yang kita butuhkan.

Edit.jsx
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Container from '@/Components/Container';
import { Head, useForm, usePage } from '@inertiajs/react';
import Input from '@/Components/Input';
import Button from '@/Components/Button';
import Card from '@/Components/Card';
import Checkbox from '@/Components/Checkbox';
import Swal from 'sweetalert2';

Selanjutnya kita membuat sebuah React Function Component dengan props auth.

Edit.jsx
export default function Edit({auth})

Berikutnya kita melakukan destructing sebuah props permissions dan role yang kita dapatkan dari PermissionController.

Edit.jsx
const { permissions, role } = usePage().props;

Kemudian kita membuat sebuah state menggunakan form helper dari inertia.

Edit.jsx
const { data, setData, post, errors } = useForm({
    name : role.name,
    selectedPermissions : role.permissions.map(permission => permission.name),
    _method: 'put'
});

Pada state selectedPermissions, kita isi dengan data permissions yang ada di dalam role tersebut dan kita gunakan method map agar kita bisa mengambil value di dalamnya, yaitu name.

Selanjutnya kita membuat sebuah method baru dengan nama handleSelectedPermissions yang kita gunakan untuk menangkap sebuah value dari sebuah component checkbox.

Edit.jsx
{permissions.map((permission, i) => (
    <Checkbox label={permission.name} value={permission.name} onChange={handleSelectedPermissions} key={i}/>
))}

Ketika component checkbox dikilik maka akan memanggil sebuah method handleSelectedpermissions yang telah kita buat sebelumnya.

Edit.jsx
const handleSelectedPermissions = (e) => {
    let items = data.selectedPermissions;

    if(items.includes(e.target.value))
        items.splice(items.indexOf(e.target.value), 1);
    else
        items.push(e.target.value);

    setData('selectedPermissions', items);
}

Di atas, pertama kita definisikan sebuah variabel dengan nama items yang berisi state selectedPermissions.

Edit.jsx
let items = data.selectedPermissions;

Selanjutnya kita lakukan pengecekan sebuah data items menggunakan method includes, jika nilainya true maka kita akan melakukan penghapusan data items menggunakan method splice.

Edit.jsx
items.splice(items.indexOf(e.target.value), 1);

Jika false, maka kita akan melakukan push data atau memasukan value dari component checkbox kedalam variabel items.

Edit.jsx
items.push(e.target.value);

Dan terakhir, kita masukan variabel items tersebut ke dalam state selectedPermissions.

Edit.jsx
setData('selectedPermissions', items);

Selanjutnya kita membuat sebuah method baru dengan nama handleUpdateData yang kita gunakan untuk mengirimkan data kita ke server menggunakan form helper yang telah disediakan oleh inertia.

Edit.jsx
const handleStoreData = async (e) => {
    e.preventDefault();

    post(route('roles.update', role.id), {
        onSuccess: () => {
            Swal.fire({
                title: 'Success!',
                text: 'Data updated successfully!',
                icon: 'success',
                showConfirmButton: false,
                timer: 1500
            })
        }
    });
}

Jika kita perhatikan, pada method post kita arahkan ke sebuah route yang bernama permissions.update dengan parameter role.id dan ketika data berhasil dikirimkan, kita memanggil sebuah sweet alert.

Edit.jsx
post(route('roles.update', role.id), {
    onSuccess: () => {
        Swal.fire({
            title: 'Success!',
            text: 'Data updated successfully!',
            icon: 'success',
            showConfirmButton: false,
            timer: 1500
        })
    }
});

Kemudian kita melakukan return sebuah component layout dengan nama AuthenticatedLayout dengan props user dan header.

Edit.jsx
<AuthenticatedLayout
      user={auth.user}
      header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Edit Role</h2>}
  >

</AuthenticatedLayout>

Berikutnya kita juga memanggil sebuah head component yang telah disedikan oleh inertia untuk mengganti sebuah title dari halaman kita.

Edit.jsx
<Head title={'Edit Roles'}/>

Kemudian kita memanggil sebuah component card dengan props title.

Edit.jsx
<Card title={'Edit role'}>
    <form onSubmit={handleUpdateData}>
        <div className='mb-4'>
            <Input label={'Role Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input role name.."/>
        </div>
        <div className='mb-4'>
        <div className={`p-4 rounded-t-lg border bg-white`}>
            <div className='flex items-center gap-2 text-sm text-gray-600'>
                Permissions
            </div>
        </div>
        <div className='p-4 rounded-b-lg border border-t-0 bg-gray-100'>
            <div className='flex flex-row flex-wrap gap-4'>
                {permissions.map((permission, i) => (
                    <Checkbox label={permission.name} value={permission.name} onChange={handleSelectedPermissions} defaultChecked={data.selectedPermissions.includes(permission.name)} key={i}/>
                ))}
            </div>
            {errors.selectedPermissions && <div className='text-xs text-red-500 mt-4'>{errors.selectedPermissions}</div>}
        </div>
        </div>
        <div className='flex items-center gap-2'>
            <Button type={'submit'}/>
            <Button type={'cancel'} url={route('roles.index')}/>
        </div>
    </form>
</Card>

Didalam component card kita memanggil beberapa component yang telah kita buat seperti :

  • Input
  • Checkbox
  • Button

Pada bagian form didalam component card kita arahkan ke sebuah method handleUpdateData yang telah kita buat sebelumnya.

Edit.jsx
<form onSubmit={handleUpdateData}>

</form>

roles-edit

Screenshoot Hasil

roles-view

roles-create

roles-edit

roles-delete

Penutup

Pada artikel kali ini kita telah berhasil menyelesaikan pembuatan module roles, selanjutnya kita akan lanjutkan untuk pembuatan module users.

Series Artikel

Berikut ini daftar series artikel dari Tutorial Laravel Inertia Roles & Permissions

1
Tutorial Inertia Roles & Permissions - #1 - Project Overview

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan bahasa teknologi apa saja yang kita gunakan dan scope dari project yang akan kita bangun.

2
Tutorial Inertia Roles & Permissions - #2 - Installasi Project

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan mulai melakukan installasi project laravel kita dari awal menggunakan composer.

3
Tutorial Inertia Roles & Permissions - #3 - Installasi Laravel Breeze & Inertia

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan melakukan installasi laravel breeze dan inertia sebagai starter-kit kita.

4
Tutorial Inertia Roles & Permissions - #4 - Installasi Laravel Spatie

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan melakukan installasi laravel spatie untuk menghandle roles & permissions.

5
Tutorial Inertia Roles & Permissions - #5 - Membuat Model, Migration, Faker Dan Seeder Laravel

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah model, migration, faker dan seeder yang akan kita gunakan di dalam project kita.

6
Tutorial Inertia Roles & Permissions - #6 - Membuat Relasi Antar Tabel Menggunakan Laravel Eloquent Relationship

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat relasi antar tabel menggunakan laravel eloquent relationship.

7
Tutorial Inertia Roles & Permissions - #7 - Membuat Share Data Global Inertia

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan belajar membuat sebuah data yang dapat diakses di semua halaman menggunakan inertia.

8
Tutorial Inertia Roles & Permissions - #8 - Membuat Reusable Component Dengan React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan belajar membuat sebuah reusable component menggunakan react.

9
Tutorial Inertia Roles & Permissions - #9 - Membuat Utils Permissions Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan belajar membuat sebuah utils untuk menghandle permissions yang kita miliki.

10
Tutorial Inertia Roles & Permissions - #10 - Membuat Module Dashboard Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah module dashboard dengan inertia react.

11
Tutorial Inertia Roles & Permissions - #11 - Membuat Module Post Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah module post dengan inertia react.

12
Tutorial Inertia Roles & Permissions - #12 - Membuat Module Permission Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah module permission dengan inertia react.

13
Tutorial Inertia Roles & Permissions - #13 - Membuat Module Role Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah module role dengan inertia react.

14
Tutorial Inertia Roles & Permissions - #14 - Membuat Module User Dengan Inertia React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan membuat sebuah module user dengan inertia react.

JurnalKoding

Mulai asah skill dengan berbagai macam teknologi - teknologi terbaru seperti Laravel, React, Vue, Inertia, Tailwind CSS, dan masih banyak lagi.

© 2024 JurnalKoding, Inc. All rights reserved.