Skip to content

Commit 1fe480f

Browse files
Merge pull request #25 from Relaticle/2.x-next
2.x next
2 parents 5982383 + 7290bad commit 1fe480f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2199
-3198
lines changed

README.md

Lines changed: 259 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use Relaticle\Flowforge\Board;
4949
use Relaticle\Flowforge\BoardPage;
5050
use Relaticle\Flowforge\Column;
5151

52-
class TaskBoard extends BoardPage
52+
class TaskBoardPage extends BoardPage
5353
{
5454
protected static ?string $navigationIcon = 'heroicon-o-view-columns';
5555

@@ -62,8 +62,9 @@ class TaskBoard extends BoardPage
6262
{
6363
return $board
6464
->query($this->getEloquentQuery())
65-
->cardTitle('title')
65+
->recordTitleAttribute('title')
6666
->columnIdentifier('status')
67+
->reorderBy('order_column')
6768
->columns([
6869
Column::make('todo')->label('To Do')->color('gray'),
6970
Column::make('in_progress')->label('In Progress')->color('blue'),
@@ -135,7 +136,7 @@ php artisan flowforge:make-board TaskBoard --model=Task
135136
```php
136137
// app/Providers/Filament/AdminPanelProvider.php
137138
->pages([
138-
App\Filament\Pages\TaskBoard::class,
139+
App\Filament\Pages\TaskBoardPage::class,
139140
])
140141
```
141142

@@ -153,7 +154,7 @@ public function board(Board $board): Board
153154
{
154155
return $board
155156
->query($this->getEloquentQuery())
156-
->cardTitle('title')
157+
->recordTitleAttribute('title')
157158
->columnIdentifier('status')
158159
->columns([
159160
Column::make('backlog')->label('Backlog'),
@@ -169,41 +170,79 @@ Add create and edit capabilities:
169170
```php
170171
use Filament\Actions\CreateAction;
171172
use Filament\Actions\EditAction;
173+
use Filament\Actions\DeleteAction;
172174
use Filament\Forms\Components\TextInput;
175+
use Filament\Forms\Components\Select;
173176

174177
public function board(Board $board): Board
175178
{
176179
return $board
177180
->query($this->getEloquentQuery())
178-
->cardTitle('title')
181+
->recordTitleAttribute('title')
179182
->columnIdentifier('status')
180-
->columns([...])
183+
->reorderBy('order_column')
184+
->columns([
185+
Column::make('todo')->label('To Do')->color('gray'),
186+
Column::make('in_progress')->label('In Progress')->color('blue'),
187+
Column::make('completed')->label('Completed')->color('green'),
188+
])
181189
->columnActions([
182-
CreateAction::make()
190+
CreateAction::make('create')
183191
->label('Add Task')
184-
->iconButton()
192+
->icon('heroicon-o-plus')
185193
->model(Task::class)
186-
->schema([
194+
->form([
187195
TextInput::make('title')->required(),
188-
]),
196+
Select::make('priority')
197+
->options(['low' => 'Low', 'medium' => 'Medium', 'high' => 'High'])
198+
->default('medium'),
199+
])
200+
->mutateFormDataUsing(function (array $data, string $columnId): array {
201+
$data['status'] = $columnId;
202+
return $data;
203+
}),
189204
])
190205
->cardActions([
191-
EditAction::make()->model(Task::class),
192-
]);
206+
EditAction::make('edit')->model(Task::class),
207+
DeleteAction::make('delete')->model(Task::class),
208+
])
209+
->cardAction('edit'); // Make cards clickable
193210
}
194211
```
195212

196-
### Advanced Board with Properties
197-
Display additional model attributes:
213+
### Advanced Board with Schema
214+
Use Filament's Schema system for rich card content:
198215

199216
```php
200-
use Relaticle\Flowforge\Property;
217+
use Filament\Infolists\Components\TextEntry;
218+
use Filament\Schemas\Schema;
201219

202-
->cardProperties([
203-
Property::make('description')->label('Description'),
204-
Property::make('due_date')->label('Due')->color('red'),
205-
Property::make('assignee.name')->label('Assigned')->icon('heroicon-o-user'),
206-
])
220+
public function board(Board $board): Board
221+
{
222+
return $board
223+
->query($this->getEloquentQuery())
224+
->recordTitleAttribute('title')
225+
->columnIdentifier('status')
226+
->cardSchema(fn (Schema $schema) => $schema
227+
->components([
228+
TextEntry::make('priority')
229+
->badge()
230+
->color(fn ($state) => match ($state) {
231+
'high' => 'danger',
232+
'medium' => 'warning',
233+
'low' => 'success',
234+
default => 'gray',
235+
}),
236+
TextEntry::make('due_date')
237+
->date()
238+
->icon('heroicon-o-calendar'),
239+
TextEntry::make('assignee.name')
240+
->icon('heroicon-o-user')
241+
->placeholder('Unassigned'),
242+
])
243+
)
244+
->columns([...]);
245+
}
207246
```
208247

209248
---
@@ -214,14 +253,26 @@ use Relaticle\Flowforge\Property;
214253

215254
| Method | Description | Required |
216255
|--------|-------------|----------|
217-
| `cardTitle(string)` | Field used for card titles ||
256+
| `query(Builder\|Closure)` | Set the data source ||
257+
| `recordTitleAttribute(string)` | Field used for card titles ||
218258
| `columnIdentifier(string)` | Field that determines column placement ||
219259
| `columns(array)` | Define board columns ||
220-
| `query(Builder)` | Set the data source ||
221-
| `defaultSort(string, ?string)` | Field and optional direction for drag & drop ordering | |
222-
| `cardProperties(array)` | Additional fields to display | |
223-
| `columnActions(array)` | Actions for column headers | |
260+
| `reorderBy(string, string)` | Enable drag & drop with field and direction | |
261+
| `cardSchema(Closure)` | Configure card content with Filament Schema | |
224262
| `cardActions(array)` | Actions for individual cards | |
263+
| `columnActions(array)` | Actions for column headers | |
264+
| `cardAction(string)` | Default action when cards are clicked | |
265+
| `searchable(array)` | Enable search across specified fields | |
266+
267+
### Livewire Methods (Available in your BoardPage)
268+
269+
| Method | Description | Usage |
270+
|--------|-------------|-------|
271+
| `updateRecordsOrderAndColumn(string, array)` | Handle drag & drop updates | Automatic |
272+
| `loadMoreItems(string, ?int)` | Load more cards for pagination | Automatic |
273+
| `getBoardRecord(int\|string)` | Get single record by ID | Manual |
274+
| `getBoardColumnRecords(string)` | Get all records for a column | Manual |
275+
| `getBoardColumnRecordCount(string)` | Count records in a column | Manual |
225276

226277
### Available Colors
227278

@@ -231,39 +282,201 @@ use Relaticle\Flowforge\Property;
231282

232283
## Troubleshooting
233284

234-
<details>
235-
<summary><strong>🔧 Cards not draggable</strong></summary>
285+
### 🔧 Cards not draggable
286+
**Cause:** Missing order column or reorderBy configuration
287+
**Solution:**
288+
1. Add integer column to your migration: `$table->integer('order_column')->nullable();`
289+
2. Add `->reorderBy('order_column')` to your board configuration
290+
3. Ensure your model's `$fillable` includes the order column
291+
292+
### 📭 Empty board showing
293+
**Cause:** Query returns no results or status field mismatch
294+
**Debug steps:**
295+
1. Check query: `dd($this->getEloquentQuery()->get());`
296+
2. Verify status values match column names exactly
297+
3. Check database field type (string vs enum)
298+
299+
### ❌ Actions not working
300+
**Cause:** Missing Filament traits or action configuration
301+
**Solution:**
302+
1. Ensure your BoardPage implements `HasActions`, `HasForms`
303+
2. Use these traits in your class:
304+
```php
305+
use InteractsWithActions;
306+
use InteractsWithForms;
307+
use InteractsWithBoard;
308+
```
309+
3. Configure actions properly with `->model(YourModel::class)`
236310

311+
### 🔄 Drag & drop updates not saving
312+
**Cause:** Missing primary key handling or invalid field names
237313
**Solution:**
238-
1. Add an integer `order_column` to your model
239-
2. Configure `->defaultSort('order_column')` in your board
314+
1. Ensure your model uses standard primary key or override `getKeyName()`
315+
2. Check status field accepts the column identifier values
316+
3. Verify order column exists and is fillable
240317

318+
### 💥 "No default Filament panel" error
319+
**Cause:** Missing panel configuration in tests/development
320+
**Solution:** Add to your panel provider:
241321
```php
242-
->defaultSort('order_column')
322+
Panel::make()->default()->id('admin')
243323
```
244-
</details>
245-
246-
<details>
247-
<summary><strong>📭 Empty board</strong></summary>
248324

325+
### 🎨 Styling not loading
326+
**Cause:** Assets not built or registered
249327
**Solution:**
250-
1. Ensure your model has records
251-
2. Check status field values match column keys
252-
3. Debug with: `dd($this->getEloquentQuery()->get())`
253-
</details>
328+
1. Run `npm run build` to compile assets
329+
2. Ensure Filament can load the assets with proper permissions
254330

255-
<details>
256-
<summary><strong>❌ Create/Edit not working</strong></summary>
331+
## Real-World Examples
257332

258-
**Solution:**
259-
1. Implement `columnActions()` for create functionality
260-
2. Implement `cardActions()` for edit functionality
261-
3. Ensure proper action configuration
333+
### Complete Task Management Board
334+
335+
```php
336+
<?php
337+
338+
namespace App\Filament\Pages;
339+
340+
use App\Models\Task;
341+
use Filament\Actions\CreateAction;
342+
use Filament\Actions\EditAction;
343+
use Filament\Actions\DeleteAction;
344+
use Filament\Forms\Components\Select;
345+
use Filament\Forms\Components\TextInput;
346+
use Filament\Forms\Components\DatePicker;
347+
use Filament\Infolists\Components\TextEntry;
348+
use Filament\Schemas\Schema;
349+
use Illuminate\Database\Eloquent\Builder;
350+
use Relaticle\Flowforge\Board;
351+
use Relaticle\Flowforge\BoardPage;
352+
use Relaticle\Flowforge\Column;
353+
354+
class TaskBoardPage extends BoardPage
355+
{
356+
protected static ?string $navigationIcon = 'heroicon-o-view-columns';
357+
protected static ?string $navigationLabel = 'Task Board';
358+
359+
public function getEloquentQuery(): Builder
360+
{
361+
return Task::query()->with('assignee');
362+
}
363+
364+
public function board(Board $board): Board
365+
{
366+
return $board
367+
->query($this->getEloquentQuery())
368+
->recordTitleAttribute('title')
369+
->columnIdentifier('status')
370+
->reorderBy('order_column', 'desc')
371+
->searchable(['title', 'description', 'assignee.name'])
372+
->columns([
373+
Column::make('todo')->label('📋 To Do')->color('gray'),
374+
Column::make('in_progress')->label('🔄 In Progress')->color('blue'),
375+
Column::make('review')->label('👁️ Review')->color('purple'),
376+
Column::make('completed')->label('✅ Completed')->color('green'),
377+
])
378+
->cardSchema(fn (Schema $schema) => $schema
379+
->components([
380+
TextEntry::make('priority')
381+
->badge()
382+
->color(fn ($state) => match ($state) {
383+
'high' => 'danger',
384+
'medium' => 'warning',
385+
'low' => 'success',
386+
default => 'gray',
387+
}),
388+
TextEntry::make('due_date')
389+
->date()
390+
->icon('heroicon-o-calendar')
391+
->color('orange'),
392+
TextEntry::make('assignee.name')
393+
->icon('heroicon-o-user')
394+
->placeholder('Unassigned'),
395+
])
396+
)
397+
->columnActions([
398+
CreateAction::make('create')
399+
->label('Add Task')
400+
->icon('heroicon-o-plus')
401+
->model(Task::class)
402+
->form([
403+
TextInput::make('title')->required(),
404+
Select::make('priority')
405+
->options(['low' => 'Low', 'medium' => 'Medium', 'high' => 'High'])
406+
->default('medium'),
407+
DatePicker::make('due_date'),
408+
])
409+
->mutateFormDataUsing(function (array $data, string $columnId): array {
410+
$data['status'] = $columnId;
411+
return $data;
412+
}),
413+
])
414+
->cardActions([
415+
EditAction::make('edit')
416+
->model(Task::class)
417+
->form([
418+
TextInput::make('title')->required(),
419+
Select::make('status')
420+
->options([
421+
'todo' => 'To Do',
422+
'in_progress' => 'In Progress',
423+
'review' => 'Review',
424+
'completed' => 'Completed',
425+
]),
426+
Select::make('priority')
427+
->options(['low' => 'Low', 'medium' => 'Medium', 'high' => 'High']),
428+
DatePicker::make('due_date'),
429+
]),
430+
DeleteAction::make('delete')
431+
->model(Task::class)
432+
->requiresConfirmation(),
433+
])
434+
->cardAction('edit'); // Make cards clickable to edit
435+
}
436+
}
437+
```
438+
439+
### Required Database Schema
440+
441+
```sql
442+
CREATE TABLE tasks (
443+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
444+
title VARCHAR(255) NOT NULL,
445+
status VARCHAR(255) DEFAULT 'todo',
446+
order_column INT NULL, -- Required for drag & drop
447+
priority VARCHAR(255) DEFAULT 'medium',
448+
due_date DATE NULL,
449+
assignee_id BIGINT UNSIGNED NULL,
450+
created_at TIMESTAMP NULL,
451+
updated_at TIMESTAMP NULL
452+
);
453+
```
454+
455+
### Testing Your Board
262456

263457
```php
264-
->columnActions([CreateAction::make()->model(Task::class)])
458+
// tests/Feature/TaskBoardTest.php
459+
use Livewire\Livewire;
460+
461+
test('task board renders successfully', function () {
462+
Task::create(['title' => 'Test Task', 'status' => 'todo']);
463+
464+
Livewire::test(TaskBoardPage::class)
465+
->assertSuccessful()
466+
->assertSee('Test Task')
467+
->assertSee('To Do');
468+
});
469+
470+
test('can move tasks between columns', function () {
471+
$task = Task::create(['title' => 'Test Task', 'status' => 'todo']);
472+
473+
Livewire::test(TaskBoardPage::class)
474+
->call('updateRecordsOrderAndColumn', 'completed', [$task->getKey()])
475+
->assertSuccessful();
476+
477+
expect($task->fresh()->status)->toBe('completed');
478+
});
265479
```
266-
</details>
267480

268481
---
269482

0 commit comments

Comments
 (0)