Laurens
Laurens
FFilament
Created by Laurens on 6/17/2024 in #❓┊help
Custom widget property being set after widget reload in action
I'm using the following custom action to navigate between between weeks of upcoming birthdays users. When the action runs, we increment the start date property. However, the property seems to lag a week behind the actual value. I believe this is happening due to the reload happening before the property is being set? How can I prevent this?
class BirthdayMembersWidget extends BaseWidget
{
public Carbon $startDate;

public function mount(): void
{
$this->startDate = today();
}

protected function getTableHeaderActions(): array
{
return [
Tables\Actions\Action::make('Next')
->label(new HtmlString('»'))
->action(function () {
$this->startDate = $this->startDate->addWeek();
}),
];
}

protected function getTableQuery(): Builder
{
$startDate = $this->getStartDate();
$endDate = $this->getEndDate();

return User::query()
->select(['id', 'full_name', 'birthday'])
->whereBirthday($startDate, $endDate)
->orderByBirthday();
}
class BirthdayMembersWidget extends BaseWidget
{
public Carbon $startDate;

public function mount(): void
{
$this->startDate = today();
}

protected function getTableHeaderActions(): array
{
return [
Tables\Actions\Action::make('Next')
->label(new HtmlString('»'))
->action(function () {
$this->startDate = $this->startDate->addWeek();
}),
];
}

protected function getTableQuery(): Builder
{
$startDate = $this->getStartDate();
$endDate = $this->getEndDate();

return User::query()
->select(['id', 'full_name', 'birthday'])
->whereBirthday($startDate, $endDate)
->orderByBirthday();
}
Thanks in advance!
3 replies
FFilament
Created by Laurens on 4/10/2024 in #❓┊help
Sidebar navigation order is different in French
I've setup our panel navigation order with the following helper in our PanelProvider:
->navigationGroups([
trans('admin.navigation_groups.user_management'),
trans('admin.navigation_groups.data'),
trans('admin.navigation_groups.content'),
trans('admin.navigation_groups.settings'),
])
->navigationGroups([
trans('admin.navigation_groups.user_management'),
trans('admin.navigation_groups.data'),
trans('admin.navigation_groups.content'),
trans('admin.navigation_groups.settings'),
])
This order seems to be respected while our app is in English. However, when we switch to French the navigation items fall back to alphabetical ordering. Is there any way to solve this? Thanks in advance!
5 replies
FFilament
Created by Laurens on 3/5/2024 in #❓┊help
Disable resource form while editing but enable relation managers?
Is this possible without manually disabling each field? What would be the best approach for this?
6 replies
FFilament
Created by Laurens on 3/1/2024 in #❓┊help
Overwrite table widget label
This is probably something super obvious I'm missing... but how do you change the label of a Filament table widget? 🤔 I've looked a bit around in the docs and properties but couldn't see a direct change. Since we usually suffix our class names (ex. TableWidget for Filament table widgets) I'd like to remove the table part. We'll eventually also need to translate the widget title. Thanks in advance!
5 replies
FFilament
Created by Laurens on 12/4/2023 in #❓┊help
Cannot use accessor for record title in global search
According to the docs (https://filamentphp.com/docs/3.x/panels/resources/getting-started#record-titles) I should be able to use accessors as the record title in global search. When I try this however I still get exceptions that the given title does not exist on the MySql table. This is the code I'm using currently. Using fullName has the same result.
// App\Filament\Resources\UserResource.php

protected static ?string $recordTitleAttribute = 'full_name';
// App\Filament\Resources\UserResource.php

protected static ?string $recordTitleAttribute = 'full_name';
// App\Models\User.php

public function fullName(): Attribute
{
return new Attribute(fn () => trim("$this->first_name $this->last_name"));
}
// App\Models\User.php

public function fullName(): Attribute
{
return new Attribute(fn () => trim("$this->first_name $this->last_name"));
}
What could be going on here? This is happening on Filament v3.1.1 and still after bumping to the .10 release. Thanks in advance!
5 replies
FFilament
Created by Laurens on 11/20/2023 in #❓┊help
Keep create action modal open on success with feedback
Is it somehow possible to keep the modal of a create action open after the record has been created? I'd like to show the feedback with some data which shouldn't be available to the user when they close the modal after creating a record. Thanks in advance!
23 replies
FFilament
Created by Laurens on 10/3/2023 in #❓┊help
Translate HasOne relation (with HasMany nested)
I'm creating a resource containing a HasOne relation which has a nested HasMany relation through a simple repeater. For translations I'm using the first party Spatie translatable package and setup the concerns in the Resource, List and Create/Edit pages. The main resource correctly gets translated and saved per locale in the JSON columns on the database. However, my relation saves the current locale to all locales. This is the setup I'm using currently:
Forms\Components\Group::make()
->schema([
Forms\Components\TextInput::make('question')
->label(trans('general.polls.attributes.question'))
->required()
->maxLength(255),
// Answers
Forms\Components\Repeater::make('answers')
->label(trans('general.polls.attributes.answers'))
->relationship('answers')
->simple(
Forms\Components\TextInput::make('name')
->required()
->maxLength(255)
)
->required()
->minItems(2)
->maxItems(5)
])
->relationship('poll')
->columnSpanFull()
->hidden(fn(Get $get) => !$get('options.enable_poll'))
Forms\Components\Group::make()
->schema([
Forms\Components\TextInput::make('question')
->label(trans('general.polls.attributes.question'))
->required()
->maxLength(255),
// Answers
Forms\Components\Repeater::make('answers')
->label(trans('general.polls.attributes.answers'))
->relationship('answers')
->simple(
Forms\Components\TextInput::make('name')
->required()
->maxLength(255)
)
->required()
->minItems(2)
->maxItems(5)
])
->relationship('poll')
->columnSpanFull()
->hidden(fn(Get $get) => !$get('options.enable_poll'))
While the poll and its answers get correctly saved in the correct poll -> poll_answers table with all relations setup, they're always saved to all locales instead of the active locales. Both the main Model as the nested Poll and PollAnswer models have the HasTranslations trait setup. What am I doing wrong here? Thanks in advance!
2 replies
FFilament
Created by Laurens on 8/17/2023 in #❓┊help
Disable tenant ownership relation
In a Jetstream based app we're using the Teams setup. In our app however, not all models are strictly separated per team. Some models are always available for all teams, some are filtered if they are associated to at least one team, and others are always associated to teams. In Filament v2, we already implemented the teams by applying global scopes on models which should vary per team. Relation managers then handle the associations between teams where required, else the create / edit pages ensure (new) models are synced to the correct team. As we couldn't switch between the active team from Filament before (and only through the Jetstream front-end), we currently didn't have a team switcher. After upgrading to Filament v3 after all third party packages have been updated, I checked out the tenancy docs to see if we could hook in onto the new Multi-tenancy setup. Tenancy worked straight out of the box by implementing the interface and adding the methods from the docs:
public function getTenants(Panel $panel): \Illuminate\Support\Collection
{
return $this->teams;
}

public function canAccessTenant(\Illuminate\Database\Eloquent\Model $tenant): bool
{
return $this->teams->contains($tenant);
}
public function getTenants(Panel $panel): \Illuminate\Support\Collection
{
return $this->teams;
}

public function canAccessTenant(\Illuminate\Database\Eloquent\Model $tenant): bool
{
return $this->teams->contains($tenant);
}
However, on every resource I'm now getting the following error:
The model [App\Models\Category] does not have a relationship named [team]
The model [App\Models\Category] does not have a relationship named [team]
Is it possible to disable this relation behavior since we're using a morphToMany? If so, we could use the first party Tenancy setup to switch the current tenant/team in Filament, while keeping our own custom business logic in place.
20 replies
FFilament
Created by Laurens on 7/17/2023 in #❓┊help
Searching by case insensitive json columns in global search
Hello, A colleague of me is having an issue where he isn't able to use the global search to find records which have data stored in JSON columns when the value contains capital letters. For example, the search query Serving or serving has no results, while erving would return the desired record. I've tried to look into this by overwriting the global search query. Here I've managed to fetch the term being searched (not sure if there's a better way :D), but passing the $term value of putting hardcoded 'serving' doesn't result in the record being found either:
protected static function getGlobalSearchEloquentQuery(): Builder
{
$updates = collect(request()?->get('updates') ?: []);

$term = $updates
->filter(function (array $update) {
$type = data_get($update, 'type');
$payload = data_get($update, 'payload.name');

if ($type !== 'syncInput') {
return false;
}

return $payload === 'search';
})
->value('payload.value');

if (! $term) {
return parent::getGlobalSearchEloquentQuery();
}

$term = strtolower($term);

return parent::getGlobalSearchEloquentQuery()->orWhereRaw("LOWER(content) LIKE '%serving%'");
}
protected static function getGlobalSearchEloquentQuery(): Builder
{
$updates = collect(request()?->get('updates') ?: []);

$term = $updates
->filter(function (array $update) {
$type = data_get($update, 'type');
$payload = data_get($update, 'payload.name');

if ($type !== 'syncInput') {
return false;
}

return $payload === 'search';
})
->value('payload.value');

if (! $term) {
return parent::getGlobalSearchEloquentQuery();
}

$term = strtolower($term);

return parent::getGlobalSearchEloquentQuery()->orWhereRaw("LOWER(content) LIKE '%serving%'");
}
The string we're searching in the example contains - Serving assets from S3 inside the JSON column. What could be a solution here? Thanks in advance!
5 replies
FFilament
Created by Laurens on 3/28/2023 in #❓┊help
Excel upload validation in Resource Action
I'm looking to validate an Excel import (maatwebsite/excel:^3.1) inside a resource action. Currently I'm using a closure rule, where I'm collecting all errors from the Excel import:
Action::make('Import')
->color('secondary')
->action(function ($data) {
...
})
->form([
Forms\Components\FileUpload::make('excel')
->disk('local')
->directory('filament-import')
->rules([
function () {
return static function (string $attribute, $value, Closure $fail) {
try {
Excel::import(new UsersImport, $value);
} catch (ValidationException $e) {
$errors = [];

foreach ($e->failures() as $failure) {
foreach ($failure->errors() as $error) {
$errors[] = 'Row ' . $failure->row() . ': ' . $error;
}
}

$fail(implode(PHP_EOL, $errors));
}
};
}
])
])
Action::make('Import')
->color('secondary')
->action(function ($data) {
...
})
->form([
Forms\Components\FileUpload::make('excel')
->disk('local')
->directory('filament-import')
->rules([
function () {
return static function (string $attribute, $value, Closure $fail) {
try {
Excel::import(new UsersImport, $value);
} catch (ValidationException $e) {
$errors = [];

foreach ($e->failures() as $failure) {
foreach ($failure->errors() as $error) {
$errors[] = 'Row ' . $failure->row() . ': ' . $error;
}
}

$fail(implode(PHP_EOL, $errors));
}
};
}
])
])
The above works, but I cannot find a way to print every error on a line. Is there a way to do this? I've also tried validating in the action callback... but I can only find how to send notifications from there, and cannot seem to insert validation errors back to the form there. (when it works I'll add a parameter to the import construction to ignore the collection and only run the validation) Thanks in advance!
6 replies
FFilament
Created by Laurens on 3/13/2023 in #❓┊help
Translate builder field content inside repeater field
Hello, I’m trying to translate the content of a builder field. This seems to work if the builder is put directly in a resource; However, when the builder is part of a repeater field being saved to another relation, translations don’t seem to be handled anymore. The code below runs on Laravel v10.3.3 and Filament v2.17.15 in a fresh project using the first party Spatie translations plugin.
// PageResource.php

->schema([
Forms\Components\TextInput::make('name'),
Forms\Components\TextInput::make('slug'),
// Translated correctly
Forms\Components\Builder::make('content')
->blocks(static::getFlexibleContentBlocks()),
// Translated incorrectly on save - data is not wrapped inside the locales and are not loaded
Forms\Components\Repeater::make('sections')
->schema([
Forms\Components\Builder::make('content')
->blocks(static::getFlexibleContentBlocks())
])
->relationship('sections')
->columnSpanFull(),
]);
// PageResource.php

->schema([
Forms\Components\TextInput::make('name'),
Forms\Components\TextInput::make('slug'),
// Translated correctly
Forms\Components\Builder::make('content')
->blocks(static::getFlexibleContentBlocks()),
// Translated incorrectly on save - data is not wrapped inside the locales and are not loaded
Forms\Components\Repeater::make('sections')
->schema([
Forms\Components\Builder::make('content')
->blocks(static::getFlexibleContentBlocks())
])
->relationship('sections')
->columnSpanFull(),
]);
The static::getFlexibleContentBlocks() method simply returns the example builder blocks from the docs. The content field is in the fillable and translatable properties in both the Page and ModelSection models. Does anyone know a workaround for this issue? Thanks in advance!
4 replies