¶Pendahuluan
Pada artikel sebelumnya kita telah berhasil membuat module roles, dan pada artikel kali ini kita akan lanjutkan dengan pembuatan module users yang dimana module ini merupakan artikel terakhir dari series ini.
¶Membuat Controller Users
Silahkan teman - teman buka terminal-nya, kemudian jalankan perintah berikut ini :
Terminal
php artisan make:controller UserController -r
Jika perintah diatas berhasil dijalankan, maka kita akan mendapatkan sebuah file yang terletak di app/Http/Controllers
dengan nama UserController.php
.
Silahkan buka file tersebut, kemudian ubah semua kodenya sebagai berikut.
UserController.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\Routing\Controllers\HasMiddleware;
class UserController extends Controller implements HasMiddleware
{
public static function middleware()
{
return [
new Middleware('permission:users-data', only : ['index']),
new Middleware('permission:users-create', only : ['create', 'store']),
new Middleware('permission:users-update', only : ['edit', 'update ']),
new Middleware('permission:users-delete', only : ['destroy']),
];
}
/**
* Display a listing of the resource.
*/
public function index()
{
// get all users
$users = User::with('roles')
->when(request('search'), fn($query) => $query->where('name', 'like', '%'.request('search').'%'))
->latest()
->paginate(6);
// render view
return inertia('Users/Index', ['users' => $users]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
// get roles
$roles = Role::where('name', '!=', 'super-admin')->get();
// render view
return inertia('Users/Create', ['roles' => $roles]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// validate request
$request->validate([
'name' => 'required|min:3|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed|min:4',
'selectedRoles' => 'required|array|min:1',
]);
// create user
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
// attach roles
$user->assignRole($request->selectedRoles);
// render view
return to_route('users.index');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(User $user)
{
// get roles
$roles = Role::where('name', '!=', 'super-admin')->get();
// load roles
$user->load('roles');
// render view
return inertia('Users/Edit', ['user' => $user, 'roles' => $roles]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, User $user)
{
// validate request
$request->validate([
'name' => 'required|min:3|max:255',
'email' => 'required|email|unique:users,email,'.$user->id,
'selectedRoles' => 'required|array|min:1',
]);
// update user data
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
// attach roles
$user->syncRoles($request->selectedRoles);
// render view
return to_route('users.index');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(User $user)
{
// delete user data
$user->delete();
// render view
return back();
}
}
Didalam class UserController
kita memiliki 7 method yang kita generate diawal dengan perintah.
Terminal
php artisan make:controller UserController -r
-r
disini maksudnya adalah resources
, jadi kita membuat UserController
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 users dan roles.
UserController.php
use App\Models\User;
use Spatie\Permission\Models\Role;
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.
UserController.php
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
Selanjutnya kita bisa menggunakan sebuah method dari interface HasMiddleware
seperti kode dibawah ini.
UserController.php
public static function middleware()
{
return [
new Middleware('permission:users-data', only : ['index']),
new Middleware('permission:users-create', only : ['create', 'store']),
new Middleware('permission:users-update', only : ['edit', 'update ']),
new Middleware('permission:users-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 users
yang ada didalam database.
UserController.php
public function index()
{
// get all users
$users = User::with('roles')
->when(request('search'), fn($query) => $query->where('name', 'like', '%'.request('search').'%'))
->latest()
->paginate(6);
// render view
return inertia('Users/Index', ['users' => $users]);
}
Pertama - tama kita membuat sebuah variabel users
yang kita gunakan untuk menampilkan sebuah data dari tabel users
disini kita juga menambah pemanggilan sebuah relasi ke tabel roles
menggunakan method with
, selain menampilkan data, kita juga menambahkan sebuah pencarian data users
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 users
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
.
UserController.php
// get all users
$users = User::with('roles')
->when(request('search'), fn($query) => $query->where('name', 'like', '%'.request('search').'%'))
->latest()
->paginate(6);
Kemudian data diatas, kita kirimkan ke sebuah view dengan nama Users/Index.jsx
, dalam bentuk props.
UserController.php
// render view
return inertia('Users/Index', ['users' => $users]);
Method Create
Method ini kita gunakan untuk menampilkan sebuah form untuk melakukan insert data baru kedalam tabel users
.
UserController.php
public function create()
{
// get roles
$roles = Role::where('name', '!=', 'super-admin')->get();
// render view
return inertia('Users/Create', ['roles' => $roles]);
}
Kemudian kita juga memanggil semua data dari tabel roles
kecuali data role super-admin
, yang nantinya akan kita kirimkan ke view dalam bentuk props.
UserController.php
$roles = Role::where('name', '!=', 'super-admin')->get();
Selanjutnya data diatas, kita kirimkan ke sebuah view dengan nama Users/Create.jsx
, dalam bentuk props.
UserController.php
// render view
return inertia('Users/Create', ['roles' => $roles]);
Method Store
Method ini kita gunakan untuk melakukan proses insert data baru kedalam tabel users
.
UserController.php
public function store(Request $request)
{
// validate request
$request->validate([
'name' => 'required|min:3|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed|min:4',
'selectedRoles' => 'required|array|min:1',
]);
// create user
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
// attach roles
$user->assignRole($request->selectedRoles);
// render view
return to_route('users.index');
}
Pada kode diatas, pertama - tama kita melakukan validasi data terlebih dahulu sebelum melakukan insert data kedalam database menggunakan method validate
.
UserController.php
// validate request
$request->validate([
'name' => 'required|min:3|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed|min:4',
'selectedRoles' => '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 users
menggunakan method create
.
UserController.php
// create user
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
Berikutnya ketika data user
berhasil di insert, kita berikan dia sebuah roles menggunakan method assignRole
.
UserController.php
// attach roles
$user->assignRole($request->selectedRoles);
Setelah berhasil menambahkan data baru, kita akan diarahkan ke sebuah route users.index
.
UserController.php
// render view
return to_route('users.index');
Method Edit
Method ini kita gunakan untuk menampilkan sebuah form untuk melakukan proses update datausers
kita.
UserController.php
public function edit(User $user)
{
// get roles
$roles = Role::where('name', '!=', 'super-admin')->get();
// load roles
$user->load('roles');
// render view
return inertia('Users/Edit', ['user' => $user, 'roles' => $roles]);
}
Pada kode diatas, method edit kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel user
merupakan dirinya sendiri.
UserController.php
public function edit(User $user)
Kemudian kita juga memanggil semua data dari tabel roles
kecuali data role super-admin
, yang nantinya akan kita kirimkan ke view dalam bentuk props.
UserController.php
$roles = Role::where('name', '!=', 'super-admin')->get();
Kita juga melakukan pemanggilan relasi menggunakan method load
.
UserController.php
$user->load('roles');
Selanjutnya data-data diatas, kita kirimkan ke sebuah view dengan nama Users/Edit
, dalam bentuk props.
UserController.php
return inertia('Users/Edit', ['user' => $user, 'roles' => $roles]);
Method Update
Method ini kita gunakan untuk melakukan proses update data yang ada didalam tabel users
kita.
UserController.php
public function update(Request $request, User $user)
{
// validate request
$request->validate([
'name' => 'required|min:3|max:255',
'email' => 'required|email|unique:users,email,'.$user->id,
'selectedRoles' => 'required|array|min:1',
]);
// update user data
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
// attach roles
$user->syncRoles($request->selectedRoles);
// render view
return to_route('users.index');
}
Pada kode diatas, method update kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel $user
merupakan dirinya sendiri.
UserController.php
public function update(Request $request, User $user)
Berikutnya kita definisikan sebuah validasi menggunakan method validate
.
UserController.php
// validate request
$request->validate([
'name' => 'required|min:3|max:255',
'email' => 'required|email|unique:users,email,'.$user->id,
'selectedRoles' => '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 users
menggunakan method update
.
UserController.php
// update user data
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
Setelah data berhasil di update kita lakukan penyesuain roles yang dimiliki oleh user tersebut menggunakan method syncRoles
.
UserController.php
// attach roles
$user->syncRoles($request->selectedRoles);
Setelah berhasil melakukan update data, kita akan diarahkan ke sebuah route dengan nama users.index
.
UserController.php
// render view
return to_route('users.index');
Method Destroy
Method ini kita gunakan untuk melakukan proses penghapusan data user
yang kita miliki.
UserController.php
public function destroy(User $user)
{
// delete user data
$user->delete();
// render view
return back();
}
Pada kode diatas, method destroy kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel $user
merupakan dirinya sendiri.
UserController.php
public function destroy(User $user)
Selanjutnya kita lakukan penghapusan data menggunakan method delete
.
UserController.php
// delete user data
$user->delete();
Terakhir setelah data berhasil dihapus, kita akan diarahkan ke sebuah route users.index
.
UserController.php
// render view
return back();
¶Membuat Route Users
Setelah berhasil membuat sebuah controller User, 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\RoleController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\PermissionController;
use App\Http\Controllers\UserController;
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');
// users route
Route::resource('/users', UserController::class);
// 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 users
, untuk memastikan route yang kita buat telah berfungsi, teman - teman bisa jalankan.
Terminal
php artisan r:l --name=users
Setelah perintah artisan diatas dijalankan, maka kita akan mendapatkan output, kurang lebih seperti berikut ini.
Terminal
GET|HEAD users ......................................................................................... users.index › UserController@index
POST users ......................................................................................... users.store › UserController@store
GET|HEAD users/create ................................................................................ users.create › UserController@create
GET|HEAD users/{user} .................................................................................... users.show › UserController@show
PUT|PATCH users/{user} ................................................................................ users.update › UserController@update
DELETE users/{user} .............................................................................. users.destroy › UserController@destroy
GET|HEAD users/{user}/edit ............................................................................... users.edit › UserController@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>}
{hasAnyPermission(['users-access']) &&
<NavLink href={route('users.index')} active={route().current('users*')}>
Users
</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>
}
{hasAnyPermission(['users-access']) &&
<ResponsiveNavLink href={route('users.index')} active={route().current('users*')}>
Users
</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 users.index
dan props active
kita set ke semua route users*
dan tidak lupa kita tambahan pengecekan sebuah hak akses yang dimiliki oleh user menggunakan utils
hasAnyPermissions
yang telah kita buat sebelumnya.
AuthenticatedLayout.jsx
{hasAnyPermission(['users-access']) &&
<NavLink href={route('users.index')} active={route().current('users*')}>
Users
</NavLink>
}
{hasAnyPermission(['users-access']) &&
<ResponsiveNavLink href={route('users.index')} active={route().current('users*')}>
Users
</ResponsiveNavLink>
}
¶Membuat View Users
Setelah berhasil membuat sebuah Route dan Navigasi User, sekarang kita akan lanjutkan untuk pembuatan view-nya, disini kita akan membuat 3 view sekaligus yaitu index
, create
, danedit
.
¶Membuat Index Users
Silahkan teman - teman buat folder baru dengan nama Users
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 users props
const { users } = usePage().props;
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Users</h2>}
>
<Head title={'Users'}/>
<Container>
<div className='mb-4 flex items-center justify-between gap-4'>
{hasAnyPermission(['users-create']) &&
<Button type={'add'} url={route('users.create')}/>
}
<div className='w-full md:w-4/6'>
<Search url={route('users.index')} placeholder={'Search users data by name...'}/>
</div>
</div>
<Table.Card title={'users'}>
<Table>
<Table.Thead>
<tr>
<Table.Th>#</Table.Th>
<Table.Th>User</Table.Th>
<Table.Th>Roles</Table.Th>
<Table.Th>Action</Table.Th>
</tr>
</Table.Thead>
<Table.Tbody>
{users.data.map((user, i) => (
<tr key={i}>
<Table.Td>{++i + (users.current_page-1) * users.per_page}</Table.Td>
<Table.Td>
{user.name}
<div className='text-sm text-gray-400'>{user.email}</div>
</Table.Td>
<Table.Td>
<div className='flex items-center gap-2 flex-wrap'>
{user.roles.map((role, 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}
</span>
))}
</div>
</Table.Td>
<Table.Td>
<div className='flex items-center gap-2'>
{hasAnyPermission(['users-update']) &&
<Button type={'edit'} url={route('users.edit', user.id)}/>
}
{hasAnyPermission(['users-delete']) &&
<Button type={'delete'} url={route('users.destroy', user.id)}/>
}
</div>
</Table.Td>
</tr>
))}
</Table.Tbody>
</Table>
</Table.Card>
<div className='flex items-center justify-center'>
{users.last_page !== 1 && (<Pagination links={users.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 users
yang kita dapatkan dari UserController
.
Index.jsx
const { users } = 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">Users</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={'Users'}/>
Selanjutnya kita memanggil sebuah utils hasAnyPermissions
yang telah kita buat sebelumnya untuk melakukan pengecekan apakah user memiliki hak akses untuk melakukan penambahan data users
.
Index.jsx
{hasAnyPermission(['users-create']) &&
<Button type={'add'} url={route('users.create')}/>
}
Kemudian kita memanggil sebuah component dengan nama Search
dengan props url
dan placeholder
.
Index.jsx
<Search url={route('users.index')} placeholder={'Search users data by name...'}/>
Pada props url
, kita arahkan pencarian data kita ke sebuah route yang bernama users.index
.
Berikutnya kita juga memanggil sebuah component table dengan props title
.
Index.jsx
<Table.Card title={'Users'}>
<Table>
<Table.Thead>
<tr>
<Table.Th>#</Table.Th>
<Table.Th>User</Table.Th>
<Table.Th>Roles</Table.Th>
<Table.Th>Action</Table.Th>
</tr>
</Table.Thead>
<Table.Tbody>
{users.data.map((user, i) => (
<tr key={i}>
<Table.Td>{++i + (users.current_page-1) * users.per_page}</Table.Td>
<Table.Td>
{user.name}
<div className='text-sm text-gray-400'>{user.email}</div>
</Table.Td>
<Table.Td>
<div className='flex items-center gap-2 flex-wrap'>
{user.roles.map((role, 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}
</span>
))}
</div>
</Table.Td>
<Table.Td>
<div className='flex items-center gap-2'>
{hasAnyPermission(['users-update']) &&
<Button type={'edit'} url={route('users.edit', user.id)}/>
}
{hasAnyPermission(['users-delete']) &&
<Button type={'delete'} url={route('users.destroy', user.id)}/>
}
</div>
</Table.Td>
</tr>
))}
</Table.Tbody>
</Table>
</Table.Card>
Pada kode diatas, kita melakukan perulangan data users
menggunakan method map
.
Index.jsx
{users.data.map((user, i) => (
....
))}
Kemudian kita juga melakukan perulangan lagi untuk menampilkan data roles
yang dimiliki oleh tiap user
.
Index.jsx
{user.roles.map((role, 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}
</span>
))}
Selanjutnya kita juga melakukan pengecekan sebuah permissions yang dimiliki oleh users dengan menggunakan utils
yang telah kita buat sebelumnya.
Index.jsx
{hasAnyPermission(['users-update']) &&
<Button type={'edit'} url={route('users.edit', user.id)}/>
}
{hasAnyPermission(['users-delete']) &&
<Button type={'delete'} url={route('users.destroy', user.id)}/>
}
Terakhir kita juga memanggil sebuah component pagination dengan sebuah props links
.
Index.jsx
{users.last_page !== 1 && (<Pagination links={users.links}/>)}
¶Membuat Create Roles
Silahkan teman-teman buat file baru dengan nama Create.jsx
didalam folder Users
, 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 roles from usepage props
const { roles } = usePage().props;
// define state with helper inertia
const { data, setData, post, errors } = useForm({
name : '',
email: '',
selectedRoles : [],
password: '',
password_confirmation: ''
});
// define method handleSelectedroles
const handleSelectedRoles = (e) => {
let items = data.selectedRoles;
items.push(e.target.value);
setData('selectedRoles', items);
}
// define method handleStoreData
const handleStoreData = async (e) => {
e.preventDefault();
post(route('users.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 User</h2>}
>
<Head title={'Create Users'}/>
<Container>
<Card title={'Create new user'}>
<form onSubmit={handleStoreData}>
<div className='mb-4'>
<Input label={'Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input name user.."/>
</div>
<div className='mb-4'>
<Input label={'Email'} type={'email'} value={data.email} onChange={e => setData('email', e.target.value)} errors={errors.email} placeholder="Input email user.."/>
</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'>
Roles
</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'>
{roles.map((role, i) => (
<Checkbox label={role.name} value={role.name} onChange={handleSelectedRoles} key={i}/>
))}
</div>
{errors.selectedRoles && <div className='text-xs text-red-500 mt-4'>{errors.selectedRoles}</div>}
</div>
</div>
<div className='mb-4'>
<Input label={'Password'} type={'password'} value={data.password} onChange={e => setData('password', e.target.value)} errors={errors.password} placeholder="Input password user.."/>
</div>
<div className='mb-4'>
<Input label={'Password Confirmation'} type={'password'} value={data.password_confirmation} onChange={e => setData('password_confirmation', e.target.value)} errors={errors.password_confirmation} placeholder="Input password confirmation..."/>
</div>
<div className='flex items-center gap-2'>
<Button type={'submit'}/>
<Button type={'cancel'} url={route('users.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 roles
yang kita dapatkan dari UserController
.
Create.jsx
const { roles } = usePage().props;
Kemudian kita membuat sebuah state menggunakan form helper dari inertia.
Create.jsx
const { data, setData, post, errors } = useForm({
name : '',
email: '',
selectedRoles : [],
password: '',
password_confirmation: ''
});
Selanjutnya kita membuat sebuah method baru dengan nama handleSelectedRoles
yang kita gunakan untuk menangkap sebuah value dari sebuah component checkbox.
Create.jsx
{roles.map((role, i) => (
<Checkbox label={role.name} value={role.name} onChange={handleSelectedRoles} key={i}/>
))}
Ketika component checkbox dikilik maka akan memanggil sebuah method handleSelectedRoles
yang telah kita buat sebelumnya.
Create.jsx
const handleSelectedRoles = (e) => {
let items = data.selectedRoles;
items.push(e.target.value);
setData('selectedRoles', items);
}
Di atas, pertama kita definisikan sebuah variabel dengan nama items
yang berisi state selectedRoles
.
Create.jsx
let items = data.selectedRoles;
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 selectedRoles
.
Create.jsx
setData('selectedRoles', 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('users.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 users.store
dan ketika data berhasil dikirimkan, kita memanggil sebuah sweet alert.
Create.jsx
post(route('users.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 User</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 Users'}/>
Kemudian kita memanggil sebuah component card dengan props title
.
Create.jsx
<Card title={'Create new user'}>
<form onSubmit={handleStoreData}>
<div className='mb-4'>
<Input label={'Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input name user.."/>
</div>
<div className='mb-4'>
<Input label={'Email'} type={'email'} value={data.email} onChange={e => setData('email', e.target.value)} errors={errors.email} placeholder="Input email user.."/>
</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'>
Roles
</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'>
{roles.map((role, i) => (
<Checkbox label={role.name} value={role.name} onChange={handleSelectedRoles} key={i}/>
))}
</div>
{errors.selectedRoles && <div className='text-xs text-red-500 mt-4'>{errors.selectedRoles}</div>}
</div>
</div>
<div className='mb-4'>
<Input label={'Password'} type={'password'} value={data.password} onChange={e => setData('password', e.target.value)} errors={errors.password} placeholder="Input password user.."/>
</div>
<div className='mb-4'>
<Input label={'Password Confirmation'} type={'password'} value={data.password_confirmation} onChange={e => setData('password_confirmation', e.target.value)} errors={errors.password_confirmation} placeholder="Input password confirmation..."/>
</div>
<div className='flex items-center gap-2'>
<Button type={'submit'}/>
<Button type={'cancel'} url={route('users.index')}/>
</div>
</form>
</Card>
Didalam component card kita memanggil beberapa component yang telah kita buat seperti :
Pada bagian form
didalam component card kita arahkan ke sebuah method handleStoreData
yang telah kita buat sebelumnya.
Create.jsx
<form onSubmit={handleStoreData}>
</form>
¶Membuat Edit Roles
Silahkan teman-teman buat file baru dengan nama Edit.jsx
didalam folder Users
, 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 roles and user from usepage props
const { user, roles } = usePage().props;
// define state with helper inertia
const { data, setData, post, errors } = useForm({
name : user.name,
email: user.email,
selectedRoles : user.roles.map(role => role.name),
_method: 'put'
});
// define method handleSelectedroles
const handleSelectedRoles = (e) => {
let items = data.selectedRoles;
if(items.includes(e.target.value))
items.splice(items.indexOf(e.target.value), 1);
else
items.push(e.target.value);
setData('selectedRoles', items);
}
// define method handleUpdateData
const handleUpdateData = async (e) => {
e.preventDefault();
post(route('users.update', user.id), {
onSuccess: () => {
Swal.fire({
title: 'Success!',
text: 'Data updated 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 User</h2>}
>
<Head title={'Create Users'}/>
<Container>
<Card title={'Create new user'}>
<form onSubmit={handleUpdateData}>
<div className='mb-4'>
<Input label={'Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input name user.."/>
</div>
<div className='mb-4'>
<Input label={'Email'} type={'email'} value={data.email} onChange={e => setData('email', e.target.value)} errors={errors.email} placeholder="Input email user.."/>
</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'>
Roles
</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'>
{roles.map((role, i) => (
<Checkbox label={role.name} value={role.name} onChange={handleSelectedRoles} defaultChecked={data.selectedRoles.includes(role.name)} key={i}/>
))}
</div>
{errors.selectedRoles && <div className='text-xs text-red-500 mt-4'>{errors.selectedRoles}</div>}
</div>
</div>
<div className='flex items-center gap-2'>
<Button type={'submit'}/>
<Button type={'cancel'} url={route('users.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 user
dan roles
yang kita dapatkan dari UserController
.
Edit.jsx
const { user, roles } = usePage().props;
Kemudian kita membuat sebuah state menggunakan form helper dari inertia.
Edit.jsx
const { data, setData, post, errors } = useForm({
name : user.name,
email: user.email,
selectedRoles : user.roles.map(role => role.name),
_method: 'put'
});
Pada state selectedRoles
, kita isi dengan data roles yang ada di dalam user tersebut dan kita gunakan method map
agar kita bisa mengambil value di dalamnya, yaitu name
.
Selanjutnya kita membuat sebuah method baru dengan nama handleSelectedRoles
yang kita gunakan untuk menangkap sebuah value dari sebuah component checkbox.
Edit.jsx
{roles.map((role, i) => (
<Checkbox label={role.name} value={role.name} onChange={handleSelectedRoles} defaultChecked={data.selectedRoles.includes(role.name)} key={i}/>
))}
Ketika component checkbox dikilik maka akan memanggil sebuah method handleSelectedRoles
yang telah kita buat sebelumnya.
Edit.jsx
const handleSelectedRoles = (e) => {
let items = data.selectedRoles;
if(items.includes(e.target.value))
items.splice(items.indexOf(e.target.value), 1);
else
items.push(e.target.value);
setData('selectedRoles', items);
}
Di atas, pertama kita definisikan sebuah variabel dengan nama items
yang berisi state selectedRoles
.
Edit.jsx
let items = data.selectedRoles;
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 selectedRoles
.
Edit.jsx
setData('selectedRoles', 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 handleUpdateData = async (e) => {
e.preventDefault();
post(route('users.update', user.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 users.update
dengan parameter user.id
dan ketika data berhasil dikirimkan, kita memanggil sebuah sweet alert.
Edit.jsx
post(route('users.update', user.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 User</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 Users'}/>
Kemudian kita memanggil sebuah component card dengan props title
.
Edit.jsx
<Card title={'Edit User'}>
<form onSubmit={handleUpdateData}>
<div className='mb-4'>
<Input label={'Name'} type={'text'} value={data.name} onChange={e => setData('name', e.target.value)} errors={errors.name} placeholder="Input name user.."/>
</div>
<div className='mb-4'>
<Input label={'Email'} type={'email'} value={data.email} onChange={e => setData('email', e.target.value)} errors={errors.email} placeholder="Input email user.."/>
</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'>
Roles
</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'>
{roles.map((role, i) => (
<Checkbox label={role.name} value={role.name} onChange={handleSelectedRoles} defaultChecked={data.selectedRoles.includes(role.name)} key={i}/>
))}
</div>
{errors.selectedRoles && <div className='text-xs text-red-500 mt-4'>{errors.selectedRoles}</div>}
</div>
</div>
<div className='flex items-center gap-2'>
<Button type={'submit'}/>
<Button type={'cancel'} url={route('users.index')}/>
</div>
</form>
</Card>
Didalam component card kita memanggil beberapa component yang telah kita buat seperti :
Pada bagian form
didalam component card kita arahkan ke sebuah method handleUpdateData
yang telah kita buat sebelumnya.
Edit.jsx
<form onSubmit={handleUpdateData}>
</form>
¶Uji Coba Aplikasi
Saat ini kita akan coba untuk melakukan registrasi menggunakan user baru, jika teman - teman lakukan registrasi dan hasil output-nya seperti dibawah ini maka kode kita sudah berjalan dengan baik, dikarenakan ketika user melakukan registrasi kita tidak memberikan role
apapun pada user tersebut, sehingga hanya halaman dashboard yang dapat diakses oleh user tersebut.
Disni kita akan coba, untuk melakukan perubahan url-nya secara langsung maka akan tampil seperti ini, dikarenakan user yang kita daftarkan tidak memiliki permissions
untuk mengakses halaman posts
.
Sekarang kita akan coba untuk memberikan sebuah permissions
kepada user yang baru saja kita daftarkan, caranya silahkan teman-teman login terlebih dahulu menggunakan akun yang telah kita buat menggunakan seeder
, kemudian buka halaman Users
dan klik tombol edit selanjutnya kita akan berikan user yang baru saja kita daftarkan sebuah permissions posts-access agar user tersebut dapat mengakses halaman posts
.
Setelah berhasil menambahkan permissions
untuk user yang baru saja kita daftarkan, teman - teman bisa coba login kembali menggunakan user tersebut, dan jika teman - teman berhasil login maka akan ada menu posts
pada users tersebut.
¶Screenshoot Hasil
¶Penutup
Pada artikel kali ini kita telah berhasil menyelesaikan pembuatan module users, dan ini merupakan artikel terakhir dari series Tutorial Laravel Inertia Roles & Permissions, saya ucapkan banyak terimakasih kepada teman - teman yang sudah mengikuti seri ini dari awal sampai akhir, jika ada pertanyaan bisa langsung ke telegram saya Rafi Taufiqurrahman. nantikan seri - seri selanjutnya dari saya, semoga bermanfaat terimakasih : )