edissavov/filament-booking-calendar

A flexible booking calendar widget for Filament v3 built on FullCalendar

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/edissavov/filament-booking-calendar

dev-main 2025-12-19 16:58 UTC

This package is auto-updated.

Last update: 2025-12-19 17:02:41 UTC


README

A flexible, feature-rich booking calendar widget for Filament v3 built on FullCalendar. Perfect for appointment scheduling, salon bookings, room reservations, and any booking-based system.

Features

  • 📅 Full CRUD operations on calendar events
  • 🎨 Status-based color coding (customizable)
  • 📱 Mobile-responsive design with auto-view switching
  • ⚙️ Highly configurable through config file
  • 🔧 Extensible through widget inheritance
  • 🌍 Multi-language support
  • ⏰ Configurable business hours and timezones
  • 📊 Multiple calendar views (Month, Week, Day, List)
  • 💡 Event tooltips
  • 🎯 Metadata support for domain-specific fields

Installation

Install the package via Composer:

composer require edissavov/filament-booking-calendar

Quick Start

1. Publish Configuration (Optional)

php artisan vendor:publish --tag=filament-booking-calendar-config

2. Run Migrations

If you don't have a bookings table:

php artisan vendor:publish --tag=filament-booking-calendar-migrations
php artisan migrate

Note: If you already have a bookings table, skip this step and configure your own model in the config file.

3. Register the Widget

Option A: As a Page Widget

Create a Filament page:

// app/Filament/Pages/Calendar.php
<?php

namespace App\Filament\Pages;

use Edissavov\FilamentBookingCalendar\Widgets\BookingCalendarWidget;
use Filament\Pages\Page;

class Calendar extends Page
{
    protected static ?string $navigationIcon = 'heroicon-o-calendar-days';
    protected static string $view = 'filament.pages.calendar';
    protected static ?string $title = 'Calendar';

    protected function getHeaderWidgets(): array
    {
        return [
            BookingCalendarWidget::class,
        ];
    }
}

Create the view:

{{-- resources/views/filament/pages/calendar.blade.php --}}
<x-filament-panels::page />

Option B: As a Dashboard Widget

Register in your Panel Provider:

// app/Providers/Filament/AdminPanelProvider.php
->widgets([
    \Edissavov\FilamentBookingCalendar\Widgets\BookingCalendarWidget::class,
])

Configuration

Using Existing Booking Model

If you already have a Booking model, point to it in the config:

// config/filament-booking-calendar.php
'models' => [
    'booking' => \App\Models\Booking::class,
],

Minimum required fields:

  • name (string)
  • phone (string)
  • email (string, nullable)
  • date (datetime)
  • status (string)
  • price (decimal, nullable)
  • notes (text, nullable)

Customize Status Colors

'statuses' => [
    'pending' => [
        'label' => 'Pending Confirmation',
        'color' => ['bg' => '#FFA500', 'border' => '#FF8C00'],
    ],
    'confirmed' => [
        'label' => 'Confirmed',
        'color' => ['bg' => '#3B82F6', 'border' => '#2563EB'],
    ],
    // Add more statuses...
],

Localization

'calendar' => [
    'locale' => 'bg',  // Bulgarian
    'timezone' => 'Europe/Sofia',
    'button_text' => [
        'today' => 'Днес',
        'month' => 'Месец',
        'week' => 'Седмица',
        'day' => 'Ден',
        'list_week' => 'Списък седмица',
        'list_day' => 'Списък ден',
    ],
],

Business Hours

'calendar' => [
    'business_hours' => [
        'start' => '09:00:00',
        'end' => '18:00:00',
    ],
],

Phone Number Formatting

'form' => [
    'enable_phone_formatting' => true,
    'phone_country_code' => '+359',  // Bulgaria
],

This will automatically convert phone numbers starting with "0" to "+359xxx".

Advanced Usage

Extending the Widget

For domain-specific customizations (like pet grooming), extend the widget:

// app/Filament/Widgets/PetGroomingCalendar.php
<?php

namespace App\Filament\Widgets;

use Edissavov\FilamentBookingCalendar\Widgets\BookingCalendarWidget;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Fieldset;

