article

Tuesday, August 5, 2025

React + Vite How to Create Portfolios No coding using Lovable AI

React + Vite How to Create Portfolios No coding using Lovable AI

Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest

Update to latest tailwindcss
npm install tailwindcss@latest

Sunday, August 3, 2025

React Vite How to Setup Path Aliases | from @/components/Navigation

React Vite How to Setup Path Aliases | from @/components/Navigation

Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest
Install react-router-dom

my-app\src\App.tsx my-app\src\App.tsx
//src\App.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import NotFound from "./pages/NotFound";

const App = () => (
  <BrowserRouter>
    <Routes>
        <Route path="/" element={<Index />} />
        <Route path="*" element={<NotFound />} />
    </Routes>
  </BrowserRouter>
);

export default App;
my-app\src\pages\Index.tsx
//pages\Index.tsx
import { Navigation } from "@/components/Navigation";

const Index = () => {
  return (
    <div className="min-h-screen">
      <Navigation />
      <main>
        <div id="home">
          Heror
        </div>
        <h1>About</h1>
        <h1>Skills</h1>
        <h1>Projects</h1>
        <h1>Contact</h1>
      </main>
      Footer
    </div>
  );
};

export default NotFound;
my-app\src\pages\NotFound.tsx
//pages\NotFound.tsx
import { useLocation } from "react-router-dom";
import { useEffect } from "react";

const NotFound = () => {
  const location = useLocation();

  useEffect(() => {
    console.error(
      "404 Error: User attempted to access non-existent route:",
      location.pathname
    );
  }, [location.pathname]);

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <div className="text-center">
        <h1 className="text-4xl font-bold mb-4">404</h1>
        <p className="text-xl text-gray-600 mb-4">Oops! Page not found</p>
        <a href="/" className="text-blue-500 hover:text-blue-700 underline">
          Return to Home
        </a>
      </div>
    </div>
  );
};

export default Navigation;
my-app\src\pages\Navigation.tsx
//pages\Navigation.tsx
import { useState, useEffect } from "react";
import logo from "@/assets/react.svg";

const navLinks = [
  { label: "Home", href: "#home" },
  { label: "About", href: "#about" },
  { label: "Skills", href: "#skills" },
  { label: "Portfolios", href: "#portfolios" },
  { label: "Contact", href: "#contact" },
];

export function Navigation() {
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      setScrolled(window.scrollY > 50);
    };
    
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  const scrollToSection = (href: string) => {
    console.log(`Navigating to ${href}`);
  };

  return (
    <nav className={`fixed top-0 left-0 right-0 z-50 transition-smooth ${
      scrolled ? "bg-background/90 backdrop-blur-md shadow-card" : "bg-transparent"
    }`}>
      <div className="container mx-auto max-w-6xl px-4">
        <div className="flex items-center justify-between h-16">
          {/* Logo */}
          <div className="text-2xl font-bold">
              <img 
                src={logo} 
                alt="logo" 
                className="h-[50px]"
              />
          </div>

          {/* Desktop Navigation */}
          <div className="hidden md:flex items-center space-x-8">
            {navLinks.map((link) => (
              <button
                key={link.label}
                onClick={() => scrollToSection(link.href)}
                className="text-muted-foreground hover:text-primary transition-smooth font-medium"
              >
                {link.label}
              </button>
            ))}
            <button>
              Hire Me
            </button>
          </div>
        </div>
      </div>
    </nav>
  );
}
Update vite.config.ts
my-app\vite.config.ts
//vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@/components': '/src/components',
      '@/assets': '/src/assets',
      '@/lib': '/src/lib',
    },
  },
});
Update tsconfig.json
my-app\tsconfig.json
//tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
        "@components/*": ["src/components/*"],
        "@assets/*": ["src/assets/*"],
        "@lib/*": ["src/lib/*"]
    }
  }
}
Run
myapp\my-app> npm run dev

Monday, July 28, 2025

python tkinter chatbot google API Gemini

python tkinter chatbot google API Gemini chatbot.py
 
import tkinter as tk
from tkinter import scrolledtext, Entry, Button
import google.generativeai as genai

class SimpleChatbot:
    def __init__(self, root):
        self.root = root
        self.root.geometry("700x480") #Initial size and position
        self.root.title("Generative AI Chatbot")

        # Configure the Generative AI API with your API key
        genai.configure(api_key='AIzaSyA8ZbJrTvnMnnDOlN3HDT3cnwsZ906321654654WhQY')  # Replace with your actual API key

        # Create a model instance (adjust parameters as per documentation)
        self.model = genai.GenerativeModel(model_name="gemini-2.0-flash")  # Example model name

        # Create a scrolled text area for displaying the conversation
        self.conversation_area = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=90, height=30)
        self.conversation_area.grid(column=0, row=0, padx=10, pady=10, columnspan=2)
        self.conversation_area.insert(tk.INSERT, "Chatbot: Hello! How can I help you today?\n")

        # Create an entry widget for the user to type their message
        self.user_input = Entry(root, width=60)
        self.user_input.grid(column=0, row=1, padx=10, pady=10)

        # Create a button to send the message
        self.send_button = Button(root, text="Send", command=self.send_message)
        self.send_button.grid(column=1, row=1, padx=10, pady=10)

    def send_message(self):
        user_message = self.user_input.get()
        self.conversation_area.insert(tk.INSERT, f"You: {user_message}\n")
        self.user_input.delete(0, tk.END)

        # Call the Generative AI API to get a response
        response = self.model.generate_content(user_message)  # Hypothetical method
        bot_response = f"Chatbot: {response.text}\n"
        self.conversation_area.insert(tk.INSERT, bot_response)

if __name__ == "__main__":
    root = tk.Tk()
    chatbot = SimpleChatbot(root)
    root.mainloop()

Wednesday, June 4, 2025

Laravel 12 Simple Inertia React Pagination Components | React Starter Kit

Laravel 12 Simple Inertia React Pagination Components | React Starter Kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller UsersController change it with the following codes:
app\Http\Controllers\UsersController.php

//app\Http\Controllers\UsersController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;

class UsersController extends Controller
{
    public function home()
    {
        $query = User::select('id', 'name', 'email', 'created_at')->latest();

        // Handle search
        if (request()->has('search')) {
            $search = request('search');
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%");
            });
        }

        $users = $query->paginate(10);

        return Inertia::render('users/index', [
            'users' => $users,
            'filters' => [
                'search' => request('search', ''),
            ]
        ]);
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
File: pages\users\index.tsx
reactjs\resources\js\pages\users\index.tsx
//reactjs\resources\js\pages\users\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import { Search } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import Pagination from '@/components/Pagination'; 
import { useState } from 'react';

interface User {
    id: number;
    name: string;
    email: string;
    created_at: string;
}

interface Props {
    users: {
        data: User[];
        current_page: number;
        last_page: number;
        per_page: number;
        total: number;
        from: number;
        to: number;
    };
    filters: {
        search: string;
        filter: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Laravel 12 Simple Inertia React Pagination Components | React Starter Kit',
        href: '/admin/users',
    },
];

export default function UsersIndex({ users, filters }: Props) {
    //console.log(users); 
    const [searchTerm, setSearchTerm] = useState(filters.search);

    const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        router.get(route('users.home'), {
            search: searchTerm,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };
    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Users" />
            <div className="flex h-full flex-1 flex-col gap-6 rounded-xl p-6 bg-gradient-to-br from-background to-muted/20">
                <div className="flex gap-4 mb-4">
                    <form onSubmit={handleSearch} className="relative flex-1">
                        <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                        <Input placeholder="Search name or email..."
                            value={searchTerm}
                            onChange={(e) => setSearchTerm(e.target.value)}
                            className="pl-10 bg-gray-200  text-black"
                        />
                    </form>                                     
                </div>
                <div className="rounded-md border">
                    <div className="relative w-full overflow-auto">
                        <table className="w-full caption-bottom text-sm  bg-white rounded-lg">
                            <thead>
                                <tr className="bg-gray-200 text-black">
                                    <th className="py-2 px-4 text-left border-b">ID</th>
                                    <th className="py-2 px-4 text-left border-b">Name</th>
                                    <th className="py-2 px-4 text-left border-b">Email</th>
                                    <th className="py-2 px-4 text-left border-b">Created At</th>                                   
                                    <th className="py-2 px-4 text-right border-b">Actions</th>                                
                                </tr>                            
                            </thead>   
                            <tbody>
                                {users.data.map((user) => (
                                    <tr key={user.id} className="hover:bg-gray-50 text-black">
                                        <td className="p-4 align-middle font-medium border-b">{user.id}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.name}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.email}</td>
                                        <td className="p-4 align-middle font-medium border-b">{new Date(user.created_at).toLocaleDateString()}</td>
                                        <td className="p-4 align-middle text-right border-b">
                                            <div className="flex justify-end gap-2">
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-blue-600 text-white rounded-lg px-4 py-2 hover:bg-blue-700"
                                                >
                                                    Edit
                                                </Button>                                                
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-red-600 text-white rounded-lg px-4 py-2 hover:bg-red-700 transition transition ml-2"
                                                >
                                                    Delete
                                                </Button>                                            
                                            </div>
                                        </td>
                                    </tr>
                                ))}
                                {users.total === 0 && (
                                    <tr>
                                        <td colSpan={6} className="p-4 text-center text-muted-foreground">
                                            No users Found                                        
                                        </td>
                                    </tr>
                                )}
                            </tbody> 
                        </table>              
                    </div>                
                </div>
                {/* Pagination */}
                <div className="flex items-center justify-between px-2">
                    <div className="text-sm text-muted-foreground">
                        Showing {users.from} to {users.to} of {users.total} results  
                    </div>                    
                    <div className="flex items-center space-x-2">
                    <Pagination links={users.links}/>                  
                    </div>                
                </div> 
            </div>
        </AppLayout>
    );
}
File: js\components\Pagination.tsx
reactjs\resources\js\components\Pagination.tsx
//reactjs\resources\js\components\Pagination.tsx
import { Link } from '@inertiajs/react'

