Compare commits
10 Commits
adf9341dd7
...
ab442dff9b
Author | SHA1 | Date |
---|---|---|
|
ab442dff9b | |
|
88e020ac4e | |
|
d7361b6713 | |
|
4538ec8ca8 | |
|
6032b9c289 | |
|
6da6453b5d | |
|
66b6a549c3 | |
|
878de3f566 | |
|
aa55b56df7 | |
|
31c582e81d |
|
@ -54,6 +54,7 @@ class ActivityResource extends JsonResource
|
||||||
'index' => route('activity.index'),
|
'index' => route('activity.index'),
|
||||||
'create' => route('activity.create'),
|
'create' => route('activity.create'),
|
||||||
'membership_masslist' => route('membership.masslist.index'),
|
'membership_masslist' => route('membership.masslist.index'),
|
||||||
|
'membership_index' => route('membership.index'),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class FormApiListAction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $filter
|
* @param string $filter
|
||||||
* @return LengthAwarePaginator<Form>
|
* @return LengthAwarePaginator<int, Form>
|
||||||
*/
|
*/
|
||||||
public function handle(string $filter, int $perPage): LengthAwarePaginator
|
public function handle(string $filter, int $perPage): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@ class FormIndexAction
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator<Form>
|
* @return LengthAwarePaginator<int, Form>
|
||||||
*/
|
*/
|
||||||
public function handle(string $filter): LengthAwarePaginator
|
public function handle(string $filter): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ class FormtemplateIndexAction
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator<Formtemplate>
|
* @return LengthAwarePaginator<int, Formtemplate>
|
||||||
*/
|
*/
|
||||||
public function handle(): LengthAwarePaginator
|
public function handle(): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@ class NamiSearchAction
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $params
|
* @param array<string, mixed> $params
|
||||||
*
|
*
|
||||||
* @return LengthAwarePaginator<MemberEntry>
|
* @return LengthAwarePaginator<int, MemberEntry>
|
||||||
*/
|
*/
|
||||||
public function handle(Api $api, int $page, array $params, int $perPage = 10): LengthAwarePaginator
|
public function handle(Api $api, int $page, array $params, int $perPage = 10): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
|
@ -36,7 +36,7 @@ class NamiSearchAction
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator<MemberEntry>
|
* @return LengthAwarePaginator<int, MemberEntry>
|
||||||
*/
|
*/
|
||||||
public function asController(ActionRequest $request): LengthAwarePaginator
|
public function asController(ActionRequest $request): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace App\Invoice\Scopes;
|
||||||
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\Models\Invoice;
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Lib\Filter;
|
|
||||||
use App\Lib\ScoutFilter;
|
use App\Lib\ScoutFilter;
|
||||||
use Laravel\Scout\Builder;
|
use Laravel\Scout\Builder;
|
||||||
use Spatie\LaravelData\Attributes\MapInputName;
|
use Spatie\LaravelData\Attributes\MapInputName;
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Data;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Normalizers\Normalizer;
|
||||||
|
use App\Lib\Normalizers\DateNormalizer;
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
use Spatie\LaravelData\Attributes\WithTransformer;
|
||||||
|
use App\Lib\Transformers\DateTransformer;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class DateData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[WithTransformer(DateTransformer::class)]
|
||||||
|
public Carbon $raw,
|
||||||
|
public string $human,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, class-string<Normalizer>>
|
||||||
|
*/
|
||||||
|
public static function normalizers(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
DateNormalizer::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Data;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
class RecordData extends Data {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
|
public string $name,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
}
|
|
@ -14,10 +14,12 @@ abstract class Filter extends Data
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Builder<T> $query
|
|
||||||
* @return Builder<T>
|
* @return Builder<T>
|
||||||
*/
|
*/
|
||||||
abstract public function apply(Builder $query): Builder;
|
abstract public function getQuery(): Builder;
|
||||||
|
|
||||||
|
/** @var Builder<T> */
|
||||||
|
protected Builder $query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed>|string|null $request
|
* @param array<string, mixed>|string|null $request
|
||||||
|
@ -36,14 +38,6 @@ abstract class Filter extends Data
|
||||||
*/
|
*/
|
||||||
public static function fromPost(?array $post = null): static
|
public static function fromPost(?array $post = null): static
|
||||||
{
|
{
|
||||||
return static::factory()->withoutMagicalCreation()->from($post ?: [])->toDefault();
|
return static::factory()->withoutMagicalCreation()->from($post ?: []);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function toDefault(): self
|
|
||||||
{
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\PaginatedDataCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin Spatie\LaravelData\Data
|
||||||
|
*/
|
||||||
|
trait HasDataMeta
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function collectPages(mixed $items): array {
|
||||||
|
$source = parent::collect($items, PaginatedDataCollection::class)->toArray();
|
||||||
|
return [
|
||||||
|
...parent::collect($items, PaginatedDataCollection::class)->toArray(),
|
||||||
|
'meta' => [...$source['meta'], ...static::meta()]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
namespace App\Lib;
|
namespace App\Lib;
|
||||||
|
|
||||||
/** @mixin \Illuminate\Http\Resources\Json\JsonResource */
|
/**
|
||||||
|
* @mixin \Illuminate\Http\Resources\Json\JsonResource
|
||||||
|
*/
|
||||||
trait HasMeta
|
trait HasMeta
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Normalizers;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Normalizers\Normalizer;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class DateNormalizer implements Normalizer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function normalize(mixed $value): ?array
|
||||||
|
{
|
||||||
|
if (!$value instanceof Carbon) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'raw' => $value,
|
||||||
|
'human' => $value->format('d.m.Y'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Transformers;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Transformers\Transformer;
|
||||||
|
use Spatie\LaravelData\Support\DataProperty;
|
||||||
|
use Spatie\LaravelData\Support\Transformation\TransformationContext;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class DateTransformer implements Transformer
|
||||||
|
{
|
||||||
|
public function transform(DataProperty $property, mixed $value, TransformationContext $context): string
|
||||||
|
{
|
||||||
|
return Carbon::parse($value)->format('Y-m-d');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Member\Data;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
use App\Member\Member;
|
||||||
|
|
||||||
|
class MemberData extends Data
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $fullname,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function fromModel(Member $member): static
|
||||||
|
{
|
||||||
|
return static::factory()->withoutMagicalCreation()->from([
|
||||||
|
'fullname' => $member->fullname
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Member\Data;
|
||||||
|
|
||||||
|
use App\Activity;
|
||||||
|
use App\Group;
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
use App\Lib\Data\DateData;
|
||||||
|
use App\Lib\Data\RecordData;
|
||||||
|
use App\Lib\HasDataMeta;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use App\Membership\FilterScope;
|
||||||
|
use App\Subactivity;
|
||||||
|
|
||||||
|
class MembershipData extends Data
|
||||||
|
{
|
||||||
|
|
||||||
|
use HasDataMeta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $links
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
|
public RecordData $activity,
|
||||||
|
public ?RecordData $subactivity,
|
||||||
|
public RecordData $group,
|
||||||
|
public ?DateData $promisedAt,
|
||||||
|
public DateData $from,
|
||||||
|
public ?DateData $to,
|
||||||
|
public MemberData $member,
|
||||||
|
public bool $isActive,
|
||||||
|
public array $links,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function fromModel(Membership $membership): static
|
||||||
|
{
|
||||||
|
return static::factory()->withoutMagicalCreation()->from([
|
||||||
|
'id' => $membership->id,
|
||||||
|
'activity' => $membership->activity,
|
||||||
|
'subactivity' => $membership->subactivity,
|
||||||
|
'isActive' => $membership->isActive(),
|
||||||
|
'from' => $membership->from,
|
||||||
|
'to' => $membership->to,
|
||||||
|
'group' => $membership->group,
|
||||||
|
'promisedAt' => $membership->promised_at,
|
||||||
|
'member' => $membership->member,
|
||||||
|
'links' => [
|
||||||
|
'update' => route('membership.update', $membership),
|
||||||
|
'destroy' => route('membership.destroy', $membership),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function meta(): array {
|
||||||
|
return [
|
||||||
|
'activities' => RecordData::collect(Activity::get()),
|
||||||
|
'subactivities' => RecordData::collect(Subactivity::get()),
|
||||||
|
'groups' => RecordData::collect(Group::get()),
|
||||||
|
'filter' => FilterScope::fromRequest(request()->input('filter', '')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -79,6 +79,19 @@ class Membership extends Model
|
||||||
->where(fn ($query) => $query->whereNull('to')->orWhere('to', '>=', now()));
|
->where(fn ($query) => $query->whereNull('to')->orWhere('to', '>=', now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Builder<Membership> $query
|
||||||
|
*
|
||||||
|
* @return Builder<Membership>
|
||||||
|
*/
|
||||||
|
public function scopeInactive(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query->where(fn ($q) => $q
|
||||||
|
->orWhere('from', '>=', now())
|
||||||
|
->orWhere('to', '<=', now())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Builder<Membership> $query
|
* @param Builder<Membership> $query
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,7 +21,7 @@ class MassStoreAction
|
||||||
use TracksJob;
|
use TracksJob;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, string>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -87,6 +87,9 @@ class MassStoreAction
|
||||||
return response()->json([], 200);
|
return response()->json([], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
public function getValidationAttributes(): array {
|
public function getValidationAttributes(): array {
|
||||||
return [
|
return [
|
||||||
'activity_id' => 'Tätigkeit',
|
'activity_id' => 'Tätigkeit',
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class IndexAction
|
class MemberIndexAction
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Membership\Actions;
|
||||||
|
|
||||||
|
use App\Member\Data\MembershipData;
|
||||||
|
use App\Membership\FilterScope;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class MembershipIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function asController(ActionRequest $request): Response
|
||||||
|
{
|
||||||
|
return Inertia::render(
|
||||||
|
'membership/Index',
|
||||||
|
['data' => MembershipData::collectPages(FilterScope::fromRequest($request->input('filter', ''))->getQuery()->paginate(20))]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Membership;
|
||||||
|
|
||||||
|
use App\Lib\Filter;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends Filter<Membership>
|
||||||
|
*/
|
||||||
|
class FilterScope extends Filter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int, int> $activities
|
||||||
|
* @param array<int, int> $subactivities
|
||||||
|
* @param array<int, int> $groups
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public array $activities = [],
|
||||||
|
public array $subactivities = [],
|
||||||
|
public array $groups = [],
|
||||||
|
public ?bool $active = true,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getQuery(): Builder
|
||||||
|
{
|
||||||
|
$query = (new Membership())->newQuery();
|
||||||
|
|
||||||
|
if ($this->active === true) {
|
||||||
|
$query = $query->active();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->active === false) {
|
||||||
|
$query = $query->inactive();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->groups)) {
|
||||||
|
$query = $query->whereIn('group_id', $this->groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->activities)) {
|
||||||
|
$query = $query->whereIn('activity_id', $this->activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->subactivities)) {
|
||||||
|
$query = $query->whereIn('subactivity_id', $this->subactivities);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ class SearchAction
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator<array<string, mixed>>
|
* @return LengthAwarePaginator<int, array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
public function handle(ActionRequest $request): LengthAwarePaginator
|
public function handle(ActionRequest $request): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
|
|
|
@ -98,6 +98,12 @@ class MemberFactory extends Factory
|
||||||
return $this->state(['nami_id' => null]);
|
return $this->state(['nami_id' => null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function name(string $name): self
|
||||||
|
{
|
||||||
|
[$firstname, $lastname] = explode(' ', $name);
|
||||||
|
return $this->state(compact('firstname', 'lastname'));
|
||||||
|
}
|
||||||
|
|
||||||
public function withBankAccount(BankAccountFactory $factory): self
|
public function withBankAccount(BankAccountFactory $factory): self
|
||||||
{
|
{
|
||||||
return $this->afterCreating(function ($member) use ($factory) {
|
return $this->afterCreating(function ($member) use ($factory) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Database\Factories\Member;
|
||||||
|
|
||||||
use App\Activity;
|
use App\Activity;
|
||||||
use App\Group;
|
use App\Group;
|
||||||
|
use App\Member\Member;
|
||||||
use App\Member\Membership;
|
use App\Member\Membership;
|
||||||
use App\Subactivity;
|
use App\Subactivity;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
@ -30,6 +31,10 @@ class MembershipFactory extends Factory
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function defaults(): self {
|
||||||
|
return $this->for(Member::factory()->defaults())->for(Group::factory())->for(Activity::factory())->for(Subactivity::factory());
|
||||||
|
}
|
||||||
|
|
||||||
public function inNami(int $namiId): self
|
public function inNami(int $namiId): self
|
||||||
{
|
{
|
||||||
return $this->state(['nami_id' => $namiId]);
|
return $this->state(['nami_id' => $namiId]);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit c20cf930f2037ddda4400dd7d35bf9ef707356eb
|
Subproject commit 35bed01848492471d6e4141f303f74ab19d1fc09
|
|
@ -418,11 +418,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: app/Mailgateway/Resources/MailgatewayResource.php
|
path: app/Mailgateway/Resources/MailgatewayResource.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Generic type Illuminate\\\\Pagination\\\\LengthAwarePaginator\\<int, App\\\\Member\\\\Member\\> in PHPDoc tag @return specifies 2 template types, but class Illuminate\\\\Pagination\\\\LengthAwarePaginator supports only 1\\: TValue$#"
|
|
||||||
count: 1
|
|
||||||
path: app/Member/Actions/SearchAction.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Class Plugins\\\\Test\\\\ServiceProvider not found\\.$#"
|
message: "#^Class Plugins\\\\Test\\\\ServiceProvider not found\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.custom-table {
|
.custom-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
& > thead > th {
|
& > thead > th, & > thead > tr > th {
|
||||||
@apply text-left px-6 text-gray-200 font-semibold py-3 border-gray-600 border-b;
|
@apply text-left px-6 text-gray-200 font-semibold py-3 border-gray-600 border-b;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > tr {
|
& > tr, & > tbody > tr {
|
||||||
@apply text-gray-200 transition-all duration-300 rounded hover:bg-gray-800;
|
@apply text-gray-200 transition-all duration-300 rounded hover:bg-gray-800;
|
||||||
& > td {
|
& > td {
|
||||||
@apply py-1 px-6;
|
@apply py-1 px-6;
|
||||||
|
@ -12,10 +12,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.custom-table-sm {
|
&.custom-table-sm {
|
||||||
& > thead > th {
|
& > thead > th, & > thead > tr > th {
|
||||||
@apply px-3 py-2;
|
@apply px-3 py-2;
|
||||||
}
|
}
|
||||||
& > tr {
|
& > tr, & > tbody > tr {
|
||||||
& > td {
|
& > td {
|
||||||
@apply py-1 px-3;
|
@apply py-1 px-3;
|
||||||
}
|
}
|
||||||
|
@ -23,20 +23,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.custom-table-light {
|
&.custom-table-light {
|
||||||
& > thead > th {
|
& > thead > th, & > thead > tr > th {
|
||||||
@apply border-gray-500;
|
@apply border-gray-500;
|
||||||
}
|
}
|
||||||
& > td {
|
& > tr, & > tbody > tr {
|
||||||
&:hover {
|
@apply hover:bg-gray-700;
|
||||||
@apply bg-gray-700;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
.custom-table > * {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.custom-table > * > * {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M6 6V2c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-4v4a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8c0-1.1.9-2 2-2h4zm2 0h4a2 2 0 0 1 2 2v4h4V2H8v4zM2 8v10h10V8H2z"/></svg>
|
After Width: | Height: | Size: 233 B |
|
@ -1,24 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<label class="flex flex-col group" :for="id" :class="sizeClass(size)">
|
<label class="flex flex-col group" :for="id" :class="sizeClass(size)">
|
||||||
<f-label v-if="label" :required="false" :value="label"></f-label>
|
<f-label v-if="label" :required="false" :value="label" />
|
||||||
<div class="relative flex-none flex">
|
<div class="relative flex-none flex">
|
||||||
<div
|
<div :class="[fieldHeight, fieldAppearance, selectAppearance]"
|
||||||
:class="[fieldHeight, fieldAppearance, selectAppearance]"
|
class="form-select flex items-center w-full"
|
||||||
class="form-select flex items-center w-full"
|
@click="visible = !visible"
|
||||||
@click="visible = !visible"
|
v-text="`${modelValue.length} Einträge ausgewählt`"
|
||||||
v-text="`${modelValue.length} Einträge ausgewählt`"
|
/>
|
||||||
></div>
|
<div v-show="visible" class="absolute w-[max-content] z-30 max-h-[25rem] overflow-auto shadow-lg bg-gray-600 border border-gray-500 rounded-lg p-2 top-7 space-y-1 mt-2">
|
||||||
<div v-show="visible" class="absolute w-[max-content] z-30 max-h-[25rem] overflow-auto shadow-lg bg-gray-600 border border-gray-500 rounded-lg p-2 top-7 space-y-1">
|
|
||||||
<div v-for="(option, index) in parsedOptions" :key="index" class="flex items-center space-x-2">
|
<div v-for="(option, index) in parsedOptions" :key="index" class="flex items-center space-x-2">
|
||||||
<f-switch
|
<f-switch :id="`${id}-${index}`"
|
||||||
:id="`${id}-${index}`"
|
:name="`${id}-${index}`"
|
||||||
:name="`${id}-${index}`"
|
size="sm"
|
||||||
size="sm"
|
:model-value="modelValue.includes(option.id)"
|
||||||
:model-value="modelValue.includes(option.id)"
|
:value="option.id"
|
||||||
:value="option.id"
|
@update:model-value="trigger(option, $event)"
|
||||||
@update:modelValue="trigger(option, $event)"
|
/>
|
||||||
></f-switch>
|
<div class="text-sm text-gray-200" v-text="option.name" />
|
||||||
<div class="text-sm text-gray-200" v-text="option.name"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,11 +60,11 @@ const parsedOptions = computed(() =>
|
||||||
Array.isArray(props.options)
|
Array.isArray(props.options)
|
||||||
? props.options
|
? props.options
|
||||||
: map(props.options, (value, key) => {
|
: map(props.options, (value, key) => {
|
||||||
return {name: value, id: key};
|
return {name: value, id: key};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
function trigger(option, v) {
|
function trigger(option, v) {
|
||||||
var value = JSON.parse(JSON.stringify(props.modelValue));
|
const value = JSON.parse(JSON.stringify(props.modelValue));
|
||||||
|
|
||||||
emit('update:modelValue', value.includes(option.id) ? value.filter((cv) => cv !== option.id) : [...value, option.id]);
|
emit('update:modelValue', value.includes(option.id) ? value.filter((cv) => cv !== option.id) : [...value, option.id]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {ref, inject, computed, onBeforeUnmount} from 'vue';
|
import { ref, inject, computed, onBeforeUnmount } from 'vue';
|
||||||
import {router} from '@inertiajs/vue3';
|
import { router } from '@inertiajs/vue3';
|
||||||
import useQueueEvents from './useQueueEvents.js';
|
import useQueueEvents from './useQueueEvents.js';
|
||||||
|
|
||||||
export function useIndex(props, siteName) {
|
export function useIndex(props, siteName) {
|
||||||
const axios = inject('axios');
|
const axios = inject('axios');
|
||||||
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
const { startListener, stopListener } = useQueueEvents(siteName, () => reload(false));
|
||||||
const rawProps = JSON.parse(JSON.stringify(props));
|
const rawProps = JSON.parse(JSON.stringify(props));
|
||||||
const inner = {
|
const inner = {
|
||||||
data: ref(rawProps.data),
|
data: ref(rawProps.data),
|
||||||
|
@ -40,7 +40,7 @@ export function useIndex(props, siteName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadPage(page) {
|
function reloadPage(page) {
|
||||||
reload(false, {page: page});
|
reload(false, { page: page });
|
||||||
}
|
}
|
||||||
|
|
||||||
function can(permission) {
|
function can(permission) {
|
||||||
|
@ -83,10 +83,10 @@ export function useIndex(props, siteName) {
|
||||||
const indexProps = {
|
const indexProps = {
|
||||||
data: {
|
data: {
|
||||||
default: () => {
|
default: () => {
|
||||||
return {data: [], meta: {}};
|
return { data: [], meta: {} };
|
||||||
},
|
},
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export {indexProps};
|
export { indexProps };
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<page-layout>
|
<page-layout>
|
||||||
<template #right>
|
<template #right>
|
||||||
<f-save-button form="actionform"></f-save-button>
|
<f-save-button form="actionform" />
|
||||||
</template>
|
</template>
|
||||||
<form id="actionform" class="grow p-3" @submit.prevent="submit">
|
<form id="actionform" class="grow p-3" @submit.prevent="submit">
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<f-select
|
<f-select :model-value="meta.activity_id"
|
||||||
:model-value="meta.activity_id"
|
:options="props.activities"
|
||||||
:options="props.activities"
|
label="Tätigkeit"
|
||||||
label="Tätigkeit"
|
size="sm"
|
||||||
size="sm"
|
name="activity_id"
|
||||||
name="activity_id"
|
@update:model-value="meta = {...meta, activity_id: $event, subactivity_id: null}"
|
||||||
@update:model-value="meta = {...meta, activity_id: $event, subactivity_id: null}"
|
/>
|
||||||
></f-select>
|
<f-select v-model="meta.subactivity_id" :options="props.subactivities[meta.activity_id]" name="subactivity_id" label="Untertätigkeit" size="sm" />
|
||||||
<f-select v-model="meta.subactivity_id" :options="props.subactivities[meta.activity_id]" name="subactivity_id" label="Untertätigkeit" size="sm"></f-select>
|
<f-select v-model="meta.group_id" :options="props.groups" label="Gruppierung" size="sm" name="group_id" />
|
||||||
<f-select v-model="meta.group_id" :options="props.groups" label="Gruppierung" size="sm" name="group_id"></f-select>
|
<f-text id="search_text" v-model="searchText" label="Suchen …" size="sm" />
|
||||||
<f-text id="search_text" v-model="searchText" label="Suchen …" size="sm"></f-text>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 grid-cols-6 mt-4">
|
<div class="grid gap-2 grid-cols-6 mt-4">
|
||||||
<f-switch v-for="member in members.hits" :id="`member-${member.id}`" :key="member.id" v-model="selected" :value="member.id" :label="member.fullname" size="sm"></f-switch>
|
<f-switch v-for="member in members.hits" :id="`member-${member.id}`" :key="member.id" v-model="selected" :value="member.id" :label="member.fullname" size="sm" />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-6">
|
<div class="px-6">
|
||||||
<ui-search-pagination class="mt-4" :value="members" @reload="reloadReal($event)"></ui-search-pagination>
|
<ui-search-pagination class="mt-4" :value="members" @reload="reloadReal($event)" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Tätigkeit erstellen</page-toolbar-button>
|
<page-toolbar-button :href="meta.links.create" color="primary" icon="plus">Tätigkeit erstellen</page-toolbar-button>
|
||||||
<page-toolbar-button :href="meta.links.membership_masslist" color="primary" icon="pencil">Mitgliedschaften zuweisen</page-toolbar-button>
|
<page-toolbar-button :href="meta.links.membership_masslist" color="primary" icon="pencil">Mitgliedschaften zuweisen</page-toolbar-button>
|
||||||
|
<page-toolbar-button :href="meta.links.membership_index" color="primary" icon="eye">Mitgliedschaften anschauen</page-toolbar-button>
|
||||||
</template>
|
</template>
|
||||||
<ui-popup v-if="deleting !== null" heading="Bitte bestätigen" @close="deleting = null">
|
<ui-popup v-if="deleting !== null" heading="Bitte bestätigen" @close="deleting = null">
|
||||||
<div>
|
<div>
|
||||||
|
@ -16,22 +17,22 @@
|
||||||
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm table">
|
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm table">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th></th>
|
<th />
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tr v-for="(activity, index) in data" :key="index">
|
<tr v-for="(activity, index) in data" :key="index">
|
||||||
<td v-text="activity.name"></td>
|
<td v-text="activity.name" />
|
||||||
<td>
|
<td>
|
||||||
<div class="flex space-x-1">
|
<div class="flex space-x-1">
|
||||||
<i-link v-tooltip="`Bearbeiten`" :href="activity.links.edit" class="inline-flex btn btn-warning btn-sm"><ui-sprite src="pencil"></ui-sprite></i-link>
|
<i-link v-tooltip="`Bearbeiten`" :href="activity.links.edit" class="inline-flex btn btn-warning btn-sm"><ui-sprite src="pencil" /></i-link>
|
||||||
<a v-tooltip="`Entfernen`" href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="deleting = activity"><ui-sprite src="trash"></ui-sprite></a>
|
<a v-tooltip="`Entfernen`" href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="deleting = activity"><ui-sprite src="trash" /></a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="px-6">
|
<div class="px-6">
|
||||||
<ui-pagination class="mt-4" :value="meta" :only="['data']"></ui-pagination>
|
<ui-pagination class="mt-4" :value="meta" :only="['data']" />
|
||||||
</div>
|
</div>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -9,15 +9,13 @@
|
||||||
<div>
|
<div>
|
||||||
<p class="mt-4">Diese Veranstaltung löschen?</p>
|
<p class="mt-4">Diese Veranstaltung löschen?</p>
|
||||||
<div class="grid grid-cols-2 gap-3 mt-6">
|
<div class="grid grid-cols-2 gap-3 mt-6">
|
||||||
<a
|
<a href="#"
|
||||||
href="#"
|
class="text-center btn btn-danger"
|
||||||
class="text-center btn btn-danger"
|
@click.prevent="
|
||||||
@click.prevent="
|
remove(deleting);
|
||||||
remove(deleting);
|
deleting = null;
|
||||||
deleting = null;
|
"
|
||||||
"
|
>Veranstaltung löschen</a>
|
||||||
>Veranstaltung löschen</a
|
|
||||||
>
|
|
||||||
<a href="#" class="text-center btn btn-primary" @click.prevent="deleting = null">Abbrechen</a>
|
<a href="#" class="text-center btn btn-primary" @click.prevent="deleting = null">Abbrechen</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,167 +24,168 @@
|
||||||
<ui-popup v-if="single !== null && single.config === null" heading="Vorlage auswählen" @close="cancel">
|
<ui-popup v-if="single !== null && single.config === null" heading="Vorlage auswählen" @close="cancel">
|
||||||
<div class="mt-3 grid gap-3 grid-cols-2">
|
<div class="mt-3 grid gap-3 grid-cols-2">
|
||||||
<a v-for="(template, index) in meta.templates" :key="index" class="py-2 px-3 border rounded bg-zinc-800 hover:bg-zinc-700 transition" href="#" @click.prevent="setTemplate(template)">
|
<a v-for="(template, index) in meta.templates" :key="index" class="py-2 px-3 border rounded bg-zinc-800 hover:bg-zinc-700 transition" href="#" @click.prevent="setTemplate(template)">
|
||||||
<span v-text="template.name"></span>
|
<span v-text="template.name" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
|
|
||||||
<ui-popup v-if="showing !== null" :heading="`Teilnehmende für ${showing.name}`" full @close="showing = null">
|
<ui-popup v-if="showing !== null" :heading="`Teilnehmende für ${showing.name}`" full @close="showing = null">
|
||||||
<participants :has-nami-field="showing.has_nami_field" :root-url="showing.links.participant_root_index" :url="showing.links.participant_index"> </participants>
|
<participants :has-nami-field="showing.has_nami_field" :root-url="showing.links.participant_root_index" :url="showing.links.participant_index" />
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
|
|
||||||
<ui-popup v-if="single !== null && single.config !== null" :heading="`Veranstaltung ${single.id ? 'bearbeiten' : 'erstellen'}`" full @close="cancel">
|
<ui-popup v-if="single !== null && single.config !== null" :heading="`Veranstaltung ${single.id ? 'bearbeiten' : 'erstellen'}`" full @close="cancel">
|
||||||
<div class="flex flex-col mt-3">
|
<div class="flex flex-col mt-3">
|
||||||
<ui-tabs v-model="active" :entries="tabs"></ui-tabs>
|
<ui-tabs v-model="active" :entries="tabs" />
|
||||||
<div v-show="active === 0" class="grid grid-cols-2 gap-3">
|
<div v-show="active === 0" class="grid grid-cols-2 gap-3">
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<f-text id="name" v-model="single.name" class="grow" label="Name" required></f-text>
|
<f-text id="name" v-model="single.name" class="grow" label="Name" required />
|
||||||
<f-switch id="is_active" v-model="single.is_active" name="is_active" label="Aktiv"></f-switch>
|
<f-switch id="is_active" v-model="single.is_active" name="is_active" label="Aktiv" />
|
||||||
<f-switch id="is_private" v-model="single.is_private" name="is_private" label="Privat"></f-switch>
|
<f-switch id="is_private" v-model="single.is_private" name="is_private" label="Privat" />
|
||||||
</div>
|
</div>
|
||||||
<f-singlefile
|
<f-singlefile id="header_image"
|
||||||
id="header_image"
|
v-model="single.header_image"
|
||||||
v-model="single.header_image"
|
label="Bild"
|
||||||
label="Bild"
|
name="header_image"
|
||||||
name="header_image"
|
parent-name="form"
|
||||||
parent-name="form"
|
:parent-id="single.id"
|
||||||
:parent-id="single.id"
|
collection="headerImage"
|
||||||
collection="headerImage"
|
required
|
||||||
required
|
/>
|
||||||
></f-singlefile>
|
<f-text id="from" v-model="single.from" type="date" label="Von" required />
|
||||||
<f-text id="from" v-model="single.from" type="date" label="Von" required></f-text>
|
<f-text id="to" v-model="single.to" type="date" label="Bis" required />
|
||||||
<f-text id="to" v-model="single.to" type="date" label="Bis" required></f-text>
|
<f-text id="registration_from" v-model="single.registration_from" type="datetime-local" label="Registrierung von" required />
|
||||||
<f-text id="registration_from" v-model="single.registration_from" type="datetime-local" label="Registrierung von" required></f-text>
|
<f-text id="registration_until" v-model="single.registration_until" type="datetime-local" label="Registrierung bis" required />
|
||||||
<f-text id="registration_until" v-model="single.registration_until" type="datetime-local" label="Registrierung bis" required></f-text>
|
<f-textarea id="excerpt"
|
||||||
<f-textarea
|
v-model="single.excerpt"
|
||||||
id="excerpt"
|
hint="Gebe hier eine kurze Beschreibung für die Veranstaltungs-Übersicht ein (Maximal 130 Zeichen)."
|
||||||
v-model="single.excerpt"
|
label="Auszug"
|
||||||
hint="Gebe hier eine kurze Beschreibung für die Veranstaltungs-Übersicht ein (Maximal 130 Zeichen)."
|
:rows="5"
|
||||||
label="Auszug"
|
required
|
||||||
:rows="5"
|
/>
|
||||||
required
|
|
||||||
></f-textarea>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="active === 1">
|
<div v-if="active === 1">
|
||||||
<f-editor id="description" v-model="single.description" name="description" label="Beschreibung" :rows="10" required></f-editor>
|
<f-editor id="description" v-model="single.description" name="description" label="Beschreibung" :rows="10" required />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="active === 2">
|
<div v-if="active === 2">
|
||||||
<ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann dieses Formular nicht mehr geändert werden. </ui-note>
|
<ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann dieses Formular nicht mehr geändert werden. </ui-note>
|
||||||
<form-builder v-model="single.config" :meta="meta"></form-builder>
|
<form-builder v-model="single.config" :meta="meta" />
|
||||||
</div>
|
</div>
|
||||||
<div v-show="active === 3" class="grid grid-cols-[1fr_300px] gap-3">
|
<div v-show="active === 3" class="grid grid-cols-[1fr_300px] gap-3">
|
||||||
<ui-note class="mt-2 col-span-full">
|
<ui-note class="mt-2 col-span-full">
|
||||||
Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br />
|
Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br>
|
||||||
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br />
|
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br>
|
||||||
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.<br />
|
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.<br>
|
||||||
Außerdem kannst du Dateien hochladen, die automatisch mit angehangen werden.
|
Außerdem kannst du Dateien hochladen, die automatisch mit angehangen werden.
|
||||||
</ui-note>
|
</ui-note>
|
||||||
<div>
|
<div>
|
||||||
<ui-tabs v-model="activeMailTab" :entries="mailTabs"></ui-tabs>
|
<ui-tabs v-model="activeMailTab" :entries="mailTabs" />
|
||||||
<f-editor v-if="activeMailTab === 0" id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" :rows="8" conditions required>
|
<f-editor v-if="activeMailTab === 0" id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" :rows="8" conditions required>
|
||||||
<template #conditions="{data, resolve}">
|
<template #conditions="{data, resolve}">
|
||||||
<conditions-form id="mail_top_conditions" :single="single" :value="data" @save="resolve"> </conditions-form>
|
<conditions-form id="mail_top_conditions" :single="single" :value="data" @save="resolve" />
|
||||||
</template>
|
</template>
|
||||||
</f-editor>
|
</f-editor>
|
||||||
<f-editor v-if="activeMailTab === 1" id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" :rows="8" conditions required>
|
<f-editor v-if="activeMailTab === 1" id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" :rows="8" conditions required>
|
||||||
<template #conditions="{data, resolve}">
|
<template #conditions="{data, resolve}">
|
||||||
<conditions-form id="mail_bottom_conditions" :single="single" :value="data" @save="resolve"> </conditions-form>
|
<conditions-form id="mail_bottom_conditions" :single="single" :value="data" @save="resolve" />
|
||||||
</template>
|
</template>
|
||||||
</f-editor>
|
</f-editor>
|
||||||
</div>
|
</div>
|
||||||
<f-multiplefiles
|
<f-multiplefiles id="mailattachments"
|
||||||
id="mailattachments"
|
v-model="single.mailattachments"
|
||||||
v-model="single.mailattachments"
|
label="Anhänge"
|
||||||
label="Anhänge"
|
name="mailattachments"
|
||||||
name="mailattachments"
|
parent-name="form"
|
||||||
parent-name="form"
|
:parent-id="single.id"
|
||||||
:parent-id="single.id"
|
collection="mailattachments"
|
||||||
collection="mailattachments"
|
class="row-span-2"
|
||||||
class="row-span-2"
|
|
||||||
>
|
>
|
||||||
<template #buttons="{file, buttonClass, iconClass}">
|
<template #buttons="{file, buttonClass, iconClass}">
|
||||||
<a v-tooltip="`Bedingungen`" href="#" :class="[buttonClass, 'bg-blue-200', 'relative']" @click.prevent="fileSettingPopup = file">
|
<a v-tooltip="`Bedingungen`" href="#" :class="[buttonClass, 'bg-blue-200', 'relative']" @click.prevent="fileSettingPopup = file">
|
||||||
<div v-if="file.properties.conditions.ifs.length" class="absolute w-2 h-2 -mt-[0.05rem] -ml-[0.05rem] flex-none bg-red-900 rounded-full top-0 left-0"></div>
|
<div v-if="file.properties.conditions.ifs.length" class="absolute w-2 h-2 -mt-[0.05rem] -ml-[0.05rem] flex-none bg-red-900 rounded-full top-0 left-0" />
|
||||||
<ui-sprite src="setting" :class="[iconClass, 'text-blue-800']"></ui-sprite>
|
<ui-sprite src="setting" :class="[iconClass, 'text-blue-800']" />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</f-multiplefiles>
|
</f-multiplefiles>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="active === 4">
|
<div v-if="active === 4">
|
||||||
<div class="grid gap-3">
|
<div class="grid gap-3">
|
||||||
<ui-remote-resource id="export" v-model="single.export.root" label="Haupt-Ordner"></ui-remote-resource>
|
<ui-remote-resource id="export" v-model="single.export.root" label="Haupt-Ordner" />
|
||||||
<f-select id="group_by" v-model="single.export.group_by" :options="allFields" label="Gruppieren nach" name="group_by"></f-select>
|
<f-select id="group_by" v-model="single.export.group_by" :options="allFields" label="Gruppieren nach" name="group_by" />
|
||||||
<f-select id="to_group_field" v-model="single.export.to_group_field" :options="allFields" label="Nach Gruppe schreiben" name="to_group_field"></f-select>
|
<f-select id="to_group_field" v-model="single.export.to_group_field" :options="allFields" label="Nach Gruppe schreiben" name="to_group_field" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="active === 5" class="grid grid-cols-2 gap-3">
|
<div v-show="active === 5" class="grid grid-cols-2 gap-3">
|
||||||
<f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention"></f-switch>
|
<f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention" />
|
||||||
<f-editor
|
<f-editor id="prevention_text"
|
||||||
id="prevention_text"
|
v-model="single.prevention_text"
|
||||||
v-model="single.prevention_text"
|
hint="Wird an die Präventions-Email angehangen, die Teilnehmende dieser Veranstaltung erhalten"
|
||||||
hint="Wird an die Präventions-Email angehangen, die Teilnehmende dieser Veranstaltung erhalten"
|
:rows="6"
|
||||||
:rows="6"
|
label="Präventions-Hinweis"
|
||||||
label="Präventions-Hinweis"
|
/>
|
||||||
></f-editor>
|
|
||||||
<ui-box heading="Bedingung für Präventions-Unterlagen">
|
<ui-box heading="Bedingung für Präventions-Unterlagen">
|
||||||
<conditions id="prevention_conditions" v-model="single.prevention_conditions" :single="single"> </conditions>
|
<conditions id="prevention_conditions" v-model="single.prevention_conditions" :single="single" />
|
||||||
</ui-box>
|
</ui-box>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a href="#" @click.prevent="submit">
|
<a href="#" @click.prevent="submit">
|
||||||
<ui-sprite src="save" class="text-zinc-400 w-6 h-6"></ui-sprite>
|
<ui-sprite src="save" class="text-zinc-400 w-6 h-6" />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
|
|
||||||
<ui-popup v-if="fileSettingPopup !== null" :heading="`Bedingungen für Datei ${fileSettingPopup.name}`" @close="fileSettingPopup = null">
|
<ui-popup v-if="fileSettingPopup !== null" :heading="`Bedingungen für Datei ${fileSettingPopup.name}`" @close="fileSettingPopup = null">
|
||||||
<conditions-form id="filesettings" :single="single" :value="fileSettingPopup.properties.conditions" @save="saveFileConditions"> </conditions-form>
|
<conditions-form id="filesettings" :single="single" :value="fileSettingPopup.properties.conditions" @save="saveFileConditions" />
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
|
|
||||||
<page-filter>
|
<page-filter>
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
|
<f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)" />
|
||||||
<f-switch id="past" :model-value="getFilter('past')" label="vergangene zeigen" name="past" size="sm" @update:model-value="setFilter('past', $event)"></f-switch>
|
<f-switch id="past" :model-value="getFilter('past')" label="vergangene zeigen" name="past" size="sm" @update:model-value="setFilter('past', $event)" />
|
||||||
<f-switch id="inactive" :model-value="getFilter('inactive')" label="inaktive zeigen" name="inactive" size="sm" @update:model-value="setFilter('inactive', $event)"></f-switch>
|
<f-switch id="inactive" :model-value="getFilter('inactive')" label="inaktive zeigen" name="inactive" size="sm" @update:model-value="setFilter('inactive', $event)" />
|
||||||
</template>
|
</template>
|
||||||
</page-filter>
|
</page-filter>
|
||||||
|
|
||||||
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
|
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Name</th>
|
<tr>
|
||||||
<th>Von</th>
|
<th>Name</th>
|
||||||
<th>Bis</th>
|
<th>Von</th>
|
||||||
<th>Anzahl TN</th>
|
<th>Bis</th>
|
||||||
<th></th>
|
<th>Anzahl TN</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tr v-for="(form, index) in data" :key="index">
|
<tbody>
|
||||||
<td>
|
<tr v-for="(form, index) in data" :key="index">
|
||||||
<div v-text="form.name"></div>
|
<td>
|
||||||
</td>
|
<div v-text="form.name" />
|
||||||
<td>
|
</td>
|
||||||
<div v-text="form.from_human"></div>
|
<td>
|
||||||
</td>
|
<div v-text="form.from_human" />
|
||||||
<td>
|
</td>
|
||||||
<div v-text="form.to_human"></div>
|
<td>
|
||||||
</td>
|
<div v-text="form.to_human" />
|
||||||
<td>
|
</td>
|
||||||
<div v-text="form.participants_count"></div>
|
<td>
|
||||||
</td>
|
<div v-text="form.participants_count" />
|
||||||
<td>
|
</td>
|
||||||
<div class="flex space-x-2">
|
<td>
|
||||||
<ui-action-button tooltip="Bearbeiten" class="btn-warning" icon="pencil" @click.prevent="edit(form)"></ui-action-button>
|
<div class="flex space-x-2">
|
||||||
<ui-action-button tooltip="Teilnehmende anzeigen" class="btn-info" icon="user" @click.prevent="showParticipants(form)"></ui-action-button>
|
<ui-action-button tooltip="Bearbeiten" class="btn-warning" icon="pencil" @click.prevent="edit(form)" />
|
||||||
<ui-action-button :href="form.links.frontend" target="_BLANK" tooltip="zur Anmeldeseite" class="btn-info" icon="eye"></ui-action-button>
|
<ui-action-button tooltip="Teilnehmende anzeigen" class="btn-info" icon="user" @click.prevent="showParticipants(form)" />
|
||||||
<ui-action-button :href="form.links.export" target="_BLANK" tooltip="als Tabellendokument exportieren" class="btn-info" icon="document"></ui-action-button>
|
<ui-action-button :href="form.links.frontend" target="_BLANK" tooltip="zur Anmeldeseite" class="btn-info" icon="eye" />
|
||||||
<ui-action-button tooltip="Löschen" class="btn-danger" icon="trash" @click.prevent="deleting = form"></ui-action-button>
|
<ui-action-button tooltip="Kopieren" class="btn-info" icon="copy" @click="onCopy(form)" />
|
||||||
</div>
|
<ui-action-button :href="form.links.export" target="_BLANK" tooltip="als Tabellendokument exportieren" class="btn-info" icon="document" />
|
||||||
</td>
|
<ui-action-button tooltip="Löschen" class="btn-danger" icon="trash" @click.prevent="deleting = form" />
|
||||||
</tr>
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="px-6">
|
<div class="px-6">
|
||||||
<ui-pagination class="mt-4" :value="meta" @reload="reloadPage"></ui-pagination>
|
<ui-pagination class="mt-4" :value="meta" @reload="reloadPage" />
|
||||||
</div>
|
</div>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
@ -201,7 +200,7 @@ import ConditionsForm from './ConditionsForm.vue';
|
||||||
import { useToast } from 'vue-toastification';
|
import { useToast } from 'vue-toastification';
|
||||||
|
|
||||||
const props = defineProps(indexProps);
|
const props = defineProps(indexProps);
|
||||||
var { meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter } = useIndex(props.data, 'form');
|
const { meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter } = useIndex(props.data, 'form');
|
||||||
const axios = inject('axios');
|
const axios = inject('axios');
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
@ -219,7 +218,7 @@ const allFields = computed(() => {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = [];
|
const result = [];
|
||||||
single.value.config.sections.forEach((section) => {
|
single.value.config.sections.forEach((section) => {
|
||||||
section.fields.forEach((field) => {
|
section.fields.forEach((field) => {
|
||||||
result.push({ id: field.key, name: field.name });
|
result.push({ id: field.key, name: field.name });
|
||||||
|
@ -229,6 +228,11 @@ const allFields = computed(() => {
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onCopy(form) {
|
||||||
|
single.value = {...meta.value.default, ...form};
|
||||||
|
single.value.id = null;
|
||||||
|
}
|
||||||
|
|
||||||
function setTemplate(template) {
|
function setTemplate(template) {
|
||||||
active.value = 0;
|
active.value = 0;
|
||||||
single.value.config = template.config;
|
single.value.config = template.config;
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<page-layout>
|
||||||
|
<page-filter>
|
||||||
|
<template #fields>
|
||||||
|
<f-multipleselect id="groups"
|
||||||
|
:model-value="getFilter('groups')"
|
||||||
|
:options="meta.groups"
|
||||||
|
label="Gruppen"
|
||||||
|
name="groups"
|
||||||
|
@update:model-value="setFilter('groups', $event)"
|
||||||
|
/>
|
||||||
|
<f-multipleselect id="activities"
|
||||||
|
:model-value="getFilter('activities')"
|
||||||
|
:options="meta.activities"
|
||||||
|
label="Tätigkeiten"
|
||||||
|
name="activities"
|
||||||
|
@update:model-value="setFilter('activities', $event)"
|
||||||
|
/>
|
||||||
|
<f-multipleselect id="subactivities"
|
||||||
|
:model-value="getFilter('subactivities')"
|
||||||
|
:options="meta.subactivities"
|
||||||
|
label="Untertätigkeiten"
|
||||||
|
name="subactivities"
|
||||||
|
@update:model-value="setFilter('subactivities', $event)"
|
||||||
|
/>
|
||||||
|
<f-select id="active"
|
||||||
|
:options="[{id: true, name: 'nur aktive'}, {id: false, name: 'nur inaktive'}]"
|
||||||
|
:model-value="getFilter('active')"
|
||||||
|
label="Aktiv"
|
||||||
|
name="active"
|
||||||
|
@update:model-value="setFilter('active', $event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</page-filter>
|
||||||
|
<div class="flex space-x-3" />
|
||||||
|
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Gruppierung</th>
|
||||||
|
<th>Tätigkeit</th>
|
||||||
|
<th>Untertätigkeit</th>
|
||||||
|
<th>Beginn</th>
|
||||||
|
<th>Ende</th>
|
||||||
|
<th>Aktiv</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(membership, index) in data" :key="index">
|
||||||
|
<td v-text="membership.member.fullname" />
|
||||||
|
<td v-text="membership.group.name" />
|
||||||
|
<td v-text="membership.activity.name" />
|
||||||
|
<td v-text="membership.subactivity?.name" />
|
||||||
|
<td v-text="membership.from.human" />
|
||||||
|
<td v-text="membership.to?.human" />
|
||||||
|
<td><ui-bool :value="membership.isActive" /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="px-6">
|
||||||
|
<ui-pagination class="mt-4" :value="meta" :only="['data', 'meta']" />
|
||||||
|
</div>
|
||||||
|
</page-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js" setup>
|
||||||
|
import {useIndex, indexProps} from '@/composables/useIndex.js';
|
||||||
|
|
||||||
|
const props = defineProps(indexProps);
|
||||||
|
|
||||||
|
const {data, meta, getFilter, setFilter} = useIndex(props.data, 'memberships');
|
||||||
|
|
||||||
|
</script>
|
|
@ -73,7 +73,9 @@ use App\Membership\Actions\IndexAction as MembershipIndexAction;
|
||||||
use App\Membership\Actions\ListForGroupAction;
|
use App\Membership\Actions\ListForGroupAction;
|
||||||
use App\Membership\Actions\MassListAction;
|
use App\Membership\Actions\MassListAction;
|
||||||
use App\Membership\Actions\MassStoreAction;
|
use App\Membership\Actions\MassStoreAction;
|
||||||
|
use App\Membership\Actions\MemberIndexAction;
|
||||||
use App\Membership\Actions\MembershipDestroyAction;
|
use App\Membership\Actions\MembershipDestroyAction;
|
||||||
|
use App\Membership\Actions\MembershipIndexAction as ActionsMembershipIndexAction;
|
||||||
use App\Membership\Actions\MembershipStoreAction;
|
use App\Membership\Actions\MembershipStoreAction;
|
||||||
use App\Membership\Actions\MembershipUpdateAction;
|
use App\Membership\Actions\MembershipUpdateAction;
|
||||||
use App\Payment\SubscriptionController;
|
use App\Payment\SubscriptionController;
|
||||||
|
@ -140,13 +142,14 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
||||||
Route::get('/member/{member}/invoice-position', PaymentPositionIndexAction::class)->name('member.invoice-position.index');
|
Route::get('/member/{member}/invoice-position', PaymentPositionIndexAction::class)->name('member.invoice-position.index');
|
||||||
|
|
||||||
// --------------------------------- membership --------------------------------
|
// --------------------------------- membership --------------------------------
|
||||||
Route::get('/member/{member}/membership', MembershipIndexAction::class)->name('member.membership.index');
|
Route::get('/member/{member}/membership', MemberIndexAction::class)->name('member.membership.index');
|
||||||
Route::post('/member/{member}/membership', MembershipStoreAction::class)->name('member.membership.store');
|
Route::post('/member/{member}/membership', MembershipStoreAction::class)->name('member.membership.store');
|
||||||
Route::patch('/membership/{membership}', MembershipUpdateAction::class)->name('membership.update');
|
Route::patch('/membership/{membership}', MembershipUpdateAction::class)->name('membership.update');
|
||||||
Route::delete('/membership/{membership}', MembershipDestroyAction::class)->name('membership.destroy');
|
Route::delete('/membership/{membership}', MembershipDestroyAction::class)->name('membership.destroy');
|
||||||
Route::post('/api/membership/member-list', ListForGroupAction::class)->name('membership.member-list');
|
Route::post('/api/membership/member-list', ListForGroupAction::class)->name('membership.member-list');
|
||||||
Route::post('/api/membership/masslist', MassStoreAction::class)->name('membership.masslist.store');
|
Route::post('/api/membership/masslist', MassStoreAction::class)->name('membership.masslist.store');
|
||||||
Route::get('/membership/masslist', MassListAction::class)->name('membership.masslist.index');
|
Route::get('/membership/masslist', MassListAction::class)->name('membership.masslist.index');
|
||||||
|
Route::get('/membership', ActionsMembershipIndexAction::class)->name('membership.index');
|
||||||
|
|
||||||
// ----------------------------------- group ----------------------------------
|
// ----------------------------------- group ----------------------------------
|
||||||
Route::get('/group', GroupIndexAction::class)->name('group.index');
|
Route::get('/group', GroupIndexAction::class)->name('group.index');
|
||||||
|
|
|
@ -63,7 +63,7 @@ it('cannot create membership when activity and subactivity doesnt belong togethe
|
||||||
])->assertJsonValidationErrors(['activity_id' => 'Tätigkeit ist nicht vorhanden.']);
|
])->assertJsonValidationErrors(['activity_id' => 'Tätigkeit ist nicht vorhanden.']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('testItDeletesAMembership', function() {
|
it('deletes a membership', function() {
|
||||||
MembershipDestroyAction::partialMock()->shouldReceive('handle')->once();
|
MembershipDestroyAction::partialMock()->shouldReceive('handle')->once();
|
||||||
MembershipStoreAction::partialMock()->shouldReceive('handle')->never();
|
MembershipStoreAction::partialMock()->shouldReceive('handle')->never();
|
||||||
ResyncAction::partialMock()->shouldReceive('handle')->once();
|
ResyncAction::partialMock()->shouldReceive('handle')->once();
|
||||||
|
@ -73,7 +73,7 @@ it('testItDeletesAMembership', function() {
|
||||||
MassStoreAction::run($member->memberships->first()->group, $member->memberships->first()->activity, $member->memberships->first()->subactivity, []);
|
MassStoreAction::run($member->memberships->first()->group, $member->memberships->first()->activity, $member->memberships->first()->subactivity, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('testItRollsbackWhenDeletionFails', function() {
|
it('rolls back when deletion fails', function() {
|
||||||
app(MembershipFake::class)
|
app(MembershipFake::class)
|
||||||
->shows(3, ['id' => 55])
|
->shows(3, ['id' => 55])
|
||||||
->shows(3, ['id' => 56])
|
->shows(3, ['id' => 56])
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Membership;
|
||||||
|
|
||||||
|
use App\Activity;
|
||||||
|
use App\Group;
|
||||||
|
use App\Member\Data\MembershipData;
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use App\Subactivity;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
|
|
||||||
|
uses(DatabaseTransactions::class);
|
||||||
|
|
||||||
|
mutates(MembershipData::class);
|
||||||
|
|
||||||
|
it('lists memberships of users', function () {
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
$activity = Activity::factory()
|
||||||
|
->hasAttached(Subactivity::factory()->name('SubAct'))
|
||||||
|
->name('Act')
|
||||||
|
->create();
|
||||||
|
$group = Group::factory()->name('GG')->create();
|
||||||
|
$member = Member::factory()->defaults()
|
||||||
|
->for($group)
|
||||||
|
->has(Membership::factory()->for($activity)->for($activity->subactivities->first())->for($group))
|
||||||
|
->male()
|
||||||
|
->name('Max Muster')
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$activity->subactivities()->first();
|
||||||
|
$this->callFilter('membership.index', [])
|
||||||
|
->assertInertia(fn(Assert $page) => $page
|
||||||
|
->has('data.data', 1)
|
||||||
|
->has('data.data.0', fn(Assert $page) => $page
|
||||||
|
->where('activity.name', 'Act')
|
||||||
|
->where('subactivity.name', 'SubAct')
|
||||||
|
->where('member.fullname', 'Max Muster')
|
||||||
|
->where('group.name', 'GG')
|
||||||
|
->where('promisedAt', null)
|
||||||
|
->where('links.update', route('membership.update', $member->memberships->first()))
|
||||||
|
->where('links.destroy', route('membership.destroy', $member->memberships->first()))
|
||||||
|
->etc()
|
||||||
|
)->has('data.meta', fn (Assert $page) => $page
|
||||||
|
->where('current_page', 1)
|
||||||
|
->where('activities.0.name', 'Act')
|
||||||
|
->where('subactivities.0.name', 'SubAct')
|
||||||
|
->where('groups.1.name', 'GG')
|
||||||
|
->where('filter.active', true)
|
||||||
|
->where('filter.groups', [])
|
||||||
|
->where('filter.activities', [])
|
||||||
|
->where('filter.subactivities', [])
|
||||||
|
->etc()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lists end date', function () {
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
$member = Member::factory()->defaults()
|
||||||
|
->has(Membership::factory()->for(Activity::factory())->for(Subactivity::factory())->for(Group::factory())->ended())
|
||||||
|
->male()
|
||||||
|
->name('Max Muster')
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->callFilter('membership.index', ['active' => null])
|
||||||
|
->assertInertia(fn(Assert $page) => $page
|
||||||
|
->has('data.data.0', fn(Assert $page) => $page
|
||||||
|
->where('to.human', now()->subDays(2)->format('d.m.Y'))
|
||||||
|
->where('links.update', route('membership.update', $member->memberships->first()))
|
||||||
|
->where('links.destroy', route('membership.destroy', $member->memberships->first()))
|
||||||
|
->etc()
|
||||||
|
)->has('data.meta', fn (Assert $page) => $page
|
||||||
|
->where('current_page', 1)
|
||||||
|
->etc()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters for active', function () {
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
Membership::factory()->defaults()->ended()->create();
|
||||||
|
Membership::factory()->defaults()->count(2)->create();
|
||||||
|
|
||||||
|
$this->callFilter('membership.index', [])->assertInertia(fn(Assert $page) => $page->has('data.data', 2));
|
||||||
|
$this->callFilter('membership.index', ['active' => null])->assertInertia(fn(Assert $page) => $page->has('data.data', 3));
|
||||||
|
$this->callFilter('membership.index', ['active' => false])->assertInertia(fn(Assert $page) => $page->has('data.data', 1));
|
||||||
|
$this->callFilter('membership.index', ['active' => true])->assertInertia(fn(Assert $page) => $page->has('data.data', 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters for group', function () {
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
$m1 = Membership::factory()->defaults()->count(2)->create();
|
||||||
|
$m2 = Membership::factory()->defaults()->create();
|
||||||
|
|
||||||
|
$this->callFilter('membership.index', [])->assertInertia(fn(Assert $page) => $page->has('data', 3));
|
||||||
|
$this->callFilter('membership.index', ['groups' => [$m1->first()->group_id]])->assertInertia(fn(Assert $page) => $page->has('data.data', 2)->where('data.meta.filter.groups', [$m1->first()->group_id]));
|
||||||
|
$this->callFilter('membership.index', ['groups' => [$m2->group_id]])->assertInertia(fn(Assert $page) => $page->has('data.data', 1)->where('data.meta.filter.groups', [$m2->group_id]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters for activity', function () {
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
$m1 = Membership::factory()->defaults()->count(2)->create();
|
||||||
|
$m2 = Membership::factory()->defaults()->create();
|
||||||
|
|
||||||
|
$this->callFilter('membership.index', [])->assertInertia(fn(Assert $page) => $page->has('data', 3));
|
||||||
|
$this->callFilter('membership.index', ['activities' => [$m1->first()->activity_id]])->assertInertia(fn(Assert $page) => $page->has('data.data', 2)->where('data.meta.filter.activities', [$m1->first()->activity_id]));
|
||||||
|
$this->callFilter('membership.index', ['activities' => [$m2->activity_id]])->assertInertia(fn(Assert $page) => $page->has('data.data', 1)->where('data.meta.filter.activities', [$m2->activity_id]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters for subactivity', function () {
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
$m1 = Membership::factory()->defaults()->count(2)->create();
|
||||||
|
$m2 = Membership::factory()->defaults()->create();
|
||||||
|
|
||||||
|
$this->callFilter('membership.index', [])->assertInertia(fn(Assert $page) => $page->has('data', 3));
|
||||||
|
$this->callFilter('membership.index', ['subactivities' => [$m1->first()->subactivity_id]])->assertInertia(fn(Assert $page) => $page->has('data.data', 2)->where('data.meta.filter.subactivities', [$m1->first()->subactivity_id]));
|
||||||
|
$this->callFilter('membership.index', ['subactivities' => [$m2->subactivity_id]])->assertInertia(fn(Assert $page) => $page->has('data.data', 1)->where('data.meta.filter.subactivities', [$m2->subactivity_id]));
|
||||||
|
});
|
|
@ -119,12 +119,24 @@ class TestCase extends BaseTestCase
|
||||||
/** @var TestResponse */
|
/** @var TestResponse */
|
||||||
$response = $this;
|
$response = $this;
|
||||||
$props = data_get($response->viewData('page'), 'props');
|
$props = data_get($response->viewData('page'), 'props');
|
||||||
|
Assert::assertTrue(Arr::has($props, $path), 'Failed that key ' . $path . ' is in Response.');
|
||||||
Assert::assertNotNull($props);
|
Assert::assertNotNull($props);
|
||||||
$json = new AssertableJsonString($props);
|
$json = new AssertableJsonString($props);
|
||||||
$json->assertPath($path, $value);
|
$json->assertPath($path, $value);
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TestResponse::macro('assertInertiaPathArray', function ($arr) {
|
||||||
|
/** @var TestResponse */
|
||||||
|
$response = $this;
|
||||||
|
|
||||||
|
foreach ($arr as $key => $value) {
|
||||||
|
$response->assertInertiaPath($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
TestResponse::macro('assertInertiaCount', function ($path, $count) {
|
TestResponse::macro('assertInertiaCount', function ($path, $count) {
|
||||||
/** @var TestResponse */
|
/** @var TestResponse */
|
||||||
$response = $this;
|
$response = $this;
|
||||||
|
@ -171,5 +183,13 @@ class TestCase extends BaseTestCase
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TestResponse::macro('assertNull', function (string $path) {
|
||||||
|
/** @var TestResponse */
|
||||||
|
$response = $this;
|
||||||
|
$response->assertHasJsonPath($path)->assertJsonPath($path, null);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue