Laravel Livewire Day-Wise Schedule Viewer with Bootstrap 5

Learn how to build a day-wise schedule viewer in Laravel Livewire using Bootstrap 5. Includes migration, seeder, navigation buttons, and UX best practices.

Managing schedules by day is a common requirement in admin dashboards, booking systems, internal tools, and SaaS applications. In this tutorial, you’ll learn how to build a day-wise schedule viewer using Laravel Livewire, styled with Bootstrap 5, and powered by weekday-based database records.

This guide covers everything from database migration and seeding to Livewire navigation logic and UX improvements.

What You’ll Learn in This Tutorial

  • How to store weekday-based schedules in Laravel
  • Build a Livewire component to show schedules dynamically
  • Navigate schedules using Previous / Next buttons
  • Disable invalid navigation (like going before today)
  • Seed realistic day-wise data (max 6 events per day)
  • Create a Bootstrap 5 UI without JavaScript

Tech stack used

  • Laravel 10+
  • Livewire 3
  • Bootstrap 5
  • Carbon (Date handling)
  • MySQL / MariaDB

Database Migration

Before creating the model, seeder, and Livewire component, we need a database table to store weekday-based schedules.

Each record represents one event for a specific weekday (Monday–Sunday).

Create Migration

To create a migration file, run the following command,

php artisan make:migration create_weekday_schedules_table

It will create a migration file named xxxx_xx_xx_create_weekday_schedules_table.php in the database/migrations folder.

Migration File

Open the migration file and add the following code into it,

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('weekday_schedules', function (Blueprint $table) {
            $table->id();

            // 1 = Monday, 7 = Sunday (ISO-8601)
            $table->unsignedTinyInteger('weekday')->index();

            $table->string('title');
            $table->time('start_time');
            $table->time('end_time');
            $table->text('description')->nullable();

            $table->timestamps();
        });
    }

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

Now, this migration file is ready to be run.

Run Migration

Run the migration using the following artisan command,

php artisan migrate

Eloquent Model

Now, create a model for weekday schedules table, created by the migration. To create an eloquent model, run the following command,

php artisan make:model WeekdaySchedule

It will create a model file named WeekdaySchedule.php inside app/Models folder. Open that model file and add the following code in the file,

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class WeekdaySchedule extends Model
{
    protected $fillable = [
        'weekday',
        'title',
        'start_time',
        'end_time',
        'description',
    ];
}

Database migration and models files are ready. It is time to create a livewire component for day-wise schedule.

Livewire Component

Livewire components can be easily created using the artisan livewire command as follows,

php artisan make:livewire DaySchedule

This command will create two files as follows,

  • Component file named DaySchedule.php inside app/Livewire folder,
  • Component view blade file named day-schedule.blade.php inside resources/views/livewire folder.

Open the DaySchedule.php file and add the following code into it,

namespace App\Livewire;

use Livewire\Component;
use Carbon\Carbon;
use App\Models\WeekdaySchedule;

class DaySchedule extends Component
{
    public Carbon $currentDate;

    public function mount()
    {
        $this->currentDate = now();
    }

    public function previousDay()
    {
        if ($this->currentDate->isToday()) {
            return;
        }

        $this->currentDate = $this->currentDate->copy()->subDay();
    }

    public function nextDay()
    {
        $this->currentDate = $this->currentDate->copy()->addDay();
    }

    public function getSchedulesProperty()
    {
        return WeekdaySchedule::where(
            'weekday',
            $this->currentDate->dayOfWeekIso
        )->get();
    }

    public function render()
    {
        return view('livewire.day-schedule');
    }
}

And open the component view file to add the following code,

<div class="card shadow-sm">

    <div class="card-header d-flex justify-content-between align-items-center">

        <button
            wire:click="previousDay"
            class="btn btn-outline-secondary btn-sm"
            @if($currentDate->isToday()) disabled @endif
        >
            ← Previous
        </button>

        <h5 class="mb-0 fw-semibold">
            {{ $currentDate->format('l, d M Y') }}
        </h5>

        <button
            wire:click="nextDay"
            class="btn btn-outline-secondary btn-sm"
        >
            Next →
        </button>

    </div>

    <div class="card-body">
        @forelse ($this->schedules as $schedule)
            <div class="border rounded p-3 mb-3">
                <div class="d-flex justify-content-between">
                    <strong>{{ $schedule->title }}</strong>
                    <span class="badge bg-primary">
                        {{ $schedule->start_time }} – {{ $schedule->end_time }}
                    </span>
                </div>

                @if($schedule->description)
                    <small class="text-muted d-block mt-1">
                        {{ $schedule->description }}
                    </small>
                @endif
            </div>
        @empty
            <div class="alert alert-light text-center mb-0">
                No schedule available for this day.
            </div>
        @endforelse
    </div>

</div>

This component is now ready. But, we don’t have any data to display. So, for data, create a seeder.

Daywise Seeder

Create a seeder using the following artisan command,

php artisan make:seeder WeekdayScheduleSeeder

This will create a file inside database/seeders folder named as WeekdayScheduleSeeder.php.

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\WeekdaySchedule;