export default function Pagination({ links }) {
    console.log(links);
    return (
        <div className="flex flex-wrap items-center space-x-1 mt-4">
            {links.map((link, index) => (
                <Link 
                    key={index}    
                    href={link.url ?? '#'}
                    dangerouslySetInnerHTML={{ __html: link.label}}
                    className={`text-lg rounded-lg px-4 py-2 border gap-2
                        ${link.active ? 'border-t-blue-600 text-white  bg-blue-600' : 'bg-white text-gray-700'}
                        ${!link.url ? 'opacity-50 pointer-events-none' : 'hover:bg-gray-100'}`}
                />
            ))}
        </div>
    )
}
php artisan tinker
User::factory()->count(1000)->create()
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\UsersController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/admin/users-pagination-search', [UsersController::class, 'home'])->name('users.home');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Friday, May 30, 2025

Laravel 12 React Starter kit Schedule Calendar | Fullcalendar CRUD

Laravel 12 React Starter kit Schedule Calendar | Fullcalendar CRUD

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Schedule Model php artisan make:model Schedule -m myapp>php artisan make:model Schedule -m Open new Schedule migrations yourproject/database/migrations laravelproject\database\migrations\_create_schedule_table.php
//laravelproject\database\migrations\_create_schedule_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('schedules', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->dateTime('start');
            $table->dateTime('end');
            $table->string('color')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('schedules');
    }
};
run
myapp>php artisan migrate
update Schedule Model
app/models/Schedule.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Schedule extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'start', 
        'end',
        'color',
    ];
}
Create ScheduleController
php artisan make:controller ScheduleController
change it with the following codes:
app\Http\Controllers\ScheduleController.php

//app\Http\Controllers\ScheduleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Schedule;

class ScheduleController extends Controller
{
    public function index()
    {
        $query = Schedule::select('id', 'title', 'start', 'end', 'color', 'created_at')->get();

        return Inertia::render('schedule/index', [
            'schedules' => $query,
            'flash' => [
                'success' => session('success'),
                'error' => session('error')
            ]
        ]);
    }

    public function store(Request $request)
    {
        Schedule::create([
            'title' => $request->title,
            'start' => $request->startdate,
            'end' => $request->enddate,
            'color' => $request->color,
        ]);

        return redirect()->route('schedule.index')->with('success', 'Schedule created successfully!');
    }

    public function destroy($id)
    {
        Schedule::findOrFail($id)->delete();
        return redirect()->route('schedule.index')->with('success', 'Schedule Deleted successfully!');
    }

    public function update(Request $request, $id)
    {
        $schedule = Schedule::findOrFail($id);
        $schedule->update($request->only('start', 'end'));

        return redirect()->route('schedule.index')->with('success', 'Schedule Updated successfully!');
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
Index.tsx File: pages\schedule\index.tsx
reactjs\resources\js\pages\schedule\index.tsx
//reactjs\resources\js\pages\schedule\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid' //npm install --save @fullcalendar/react @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction
import interactionPlugin from "@fullcalendar/interaction"; // needed for dayClick
import {
  DateSelectArg,
  formatDate,
} from "@fullcalendar/core"; //   npm install @fullcalendar/interaction https://fullcalendar.io/docs/react
import { useState, useEffect } from 'react'; 
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { DialogDescription } from '@radix-ui/react-dialog';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { CheckCircle2, XCircle } from 'lucide-react';

interface Schedule {
    id: number;
    title: string;
    start: Date;
    end: Date;
    color: string;
}
  
interface Props {
    schedules: Schedule[];
    flash?: {
        success?: string;
        error?: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Schedule',
        href: '/admin/schedule',
    },
];

export default function Dashboard({ schedules, flash }: Props) {
    //console.log(schedules);
    const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
    const [title, setTitle] = useState<string>(""); 
    const [color, setColor] = useState<string>("#000000");
    const [startdate, setStart] = useState<DateSelectArg | null>(null);
    const [enddate, setEnd] = useState<DateSelectArg | null>(null);
    const [toastMessage, setToastMessage] = useState('');
    const [toastType, setToastType] = useState<'success' | 'error'>('success');
    const [showToast, setShowToast] = useState(false);
    //console.log(flash);
    useEffect(() => { 
      if (flash?.success) { 
          setToastMessage(flash.success);
          setToastType('success');
          setShowToast(true); 
      } else if (flash?.error) { 
          setToastMessage(flash.error);
          setToastType('error');
          setShowToast(true);
      }
    }, [flash]);

    useEffect(() => {
        if (showToast) { 
            const timer = setTimeout(() => {
                setShowToast(false);
            }, 3000);
            return () => clearTimeout(timer);
        }
    }, [showToast]);

    const handleDateClick = (selected: DateSelectArg) => {
        const start = selected.startStr
        const end = selected.endStr
        //console.log(start)
        setStart(start)
        setEnd(end)
        setIsDialogOpen(true);
    };

    function handleSubmit(e: React.FormEvent) {
        e.preventDefault();
        //console.log(color);
        router.post(route('schedule.store'), {
            _method: 'post',
            title,
            startdate,
            enddate,
            color,
        });
        handleCloseDialog();
    };

    const handleCloseDialog = () => {
        setIsDialogOpen(false);
    };

    function formatEvents() {
        return schedules.map(schedule => {
            const {id, title, start, end, color} = schedule
      
            let startTime = new Date(start)
            let endTime = new Date(end)
      
            return {
              id, 
              title, 
              start: startTime,
              end: endTime, 
              color,
              }
        })
    }

    function handleEventDrop(info) {
        if(window.confirm("Are you sure you want to change the event date?")){
            //console.log('change confirmed')
            //console.log(info.event.start)
            const start = (new Date(info.event.start)).toISOString().slice(0, 10)
            const end = (new Date(info.event.end)).toISOString().slice(0, 10)
  
            router.put(`/admin/schedule/${info.event.id}`, {
              _method: 'put',
              start,
              end,
            });
        } else {
            console.log('change aborted')
        }
    }

    function handleEventClick(data) {
        console.log(data);
        console.log(data.event.id);
        if (confirm(`Are you sure you want to delete the event '${data.event.title}'`)) {
            router.delete(`/admin/schedule/${data.event.id}`, {
                onSuccess: () => {
                    router.reload();
                },
                onError: () => {
                    console.error("Failed to delete post.");
                },
            });
            data.event.remove()
        }
    }

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Schedule" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                {showToast && (
                    <div className={`fixed top-4 right-4 z-50 flex items-center gap-2 rounded-lg p-4 shadow-lg ${toastType === 'success' ? 'bg-green-500' : 'bg-red-500'
                        } text-white animate-in fade-in slide-in-from-top-5`}>
                        {toastType === 'success' ? (
                            <CheckCircle2 className="h-5 w-5" />
                        ) : (
                            <XCircle className="h-5 w-5" />
                        )}
                        <span>{toastMessage}</span>                    
                </div>)}

                <h1>Laravel 12 React Starter kit Schedule Calendar | Fullcalendar</h1>
                <FullCalendar
                    plugins={[dayGridPlugin, interactionPlugin]} // Initialize calendar with required plugins.
                    headerToolbar = {{
                        left: 'prev,next' ,
                        center: 'title',
                        right: 'dayGridMonth,timeGridWeek,timeGridDay'
                    }}
                    initialView="dayGridMonth"
                    editable={true}
                    selectable={true} // Allow dates to be selectable.
                    selectMirror={true} // Mirror selections visually.
                    select={handleDateClick} // Handle date selection to create new events.
                    events={formatEvents()}
                    eventDrop={handleEventDrop}
                    eventClick={handleEventClick}
                    //events={[
                    //    {title : 'Event 1', start: '2025-05-19', end: '2025-05-21'},
                    //]}
                />
            </div>
            <div className="flex justify-between items-center">                
                    <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>                     
                        <DialogContent>
                            <DialogHeader>
                                <DialogTitle>Add New Event Details</DialogTitle>   
                                <DialogDescription>
                                    Make changes to your category here. Click update when you're done.
                                </DialogDescription>                        
                            </DialogHeader>   
                            <form onSubmit={handleSubmit} className="space-y-4">
                                <div className="space-y-2">
                                    <Label htmlFor="title">Title</Label>                                    
                                    <Input id="title"
                                    type="text"
                                    value={title}
                                    onChange={(e) => setTitle(e.target.value)}
                                    />
                                </div>                               
                                <div className="space-y-2">
                                    <Label htmlFor="color">Color</Label>                                    
                                    <Input type="color" id="color" name="color"
                                    value={color}
                                    onChange={(e) => setColor(e.target.value)}
                                    />
                                </div>                               
                                <Button type="submit">
                                    Create
                                </Button>                            
                            </form>                
                        </DialogContent>                    
                    </Dialog>                
            </div>
        </AppLayout>
    );
}
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\ScheduleController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/admin/schedule', [ScheduleController::class, 'index'])->name('schedule.index');
    Route::post('/admin/schedule', [ScheduleController::class, 'store'])->name('schedule.store');
    Route::delete('/admin/schedule/{id}', [ScheduleController::class, 'destroy'])->name('schedule.destroy');
    Route::put('/admin/schedule/{id}', [ScheduleController::class, 'update'])->name('users.update');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Wednesday, May 28, 2025

Laravel 12 Schedule Calendar | Fullcalendar

Laravel 12 Schedule Calendar | Fullcalendar

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Schedule Model php artisan make:model Schedule -m myapp>php artisan make:model Schedule -m Open new Schedule migrations yourproject/database/migrations laravelproject\database\migrations\_create_schedule_table.php
//laravelproject\database\migrations\_create_schedule_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('schedules', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->dateTime('start');
            $table->dateTime('end');
            $table->string('color')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('schedules');
    }
};
run
myapp>php artisan migrate
update Schedule Model
app/models/Schedule.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Schedule extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'start', 
        'end',
        'color',
    ];
}
Create ScheduleController
php artisan make:controller ScheduleController
change it with the following codes:
app\Http\Controllers\ScheduleController.php

//app\Http\Controllers\ScheduleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Schedule;
use Carbon\Carbon;

class ScheduleController extends Controller
{
    public function index()
    {
        return view('schedule.index');
    }

    public function store(Request $request)
    {
        $item = new Schedule();
        $item->title = $request->title;
        $item->start = $request->start;
        $item->end = $request->end;
        $item->color = $request->color;
        $item->save();

        return redirect()->route("schedule.index");
    }

    public function getEvents()
    {
        $schedules = Schedule::all();
        return response()->json($schedules);
    }

    public function update(Request $request, $id)
    {
        $schedule = Schedule::findOrFail($id);

        $schedule->update([
            'start' => Carbon::parse($request->input('start_date'))->setTimezone('UTC'),
            'end' => Carbon::parse($request->input('end_date'))->setTimezone('UTC'),
        ]);

        return response()->json(['message' => 'Event moved successfully']);
    }

    public function deleteEvent($id)
    {
        $schedule = Schedule::findOrFail($id);
        $schedule->delete();

        return response()->json(['message' => 'Event deleted successfully']);
    }

    public function search(Request $request)
    {
        $searchKeywords = $request->input('title');
        $matchingEvents = Schedule::where('title', 'like', '%' . $searchKeywords . '%')->get();

        return response()->json($matchingEvents);
    }
}
Create AuthController
php artisan make:controller AuthController
change it with the following codes:
app\Http\Controllers\AuthController.php

//app\Http\Controllers\AuthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function login()
    {
        $pass = Hash::make("123456789");
        return view("login")->with('password', $pass);
    }

    public function loginAction(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email',
            'password' => 'required'
        ]);

        if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
            throw ValidationException::withMessages([
                'email' => trans('auth.failed')
            ]);
        }

        $request->session()->regenerate();
        return redirect()->route("products.index")->with("success", "Login successfully");
    }

    public function logout(Request $request)
    {
        Auth::guard('web')->logout();

        $request->session()->invalidate();

        return redirect('/login');
    }
}
Create View File resources/views/layouts/app.blade.php
//resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
    <title>Schedule Fullcalendar</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
</head>

<body>
    <div class="layout-wrapper layout-content-navbar">
        <div class="layout-container">
            <div class="layout-page">
                <div class="content-wrapper">
                    @yield('content')
                </div>
            </div>
        </div>
    </div>
</body>

</html>
Home resources/views/schedule/index.blade.php
//resources/views/schedule/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container mt-5">
    <div class="row">
        <h1>Laravel 12 Schedule Calendar | Fullcalendar</h1>
        <div class="col-md-6">
            <div class="input-group mb-3">
                <input type="text" id="searchInput" class="form-control" placeholder="Search events">
                <div class="input-group-append">
                    <button id="searchButton" class="btn btn-primary">Search</button>
                </div>
            </div>
        </div>
        <div class="col-md-6">
            Color : <input type="color" id="myColor" class='form-control' name="colorpicker" onchange="myFunction()" />
            <p id="demo"></p>
        </div>
    </div>

    @if (Session::has('success'))
    <span class="alert alert-success p-2">{{ Session::get('success')}}</span>
    @endif
    @if (Session::has('error'))
    <span>{{ Session::get('error')}}</span>
    @endif

    <div class="card">
        <div class="card-body">
            <div id="calendar" style="width: 100%;height:100vh"></div>
        </div>
    </div>
</div>
<script
    src="https://code.jquery.com/jquery-3.7.1.js"
    integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4="
    crossorigin="anonymous"></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.17/index.global.min.js'></script> <!--fullcalendar.io/docs/initialize-globals-->

<script type="text/javascript">
    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

    var calendarEl = document.getElementById('calendar');
    var events = [];
    var calendar = new FullCalendar.Calendar(calendarEl, {
        headerToolbar: {
            left: 'prev,next today',
            center: 'title',
            right: 'dayGridMonth,timeGridWeek,timeGridDay'
        },
        initialView: 'dayGridMonth',
        events: '/events',
        editable: true,
        selectable: true,
        select: function(info) {
            var title = prompt('Event Title:');
            var color = prompt("Color", "#0d6efd");
            if (title && color) {
                //alert('selected ' + info.startStr + ' to ' + info.endStr);
                $.ajax({
                    url: "/create-schedule",
                    data: 'title=' + title + '&start=' + info.startStr + '&end=' + info.endStr + '&color=' + color + '&_token=' + "{{ csrf_token() }}",
                    type: "post",
                    success: function(data) {
                        alert("Added Successfully");
                        calendar.refetchEvents(); // Refresh events
                    }
                });

            }
        },
        // Drag And Drop
        eventDrop: function(info) {
            var eventId = info.event.id;
            var newStartDate = info.event.start;
            var newEndDate = info.event.end || newStartDate;
            var newStartDateUTC = newStartDate.toISOString().slice(0, 10);
            var newEndDateUTC = newEndDate.toISOString().slice(0, 10);

            $.ajax({
                method: 'post',
                url: `/schedule/${eventId}`,
                data: {
                    '_token': "{{ csrf_token() }}",
                    start_date: newStartDateUTC,
                    end_date: newEndDateUTC,
                },
                success: function(response) {
                    alert(response.message);
                    console.log('Event moved successfully.');
                },
                error: function(error) {
                    console.error('Error moving event:', error);
                }
            });
        },
        // Deleting The Event
        eventContent: function(info) {
            var eventTitle = info.event.title;
            var eventElement = document.createElement('div');
            eventElement.innerHTML = '<span style="cursor: pointer;">❌</span> ' + eventTitle;

            eventElement.querySelector('span').addEventListener('click', function() {
                if (confirm("Are you sure you want to delete this event?")) {
                    var eventId = info.event.id;
                    $.ajax({
                        method: 'get',
                        url: '/schedule/delete/' + eventId,
                        headers: {
                            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                        },
                        success: function(response) {
                            alert(response.message);
                            console.log('Event deleted successfully.');
                            calendar.refetchEvents(); // Refresh events after deletion
                        },
                        error: function(error) {
                            console.error('Error deleting event:', error);
                        }
                    });
                }
            });
            return {
                domNodes: [eventElement]
            };
        },
    });
    calendar.render();

    document.getElementById('searchButton').addEventListener('click', function() {
        var searchKeywords = document.getElementById('searchInput').value.toLowerCase();
        filterAndDisplayEvents(searchKeywords);
    });

    function filterAndDisplayEvents(searchKeywords) {
        $.ajax({
            method: 'GET',
            url: `/events/search?title=${searchKeywords}`,
            success: function(response) {
                calendar.removeAllEvents();
                calendar.addEventSource(response);
            },
            error: function(error) {
                console.error('Error searching events:', error);
            }
        });
    }

    function myFunction() {
        var x = document.getElementById("myColor").value;
        document.getElementById("demo").innerHTML = x;
    }
</script>
@endsection
resources/views/login.blade.php
//resources/views/login.blade.php
@extends('layouts.app')
@section('content')
<div class="container mt-5">
    <div class="card">
        <div class="card-header">
            Login
        </div>
        <div class="card-body">
            <form action="{{ route('login.action') }}" method="POST" class="user">
                @csrf
                @if ($errors->any())
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
                @endif
                <div class="mb-3">
                    <label class="form-label">Email</label>
                    <input name="email" type="email" class="form-control form-control-user" id="exampleInputEmail" aria-describedby="emailHelp" placeholder="Enter Email Address...">
                </div>
                <div class="mb-3">
                    <label class="form-label">Password</label>
                    <input name="password" type="password" class="form-control form-control-user" id="exampleInputPassword" placeholder="Password">
                    {{ $password }}
                </div>
                <div class="mb-3">
                    <div class="custom-control custom-checkbox small">
                        <input name="remember" type="checkbox" class="custom-control-input" id="customCheck">
                        <label class="custom-control-label" for="customCheck">Remember
                            Me</label>
                    </div>
                </div>
                <button type="submit" class="btn btn-primary btn-block btn-user">Login</button>
            </form>
        </div>
    </div>

</div>

@endsection
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ScheduleController; //php artisan make:controller ScheduleController
use App\Http\Controllers\AuthController;

Route::get('/', function () {
    return view('welcome');
});

Route::middleware('auth')->group(function () {
    Route::controller(ScheduleController::class)->group(function () {
        Route::get('/fullcalender', 'index')->name('schedule.index');
        Route::post('/create-schedule', 'store')->name('schedule.store');
        Route::get('/events', 'getEvents')->name('schedule.events');
        Route::post('/schedule/{id}', 'update')->name('schedule.update');
        Route::get('/schedule/delete/{id}', 'deleteEvent')->name('schedule.deleteEvent');
        Route::get('/events/search', 'search')->name('schedule.search');
    });
});

Route::controller(AuthController::class)->group(function () {
    Route::get('login', 'login')->name('login');
    Route::post('login', 'loginAction')->name('login.action');
    Route::get('logout', 'logout')->middleware('auth')->name('logout');
});

Wednesday, May 14, 2025

Laravel 12 React Pagination and Search | React Starter kit

Laravel 12 React Pagination and Search | React Starter kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller UsersController change it with the following codes:
app\Http\Controllers\UsersController.php

//app\Http\Controllers\UsersController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;

class UsersController extends Controller
{
    public function home()
    {
        $query = User::select('id', 'name', 'email', 'created_at')->latest();

        // Handle search
        if (request()->has('search')) {
            $search = request('search');
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%");
            });
        }

        $users = $query->paginate(10);

        return Inertia::render('users/index', [
            'users' => $users,
            'filters' => [
                'search' => request('search', ''),
            ]
        ]);
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
File: pages\users\index.tsx
reactjs\resources\js\pages\users\index.tsx
//reactjs\resources\js\pages\users\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import { Button } from '@/components/ui/button';
import { Search, ChevronLeft, ChevronRight } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { useState } from 'react';

interface User {
    id: number;
    name: string;
    email: string;
    created_at: string;
}

interface Props {
    users: {
        data: User[];
        current_page: number;
        last_page: number;
        per_page: number;
        total: number;
        from: number;
        to: number;
    };
    filters: {
        search: string;
        filter: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Users',
        href: '/admin/users',
    },
];

export const generatePaginationnumber = (currentPage, totalPages) => {

    if (totalPages <= 7) {
       return Array.from({ length: totalPages }, (_, i) => i + 1); 
    }
      
    if (currentPage <= 3) {
        return [1, 2, 3 , "...", totalPages - 1, totalPages];
    }
     
    if (currentPage >= totalPages - 3) {
       console.log("2");
       return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
    }
     
    return [
      1,
      "...",
      currentPage - 1,
      currentPage,
      currentPage + 1,
      "...",
      totalPages,
    ];
};

export default function UsersIndex({ users, filters }: Props) {
    //console.log(users); 

    const params = new URLSearchParams(window.location.search);
    const currentPage = Number(params.get("page")) || 1;
    //console.log(currentPage); 

    const totalPages = Math.ceil(Number(users.total) / Number(users.per_page));
    //const totalPages = users.total;
    console.log(totalPages);

    const allPages = generatePaginationnumber(currentPage, totalPages);

    const [searchTerm, setSearchTerm] = useState(filters.search);

    const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        router.get(route('users.home'), {
            search: searchTerm,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };
    
    const handlePageChange = (page: number) => {
        router.get(route('users.home'), {
            page,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Users" />
            <div className="flex h-full flex-1 flex-col gap-6 rounded-xl p-6 bg-gradient-to-br from-background to-muted/20">
                <div className="flex gap-4 mb-4">
                    <form onSubmit={handleSearch} className="relative flex-1">
                        <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                        <Input placeholder="Search name or email..."
                            value={searchTerm}
                            onChange={(e) => setSearchTerm(e.target.value)}
                            className="pl-10 bg-gray-200  text-black"
                        />
                    </form>                                    
                </div>
                <div className="rounded-md border">
                    <div className="relative w-full overflow-auto">
                        <table className="w-full caption-bottom text-sm  bg-white rounded-lg">
                            <thead>
                                <tr className="bg-gray-200 text-black">
                                    <th className="py-2 px-4 text-left border-b">ID</th>
                                    <th className="py-2 px-4 text-left border-b">Name</th>
                                    <th className="py-2 px-4 text-left border-b">Email</th>
                                    <th className="py-2 px-4 text-left border-b">Created At</th>                                   
                                    <th className="py-2 px-4 text-right border-b">Actions</th>                                
                                </tr>                            
                            </thead>     
                            <tbody>
                                {users.data.map((user) => (
                                    <tr key={user.id} className="hover:bg-gray-50 text-black">
                                        <td className="p-4 align-middle font-medium border-b">{user.id}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.name}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.email}</td>
                                        <td className="p-4 align-middle font-medium border-b">{new Date(user.created_at).toLocaleDateString()}</td>
                                        <td className="p-4 align-middle text-right border-b">
                                            <div className="flex justify-end gap-2">
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-blue-600 text-white rounded-lg px-4 py-2 hover:bg-blue-700"
                                                >
                                                    Edit
                                                </Button>                                                
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-red-600 text-white rounded-lg px-4 py-2 hover:bg-red-700 transition transition ml-2"
                                                >
                                                    Delete
                                                </Button>                                            
                                            </div>
                                        </td>
                                    </tr>
                                ))}
                                {users.total === 0 && (
                                    <tr>
                                        <td colSpan={6} className="p-4 text-center text-muted-foreground">
                                            No users Found                                        
                                        </td>
                                    </tr>
                                )}
                            </tbody> 
                        </table>                    
                    </div>                
                </div>

                {/* Pagination */}
                <div className="flex items-center justify-between px-2">
                    <div className="text-sm text-muted-foreground">
                        Showing {users.from} to {users.to} of {users.total} results  
                    </div>                    
                    <div className="flex items-center space-x-2">
                        <Button variant="outline"
                            size="icon"
                            onClick={() => handlePageChange(users.current_page - 1)}
                            disabled={users.current_page === 1}
                        >
                            <ChevronLeft className="h-4 w-4" />
                        </Button>   
                        <div className="flex items-center space-x-1">
                        {allPages.map((page, index) => (

                            <Button key={index}
                            variant={page === users.current_page ? "default" : "outline"}
                            size="icon"
                            onClick={() => handlePageChange(page)}
                            >
                            {page}
                            </Button> 
                           
                        ))}
                        </div>                        
                        <Button variant="outline"
                            size="icon"
                            onClick={() => handlePageChange(users.current_page + 1)}
                            disabled={users.current_page === users.last_page}
                        >
                            <ChevronRight className="h-4 w-4" />
                        </Button>                    
                    </div>                
                </div> 
            </div>
        </AppLayout>
    );
}
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\UsersController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/admin/users-pagination-search', [UsersController::class, 'home'])->name('users.home');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Sunday, May 11, 2025

Laravel 12 CRUD Image Upload, Search, Soft Deletes Bootstrap and Vite

Laravel 12 CRUD Image Upload, Search, Soft Deletes Bootstrap and Vite e

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Product Model php artisan make:model Product -m myapp>php artisan make:model Product -m Open new Products migrations yourproject/database/migrations laravelproject\database\migrations\_create_products_table.php
//laravelproject\database\migrations\_create_products_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('description')->nullable();
            $table->integer('quantity');
            $table->float('price');
            $table->string('image')->nullable();
            $table->enum('status', ['active', 'in-active'])->default('active');
            $table->softDeletes();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
run
myapp>php artisan migrate
update Product Model
app/models/Product.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;

class Product extends Model
{
    use SoftDeletes, HasFactory;

    protected $fillable = [
        "name",
        "description",
        "quantity",
        "price",
        "status",
        "image",
    ];
}
Create PostController
php artisan make:controller PostController
change it with the following codes:
app\Http\Controllers\PostController.php

//app\Http\Controllers\ProductController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\Storage;

class ProductController extends Controller
{
    public function index(Request $request)
    {
        // $products = Product::all();
        $query = Product::query();
        if (request()->has("search") && $request->search) {
            $query = $query->where("name", "like", "%" . $request->search . "%")
                ->orWhere('description', 'like', "%" . $request->search . "%");
        }
        $products = $query->latest()->paginate(3);

        return view("product.index", compact("products"));
    }

    public function create()
    {
        return view("product.create");
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            "name" => "required|string",
            "description" => "nullable|string",
            "price" => "required|numeric",
            "quantity" => "required|numeric",
            "status" => "required",
            "image" => "nullable|image|mimes:jpg,png",
        ]);

        if ($request->hasFile("image")) { //php artisan storage:link
            $validated["image"] = $request->file("image")->store("products", "public");
        }
        Product::create($validated);

        return redirect()->route("products.index")->with("success", "product added successfully");
    }

    public function show($id)
    {
        $product = Product::find($id);
        return view("product.show", compact("product"));
    }

    public function edit($id)
    {
        $product = Product::find($id);
        return view("product.edit", compact("product"));
    }

    public function update(Request $request, $id)
    {
        $validated = $request->validate([
            "name" => "required|string",
            "description" => "nullable|string",
            "price" => "required|numeric",
            "quantity" => "required|numeric",
            "status" => "required",
            "image" => "nullable|image|mimes:jpg,png",
        ]);

        if ($request->hasFile("image")) {
            if ($request->image && Storage::disk("public")->exists($request->image)) {
                Storage::disk("public")->delete($request->image);
            }
            $validated["image"] = $request->file("image")->store("products", "public");
        }

        Product::find($id)->update($validated);

        return redirect()->route("products.index")->with("success", "product updated successfully!");
    }

    public function destroy($id)
    {
        Product::find($id)->delete();
        return redirect()->route("products.index")->with("success", "product deleted successfully!");
    }

    public function trashedProducts(Request $request)
    {
        $query = Product::query()->onlyTrashed();
        $products = $query->paginate(3);
        return view("product.deleted-products", compact("products"));
    }

    public function showTrashed($id)
    {
        $product = Product::onlyTrashed()->findOrFail($id);
        return view("product.show", compact("product"));
    }

    public function restoreProduct($id)
    {
        $product = Product::onlyTrashed()->findOrFail($id);
        $product->restore();
        return redirect()->route("products.index")->with("success", "product restored successfully");
    }

    public function destroyProduct($id)
    {
        $product = Product::onlyTrashed()->findOrFail($id);
        if ($product->image && Storage::exists($product->image)) {
            Storage::delete($product->image);
        }
        $product->forceDelete();

        return redirect()->route("products.index")->with("success", "product was force deleted successfully!");
    }
}
Create AuthController
php artisan make:controller AuthController
change it with the following codes:
app\Http\Controllers\AuthController.php

//app\Http\Controllers\AuthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function login()
    {
        $pass = Hash::make("123456789");
        return view("login")->with('password', $pass);
    }

    public function loginAction(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email',
            'password' => 'required'
        ]);

        if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
            throw ValidationException::withMessages([
                'email' => trans('auth.failed')
            ]);
        }

        $request->session()->regenerate();
        return redirect()->route("products.index")->with("success", "Login successfully");
    }

    public function logout(Request $request)
    {
        Auth::guard('web')->logout();

        $request->session()->invalidate();

        return redirect('/login');
    }
}
Create View File resources/views/layouts/app.blade.php
//resources/views/layouts/app.blade.php
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 12 CRUD</title>
    @vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>

