Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Дії, інфолисти й віджети»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

Дія (action) у Filament - це кнопка разом із логікою: що станеться після натискання, чи відкриється модальне вікно, хто її бачить. Усі дії в Filament 5 живуть у просторі імен Filament\Actions - і в таблицях, і на сторінках, і у формах.

Готові дії для типових операцій з Eloquent:

  • CreateAction, EditAction, ViewAction, DeleteAction;
  • ReplicateAction - копія запису (excludeAttributes(['slug']) - не копіювати унікальні поля);
  • ForceDeleteAction, RestoreAction - для моделей з SoftDeletes;
  • ImportAction, ExportAction - CSV/XLSX через черги;
  • масові варіанти: DeleteBulkAction, ForceDeleteBulkAction, RestoreBulkAction.
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Actions\ReplicateAction;

->recordActions([
    EditAction::make(),
    ReplicateAction::make()->excludeAttributes(['slug']),
    DeleteAction::make(),
])

Готові дії вже вміють модальне вікно, сповіщення про успіх і перевірку політики моделі в ресурсі.

Власна дія з підтвердженням:

use Filament\Actions\Action;
use Filament\Support\Icons\Heroicon;

Action::make('cancel')
    ->label('Скасувати замовлення')
    ->icon(Heroicon::OutlinedXCircle)
    ->color('danger')
    ->requiresConfirmation()
    ->modalHeading('Скасувати замовлення?')
    ->modalDescription('Клієнт отримає лист, а оплату буде повернено.')
    ->modalSubmitActionLabel('Так, скасувати')
    ->action(fn (Order $record) => $record->cancel())
    ->successNotificationTitle('Замовлення скасовано');

requiresConfirmation() відкриває стандартне вікно підтвердження, і код в action() виконається лише після згоди.

Пастки:

  • url() і підтвердження несумісні: якщо дія лише відкриває посилання (->url(...)), вікно підтвердження не з'явиться. Для підтвердження перед переходом потрібен action() з редиректом усередині;
  • кілька дій з однаковою назвою в одному місці конфліктують - ім'я в make() має бути унікальним;
  • підтвердження - не авторизація. Хто може натиснути кнопку, вирішують visible()/authorize(), а не модальне вікно.

Докладніше в документації: Дії: огляд

Спливаючі сповіщення («тости») створює клас Filament\Notifications\Notification з флюентним API:

use Filament\Actions\Action;
use Filament\Notifications\Notification;

Notification::make()
    ->title('Замовлення збережено')
    ->body('Клієнту надіслано лист з підтвердженням.')
    ->success()
    ->send();

Стан і вигляд:

  • success(), warning(), danger(), info() - колір та іконка;
  • icon(), iconColor() - власна іконка;
  • duration(5000) / seconds(5) - скільки показувати (за замовчуванням 6 секунд);
  • persistent() - не зникати, доки користувач не закриє.

Дії в сповіщенні:

Notification::make()
    ->title('Імпорт завершено з помилками')
    ->warning()
    ->persistent()
    ->actions([
        Action::make('view')
            ->label('Переглянути')
            ->button()
            ->url(route('imports.show', $import)),
    ])
    ->send();

Як це працює. Сповіщення записується в сесію і показується на наступному рендері. Тому його можна надіслати звідки завгодно: з дії, з контролера перед редиректом, зі звичайного Livewire-компонента. Є і JavaScript-версія: new FilamentNotification().title('...').send().

У готових діях (CreateAction, EditAction, сторінки ресурсів) сповіщення про успіх уже є - текст міняють через successNotificationTitle(), а прибирають - successNotification(null).

Пастки:

  • send() показує сповіщення поточному користувачу в браузері. Щоб повідомити іншого користувача чи зберегти повідомлення надовго - сповіщення в базу даних (sendToDatabase()) чи трансляція (broadcast());
  • заголовок може містити простий безпечний HTML. Дані користувача варто вставляти обережно - санітайзер Filament пропускає атрибути style;
  • надіслане з джоби в черзі сповіщення через send() нікого не досягне: у воркера немає сесії браузера.

Докладніше в документації: Сповіщення

