a909m/filament-statefusion

Filament StateFusion is a powerful FilamentPHP plugin that seamlessly integrates Spatie Laravel Model States into the Filament admin panel. This package provides an intuitive way to manage model states, transitions, and filtering within Filament, enhancing the user experience and developer productiv

dev-main 2025-04-10 06:06 UTC

This package is auto-updated.

Last update: 2025-04-10 06:06:59 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Filament StateFusion is a powerful FilamentPHP plugin that seamlessly integrates Spatie Laravel Model States into the Filament admin panel. This package provides an intuitive way to manage model states, transitions, and filtering within Filament, enhancing the user experience and developer productivity.

Introduction

The Model States for Filament plug-in is a powerful tool that seamlessly integrates model states and transitions into the Filament UI. It supports Spatie Laravel Model States out-of-the-box, which makes transitioning, listing, and filtering states effortless tasks.

Example

Consider a scenario where you're managing an e-commerce platform with various order states like new, processing, shipped, delivered, and canceled. With this plug-in, you can effortlessly list all products based on their states, apply filters to quickly locate specific orders in each state, and seamlessly transition between states directly within Filament's intuitive interface. For instance, you can move an order from processing to shipped with just a few clicks, streamlining your workflow and enhancing productivity.

This plug-in leverages the robust capabilities of Filament PHP and the support of Spatie Laravel Model States out-of-the-box, providing a seamless solution for managing model states and transitions with efficiency and ease.

tateDiagram-v2
  [*] --> New
  New --> Processing
  New --> Canceled
  Processing --> Shipped
  Processing --> Canceled
  Shipped --> Delivered
  Delivered --> [*]
  Canceled --> [*]
Loading

Features

  • Listing states within tables and exports
  • Filtering records by states
  • Grouping records by states
  • Transitioning to valid states using select or toggle button components
  • Transitioning to valid states using page and table actions
  • Bulk transition to valid states using bulk actions
  • Out-of-the-box support for the Spatie Laravel Model States package
  • Compatible with dark mode

Installation

You can install the package via composer:

composer require a909m/filament-statefusion

Setup

In this paragraph, we list the steps you need to follow to get up and running with the out-of-the-box supported Spatie integration.

Spatie

Make sure you have configured at least one Spatie Laravel model state. For more information, refer to the official Spatie documentation.

State Preparation

When utilizing Spatie Laravel Model States, you'll have several abstract state classes. These abstract classes require certain modifications. To properly integrate them, it's necessary to implement the FilamentSpatieState interface and utilize the ProvidesSpatieStateToFilament trait.

Here's an example of the OrderState abstract class with the necessary modifications already applied.

<?php

namespace App\States;

use App\Models\Order;
use A909M\FilamentStateFusion\Concerns\StateFusionInfo;
use A909M\FilamentStateFusion\Contracts\HasFilamentStateFusion;
use Spatie\ModelStates\State;
use Spatie\ModelStates\StateConfig;


abstract class OrderState extends State implements HasFilamentStateFusion
{
    use StateFusionInfo;

    public static function config(): StateConfig
    {
        return parent::config()
            ->default(NewState::class)
            ->allowTransition(NewState::class, ProcessingState::class)
            ->allowTransition(ProcessingState::class, ShippedState::class)
            ->allowTransition(ShippedState::class, DeliveredState::class)
            ->allowTransition([NewState::class, ProcessingState::class], CancelledState::class, ToCancelled::class);
    }
}

Tip

More information about state configuration can be found in the official Spatie documentation.

Transition Preparation

Spatie Laravel model states offer support for custom transition classes. All custom transition classes must implement the HasFilamentStateFusion interface and use the StateFusionInfo trait before they can be used within Filament.

Here is an example of the ToCancelled transition class with the necessary modifications in place.

<?php

namespace App\States;

use App\Models\Order;
use Spatie\ModelStates\Transition;


final class ToCancelled extends Transition
{

    public function __construct(
        private readonly Order $order,
    ) {
    }

    public function handle(): Order
    {
        $this->order->state = new CancelledState($this->order);
        $this->order->cancelled_at = now();

        $this->order->save();

        return $this->order;
    }
}

Tip

For more information about transition configuration, refer to the official Spatie documentation.

Additional Transition Data

Most of the time, additional data is needed before transitioning to a new state. Considering the ToCancelled transition, it would be beneficial to store a reason explaining why the state transitioned to cancelled state. By adding a form method to the transition class, a form will be displayed when initiating the transition.

Here is an example ToCancelled transition class with the form is place. This transition will display a reason textarea when the StateFusionAction or StateFusionTableAction button is clicked.

<?php

namespace App\States;

use App\Models\Order;
use Spatie\ModelStates\Transition;


final class ToCancelled extends Transition 
{

    public function __construct(
        private readonly Order $order,
        private readonly string $reason = '',
    ) {
    }

    public function handle(): Order
    {
        $this->order->state = new CancelledState($this->order);
        $this->order->cancelled_at = now();
        $this->order->cancellation_reason = $this->reason;

        $this->order->save();

        return $this->order;
    }

    public function form(): array | Closure | null
    {
        return [
            Textarea::make('reason')
                ->required()
                ->minLength(1)
                ->maxLength(1000)
                ->rows(5)
                ->helperText(__('This reason will be sent to the customer.')),
        ];
    }
}

Warning

Since the plug-in needs to create transition instances to determine if there is a form, all constructor properties, except for the model, must have default values.

By default, this plug-in will map the form component names to their constructor property names. Considering the previous ToCancelled transition, the reason textarea input will correspond to the constructor property $reason. If you want to make any modifications before creating the transition instance, you can override the static method fill.

For example, you can prefix the reason:

<?php

namespace App\States;

use App\Models\Order;
use Illuminate\Support\Arr;
use Spatie\ModelStates\Transition;


final class ToCancelled extends Transition i
{

    public function __construct(
        private readonly Order $order,
        private readonly string $reason = '',
    ) {
    }

    public static function fill(Model $model, array $formData): SpatieTransition
    {
        return new self(
            order: $model,
            reason: 'The order is cancelled because: ' . Arr::get($formData, 'reason'),
        );
    }

    public function handle(): Order
    {
        $this->order->state = new CancelledState($this->order);
        $this->order->cancelled_at = now();
        $this->order->cancellation_reason = $this->reason;

        $this->order->save();

        return $this->order;
    }

    public function form(): array | Closure | null
    {
        return [
            Textarea::make('reason')
                ->required()
                ->minLength(1)
                ->maxLength(1000)
                ->rows(5)
                ->helperText(__('This reason will be sent to the customer.')),
        ];
    }
}

Optional Label, Description, Color and Icon

By default, the name of the state class is used as a label (for example, CancelledState will have the label Cancelled), without any assigned description, color or icon. If you desire a different label, description, color, or icon, you must implement the HasLabel, HasDescription, HasColor, or HasIcon interface.

Here is an example of the Cancelled state with HasLabel, HasDescription, HasColor, and HasIcon implemented.

<?php

namespace App\States;

use Filament\Support\Colors\Color;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasDescription;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;

final class CancelledState extends OrderState implements HasDescription, HasColor, HasIcon, HasLabel
{
    public function getLabel(): string
    {
        return __('Order Cancelled');
    }

    public function getColor(): array
    {
        return Color::Red;
    }

    public function getIcon(): string
    {
        return 'heroicon-o-x-circle';
    }

    public function getDescription(): ?string
    {
        return 'Order cancelled, transaction reversed.';
    }
}

Note

The description is used when utilizing the StateFusionRadio component.

By default, "Transition to" followed by the name of the destination state is used as the transition label. Like states, it has no color or icon. If you want a different label, or if you want to use a color or icon; you have to implement the HasLabel, HasColor or HasIcon interface.

Here is an example ToCancelled transtition with HasLabel, HasColor and HasIcon implemented.

<?php

namespace App\States;

use App\Models\Order;
use Filament\Support\Colors\Color;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
use Spatie\ModelStates\Transition;


final class ToCancelled extends Transition implements HasLabel, HasColor, HasIcon
{


    public function __construct(
        private readonly Order $order,
        private readonly string $reason = '',
    ) {
    }

    public function handle(): Order
    {
        $this->order->state = new CancelledState($this->order);
        $this->order->cancelled_at = now();
        $this->order->cancellation_reason = $this->reason;

        $this->order->save();

        return $this->order;
    }

    public function getLabel(): string
    {
        return __('Mark as Cancelled');
    }

    public function getColor(): array
    {
        return Color::Red;
    }

    public function getIcon(): string
    {
        return 'heroicon-o-x-circle';
    }

    public function form(): array | Closure | null
    {
        return [
            Textarea::make('reason')
                ->required()
                ->minLength(1)
                ->maxLength(1000)
                ->rows(5)
                ->helperText(__('This reason will be sent to the customer.')),
        ];
    }
}

Usage

$filamentStateFusion = new A909M\FilamentStateFusion();
echo $filamentStateFusion->echoPhrase('Hello, A909M!');

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

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