<body>
    <nav class="py-2 bg-body-tertiary border-bottom">
        <div class="container d-flex flex-wrap">
            <ul class="nav me-auto">
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2 active" aria-current="page">Home</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Features</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Pricing</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">FAQs</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">About</a></li>
            </ul>
            <ul class="nav">
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Login</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Sign up</a></li>
            </ul>
        </div>
    </nav>
    <header class="py-3 mb-4 border-bottom">
        <div class="container d-flex flex-wrap justify-content-center"> <a href="/" class="d-flex align-items-center mb-3 mb-lg-0 me-lg-auto link-body-emphasis text-decoration-none">
                <svg class="bi me-2" width="40" height="32" aria-hidden="true">
                    <use xlink:href="#bootstrap"></use>
                </svg> <span class="fs-4">Cairocoders</span> </a>
        </div>
    </header>
    @yield('content')
</body>

</html>
Home resources/views/product/index.blade.php
//resources/views/product/index.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            <div class="row">
                <div class="col">
                    <h2>Product List</h2>
                </div>
                <div class="col">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="row">
                                <div class="col">
                                    <div><a href="{{ route('products.trashed')}}" class="float-end btn btn-danger" style="margin-left:10px;">Deleted</a></div>
                                    <div><a href="{{ route('products.create')}}" class="float-end btn btn-primary">Add New</a></div>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-12" style="padding-top:20px;">
                            <form class="d-flex" role="search" action="{{ route('products.index')}}" method="GET">
                                @csrf
                                <input class="form-control me-2" name="search" type="search" placeholder="Search"
                                    aria-label="Search">
                                <button class="btn btn-outline-success" type="submit">Search</button>
                            </form>
                        </div>
                    </div>

                </div>
            </div>
        </div>
        @if (Session::has('success'))
        <span class="alert alert-success p-2">{{ Session::get('success')}}</span>
        @endif
        @if (Session::has('error'))
        <span>{{ Session::get('error')}}</span>
        @endif
        <div class="card-body">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">Photo</th>
                        <th scope="col">Product Name</th>
                        <th scope="col">Quantity</th>
                        <th scope="col">Price</th>
                        <th scope="col">Status</th>
                        <th scope="col">Description</th>
                        <th scope="col">Action</th>
                    </tr>
                </thead>
                <tbody>
                    @if (count($products) > 0)
                    @foreach ($products as $product)
                    <tr>
                        <th scope="row">{{ $loop->iteration }}</th>
                        <td><img src="{{ asset('storage/' . $product->image) }}" width="100"></td>
                        <td>{{ str($product->name)->words(2)}}</td>
                        <td>{{ $product->quantity}}</td>
                        <td>{{ $product->price}}</td>
                        <td>{{ $product->status}}</td>
                        <td>{{ str($product->description)->words(5)}}</td>
                        <td>
                            <a href="{{ route('products.show', $product->id) }} " class="btn btn-success btn">show</a>
                            <a href="{{ route('products.edit', $product->id) }} " class="btn btn-primary btn">edit</a>
                            <form action="{{ route('products.destroy', $product->id) }}" method="POST"
                                style="display:inline-block">
                                @csrf @method('DELETE')
                                <button onclick="return confirm('Are you sure?')"
                                    class="btn btn btn-danger">Delete</button>
                            </form>
                        </td>
                    </tr>
                    @endforeach
                    @else
                    <tr>
                        <td colspan="8" class="text-center">No Data Found!</td>
                    </tr>
                    @endif

                </tbody>
            </table>
            {{ $products->links('pagination::bootstrap-5') }}
        </div>
    </div>
</div>
@endsection
resources/views/product/create.blade.php
//resources/views/product/create.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            Add New Product
        </div>
        <div class="card-body">
            <form action="{{ route('products.store')}}" method="post" enctype="multipart/form-data">
                @csrf
                @include('product.form')

                <button type="submit" class="btn btn-primary">Save</button>
                <a href="{{ route('products.index')}}" class="btn btn-secondary">Cancel</a>
            </form>
        </div>
    </div>

</div>

@endsection
resources/views/product/form.blade.php
//resources/views/product/form.blade.php
<div class="mb-3">
    <label class="form-label">Product Name *</label>
    <input type="text" name="name" class="form-control" value="{{ old('name', $product->name ?? '') }}">
    @error('name')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Description</label>
    <textarea name="description" class="form-control">{{ old('description', $product->description ?? '') }}</textarea>
    @error('description')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Price *</label>
    <input type="number" name="price" step="0.01" class="form-control"
        value="{{ old('price', $product->price ?? '') }}">
    @error('price')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Quantity *</label>
    <input type="number" name="quantity" class="form-control" value="{{ old('quantity', $product->quantity ?? '') }}">
    @error('quantity')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Status *</label>
    <select name="status" class="form-select">
        <option value="active" {{ (old('status', $product->status ?? '') == 'active') ? 'selected' : '' }}>Active</option>
        <option value="in-active" {{ (old('status', $product->status ?? '') == 'in-active') ? 'selected' : '' }}>Inactive
        </option>
    </select>
    @error('status')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Product Image</label>
    <input type="file" name="image" class="form-control">
    @error('image')
    <span class="text-danger">{{ $message }}</span>
    @enderror
    @if (!empty($product->image))
    <img src="{{ asset('storage/' . $product->image) }}" class="mt-2" width="300">
    @endif
</div>
resources/views/product/edit.blade.php
//resources/views/product/edit.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            Edit Product
        </div>
        <div class="card-body">
            <form action="{{ route('products.update', $product->id) }}" method="POST" enctype="multipart/form-data">
                @csrf
                @method('PUT')
                @include('product.form')

                <button type="submit" class="btn btn-primary">Update</button>
                <a href="{{ route('products.index')}}" class="btn btn-secondary">Cancel</a>
            </form>
        </div>
    </div>

</div>

@endsection
resources/views/product/show.blade.php
//resources/views/product/show.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <h2>Product Details</h2>

    <div class="card">
        <div class="card-body">
            @if($product->image)
            <img src="{{ asset('storage/' . $product->image) }}" width="250" class="mb-3">
            @endif

            <p><strong>Name:</strong> {{ $product->name }}</p>
            <p><strong>Description:</strong> {{ $product->description }}</p>
            <p><strong>Price:</strong> ${{ number_format($product->price, 2) }}</p>
            <p><strong>Quantity:</strong> {{ $product->quantity }}</p>
            <p><strong>Status:</strong>
                <span class="badge bg-{{ $product->status === 'active' ? 'success' : 'secondary' }}">
                    {{ ucfirst($product->status) }}
                </span>
            </p>

            <a href="{{ route('products.edit', $product->id) }}" class="btn btn-warning">Edit</a>
            <a href="{{ route('products.index') }}" class="btn btn-secondary">Back</a>
        </div>
    </div>
</div>

@endsection
resources/views/product/deleted-products.blade.php
//resources/views/product/deleted-products.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            <div class="row">
                <div class="col">
                    <h2>Product List</h2>
                </div>
                <div class="col">
                    <div class="row">
                        <div class="col-md-12">
                            <a href="{{ route('products.index')}}" class="float-end btn btn-warning">View All
                                Products</a>
                        </div>
                        <div class="col-md-12" style="padding-top:20px;">
                            <form class="d-flex" role="search" action="{{ route('products.trashed')}}" method="GET">
                                @csrf
                                <input class="form-control me-2" name="search" type="search" placeholder="Search"
                                    aria-label="Search">
                                <button class="btn btn-outline-success" type="submit">Search</button>
                            </form>
                        </div>
                    </div>

                </div>
            </div>
        </div>
        @if (Session::has('success'))
        <span class="alert alert-success p-2">{{ Session::get('success')}}</span>
        @endif
        @if (Session::has('error'))
        <span>{{ Session::get('error')}}</span>
        @endif
        <div class="card-body">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">Photo</th>
                        <th scope="col">Product Name</th>
                        <th scope="col">Quantity</th>
                        <th scope="col">Price</th>
                        <th scope="col">Status</th>
                        <th scope="col">Description</th>
                        <th scope="col">Action</th>
                    </tr>
                </thead>
                <tbody>
                    @if (count($products) > 0)
                    @foreach ($products as $product)
                    <tr>
                        <th scope="row">{{ $loop->iteration }}</th>
                        <td><img src="{{ asset('storage/' . $product->image) }}" width="100"></td>
                        <td>{{ $product->name}}</td>
                        <td>{{ $product->quantity}}</td>
                        <td>{{ $product->price}}</td>
                        <td>{{ $product->status}}</td>
                        <td>{{ $product->description}}</td>
                        <td>
                            <a href="{{ route('trashed.show', $product->id) }}" class="btn btn-success btn">show</a>
                            <form action="{{ route('trashed.restore', $product->id) }}" method="POST"
                                style="display:inline-block">
                                @csrf
                                @method('PUT')
                                <button onclick="return confirm('Are you sure?')"
                                    class="btn btn btn-info">Restore</button>
                            </form>
                            <form action="{{ route('trashed.delete', $product->id) }}" method="POST"
                                style="display:inline-block">
                                @csrf
                                @method('DELETE')
                                <button onclick="return confirm('Are you sure?')"
                                    class="btn btn btn-danger">Delete</button>
                            </form>
                        </td>
                    </tr>
                    @endforeach
                    @else
                    <tr>
                        <td colspan="8" class="text-center">No Data Found!</td>
                    </tr>
                    @endif

                </tbody>
            </table>
            {{ $products->links() }}
        </div>
    </div>
