Simplify your life with automated, omnichannel reminders for all your critical documents. Try Remindash Now

Adding a Kanban Board per Resource in Filament PHP

Adding a Kanban Board per Resource in Filament PHP
Adding a Kanban Board per Resource in Filament PHP

When I started building Vendex, a vendor and proposal management system, I envisioned a way to streamline the process of managing proposals. I wanted to allow the admin to visualize proposals submitted for a project in a Kanban board format. The goal? Make it easy to drag and drop proposals across various statuses and manage them with a few clicks.

Instead of starting from scratch, I decided to leverage the excellent Filament Kanban Plugin. This plugin provides a robust foundation for building Kanban boards in Filament PHP applications. However, one challenge arose: the plugin didn’t natively support adding a Kanban board per resource. For Vendex, every project had its own unique set of proposals, so I needed to extend the functionality.

Here’s how I solved this, step by step.

Creating a Custom Kanban Page

To create a Kanban board tied to a specific resource (in my case, projects), I extended the plugin’s KanbanBoard class by creating a custom Filament page:

class ProjectProposals extends KanbanBoard
{
    protected static string $model = Proposal::class;
    protected static string $statusEnum = ProposalStatus::class;
    protected static bool $shouldRegisterNavigation = false;

    protected static string $view = 'filament.app.pages.proposals.kanban.kanban-board';
    protected static string $headerView = 'filament.app.pages.proposals.kanban.kanban-header';
    protected static string $recordView = 'filament.app.pages.proposals.kanban.kanban-record';
    protected static string $statusView = 'filament.app.pages.proposals.kanban.kanban-status';

    public bool $disableEditModal = true;
    public ?int $project_id = null;

    public Project $project;
    public Proposal $proposal;

    public function mount(): void
    {
        parent::mount();
        $this->project = Project::where('id', $this->project_id)->firstOrFail();
    }

    public function getTitle(): string | Htmlable
    {
        return 'Project : ' . $this->project->title;
    }
}

Breaking It Down: Customizing the Mount Function

The mount method is one of the first things to understand. Its role is to prepare the page for rendering. Here’s the critical piece:

public function mount(): void
{
    parent::mount();
    $this->project = Project::where('id', $this->project_id)->firstOrFail();
}

At first glance, this snippet looks simple, but it’s crucial.

  • parent::mount();: This ensures that any initialization logic in the parent KanbanBoard class is executed. Skipping this could break core functionality provided by the plugin.
  • Fetching the project: I used the where clause to retrieve the project associated with the provided project_id. The firstOrFail() method ensures the system throws a 404 error if no matching project is found, which is a safe way to handle invalid project IDs.
  • Why store the project? By storing the project in the $project variable, I could access its details throughout the class without repeatedly querying the database.

Displaying the Board Title Dynamically

To give the admin a clear context of what they’re managing, I dynamically set the board title based on the project name:

public function getTitle(): string | Htmlable
{
    return 'Project : ' . $this->project->title;
}

This line ensures that when the Kanban page is loaded, the admin sees a heading like "Project: New Office Construction" instead of a generic title.

Fetching Records for the Kanban Board

Every Kanban column needs data, and the records method is responsible for retrieving the proposals for a specific project:

protected function records(): Collection
{
    return $this->project->proposals()
        ->with(['project', 'vendor', 'user'])
        ->ordered()
        ->get();
}

Handling Status Changes

Dragging a proposal from one status to another triggers the onStatusChanged method:

public function onStatusChanged(int $recordId, string $status, array $fromOrderedIds, array $toOrderedIds): void
{
    $proposal = Proposal::find($recordId);
    $proposal->update(['status' => $status]);

    Proposal::setNewOrder($toOrderedIds);

    if ($status === ProposalStatus::Accepted->value) {
        $vendor = Vendor::find($proposal->vendor->id);
        Mail::to($vendor)->send(new ProposalAccepted($vendor, $proposal));
        Notification::make()
            ->title('Mail Sent To Vendor successfully')
            ->success()
            ->send();
    }

    Notification::make()
        ->title('Status Changed successfully')
        ->success()
        ->send();
}

What Happens Here?

  1. Status Update: When a proposal’s status changes (e.g., from Pending to Accepted), it updates the status field in the database.
  2. Reordering Records: The new order of proposals in the target column is saved using setNewOrder().
  3. Sending Notifications: If the status is set to Accepted, the system sends an email to the vendor and notifies the admin of the successful action.

Adding Routes

To make the page accessible, I added a custom route in web.php:

Route::name('filament.')
    ->group(function () {
        foreach (Filament::getPanels() as $panel) {
            Route::get('/project/{project_id}/proposals', ProjectProposals::class)
                ->name('project.proposals');
        }
    });

This route dynamically passes the project_id to the ProjectProposals page, ensuring each project has its own unique Kanban board.

Customizing Views

The Kanban plugin allows for highly customizable views. For Vendex, I created specific views for:

  • Kanban Header: Displays project details.
  • Kanban Record: Custom proposal cards.
  • Kanban Status: Labels like Pending, Reviewed, Accepted.

These views were registered using properties like $headerView and $recordView.

Conclusion

By extending the Mokhosh Kanban plugin, I was able to integrate a dynamic, resource-specific Kanban board into Vendex. This approach not only saved development time but also provided a flexible way to manage proposals visually.

If you’re working on a similar Filament PHP project, consider checking out the Vendex to get started. With a little customization, the possibilities are endless!

Effortless Expiration Tracking for Busy Professionals

From invoices to contracts, get notified on time, every time, across email, SMS, and WhatsApp