class WeekdayScheduleSeeder extends Seeder
{
    public function run(): void
    {
        $weeklyData = [
            1 => [
                ['Standup Meeting', '09:00', '09:15', 'Daily sync'],
                ['Development', '09:30', '12:30', 'Feature work'],
                ['Lunch', '12:30', '13:30', 'Break'],
                ['Code Review', '13:30', '15:00', 'Review PRs'],
                ['Client Call', '15:15', '16:00', 'Client discussion'],
                ['Wrap Up', '16:15', '17:00', 'Day end'],
            ],
            2 => [
                ['Standup', '09:00', '09:15', 'Team sync'],
                ['Development', '09:30', '12:30', 'Coding'],
                ['Learning', '13:30', '14:30', 'Upskilling'],
                ['Bug Fixing', '14:30', '16:00', 'Fix issues'],
            ],
            7 => [
                ['Off Day', '00:00', '23:59', 'No work scheduled'],
            ],
        ];

        foreach ($weeklyData as $weekday => $events) {
            foreach (array_slice($events, 0, 6) as $event) {
                WeekdaySchedule::create([
                    'weekday' => $weekday,
                    'title' => $event[0],
                    'start_time' => $event[1],
                    'end_time' => $event[2],
                    'description' => $event[3],
                ]);
            }
        }
    }
}

Run seeder using the following command,

php artisan db:seed --class=WeekdayScheduleSeeder

With component code and some data, it is now ready to be display on any page. To attach this livewire component, use the following code,

<livewire:day-schedule />

Final Results

  • Users see today’s schedule by default
  • Can navigate future days using Next
  • Previous button is disabled on today
  • Clean Bootstrap UI
  • Fully reactive Livewire experience
  • Easily extendable for Filament or APIs

Conclusion

This approach provides a clean, scalable way to manage day-wise schedules in Laravel using Livewire and Bootstrap 5. It avoids JavaScript complexity, keeps logic server-driven, and delivers a smooth UX.

If you’re building dashboards, booking systems, or internal tools — this pattern fits perfectly.

How I Built a Full File Explorer in Laravel Livewire with Search

Learn how to create a complete file explorer in Laravel using Livewire and Bootstrap – with folders, subfolders, and document search. Step-by-step tutorial with code.

Have you ever wanted to give your Laravel app a professional, desktop-like file explorer – with folders, nested subfolders and document search?

In this tutorial, I’ll walk you through building full file explorer using Laravel Livewire and Bootstrap 5. We’ll create a clean two-pane layout: the left sidebar displays your folder structure (applications and subfolders), while the right pane dynamically lists documents with instant search and filtering.

By the end, you’ll have a fully functional, real-time file manager where users can browse folders, search documents by name and group them by type – all powered by Livewire’s reactivity and Laravel’s filesystem magic.

What We’re Building

We’ll create a two-pane interface like a typical file explorer:

  • Left sidebar → list of applications/folders and nested subfolders.
  • Right pane → list of documents for the selected application.
  • Features:
    • Real-time search
    • Subfolder navigation
    • Document type–based organization

Step 1. Setting Up the Database

We’ll use two tables: applications and documents. applications table holds our folders and subfolders, while documents table holds all documents with name and path with application relation.

Schema::create('applications', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->foreignId('parent_id')->nullable()->constrained('applications')->cascadeOnDelete();
    $table->timestamps();
});

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->foreignId('application_id')->constrained()->cascadeOnDelete();
    $table->string('title');
    $table->string('file_path');  // e.g. "documents/report.pdf"
    $table->string('type')->nullable(); // e.g. "invoice", "report", "letter"
    $table->timestamps();
});

Step 2. Eloquent Relationships

Define relationships in models as follows,

// Application.php
class Application extends Model
{
    protected $fillable = ['name', 'parent_id'];

    public function parent()
    {
        return $this->belongsTo(Application::class, 'parent_id');
    }

    public function children()
    {
        return $this->hasMany(Application::class, 'parent_id');
    }

    public function documents()
    {
        return $this->hasMany(Document::class);
    }
}

// Document.php
class Document extends Model
{
    protected $fillable = ['application_id', 'title', 'file_path', 'type'];

    public function application()
    {
        return $this->belongsTo(Application::class);
    }
}

Step 3. The Livewire Component

Run the following command to create the livewire component:

php artisan make:livewire ApplicationExplorer

It will create 2 files as follows,

  • app/Http/Livewire/ApplicationExplorer.php for file explorer processing.
  • resources/views/livewire/application-explorer.blade.php for file explorer view.

Define all Processes

Open app/Http/Livewire/ApplicationExplorer.php and add the following code:

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Application;
use App\Models\Document;
use Livewire\WithPagination;
use Illuminate\Support\Facades\Storage;
use ZipArchive;

class ApplicationExplorer extends Component
{
    use WithPagination;
    protected $paginationTheme = 'bootstrap';

    public $selectedAppId = null;
    public $search = '';
    public $filterType = '';

    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function selectApp($appId)
    {
        $this->selectedAppId = $appId;
        $this->reset(['search', 'filterType']);
        $this->resetPage();
    }