class PetGroomingCalendar extends BookingCalendarWidget
{
    protected function getFormSchema(): array
    {
        $baseSchema = parent::getFormSchema();

        // Add pet-specific fields
        $petFields = Fieldset::make('Pet Information')
            ->schema([
                TextInput::make('metadata.pet_name')
                    ->label('Pet Name')
                    ->required(),
                TextInput::make('metadata.breed')
                    ->label('Breed'),
                Select::make('metadata.weight')
                    ->label('Weight')
                    ->options([
                        'Small (< 5kg)' => 'Small (< 5kg)',
                        'Medium (5-15kg)' => 'Medium (5-15kg)',
                        'Large (15-30kg)' => 'Large (15-30kg)',
                    ]),
            ])
            ->columns(3);

        // Insert after customer information
        array_splice($baseSchema, 1, 0, [$petFields]);

        return $baseSchema;
    }

    protected function getEventTitle($booking): string
    {
        $petName = $booking->metadata['pet_name'] ?? null;
        return $booking->name . ($petName ? " ($petName)" : '');
    }
}

Then configure the title suffix:

'fields' => [
    'title' => 'name',
    'title_suffix' => 'metadata.pet_name',  // Shows "Owner (Pet Name)"
],

Using Service Providers

If your bookings are assigned to specific staff members:

'models' => [
    'booking' => \App\Models\Booking::class,
    'service_provider' => \App\Models\User::class,  // Your staff model
],

The widget will automatically add a "Service Provider" select field.

Custom Event Colors

Override the color logic for advanced scenarios:

protected function getEventColors(string $status): array
{
    // Custom logic, e.g., color by service provider
    if ($this->record->service_provider_id === 1) {
        return ['bg' => '#FF6B6B', 'border' => '#EE5555'];
    }

    return parent::getEventColors($status);
}

Migration Guide

From Pet Grooming App

If you're migrating from a pet-specific implementation:

  1. Migrate pet fields to metadata:
DB::table('bookings')->get()->each(function ($booking) {
    DB::table('bookings')
        ->where('id', $booking->id)
        ->update([
            'metadata' => json_encode([
                'pet_name' => $booking->pet_name,
                'breed' => $booking->breed,
                'weight' => $booking->weight,
            ])
        ]);
});
  1. Configure for Bulgarian locale:
'calendar' => [
    'locale' => 'bg',
    'timezone' => 'Europe/Sofia',
],

'statuses' => [
    'непотвърдена' => ['label' => 'Непотвърдена', 'color' => ['bg' => '#EAB308', 'border' => '#CA8A04']],
    'потвърдена' => ['label' => 'Потвърдена', 'color' => ['bg' => '#3B82F6', 'border' => '#2563EB']],
    'успешна' => ['label' => 'Успешна', 'color' => ['bg' => '#10B981', 'border' => '#059669']],
    'отказана' => ['label' => 'Отказана', 'color' => ['bg' => '#EF4444', 'border' => '#DC2626']],
],

API Reference

BookingCalendarWidget Methods

Overridable Methods:

  • getEventTitle($booking): string - Customize event title display
  • getEventColors(string $status): array - Customize event colors
  • getEventExtendedProps($booking): array - Add custom tooltip data
  • formatPhoneNumber(?string $phone): ?string - Custom phone formatting
  • getFormSchema(): array - Customize the booking form

Model Scopes:

Booking::byStatus('confirmed')->get();
Booking::betweenDates('2024-01-01', '2024-01-31')->get();
Booking::upcoming()->get();
Booking::past()->get();

Examples

Salon Booking System

'models' => [
    'booking' => \App\Models\Appointment::class,
    'service_provider' => \App\Models\Stylist::class,
],

'statuses' => [
    'scheduled' => ['label' => 'Scheduled', 'color' => ['bg' => '#3B82F6', 'border' => '#2563EB']],
    'completed' => ['label' => 'Completed', 'color' => ['bg' => '#10B981', 'border' => '#059669']],
    'no_show' => ['label' => 'No Show', 'color' => ['bg' => '#EF4444', 'border' => '#DC2626']],
],

Room Reservations

'models' => [
    'booking' => \App\Models\Reservation::class,
    'resource' => \App\Models\Room::class,
],

'fields' => [
    'title' => 'name',
    'title_suffix' => 'resource.name',  // Shows "Guest (Room 101)"
],

Troubleshooting

Migration Conflicts

If you already have a bookings table, the migration will automatically skip. Configure your own model:

'models' => [
    'booking' => \App\Models\Booking::class,
],

Relationship Errors

If you get "Model not configured" errors, make sure to set the relationship models in config:

'models' => [
    'service_provider' => \App\Models\User::class,  // Required if using this relationship
],

Events Not Showing

  1. Check that your booking model has the required fields
  2. Verify the model is set correctly in config
  3. Check that bookings exist in the database

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

The MIT License (MIT). Please see License File for more information.

Credits