You've already forked Atomcms-edit
Initial commit
This commit is contained in:
Executable
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Filters;
|
||||
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DateRangeFilter extends Filter
|
||||
{
|
||||
#[\Override]
|
||||
public static function make(?string $name = null): static
|
||||
{
|
||||
return parent::make($name)
|
||||
->schema([
|
||||
DatePicker::make("{$name}_from"),
|
||||
DatePicker::make("{$name}_until"),
|
||||
])
|
||||
->query(fn (Builder $query, array $data): Builder => $query
|
||||
->when(
|
||||
$data["{$name}_from"],
|
||||
fn (Builder $query, ?string $date) => $query->whereDate($name ?? '', '>=', $date),
|
||||
)
|
||||
->when(
|
||||
$data["{$name}_until"],
|
||||
fn (Builder $query, ?string $date) => $query->whereDate($name ?? '', '<=', $date),
|
||||
));
|
||||
}
|
||||
}
|
||||
Executable
+338
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\User;
|
||||
use App\Services\Parsers\ExternalTextsParser;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\Action as PageAction;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @property-read Form $form
|
||||
*/
|
||||
class BadgePage extends Page
|
||||
{
|
||||
use InteractsWithForms, TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.badge-page';
|
||||
|
||||
protected static string $translateIdentifier = 'badge-resource';
|
||||
|
||||
public bool $badgeWasPreviouslyCreated = false;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = ['code' => '', 'image' => '', 'nitro' => ['title' => '', 'description' => '']];
|
||||
|
||||
public static string $roleName = 'badge_page';
|
||||
|
||||
#[\Override]
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user && $user->can('view::admin::' . static::$roleName);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return (string) __(
|
||||
sprintf('filament::resources.resources.%s.navigation_label', static::$translateIdentifier),
|
||||
);
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make((string) __('filament::resources.tabs.Main'))
|
||||
->schema([
|
||||
TextInput::make('code')
|
||||
->label((string) __('filament::resources.inputs.badge_code'))
|
||||
->helperText((string) __('filament::resources.helpers.badge_code_helper'))
|
||||
->afterStateUpdated(function (?string $state, Set $set) {
|
||||
$set('code', strtoupper((string) $state));
|
||||
})
|
||||
->suffixAction(fn (): PageAction => PageAction::make('search')->icon('heroicon-o-magnifying-glass')->action(fn () => $this->searchBadgesByCode()),
|
||||
),
|
||||
|
||||
TextInput::make('image')
|
||||
->label((string) __('filament::resources.inputs.badge_image'))
|
||||
->placeholder('...')
|
||||
->autocomplete()
|
||||
->visible(fn (Get $get) => isset($this->data['image']))
|
||||
->prefixAction(
|
||||
fn (?string $state): PageAction => PageAction::make('visit')
|
||||
->icon('heroicon-s-arrow-top-right-on-square')
|
||||
->tooltip((string) __('filament::resources.common.Open link'))
|
||||
->url($state)
|
||||
->visible(fn () => ! in_array($state, [null, '', '0'], true))
|
||||
->openUrlInNewTab(),
|
||||
),
|
||||
]),
|
||||
|
||||
Section::make(__('Nitro Texts'))
|
||||
->collapsible()
|
||||
->visible(fn () => isset($this->data['nitro']) && ! empty($this->data['nitro']))
|
||||
->schema([
|
||||
TextInput::make('nitro.title')
|
||||
->label(__('filament::resources.inputs.badge_title'))
|
||||
->placeholder('...')
|
||||
->visible(fn () => isset($this->data['nitro']['title'])),
|
||||
|
||||
TextInput::make('nitro.description')
|
||||
->label(__('filament::resources.inputs.badge_description'))
|
||||
->placeholder('...')
|
||||
->visible(fn () => isset($this->data['nitro']['description'])),
|
||||
]),
|
||||
|
||||
Section::make(__('Flash Texts'))
|
||||
->collapsible()
|
||||
->visible(fn () => isset($this->data['flash']) && ! empty($this->data['flash']))
|
||||
->schema([
|
||||
TextInput::make('flash.title')
|
||||
->label(__('filament::resources.inputs.badge_title'))
|
||||
->placeholder('...')
|
||||
->visible(fn () => isset($this->data['flash']['title'])),
|
||||
|
||||
TextInput::make('flash.description')
|
||||
->label(__('filament::resources.inputs.badge_description'))
|
||||
->placeholder('...')
|
||||
->visible(fn () => isset($this->data['flash']['description'])),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
private function searchBadgesByCode(): void
|
||||
{
|
||||
$badgeCode = $this->form->getState()['code'] ?? null;
|
||||
|
||||
if (empty($badgeCode)) {
|
||||
Notification::make()
|
||||
->icon('heroicon-o-exclamation-triangle')
|
||||
->iconColor('danger')
|
||||
->color('danger')
|
||||
->title(__('filament::resources.notifications.badge_code_required'))
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$badgeData = app(ExternalTextsParser::class)->getBadgeData($badgeCode);
|
||||
$this->badgeWasPreviouslyCreated = is_array($badgeData['nitro'] ?? null) || is_array($badgeData['flash'] ?? null);
|
||||
|
||||
if ($this->badgeWasPreviouslyCreated) {
|
||||
Notification::make()
|
||||
->icon('heroicon-o-check-circle')
|
||||
->iconColor('success')
|
||||
->color('success')
|
||||
->title(__('filament::resources.notifications.badge_found'))
|
||||
->send();
|
||||
|
||||
$this->data = [
|
||||
'code' => $badgeCode,
|
||||
...$this->getDefaultDataBehavior(
|
||||
$badgeData['image'] ?? null,
|
||||
$badgeData['nitro']['title'] ?? null,
|
||||
$badgeData['nitro']['description'] ?? null,
|
||||
$badgeData['flash']['title'] ?? null,
|
||||
$badgeData['flash']['description'] ?? null,
|
||||
),
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->color('success')
|
||||
->icon('heroicon-o-check-circle')
|
||||
->iconColor('success')
|
||||
->title(__('filament::resources.notifications.create_badge'))
|
||||
->send();
|
||||
|
||||
$this->data = [
|
||||
'code' => $badgeCode,
|
||||
...$this->getDefaultDataBehavior(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getDefaultDataBehavior(
|
||||
?string $badgeImageUrl = null,
|
||||
?string $nitroTitle = null,
|
||||
?string $nitroDesc = null,
|
||||
?string $flashTitle = null,
|
||||
?string $flashDesc = null,
|
||||
): array {
|
||||
return [
|
||||
'image' => $badgeImageUrl ?? '',
|
||||
'nitro' => [
|
||||
'title' => $nitroTitle ?? '',
|
||||
'description' => $nitroDesc ?? '',
|
||||
],
|
||||
'flash' => [
|
||||
'title' => $flashTitle ?? '',
|
||||
'description' => $flashDesc ?? '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
$nitroEnabled = config('hotel.client.nitro.enabled');
|
||||
$flashEnabled = config('hotel.client.flash.enabled');
|
||||
|
||||
// image and code fields are required when creating a new badge
|
||||
if (! $this->badgeWasPreviouslyCreated && (empty($this->data['image']) || empty($this->data['code']))) {
|
||||
$notificationTitle = empty($this->data['image']) ?
|
||||
__('filament::resources.notifications.badge_image_required') :
|
||||
__('filament::resources.notifications.badge_code_required');
|
||||
|
||||
Notification::make()
|
||||
->icon('heroicon-o-exclamation-triangle')
|
||||
->iconColor('danger')
|
||||
->color('danger')
|
||||
->title($notificationTitle)
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$externalTextsParser = app(ExternalTextsParser::class);
|
||||
|
||||
if ((empty($this->data['nitro']) && $nitroEnabled) || (empty($this->data['flash']) && $flashEnabled)) {
|
||||
Notification::make()
|
||||
->icon('heroicon-o-exclamation-triangle')
|
||||
->iconColor('danger')
|
||||
->color('danger')
|
||||
->title(__('filament::resources.notifications.badge_texts_required'))
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->uploadBadgeImage($externalTextsParser);
|
||||
|
||||
$nitroData = $this->data['nitro'] ?? [];
|
||||
if (! empty($nitroData) && $nitroEnabled && is_array($nitroData)) {
|
||||
$externalTextsParser->updateNitroBadgeTexts($this->data['code'], ...$nitroData);
|
||||
}
|
||||
|
||||
$flashData = $this->data['flash'] ?? [];
|
||||
if (! empty($flashData) && $flashEnabled && is_array($flashData)) {
|
||||
$externalTextsParser->updateFlashBadgeTexts($this->data['code'], ...$flashData);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
Log::channel('badge')->error('[ORION BADGE RESOURCE] - ERROR: ' . $exception->getMessage());
|
||||
|
||||
Notification::make()
|
||||
->icon('heroicon-o-exclamation-triangle')
|
||||
->iconColor('danger')
|
||||
->color('danger')
|
||||
->title(__('filament::resources.notifications.badge_update_failed'))
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data['image'] = $externalTextsParser->getBadgeImageUrl($this->data['code']);
|
||||
$this->badgeWasPreviouslyCreated = true;
|
||||
|
||||
Notification::make()
|
||||
->icon('heroicon-o-check-circle')
|
||||
->iconColor('success')
|
||||
->color('success')
|
||||
->title(__('filament::resources.notifications.badge_updated'))
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function uploadBadgeImage(ExternalTextsParser $parser): void
|
||||
{
|
||||
if (empty($this->data['image']) || ! filter_var($this->data['image'], FILTER_VALIDATE_URL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->data['image'] == $parser->getBadgeImageUrl($this->data['code'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image = Http::get($this->data['image']);
|
||||
|
||||
if (! $image->successful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contentType = $image->header('content-type');
|
||||
|
||||
$gdImage = match ($contentType) {
|
||||
'image/png' => imagecreatefrompng($this->data['image']),
|
||||
'image/gif' => imagecreatefromgif($this->data['image']),
|
||||
'image/jpeg' => imagecreatefromjpeg($this->data['image']),
|
||||
default => false
|
||||
};
|
||||
|
||||
if ($gdImage === false) {
|
||||
Notification::make()
|
||||
->icon('heroicon-o-exclamation-triangle')
|
||||
->iconColor('danger')
|
||||
->color('danger')
|
||||
->title(__('filament::resources.notifications.badge_image_upload_failed'))
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$uploadPath = public_path(sprintf('%s%s%s.gif',
|
||||
rtrim((string) config('hotel.client.flash.relative_files_path'), '\//'),
|
||||
'/c_images/album1584/',
|
||||
$this->data['code'],
|
||||
));
|
||||
|
||||
imagegif($gdImage, $uploadPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Action|ActionGroup>
|
||||
*/
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
PageAction::make('save')
|
||||
->label(__('filament::resources.common.Update'))
|
||||
->action(fn () => $this->create())
|
||||
->color('primary')
|
||||
->visible(fn () => isset($this->data['code']) && $this->badgeWasPreviouslyCreated),
|
||||
|
||||
PageAction::make('create')
|
||||
->label(__('filament::resources.common.Create'))
|
||||
->action(fn () => $this->create())
|
||||
->color('success')
|
||||
->visible(fn () => isset($this->data['code']) && ! $this->badgeWasPreviouslyCreated),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use Filament\Pages\Dashboard as FilamentDashboard;
|
||||
|
||||
class Dashboard extends FilamentDashboard
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Dashboard';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = null;
|
||||
|
||||
#[\Override]
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Homepage');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-home';
|
||||
|
||||
public static string $translateIdentifier = 'dashboard';
|
||||
}
|
||||
Executable
+2197
File diff suppressed because it is too large
Load Diff
Executable
+114
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||
use Filament\Auth\Http\Responses\Contracts\LoginResponse;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class Login extends \Filament\Auth\Pages\Login
|
||||
{
|
||||
public string $username = '';
|
||||
|
||||
#[\Override]
|
||||
public function authenticate(): ?LoginResponse
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(15);
|
||||
} catch (TooManyRequestsException $exception) {
|
||||
$titleMessage = __('filament-panels::pages/auth/login.notifications.throttled.title', [
|
||||
'seconds' => $exception->secondsUntilAvailable,
|
||||
'minutes' => ceil($exception->secondsUntilAvailable / 60),
|
||||
]);
|
||||
|
||||
$bodyMessage = __('filament-panels::pages/auth/login.notifications.throttled.body', [
|
||||
'seconds' => $exception->secondsUntilAvailable,
|
||||
'minutes' => ceil($exception->secondsUntilAvailable / 60),
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title($titleMessage)
|
||||
->body(is_string($bodyMessage) ? $bodyMessage : null)
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $this->form->getState();
|
||||
|
||||
if (! Filament::auth()->attempt($this->getCredentialsFromFormData($data), $data['remember'] ?? false)) {
|
||||
$this->throwFailureValidationException();
|
||||
}
|
||||
|
||||
$user = Filament::auth()->user();
|
||||
|
||||
$panel = Filament::getCurrentOrDefaultPanel();
|
||||
if (
|
||||
($user instanceof FilamentUser) &&
|
||||
(! $user->canAccessPanel($panel))
|
||||
) {
|
||||
Filament::auth()->logout();
|
||||
|
||||
$this->throwFailureValidationException();
|
||||
}
|
||||
|
||||
session()->regenerate();
|
||||
|
||||
return app(LoginResponse::class);
|
||||
}
|
||||
|
||||
protected function throwFailureValidationException(): never
|
||||
{
|
||||
throw ValidationException::withMessages([
|
||||
'data.username' => __('filament-panels::pages/auth/login.messages.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('username')
|
||||
->label(__('filament::login.fields.username.label'))
|
||||
->required()
|
||||
->autocomplete(),
|
||||
TextInput::make('password')
|
||||
->label(__('filament::login.fields.password.label'))
|
||||
->password()
|
||||
->required(),
|
||||
Checkbox::make('remember')
|
||||
->label(__('filament::login.fields.remember.label')),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function getEmailFormComponent(): Component
|
||||
{
|
||||
return TextInput::make('username')
|
||||
->label(__('filament::login.fields.username.label'))
|
||||
->required()
|
||||
->autocomplete()
|
||||
->autofocus()
|
||||
->extraInputAttributes(['tabindex' => 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[\Override]
|
||||
protected function getCredentialsFromFormData(array $data): array
|
||||
{
|
||||
return [
|
||||
'username' => $data['username'],
|
||||
'password' => $data['password'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+2854
File diff suppressed because it is too large
Load Diff
+1944
File diff suppressed because it is too large
Load Diff
+239
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\RadioContest;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
final class ContestManagement extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-trophy';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = null;
|
||||
|
||||
#[\Override]
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Contests');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'Competities Beheer';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.radio.contest-management';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$contests = RadioContest::orderBy('created_at', 'desc')->get();
|
||||
|
||||
$this->data['contests'] = $contests->toArray();
|
||||
$this->data['active_contest'] = $contests->firstWhere('is_active', true)?->id;
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Nieuwe Competitie Aanmaken')
|
||||
->description('Maak een nieuwe competitie aan')
|
||||
->columns(1)
|
||||
->headerActions([
|
||||
Action::make('create_contest')
|
||||
->label('Competitie Aanmaken')
|
||||
->action('createContest')
|
||||
->color('success'),
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('contest.title')
|
||||
->label('Titel')
|
||||
->required()
|
||||
->placeholder('Bijv. Beste Song Suggestie'),
|
||||
Textarea::make('contest.description')
|
||||
->label('Beschrijving')
|
||||
->rows(3)
|
||||
->placeholder('Beschrijf de competitie regels...'),
|
||||
TextInput::make('contest.prize')
|
||||
->label('Prijs')
|
||||
->required()
|
||||
->placeholder('Bijv. VIP voor 1 maand'),
|
||||
TextInput::make('contest.max_winners')
|
||||
->label('Aantal Winnaars')
|
||||
->numeric()
|
||||
->default(1)
|
||||
->placeholder('1'),
|
||||
DateTimePicker::make('contest.starts_at')
|
||||
->label('Start Datum/Tijd')
|
||||
->required()
|
||||
->default(now()),
|
||||
DateTimePicker::make('contest.ends_at')
|
||||
->label('Eind Datum/Tijd')
|
||||
->required()
|
||||
->default(now()->addDays(14)),
|
||||
Toggle::make('contest.require_approval')
|
||||
->label('Goedkeuring Vereist')
|
||||
->default(false),
|
||||
]),
|
||||
Section::make('Actieve Competities')
|
||||
->description('Beheer lopende competities')
|
||||
->columns(1)
|
||||
->schema([
|
||||
Select::make('selected_contest')
|
||||
->label('Selecteer Competitie')
|
||||
->options(RadioContest::where('is_active', true)->pluck('title', 'id'))
|
||||
->placeholder('Kies een competitie...')
|
||||
->live(),
|
||||
]),
|
||||
Section::make('Acties')
|
||||
->description('Competitie beheer')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Action::make('end_contest')
|
||||
->label('Competitie Beëindigen')
|
||||
->action('endContest')
|
||||
->color('danger')
|
||||
->requiresConfirmation(),
|
||||
Action::make('declare_winners')
|
||||
->label('Winnaars Bekend Maken')
|
||||
->action('declareWinners')
|
||||
->color('warning')
|
||||
->requiresConfirmation(),
|
||||
]),
|
||||
Section::make('Afgelopen Competities')
|
||||
->description('Bekijk resultaten van afgelopen competities')
|
||||
->columns(1)
|
||||
->schema([
|
||||
Select::make('ended_contest')
|
||||
->label('Selecteer Afgelopen Competitie')
|
||||
->options(RadioContest::where('is_ended', true)->pluck('title', 'id'))
|
||||
->placeholder('Kies een competitie...'),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function createContest(): void
|
||||
{
|
||||
$data = $this->data['contest'] ?? [];
|
||||
|
||||
$contest = RadioContest::create([
|
||||
'title' => $data['title'],
|
||||
'description' => $data['description'] ?? '',
|
||||
'prize' => $data['prize'],
|
||||
'max_winners' => $data['max_winners'] ?? 1,
|
||||
'starts_at' => $data['starts_at'] ?? now(),
|
||||
'ends_at' => $data['ends_at'] ?? now()->addDays(14),
|
||||
'is_active' => true,
|
||||
'is_ended' => false,
|
||||
'winners_announced' => false,
|
||||
'require_approval' => $data['require_approval'] ?? false,
|
||||
]);
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Competitie Aangemaakt')
|
||||
->body("Competitie '{$contest->title}' is succesvol aangemaakt!")
|
||||
->send();
|
||||
}
|
||||
|
||||
public function endContest(): void
|
||||
{
|
||||
$contestId = $this->data['selected_contest'] ?? null;
|
||||
|
||||
if (! $contestId) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Geen Competitie Geselecteerd')
|
||||
->body('Selecteer eerst een competitie.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$contest = RadioContest::find($contestId);
|
||||
|
||||
$contest->update(['is_ended' => true, 'is_active' => false]);
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Competitie Beëindigd')
|
||||
->body("Competitie '{$contest->title}' is beëindigd.")
|
||||
->send();
|
||||
}
|
||||
|
||||
public function declareWinners(): void
|
||||
{
|
||||
$contestId = $this->data['selected_contest'] ?? null;
|
||||
|
||||
if (! $contestId) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Geen Competitie Geselecteerd')
|
||||
->body('Selecteer eerst een competitie.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$contest = RadioContest::find($contestId);
|
||||
$entries = $contest->entries()->with('user')->get();
|
||||
|
||||
if ($entries->isEmpty()) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Geen Inzendingen')
|
||||
->body('Er zijn nog geen inzendingen.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$winners = $entries->random(min($contest->max_winners, $entries->count()));
|
||||
|
||||
foreach ($winners as $winner) {
|
||||
$winner->markAsWinner();
|
||||
}
|
||||
|
||||
$contest->update(['winners_announced' => true]);
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Winnaars Bekend!')
|
||||
->body(count($winners) . ' winnaars zijn geselecteerd!')
|
||||
->send();
|
||||
}
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\RadioGiveaway;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
final class GiveawayManagement extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-gift';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = null;
|
||||
|
||||
#[\Override]
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Giveaways');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'Winacties Beheer';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.radio.giveaway-management';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = [];
|
||||
|
||||
public RadioGiveaway $currentGiveaway;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$giveaways = RadioGiveaway::orderBy('created_at', 'desc')->get();
|
||||
|
||||
$this->data['giveaways'] = $giveaways->toArray();
|
||||
$this->data['active_giveaway'] = $giveaways->firstWhere('is_active', true)?->id;
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Nieuwe Winactie Aanmaken')
|
||||
->description('Maak een nieuwe winactie aan voor luisteraars')
|
||||
->columns(1)
|
||||
->headerActions([
|
||||
Action::make('create_giveaway')
|
||||
->label('Winactie Aanmaken')
|
||||
->action('createGiveaway')
|
||||
->color('success'),
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('giveaway.title')
|
||||
->label('Titel')
|
||||
->required()
|
||||
->placeholder('Bijv. Win VIP voor 1 maand!'),
|
||||
Textarea::make('giveaway.description')
|
||||
->label('Beschrijving')
|
||||
->rows(3)
|
||||
->placeholder('Beschrijf de winactie en de prijs...'),
|
||||
TextInput::make('giveaway.prize')
|
||||
->label('Prijs')
|
||||
->required()
|
||||
->placeholder('Bijv. VIP Lifetime'),
|
||||
TextInput::make('giveaway.prize_value')
|
||||
->label('Prijs Waarde (EUR)')
|
||||
->numeric()
|
||||
->placeholder('Bijv. 25.00'),
|
||||
DateTimePicker::make('giveaway.starts_at')
|
||||
->label('Start Datum/Tijd')
|
||||
->required()
|
||||
->default(now()),
|
||||
DateTimePicker::make('giveaway.ends_at')
|
||||
->label('Eind Datum/Tijd')
|
||||
->required()
|
||||
->default(now()->addDays(7)),
|
||||
]),
|
||||
Section::make('Actieve Winacties')
|
||||
->description('Beheer lopende winacties')
|
||||
->columns(1)
|
||||
->schema([
|
||||
Select::make('selected_giveaway')
|
||||
->label('Selecteer Winactie')
|
||||
->options(RadioGiveaway::where('is_active', true)->pluck('title', 'id'))
|
||||
->placeholder('Kies een winactie...')
|
||||
->live()
|
||||
->afterStateUpdated(function ($state) {
|
||||
if ($state) {
|
||||
$this->currentGiveaway = RadioGiveaway::find($state);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
Section::make('Acties')
|
||||
->description('Winactie beheer')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Action::make('export_participants')
|
||||
->label('Deelnemers Exporteren')
|
||||
->action('exportParticipants')
|
||||
->color('info'),
|
||||
Action::make('pick_winner')
|
||||
->label('Winnaar Trekken')
|
||||
->action('pickWinner')
|
||||
->color('warning')
|
||||
->requiresConfirmation(),
|
||||
Action::make('end_giveaway')
|
||||
->label('Winactie Beëindigen')
|
||||
->action('endGiveaway')
|
||||
->color('danger')
|
||||
->requiresConfirmation(),
|
||||
]),
|
||||
Section::make('Afgelopen Winacties')
|
||||
->description('Bekijk resultaten van afgelopen winacties')
|
||||
->columns(1)
|
||||
->schema([
|
||||
Select::make('ended_giveaway')
|
||||
->label('Selecteer Afgelopen Winactie')
|
||||
->options(RadioGiveaway::where('is_ended', true)->pluck('title', 'id'))
|
||||
->placeholder('Kies een winactie...'),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function createGiveaway(): void
|
||||
{
|
||||
$data = $this->data['giveaway'] ?? [];
|
||||
|
||||
$giveaway = RadioGiveaway::create([
|
||||
'title' => $data['title'],
|
||||
'description' => $data['description'] ?? '',
|
||||
'prize' => $data['prize'],
|
||||
'prize_value' => $data['prize_value'] ?? 0,
|
||||
'starts_at' => $data['starts_at'] ?? now(),
|
||||
'ends_at' => $data['ends_at'] ?? now()->addDays(7),
|
||||
'is_active' => true,
|
||||
'is_ended' => false,
|
||||
'winner_announced' => false,
|
||||
]);
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Winactie Aangemaakt')
|
||||
->body("Winactie '{$giveaway->title}' is succesvol aangemaakt!")
|
||||
->send();
|
||||
}
|
||||
|
||||
public function pickWinner(): void
|
||||
{
|
||||
$giveawayId = $this->data['selected_giveaway'] ?? null;
|
||||
|
||||
if (! $giveawayId) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Geen Winactie Geselecteerd')
|
||||
->body('Selecteer eerst een winactie.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$giveaway = RadioGiveaway::find($giveawayId);
|
||||
|
||||
if (! $giveaway) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Winactie Niet Gevonden')
|
||||
->body('De geselecteerde winactie bestaat niet meer.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$participants = $giveaway->participants()->with('user')->get();
|
||||
|
||||
if ($participants->isEmpty()) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Geen Deelnemers')
|
||||
->body('Er zijn nog geen deelnemers voor deze winactie.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$winner = $participants->random();
|
||||
|
||||
$giveaway->announceWinner($winner->user);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Winnaar Gekozen!')
|
||||
->body("Gefeliciteerd {$winner->user->username}! Je hebt {$giveaway->prize} gewonnen!")
|
||||
->send();
|
||||
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
public function endGiveaway(): void
|
||||
{
|
||||
$giveawayId = $this->data['selected_giveaway'] ?? null;
|
||||
|
||||
if (! $giveawayId) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Geen Winactie Geselecteerd')
|
||||
->body('Selecteer eerst een winactie.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$giveaway = RadioGiveaway::find($giveawayId);
|
||||
|
||||
$giveaway->update(['is_ended' => true, 'is_active' => false]);
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Winactie Beëindigd')
|
||||
->body("Winactie '{$giveaway->title}' is beëindigd.")
|
||||
->send();
|
||||
}
|
||||
|
||||
public function exportParticipants(): void
|
||||
{
|
||||
Notification::make()
|
||||
->info()
|
||||
->title('Export Gestart')
|
||||
->body('Deelnemerslijst wordt gedownload...')
|
||||
->send();
|
||||
}
|
||||
}
|
||||
Executable
+245
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\RadioListenerPoint;
|
||||
use App\Models\User;
|
||||
use App\Services\PointsService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Slider;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
final class PointsSettings extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-currency-euro';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = null;
|
||||
|
||||
#[\Override]
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Points System');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'Punten Systeem';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.radio.points-settings';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = [];
|
||||
|
||||
private PointsService $pointsService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pointsService = new PointsService;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$this->data = [
|
||||
'points_enabled' => $this->getSettingBool('points_enabled'),
|
||||
'points_per_minute' => (int) $this->getSetting('points_per_minute', '1'),
|
||||
'max_points_per_day' => (int) $this->getSetting('max_points_per_day', '100'),
|
||||
'points_for_request' => (int) $this->getSetting('points_for_request', '5'),
|
||||
'points_for_vote' => (int) $this->getSetting('points_for_vote', '2'),
|
||||
'points_for_giveaway_win' => (int) $this->getSetting('points_for_giveaway_win', '50'),
|
||||
'points_for_contest_win' => (int) $this->getSetting('points_for_contest_win', '100'),
|
||||
'points_for_shout' => (int) $this->getSetting('points_for_shout', '1'),
|
||||
];
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Punten Configuratie')
|
||||
->description('Configureer het punten systeem')
|
||||
->columns(2)
|
||||
->headerActions([
|
||||
Action::make('save_points')
|
||||
->label('Opslaan')
|
||||
->action('savePoints')
|
||||
->color('primary'),
|
||||
])
|
||||
->schema([
|
||||
Toggle::make('points_enabled')
|
||||
->label('Punten Systeem Inschakelen')
|
||||
->columnSpanFull(),
|
||||
Section::make('Luisteren')
|
||||
->description('Punten voor luisteren')
|
||||
->columns(1)
|
||||
->schema([
|
||||
Slider::make('points_per_minute')
|
||||
->label('Punten per Minuut')
|
||||
->minValue(0)
|
||||
->maxValue(10)
|
||||
->step(0.5)
|
||||
->default(1),
|
||||
TextInput::make('max_points_per_day')
|
||||
->label('Max. Punten per Dag')
|
||||
->numeric()
|
||||
->default(100),
|
||||
]),
|
||||
Section::make('Acties')
|
||||
->description('Punten voor acties')
|
||||
->columns(1)
|
||||
->schema([
|
||||
TextInput::make('points_for_request')
|
||||
->label('Punten voor Song Request')
|
||||
->numeric()
|
||||
->default(5),
|
||||
TextInput::make('points_for_vote')
|
||||
->label('Punten voor Stemmen')
|
||||
->numeric()
|
||||
->default(2),
|
||||
TextInput::make('points_for_shout')
|
||||
->label('Punten voor Shout')
|
||||
->numeric()
|
||||
->default(1),
|
||||
]),
|
||||
Section::make('Winnaars')
|
||||
->description('Punten voor winst')
|
||||
->columns(1)
|
||||
->schema([
|
||||
TextInput::make('points_for_giveaway_win')
|
||||
->label('Punten voor Winactie Winst')
|
||||
->numeric()
|
||||
->default(50),
|
||||
TextInput::make('points_for_contest_win')
|
||||
->label('Punten voor Competitie Winst')
|
||||
->numeric()
|
||||
->default(100),
|
||||
]),
|
||||
]),
|
||||
Section::make('Acties')
|
||||
->description('Systeem acties')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Action::make('reset_leaderboard')
|
||||
->label('Leaderboard Resetten')
|
||||
->action('resetLeaderboard')
|
||||
->color('warning')
|
||||
->requiresConfirmation(),
|
||||
Action::make('view_stats')
|
||||
->label('Bekijk Statistieken')
|
||||
->action('viewStats')
|
||||
->color('info'),
|
||||
Action::make('export_leaderboard')
|
||||
->label('Exporteren')
|
||||
->action('exportLeaderboard')
|
||||
->color('secondary'),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function savePoints(): void
|
||||
{
|
||||
$keys = [
|
||||
'points_enabled',
|
||||
'points_per_minute',
|
||||
'max_points_per_day',
|
||||
'points_for_request',
|
||||
'points_for_vote',
|
||||
'points_for_shout',
|
||||
'points_for_giveaway_win',
|
||||
'points_for_contest_win',
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$value = $this->data[$key] ?? '';
|
||||
|
||||
if ($key === 'points_enabled') {
|
||||
$dbValue = $value ? '1' : '0';
|
||||
} else {
|
||||
$dbValue = (string) $value;
|
||||
}
|
||||
|
||||
WebsiteSetting::updateOrCreate(['key' => $key], ['value' => $dbValue]);
|
||||
}
|
||||
|
||||
$this->pointsService->clearSettingsCache();
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(__('Saved'))
|
||||
->body('Punten instellingen zijn bijgewerkt!')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function resetLeaderboard(): void
|
||||
{
|
||||
User::where('radio_points', '>', 0)->update(['radio_points' => 0]);
|
||||
RadioListenerPoint::query()->delete();
|
||||
|
||||
$this->pointsService->clearLeaderboardCache();
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Leaderboard Gereset')
|
||||
->body('Alle punten zijn gewist.')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function viewStats(): void
|
||||
{
|
||||
$stats = $this->pointsService->getStats();
|
||||
|
||||
Notification::make()
|
||||
->title('Punten Statistieken')
|
||||
->body(
|
||||
"Totale punten: {$stats['total_points_awarded']}\n" .
|
||||
"Actieve gebruikers: {$stats['total_active_users']}",
|
||||
)
|
||||
->info()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function exportLeaderboard(): void
|
||||
{
|
||||
Notification::make()
|
||||
->info()
|
||||
->title('Export Gestart')
|
||||
->body('Leaderboard wordt gedownload...')
|
||||
->send();
|
||||
}
|
||||
|
||||
private function getSettingBool(string $key): bool
|
||||
{
|
||||
return (bool) WebsiteSetting::where('key', $key)->first()?->value;
|
||||
}
|
||||
|
||||
private function getSetting(string $key, string $default = ''): string
|
||||
{
|
||||
return WebsiteSetting::where('key', $key)->first()?->value ?? $default;
|
||||
}
|
||||
}
|
||||
Executable
+1630
File diff suppressed because it is too large
Load Diff
+310
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Services\StreamMonitoringService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
final class StreamMonitoring extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-signal';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = null;
|
||||
|
||||
#[\Override]
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Stream Status');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'Stream Monitoring';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.radio.stream-monitoring';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = [];
|
||||
|
||||
private StreamMonitoringService $monitoringService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->monitoringService = new StreamMonitoringService;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$this->data = [
|
||||
'radio_stream_url' => $this->getSetting('radio_stream_url', ''),
|
||||
'radio_monitoring_enabled' => $this->getSettingBool('radio_monitoring_enabled'),
|
||||
'radio_monitoring_interval' => (int) $this->getSetting('radio_monitoring_interval', '5'),
|
||||
'radio_alert_email' => $this->getSetting('radio_alert_email', ''),
|
||||
'radio_alert_enabled' => $this->getSettingBool('radio_alert_enabled'),
|
||||
];
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make('Monitoring')
|
||||
->tabs([
|
||||
Tab::make('Huidige Status')
|
||||
->icon('heroicon-o-signal')
|
||||
->schema([
|
||||
Section::make('Stream Status')
|
||||
->description('Real-time stream monitoring')
|
||||
->columns(2)
|
||||
->headerActions([
|
||||
Action::make('refresh')
|
||||
->label('Vernieuwen')
|
||||
->action('refreshStatus')
|
||||
->color('primary')
|
||||
->icon('heroicon-o-arrow-path'),
|
||||
])
|
||||
->schema([
|
||||
Section::make('Info')
|
||||
->description('Stream informatie')
|
||||
->columns(1)
|
||||
->schema([
|
||||
TextInput::make('status.online')
|
||||
->label('Status')
|
||||
->disabled(),
|
||||
TextInput::make('status.listeners')
|
||||
->label('Luisteraars')
|
||||
->disabled(),
|
||||
TextInput::make('status.bitrate')
|
||||
->label('Bitrate')
|
||||
->disabled(),
|
||||
TextInput::make('status.format')
|
||||
->label('Formaat')
|
||||
->disabled(),
|
||||
TextInput::make('status.server')
|
||||
->label('Server')
|
||||
->disabled(),
|
||||
]),
|
||||
]),
|
||||
Section::make('Uptime')
|
||||
->description('Uptime statistieken')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('uptime.last_online')
|
||||
->label('Laatst Online')
|
||||
->disabled(),
|
||||
TextInput::make('uptime.uptime_pct')
|
||||
->label('Uptime %')
|
||||
->disabled(),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Instellingen')
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->schema([
|
||||
Section::make('Monitoring Configuratie')
|
||||
->description('Configureer monitoring')
|
||||
->columns(2)
|
||||
->headerActions([
|
||||
Action::make('save_monitoring')
|
||||
->label('Opslaan')
|
||||
->action('saveMonitoring')
|
||||
->color('primary'),
|
||||
])
|
||||
->schema([
|
||||
Toggle::make('radio_monitoring_enabled')
|
||||
->label('Monitoring Inschakelen')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('radio_monitoring_interval')
|
||||
->label('Check Interval (minuten)')
|
||||
->numeric()
|
||||
->default(5),
|
||||
]),
|
||||
Section::make('Alert Instellingen')
|
||||
->description('Alert e-mails')
|
||||
->columns(2)
|
||||
->headerActions([
|
||||
Action::make('test_alert')
|
||||
->label('Test Alert')
|
||||
->action('testAlert')
|
||||
->color('info'),
|
||||
])
|
||||
->schema([
|
||||
Toggle::make('radio_alert_enabled')
|
||||
->label('Alerts Inschakelen')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('radio_alert_email')
|
||||
->label('Alert E-mail')
|
||||
->email()
|
||||
->placeholder('admin@jouwdomein.nl'),
|
||||
]),
|
||||
Section::make('Stream URL')
|
||||
->description('Stream configuratie')
|
||||
->columns(1)
|
||||
->schema([
|
||||
TextInput::make('radio_stream_url')
|
||||
->label('Stream URL')
|
||||
->url()
|
||||
->placeholder('https://radio.nl/stream')
|
||||
->columnSpanFull(),
|
||||
Action::make('test_stream')
|
||||
->label('Test Stream')
|
||||
->action('testStream')
|
||||
->color('success'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function refreshStatus(): void
|
||||
{
|
||||
$status = $this->monitoringService->getStatus();
|
||||
|
||||
$this->data['status'] = [
|
||||
'online' => $status['online'] ? 'Online' : 'Offline',
|
||||
'listeners' => $status['listeners'],
|
||||
'bitrate' => $status['bitrate'] ? $status['bitrate'] . ' kbps' : 'N/A',
|
||||
'format' => $status['format'] ?? 'N/A',
|
||||
'server' => $status['server_type'] ?? 'N/A',
|
||||
];
|
||||
|
||||
$uptime = $this->monitoringService->getUptime();
|
||||
|
||||
$this->data['uptime'] = [
|
||||
'last_online' => $uptime['last_online_human'] ?? 'Nooit',
|
||||
'uptime_pct' => ($uptime['uptime_percentage'] ?? 0) . '%',
|
||||
];
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Status Vernieuwd')
|
||||
->body($status['online'] ? 'Stream is Online' : 'Stream is Offline')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function saveMonitoring(): void
|
||||
{
|
||||
$keys = [
|
||||
'radio_monitoring_enabled', 'radio_monitoring_interval',
|
||||
'radio_alert_enabled', 'radio_alert_email',
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$value = $this->data[$key] ?? '';
|
||||
|
||||
if (in_array($key, ['radio_monitoring_enabled', 'radio_alert_enabled'])) {
|
||||
$dbValue = $value ? '1' : '0';
|
||||
} else {
|
||||
$dbValue = (string) $value;
|
||||
}
|
||||
|
||||
WebsiteSetting::updateOrCreate(['key' => $key], ['value' => $dbValue]);
|
||||
}
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(__('Saved'))
|
||||
->body('Monitoring instellingen zijn bijgewerkt!')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function testStream(): void
|
||||
{
|
||||
$url = $this->data['radio_stream_url'] ?? '';
|
||||
|
||||
if (empty($url)) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('URL Ontbreekt')
|
||||
->body('Voer eerst een stream URL in.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(10)->head($url);
|
||||
|
||||
if ($response->successful()) {
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Stream Bereikbaar!')
|
||||
->body('De stream URL is correct.')
|
||||
->send();
|
||||
} else {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Stream Niet Bereikbaar')
|
||||
->body('Status: ' . $response->status())
|
||||
->send();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Verbindingsfout')
|
||||
->body($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function testAlert(): void
|
||||
{
|
||||
$email = $this->data['radio_alert_email'] ?? '';
|
||||
|
||||
if (empty($email) || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('Ongeldig E-mail')
|
||||
->body('Voer een geldig e-mailadres in.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->info()
|
||||
->title('Test Alert')
|
||||
->body('Test alert zou verstuurd moeten worden.')
|
||||
->send();
|
||||
}
|
||||
|
||||
private function getSettingBool(string $key): bool
|
||||
{
|
||||
return (bool) WebsiteSetting::where('key', $key)->first()?->value;
|
||||
}
|
||||
|
||||
private function getSetting(string $key, string $default = ''): string
|
||||
{
|
||||
return WebsiteSetting::where('key', $key)->first()?->value ?? $default;
|
||||
}
|
||||
}
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Services\ContentModerationService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Slider;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
final class WordFilterSettings extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = null;
|
||||
|
||||
#[\Override]
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('Word Filter');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'AI Woord Filter';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.radio.word-filter-settings';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = [];
|
||||
|
||||
/** @var array<mixed> */
|
||||
public array $blockedWords = [];
|
||||
|
||||
private ContentModerationService $moderationService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->moderationService = new ContentModerationService;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$this->data = [
|
||||
'ai_filter_enabled' => $this->getSettingBool('ai_filter_enabled'),
|
||||
'ai_filter_auto_reject' => $this->getSettingBool('ai_filter_auto_reject'),
|
||||
'ai_filter_sensitivity' => (float) $this->getSetting('ai_filter_sensitivity', '0.7'),
|
||||
'ai_filter_method' => $this->getSetting('ai_filter_method', 'local'),
|
||||
'ai_filter_shouts' => $this->getSettingBool('ai_filter_shouts'),
|
||||
'ai_filter_chat' => $this->getSettingBool('ai_filter_chat'),
|
||||
'ai_filter_applications' => $this->getSettingBool('ai_filter_applications'),
|
||||
'ai_filter_requests' => $this->getSettingBool('ai_filter_requests'),
|
||||
'ai_filter_log_rejected' => $this->getSettingBool('ai_filter_log_rejected'),
|
||||
'openai_api_key' => $this->getSetting('openai_api_key', ''),
|
||||
];
|
||||
|
||||
$this->blockedWords = array_map(fn ($word) => ['word' => $word], $this->moderationService->getBlockedWords());
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Algemene Instellingen')
|
||||
->description('Basis configuratie voor het AI woordfilter')
|
||||
->columns(2)
|
||||
->afterHeader([
|
||||
Action::make('test_filter')
|
||||
->label('Filter Testen')
|
||||
->action('testFilter')
|
||||
->color('info'),
|
||||
Action::make('save_general')
|
||||
->label('Opslaan')
|
||||
->action('saveGeneral')
|
||||
->color('primary'),
|
||||
])
|
||||
->schema([
|
||||
Toggle::make('ai_filter_enabled')
|
||||
->label('Filter Inschakelen')
|
||||
->columnSpanFull()
|
||||
->helperText('Schakel AI-gebaseerde inhoudsfiltratie in voor alle radio gerelateerde content'),
|
||||
Toggle::make('ai_filter_auto_reject')
|
||||
->label('Auto Afwijzen')
|
||||
->helperText('Inhoud automatisch afwijzen zonder handmatige goedkeuring'),
|
||||
Select::make('ai_filter_method')
|
||||
->label('Filter Methode')
|
||||
->options([
|
||||
'local' => 'Lokaal (Woordenlijst)',
|
||||
'openai' => 'OpenAI API (AI-gebaseerd)',
|
||||
'both' => 'Beide Gecombineerd',
|
||||
])
|
||||
->default('local'),
|
||||
Slider::make('ai_filter_sensitivity')
|
||||
->label('Gevoeligheid')
|
||||
->minValue(0.1)
|
||||
->maxValue(1.0)
|
||||
->step(0.05)
|
||||
->default(0.7)
|
||||
->helperText('Hogere waarde = strenger filter (0.1 = lax, 1.0 = strikt)'),
|
||||
]),
|
||||
Section::make('OpenAI API Configuratie')
|
||||
->description('Configureer OpenAI voor AI-gebaseerde detectie')
|
||||
->columns(1)
|
||||
->afterHeader([
|
||||
Action::make('test_openai')
|
||||
->label('Test API')
|
||||
->action('testOpenAI')
|
||||
->color('success'),
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('openai_api_key')
|
||||
->label('OpenAI API Key')
|
||||
->password()
|
||||
->placeholder('sk-...')
|
||||
->helperText('Gratis moderatie endpoint beschikbaar via OpenAI. Haal je key op bij platform.openai.com'),
|
||||
]),
|
||||
Section::make('Te Filteren Inhoud')
|
||||
->description('Selecteer welke types content gefilterd moeten worden')
|
||||
->columns(2)
|
||||
->afterHeader([
|
||||
Action::make('save_content')
|
||||
->label('Opslaan')
|
||||
->action('saveContent')
|
||||
->color('primary'),
|
||||
])
|
||||
->schema([
|
||||
Toggle::make('ai_filter_shouts')
|
||||
->label('Shouts/Chat Berichten')
|
||||
->helperText('Filter alle shouts en chat berichten'),
|
||||
Toggle::make('ai_filter_chat')
|
||||
->label('Live Chat')
|
||||
->helperText('Filter live chat berichten in real-time'),
|
||||
Toggle::make('ai_filter_applications')
|
||||
->label('DJ Aanmeldingen')
|
||||
->helperText('Filter DJ sollicitatie formulieren'),
|
||||
Toggle::make('ai_filter_requests')
|
||||
->label('Song Verzoeken')
|
||||
->helperText('Filter song request berichten'),
|
||||
Toggle::make('ai_filter_log_rejected')
|
||||
->label('Log Afwijzingen')
|
||||
->helperText('Bewaar een logboek van afgewezen berichten'),
|
||||
]),
|
||||
Section::make('Geblokkeerde Woorden')
|
||||
->description('Beheer de lokale woordenlijst voor directe filtering')
|
||||
->columns(1)
|
||||
->afterHeader([
|
||||
Action::make('add_word')
|
||||
->label('Woord Toevoegen')
|
||||
->action('addWord')
|
||||
->color('success'),
|
||||
Action::make('clear_all')
|
||||
->label('Alles Wissen')
|
||||
->action('clearAllWords')
|
||||
->color('danger')
|
||||
->requiresConfirmation(),
|
||||
Action::make('save_words')
|
||||
->label('Opslaan')
|
||||
->action('saveWords')
|
||||
->color('primary'),
|
||||
])
|
||||
->schema([
|
||||
Repeater::make('blocked_words')
|
||||
->label('Geblokkeerde Woorden')
|
||||
->schema([
|
||||
TextInput::make('word')
|
||||
->label('Woord')
|
||||
->required()
|
||||
->placeholder('vul een woord in'),
|
||||
])
|
||||
->addActionLabel('Woord toevoegen')
|
||||
->defaultItems(0),
|
||||
]),
|
||||
Section::make('Bulk Acties')
|
||||
->description('Snelle acties voor de woordenlijst')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Action::make('add_common_words')
|
||||
->label('Veelvoorkomende Woorden')
|
||||
->action('addCommonWords')
|
||||
->color('secondary')
|
||||
->requiresConfirmation(),
|
||||
Action::make('export_words')
|
||||
->label('Exporteren')
|
||||
->action('exportWords')
|
||||
->color('secondary'),
|
||||
]),
|
||||
Section::make('Filter Statistieken')
|
||||
->description('Overzicht van filter activiteit')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Action::make('view_stats')
|
||||
->label('Bekijk Statistieken')
|
||||
->action('viewStats')
|
||||
->color('info'),
|
||||
Action::make('reset_stats')
|
||||
->label('Resetten')
|
||||
->action('resetStats')
|
||||
->color('warning')
|
||||
->requiresConfirmation(),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function testFilter(): void
|
||||
{
|
||||
$testPhrases = [
|
||||
'Dit is een normale zin',
|
||||
'Dit bevat slecht_woord',
|
||||
];
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($testPhrases as $phrase) {
|
||||
$result = $this->moderationService->moderate($phrase);
|
||||
$results[] = [
|
||||
'phrase' => $phrase,
|
||||
'approved' => $result['approved'],
|
||||
'score' => $result['score'],
|
||||
];
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Filter Test')
|
||||
->body(json_encode($results, JSON_PRETTY_PRINT))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function testOpenAI(): void
|
||||
{
|
||||
$apiKey = $this->data['openai_api_key'] ?? '';
|
||||
|
||||
if (empty($apiKey)) {
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title('API Key Ontbreekt')
|
||||
->body('Voer eerst een OpenAI API key in.')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
])->post('https://api.openai.com/v1/moderations', [
|
||||
'input' => 'Test message',
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('OpenAI API Werkt!')
|
||||
->body('API verbinding succesvol.')
|
||||
->send();
|
||||
} else {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('API Fout')
|
||||
->body('Status: ' . $response->status())
|
||||
->send();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Verbindingsfout')
|
||||
->body($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function addWord(): void
|
||||
{
|
||||
Notification::make()
|
||||
->info()
|
||||
->title('Woord Toevoegen')
|
||||
->body('Gebruik de repeater om woorden toe te voegen.')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function addCommonWords(): void
|
||||
{
|
||||
$commonWords = [
|
||||
'slecht', 'vervelend', 'stom', 'idiot', 'debiel', 'kanker', 'hoer',
|
||||
'kut', 'godver', 'verdomme', 'fuck', 'shit', 'bitch', 'asshole',
|
||||
];
|
||||
|
||||
foreach ($commonWords as $word) {
|
||||
$this->moderationService->addBlockedWord(trim($word));
|
||||
}
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Woorden Toegevoegd')
|
||||
->body(count($commonWords) . ' woorden toegevoegd.')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function clearAllWords(): void
|
||||
{
|
||||
foreach ($this->moderationService->getBlockedWords() as $word) {
|
||||
$this->moderationService->removeBlockedWord($word);
|
||||
}
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Wissen Succesvol')
|
||||
->body('Alle woorden verwijderd.')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function saveGeneral(): void
|
||||
{
|
||||
$this->saveSettings([
|
||||
'ai_filter_enabled', 'ai_filter_auto_reject', 'ai_filter_sensitivity',
|
||||
'ai_filter_method', 'openai_api_key',
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveContent(): void
|
||||
{
|
||||
$this->saveSettings([
|
||||
'ai_filter_shouts', 'ai_filter_chat', 'ai_filter_applications',
|
||||
'ai_filter_requests', 'ai_filter_log_rejected',
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveWords(): void
|
||||
{
|
||||
foreach ($this->blockedWords as $item) {
|
||||
if (! in_array(trim($item['word'] ?? ''), ['', '0'], true)) {
|
||||
$this->moderationService->addBlockedWord(trim((string) $item['word']));
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(__('Saved'))
|
||||
->body('Woordenlijst bijgewerkt.')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function exportWords(): void
|
||||
{
|
||||
$words = $this->moderationService->getBlockedWords();
|
||||
|
||||
$filename = 'blocked_words_' . date('Y-m-d') . '.json';
|
||||
|
||||
response()->streamDownload(function () use ($words) {
|
||||
echo json_encode($words, JSON_PRETTY_PRINT);
|
||||
}, $filename)->send();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Export Gestart')
|
||||
->body('Download is gestart.')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function viewStats(): void
|
||||
{
|
||||
$stats = $this->moderationService->getStats();
|
||||
|
||||
Notification::make()
|
||||
->title('Filter Statistieken')
|
||||
->body(json_encode($stats, JSON_PRETTY_PRINT))
|
||||
->info()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function resetStats(): void
|
||||
{
|
||||
WebsiteSetting::where('key', 'like', 'filter_stats_%')->delete();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Gereset')
|
||||
->body('Statistieken zijn gewist.')
|
||||
->send();
|
||||
}
|
||||
|
||||
private function saveSettings(array $keys): void
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
$value = $this->data[$key] ?? '';
|
||||
|
||||
$boolKeys = [
|
||||
'ai_filter_enabled', 'ai_filter_auto_reject', 'ai_filter_shouts',
|
||||
'ai_filter_chat', 'ai_filter_applications', 'ai_filter_requests',
|
||||
'ai_filter_log_rejected',
|
||||
];
|
||||
|
||||
$dbValue = in_array($key, $boolKeys) ? ($value ? '1' : '0') : (string) $value;
|
||||
|
||||
WebsiteSetting::updateOrCreate(['key' => $key], ['value' => $dbValue]);
|
||||
}
|
||||
|
||||
$this->fillForm();
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(__('Saved'))
|
||||
->body('Instellingen zijn opgeslagen!')
|
||||
->send();
|
||||
}
|
||||
|
||||
private function getSettingBool(string $key): bool
|
||||
{
|
||||
return (bool) WebsiteSetting::where('key', $key)->first()?->value;
|
||||
}
|
||||
|
||||
private function getSetting(string $key, string $default = ''): string
|
||||
{
|
||||
return WebsiteSetting::where('key', $key)->first()?->value ?? $default;
|
||||
}
|
||||
}
|
||||
Executable
+439
@@ -0,0 +1,439 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\VPN;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteBlockedCountry;
|
||||
use App\Models\Miscellaneous\WebsiteIpBlacklist;
|
||||
use App\Models\Miscellaneous\WebsiteIpWhitelist;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Services\IpLookupService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
|
||||
final class VPNManagement extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shield-exclamation';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Website';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = 'VPN Beheer';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'VPN & IP Beheer';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.vpn.vpn-management';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $data = [];
|
||||
|
||||
public $blockedCountries;
|
||||
|
||||
public $whitelistedIps;
|
||||
|
||||
public $blacklistedIps;
|
||||
|
||||
public $blocklistStats;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
$this->loadData();
|
||||
}
|
||||
|
||||
protected function loadData(): void
|
||||
{
|
||||
$this->blockedCountries = WebsiteBlockedCountry::orderBy('country_name')->get();
|
||||
$this->whitelistedIps = WebsiteIpWhitelist::orderBy('created_at', 'desc')->get();
|
||||
$this->blacklistedIps = WebsiteIpBlacklist::orderBy('created_at', 'desc')->get();
|
||||
|
||||
$ipService = new IpLookupService('');
|
||||
$this->blocklistStats = $ipService->getBlocklistStats();
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$this->data = [
|
||||
'vpn_block_enabled' => $this->getSettingBool('vpn_block_enabled'),
|
||||
'country_block_enabled' => $this->getSettingBool('country_block_enabled'),
|
||||
'block_vpn' => $this->getSettingBool('block_vpn'),
|
||||
'block_tor' => $this->getSettingBool('block_tor'),
|
||||
'block_malicious' => $this->getSettingBool('block_malicious'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getSetting(string $key, string $default = ''): string
|
||||
{
|
||||
return WebsiteSetting::where('key', $key)->first()?->value ?? $default;
|
||||
}
|
||||
|
||||
protected function getSettingBool(string $key): bool
|
||||
{
|
||||
return WebsiteSetting::where('key', $key)->first()?->value === '1';
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make('VPN Beheer')
|
||||
->tabs([
|
||||
Tab::make('Instellingen')
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->schema([
|
||||
Section::make('Gratis Blokkering (Onbeperkt)')
|
||||
->description('Gebruikt gratis community blocklists - geen API keys nodig')
|
||||
->schema([
|
||||
Toggle::make('data.vpn_block_enabled')
|
||||
->label('VPN/TOR/Proxy Blokker Actief')
|
||||
->helperText('Blokkeer VPN, TOR exit nodes, proxies en bekende malicious IPs'),
|
||||
]),
|
||||
Section::make('Wat blokkeren?')
|
||||
->schema([
|
||||
Toggle::make('data.block_vpn')
|
||||
->label('VPN & Proxies')
|
||||
->helperText('FireHol blocklist'),
|
||||
Toggle::make('data.block_tor')
|
||||
->label('TOR Exit Nodes')
|
||||
->helperText('Alle bekende TOR exit nodes'),
|
||||
Toggle::make('data.block_malicious')
|
||||
->label('Malicious IPs')
|
||||
->helperText('Bekende kwaadwillende IPs uit community databases'),
|
||||
]),
|
||||
Section::make('Land Blokkering')
|
||||
->schema([
|
||||
Toggle::make('data.country_block_enabled')
|
||||
->label('Land Blokkering Actief')
|
||||
->helperText('Sta alleen bezoekers uit toegestane landen toe'),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Geblokkeerde Landen')
|
||||
->icon('heroicon-o-globe-alt')
|
||||
->schema([
|
||||
Section::make('Land toevoegen')
|
||||
->schema([
|
||||
Select::make('country_to_block')
|
||||
->label('Selecteer Land')
|
||||
->options($this->getCountryOptions())
|
||||
->searchable()
|
||||
->placeholder('Kies een land'),
|
||||
Action::make('addCountry')
|
||||
->label('Blokkeer Land')
|
||||
->action('addBlockedCountry')
|
||||
->color('danger'),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Whitelist')
|
||||
->icon('heroicon-o-check-circle')
|
||||
->schema([
|
||||
Section::make('Whitelist toevoegen')
|
||||
->schema([
|
||||
TextInput::make('whitelist_ip')
|
||||
->label('IP Adres')
|
||||
->placeholder('192.168.1.1'),
|
||||
TextInput::make('whitelist_asn')
|
||||
->label('ASN')
|
||||
->placeholder('AS15169'),
|
||||
Select::make('whitelist_country')
|
||||
->label('Land')
|
||||
->options($this->getCountryOptions())
|
||||
->searchable(),
|
||||
Action::make('addToWhitelist')
|
||||
->label('Toevoegen')
|
||||
->action('addToWhitelist')
|
||||
->color('success'),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Blacklist')
|
||||
->icon('heroicon-o-x-circle')
|
||||
->schema([
|
||||
Section::make('Blacklist toevoegen')
|
||||
->schema([
|
||||
TextInput::make('blacklist_ip')
|
||||
->label('IP Adres')
|
||||
->placeholder('192.168.1.1'),
|
||||
TextInput::make('blacklist_asn')
|
||||
->label('ASN')
|
||||
->placeholder('AS15169'),
|
||||
Select::make('blacklist_country')
|
||||
->label('Land')
|
||||
->options($this->getCountryOptions())
|
||||
->searchable(),
|
||||
Action::make('addToBlacklist')
|
||||
->label('Toevoegen')
|
||||
->action('addToBlacklist')
|
||||
->color('danger'),
|
||||
]),
|
||||
]),
|
||||
Tab::make('IP Lookup')
|
||||
->icon('heroicon-o-magnifying-glass')
|
||||
->schema([
|
||||
Section::make('IP Opzoeken')
|
||||
->schema([
|
||||
TextInput::make('lookup_ip')
|
||||
->label('IP Adres')
|
||||
->placeholder('Voer IP in om te controleren'),
|
||||
Action::make('lookupIp')
|
||||
->label('Controleer')
|
||||
->action('lookupIp')
|
||||
->color('info'),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Blocklist Stats')
|
||||
->icon('heroicon-o-chart-bar')
|
||||
->schema([
|
||||
Section::make('Huidige Blocklists')
|
||||
->description('Deze lijsten worden automatisch gedownload en dagelijks ververst')
|
||||
->schema([
|
||||
TextColumn::make('type')->label('Type'),
|
||||
TextColumn::make('count')->label('Aantal IPs'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
protected function getCountryOptions(): array
|
||||
{
|
||||
return [
|
||||
'AD' => 'Andorra', 'AE' => 'United Arab Emirates', 'AF' => 'Afghanistan',
|
||||
'AG' => 'Antigua and Barbuda', 'AI' => 'Anguilla', 'AL' => 'Albania',
|
||||
'AM' => 'Armenia', 'AO' => 'Angola', 'AR' => 'Argentina',
|
||||
'AT' => 'Austria', 'AU' => 'Australia', 'AW' => 'Aruba',
|
||||
'AZ' => 'Azerbaijan', 'BA' => 'Bosnia and Herzegovina', 'BB' => 'Barbados',
|
||||
'BD' => 'Bangladesh', 'BE' => 'Belgium', 'BG' => 'Bulgaria',
|
||||
'BH' => 'Bahrain', 'BI' => 'Burundi', 'BJ' => 'Benin',
|
||||
'BM' => 'Bermuda', 'BN' => 'Brunei', 'BO' => 'Bolivia',
|
||||
'BR' => 'Brazil', 'BS' => 'Bahamas', 'BT' => 'Bhutan',
|
||||
'BW' => 'Botswana', 'BY' => 'Belarus', 'BZ' => 'Belize',
|
||||
'CA' => 'Canada', 'CH' => 'Switzerland', 'CL' => 'Chile',
|
||||
'CN' => 'China', 'CO' => 'Colombia', 'CR' => 'Costa Rica',
|
||||
'CU' => 'Cuba', 'CY' => 'Cyprus', 'CZ' => 'Czechia',
|
||||
'DE' => 'Germany', 'DK' => 'Denmark', 'DO' => 'Dominican Republic',
|
||||
'DZ' => 'Algeria', 'EC' => 'Ecuador', 'EE' => 'Estonia',
|
||||
'EG' => 'Egypt', 'ES' => 'Spain', 'ET' => 'Ethiopia',
|
||||
'FI' => 'Finland', 'FJ' => 'Fiji', 'FR' => 'France',
|
||||
'GB' => 'United Kingdom', 'GD' => 'Grenada', 'GE' => 'Georgia',
|
||||
'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GL' => 'Greenland',
|
||||
'GM' => 'Gambia', 'GN' => 'Guinea', 'GR' => 'Greece',
|
||||
'GT' => 'Guatemala', 'HK' => 'Hong Kong', 'HN' => 'Honduras',
|
||||
'HR' => 'Croatia', 'HU' => 'Hungary', 'ID' => 'Indonesia',
|
||||
'IE' => 'Ireland', 'IL' => 'Israel', 'IN' => 'India',
|
||||
'IQ' => 'Iraq', 'IR' => 'Iran', 'IS' => 'Iceland',
|
||||
'IT' => 'Italy', 'JM' => 'Jamaica', 'JO' => 'Jordan',
|
||||
'JP' => 'Japan', 'KE' => 'Kenya', 'KH' => 'Cambodia',
|
||||
'KR' => 'South Korea', 'KW' => 'Kuwait', 'KZ' => 'Kazakhstan',
|
||||
'LA' => 'Laos', 'LB' => 'Lebanon', 'LK' => 'Sri Lanka',
|
||||
'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'LV' => 'Latvia',
|
||||
'MA' => 'Morocco', 'MC' => 'Monaco', 'MD' => 'Moldova',
|
||||
'ME' => 'Montenegro', 'MG' => 'Madagascar', 'MK' => 'North Macedonia',
|
||||
'MM' => 'Myanmar', 'MN' => 'Mongolia', 'MO' => 'Macao',
|
||||
'MT' => 'Malta', 'MU' => 'Mauritius', 'MV' => 'Maldives',
|
||||
'MX' => 'Mexico', 'MY' => 'Malaysia', 'MZ' => 'Mozambique',
|
||||
'NA' => 'Namibia', 'NG' => 'Nigeria', 'NI' => 'Nicaragua',
|
||||
'NL' => 'Netherlands', 'NO' => 'Norway', 'NP' => 'Nepal',
|
||||
'NZ' => 'New Zealand', 'OM' => 'Oman', 'PA' => 'Panama',
|
||||
'PE' => 'Peru', 'PH' => 'Philippines', 'PK' => 'Pakistan',
|
||||
'PL' => 'Poland', 'PR' => 'Puerto Rico', 'PT' => 'Portugal',
|
||||
'PY' => 'Paraguay', 'QA' => 'Qatar', 'RO' => 'Romania',
|
||||
'RS' => 'Serbia', 'RU' => 'Russia', 'RW' => 'Rwanda',
|
||||
'SA' => 'Saudi Arabia', 'SC' => 'Seychelles', 'SD' => 'Sudan',
|
||||
'SE' => 'Sweden', 'SG' => 'Singapore', 'SI' => 'Slovenia',
|
||||
'SK' => 'Slovakia', 'SN' => 'Senegal', 'SO' => 'Somalia',
|
||||
'SR' => 'Suriname', 'SV' => 'El Salvador', 'SY' => 'Syria',
|
||||
'TH' => 'Thailand', 'TJ' => 'Tajikistan', 'TN' => 'Tunisia',
|
||||
'TR' => 'Turkey', 'TT' => 'Trinidad and Tobago', 'TW' => 'Taiwan',
|
||||
'TZ' => 'Tanzania', 'UA' => 'Ukraine', 'UG' => 'Uganda',
|
||||
'US' => 'United States', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan',
|
||||
'VE' => 'Venezuela', 'VN' => 'Vietnam', 'ZA' => 'South Africa',
|
||||
'ZM' => 'Zambia', 'ZW' => 'Zimbabwe',
|
||||
];
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$settings = [
|
||||
'vpn_block_enabled' => $this->data['vpn_block_enabled'] ? '1' : '0',
|
||||
'country_block_enabled' => $this->data['country_block_enabled'] ? '1' : '0',
|
||||
'block_vpn' => $this->data['block_vpn'] ?? true ? '1' : '0',
|
||||
'block_tor' => $this->data['block_tor'] ?? true ? '1' : '0',
|
||||
'block_malicious' => $this->data['block_malicious'] ?? true ? '1' : '0',
|
||||
];
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
WebsiteSetting::updateOrCreate(['key' => $key], ['value' => $value]);
|
||||
}
|
||||
|
||||
Notification::make()->title(__('Saved'))->success()->send();
|
||||
}
|
||||
|
||||
public function refreshBlocklists(): void
|
||||
{
|
||||
$ipService = new IpLookupService('');
|
||||
$ipService->refreshBlocklists();
|
||||
$this->loadData();
|
||||
|
||||
Notification::make()->title('Blocklists vernieuwd')->success()->send();
|
||||
}
|
||||
|
||||
public function addBlockedCountry(): void
|
||||
{
|
||||
$countryCode = $this->data['country_to_block'] ?? null;
|
||||
if (! $countryCode) {
|
||||
Notification::make()->title('Selecteer een land')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (WebsiteBlockedCountry::where('country_code', $countryCode)->exists()) {
|
||||
Notification::make()->title('Land is al geblokkeerd')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$countries = $this->getCountryOptions();
|
||||
$countryCode = (string) $countryCode;
|
||||
WebsiteBlockedCountry::create([
|
||||
'country_code' => $countryCode,
|
||||
'country_name' => $countries[$countryCode] ?? $countryCode,
|
||||
]);
|
||||
|
||||
$this->loadData();
|
||||
Notification::make()->title('Land geblokkeerd')->success()->send();
|
||||
}
|
||||
|
||||
public function removeBlockedCountry(int $id): void
|
||||
{
|
||||
WebsiteBlockedCountry::destroy($id);
|
||||
$this->loadData();
|
||||
Notification::make()->title('Verwijderd')->success()->send();
|
||||
}
|
||||
|
||||
public function addToWhitelist(): void
|
||||
{
|
||||
$ip = $this->data['whitelist_ip'] ?? null;
|
||||
$asn = $this->data['whitelist_asn'] ?? null;
|
||||
$countryCode = $this->data['whitelist_country'] ?? null;
|
||||
|
||||
if (! $ip && ! $asn && ! $countryCode) {
|
||||
Notification::make()->title('Voer IP, ASN of land in')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$countries = $this->getCountryOptions();
|
||||
$countryCodeStr = (string) ($countryCode ?? '');
|
||||
$countryName = $countryCode ? ($countries[$countryCodeStr] ?? null) : null;
|
||||
|
||||
WebsiteIpWhitelist::create([
|
||||
'ip_address' => $ip ?? null,
|
||||
'asn' => $asn ?? null,
|
||||
'country_code' => $countryCodeStr,
|
||||
'country_name' => $countryName,
|
||||
'whitelist_asn' => ! empty($asn),
|
||||
'whitelist_country' => ! empty($countryCode),
|
||||
]);
|
||||
|
||||
$this->loadData();
|
||||
Notification::make()->title('Toegevoegd aan whitelist')->success()->send();
|
||||
}
|
||||
|
||||
public function addToBlacklist(): void
|
||||
{
|
||||
$ip = $this->data['blacklist_ip'] ?? null;
|
||||
$asn = $this->data['blacklist_asn'] ?? null;
|
||||
$countryCode = $this->data['blacklist_country'] ?? null;
|
||||
|
||||
if (! $ip && ! $asn && ! $countryCode) {
|
||||
Notification::make()->title('Voer IP, ASN of land in')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$countries = $this->getCountryOptions();
|
||||
$countryCodeStr = (string) ($countryCode ?? '');
|
||||
$countryName = $countryCode ? ($countries[$countryCodeStr] ?? null) : null;
|
||||
|
||||
WebsiteIpBlacklist::create([
|
||||
'ip_address' => $ip ?? null,
|
||||
'asn' => $asn ?? null,
|
||||
'country_code' => $countryCodeStr,
|
||||
'country_name' => $countryName,
|
||||
'blacklist_asn' => ! empty($asn),
|
||||
'blacklist_country' => ! empty($countryCode),
|
||||
]);
|
||||
|
||||
$this->loadData();
|
||||
Notification::make()->title('Toegevoegd aan blacklist')->success()->send();
|
||||
}
|
||||
|
||||
public function removeFromWhitelist(int $id): void
|
||||
{
|
||||
WebsiteIpWhitelist::destroy($id);
|
||||
$this->loadData();
|
||||
Notification::make()->title('Verwijderd')->success()->send();
|
||||
}
|
||||
|
||||
public function removeFromBlacklist(int $id): void
|
||||
{
|
||||
WebsiteIpBlacklist::destroy($id);
|
||||
$this->loadData();
|
||||
Notification::make()->title('Verwijderd')->success()->send();
|
||||
}
|
||||
|
||||
public function lookupIp(): void
|
||||
{
|
||||
$ip = $this->data['lookup_ip'] ?? null;
|
||||
if (! $ip) {
|
||||
Notification::make()->title('Voer een IP in')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ipService = new IpLookupService('');
|
||||
$countryInfo = $ipService->getCountryInfo($ip);
|
||||
$threatInfo = $ipService->checkVpnProxyTor($ip);
|
||||
|
||||
$message = "IP: {$ip}\n\n";
|
||||
|
||||
if (! empty($countryInfo['country_name'])) {
|
||||
$message .= "📍 {$countryInfo['country_name']} ({$countryInfo['country_code']})\n";
|
||||
$message .= " {$countryInfo['city']} - {$countryInfo['isp']}\n\n";
|
||||
}
|
||||
|
||||
$message .= "🚫 Blokkeren:\n";
|
||||
$message .= ' VPN/Proxy: ' . ($threatInfo['is_vpn'] ? 'JA' : 'Nee') . "\n";
|
||||
$message .= ' TOR: ' . ($threatInfo['is_tor'] ? 'JA' : 'Nee') . "\n";
|
||||
$message .= ' Malicious: ' . ($threatInfo['is_malicious'] ? 'JA' : 'Nee') . "\n";
|
||||
|
||||
Notification::make()->title('Resultaat')->body($message)->success()->send();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('save')->label('Opslaan')->action('save')->color('primary'),
|
||||
Action::make('refreshBlocklists')->label('Vernieuw Blocklists')->action('refreshBlocklists')->color('warning'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+241
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+62
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -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;
|
||||
}
|
||||
Executable
+23
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+28
@@ -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';
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -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;
|
||||
}
|
||||
+52
@@ -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
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
+23
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
Executable
+56
@@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+125
@@ -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('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+23
@@ -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
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+95
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
+55
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
+184
@@ -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
@@ -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('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+75
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateBadgeTextEditor extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeTextEditorResource::class;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PDOException;
|
||||
|
||||
class EditBadgeTextEditor extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeTextEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [DeleteAction::make()];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void {}
|
||||
|
||||
#[\Override]
|
||||
protected function handleRecordUpdate(Model $record, array $data): Model
|
||||
{
|
||||
try {
|
||||
return parent::handleRecordUpdate($record, $data);
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() === '23000') {
|
||||
Log::error('Duplicate badge key error: ' . $e->getMessage());
|
||||
|
||||
Notification::make()
|
||||
->title('Duplicate Badge Key')
|
||||
->body('The badge key already exists. Please use a unique badge key.')
|
||||
->danger()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
return $record;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
|
||||
use App\Models\WebsiteBadge;
|
||||
use App\Services\SettingsService;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ListBadgeTextEditors extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeTextEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Add Badge')
|
||||
->color('info')
|
||||
->modalHeading('Add a New Badge')
|
||||
->modalButton('Create Badge')
|
||||
->after(function () {
|
||||
Notification::make()
|
||||
->title('Badge Created')
|
||||
->body('The badge was successfully created.')
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
Action::make('export')
|
||||
->label('Export to ExternalTexts')
|
||||
->action('exportToJson'),
|
||||
Action::make('backup')
|
||||
->label('Create Backup of ExternalTexts')
|
||||
->color('success')
|
||||
->action('createBackup'),
|
||||
];
|
||||
}
|
||||
|
||||
public function exportToJson(SettingsService $settingsService): void
|
||||
{
|
||||
$jsonPath = $settingsService->getOrDefault('nitro_external_texts_file');
|
||||
|
||||
if (empty($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Export Failed')
|
||||
->body('The JSON file path is not configured in the website settings.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! file_exists($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Export Failed')
|
||||
->body('The JSON file does not exist at the specified path.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$jsonData = json_decode(file_get_contents($jsonPath), true);
|
||||
|
||||
$badges = WebsiteBadge::all();
|
||||
$badgeKeys = $badges->pluck('badge_key')->toArray();
|
||||
|
||||
foreach ($jsonData as $key => $value) {
|
||||
if (
|
||||
(str_starts_with((string) $key, 'badge_desc_') || str_starts_with((string) $key, 'badge_name_')) &&
|
||||
! in_array(str_replace(['badge_desc_', 'badge_name_'], '', $key), $badgeKeys)
|
||||
) {
|
||||
unset($jsonData[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($badges as $badge) {
|
||||
$jsonData['badge_desc_' . $badge->badge_key] = $badge->badge_description;
|
||||
$jsonData['badge_name_' . $badge->badge_key] = $badge->badge_name;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = file_put_contents(
|
||||
$jsonPath,
|
||||
json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
throw new Exception('Failed to write to the JSON file.');
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Export Successful')
|
||||
->body('Badge data exported successfully.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to export badge data: ' . $e->getMessage());
|
||||
|
||||
Notification::make()
|
||||
->title('Export Failed')
|
||||
->body('Failed to export badge data. Please check file permissions or contact your administrator.')
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function createBackup(SettingsService $settingsService): void
|
||||
{
|
||||
$jsonPath = $settingsService->getOrDefault('nitro_external_texts_file');
|
||||
|
||||
if (empty($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Backup Failed')
|
||||
->body('The JSON file path is not configured in the website settings.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! file_exists($jsonPath)) {
|
||||
Notification::make()
|
||||
->title('Backup Failed')
|
||||
->body('The JSON file does not exist at the specified path.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$backupPath = dirname((string) $jsonPath) . '/ExternalTexts_' . time() . '.json';
|
||||
|
||||
if (copy($jsonPath, $backupPath)) {
|
||||
Notification::make()
|
||||
->title('Backup Successful')
|
||||
->body('A backup of the JSON file has been created: ' . basename($backupPath))
|
||||
->success()
|
||||
->send();
|
||||
} else {
|
||||
Notification::make()
|
||||
->title('Backup Failed')
|
||||
->body('Failed to create a backup of the JSON file.')
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeUploads;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeUploads\Pages\ManageBadgeUploads;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class BadgeUploadResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-gif';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $label = 'Badge Upload';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
FileUpload::make('badge_file')
|
||||
->label('Upload Badge')
|
||||
->disk('local')
|
||||
->directory(setting('badge_path_filesystem'))
|
||||
->required()
|
||||
->getUploadedFileNameForStorageUsing(
|
||||
fn (TemporaryUploadedFile $file): string => strtolower(str_replace([' ', '-', 'æ', 'ø', 'å'], ['_', '_', 'ae', 'oe', 'aa'], $file->getClientOriginalName())),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('filename')
|
||||
->label('File Name')
|
||||
->sortable(),
|
||||
TextColumn::make('path')
|
||||
->label('File Path'),
|
||||
])
|
||||
->filters([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageBadgeUploads::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getFiles(): array
|
||||
{
|
||||
$badgePath = env('BadgePath', 'badges');
|
||||
$files = Storage::disk('local')->files($badgePath);
|
||||
|
||||
return collect($files)->map(fn ($file) => [
|
||||
'filename' => basename($file),
|
||||
'path' => $file,
|
||||
])->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\BadgeUploads\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\BadgeUploads\BadgeUploadResource;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Schemas\Components\Form;
|
||||
|
||||
/**
|
||||
* @property-read Form $form
|
||||
*/
|
||||
class ManageBadgeUploads extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
public $badge_file;
|
||||
|
||||
#[\Override]
|
||||
protected static string $resource = BadgeUploadResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.manage-badge-uploads';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
// initialize form; Filament handles default values via getFormSchema
|
||||
// Avoid using fill() on the Form component in this context
|
||||
// No explicit form fill here
|
||||
}
|
||||
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
FileUpload::make('badge_file')
|
||||
->label('Upload Badge')
|
||||
->disk('badges')
|
||||
->preserveFilenames()
|
||||
->acceptedFileTypes(['image/gif'])
|
||||
->rules(['mimes:gif'])
|
||||
->required(),
|
||||
];
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->form->getState();
|
||||
|
||||
Notification::make()
|
||||
->title('Badge uploaded successfully!')
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors;
|
||||
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CatalogEditorResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = CatalogPage::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = 'Catalog Editor';
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')->label('ID')->sortable(),
|
||||
TextColumn::make('caption')->label('Page Name')->searchable(),
|
||||
TextColumn::make('parent_id')->label('Parent ID'),
|
||||
TextColumn::make('order_num')->label('Order'),
|
||||
IconColumn::make('visible')->boolean()->label('Visible'),
|
||||
IconColumn::make('enabled')->boolean()->label('Enabled'),
|
||||
])
|
||||
->recordActions([])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('caption')
|
||||
->label('Page Name')
|
||||
->required()
|
||||
->maxLength(128),
|
||||
|
||||
TextInput::make('caption_save')
|
||||
->label('Name TAG')
|
||||
->maxLength(25)
|
||||
->helperText('Lowercase letters only (a-z), no spaces.'),
|
||||
|
||||
Select::make('parent_id')
|
||||
->label('Parent Page')
|
||||
->options(fn () => CatalogPage::all()
|
||||
->pluck('caption', 'id')
|
||||
->toArray())
|
||||
->default(-1),
|
||||
|
||||
TextInput::make('order_num')
|
||||
->label('Order')
|
||||
->numeric()
|
||||
->default(1),
|
||||
|
||||
TextInput::make('icon_image')
|
||||
->label('Icon Number')
|
||||
->numeric()
|
||||
->default(1),
|
||||
|
||||
TextInput::make('page_layout')
|
||||
->label('Page Layout')
|
||||
->default('default_3x3'),
|
||||
|
||||
TextInput::make('min_rank')
|
||||
->label('Min Rank')
|
||||
->numeric()
|
||||
->default(1),
|
||||
|
||||
Toggle::make('visible')
|
||||
->label('Visible')
|
||||
->default(true),
|
||||
|
||||
Toggle::make('enabled')
|
||||
->label('Enabled')
|
||||
->default(true),
|
||||
|
||||
Toggle::make('club_only')
|
||||
->label('Club Only (HC)')
|
||||
->default(false),
|
||||
|
||||
Toggle::make('vip_only')
|
||||
->label('VIP Only')
|
||||
->default(false),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ManageCatalogEditor::route('/'),
|
||||
'create' => Pages\CreateCatalogEditor::route('/create'),
|
||||
'view' => Pages\ViewCatalogEditor::route('/{record}/view'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CatalogEditors\CatalogEditorResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCatalogEditor extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = CatalogEditorResource::class;
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CatalogEditors\CatalogEditorResource;
|
||||
use App\Models\Game\Furniture\CatalogItem;
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use Filament\Actions\Action as FilamentAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ManageCatalogEditor extends Page implements HasTable
|
||||
{
|
||||
use InteractsWithTable;
|
||||
|
||||
#[\Override]
|
||||
protected static string $resource = CatalogEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.resources.hotel.catalog-editors.pages.manage-catalog-editor';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $pageSearch = '';
|
||||
|
||||
public ?CatalogPage $selectedPage = null;
|
||||
|
||||
/** @var array<mixed> */
|
||||
public array $expandedPages = [];
|
||||
|
||||
/** @var array<mixed> */
|
||||
public array $selectedItemIds = [];
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('createPage')
|
||||
->label('New Page')
|
||||
->icon('heroicon-m-plus')
|
||||
->url(fn () => static::getResource()::getUrl('create')),
|
||||
|
||||
FilamentAction::make('editPageHeader')
|
||||
->label('Edit Page')
|
||||
->icon('heroicon-m-pencil')
|
||||
->button()
|
||||
->action(function () {
|
||||
if ($this->selectedPage instanceof CatalogPage) {
|
||||
$this->selectedPage->refresh();
|
||||
$this->dispatch('open-edit-page', pageId: $this->selectedPage->id);
|
||||
}
|
||||
})
|
||||
->modalHeading('Edit page')
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalWidth('3xl')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('caption')->label('Name')->maxLength(128)->required(),
|
||||
Forms\Components\TextInput::make('caption_save')->label('Name TAG')->maxLength(25)->nullable(),
|
||||
Forms\Components\TextInput::make('order_num')->label('Order')->numeric()->minValue(0)->required(),
|
||||
Forms\Components\TextInput::make('icon_image')->label('Icon number')->numeric()->minValue(1)->required(),
|
||||
])
|
||||
->fillForm(function () {
|
||||
if (! $this->selectedPage instanceof CatalogPage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'caption' => $this->selectedPage->caption ?? '',
|
||||
'caption_save' => $this->selectedPage->caption_save ?? '',
|
||||
'order_num' => $this->selectedPage->order_num ?? 1,
|
||||
'icon_image' => $this->selectedPage->icon_image ?? 1,
|
||||
];
|
||||
})
|
||||
->action(function (array $data) {
|
||||
if ($this->selectedPage instanceof CatalogPage) {
|
||||
$this->selectedPage->update($data);
|
||||
Notification::make()->title('Page updated')->success()->send();
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected function escapeLike(string $value, string $escapeChar = '\\'): string
|
||||
{
|
||||
return str_replace(
|
||||
[$escapeChar, '%', '_'],
|
||||
[$escapeChar . $escapeChar, $escapeChar . '%', $escapeChar . '_'],
|
||||
$value,
|
||||
);
|
||||
}
|
||||
|
||||
public function selectPage(int $pageId): void
|
||||
{
|
||||
$this->selectedPage = CatalogPage::find($pageId);
|
||||
$this->selectedItemIds = [];
|
||||
|
||||
if ($this->pageSearch !== '') {
|
||||
$this->pageSearch = '';
|
||||
}
|
||||
|
||||
$this->expandedPages = $this->collectParentIds($pageId);
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
protected function collectParentIds(int $pageId): array
|
||||
{
|
||||
$pages = CatalogPage::pluck('parent_id', 'id');
|
||||
$ids = [$pageId];
|
||||
$parentId = $pages[$pageId] ?? null;
|
||||
while ($parentId && $parentId > 0) {
|
||||
$ids[] = $parentId;
|
||||
$parentId = $pages[$parentId] ?? null;
|
||||
}
|
||||
|
||||
return array_unique($ids);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getMaxContentWidth(): ?string
|
||||
{
|
||||
return 'full';
|
||||
}
|
||||
|
||||
public function resetView(): void
|
||||
{
|
||||
$this->pageSearch = '';
|
||||
$this->selectedPage = null;
|
||||
$this->expandedPages = [];
|
||||
$this->selectedItemIds = [];
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()
|
||||
->title('View reset')
|
||||
->body('Catalog view restored to default.')
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function toggleExpand(int $pageId): void
|
||||
{
|
||||
if (in_array($pageId, $this->expandedPages, true)) {
|
||||
$this->expandedPages = array_values(array_diff($this->expandedPages, [$pageId]));
|
||||
} else {
|
||||
$this->expandedPages[] = $pageId;
|
||||
}
|
||||
}
|
||||
|
||||
public function isExpanded(int $pageId): bool
|
||||
{
|
||||
return in_array($pageId, $this->expandedPages, true);
|
||||
}
|
||||
|
||||
public function toggleSelectItem(int $itemId, bool $ctrl = false): void
|
||||
{
|
||||
if ($ctrl) {
|
||||
if (in_array($itemId, $this->selectedItemIds, true)) {
|
||||
$this->selectedItemIds = array_values(array_diff($this->selectedItemIds, [$itemId]));
|
||||
} else {
|
||||
$this->selectedItemIds[] = $itemId;
|
||||
}
|
||||
} else {
|
||||
$this->selectedItemIds = [$itemId];
|
||||
}
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
public function updatedPageSearch(): void
|
||||
{
|
||||
$this->resetTable();
|
||||
|
||||
$needle = trim($this->pageSearch);
|
||||
|
||||
if ($needle === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$like = '%' . $this->escapeLike($needle) . '%';
|
||||
|
||||
$matchingPage = CatalogPage::query()
|
||||
->whereRaw("caption LIKE ? ESCAPE '\\\\'", [$like])
|
||||
->first();
|
||||
|
||||
if ($matchingPage) {
|
||||
$this->selectedPage = $matchingPage;
|
||||
$this->expandedPages[] = $matchingPage->id;
|
||||
$this->resetTable();
|
||||
$this->dispatch('scroll-to-page', id: $matchingPage->id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$matchingItem = CatalogItem::query()
|
||||
->whereRaw("catalog_name LIKE ? ESCAPE '\\\\'", [$like])
|
||||
->orWhere('id', ctype_digit($needle) ? (int) $needle : -1)
|
||||
->first();
|
||||
|
||||
if ($matchingItem) {
|
||||
$page = CatalogPage::find($matchingItem->page_id);
|
||||
if ($page) {
|
||||
$this->selectedPage = $page;
|
||||
$this->expandedPages[] = $page->id;
|
||||
$this->selectedItemIds = [$matchingItem->id];
|
||||
$this->resetTable();
|
||||
$this->dispatch('scroll-to-page', id: $page->id);
|
||||
|
||||
Notification::make()
|
||||
->title('Item found')
|
||||
->body("Opened page: {$page->caption}")
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTableQuery()
|
||||
{
|
||||
if (! $this->selectedPage instanceof CatalogPage) {
|
||||
return CatalogItem::query()->whereRaw('1=0');
|
||||
}
|
||||
|
||||
$query = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id);
|
||||
|
||||
if (filled($this->pageSearch)) {
|
||||
$needle = trim($this->pageSearch);
|
||||
$like = '%' . $this->escapeLike($needle) . '%';
|
||||
$isNumeric = ctype_digit($needle);
|
||||
|
||||
$query->where(function ($q) use ($like, $needle, $isNumeric) {
|
||||
$q->whereRaw("catalog_name LIKE ? ESCAPE '\\\\'", [$like]);
|
||||
|
||||
if ($isNumeric) {
|
||||
$n = (int) $needle;
|
||||
|
||||
$q->orWhere('id', $n)
|
||||
->orWhere('cost_credits', $n)
|
||||
->orWhere('cost_points', $n)
|
||||
->orWhere('points_type', $n);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (! $this->getTableSortColumn()) {
|
||||
$query->orderBy('order_number')->orderBy('catalog_name')->orderBy('id');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function findPrevNeighbor(CatalogItem $record): ?CatalogItem
|
||||
{
|
||||
return CatalogItem::query()
|
||||
->where('page_id', $record->page_id)
|
||||
->where('order_number', '!=', -1)
|
||||
->where(function ($q) use ($record) {
|
||||
$q->where('order_number', '<', $record->order_number)
|
||||
->orWhere(function ($q2) use ($record) {
|
||||
$q2->where('order_number', $record->order_number)
|
||||
->where('id', '<', $record->id);
|
||||
});
|
||||
})
|
||||
->orderBy('order_number', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
protected function findNextNeighbor(CatalogItem $record): ?CatalogItem
|
||||
{
|
||||
return CatalogItem::query()
|
||||
->where('page_id', $record->page_id)
|
||||
->where('order_number', '!=', -1)
|
||||
->where(function ($q) use ($record) {
|
||||
$q->where('order_number', '>', $record->order_number)
|
||||
->orWhere(function ($q2) use ($record) {
|
||||
$q2->where('order_number', $record->order_number)
|
||||
->where('id', '>', $record->id);
|
||||
});
|
||||
})
|
||||
->orderBy('order_number', 'asc')
|
||||
->orderBy('id', 'asc')
|
||||
->first();
|
||||
}
|
||||
|
||||
protected function canMoveUp(CatalogItem $record): bool
|
||||
{
|
||||
if ($record->order_number === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->findPrevNeighbor($record);
|
||||
}
|
||||
|
||||
protected function canMoveDown(CatalogItem $record): bool
|
||||
{
|
||||
if ($record->order_number === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->findNextNeighbor($record);
|
||||
}
|
||||
|
||||
protected function nudgeRecord(CatalogItem $record, string $direction): void
|
||||
{
|
||||
if ($record->order_number === -1) {
|
||||
Notification::make()->title('Locked')->body('This item is locked (order = -1).')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$neighbor = $direction === 'up'
|
||||
? $this->findPrevNeighbor($record)
|
||||
: $this->findNextNeighbor($record);
|
||||
|
||||
if (! $neighbor instanceof CatalogItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($record, $neighbor) {
|
||||
$a = $record->order_number;
|
||||
$b = $neighbor->order_number;
|
||||
|
||||
$record->update(['order_number' => $b]);
|
||||
$neighbor->update(['order_number' => $a]);
|
||||
});
|
||||
|
||||
$this->normalizeOrderForSelectedPage();
|
||||
|
||||
Notification::make()->title('Order updated')->success()->send();
|
||||
}
|
||||
|
||||
protected function normalizeOrderForSelectedPage(): void
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', '!=', -1)
|
||||
->orderBy('order_number')
|
||||
->orderBy('id')
|
||||
->get(['id']);
|
||||
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items->values() as $index => $item) {
|
||||
CatalogItem::whereKey($item->id)
|
||||
->update(['order_number' => ($index + 1) * 10]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
public function pageHasLockedItems(): bool
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', -1)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function autoOrderItems(): void
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
Notification::make()->title('Select a page first')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->pageHasLockedItems()) {
|
||||
Notification::make()
|
||||
->title('Action not allowed')
|
||||
->body('This page contains item(s) with order_number = -1. Remove or change them before auto-ordering.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$affected = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', '!=', -1)
|
||||
->update(['order_number' => 99]);
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
if ($affected > 0) {
|
||||
Notification::make()->title('Items auto-ordered')->body("Updated {$affected} item(s).")->success()->send();
|
||||
} else {
|
||||
Notification::make()->title('Nothing to update')->body('No items were changed (none on this page or all are set to -1).')->warning()->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function manualOrderItems(): void
|
||||
{
|
||||
if (! $this->selectedPage?->id) {
|
||||
Notification::make()->title('Select a page first')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->pageHasLockedItems()) {
|
||||
Notification::make()->title('Action not allowed')->body('This page contains item(s) with order_number = -1. Change/remove them before manual ordering.')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$items = CatalogItem::query()
|
||||
->where('page_id', $this->selectedPage->id)
|
||||
->where('order_number', '!=', -1)
|
||||
->orderBy('catalog_name', 'asc')
|
||||
->orderBy('id', 'asc')
|
||||
->get(['id']);
|
||||
|
||||
if ($items->isEmpty()) {
|
||||
Notification::make()->title('Nothing to update')->body('No items on this page (or all are locked to -1).')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items->values() as $index => $item) {
|
||||
CatalogItem::whereKey($item->id)->update(['order_number' => ($index + 1) * 10]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()->title('Items manually ordered')->body('Items sorted A→Z and numbered 10, 20, 30, …')->success()->send();
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table->paginated(false);
|
||||
}
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
Tables\Columns\ViewColumn::make('select_item')
|
||||
->label('')
|
||||
->view('filament.tables.columns.catalog-item-select')
|
||||
->viewData([
|
||||
'itemId' => fn ($record) => $record->id,
|
||||
'isSelected' => fn ($record) => in_array($record->id, $this->selectedItemIds, true),
|
||||
])
|
||||
->width('36px')
|
||||
->sortable(false)
|
||||
->searchable(false),
|
||||
|
||||
Tables\Columns\ViewColumn::make('item_display')
|
||||
->label('Item')
|
||||
->view('filament.tables.columns.catalog-item-draggable')
|
||||
->viewData([
|
||||
'icon' => fn ($record) => $this->buildFurniIconUrl($record->catalog_name),
|
||||
'name' => fn ($record) => $record->catalog_name,
|
||||
'itemId' => fn ($record) => $record->id,
|
||||
'isSelected' => fn ($record) => in_array($record->id, $this->selectedItemIds, true),
|
||||
])
|
||||
->sortable(false)
|
||||
->searchable(false),
|
||||
|
||||
Tables\Columns\TextColumn::make('cost_credits')
|
||||
->label('Credits')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('cost_points')
|
||||
->label('Points')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('points_type')
|
||||
->label('Type')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
->label('Amount')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('order_number')
|
||||
->label('Order')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
Tables\Columns\IconColumn::make('club_only')
|
||||
->boolean()
|
||||
->label('Club Only')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTableActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('move_up')
|
||||
->label('')
|
||||
->icon('heroicon-m-chevron-up')
|
||||
->color('gray')
|
||||
->tooltip('Move up')
|
||||
->action(fn (CatalogItem $record) => $this->nudgeRecord($record, 'up'))
|
||||
->visible(fn (CatalogItem $record) => $this->pageSearch === '' && $this->canMoveUp($record))
|
||||
->size('sm'),
|
||||
|
||||
FilamentAction::make('move_down')
|
||||
->label('')
|
||||
->icon('heroicon-m-chevron-down')
|
||||
->color('gray')
|
||||
->tooltip('Move down')
|
||||
->action(fn (CatalogItem $record) => $this->nudgeRecord($record, 'down'))
|
||||
->visible(fn (CatalogItem $record) => $this->pageSearch === '' && $this->canMoveDown($record))
|
||||
->size('sm'),
|
||||
|
||||
EditAction::make('edit')
|
||||
->label('Edit')
|
||||
->icon('heroicon-m-pencil-square')
|
||||
->modalHeading('Edit catalog item')
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalWidth('md')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('cost_credits')->label('Credits')->numeric()->minValue(0)->required(),
|
||||
Forms\Components\TextInput::make('cost_points')->label('Points')->numeric()->minValue(0)->required(),
|
||||
Forms\Components\TextInput::make('points_type')->label('Type')->numeric()->minValue(0)->maxValue(999)->maxLength(50),
|
||||
Forms\Components\TextInput::make('amount')->label('Amount')->numeric()->minValue(1)->default(1)->required(),
|
||||
Forms\Components\TextInput::make('order_number')
|
||||
->label('Order')
|
||||
->numeric()
|
||||
->minValue(-1)
|
||||
->step(1)
|
||||
->helperText('Use -1 to lock, or a non-negative number to sort (lower = earlier).')
|
||||
->required(),
|
||||
Forms\Components\Toggle::make('club_only')->label('Club only'),
|
||||
])
|
||||
->fillForm(fn (CatalogItem $record) => [
|
||||
'cost_credits' => $record->cost_credits,
|
||||
'cost_points' => $record->cost_points,
|
||||
'points_type' => $record->points_type,
|
||||
'amount' => $record->amount,
|
||||
'order_number' => $record->order_number,
|
||||
'club_only' => $record->club_only === '1',
|
||||
])
|
||||
->action(function (CatalogItem $record, array $data): void {
|
||||
$record->update([
|
||||
'cost_credits' => (int) $data['cost_credits'],
|
||||
'cost_points' => (int) $data['cost_points'],
|
||||
'points_type' => $data['points_type'] ?? null,
|
||||
'amount' => (int) $data['amount'],
|
||||
'order_number' => (int) $data['order_number'],
|
||||
'club_only' => empty($data['club_only']) ? '0' : '1',
|
||||
]);
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()->title('Item updated')->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('editPage')
|
||||
->label('Edit page')
|
||||
->modalHeading(function (array $arguments): string {
|
||||
$page = CatalogPage::find($arguments['pageId'] ?? null);
|
||||
|
||||
return $page ? 'Edit: ' . $page->caption : 'Edit page';
|
||||
})
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalWidth('3xl')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('caption')->label('Name')->maxLength(128)->required(),
|
||||
|
||||
Forms\Components\TextInput::make('caption_save')
|
||||
->label('Name TAG')
|
||||
->maxLength(25)
|
||||
->nullable(),
|
||||
|
||||
Forms\Components\TextInput::make('order_num')
|
||||
->label('Order')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->step(1)
|
||||
->required()
|
||||
->helperText('Lower number appears earlier in the menu.'),
|
||||
|
||||
Forms\Components\TextInput::make('icon_image')
|
||||
->label('Icon number')
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->required()
|
||||
->default(1),
|
||||
])
|
||||
->fillForm(function (array $arguments): array {
|
||||
$page = CatalogPage::find($arguments['pageId'] ?? null);
|
||||
|
||||
return [
|
||||
'caption' => $page?->caption ?? '',
|
||||
'caption_save' => $page?->caption_save ?? '',
|
||||
'order_num' => $page?->order_num ?? 1,
|
||||
'icon_image' => $page?->icon_image ?? 1,
|
||||
];
|
||||
})
|
||||
->action(function (array $data, array $arguments): void {
|
||||
$page = CatalogPage::find($arguments['pageId'] ?? null);
|
||||
if (! $page) {
|
||||
Notification::make()->title('Page not found')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$icon = max(1, (int) ($data['icon_image'] ?: 1));
|
||||
|
||||
$page->update([
|
||||
'caption' => $data['caption'],
|
||||
'caption_save' => $data['caption_save'] ?? '',
|
||||
'order_num' => (int) ($data['order_num'] ?? 1),
|
||||
'icon_image' => $icon,
|
||||
]);
|
||||
|
||||
$this->selectPage($page->id);
|
||||
Notification::make()->title('Page updated')->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function openEditPage(int $pageId): void
|
||||
{
|
||||
$this->mountAction('editPage', ['pageId' => $pageId]);
|
||||
}
|
||||
|
||||
public function moveItemToPage(int $itemId, int $targetPageId): void
|
||||
{
|
||||
$this->moveItemsToPage((string) $itemId, $targetPageId);
|
||||
}
|
||||
|
||||
public function moveItemsToPage(string $itemIdsCsv, int $targetPageId): void
|
||||
{
|
||||
$target = CatalogPage::find($targetPageId);
|
||||
|
||||
$ids = collect(explode(',', $itemIdsCsv))
|
||||
->map(fn ($v) => (int) trim($v))
|
||||
->filter(fn ($v) => $v > 0)
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if (empty($ids) || ! $target) {
|
||||
Notification::make()->title('Move failed')->body('No items selected or target page not found.')->danger()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($ids, $targetPageId) {
|
||||
$maxOrder = (int) (CatalogItem::where('page_id', $targetPageId)->max('order_number') ?? 0);
|
||||
|
||||
foreach ($ids as $i => $id) {
|
||||
CatalogItem::whereKey($id)->update([
|
||||
'page_id' => $targetPageId,
|
||||
'order_number' => $maxOrder + 1 + $i,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
$this->selectedItemIds = [];
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->title('Items moved')
|
||||
->body('Moved ' . count($ids) . ' item(s) to: ' . ($target->caption ?? ('#' . $targetPageId)))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function buildFurniIconUrl(string $catalogName): string
|
||||
{
|
||||
$base = $this->getFurniIconBasePath();
|
||||
$safeName = str_replace('*', '_', $catalogName);
|
||||
$path = rtrim($base, '/') . '/' . $safeName . '_icon.png';
|
||||
|
||||
if (preg_match('#^(https?:)?//#', $path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return asset($path);
|
||||
}
|
||||
|
||||
protected function getFurniIconBasePath(): string
|
||||
{
|
||||
$setting = WebsiteSetting::where('key', 'furniture_icons_path')->first();
|
||||
|
||||
return $setting && $setting->value ? rtrim($setting->value, '/') : '/images/furniture';
|
||||
}
|
||||
|
||||
protected function getCatalogIconBasePath(): string
|
||||
{
|
||||
$setting = WebsiteSetting::where('key', 'catalog_icons_path')->first();
|
||||
|
||||
return $setting && $setting->value ? rtrim($setting->value, '/') : '/gamedata/c_images/catalogue';
|
||||
}
|
||||
|
||||
protected function buildCatalogIconUrl(int $iconImage): string
|
||||
{
|
||||
$iconImage = $iconImage > 0 ? $iconImage : 1;
|
||||
$base = $this->getCatalogIconBasePath();
|
||||
$path = $base . '/icon_' . $iconImage . '.png';
|
||||
|
||||
if (preg_match('#^(https?:)?//#', $path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return asset($path);
|
||||
}
|
||||
|
||||
public function reorderItems(array $orderedIds): void
|
||||
{
|
||||
if (filled($this->pageSearch)) {
|
||||
Notification::make()
|
||||
->title('Ordering disabled in search mode')
|
||||
->body('You cannot reorder items while viewing search results.')
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->selectedPage?->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($orderedIds) {
|
||||
foreach ($orderedIds as $index => $id) {
|
||||
CatalogItem::whereKey($id)->update([
|
||||
'order_number' => ($index + 1) * 10,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->normalizeOrderForSelectedPage();
|
||||
|
||||
Notification::make()
|
||||
->title('Items reordered')
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$this->resetTable();
|
||||
}
|
||||
|
||||
protected function getTableHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
FilamentAction::make('massEdit')
|
||||
->label('Mass edit selected')
|
||||
->icon('heroicon-m-pencil-square')
|
||||
->color('primary')
|
||||
->disabled(fn () => $this->selectedItemIds === [])
|
||||
->modalHeading('Edit selected catalog items')
|
||||
->modalSubmitActionLabel('Apply changes')
|
||||
->modalWidth('lg')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('cost_credits')->label('Credits')->numeric()->minValue(0)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('cost_points')->label('Points')->numeric()->minValue(0)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('points_type')->label('Type (points_type)')->numeric()->minValue(0)->maxValue(999)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('amount')->label('Amount')->numeric()->minValue(1)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\TextInput::make('order_number')->label('Order')->numeric()->minValue(-1)->nullable()->helperText('Leave empty to keep unchanged'),
|
||||
Forms\Components\Select::make('club_only')
|
||||
->label('Club only')
|
||||
->options(['' => '— No change —', '1' => 'Yes', '0' => 'No'])
|
||||
->native(false)
|
||||
->nullable()
|
||||
->default('')
|
||||
->helperText('Choose Yes/No, or leave as "No change"'),
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
$ids = collect($this->selectedItemIds)
|
||||
->filter(fn ($v) => (int) $v > 0)
|
||||
->map(fn ($v) => (int) $v)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if (empty($ids)) {
|
||||
Notification::make()->title('No items selected')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
|
||||
if ($data['cost_credits'] !== null && $data['cost_credits'] !== '') {
|
||||
$updates['cost_credits'] = (int) $data['cost_credits'];
|
||||
}
|
||||
if ($data['cost_points'] !== null && $data['cost_points'] !== '') {
|
||||
$updates['cost_points'] = (int) $data['cost_points'];
|
||||
}
|
||||
if ($data['points_type'] !== null && $data['points_type'] !== '') {
|
||||
$updates['points_type'] = (int) $data['points_type'];
|
||||
}
|
||||
if ($data['amount'] !== null && $data['amount'] !== '') {
|
||||
$updates['amount'] = (int) $data['amount'];
|
||||
}
|
||||
if ($data['order_number'] !== null && $data['order_number'] !== '') {
|
||||
$updates['order_number'] = (int) $data['order_number'];
|
||||
}
|
||||
if ($data['club_only'] !== null && $data['club_only'] !== '') {
|
||||
$updates['club_only'] = $data['club_only'] === '1' ? '1' : '0';
|
||||
}
|
||||
|
||||
if ($updates === []) {
|
||||
Notification::make()->title('Nothing to update')->body('Fill at least one field to apply to the selected items.')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CatalogItem::whereIn('id', $ids)->update($updates);
|
||||
|
||||
$count = count($ids);
|
||||
$this->resetTable();
|
||||
$this->selectedItemIds = [];
|
||||
|
||||
Notification::make()->title('Updated items')->body("Applied changes to {$count} item(s).")->success()->send();
|
||||
}),
|
||||
FilamentAction::make('updateOrder')
|
||||
->label('Update Order')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->color('secondary')
|
||||
->visible(fn () => $this->selectedPage && $this->pageSearch === '')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Confirm Update Order')
|
||||
->modalDescription('This will save the current item order (as currently sorted) into the database. Continue?')
|
||||
->modalSubmitActionLabel('Update Order')
|
||||
->action(function (): void {
|
||||
if (! $this->selectedPage?->id) {
|
||||
Notification::make()->title('No page selected')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->pageSearch !== '') {
|
||||
Notification::make()
|
||||
->title('Disabled in search mode')
|
||||
->body('Cannot update order while search results are active.')
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sortColumn = $this->getTableSortColumn();
|
||||
$sortDirection = $this->getTableSortDirection() ?? 'asc';
|
||||
|
||||
$query = $this->getTableQuery();
|
||||
|
||||
if ($sortColumn) {
|
||||
$query->orderBy($sortColumn, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('order_number')->orderBy('id');
|
||||
}
|
||||
|
||||
$items = $query->get(['id']);
|
||||
|
||||
if ($items->isEmpty()) {
|
||||
Notification::make()->title('No items')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items->values() as $index => $item) {
|
||||
CatalogItem::whereKey($item->id)->update([
|
||||
'order_number' => ($index + 1) * 10,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
Notification::make()->title('Order updated')->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CatalogEditors\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CatalogEditors\CatalogEditorResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewCatalogEditor extends ViewRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = CatalogEditorResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogPrivates;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogPrivates\Pages\ManageChatlogPrivates;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\ChatlogPrivate;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ChatlogPrivateResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = ChatlogPrivate::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
|
||||
|
||||
public static string $translateIdentifier = 'chatlog-private';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/chatlog-private';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('sender')
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($record) => $record->sender?->username)
|
||||
->label(__('filament::resources.inputs.sender')),
|
||||
|
||||
TextInput::make('receiver')
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($record) => $record->receiver?->username)
|
||||
->label(__('filament::resources.inputs.receiver')),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('filament::resources.inputs.message'))
|
||||
->columnSpanFull()
|
||||
->disabled(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->columns(self::getTable())
|
||||
->filters([])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
public static function getTable(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('sender.username')
|
||||
->label(__('filament::resources.columns.sender'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('receiver.username')
|
||||
->label(__('filament::resources.columns.receiver'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('message')
|
||||
->label(__('filament::resources.columns.message'))
|
||||
->limit(40)
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('timestamp')
|
||||
->label(__('filament::resources.columns.executed_at'))
|
||||
->dateTime('Y-m-d H:i')
|
||||
->toggleable(),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageChatlogPrivates::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogPrivates\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogPrivates\ChatlogPrivateResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageChatlogPrivates extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = ChatlogPrivateResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogRooms;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogRooms\Pages\ManageChatlogRooms;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\ChatlogRoom;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ChatlogRoomResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = ChatlogRoom::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
|
||||
|
||||
public static string $translateIdentifier = 'chatlog-rooms';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/chatlog-room';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('room')
|
||||
->label(__('filament::resources.inputs.room'))
|
||||
->formatStateUsing(fn ($record) => $record->room?->name)
|
||||
->columnSpanFull()
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('sender')
|
||||
->label(__('filament::resources.inputs.sender'))
|
||||
->formatStateUsing(fn ($record) => $record->sender?->username)
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('receiver')
|
||||
->label(__('filament::resources.inputs.receiver'))
|
||||
->formatStateUsing(fn ($record) => $record->receiver?->username)
|
||||
->disabled(),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('filament::resources.inputs.message'))
|
||||
->columnSpanFull()
|
||||
->disabled(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->columns(self::getTable())
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
public static function getTable(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('room.name')
|
||||
->label(__('filament::resources.columns.room'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('sender.username')
|
||||
->label(__('filament::resources.columns.sender'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('receiver.username')
|
||||
->label(__('filament::resources.columns.receiver'))
|
||||
->toggleable()
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('message')
|
||||
->label(__('filament::resources.columns.message'))
|
||||
->limit(40)
|
||||
->searchable(isIndividual: true),
|
||||
|
||||
TextColumn::make('timestamp')
|
||||
->label(__('filament::resources.columns.executed_at'))
|
||||
->dateTime('Y-m-d H:i')
|
||||
->toggleable(),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageChatlogRooms::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\ChatlogRooms\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\ChatlogRooms\ChatlogRoomResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageChatlogRooms extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = ChatlogRoomResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CommandLogs;
|
||||
|
||||
use App\Filament\Resources\Hotel\CommandLogs\Pages\ManageCommandLogs;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\CommandLog;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CommandLogResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = CommandLog::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-bottom-center-text';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
|
||||
|
||||
public static string $translateIdentifier = 'command-logs';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'logs/commands';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->columns([
|
||||
TextColumn::make('user.username')
|
||||
->label(__('filament::resources.columns.username'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('command')
|
||||
->label(__('filament::resources.columns.command'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('succes')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'yes' => 'primary',
|
||||
'no' => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->label(__('filament::resources.columns.success'))
|
||||
->formatStateUsing(fn (string $state): string => __("filament::resources.options.{$state}")),
|
||||
|
||||
TextColumn::make('timestamp')
|
||||
->label(__('filament::resources.columns.executed_at'))
|
||||
->dateTime('Y-m-d H:i')
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('succes')
|
||||
->label(__('filament::resources.filters.success'))
|
||||
->options([
|
||||
'yes' => __('filament::resources.options.yes'),
|
||||
'no' => __('filament::resources.options.no'),
|
||||
]),
|
||||
])
|
||||
->recordActions([])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageCommandLogs::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\CommandLogs\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\CommandLogs\CommandLogResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageCommandLogs extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = CommandLogResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPrimaryKey(): string
|
||||
{
|
||||
return 'timestamp';
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CustomQueryBuilder extends Builder
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Call the parent constructor with a dummy query
|
||||
parent::__construct(app('db')->query());
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get($columns = ['*']): Collection
|
||||
{
|
||||
return collect(); // Return an empty collection
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\Pages\CreateEmulatorSetting;
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\Pages\EditEmulatorSetting;
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\Pages\ListEmulatorSettings;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\EmulatorSetting;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EmulatorSettingResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = EmulatorSetting::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-adjustments-horizontal';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
public static string $translateIdentifier = 'emulator-settings';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/emulator-settings';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('key')
|
||||
->label(__('filament::resources.inputs.key'))
|
||||
->required()
|
||||
->maxLength(100)
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
TextInput::make('value')
|
||||
->label(__('filament::resources.inputs.value'))
|
||||
->required()
|
||||
->maxLength(512),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(__('filament::resources.columns.key'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('value')
|
||||
->label(__('filament::resources.columns.value'))
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListEmulatorSettings::route('/'),
|
||||
'create' => CreateEmulatorSetting::route('/create'),
|
||||
'edit' => EditEmulatorSetting::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\EmulatorSettingResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateEmulatorSetting extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorSettingResource::class;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\EmulatorSettingResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditEmulatorSetting extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorSettingResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorSettings\EmulatorSettingResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListEmulatorSettings extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorSettingResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorTexts;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorTexts\Pages\ManageEmulatorTexts;
|
||||
use App\Filament\Traits\TranslatableResource;
|
||||
use App\Models\EmulatorText;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EmulatorTextResource extends Resource
|
||||
{
|
||||
use TranslatableResource;
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $model = EmulatorText::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clipboard-document-list';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $slug = 'hotel/emulator-texts';
|
||||
|
||||
public static string $translateIdentifier = 'emulator-texts';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('key')
|
||||
->label(__('filament::resources.inputs.key'))
|
||||
->required()
|
||||
->maxLength(100)
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
TextInput::make('value')
|
||||
->label(__('filament::resources.inputs.value'))
|
||||
->required()
|
||||
->maxLength(512),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(__('filament::resources.columns.key'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('value')
|
||||
->label(__('filament::resources.columns.value'))
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageEmulatorTexts::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\EmulatorTexts\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\EmulatorTexts\EmulatorTextResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageEmulatorTexts extends ManageRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = EmulatorTextResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make('create'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getPrimaryKey(): string
|
||||
{
|
||||
return 'key';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\Pages\CreateOpenPosition;
|
||||
use App\Filament\Resources\Hotel\OpenPositions\Pages\EditOpenPosition;
|
||||
use App\Filament\Resources\Hotel\OpenPositions\Pages\ListOpenPositions;
|
||||
use App\Models\Community\Staff\WebsiteOpenPosition;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OpenPositionResource extends Resource
|
||||
{
|
||||
#[\Override]
|
||||
protected static ?string $model = WebsiteOpenPosition::class;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-briefcase';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
|
||||
|
||||
#[\Override]
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
ToggleButtons::make('position_kind')
|
||||
->label('Type')
|
||||
->inline()
|
||||
->options([
|
||||
'rank' => 'Ranks',
|
||||
'team' => 'Teams',
|
||||
])
|
||||
->default('rank')
|
||||
->required()
|
||||
->live()
|
||||
->grouped(),
|
||||
|
||||
Select::make('permission_id')
|
||||
->label('Rank')
|
||||
->relationship('permission', 'rank_name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->placeholder('Select a rank')
|
||||
->visible(fn (Get $get) => $get('position_kind') === 'rank')
|
||||
->required(fn (Get $get) => $get('position_kind') === 'rank')
|
||||
->dehydrated(fn (Get $get) => $get('position_kind') === 'rank')
|
||||
->unique(
|
||||
table: WebsiteOpenPosition::class,
|
||||
column: 'permission_id',
|
||||
ignoreRecord: true,
|
||||
),
|
||||
|
||||
Select::make('team_id')
|
||||
->label('Team')
|
||||
->relationship('team', 'rank_name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->placeholder('Select a team')
|
||||
->visible(fn (Get $get) => $get('position_kind') === 'team')
|
||||
->required(fn (Get $get) => $get('position_kind') === 'team')
|
||||
->dehydrated(fn (Get $get) => $get('position_kind') === 'team')
|
||||
->unique(
|
||||
table: WebsiteOpenPosition::class,
|
||||
column: 'team_id',
|
||||
ignoreRecord: true,
|
||||
)
|
||||
->placeholder('Select a rank'),
|
||||
|
||||
Textarea::make('description')
|
||||
->label('Position Description')
|
||||
->required()
|
||||
->maxLength(65535)
|
||||
->columnSpanFull(),
|
||||
|
||||
DateTimePicker::make('apply_from')
|
||||
->label('Application Start Date')
|
||||
->nullable(),
|
||||
|
||||
DateTimePicker::make('apply_to')
|
||||
->label('Application End Date')
|
||||
->nullable(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('position_kind')
|
||||
->label('Type')
|
||||
->badge()
|
||||
->formatStateUsing(fn (string $state) => ucfirst($state))
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('permission.rank_name')
|
||||
->label('Rank')
|
||||
->toggleable(isToggledHiddenByDefault: false),
|
||||
|
||||
TextColumn::make('team.rank_name')
|
||||
->label('Team')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
TextColumn::make('description')
|
||||
->label('Description')
|
||||
->limit(50)
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('apply_from')
|
||||
->label('Apply From')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('apply_to')
|
||||
->label('Apply To')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Delete Open Position')
|
||||
->modalDescription('This will also delete related rank-based staff applications (if any). Are you sure?')
|
||||
->modalSubmitActionLabel('Yes, delete')
|
||||
->successNotification(
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Open Position Deleted')
|
||||
->body('The open position and its related staff applications (if rank-based) have been deleted successfully.'),
|
||||
),
|
||||
])
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Delete Open Positions')
|
||||
->modalDescription('This will also delete related rank-based staff applications (if any). Are you sure?')
|
||||
->modalSubmitActionLabel('Yes, delete')
|
||||
->successNotification(
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Open Positions Deleted')
|
||||
->body('The selected open positions and their related applications (if rank-based) have been deleted successfully.'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListOpenPositions::route('/'),
|
||||
'create' => CreateOpenPosition::route('/create'),
|
||||
'edit' => EditOpenPosition::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\OpenPositionResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateOpenPosition extends CreateRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = OpenPositionResource::class;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\OpenPositionResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOpenPosition extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = OpenPositionResource::class;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\OpenPositions\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\OpenPositions\OpenPositionResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOpenPositions extends ListRecords
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = OpenPositionResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Hotel\StaffApplications\Pages;
|
||||
|
||||
use App\Filament\Resources\Hotel\StaffApplications\StaffApplicationResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditStaffApplication extends EditRecord
|
||||
{
|
||||
#[\Override]
|
||||
protected static string $resource = StaffApplicationResource::class;
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user