Infolist - схема для перегляду даних запису у вигляді «підпис - значення». Вона використовується на сторінці перегляду ресурсу (ViewRecord), у модальних вікнах ViewAction, у relation managers.

use Filament\Infolists\Components\IconEntry;
use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;

public static function infolist(Schema $schema): Schema
{
    return $schema
        ->components([
            Section::make('Замовлення')
                ->schema([
                    TextEntry::make('number')->copyable(),
                    TextEntry::make('customer.name')->label('Клієнт'),
                    TextEntry::make('status')->badge(),
                    TextEntry::make('total')->money('UAH'),
                    IconEntry::make('is_paid')->boolean(),
                    TextEntry::make('created_at')->dateTime('d.m.Y H:i'),
                ])
                ->columns(3)
                ->columnSpanFull(),

            RepeatableEntry::make('items')
                ->schema([
                    TextEntry::make('product.name'),
                    TextEntry::make('qty'),
                ])
                ->columns(2)
                ->columnSpanFull(),
        ]);
}

Записи (entries) живуть у Filament\Infolists\Components: TextEntry, IconEntry, ImageEntry, ColorEntry, CodeEntry, KeyValueEntry, RepeatableEntry. Макет - ті самі Section, Grid, Tabs зі схем.

Чим краще за форму з disabled():

  • форматування як у таблицях: бейджі, гроші, дати, іконки, markdown, копіювання в буфер, посилання;
  • жодних полів введення - менше HTML, немає стану форми в Livewire-компоненті;
  • зв'язки й JSON читаються крапковою нотацією: customer.name, meta.title;
  • вимкнена форма виглядає як «щось зламалося» і погано читається.

Що варто знати:

  • сторінку перегляду додають у ресурс опцією --view при генерації (або вручну в getPages());
  • якщо метод infolist() не визначено, Filament показує на сторінці перегляду вимкнену форму;
  • TextEntry з html() чи markdown() санітизує вміст - на відміну від власного Blade з {!! !!};
  • relation managers на сторінці перегляду за замовчуванням працюють лише в режимі читання.

Докладніше в документації: Infolists

Віджет статистики - кілька карток з числом, описом і міні-графіком.

php artisan make:filament-widget OrdersOverview --stats-overview
use Filament\Support\Icons\Heroicon;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class OrdersOverview extends StatsOverviewWidget
{
    protected ?string $pollingInterval = '60s';

    protected function getStats(): array
    {
        $today = Order::whereDate('created_at', today());

        return [
            Stat::make('Замовлень сьогодні', $today->count())
                ->description('+12% до вчора')
                ->descriptionIcon(Heroicon::ArrowTrendingUp)
                ->color('success')
                ->chart([7, 3, 4, 5, 6, 3, 9]),

            Stat::make('Виручка сьогодні', Number::currency($today->sum('total'), 'UAH', 'uk')),

            Stat::make('Нових клієнтів', Customer::whereDate('created_at', today())->count()),
        ];
    }
}

Віджети з каталогу app/Filament/Widgets автоматично з'являються на дашборді панелі.

Корисні налаштування:

  • protected static ?int $sort = 1; - порядок на дашборді;
  • protected int | string | array $columnSpan = 'full'; - ширина;
  • public static function canView(): bool - кому показувати;
  • protected static bool $isLazy = false; - вимкнути ліниве завантаження.

Дві важливі поведінки за замовчуванням:

  • опитування кожні 5 секунд. Віджет статистики (і графік) сам оновлюється раз на 5 секунд. Якщо в getStats() важкі агрегати по великих таблицях, кожен відкритий дашборд створює постійне навантаження на базу. Інтервал варто збільшити ($pollingInterval = '60s') або вимкнути (null);
  • ліниве завантаження. Віджети завантажуються окремим запитом після сторінки, тож повільний віджет не блокує решту дашборда.

Пастки:

  • canView() ховає віджет з дашборда, але якщо той самий віджет вбудовано на іншу сторінку, умову треба перевірити й там;
  • важкі метрики варто кешувати (Cache::remember на кілька хвилин) - точність до секунди на дашборді рідко потрібна;
  • число без форматування (1234567) погано читається: краще Number::format() чи Number::abbreviate().

Докладніше в документації: Віджет статистики