Files
fotospiel-app/app/Filament/Tenant/Resources/TaskCollectionResource.php
Codex Agent 1a4bdb1fe1 tenant admin startseite schicker gestaltet und super-admin und tenant admin (filament) aufgesplittet.
Es gibt nun task collections und vordefinierte tasks für alle. Onboarding verfeinert und webseite-carousel gefixt (logging später entfernen!)
2025-10-14 15:17:52 +02:00

243 lines
9.6 KiB
PHP

<?php
namespace App\Filament\Tenant\Resources;
use App\Filament\Tenant\Resources\TaskCollectionResource\Pages;
use App\Models\Event;
use App\Models\EventType;
use App\Models\TaskCollection;
use App\Services\Tenant\TaskCollectionImportService;
use Filament\Facades\Filament;
use Filament\Schemas\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Schemas\Schema;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Actions;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Filament\Notifications\Notification;
use Illuminate\Support\Str;
use BackedEnum;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use App\Support\TenantOnboardingState;
class TaskCollectionResource extends Resource
{
protected static ?string $model = TaskCollection::class;
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-folder';
protected static ?int $navigationSort = 50;
public static function shouldRegisterNavigation(): bool
{
return TenantOnboardingState::completed();
}
public static function getNavigationGroup(): string
{
return __('admin.nav.library');
}
public static function form(Schema $schema): Schema
{
$tenantId = auth()->user()?->tenant_id;
return $schema->components([
Section::make(__('Task Collection Details'))
->schema([
TextInput::make('name_translations.de')
->label(__('Name (DE)'))
->required()
->maxLength(255)
->disabled(fn (?TaskCollection $record) => $record?->tenant_id !== $tenantId && $record !== null),
TextInput::make('name_translations.en')
->label(__('Name (EN)'))
->maxLength(255)
->disabled(fn (?TaskCollection $record) => $record?->tenant_id !== $tenantId && $record !== null),
Select::make('event_type_id')
->label(__('Event Type'))
->options(fn () => EventType::orderBy('name->' . app()->getLocale())
->get()
->mapWithKeys(function (EventType $type) {
$name = $type->name[app()->getLocale()] ?? $type->name['de'] ?? reset($type->name);
return [$type->id => $name];
})->toArray())
->searchable()
->required()
->disabled(fn (?TaskCollection $record) => $record?->tenant_id !== $tenantId && $record !== null),
Textarea::make('description_translations.de')
->label(__('Description (DE)'))
->rows(3)
->disabled(fn (?TaskCollection $record) => $record?->tenant_id !== $tenantId && $record !== null),
Textarea::make('description_translations.en')
->label(__('Description (EN)'))
->rows(3)
->disabled(fn (?TaskCollection $record) => $record?->tenant_id !== $tenantId && $record !== null),
])->columns(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->label(__('Name'))
->searchable(['name_translations->de', 'name_translations->en'])
->sortable(),
BadgeColumn::make('eventType.name')
->label(__('Event Type'))
->color('info'),
IconColumn::make('tenant_id')
->label(__('Scope'))
->boolean()
->trueIcon('heroicon-o-user-group')
->falseIcon('heroicon-o-globe-alt')
->state(fn (TaskCollection $record) => $record->tenant_id !== null)
->tooltip(fn (TaskCollection $record) => $record->tenant_id ? __('Tenant-only') : __('Global template')),
TextColumn::make('tasks_count')
->label(__('Tasks'))
->counts('tasks')
->sortable(),
])
->filters([
SelectFilter::make('event_type_id')
->label(__('Event Type'))
->relationship('eventType', 'name->' . app()->getLocale()),
SelectFilter::make('scope')
->options([
'global' => __('Global template'),
'tenant' => __('Tenant-owned'),
])
->query(function ($query, $value) {
$tenantId = auth()->user()?->tenant_id;
if ($value === 'global') {
$query->whereNull('tenant_id');
}
if ($value === 'tenant') {
$query->where('tenant_id', $tenantId);
}
}),
])
->actions([
\Filament\Actions\Action::make('import')
->label(__('Import to Event'))
->icon('heroicon-o-cloud-arrow-down')
->form([
Select::make('event_slug')
->label(__('Select Event'))
->options(function () {
$tenantId = auth()->user()?->tenant_id;
return Event::where('tenant_id', $tenantId)
->orderBy('date', 'desc')
->get()
->mapWithKeys(function (Event $event) {
$name = $event->name[app()->getLocale()] ?? $event->name['de'] ?? reset($event->name);
return [
$event->slug => sprintf('%s (%s)', $name, $event->date?->format('d.m.Y')),
];
})->toArray();
})
->required()
->searchable(),
])
->action(function (TaskCollection $record, array $data) {
$event = Event::where('slug', $data['event_slug'])
->where('tenant_id', auth()->user()?->tenant_id)
->firstOrFail();
/** @var TaskCollectionImportService $service */
$service = app(TaskCollectionImportService::class);
$service->import($record, $event);
Notification::make()
->title(__('Task collection imported'))
->body(__('The collection :name has been imported.', ['name' => $record->name]))
->success()
->send();
}),
Actions\EditAction::make()
->label(__('Edit'))
->visible(fn (TaskCollection $record) => $record->tenant_id === auth()->user()?->tenant_id),
])
->headerActions([
Actions\CreateAction::make()
->label(__('Create Task Collection'))
->mutateFormDataUsing(function (array $data) {
$tenantId = auth()->user()?->tenant_id;
$data['tenant_id'] = $tenantId;
$data['slug'] = static::generateSlug($data['name_translations']['en'] ?? $data['name_translations']['de'] ?? 'collection', $tenantId);
return $data;
}),
])
->bulkActions([
Actions\DeleteBulkAction::make()
->visible(fn () => false),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListTaskCollections::route('/'),
'create' => Pages\CreateTaskCollection::route('/create'),
'edit' => Pages\EditTaskCollection::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
$tenantId = auth()->user()?->tenant_id;
return parent::getEloquentQuery()
->forTenant($tenantId)
->with('eventType')
->withCount('tasks');
}
public static function getGloballySearchableAttributes(): array
{
return ['name_translations->de', 'name_translations->en'];
}
public static function generateSlug(string $base, int $tenantId): string
{
$slugBase = Str::slug($base) ?: 'collection';
do {
$candidate = $slugBase . '-' . $tenantId . '-' . Str::random(4);
} while (TaskCollection::where('slug', $candidate)->exists());
return $candidate;
}
public static function scopeEloquentQueryToTenant(Builder $query, ?Model $tenant): Builder
{
$tenant ??= Filament::getTenant();
if (! $tenant) {
return $query;
}
return $query->where(function (Builder $innerQuery) use ($tenant) {
$innerQuery->whereNull('tenant_id')
->orWhere('tenant_id', $tenant->getKey());
});
}
}