--wip-- [skip ci]
This commit is contained in:
parent
528b716705
commit
653d59fc18
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
|
||||||
|
class LivewireServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
Blade::componentNamespace('App\\View\\Ui', 'ui');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 @class([
|
||||||
|
'bg-gray-800 group-[.is-popup]:bg-zinc-700' => !$second,
|
||||||
|
'bg-gray-700 group-[.is-popup]:bg-zinc-600' => $second,
|
||||||
|
'p-3 rounded-lg flex flex-col' => true
|
||||||
|
])>
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -168,7 +168,6 @@ return [
|
||||||
*/
|
*/
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\AuthServiceProvider::class,
|
App\Providers\AuthServiceProvider::class,
|
||||||
// App\Providers\BroadcastServiceProvider::class,
|
|
||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
App\Providers\HorizonServiceProvider::class,
|
App\Providers\HorizonServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
@ -178,6 +177,8 @@ return [
|
||||||
App\Setting\SettingServiceProvider::class,
|
App\Setting\SettingServiceProvider::class,
|
||||||
App\Dashboard\DashboardServiceProvider::class,
|
App\Dashboard\DashboardServiceProvider::class,
|
||||||
App\Providers\PluginServiceProvider::class,
|
App\Providers\PluginServiceProvider::class,
|
||||||
|
// Modules\Dashboard\DashboardServiceProvider::class,
|
||||||
|
// App\Providers\LivewireServiceProvider::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,33 @@
|
||||||
|
<?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'
|
||||||
|
<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()}}" :component="$block" :second="true">
|
||||||
|
<livewire:dynamic-component :is="$block"></livewire:dynamic-component>
|
||||||
|
</x-ui::box>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Dashboard;
|
||||||
|
|
||||||
|
use Modules\Invoice\MemberPaymentBlock;
|
||||||
|
use Modules\Member\AgeGroupCountBlock;
|
||||||
|
use Modules\Member\TestersBlock;
|
||||||
|
use Modules\Prevention\EfzPendingBlock;
|
||||||
|
use Modules\Prevention\PsPendingBlock;
|
||||||
|
|
||||||
|
class DashboardFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<int, class-string<Block>>
|
||||||
|
*/
|
||||||
|
private array $blocks = [
|
||||||
|
AgeGroupCountBlock::class,
|
||||||
|
MemberPaymentBlock::class,
|
||||||
|
TestersBlock::class,
|
||||||
|
EfzPendingBlock::class,
|
||||||
|
PsPendingBlock::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
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,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Dashboard\Events;
|
||||||
|
|
||||||
|
use Modules\Dashboard\Block;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class DashboardShowing
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int, Block>
|
||||||
|
*/
|
||||||
|
public array $blocks = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function purge(): void
|
||||||
|
{
|
||||||
|
$this->blocks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param class-string<Block> $block
|
||||||
|
*/
|
||||||
|
public function push(string $block): void
|
||||||
|
{
|
||||||
|
$this->blocks[] = $block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('channel-name'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?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')
|
||||||
|
->assertSee('Example Content');
|
||||||
|
});
|
||||||
|
|
||||||
|
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,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Invoice;
|
||||||
|
|
||||||
|
use Modules\Dashboard\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';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
return '<div></div>';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Member;
|
||||||
|
|
||||||
|
use Modules\Dashboard\Block;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class AgeGroupCountBlock extends Block
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Builder<Membership>
|
||||||
|
*/
|
||||||
|
public 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>
|
||||||
|
*/
|
||||||
|
public 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'groups' => [
|
||||||
|
...$this->memberQuery()->get()->toArray(),
|
||||||
|
...$this->leaderQuery()->get()->toArray(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function component(): string
|
||||||
|
{
|
||||||
|
return 'age-group-count';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(): string
|
||||||
|
{
|
||||||
|
return 'Gruppierungs-Verteilung';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
return '<div></div>';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Member;
|
||||||
|
|
||||||
|
use Modules\Dashboard\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';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
return '<div></div>';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Prevention;
|
||||||
|
|
||||||
|
use Modules\Dashboard\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';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
return '<div></div>';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Prevention;
|
||||||
|
|
||||||
|
use Modules\Dashboard\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';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
return '<div></div>';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<html>
|
||||||
|
{{ $slot }}
|
||||||
|
</html>
|
Loading…
Reference in New Issue