components([ SC\Select::make('emotion_id')->relationship('emotion', 'name')->required()->searchable()->preload(), SC\Select::make('event_type_id')->relationship('eventType', 'name')->searchable()->preload()->label('Event Type (optional)'), SC\KeyValue::make('title')->label('Title (de/en)')->default(['de' => '', 'en' => ''])->required(), SC\KeyValue::make('description')->label('Description (de/en)'), SC\Select::make('difficulty')->options([ 'easy' => 'Easy', 'medium' => 'Medium', 'hard' => 'Hard', ])->default('easy'), SC\KeyValue::make('example_text')->label('Example (de/en)'), SC\TextInput::make('sort_order')->numeric()->default(0), SC\Toggle::make('is_active')->default(true), ])->columns(2); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('id')->sortable(), Tables\Columns\TextColumn::make('emotion.name')->label('Emotion')->sortable()->searchable(), Tables\Columns\TextColumn::make('eventType.name')->label('Event Type')->toggleable(), Tables\Columns\TextColumn::make('title')->searchable()->limit(40), Tables\Columns\TextColumn::make('difficulty')->badge(), Tables\Columns\IconColumn::make('is_active')->boolean(), Tables\Columns\TextColumn::make('sort_order')->sortable(), ]) ->filters([]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } public static function getPages(): array { return [ 'index' => Pages\ManageTasks::route('/'), 'import' => Pages\ImportTasks::route('/import'), ]; } } namespace App\Filament\Resources\TaskResource\Pages; use App\Filament\Resources\TaskResource; use Filament\Resources\Pages\ManageRecords; use Filament\Actions; use Filament\Resources\Pages\Page; use Filament\Forms; use Filament\Notifications\Notification; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\DB; class ManageTasks extends ManageRecords { protected static string $resource = TaskResource::class; protected function getHeaderActions(): array { return [ Actions\Action::make('import') ->label('Import CSV') ->icon('heroicon-o-arrow-up-tray') ->url(TaskResource::getUrl('import')), Actions\Action::make('template') ->label('Download CSV Template') ->icon('heroicon-o-document-arrow-down') ->url(url('/super-admin/templates/tasks.csv')), ]; } } class ImportTasks extends Page { protected static string $resource = TaskResource::class; protected string $view = 'filament.pages.blank'; protected ?string $heading = 'Import Tasks (CSV)'; public ?string $file = null; protected function getFormSchema(): array { return [ Forms\Components\FileUpload::make('file') ->label('CSV file') ->acceptedFileTypes(['text/csv', 'text/plain']) ->directory('imports') ->required(), ]; } protected function getFormActions(): array { return [ Forms\Components\Actions\Action::make('import') ->label('Import') ->action('doImport') ->color('primary') ]; } public function doImport(): void { $state = $this->form->getState(); $path = $state['file'] ?? null; if (! $path || ! Storage::disk('public')->exists($path)) { Notification::make()->danger()->title('File not found')->send(); return; } $full = Storage::disk('public')->path($path); [$ok, $fail] = $this->importTasksCsv($full); Notification::make()->success()->title("Imported {$ok} rows")->body($fail ? "${fail} failed" : null)->send(); } private function importTasksCsv(string $file): array { $h = fopen($file, 'r'); if (! $h) return [0,0]; $ok = 0; $fail = 0; // Expected headers: emotion_name,emotion_name_de,emotion_name_en,event_type_slug,title_de,title_en,description_de,description_en,difficulty,example_text_de,example_text_en,sort_order,is_active $headers = fgetcsv($h, 0, ','); if (! $headers) return [0,0]; $map = array_flip($headers); while (($row = fgetcsv($h, 0, ',')) !== false) { try { $emotionName = trim($row[$map['emotion_name']] ?? ''); $emotionNameDe = trim($row[$map['emotion_name_de']] ?? ''); $emotionNameEn = trim($row[$map['emotion_name_en']] ?? ''); $emotionId = null; if ($emotionName !== '') { $emotionId = DB::table('emotions')->where('name', $emotionName)->value('id'); } if (! $emotionId && $emotionNameDe !== '') { $emotionId = DB::table('emotions')->where('name', 'like', '%"de":"'.str_replace('"','""',$emotionNameDe).'"%')->value('id'); } if (! $emotionId && $emotionNameEn !== '') { $emotionId = DB::table('emotions')->where('name', 'like', '%"en":"'.str_replace('"','""',$emotionNameEn).'"%')->value('id'); } if (! $emotionId) { $fail++; continue; } $eventTypeSlug = trim($row[$map['event_type_slug']] ?? ''); $eventTypeId = null; if ($eventTypeSlug !== '') { $eventTypeId = DB::table('event_types')->where('slug', $eventTypeSlug)->value('id'); } $data = [ 'emotion_id' => $emotionId, 'event_type_id' => $eventTypeId, 'title' => [ 'de' => $row[$map['title_de']] ?? null, 'en' => $row[$map['title_en']] ?? null, ], 'description' => [ 'de' => $row[$map['description_de']] ?? null, 'en' => $row[$map['description_en']] ?? null, ], 'difficulty' => $row[$map['difficulty']] ?? 'easy', 'example_text' => [ 'de' => $row[$map['example_text_de']] ?? null, 'en' => $row[$map['example_text_en']] ?? null, ], 'sort_order' => (int)($row[$map['sort_order']] ?? 0), 'is_active' => (int)($row[$map['is_active']] ?? 1) ? 1 : 0, 'created_at' => now(), 'updated_at' => now(), ]; DB::table('tasks')->insert($data); $ok++; } catch (\Throwable $e) { $fail++; } } fclose($h); return [$ok, $fail]; } }