diff --git a/.github/workflows/_rector.yml b/.github/workflows/_rector.yml index 435f3f978..ae703c20a 100644 --- a/.github/workflows/_rector.yml +++ b/.github/workflows/_rector.yml @@ -48,4 +48,4 @@ jobs: - name: Run Rector run: | - vendor/bin/rector --debug --dry-run --output-format=github + vendor/bin/rector --dry-run --output-format=github diff --git a/app-modules/character/src/Entities/LevelEntity.php b/app-modules/character/src/Entities/LevelEntity.php index c5731604d..250cddd11 100644 --- a/app-modules/character/src/Entities/LevelEntity.php +++ b/app-modules/character/src/Entities/LevelEntity.php @@ -73,6 +73,11 @@ public function getPercentageExperience(): float return floor($difference / (100 * 89) * 100); } + public function experiencePercentageRemaining(): float + { + return ($this->experience * ($this->getPercentageExperience())) / 100; + } + private function addExperience(int $experience): void { $this->experience += $experience; diff --git a/app-modules/character/src/Models/Character.php b/app-modules/character/src/Models/Character.php index f6cafca7c..4b1bf12ab 100644 --- a/app-modules/character/src/Models/Character.php +++ b/app-modules/character/src/Models/Character.php @@ -101,4 +101,19 @@ protected function getLevelAttribute(): int { return (new LevelEntity($this->experience))->getLevel(); } + + protected function getExperienceProgressAttribute(): int + { + return (new LevelEntity($this->experience))->getLevelUpStatus(); + } + + protected function getPercentageExperienceAttribute(): float + { + return (new LevelEntity($this->experience))->getPercentageExperience(); + } + + protected function getExperiencePercentageRemainingAttribute(): float + { + return (new LevelEntity($this->experience))->experiencePercentageRemaining(); + } } diff --git a/app-modules/events/database/factories/EventFactory.php b/app-modules/events/database/factories/EventFactory.php index 3786da252..77b67c702 100644 --- a/app-modules/events/database/factories/EventFactory.php +++ b/app-modules/events/database/factories/EventFactory.php @@ -4,9 +4,12 @@ namespace He4rt\Events\Database\Factories; +use Exception; +use He4rt\Events\Enums\AttendingStatusEnum; use He4rt\Events\Enums\EventTypeEnum; use He4rt\Events\Models\EventModel; use He4rt\Tenant\Models\Tenant; +use He4rt\User\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Date; @@ -24,17 +27,38 @@ public function definition(): array 'event_type' => fake()->randomElement(EventTypeEnum::cases()), 'slug' => fake()->slug(), 'active' => true, - 'title' => fake()->word(), + 'title' => fake()->sentence(4), 'description' => fake()->text(), 'event_at' => Date::now(), 'start_at' => Date::now(), 'end_at' => Date::now(), - 'location' => fake()->word(), - 'max_attendees' => fake()->randomNumber(), + 'location' => fake()->sentence(3), + 'max_attendees' => fake()->numberBetween(10, 100), 'attendees_count' => 0, 'waitlist_count' => 0, 'created_at' => Date::now(), 'updated_at' => Date::now(), ]; } + + public function withStatus(AttendingStatusEnum $status = AttendingStatusEnum::Attending): self + { + return $this->afterCreating(function (EventModel $model) use ($status): void { + $attendees = User::factory()->count(fake()->numberBetween(3, 10))->create(); + $column = match ($status) { + AttendingStatusEnum::Attending => 'attendees_count', + AttendingStatusEnum::Waitlist => 'waitlist_count', + AttendingStatusEnum::NotAttending => throw new Exception('Event is not attending anymore'), + }; + $model->update([ + $column => $attendees->count(), + ]); + + foreach ($attendees as $user) { + $model->attendees()->attach($user->getKey(), [ + 'status' => $status, + ]); + } + }); + } } diff --git a/app-modules/events/database/factories/TalkFactory.php b/app-modules/events/database/factories/TalkFactory.php index ffb969308..e49da1359 100644 --- a/app-modules/events/database/factories/TalkFactory.php +++ b/app-modules/events/database/factories/TalkFactory.php @@ -4,6 +4,7 @@ namespace He4rt\Events\Database\Factories; +use He4rt\Events\Enums\Talks\TalkStatusEnum; use He4rt\Events\Models\EventModel; use He4rt\Events\Models\Talk; use He4rt\Tenant\Models\Tenant; @@ -24,7 +25,7 @@ public function definition(): array 'tenant_id' => Tenant::factory(), 'event_id' => EventModel::factory(), 'user_id' => User::factory(), - 'status' => fake()->word(), + 'status' => fake()->randomElement(TalkStatusEnum::cases()), 'field_type' => fake()->word(), 'title' => fake()->word(), 'description' => fake()->text(), diff --git a/app-modules/events/resources/views/app/list-events.blade.php b/app-modules/events/resources/views/app/list-events.blade.php new file mode 100644 index 000000000..f434910bd --- /dev/null +++ b/app-modules/events/resources/views/app/list-events.blade.php @@ -0,0 +1,96 @@ + + @php + $events = $this->getTableRecords(); + @endphp + +
+ @foreach ($events as $event) + + +
+ {{ $event->event_type->getLabel() }} +
+ + {{ $event->end_at < now() ? 'Past' : 'Upcoming' }} + + + +
+
+
+
+
+

+ {{ $event->title }} +

+

{{ $event->description }}