</div>
@endsection
resources/views/login.blade.php
//resources/views/login.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            Login
        </div>
        <div class="card-body">
            <form action="{{ route('login.action') }}" method="POST" class="user">
                @csrf
                @if ($errors->any())
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
                @endif
                <div class="mb-3">
                    <label class="form-label">Email</label>
                    <input name="email" type="email" class="form-control form-control-user" id="exampleInputEmail" aria-describedby="emailHelp" placeholder="Enter Email Address...">
                </div>
                <div class="mb-3">
                    <label class="form-label">Password</label>
                    <input name="password" type="password" class="form-control form-control-user" id="exampleInputPassword" placeholder="Password">
                    {{ $password }}
                </div>
                <div class="mb-3">
                    <div class="custom-control custom-checkbox small">
                        <input name="remember" type="checkbox" class="custom-control-input" id="customCheck">
                        <label class="custom-control-label" for="customCheck">Remember
                            Me</label>
                    </div>
                </div>
                <button type="submit" class="btn btn-primary btn-block btn-user">Login</button>
            </form>
        </div>
    </div>

</div>

@endsection
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\AuthController;

Route::get('/', function () {
    return view('welcome');
});

Route::middleware('auth')->group(function () {
    //Route::get('dashboard', function () {
    //    return view('dashboard');
    //})->name('dashboard');

    Route::controller(ProductController::class)->group(function () {
        Route::get('/admin/products', 'index')->name('products.index');
        Route::get('/admin/products/create', 'create')->name('products.create');
        Route::post('/admin/products', 'store')->name('products.store');
        Route::get('/admin/products/show/{product}', 'show')->name('products.show');
        Route::get('/admin/products/{product}/edit', 'edit')->name('products.edit');
        Route::put('/admin/products/{product}', 'update')->name('products.update');
        Route::delete('/admin/products/{product}', 'destroy')->name('products.destroy');

        Route::get('/admin/deleted-products', 'trashedProducts')->name('products.trashed');
        Route::get('/admin/show-trashed-product/{id}', 'showTrashed')->name('trashed.show');
        Route::put('/admin/restore-product/{id}', 'restoreProduct')->name('trashed.restore');
        Route::delete('/admin/delete-product/{id}', 'destroyProduct')->name('trashed.delete');
    });
});

Route::controller(AuthController::class)->group(function () {
    Route::get('login', 'login')->name('login');
    Route::post('login', 'loginAction')->name('login.action');
    Route::get('logout', 'logout')->middleware('auth')->name('logout');
});

Thursday, May 8, 2025

Laravel 12 Blog | CRUD Category Pagination Search and Upload picture | React Starter kit ShadCN/UI

Laravel 12 Blog | CRUD Category Pagination Search and Upload picture | React Starter kit ShadCN/UI

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Post Model php artisan make:model Post -m myapp>php artisan make:model Post -m Open new products migrations yourproject/database/migrations laravelproject\database\migrations\_create_posts_table.php
//laravelproject\database\migrations\_create_posts_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->integer('categories_id');
            $table->boolean('poststatus')->default(false);
            $table->string('slug');
            $table->string('picture')->nullable(); // Add picture column (nullable)
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Create tables Category Model php artisan make:model Category -m myapp>php artisan make:model Category -m Open new products migrations yourproject/database/migrations laravelproject\database\migrations\_create_categories_table.php
//laravelproject\database\migrations\_create_categories_table.php
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('categories');
    }
};
run
myapp>php artisan migrate
update Post Model
app/models/Post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'content',
        'poststatus',
        'slug',
        'picture',
        'categories_id'
    ];

    protected $casts = [
        'poststatus' => 'boolean'
    ];

    public function categories(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'categories_id');
    }
}
update Category Model
app/models/Category.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Category extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'user_id'
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
Create PostController
php artisan make:controller PostController
change it with the following codes:
app\Http\Controllers\PostController.php

//app\Http\Controllers\PostController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Category;
use App\Models\Post;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;

class PostController extends Controller
{
    public function index()
    {
        $query = Post::with('categories')
            ->whereHas('categories', function ($query) {
                $query->where('user_id', auth()->id());
            })
            ->orderBy('created_at', 'desc');

        // Handle search
        if (request()->has('search')) {
            $search = request('search');
            $query->where(function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                    ->orWhere('content', 'like', "%{$search}%");
            });
        }

        // Handle filter
        if (request()->has('filter') && request('filter') !== 'all') {
            $query->where('poststatus', request('filter') === 'published');
        }

        $posts = $query->paginate(2);

        $categories = Category::where('user_id', auth()->id())->get();

        return Inertia::render('posts/index', [
            'posts' => $posts,
            'categories' => $categories,
            'filters' => [
                'search' => request('search', ''),
                'filter' => request('filter', 'all'),
            ],
            'flash' => [
                'success' => session('success'),
                'error' => session('error')
            ]
        ]);
    }

    public function create()
    {
        $categories = Category::where('user_id', auth()->id())->get();
        return Inertia::render('posts/create', [
            'categories' => $categories,
            'flash' => [
                'success' => session('success'),
                'error' => session('error')
            ]
        ]);
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'categories_id' => 'required|exists:categories,id',
            'poststatus' => 'boolean',
            'picture' => 'required|image',
        ]);

        $title = $request->input('title');
        $validated['slug'] = Str::slug($title);

        if ($request->hasFile('picture')) {
            $validated['picture'] = Storage::disk('public')->put('posts', $request->file('picture'));
        }

        Post::create($validated);

        return redirect()->route('posts.index')->with('success', 'Post created successfully!');
    }

    public function edit(Post $post)
    {
        $categories = Category::where('user_id', auth()->id())->get();
        return Inertia::render('posts/edit', [
            'post' => $post,
            'categories' => $categories,
        ]);
    }

    public function update(Request $request, Post $post)
    {
        $data = $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'categories_id' => 'required|exists:categories,id',
            'poststatus' => 'boolean',
            'picture' => 'required|image',
        ]);

        $title = $request->input('title');
        $data['slug'] = Str::slug($title);

        if ($request->hasFile('picture')) {
            Storage::disk('public')->delete($post->picture);
            $data['picture'] = Storage::disk('public')->put('posts', $request->file('picture'));
        }
        $post->update($data);

        return redirect()->route('posts.index')->with('success', 'Post updated successfully!');
    }

    public function destroy(Post $post)
    {
        Storage::disk('public')->delete($post->picture);

        $post->delete();
        return redirect()->route('posts.index')->with('success', 'Post deleted successfully!');
    }
}
Create CategoryController
php artisan make:controller CategoryController
change it with the following codes:
app\Http\Controllers\CategoryController.php

//app\Http\Controllers\CategoryController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Category;