    public function render()
    {
        $applications = Application::with('children')->whereNull('parent_id')->get();

        $selectedApp = $this->selectedAppId ? Application::find($this->selectedAppId) : null;

        $documents = collect();
        if ($selectedApp) {
            $documents = Document::where('application_id', $this->selectedAppId)
                ->when($this->filterType, fn($q) => $q->where('type', $this->filterType))
                ->when($this->search, fn($q) => $q->where('title', 'like', "%{$this->search}%"))
                ->paginate(10);
        }

        return view('livewire.application-explorer', [
            'applications' => $applications,
            'selectedApp'  => $selectedApp,
            'documents'    => $documents,
        ]);
    }
}

Update the View

To update the view, open resources/views/livewire/application-explorer.blade.php and add the below code,

<div class="container-fluid vh-100">
    <div class="row h-100">
        {{-- Sidebar --}}
        <div class="col-3 border-end bg-light p-0 overflow-auto">
            <div class="p-3 border-bottom"><h5 class="mb-0">Applications</h5></div>
            <ul class="list-group list-group-flush">
                @foreach($applications as $app)
                    @include('livewire.partials.sidebar-app', ['app' => $app, 'level' => 0])
                @endforeach
            </ul>
        </div>

        {{-- Right Pane --}}
        <div class="col-9 p-4">
            @if($selectedApp)
                <div class="d-flex justify-content-between align-items-center mb-3">
                    <h4 class="mb-0">Documents in "{{ $selectedApp->name }}"</h4>
                    <div class="d-flex gap-2 align-items-center" style="width:50%;">
                        <select class="form-select w-auto" wire:model="filterType">
                            <option value="">All Types</option>
                            <option value="invoice">Invoice</option>
                            <option value="report">Report</option>
                            <option value="letter">Letter</option>
                        </select>
                        <input type="text" class="form-control" placeholder="Search..." wire:model.debounce.300ms="search">
                    </div>
                </div>

                @if($documents->count())
                    <div class="table-responsive">
                        <table class="table table-striped table-bordered align-middle">
                            <thead class="table-dark">
                                <tr>
                                    <th>#</th>
                                    <th>Document</th>
                                    <th>Type</th>
                                    <th>Created</th>
                                    <th>Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                @foreach($documents as $i => $doc)
                                    <tr>
                                        <td>{{ $i + 1 }}</td>
                                        <td>📄 {{ $doc->title }}</td>
                                        <td>{{ $doc->type ?? '-' }}</td>
                                        <td>{{ $doc->created_at->format('d M Y') }}</td>
                                        <td><button class="btn btn-sm btn-primary">View</button></td>
                                    </tr>
                                @endforeach
                            </tbody>
                        </table>
                    </div>

                    {{ $documents->links() }}
                @else
                    <div class="text-muted fst-italic">No documents found.</div>
                @endif
            @else
                <div class="text-muted fst-italic">Select an application to view documents.</div>
            @endif
        </div>
    </div>
</div>

For sidebar, create a new file at resources/views/livewire/partials/sidebar-app.blade.php and add the following code,

<li class="list-group-item p-0 d-flex justify-content-between align-items-center">
    <button wire:click="selectApp({{ $app->id }})"
            class="btn flex-grow-1 text-start px-3 py-2 {{ $selectedAppId === $app->id ? 'btn-secondary' : 'btn-light' }}"
            style="padding-left: {{ 12 + ($level * 15) }}px;">
        📁 <span class="ms-2">{{ $app->name }}</span>
    </button>
</li>

@if($app->children->count())
    <ul class="list-group list-group-flush">
        @foreach($app->children as $child)
            @include('livewire.partials.sidebar-app', ['app' => $child, 'level' => $level + 1])
        @endforeach
    </ul>
@endif

Step 4. File Storage Notes

Documents are uploaded to storage/app/public/documents/.... So, make sure that your public link exists:

php artisan storage:link

Always use:

Storage::disk('public')->exists($path);
Storage::disk('public')->path($path);

This ensures correct behavior since your files are under the public disk.

Step 5. Directory Structure by Document Type

When uploading new documents, if you want to make a directory structure based on document types, you can follow this approach:

$file->store("documents/{$applicationId}/" . strtolower($type), 'public');

This automatically creates folders like:

/storage/app/public/documents/3/invoices/file.pdf

Final Result

  • Left Sidebar: Displays all applications + nested subfolders.
  • Right Pane: Shows searchable, paginated, filterable document table.

Conclusion

And there you have it – a complete file explorer built with Laravel Livewire, featuring a responsive sidebar, real-time search and type-based organization.

This approach not only showcases the power of Livewire for dynamic interfaces but also demonstrates how easily Laravel’s Storage, and Eloquent relationships can come together to create a feature-rich, production-ready document management system.

From here, you can take it even further – add drag-and-drop uploads, access control, file previews, download all files in single zip with complete folder structure or even integrate third-party storage like AWS S3. With this foundation, your Laravel app can handle documents as efficiently as any modern file manager.