+
+ +
+
+ {{-- Date --}} +
+ + + {{ \Carbon\Carbon::parse($event->event_at)->format('d/m/Y') }} + +
+ +
+ + + {{ \Carbon\Carbon::parse($event->start_at)->format('H:i:s') }} - + {{ \Carbon\Carbon::parse($event->end_at)->format('H:i:s') }} + +
+ +
+ + {{ $event->location }} +
+ +
+ + {{ $event->attendees_count }} / {{ $event->max_attendees }} participants +
+
+
+ @if ($event->isAttending() &&! $event->isPast() &&! $event->isParticipating(auth()->user()->getKey())) + + Join + + @elseif ($event->onWaitlist() &&! $event->isPast() &&! $event->isParticipating(auth()->user()->getKey())) + + Join Waitlist + + @elseif ($event->isParticipating(auth()->user()->getKey()) === true && ! $event->isPast()) + + Leave + + @endif +
+
+
+
+ @endforeach +
+ +
diff --git a/app-modules/events/resources/views/app/view-event.blade.php b/app-modules/events/resources/views/app/view-event.blade.php new file mode 100644 index 000000000..0f83fdaac --- /dev/null +++ b/app-modules/events/resources/views/app/view-event.blade.php @@ -0,0 +1,3 @@ + + {{ $this->eventInfoList }} + diff --git a/app-modules/events/src/Actions/AttendEventAction.php b/app-modules/events/src/Actions/AttendEventAction.php new file mode 100644 index 000000000..db367897f --- /dev/null +++ b/app-modules/events/src/Actions/AttendEventAction.php @@ -0,0 +1,19 @@ +attendees()->first()->pivot->status; + $eventModel->attend(auth()->user()->id, $attendingStatus); + } +} diff --git a/app-modules/events/src/Actions/LeaveEventAction.php b/app-modules/events/src/Actions/LeaveEventAction.php new file mode 100644 index 000000000..db764c306 --- /dev/null +++ b/app-modules/events/src/Actions/LeaveEventAction.php @@ -0,0 +1,15 @@ +leave(auth()->user()->getKey()); + } +} diff --git a/app-modules/events/src/AdminEventPanelPlugin.php b/app-modules/events/src/AdminEventPanelPlugin.php index c5fb9b2f6..51bfaf0ef 100644 --- a/app-modules/events/src/AdminEventPanelPlugin.php +++ b/app-modules/events/src/AdminEventPanelPlugin.php @@ -7,8 +7,8 @@ use App\Enums\FilamentPanel; use Filament\Contracts\Plugin; use Filament\Panel; -use He4rt\Events\Filament\Resources\Events\EventResource; -use He4rt\Events\Filament\Resources\Talks\TalkResource; +use He4rt\Events\Filament\Admin\Resources\Events\EventResource; +use He4rt\Events\Filament\Admin\Resources\Talks\TalkResource; class AdminEventPanelPlugin implements Plugin { diff --git a/app-modules/events/src/AppEventPanelPlugin.php b/app-modules/events/src/AppEventPanelPlugin.php new file mode 100644 index 000000000..2bd7f1913 --- /dev/null +++ b/app-modules/events/src/AppEventPanelPlugin.php @@ -0,0 +1,29 @@ +moduleName('event'); + } + + public function register(Panel $panel): void + { + $panel->resources([ + EventModelResource::class, + TalkResource::class, + ]); + } + + public function boot(Panel $panel): void {} +} diff --git a/app-modules/events/src/Filament/Resources/Events/EventResource.php b/app-modules/events/src/Filament/Admin/Resources/Events/EventResource.php similarity index 68% rename from app-modules/events/src/Filament/Resources/Events/EventResource.php rename to app-modules/events/src/Filament/Admin/Resources/Events/EventResource.php index 4f62281d3..86fda7823 100644 --- a/app-modules/events/src/Filament/Resources/Events/EventResource.php +++ b/app-modules/events/src/Filament/Admin/Resources/Events/EventResource.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Events; +namespace He4rt\Events\Filament\Admin\Resources\Events; use BackedEnum; use Filament\Resources\Resource; use Filament\Schemas\Schema; use Filament\Support\Icons\Heroicon; use Filament\Tables\Table; -use He4rt\Events\Filament\Resources\Events\Pages\CreateEvent; -use He4rt\Events\Filament\Resources\Events\Pages\EditEvent; -use He4rt\Events\Filament\Resources\Events\Pages\ListEvents; -use He4rt\Events\Filament\Resources\Events\Schemas\EventForm; -use He4rt\Events\Filament\Resources\Events\Tables\EventsTable; +use He4rt\Events\Filament\Admin\Resources\Events\Pages\CreateEvent; +use He4rt\Events\Filament\Admin\Resources\Events\Pages\EditEvent; +use He4rt\Events\Filament\Admin\Resources\Events\Pages\ListEvents; +use He4rt\Events\Filament\Admin\Resources\Events\Schemas\EventForm; +use He4rt\Events\Filament\Admin\Resources\Events\Tables\EventsTable; use He4rt\Events\Models\EventModel; use UnitEnum; @@ -25,6 +25,8 @@ class EventResource extends Resource protected static string|BackedEnum|null $navigationIcon = Heroicon::Calendar; + protected static ?string $label = 'Events'; + public static function form(Schema $schema): Schema { return EventForm::configure($schema); diff --git a/app-modules/events/src/Filament/Resources/Events/Pages/CreateEvent.php b/app-modules/events/src/Filament/Admin/Resources/Events/Pages/CreateEvent.php similarity index 59% rename from app-modules/events/src/Filament/Resources/Events/Pages/CreateEvent.php rename to app-modules/events/src/Filament/Admin/Resources/Events/Pages/CreateEvent.php index 066eefc33..dfe249658 100644 --- a/app-modules/events/src/Filament/Resources/Events/Pages/CreateEvent.php +++ b/app-modules/events/src/Filament/Admin/Resources/Events/Pages/CreateEvent.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Events\Pages; +namespace He4rt\Events\Filament\Admin\Resources\Events\Pages; use Filament\Resources\Pages\CreateRecord; -use He4rt\Events\Filament\Resources\Events\EventResource; +use He4rt\Events\Filament\Admin\Resources\Events\EventResource; class CreateEvent extends CreateRecord { diff --git a/app-modules/events/src/Filament/Resources/Events/Pages/EditEvent.php b/app-modules/events/src/Filament/Admin/Resources/Events/Pages/EditEvent.php similarity index 72% rename from app-modules/events/src/Filament/Resources/Events/Pages/EditEvent.php rename to app-modules/events/src/Filament/Admin/Resources/Events/Pages/EditEvent.php index e28e20165..70b254a73 100644 --- a/app-modules/events/src/Filament/Resources/Events/Pages/EditEvent.php +++ b/app-modules/events/src/Filament/Admin/Resources/Events/Pages/EditEvent.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Events\Pages; +namespace He4rt\Events\Filament\Admin\Resources\Events\Pages; use Filament\Actions\DeleteAction; use Filament\Resources\Pages\EditRecord; -use He4rt\Events\Filament\Resources\Events\EventResource; +use He4rt\Events\Filament\Admin\Resources\Events\EventResource; class EditEvent extends EditRecord { diff --git a/app-modules/events/src/Filament/Resources/Events/Pages/ListEvents.php b/app-modules/events/src/Filament/Admin/Resources/Events/Pages/ListEvents.php similarity index 72% rename from app-modules/events/src/Filament/Resources/Events/Pages/ListEvents.php rename to app-modules/events/src/Filament/Admin/Resources/Events/Pages/ListEvents.php index 75745956d..039da9803 100644 --- a/app-modules/events/src/Filament/Resources/Events/Pages/ListEvents.php +++ b/app-modules/events/src/Filament/Admin/Resources/Events/Pages/ListEvents.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Events\Pages; +namespace He4rt\Events\Filament\Admin\Resources\Events\Pages; use Filament\Actions\CreateAction; use Filament\Resources\Pages\ListRecords; -use He4rt\Events\Filament\Resources\Events\EventResource; +use He4rt\Events\Filament\Admin\Resources\Events\EventResource; class ListEvents extends ListRecords { diff --git a/app-modules/events/src/Filament/Resources/Events/Schemas/EventForm.php b/app-modules/events/src/Filament/Admin/Resources/Events/Schemas/EventForm.php similarity index 97% rename from app-modules/events/src/Filament/Resources/Events/Schemas/EventForm.php rename to app-modules/events/src/Filament/Admin/Resources/Events/Schemas/EventForm.php index 61593b7a4..945984964 100644 --- a/app-modules/events/src/Filament/Resources/Events/Schemas/EventForm.php +++ b/app-modules/events/src/Filament/Admin/Resources/Events/Schemas/EventForm.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Events\Schemas; +namespace He4rt\Events\Filament\Admin\Resources\Events\Schemas; use Filament\Forms\Components\DateTimePicker; use Filament\Forms\Components\RichEditor; diff --git a/app-modules/events/src/Filament/Resources/Events/Tables/EventsTable.php b/app-modules/events/src/Filament/Admin/Resources/Events/Tables/EventsTable.php similarity index 95% rename from app-modules/events/src/Filament/Resources/Events/Tables/EventsTable.php rename to app-modules/events/src/Filament/Admin/Resources/Events/Tables/EventsTable.php index 7a3592b68..f6260f2e0 100644 --- a/app-modules/events/src/Filament/Resources/Events/Tables/EventsTable.php +++ b/app-modules/events/src/Filament/Admin/Resources/Events/Tables/EventsTable.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Events\Tables; +namespace He4rt\Events\Filament\Admin\Resources\Events\Tables; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; diff --git a/app-modules/events/src/Filament/Admin/Resources/Talks/Pages/CreateTalk.php b/app-modules/events/src/Filament/Admin/Resources/Talks/Pages/CreateTalk.php new file mode 100644 index 000000000..c0cf24a48 --- /dev/null +++ b/app-modules/events/src/Filament/Admin/Resources/Talks/Pages/CreateTalk.php @@ -0,0 +1,13 @@ + ListEventModels::route('/'), + 'show' => ViewEventModel::route('/{record}'), + ]; + } +} diff --git a/app-modules/events/src/Filament/App/EventModels/Pages/ListEventModels.php b/app-modules/events/src/Filament/App/EventModels/Pages/ListEventModels.php new file mode 100644 index 000000000..712535f08 --- /dev/null +++ b/app-modules/events/src/Filament/App/EventModels/Pages/ListEventModels.php @@ -0,0 +1,47 @@ +find($eventModelId); + app(AttendEventAction::class)->execute($eventModel); + + Notification::make() + ->success() + ->body('Send Successfully') + ->send(); + } + + public function leave(string|int $eventModelId): void + { + $eventModel = EventModel::query()->find($eventModelId); + + app(LeaveEventAction::class)->execute($eventModel); + Notification::make() + ->success() + ->body('Leaved Event Successfully') + ->send(); + } + + protected function modifyQueryWithActiveTab(Builder $query): Builder + { + return $query->where('active', true)->with('attendees')->latest('end_at'); + } +} diff --git a/app-modules/events/src/Filament/App/EventModels/Pages/ViewEventModel.php b/app-modules/events/src/Filament/App/EventModels/Pages/ViewEventModel.php new file mode 100644 index 000000000..039298dcb --- /dev/null +++ b/app-modules/events/src/Filament/App/EventModels/Pages/ViewEventModel.php @@ -0,0 +1,111 @@ +record($this->record) + ->components([ + Section::make('Detalhes do Evento') + ->description('Informações básicas e descrição do evento.') + ->icon('heroicon-o-calendar-days') + ->columns(2) + ->schema([ + Grid::make(1) + ->columnSpan(1) + ->schema([ + TextEntry::make('title') + ->label('Título') + ->size('lg') + ->weight('bold') + ->color('primary'), + + TextEntry::make('event_type') + ->label('Tipo de Evento') + ->badge(), + ]), + + Grid::make(1) + ->columnSpan(1) + ->schema([ + IconEntry::make('active') + ->label('Status') + ->icon(fn (bool $state): string => $state ? 'heroicon-o-check-circle' : 'heroicon-o-x-circle') + ->color(fn (bool $state): string => $state ? 'success' : 'danger') + ->alignment(Alignment::Start), + ]), + TextEntry::make('description') + ->label('Descrição Detalhada') + ->columnSpanFull() + ->markdown(), + ]), + + Section::make('Agenda e Local') + ->icon('heroicon-o-clock') + ->columns(3) + ->schema([ + TextEntry::make('event_at') + ->label('Data e Hora do Evento') + ->dateTime('d/m/Y H:i') + ->icon('heroicon-o-calendar-days'), + + TextEntry::make('start_at') + ->label('Início') + ->dateTime('H:i') + ->icon('heroicon-o-clock'), + + TextEntry::make('end_at') + ->label('Fim') + ->dateTime('H:i') + ->icon('heroicon-o-clock'), + + TextEntry::make('location') + ->label('Localização') + ->icon('heroicon-o-map-pin') + ->columnSpanFull(), + ]), + Section::make('Participação') + ->icon('heroicon-o-users') + ->columns(3) + ->schema([ + TextEntry::make('max_attendees') + ->label('Capacidade Máxima') + ->numeric() + ->icon('heroicon-o-user-group') + ->badge(), + + TextEntry::make('attendees_count') + ->label('Participantes Confirmados') + ->numeric() + ->color('success') + ->icon('heroicon-o-user'), + + TextEntry::make('waitlist_count') + ->label('Lista de Espera') + ->numeric() + ->color('warning') + ->icon('heroicon-o-queue-list'), + ]), + ]); + } +} diff --git a/app-modules/events/src/Filament/App/EventModels/Widgets/LatestEvents.php b/app-modules/events/src/Filament/App/EventModels/Widgets/LatestEvents.php new file mode 100644 index 000000000..57c30df69 --- /dev/null +++ b/app-modules/events/src/Filament/App/EventModels/Widgets/LatestEvents.php @@ -0,0 +1,57 @@ +query(fn (): Builder => EventModel::query()->where('tenant_id', Filament::getTenant()->getKey())->latest()) + ->columns([ + TextColumn::make('event_type') + ->badge() + ->searchable(), + IconColumn::make('active') + ->boolean(), + TextColumn::make('title') + ->searchable(), + TextColumn::make('location') + ->searchable(), + TextColumn::make('event_at') + ->formatStateUsing(fn ($state) => $state->format('d/m/Y')) + ->sortable(), + TextColumn::make('start_at') + ->label('Event Hour') + ->formatStateUsing(fn ($state) => $state->format('d/m/Y H:i')) + ->description(fn ($record) => $record->end_at->format('d/m/Y H:i')) + ->sortable(), + TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->recordActions([ + Action::make('Details') + ->label('Details') + ->icon('heroicon-s-eye') + ->action(fn (EventModel $record) => $this->redirect(EventModelResource::getUrl('show', ['record' => $record->getKey()]))), + ]); + } +} diff --git a/app-modules/events/src/Filament/Resources/Talks/Pages/CreateTalk.php b/app-modules/events/src/Filament/App/Talks/Pages/CreateTalk.php similarity index 61% rename from app-modules/events/src/Filament/Resources/Talks/Pages/CreateTalk.php rename to app-modules/events/src/Filament/App/Talks/Pages/CreateTalk.php index e6dcdd41b..5af17e707 100644 --- a/app-modules/events/src/Filament/Resources/Talks/Pages/CreateTalk.php +++ b/app-modules/events/src/Filament/App/Talks/Pages/CreateTalk.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Talks\Pages; +namespace He4rt\Events\Filament\App\Talks\Pages; use Filament\Resources\Pages\CreateRecord; -use He4rt\Events\Filament\Resources\Talks\TalkResource; +use He4rt\Events\Filament\App\Talks\TalkResource; class CreateTalk extends CreateRecord { diff --git a/app-modules/events/src/Filament/Resources/Talks/Pages/ListTalks.php b/app-modules/events/src/Filament/App/Talks/Pages/ListTalks.php similarity index 75% rename from app-modules/events/src/Filament/Resources/Talks/Pages/ListTalks.php rename to app-modules/events/src/Filament/App/Talks/Pages/ListTalks.php index 241be2817..e3db3e067 100644 --- a/app-modules/events/src/Filament/Resources/Talks/Pages/ListTalks.php +++ b/app-modules/events/src/Filament/App/Talks/Pages/ListTalks.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace He4rt\Events\Filament\Resources\Talks\Pages; +namespace He4rt\Events\Filament\App\Talks\Pages; use Filament\Actions\CreateAction; use Filament\Resources\Pages\ListRecords; -use He4rt\Events\Filament\Resources\Talks\TalkResource; +use He4rt\Events\Filament\App\Talks\TalkResource; class ListTalks extends ListRecords { diff --git a/app-modules/events/src/Filament/App/Talks/Schemas/TalkForm.php b/app-modules/events/src/Filament/App/Talks/Schemas/TalkForm.php new file mode 100644 index 000000000..33cab1dd2 --- /dev/null +++ b/app-modules/events/src/Filament/App/Talks/Schemas/TalkForm.php @@ -0,0 +1,76 @@ +components([ + Section::make('Proposta da Palestra') + ->description('Defina o evento, título e tipo da sua proposta.') + ->icon('heroicon-m-clipboard-document-list') + ->columns(3) + ->columnSpanFull() + ->schema([ + Select::make('event_id') + ->label('Evento') + ->searchable() + ->relationship( + name: 'event', + titleAttribute: 'title', + modifyQueryUsing: fn (Builder $query) => $query->where('tenant_id', Filament::getTenant()->getKey()) + ) + ->required() + ->columnSpan(2), + + TextInput::make('field_type') + ->label('Tipo') + ->minLength(3) + ->maxlength(255) + ->required() + ->columnSpan(1), + TextInput::make('title') + ->label('Título da Proposta') + ->minLength(3) + ->maxlength(255) + ->required() + ->columnSpanFull(), + ]), + + Section::make('Detalhes e Conteúdo') + ->description('Forneça a descrição completa da sua palestra e o que o público aprenderá.') + ->icon('heroicon-m-document-text') + ->schema([ + RichEditor::make('description') + ->label('Descrição Completa') + ->required() + ->columnSpanFull(), + ]) + ->columnSpanFull(), + + Hidden::make('user_id') + ->default(auth()->user()->getKey()) + ->required(), + Hidden::make('tenant_id') + ->default(Filament::getTenant()->getKey()) + ->required(), + Hidden::make('status') + ->default(TalkStatusEnum::Pending) + ->required(), + ]); + } +} diff --git a/app-modules/events/src/Filament/App/Talks/Tables/TalksTable.php b/app-modules/events/src/Filament/App/Talks/Tables/TalksTable.php new file mode 100644 index 000000000..4faa5d213 --- /dev/null +++ b/app-modules/events/src/Filament/App/Talks/Tables/TalksTable.php @@ -0,0 +1,51 @@ +modifyQueryUsing(fn ($query) => $query->where('user_id', auth()->user()->getKey())->where('tenant_id', Filament::getTenant()->getKey())) + ->columns([ + TextColumn::make('event.title') + ->searchable(), + TextColumn::make('tenant.name') + ->badge() + ->searchable(), + TextColumn::make('status') + ->badge() + ->searchable(), + TextColumn::make('field_type') + ->searchable(), + TextColumn::make('title') + ->searchable(), + TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->recordActions([ + ViewAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]); + } +} diff --git a/app-modules/events/src/Filament/App/Talks/TalkResource.php b/app-modules/events/src/Filament/App/Talks/TalkResource.php new file mode 100644 index 000000000..a33123971 --- /dev/null +++ b/app-modules/events/src/Filament/App/Talks/TalkResource.php @@ -0,0 +1,45 @@ + ListTalks::route('/'), + 'create' => CreateTalk::route('/create'), + ]; + } +} diff --git a/app-modules/events/src/Models/EventModel.php b/app-modules/events/src/Models/EventModel.php index ccdb12728..21a01cabd 100644 --- a/app-modules/events/src/Models/EventModel.php +++ b/app-modules/events/src/Models/EventModel.php @@ -4,6 +4,7 @@ namespace He4rt\Events\Models; +use Carbon\Traits\Date; use Exception; use He4rt\Events\Database\Factories\EventFactory; use He4rt\Events\Enums\AttendingStatusEnum; @@ -30,11 +31,13 @@ * @property int $attendees_count * @property int $waitlist_count * @property int $tenant_id + * @property Date $end_at */ #[UseFactory(EventFactory::class)] class EventModel extends Model { use HasFactory; + protected $table = 'events'; protected $fillable = [ @@ -70,8 +73,11 @@ public function attendees(): BelongsToMany ->withTimestamps(); } - public function attend(mixed $userId, AttendingStatusEnum $status = AttendingStatusEnum::Attending): bool + public function attend(mixed $userId, AttendingStatusEnum $status = AttendingStatusEnum::Attending): void { + if ($this->isParticipating($userId)) { + return; + } $this->attendees()->attach($userId, ['status' => $status]); @@ -80,8 +86,11 @@ public function attend(mixed $userId, AttendingStatusEnum $status = AttendingSta AttendingStatusEnum::Waitlist => $this->increment('waitlist_count'), default => throw new Exception('Unexpected match value'), }; + } - return true; + public function isPast(): bool + { + return $this->end_at < now(); } public function leave(mixed $userId): bool @@ -103,6 +112,21 @@ public function leave(mixed $userId): bool return true; } + public function isParticipating($userId): bool + { + return $this->attendees()->where('user_id', $userId)->exists(); + } + + public function isAttending(): bool + { + return $this->attendees->first()->pivot->status === AttendingStatusEnum::Attending; + } + + public function onWaitlist(): bool + { + return $this->attendees->first()->pivot->status === AttendingStatusEnum::Waitlist; + } + /** * @return BelongsTo */ diff --git a/app-modules/events/src/Providers/EventsServiceProvider.php b/app-modules/events/src/Providers/EventsServiceProvider.php index 75e199726..7ea32b505 100644 --- a/app-modules/events/src/Providers/EventsServiceProvider.php +++ b/app-modules/events/src/Providers/EventsServiceProvider.php @@ -7,6 +7,7 @@ use App\Enums\FilamentPanel; use Filament\Panel; use He4rt\Events\AdminEventPanelPlugin; +use He4rt\Events\AppEventPanelPlugin; use Illuminate\Support\ServiceProvider; class EventsServiceProvider extends ServiceProvider @@ -16,6 +17,7 @@ public function register(): void Panel::configureUsing(function (Panel $panel): void { match ($panel->currentPanel()) { FilamentPanel::Admin => $panel->plugin(new AdminEventPanelPlugin), + FilamentPanel::User => $panel->plugin(new AppEventPanelPlugin), default => null, }; }); @@ -24,5 +26,6 @@ public function register(): void public function boot(): void { $this->loadMigrationsFrom(__DIR__.'/../../database/migrations'); + $this->loadViewsFrom(__DIR__.'/../../resources/views', 'events'); } } diff --git a/app-modules/events/tests/Feature/Filament/Admin/Event/CreateEventTest.php b/app-modules/events/tests/Feature/Filament/Admin/Event/CreateEventTest.php index cb236ecfe..72b5749b8 100644 --- a/app-modules/events/tests/Feature/Filament/Admin/Event/CreateEventTest.php +++ b/app-modules/events/tests/Feature/Filament/Admin/Event/CreateEventTest.php @@ -5,7 +5,7 @@ use App\Enums\FilamentPanel; use Filament\Facades\Filament; use He4rt\Events\Enums\EventTypeEnum; -use He4rt\Events\Filament\Resources\Events\Pages\CreateEvent; +use He4rt\Events\Filament\Admin\Resources\Events\Pages\CreateEvent; use He4rt\Events\Models\EventModel; use Illuminate\Support\Facades\Date; diff --git a/app-modules/events/tests/Feature/Filament/Admin/Event/EditEventTest.php b/app-modules/events/tests/Feature/Filament/Admin/Event/EditEventTest.php index f324bdb53..12ba92a84 100644 --- a/app-modules/events/tests/Feature/Filament/Admin/Event/EditEventTest.php +++ b/app-modules/events/tests/Feature/Filament/Admin/Event/EditEventTest.php @@ -4,7 +4,7 @@ use App\Enums\FilamentPanel; use Filament\Facades\Filament; -use He4rt\Events\Filament\Resources\Events\Pages\EditEvent; +use He4rt\Events\Filament\Admin\Resources\Events\Pages\EditEvent; use He4rt\Events\Models\EventModel; use function Pest\Livewire\livewire; diff --git a/app-modules/events/tests/Feature/Filament/Admin/Event/ListEventsTest.php b/app-modules/events/tests/Feature/Filament/Admin/Event/ListEventsTest.php index 200ff7b72..6db39eb71 100644 --- a/app-modules/events/tests/Feature/Filament/Admin/Event/ListEventsTest.php +++ b/app-modules/events/tests/Feature/Filament/Admin/Event/ListEventsTest.php @@ -4,7 +4,7 @@ use App\Enums\FilamentPanel; use Filament\Facades\Filament; -use He4rt\Events\Filament\Resources\Events\Pages\ListEvents; +use He4rt\Events\Filament\Admin\Resources\Events\Pages\ListEvents; use function Pest\Livewire\livewire; diff --git a/app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php b/app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php index 032605cd4..6790311d7 100644 --- a/app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php +++ b/app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php @@ -5,7 +5,6 @@ use App\Enums\FilamentPanel; use Filament\Facades\Filament; use He4rt\Events\Enums\Talks\TalkStatusEnum; -use He4rt\Events\Filament\Resources\Talks\Pages\CreateTalk; use He4rt\Events\Models\EventModel; use He4rt\Events\Models\Talk; use He4rt\User\Models\User; diff --git a/app-modules/events/tests/Feature/Filament/Admin/Talk/EditTalk.php b/app-modules/events/tests/Feature/Filament/Admin/Talk/EditTalk.php index 545e976dd..5c4e9732d 100644 --- a/app-modules/events/tests/Feature/Filament/Admin/Talk/EditTalk.php +++ b/app-modules/events/tests/Feature/Filament/Admin/Talk/EditTalk.php @@ -4,7 +4,6 @@ use App\Enums\FilamentPanel; use Filament\Facades\Filament; -use He4rt\Events\Filament\Resources\Talks\Pages\EditTalk; use He4rt\Events\Models\Talk; use function Pest\Livewire\livewire; diff --git a/app-modules/events/tests/Feature/Filament/Admin/Talk/ListTalks.php b/app-modules/events/tests/Feature/Filament/Admin/Talk/ListTalks.php index db0cb4469..a6b7d8b83 100644 --- a/app-modules/events/tests/Feature/Filament/Admin/Talk/ListTalks.php +++ b/app-modules/events/tests/Feature/Filament/Admin/Talk/ListTalks.php @@ -4,7 +4,6 @@ use App\Enums\FilamentPanel; use Filament\Facades\Filament; -use He4rt\Events\Filament\Resources\Talks\Pages\ListTalks; use function Pest\Livewire\livewire; diff --git a/app-modules/events/tests/Feature/Filament/App/Events/ListEventsTest.php b/app-modules/events/tests/Feature/Filament/App/Events/ListEventsTest.php new file mode 100644 index 000000000..d0af6d3fb --- /dev/null +++ b/app-modules/events/tests/Feature/Filament/App/Events/ListEventsTest.php @@ -0,0 +1,138 @@ +value); + actingAs(User::factory()->create()); + $this->tenant = Tenant::factory()->create(); + Filament::setTenant($this->tenant); + + $this->events = EventModel::factory()->count(10) + ->afterCreating(function (EventModel $event): void { + $attendees = User::factory()->count(4)->create(); + + foreach ($attendees as $user) { + $event->attendees()->attach($user->id, [ + 'status' => fake()->randomElement(AttendingStatusEnum::cases()), + ]); + } + }) + ->create([ + 'tenant_id' => $this->tenant->getKey(), + 'end_at' => Date::tomorrow(), + ]); +}); + +it('should render', function (): void { + livewire(ListEventModels::class, ['tenant' => $this->tenant->slug]) + ->assertOk(); +}); + +it('should render events', function (): void { + $this->events->each(function (EventModel $event): void { + livewire(ListEventModels::class, ['tenant' => $this->tenant->slug]) + ->assertOk() + ->assertSeeText($event->title) + ->assertSeeText($event->location) + ->assertSeeText(Date::parse($event->event_at)->format('d/m/Y')) + ->assertSeeText(Date::parse($event->start_at)->format('H:i:s')) + ->assertSeeText(Date::parse($event->end_at)->format('H:i:s')) + ->assertSeeText($event->event_type->getLabel()); + }); +}); + +it('should see Register or Join Waitlist based on status', function ($status, $text, $dontSeeText): void { + $this->events->each(function (EventModel $event) use ($status): void { + $attendeeIds = $event->attendees->pluck('id'); + $event->attendees()->updateExistingPivot( + $attendeeIds, + ['status' => $status] + ); + }); + + $this->events->fresh(); + livewire(ListEventModels::class, ['tenant' => $this->tenant->slug]) + ->assertOk() + ->assertSeeText($text) + ->assertDontSeeText($dontSeeText); +})->with([ + 'attending' => [AttendingStatusEnum::Attending->value, 'Join', 'Join Waitlist'], + 'waitlist' => [AttendingStatusEnum::Waitlist->value, 'Join Waitlist', 'Join123'], +]); + +it('should be able to participate to an event', function (): void { + $event = $this->events->first(); + $attendeeIds = $event->attendees->pluck('id'); + + $event->attendees()->updateExistingPivot( + $attendeeIds, + ['status' => AttendingStatusEnum::Waitlist], + ); + livewire(ListEventModels::class, ['tenant' => $this->tenant->slug]) + ->assertOk() + ->call('attend', $event->getKey()) + ->assertNotified( + Notification::make() + ->success() + ->body('Send Successfully'), + ); + + expect($event->attendees()->count())->toBe(5) + ->and($event->attendees()->get()->last()->getKey())->toBe(auth()->user()->getKey()); +}); + +it('should go to waitlist', function (): void { + $event = $this->events->first(); + $attendeeIds = $event->attendees->pluck('id'); + + $event->attendees()->updateExistingPivot( + $attendeeIds, + ['status' => AttendingStatusEnum::Waitlist], + ); + + livewire(ListEventModels::class, ['tenant' => $this->tenant->slug]) + ->assertOk() + ->call('attend', $event->getKey()) + ->assertNotified( + Notification::make() + ->success() + ->body('Send Successfully'), + ); + + expect($event->attendees()->count())->toBe(5) + ->and($event->fresh()->waitlist_count)->toBe(1) + ->and($event->isParticipating(auth()->user()->id))->toBeTrue(); +}); + +it('should be able to leave an event', function (): void { + $event = $this->events->first(); + $event->attendees()->attach( + auth()->user()->getKey(), + ['status' => AttendingStatusEnum::Waitlist] + ); + livewire(ListEventModels::class, ['tenant' => $this->tenant->slug]) + ->assertOk() + ->call('leave', $event->getKey()) + ->assertNotified(Notification::make() + ->success() + ->body('Leaved Event Successfully') + ->send()); + + $event->refresh(); + expect($event->attendees()->count())->toBe(4) + ->and($event->isParticipating(auth()->user()->id))->tobeFalse(); +}); diff --git a/app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php b/app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php new file mode 100644 index 000000000..790b8eb2b --- /dev/null +++ b/app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php @@ -0,0 +1,99 @@ +value); + actingAs(User::factory()->create()); + $this->tenant = Tenant::factory()->create(); + Filament::setTenant($this->tenant); + $this->event = EventModel::factory()->recycle($this->tenant)->create(); +}); + +it('should render', function (): void { + livewire(CreateTalk::class) + ->assertOk(); +}); + +it('should send a talk call for paper', function (): void { + livewire(CreateTalk::class) + ->assertOk() + ->fillForm([ + 'event_id' => $this->event->getKey(), + 'field_type' => 'whatever', + 'title' => 'title whatever', + 'description' => 'description whatever', + ]) + ->call('create') + ->assertHasNoFormErrors(); + + assertDatabaseHas(Talk::class, [ + 'event_id' => $this->event->getKey(), + 'field_type' => 'whatever', + 'title' => 'title whatever', + 'user_id' => auth()->user()->getKey(), + 'tenant_id' => Filament::getTenant()->getKey(), + ]); +}); +it('should create talk that only events that belongs to the tenant', function (): void { + livewire(CreateTalk::class) + ->assertOk() + ->fillForm([ + 'event_id' => EventModel::factory()->create()->getKey(), + 'field_type' => 'whatever', + 'title' => 'title whatever', + 'description' => 'description whatever', + ]) + ->call('create') + ->assertHasFormErrors(['event_id']); + + assertDatabaseMissing(Talk::class, [ + 'event_id' => EventModel::factory()->create()->getKey(), + 'field_type' => 'whatever', + 'title' => 'title whatever', + ]); +}); + +describe('validation rules', function (): void { + test('field_type::validations', function ($rule, $value): void { + livewire(CreateTalk::class) + ->assertOk() + ->fillForm([ + 'field_type' => $value, + + ]) + ->call('create') + ->assertHasNoFormErrors(['field_type' => $rule]); + })->with([ + 'required' => ['', 'required'], + 'min:3' => ['aa', 'min:3'], + 'max:256' => [str_repeat('a', 256), 'max:255'], + ]); + test('title::validations', function ($rule, $value): void { + livewire(CreateTalk::class) + ->assertOk() + ->fillForm([ + 'title' => $value, + + ]) + ->call('create') + ->assertHasNoFormErrors(['title' => $rule]); + })->with([ + 'required' => ['', 'required'], + 'min:3' => ['aa', 'min:3'], + 'max:256' => [str_repeat('a', 256), 'max:255'], + ]); +}); diff --git a/app-modules/events/tests/Feature/Filament/App/Talks/ListTalkTest.php b/app-modules/events/tests/Feature/Filament/App/Talks/ListTalkTest.php new file mode 100644 index 000000000..1a169343a --- /dev/null +++ b/app-modules/events/tests/Feature/Filament/App/Talks/ListTalkTest.php @@ -0,0 +1,63 @@ +value); + actingAs(User::factory()->create()); + $this->tenant = Tenant::factory()->create(); + Filament::setTenant($this->tenant); + $this->talks = Talk::factory() + ->recycle($this->tenant) + ->recycle(auth()->user()) + ->count(10) + ->create(); +}); + +it('should render', function (): void { + livewire(ListTalks::class) + ->assertOk(); +}); + +it('should see all talks belongs to the user', function (): void { + livewire(ListTalks::class) + ->assertOk() + ->assertCanSeeTableRecords($this->talks) + ->assertCountTableRecords($this->talks->count()); +}); +it('should see only talks that belongs to the user', function (): void { + $anotherTalks = Talk::factory()->for($this->tenant)->count(10)->create(); + livewire(ListTalks::class) + ->assertOk() + ->assertCanSeeTableRecords($this->talks) + ->assertCanNotSeeTableRecords($anotherTalks) + ->assertCountTableRecords($this->talks->count()); +}); +it('should see only talks that belongs to the user and current tenant', function (): void { + $anotherTenant = Tenant::factory()->create(); + $this->talks->each(function (Talk $talk) use ($anotherTenant): void { + $talk->update(['tenant_id' => $anotherTenant->getKey()]); + }); + $this->talks->fresh(); + + livewire(ListTalks::class) + ->assertOk() + ->assertCanNotSeeTableRecords($this->talks) + ->assertCountTableRecords(0); + + Filament::setTenant($anotherTenant); + livewire(ListTalks::class) + ->assertOk() + ->assertCanSeeTableRecords($this->talks) + ->assertCountTableRecords($this->talks->count()); +}); diff --git a/app-modules/feedback/src/Filament/Admin/Resources/Feedback/FeedbackResource.php b/app-modules/feedback/src/Filament/Admin/Resources/Feedback/FeedbackResource.php index 5e69e9bc4..0141fe720 100644 --- a/app-modules/feedback/src/Filament/Admin/Resources/Feedback/FeedbackResource.php +++ b/app-modules/feedback/src/Filament/Admin/Resources/Feedback/FeedbackResource.php @@ -23,7 +23,7 @@ class FeedbackResource extends Resource protected static string|UnitEnum|null $navigationGroup = 'General'; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; + protected static string|BackedEnum|null $navigationIcon = Heroicon::SpeakerWave; protected static ?string $recordTitleAttribute = 'message'; diff --git a/app-modules/message/src/Filament/Admin/Resources/Messages/MessageResource.php b/app-modules/message/src/Filament/Admin/Resources/Messages/MessageResource.php index 9217d053c..5b79e15af 100644 --- a/app-modules/message/src/Filament/Admin/Resources/Messages/MessageResource.php +++ b/app-modules/message/src/Filament/Admin/Resources/Messages/MessageResource.php @@ -23,7 +23,7 @@ class MessageResource extends Resource protected static string|UnitEnum|null $navigationGroup = 'Gamefication'; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; + protected static string|BackedEnum|null $navigationIcon = Heroicon::ChatBubbleBottomCenter; protected static ?string $recordTitleAttribute = 'content'; diff --git a/app-modules/season/src/Models/Season.php b/app-modules/season/src/Models/Season.php index e34cd9452..279bfa666 100644 --- a/app-modules/season/src/Models/Season.php +++ b/app-modules/season/src/Models/Season.php @@ -13,7 +13,19 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Facades\Date; +/** + * @property int $tenant_id + * @property string $name + * @property string $description + * @property int $messages_count + * @property int $participants_count + * @property int $meetings_count + * @property int $badges_count + * @property Date $started_at + * @property Date $ended_at + */ final class Season extends Model { use HasFactory; @@ -62,4 +74,12 @@ protected static function newFactory(): SeasonFactory { return SeasonFactory::new(); } + + protected function casts(): array + { + return [ + 'started_at' => 'datetime', + 'ended_at' => 'datetime', + ]; + } } diff --git a/app-modules/sponsors/src/Filament/Resources/Sponsors/SponsorResource.php b/app-modules/sponsors/src/Filament/Resources/Sponsors/SponsorResource.php index 063c8d052..825e1e297 100644 --- a/app-modules/sponsors/src/Filament/Resources/Sponsors/SponsorResource.php +++ b/app-modules/sponsors/src/Filament/Resources/Sponsors/SponsorResource.php @@ -23,7 +23,7 @@ class SponsorResource extends Resource protected static string|UnitEnum|null $navigationGroup = 'General'; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; + protected static string|BackedEnum|null $navigationIcon = Heroicon::Banknotes; protected static ?string $recordTitleAttribute = 'name'; diff --git a/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Schemas/TenantForm.php b/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Schemas/TenantForm.php index cb3540d35..fb43c874c 100644 --- a/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Schemas/TenantForm.php +++ b/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Schemas/TenantForm.php @@ -6,6 +6,7 @@ use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Toggle; use Filament\Schemas\Schema; class TenantForm @@ -18,7 +19,8 @@ public static function configure(Schema $schema): Schema TextInput::make('slug'), Select::make('owner_id') ->relationship('owner', 'name'), - Select::make('active')->boolean(), + Toggle::make('active') + ->required(), ]); } } diff --git a/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Tables/TenantsTable.php b/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Tables/TenantsTable.php index 428839ae2..b7d834491 100644 --- a/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Tables/TenantsTable.php +++ b/app-modules/tenant/src/Filament/Admin/Resources/Tenants/Tables/TenantsTable.php @@ -7,6 +7,8 @@ use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; +use Filament\Tables\Columns\IconColumn; +use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; class TenantsTable @@ -15,10 +17,14 @@ public static function configure(Table $table): Table { return $table ->columns([ - // - ]) - ->filters([ - // + TextColumn::make('name') + ->searchable(), + TextColumn::make('slug') + ->searchable(), + TextColumn::make('owner.name') + ->searchable(), + IconColumn::make('active') + ->boolean(), ]) ->recordActions([ EditAction::make(), diff --git a/app-modules/tenant/src/Filament/Admin/Resources/Tenants/TenantResource.php b/app-modules/tenant/src/Filament/Admin/Resources/Tenants/TenantResource.php index 6424e4068..1939cb522 100644 --- a/app-modules/tenant/src/Filament/Admin/Resources/Tenants/TenantResource.php +++ b/app-modules/tenant/src/Filament/Admin/Resources/Tenants/TenantResource.php @@ -23,7 +23,7 @@ class TenantResource extends Resource protected static string|UnitEnum|null $navigationGroup = 'Administration'; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; + protected static string|BackedEnum|null $navigationIcon = Heroicon::Cube; protected static ?string $recordTitleAttribute = 'name'; diff --git a/app-modules/tenant/src/Models/Tenant.php b/app-modules/tenant/src/Models/Tenant.php index 7c8ac75c2..751709b2b 100644 --- a/app-modules/tenant/src/Models/Tenant.php +++ b/app-modules/tenant/src/Models/Tenant.php @@ -4,13 +4,17 @@ namespace He4rt\Tenant\Models; +use He4rt\Character\Models\PastSeason; +use He4rt\Events\Models\EventModel; use He4rt\Provider\Models\Provider; +use He4rt\Season\Models\Season; use He4rt\Tenant\Database\Factories\TenantFactory; use He4rt\User\Models\User; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\SoftDeletes; @@ -52,6 +56,30 @@ public function providers(): MorphMany return $this->morphMany(Provider::class, 'model'); } + /** + * @return HasMany + */ + public function events(): HasMany + { + return $this->hasMany(EventModel::class); + } + + /** + * @return HasMany + */ + public function seasons(): HasMany + { + return $this->hasMany(Season::class); + } + + /** + * @return HasMany + */ + public function pastSeasons(): HasMany + { + return $this->hasMany(PastSeason::class); + } + protected static function newFactory(): TenantFactory { return TenantFactory::new(); diff --git a/app-modules/user/resources/views/filament/app-dashboard.blade.php b/app-modules/user/resources/views/filament/app-dashboard.blade.php new file mode 100644 index 000000000..05854026d --- /dev/null +++ b/app-modules/user/resources/views/filament/app-dashboard.blade.php @@ -0,0 +1,150 @@ +@php + use Carbon\Carbon; + use He4rt\Events\Filament\App\EventModels\Widgets\LatestEvents; + use Illuminate\Support\Facades\Date; + $userExperience = $this->stats->experience ?? 0; + $level = $this->stats->level ?? 1; + + $reputation = $this->stats->reputation ?? 0; + $experienceRequiredForNextLevel = $this->stats->experienceProgress ?? 0; + $experienceRemaining = $this->stats->experiencePercentageRemaining ?? 0; + $nextLevelExperience = $experienceRequiredForNextLevel + $userExperience; + $experiencePercentage = $nextLevelExperience > 0 ? ($userExperience / $nextLevelExperience) * 100 : 0; + + $user = auth()->user(); + + $address = $user?->address; + + $userFullAddress = $address ? implode(', ', array_filter([$address->city ?? null, $address->state ?? null, $address->country ?? null])) : ''; + + $userName = $user?->name ?? ''; + $profileAbout = $user?->information?->about ?? ''; + $profileAvatarUrl = 'https://ui-avatars.com/api/?name=' . urlencode($user?->name ?? '') . '&background=0D8ABC&color=fff'; + $githubUrl = $user?->information?->github_url ?? null; + $linkedinUrl = $user?->information?->linkedin_url ?? null; +@endphp + + +
+ + +

