¶Pendahuluan
Pada artikel sebelumnya kita telah berhasil membuat module dashboard, dan pada artikel kali ini kita akan lajutkan untuk melakukan pembuatan module posts.
¶Membuat Controller Posts
Silahkan teman - teman buka terminal-nya, kemudian jalankan perintah berikut ini :
Terminal
php artisan make:controller PostController -r
Jika perintah diatas berhasil dijalankan, maka kita akan mendapatkan sebuah file yang terletak di app/Http/Controllers
dengan nama PostController.php
.
Silahkan buka file tersebut, kemudian ubah semua kodenya sebagai berikut.
PostController.php
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class PostController extends Controller implements HasMiddleware
{
public static function middleware()
{
return [
new Middleware('permission:posts-access', only: ['index']),
new Middleware('permission:posts-create', only: ['store']),
new Middleware('permission:posts-update', only: ['update']),
new Middleware('permission:posts-delete', only: ['destroy']),
];
}
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
// get all posts
$posts = Post::with('user')
->whereUserId($request->user()->id)
->when($request->search, fn($query) => $query->where('title', 'like', '%'.$request->search.'%'))
->latest()
->paginate(6)->withQueryString();
// render view
return inertia('Posts/Index', ['posts' => $posts]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// validate request
$request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:3|max:255',
]);
// create new post
Post::create([
'user_id' => $request->user()->id,
'title' => $request->title,
'content' => $request->content,
]);
// render view
return back();
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Post $post)
{
// validate request
$request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:3|max:255',
]);
// update post
$post->update([
'title' => $request->title,
'content' => $request->content,
]);
// render view
return back();
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Post $post)
{
// delete post
$post->delete();
// render view
return back();
}
}
Didalam class PostController
kita memiliki 7 method yang kita generate diawal dengan perintah.
Terminal
php artisan make:controller PostController -r
-r
disini maksudnya adalah resources
, jadi kita membuat PostController
dengan full resource sehingga kita mendapatkan 7 method sekaligus dengan satu perintah artisan, tapi disini kita hanya akan menggunakan 4 method yaitu :
-
index
-
store
-
update
-
destroy
Dari kode diatas, pertama kita lakukan import model post.
PostController.php
use App\Models\Post;
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.
PostController.php
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
Selanjutnya kita bisa menggunakan sebuah method dari interface HasMiddleware
seperti kode dibawah ini.
PostController.php
public static function middleware()
{
return [
new Middleware('permission:posts-access', only: ['index']),
new Middleware('permission:posts-create', only: ['store']),
new Middleware('permission:posts-update', only: ['update']),
new Middleware('permission:posts-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 posts
yang ada didalam database.
PostController.php
public function index(Request $request)
{
// get all posts
$posts = Post::with('user')
->whereUserId($request->user()->id)
->when($request->search, fn($query) => $query->where('title', 'like', '%'.$request->search.'%'))
->latest()
->paginate(6)->withQueryString();
// render view
return inertia('Posts/Index', ['posts' => $posts]);
}
Pertama - tama kita membuat sebuah variabel posts
yang kita gunakan untuk menampilkan sebuah data dari tabel posts
. Data yang akan kita tampilkan, tentu saja hanya data posts
yang dimiliki oleh pengguna yang sedang login menggunakan method whereUserId
. selain menampilkan data, kita juga menambahkan sebuah pencarian data posts
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 posts
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
.
PostController.php
// get all posts
$posts = Post::with('user')
->whereUserId($request->user()->id)
->when($reques t->search, fn($query) => $query->where('title', 'like', '%'.$request->search.'%'))
->latest()
->paginate(6);
Kemudian data diatas, kita kirimkan ke sebuah view dengan nama Posts/Index.jsx
, dalam bentuk props.
PostController.php
// render view
return inertia('Posts/Index', ['posts' => $posts]);
Method Store
Method ini kita gunakan untuk melakukan proses insert data baru kedalam tabel posts
.
PostController.php
public function store(Request $request)
{
// validate request
$request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:3|max:255',
]);
// create new post
Post::create([
'user_id' => $request->user()->id,
'title' => $request->title,
'content' => $request->content,
]);
// render view
return back();
}
Pada kode diatas, pertama - tama kita melakukan validasi data terlebih dahulu sebelum melakukan insert data kedalam database menggunakan method validate
.
PostController.php
// validate request
$request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:3|max:255',
]);
Selanjutnya jika request yang kita kirimkan sudah sesuai dengan kriteria validasi yang kita definisikan, maka kita akan melakukan insert data baru kedalam tabel posts
menggunakan method create
.
PostController.php
// create new post
Post::create([
'user_id' => $request->user()->id,
'title' => $request->title,
'content' => $request->content,
]);
Setelah berhasil menambahkan data baru, kita akan diarahkan ke sebuah route posts.index
.
PostController.php
// render view
return back();
Method Update
Method ini kita gunakan untuk melakukan proses update data yang ada didalam tabel posts
kita.
PostController.php
public function update(Request $request, Post $post)
{
// validate request
$request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:3|max:255',
]);
// update post
$post->update([
'title' => $request->title,
'content' => $request->content,
]);
// render view
return back();
}
Pada kode diatas, method update kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel $post
merupakan dirinya sendiri.
PostController.php
public function update(Request $request, Post $post)
Berikutnya kita definisikan sebuah validasi menggunakan method validate
.
PostController.php
// validate request
$request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:3|max:255',
]);
Kemudian jika request yang kita kirimkan sudah sesuai dengan kriteria validasi yang kita definisikan, maka kita akan melakukan insert data baru kedalam tabel posts
menggunakan method update
.
PostController.php
// update post
$post->update([
'title' => $request->title,
'content' => $request->content,
]);
Setelah berhasil melakukan update data, kita akan diarahkan ke sebuah route posts.index
.
PostController.php
// render view
return back();
Method Destroy
Method ini kita gunakan untuk melakukan proses penghapusan data posts
yang kita miliki.
PostController.php
public function destroy(Post $post)
{
// delete post
$post->delete();
// render view
return back();
}
Pada kode diatas, method destroy kita modifikasi menggunakan sebuah route model binding yang menandakan bahwa variabel $post
merupakan dirinya sendiri.
PostController.php
public function destroy(Post $post)
Selanjutnya kita lakukan penghapusan data menggunakan method delete
.
PostController.php
// delete post
$post->delete();
Terakhir setelah data berhasil dihapus, kita akan diarahkan ke sebuah route posts.index
.
PostController.php
// render view
return back();
¶Membuat Route Posts
Setelah berhasil membuat sebuah controller Post, 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 App\Http\Controllers\DashboardController;
use App\Http\Controllers\PostController;
use App\Http\Controllers\ProfileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
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');
// 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 posts
, untuk memastikan route yang kita buat telah berfungsi, teman - teman bisa jalankan.
Terminal
php artisan r:l --name=posts
Setelah perintah artisan diatas dijalankan, maka kita akan mendapatkan output, kurang lebih seperti berikut ini.
GET|HEAD posts ..................................................................... posts.index › PostController@index
POST posts ..................................................................... posts.store › PostController@store
PUT|PATCH posts/{post} ............................................................ posts.update › PostController@update
DELETE posts/{post} .......................................................... posts.destroy › PostController@destroy
¶Membuat Navigasi Posts
Silahkan teman - teman buka file yang bernama AuthenticatedLayout.jsx
yang terletak di folder resources/js/layouts
, kemudian ubah kodenya menjadi seperti berikut ini :
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>
}
</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>
}
</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 posts.index
dan props active
kita set ke semua route posts*
dan tidak lupa kita tambahan pengecekan sebuah hak akses yang dimiliki oleh user menggunakan utils
hasAnyPermissions
yang telah kita buat sebelumnya.
{hasAnyPermission(['posts-access']) &&
<NavLink href={route('posts.index')} active={route().current('posts*')}>
Posts
</NavLink>
}
{hasAnyPermission(['posts-access']) &&
<ResponsiveNavLink href={route('posts.index')} active={route().current('posts*')}>
Posts
</ResponsiveNavLink>
}
¶Membuat View Posts
Silahkan teman - teman buat folder baru dengan nama Posts
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 { Head, usePage, useForm } from '@inertiajs/react';
import { IconPencilCog, IconPlus } from '@tabler/icons-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 Search from '@/Components/Search';
import hasAnyPermission from '@/Utils/Permissions';
import Modal from '@/Components/Modal';
import Card from '@/Components/Card';
import Input from '@/Components/Input';
import Textarea from '@/Components/Textarea';
import Swal from 'sweetalert2';
export default function Index({auth}) {
// destruct posts props
const { posts } = usePage().props;
// define state with helper inertia
const { data, setData, post, errors, transform } = useForm({
id: '',
title : '',
content : '',
modalOpen: false,
isUpdateMode: false,
})
// define method handleModalUpdate
const handleModalUpdate = (post) => {
console.log(post);
setData(prevData => ({
...prevData,
id: post.id,
title: post.title,
content: post.content,
modalOpen: true,
isUpdateMode: true
}))
}
transform((data) => ({
...data,
_method: data.isUpdateMode ? 'put' : 'post',
}))
// define method handleStoreData
const handleStoreData = async (e) => {
e.preventDefault();
post(route('posts.store'), {
onSuccess: () => {
setData('modalOpen', !data.modalOpen);
Swal.fire({
title: 'Success!',
text: 'Data created successfully!',
icon: 'success',
showConfirmButton: false,
timer: 1500
});
}
});
}
// define method handleUpdateData
const handleUpdateData = async (e) => {
e.preventDefault();
post(route('posts.update', data.id), {
onSuccess: () => {
setData('modalOpen', !data.modalOpen);
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">Posts</h2>}
>
<Head title={'Posts'}/>
<Container>
<Modal show={data.modalOpen} onClose={() => setData('modalOpen', false)}>
<Card title={data.isUpdateMode ? 'Update Post' : 'Create New Post'}>
<form onSubmit={data.isUpdateMode ? handleUpdateData : handleStoreData}>
<div className='mb-4'>
<Input label={'Title'} type={'text'} value={data.title} onChange={e => setData('title', e.target.value)} errors={errors.title} placeholder="Input title post.."/>
</div>
<div className='mb-4'>
<Textarea rows={4} label={'Content'} value={data.content} onChange={e => setData('content', e.target.value)} errors={errors.content} placeholder="Input content post.."/>
</div>
<div className='flex items-center gap-2'>
<Button type={'submit'}/>
</div>
</form>
</Card>
</Modal>
<div className='mb-4 flex items-center justify-between gap-4'>
{hasAnyPermission(['posts-create']) &&
<Button className={'bg-white text-gray-700 hover:bg-gray-100'} type={'modal'} onClick={() => setData({modalOpen: !data.modalOpen})}>
<IconPlus size={18} strokeWidth={1.5}/> <span className='hidden lg:flex'>Create New Data</span>
</Button>
}
<div className='w-full md:w-4/6'>
<Search url={route('posts.index')} placeholder={'Search posts data by title...'}/>
</div>
</div>
<Table.Card title={'Posts'}>
<Table>
<Table.Thead>
<tr>
<Table.Th>#</Table.Th>
<Table.Th>Title</Table.Th>
<Table.Th>Content</Table.Th>
<Table.Th>Action</Table.Th>
</tr>
</Table.Thead>
<Table.Tbody>
{posts.data.map((post, i) => (
<tr key={i}>
<Table.Td>{++i + (posts.current_page-1) * posts.per_page}</Table.Td>
<Table.Td>{post.title}</Table.Td>
<Table.Td>
<div className='whitespace-pre-wrap'>
{post.content}
</div>
</Table.Td>
<Table.Td>
<div className='flex items-center gap-2'>
{hasAnyPermission(['post-update']) &&
<Button className={'bg-orange-50 text-orange-500 hover:bg-orange-100 border-0'} type={'modal'} onClick={() => handleModalUpdate(post)}>
<IconPencilCog size={18} strokeWidth={1.5}/>
</Button>
}
{hasAnyPermission(['posts-delete']) &&
<Button type={'delete'} url={route('posts.destroy', post.id)}/>
}
</div>
</Table.Td>
</tr>
))}
</Table.Tbody>
</Table>
</Table.Card>
<div className='flex items-center justify-center'>
{posts.last_page !== 1 && (<Pagination links={posts.links}/>)}
</div>
</Container>
</AuthenticatedLayout>
)
}
Pada kode diatas, pertama - tama kita import beberapa file yang kita butuhkan.
Index.jsx
import React from 'react'
import { Head, usePage, useForm } from '@inertiajs/react';
import { IconPencilCog, IconPlus } from '@tabler/icons-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 Search from '@/Components/Search';
import hasAnyPermission from '@/Utils/Permissions';
import Modal from '@/Components/Modal';
import Card from '@/Components/Card';
import Input from '@/Components/Input';
import Textarea from '@/Components/Textarea';
import Swal from 'sweetalert2';
Selanjutnya kita membuat sebuah React Function Component dengan props auth
.
Index.jsx
export default function Index({auth})
Berikutnya kita melakukan destructing sebuah props posts
yang kita dapatkan dari PostController
.
Index.jsx
const { posts } = usePage().props;
Kemudian kita membuat sebuah state menggunakan form helper dari inertia.
Index.jsx
const { data, setData, post, errors, transform } = useForm({
id: '',
title : '',
content : '',
modalOpen: false,
isUpdateMode: false,
})
Selanjutnya kita membuat sebuah method baru dengan nama handleModalUpdate
dengan sebuah parameter post
.
Index.jsx
const handleModalUpdate = (post) => {
setData(prevData => ({
...prevData,
id: post.id,
title: post.title,
content: post.content,
modalOpen: true,
isUpdateMode: true
}))
}
Pada kode diatas, kita lakukan pembaruan beberapa value state yang telah kita definisikan sebelumnya menggunakan parameter post
.
Index.jsx
setData(prevData => ({
...prevData,
id: post.id,
title: post.title,
content: post.content,
modalOpen: true,
isUpdateMode: true
}))
Berikutnya kita memanfaatkan sebuah method transform
yang telah disediakan oleh form helper dari inertia, yang fungsinya untuk memanipulasi data sebelum data dikirimkan ke server.
Index.jsx
transform((data) => ({
...data,
_method: data.isUpdateMode ? 'put' : 'post',
}))
Pada kode diatas, kita memanggil sebuah method transform
dengan menerima sebuah callback state data
, kemudian kita melakukan clone state data menggunakan spread operator ...data
, kemudian kita juga menambahkan state baru dengan nama _method
.
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.
Index.jsx
const handleStoreData = async (e) => {
e.preventDefault();
post(route('posts.store'), {
onSuccess: () => {
setData('modalOpen', !data.modalOpen);
Swal.fire({
title: 'Success!',
text: 'Data created successfully!',
icon: 'success',
showConfirmButton: false,
timer: 1500
});
}
});
}
JIka kita perhatikan, pada method post
kita arahakan ke sebuah route yang bernama posts.store
, dan ketika data berhasil dikirimkan, kita melakukan update sebuah state modalOpen
dan memanggil sebuah sweet alert.
Index.jsx
post(route('posts.store'), {
onSuccess: () => {
setData('modalOpen', !data.modalOpen);
Swal.fire({
title: 'Success!',
text: 'Data created successfully!',
icon: 'success',
showConfirmButton: false,
timer: 1500
});
}
});
Berikutnya kita juga membuat sebuah method baru dengan nama handleUpdateData
yang kita gunakan untuk melakukan proses update data dengan memanfaatkan form helper dari inertia.
Index.jsx
const handleUpdateData = async (e) => {
e.preventDefault();
post(route('posts.update', data.id), {
onSuccess: () => {
setData('modalOpen', !data.modalOpen);
Swal.fire({
title: 'Success!',
text: 'Data updated successfully!',
icon: 'success',
showConfirmButton: false,
timer: 1500
});
}
});
}
Sama halnya dengan penjelasan method handleStoreData
, disini kita hanya melakukan perubahan route
menjadi posts.update
dengan menambahkan value dari state data.id
.
Setelah membuat beberapa logic diatas, kemudian 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">Posts</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={'Posts'}/>
Kemudian kita memanggil sebuah component modal dengan beberapa props diantarnya show
dan onClose
.
Index.jsx
<Modal show={data.modalOpen} onClose={() => setData('modalOpen', false)}>
<Card title={data.isUpdateMode ? 'Update Post' : 'Create New Post'}>
<form onSubmit={data.isUpdateMode ? handleUpdateData : handleStoreData}>
<div className='mb-4'>
<Input label={'Title'} type={'text'} value={data.title} onChange={e => setData('title', e.target.value)} errors={errors.title} placeholder="Input title post.."/>
</div>
<div className='mb-4'>
<Textarea rows={4} label={'Content'} value={data.content} onChange={e => setData('content', e.target.value)} errors={errors.content} placeholder="Input content post.."/>
</div>
<div className='flex items-center gap-2'>
<Button type={'submit'}/>
</div>
</form>
</Card>
</Modal>
Didalam component modal kita memanggil beberapa component yang telah kita buat seperti :
-
Card
-
Input
-
Textarea
-
Button
Selanjutnya kita memanggil sebuah utils hasAnyPermissions
yang telah kita buat sebelumnya untuk melakukan pengecekan apakah user memiliki hak akses untuk melakukan penambahan data posts
.
Index.jsx
{hasAnyPermission(['posts-create']) &&
<Button className={'bg-white text-gray-700 hover:bg-gray-100'} type={'modal'} onClick={() => setData({modalOpen: !data.modalOpen})}>
<IconPlus size={18} strokeWidth={1.5}/> <span className='hidden lg:flex'>Create New Data</span>
</Button>
}
Kemudian kita memanggil sebuah component dengan nama Search
dengan props url
dan placeholder
.
Index.jsx
<Search url={route('posts.index')} placeholder={'Search posts data by title...'}/>
Pada props url
, kita arahkan pencarian data kita ke sebuah route yang bernama posts.index
.
Berikutnya kita juga memanggil sebuah component table dengan props title
.
Index.jsx
<Table.Card title={'Posts'}>
<Table>
<Table.Thead>
<tr>
<Table.Th>#</Table.Th>
<Table.Th>Title</Table.Th>
<Table.Th>Content</Table.Th>
<Table.Th>Action</Table.Th>
</tr>
</Table.Thead>
<Table.Tbody>
{posts.data.map((post, i) => (
<tr key={i}>
<Table.Td>{++i + (posts.current_page-1) * posts.per_page}</Table.Td>
<Table.Td>{post.title}</Table.Td>
<Table.Td>
<div className='whitespace-pre-wrap'>
{post.content}
</div>
</Table.Td>
<Table.Td>
<div className='flex items-center gap-2'>
{hasAnyPermission(['post-update']) &&
<Button className={'bg-orange-50 text-orange-500 hover:bg-orange-100 border-0'} type={'modal'} onClick={() => handleModalUpdate(post)}>
<IconPencilCog size={18} strokeWidth={1.5}/>
</Button>
}
{hasAnyPermission(['posts-delete']) &&
<Button type={'delete'} url={route('posts.destroy', post.id)}/>
}
</div>
</Table.Td>
</tr>
))}
</Table.Tbody>
</Table>
</Table.Card>
Pada kode diatas, kita melakukan perulangan data posts
menggunakan method map
.
Index.jsx
{posts.data.map((post, i) => (
.....
))}
Selanjutnya kita juga melakukan pengecekan sebuah permissions yang dimiliki oleh users dengan menggunakan utils
yang telah kita buat sebelumnya.
Index.jsx
{hasAnyPermission(['post-update']) &&
<Button className={'bg-orange-50 text-orange-500 hover:bg-orange-100 border-0'} type={'modal'} onClick={() => handleModalUpdate(post)}>
<IconPencilCog size={18} strokeWidth={1.5}/>
</Button>
}
{hasAnyPermission(['posts-delete']) &&
<Button type={'delete'} url={route('posts.destroy', post.id)}/>
}
Terakhir kita juga memanggil sebuah component pagination dengan sebuah props links
.
Index.jsx
{posts.last_page !== 1 && (<Pagination links={posts.links}/>)}
¶Screenshoot Hasil




¶Penutup
Pada artikel kali ini kita telah berhasil menyelesaikan pembuatan module posts, selanjutnya kita akan lanjutkan untuk pembuatan module permissions.