feat: Refactor Filament resources and fix tenant admin login
This commit is contained in:
@@ -4,29 +4,36 @@ namespace App\Filament\Resources;
|
|||||||
|
|
||||||
use App\Filament\Resources\EmotionResource\Pages;
|
use App\Filament\Resources\EmotionResource\Pages;
|
||||||
use App\Models\Emotion;
|
use App\Models\Emotion;
|
||||||
use Filament\Schemas\Schema as Schema;
|
use Filament\Actions;
|
||||||
use Filament\Schemas\Components as SC;
|
use Filament\Forms\Components\KeyValue;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
|
||||||
class EmotionResource extends Resource
|
class EmotionResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Emotion::class;
|
protected static ?string $model = Emotion::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-face-smile';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-face-smile';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Library';
|
protected static UnitEnum|string|null $navigationGroup = 'Library';
|
||||||
protected static ?int $navigationSort = 10;
|
protected static ?int $navigationSort = 10;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Schema $form): Schema
|
||||||
{
|
{
|
||||||
return $schema->components([
|
return $form->schema([
|
||||||
SC\KeyValue::make('name')->label('Name (de/en)')->keyLabel('locale')->valueLabel('value')->default(['de' => '', 'en' => ''])->required(),
|
KeyValue::make('name')->label('Name (de/en)')->keyLabel('locale')->valueLabel('value')->default(['de' => '', 'en' => ''])->required(),
|
||||||
SC\TextInput::make('icon')->label('Icon/Emoji')->maxLength(50),
|
TextInput::make('icon')->label('Icon/Emoji')->maxLength(50),
|
||||||
SC\TextInput::make('color')->maxLength(7)->helperText('#RRGGBB'),
|
TextInput::make('color')->maxLength(7)->helperText('#RRGGBB'),
|
||||||
SC\KeyValue::make('description')->label('Description (de/en)')->keyLabel('locale')->valueLabel('value'),
|
KeyValue::make('description')->label('Description (de/en)')->keyLabel('locale')->valueLabel('value'),
|
||||||
SC\TextInput::make('sort_order')->numeric()->default(0),
|
TextInput::make('sort_order')->numeric()->default(0),
|
||||||
SC\Toggle::make('is_active')->default(true),
|
Toggle::make('is_active')->default(true),
|
||||||
SC\Select::make('eventTypes')
|
Select::make('eventTypes')
|
||||||
->label('Event Types')
|
->label('Event Types')
|
||||||
->multiple()
|
->multiple()
|
||||||
->searchable()
|
->searchable()
|
||||||
@@ -48,12 +55,10 @@ class EmotionResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\EditAction::make(),
|
Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,128 +70,3 @@ class EmotionResource extends Resource
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace App\Filament\Resources\EmotionResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\EmotionResource;
|
|
||||||
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 ManageEmotions extends ManageRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = EmotionResource::class;
|
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Actions\Action::make('import')
|
|
||||||
->label('Import CSV')
|
|
||||||
->icon('heroicon-o-arrow-up-tray')
|
|
||||||
->url(EmotionResource::getUrl('import')),
|
|
||||||
Actions\Action::make('template')
|
|
||||||
->label('Download CSV Template')
|
|
||||||
->icon('heroicon-o-document-arrow-down')
|
|
||||||
->url(url('/super-admin/templates/emotions.csv')),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImportEmotions extends Page
|
|
||||||
{
|
|
||||||
protected static string $resource = EmotionResource::class;
|
|
||||||
protected string $view = 'filament.pages.blank';
|
|
||||||
protected ?string $heading = 'Import Emotions (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->importEmotionsCsv($full);
|
|
||||||
Notification::make()->success()->title("Imported {$ok} rows")->body($fail ? "{$fail} failed" : null)->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function importEmotionsCsv(string $file): array
|
|
||||||
{
|
|
||||||
$h = fopen($file, 'r');
|
|
||||||
if (! $h) return [0,0];
|
|
||||||
$ok = 0; $fail = 0;
|
|
||||||
// Expected headers: name_de,name_en,icon,color,description_de,description_en,sort_order,is_active,event_types
|
|
||||||
$headers = fgetcsv($h, 0, ',');
|
|
||||||
if (! $headers) return [0,0];
|
|
||||||
$map = array_flip($headers);
|
|
||||||
while (($row = fgetcsv($h, 0, ',')) !== false) {
|
|
||||||
try {
|
|
||||||
$nameDe = trim($row[$map['name_de']] ?? '');
|
|
||||||
$nameEn = trim($row[$map['name_en']] ?? '');
|
|
||||||
$name = $nameDe ?: $nameEn;
|
|
||||||
if ($name === '') { $fail++; continue; }
|
|
||||||
$data = [
|
|
||||||
'name' => ['de' => $nameDe, 'en' => $nameEn],
|
|
||||||
'icon' => $row[$map['icon']] ?? null,
|
|
||||||
'color' => $row[$map['color']] ?? null,
|
|
||||||
'description' => [
|
|
||||||
'de' => $row[$map['description_de']] ?? null,
|
|
||||||
'en' => $row[$map['description_en']] ?? null,
|
|
||||||
],
|
|
||||||
'sort_order' => (int)($row[$map['sort_order']] ?? 0),
|
|
||||||
'is_active' => (int)($row[$map['is_active']] ?? 1) ? 1 : 0,
|
|
||||||
];
|
|
||||||
$id = DB::table('emotions')->insertGetId(array_merge($data, [
|
|
||||||
'created_at' => now(), 'updated_at' => now(),
|
|
||||||
]));
|
|
||||||
// Attach event types if provided (by slug list separated by '|')
|
|
||||||
$et = $row[$map['event_types']] ?? '';
|
|
||||||
if ($et) {
|
|
||||||
$slugs = array_filter(array_map('trim', explode('|', $et)));
|
|
||||||
if ($slugs) {
|
|
||||||
$ids = DB::table('event_types')->whereIn('slug', $slugs)->pluck('id')->all();
|
|
||||||
foreach ($ids as $eid) {
|
|
||||||
DB::table('emotion_event_type')->insertOrIgnore([
|
|
||||||
'emotion_id' => $id,
|
|
||||||
'event_type_id' => $eid,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$ok++;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$fail++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose($h);
|
|
||||||
return [$ok, $fail];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
109
app/Filament/Resources/EmotionResource/Pages/ImportEmotions.php
Normal file
109
app/Filament/Resources/EmotionResource/Pages/ImportEmotions.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EmotionResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EmotionResource;
|
||||||
|
use App\Models\Emotion;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Resources\Pages\Page;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ImportEmotions extends Page
|
||||||
|
{
|
||||||
|
protected static string $resource = EmotionResource::class;
|
||||||
|
protected string $view = 'filament.resources.emotion-resource.pages.import-emotions';
|
||||||
|
protected ?string $heading = 'Import Emotions (CSV)';
|
||||||
|
|
||||||
|
public ?string $file = null;
|
||||||
|
|
||||||
|
public function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form->schema([
|
||||||
|
FileUpload::make('file')
|
||||||
|
->label('CSV file')
|
||||||
|
->acceptedFileTypes(['text/csv', 'text/plain'])
|
||||||
|
->directory('imports')
|
||||||
|
->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doImport(): void
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$path = $this->form->getState()['file'] ?? null;
|
||||||
|
if (!$path || !Storage::disk('public')->exists($path)) {
|
||||||
|
Notification::make()->danger()->title('File not found')->send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullPath = Storage::disk('public')->path($path);
|
||||||
|
[$ok, $fail] = $this->importEmotionsCsv($fullPath);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title("Imported {$ok} rows")
|
||||||
|
->body($fail ? "{$fail} failed" : null)
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importEmotionsCsv(string $file): array
|
||||||
|
{
|
||||||
|
$handle = fopen($file, 'r');
|
||||||
|
if (!$handle) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok = 0;
|
||||||
|
$fail = 0;
|
||||||
|
$headers = fgetcsv($handle, 0, ',');
|
||||||
|
if (!$headers) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = array_flip($headers);
|
||||||
|
|
||||||
|
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($row, $map, &$ok) {
|
||||||
|
$nameDe = trim($row[$map['name_de']] ?? '');
|
||||||
|
$nameEn = trim($row[$map['name_en']] ?? '');
|
||||||
|
|
||||||
|
if (empty($nameDe) && empty($nameEn)) {
|
||||||
|
throw new \Exception('Name is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$emotion = Emotion::create([
|
||||||
|
'name' => ['de' => $nameDe, 'en' => $nameEn],
|
||||||
|
'icon' => $row[$map['icon']] ?? null,
|
||||||
|
'color' => $row[$map['color']] ?? null,
|
||||||
|
'description' => [
|
||||||
|
'de' => $row[$map['description_de']] ?? null,
|
||||||
|
'en' => $row[$map['description_en']] ?? null,
|
||||||
|
],
|
||||||
|
'sort_order' => (int)($row[$map['sort_order']] ?? 0),
|
||||||
|
'is_active' => (int)($row[$map['is_active']] ?? 1) ? 1 : 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$eventTypes = $row[$map['event_types']] ?? '';
|
||||||
|
if ($eventTypes) {
|
||||||
|
$slugs = array_filter(array_map('trim', explode('|', $eventTypes)));
|
||||||
|
if ($slugs) {
|
||||||
|
$eventTypeIds = DB::table('event_types')->whereIn('slug', $slugs)->pluck('id')->all();
|
||||||
|
$emotion->eventTypes()->attach($eventTypeIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ok++;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
return [$ok, $fail];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EmotionResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EmotionResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ManageRecords;
|
||||||
|
|
||||||
|
class ManageEmotions extends ManageRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = EmotionResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
Actions\Action::make('import')
|
||||||
|
->label('Import CSV')
|
||||||
|
->icon('heroicon-o-arrow-up-tray')
|
||||||
|
->url(EmotionResource::getUrl('import')),
|
||||||
|
Actions\Action::make('template')
|
||||||
|
->label('Download CSV Template')
|
||||||
|
->icon('heroicon-o-document-arrow-down')
|
||||||
|
->url(url('/super-admin/templates/emotions.csv')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@ use App\Models\Event;
|
|||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Filament\Actions;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
|
||||||
class EventResource extends Resource
|
class EventResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Event::class;
|
protected static ?string $model = Event::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-calendar';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-calendar';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Platform';
|
protected static UnitEnum|string|null $navigationGroup = 'Platform';
|
||||||
protected static ?int $navigationSort = 20;
|
protected static ?int $navigationSort = 20;
|
||||||
|
|
||||||
public static function table(Table $table): Table
|
public static function table(Table $table): Table
|
||||||
@@ -34,13 +37,12 @@ class EventResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\ViewAction::make(),
|
Actions\EditAction::make(),
|
||||||
Tables\Actions\EditAction::make(),
|
Actions\Action::make('toggle')
|
||||||
Tables\Actions\Action::make('toggle')
|
|
||||||
->label('Toggle Active')
|
->label('Toggle Active')
|
||||||
->icon('heroicon-o-power')
|
->icon('heroicon-o-power')
|
||||||
->action(fn($record) => $record->update(['is_active' => ! (bool)$record->is_active])),
|
->action(fn($record) => $record->update(['is_active' => !$record->is_active])),
|
||||||
Tables\Actions\Action::make('join_link')
|
Actions\Action::make('join_link')
|
||||||
->label('Join Link / QR')
|
->label('Join Link / QR')
|
||||||
->icon('heroicon-o-qr-code')
|
->icon('heroicon-o-qr-code')
|
||||||
->modalHeading('Event Join Link')
|
->modalHeading('Event Join Link')
|
||||||
@@ -50,9 +52,7 @@ class EventResource extends Resource
|
|||||||
])),
|
])),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,26 +64,4 @@ class EventResource extends Resource
|
|||||||
'edit' => Pages\EditEvent::route('/{record}/edit'),
|
'edit' => Pages\EditEvent::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace App\Filament\Resources\EventResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\EventResource;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
|
||||||
use Filament\Resources\Pages\EditRecord;
|
|
||||||
|
|
||||||
class ListEvents extends ListRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = EventResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewEvent extends ViewRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = EventResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditEvent extends EditRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = EventResource::class;
|
|
||||||
}
|
|
||||||
11
app/Filament/Resources/EventResource/Pages/EditEvent.php
Normal file
11
app/Filament/Resources/EventResource/Pages/EditEvent.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EventResource;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditEvent extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = EventResource::class;
|
||||||
|
}
|
||||||
11
app/Filament/Resources/EventResource/Pages/ListEvents.php
Normal file
11
app/Filament/Resources/EventResource/Pages/ListEvents.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EventResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListEvents extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = EventResource::class;
|
||||||
|
}
|
||||||
11
app/Filament/Resources/EventResource/Pages/ViewEvent.php
Normal file
11
app/Filament/Resources/EventResource/Pages/ViewEvent.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EventResource;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewEvent extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = EventResource::class;
|
||||||
|
}
|
||||||
@@ -4,27 +4,32 @@ namespace App\Filament\Resources;
|
|||||||
|
|
||||||
use App\Filament\Resources\EventTypeResource\Pages;
|
use App\Filament\Resources\EventTypeResource\Pages;
|
||||||
use App\Models\EventType;
|
use App\Models\EventType;
|
||||||
use Filament\Schemas\Schema as Schema;
|
use Filament\Forms\Components\KeyValue;
|
||||||
use Filament\Schemas\Components as SC;
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
|
use Filament\Actions;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
|
||||||
class EventTypeResource extends Resource
|
class EventTypeResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = EventType::class;
|
protected static ?string $model = EventType::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-swatch';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-swatch';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Library';
|
protected static UnitEnum|string|null $navigationGroup = 'Library';
|
||||||
protected static ?int $navigationSort = 20;
|
protected static ?int $navigationSort = 20;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Schema $form): Schema
|
||||||
{
|
{
|
||||||
return $schema->components([
|
return $form->schema([
|
||||||
SC\KeyValue::make('name')->label('Name (de/en)')->default(['de' => '', 'en' => ''])->required(),
|
KeyValue::make('name')->label('Name (de/en)')->default(['de' => '', 'en' => ''])->required(),
|
||||||
SC\TextInput::make('slug')->required()->unique(ignoreRecord: true),
|
TextInput::make('slug')->required()->unique(ignoreRecord: true),
|
||||||
SC\TextInput::make('icon')->maxLength(64),
|
TextInput::make('icon')->maxLength(64),
|
||||||
SC\KeyValue::make('settings')->label('Settings')->keyLabel('key')->valueLabel('value'),
|
KeyValue::make('settings')->label('Settings')->keyLabel('key')->valueLabel('value'),
|
||||||
SC\Select::make('emotions')
|
Select::make('emotions')
|
||||||
->label('Emotions')
|
->label('Emotions')
|
||||||
->multiple()
|
->multiple()
|
||||||
->searchable()
|
->searchable()
|
||||||
@@ -45,12 +50,10 @@ class EventTypeResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\EditAction::make(),
|
Actions\EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,14 +63,4 @@ class EventTypeResource extends Resource
|
|||||||
'index' => Pages\ManageEventTypes::route('/'),
|
'index' => Pages\ManageEventTypes::route('/'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace App\Filament\Resources\EventTypeResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\EventTypeResource;
|
|
||||||
use Filament\Resources\Pages\ManageRecords;
|
|
||||||
|
|
||||||
class ManageEventTypes extends ManageRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = EventTypeResource::class;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EventTypeResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EventTypeResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ManageRecords;
|
||||||
|
|
||||||
|
class ManageEventTypes extends ManageRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = EventTypeResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@ use App\Models\LegalPage;
|
|||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Actions;
|
||||||
|
|
||||||
class LegalPageResource extends Resource
|
class LegalPageResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = LegalPage::class;
|
protected static ?string $model = LegalPage::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-scale';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-scale';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Platform';
|
protected static UnitEnum|string|null $navigationGroup = 'Platform';
|
||||||
protected static ?int $navigationSort = 40;
|
protected static ?int $navigationSort = 40;
|
||||||
|
|
||||||
public static function table(Table $table): Table
|
public static function table(Table $table): Table
|
||||||
@@ -28,13 +31,10 @@ class LegalPageResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\ViewAction::make(),
|
Actions\EditAction::make(),
|
||||||
Tables\Actions\EditAction::make(),
|
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,25 +47,3 @@ class LegalPageResource extends Resource
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace App\Filament\Resources\LegalPageResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\LegalPageResource;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
|
||||||
use Filament\Resources\Pages\EditRecord;
|
|
||||||
|
|
||||||
class ListLegalPages extends ListRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = LegalPageResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewLegalPage extends ViewRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = LegalPageResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditLegalPage extends EditRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = LegalPageResource::class;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\LegalPageResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\LegalPageResource;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditLegalPage extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = LegalPageResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\LegalPageResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\LegalPageResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListLegalPages extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = LegalPageResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\LegalPageResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\LegalPageResource;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewLegalPage extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = LegalPageResource::class;
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@ use App\Models\Photo;
|
|||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Actions;
|
||||||
|
|
||||||
class PhotoResource extends Resource
|
class PhotoResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Photo::class;
|
protected static ?string $model = Photo::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-photo';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-photo';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Content';
|
protected static UnitEnum|string|null $navigationGroup = 'Content';
|
||||||
protected static ?int $navigationSort = 30;
|
protected static ?int $navigationSort = 30;
|
||||||
|
|
||||||
public static function table(Table $table): Table
|
public static function table(Table $table): Table
|
||||||
@@ -28,32 +31,29 @@ class PhotoResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\ViewAction::make(),
|
Actions\EditAction::make(),
|
||||||
Tables\Actions\EditAction::make(),
|
Actions\Action::make('feature')
|
||||||
Tables\Actions\Action::make('feature')
|
|
||||||
->label('Feature')
|
->label('Feature')
|
||||||
->visible(fn($record) => ! (bool)$record->is_featured)
|
->visible(fn($record) => !$record->is_featured)
|
||||||
->action(fn($record) => $record->update(['is_featured' => 1]))
|
->action(fn($record) => $record->update(['is_featured' => true]))
|
||||||
->icon('heroicon-o-star'),
|
->icon('heroicon-o-star'),
|
||||||
Tables\Actions\Action::make('unfeature')
|
Actions\Action::make('unfeature')
|
||||||
->label('Unfeature')
|
->label('Unfeature')
|
||||||
->visible(fn($record) => (bool)$record->is_featured)
|
->visible(fn($record) => $record->is_featured)
|
||||||
->action(fn($record) => $record->update(['is_featured' => 0]))
|
->action(fn($record) => $record->update(['is_featured' => false]))
|
||||||
->icon('heroicon-o-star'),
|
->icon('heroicon-o-star'),
|
||||||
Tables\Actions\DeleteAction::make(),
|
Actions\DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\BulkAction::make('feature')
|
||||||
Tables\Actions\BulkAction::make('feature')
|
->label('Feature selected')
|
||||||
->label('Feature selected')
|
->icon('heroicon-o-star')
|
||||||
->icon('heroicon-o-star')
|
->action(fn($records) => $records->each->update(['is_featured' => true])),
|
||||||
->action(fn($records) => $records->each->update(['is_featured' => 1])),
|
Actions\BulkAction::make('unfeature')
|
||||||
Tables\Actions\BulkAction::make('unfeature')
|
->label('Unfeature selected')
|
||||||
->label('Unfeature selected')
|
->icon('heroicon-o-star')
|
||||||
->icon('heroicon-o-star')
|
->action(fn($records) => $records->each->update(['is_featured' => false])),
|
||||||
->action(fn($records) => $records->each->update(['is_featured' => 0])),
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,26 +65,4 @@ class PhotoResource extends Resource
|
|||||||
'edit' => Pages\EditPhoto::route('/{record}/edit'),
|
'edit' => Pages\EditPhoto::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace App\Filament\Resources\PhotoResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\PhotoResource;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
|
||||||
use Filament\Resources\Pages\EditRecord;
|
|
||||||
|
|
||||||
class ListPhotos extends ListRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = PhotoResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewPhoto extends ViewRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = PhotoResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditPhoto extends EditRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = PhotoResource::class;
|
|
||||||
}
|
|
||||||
11
app/Filament/Resources/PhotoResource/Pages/EditPhoto.php
Normal file
11
app/Filament/Resources/PhotoResource/Pages/EditPhoto.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\PhotoResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\PhotoResource;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditPhoto extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = PhotoResource::class;
|
||||||
|
}
|
||||||
11
app/Filament/Resources/PhotoResource/Pages/ListPhotos.php
Normal file
11
app/Filament/Resources/PhotoResource/Pages/ListPhotos.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\PhotoResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\PhotoResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListPhotos extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = PhotoResource::class;
|
||||||
|
}
|
||||||
11
app/Filament/Resources/PhotoResource/Pages/ViewPhoto.php
Normal file
11
app/Filament/Resources/PhotoResource/Pages/ViewPhoto.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\PhotoResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\PhotoResource;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewPhoto extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = PhotoResource::class;
|
||||||
|
}
|
||||||
@@ -4,34 +4,40 @@ namespace App\Filament\Resources;
|
|||||||
|
|
||||||
use App\Filament\Resources\TaskResource\Pages;
|
use App\Filament\Resources\TaskResource\Pages;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use Filament\Schemas\Schema as Schema;
|
use Filament\Forms\Components\KeyValue;
|
||||||
use Filament\Schemas\Components as SC;
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Filament\Actions;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
|
||||||
class TaskResource extends Resource
|
class TaskResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Task::class;
|
protected static ?string $model = Task::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clipboard-document-check';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-clipboard-document-check';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Library';
|
protected static UnitEnum|string|null $navigationGroup = 'Library';
|
||||||
protected static ?int $navigationSort = 30;
|
protected static ?int $navigationSort = 30;
|
||||||
|
|
||||||
public static function form(Schema $schema): Schema
|
public static function form(Schema $form): Schema
|
||||||
{
|
{
|
||||||
return $schema->components([
|
return $form->schema([
|
||||||
SC\Select::make('emotion_id')->relationship('emotion', 'name')->required()->searchable()->preload(),
|
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)'),
|
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(),
|
KeyValue::make('title')->label('Title (de/en)')->default(['de' => '', 'en' => ''])->required(),
|
||||||
SC\KeyValue::make('description')->label('Description (de/en)'),
|
KeyValue::make('description')->label('Description (de/en)'),
|
||||||
SC\Select::make('difficulty')->options([
|
Select::make('difficulty')->options([
|
||||||
'easy' => 'Easy',
|
'easy' => 'Easy',
|
||||||
'medium' => 'Medium',
|
'medium' => 'Medium',
|
||||||
'hard' => 'Hard',
|
'hard' => 'Hard',
|
||||||
])->default('easy'),
|
])->default('easy'),
|
||||||
SC\KeyValue::make('example_text')->label('Example (de/en)'),
|
KeyValue::make('example_text')->label('Example (de/en)'),
|
||||||
SC\TextInput::make('sort_order')->numeric()->default(0),
|
TextInput::make('sort_order')->numeric()->default(0),
|
||||||
SC\Toggle::make('is_active')->default(true),
|
Toggle::make('is_active')->default(true),
|
||||||
])->columns(2);
|
])->columns(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,13 +55,11 @@ class TaskResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\EditAction::make(),
|
Actions\EditAction::make(),
|
||||||
Tables\Actions\DeleteAction::make(),
|
Actions\DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,138 +70,4 @@ class TaskResource extends Resource
|
|||||||
'import' => Pages\ImportTasks::route('/import'),
|
'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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
125
app/Filament/Resources/TaskResource/Pages/ImportTasks.php
Normal file
125
app/Filament/Resources/TaskResource/Pages/ImportTasks.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TaskResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TaskResource;
|
||||||
|
use App\Models\Task;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Resources\Pages\Page;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ImportTasks extends Page
|
||||||
|
{
|
||||||
|
protected static string $resource = TaskResource::class;
|
||||||
|
protected string $view = 'filament.resources.task-resource.pages.import-tasks';
|
||||||
|
protected ?string $heading = 'Import Tasks (CSV)';
|
||||||
|
|
||||||
|
public ?string $file = null;
|
||||||
|
|
||||||
|
public function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form->schema([
|
||||||
|
FileUpload::make('file')
|
||||||
|
->label('CSV file')
|
||||||
|
->acceptedFileTypes(['text/csv', 'text/plain'])
|
||||||
|
->directory('imports')
|
||||||
|
->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doImport(): void
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$path = $this->form->getState()['file'] ?? null;
|
||||||
|
if (!$path || !Storage::disk('public')->exists($path)) {
|
||||||
|
Notification::make()->danger()->title('File not found')->send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullPath = Storage::disk('public')->path($path);
|
||||||
|
[$ok, $fail] = $this->importTasksCsv($fullPath);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title("Imported {$ok} rows")
|
||||||
|
->body($fail ? "{$fail} failed" : null)
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importTasksCsv(string $file): array
|
||||||
|
{
|
||||||
|
$handle = fopen($file, 'r');
|
||||||
|
if (!$handle) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok = 0;
|
||||||
|
$fail = 0;
|
||||||
|
$headers = fgetcsv($handle, 0, ',');
|
||||||
|
if (!$headers) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = array_flip($headers);
|
||||||
|
|
||||||
|
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($row, $map, &$ok) {
|
||||||
|
$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');
|
||||||
|
} elseif ($emotionNameDe) {
|
||||||
|
$emotionId = DB::table('emotions')->where('name->de', $emotionNameDe)->value('id');
|
||||||
|
} elseif ($emotionNameEn) {
|
||||||
|
$emotionId = DB::table('emotions')->where('name->en', $emotionNameEn)->value('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$emotionId) {
|
||||||
|
throw new \Exception('Emotion not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventTypeId = null;
|
||||||
|
$eventTypeSlug = trim($row[$map['event_type_slug']] ?? '');
|
||||||
|
if ($eventTypeSlug) {
|
||||||
|
$eventTypeId = DB::table('event_types')->where('slug', $eventTypeSlug)->value('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::create([
|
||||||
|
'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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ok++;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
return [$ok, $fail];
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Filament/Resources/TaskResource/Pages/ManageTasks.php
Normal file
27
app/Filament/Resources/TaskResource/Pages/ManageTasks.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TaskResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TaskResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ManageRecords;
|
||||||
|
|
||||||
|
class ManageTasks extends ManageRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = TaskResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
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')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@ use App\Models\Tenant;
|
|||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Actions;
|
||||||
|
|
||||||
class TenantResource extends Resource
|
class TenantResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Tenant::class;
|
protected static ?string $model = Tenant::class;
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-building-office';
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-building-office';
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'Platform';
|
protected static UnitEnum|string|null $navigationGroup = 'Platform';
|
||||||
protected static ?int $navigationSort = 10;
|
protected static ?int $navigationSort = 10;
|
||||||
|
|
||||||
public static function table(Table $table): Table
|
public static function table(Table $table): Table
|
||||||
@@ -29,13 +32,10 @@ class TenantResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([])
|
->filters([])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\ViewAction::make(),
|
Actions\EditAction::make(),
|
||||||
Tables\Actions\EditAction::make(),
|
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,25 +48,3 @@ class TenantResource extends Resource
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace App\Filament\Resources\TenantResource\Pages;
|
|
||||||
|
|
||||||
use App\Filament\Resources\TenantResource;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
|
||||||
use Filament\Resources\Pages\EditRecord;
|
|
||||||
|
|
||||||
class ListTenants extends ListRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = TenantResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewTenant extends ViewRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = TenantResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditTenant extends EditRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = TenantResource::class;
|
|
||||||
}
|
|
||||||
|
|||||||
11
app/Filament/Resources/TenantResource/Pages/EditTenant.php
Normal file
11
app/Filament/Resources/TenantResource/Pages/EditTenant.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TenantResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TenantResource;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditTenant extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = TenantResource::class;
|
||||||
|
}
|
||||||
11
app/Filament/Resources/TenantResource/Pages/ListTenants.php
Normal file
11
app/Filament/Resources/TenantResource/Pages/ListTenants.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TenantResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TenantResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListTenants extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = TenantResource::class;
|
||||||
|
}
|
||||||
11
app/Filament/Resources/TenantResource/Pages/ViewTenant.php
Normal file
11
app/Filament/Resources/TenantResource/Pages/ViewTenant.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TenantResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TenantResource;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewTenant extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = TenantResource::class;
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
export async function login(email: string, password: string): Promise<{ token: string }> {
|
export async function login(email: string, password: string): Promise<{ token: string }> {
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||||
const res = await fetch('/api/v1/tenant/login', {
|
const res = await fetch('/api/v1/tenant/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': csrfToken || '',
|
||||||
|
},
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error('Login failed');
|
if (!res.ok) throw new Error('Login failed');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<title>Tenant Admin</title>
|
<title>Tenant Admin</title>
|
||||||
@vite('resources/js/admin/main.tsx')
|
@vite('resources/js/admin/main.tsx')
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<x-filament-panels::page>
|
||||||
|
<x-filament-panels::form wire:submit="doImport">
|
||||||
|
{{ $this->form }}
|
||||||
|
|
||||||
|
<x-filament-panels::form.actions
|
||||||
|
:actions="[
|
||||||
|
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<x-filament::button type="submit" >
|
||||||
|
Import
|
||||||
|
</x-filament::button>
|
||||||
|
</x-filament-panels::form>
|
||||||
|
</x-filament-panels::page>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<x-filament-panels::page>
|
||||||
|
<x-filament-panels::form wire:submit="doImport">
|
||||||
|
{{ $this->form }}
|
||||||
|
|
||||||
|
<x-filament-panels::form.actions
|
||||||
|
:actions="[
|
||||||
|
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<x-filament::button type="submit" >
|
||||||
|
Import
|
||||||
|
</x-filament::button>
|
||||||
|
</x-filament-panels::form>
|
||||||
|
</x-filament-panels::page>
|
||||||
41
tests/Feature/EmotionResourceTest.php
Normal file
41
tests/Feature/EmotionResourceTest.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EmotionResource\Pages\ImportEmotions;
|
||||||
|
use App\Models\Emotion;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class EmotionResourceTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_import_emotions_csv()
|
||||||
|
{
|
||||||
|
Storage::fake('public');
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$csvData = "name_de,name_en,icon,color,description_de,description_en,sort_order,is_active,event_types\nGlück,Joy,😊,#FFD700,Gefühl des Glücks,Feeling of joy,1,1,wedding|birthday";
|
||||||
|
$csvFile = UploadedFile::fake()->createWithContent('emotions.csv', $csvData);
|
||||||
|
|
||||||
|
Livewire::test(ImportEmotions::class)
|
||||||
|
->set('file', $csvFile->getRealPath())
|
||||||
|
->call('doImport');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('emotions', [
|
||||||
|
'name' => json_encode(['de' => 'Glück', 'en' => 'Joy']),
|
||||||
|
'icon' => '😊',
|
||||||
|
'color' => '#FFD700',
|
||||||
|
'description' => json_encode(['de' => 'Gefühl des Glücks', 'en' => 'Feeling of joy']),
|
||||||
|
'sort_order' => 1,
|
||||||
|
'is_active' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$emotion = Emotion::where('name->de', 'Glück')->first();
|
||||||
|
$this->assertNotNull($emotion);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user