Add dashboard blocks
This commit is contained in:
parent
a16c0f469c
commit
5a97574a86
app
config
resources/js/views/home
routes
tests/Feature
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Efz;
|
||||||
|
|
||||||
|
use App\Home\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');
|
||||||
|
})
|
||||||
|
->orderByRaw('lastname, firstname')
|
||||||
|
->whereHas('memberships', fn ($builder) => $builder->isLeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{member: 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';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Home\Actions;
|
||||||
|
|
||||||
|
use App\Home\DashboardFactory;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class IndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<array-key, mixed>
|
||||||
|
*/
|
||||||
|
public function handle(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'blocks' => app(DashboardFactory::class)->render(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(Request $request): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'dashboard');
|
||||||
|
session()->put('title', 'Dashboard');
|
||||||
|
|
||||||
|
return Inertia::render('home/VIndex', $this->handle());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Home\Blocks;
|
||||||
|
|
||||||
|
abstract class Block
|
||||||
|
{
|
||||||
|
abstract protected function data(): array;
|
||||||
|
|
||||||
|
abstract protected function title(): string;
|
||||||
|
|
||||||
|
abstract protected function component(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{data: array<array-key, mixed>, title: string, component: string}
|
||||||
|
*/
|
||||||
|
public function render(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->data(),
|
||||||
|
'title' => $this->title(),
|
||||||
|
'component' => $this->component(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Home;
|
||||||
|
|
||||||
|
use App\Efz\EfzPendingBlock;
|
||||||
|
use App\Home\Blocks\Block;
|
||||||
|
use App\Membership\AgeGroupCountBlock;
|
||||||
|
use App\Membership\TestersBlock;
|
||||||
|
use App\Payment\MemberPaymentBlock;
|
||||||
|
|
||||||
|
class DashboardFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<int, class-string<Block>>
|
||||||
|
*/
|
||||||
|
private array $blocks = [
|
||||||
|
AgeGroupCountBlock::class,
|
||||||
|
MemberPaymentBlock::class,
|
||||||
|
TestersBlock::class,
|
||||||
|
EfzPendingBlock::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<array-key, mixed>
|
||||||
|
*/
|
||||||
|
public function render(): array
|
||||||
|
{
|
||||||
|
return collect($this->blocks)->map(fn ($block): array => app($block)->render())->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,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Home;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class HomeServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
app()->singleton(DashboardFactory::class, fn () => new DashboardFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Home\Queries;
|
|
||||||
|
|
||||||
use App\Member\Membership;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
|
|
||||||
class GroupQuery
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Builder<Membership>
|
|
||||||
*/
|
|
||||||
private Builder $query;
|
|
||||||
|
|
||||||
public function execute(): self
|
|
||||||
{
|
|
||||||
$this->query = 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()
|
|
||||||
->groupBy('subactivities.slug', 'subactivities.name')
|
|
||||||
->orderBy('subactivity_id');
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, array{slug: string, name: string, count: int}>
|
|
||||||
*/
|
|
||||||
public function getResult(): array
|
|
||||||
{
|
|
||||||
return $this->query->get()->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Http\Views\HomeView;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class HomeController extends Controller
|
|
||||||
{
|
|
||||||
public function __invoke(Request $request): Response
|
|
||||||
{
|
|
||||||
session()->put('menu', 'dashboard');
|
|
||||||
session()->put('title', 'Dashboard');
|
|
||||||
|
|
||||||
return \Inertia::render('home/VIndex', app(HomeView::class)->index($request));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Views;
|
|
||||||
|
|
||||||
use App\Home\Queries\GroupQuery;
|
|
||||||
use App\Member\Member;
|
|
||||||
use App\Payment\Payment;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class HomeView
|
|
||||||
{
|
|
||||||
public function index(Request $request): array
|
|
||||||
{
|
|
||||||
/** @var object{a: string} */
|
|
||||||
$amount = Payment::whereNeedsPayment()->selectRaw('sum(subscriptions.amount) AS a')->join('subscriptions', 'subscriptions.id', 'payments.subscription_id')->first();
|
|
||||||
$members = Member::whereHasPendingPayment()->count();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'data' => [
|
|
||||||
'payments' => [
|
|
||||||
'users' => $members,
|
|
||||||
'all_users' => Member::count(),
|
|
||||||
'amount' => number_format($amount->a / 100, 2, ',', '.').' €',
|
|
||||||
],
|
|
||||||
'groups' => app(GroupQuery::class)->execute()->getResult(),
|
|
||||||
'ending_tries' => MemberTriesResource::collection(Member::endingTries()->get()),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Views;
|
|
||||||
|
|
||||||
use App\Member\MemberResource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @mixin \App\Member\Member
|
|
||||||
*/
|
|
||||||
class MemberTriesResource extends MemberResource
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Transform the resource into an array.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function toArray($request)
|
|
||||||
{
|
|
||||||
return array_merge(parent::toArray($request), [
|
|
||||||
'try_ends_at' => $this->getModel()->try_created_at->addWeeks(8)->format('d.m.Y'),
|
|
||||||
'try_ends_at_human' => $this->getModel()->try_created_at->addWeeks(8)->diffForHumans(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,10 +28,9 @@ use Zoomyboy\LaravelNami\Api;
|
||||||
use Zoomyboy\LaravelNami\Data\MembershipEntry;
|
use Zoomyboy\LaravelNami\Data\MembershipEntry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $subscription_name
|
* @property string $subscription_name
|
||||||
* @property int $pending_payment
|
* @property int $pending_payment
|
||||||
* @property bool $is_confirmed
|
* @property bool $is_confirmed
|
||||||
* @property \Carbon\Carbon $try_created_at
|
|
||||||
*/
|
*/
|
||||||
class Member extends Model
|
class Member extends Model
|
||||||
{
|
{
|
||||||
|
@ -356,19 +355,6 @@ class Member extends Model
|
||||||
return $q;
|
return $q;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeEndingTries(Builder $q): Builder
|
|
||||||
{
|
|
||||||
return $q->whereHas('memberships', fn ($q) => $q
|
|
||||||
->where('created_at', '<=', now()->subWeeks(7))
|
|
||||||
->trying()
|
|
||||||
)
|
|
||||||
->addSelect([
|
|
||||||
'try_created_at' => Membership::select('created_at')
|
|
||||||
->whereColumn('memberships.member_id', 'members.id')
|
|
||||||
->trying(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromVcard(string $url, string $data): static
|
public static function fromVcard(string $url, string $data): static
|
||||||
{
|
{
|
||||||
$settings = app(NamiSettings::class);
|
$settings = app(NamiSettings::class);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Membership;
|
||||||
|
|
||||||
|
use App\Home\Blocks\Block;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class AgeGroupCountBlock extends Block
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Builder<Membership>
|
||||||
|
*/
|
||||||
|
public function query(): 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()
|
||||||
|
->groupBy('subactivities.slug', 'subactivities.name')
|
||||||
|
->orderBy('subactivity_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'groups' => $this->query()->get()->toArray(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function component(): string
|
||||||
|
{
|
||||||
|
return 'age-group-count';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(): string
|
||||||
|
{
|
||||||
|
return 'Gruppierungs-Verteilung';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Membership;
|
||||||
|
|
||||||
|
use App\Home\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
|
||||||
|
->where('created_at', '<=', now()->subWeeks(7))
|
||||||
|
->trying()
|
||||||
|
)
|
||||||
|
->with(['memberships' => fn ($query) => $query->trying()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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()->created_at->addWeeks(8)->format('d.m.Y'),
|
||||||
|
'try_ends_at_human' => $member->memberships->first()->created_at->addWeeks(8)->diffForHumans(),
|
||||||
|
])->toArray(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function component(): string
|
||||||
|
{
|
||||||
|
return 'testers';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(): string
|
||||||
|
{
|
||||||
|
return 'Endende Schhnupperzeiten';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Payment;
|
||||||
|
|
||||||
|
use App\Home\Blocks\Block;
|
||||||
|
use App\Member\Member;
|
||||||
|
|
||||||
|
class MemberPaymentBlock extends Block
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, string|int>
|
||||||
|
*/
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
$amount = Payment::whereNeedsPayment()->selectRaw('sum(subscriptions.amount) AS nr')->join('subscriptions', 'subscriptions.id', 'payments.subscription_id')->first();
|
||||||
|
$members = Member::whereHasPendingPayment()->count();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'members' => $members,
|
||||||
|
'total_members' => Member::count(),
|
||||||
|
'amount' => number_format($amount->nr / 100, 2, ',', '.').' €',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function component(): string
|
||||||
|
{
|
||||||
|
return 'member-payment';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(): string
|
||||||
|
{
|
||||||
|
return 'Ausstehende Mitgliedsbeiträge';
|
||||||
|
}
|
||||||
|
}
|
|
@ -188,6 +188,7 @@ return [
|
||||||
App\Tex\TexServiceProvider::class,
|
App\Tex\TexServiceProvider::class,
|
||||||
App\Dav\ServiceProvider::class,
|
App\Dav\ServiceProvider::class,
|
||||||
App\Setting\SettingServiceProvider::class,
|
App\Setting\SettingServiceProvider::class,
|
||||||
|
App\Home\HomeServiceProvider::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(group, index) in inner.groups"
|
||||||
|
:key="index"
|
||||||
|
class="flex mt-2 items-center leading-none text-gray-100"
|
||||||
|
>
|
||||||
|
<svg-sprite class="w-4 h-4 mr-2" src="lilie" :class="`text-${group.slug}`"></svg-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>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<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>
|
|
@ -1,25 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="gap-6 grid-cols-4 grid p-6">
|
<div class="gap-6 grid-cols-4 grid p-6">
|
||||||
|
<v-block v-for="(block, index) in blocks" :key="index" :title="block.title">
|
||||||
|
<v-component :data="block.data" :is="block.component"></v-component>
|
||||||
|
</v-block>
|
||||||
|
<!--
|
||||||
<v-block title="Ausstehende Mitgliedsbeiträge">
|
<v-block title="Ausstehende Mitgliedsbeiträge">
|
||||||
<div class="text-gray-100">
|
|
||||||
<span class="text-xl mr-1 font-semibold" v-text="data.payments.amount"></span>
|
|
||||||
<span class="text-sm" v-text="`von ${data.payments.users} / ${data.payments.all_users} Mitgliedern`"></span>
|
|
||||||
</div>
|
|
||||||
</v-block>
|
</v-block>
|
||||||
<v-block title="Gruppierungs-Verteilung">
|
<v-block title="Gruppierungs-Verteilung">
|
||||||
<div v-for="group, index in data.groups" :key="index" class="flex mt-2 items-center leading-none text-gray-100">
|
|
||||||
<svg-sprite class="w-4 h-4 mr-2" src="lilie" :class="`text-${group.slug}`"></svg-sprite>
|
|
||||||
<span v-text="group.name" class="grow"></span>
|
|
||||||
<span v-text="group.count"></span>
|
|
||||||
</div>
|
|
||||||
</v-block>
|
</v-block>
|
||||||
<v-block title="Endende Schhnupperzeiten">
|
<v-block title="Endende Schhnupperzeiten">
|
||||||
<div v-for="member, index in data.ending_tries" :key="index" class="flex mt-2 items-center leading-none text-gray-100">
|
|
||||||
<span class="grow" v-text="`${member.firstname} ${member.lastname}`"></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>
|
|
||||||
</v-block>
|
</v-block>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -27,12 +18,15 @@
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
data: {},
|
data: {},
|
||||||
blocks: {}
|
blocks: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
'VBlock': () => import('./VBlock')
|
'VBlock': () => import('./VBlock'),
|
||||||
}
|
'age-group-count': () => import('./AgeGroupCount.vue'),
|
||||||
|
'efz-pending': () => import('./EfzPending.vue'),
|
||||||
|
'testers': () => import('./Testers.vue'),
|
||||||
|
'member-payment': () => import('./MemberPayment.vue'),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use App\Contribution\ContributionController;
|
use App\Contribution\ContributionController;
|
||||||
use App\Course\Controllers\CourseController;
|
use App\Course\Controllers\CourseController;
|
||||||
use App\Efz\ShowEfzDocumentAction;
|
use App\Efz\ShowEfzDocumentAction;
|
||||||
use App\Http\Controllers\HomeController;
|
use App\Home\Actions\IndexAction as HomeIndexAction;
|
||||||
use App\Initialize\Actions\InitializeAction;
|
use App\Initialize\Actions\InitializeAction;
|
||||||
use App\Initialize\Actions\InitializeFormAction;
|
use App\Initialize\Actions\InitializeFormAction;
|
||||||
use App\Member\Controllers\MemberResyncController;
|
use App\Member\Controllers\MemberResyncController;
|
||||||
|
@ -22,7 +22,7 @@ Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void {
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['middleware' => 'auth:web'], function (): void {
|
Route::group(['middleware' => 'auth:web'], function (): void {
|
||||||
Route::get('/', HomeController::class)->name('home');
|
Route::get('/', HomeIndexAction::class)->name('home');
|
||||||
Route::get('/initialize', InitializeFormAction::class)->name('initialize.form');
|
Route::get('/initialize', InitializeFormAction::class)->name('initialize.form');
|
||||||
Route::post('/initialize', InitializeAction::class)->name('initialize.store');
|
Route::post('/initialize', InitializeAction::class)->name('initialize.store');
|
||||||
Route::resource('member', MemberController::class);
|
Route::resource('member', MemberController::class);
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Member\Member;
|
use App\Home\Blocks\Block;
|
||||||
use App\Member\Membership;
|
use App\Home\DashboardFactory;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
@ -11,34 +11,39 @@ class HomeTest extends TestCase
|
||||||
{
|
{
|
||||||
use DatabaseTransactions;
|
use DatabaseTransactions;
|
||||||
|
|
||||||
public function testItDisplaysAgeGroups(): void
|
public function testItDisplaysBlock(): void
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling();
|
$this->withoutExceptionHandling();
|
||||||
Member::factory()->count(3)
|
app(DashboardFactory::class)->purge();
|
||||||
->has(Membership::factory()->in('€ Mitglied', 1, 'Biber', 2))
|
app(DashboardFactory::class)->register(ExampleBlock::class);
|
||||||
->defaults()
|
|
||||||
->create();
|
|
||||||
Member::factory()->count(4)
|
|
||||||
->has(Membership::factory()->in('€ Mitglied', 1, 'Wölfling', 3))
|
|
||||||
->defaults()
|
|
||||||
->create();
|
|
||||||
Member::factory()->has(Membership::factory()->in('€ LeiterIn', 2, 'Wölfling', 3))
|
|
||||||
->defaults()
|
|
||||||
->create();
|
|
||||||
|
|
||||||
$this->login()->loginNami();
|
$this->login()->loginNami();
|
||||||
|
|
||||||
$response = $this->get('/');
|
$response = $this->get('/');
|
||||||
|
|
||||||
$this->assertInertiaHas([
|
$this->assertInertiaHas(['class' => 'name'], $response, 'blocks.0.data');
|
||||||
'slug' => 'biber',
|
$this->assertInertiaHas('Example', $response, 'blocks.0.title');
|
||||||
'name' => 'Biber',
|
$this->assertInertiaHas('exa', $response, 'blocks.0.component');
|
||||||
'count' => 3,
|
}
|
||||||
], $response, 'data.groups.0');
|
}
|
||||||
$this->assertInertiaHas([
|
|
||||||
'slug' => 'woelfling',
|
class ExampleBlock extends Block
|
||||||
'name' => 'Wölfling',
|
{
|
||||||
'count' => 4,
|
public function title(): string
|
||||||
], $response, 'data.groups.1');
|
{
|
||||||
|
return 'Example';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
return ['class' => 'name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function component(): string
|
||||||
|
{
|
||||||
|
return 'exa';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Membership;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use App\Membership\AgeGroupCountBlock;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AgeGroupCountBlockTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function testItDisplaysAgeGroups(): void
|
||||||
|
{
|
||||||
|
$this->withoutExceptionHandling();
|
||||||
|
Member::factory()->count(3)
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->create();
|
||||||
|
Member::factory()->count(4)
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 1, 'Wölfling', 3))
|
||||||
|
->defaults()
|
||||||
|
->create();
|
||||||
|
Member::factory()->has(Membership::factory()->in('€ LeiterIn', 2, 'Wölfling', 3))
|
||||||
|
->defaults()
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$data = app(AgeGroupCountBlock::class)->render();
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'groups' => [
|
||||||
|
['slug' => 'biber', 'name' => 'Biber', 'count' => 3],
|
||||||
|
['slug' => 'woelfling', 'name' => 'Wölfling', 'count' => 4],
|
||||||
|
],
|
||||||
|
], $data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Membership;
|
||||||
|
|
||||||
|
use App\Efz\EfzPendingBlock;
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class EfzPendingBlockTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function testItDisplaysEfzPending(): void
|
||||||
|
{
|
||||||
|
$this->withoutExceptionHandling();
|
||||||
|
Member::factory()
|
||||||
|
->has(Membership::factory()->in('€ LeiterIn', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->create(['firstname' => 'Max', 'lastname' => 'Muster', 'efz' => now()->subYear()]);
|
||||||
|
Member::factory()
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->create(['firstname' => 'Jane', 'lastname' => 'Muster', 'efz' => now()->subYear()]);
|
||||||
|
Member::factory()
|
||||||
|
->has(Membership::factory()->in('€ LeiterIn', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->create(['firstname' => 'Mae', 'lastname' => 'Muster', 'efz' => now()->subYears(5)->startOfYear()]);
|
||||||
|
Member::factory()
|
||||||
|
->has(Membership::factory()->in('€ LeiterIn', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->create(['firstname' => 'Joe', 'lastname' => 'Muster', 'efz' => now()->subYears(5)->endOfYear()]);
|
||||||
|
Member::factory()
|
||||||
|
->has(Membership::factory()->in('€ LeiterIn', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->create(['firstname' => 'Moa', 'lastname' => 'Muster', 'efz' => null]);
|
||||||
|
Member::factory()
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 1, 'Biber', 2))
|
||||||
|
->defaults()
|
||||||
|
->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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Membership;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use App\Membership\TestersBlock;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class TestersBlockTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function testItHasData(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->has(Membership::factory()->in('Schnuppermitgliedschaft', 7, 'Wölfling', 8)->state(['created_at' => now()->subMonths(10)]))
|
||||||
|
->create(['firstname' => 'Max', 'lastname' => 'Muster']);
|
||||||
|
|
||||||
|
$data = app(TestersBlock::class)->render();
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Payment;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Payment\MemberPaymentBlock;
|
||||||
|
use App\Payment\Payment;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class TestersBlockTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function testItHasData(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->has(Payment::factory()->notPaid()->subscription('example', 3400))
|
||||||
|
->create();
|
||||||
|
Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$data = app(MemberPaymentBlock::class)->render();
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'amount' => '34,00 €',
|
||||||
|
'members' => 1,
|
||||||
|
'total_members' => 2,
|
||||||
|
], $data);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue