Initial commit

This commit is contained in:
root
2026-05-09 17:28:23 +02:00
commit 9d73f82529
5575 changed files with 281989 additions and 0 deletions
+241
View File
@@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
/**
* @phpstan-type AuthUser = \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable|null
*/
namespace App\Filament\Resources\Atom\Articles;
use App\Filament\Resources\Atom\Articles\Pages\CreateArticle;
use App\Filament\Resources\Atom\Articles\Pages\EditArticle;
use App\Filament\Resources\Atom\Articles\Pages\ListArticles;
use App\Filament\Resources\Atom\Articles\Pages\ViewArticle;
use App\Filament\Resources\Atom\Articles\RelationManagers\TagsRelationManager;
use App\Filament\Traits\TranslatableResource;
use App\Models\Articles\WebsiteArticle;
use Exception;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Facades\Auth;
class ArticleResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteArticle::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-newspaper';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/articles';
public static string $translateIdentifier = 'articles';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
Tabs::make('Main')
->tabs([
Tab::make(__('filament::resources.tabs.Home'))
->icon('heroicon-o-home')
->schema([
TextInput::make('title')
->label(__('filament::resources.inputs.title'))
->required()
->autocomplete()
->maxLength(255)
->columnSpan('full'),
TextInput::make('short_story')
->label(__('filament::resources.inputs.description'))
->required()
->maxLength(255)
->autocomplete()
->columnSpan('full'),
FileUpload::make('image')
->label(__('filament::resources.inputs.image'))
->directory('website_news_images')
->visibility('public'),
RichEditor::make('full_story')
->label(__('filament::resources.inputs.content'))
->required()
->columnSpan('full'),
Hidden::make('user_id')
->default(Auth::id() ?? 0),
]),
Tab::make(__('filament::resources.tabs.Configurations'))
->icon('heroicon-o-cog')
->schema([
Toggle::make('is_visible')
->label(__('filament::resources.inputs.visible'))
->onIcon('heroicon-s-check')
->offIcon('heroicon-s-x-mark')
->default(true)
->live()
->afterStateUpdated(function (string $operation, $state, $record) {
/** @var WebsiteArticle $record */
if ($operation !== 'edit' || ! $record instanceof WebsiteArticle) {
return;
}
try {
if ($state) {
$record->restore();
} else {
$record->delete();
}
} catch (Exception $e) {
report($e);
}
})
->formatStateUsing(function ($record) {
if (is_null($record)) {
return true;
}
return is_null($record->deleted_at);
}),
Toggle::make('can_comment')
->onIcon('heroicon-s-check')
->label(__('filament::resources.inputs.allow_comments'))
->default(true)
->offIcon('heroicon-s-x-mark'),
]),
])->columnSpanFull(),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->poll('60s')
->columns(static::getTable())
->filters([
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
DeleteAction::make(),
RestoreAction::make(),
ForceDeleteAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
RestoreBulkAction::make(),
ForceDeleteBulkAction::make(),
]);
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
ImageColumn::make('image')
->circular()
->extraAttributes(['style' => 'image-rendering: pixelated'])
->size(50)
->label(__('filament::resources.columns.image')),
TextColumn::make('title')
->label(__('filament::resources.columns.title'))
->searchable()
->limit(50),
TextColumn::make('user.username')
->searchable()
->label(__('filament::resources.columns.by')),
ToggleColumn::make('is_visible')
->label(__('filament::resources.columns.visible'))
->onIcon('heroicon-s-check')
->toggleable()
->state(fn ($record) => is_null($record->deleted_at))
->disabled(),
ToggleColumn::make('allow_comments')
->label(__('filament::resources.columns.allow_comments'))
->onIcon('heroicon-s-check')
->toggleable()
->disabled(),
];
}
#[\Override]
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
#[\Override]
public static function getRelations(): array
{
return [
TagsRelationManager::class,
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListArticles::route('/'),
'create' => CreateArticle::route('/create'),
'view' => ViewArticle::route('/{record}'),
'edit' => EditArticle::route('/{record}/edit'),
];
}
#[\Override]
public static function getGlobalSearchEloquentQuery(): Builder
{
return parent::getGlobalSearchEloquentQuery()->withTrashed();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use App\Models\Article;
use Filament\Resources\Pages\CreateRecord;
class CreateArticle extends CreateRecord
{
#[\Override]
protected static string $resource = ArticleResource::class;
protected function afterCreate(): void
{
/** @var null|Article $articleCreated */
$articleCreated = $this->getRecord();
if (! $articleCreated || ! $articleCreated->visible) {
return;
}
$articleCreated->createFollowersNotification();
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditArticle extends EditRecord
{
#[\Override]
protected static string $resource = ArticleResource::class;
#[\Override]
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListArticles extends ListRecords
{
#[\Override]
protected static string $resource = ArticleResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use App\Models\Article;
use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Support\Facades\Auth;
class ViewArticle extends ViewRecord
{
#[\Override]
protected static string $resource = ArticleResource::class;
#[\Override]
public function getHeaderActions(): array
{
return [
Action::make('Send Notification')
->label(__('Send notifications'))
->color('gray')
->visible(fn (Article $record) => $record->user_id === Auth::id())
->requiresConfirmation()
->action(function (Article $record) {
$record->createFollowersNotification();
}),
EditAction::make(),
];
}
}
@@ -0,0 +1,62 @@
<?php
namespace App\Filament\Resources\Atom\Articles\RelationManagers;
use App\Filament\Resources\Atom\Tags\TagResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\CreateAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\TextInput;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class TagsRelationManager extends RelationManager
{
use TranslatableResource;
#[\Override]
protected static string $relationship = 'tags';
#[\Override]
protected static ?string $recordTitleAttribute = 'name';
public static string $translateIdentifier = 'tags';
#[\Override]
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->columns(TagResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest())
->filters([
//
])
->headerActions([
CreateAction::make()
->schema(TagResource::getForm()),
AttachAction::make()->preloadRecordSelect(),
])
->recordActions([
ViewAction::make(),
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
@@ -0,0 +1,101 @@
<?php
namespace App\Filament\Resources\Atom\CameraWebs;
use App\Filament\Resources\Atom\CameraWebs\Pages\EditCameraWeb;
use App\Filament\Resources\Atom\CameraWebs\Pages\ListCameraWeb;
use App\Models\Miscellaneous\CameraWeb;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
class CameraWebResource extends Resource
{
#[\Override]
protected static ?string $model = CameraWeb::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-photo';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'camera-web';
#[\Override]
protected static ?string $pluralModelLabel = 'photos';
#[\Override]
protected static ?string $navigationLabel = 'Web Camera';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Toggle::make('visible')
->label(__('Visible'))
->default(true),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament::resources.columns.id'))
->sortable(),
TextColumn::make('user_id')
->label(__('filament::resources.columns.user_id')),
TextColumn::make('room_id')
->label(__('filament::resources.columns.room_id')),
TextColumn::make('timestamp')
->label(__('filament::resources.columns.created_at'))
->dateTime(),
ImageColumn::make('url')
->label(__('filament::resources.columns.image'))
->extraAttributes(['style' => 'image-rendering: pixelated'])
->size(125),
ToggleColumn::make('visible')
->label(__('Visible')),
])
->recordActions([
DeleteAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
#[\Override]
public static function getRelations(): array
{
return [
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListCameraWeb::route('/'),
'edit' => EditCameraWeb::route('/{record}/edit'),
];
}
#[\Override]
public static function canCreate(): bool
{
return false;
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\CameraWebs\Pages;
use App\Filament\Resources\Atom\CameraWebs\CameraWebResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditCameraWeb extends EditRecord
{
#[\Override]
protected static string $resource = CameraWebResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\CameraWebs\Pages;
use App\Filament\Resources\Atom\CameraWebs\CameraWebResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListCameraWeb extends ListRecords
{
#[\Override]
protected static string $resource = CameraWebResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -0,0 +1,117 @@
<?php
namespace App\Filament\Resources\Atom\CmsSettings;
use App\Filament\Resources\Atom\CmsSettings\Pages\ManageCmsSettings;
use App\Filament\Traits\TranslatableResource;
use App\Models\Miscellaneous\WebsiteSetting;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
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 CmsSettingResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteSetting::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-cpu-chip';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/cms-settings';
public static string $translateIdentifier = 'cms-settings';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make()
->schema([
TextInput::make('key')
->label(__('filament::resources.inputs.key'))
->maxLength(50)
->autocomplete()
->unique(ignoreRecord: true)
->required(),
TextInput::make('value')
->label(__('filament::resources.inputs.value'))
->required()
->maxLength(255)
->autocomplete(),
TextInput::make('comment')
->label(__('filament::resources.inputs.comment'))
->nullable()
->maxLength(255)
->autocomplete()
->columnSpanFull(),
])
->columns([
'sm' => 2,
]),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('key')
->label(__('filament::resources.columns.key'))
->searchable(),
TextColumn::make('value')
->label(__('filament::resources.columns.value'))
->searchable()
->limit(30),
TextColumn::make('comment')
->label(__('filament::resources.columns.comment'))
->toggleable()
->searchable()
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (! is_string($state) || strlen($state) <= $column->getCharacterLimit()) {
return null;
}
return $state;
})
->limit(60),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
// ...
]);
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ManageCmsSettings::route('/'),
];
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\CmsSettings\Pages;
use App\Filament\Resources\Atom\CmsSettings\CmsSettingResource;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ManageRecords;
use Illuminate\Support\Facades\Cache;
class ManageCmsSettings extends ManageRecords
{
#[\Override]
protected static string $resource = CmsSettingResource::class;
#[\Override]
protected function getActions(): array
{
return [
Action::make('reload_cache')
->label('Reload Cache')
->icon('heroicon-o-arrow-path')
->color('warning')
->requiresConfirmation()
->modalHeading('Reload Settings Cache')
->modalDescription('This will clear and reload the website settings cache. The cache will be automatically rebuilt on the next request.')
->modalSubmitActionLabel('Reload Cache')
->action(function () {
Cache::forget('website_settings');
Notification::make()
->success()
->title('Cache Cleared')
->body('Settings cache has been cleared successfully.')
->send();
}),
CreateAction::make(),
];
}
protected function getTableRecordsPerPageSelectOptions(): array
{
return [25, 50, 100];
}
}
@@ -0,0 +1,127 @@
<?php
namespace App\Filament\Resources\Atom;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages\CreateHelpQuestionCategory;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages\EditHelpQuestionCategory;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages\ListHelpQuestionCategories;
use App\Filament\Traits\TranslatableResource;
use App\Models\Help\WebsiteHelpCenterCategory;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
class HelpQuestionCategoryResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteHelpCenterCategory::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-folder';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Help Center';
#[\Override]
protected static ?string $slug = 'help/categories';
public static string $translateIdentifier = 'help-categories';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
TextInput::make('name')
->label(__('Name'))
->required()
->maxLength(255)
->helperText(__('Use :hotel for dynamic hotel name')),
Textarea::make('content')
->label(__('Content'))
->rows(8)
->helperText(__('Use :hotel for dynamic hotel name. Use <br/> for line breaks.')),
TextInput::make('image_url')
->label(__('Image URL'))
->maxLength(255),
TextInput::make('button_text')
->label(__('Button Text'))
->maxLength(255),
TextInput::make('button_url')
->label(__('Button URL'))
->maxLength(255),
ColorPicker::make('button_color')
->label(__('Button Color')),
ColorPicker::make('button_border_color')
->label(__('Button Border Color')),
Toggle::make('small_box')
->label(__('Small Box'))
->helperText(__('Show as small box on the right side')),
TextInput::make('position')
->label(__('Position'))
->numeric()
->default(0),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->columns(static::getTable());
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('ID'))
->sortable(),
TextColumn::make('name')
->label(__('Name'))
->searchable(),
TextColumn::make('button_text')
->label(__('Button Text')),
ToggleColumn::make('small_box')
->label(__('Small Box')),
TextColumn::make('position')
->label(__('Position'))
->sortable(),
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListHelpQuestionCategories::route('/'),
'create' => CreateHelpQuestionCategory::route('/create'),
'edit' => EditHelpQuestionCategory::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Resources\Pages\CreateRecord;
class CreateHelpQuestionCategory extends CreateRecord
{
#[\Override]
protected static string $resource = HelpQuestionCategoryResource::class;
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditHelpQuestionCategory extends EditRecord
{
#[\Override]
protected static string $resource = HelpQuestionCategoryResource::class;
#[\Override]
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListHelpQuestionCategories extends ListRecords
{
#[\Override]
protected static string $resource = HelpQuestionCategoryResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
protected function getTableReorderColumn(): ?string
{
return 'order';
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Resources\Pages\ViewRecord;
class ViewHelpQuestionCategory extends ViewRecord
{
#[\Override]
protected static string $resource = HelpQuestionCategoryResource::class;
}
@@ -0,0 +1,52 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\RelationManagers;
use App\Filament\Resources\Atom\HelpQuestionResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class QuestionsRelationManager extends RelationManager
{
use TranslatableResource;
#[\Override]
protected static string $relationship = 'questions';
#[\Override]
protected static ?string $recordTitleAttribute = 'title';
public static string $translateIdentifier = 'help-questions';
#[\Override]
protected static ?string $inverseRelationship = 'categories';
#[\Override]
public function form(Schema $schema): Schema
{
return $schema->components(HelpQuestionResource::getForm(true));
}
public function table(Table $table): Table
{
return $table->columns(HelpQuestionResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest())
->filters([
//
])
->headerActions([
AttachAction::make(),
])
->recordActions([
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace App\Filament\Resources\Atom;
use App\Filament\Resources\Atom\HelpQuestionResource\Pages\CreateHelpQuestion;
use App\Filament\Resources\Atom\HelpQuestionResource\Pages\EditHelpQuestion;
use App\Filament\Resources\Atom\HelpQuestionResource\Pages\ListHelpQuestions;
use App\Filament\Resources\Atom\HelpQuestionResource\Pages\ViewHelpQuestion;
use App\Filament\Resources\Atom\HelpQuestionResource\RelationManagers\CategoriesRelationManager;
use App\Filament\Traits\TranslatableResource;
use App\Models\Help\WebsiteHelpCenterTicket;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
class HelpQuestionResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteHelpCenterTicket::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Help Center';
#[\Override]
protected static ?string $slug = 'help/questions';
public static string $translateIdentifier = 'help-questions';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(bool $forRelationManager = false): array
{
return [
TextInput::make('title')
->label(__('Title'))
->required()
->maxLength(255),
Textarea::make('content')
->label(__('Content'))
->required(),
Select::make('category_id')
->label(__('Category'))
->relationship('category', 'name')
->required()
->visible(! $forRelationManager),
Select::make('user_id')
->label(__('User'))
->relationship('user', 'username')
->required(),
Toggle::make('open')
->label(__('Open'))
->default(true),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->columns(static::getTable());
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('ID'))
->sortable(),
TextColumn::make('title')
->label(__('Title'))
->searchable(),
TextColumn::make('user.username')
->label(__('User'))
->searchable(),
TextColumn::make('category.name')
->label(__('Category')),
ToggleColumn::make('open')
->label(__('Open')),
TextColumn::make('created_at')
->label(__('Created'))
->dateTime(),
];
}
#[\Override]
public static function getRelations(): array
{
return [
CategoriesRelationManager::class,
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListHelpQuestions::route('/'),
'create' => CreateHelpQuestion::route('/create'),
'view' => ViewHelpQuestion::route('/{record}'),
'edit' => EditHelpQuestion::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Resources\Pages\CreateRecord;
class CreateHelpQuestion extends CreateRecord
{
#[\Override]
protected static string $resource = HelpQuestionResource::class;
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditHelpQuestion extends EditRecord
{
#[\Override]
protected static string $resource = HelpQuestionResource::class;
#[\Override]
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListHelpQuestions extends ListRecords
{
#[\Override]
protected static string $resource = HelpQuestionResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Resources\Pages\ViewRecord;
class ViewHelpQuestion extends ViewRecord
{
#[\Override]
protected static string $resource = HelpQuestionResource::class;
}
@@ -0,0 +1,56 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionResource\RelationManagers;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\CreateAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Actions\EditAction;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class CategoriesRelationManager extends RelationManager
{
use TranslatableResource;
#[\Override]
protected static string $relationship = 'categories';
#[\Override]
protected static ?string $recordTitleAttribute = 'name';
public static string $translateIdentifier = 'help-question-categories';
#[\Override]
protected static ?string $inverseRelationship = 'questions';
#[\Override]
public function form(Schema $schema): Schema
{
return $schema->components(HelpQuestionCategoryResource::getForm());
}
public function table(Table $table): Table
{
return $table->columns(HelpQuestionCategoryResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest('id'))
->filters([
//
])
->headerActions([
CreateAction::make(),
AttachAction::make(),
])
->recordActions([
EditAction::make(),
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
@@ -0,0 +1,125 @@
<?php
namespace App\Filament\Resources\Atom\HousekeepingPermissions;
use App\Filament\Resources\Atom\HousekeepingPermissions\Pages\ListHousekeepingPermissions;
use App\Models\WebsiteHousekeepingPermission;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
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 HousekeepingPermissionResource extends Resource
{
#[\Override]
protected static ?string $model = WebsiteHousekeepingPermission::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/housekeeping-permissions';
#[\Override]
protected static ?string $navigationLabel = 'Housekeeping permissions';
public static string $translateIdentifier = 'housekeeping-permissions';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make()
->schema([
TextInput::make('permission')
->label(__('filament::resources.inputs.permission'))
->maxLength(50)
->autocomplete()
->unique(ignoreRecord: true)
->required(),
TextInput::make('min_rank')
->label(__('filament::resources.inputs.min_rank'))
->required()
->maxLength(255)
->autocomplete(),
TextInput::make('description')
->label(__('filament::resources.inputs.description'))
->nullable()
->maxLength(255)
->autocomplete()
->columnSpanFull(),
])
->columns([
'sm' => 2,
]),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'asc')
->columns([
TextColumn::make('permission')
->label(__('filament::resources.columns.permission'))
->searchable(),
TextColumn::make('min_rank')
->label(__('filament::resources.columns.min_rank'))
->searchable()
->limit(30),
TextColumn::make('description')
->label(__('filament::resources.columns.description'))
->toggleable()
->searchable()
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (! is_string($state) || strlen($state) <= $column->getCharacterLimit()) {
return null;
}
return $state;
})
->limit(60),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
//
]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListHousekeepingPermissions::route('/'),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\HousekeepingPermissions\Pages;
use App\Filament\Resources\Atom\HousekeepingPermissions\HousekeepingPermissionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListHousekeepingPermissions extends ListRecords
{
#[\Override]
protected static string $resource = HousekeepingPermissionResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
namespace App\Filament\Resources\Atom;
use App\Filament\Resources\Atom\NavigationResource\Pages\CreateNavigation;
use App\Filament\Resources\Atom\NavigationResource\Pages\EditNavigation;
use App\Filament\Resources\Atom\NavigationResource\Pages\ListNavigations;
use App\Filament\Resources\Atom\NavigationResource\RelationManagers\SubNavigationsRelationManager;
use App\Filament\Traits\TranslatableResource;
use App\Models\WebsiteNavigation;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class NavigationResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteNavigation::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bars-3';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/navigations';
public static string $translateIdentifier = 'navigations';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
TextInput::make('name')
->label(__('Name'))
->required()
->maxLength(255),
TextInput::make('url')
->label(__('URL'))
->required()
->maxLength(255),
TextInput::make('icon')
->label(__('Icon'))
->maxLength(255),
TextInput::make('order')
->label(__('Order'))
->numeric()
->default(0),
Select::make('parent_id')
->label(__('Parent Navigation'))
->relationship('parent', 'name')
->nullable(),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->columns(static::getTable());
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('ID'))
->sortable(),
TextColumn::make('name')
->label(__('Name'))
->searchable(),
TextColumn::make('url')
->label(__('URL')),
TextColumn::make('order')
->label(__('Order'))
->sortable(),
TextColumn::make('parent.name')
->label(__('Parent')),
];
}
#[\Override]
public static function getRelations(): array
{
return [
SubNavigationsRelationManager::class,
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListNavigations::route('/'),
'create' => CreateNavigation::route('/create'),
'edit' => EditNavigation::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\NavigationResource\Pages;
use App\Filament\Resources\Atom\NavigationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateNavigation extends CreateRecord
{
#[\Override]
protected static string $resource = NavigationResource::class;
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\NavigationResource\Pages;
use App\Filament\Resources\Atom\NavigationResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditNavigation extends EditRecord
{
#[\Override]
protected static string $resource = NavigationResource::class;
#[\Override]
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\NavigationResource\Pages;
use App\Filament\Resources\Atom\NavigationResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListNavigations extends ListRecords
{
#[\Override]
protected static string $resource = NavigationResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -0,0 +1,95 @@
<?php
namespace App\Filament\Resources\Atom\NavigationResource\RelationManagers;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\DissociateAction;
use Filament\Actions\DissociateBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
class SubNavigationsRelationManager extends RelationManager
{
#[\Override]
protected static string $relationship = 'subNavigations';
#[\Override]
protected static ?string $recordTitleAttribute = 'label';
#[\Override]
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('label')
->label(__('filament::resources.inputs.label'))
->columnSpanFull()
->required()
->maxLength(255),
TextInput::make('slug')
->label(__('filament::resources.inputs.slug')),
TextInput::make('order')
->numeric()
->minValue(0)
->default(0)
->label(__('filament::resources.columns.order')),
Toggle::make('visible')
->label(__('filament::resources.columns.visible')),
Toggle::make('new_tab')
->label(__('filament::resources.columns.new_tab')),
])
->columns([
'sm' => 2,
]);
}
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('label'),
TextColumn::make('slug')
->label(__('filament::resources.columns.slug')),
ToggleColumn::make('visible')
->label(__('filament::resources.columns.visible')),
ToggleColumn::make('new_tab')
->label(__('filament::resources.columns.new_tab')),
TextColumn::make('order')
->label(__('filament::resources.columns.order')),
])
->reorderable('order')
->filters([
//
])
->headerActions([
CreateAction::make(),
// Tables\Actions\AssociateAction::make(),
])
->recordActions([
EditAction::make(),
DissociateAction::make(),
DeleteAction::make(),
])
->toolbarActions([
DissociateBulkAction::make(),
DeleteBulkAction::make(),
]);
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Resources\Pages\CreateRecord;
class CreatePermission extends CreateRecord
{
#[\Override]
protected static string $resource = PermissionResource::class;
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditPermission extends EditRecord
{
#[\Override]
protected static string $resource = PermissionResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListPermissions extends ListRecords
{
#[\Override]
protected static string $resource = PermissionResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewPermission extends ViewRecord
{
#[\Override]
protected static string $resource = PermissionResource::class;
#[\Override]
public function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}
@@ -0,0 +1,276 @@
<?php
namespace App\Filament\Resources\Atom\Permissions;
use App\Filament\Resources\Atom\Permissions\Pages\CreatePermission;
use App\Filament\Resources\Atom\Permissions\Pages\EditPermission;
use App\Filament\Resources\Atom\Permissions\Pages\ListPermissions;
use App\Filament\Resources\Atom\Permissions\Pages\ViewPermission;
use App\Filament\Tables\Columns\HabboBadgeColumn;
use App\Filament\Traits\TranslatableResource;
use App\Models\Game\Permission;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Pages\Enums\SubNavigationPosition;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
// ensure Str is imported once
class PermissionResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = Permission::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/permissions';
public static string $translateIdentifier = 'permissions';
#[\Override]
protected static ?string $recordTitleAttribute = 'rank_name';
#[\Override]
protected static ?SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Top;
#[\Override]
public static function form(\Filament\Schemas\Schema $schema): \Filament\Schemas\Schema
{
/**
* @param string $name
* @param bool $needsSecondOption = false
*/
$groupedToggleButton = fn (string $name, bool $needsSecondOption = false): ToggleButtons => ToggleButtons::make($name)
->label(function () use ($name) {
$translationKey = "filament::resources.permissions.{$name}";
$translation = __($translationKey);
if ($translationKey == $translation) {
return $name;
}
return $translation;
})
->options(function () use ($needsSecondOption) {
$options = [
'0' => __('filament::resources.options.no'),
'1' => __('filament::resources.options.yes'),
];
if ($needsSecondOption) {
$options['2'] = __('filament::resources.options.rights');
}
return $options;
})
->icons(['0' => 'heroicon-o-check', '1' => 'heroicon-o-x-mark', '2' => 'heroicon-o-sparkles'])
->colors(['0' => 'danger', '1' => 'success'])
->grouped();
return $schema
->components([
Tabs::make('Main')
->tabs([
Tab::make(__('filament::resources.tabs.General Information'))
->schema([
TextInput::make('rank_name')
->label(__('filament::resources.inputs.name'))
->maxLength(25)
->required(),
TextInput::make('badge')
->label(__('filament::resources.inputs.badge_code'))
->maxLength(12)
->required(),
TextInput::make('level')
->label(__('filament::resources.inputs.level'))
->required(),
TextInput::make('room_effect')
->label(__('filament::resources.inputs.room_effect'))
->required(),
]),
Tab::make(__('filament::resources.tabs.In-game Permissions'))
->schema([
Section::make(__('filament::resources.sections.permissions.title'))
->description(new HtmlString(__('filament::resources.sections.permissions.description')))
->schema([
Grid::make()
->columns([
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema(function () use ($groupedToggleButton) {
$columns = Schema::getColumns('permissions');
$arcturusPermissions = collect($columns)->filter(function (array $column) {
$columnName = $column['name'] ?? null;
if (! $columnName) {
return false;
}
return str_starts_with($columnName, 'cmd')
|| str_starts_with($columnName, 'acc')
|| str_ends_with($columnName, 'cmd');
})->values();
return $arcturusPermissions->map(function (array $column) use ($groupedToggleButton) {
$columnName = $column['name'];
$needsSecondOption = $column['type_name'] == 'enum' && str_ends_with((string) $column['type'], "'2')");
return $groupedToggleButton($columnName, $needsSecondOption);
})->toArray();
}),
]),
]),
Tab::make(__('filament::resources.tabs.Configurations'))
->schema([
Grid::make(['default' => 2])
->schema([
Select::make('log_commands')
->label(__('filament::resources.inputs.log_commands'))
->columnSpanFull()
->options([
'0' => __('filament::resources.options.no'),
'1' => __('filament::resources.options.yes'),
]),
TextInput::make('prefix')
->label(__('filament::resources.inputs.prefix'))
->maxLength(5)
->required(),
ColorPicker::make('prefix_color')
->label(__('filament::resources.inputs.prefix_color'))
->required(),
Toggle::make('hidden_rank')
->label(__('filament::resources.inputs.is_hidden'))
->columnSpanFull(),
Section::make()
->schema([
Grid::make()
->columns([
'md' => 2,
])
->schema([
TextInput::make('auto_credits_amount')
->columnSpan(1)
->label(__('filament::resources.inputs.auto_credits_amount'))
->required(),
TextInput::make('auto_pixels_amount')
->label(__('filament::resources.inputs.auto_pixels_amount'))
->required(),
TextInput::make('auto_gotw_amount')
->label(__('filament::resources.inputs.auto_gotw_amount'))
->required(),
TextInput::make('auto_points_amount')
->label(__('filament::resources.inputs.auto_points_amount'))
->required(),
]),
]),
]),
]),
])
->columnSpanFull()
->persistTabInQueryString(),
]);
}
#[\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')
->alignCenter()
->label(__('filament::resources.columns.image')),
TextColumn::make('rank_name')
->label(__('filament::resources.columns.name'))
->description(fn (Permission $record) => Str::limit($record->description, 40))
->tooltip(function (Permission $record): ?string {
$description = $record->description;
if (strlen($description) <= 40) {
return null;
}
return $description;
})
->searchable(),
TextColumn::make('prefix')
->label(__('filament::resources.columns.prefix'))
->description(fn (Permission $record) => $record->prefix_color)
->searchable(),
ToggleColumn::make('hidden_rank')
->label(__('filament::resources.columns.is_hidden')),
])
->filters([
//
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListPermissions::route('/'),
'create' => CreatePermission::route('/create'),
'view' => ViewPermission::route('/{record}'),
'edit' => EditPermission::route('/{record}/edit'),
];
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTag extends CreateRecord
{
#[\Override]
protected static string $resource = TagResource::class;
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditTag extends EditRecord
{
#[\Override]
protected static string $resource = TagResource::class;
#[\Override]
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListTags extends ListRecords
{
#[\Override]
protected static string $resource = TagResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Resources\Pages\ViewRecord;
class ViewTag extends ViewRecord
{
#[\Override]
protected static string $resource = TagResource::class;
}
@@ -0,0 +1,55 @@
<?php
namespace App\Filament\Resources\Atom\Tags\RelationManagers;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Actions\ViewAction;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class ArticlesRelationManager extends RelationManager
{
use TranslatableResource;
// Use camelCase to match the method in the Tag model
#[\Override]
protected static string $relationship = 'websiteArticles';
#[\Override]
protected static ?string $recordTitleAttribute = 'title';
public static string $translateIdentifier = 'article';
#[\Override]
public function form(Schema $schema): Schema
{
return $schema
->components(ArticleResource::getForm());
}
public function table(Table $table): Table
{
return $table
->columns(ArticleResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest())
->filters([
//
])
->headerActions([
AttachAction::make()
->preloadRecordSelect(),
])
->recordActions([
ViewAction::make(),
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace App\Filament\Resources\Atom\Tags;
use App\Filament\Resources\Atom\Tags\Pages\CreateTag;
use App\Filament\Resources\Atom\Tags\Pages\EditTag;
use App\Filament\Resources\Atom\Tags\Pages\ListTags;
use App\Filament\Resources\Atom\Tags\Pages\ViewTag;
use App\Filament\Resources\Atom\Tags\RelationManagers\ArticlesRelationManager;
use App\Filament\Traits\TranslatableResource;
use App\Models\Articles\Tag;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\ColorPicker;
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\ColorColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class TagResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = Tag::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/tags';
public static string $translateIdentifier = 'tags';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
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(255)
->autocomplete()
->columnSpan('full'),
ColorPicker::make('background_color')
->label(__('filament::resources.inputs.background_color'))
->required()
->columnSpan('full'),
]),
])->columnSpanFull(),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns(static::getTable())
->filters([
//
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
TextColumn::make('name')
->label(__('filament::resources.columns.name'))
->searchable()
->limit(50),
ColorColumn::make('background_color')
->label(__('filament::resources.columns.background_color'))
->searchable()
->copyable()
->copyMessage(__('filament::resources.common.Sucessfull'))
->copyMessageDuration(1500),
];
}
#[\Override]
public static function getRelations(): array
{
return [
ArticlesRelationManager::class,
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListTags::route('/'),
'create' => CreateTag::route('/create'),
'view' => ViewTag::route('/{record}'),
'edit' => EditTag::route('/{record}/edit'),
];
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Teams\Pages;
use App\Filament\Resources\Atom\Teams\TeamResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTeam extends CreateRecord
{
#[\Override]
protected static string $resource = TeamResource::class;
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Teams\Pages;
use App\Filament\Resources\Atom\Teams\TeamResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditTeam extends EditRecord
{
#[\Override]
protected static string $resource = TeamResource::class;
#[\Override]
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\Teams\Pages;
use App\Filament\Resources\Atom\Teams\TeamResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListTeams extends ListRecords
{
#[\Override]
protected static string $resource = TeamResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
namespace App\Filament\Resources\Atom\Teams;
use App\Filament\Resources\Atom\Teams\Pages\CreateTeam;
use App\Filament\Resources\Atom\Teams\Pages\EditTeam;
use App\Filament\Resources\Atom\Teams\Pages\ListTeams;
use App\Filament\Tables\Columns\HabboBadgeColumn;
use App\Filament\Traits\TranslatableResource;
use App\Models\Community\Staff\WebsiteTeam;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class TeamResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteTeam::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user-group';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/teams';
public static string $translateIdentifier = 'teams';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make()
->schema([
TextInput::make('rank_name')
->autofocus()
->maxLength(255)
->required()
->label(__('filament::resources.inputs.name')),
TextInput::make('job_description')
->maxLength(255)
->label(__('filament::resources.inputs.description')),
TextInput::make('badge')
->maxLength(255)
->label(__('filament::resources.inputs.badge_code'))
->required(),
Toggle::make('hidden_rank')
->label(__('filament::resources.inputs.is_hidden')),
]),
]);
}
#[\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('rank_name')
->label(__('filament::resources.columns.name')),
TextColumn::make('job_description')
->label(__('filament::resources.inputs.description')),
IconColumn::make('hidden_rank')
->label(__('filament::resources.columns.is_hidden'))
->icon(fn (WebsiteTeam $record) => $record->hidden_rank ? 'heroicon-o-check-circle' : 'heroicon-o-x-circle')
->colors([
'danger' => false,
'success' => true,
]),
])
->filters([
//
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListTeams::route('/'),
'create' => CreateTeam::route('/create'),
'edit' => EditTeam::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\WebsiteDrawBadges\Pages;
use App\Filament\Resources\Atom\WebsiteDrawBadges\WebsiteDrawBadgeResource;
use Filament\Resources\Pages\EditRecord;
class EditWebsiteDrawBadge extends EditRecord
{
#[\Override]
protected static string $resource = WebsiteDrawBadgeResource::class;
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\WebsiteDrawBadges\Pages;
use App\Filament\Resources\Atom\WebsiteDrawBadges\WebsiteDrawBadgeResource;
use Filament\Resources\Pages\ListRecords;
class ListWebsiteDrawBadge extends ListRecords
{
#[\Override]
protected static string $resource = WebsiteDrawBadgeResource::class;
}
@@ -0,0 +1,184 @@
<?php
namespace App\Filament\Resources\Atom\WebsiteDrawBadges;
use App\Filament\Resources\Atom\WebsiteDrawBadges\Pages\EditWebsiteDrawBadge;
use App\Filament\Resources\Atom\WebsiteDrawBadges\Pages\ListWebsiteDrawBadge;
use App\Models\WebsiteDrawBadge;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\DB;
class WebsiteDrawBadgeResource extends Resource
{
#[\Override]
protected static ?string $model = WebsiteDrawBadge::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-trophy';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'draw-badges';
#[\Override]
protected static ?string $pluralModelLabel = 'draw badges';
#[\Override]
protected static ?string $navigationLabel = 'Draw Badges';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('badge_name')
->label(__('Badge Name'))
->nullable()
->maxLength(24)
->autocomplete(false),
TextInput::make('badge_desc')
->label(__('Badge Description'))
->nullable()
->maxLength(255)
->autocomplete(false)
->columnSpanFull(),
Toggle::make('published')
->label(__('Published'))
->default(false),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('ID'))
->sortable(),
TextColumn::make('user_id')
->label(__('User ID')),
TextColumn::make('user.username')
->label(__('Username'))
->sortable()
->searchable(),
TextColumn::make('badge_name')
->limit(8)
->label(__('Badge Name')),
TextColumn::make('badge_desc')
->label(__('Badge description'))
->limit(35)
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (strlen($state) <= $column->getCharacterLimit()) {
return null;
}
return $state;
}),
TextColumn::make('created_at')
->label(__('Created At'))
->dateTime(),
ImageColumn::make('badge_url')
->label(__('Badge'))
->getStateUsing(fn ($record) => config('app.url') . $record->badge_url)
->extraAttributes(['style' => 'image-rendering: pixelated'])
->size(40),
ToggleColumn::make('published')
->label(__('Published')),
])
->recordActions([
DeleteAction::make()
->before(function (DeleteAction $action, WebsiteDrawBadge $record) {
$badgeCode = pathinfo($record->badge_path, PATHINFO_FILENAME);
// Remove the badge from any user before deleting it.
if ($record->published) {
DB::table('users_badges')
->where('user_id', $record->user_id)
->where('badge_code', $badgeCode)
->delete();
}
// Remove from JSON
$filePath = DB::table('website_settings')->where('key', 'nitro_external_texts_file')->value('value');
if ($filePath && file_exists($filePath) && is_writable($filePath)) {
$json = json_decode(file_get_contents($filePath), true);
unset($json["badge_name_{$badgeCode}"]);
unset($json["badge_desc_{$badgeCode}"]);
file_put_contents($filePath, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
// Delete the badge file from the filesystem
$badgePath = $record->badge_path;
if ($badgePath && file_exists($badgePath)) {
unlink($badgePath);
}
}),
])
->toolbarActions([
DeleteBulkAction::make()
->before(function (DeleteBulkAction $action, $records) {
foreach ($records as $record) {
$badgeCode = pathinfo((string) $record->badge_path, PATHINFO_FILENAME);
// Remove the badge from any user before deleting it.
if ($record->published) {
DB::table('users_badges')
->where('user_id', $record->user_id)
->where('badge_code', $badgeCode)
->delete();
}
$filePath = DB::table('website_settings')->where('key', 'nitro_external_texts_file')->value('value');
if ($filePath && file_exists($filePath) && is_writable($filePath)) {
$json = json_decode(file_get_contents($filePath), true);
unset($json["badge_name_{$badgeCode}"]);
unset($json["badge_desc_{$badgeCode}"]);
file_put_contents($filePath, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
$badgePath = $record->badge_path;
if ($badgePath && file_exists($badgePath)) {
unlink($badgePath);
}
}
}),
]);
}
#[\Override]
public static function getRelations(): array
{
return [];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ListWebsiteDrawBadge::route('/'),
'edit' => EditWebsiteDrawBadge::route('/{record}/edit'),
];
}
#[\Override]
public static function canCreate(): bool
{
return false;
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace App\Filament\Resources\Atom;
use App\Filament\Resources\Atom\WriteableBoxResource\Pages\ManageWriteableBoxes;
use App\Filament\Traits\TranslatableResource;
use App\Models\WebsiteWriteableBox;
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 WriteableBoxResource extends Resource
{
use TranslatableResource;
#[\Override]
protected static ?string $model = WebsiteWriteableBox::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-pencil-square';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Website';
#[\Override]
protected static ?string $slug = 'website/writeable-boxes';
public static string $translateIdentifier = 'writeable-boxes';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
TextInput::make('title')
->label(__('Title'))
->required()
->maxLength(255),
Textarea::make('content')
->label(__('Content'))
->required(),
TextInput::make('order')
->label(__('Order'))
->numeric()
->default(0),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->columns(static::getTable());
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('ID'))
->sortable(),
TextColumn::make('title')
->label(__('Title'))
->searchable(),
TextColumn::make('order')
->label(__('Order'))
->sortable(),
TextColumn::make('created_at')
->label(__('Created'))
->dateTime(),
];
}
#[\Override]
public static function getPages(): array
{
return [
'index' => ManageWriteableBoxes::route('/'),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Atom\WriteableBoxResource\Pages;
use App\Filament\Resources\Atom\WriteableBoxResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ManageRecords;
class ManageWriteableBoxes extends ManageRecords
{
#[\Override]
protected static string $resource = WriteableBoxResource::class;
#[\Override]
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Base;
use Filament\Resources\Resource;
abstract class BaseResource extends Resource
{
/**
* Get the navigation group for the resource.
* Override in child classes to customize.
*/
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = null;
/**
* Get the navigation icon for the resource.
* Override in child classes to customize.
*/
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
/**
* Get the navigation sort order for the resource.
*/
#[\Override]
protected static ?int $navigationSort = null;
/**
* Get the form schema for the resource.
* Override this method in child classes.
*/
public static function getFormSchema(): array
{
return [];
}
/**
* Get the table columns for the resource.
* Override this method in child classes.
*/
public static function getTableColumns(): array
{
return [];
}
/**
* Get the filters for the resource.
* Override this method in child classes.
*/
public static function getFilters(): array
{
return [];
}
/**
* Get the actions for the resource.
* Override this method in child classes.
*/
public static function getActions(): array
{
return [];
}
/**
* Get the bulk actions for the resource.
* Override this method in child classes.
*/
public static function getBulkActions(): array
{
return [];
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Filament\Resources\DashboardResource\Widgets;
use App\Filament\Resources\Shop\ShopOrderResource;
use App\Models\User\UserOrder;
use Filament\Actions\ViewAction;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class LatestOrders extends BaseWidget
{
#[\Override]
protected int|string|array $columnSpan = 'full';
#[\Override]
public function table(Table $table): Table
{
return $table
->query(UserOrder::latest())
->paginated([3, 5, 8])
->columns(ShopOrderResource::getTable())
->recordActions([
ViewAction::make()->schema(ShopOrderResource::getForm()),
]);
}
}
@@ -0,0 +1,81 @@
<?php
namespace App\Filament\Resources\DashboardResource\Widgets;
use App\Models\User\UserOrder;
use Filament\Widgets\ChartWidget;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Illuminate\Contracts\Support\Htmlable;
class OrdersAggregateChart extends ChartWidget
{
#[\Override]
protected ?string $maxHeight = '300px';
#[\Override]
protected string $color = 'secondary';
#[\Override]
public function getHeading(): string|Htmlable|null
{
return __('filament::resources.stats.orders_chart.title');
}
#[\Override]
public function getDescription(): string|Htmlable|null
{
return __('filament::resources.stats.orders_chart.description');
}
#[\Override]
protected function getData(): array
{
$pendingOrder = Trend::query(UserOrder::pending())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$cancelledOrder = Trend::query(UserOrder::cancelled())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$completedOrder = Trend::query(UserOrder::completed())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$datasets = [
$this->getDataset($pendingOrder, __('filament::resources.stats.orders_chart.pending'), '#fbbf24', '#f59e0b'),
$this->getDataset($cancelledOrder, __('filament::resources.stats.orders_chart.cancelled'), '#dc2626', '#b91c1c'),
$this->getDataset($completedOrder, __('filament::resources.stats.orders_chart.completed'), '#10b981', '#059669'),
];
$data = $pendingOrder->map(fn (TrendValue $value) => $value->date)->merge(
$cancelledOrder->map(fn (TrendValue $value) => $value->date),
)->merge(
$completedOrder->map(fn (TrendValue $value) => $value->date),
)->unique()->sort()->flatten();
return [
'datasets' => $datasets,
'labels' => $data,
];
}
protected function getDataset($data, $label, string $backgroundColor, string $borderColor): array
{
return [
'label' => $label,
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
'backgroundColor' => $backgroundColor,
'borderColor' => $borderColor,
];
}
protected function getType(): string
{
return 'bar';
}
}
@@ -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'),
];
}
}
@@ -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;
}
@@ -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;
}
}
}
@@ -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('/'),
];
}
}
@@ -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
View File
@@ -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'),
];
}
}
@@ -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;
}
@@ -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(),
];
}
}
@@ -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(),
];
}
}
@@ -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(),
];
}
}
@@ -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(),
];
}
}
@@ -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('/'),
];
}
}
@@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Miscellaneous\AlertLogResource;
use App\Models\Miscellaneous\AlertLog;
use App\Services\EmulatorUpdateService;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Cache;
class AlertLogResource extends Resource
{
#[\Override]
protected static ?string $model = AlertLog::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bell';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Commandocentrum';
#[\Override]
protected static ?string $navigationLabel = 'Alert Database';
protected static ?string $title = 'Alert Database';
#[\Override]
protected static ?string $slug = 'monitoring/alerts';
#[\Override]
public static function canCreate(): bool
{
return false;
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('created_at', 'desc')
->columns([
TextColumn::make('type')
->label('Type')
->formatStateUsing(fn (string $state): string => ucfirst(str_replace('_', ' ', $state)))
->searchable(),
BadgeColumn::make('severity')
->label('Severity')
->colors([
'info' => 'info',
'warning' => 'warning',
'error' => 'error',
'danger' => 'critical',
])
->formatStateUsing(fn (string $state): string => ucfirst($state)),
TextColumn::make('message')
->label('Message')
->limit(50)
->tooltip(fn (AlertLog $record): ?string => $record->message),
TextColumn::make('created_at')
->label('Time')
->dateTime('d M Y, H:i:s')
->sortable(),
BadgeColumn::make('is_read')
->label('Status')
->colors([
'secondary' => false,
'success' => true,
])
->formatStateUsing(fn (bool $state): string => $state ? 'Read' : 'Unread'),
BadgeColumn::make('sent_via_email')
->label('Email')
->colors([
'success' => true,
'secondary' => false,
])
->formatStateUsing(fn (bool $state): string => $state ? 'Yes' : 'No'),
BadgeColumn::make('sent_via_discord')
->label('Discord')
->colors([
'success' => true,
'secondary' => false,
])
->formatStateUsing(fn (bool $state): string => $state ? 'Yes' : 'No'),
])
->filters([
SelectFilter::make('severity')
->options([
'info' => 'Info',
'warning' => 'Warning',
'error' => 'Error',
'critical' => 'Critical',
]),
TernaryFilter::make('is_read')
->label('Read Status'),
])
->recordActions([
Action::make('mark_read')
->label('Mark as Read')
->action(fn (AlertLog $record) => $record->markAsRead())
->visible(fn (AlertLog $record) => ! $record->is_read)
->icon('heroicon-o-check'),
Action::make('delete')
->label('Delete')
->action(fn (AlertLog $record) => $record->delete())
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation(),
])
->toolbarActions([
Action::make('mark_all_read')
->label('Mark All as Read')
->action(fn () => AlertLog::where('is_read', false)->update(['is_read' => true, 'read_at' => now()]))
->icon('heroicon-o-check-circle'),
Action::make('delete_all')
->label('Delete All Logs')
->action(fn () => AlertLog::truncate())
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation(),
Action::make('clear_all_logs')
->label('Leeg Alle Logs (Systeem)')
->icon('heroicon-o-trash')
->color('gray')
->action(function () {
$updateService = new EmulatorUpdateService;
$result = $updateService->clearAllLogs();
Cache::flush();
AlertLog::truncate();
Notification::make()
->success()
->title('🗑️ Alle Logs Geleegd!')
->body($result['message'])
->send();
})
->requiresConfirmation(),
]);
}
#[\Override]
public static function getPages(): array
{
return [
'index' => Pages\ListAlertLogs::route('/'),
];
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Miscellaneous\AlertLogResource\Pages;
use App\Filament\Resources\Miscellaneous\AlertLogResource\AlertLogResource;
use Filament\Resources\Pages\ListRecords;
class ListAlertLogs extends ListRecords
{
#[\Override]
protected static string $resource = AlertLogResource::class;
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\RadioApplications\Pages;
use App\Filament\Resources\RadioApplications\RadioApplicationResource;
use Filament\Resources\Pages\EditRecord;
class EditRadioApplication extends EditRecord
{
#[\Override]
protected static string $resource = RadioApplicationResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
//
];
}
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\RadioApplications\Pages;
use App\Filament\Resources\RadioApplications\RadioApplicationResource;
use Filament\Resources\Pages\ListRecords;
class ListRadioApplications extends ListRecords
{
#[\Override]
protected static string $resource = RadioApplicationResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
//
];
}
}
@@ -0,0 +1,265 @@
<?php
namespace App\Filament\Resources\RadioApplications;
use App\Models\RadioApplication;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
class RadioApplicationResource extends Resource
{
#[\Override]
protected static ?string $model = RadioApplication::class;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-microphone';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
#[\Override]
protected static ?string $slug = 'radio/applications';
#[\Override]
public static function getNavigationLabel(): string
{
return 'DJ Aanmeldingen';
}
#[\Override]
public static function getModelLabel(): string
{
return 'DJ Aanmelding';
}
#[\Override]
public static function getPluralModelLabel(): string
{
return 'DJ Aanmeldingen';
}
#[\Override]
public static function canCreate(): bool
{
return false;
}
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make('Persoonlijke Gegevens')
->schema([
Select::make('user_id')
->relationship('user', 'username')
->required()
->searchable()
->label('Gebruiker'),
TextInput::make('real_name')
->required()
->label('Echte Naam'),
TextInput::make('age')
->required()
->numeric()
->label('Leeftijd'),
]),
Section::make('DJ Gegevens')
->schema([
Textarea::make('availability')
->required()
->label('Beschikbaarheid')
->helperText('Wanneer kan je draaien? (bijv. maandag 20:00-22:00)')
->rows(3),
Textarea::make('experience')
->label('Ervaring')
->helperText('Heb je ervaring als DJ?')
->rows(3),
Textarea::make('motivation')
->required()
->label('Motivatie')
->helperText('Waarom wil je DJ worden?')
->rows(3),
Textarea::make('music_style')
->label('Muziekstijl')
->helperText('Welke muziekstijl draai je graag?')
->rows(2),
TextInput::make('discord_username')
->label('Discord Gebruikersnaam')
->helperText('Voor contact'),
Select::make('rank_id')
->relationship('rank', 'name')
->label('Gewenste Rank')
->nullable(),
]),
Section::make('Status')
->schema([
Select::make('status')
->options([
'pending' => 'In afwachting',
'approved' => 'Goedgekeurd',
'rejected' => 'Afgewezen',
])
->required()
->label('Status'),
]),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('created_at', 'desc')
->columns([
TextColumn::make('user.username')
->label('Gebruiker')
->sortable()
->searchable(),
TextColumn::make('real_name')
->label('Naam')
->sortable()
->searchable(),
TextColumn::make('age')
->label('Leeftijd')
->sortable(),
TextColumn::make('rank.name')
->label('Gewenste Rank')
->sortable(),
TextColumn::make('status')
->label('Status')
->badge()
->formatStateUsing(fn (?string $state) => match ($state) {
'pending' => 'In afwachting',
'approved' => 'Goedgekeurd',
'rejected' => 'Afgewezen',
default => 'In afwachting',
})
->color(fn (?string $state) => [
'pending' => 'warning',
'approved' => 'success',
'rejected' => 'danger',
][$state ?? 'pending'] ?? 'gray')
->sortable(),
TextColumn::make('created_at')
->label('Aangemeld op')
->dateTime('d-m-Y H:i')
->sortable(),
])
->filters([
SelectFilter::make('status')
->options([
'pending' => 'In afwachting',
'approved' => 'Goedgekeurd',
'rejected' => 'Afgewezen',
])
->label('Status'),
])
->recordActions([
Action::make('approve')
->label('Goedkeuren')
->icon('heroicon-o-check-circle')
->color('success')
->visible(fn (RadioApplication $record) => $record->status === 'pending')
->requiresConfirmation()
->modalHeading('DJ Aanmelding Goedkeuren')
->modalDescription('Weet je zeker dat je deze aanmelding wilt goedkeuren?')
->action(function (RadioApplication $record) {
$record->update([
'status' => 'approved',
'approved_by' => auth()->id(),
'approved_at' => now(),
]);
Notification::make()
->success()
->title('Goedgekeurd')
->body("De aanmelding van {$record->user->username} is goedgekeurd.")
->send();
}),
Action::make('reject')
->label('Afwijzen')
->icon('heroicon-o-x-circle')
->color('danger')
->visible(fn (RadioApplication $record) => in_array($record->status, ['pending', 'approved']))
->requiresConfirmation()
->modalHeading('DJ Aanmelding Afwijzen')
->modalDescription('Weet je zeker dat je deze aanmelding wilt afwijzen?')
->action(function (RadioApplication $record) {
$record->update([
'status' => 'rejected',
'rejected_by' => auth()->id(),
'rejected_at' => now(),
]);
Notification::make()
->success()
->title('Afgewezen')
->body("De aanmelding van {$record->user->username} is afgewezen.")
->send();
}),
Action::make('reopen')
->label('Heropenen')
->icon('heroicon-o-arrow-path')
->color('warning')
->visible(fn (RadioApplication $record) => $record->status === 'rejected')
->requiresConfirmation()
->modalHeading('Aanmelding Heropenen')
->modalDescription('Dit zet de status terug naar "In afwachting".')
->action(function (RadioApplication $record) {
$record->update([
'status' => 'pending',
'rejected_by' => null,
'rejected_at' => null,
]);
Notification::make()
->success()
->title('Heropend')
->body('De aanmelding is heropend.')
->send();
}),
DeleteAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
#[\Override]
public static function getPages(): array
{
return [
'index' => Pages\ListRadioApplications::route('/'),
'edit' => Pages\EditRadioApplication::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\RadioBanners\Pages;
use App\Filament\Resources\RadioBanners\RadioBannerResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ManageRecords;
class ManageRadioBanners extends ManageRecords
{
#[\Override]
protected static string $resource = RadioBannerResource::class;
#[\Override]
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

Some files were not shown because too many files have changed in this diff Show More