composer.json
{ // "require": { // "inertiajs/inertia-laravel": "^0.3.5", // }, //}
{ // "require": { // "inertiajs/inertia-laravel": "^0.3.5", // }, //}
{ // "devDependencies": { // "@inertiajs/inertia": "^0.9.0", "@inertiajs/inertia-react": "^0.6.0", "@inertiajs/progress": "^0.2.4", // }, //}
use Illuminate\Http\Request;use App\Models\Category;use Illuminate\Validation\Rule; class CategoryController extends Controller{ public function index(Request $request) { if ($request->q != null) { $query = Category::where('name', 'like', '%'.$request->q.'%') ->orWhere('description', 'like', '%'.$request->q.'%') ->orderBy('created_at', 'asc') ->paginate(10); } else { $query = Category::orderBy('created_at', 'asc')->paginate(10); } return inertia('Category', [ 'categories' => $query, '_search' => $request->q ? $request->q : '' ]); } public function store(Request $request) { $request->validate([ 'name' => [ 'required', 'string', 'max:255', Rule::unique('categories', 'name')->where(function ($query) { return $query->where('deleted_at', null); }) ], 'description' => 'required|string|max:255', 'amount' => 'required|numeric|max:999999999|min:1' ]); $category = Category::create([ 'name' => $request->name, 'description' => $request->description, 'default_budget' => $request->amount ]); $category->budgets()->create([ 'budget' => $request->amount, 'start_date' => now()->toDateString(), 'end_date' => null, 'remain' => $request->amount ]); return redirect()->route('categories'); } public function update(Request $request, Category $category) { $request->validate([ 'name' => 'required|string|max:255', 'description' => 'required|string|max:255', 'amount' => 'required|numeric|max:999999999|min:1' ]); $category->update([ 'name' => $request->name, 'description' => $request->description, 'default_budget' => $request->amount, ]); $budget = $category->budgets()->where('end_date', null)->first(); $budget->update([ 'budget' => $request->amount, 'remain' => ($request->amount + $budget->rollover) - ($budget->total_used) ]); return redirect()->route('categories'); } public function destroy(Category $category) { $category->budgets()->delete(); $category->delete(); return redirect()->route('categories'); }}
import React, { useState, useEffect } from 'react'import NumberFormat from 'react-number-format'import { usePrevious } from 'react-use'import { toast } from 'react-toastify'import { Head, useForm } from '@inertiajs/inertia-react'import { Inertia } from '@inertiajs/inertia'import { formatIDR } from '@/utils'import Pagination from '@/Components/Pagination'import Authenticated from '@/Layouts/Authenticated' export default function Category(props) { const { _search } = props const [search, setSearch] = useState(_search) const preValue = usePrevious(search) const [category, setCategory] = useState(null) const { data: categories , links } = props.categories const { data, setData, errors, post, put, processing, delete: destroy } = useForm({ name: '', description: '', amount: 0 }) const handleChange = (e) => { const key = e.target.id; const value = e.target.value setData(key, value) } const handleReset = () => { setCategory(null) setData({ name: '', description: '', amount: '' }) } const handleEdit = (category) => { setCategory(category) setData({ name: category.name, description: category.description, amount: category.default_budget }) } const handleDelete = (category) => { destroy(route('categories.destroy', category), { onBefore: () => confirm('Are you sure you want to delete this record?'), onSuccess: () => Promise.all([ handleReset(), toast.success('data has been deleted') ]) }) } const handleSubmit = (e) => { e.preventDefault() if(category !== null) { put(route('categories.update', category), { onSuccess: () => Promise.all([ handleReset(), toast.success('The Data has been changed') ]) }) return } post(route('categories.store'), { onSuccess: () => Promise.all([ handleReset(), toast.success('Data has been saved') ]) }) } useEffect(() => { if (preValue) { Inertia.get(route(route().current()), { q: search }, { replace: true, preserveState: true, }) } }, [search]) return ( <Authenticated errors={props.errors} header={ <h2 className="font-semibold text-xl text-gray-800 leading-tight"> Category </h2> } > <Head title="Category" /> <div className="flex flex-col space-y-2 md:space-y-0 md:flex-row py-12"> <div className="w-full md:w-1/3 px-6 md:pl-8"> <div className="card bg-white"> <div className="card-body"> <div className="form-control"> <label className="label"> <span className="label-text"> Category Name </span> </label> <input type="text" placeholder="Name" className={`input input-bordered ${ errors.name ? 'input-error' : '' }`} id="name" value={data.name} onChange={handleChange} /> <label className="label"> <span className="label-text-alt"> {errors.name} </span> </label> </div> <div className="form-control"> <label className="label"> <span className="label-text"> Description </span> </label> <input type="text" placeholder="Description" className={`input input-bordered ${ errors.description ? 'input-error' : '' }`} id="description" value={data.description} onChange={handleChange} /> <label className="label"> <span className="label-text-alt"> {errors.description} </span> </label> </div> <div className="form-control"> <label className="label"> <span className="label-text">Amount</span> </label> <NumberFormat thousandSeparator={true} className={`input input-bordered ${ errors.amount ? 'input-error' : '' }`} value={data.amount} thousandSeparator="." decimalSeparator="," onValueChange={({ value }) => setData('amount', value) } /> <label className="label"> <span className="label-text-alt"> {errors.amount} </span> </label> </div> <div className="card-actions"> <button className={`btn btn-primary ${ processing && 'animate-spin' }`} onClick={handleSubmit} disabled={processing} > Add </button> <button className="btn btn-secondary" onClick={handleReset} disabled={processing} > Clear </button> </div> </div> </div> </div> <div className="w-full md:w-2/3 px-6 md:pr-8"> <div className="card bg-white"> <div className="card-body"> <div className="flex justify-end my-1"> <div className="form-control"> <input type="text" className="input input-bordered" value={search} onChange={(e) => setSearch(e.target.value) } placeholder="Search" /> </div> </div> <div className="overflow-x-auto"> <table className="table w-full table-zebra"> <thead> <tr> <th></th> <th className="w-36"> Category Name </th> <th>Description</th> <th>Amount</th> <th className="w-52"></th> </tr> </thead> <tbody className={processing ? 'opacity-70' : ''} > {categories?.map((category) => ( <tr key={category.id}> <th>{category.id}</th> <td>{category.name}</td> <td>{category.description}</td> <td> {formatIDR( category.default_budget )} </td> <td> <div className="btn btn-warning mx-1" onClick={() => handleEdit(category) } > Edit </div> <div className="btn btn-error mx-1" onClick={() => handleDelete(category) } > Delete </div> </td> </tr> ))} </tbody> </table> </div> <Pagination links={links} params={{ q: search }}/> </div> </div> </div> </div> </Authenticated> )}