class CategoryController extends Controller
{
    public function index()
    {
        $categories = Category::where('user_id', auth()->id())
            ->get();

        return Inertia::render('category/index', [
            'categories' => $categories,
            'flash' => [
                'success' => session('success'),
                'error' => session('error')
            ]
        ]);
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255'
        ]);

        Category::create([
            ...$validated,
            'user_id' => auth()->id()
        ]);

        return redirect()->route('category.index')->with('success', 'Category created successfully!');
    }

    public function update(Request $request, Category $category)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255'
        ]);

        $category->update($validated);

        return redirect()->route('category.index')->with('success', 'Categroy updated successfully! ' . $validated['title'] . ' ');
    }

    public function destroy(Category $category)
    {
        $category->delete();
        return redirect()->route('category.index')->with('success', 'Category deleted successfully!');
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
Index.tsx File: pages\posts\index.tsx
reactjs\resources\js\pages\posts\index.tsx
//reactjs\resources\js\pages\posts\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import { Button } from '@/components/ui/button';
import { Plus, CheckCircle2, XCircle, List, CheckCircle, Search, ChevronLeft, ChevronRight } from 'lucide-react';
import { Link } from '@inertiajs/react';
import { useState, useEffect } from 'react';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';

interface Post {
    id: number;
    title: string;
    content: string | null;
    poststatus: boolean;
    slug: string;
    picture: string;
    categories_id: number;
    categories: {
        id: number;
        title: string;
    };
}

interface Category {
    id: number;
    title: string;
}

interface Props {
    posts: {
        data: Post[];
        current_page: number;
        last_page: number;
        per_page: number;
        total: number;
        from: number;
        to: number;
    };
    categories: Category[];
    filters: {
        search: string;
        filter: string;
    };
    flash?: {
        success?: string;
        error?: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Posts',
        href: '/admin/posts',
    },
];

export default function PostsIndex({ posts, categories, filters, flash }: Props) {
    console.log(posts);
    //console.log(categories);
    const [showToast, setShowToast] = useState(false);
    const [toastMessage, setToastMessage] = useState('');
    const [toastType, setToastType] = useState<'success' | 'error'>('success');
    const [searchTerm, setSearchTerm] = useState(filters.search);
    const [completionFilter, setCompletionFilter] = useState<'all' | 'published' | 'draft'>(filters.filter as 'all' | 'published' | 'draft');

    useEffect(() => {
        if (flash?.success) {
            setToastMessage(flash.success);
            setToastType('success');
            setShowToast(true);
        } else if (flash?.error) {
            setToastMessage(flash.error);
            setToastType('error');
            setShowToast(true);
        }
    }, [flash]);

    useEffect(() => {
        if (showToast) {
            const timer = setTimeout(() => {
                setShowToast(false);
            }, 3000);
            return () => clearTimeout(timer);
        }
    }, [showToast]);

    const handleDelete = (postId: number) => {
        console.log("deleted" + postId);
        
        router.delete(`/admin/posts/${postId}`, {
        onSuccess: () => {
            router.reload();
        },
        onError: () => {
            console.error("Failed to delete post.");
        },
        });
    };

    const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        router.get(route('posts.index'), {
            search: searchTerm,
            filter: completionFilter,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };
    
    const handleFilterChange = (value: 'all' | 'published' | 'draft') => {
        setCompletionFilter(value);
        router.get(route('posts.index'), {
            search: searchTerm,
            filter: value,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };
    
    const handlePageChange = (page: number) => {
        router.get(route('posts.index'), {
            page,
            search: searchTerm,
            filter: completionFilter,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Posts" />
            <div className="flex h-full flex-1 flex-col gap-6 rounded-xl p-6 bg-gradient-to-br from-background to-muted/20">
                {showToast && (
                    <div className={`fixed top-4 right-4 z-50 flex items-center gap-2 rounded-lg p-4 shadow-lg ${toastType === 'success' ? 'bg-green-500' : 'bg-red-500'
                        } text-white animate-in fade-in slide-in-from-top-5`}>
                        {toastType === 'success' ? (
                            <CheckCircle2 className="h-5 w-5" />
                        ) : (
                            <XCircle className="h-5 w-5" />
                        )}
                        <span>{toastMessage}</span>                    
                    </div>)
                }
                
                <div className="flex justify-between items-center">
                    <div>
                        <h1 className="text-3xl font-bold tracking-tight">Posts</h1>                        
                        <p className="text-muted-foreground mt-1">Manage your posts</p>                    
                    </div>                    
                    <Button variant="outline" className="bg-primary hover:bg-primary/90 text-black shadow-lg">
                        <Plus className="h-4 w-4 mr-2" />
                        <Link href="/admin/posts/create">New Post</Link>
                    </Button>
                </div>

                <div className="flex gap-4 mb-4">
                    <form onSubmit={handleSearch}  className="relative flex-1">
                        <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                        <Input placeholder="Search post..."
                            value={searchTerm}
                            onChange={(e) => setSearchTerm(e.target.value)}
                            className="pl-10"
                        />
                    </form>  
                    <Select value={completionFilter}
                        onValueChange={handleFilterChange}
                    >
                        <SelectTrigger className="w-[180px]">
                            <SelectValue placeholder="Filter by status" />
                        </SelectTrigger>                        
                        <SelectContent>
                            <SelectItem value="all">All Posts</SelectItem>                            
                            <SelectItem value="published">Published</SelectItem>                            
                            <SelectItem value="draft">Draft</SelectItem>                        
                        </SelectContent>                    
                    </Select>                                
                </div>

                <div className="rounded-md border">
                    <div className="relative w-full overflow-auto">
                        <table className="w-full caption-bottom text-sm">
                            <thead className="[&_tr]:border-b">
                                <tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
                                    <th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">Photo</th>
                                    <th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">Title</th>
                                    <th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">Content</th>
                                    <th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">Category</th>
                                    <th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">Status</th>  
                                    <th className="h-12 px-4 text-right align-middle font-medium text-muted-foreground">Actions</th> 
                                </tr>                            
                            </thead>      
                            <tbody className="[&_tr:last-child]:border-0">
                                {posts.data.map((post) => (
                                    <tr key={post.id} className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
                                        <td className="p-4 align-middle font-medium">
                                            {post.picture ? <img src={`http://127.0.0.1:8000/storage/${post.picture}`} alt="" height={50} width={90} /> : "No Picture"}
                                        </td>
                                        <td className="p-4 align-middle font-medium">{post.title}</td>
                                        <td className="p-4 align-middle max-w-[200px] truncate">
                                            {post.content || 'No content'}
                                        </td>
                                        <td className="p-4 align-middle">
                                            <div className="flex items-center gap-2">
                                                <List className="h-4 w-4 text-muted-foreground" />
                                                {post.categories.title}
                                            </div>
                                        </td>
                                        <td className="p-4 align-middle">
                                            {post.poststatus ? (
                                                <div className="flex items-center gap-2 text-green-500">
                                                    <CheckCircle className="h-4 w-4" />
                                                    <span>Published</span>                                                
                                                </div>
                                            ) : (
                                                <div className="flex items-center gap-2 text-yellow-500">
                                                    <span>Draft</span>                                                
                                                </div>)}
                                        </td>
                                        <td className="p-4 align-middle text-right">
                                            <div className="flex justify-end gap-2">
                                                <Button className="bg-green-500 text-white px-2 py-1 rounded hover:bg-green-600 mr-2">
                                                    <Link href={`/admin/posts/${post.id}/edit`}>Edit Post</Link>
                                                </Button>
                                     
                                                <Button onClick={() => handleDelete(post.id)}
                                                    className="bg-red-500 text-sm text-white px-3 py-2 rounded"
                                                >
                                                    Delete
                                                </Button>                                            
                                            </div>
                                        </td>
                                    </tr>
                                ))}
                                {posts.data.length === 0 && (
                                    <tr>
                                        <td colSpan={6} className="p-4 text-center text-muted-foreground">
                                            No record                                        
                                        </td>
                                    </tr>
                                )}
                            </tbody>                                    
                        </table>                    
                    </div>                
                </div>

                {/* Pagination */}
                <div className="flex items-center justify-between px-2">
                    <div className="text-sm text-muted-foreground">
                        Showing {posts.from} to {posts.to} of {posts.total} results
                    </div>                    
                    <div className="flex items-center space-x-2">
                        <Button variant="outline"
                            size="icon"
                            onClick={() => handlePageChange(posts.current_page - 1)}
                            disabled={posts.current_page === 1}
                        >
                            <ChevronLeft className="h-4 w-4" />
                        </Button>  
                        <div className="flex items-center space-x-1">
                            {Array.from({ length: posts.last_page }, (_, i) => i + 1).map((page) => (
                                <Button key={page}
                                    variant={page === posts.current_page ? "default" : "outline"}
                                    size="icon"
                                    onClick={() => handlePageChange(page)}
                                >
                                    {page}
                                </Button>))}
                        </div>  
                        <Button variant="outline"
                            size="icon"
                            onClick={() => handlePageChange(posts.current_page + 1)}
                            disabled={posts.current_page === posts.last_page}
                        >
                            <ChevronRight className="h-4 w-4" />
                        </Button>                 
                    </div>                
                </div> 
            </div>
        </AppLayout>
    );
}
create.tsx File: pages\posts\create.tsx
reactjs\resources\js\pages\posts\create.tsx
//reactjs\resources\js\pages\posts\create.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, useForm } from '@inertiajs/react';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from "@/components/ui/textarea"
import { Button } from '@/components/ui/button';
import { FormEventHandler, useState } from 'react';
import InputError from '@/components/input-error';
import { LoaderCircle } from 'lucide-react';

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Create Post',
        href: '/admin/posts/create',
    },
];

type CreateForm = {
    title: string;
    content: string;
    poststatus: boolean;
    picture: string;
    categories_id: number;
    categories: {
        id: number;
        title: string;
    };
};
 
interface Category {
    id: number;
    title: string;
}

interface CreateProps {
    categories: Category[];
    status?: string;
}

export default function ProductCreate({ status, categories }: CreateProps) {
    //console.log(categories);

    const { data, setData, post, processing, errors } = useForm<Required<CreateForm>>({
        title: '',
        content: '',
        poststatus: Boolean,
        picture: '',
    });

    const [preview, setPreview] = useState<string>("");
    
    const submit: FormEventHandler = (e) => {
        e.preventDefault();
        console.log(data);
        post(route('posts.store'));
    };

    const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.files && e.target.files[0]) {
        const file = e.target.files[0];
        setData('picture', file);
        setPreview(URL.createObjectURL(file));
        }
    };
    
    const [isChecked, setIsChecked] = useState(false)
    const checkHandler = () => {
        setIsChecked(!isChecked)
        console.log(!isChecked);
        setData('poststatus', !isChecked)
    }

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Create Product" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                <div className="flex justify-end">Create New Post</div>
                <div className="p-10 border-sidebar-border/70 dark:border-sidebar-border relative min-h-[100vh] flex-1 overflow-hidden rounded-xl border md:min-h-min">
                <form className="flex flex-col gap-6" onSubmit={submit}>
                    <div className="grid gap-6">
                        <div className="grid gap-2">
                            <Label htmlFor="title">Title</Label>
                            <Input
                                id="title"
                                type="text"
                                autoFocus
                                tabIndex={1}
                                value={data.title}
                                onChange={(e) => setData('title', e.target.value)}
                                placeholder="Title"
                            />
                            <InputError message={errors.title} />
                        </div>
                    </div>
 
                    <div className="grid gap-2">
                        <div className="flex items-center">
                            <Label htmlFor="content">Content</Label>
                        </div>
                        <Textarea 
                            id="content"
                            placeholder="Content"
                            tabIndex={2}
                            value={data.content}
                            onChange={(e) => setData('content', e.target.value)}
                        />
                        <InputError message={errors.content} />
                    </div>

                    <div className="grid gap-2">
                        <div className="flex items-center">
                            <Label htmlFor="category">Category</Label>
                        </div>
                        <select id="categories_id" className="block w-full p-2 mb-6 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                            onChange={(e) => setData('categories_id', e.target.value)}
                            >
                            <option>Select a category</option>
                            {categories.map((list) => (
                                <option key={list.id} value={list.id.toString()}>{list.title}</option>
                            ))}
                        </select> 
                        <InputError message={errors.categories_id} /> 
                    </div>

                    <div className="grid gap-2">
                        <div className="flex items-center">
                            <Label htmlFor="picture">Picture</Label>
                        </div>
                        <Input
                            id="picture"
                            type="file"
                            placeholder="Picture"
                            onChange={handleFileChange}
                        />
                        {preview && (
                            <div className="mb-3">
                                <p className="text-sm mb-1">Image Preview:</p>
                                <img src={preview} alt="Preview" className="w-32 h-32 object-cover rounded" />
                            </div>
                        )}
                        <InputError message={errors.picture} />
                    </div>

                    <div className="grid gap-2">
                        <input
                            type="checkbox"
                            id="poststatus"
                            checked={isChecked}
                            onChange={checkHandler}
                            className="h-4 w-4 rounded border-gray-300 focus:ring-2 focus:ring-primary"
                        />  
                        <Label htmlFor="poststatus">Published</Label>   
                    </div>

                        <Button type="submit" className="mt-4 w-full" tabIndex={4} disabled={processing}>
                            {processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
                            Submit
                        </Button>
                </form>
                {status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
                </div>
            </div>
        </AppLayout>
    );
}
edit.tsx File: pages\posts\edit.tsx
reactjs\resources\js\pages\posts\edit.tsx
//reactjs\resources\js\pages\posts\edit.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, usePage, router } from '@inertiajs/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from "@/components/ui/textarea"
import { useState } from 'react';
import InputError from '@/components/input-error';

const breadcrumbs: BreadcrumbItem[] = [
  {
    title: 'Posts',
    href: '/admin/posts/edit',
  },
];

interface Category {
    id: number;
    title: string;
}

interface Props {
    categories: Category[];  
}

export default function EditPosts({ post, categories }: Props) {
    //console.log(categories);
    const [title, setTitle] = useState<string>(post.title);
    const [content, setContent] = useState<string>(post.content);
    const [poststatus, setPoststatus] = useState<boolean>(post.poststatus);
    const [categories_id, setCategory] = useState(post.categories_id);
    const [picture, setPicture] = useState<File | null>(post.picture);
    const [imagePreview, setImagePreview] = useState<string | null>(null);
    const { errors } = usePage().props;

    const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0];
        if (file) {
            setPicture(file); //console.log(file.name);
            setImagePreview(URL.createObjectURL(file));
        }
    }
    
    function submit(e: React.FormEvent) {
        e.preventDefault();
        //console.log(title);
        router.post(route('posts.update', post.id), {
            _method: 'put',
            title,
            content,
            isChecked,
            poststatus,
            categories_id,
            picture,
        });
    };

    const [isChecked, setIsChecked] = useState(false)

    const checkHandler = () => {
        setIsChecked(!isChecked)
        console.log(!isChecked);
        setPoststatus(!isChecked)
    }
    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Edit Post" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                <div className="flex justify-end">Edit Post</div>
                <div className="p-10 border-sidebar-border/70 dark:border-sidebar-border relative min-h-[100vh] flex-1 overflow-hidden rounded-xl border md:min-h-min">
                    
                    <form className="flex flex-col gap-6" onSubmit={submit}>
                        <div className="grid gap-6">
                            <div className="grid gap-2">
                                <Label htmlFor="title">Title</Label>
                                <Input
                                id="title"
                                type="text"
                                value={title}
                                onChange={(e) => setTitle(e.target.value)}
                                placeholder="Title"
                                />
                                <InputError message={errors.title} />
                            </div>
                        </div>

                        <div className="grid gap-2">
                            <div className="flex items-center">
                                <Label htmlFor="content">Content</Label>
                            </div>
                            <Textarea 
                                id="content"
                                placeholder="content"
                                value={content}
                                onChange={(e) => setContent(e.target.value)}
                            />
                            <InputError message={errors.content} />
                        </div>

                        <div className="grid gap-2">
                            <div className="flex items-center">
                                <Label htmlFor="category">Category</Label>
                            </div>
                            <select id="categories_id" className="block w-full p-2 mb-6 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                                value={categories_id} 
                                onChange={e => setCategory(e.target.value)} 
                                >
                                {categories.map((list) => (
                                    <option key={list.id} value={list.id.toString()}>{list.title}</option>
                                ))}
                            </select>
                            <InputError message={errors.categories_id} /> 
                        </div>

                        <div className="grid gap-2">
                            <div className="flex items-center">
                                <Label htmlFor="picture">Picture</Label>
                            </div>
                            <Input
                                id="picture"
                                type="file"
                                placeholder="Picture"
                                onChange={handleFileChange}
                            />
                            <InputError message={errors.picture} />
                            <img src={`http://127.0.0.1:8000/storage/${post.picture}`} alt="" height={50} width={90} />
                            {imagePreview && <img src={imagePreview} alt="Preview" height={350} width={390}/>}
                        </div>

                        <div className="grid gap-2">
                            <input
                            type="checkbox"
                            id="poststatus"
                            checked={poststatus}
                            onChange={checkHandler}
                            className="h-4 w-4 rounded border-gray-300 focus:ring-2 focus:ring-primary"
                            />
                            <Label htmlFor="poststatus">Published</Label>    
                        </div>

                        <Button type="submit" className="mt-4 w-full" tabIndex={4}>
                            Update
                        </Button>

                    </form>
                </div>
            </div>
        </AppLayout>
    );
}
category/index.tsx File: pages\category\index.tsx
reactjs\resources\js\pages\category\index.tsx
//reactjs\resources\js\pages\category\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Plus, Pencil, Trash2, CheckCircle2, XCircle } from 'lucide-react';
import { DialogDescription } from '@radix-ui/react-dialog';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { useForm } from '@inertiajs/react';
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

