article

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

Related Post