From 5b116a54c4dbd7c2035df13da2f98b76eb179984 Mon Sep 17 00:00:00 2001 From: philipp lang <philipp@aweos.de> Date: Sun, 13 Oct 2024 21:00:47 +0200 Subject: [PATCH] Add Dashboard --- app/Efz/EfzPendingBlock.php | 44 -------------- app/Invoice/MemberPaymentBlock.php | 37 ------------ app/Member/PsPendingBlock.php | 50 ---------------- app/Membership/TestersBlock.php | 43 -------------- .../Providers}/BaseServiceProvider.php | 11 +--- .../Components => app/View}/Page/Header.php | 2 +- app/View/Page/Layout.php | 57 +++++++++++++++++++ .../View}/Page/MenuEntry.php | 2 +- .../Components => app/View}/Page/Sidebar.php | 20 +++++-- .../Base/Components => app/View}/Ui/Box.php | 2 +- .../Components => app/View}/Ui/Sprite.php | 2 +- config/app.php | 2 +- modules/Base/Components/Page/Layout.php | 54 ------------------ modules/Invoice/MemberPaymentBlock.php | 28 +++++---- modules/Member/AgeGroupCountBlock.php | 44 ++++++++++---- modules/Member/TestersBlock.php | 39 +++++++------ modules/Prevention/EfzPendingBlock.php | 33 ++++++----- modules/Prevention/PsPendingBlock.php | 36 ++++++------ .../js/views/dashboard/AgeGroupCount.vue | 29 ---------- resources/js/views/dashboard/EfzPending.vue | 31 ---------- .../js/views/dashboard/MemberPayment.vue | 26 --------- resources/js/views/dashboard/PsPending.vue | 31 ---------- resources/js/views/dashboard/Testers.vue | 33 ----------- resources/livewire-js/app.js | 1 + tailwind.config.js | 12 ++-- .../Feature/Base}/PageLayoutTest.php | 4 +- .../Invoice/MemberPaymentBlockTest.php | 12 ++-- tests/Feature/Member/PsPendingBlockTest.php | 30 +++++----- .../Membership/AgeGroupCountBlockTest.php | 17 +++--- .../Membership/EfzPendingBlockTest.php | 16 +++--- tests/Feature/Membership/TestersBlockTest.php | 23 +++----- vite.config.js | 2 +- 32 files changed, 237 insertions(+), 536 deletions(-) delete mode 100644 app/Efz/EfzPendingBlock.php delete mode 100644 app/Invoice/MemberPaymentBlock.php delete mode 100644 app/Member/PsPendingBlock.php delete mode 100644 app/Membership/TestersBlock.php rename {modules/Base => app/Providers}/BaseServiceProvider.php (80%) rename {modules/Base/Components => app/View}/Page/Header.php (96%) create mode 100644 app/View/Page/Layout.php rename {modules/Base/Components => app/View}/Page/MenuEntry.php (94%) rename {modules/Base/Components => app/View}/Page/Sidebar.php (72%) rename {modules/Base/Components => app/View}/Ui/Box.php (96%) rename {modules/Base/Components => app/View}/Ui/Sprite.php (90%) delete mode 100644 modules/Base/Components/Page/Layout.php delete mode 100644 resources/js/views/dashboard/AgeGroupCount.vue delete mode 100644 resources/js/views/dashboard/EfzPending.vue delete mode 100644 resources/js/views/dashboard/MemberPayment.vue delete mode 100644 resources/js/views/dashboard/PsPending.vue delete mode 100644 resources/js/views/dashboard/Testers.vue create mode 100644 resources/livewire-js/app.js rename {modules/Base/tests => tests/Feature/Base}/PageLayoutTest.php (88%) diff --git a/app/Efz/EfzPendingBlock.php b/app/Efz/EfzPendingBlock.php deleted file mode 100644 index 8fa20b2f..00000000 --- a/app/Efz/EfzPendingBlock.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -namespace App\Efz; - -use App\Dashboard\Blocks\Block; -use App\Member\Member; -use Illuminate\Database\Eloquent\Builder; - -class EfzPendingBlock extends Block -{ - /** - * @return Builder<Member> - */ - public function query(): Builder - { - return Member::where(function ($query) { - return $query->where('efz', '<=', now()->subYears(5)->endOfYear()) - ->orWhereNull('efz'); - }) - ->whereCurrentGroup() - ->orderByRaw('lastname, firstname') - ->whereHas('memberships', fn ($builder) => $builder->isLeader()->active()); - } - - /** - * @return array{members: array<int, string>} - */ - public function data(): array - { - return [ - 'members' => $this->query()->get()->map(fn ($member) => $member->fullname)->toArray(), - ]; - } - - public function component(): string - { - return 'efz-pending'; - } - - public function title(): string - { - return 'Ausstehende Führungszeugnisse'; - } -} diff --git a/app/Invoice/MemberPaymentBlock.php b/app/Invoice/MemberPaymentBlock.php deleted file mode 100644 index 71fb4aa3..00000000 --- a/app/Invoice/MemberPaymentBlock.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -namespace App\Invoice; - -use App\Dashboard\Blocks\Block; -use App\Invoice\Models\InvoicePosition; -use App\Member\Member; - -class MemberPaymentBlock extends Block -{ - /** - * @return array<string, string|int> - */ - public function data(): array - { - $amount = InvoicePosition::whereHas('invoice', fn ($query) => $query->whereNeedsPayment()) - ->selectRaw('sum(price) AS price') - ->first(); - $members = Member::whereHasPendingPayment()->count(); - - return [ - 'members' => $members, - 'total_members' => Member::count(), - 'amount' => number_format((int) $amount->price / 100, 2, ',', '.') . ' €', - ]; - } - - public function component(): string - { - return 'member-payment'; - } - - public function title(): string - { - return 'Ausstehende Mitgliedsbeiträge'; - } -} diff --git a/app/Member/PsPendingBlock.php b/app/Member/PsPendingBlock.php deleted file mode 100644 index 5cc59c14..00000000 --- a/app/Member/PsPendingBlock.php +++ /dev/null @@ -1,50 +0,0 @@ -<?php - -namespace App\Member; - -use App\Dashboard\Blocks\Block; -use Illuminate\Database\Eloquent\Builder; - -class PsPendingBlock extends Block -{ - /** - * @return Builder<Member> - */ - public function query(): Builder - { - return Member::where(function ($query) { - $time = now()->subYears(5)->endOfYear(); - - return $query - ->orWhere(fn ($query) => $query->whereNull('ps_at')->whereNull('more_ps_at')) - ->orWhere(fn ($query) => $query->whereNull('ps_at')->where('more_ps_at', '<=', $time)) - ->orWhere(fn ($query) => $query->where('ps_at', '<=', $time)->whereNull('more_ps_at')) - ->orWhere(fn ($query) => $query->where('ps_at', '>=', $time)->where('more_ps_at', '<=', $time)); - }) - ->whereCurrentGroup() - ->orderByRaw('lastname, firstname') - ->whereHas('memberships', fn ($builder) => $builder->isLeader()->active()); - } - - /** - * @return array{members: array{fullname: string}} - */ - public function data(): array - { - return [ - 'members' => $this->query()->get()->map(fn ($member) => [ - 'fullname' => $member->fullname, - ])->toArray(), - ]; - } - - public function component(): string - { - return 'ps-pending'; - } - - public function title(): string - { - return 'Ausstehende Präventionsschulungen'; - } -} diff --git a/app/Membership/TestersBlock.php b/app/Membership/TestersBlock.php deleted file mode 100644 index 49cf7f25..00000000 --- a/app/Membership/TestersBlock.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php - -namespace App\Membership; - -use App\Dashboard\Blocks\Block; -use App\Member\Member; -use Illuminate\Database\Eloquent\Builder; - -class TestersBlock extends Block -{ - /** - * @return Builder<Member> - */ - public function query(): Builder - { - return Member::whereHas('memberships', fn ($q) => $q->isTrying()) - ->with('memberships', fn ($q) => $q->isTrying()); - } - - /** - * @return array{members: array<int, array{name: string, try_ends_at: string, try_ends_at_human: string}>} - */ - public function data(): array - { - return [ - 'members' => $this->query()->get()->map(fn ($member) => [ - 'name' => $member->fullname, - 'try_ends_at' => $member->memberships->first()->from->addWeeks(8)->format('d.m.Y'), - 'try_ends_at_human' => $member->memberships->first()->from->addWeeks(8)->diffForHumans(), - ])->toArray(), - ]; - } - - public function component(): string - { - return 'testers'; - } - - public function title(): string - { - return 'Endende Schhnupperzeiten'; - } -} diff --git a/modules/Base/BaseServiceProvider.php b/app/Providers/BaseServiceProvider.php similarity index 80% rename from modules/Base/BaseServiceProvider.php rename to app/Providers/BaseServiceProvider.php index 0cb597eb..ce9ec2c7 100644 --- a/modules/Base/BaseServiceProvider.php +++ b/app/Providers/BaseServiceProvider.php @@ -1,6 +1,6 @@ <?php -namespace Modules\Base; +namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Blade; @@ -21,9 +21,8 @@ class BaseServiceProvider extends ServiceProvider */ public function register(): void { - Blade::componentNamespace('Modules\\Base\\Components\\Ui', 'ui'); - Blade::componentNamespace('Modules\\Base\\Components\\Page', 'page'); - + Blade::componentNamespace('App\\View\\Ui', 'ui'); + Blade::componentNamespace('App\\View\\Page', 'page'); app(DashboardFactory::class)->register(AgeGroupCountBlock::class); app(DashboardFactory::class)->register(MemberPaymentBlock::class); @@ -31,8 +30,6 @@ class BaseServiceProvider extends ServiceProvider app(DashboardFactory::class)->register(EfzPendingBlock::class); app(DashboardFactory::class)->register(PsPendingBlock::class); - Livewire::component('page.sidebar', Sidebar::class); - ComponentAttributeBag::macro('mergeWhen', function ($condition, $key, $attributes) { /** @var ComponentAttributeBag */ $self = $this; @@ -45,7 +42,5 @@ class BaseServiceProvider extends ServiceProvider */ public function boot(): void { - Livewire::component('pagesidebar', Sidebar::class); - // } } diff --git a/modules/Base/Components/Page/Header.php b/app/View/Page/Header.php similarity index 96% rename from modules/Base/Components/Page/Header.php rename to app/View/Page/Header.php index 960223ad..40fb1924 100644 --- a/modules/Base/Components/Page/Header.php +++ b/app/View/Page/Header.php @@ -1,6 +1,6 @@ <?php -namespace Modules\Base\Components\Page; +namespace App\View\Page; use Illuminate\View\Component; diff --git a/app/View/Page/Layout.php b/app/View/Page/Layout.php new file mode 100644 index 00000000..1a4851a6 --- /dev/null +++ b/app/View/Page/Layout.php @@ -0,0 +1,57 @@ +<?php + +namespace App\View\Page; + +use Illuminate\View\Component; + +class Layout extends Component +{ + + public function __construct(public string $pageClass = '') + { + } + + public function userName(): string + { + return auth()->user()->firstname . ' ' . auth()->user()->lastname; + } + + public function userAvatar(): string + { + return auth()->user()->getGravatarUrl(); + } + + public function render() + { + return <<<'HTML' + <div class="grow flex flex-col"> + <div class="grow bg-gray-900 flex flex-col duration-300 navbar:ml-60"> + <x-page::header title="{{ session()->get('title') }}"> + <x-slot:beforeTitle> + <a href="#" class="mr-2 lg:hidden" wire:click.prevent="dispatch('toggle-sidebar')"> + <x-ui::sprite src="menu" class="text-gray-100 w-5 h-5"></x-ui::sprite> + </a> + </x-slot:beforeTitle> + <x-slot:toolbar> + {{ $toolbar ?? ''}} + </x-slot:toolbar> + <x-slot:right> + {{ $right ?? '' }} + <div class="flex items-center space-x-2"> + <div class="rounded-full overflow-hidden border-2 border-solid border-gray-300"> + <img src="{{ $userAvatar() }}" class="w-8 h-8 object-cover" /> + </div> + <div class="text-gray-300"">{{ $userName() }}</div> + </div> + </x-slot:right> + </x-page::header> + + <div class="grow flex flex-col {{$pageClass}}"> + {{ $slot }} + </div> + </div> + <livewire:page.sidebar :mobile="true" /> + </div> + HTML; + } +} diff --git a/modules/Base/Components/Page/MenuEntry.php b/app/View/Page/MenuEntry.php similarity index 94% rename from modules/Base/Components/Page/MenuEntry.php rename to app/View/Page/MenuEntry.php index 85ea81df..260fc395 100644 --- a/modules/Base/Components/Page/MenuEntry.php +++ b/app/View/Page/MenuEntry.php @@ -1,6 +1,6 @@ <?php -namespace Modules\Base\Components\Page; +namespace App\View\Page; use Illuminate\View\Component; diff --git a/modules/Base/Components/Page/Sidebar.php b/app/View/Page/Sidebar.php similarity index 72% rename from modules/Base/Components/Page/Sidebar.php rename to app/View/Page/Sidebar.php index deab75e4..e44cf987 100644 --- a/modules/Base/Components/Page/Sidebar.php +++ b/app/View/Page/Sidebar.php @@ -1,19 +1,25 @@ <?php -namespace Modules\Base\Components\Page; +namespace App\View\Page; use Livewire\Component; class Sidebar extends Component { - public $isShifted = false; + public $mobile = false; public function render() { return <<<'HTML' <div - class="fixed z-40 bg-gray-800 p-6 w-56 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between transition-all {{ $isShifted ? '-left-[14rem]' : 'left-0' }}" + class="fixed z-40 bg-gray-800 p-6 w-60 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between duration-300 + @if (!$mobile) left-[-16rem] navbar:left-0 @endif" + @if($mobile) + x-data="{ visible: false }" + x-on:toggle-sidebar.window="visible = true" + :class="{'left-[-16rem]' : !visible, 'left-0': visible}" + @endif > <div class="grid gap-2"> <x-page::menu-entry href="/" menu="dashboard" icon="loss">Dashboard</x-page::menu-entry> @@ -27,16 +33,18 @@ class Sidebar extends Component <x-page::menu-entry href="/maildispatcher" menu="maildispatcher" icon="at">Mail-Verteiler</x-page::menu-entry> </div> <div class="grid gap-2"> - <a href="#" class="flex w-full px-3 py-2 rounded-xl text-gray-300 bg-gray-700" @click.prevent="searchVisible = true"> + <a href="#" class="flex w-full px-3 py-2 rounded-xl text-gray-300 bg-gray-700" @click.prevent="dispatch('show-search')"> <x-ui::sprite class="text-white w-6 h-6 mr-4" src="search"></x-ui::sprite> <div class="">Suchen</div> </a> <x-page::menu-entry href="/setting" menu="setting" icon="setting">Einstellungen</x-page::menu-entry> <x-page::menu-entry href="/logout" menu="" icon="logout">Abmelden</x-page::menu-entry> </div> - <a v-if="menuStore.hideable" href="#" class="absolute right-0 top-0 mr-2 mt-2" @click.prevent="menuStore.hide()"> - <ui-sprite src="close" class="w-5 h-5 text-gray-300"></ui-sprite> + @if($mobile) + <a href="#" class="absolute right-0 top-0 mr-2 mt-2" @click.prevent="visible = false"> + <x-ui::sprite src="close" class="w-5 h-5 text-gray-300"></x-ui::sprite> </a> + @endif </div> HTML; } diff --git a/modules/Base/Components/Ui/Box.php b/app/View/Ui/Box.php similarity index 96% rename from modules/Base/Components/Ui/Box.php rename to app/View/Ui/Box.php index 747a2701..03e51808 100644 --- a/modules/Base/Components/Ui/Box.php +++ b/app/View/Ui/Box.php @@ -1,6 +1,6 @@ <?php -namespace Modules\Base\Components\Ui; +namespace App\View\Ui; use Illuminate\View\Component; diff --git a/modules/Base/Components/Ui/Sprite.php b/app/View/Ui/Sprite.php similarity index 90% rename from modules/Base/Components/Ui/Sprite.php rename to app/View/Ui/Sprite.php index 41cd9687..679eaf18 100644 --- a/modules/Base/Components/Ui/Sprite.php +++ b/app/View/Ui/Sprite.php @@ -1,6 +1,6 @@ <?php -namespace Modules\Base\Components\Ui; +namespace App\View\Ui; use Illuminate\View\Component; diff --git a/config/app.php b/config/app.php index f567ace3..6330d6d8 100644 --- a/config/app.php +++ b/config/app.php @@ -178,7 +178,7 @@ return [ // App\Dashboard\DashboardServiceProvider::class, App\Providers\PluginServiceProvider::class, Modules\Dashboard\DashboardServiceProvider::class, - Modules\Base\BaseServiceProvider::class, + App\Providers\BaseServiceProvider::class, ], /* diff --git a/modules/Base/Components/Page/Layout.php b/modules/Base/Components/Page/Layout.php deleted file mode 100644 index d0cb95d8..00000000 --- a/modules/Base/Components/Page/Layout.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -namespace Modules\Base\Components\Page; - -use Illuminate\View\Component; - -class Layout extends Component -{ - - public function __construct() - { - } - - public function userName(): string - { - return auth()->user()->firstname . ' ' . auth()->user()->lastname; - } - - public function userAvatar(): string - { - return auth()->user()->getGravatarUrl(); - } - - public function render() - { - return <<<'HTML' - <div class="grow bg-gray-900 flex flex-col transition-all ml-56" :class="{'ml-56': menuStore.visible, 'ml-0': !menuStore.visible}"> - <x-page::header title="{{ session()->get('title') }}"> - <x-slot:beforeTitle> - <a href="#" class="mr-2 lg:hidden" @click.prevent="menuStore.toggle()"> - <ui-sprite src="menu" class="text-gray-100 w-5 h-5"></ui-sprite> - </a> - </x-slot:beforeTitle> - <x-slot:toolbar> - {{ $toolbar ?? ''}} - </x-slot:toolbar> - <x-slot:right> - {{ $right ?? '' }} - <div class="flex items-center space-x-2"> - <div class="rounded-full overflow-hidden border-2 border-solid border-gray-300"> - <img src="{{ $userAvatar() }}" class="w-8 h-8 object-cover" /> - </div> - <div class="text-gray-300"">{{ $userName() }}</div> - </div> - </x-slot:right> - </x-page::header> - - <div :class="pageClass" class="grow flex flex-col"> - {{ $slot }} - </div> - </div> - HTML; - } -} diff --git a/modules/Invoice/MemberPaymentBlock.php b/modules/Invoice/MemberPaymentBlock.php index ecd0c7db..0d435ed4 100644 --- a/modules/Invoice/MemberPaymentBlock.php +++ b/modules/Invoice/MemberPaymentBlock.php @@ -8,21 +8,20 @@ use App\Member\Member; class MemberPaymentBlock extends Block { - /** - * @return array<string, string|int> - */ - public function data(): array + + public string $amount = ''; + public int $members = 0; + public int $totalMembers = 0; + + public function mount(): void { $amount = InvoicePosition::whereHas('invoice', fn ($query) => $query->whereNeedsPayment()) ->selectRaw('sum(price) AS price') ->first(); - $members = Member::whereHasPendingPayment()->count(); - return [ - 'members' => $members, - 'total_members' => Member::count(), - 'amount' => number_format((int) $amount->price / 100, 2, ',', '.') . ' €', - ]; + $this->amount = number_format((int) $amount->price / 100, 2, ',', '.') . ' €'; + $this->members = Member::whereHasPendingPayment()->count(); + $this->totalMembers = Member::count(); } public function title(): string @@ -32,6 +31,13 @@ class MemberPaymentBlock extends Block public function render(): string { - return '<div></div>'; + return <<<'HTML' + <div> + <div class="text-gray-100"> + <span class="text-xl mr-1 font-semibold">{{$amount}}</span> + <span class="text-sm">von {{$members}} / {{$totalMembers}} Mitgliedern</span> + </div> + </div> + HTML; } } diff --git a/modules/Member/AgeGroupCountBlock.php b/modules/Member/AgeGroupCountBlock.php index 48d88d79..20ed9eb1 100644 --- a/modules/Member/AgeGroupCountBlock.php +++ b/modules/Member/AgeGroupCountBlock.php @@ -8,10 +8,13 @@ use Illuminate\Database\Eloquent\Builder; class AgeGroupCountBlock extends Block { + + public $groups; + /** * @return Builder<Membership> */ - public function memberQuery(): Builder + protected function memberQuery(): Builder { return Membership::select('subactivities.slug', 'subactivities.name') ->selectRaw('COUNT(member_id) AS count') @@ -27,7 +30,7 @@ class AgeGroupCountBlock extends Block /** * @return Builder<Membership> */ - public function leaderQuery(): Builder + protected function leaderQuery(): Builder { return Membership::selectRaw('"leiter" AS slug, "Leiter" AS name, COUNT(member_id) AS count') ->join('activities', 'memberships.activity_id', 'activities.id') @@ -36,13 +39,11 @@ class AgeGroupCountBlock extends Block ->isLeader(); } - protected function data(): array + public function mount(): void { - return [ - 'groups' => [ - ...$this->memberQuery()->get()->toArray(), - ...$this->leaderQuery()->get()->toArray(), - ], + $this->groups = [ + ...$this->memberQuery()->get(), + ...$this->leaderQuery()->get(), ]; } @@ -51,11 +52,30 @@ class AgeGroupCountBlock extends Block return 'Gruppierungs-Verteilung'; } + public function groupColor(string $slug): string + { + return data_get([ + 'biber' => 'text-biber', + 'woelfling' => 'text-woelfling', + 'jungpfadfinder' => 'text-jungpfadfinder', + 'pfadfinder' => 'text-pfadfinder', + 'rover' => 'text-rover', + 'leiter' => 'text-leiter', + ], $slug); + } + public function render(): string { - return '<div> - - lalala - </div>'; + return <<<'HTML' + <div> + @foreach($groups as $group) + <div class="flex mt-2 items-center leading-none text-gray-100"> + <x-ui::sprite class="w-4 h-4 mr-2 {{ $this->groupColor($group->slug) }}" src="lilie"></x-ui::sprite> + <span class="grow">{{$group->name}}</span> + <span>{{$group->count}}</span> + </div> + @endforeach + </div> + HTML; } } diff --git a/modules/Member/TestersBlock.php b/modules/Member/TestersBlock.php index e8fbd71e..0e74a6f4 100644 --- a/modules/Member/TestersBlock.php +++ b/modules/Member/TestersBlock.php @@ -5,30 +5,23 @@ namespace Modules\Member; use Modules\Dashboard\Block; use App\Member\Member; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; class TestersBlock extends Block { - /** - * @return Builder<Member> - */ - public function query(): Builder - { - return Member::whereHas('memberships', fn ($q) => $q->isTrying()) - ->with('memberships', fn ($q) => $q->isTrying()); - } + + private $months = 8; /** - * @return array{members: array<int, array{name: string, try_ends_at: string, try_ends_at_human: string}>} + * @var Collection<Member> */ - public function data(): array + public Collection $members; + + public function mount(): void { - return [ - 'members' => $this->query()->get()->map(fn ($member) => [ - 'name' => $member->fullname, - 'try_ends_at' => $member->memberships->first()->from->addWeeks(8)->format('d.m.Y'), - 'try_ends_at_human' => $member->memberships->first()->from->addWeeks(8)->diffForHumans(), - ])->toArray(), - ]; + $this->members = Member::whereHas('memberships', fn ($q) => $q->isTrying()) + ->with('memberships', fn ($q) => $q->isTrying()) + ->get(); } public function title(): string @@ -38,6 +31,16 @@ class TestersBlock extends Block public function render(): string { - return '<div></div>'; + return <<<'HTML' + <div> + @foreach($members as $member) + <div class="flex mt-2 items-center leading-none text-gray-100"> + <span class="grow">{{ $member->fullname }}</span> + <span class="mr-2 text-sm tex-gray-600">{{ $member->memberships->first()->from->addWeeks($this->months)->format('d.m.Y') }}</span> + <span class="text-xs tex-gray-600">{{ $member->memberships->first()->from->addWeeks($this->months)->diffForHumans() }}</span> + </div> + @endforeach + </div> + HTML; } } diff --git a/modules/Prevention/EfzPendingBlock.php b/modules/Prevention/EfzPendingBlock.php index 473ae64d..38c2e6cc 100644 --- a/modules/Prevention/EfzPendingBlock.php +++ b/modules/Prevention/EfzPendingBlock.php @@ -5,31 +5,26 @@ namespace Modules\Prevention; use Modules\Dashboard\Block; use App\Member\Member; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; class EfzPendingBlock extends Block { + /** - * @return Builder<Member> + * @var Collection<Member> */ - public function query(): Builder + public Collection $members; + + public function mount(): void { - return Member::where(function ($query) { + $this->members = Member::where(function ($query) { return $query->where('efz', '<=', now()->subYears(5)->endOfYear()) ->orWhereNull('efz'); }) ->whereCurrentGroup() ->orderByRaw('lastname, firstname') - ->whereHas('memberships', fn ($builder) => $builder->isLeader()->active()); - } - - /** - * @return array{members: array<int, string>} - */ - public function data(): array - { - return [ - 'members' => $this->query()->get()->map(fn ($member) => $member->fullname)->toArray(), - ]; + ->whereHas('memberships', fn ($builder) => $builder->isLeader()->active()) + ->get(); } public function title(): string @@ -39,6 +34,14 @@ class EfzPendingBlock extends Block public function render(): string { - return '<div></div>'; + return <<<'HTML' + <div> + @foreach($members as $member) + <div class="flex mt-2 items-center leading-none text-gray-100"> + <span class="grow">{{$member->fullname}}</span> + </div> + @endforeach + </div> + HTML; } } diff --git a/modules/Prevention/PsPendingBlock.php b/modules/Prevention/PsPendingBlock.php index e6bef7e7..c4825149 100644 --- a/modules/Prevention/PsPendingBlock.php +++ b/modules/Prevention/PsPendingBlock.php @@ -2,17 +2,22 @@ namespace Modules\Prevention; +use App\Member\Member; use Modules\Dashboard\Block; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; class PsPendingBlock extends Block { + /** - * @return Builder<Member> + * @var Collection<Member> */ - public function query(): Builder + public Collection $members; + + public function mount(): void { - return Member::where(function ($query) { + $this->members = Member::where(function ($query) { $time = now()->subYears(5)->endOfYear(); return $query @@ -23,19 +28,8 @@ class PsPendingBlock extends Block }) ->whereCurrentGroup() ->orderByRaw('lastname, firstname') - ->whereHas('memberships', fn ($builder) => $builder->isLeader()->active()); - } - - /** - * @return array{members: array{fullname: string}} - */ - public function data(): array - { - return [ - 'members' => $this->query()->get()->map(fn ($member) => [ - 'fullname' => $member->fullname, - ])->toArray(), - ]; + ->whereHas('memberships', fn ($builder) => $builder->isLeader()->active()) + ->get(); } public function title(): string @@ -45,6 +39,14 @@ class PsPendingBlock extends Block public function render(): string { - return '<div></div>'; + return <<<'HTML' + <div> + @foreach ($members as $member) + <div class="flex mt-2 items-center leading-none text-gray-100"> + <span class="grow">{{ $member->fullname }}</span> + </div> + @endforeach + </div> + HTML; } } diff --git a/resources/js/views/dashboard/AgeGroupCount.vue b/resources/js/views/dashboard/AgeGroupCount.vue deleted file mode 100644 index 0ae396a9..00000000 --- a/resources/js/views/dashboard/AgeGroupCount.vue +++ /dev/null @@ -1,29 +0,0 @@ -<template> - <div> - <div v-for="(group, index) in inner.groups" :key="index" class="flex mt-2 items-center leading-none text-gray-100"> - <ui-sprite class="w-4 h-4 mr-2" src="lilie" :class="`text-${group.slug}`"></ui-sprite> - <span v-text="group.name" class="grow"></span> - <span v-text="group.count"></span> - </div> - </div> -</template> - -<script> -export default { - data: function () { - return { - inner: { - groups: [], - }, - }; - }, - - props: { - data: {}, - }, - - created() { - this.inner = this.data; - }, -}; -</script> diff --git a/resources/js/views/dashboard/EfzPending.vue b/resources/js/views/dashboard/EfzPending.vue deleted file mode 100644 index d673b322..00000000 --- a/resources/js/views/dashboard/EfzPending.vue +++ /dev/null @@ -1,31 +0,0 @@ -<template> - <div> - <div - v-for="(member, index) in inner.members" - :key="index" - class="flex mt-2 items-center leading-none text-gray-100" - > - <span class="grow" v-text="`${member}`"></span> - </div> - </div> -</template> - -<script> -export default { - data: function () { - return { - inner: { - members: [], - }, - }; - }, - - props: { - data: {}, - }, - - created() { - this.inner = this.data; - }, -}; -</script> diff --git a/resources/js/views/dashboard/MemberPayment.vue b/resources/js/views/dashboard/MemberPayment.vue deleted file mode 100644 index 48bda0c0..00000000 --- a/resources/js/views/dashboard/MemberPayment.vue +++ /dev/null @@ -1,26 +0,0 @@ -<template> - <div> - <div class="text-gray-100"> - <span class="text-xl mr-1 font-semibold" v-text="inner.amount"></span> - <span class="text-sm" v-text="`von ${inner.members} / ${inner.total_members} Mitgliedern`"></span> - </div> - </div> -</template> - -<script> -export default { - data: function () { - return { - inner: {}, - }; - }, - - props: { - data: {}, - }, - - created() { - this.inner = this.data; - }, -}; -</script> diff --git a/resources/js/views/dashboard/PsPending.vue b/resources/js/views/dashboard/PsPending.vue deleted file mode 100644 index c2832b48..00000000 --- a/resources/js/views/dashboard/PsPending.vue +++ /dev/null @@ -1,31 +0,0 @@ -<template> - <div> - <div - v-for="(member, index) in inner.members" - :key="index" - class="flex mt-2 items-center leading-none text-gray-100" - > - <span class="grow" v-text="`${member.fullname}`"></span> - </div> - </div> -</template> - -<script> -export default { - data: function () { - return { - inner: { - members: [], - }, - }; - }, - - props: { - data: {}, - }, - - created() { - this.inner = this.data; - }, -}; -</script> diff --git a/resources/js/views/dashboard/Testers.vue b/resources/js/views/dashboard/Testers.vue deleted file mode 100644 index 69334c7a..00000000 --- a/resources/js/views/dashboard/Testers.vue +++ /dev/null @@ -1,33 +0,0 @@ -<template> - <div> - <div - v-for="(member, index) in inner.members" - :key="index" - class="flex mt-2 items-center leading-none text-gray-100" - > - <span class="grow" v-text="`${member.name}`"></span> - <span class="mr-2 text-sm tex-gray-600" v-text="`${member.try_ends_at}`"></span> - <span class="text-xs tex-gray-600" v-text="`${member.try_ends_at_human}`"></span> - </div> - </div> -</template> - -<script> -export default { - data: function () { - return { - inner: { - members: [], - }, - }; - }, - - props: { - data: {}, - }, - - created() { - this.inner = this.data; - }, -}; -</script> diff --git a/resources/livewire-js/app.js b/resources/livewire-js/app.js new file mode 100644 index 00000000..f65cc9bd --- /dev/null +++ b/resources/livewire-js/app.js @@ -0,0 +1 @@ +import '../css/app.css'; diff --git a/tailwind.config.js b/tailwind.config.js index 36d36a5d..b38b5aca 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,14 +1,7 @@ const {colors} = require('tailwindcss/defaultTheme'); module.exports = { - content: [ - 'resources/js/views/**/*.vue', - 'resources/js/components/**/*.vue', - 'resources/js/layouts/**/*.vue', - 'resources/views/**/*.blade.php', - 'resources/js/composables/**/*.js', - 'packages/medialibrary-helper/**/*.vue', - ], + content: ['app/View/**/*.php', 'resources/views/**/*.blade.php', 'modules/**/*.php'], theme: { extend: { colors: { @@ -30,6 +23,9 @@ module.exports = { 900: 'hsl(181, 94%, 10%)', }, }, + screens: { + navbar: '1024px', + }, }, }, diff --git a/modules/Base/tests/PageLayoutTest.php b/tests/Feature/Base/PageLayoutTest.php similarity index 88% rename from modules/Base/tests/PageLayoutTest.php rename to tests/Feature/Base/PageLayoutTest.php index 6f2be204..c8dced7e 100644 --- a/modules/Base/tests/PageLayoutTest.php +++ b/tests/Feature/Base/PageLayoutTest.php @@ -1,14 +1,12 @@ <?php -namespace Modules\Dashboard\Tests; +namespace Tests\Feature\Base; use Illuminate\Foundation\Testing\DatabaseTransactions; use Livewire\Component; use Livewire\Livewire; -use Tests\TestCase; uses(DatabaseTransactions::class); -uses(TestCase::class); it('renders successfully', function () { $this->login()->loginNami(); diff --git a/tests/Feature/Invoice/MemberPaymentBlockTest.php b/tests/Feature/Invoice/MemberPaymentBlockTest.php index dbdc6dfd..0808e214 100644 --- a/tests/Feature/Invoice/MemberPaymentBlockTest.php +++ b/tests/Feature/Invoice/MemberPaymentBlockTest.php @@ -8,6 +8,8 @@ use App\Invoice\Models\Invoice; use App\Invoice\Models\InvoicePosition; use App\Member\Member; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Invoice\MemberPaymentBlock as InvoiceMemberPaymentBlock; use Tests\TestCase; class MemberPaymentBlockTest extends TestCase @@ -27,12 +29,8 @@ class MemberPaymentBlockTest extends TestCase Invoice::factory()->has(InvoicePosition::factory()->price(600)->for($member), 'positions')->status(InvoiceStatus::NEW)->create(); Invoice::factory()->has(InvoicePosition::factory()->price(1000)->for($member), 'positions')->status(InvoiceStatus::PAID)->create(); - $data = app(MemberPaymentBlock::class)->render()['data']; - - $this->assertEquals([ - 'amount' => '51,00 €', - 'members' => 1, - 'total_members' => 2, - ], $data); + Livewire::test(InvoiceMemberPaymentBlock::class) + ->assertSee('1 / 2') + ->assertSee('51,00 €'); } } diff --git a/tests/Feature/Member/PsPendingBlockTest.php b/tests/Feature/Member/PsPendingBlockTest.php index aef909d6..07ae4f74 100644 --- a/tests/Feature/Member/PsPendingBlockTest.php +++ b/tests/Feature/Member/PsPendingBlockTest.php @@ -5,8 +5,9 @@ namespace Tests\Feature\Member; use App\Group; use App\Member\Member; use App\Member\Membership; -use App\Member\PsPendingBlock; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Prevention\PsPendingBlock; use Tests\TestCase; class PsPendingBlockTest extends TestCase @@ -47,7 +48,7 @@ class PsPendingBlockTest extends TestCase ->defaults() ->for($group) ->has(Membership::factory()->in('€ LeiterIn', 5, 'Wölfling', 8)->ended()) - ->create(['firstname' => 'Nora', 'lastname' => 'Doe', 'more_ps_at' => now()->subYears(5)]); + ->create(['firstname' => 'Lisa', 'lastname' => 'Doe', 'more_ps_at' => now()->subYears(5)]); $invalidPsButValidMorePs = Member::factory() ->defaults() ->for($group) @@ -59,15 +60,15 @@ class PsPendingBlockTest extends TestCase ->has(Membership::factory()->in('€ Mitglied', 5, 'Wölfling', 8)) ->create(['firstname' => 'Mae', 'lastname' => 'Doe']); - $data = app(PsPendingBlock::class)->render()['data']; - - $this->assertEquals([ - 'members' => [ - ['fullname' => 'Jane Doe'], - ['fullname' => 'Mike Doe'], - ['fullname' => 'Nora Doe'], - ], - ], $data); + Livewire::test(PsPendingBlock::class) + ->assertSee('Jane Doe') + ->assertDontSee('Max Doe') + ->assertDontSee('Joe Doe') + ->assertSee('Mike Doe') + ->assertSee('Nora Doe') + ->assertDontSee('Lisa Doe') + ->assertDontSee('Hey Doe') + ->assertDontSee('Mae Doe'); } public function testItExcludesForeignGroups(): void @@ -80,10 +81,9 @@ class PsPendingBlockTest extends TestCase ->defaults() ->for($otherGroup) ->has(Membership::factory()->in('€ LeiterIn', 5, 'Wölfling', 8)) - ->create(); + ->create(['lastname' => 'Doe']); - $data = app(PsPendingBlock::class)->render()['data']; - - $this->assertCount(0, $data['members']); + Livewire::test(PsPendingBlock::class) + ->assertDontSee('Doe'); } } diff --git a/tests/Feature/Membership/AgeGroupCountBlockTest.php b/tests/Feature/Membership/AgeGroupCountBlockTest.php index 45ba040f..834be7fd 100644 --- a/tests/Feature/Membership/AgeGroupCountBlockTest.php +++ b/tests/Feature/Membership/AgeGroupCountBlockTest.php @@ -4,8 +4,9 @@ namespace Tests\Feature\Membership; use App\Member\Member; use App\Member\Membership; -use App\Membership\AgeGroupCountBlock; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Member\AgeGroupCountBlock; use Tests\TestCase; class AgeGroupCountBlockTest extends TestCase @@ -33,14 +34,10 @@ class AgeGroupCountBlockTest extends TestCase ->defaults() ->create(); - $data = app(AgeGroupCountBlock::class)->render()['data']; - - $this->assertEquals([ - 'groups' => [ - ['slug' => 'biber', 'name' => 'Biber', 'count' => 3], - ['slug' => 'woelfling', 'name' => 'Wölfling', 'count' => 4], - ['slug' => 'leiter', 'name' => 'Leiter', 'count' => 4], - ], - ], $data); + Livewire::test(AgeGroupCountBlock::class) + ->assertSee('Biber') + ->assertSee('Wölfling') + ->assertSee('Leiter') + ->assertSeeInOrder([3, 4, 4]); } } diff --git a/tests/Feature/Membership/EfzPendingBlockTest.php b/tests/Feature/Membership/EfzPendingBlockTest.php index 86c34116..8892ba62 100644 --- a/tests/Feature/Membership/EfzPendingBlockTest.php +++ b/tests/Feature/Membership/EfzPendingBlockTest.php @@ -7,6 +7,8 @@ use App\Group; use App\Member\Member; use App\Member\Membership; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Prevention\EfzPendingBlock as PreventionEfzPendingBlock; use Tests\TestCase; class EfzPendingBlockTest extends TestCase @@ -54,11 +56,10 @@ class EfzPendingBlockTest extends TestCase ->for($group) ->create(['firstname' => 'Doe', 'lastname' => 'Muster', 'efz' => now()->subYears(5)]); - $data = app(EfzPendingBlock::class)->render()['data']; - - $this->assertEquals([ - 'members' => ['Joe Muster', 'Mae Muster', 'Moa Muster'], - ], $data); + Livewire::test(PreventionEfzPendingBlock::class) + ->assertSee('Joe Muster') + ->assertSee('Mae Muster') + ->assertSee('Moa Muster'); } public function testItExcludesForeignGroups(): void @@ -73,8 +74,7 @@ class EfzPendingBlockTest extends TestCase ->for($otherGroup) ->create(['firstname' => 'Joe', 'lastname' => 'Muster', 'efz' => now()->subYears(5)->endOfYear()]); - $data = app(EfzPendingBlock::class)->render()['data']; - - $this->assertCount(0, $data['members']); + Livewire::test(PreventionEfzPendingBlock::class) + ->assertDontSee('Joe'); } } diff --git a/tests/Feature/Membership/TestersBlockTest.php b/tests/Feature/Membership/TestersBlockTest.php index 6ec66e1b..a1363dcd 100644 --- a/tests/Feature/Membership/TestersBlockTest.php +++ b/tests/Feature/Membership/TestersBlockTest.php @@ -4,8 +4,9 @@ namespace Tests\Feature\Membership; use App\Member\Member; use App\Member\Membership; -use App\Membership\TestersBlock; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Member\TestersBlock; use Tests\TestCase; class TestersBlockTest extends TestCase @@ -20,21 +21,15 @@ class TestersBlockTest extends TestCase ->defaults() ->has(Membership::factory()->in('Schnuppermitgliedschaft', 7, 'Wölfling', 8)->state(['from' => now()->subMonths(10)])) ->create(['firstname' => 'Max', 'lastname' => 'Muster']); - $inactiveMember = Member::factory() + Member::factory() ->defaults() ->has(Membership::factory()->ended()->in('Schnuppermitgliedschaft', 7, 'Wölfling', 8)->state(['from' => now()->subMonths(10)])) - ->create(['firstname' => 'Max', 'lastname' => 'Muster']); + ->create(['firstname' => 'Jane', 'lastname' => 'Muster']); - $data = app(TestersBlock::class)->render()['data']; - - $this->assertEquals([ - 'members' => [ - [ - 'name' => 'Max Muster', - 'try_ends_at' => now()->subMonths(10)->addWeeks(8)->format('d.m.Y'), - 'try_ends_at_human' => now()->subMonths(10)->addWeeks(8)->diffForHumans(), - ], - ], - ], $data); + Livewire::test(TestersBlock::class) + ->assertSee('Max Muster') + ->assertSee(now()->subMonths(10)->addWeeks(8)->format('d.m.Y')) + ->assertSee(now()->subMonths(10)->addWeeks(8)->diffForHumans()) + ->assertDontSee('Jane'); } } diff --git a/vite.config.js b/vite.config.js index 17dc260c..b77ca7e3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -5,7 +5,7 @@ import path from 'path'; export default defineConfig({ plugins: [ - laravel(['resources/js/app.js', 'resources/livewire-js/app.js']), + laravel(['resources/livewire-js/app.js']), vue({ template: { transformAssetUrls: {