Es gibt nun task collections und vordefinierte tasks für alle. Onboarding verfeinert und webseite-carousel gefixt (logging später entfernen!)
243 lines
9.6 KiB
PHP
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());
|
|
});
|
|
}
|
|
}
|