nowak
nowak
FFilament
Created by nowak on 6/28/2024 in #❓┊help
Maximum execution time of 30 seconds exceeded
Since I am only using livewire / blade for my filament admin panel in my laravel app, and I am getting this error a lot when developing the filament admin panel locally, but not the vue frontend, I thought this was the best place to ask about this. I am getting this error every time I navigate to a page, where I have to refresh the page at least one time to get rid of it:
Maximum execution time of 30 seconds exceeded
Maximum execution time of 30 seconds exceeded
Which usually is pointing to the vendor/symfony/finder/Iterator/SortableIterator.php:51 file where line 51 is this:
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
 Flare error share: https://flareapp.io/share/NPLx0BQP Has anyone else experienced this? Or know what the most probable cause is for this? It is getting quite difficult for me to do local development on my filament admin panel based on this, so I would really love to find the cause for this and fix it. Any help is highly appreciated!
16 replies
FFilament
Created by nowak on 6/11/2024 in #❓┊help
When using the Tabs layout in custom page, how can I make an action react to tab changes?
I have a form with a tab layout, with 3 tabs and 3 action buttons. Now I want to make sure that only one action shows for each tab. This is my Tabs layout schema:
protected function getFormSchema(): array
{
return [
Tabs::make('Settings')
->tabs([
Tabs\Tab::make('Business Hours')
->schema($this->businessHoursSchema())
->columns(1),
Tabs\Tab::make('General')
->schema($this->generalSchema())
->columns(1),
Tabs\Tab::make('Billing')
->schema($this->billingSchema())
->columns(1),
])
->persistTabInQueryString()
->id('settings-tabs'),
];
}
protected function getFormSchema(): array
{
return [
Tabs::make('Settings')
->tabs([
Tabs\Tab::make('Business Hours')
->schema($this->businessHoursSchema())
->columns(1),
Tabs\Tab::make('General')
->schema($this->generalSchema())
->columns(1),
Tabs\Tab::make('Billing')
->schema($this->billingSchema())
->columns(1),
])
->persistTabInQueryString()
->id('settings-tabs'),
];
}
Where I attempted to fetch the current tab from the url parameter and hide my actions conditionally like this:
public function getFormActions(): array
{

$currentTab = request()->query('tab', 'settings-tabs-business-hours-tab');
return [
Action::make('saveBusinessHours')
->label('Save Business Hours')
->action(fn () => $this->saveBusinessHours())
->hidden(fn () => $currentTab !== 'settings-tabs-business-hours-tab'),
Action::make('saveGeneralSettings')
->label('Save General Settings')
->action(fn () => $this->saveGeneralSettings())
->hidden(fn () => $currentTab !== 'settings-tabs-general-tab'),
Action::make('saveBillingSettings')
->label('Save Billing Settings')
->action(fn () => $this->saveBillingSettings())
->hidden(fn () => $currentTab !== 'settings-tabs-billing-tab'),
];
}
public function getFormActions(): array
{

$currentTab = request()->query('tab', 'settings-tabs-business-hours-tab');
return [
Action::make('saveBusinessHours')
->label('Save Business Hours')
->action(fn () => $this->saveBusinessHours())
->hidden(fn () => $currentTab !== 'settings-tabs-business-hours-tab'),
Action::make('saveGeneralSettings')
->label('Save General Settings')
->action(fn () => $this->saveGeneralSettings())
->hidden(fn () => $currentTab !== 'settings-tabs-general-tab'),
Action::make('saveBillingSettings')
->label('Save Billing Settings')
->action(fn () => $this->saveBillingSettings())
->hidden(fn () => $currentTab !== 'settings-tabs-billing-tab'),
];
}
This only works on page load, but not when I change the tabs dynamically. How can I make this work when switching tabs dynamically?
12 replies
FFilament
Created by nowak on 6/11/2024 in #❓┊help
Is it possible to create a Filament settings page with a tabbed form, for multiple settings groups?
I have created a few Filament Spatie Settings pages, but I feel like this quickly gets overwhelming with more and more settings pages. So I was wondering how to create a settings page, that contains a tabbed view of all settings groups, where each tab would be a form for that specific settings group?
15 replies
FFilament
Created by nowak on 6/5/2024 in #❓┊help
How to trigger a custom action modal directly from a TextInputColumn updateStateUsing?
I have a TextInputColumn on a table for my resources route_id attribute. When this is changed to a route_id that does not exist, I want to prompt the user with an action modal, where they can create a new route record, which would update the route_id column with the id of the newly created route. I am trying to do this:
TextInputColumn::make('route_id')
->rules(['numeric', 'integer', 'min:1'])
->extraAttributes(['style' => 'min-width:60px'])
->updateStateUsing(function ($state, $record) {
if ($state > 1 || $state !== null) {
$route = Route::find($state);

if (!$route) {
// Trigger the createRoute action if the route does not exist
return Action::make('createRoute')
->form([
TextInput::make('name')->required()->maxLength(255),
ColorPicker::make('color')->required(),
])
->action(function (array $data) use ($state) {
$data['id'] = $state;
Route::create($data);
})
->modalHeading('Create Route')
->modalSubmitActionLabel('Create')
->modalWidth('lg')
->visible(fn () => true);
}
$groupOrderUpdater = app(UpdatesGroupOrders::class);
$groupOrderUpdater->update($record, ['route_id' => $state]);
}
return $record->route_id;
}),
TextInputColumn::make('route_id')
->rules(['numeric', 'integer', 'min:1'])
->extraAttributes(['style' => 'min-width:60px'])
->updateStateUsing(function ($state, $record) {
if ($state > 1 || $state !== null) {
$route = Route::find($state);

if (!$route) {
// Trigger the createRoute action if the route does not exist
return Action::make('createRoute')
->form([
TextInput::make('name')->required()->maxLength(255),
ColorPicker::make('color')->required(),
])
->action(function (array $data) use ($state) {
$data['id'] = $state;
Route::create($data);
})
->modalHeading('Create Route')
->modalSubmitActionLabel('Create')
->modalWidth('lg')
->visible(fn () => true);
}
$groupOrderUpdater = app(UpdatesGroupOrders::class);
$groupOrderUpdater->update($record, ['route_id' => $state]);
}
return $record->route_id;
}),
But the action modal does not get triggered at all, what am I doing wrong?
41 replies
FFilament
Created by nowak on 6/3/2024 in #❓┊help
Developing on Filament and Filament Plugin locally causes duplicate classes of same namespaces
Hi, I am running a local fork of Filament in my app, and point to this in my composer.json file. Then I am also running a local fork of the Filament Google Maps plugin, which introduces it's own Filament instance it seems, which breaks my overrides of methods in classes from my initial Filament package fork. I am including these in my composer.json like this:
"require": {
"filament/filament": "*",
"cheesegrits/filament-google-maps": "*",
},
"repositories": [
{
"type": "path",
"url": "filament/packages/*"
},
{
"type": "path",
"url": "./filament-google-maps"
}
],
"require": {
"filament/filament": "*",
"cheesegrits/filament-google-maps": "*",
},
"repositories": [
{
"type": "path",
"url": "filament/packages/*"
},
{
"type": "path",
"url": "./filament-google-maps"
}
],
And I am overriding the Filament\Tables\Concerns\CanReorderRecords::reorderTable method which I have customized in my Filament package fork like this:
public function reorderTable(array $order, $draggedRecordKey = null): void
{
parent::reorderTable($order, $draggedRecordKey);
$reorderAction = app(ReordersGroupOrders::class);
$reorderAction->reorder($order, $draggedRecordKey);
}
public function reorderTable(array $order, $draggedRecordKey = null): void
{
parent::reorderTable($order, $draggedRecordKey);
$reorderAction = app(ReordersGroupOrders::class);
$reorderAction->reorder($order, $draggedRecordKey);
}
Where when I cmd + hover over parent::reorderTable, I see the following definitions:
Filament\Tables\Concerns\CanReorderRecords::reorderTable