interface Category {
    id: number;
    title: string;
    posts_count?: number;
}

interface Props {
    categories: Category[];
    flash?: {
        success?: string;
        error?: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Categories',
        href: '/category',
    },
];

export default function CategoryIndex({ categories, flash }: Props) {
    console.log(categories);
    const [isOpen, setIsOpen] = useState(false);
    const [showToast, setShowToast] = useState(false);
    const [toastMessage, setToastMessage] = useState('');
    const [toastType, setToastType] = useState<'success' | 'error'>('success');
    const [editingCategory, setEditingCategory] = useState<Category | null>(null);

    useEffect(() => {
        if (flash?.success) {
            setToastMessage(flash.success);
            setToastType('success');
            setShowToast(true);
        } else if (flash?.error) {
            setToastMessage(flash.error);
            setToastType('error');
            setShowToast(true);
        }
    }, [flash]);

    useEffect(() => {
        if (showToast) {
            const timer = setTimeout(() => {
                setShowToast(false);
            }, 3000);
            return () => clearTimeout(timer);
        }
    }, [showToast]);

    const { data, setData, post, put, processing, reset } = useForm({
        title: ''
    });

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        if (editingCategory) {
             console.log("updated");
            put(route('category.update', editingCategory.id), {
                onSuccess: () => {
                    setIsOpen(false);
                    reset();
                    setEditingCategory(null);
                },
            });
        } else {
            post(route('category.store'), {
                onSuccess: () => {
                    setIsOpen(false);
                    reset();
                },
            });
        }
    };

    const handleEdit = (categories: Category) => {
        setEditingCategory(categories);
        setData({
            title: categories.title,
        });
        setIsOpen(true);
    };

    const handleDelete = (id: number) => {
        router.delete(`/admin/category/${id}`, {
        onSuccess: () => {
            router.reload();
            console.log("success");
        },
        onError: () => {
            console.error("Failed to delete post.");
        },
        });
    };
    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Category" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                {showToast && (
                    <div className={`fixed top-4 right-4 z-50 flex items-center gap-2 rounded-lg p-4 shadow-lg ${toastType === 'success' ? 'bg-green-500' : 'bg-red-500'
                        } text-white animate-in fade-in slide-in-from-top-5`}>
                        {toastType === 'success' ? (
                            <CheckCircle2 className="h-5 w-5" />
                        ) : (
                            <XCircle className="h-5 w-5" />
                        )}
                        <span>{toastMessage}</span>                    
                    </div>)}

                <div className="flex justify-between items-center">
                    <h1 className="text-2xl font-bold">Category</h1>                    
                    <Dialog open={isOpen} onOpenChange={setIsOpen}>
                        <DialogTrigger asChild>
                            <Button variant="outline" className="bg-primary hover:bg-primary/90 text-black shadow-lg">
                                <Plus className="h-4 w-4 mr-2" />
                                New Category
                            </Button>
                        </DialogTrigger>                       
                        <DialogContent>
                            <DialogHeader>
                                <DialogTitle>Create New Category</DialogTitle>   
                                <DialogDescription>
                                    Make changes to your category here. Click update when you're done.
                                </DialogDescription>                        
                            </DialogHeader>                            
                            <form onSubmit={handleSubmit} className="space-y-4">
                                <div className="space-y-2">
                                    <Label htmlFor="title">Title</Label>                                    
                                    <Input id="title"
                                        value={data.title}
                                        onChange={(e) => setData('title', e.target.value)}
                                        required />
                                </div>                               
                                <Button type="submit" disabled={processing}>
                                    Create
                                </Button>                            
                            </form>                        
                        </DialogContent>                    
                    </Dialog>                
                </div>

                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                    {categories.map((category) => (
                        <Card key={category.id} className="hover:bg-accent/50 transition-colors">
                            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                                <CardTitle className="text-lg font-medium">{category.title}</CardTitle>                                
                                <div className="flex gap-2">
                                    <Button variant="ghost"
                                        size="icon"
                                        onClick={() => handleEdit(category)}
                                    >
                                        <Pencil className="h-4 w-4" />
                                    </Button>                                    
                                    <Button
                                        variant="ghost"
                                        size="icon"
                                        onClick={() => handleDelete(category.id)}
                                        className="text-destructive hover:text-destructive/90"
                                    >
                                        <Trash2 className="h-4 w-4" />
                                    </Button>
                                </div>
                            </CardHeader>                            
                            <CardContent>
                                {category.posts_count !== undefined && (
                                    <p className="text-sm text-muted-foreground mt-2">
                                        {category.posts_count} Category                                    
                                    </p>
                                )}
                            </CardContent>                        
                        </Card>
                    ))}
                </div>
            </div>
        </AppLayout>
    );
}
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\CategoryController;
use App\Http\Controllers\PostController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::resource('admin/category', CategoryController::class);
    Route::resource('admin/posts', PostController::class);
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Related Post