Compare commits
4 Commits
653d59fc18
...
580238d665
Author | SHA1 | Date |
---|---|---|
|
580238d665 | |
|
6d85a7c37c | |
|
f2c30b013b | |
|
acaccbb106 |
|
@ -40,3 +40,4 @@ Homestead.json
|
|||
/public/sprite.svg
|
||||
/.php-cs-fixer.cache
|
||||
/groups.sql
|
||||
/.phpunit.cache/
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Base\Components\Page\Sidebar;
|
||||
use Modules\Dashboard\DashboardFactory;
|
||||
use Modules\Invoice\MemberPaymentBlock;
|
||||
use Modules\Member\AgeGroupCountBlock;
|
||||
use Modules\Member\TestersBlock;
|
||||
use Modules\Prevention\EfzPendingBlock;
|
||||
use Modules\Prevention\PsPendingBlock;
|
||||
|
||||
class BaseServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Blade::componentNamespace('App\\View\\Ui', 'ui');
|
||||
Blade::componentNamespace('App\\View\\Page', 'page');
|
||||
|
||||
app(DashboardFactory::class)->register(AgeGroupCountBlock::class);
|
||||
app(DashboardFactory::class)->register(MemberPaymentBlock::class);
|
||||
app(DashboardFactory::class)->register(TestersBlock::class);
|
||||
app(DashboardFactory::class)->register(EfzPendingBlock::class);
|
||||
app(DashboardFactory::class)->register(PsPendingBlock::class);
|
||||
|
||||
ComponentAttributeBag::macro('mergeWhen', function ($condition, $key, $attributes) {
|
||||
/** @var ComponentAttributeBag */
|
||||
$self = $this;
|
||||
return $condition ? $self->merge([$key => $attributes]) : $self;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Header extends Component
|
||||
{
|
||||
|
||||
public function __construct(public string $title, public bool $closeable = false)
|
||||
{
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div class="h-16 px-6 flex items-center justify-between border-b border-solid border-gray-600 group-[.is-bright]:border-gray-500">
|
||||
<div class="flex items-center space-x-2">
|
||||
{{ $beforeTitle ?? ''}}
|
||||
<span class="text-sm md:text-xl font-semibold leading-none text-white">{{ $title }}</span>
|
||||
{{ $toolbar ?? '' }}
|
||||
</div>
|
||||
<div class="flex items-center space-x-4 ml-2">
|
||||
@if ($closeable)
|
||||
<a href="#" class="btn label btn-primary-light icon" wire:click="close">
|
||||
<ui-sprite class="w-3 h-3" src="close"></ui-sprite>
|
||||
</a>
|
||||
@endif
|
||||
{{ $right ?? '' }}
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class MenuEntry extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $href,
|
||||
public string $menu,
|
||||
public string $icon,
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<a class="flex text-white py-2 px-3 rounded-lg hover:bg-gray-600 {{ $menu === session('menu') ? 'bg-gray-700' : '' }}" href="{{$href}}">
|
||||
<x-ui::sprite class="text-white w-6 h-6 mr-4" src="{{$icon}}"></x-ui::sprite>
|
||||
<span class="font-semibold">
|
||||
{{ $slot }}
|
||||
</span>
|
||||
</a>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Sidebar extends Component
|
||||
{
|
||||
|
||||
public $mobile = false;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div
|
||||
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>
|
||||
<x-page::menu-entry href="/member" menu="member" icon="user">Mitglieder</x-page::menu-entry>
|
||||
<x-page::menu-entry v-show="hasModule('bill')" href="/subscription" menu="subscription" icon="money">Beiträge</x-page::menu-entry>
|
||||
<x-page::menu-entry v-show="hasModule('bill')" href="/invoice" menu="invoice" icon="moneypaper">Rechnungen</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/contribution" menu="contribution" icon="contribution">Zuschüsse</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/activity" menu="activity" icon="activity">Tätigkeiten</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/group" menu="group" icon="group">Gruppierungen</x-page::menu-entry>
|
||||
<x-page::menu-entry v-if="hasModule('event')" href="/form" menu="form" icon="event">Veranstaltungen</x-page::menu-entry>
|
||||
<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="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>
|
||||
@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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Box extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $containerClass = '',
|
||||
public bool $second = false,
|
||||
public string $title = '',
|
||||
public string $inTitle = '',
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<section {!! $attributes
|
||||
->mergeWhen($second, 'class', 'bg-gray-700 group-[.is-popup]:bg-zinc-600')
|
||||
->mergeWhen(!$second, 'class', 'bg-gray-800 group-[.is-popup]:bg-zinc-700')
|
||||
->mergeWhen(true, 'class', 'p-3 rounded-lg flex flex-col')
|
||||
!!}>
|
||||
<div class="flex items-center">
|
||||
@if($title)
|
||||
<div class="col-span-full font-semibold text-gray-300 group-[.is-popup]:text-zinc-300">{{$title}}</div>
|
||||
@endif
|
||||
{{$inTitle}}
|
||||
</div>
|
||||
<main class="{{ $title ? 'mt-2' : '' }} {{ $containerClass }}">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</section>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Sprite extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $src = '',
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<svg {{ $attributes->merge(['class' => 'fill-current']) }}"><use xlink:href="/sprite.svg#{{$src}}" /></svg>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -114,6 +114,7 @@
|
|||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Modules\\Dashboard\\Tests\\": "modules/dashboard/tests/",
|
||||
"Modules\\Base\\Tests\\": "modules/base/tests/",
|
||||
"Tests\\": "tests/",
|
||||
"Zoomyboy\\LaravelNami\\Tests\\": "packages/laravel-nami/tests/"
|
||||
}
|
||||
|
|
|
@ -168,7 +168,6 @@ return [
|
|||
*/
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
|
@ -176,8 +175,10 @@ return [
|
|||
App\Tex\TexServiceProvider::class,
|
||||
App\Dav\ServiceProvider::class,
|
||||
App\Setting\SettingServiceProvider::class,
|
||||
App\Dashboard\DashboardServiceProvider::class,
|
||||
// App\Dashboard\DashboardServiceProvider::class,
|
||||
App\Providers\PluginServiceProvider::class,
|
||||
Modules\Dashboard\DashboardServiceProvider::class,
|
||||
App\Providers\BaseServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Class Namespace
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the root class namespace for Livewire component classes in
|
||||
| your application. This value will change where component auto-discovery
|
||||
| finds components. It's also referenced by the file creation commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'class_namespace' => 'App\\View',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| View Path
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value is used to specify where Livewire component Blade templates are
|
||||
| stored when running file creation commands like `artisan make:livewire`.
|
||||
| It is also used if you choose to omit a component's render() method.
|
||||
|
|
||||
*/
|
||||
|
||||
'view_path' => resource_path('views/livewire'),
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Layout
|
||||
|---------------------------------------------------------------------------
|
||||
| The view that will be used as the layout when rendering a single component
|
||||
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
|
||||
| In this case, the view returned by CreatePost will render into $slot.
|
||||
|
|
||||
*/
|
||||
|
||||
'layout' => 'components.layouts.app',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Lazy Loading Placeholder
|
||||
|---------------------------------------------------------------------------
|
||||
| Livewire allows you to lazy load components that would otherwise slow down
|
||||
| the initial page load. Every component can have a custom placeholder or
|
||||
| you can define the default placeholder view for all components below.
|
||||
|
|
||||
*/
|
||||
|
||||
'lazy_placeholder' => null,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Temporary File Uploads
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire handles file uploads by storing uploads in a temporary directory
|
||||
| before the file is stored permanently. All file uploads are directed to
|
||||
| a global endpoint for temporary storage. You may configure this below:
|
||||
|
|
||||
*/
|
||||
|
||||
'temporary_file_upload' => [
|
||||
'disk' => null, // Example: 'local', 's3' | Default: 'default'
|
||||
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
|
||||
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
||||
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
|
||||
'mov', 'avi', 'wmv', 'mp3', 'm4a',
|
||||
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
|
||||
],
|
||||
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
|
||||
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
|
||||
],
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Render On Redirect
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines if Livewire will run a component's `render()` method
|
||||
| after a redirect has been triggered using something like `redirect(...)`
|
||||
| Setting this to true will render the view once more before redirecting
|
||||
|
|
||||
*/
|
||||
|
||||
'render_on_redirect' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Eloquent Model Binding
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Previous versions of Livewire supported binding directly to eloquent model
|
||||
| properties using wire:model by default. However, this behavior has been
|
||||
| deemed too "magical" and has therefore been put under a feature flag.
|
||||
|
|
||||
*/
|
||||
|
||||
'legacy_model_binding' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Auto-inject Frontend Assets
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| By default, Livewire automatically injects its JavaScript and CSS into the
|
||||
| <head> and <body> of pages containing Livewire components. By disabling
|
||||
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
||||
|
|
||||
*/
|
||||
|
||||
'inject_assets' => true,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Navigate (SPA mode)
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| By adding `wire:navigate` to links in your Livewire application, Livewire
|
||||
| will prevent the default link handling and instead request those pages
|
||||
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
||||
|
|
||||
*/
|
||||
|
||||
'navigate' => [
|
||||
'show_progress_bar' => true,
|
||||
'progress_bar_color' => '#2299dd',
|
||||
],
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| HTML Morph Markers
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
|
||||
| after each update. To make this process more reliable, Livewire injects
|
||||
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
||||
|
|
||||
*/
|
||||
|
||||
'inject_morph_markers' => true,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Pagination Theme
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| When enabling Livewire's pagination feature by using the `WithPagination`
|
||||
| trait, Livewire will use Tailwind templates to render pagination views
|
||||
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
||||
|
|
||||
*/
|
||||
|
||||
'pagination_theme' => 'tailwind',
|
||||
];
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
abstract class Block extends Component
|
||||
{
|
||||
abstract protected function title(): string;
|
||||
|
||||
abstract public function render(): string;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard\Components;
|
||||
|
||||
use Livewire\Component;
|
||||
use Modules\Dashboard\DashboardFactory;
|
||||
|
||||
class DashboardComponent extends Component
|
||||
{
|
||||
|
||||
private array $blocks = [];
|
||||
|
||||
public function mount(DashboardFactory $factory): void
|
||||
{
|
||||
session()->put('menu', 'dashboard');
|
||||
session()->put('title', 'Dashboard');
|
||||
|
||||
$this->blocks = $factory->load();
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::layout>
|
||||
<div class="gap-6 md:grid-cols-2 xl:grid-cols-4 grid p-6">
|
||||
@foreach($this->blocks as $block)
|
||||
<x-ui::box title="{{$block->title()}}" :second="true">
|
||||
<livewire:dynamic-component is="{{ get_class($block) }}" lazy></livewire:dynamic-component>
|
||||
</x-ui::box>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-page::layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard;
|
||||
|
||||
use Livewire\Livewire;
|
||||
|
||||
class DashboardFactory
|
||||
{
|
||||
/**
|
||||
* @var array<int, class-string<Block>>
|
||||
*/
|
||||
private array $blocks = [];
|
||||
|
||||
/**
|
||||
* @return array<int, Block>
|
||||
*/
|
||||
public function load(): array
|
||||
{
|
||||
return collect($this->blocks)->map(fn ($block) => app($block))->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Block> $block
|
||||
*/
|
||||
public function register(string $block): self
|
||||
{
|
||||
$this->blocks[] = $block;
|
||||
|
||||
$componentName = str($block)->replace('\\', '.')->explode('.')->map(fn ($part) => str($part)->lcfirst()->kebab())->implode('.');
|
||||
Livewire::component($componentName, $block);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function purge(): self
|
||||
{
|
||||
$this->blocks = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Dashboard\Components\DashboardComponent;
|
||||
|
||||
class DashboardServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
app()->singleton(DashboardFactory::class, fn () => new DashboardFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/', DashboardComponent::class)->name('home');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard\Tests;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use Modules\Dashboard\Components\DashboardComponent;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Dashboard\DashboardFactory;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
uses(TestCase::class);
|
||||
|
||||
it('renders successfully', function () {
|
||||
$this->login()->loginNami();
|
||||
|
||||
app(DashboardFactory::class)->purge();
|
||||
app(DashboardFactory::class)->register(ExampleBlock::class);
|
||||
|
||||
Livewire::test(DashboardComponent::class)
|
||||
->assertSee('ExampleTitle');
|
||||
});
|
||||
|
||||
it('renders page successfully', function () {
|
||||
$this->login()->loginNami();
|
||||
|
||||
$this->get('/')->assertOk()->assertSee('Dashboard');
|
||||
});
|
||||
|
||||
class ExampleBlock extends Block
|
||||
{
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'ExampleTitle';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
Example Content
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Invoice;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use App\Invoice\Models\InvoicePosition;
|
||||
use App\Member\Member;
|
||||
|
||||
class MemberPaymentBlock extends Block
|
||||
{
|
||||
|
||||
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();
|
||||
|
||||
$this->amount = number_format((int) $amount->price / 100, 2, ',', '.') . ' €';
|
||||
$this->members = Member::whereHasPendingPayment()->count();
|
||||
$this->totalMembers = Member::count();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Mitgliedsbeiträge';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Member;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use App\Member\Membership;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class AgeGroupCountBlock extends Block
|
||||
{
|
||||
|
||||
public $groups;
|
||||
|
||||
/**
|
||||
* @return Builder<Membership>
|
||||
*/
|
||||
protected function memberQuery(): Builder
|
||||
{
|
||||
return Membership::select('subactivities.slug', 'subactivities.name')
|
||||
->selectRaw('COUNT(member_id) AS count')
|
||||
->join('activities', 'memberships.activity_id', 'activities.id')
|
||||
->join('subactivities', 'memberships.subactivity_id', 'subactivities.id')
|
||||
->isAgeGroup()
|
||||
->isMember()
|
||||
->active()
|
||||
->groupBy('subactivities.slug', 'subactivities.name')
|
||||
->orderBy('subactivity_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<Membership>
|
||||
*/
|
||||
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')
|
||||
->join('subactivities', 'memberships.subactivity_id', 'subactivities.id')
|
||||
->active()
|
||||
->isLeader();
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->groups = [
|
||||
...$this->memberQuery()->get(),
|
||||
...$this->leaderQuery()->get(),
|
||||
];
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
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 <<<'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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
private $months = 8;
|
||||
|
||||
/**
|
||||
* @var Collection<Member>
|
||||
*/
|
||||
public Collection $members;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->members = Member::whereHas('memberships', fn ($q) => $q->isTrying())
|
||||
->with('memberships', fn ($q) => $q->isTrying())
|
||||
->get();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Endende Schhnupperzeiten';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Collection<Member>
|
||||
*/
|
||||
public Collection $members;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$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())
|
||||
->get();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Führungszeugnisse';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member;
|
||||
namespace Modules\Prevention;
|
||||
|
||||
use App\Dashboard\Blocks\Block;
|
||||
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,28 +28,25 @@ 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(),
|
||||
];
|
||||
}
|
||||
|
||||
public function component(): string
|
||||
{
|
||||
return 'ps-pending';
|
||||
->whereHas('memberships', fn ($builder) => $builder->isLeader()->active())
|
||||
->get();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Präventionsschulungen';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
import '../css/app.css';
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-full" lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||
|
||||
<title>{{ $title ?? 'Page Title' }}</title>
|
||||
<meta name="socketport" content="{{env('SOCKET_PORT')}}" />
|
||||
<meta name="adrema_base_url" content="/">
|
||||
@if(auth()->id())
|
||||
<meta name="meilisearch_key" content="{{config('scout.meilisearch.key')}}" />
|
||||
@endif
|
||||
@vite('resources/livewire-js/app.js')
|
||||
</head>
|
||||
<body class="min-h-full flex flex-col">
|
||||
<livewire:page.sidebar />
|
||||
{{ $slot }}
|
||||
<page-search-modal v-if="searchVisible" @close="searchVisible = false"></page-search-modal>
|
||||
</body>
|
||||
</html>
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Base;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Component;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
it('renders successfully', function () {
|
||||
$this->login()->loginNami();
|
||||
|
||||
Livewire::test(DummyComponent::class)
|
||||
->assertSee('Testcontent')
|
||||
->assertSee(auth()->user()->lastname);
|
||||
});
|
||||
|
||||
class DummyComponent extends Component
|
||||
{
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
<x-page::layout>
|
||||
Testcontent
|
||||
</x-page::layout>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -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 €');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import path from 'path';
|
|||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel(['resources/js/app.js']),
|
||||
laravel(['resources/livewire-js/app.js']),
|
||||
vue({
|
||||
template: {
|
||||
transformAssetUrls: {
|
||||
|
|
Loading…
Reference in New Issue