<?php
public function reorderTable(array $order): void { }
@param array<int|string> $order

@return void

Filament\Tables\Concerns\CanReorderRecords::reorderTable

<?php
public function reorderTable(array $order, null|int|string $draggedRecordKey = null): void { }
@param array<int|string> $order

@param null|int|string $draggedRecordKey

@return void

function ListRecords::reorderTable(
array<int|string> $order
): void
Filament\Tables\Concerns\CanReorderRecords::reorderTable

<?php
public function reorderTable(array $order): void { }
@param array<int|string> $order

@return void

Filament\Tables\Concerns\CanReorderRecords::reorderTable

<?php
public function reorderTable(array $order, null|int|string $draggedRecordKey = null): void { }
@param array<int|string> $order

@param null|int|string $draggedRecordKey

@return void

function ListRecords::reorderTable(
array<int|string> $order
): void
And when I cmd + click parent::reorderTable, I navigated to:
filament-google-maps/vendor/filament/tables/src/Concerns/CanReorderRecords.php
filament-google-maps/vendor/filament/tables/src/Concerns/CanReorderRecords.php
Instead of to:
filament/packages/tables/src/Concerns/CanReorderRecords.php
filament/packages/tables/src/Concerns/CanReorderRecords.php
How can I make sure that I override the reorderTable method in the correct package?
4 replies
FFilament
Created by nowak on 6/2/2024 in #❓┊help
How to treat Filament resource logic in relation to the rest of my laravel app architecture?
I am not sure how to ask this, but I hope the essense of my question makes sense. I am wondering how I should interpret Filament resource files compared with the rest of the laravel architecture. For example if I have a textinput column in a resource table, changing this would trigger a eloquent model update. Then I could technically use the model observer to add logic when the model is updated. But I prefer to use actions, as I usually send requests to a controller, which then triggers an action, which uses methods from a Service class that contains the business logic, and dispatch an event from the action, etc.. So for example if I have an action button in a filament resource table, that updates an attribute of a record, I could also trigger methods from a Service or trigger an app/Action/UpdateXyz class. So should I treat whatever happens in Filament as a controller, or where should I place whatever happens in filament in relation to the rest of my laravel app?
4 replies
FFilament
Created by nowak on 5/29/2024 in #❓┊help
Hide TextInput form field but keep spacing it's spacing
Hi, I am looking for a way to conditionally hide a TextInput field, without messing up the height of the schema, is this possible? This is my TextInput field:
TextInput::make('other_reason_text')
->hidden(fn (Get $get) => $get('reason') !== 'Other')
->nullable()
->maxLength(255),
TextInput::make('other_reason_text')
->hidden(fn (Get $get) => $get('reason') !== 'Other')
->nullable()
->maxLength(255),
Which is used in a Repeater item schema like this:
Repeater::make('complaintDetails')
->label('Order Items')
->relationship('complaintDetails')
->schema([
Select::make('reason')
->options([
'Missing from delivery' => 'Missing from delivery',
'Damaged product' => 'Damaged product',
'Wrong item' => 'Wrong item',
'Other' => 'Other',
])
->required()
->placeholder('Select a Reason')
->disableOptionsWhenSelectedInSiblingRepeaterItems()
->native(false),
TextInput::make('other_reason_text')
->hidden(fn (Get $get) => $get('reason') !== 'Other')
->nullable()
->maxLength(255),
Select::make('user_order_item_ids')
->multiple()
->relationship('userOrderItems', 'product_name', function (Builder $query, Get $get) {
$userOrderId = $get('../../user_order_id');
if ($userOrderId) {
$query->where('user_order_id', $userOrderId);
} else {
$query->whereRaw('1 = 0');
}
})
->placeholder('Select an Order Item')
->searchable()
->preload(),
])
->grid(3)
->columnSpanFull(),
Repeater::make('complaintDetails')
->label('Order Items')
->relationship('complaintDetails')
->schema([
Select::make('reason')
->options([
'Missing from delivery' => 'Missing from delivery',
'Damaged product' => 'Damaged product',
'Wrong item' => 'Wrong item',
'Other' => 'Other',
])
->required()
->placeholder('Select a Reason')
->disableOptionsWhenSelectedInSiblingRepeaterItems()
->native(false),
TextInput::make('other_reason_text')
->hidden(fn (Get $get) => $get('reason') !== 'Other')
->nullable()
->maxLength(255),
Select::make('user_order_item_ids')
->multiple()
->relationship('userOrderItems', 'product_name', function (Builder $query, Get $get) {
$userOrderId = $get('../../user_order_id');
if ($userOrderId) {
$query->where('user_order_id', $userOrderId);
} else {
$query->whereRaw('1 = 0');
}
})
->placeholder('Select an Order Item')
->searchable()
->preload(),
])
->grid(3)
->columnSpanFull(),
6 replies
FFilament
Created by nowak on 5/27/2024 in #❓┊help
How to add a link to resource entry in a database notification?
I have a Controller in my laravel app that gets triggered when a create record form is submitted from a Vue frontend, and I am wondering how to add an action to the notification that navigates to the created record. I tried this:
return DB::transaction(function () use ($input) {
$complaint = new UserOrderComplaint([
'user_order_id' => $input['user_order_id'],
'reason' => $input['reason'],
'status' => $input['status'],
]);

$complaint->save();

// Notify all users with the super_admin role
$superAdmins = User::role('super_admin')->get();

foreach ($superAdmins as $superAdmin) {
Notification::make()
->title('New User Order Complaint')
->body('A new complaint has been submitted.')
->actions([
Action::make('view')
->button()
->url(fn ($complaint): string => UserOrderComplaintResource::getUrl('edit', ['complaint' => $complaint->id])),
])
->sendToDatabase($superAdmin);
event(new DatabaseNotificationsSent($superAdmin));
}

return $complaint;
});
return DB::transaction(function () use ($input) {
$complaint = new UserOrderComplaint([
'user_order_id' => $input['user_order_id'],
'reason' => $input['reason'],
'status' => $input['status'],
]);

$complaint->save();

// Notify all users with the super_admin role
$superAdmins = User::role('super_admin')->get();

foreach ($superAdmins as $superAdmin) {
Notification::make()
->title('New User Order Complaint')
->body('A new complaint has been submitted.')
->actions([
Action::make('view')
->button()
->url(fn ($complaint): string => UserOrderComplaintResource::getUrl('edit', ['complaint' => $complaint->id])),
])
->sendToDatabase($superAdmin);
event(new DatabaseNotificationsSent($superAdmin));
}

return $complaint;
});
Where I get this error:
Error creating complaint: An attempt was made to evaluate a closure for [Filament\Notifications\Actions\Action], but [$complaint] was unresolvable.
Error creating complaint: An attempt was made to evaluate a closure for [Filament\Notifications\Actions\Action], but [$complaint] was unresolvable.
Any help is much appreciated!
4 replies
FFilament
Created by nowak on 4/25/2024 in #❓┊help
How to fully disable export action modal?
No description
12 replies
FFilament
Created by nowak on 4/23/2024 in #❓┊help
How to get all table records in header action
I want to add a header action for my admin panel resource table, where I need to send the records to a blade view to create a pdf export. What is the best way to get a Collection of the current tables records in a header action? I tried to do this:
->headerActions([
Action::make('downloadPdf')
->label('Download Pdf')
->icon('heroicon-o-arrow-down-tray')
->action(function (Collection $records, Table $table, Component $livewire) {
\Log::info('Pdf Export', ['Collection' => $records, 'Table Query' => $table->getQuery(), 'Component' => $livewire]);
}),
])
->headerActions([
Action::make('downloadPdf')
->label('Download Pdf')
->icon('heroicon-o-arrow-down-tray')
->action(function (Collection $records, Table $table, Component $livewire) {
\Log::info('Pdf Export', ['Collection' => $records, 'Table Query' => $table->getQuery(), 'Component' => $livewire]);
}),
])
But the looks don't show anything useful:
{"Collection":{"Illuminate\\Support\\Collection":[]},"Table Query":{"Illuminate\\Database\\Eloquent\\Builder":[]},"Component":{"App\\Filament\\Resources\\GroupOrderManagementResource\\Pages\\ListGroupOrderManagement":{"mountedActions":[],"mountedActionsArguments":[],"mountedActionsData":[],"defaultAction":null,"defaultActionArguments":null,"componentFileAttachments":[],"mountedFormComponentActions":[],"mountedFormComponentActionsArguments":[],"mountedFormComponentActionsData":[],"mountedFormComponentActionsComponents":[],"mountedInfolistActions":[],"mountedInfolistActionsData":[],"mountedInfolistActionsComponent":null,"mountedInfolistActionsInfolist":null,"isTableReordering":false,"tableFilters":{"status":{"show_declined":false}},"tableGrouping":null,"tableGroupingDirection":null,"tableSearch":"","tableSortColumn":null,"tableSortDirection":null,"activeTab":"dinner","isTableLoaded":false,"tableRecordsPerPage":10,"tableColumnSearches":[],"toggledTableColumns":[],"mountedTableActions":["downloadPdf"],"mountedTableActionsData":[[]],"mountedTableActionsArguments":[[],[]],"mountedTableActionRecord":null,"defaultTableAction":[],"defaultTableActionArguments":[],"defaultTableActionRecord":[],"selectedTableRecords":[],"mountedTableBulkAction":null,"mountedTableBulkActionData":[],"tableDeferredFilters":null,"paginators":{"page":1}}}}
{"Collection":{"Illuminate\\Support\\Collection":[]},"Table Query":{"Illuminate\\Database\\Eloquent\\Builder":[]},"Component":{"App\\Filament\\Resources\\GroupOrderManagementResource\\Pages\\ListGroupOrderManagement":{"mountedActions":[],"mountedActionsArguments":[],"mountedActionsData":[],"defaultAction":null,"defaultActionArguments":null,"componentFileAttachments":[],"mountedFormComponentActions":[],"mountedFormComponentActionsArguments":[],"mountedFormComponentActionsData":[],"mountedFormComponentActionsComponents":[],"mountedInfolistActions":[],"mountedInfolistActionsData":[],"mountedInfolistActionsComponent":null,"mountedInfolistActionsInfolist":null,"isTableReordering":false,"tableFilters":{"status":{"show_declined":false}},"tableGrouping":null,"tableGroupingDirection":null,"tableSearch":"","tableSortColumn":null,"tableSortDirection":null,"activeTab":"dinner","isTableLoaded":false,"tableRecordsPerPage":10,"tableColumnSearches":[],"toggledTableColumns":[],"mountedTableActions":["downloadPdf"],"mountedTableActionsData":[[]],"mountedTableActionsArguments":[[],[]],"mountedTableActionRecord":null,"defaultTableAction":[],"defaultTableActionArguments":[],"defaultTableActionRecord":[],"selectedTableRecords":[],"mountedTableBulkAction":null,"mountedTableBulkActionData":[],"tableDeferredFilters":null,"paginators":{"page":1}}}}
5 replies
FFilament
Created by nowak on 4/23/2024 in #❓┊help
Add summaries to exports
I tried to add ->summarize([]) to an exporter, but Filament\Actions\Exports\ExportColumn::summarize does not exist. Are there any workarounds for doing this? I need to export a table and keep the summary information in the export.
2 replies
FFilament
Created by nowak on 4/22/2024 in #❓┊help
Summing TextColumn on nested relationship
I have a GroupOrderResource that has many UserOrder, with the relationship userOrders, then my UserOrder model has this relationship with ProductSku:
public function productSkus()
{
return $this->belongsToMany(ProductSku::class, 'product_sku_user_order')
->withPivot('quantity', 'price', 'product_name', 'product_variation', 'servings')
->withTrashed()
->withTimestamps()
->as('userOrderItem');
}
public function productSkus()
{
return $this->belongsToMany(ProductSku::class, 'product_sku_user_order')
->withPivot('quantity', 'price', 'product_name', 'product_variation', 'servings')
->withTrashed()
->withTimestamps()
->as('userOrderItem');
}
Then I want my TextColumn to show all ProductSku quantities for each Group Order, and my attempt was to do this:
TextColumn::make('userOrders.productSkus_sum_quantity')
->sum('userOrders.productSkus', 'quantity'),
TextColumn::make('userOrders.productSkus_sum_quantity')
->sum('userOrders.productSkus', 'quantity'),
But I get this error:
Call to undefined method App\Models\GroupOrder::userOrders.productSkus()
Call to undefined method App\Models\GroupOrder::userOrders.productSkus()
Is this an issue with attempting to sum a number from a nested relationship, or am I doing something else wrong?
6 replies
FFilament
Created by nowak on 4/19/2024 in #❓┊help
Searchable select field shows incorrect search results
I added a select field to my form like this:
Select::make('product_id')
->label('Product')
->relationship('productSku.product', 'name', function (Builder $query, Get $get) {
$mealTypeId = $get('../../meal_type_id');
$query->byMealType($mealTypeId)->orderBy('id');
})
->afterStateUpdated(fn (Select $component) => $component
->getContainer()
->getComponent('dynamicSkuFields')
->getChildComponentContainer()
->fill())
->afterStateHydrated(function (Set $set, Get $get, $record) {
if ($record) {
$productSku = ProductSku::find($get('product_sku_id'));
if ($productSku) {
$set('product_id', $productSku->product_id);
}
}
})
->preload()
->required()
->dehydrated(false)
->live()
->columnSpan([
'md' => 5,
])
->searchable()
->hidden(function (Get $get, ?UserOrderItem $record) {
$status_id = $get('../../status_id');
return $record !== null && $status_id !== 1;
}),
Select::make('product_id')
->label('Product')
->relationship('productSku.product', 'name', function (Builder $query, Get $get) {
$mealTypeId = $get('../../meal_type_id');
$query->byMealType($mealTypeId)->orderBy('id');
})
->afterStateUpdated(fn (Select $component) => $component
->getContainer()
->getComponent('dynamicSkuFields')
->getChildComponentContainer()
->fill())
->afterStateHydrated(function (Set $set, Get $get, $record) {
if ($record) {
$productSku = ProductSku::find($get('product_sku_id'));
if ($productSku) {
$set('product_id', $productSku->product_id);
}
}
})
->preload()
->required()
->dehydrated(false)
->live()
->columnSpan([
'md' => 5,
])
->searchable()
->hidden(function (Get $get, ?UserOrderItem $record) {
$status_id = $get('../../status_id');
return $record !== null && $status_id !== 1;
}),
When I then search for a string, results are shown for records that do not contain the string added to the input field, which is not what I would expect to happen. Is this happening due to the complexitty of my Select field? Or is this standard searchable select field behaviour?
2 replies
FFilament
Created by nowak on 4/5/2024 in #❓┊help
How to customise completed export notification/events?
I want to change the way I receive the completed export notification, or add to it. I want to either broadcast the notification in real time, or send the completed export notification to the UI as well as to the database. I know I can control this fully in the ExportCompletion.php jobs handle method:
class ExportCompletion implements ShouldQueue
{
// Truncated

public function handle(): void
{
$this->export->touch('completed_at');

if (! $this->export->user instanceof Authenticatable) {
return;
}

$failedRowsCount = $this->export->getFailedRowsCount();

Notification::make()
->title(__('filament-actions::export.notifications.completed.title'))
->body($this->exporter::getCompletedNotificationBody($this->export))
->when(
! $failedRowsCount,
fn (Notification $notification) => $notification->success(),
)
->when(
$failedRowsCount && ($failedRowsCount < $this->export->total_rows),
fn (Notification $notification) => $notification->warning(),
)
->when(
$failedRowsCount === $this->export->total_rows,
fn (Notification $notification) => $notification->danger(),
)
->when(
$failedRowsCount < $this->export->total_rows,
fn (Notification $notification) => $notification->actions(array_map(
fn (ExportFormat $format): NotificationAction => $format->getDownloadNotificationAction($this->export),
$this->formats,
)),
)
->sendToDatabase($this->export->user);
}
}
class ExportCompletion implements ShouldQueue
{
// Truncated

public function handle(): void
{
$this->export->touch('completed_at');

if (! $this->export->user instanceof Authenticatable) {
return;
}

$failedRowsCount = $this->export->getFailedRowsCount();

Notification::make()
->title(__('filament-actions::export.notifications.completed.title'))
->body($this->exporter::getCompletedNotificationBody($this->export))
->when(
! $failedRowsCount,
fn (Notification $notification) => $notification->success(),
)
->when(
$failedRowsCount && ($failedRowsCount < $this->export->total_rows),
fn (Notification $notification) => $notification->warning(),
)
->when(
$failedRowsCount === $this->export->total_rows,
fn (Notification $notification) => $notification->danger(),
)
->when(
$failedRowsCount < $this->export->total_rows,
fn (Notification $notification) => $notification->actions(array_map(
fn (ExportFormat $format): NotificationAction => $format->getDownloadNotificationAction($this->export),
$this->formats,
)),
)
->sendToDatabase($this->export->user);
}
}
I hear that overriding files like filament/packages/actions/src/Exports/Jobs/ExportCompletion.php is not advised. So what is the best way to customise what happens when an export job completes? Any help is much appreciated!
11 replies
FFilament
Created by nowak on 4/3/2024 in #❓┊help
Why is this bad? Sending export completion notification to the UI as well as to the database.
I played around with the built in filament/packages/actions/src/Exports/Jobs/ExportCompletion.php handle() Notification, where adding ->send() to this shows the notification directly in the UI instead of only being hidden in the notifications tray:
public function handle(): void
{
$this->export->touch('completed_at');

if (! $this->export->user instanceof Authenticatable) {
return;
}

$failedRowsCount = $this->export->getFailedRowsCount();

Notification::make()
->title(__('filament-actions::export.notifications.completed.title'))
->body($this->exporter::getCompletedNotificationBody($this->export))
->when(
! $failedRowsCount,
fn (Notification $notification) => $notification->success(),
)
->when(
$failedRowsCount && ($failedRowsCount < $this->export->total_rows),
fn (Notification $notification) => $notification->warning(),
)
->when(
$failedRowsCount === $this->export->total_rows,
fn (Notification $notification) => $notification->danger(),
)
->when(
$failedRowsCount < $this->export->total_rows,
fn (Notification $notification) => $notification->actions(array_map(
fn (ExportFormat $format): NotificationAction => $format->getDownloadNotificationAction($this->export),
$this->formats,
)),
)
+ ->send()
->sendToDatabase($this->export->user);
}
public function handle(): void
{
$this->export->touch('completed_at');

if (! $this->export->user instanceof Authenticatable) {
return;
}

$failedRowsCount = $this->export->getFailedRowsCount();

Notification::make()
->title(__('filament-actions::export.notifications.completed.title'))
->body($this->exporter::getCompletedNotificationBody($this->export))
->when(
! $failedRowsCount,
fn (Notification $notification) => $notification->success(),
)
->when(
$failedRowsCount && ($failedRowsCount < $this->export->total_rows),
fn (Notification $notification) => $notification->warning(),
)
->when(
$failedRowsCount === $this->export->total_rows,
fn (Notification $notification) => $notification->danger(),
)
->when(
$failedRowsCount < $this->export->total_rows,
fn (Notification $notification) => $notification->actions(array_map(
fn (ExportFormat $format): NotificationAction => $format->getDownloadNotificationAction($this->export),
$this->formats,
)),
)
+ ->send()
->sendToDatabase($this->export->user);
}
I feel like this would make the user experience more intuitive, and reduces the amount of clicks needed by the user to download a file, and I am wondering why it wasn't implemented like this originally?
6 replies
FFilament
Created by nowak on 4/3/2024 in #❓┊help
Understanding the DatabaseNotificationsSent event. Not seeing websocket notifications in admin panel
I have Echo and Soketi running for real time notifications, where I want to dispatch filaments built in DatabaseNotificationsSent event. Since I want to use this with export actions, I quickly added the DatabaseNotificationsSent event to the bottom of the /actions/src/Exports/Jobs/ExportCompletion.php handler to test:
event(new DatabaseNotificationsSent($this->export->user));
event(new DatabaseNotificationsSent($this->export->user));
The event seems to be dispatched correctly, as I see this in my soketi docker logs:
meinRad_soketi_server | [Wed Apr 03 2024 12:11:54 GMT+0000 (Coordinated Universal Time)] :zap: HTTP Payload received
meinRad_soketi_server | {
meinRad_soketi_server | name: 'database-notifications.sent',
meinRad_soketi_server | data: '{"user":{"id":2,"name":"k","email":"mail@mail.com","email_verified_at":"2024-04-03T11:36:01.000000Z","created_at":"2024-04-02T12:07:57.000000Z","updated_at":"2024-04-02T12:07:57.000000Z","last_name":"n","phone_number":"0792345678","address":"Kapellplatz 2","latitude":47.0501,"longitude":8.3103,"road":"Kapellplatz","house_number":"2","postal_code":"6004","city":"Lucerne","country":"Switzerland","current_group_id":2}}',
meinRad_soketi_server | channel: 'private-App.Models.User.2'
meinRad_soketi_server | }
meinRad_soketi_server | [Wed Apr 03 2024 12:11:54 GMT+0000 (Coordinated Universal Time)] :zap: HTTP Payload received
meinRad_soketi_server | {
meinRad_soketi_server | name: 'database-notifications.sent',
meinRad_soketi_server | data: '{"user":{"id":2,"name":"k","email":"mail@mail.com","email_verified_at":"2024-04-03T11:36:01.000000Z","created_at":"2024-04-02T12:07:57.000000Z","updated_at":"2024-04-02T12:07:57.000000Z","last_name":"n","phone_number":"0792345678","address":"Kapellplatz 2","latitude":47.0501,"longitude":8.3103,"road":"Kapellplatz","house_number":"2","postal_code":"6004","city":"Lucerne","country":"Switzerland","current_group_id":2}}',
meinRad_soketi_server | channel: 'private-App.Models.User.2'
meinRad_soketi_server | }
But I do not see any websocket notification in the filament panel after this is triggered. Do I need to add any other configurations or setup something else, other than what is needed for database notifications? I see the "Export Completed" database notifications in the notification tray, so that works. Maybe I misunderstood something, should DatabaseNotificationsSent not result in a notification in the UI?
2 replies
FFilament
Created by nowak on 4/2/2024 in #❓┊help
How to download Export instead of storing on server?
I have created an export header action on my table like this:
->headerActions([
ExportAction::make()
->exporter(GroupOrderExporter::class),
])
->headerActions([
ExportAction::make()
->exporter(GroupOrderExporter::class),
])
This downloads the export straight to public/storage/filament_export/#/files. Is it possible that this would be downloaded to the users device as well, or instead? I don't see anything about this being documented in the Export action documentation: https://filamentphp.com/docs/3.x/actions/prebuilt-actions/export
7 replies
FFilament
Created by nowak on 3/30/2024 in #❓┊help
Can someone explain to my why ->dehydrated() works for storing data on disabled fields?
I have been using the ->saveRelationshipsWhenDisabled() method in my Select relationship fields, and it has been a life saver! Now I wanted to mimic this behaviour on a standard TextInput form field, where I found out that adding ->dehydrated() after ->disabled() does exactly that. But after reading the documentation on Field dehydration (https://filamentphp.com/docs/3.x/forms/advanced#field-dehydration) a dozen times, my brain still can't comprehend this concept. Any help understanding this is highly appreciated!
7 replies
FFilament
Created by nowak on 3/26/2024 in #❓┊help
Filter modal layout footer actions not centered
No description
6 replies
FFilament
Created by nowak on 3/26/2024 in #❓┊help
How to poll the panel filter tabs when getDefaultActiveTab is dynamic and time based?
In my resources List page I have added my public function getTabs(): array {} method, and this getDefaultActiveTab() method:
public function getDefaultActiveTab(): string | int | null
{
$activeMealType = MealType::getActiveMealType();
return strtolower($activeMealType->name);
}
public function getDefaultActiveTab(): string | int | null
{
$activeMealType = MealType::getActiveMealType();
return strtolower($activeMealType->name);
}
Which changes based on time, but the List page does not refresh or poll the active tab by default when the $activeMealType changes. Is it possibly to add poll the default active tab when using filter tabs, to make sure it live updates?
5 replies