Profile

+ +
+
+ + + +

{{ $userName }}

+
+

+ {{ $profileAbout }} +

+ + @if (! empty($userFullAddress)) +
+ + {{ $userFullAddress }} +
+ @endif + + @if (! empty($githubUrl) || ! empty($linkedinUrl)) +
+ @if (! empty($githubUrl)) + + + GitHub + + @endif + + @if (! empty($linkedinUrl)) + + + LinkedIn + + @endif +
+ @endif +
+
+ + + +

Character Stats

+ +
+ +
+
+
+ + + Level + {{ $level }} + +
+ + {{ $userExperience }} + / + {{ $nextLevelExperience }} + XP + +
+ + +
+
+
+ +
+ {{ (int) $experienceRequiredForNextLevel }} + to next level +
+
+ + +
+
+ +
+

Reputation

+

{{ $reputation }}

+
+
+ +
+ +
+

Daily Bonus

+

+ {{ Date::today()->format('d/M/Y') }} +

+
+
+
+
+
+
+ + @livewire(LatestEvents::class) +
diff --git a/app-modules/user/src/Filament/Admin/Resources/Users/UserResource.php b/app-modules/user/src/Filament/Admin/Resources/Users/UserResource.php index 05f9743b1..f0b4a691a 100644 --- a/app-modules/user/src/Filament/Admin/Resources/Users/UserResource.php +++ b/app-modules/user/src/Filament/Admin/Resources/Users/UserResource.php @@ -24,7 +24,7 @@ class UserResource extends Resource protected static string|UnitEnum|null $navigationGroup = 'Administration'; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; + protected static string|BackedEnum|null $navigationIcon = Heroicon::Users; protected static ?string $recordTitleAttribute = 'name'; diff --git a/app-modules/user/src/Filament/User/Pages/Dashboard.php b/app-modules/user/src/Filament/User/Pages/Dashboard.php new file mode 100644 index 000000000..488536c69 --- /dev/null +++ b/app-modules/user/src/Filament/User/Pages/Dashboard.php @@ -0,0 +1,30 @@ +tenant = auth()->user()->tenants()->where('slug', '=', $tenant->slug)->first(); + } + + #[Computed] + public function stats() + { + return auth()->user()->character()->where('tenant_id', '=', $this->tenant->getKey())->first(); + } +} diff --git a/app-modules/user/src/Filament/User/Pages/UserProfile.php b/app-modules/user/src/Filament/User/Pages/UserProfile.php index edc21196a..1e96877fc 100644 --- a/app-modules/user/src/Filament/User/Pages/UserProfile.php +++ b/app-modules/user/src/Filament/User/Pages/UserProfile.php @@ -50,7 +50,7 @@ /** * @property-read Schema $form */ -final class UserProfile extends Page +class UserProfile extends Page { use CanUseDatabaseTransactions; use HasMaxWidth; diff --git a/app-modules/user/src/Plugins/AppUserPanelPlugin.php b/app-modules/user/src/Plugins/AppUserPanelPlugin.php index 3345558ea..99c1f0aa6 100644 --- a/app-modules/user/src/Plugins/AppUserPanelPlugin.php +++ b/app-modules/user/src/Plugins/AppUserPanelPlugin.php @@ -7,6 +7,8 @@ use App\Enums\FilamentPanel; use Filament\Contracts\Plugin; use Filament\Panel; +use He4rt\Events\Filament\App\EventModels\Widgets\LatestEvents; +use He4rt\User\Filament\User\Pages\Dashboard; use He4rt\User\Filament\User\Pages\UserProfile; class AppUserPanelPlugin implements Plugin @@ -20,6 +22,10 @@ public function register(Panel $panel): void { $panel->pages([ UserProfile::class, + Dashboard::class, + ]); + $panel->widgets([ + LatestEvents::class, ]); } diff --git a/app-modules/user/src/Providers/UserServiceProvider.php b/app-modules/user/src/Providers/UserServiceProvider.php index bd17496d9..ce04606e3 100644 --- a/app-modules/user/src/Providers/UserServiceProvider.php +++ b/app-modules/user/src/Providers/UserServiceProvider.php @@ -30,5 +30,6 @@ public function register(): void public function boot(): void { $this->loadMigrationsFrom(__DIR__.'/../../database/migrations'); + $this->loadViewsFrom(__DIR__.'/../../resources/views', 'users'); } } diff --git a/app-modules/user/tests/Feature/Filament/App/Pages/DashboardPageTest.php b/app-modules/user/tests/Feature/Filament/App/Pages/DashboardPageTest.php new file mode 100644 index 000000000..048db5969 --- /dev/null +++ b/app-modules/user/tests/Feature/Filament/App/Pages/DashboardPageTest.php @@ -0,0 +1,69 @@ +user = User::factory()->create(); + $tenant = Tenant::factory() + ->for($this->user, 'owner') + ->afterCreating(fn (Tenant $tenant) => $tenant->members()->attach($this->user)) + ->create(); + + actingAs($this->user); + + Filament::setTenant($tenant); + + $this->character = Character::factory()->create([ + 'user_id' => $this->user->getKey(), + 'tenant_id' => $tenant->getKey(), + ]); + $this->events = EventModel::factory()->count(5)->create([ + 'tenant_id' => $tenant->getKey(), + ]); + Season::factory() + ->recycle($tenant) + ->create([ + 'name' => 'Season 1', + 'started_at' => now()->subMonth(), + 'ended_at' => today(), + ]); +}); + +it('should render', function (): void { + livewire(Dashboard::class) + ->assertOk(); +}); + +it('should be able to see user experience/stats', function (): void { + $nextLevelXp = $this->character->percentageExperience + $this->character->experience; + + livewire(Dashboard::class) + ->assertOk() + ->assertSeeTextInOrder(['Level', $this->character->level]) + ->assertSeeTextInOrder(['Reputation', $this->character->reputation]) + ->assertSeeTextInOrder([$this->character->experience, '/', $nextLevelXp]) + ->assertSeeTextInOrder([(int) $this->character->experiencePercentageRemaining, '%', 'to next level']); +})->skip(); + +it('should be able to see events details', function (): void { + $this->events->each(function (EventModel $event): void { + livewire(Dashboard::class) + ->assertOk() + ->assertSeeText($event->title) + ->assertSeeText(Date::parse($event->starts_at)->format('d/m/Y H:i')) + ->assertSeeText(Date::parse($event->ends_at)->format('d/m/Y H:i')); + }); +}); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f539fc179..fee7675ba 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; final class AppServiceProvider extends ServiceProvider @@ -13,6 +14,6 @@ final class AppServiceProvider extends ServiceProvider */ public function register(): void { - // + Model::automaticallyEagerLoadRelationships(); } } diff --git a/app/Providers/Filament/UserPanelProvider.php b/app/Providers/Filament/UserPanelProvider.php index 4bc16727d..ac1d708bf 100644 --- a/app/Providers/Filament/UserPanelProvider.php +++ b/app/Providers/Filament/UserPanelProvider.php @@ -10,11 +10,11 @@ use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DispatchServingFilamentEvent; -use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; use He4rt\Tenant\Models\Tenant; +use He4rt\User\Filament\User\Pages\Dashboard; use He4rt\User\Filament\User\Pages\UserProfile; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; @@ -33,7 +33,7 @@ public function panel(Panel $panel): Panel ->path('app') ->login(Login::class) ->sidebarCollapsibleOnDesktop() - ->viteTheme('resources/css/filament/admin/theme.css') + ->viteTheme('resources/css/filament/user/theme.css') ->tenant(Tenant::class, 'slug', 'ownedTenants') ->userMenuItems([ Action::make('settings') diff --git a/composer.json b/composer.json index b628cea1c..7f093f430 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,8 @@ "license": "MIT", "require": { "php": "^8.3", - "filament/filament": "^4.2.0", - "filament/spatie-laravel-media-library-plugin": "^4.2.0", + "filament/filament": "^4.2.2", + "filament/spatie-laravel-media-library-plugin": "^4.2.2", "guzzlehttp/guzzle": "^7.10.0", "he4rt/authentication": ">=1", "he4rt/badge": ">=1", @@ -30,14 +30,14 @@ "he4rt/tenant": ">=1", "he4rt/user": ">=1", "internachi/modular": "^2.3.0", - "laravel/framework": "^12.38.0", + "laravel/framework": "^12.38.1", "laravel/sanctum": "^4.2.0", "laravel/tinker": "^2.10.1", "marvinlabs/laravel-discord-logger": "^1.4.3", "otavio-araujo/filament-smart-cep": "^4.0.1", "owenvoke/blade-fontawesome": "^3.0", "predis/predis": "^2.4.1", - "spatie/laravel-medialibrary": "^11.17.4" + "spatie/laravel-medialibrary": "^11.17.5" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.16", @@ -57,7 +57,7 @@ "pestphp/pest-plugin-laravel": "^4.0.0", "pestphp/pest-plugin-livewire": "^4.0.1", "phpstan/extension-installer": "^1.4.3", - "rector/rector": "2.2.7" + "rector/rector": "^2.2.8" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 801dbb60a..bdd850159 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "306cf317839e814f13a9622f2ca3cc40", + "content-hash": "928fdc506212d35d819c245b46199748", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -653,26 +653,27 @@ }, { "name": "composer/composer", - "version": "2.8.12", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "3e38919bc9a2c3c026f2151b5e56d04084ce8f0b" + "reference": "35cb6d47d03b0cae52dc12d686f941365b20f08b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/3e38919bc9a2c3c026f2151b5e56d04084ce8f0b", - "reference": "3e38919bc9a2c3c026f2151b5e56d04084ce8f0b", + "url": "https://api.github.com/repos/composer/composer/zipball/35cb6d47d03b0cae52dc12d686f941365b20f08b", + "reference": "35cb6d47d03b0cae52dc12d686f941365b20f08b", "shasum": "" }, "require": { "composer/ca-bundle": "^1.5", "composer/class-map-generator": "^1.4.0", "composer/metadata-minifier": "^1.0", - "composer/pcre": "^2.2 || ^3.2", + "composer/pcre": "^2.3 || ^3.3", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-json": "*", "justinrainbow/json-schema": "^6.5.1", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", @@ -680,13 +681,13 @@ "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", - "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10", - "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10", - "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", - "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10" + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.11.8", @@ -694,12 +695,13 @@ "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.0", "phpstan/phpstan-symfony": "^1.4.0", - "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3" + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" }, "suggest": { - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests" + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" }, "bin": [ "bin/composer" @@ -712,7 +714,7 @@ ] }, "branch-alias": { - "dev-main": "2.8-dev" + "dev-main": "2.9-dev" } }, "autoload": { @@ -747,7 +749,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.8.12" + "source": "https://github.com/composer/composer/tree/2.9.1" }, "funding": [ { @@ -759,7 +761,7 @@ "type": "github" } ], - "time": "2025-09-19T11:41:59+00:00" + "time": "2025-11-13T15:10:38+00:00" }, { "name": "composer/metadata-minifier", @@ -1612,16 +1614,16 @@ }, { "name": "filament/actions", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "63058c9123407559a3066f7877147a556753b0f3" + "reference": "c5f0eadb3a438f6f1dbe2f5478a73e85ecb9de45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/63058c9123407559a3066f7877147a556753b0f3", - "reference": "63058c9123407559a3066f7877147a556753b0f3", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/c5f0eadb3a438f6f1dbe2f5478a73e85ecb9de45", + "reference": "c5f0eadb3a438f6f1dbe2f5478a73e85ecb9de45", "shasum": "" }, "require": { @@ -1657,20 +1659,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:19:49+00:00" + "time": "2025-11-14T12:11:51+00:00" }, { "name": "filament/filament", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "0877be87a523a469544f4e9c866ac1ead0e206ab" + "reference": "49ad212a944f50f692d2719793ec6da4b4cb9769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/0877be87a523a469544f4e9c866ac1ead0e206ab", - "reference": "0877be87a523a469544f4e9c866ac1ead0e206ab", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/49ad212a944f50f692d2719793ec6da4b4cb9769", + "reference": "49ad212a944f50f692d2719793ec6da4b4cb9769", "shasum": "" }, "require": { @@ -1714,20 +1716,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:20:20+00:00" + "time": "2025-11-14T12:11:52+00:00" }, { "name": "filament/forms", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "aa46c3985d2c5d6f1a415618b7f095bacf812a95" + "reference": "0e3fb2da20208aff1170214e173b05ec07b78887" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/aa46c3985d2c5d6f1a415618b7f095bacf812a95", - "reference": "aa46c3985d2c5d6f1a415618b7f095bacf812a95", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/0e3fb2da20208aff1170214e173b05ec07b78887", + "reference": "0e3fb2da20208aff1170214e173b05ec07b78887", "shasum": "" }, "require": { @@ -1764,20 +1766,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:19:59+00:00" + "time": "2025-11-14T12:09:31+00:00" }, { "name": "filament/infolists", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", - "reference": "024a9e74dd21436d11c65bb4c4f283be09951794" + "reference": "d34039e7d7c07cba7a2afad3ca69f79a60efa5f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/infolists/zipball/024a9e74dd21436d11c65bb4c4f283be09951794", - "reference": "024a9e74dd21436d11c65bb4c4f283be09951794", + "url": "https://api.github.com/repos/filamentphp/infolists/zipball/d34039e7d7c07cba7a2afad3ca69f79a60efa5f4", + "reference": "d34039e7d7c07cba7a2afad3ca69f79a60efa5f4", "shasum": "" }, "require": { @@ -1809,20 +1811,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-10-21T10:01:54+00:00" + "time": "2025-11-14T12:08:27+00:00" }, { "name": "filament/notifications", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/notifications.git", - "reference": "3d7fc952a2610b78d4f95c6a6688674d4f1d4098" + "reference": "e14b887e035223028652447b2148806b622f4b0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/notifications/zipball/3d7fc952a2610b78d4f95c6a6688674d4f1d4098", - "reference": "3d7fc952a2610b78d4f95c6a6688674d4f1d4098", + "url": "https://api.github.com/repos/filamentphp/notifications/zipball/e14b887e035223028652447b2148806b622f4b0b", + "reference": "e14b887e035223028652447b2148806b622f4b0b", "shasum": "" }, "require": { @@ -1856,20 +1858,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:19:40+00:00" + "time": "2025-11-14T12:11:36+00:00" }, { "name": "filament/query-builder", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/query-builder.git", - "reference": "9304ff5fbe7480e7ed06269aa0168dfac716b8a2" + "reference": "0387b4192f8428d88effe19bf83fe0869096aabd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/query-builder/zipball/9304ff5fbe7480e7ed06269aa0168dfac716b8a2", - "reference": "9304ff5fbe7480e7ed06269aa0168dfac716b8a2", + "url": "https://api.github.com/repos/filamentphp/query-builder/zipball/0387b4192f8428d88effe19bf83fe0869096aabd", + "reference": "0387b4192f8428d88effe19bf83fe0869096aabd", "shasum": "" }, "require": { @@ -1902,20 +1904,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T16:56:49+00:00" + "time": "2025-11-14T12:09:18+00:00" }, { "name": "filament/schemas", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/schemas.git", - "reference": "157e01ad569225b304e5b28ea3bde8f8cc6d2192" + "reference": "5b8b8c6c769627e99edd66e6ed2c7e66d85b209b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/schemas/zipball/157e01ad569225b304e5b28ea3bde8f8cc6d2192", - "reference": "157e01ad569225b304e5b28ea3bde8f8cc6d2192", + "url": "https://api.github.com/repos/filamentphp/schemas/zipball/5b8b8c6c769627e99edd66e6ed2c7e66d85b209b", + "reference": "5b8b8c6c769627e99edd66e6ed2c7e66d85b209b", "shasum": "" }, "require": { @@ -1947,11 +1949,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:20:05+00:00" + "time": "2025-11-11T10:16:25+00:00" }, { "name": "filament/spatie-laravel-media-library-plugin", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git", @@ -1988,16 +1990,16 @@ }, { "name": "filament/support", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", - "reference": "06bafcfc604fe4acc5494cd5193909b51911aeeb" + "reference": "05d57ad1d227fe8f6afa3fbd3141e3d32d24f046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/06bafcfc604fe4acc5494cd5193909b51911aeeb", - "reference": "06bafcfc604fe4acc5494cd5193909b51911aeeb", + "url": "https://api.github.com/repos/filamentphp/support/zipball/05d57ad1d227fe8f6afa3fbd3141e3d32d24f046", + "reference": "05d57ad1d227fe8f6afa3fbd3141e3d32d24f046", "shasum": "" }, "require": { @@ -2042,20 +2044,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:20:06+00:00" + "time": "2025-11-14T12:11:01+00:00" }, { "name": "filament/tables", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "8ff5191cc7a33db20856600cd5b1f5f137847111" + "reference": "7d6e4213a886a5fe680a93da2318b29b7c01617e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/8ff5191cc7a33db20856600cd5b1f5f137847111", - "reference": "8ff5191cc7a33db20856600cd5b1f5f137847111", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/7d6e4213a886a5fe680a93da2318b29b7c01617e", + "reference": "7d6e4213a886a5fe680a93da2318b29b7c01617e", "shasum": "" }, "require": { @@ -2088,20 +2090,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-02T17:20:05+00:00" + "time": "2025-11-14T12:11:23+00:00" }, { "name": "filament/widgets", - "version": "v4.2.0", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", - "reference": "9ad4b80ae890768f38bc5e773fde7fc980375f22" + "reference": "2d9068f65e8981b468daab00672027b9c64b66dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/widgets/zipball/9ad4b80ae890768f38bc5e773fde7fc980375f22", - "reference": "9ad4b80ae890768f38bc5e773fde7fc980375f22", + "url": "https://api.github.com/repos/filamentphp/widgets/zipball/2d9068f65e8981b468daab00672027b9c64b66dd", + "reference": "2d9068f65e8981b468daab00672027b9c64b66dd", "shasum": "" }, "require": { @@ -2132,7 +2134,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-10-21T10:01:25+00:00" + "time": "2025-11-14T12:11:44+00:00" }, { "name": "fruitcake/php-cors", @@ -3353,16 +3355,16 @@ }, { "name": "kirschbaum-development/eloquent-power-joins", - "version": "4.2.9", + "version": "4.2.10", "source": { "type": "git", "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "32ec75ffee5f8f66e2c95e4030fa0b2454e51048" + "reference": "ccda351a75701f5b0a6f94586d9a40f1114302b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/32ec75ffee5f8f66e2c95e4030fa0b2454e51048", - "reference": "32ec75ffee5f8f66e2c95e4030fa0b2454e51048", + "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/ccda351a75701f5b0a6f94586d9a40f1114302b4", + "reference": "ccda351a75701f5b0a6f94586d9a40f1114302b4", "shasum": "" }, "require": { @@ -3410,22 +3412,22 @@ ], "support": { "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.9" + "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.10" }, - "time": "2025-10-25T11:39:00+00:00" + "time": "2025-11-13T14:57:49+00:00" }, { "name": "laravel/framework", - "version": "v12.38.0", + "version": "v12.38.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1c30f547a3117bac99dc62a0afe767810cb112fa" + "reference": "7f3012af6059f5f64a12930701cd8caed6cf7c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1c30f547a3117bac99dc62a0afe767810cb112fa", - "reference": "1c30f547a3117bac99dc62a0afe767810cb112fa", + "url": "https://api.github.com/repos/laravel/framework/zipball/7f3012af6059f5f64a12930701cd8caed6cf7c17", + "reference": "7f3012af6059f5f64a12930701cd8caed6cf7c17", "shasum": "" }, "require": { @@ -3631,7 +3633,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-12T16:51:30+00:00" + "time": "2025-11-13T02:12:47+00:00" }, { "name": "laravel/prompts", @@ -7386,16 +7388,16 @@ }, { "name": "spatie/laravel-medialibrary", - "version": "11.17.4", + "version": "11.17.5", "source": { "type": "git", "url": "https://github.com/spatie/laravel-medialibrary.git", - "reference": "b6f8d70862b02058904dd7674820ea2cc0cf65d2" + "reference": "eef29bbc701d786f2f6233ca4c40deb61282ac36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/b6f8d70862b02058904dd7674820ea2cc0cf65d2", - "reference": "b6f8d70862b02058904dd7674820ea2cc0cf65d2", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/eef29bbc701d786f2f6233ca4c40deb61282ac36", + "reference": "eef29bbc701d786f2f6233ca4c40deb61282ac36", "shasum": "" }, "require": { @@ -7480,7 +7482,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-medialibrary/issues", - "source": "https://github.com/spatie/laravel-medialibrary/tree/11.17.4" + "source": "https://github.com/spatie/laravel-medialibrary/tree/11.17.5" }, "funding": [ { @@ -7492,7 +7494,7 @@ "type": "github" } ], - "time": "2025-11-12T14:19:45+00:00" + "time": "2025-11-13T11:36:18+00:00" }, { "name": "spatie/laravel-package-tools", @@ -13869,21 +13871,21 @@ }, { "name": "rector/rector", - "version": "2.2.7", + "version": "2.2.8", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "022038537838bc8a4e526af86c2d6e38eaeff7ef" + "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/022038537838bc8a4e526af86c2d6e38eaeff7ef", - "reference": "022038537838bc8a4e526af86c2d6e38eaeff7ef", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/303aa811649ccd1d32e51e62d5c85949d01b5f1b", + "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.26" + "phpstan/phpstan": "^2.1.32" }, "conflict": { "rector/rector-doctrine": "*", @@ -13917,7 +13919,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.2.7" + "source": "https://github.com/rectorphp/rector/tree/2.2.8" }, "funding": [ { @@ -13925,7 +13927,7 @@ "type": "github" } ], - "time": "2025-10-29T15:46:12+00:00" + "time": "2025-11-12T18:38:00+00:00" }, { "name": "sebastian/cli-parser", @@ -15013,16 +15015,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/d74205c497bfbca49f34d4bc4c19c17e22db4ebb", + "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb", "shasum": "" }, "require": { @@ -15051,7 +15053,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.0" }, "funding": [ { @@ -15059,7 +15061,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-13T13:44:09+00:00" }, { "name": "webmozart/assert", @@ -15129,5 +15131,5 @@ "php": "^8.3" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 276f797a3..71d9bf3e0 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -5,9 +5,15 @@ namespace Database\Seeders; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use He4rt\Character\Models\Character; +use He4rt\Events\Models\EventModel; +use He4rt\Season\Models\Season; use He4rt\Tenant\Models\Tenant; +use He4rt\User\Models\Address; +use He4rt\User\Models\Information; use He4rt\User\Models\User; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Hash; final class DatabaseSeeder extends Seeder @@ -17,16 +23,18 @@ final class DatabaseSeeder extends Seeder */ public function run(): void { - // \App\Models\User::factory(10)->create(); + $user = User::factory() + ->create([ + 'username' => 'admin', + 'name' => 'admin', + 'email' => 'admin@admin.com', + 'password' => Hash::make('admin'), + ]); - $user = User::factory()->create([ - 'username' => 'admin', - 'name' => 'admin', - 'email' => 'admin@admin.com', - 'password' => Hash::make('admin'), - ]); + Information::factory()->recycle($user)->create(); + Address::factory()->recycle($user)->create(); - Tenant::factory() + $tenant = Tenant::factory() ->for($user, 'owner') ->afterCreating(fn (Tenant $tenant) => $tenant->members()->attach($user)) ->create([ @@ -34,5 +42,24 @@ public function run(): void 'slug' => 'he4rt', ]); + Character::factory() + ->recycle($user) + ->recycle($tenant) + ->createOne(); + + EventModel::factory()->count(10) + ->withStatus() + ->recycle($tenant) + ->create([ + 'end_at' => Date::tomorrow(), + ]); + + Season::factory() + ->recycle($tenant) + ->create([ + 'name' => 'Season 1', + 'started_at' => now()->subMonth(), + 'ended_at' => today(), + ]); } } diff --git a/pint.json b/pint.json index 34df4b964..50e3b984b 100644 --- a/pint.json +++ b/pint.json @@ -13,11 +13,8 @@ "lowercase_keywords": true, "lowercase_static_reference": true, "final_class": false, - "final_internal_class": { - "annotation_include": [], - "annotation_exclude": ["internal"] - }, - "final_public_method_for_abstract_class": true, + "final_internal_class": false, + "final_public_method_for_abstract_class": false, "fully_qualified_strict_types": true, "global_namespace_import": { "import_classes": true, diff --git a/resources/css/filament/user/theme.css b/resources/css/filament/user/theme.css new file mode 100644 index 000000000..7ff9e7138 --- /dev/null +++ b/resources/css/filament/user/theme.css @@ -0,0 +1,7 @@ +@import '../../../../vendor/filament/filament/resources/css/theme.css'; + +@source '../../../../app/Filament/**/*'; +@source '../../../../app/Filament/**/*'; +@source '../../../../resources/views/filament/**/*'; +@source '../../../../app-modules/**/src/Filament/**/*'; +@source '../../../../app-modules/**/resources/views/**/*'; diff --git a/vite.config.js b/vite.config.js index 7243a570f..36122af2b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -9,6 +9,7 @@ export default defineConfig({ 'resources/css/app.css', 'resources/js/app.js', 'resources/css/filament/admin/theme.css', + 'resources/css/filament/user/theme.css', 'app-modules/he4rt/resources/css/theme.css', ], refresh: true,