You've already forked Atomcms-edit
Initial commit
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\Achievements;
|
||||
|
||||
use App\Enums\AchievementCategory;
|
||||
use App\Enums\CurrencyTypes;
|
||||
use App\Filament\Resources\Hotel\Achievements\Pages\CreateAchievement;
|
||||
use App\Filament\Resources\Hotel\Achievements\Pages\EditAchievement;
|
||||
use App\Filament\Resources\Hotel\Achievements\Pages\ListAchievements;
|
||||
use App\Filament\Resources\Hotel\Achievements\Pages\ViewAchievement;
|
||||
use App\Filament\Tables\Columns\HabboBadgeColumn;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\Achievement;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class AchievementResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = Achievement::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-academic-cap';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
public static string $translateIdentifier = 'achievements';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/achievements';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make('Main')
|
||||
->tabs([
|
||||
Tab::make(__('filament::resources.tabs.Home'))
|
||||
->icon('heroicon-o-home')
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('filament::resources.inputs.name'))
|
||||
->required()
|
||||
->maxLength(64)
|
||||
->autocomplete()
|
||||
->columnSpan('full'),
|
||||
|
||||
TextInput::make('level')
|
||||
->label(__('filament::resources.inputs.level'))
|
||||
->numeric()
|
||||
->required()
|
||||
->autocomplete()
|
||||
->columnSpan('full'),
|
||||
|
||||
Select::make('category')
|
||||
->native(false)
|
||||
->label(__('filament::resources.inputs.category'))
|
||||
->options(AchievementCategory::toInput()),
|
||||
]),
|
||||
|
||||
Tab::make(__('filament::resources.tabs.Configurations'))
|
||||
->icon('heroicon-o-cog')
|
||||
->schema([
|
||||
Select::make('visible')
|
||||
->native(false)
|
||||
->label(__('filament::resources.inputs.visible'))
|
||||
->options([
|
||||
'1' => __('filament::resources.common.Yes'),
|
||||
'0' => __('filament::resources.common.No'),
|
||||
]),
|
||||
|
||||
Select::make('reward_type')
|
||||
->native(false)
|
||||
->label(__('filament::resources.inputs.reward_type'))
|
||||
->options(CurrencyTypes::toInput()),
|
||||
|
||||
TextInput::make('reward_amount')
|
||||
->label(__('filament::resources.inputs.reward_amount'))
|
||||
->numeric()
|
||||
->required(),
|
||||
|
||||
TextInput::make('points')
|
||||
->label(__('filament::resources.inputs.points'))
|
||||
->helperText(__('filament::resources.helpers.achievement_points'))
|
||||
->numeric()
|
||||
->required(),
|
||||
|
||||
TextInput::make('progress_needed')
|
||||
->label(__('filament::resources.inputs.progress_needed'))
|
||||
->helperText(__('filament::resources.helpers.achievement_progress_needed'))
|
||||
->numeric()
|
||||
->required(),
|
||||
]),
|
||||
])->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('id', 'desc')
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label(__('filament::resources.columns.id')),
|
||||
|
||||
HabboBadgeColumn::make('badge')
|
||||
->label(__('filament::resources.columns.badge')),
|
||||
|
||||
TextColumn::make('name')
|
||||
->label(__('filament::resources.columns.name'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('level')
|
||||
->label(__('filament::resources.columns.level')),
|
||||
|
||||
TextColumn::make('category')
|
||||
->badge()
|
||||
->searchable()
|
||||
->label(__('filament::resources.columns.category'))
|
||||
->toggleable(),
|
||||
|
||||
ToggleColumn::make('visible')
|
||||
->label(__('filament::resources.columns.visible'))
|
||||
->disabled()
|
||||
->toggleable(),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('visible')
|
||||
->options([
|
||||
'1' => __('filament::resources.common.Yes'),
|
||||
'0' => __('filament::resources.common.No'),
|
||||
])
|
||||
->label(__('filament::resources.columns.visible'))
|
||||
->placeholder(__('filament::resources.common.All')),
|
||||
|
||||
SelectFilter::make('category')
|
||||
->options(AchievementCategory::toInput())
|
||||
->label(__('filament::resources.columns.category'))
|
||||
->placeholder(__('filament::resources.common.All')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListAchievements::route('/'),
|
||||
'create' => CreateAchievement::route('/create'),
|
||||
'view' => ViewAchievement::route('/{record}'),
|
||||
'edit' => EditAchievement::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\Achievements\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateAchievement extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = AchievementResource::class;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\Achievements\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
|
||||
use Filament\Pages\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditAchievement extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = AchievementResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\Achievements\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
|
||||
use Filament\Pages\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAchievements extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = AchievementResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\Achievements\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewAchievement extends ViewRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = AchievementResource::class;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\Pages\CreateBadgeTextEditor;
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\Pages\EditBadgeTextEditor;
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\Pages\ListBadgeTextEditors;
|
||||
use App\Models\WebsiteBadge;
|
||||
use App\Services\SettingsService;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BadgeTextEditorResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = WebsiteBadge::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-pencil-square';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = 'Badge Editor';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $modelLabel = 'Badge Text';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/badge-text-editor';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('badge_key')
|
||||
->required()
|
||||
->label('Badge Key - Expl. ATOM101')
|
||||
->placeholder('This is the badge code'),
|
||||
TextInput::make('badge_name')
|
||||
->required()
|
||||
->label('Badge Name')
|
||||
->placeholder('This is the name of the badge: Expl. The ATOM Badge'),
|
||||
Textarea::make('badge_description')
|
||||
->required()
|
||||
->label('Badge Description')
|
||||
->placeholder('Please add a description for the badge.'),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
$settingsService = app(SettingsService::class);
|
||||
$badgesPath = $settingsService->getOrDefault('badges_path', '/gamedata/c_images/album1584/');
|
||||
|
||||
return $table
|
||||
->columns([
|
||||
ImageColumn::make('badge_key')
|
||||
->label('Badge Image')
|
||||
->getStateUsing(function ($record) use ($badgesPath) {
|
||||
$badgeName = str_replace('badge_desc_', '', $record->badge_key);
|
||||
|
||||
return asset($badgesPath . $badgeName . '.gif');
|
||||
})
|
||||
->width(50)
|
||||
->height(50),
|
||||
TextColumn::make('badge_name')
|
||||
->label('Badge Code & Name')
|
||||
->formatStateUsing(fn ($record) => $record->badge_key . ' : ' . $record->badge_name)
|
||||
->searchable(query: function ($query, $search) {
|
||||
$query->where('badge_key', 'like', "%{$search}%")
|
||||
->orWhere('badge_name', 'like', "%{$search}%");
|
||||
})
|
||||
->sortable(),
|
||||
TextColumn::make('badge_description')
|
||||
->label('Badge Description')
|
||||
->getStateUsing(fn ($record) => Str::limit($record->badge_description, 65))
|
||||
->searchable(),
|
||||
])
|
||||
->filters([])
|
||||
->defaultSort('badge_key', 'asc')
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListBadgeTextEditors::route('/'),
|
||||
'create' => CreateBadgeTextEditor::route('/create'),
|
||||
'edit' => EditBadgeTextEditor::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateBadgeTextEditor extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeTextEditorResource::class;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PDOException;
|
||||
|
||||
class EditBadgeTextEditor extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeTextEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [DeleteAction::make()];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void {}
|
||||
|
||||
#[\Override]
|
||||
protected function handleRecordUpdate(Model $record, array $data): Model
|
||||
{
|
||||
try {
|
||||
return parent::handleRecordUpdate($record, $data);
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() === '23000') {
|
||||
Log::error('Duplicate badge key error: ' . $e->getMessage());
|
||||
|
||||
Notification::make()
|
||||
->title('Duplicate Badge Key')
|
||||
->body('The badge key already exists. Please use a unique badge key.')
|
||||
->danger()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
return $record;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
|
||||
use App\Models\WebsiteBadge;
|
||||
use App\Services\SettingsService;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ListBadgeTextEditors extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeTextEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Add Badge')
|
||||
->color('info')
|
||||
->modalHeading('Add a New Badge')
|
||||
->modalButton('Create Badge')
|
||||
->after(function () {
|
||||
Notification::make()
|
||||
->title('Badge Created')
|
||||
->body('The badge was successfully created.')
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
Action::make('export')
|
||||
->label('Export to ExternalTexts')
|
||||
->action('exportToJson'),
|
||||
Action::make('backup')
|
||||
->label('Create Backup of ExternalTexts')
|
||||
->color('success')
|
||||
->action('createBackup'),
|
||||
];
|
||||
}
|
||||
|
||||
public function exportToJson(SettingsService $settingsService): void
|
||||
{
|
||||
$jsonPath = $settingsService->getOrDefault('nitro_external_texts_file');
|
||||
|
||||
if (empty($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Export Failed')
|
||||
->body('The JSON file path is not configured in the website settings.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! file_exists($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Export Failed')
|
||||
->body('The JSON file does not exist at the specified path.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$jsonData = json_decode(file_get_contents($jsonPath), true);
|
||||
|
||||
$badges = WebsiteBadge::all();
|
||||
$badgeKeys = $badges->pluck('badge_key')->toArray();
|
||||
|
||||
foreach ($jsonData as $key => $value) {
|
||||
if (
|
||||
(str_starts_with((string) $key, 'badge_desc_') || str_starts_with((string) $key, 'badge_name_')) &&
|
||||
! in_array(str_replace(['badge_desc_', 'badge_name_'], '', $key), $badgeKeys)
|
||||
) {
|
||||
unset($jsonData[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($badges as $badge) {
|
||||
$jsonData['badge_desc_' . $badge->badge_key] = $badge->badge_description;
|
||||
$jsonData['badge_name_' . $badge->badge_key] = $badge->badge_name;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = file_put_contents(
|
||||
$jsonPath,
|
||||
json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
throw new Exception('Failed to write to the JSON file.');
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Export Successful')
|
||||
->body('Badge data exported successfully.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to export badge data: ' . $e->getMessage());
|
||||
|
||||
Notification::make()
|
||||
->title('Export Failed')
|
||||
->body('Failed to export badge data. Please check file permissions or contact your administrator.')
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function createBackup(SettingsService $settingsService): void
|
||||
{
|
||||
$jsonPath = $settingsService->getOrDefault('nitro_external_texts_file');
|
||||
|
||||
if (empty($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Backup Failed')
|
||||
->body('The JSON file path is not configured in the website settings.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! file_exists($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Backup Failed')
|
||||
->body('The JSON file does not exist at the specified path.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$backupPath = dirname((string) $jsonPath) . '/ExternalTexts_' . time() . '.json';
|
||||
|
||||
if (copy($jsonPath, $backupPath)) {
|
||||
Notification::make()
|
||||
->title('Backup Successful')
|
||||
->body('A backup of the JSON file has been created: ' . basename($backupPath))
|
||||
->success()
|
||||
->send();
|
||||
} else {
|
||||
Notification::make()
|
||||
->title('Backup Failed')
|
||||
->body('Failed to create a backup of the JSON file.')
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeUploads;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeUploads\Pages\ManageBadgeUploads;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class BadgeUploadResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-gif';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $label = 'Badge Upload';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
FileUpload::make('badge_file')
|
||||
->label('Upload Badge')
|
||||
->disk('local')
|
||||
->directory(setting('badge_path_filesystem'))
|
||||
->required()
|
||||
->getUploadedFileNameForStorageUsing(
|
||||
fn (TemporaryUploadedFile $file): string => strtolower(str_replace([' ', '-', 'æ', 'ø', 'å'], ['_', '_', 'ae', 'oe', 'aa'], $file->getClientOriginalName())),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('filename')
|
||||
->label('File Name')
|
||||
->sortable(),
|
||||
TextColumn::make('path')
|
||||
->label('File Path'),
|
||||
])
|
||||
->filters([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageBadgeUploads::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getFiles(): array
|
||||
{
|
||||
$badgePath = env('BadgePath', 'badges');
|
||||
$files = Storage::disk('local')->files($badgePath);
|
||||
|
||||
return collect($files)->map(fn ($file) => [
|
||||
'filename' => basename($file),
|
||||
'path' => $file,
|
||||
])->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeUploads\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeUploads\BadgeUploadResource;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Schemas\Components\Form;
|
||||
|
||||
/**
|
||||
* @property-read Form $form
|
||||
*/
|
||||
class ManageBadgeUploads extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
public $badge_file;
|
||||
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeUploadResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.manage-badge-uploads';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
// initialize form; Filament handles default values via getFormSchema
|
||||
// Avoid using fill() on the Form component in this context
|
||||
// No explicit form fill here
|
||||
}
|
||||
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
FileUpload::make('badge_file')
|
||||
->label('Upload Badge')
|
||||
->disk('badges')
|
||||
->preserveFilenames()
|
||||
->acceptedFileTypes(['image/gif'])
|
||||
->rules(['mimes:gif'])
|
||||
->required(),
|
||||
];
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->form->getState();
|
||||
|
||||
Notification::make()
|
||||
->title('Badge uploaded successfully!')
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors;
|
||||
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CatalogEditorResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = CatalogPage::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = 'Catalog Editor';
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')->label('ID')->sortable(),
|
||||
TextColumn::make('caption')->label('Page Name')->searchable(),
|
||||
TextColumn::make('parent_id')->label('Parent ID'),
|
||||
TextColumn::make('order_num')->label('Order'),
|
||||
IconColumn::make('visible')->boolean()->label('Visible'),
|
||||
IconColumn::make('enabled')->boolean()->label('Enabled'),
|
||||
])
|
||||
->recordActions([])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('caption')
|
||||
->label('Page Name')
|
||||
->required()
|
||||
->maxLength(128),
|
||||
|
||||
TextInput::make('caption_save')
|
||||
->label('Name TAG')
|
||||
->maxLength(25)
|
||||
->helperText('Lowercase letters only (a-z), no spaces.'),
|
||||
|
||||
Select::make('parent_id')
|
||||
->label('Parent Page')
|
||||
->options(fn () => CatalogPage::all()
|
||||
->pluck('caption', 'id')
|
||||
->toArray())
|
||||
->default(-1),
|
||||
|
||||
TextInput::make('order_num')
|
||||
->label('Order')
|
||||
->numeric()
|
||||
->default(1),
|
||||
|
||||
TextInput::make('icon_image')
|
||||
->label('Icon Number')
|
||||
->numeric()
|
||||
->default(1),
|
||||
|
||||
TextInput::make('page_layout')
|
||||
->label('Page Layout')
|
||||
->default('default_3x3'),
|
||||
|
||||
TextInput::make('min_rank')
|
||||
->label('Min Rank')
|
||||
->numeric()
|
||||
->default(1),
|
||||
|
||||
Toggle::make('visible')
|
||||
->label('Visible')
|
||||
->default(true),
|
||||
|
||||
Toggle::make('enabled')
|
||||
->label('Enabled')
|
||||
->default(true),
|
||||
|
||||
Toggle::make('club_only')
|
||||
->label('Club Only (HC)')
|
||||
->default(false),
|
||||
|
||||
Toggle::make('vip_only')
|
||||
->label('VIP Only')
|
||||
->default(false),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ManageCatalogEditor::route('/'),
|
||||
'create' => Pages\CreateCatalogEditor::route('/create'),
|
||||
'view' => Pages\ViewCatalogEditor::route('/{record}/view'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CatalogEditors\CatalogEditorResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCatalogEditor extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = CatalogEditorResource::class;
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CatalogEditors\CatalogEditorResource;
|
||||
use App\Models\Game\Furniture\CatalogItem;
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use Filament\Actions\Action as FilamentAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ManageCatalogEditor extends Page implements HasTable
|
||||
{
|
||||
use InteractsWithTable;
|
||||
|
||||
#[\Override]
|
||||
protected static string $resource = CatalogEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.resources.hotel.catalog-editors.pages.manage-catalog-editor';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $pageSearch = '';
|
||||
|
||||
public ?CatalogPage $selectedPage = null;
|
||||
|
||||
/** @var array<mixed> */
|
||||
public array $expandedPages = [];
|
||||
|
||||
/** @var array<mixed> */
|
||||
public array $selectedItemIds = [];
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('createPage')
|
||||
->label('New Page')
|
||||
->icon('heroicon-m-plus')
|
||||
->url(fn () => static::getResource()::getUrl('create')),
|
||||
|
||||
FilamentAction::make('editPageHeader')
|
||||
->label('Edit Page')
|
||||
->icon('heroicon-m-pencil')
|
||||
->button()
|
||||
->action(function () {
|
||||
if ($this->selectedPage instanceof CatalogPage) {
|
||||
$this->selectedPage->refresh();
|
||||
$this->dispatch('open-edit-page', pageId: $this->selectedPage->id);
|
||||
}
|
||||
})
|
||||
->modalHeading('Edit page')
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalWidth('3xl')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('caption')->label('Name')->maxLength(128)->required(),
|
||||
Forms\Components\TextInput::make('caption_save')->label('Name TAG')->maxLength(25)->nullable(),
|
||||
Forms\Components\TextInput::make('order_num')->label('Order')->numeric()->minValue(0)->required(),
|
||||
Forms\Components\TextInput::make('icon_image')->label('Icon number')->numeric()->minValue(1)->required(),
|
||||
])
|
||||
->fillForm(function () {
|
||||
if (! $this->selectedPage instanceof CatalogPage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'caption' => $this->selectedPage->caption ?? '',
|
||||
'caption_save' => $this->selectedPage->caption_save ?? '',
|
||||
'order_num' => $this->selectedPage->order_num ?? 1,
|
||||
'icon_image' => $this->selectedPage->icon_image ?? 1,
|
||||
];
|
||||
})
|
||||
->action(function (array $data) {
|
||||
if ($this->selectedPage instanceof CatalogPage) {
|
||||
$this->selectedPage->update($data);
|
||||
Notification::make()->title('Page updated')->success()->send();
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected function escapeLike(string $value, string $escapeChar = '\\'): string
|
||||
{
|
||||
return str_replace(
|
||||
[$escapeChar, '%', '_'],
|
||||
[$escapeChar . $escapeChar, $escapeChar . '%', $escapeChar . '_'],
|
||||
$value,
|
||||
);
|
||||
}
|
||||
|
||||
public function selectPage(int $pageId): void
|
||||
{
|
||||
$this->selectedPage = CatalogPage::find($pageId);
|
||||
$this->selectedItemIds = [];
|
||||
|
||||
if ($this->pageSearch !== '') {
|
||||
$this->pageSearch = '';
|
||||
}
|
||||
|
||||
$this->expandedPages = $this->collectParentIds($pageId);
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
protected function collectParentIds(int $pageId): array
|
||||
{
|
||||
$pages = CatalogPage::pluck('parent_id', 'id');
|
||||
$ids = [$pageId];
|
||||
$parentId = $pages[$pageId] ?? null;
|
||||
while ($parentId && $parentId > 0) {
|
||||
$ids[] = $parentId;
|
||||
$parentId = $pages[$parentId] ?? null;
|
||||
}
|
||||
|
||||
return array_unique($ids);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getMaxContentWidth(): ?string
|
||||
{
|
||||
return 'full';
|
||||
}
|
||||
|
||||
public function resetView(): void
|
||||
{
|
||||
$this->pageSearch = '';
|
||||
$this->selectedPage = null;
|
||||
$this->expandedPages = [];
|
||||
$this->selectedItemIds = [];
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()
|
||||
->title('View reset')
|
||||
->body('Catalog view restored to default.')
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function toggleExpand(int $pageId): void
|
||||
{
|
||||
if (in_array($pageId, $this->expandedPages, true)) {
|
||||
$this->expandedPages = array_values(array_diff($this->expandedPages, [$pageId]));
|
||||
} else {
|
||||
$this->expandedPages[] = $pageId;
|
||||
}
|
||||
}
|
||||
|
||||
public function isExpanded(int $pageId): bool
|
||||
{
|
||||
return in_array($pageId, $this->expandedPages, true);
|
||||
}
|
||||
|
||||
public function toggleSelectItem(int $itemId, bool $ctrl = false): void
|
||||
{
|
||||
if ($ctrl) {
|
||||
if (in_array($itemId, $this->selectedItemIds, true)) {
|
||||
$this->selectedItemIds = array_values(array_diff($this->selectedItemIds, [$itemId]));
|
||||
} else {
|
||||
$this->selectedItemIds[] = $itemId;
|
||||
}
|
||||
} else {
|
||||
$this->selectedItemIds = [$itemId];
|
||||
}
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
public function updatedPageSearch(): void
|
||||
{
|
||||
$this->resetTable();
|
||||
|
||||
$needle = trim($this->pageSearch);
|
||||
|
||||
if ($needle === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$like = '%' . $this->escapeLike($needle) . '%';
|
||||
|
||||
$matchingPage = CatalogPage::query()
|
||||
->whereRaw("caption LIKE ? ESCAPE '\\\\'", [$like])
|
||||
->first();
|
||||
|
||||
if ($matchingPage) {
|
||||
$this->selectedPage = $matchingPage;
|
||||
$this->expandedPages[] = $matchingPage->id;
|
||||
$this->resetTable();
|
||||
$this->dispatch('scroll-to-page', id: $matchingPage->id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$matchingItem = CatalogItem::query()
|
||||
->whereRaw("catalog_name LIKE ? ESCAPE '\\\\'", [$like])
|
||||
->orWhere('id', ctype_digit($needle) ? (int) $needle : -1)
|
||||
->first();
|
||||
|
||||
if ($matchingItem) {
|
||||
$page = CatalogPage::find($matchingItem->page_id);
|
||||
if ($page) {
|
||||
$this->selectedPage = $page;
|
||||
$this->expandedPages[] = $page->id;
|
||||
$this->selectedItemIds = [$matchingItem->id];
|
||||
$this->resetTable();
|
||||
$this->dispatch('scroll-to-page', id: $page->id);
|
||||
|
||||
Notification::make()
|
||||
->title('Item found')
|
||||
->body("Opened page: {$page->caption}")
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTableQuery()
|
||||
{
|
||||
if (! $this->selectedPage instanceof CatalogPage) {
|
||||
return CatalogItem::query()->whereRaw('1=0');
|
||||
}
|
||||
|
||||
$query = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id);
|
||||
|
||||
if (filled($this->pageSearch)) {
|
||||
$needle = trim($this->pageSearch);
|
||||
$like = '%' . $this->escapeLike($needle) . '%';
|
||||
$isNumeric = ctype_digit($needle);
|
||||
|
||||
$query->where(function ($q) use ($like, $needle, $isNumeric) {
|
||||
$q->whereRaw("catalog_name LIKE ? ESCAPE '\\\\'", [$like]);
|
||||
|
||||
if ($isNumeric) {
|
||||
$n = (int) $needle;
|
||||
|
||||
$q->orWhere('id', $n)
|
||||
->orWhere('cost_credits', $n)
|
||||
->orWhere('cost_points', $n)
|
||||
->orWhere('points_type', $n);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (! $this->getTableSortColumn()) {
|
||||
$query->orderBy('order_number')->orderBy('catalog_name')->orderBy('id');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function findPrevNeighbor(CatalogItem $record): ?CatalogItem
|
||||
{
|
||||
return CatalogItem::query()
|
||||
->where('page_id', $record->page_id)
|
||||
->where('order_number', '!=', -1)
|
||||
->where(function ($q) use ($record) {
|
||||
$q->where('order_number', '<', $record->order_number)
|
||||
->orWhere(function ($q2) use ($record) {
|
||||
$q2->where('order_number', $record->order_number)
|
||||
->where('id', '<', $record->id);
|
||||
});
|
||||
})
|
||||
->orderBy('order_number', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
protected function findNextNeighbor(CatalogItem $record): ?CatalogItem
|
||||
{
|
||||
return CatalogItem::query()
|
||||
->where('page_id', $record->page_id)
|
||||
->where('order_number', '!=', -1)
|
||||
->where(function ($q) use ($record) {
|
||||
$q->where('order_number', '>', $record->order_number)
|
||||
->orWhere(function ($q2) use ($record) {
|
||||
$q2->where('order_number', $record->order_number)
|
||||
->where('id', '>', $record->id);
|
||||
});
|
||||
})
|
||||
->orderBy('order_number', 'asc')
|
||||
->orderBy('id', 'asc')
|
||||
->first();
|
||||
}
|
||||
|
||||
protected function canMoveUp(CatalogItem $record): bool
|
||||
{
|
||||
if ($record->order_number === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->findPrevNeighbor($record);
|
||||
}
|
||||
|
||||
protected function canMoveDown(CatalogItem $record): bool
|
||||
{
|
||||
if ($record->order_number === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->findNextNeighbor($record);
|
||||
}
|
||||
|
||||
protected function nudgeRecord(CatalogItem $record, string $direction): void
|
||||
{
|
||||
if ($record->order_number === -1) {
|
||||
Notification::make()->title('Locked')->body('This item is locked (order = -1).')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$neighbor = $direction === 'up'
|
||||
? $this->findPrevNeighbor($record)
|
||||
: $this->findNextNeighbor($record);
|
||||
|
||||
if (! $neighbor instanceof CatalogItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($record, $neighbor) {
|
||||
$a = $record->order_number;
|
||||
$b = $neighbor->order_number;
|
||||
|
||||
$record->update(['order_number' => $b]);
|
||||
$neighbor->update(['order_number' => $a]);
|
||||
});
|
||||
|
||||
$this->normalizeOrderForSelectedPage();
|
||||
|
||||
Notification::make()->title('Order updated')->success()->send();
|
||||
}
|
||||
|
||||
protected function normalizeOrderForSelectedPage(): void
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', '!=', -1)
|
||||
->orderBy('order_number')
|
||||
->orderBy('id')
|
||||
->get(['id']);
|
||||
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items->values() as $index => $item) {
|
||||
CatalogItem::whereKey($item->id)
|
||||
->update(['order_number' => ($index + 1) * 10]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
public function pageHasLockedItems(): bool
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', -1)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function autoOrderItems(): void
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
Notification::make()->title('Select a page first')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->pageHasLockedItems()) {
|
||||
Notification::make()
|
||||
->title('Action not allowed')
|
||||
->body('This page contains item(s) with order_number = -1. Remove or change them before auto-ordering.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$affected = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', '!=', -1)
|
||||
->update(['order_number' => 99]);
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
if ($affected > 0) {
|
||||
Notification::make()->title('Items auto-ordered')->body("Updated {$affected} item(s).")->success()->send();
|
||||
} else {
|
||||
Notification::make()->title('Nothing to update')->body('No items were changed (none on this page or all are set to -1).')->warning()->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function manualOrderItems(): void
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
Notification::make()->title('Select a page first')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->pageHasLockedItems()) {
|
||||
Notification::make()->title('Action not allowed')->body('This page contains item(s) with order_number = -1. Change/remove them before manual ordering.')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$items = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', '!=', -1)
|
||||
->orderBy('catalog_name', 'asc')
|
||||
->orderBy('id', 'asc')
|
||||
->get(['id']);
|
||||
|
||||
if ($items->isEmpty()) {
|
||||
Notification::make()->title('Nothing to update')->body('No items on this page (or all are locked to -1).')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items->values() as $index => $item) {
|
||||
CatalogItem::whereKey($item->id)->update(['order_number' => ($index + 1) * 10]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()->title('Items manually ordered')->body('Items sorted A→Z and numbered 10, 20, 30, …')->success()->send();
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table->paginated(false);
|
||||
}
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
Tables\Columns\ViewColumn::make('select_item')
|
||||
->label('')
|
||||
->view('filament.tables.columns.catalog-item-select')
|
||||
->viewData([
|
||||
'itemId' => fn ($record) => $record->id,
|
||||
'isSelected' => fn ($record) => in_array($record->id, $this->selectedItemIds, true),
|
||||
])
|
||||
->width('36px')
|
||||
->sortable(false)
|
||||
->searchable(false),
|
||||
|
||||
Tables\Columns\ViewColumn::make('item_display')
|
||||
->label('Item')
|
||||
->view('filament.tables.columns.catalog-item-draggable')
|
||||
->viewData([
|
||||
'icon' => fn ($record) => $this->buildFurniIconUrl($record->catalog_name),
|
||||
'name' => fn ($record) => $record->catalog_name,
|
||||
'itemId' => fn ($record) => $record->id,
|
||||
'isSelected' => fn ($record) => in_array($record->id, $this->selectedItemIds, true),
|
||||
])
|
||||
->sortable(false)
|
||||
->searchable(false),
|
||||
|
||||
Tables\Columns\TextColumn::make('cost_credits')
|
||||
->label('Credits')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('cost_points')
|
||||
->label('Points')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('points_type')
|
||||
->label('Type')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
->label('Amount')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('order_number')
|
||||
->label('Order')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
Tables\Columns\IconColumn::make('club_only')
|
||||
->boolean()
|
||||
->label('Club Only')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTableActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('move_up')
|
||||
->label('')
|
||||
->icon('heroicon-m-chevron-up')
|
||||
->color('gray')
|
||||
->tooltip('Move up')
|
||||
->action(fn (CatalogItem $record) => $this->nudgeRecord($record, 'up'))
|
||||
->visible(fn (CatalogItem $record) => $this->pageSearch === '' && $this->canMoveUp($record))
|
||||
->size('sm'),
|
||||
|
||||
FilamentAction::make('move_down')
|
||||
->label('')
|
||||
->icon('heroicon-m-chevron-down')
|
||||
->color('gray')
|
||||
->tooltip('Move down')
|
||||
->action(fn (CatalogItem $record) => $this->nudgeRecord($record, 'down'))
|
||||
->visible(fn (CatalogItem $record) => $this->pageSearch === '' && $this->canMoveDown($record))
|
||||
->size('sm'),
|
||||
|
||||
EditAction::make('edit')
|
||||
->label('Edit')
|
||||
->icon('heroicon-m-pencil-square')
|
||||
->modalHeading('Edit catalog item')
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalWidth('md')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('cost_credits')->label('Credits')->numeric()->minValue(0)->required(),
|
||||
Forms\Components\TextInput::make('cost_points')->label('Points')->numeric()->minValue(0)->required(),
|
||||
Forms\Components\TextInput::make('points_type')->label('Type')->numeric()->minValue(0)->maxValue(999)->maxLength(50),
|
||||
Forms\Components\TextInput::make('amount')->label('Amount')->numeric()->minValue(1)->default(1)->required(),
|
||||
Forms\Components\TextInput::make('order_number')
|
||||
->label('Order')
|
||||
->numeric()
|
||||
->minValue(-1)
|
||||
->step(1)
|
||||
->helperText('Use -1 to lock, or a non-negative number to sort (lower = earlier).')
|
||||
->required(),
|
||||
Forms\Components\Toggle::make('club_only')->label('Club only'),
|
||||
])
|
||||
->fillForm(fn (CatalogItem $record) => [
|
||||
'cost_credits' => $record->cost_credits,
|
||||
'cost_points' => $record->cost_points,
|
||||
'points_type' => $record->points_type,
|
||||
'amount' => $record->amount,
|
||||
'order_number' => $record->order_number,
|
||||
'club_only' => $record->club_only === '1',
|
||||
])
|
||||
->action(function (CatalogItem $record, array $data): void {
|
||||
$record->update([
|
||||
'cost_credits' => (int) $data['cost_credits'],
|
||||
'cost_points' => (int) $data['cost_points'],
|
||||
'points_type' => $data['points_type'] ?? null,
|
||||
'amount' => (int) $data['amount'],
|
||||
'order_number' => (int) $data['order_number'],
|
||||
'club_only' => empty($data['club_only']) ? '0' : '1',
|
||||
]);
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()->title('Item updated')->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('editPage')
|
||||
->label('Edit page')
|
||||
->modalHeading(function (array $arguments): string {
|
||||
$page = CatalogPage::find($arguments['pageId'] ?? null);
|
||||
|
||||
return $page ? 'Edit: ' . $page->caption : 'Edit page';
|
||||
})
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalWidth('3xl')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('caption')->label('Name')->maxLength(128)->required(),
|
||||
|
||||
Forms\Components\TextInput::make('caption_save')
|
||||
->label('Name TAG')
|
||||
->maxLength(25)
|
||||
->nullable(),
|
||||
|
||||
Forms\Components\TextInput::make('order_num')
|
||||
->label('Order')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->step(1)
|
||||
->required()
|
||||
->helperText('Lower number appears earlier in the menu.'),
|
||||
|
||||
Forms\Components\TextInput::make('icon_image')
|
||||
->label('Icon number')
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->required()
|
||||
->default(1),
|
||||
])
|
||||
->fillForm(function (array $arguments): array {
|
||||
$page = CatalogPage::find($arguments['pageId'] ?? null);
|
||||
|
||||
return [
|
||||
'caption' => $page?->caption ?? '',
|
||||
'caption_save' => $page?->caption_save ?? '',
|
||||
'order_num' => $page?->order_num ?? 1,
|
||||
'icon_image' => $page?->icon_image ?? 1,
|
||||
];
|
||||
})
|
||||
->action(function (array $data, array $arguments): void {
|
||||
$page = CatalogPage::find($arguments['pageId'] ?? null);
|
||||
if (! $page) {
|
||||
Notification::make()->title('Page not found')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$icon = max(1, (int) ($data['icon_image'] ?: 1));
|
||||
|
||||
$page->update([
|
||||
'caption' => $data['caption'],
|
||||
'caption_save' => $data['caption_save'] ?? '',
|
||||
'order_num' => (int) ($data['order_num'] ?? 1),
|
||||
'icon_image' => $icon,
|
||||
]);
|
||||
|
||||
$this->selectPage($page->id);
|
||||
Notification::make()->title('Page updated')->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function openEditPage(int $pageId): void
|
||||
{
|
||||
$this->mountAction('editPage', ['pageId' => $pageId]);
|
||||
}
|
||||
|
||||
public function moveItemToPage(int $itemId, int $targetPageId): void
|
||||
{
|
||||
$this->moveItemsToPage((string) $itemId, $targetPageId);
|
||||
}
|
||||
|
||||
public function moveItemsToPage(string $itemIdsCsv, int $targetPageId): void
|
||||
{
|
||||
$target = CatalogPage::find($targetPageId);
|
||||
|
||||
$ids = collect(explode(',', $itemIdsCsv))
|
||||
->map(fn ($v) => (int) trim($v))
|
||||
->filter(fn ($v) => $v > 0)
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if (empty($ids) || ! $target) {
|
||||
Notification::make()->title('Move failed')->body('No items selected or target page not found.')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($ids, $targetPageId) {
|
||||
$maxOrder = (int) (CatalogItem::where('page_id', $targetPageId)->max('order_number') ?? 0);
|
||||
|
||||
foreach ($ids as $i => $id) {
|
||||
CatalogItem::whereKey($id)->update([
|
||||
'page_id' => $targetPageId,
|
||||
'order_number' => $maxOrder + 1 + $i,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
$this->selectedItemIds = [];
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->title('Items moved')
|
||||
->body('Moved ' . count($ids) . ' item(s) to: ' . ($target->caption ?? ('#' . $targetPageId)))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function buildFurniIconUrl(string $catalogName): string
|
||||
{
|
||||
$base = $this->getFurniIconBasePath();
|
||||
$safeName = str_replace('*', '_', $catalogName);
|
||||
$path = rtrim($base, '/') . '/' . $safeName . '_icon.png';
|
||||
|
||||
if (preg_match('#^(https?:)?//#', $path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return asset($path);
|
||||
}
|
||||
|
||||
protected function getFurniIconBasePath(): string
|
||||
{
|
||||
$setting = WebsiteSetting::where('key', 'furniture_icons_path')->first();
|
||||
|
||||
return $setting && $setting->value ? rtrim($setting->value, '/') : '/images/furniture';
|
||||
}
|
||||
|
||||
protected function getCatalogIconBasePath(): string
|
||||
{
|
||||
$setting = WebsiteSetting::where('key', 'catalog_icons_path')->first();
|
||||
|
||||
return $setting && $setting->value ? rtrim($setting->value, '/') : '/gamedata/c_images/catalogue';
|
||||
}
|
||||
|
||||
protected function buildCatalogIconUrl(int $iconImage): string
|
||||
{
|
||||
$iconImage = $iconImage > 0 ? $iconImage : 1;
|
||||
$base = $this->getCatalogIconBasePath();
|
||||
$path = $base . '/icon_' . $iconImage . '.png';
|
||||
|
||||
if (preg_match('#^(https?:)?//#', $path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return asset($path);
|
||||
}
|
||||
|
||||
public function reorderItems(array $orderedIds): void
|
||||
{
|
||||
if (filled($this->pageSearch)) {
|
||||
Notification::make()
|
||||
->title('Ordering disabled in search mode')
|
||||
->body('You cannot reorder items while viewing search results.')
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->selectedPage?->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($orderedIds) {
|
||||
foreach ($orderedIds as $index => $id) {
|
||||
CatalogItem::whereKey($id)->update([
|
||||
'order_number' => ($index + 1) * 10,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->normalizeOrderForSelectedPage();
|
||||
|
||||
Notification::make()
|
||||
->title('Items reordered')
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
protected function getTableHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('massEdit')
|
||||
->label('Mass edit selected')
|
||||
->icon('heroicon-m-pencil-square')
|
||||
->color('primary')
|
||||
->disabled(fn () => $this->selectedItemIds === [])
|
||||
->modalHeading('Edit selected catalog items')
|
||||
->modalSubmitActionLabel('Apply changes')
|
||||
->modalWidth('lg')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('cost_credits')->label('Credits')->numeric()->minValue(0)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('cost_points')->label('Points')->numeric()->minValue(0)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('points_type')->label('Type (points_type)')->numeric()->minValue(0)->maxValue(999)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('amount')->label('Amount')->numeric()->minValue(1)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('order_number')->label('Order')->numeric()->minValue(-1)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\Select::make('club_only')
|
||||
->label('Club only')
|
||||
->options(['' => '— No change —', '1' => 'Yes', '0' => 'No'])
|
||||
->native(false)
|
||||
->nullable()
|
||||
->default('')
|
||||
->helperText('Choose Yes/No, or leave as "No change"'),
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
$ids = collect($this->selectedItemIds)
|
||||
->filter(fn ($v) => (int) $v > 0)
|
||||
->map(fn ($v) => (int) $v)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if (empty($ids)) {
|
||||
Notification::make()->title('No items selected')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
|
||||
if ($data['cost_credits'] !== null && $data['cost_credits'] !== '') {
|
||||
$updates['cost_credits'] = (int) $data['cost_credits'];
|
||||
}
|
||||
if ($data['cost_points'] !== null && $data['cost_points'] !== '') {
|
||||
$updates['cost_points'] = (int) $data['cost_points'];
|
||||
}
|
||||
if ($data['points_type'] !== null && $data['points_type'] !== '') {
|
||||
$updates['points_type'] = (int) $data['points_type'];
|
||||
}
|
||||
if ($data['amount'] !== null && $data['amount'] !== '') {
|
||||
$updates['amount'] = (int) $data['amount'];
|
||||
}
|
||||
if ($data['order_number'] !== null && $data['order_number'] !== '') {
|
||||
$updates['order_number'] = (int) $data['order_number'];
|
||||
}
|
||||
if ($data['club_only'] !== null && $data['club_only'] !== '') {
|
||||
$updates['club_only'] = $data['club_only'] === '1' ? '1' : '0';
|
||||
}
|
||||
|
||||
if ($updates === []) {
|
||||
Notification::make()->title('Nothing to update')->body('Fill at least one field to apply to the selected items.')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CatalogItem::whereIn('id', $ids)->update($updates);
|
||||
|
||||
$count = count($ids);
|
||||
$this->resetTable();
|
||||
$this->selectedItemIds = [];
|
||||
|
||||
Notification::make()->title('Updated items')->body("Applied changes to {$count} item(s).")->success()->send();
|
||||
}),
|
||||
FilamentAction::make('updateOrder')
|
||||
->label('Update Order')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->color('secondary')
|
||||
->visible(fn () => $this->selectedPage && $this->pageSearch === '')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Confirm Update Order')
|
||||
->modalDescription('This will save the current item order (as currently sorted) into the database. Continue?')
|
||||
->modalSubmitActionLabel('Update Order')
|
||||
->action(function (): void {
|
||||
if (! $this->selectedPage?->id) {
|
||||
Notification::make()->title('No page selected')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->pageSearch !== '') {
|
||||
Notification::make()
|
||||
->title('Disabled in search mode')
|
||||
->body('Cannot update order while search results are active.')
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sortColumn = $this->getTableSortColumn();
|
||||
$sortDirection = $this->getTableSortDirection() ?? 'asc';
|
||||
|
||||
$query = $this->getTableQuery();
|
||||
|
||||
if ($sortColumn) {
|
||||
$query->orderBy($sortColumn, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('order_number')->orderBy('id');
|
||||
}
|
||||
|
||||
$items = $query->get(['id']);
|
||||
|
||||
if ($items->isEmpty()) {
|
||||
Notification::make()->title('No items')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items->values() as $index => $item) {
|
||||
CatalogItem::whereKey($item->id)->update([
|
||||
'order_number' => ($index + 1) * 10,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()->title('Order updated')->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CatalogEditors\CatalogEditorResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewCatalogEditor extends ViewRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = CatalogEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogPrivates;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogPrivates\Pages\ManageChatlogPrivates;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\ChatlogPrivate;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ChatlogPrivateResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = ChatlogPrivate::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
|
||||
|
||||
public static string $translateIdentifier = 'chatlog-private';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/chatlog-private';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('sender')
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($record) => $record->sender?->username)
|
||||
->label(__('filament::resources.inputs.sender')),
|
||||
|
||||
TextInput::make('receiver')
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($record) => $record->receiver?->username)
|
||||
->label(__('filament::resources.inputs.receiver')),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('filament::resources.inputs.message'))
|
||||
->columnSpanFull()
|
||||
->disabled(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->columns(self::getTable())
|
||||
->filters([])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
public static function getTable(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('sender.username')
|
||||
->label(__('filament::resources.columns.sender'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('receiver.username')
|
||||
->label(__('filament::resources.columns.receiver'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('message')
|
||||
->label(__('filament::resources.columns.message'))
|
||||
->limit(40)
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('timestamp')
|
||||
->label(__('filament::resources.columns.executed_at'))
|
||||
->dateTime('Y-m-d H:i')
|
||||
->toggleable(),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageChatlogPrivates::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogPrivates\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogPrivates\ChatlogPrivateResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageChatlogPrivates extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = ChatlogPrivateResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogRooms;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogRooms\Pages\ManageChatlogRooms;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\ChatlogRoom;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ChatlogRoomResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = ChatlogRoom::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
|
||||
|
||||
public static string $translateIdentifier = 'chatlog-rooms';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/chatlog-room';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('room')
|
||||
->label(__('filament::resources.inputs.room'))
|
||||
->formatStateUsing(fn ($record) => $record->room?->name)
|
||||
->columnSpanFull()
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('sender')
|
||||
->label(__('filament::resources.inputs.sender'))
|
||||
->formatStateUsing(fn ($record) => $record->sender?->username)
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('receiver')
|
||||
->label(__('filament::resources.inputs.receiver'))
|
||||
->formatStateUsing(fn ($record) => $record->receiver?->username)
|
||||
->disabled(),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('filament::resources.inputs.message'))
|
||||
->columnSpanFull()
|
||||
->disabled(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->columns(self::getTable())
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
public static function getTable(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('room.name')
|
||||
->label(__('filament::resources.columns.room'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('sender.username')
|
||||
->label(__('filament::resources.columns.sender'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('receiver.username')
|
||||
->label(__('filament::resources.columns.receiver'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('message')
|
||||
->label(__('filament::resources.columns.message'))
|
||||
->limit(40)
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('timestamp')
|
||||
->label(__('filament::resources.columns.executed_at'))
|
||||
->dateTime('Y-m-d H:i')
|
||||
->toggleable(),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageChatlogRooms::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogRooms\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogRooms\ChatlogRoomResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageChatlogRooms extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = ChatlogRoomResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CommandLogs;
|
||||
|
||||
use App\Filament\Resources\Hotel\CommandLogs\Pages\ManageCommandLogs;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\CommandLog;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CommandLogResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = CommandLog::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-bottom-center-text';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
|
||||
|
||||
public static string $translateIdentifier = 'command-logs';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'logs/commands';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->columns([
|
||||
TextColumn::make('user.username')
|
||||
->label(__('filament::resources.columns.username'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('command')
|
||||
->label(__('filament::resources.columns.command'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('succes')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'yes' => 'primary',
|
||||
'no' => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->label(__('filament::resources.columns.success'))
|
||||
->formatStateUsing(fn (string $state): string => __("filament::resources.options.{$state}")),
|
||||
|
||||
TextColumn::make('timestamp')
|
||||
->label(__('filament::resources.columns.executed_at'))
|
||||
->dateTime('Y-m-d H:i')
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('succes')
|
||||
->label(__('filament::resources.filters.success'))
|
||||
->options([
|
||||
'yes' => __('filament::resources.options.yes'),
|
||||
'no' => __('filament::resources.options.no'),
|
||||
]),
|
||||
])
|
||||
->recordActions([])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageCommandLogs::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CommandLogs\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CommandLogs\CommandLogResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageCommandLogs extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = CommandLogResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPrimaryKey(): string
|
||||
{
|
||||
return 'timestamp';
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CustomQueryBuilder extends Builder
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Call the parent constructor with a dummy query
|
||||
parent::__construct(app('db')->query());
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get($columns = ['*']): Collection
|
||||
{
|
||||
return collect(); // Return an empty collection
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\Pages\CreateEmulatorSetting;
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\Pages\EditEmulatorSetting;
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\Pages\ListEmulatorSettings;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\EmulatorSetting;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EmulatorSettingResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = EmulatorSetting::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-adjustments-horizontal';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
public static string $translateIdentifier = 'emulator-settings';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/emulator-settings';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('key')
|
||||
->label(__('filament::resources.inputs.key'))
|
||||
->required()
|
||||
->maxLength(100)
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
TextInput::make('value')
|
||||
->label(__('filament::resources.inputs.value'))
|
||||
->required()
|
||||
->maxLength(512),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(__('filament::resources.columns.key'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('value')
|
||||
->label(__('filament::resources.columns.value'))
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListEmulatorSettings::route('/'),
|
||||
'create' => CreateEmulatorSetting::route('/create'),
|
||||
'edit' => EditEmulatorSetting::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\EmulatorSettingResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateEmulatorSetting extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorSettingResource::class;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\EmulatorSettingResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditEmulatorSetting extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorSettingResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\EmulatorSettingResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListEmulatorSettings extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorSettingResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorTexts;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorTexts\Pages\ManageEmulatorTexts;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\EmulatorText;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EmulatorTextResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = EmulatorText::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clipboard-document-list';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/emulator-texts';
|
||||
|
||||
public static string $translateIdentifier = 'emulator-texts';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('key')
|
||||
->label(__('filament::resources.inputs.key'))
|
||||
->required()
|
||||
->maxLength(100)
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
TextInput::make('value')
|
||||
->label(__('filament::resources.inputs.value'))
|
||||
->required()
|
||||
->maxLength(512),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(__('filament::resources.columns.key'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('value')
|
||||
->label(__('filament::resources.columns.value'))
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageEmulatorTexts::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorTexts\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorTexts\EmulatorTextResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageEmulatorTexts extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorTextResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make('create'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getPrimaryKey(): string
|
||||
{
|
||||
return 'key';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\Pages\CreateOpenPosition;
|
||||
use App\Filament\Resources\Hotel\OpenPositions\Pages\EditOpenPosition;
|
||||
use App\Filament\Resources\Hotel\OpenPositions\Pages\ListOpenPositions;
|
||||
use App\Models\Community\Staff\WebsiteOpenPosition;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OpenPositionResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = WebsiteOpenPosition::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-briefcase';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
ToggleButtons::make('position_kind')
|
||||
->label('Type')
|
||||
->inline()
|
||||
->options([
|
||||
'rank' => 'Ranks',
|
||||
'team' => 'Teams',
|
||||
])
|
||||
->default('rank')
|
||||
->required()
|
||||
->live()
|
||||
->grouped(),
|
||||
|
||||
Select::make('permission_id')
|
||||
->label('Rank')
|
||||
->relationship('permission', 'rank_name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->placeholder('Select a rank')
|
||||
->visible(fn (Get $get) => $get('position_kind') === 'rank')
|
||||
->required(fn (Get $get) => $get('position_kind') === 'rank')
|
||||
->dehydrated(fn (Get $get) => $get('position_kind') === 'rank')
|
||||
->unique(
|
||||
table: WebsiteOpenPosition::class,
|
||||
column: 'permission_id',
|
||||
ignoreRecord: true,
|
||||
),
|
||||
|
||||
Select::make('team_id')
|
||||
->label('Team')
|
||||
->relationship('team', 'rank_name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->placeholder('Select a team')
|
||||
->visible(fn (Get $get) => $get('position_kind') === 'team')
|
||||
->required(fn (Get $get) => $get('position_kind') === 'team')
|
||||
->dehydrated(fn (Get $get) => $get('position_kind') === 'team')
|
||||
->unique(
|
||||
table: WebsiteOpenPosition::class,
|
||||
column: 'team_id',
|
||||
ignoreRecord: true,
|
||||
)
|
||||
->placeholder('Select a rank'),
|
||||
|
||||
Textarea::make('description')
|
||||
->label('Position Description')
|
||||
->required()
|
||||
->maxLength(65535)
|
||||
->columnSpanFull(),
|
||||
|
||||
DateTimePicker::make('apply_from')
|
||||
->label('Application Start Date')
|
||||
->nullable(),
|
||||
|
||||
DateTimePicker::make('apply_to')
|
||||
->label('Application End Date')
|
||||
->nullable(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('position_kind')
|
||||
->label('Type')
|
||||
->badge()
|
||||
->formatStateUsing(fn (string $state) => ucfirst($state))
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('permission.rank_name')
|
||||
->label('Rank')
|
||||
->toggleable(isToggledHiddenByDefault: false),
|
||||
|
||||
TextColumn::make('team.rank_name')
|
||||
->label('Team')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
TextColumn::make('description')
|
||||
->label('Description')
|
||||
->limit(50)
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('apply_from')
|
||||
->label('Apply From')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('apply_to')
|
||||
->label('Apply To')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Delete Open Position')
|
||||
->modalDescription('This will also delete related rank-based staff applications (if any). Are you sure?')
|
||||
->modalSubmitActionLabel('Yes, delete')
|
||||
->successNotification(
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Open Position Deleted')
|
||||
->body('The open position and its related staff applications (if rank-based) have been deleted successfully.'),
|
||||
),
|
||||
])
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Delete Open Positions')
|
||||
->modalDescription('This will also delete related rank-based staff applications (if any). Are you sure?')
|
||||
->modalSubmitActionLabel('Yes, delete')
|
||||
->successNotification(
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Open Positions Deleted')
|
||||
->body('The selected open positions and their related applications (if rank-based) have been deleted successfully.'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListOpenPositions::route('/'),
|
||||
'create' => CreateOpenPosition::route('/create'),
|
||||
'edit' => EditOpenPosition::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\OpenPositionResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateOpenPosition extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = OpenPositionResource::class;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\OpenPositionResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOpenPosition extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = OpenPositionResource::class;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\OpenPositionResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOpenPositions extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = OpenPositionResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\StaffApplications\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\StaffApplications\StaffApplicationResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditStaffApplication extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = StaffApplicationResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\StaffApplications\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\StaffApplications\StaffApplicationResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListStaffApplications extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = StaffApplicationResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\StaffApplications;
|
||||
|
||||
use App\Filament\Resources\Hotel\StaffApplications\Pages\ListStaffApplications;
|
||||
use App\Models\Community\Staff\WebsiteStaffApplications;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class StaffApplicationResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = WebsiteStaffApplications::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user-group';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
public static function canCreate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Select::make('user_id')
|
||||
->relationship('user', 'username')
|
||||
->required()
|
||||
->searchable(),
|
||||
|
||||
Select::make('rank_id')
|
||||
->label('Rank')
|
||||
->relationship('rank', 'rank_name')
|
||||
->searchable()
|
||||
->nullable(),
|
||||
|
||||
Select::make('team_id')
|
||||
->label('Team')
|
||||
->relationship('team', 'rank_name')
|
||||
->searchable()
|
||||
->nullable(),
|
||||
|
||||
Textarea::make('content')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('user.username')
|
||||
->label('User')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('applied_for')
|
||||
->label('Applied For')
|
||||
->state(fn (WebsiteStaffApplications $record) => $record->team_id
|
||||
? ($record->team->rank_name ?? '-')
|
||||
: ($record->rank->rank_name ?? '-'))
|
||||
->searchable(query: function ($query, string $search) {
|
||||
$query
|
||||
->orWhereHas('rank', fn ($q) => $q->where('rank_name', 'like', "%{$search}%"))
|
||||
->orWhereHas('team', fn ($q) => $q->where('rank_name', 'like', "%{$search}%"));
|
||||
})
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->formatStateUsing(fn (?string $state) => ucfirst($state ?? 'pending'))
|
||||
->color(fn (?string $state) => [
|
||||
'pending' => 'warning',
|
||||
'approved' => 'success',
|
||||
'rejected' => 'danger',
|
||||
][$state ?? 'pending'] ?? 'gray')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('content')->limit(50)->wrap()->sortable(),
|
||||
TextColumn::make('created_at')->dateTime()->sortable(),
|
||||
TextColumn::make('updated_at')->dateTime()->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
Action::make('approveTeam')
|
||||
->label('Approve to Team')
|
||||
->icon('heroicon-o-check-circle')
|
||||
->color('success')
|
||||
->visible(fn (WebsiteStaffApplications $r) => filled($r->team_id) && ($r->status === 'pending' || is_null($r->status)))
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Approve to Team')
|
||||
->modalDescription(function (WebsiteStaffApplications $r): string {
|
||||
$user = $r->user;
|
||||
$targetTeam = optional($r->team)->rank_name ?? '—';
|
||||
$currentTeam = optional($user?->team)->rank_name;
|
||||
|
||||
if ($currentTeam && $user?->team_id !== $r->team_id) {
|
||||
return "This user is currently in '{$currentTeam}'. Approving will move them to '{$targetTeam}'. Continue?";
|
||||
}
|
||||
|
||||
return "Approve this application and assign the user to '{$targetTeam}'?";
|
||||
})
|
||||
->action(function (WebsiteStaffApplications $r) {
|
||||
$user = $r->user;
|
||||
$team = $r->team;
|
||||
|
||||
if (! $user || ! $team) {
|
||||
Notification::make()
|
||||
->danger()->title('Unable to approve')
|
||||
->body('Missing user or team on this application.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int) $user->team_id !== (int) $team->id) {
|
||||
$user->update(['team_id' => $team->id]);
|
||||
}
|
||||
|
||||
$r->update([
|
||||
'status' => 'approved',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
'rejected_by' => null,
|
||||
'rejected_at' => null,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->success()->title('Approved')
|
||||
->body("{$user->username} has been added to '{$team->rank_name}'.")
|
||||
->send();
|
||||
}),
|
||||
|
||||
Action::make('rejectTeam')
|
||||
->label('Reject')
|
||||
->icon('heroicon-o-x-circle')
|
||||
->color('danger')
|
||||
->visible(fn (WebsiteStaffApplications $r) => filled($r->team_id) && in_array($r->status, ['pending', 'approved', null], true))
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Reject Application')
|
||||
->modalDescription(function (WebsiteStaffApplications $r): string {
|
||||
$user = $r->user;
|
||||
$teamName = optional($r->team)->rank_name ?? '—';
|
||||
|
||||
if ($r->status === 'approved') {
|
||||
return "This will mark the application as rejected and remove {$user->username} from '{$teamName}' (if still on it). Continue?";
|
||||
}
|
||||
|
||||
return 'This will mark the application as rejected. Continue?';
|
||||
})
|
||||
->action(function (WebsiteStaffApplications $r) {
|
||||
$user = $r->user;
|
||||
$team = $r->team;
|
||||
|
||||
if (! $user || ! $team) {
|
||||
Notification::make()
|
||||
->danger()->title('Unable to reject')
|
||||
->body('Missing user or team on this application.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($r->status === 'approved' && (int) $user->team_id === (int) $team->id) {
|
||||
$user->update(['team_id' => null]);
|
||||
}
|
||||
|
||||
$r->update([
|
||||
'status' => 'rejected',
|
||||
'rejected_by' => auth()->id(),
|
||||
'rejected_at' => now(),
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->success()->title('Rejected')
|
||||
->body($r->status === 'approved'
|
||||
? "{$user->username} has been removed from '{$team->rank_name}' and the application marked as rejected."
|
||||
: 'Application has been marked as rejected.')
|
||||
->send();
|
||||
}),
|
||||
|
||||
Action::make('reopen')
|
||||
->label('Re-open')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->color('warning')
|
||||
->visible(fn (WebsiteStaffApplications $r) => $r->status === 'rejected')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Re-open Application')
|
||||
->modalDescription('This will set the application status back to pending.')
|
||||
->action(function (WebsiteStaffApplications $r) {
|
||||
$r->update([
|
||||
'status' => 'pending',
|
||||
'rejected_by' => null,
|
||||
'rejected_at' => null,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->success()->title('Re-opened')
|
||||
->body('Application status set to pending.')
|
||||
->send();
|
||||
}),
|
||||
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListStaffApplications::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\WebsiteAds\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\WebsiteAds\WebsiteAdResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateWebsiteAd extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = WebsiteAdResource::class;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\WebsiteAds\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\WebsiteAds\WebsiteAdResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditWebsiteAd extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = WebsiteAdResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\WebsiteAds\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\WebsiteAds\WebsiteAdResource;
|
||||
use App\Models\WebsiteAd;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class ListWebsiteAds extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = WebsiteAdResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Create new ADS')
|
||||
->color('success'),
|
||||
Action::make('importAdsData')
|
||||
->label('Import ADS Images from folder')
|
||||
->color('info')
|
||||
->action(function () {
|
||||
Artisan::call('import:ads-data');
|
||||
session()->flash('success', 'ADS data imported successfully!');
|
||||
})
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Import ADS Data')
|
||||
->modalDescription('Are you sure you want to import ADS data? This action cannot be undone.')
|
||||
->modalButton('Yes, import data'),
|
||||
Action::make('emptyTable')
|
||||
->label('Empty Database Table')
|
||||
->color('danger')
|
||||
->action(function () {
|
||||
WebsiteAd::truncate();
|
||||
session()->flash('success', 'The table has been emptied successfully!');
|
||||
})
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Empty Table')
|
||||
->modalDescription('Are you sure you want to empty the table? This action cannot be undone and will delete all records.')
|
||||
->modalButton('Yes, empty table'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\WebsiteAds;
|
||||
|
||||
use App\Filament\Resources\Hotel\WebsiteAds\Pages\CreateWebsiteAd;
|
||||
use App\Filament\Resources\Hotel\WebsiteAds\Pages\ListWebsiteAds;
|
||||
use App\Models\WebsiteAd;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\Layout\Stack;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class WebsiteAdResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = WebsiteAd::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = 'ADS Images';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-sparkles';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
FileUpload::make('image')
|
||||
->label('Image')
|
||||
->disk('ads')
|
||||
->preserveFilenames()
|
||||
->image()
|
||||
->rules(['required', 'image', 'mimes:jpeg,png,jpg,gif'])
|
||||
->validationMessages([
|
||||
'required' => 'Please upload an image.', 'image' => 'The file must be a valid image.', 'mimes' => 'Only JPEG, PNG, JPG, and GIF images are allowed.'])
|
||||
->required()
|
||||
->getUploadedFileNameForStorageUsing(
|
||||
fn (TemporaryUploadedFile $file): string => strtolower(str_replace([' ', '-', 'æ', 'ø', 'å'], ['_', '_', 'ae', 'oe', 'aa'], $file->getClientOriginalName())),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Stack::make([
|
||||
ImageColumn::make('image_url')
|
||||
->label('')
|
||||
->extraAttributes(['style' => 'image-rendering: pixelated'])
|
||||
->size(125),
|
||||
TextColumn::make('image')
|
||||
->label('')
|
||||
->alignCenter()
|
||||
->searchable(),
|
||||
]),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime(),
|
||||
])
|
||||
->filters([
|
||||
])
|
||||
->recordActions([
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->searchable();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListWebsiteAds::route('/'),
|
||||
'create' => CreateWebsiteAd::route('/create'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\WordFilters\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\WordFilters\WordFilterResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageWordFilters extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = WordFilterResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\WordFilters;
|
||||
|
||||
use App\Filament\Resources\Hotel\WordFilters\Pages\ManageWordFilters;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\Wordfilter;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class WordFilterResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = Wordfilter::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-eye-slash';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/wordfilters';
|
||||
|
||||
public static string $translateIdentifier = 'word-filters';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('key')
|
||||
->label(__('filament::resources.inputs.key'))
|
||||
->maxLength(256)
|
||||
->unique('wordfilter', 'key', ignoreRecord: true)
|
||||
->required(),
|
||||
|
||||
TextInput::make('replacement')
|
||||
->label(__('filament::resources.inputs.replacement'))
|
||||
->maxLength(16)
|
||||
->required(),
|
||||
|
||||
Select::make('hide')
|
||||
->native(false)
|
||||
->label(__('filament::resources.inputs.hideable'))
|
||||
->default('0')
|
||||
->options([
|
||||
'0' => __('filament::resources.options.no'),
|
||||
'1' => __('filament::resources.options.yes'),
|
||||
]),
|
||||
|
||||
Select::make('report')
|
||||
->native(false)
|
||||
->label(__('filament::resources.inputs.reportable'))
|
||||
->default('0')
|
||||
->options([
|
||||
'0' => __('filament::resources.options.no'),
|
||||
'1' => __('filament::resources.options.yes'),
|
||||
]),
|
||||
|
||||
TextInput::make('mute')
|
||||
->label(__('filament::resources.inputs.mute_time'))
|
||||
->columnSpanFull()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(__('filament::resources.columns.key'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('replacement')
|
||||
->label(__('filament::resources.columns.replacement'))
|
||||
->searchable(),
|
||||
|
||||
IconColumn::make('hide')
|
||||
->label(__('filament::resources.columns.hideable'))
|
||||
->icon(fn (string $state): string => $state === '0' ? 'heroicon-o-x-circle' : 'heroicon-o-check-circle')
|
||||
->colors([
|
||||
'danger' => '0',
|
||||
'success' => '1',
|
||||
]),
|
||||
|
||||
IconColumn::make('report')
|
||||
->label(__('filament::resources.columns.reportable'))
|
||||
->icon(fn (string $state): string => $state === '0' ? 'heroicon-o-x-circle' : 'heroicon-o-check-circle')
|
||||
->colors([
|
||||
'danger' => '0',
|
||||
'success' => '1',
|
||||
]),
|
||||
|
||||
TextColumn::make('mute')
|
||||
->label(__('filament::resources.columns.mute_time'))
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageWordFilters::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user