Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
|
33149e8b79 | |
|
50878a9a3c | |
|
63799c87ec | |
|
7b00d1d3ee | |
|
d837bb48a4 | |
|
55b3bc7fe9 | |
|
bf067d7352 | |
|
ef4cf07647 | |
|
7b54d29345 | |
|
fd8c0f1085 | |
|
286148de2e | |
|
0c4e12132a | |
|
2e89002641 |
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Data;
|
||||
|
||||
use Spatie\LaravelData\Normalizers\Normalizer;
|
||||
use App\Lib\Normalizers\DateNormalizer;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\WithTransformer;
|
||||
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||
use App\Lib\Transformers\DateTransformer;
|
||||
use Carbon\Carbon;
|
||||
|
||||
#[MapInputName(SnakeCaseMapper::class)]
|
||||
#[MapOutputName(SnakeCaseMapper::class)]
|
||||
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,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');
|
||||
}
|
||||
}
|
|
@ -17,16 +17,19 @@ class MemberShowAction
|
|||
*/
|
||||
public function handle(Member $member): array
|
||||
{
|
||||
$member = Member::withPendingPayment()->with([
|
||||
'memberships.activity',
|
||||
'memberships.subactivity',
|
||||
'invoicePositions.invoice',
|
||||
'nationality',
|
||||
'region',
|
||||
'subscription',
|
||||
'courses.course',
|
||||
'bankAccount',
|
||||
])->find($member->id);
|
||||
|
||||
return [
|
||||
'data' => new MemberResource(
|
||||
$member
|
||||
->load('memberships')
|
||||
->load('invoicePositions.invoice')
|
||||
->load('nationality')
|
||||
->load('region')
|
||||
->load('subscription')
|
||||
->load('courses.course')
|
||||
),
|
||||
'data' => new MemberResource($member),
|
||||
'meta' => MemberResource::meta(),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member\Data;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||
|
||||
#[MapInputName(SnakeCaseMapper::class)]
|
||||
#[MapOutputName(SnakeCaseMapper::class)]
|
||||
class ActivityData extends Data {
|
||||
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $name,
|
||||
) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member\Data;
|
||||
|
||||
use App\Group;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||
|
||||
#[MapInputName(SnakeCaseMapper::class)]
|
||||
#[MapOutputName(SnakeCaseMapper::class)]
|
||||
class GroupData extends Data {
|
||||
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $name,
|
||||
) {}
|
||||
|
||||
public static function fromId(int $id): static {
|
||||
$group = Group::findOrFail($id);
|
||||
|
||||
return static::from([
|
||||
'name' => $group->name,
|
||||
'id' => $group->id,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member\Data;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
use App\Member\Membership;
|
||||
use App\Member\Member;
|
||||
use App\Lib\Data\DateData;
|
||||
|
||||
class MembershipData extends Data
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public ?ActivityData $activity = null,
|
||||
public ?SubactivityData $subactivity = null,
|
||||
public ?GroupData $group = null,
|
||||
public ?DateData $promisedAt = null,
|
||||
public ?DateData $from = null,
|
||||
public bool $isActive = false,
|
||||
public array $links = [],
|
||||
) {}
|
||||
|
||||
public static function fromModel(Membership $membership): static
|
||||
{
|
||||
return static::factory()->withoutMagicalCreation()->from([
|
||||
'activity' => $membership->activity,
|
||||
'subactivity' => $membership?->subactivity,
|
||||
'isActive' => $membership->isActive(),
|
||||
'from' => $membership->from,
|
||||
'group' => $membership->group,
|
||||
'promisedAt' => $membership->promised_at,
|
||||
'links' => [
|
||||
'update' => route('membership.update', $membership),
|
||||
'destroy' => route('membership.destroy', $membership),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function memberMeta(Member $member): MembershipMeta
|
||||
{
|
||||
return MembershipMeta::fromMember($member);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member\Data;
|
||||
|
||||
use App\Activity;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[MapInputName(SnakeCaseMapper::class)]
|
||||
#[MapOutputName(SnakeCaseMapper::class)]
|
||||
class MembershipMeta extends Data
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public array $links,
|
||||
public Collection $groups,
|
||||
public Collection $activities,
|
||||
public Collection $subactivities,
|
||||
public MembershipData $default,
|
||||
) {}
|
||||
|
||||
public static function fromMember(Member $member): static
|
||||
{
|
||||
$activities = Activity::with('subactivities')->get();
|
||||
|
||||
return static::factory()->withoutMagicalCreation()->from([
|
||||
'links' => [
|
||||
'store' => route('member.membership.store', ['member' => $member]),
|
||||
],
|
||||
'groups' => NestedGroup::cacheForSelect(),
|
||||
'activities' => $activities->map(fn($activity) => ['id' => $activity->id, 'name' => $activity->name]),
|
||||
'subactivities' => $activities->mapWithKeys(fn($activity) => [$activity->id => $activity->subactivities->map(fn($subactivity) => ['id' => $subactivity->id, 'name' => $subactivity->name, 'is_age_group' => $subactivity->is_age_group])]),
|
||||
'default' => MembershipData::from(['group' => $member->group_id]),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member\Data;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||
|
||||
#[MapInputName(SnakeCaseMapper::class)]
|
||||
#[MapOutputName(SnakeCaseMapper::class)]
|
||||
class SubactivityData extends Data {
|
||||
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $name,
|
||||
) {}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@ use App\Member\Data\NestedGroup;
|
|||
use App\Member\Resources\BankAccountResource;
|
||||
use App\Member\Resources\NationalityResource;
|
||||
use App\Member\Resources\RegionResource;
|
||||
use App\Membership\MembershipResource;
|
||||
use App\Member\Data\MembershipData;
|
||||
use App\Nationality;
|
||||
use App\Payment\Subscription;
|
||||
use App\Payment\SubscriptionResource;
|
||||
|
@ -75,7 +75,7 @@ class MemberResource extends JsonResource
|
|||
'pending_payment' => $this->pending_payment ? number_format($this->pending_payment / 100, 2, ',', '.') . ' €' : null,
|
||||
'age_group_icon' => $this->ageGroupMemberships->first()?->subactivity->slug,
|
||||
'courses' => CourseMemberResource::collection($this->whenLoaded('courses')),
|
||||
'memberships' => MembershipResource::collection($this->whenLoaded('memberships')),
|
||||
'memberships' => $this->relationLoaded('memberships') ? MembershipData::collect($this->memberships) : null,
|
||||
'invoicePositions' => InvoicePositionResource::collection($this->whenLoaded('invoicePositions')),
|
||||
'nationality' => new NationalityResource($this->whenLoaded('nationality')),
|
||||
'region' => new RegionResource($this->whenLoaded('region')),
|
||||
|
@ -138,7 +138,7 @@ class MemberResource extends JsonResource
|
|||
return [
|
||||
'filterActivities' => Activity::where('is_filterable', true)->get()->map(fn($a) => ['id' => $a->id, 'name' => $a->name]),
|
||||
'filterSubactivities' => Subactivity::where('is_filterable', true)->get()->map(fn($a) => ['id' => $a->id, 'name' => $a->name]),
|
||||
'formActivities' => $activities->pluck('name', 'id'),
|
||||
'formActivities' => $activities->map(fn($a) => ['id' => $a->id, 'name' => $a->name]),
|
||||
'formSubactivities' => $activities->map(function (Activity $activity) {
|
||||
return ['subactivities' => $activity->subactivities->pluck('name', 'id'), 'id' => $activity->id];
|
||||
})->pluck('subactivities', 'id'),
|
||||
|
@ -148,11 +148,11 @@ class MemberResource extends JsonResource
|
|||
})->pluck('subactivities', 'id'),
|
||||
'groups' => NestedGroup::cacheForSelect(),
|
||||
'filter' => FilterScope::fromRequest(request()->input('filter', '')),
|
||||
'courses' => Course::pluck('name', 'id'),
|
||||
'regions' => Region::forSelect(),
|
||||
'subscriptions' => Subscription::pluck('name', 'id'),
|
||||
'countries' => Country::pluck('name', 'id'),
|
||||
'genders' => Gender::pluck('name', 'id'),
|
||||
'courses' => Course::get()->map(fn($a) => ['id' => $a->id, 'name' => $a->name]),
|
||||
'regions' => Region::get()->map(fn($a) => ['id' => $a->id, 'name' => $a->name]),
|
||||
'subscriptions' => Subscription::get()->map(fn($a) => ['id' => $a->id, 'name' => $a->name]),
|
||||
'countries' => Country::get()->map(fn($c) => ['id' => $c->id, 'name' => $c->name]),
|
||||
'genders' => Gender::get()->map(fn($c) => ['id' => $c->id, 'name' => $c->name]),
|
||||
'billKinds' => BillKind::forSelect(),
|
||||
'nationalities' => Nationality::pluck('name', 'id'),
|
||||
'members' => Member::ordered()->get()->map(fn($member) => ['id' => $member->id, 'name' => $member->fullname]),
|
||||
|
|
|
@ -4,10 +4,10 @@ namespace App\Membership\Actions;
|
|||
|
||||
use App\Member\Member;
|
||||
use App\Member\Membership;
|
||||
use App\Membership\MembershipResource;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Member\Data\MembershipData;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class IndexAction
|
||||
{
|
||||
|
@ -21,11 +21,11 @@ class IndexAction
|
|||
return $member->memberships;
|
||||
}
|
||||
|
||||
public function asController(Member $member): AnonymousResourceCollection
|
||||
public function asController(Member $member): JsonResponse
|
||||
{
|
||||
return MembershipResource::collection($this->handle($member))
|
||||
->additional([
|
||||
'meta' => MembershipResource::memberMeta($member)
|
||||
]);
|
||||
return response()->json([
|
||||
'data' => MembershipData::collect($this->handle($member)),
|
||||
'meta' => MembershipData::memberMeta($member),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Membership;
|
||||
|
||||
use App\Activity;
|
||||
use App\Lib\HasMeta;
|
||||
use App\Member\Data\NestedGroup;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin \App\Member\Membership
|
||||
*/
|
||||
class MembershipResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'group_id' => $this->group_id,
|
||||
'activity_id' => $this->activity_id,
|
||||
'activity_name' => $this->activity->name,
|
||||
'subactivity_id' => $this->subactivity_id,
|
||||
'subactivity_name' => $this->subactivity?->name,
|
||||
'human_date' => $this->from->format('d.m.Y'),
|
||||
'promised_at' => $this->promised_at?->format('Y-m-d'),
|
||||
'is_active' => $this->isActive(),
|
||||
'links' => [
|
||||
'update' => route('membership.update', ['membership' => $this->getModel()]),
|
||||
'destroy' => route('membership.destroy', ['membership' => $this->getModel()]),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function memberMeta(Member $member): array
|
||||
{
|
||||
$activities = Activity::with('subactivities')->get();
|
||||
|
||||
return [
|
||||
'links' => [
|
||||
'store' => route('member.membership.store', ['member' => $member]),
|
||||
],
|
||||
'groups' => NestedGroup::cacheForSelect(),
|
||||
'activities' => $activities->map(fn ($activity) => ['id' => $activity->id, 'name' => $activity->name]),
|
||||
'subactivities' => $activities->mapWithKeys(fn ($activity) => [$activity->id => $activity->subactivities->map(fn ($subactivity) => ['id' => $subactivity->id, 'name' => $subactivity->name, 'is_age_group' => $subactivity->is_age_group])]),
|
||||
'default' => [
|
||||
'group_id' => $member->group_id,
|
||||
'activity_id' => null,
|
||||
'subactivity_id' => null,
|
||||
'promised_at' => null,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -70,6 +70,7 @@
|
|||
"spatie/laravel-data": "^4.0",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"spatie/laravel-settings": "^3.0",
|
||||
"spatie/laravel-typescript-transformer": "^2.5",
|
||||
"worksome/request-factories": "^3.0",
|
||||
"zoomyboy/laravel-nami": "dev-master",
|
||||
"zoomyboy/medialibrary-helper": "dev-master as 1.0",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b901e66375d81c04e5067a7352780f13",
|
||||
"content-hash": "e5a34643b9d5b3f0fe8e4cc9c7e9570d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amphp/amp",
|
||||
|
@ -11389,6 +11389,87 @@
|
|||
],
|
||||
"time": "2025-02-14T14:40:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-typescript-transformer",
|
||||
"version": "2.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-typescript-transformer.git",
|
||||
"reference": "a268a08341f5a5d8f80a79493642a43275d219a5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-typescript-transformer/zipball/a268a08341f5a5d8f80a79493642a43275d219a5",
|
||||
"reference": "a268a08341f5a5d8f80a79493642a43275d219a5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "^8.83|^9.30|^10.0|^11.0|^12.0",
|
||||
"php": "^8.1",
|
||||
"spatie/laravel-package-tools": "^1.12",
|
||||
"spatie/typescript-transformer": "^2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"mockery/mockery": "^1.4",
|
||||
"nesbot/carbon": "^2.63|^3.0",
|
||||
"orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^1.22|^2.0|^3.0",
|
||||
"phpunit/phpunit": "^9.0|^10.0|^11.0|^12.0",
|
||||
"spatie/data-transfer-object": "^2.0",
|
||||
"spatie/enum": "^3.0",
|
||||
"spatie/laravel-model-states": "^1.6|^2.0",
|
||||
"spatie/pest-plugin-snapshots": "^1.1|^2.0",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.2|^5.0",
|
||||
"spatie/temporary-directory": "^1.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Spatie\\LaravelTypeScriptTransformer\\TypeScriptTransformerServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\LaravelTypeScriptTransformer\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ruben Van Assche",
|
||||
"email": "ruben@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Transform your PHP structures to TypeScript types",
|
||||
"homepage": "https://github.com/spatie/typescript-transformer",
|
||||
"keywords": [
|
||||
"spatie",
|
||||
"typescript-transformer"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-typescript-transformer/issues",
|
||||
"source": "https://github.com/spatie/laravel-typescript-transformer/tree/2.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://spatie.be/open-source/support-us",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-25T14:09:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/php-structure-discoverer",
|
||||
"version": "2.3.1",
|
||||
|
@ -11529,6 +11610,78 @@
|
|||
],
|
||||
"time": "2025-01-13T13:04:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/typescript-transformer",
|
||||
"version": "2.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/typescript-transformer.git",
|
||||
"reference": "dd7cbb90b6b8c34f2aee68701cf39c5432400c0d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/typescript-transformer/zipball/dd7cbb90b6b8c34f2aee68701cf39c5432400c0d",
|
||||
"reference": "dd7cbb90b6b8c34f2aee68701cf39c5432400c0d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nikic/php-parser": "^4.18|^5.0",
|
||||
"php": "^8.1",
|
||||
"phpdocumentor/type-resolver": "^1.6.2",
|
||||
"symfony/process": "^5.2|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.40",
|
||||
"larapack/dd": "^1.1",
|
||||
"myclabs/php-enum": "^1.7",
|
||||
"pestphp/pest": "^1.22",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"spatie/data-transfer-object": "^2.0",
|
||||
"spatie/enum": "^3.0",
|
||||
"spatie/pest-plugin-snapshots": "^1.1",
|
||||
"spatie/temporary-directory": "^1.2|^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\TypeScriptTransformer\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ruben Van Assche",
|
||||
"email": "ruben@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Transform your PHP structures to TypeScript types",
|
||||
"homepage": "https://github.com/spatie/typescript-transformer",
|
||||
"keywords": [
|
||||
"spatie",
|
||||
"typescript-transformer"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/typescript-transformer/issues",
|
||||
"source": "https://github.com/spatie/typescript-transformer/tree/2.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://spatie.be/open-source/support-us",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-25T13:53:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "staabm/side-effects-detector",
|
||||
"version": "1.0.5",
|
||||
|
@ -14702,7 +14855,7 @@
|
|||
"dist": {
|
||||
"type": "path",
|
||||
"url": "./packages/tex",
|
||||
"reference": "48251272de62e3fea044a7ad31e1a411c15eb4c6"
|
||||
"reference": "ed283d97ca7680b3c27b2d75da9937f4f379e321"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -15792,32 +15945,7 @@
|
|||
"time": "2025-03-03T07:12:39+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "league/flysystem-webdav",
|
||||
"version": "9999999-dev",
|
||||
"alias": "3.28.0",
|
||||
"alias_normalized": "3.28.0.0"
|
||||
},
|
||||
{
|
||||
"package": "zoomyboy/medialibrary-helper",
|
||||
"version": "9999999-dev",
|
||||
"alias": "1.0",
|
||||
"alias_normalized": "1.0.0.0"
|
||||
},
|
||||
{
|
||||
"package": "zoomyboy/table-document",
|
||||
"version": "9999999-dev",
|
||||
"alias": "1.0",
|
||||
"alias_normalized": "1.0.0.0"
|
||||
},
|
||||
{
|
||||
"package": "zoomyboy/tex",
|
||||
"version": "dev-main",
|
||||
"alias": "1.0",
|
||||
"alias_normalized": "1.0.0.0"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"league/flysystem-webdav": 20,
|
||||
|
@ -15832,5 +15960,5 @@
|
|||
"php": "^8.3"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.2.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
* The paths where typescript-transformer will look for PHP classes
|
||||
* to transform, this will be the `app` path by default.
|
||||
*/
|
||||
|
||||
'auto_discover_types' => [
|
||||
app_path(),
|
||||
],
|
||||
|
||||
/*
|
||||
* Collectors will search for classes in the `auto_discover_types` paths and choose the correct
|
||||
* transformer to transform them. By default, we include a DefaultCollector which will search
|
||||
* for @typescript annotated and #[TypeScript] attributed classes to transform.
|
||||
*/
|
||||
|
||||
'collectors' => [
|
||||
Spatie\TypeScriptTransformer\Collectors\DefaultCollector::class,
|
||||
Spatie\TypeScriptTransformer\Collectors\EnumCollector::class,
|
||||
Spatie\LaravelData\Support\TypeScriptTransformer\DataTypeScriptCollector::class,
|
||||
],
|
||||
|
||||
/*
|
||||
* Transformers take PHP classes(e.g., enums) as an input and will output
|
||||
* a TypeScript representation of the PHP class.
|
||||
*/
|
||||
|
||||
'transformers' => [
|
||||
Spatie\LaravelTypeScriptTransformer\Transformers\SpatieStateTransformer::class,
|
||||
Spatie\TypeScriptTransformer\Transformers\EnumTransformer::class,
|
||||
Spatie\TypeScriptTransformer\Transformers\SpatieEnumTransformer::class,
|
||||
Spatie\LaravelData\Support\TypeScriptTransformer\DataTypeScriptTransformer::class,
|
||||
Spatie\LaravelTypeScriptTransformer\Transformers\DtoTransformer::class,
|
||||
],
|
||||
|
||||
/*
|
||||
* In your classes, you sometimes have types that should always be replaced
|
||||
* by the same TypeScript representations. For example, you can replace a
|
||||
* Datetime always with a string. You define these replacements here.
|
||||
*/
|
||||
|
||||
'default_type_replacements' => [
|
||||
DateTime::class => 'string',
|
||||
DateTimeImmutable::class => 'string',
|
||||
Carbon\CarbonInterface::class => 'string',
|
||||
Carbon\CarbonImmutable::class => 'string',
|
||||
Carbon\Carbon::class => 'string',
|
||||
],
|
||||
|
||||
/*
|
||||
* The package will write the generated TypeScript to this file.
|
||||
*/
|
||||
|
||||
'output_file' => resource_path('types/generated.d.ts'),
|
||||
|
||||
/*
|
||||
* When the package is writing types to the output file, a writer is used to
|
||||
* determine the format. By default, this is the `TypeDefinitionWriter`.
|
||||
* But you can also use the `ModuleWriter` or implement your own.
|
||||
*/
|
||||
|
||||
'writer' => Spatie\TypeScriptTransformer\Writers\TypeDefinitionWriter::class,
|
||||
|
||||
/*
|
||||
* The generated TypeScript file can be formatted. We ship a Prettier formatter
|
||||
* out of the box: `PrettierFormatter` but you can also implement your own one.
|
||||
* The generated TypeScript will not be formatted when no formatter was set.
|
||||
*/
|
||||
|
||||
'formatter' => null,
|
||||
|
||||
/*
|
||||
* Enums can be transformed into types or native TypeScript enums, by default
|
||||
* the package will transform them to types.
|
||||
*/
|
||||
|
||||
'transform_to_native_enums' => false,
|
||||
|
||||
/*
|
||||
* By default, this package will convert PHP nullable properties to TypeScript
|
||||
* types using a `null` type union. Setting `transform_null_to_optional` will
|
||||
* make them optional instead.
|
||||
*/
|
||||
|
||||
'transform_null_to_optional' => false,
|
||||
];
|
|
@ -103,6 +103,8 @@ services:
|
|||
- ./data/redis:/data
|
||||
|
||||
meilisearch:
|
||||
ports:
|
||||
- "7700:7700"
|
||||
image: getmeili/meilisearch:v1.6
|
||||
volumes:
|
||||
- ./data/meilisearch:/meili_data
|
||||
|
|
|
@ -68,11 +68,6 @@ parameters:
|
|||
count: 1
|
||||
path: app/Member/MemberRequest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Membership\\\\MembershipResource\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: app/Membership/MembershipResource.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Payment\\\\SubscriptionResource\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
.custom-table {
|
||||
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;
|
||||
}
|
||||
|
||||
& > tr {
|
||||
& > tr, & > tbody > tr {
|
||||
@apply text-gray-200 transition-all duration-300 rounded hover:bg-gray-800;
|
||||
& > td {
|
||||
@apply py-1 px-6;
|
||||
|
@ -12,10 +12,10 @@
|
|||
}
|
||||
|
||||
&.custom-table-sm {
|
||||
& > thead > th {
|
||||
& > thead > th, & > thead > tr > th {
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
& > tr {
|
||||
& > tr, & > tbody > tr {
|
||||
& > td {
|
||||
@apply py-1 px-3;
|
||||
}
|
||||
|
@ -23,20 +23,11 @@
|
|||
}
|
||||
|
||||
&.custom-table-light {
|
||||
& > thead > th {
|
||||
& > thead > th, & > thead > tr > th {
|
||||
@apply border-gray-500;
|
||||
}
|
||||
& > td {
|
||||
&:hover {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
& > tr, & > tbody > tr {
|
||||
@apply hover:bg-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.custom-table > * {
|
||||
display: table-row;
|
||||
}
|
||||
.custom-table > * > * {
|
||||
display: table-cell;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<label class="flex flex-col group" :for="id" :class="sizeClass(size)">
|
||||
<f-label v-if="label" :required="required" :value="label"></f-label>
|
||||
<f-label v-if="label" :required="required" :value="label" />
|
||||
<div class="relative flex-none flex">
|
||||
<select v-model="inner" :disabled="disabled" :name="name" :class="[fieldHeight, fieldAppearance, selectAppearance]">
|
||||
<option v-if="placeholder" :value="def">{{ placeholder }}</option>
|
||||
<option v-for="option in parsedOptions" :key="option.id" :value="option.id">{{ option.name }}</option>
|
||||
</select>
|
||||
<f-hint v-if="hint" :value="hint"></f-hint>
|
||||
<f-hint v-if="hint" :value="hint" />
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from 'vue';
|
||||
import useFieldSize from '../../composables/useFieldSize.js';
|
||||
import map from 'lodash/map';
|
||||
|
@ -59,11 +59,6 @@ const props = defineProps({
|
|||
default: '--kein--',
|
||||
type: String,
|
||||
},
|
||||
def: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
name: {
|
||||
required: true,
|
||||
},
|
||||
|
@ -79,8 +74,8 @@ const parsedOptions = computed(() => {
|
|||
return Array.isArray(props.options)
|
||||
? props.options
|
||||
: map(props.options, (value, key) => {
|
||||
return {name: value, id: key};
|
||||
});
|
||||
return {name: value, id: key};
|
||||
});
|
||||
});
|
||||
|
||||
const def = ref('iu1Feixah5AeKai3ewooJahjeaegee0eiD4maeth1oul4Hei7u');
|
||||
|
|
|
@ -1,28 +1,14 @@
|
|||
<template>
|
||||
<a v-tooltip="tooltip" :href="href" :target="blank ? '_BLANK' : '_SELF'" class="inline-flex btn btn-sm">
|
||||
<ui-sprite :src="icon"></ui-sprite>
|
||||
<ui-sprite :src="icon" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
tooltip: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
default: () => '#',
|
||||
required: false,
|
||||
},
|
||||
blank: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
required: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
<script lang="ts" setup>
|
||||
const {tooltip, icon, blank = false, href = '#'} = defineProps<{
|
||||
tooltip: string,
|
||||
href?: string,
|
||||
blank?: boolean,
|
||||
icon: string,
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
@ -2,6 +2,5 @@
|
|||
<svg v-bind="$attrs" class="fill-current"><use :xlink:href="`/sprite.svg#${$attrs.src}`" /></svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
modelValue: number,
|
||||
entries: {title: string}[]
|
||||
|
|
|
@ -1,84 +1,73 @@
|
|||
import {ref, inject, onBeforeUnmount} from 'vue';
|
||||
import {router} from '@inertiajs/vue3';
|
||||
import type {Ref} from 'vue';
|
||||
import useQueueEvents from './useQueueEvents.js';
|
||||
import { Axios } from 'axios';
|
||||
|
||||
export function useApiIndex(firstUrl, siteName = null) {
|
||||
const axios = inject('axios');
|
||||
export function useApiIndex<D, M extends Custom.PageMetadata>(firstUrl, siteName = null) {
|
||||
const axios = inject<Axios>('axios');
|
||||
|
||||
if (siteName !== null) {
|
||||
var {startListener, stopListener} = useQueueEvents(siteName, () => reload());
|
||||
}
|
||||
|
||||
const single = ref(null);
|
||||
const single: Ref<D|null> = ref(null);
|
||||
|
||||
const url = ref(firstUrl);
|
||||
const inner = {
|
||||
data: ref([]),
|
||||
meta: ref({}),
|
||||
const inner: {data: Ref<D[]|null>, meta: Ref<M|null>} = {
|
||||
data: ref(null),
|
||||
meta: ref(null),
|
||||
};
|
||||
|
||||
async function reload(resetPage = true, p = {}) {
|
||||
var params = {
|
||||
page: resetPage ? 1 : inner.meta.value.current_page,
|
||||
const params = {
|
||||
page: resetPage ? 1 : inner.meta.value?.current_page,
|
||||
...p,
|
||||
};
|
||||
|
||||
var response = (await axios.get(url.value, {params})).data;
|
||||
const response = (await axios.get(url.value, {params})).data;
|
||||
inner.data.value = response.data;
|
||||
inner.meta.value = response.meta;
|
||||
}
|
||||
|
||||
async function reloadPage(page, p = {}) {
|
||||
inner.meta.value.current_page = page;
|
||||
async function reloadPage(page: number, p = {}) {
|
||||
if (inner.meta.value?.current_page) {
|
||||
inner.meta.value.current_page = page;
|
||||
}
|
||||
await reload(false, p);
|
||||
}
|
||||
|
||||
function create() {
|
||||
single.value = JSON.parse(JSON.stringify(inner.meta.value.default));
|
||||
single.value = JSON.parse(JSON.stringify(inner.meta.value?.default));
|
||||
}
|
||||
|
||||
function edit(model) {
|
||||
function edit(model: D) {
|
||||
single.value = JSON.parse(JSON.stringify(model));
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (single.value === null) {
|
||||
return;
|
||||
}
|
||||
single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(inner.meta.value.links.store, single.value);
|
||||
await reload();
|
||||
single.value = null;
|
||||
}
|
||||
|
||||
async function remove(model) {
|
||||
async function remove(model: D) {
|
||||
await axios.delete(model.links.destroy);
|
||||
await reload();
|
||||
}
|
||||
|
||||
function can(permission) {
|
||||
return inner.meta.value.can[permission];
|
||||
}
|
||||
|
||||
function toFilterString(data) {
|
||||
return btoa(encodeURIComponent(JSON.stringify(data)));
|
||||
}
|
||||
|
||||
function requestCallback(successMessage, failureMessage) {
|
||||
return {
|
||||
onSuccess: () => {
|
||||
this.$success(successMessage);
|
||||
reload(false);
|
||||
},
|
||||
onFailure: () => {
|
||||
this.$error(failureMessage);
|
||||
reload(false);
|
||||
},
|
||||
preserveState: true,
|
||||
};
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
single.value = null;
|
||||
}
|
||||
|
||||
function updateUrl(newUrl) {
|
||||
function updateUrl(newUrl: string) {
|
||||
url.value = newUrl;
|
||||
}
|
||||
|
||||
|
@ -95,8 +84,6 @@ export function useApiIndex(firstUrl, siteName = null) {
|
|||
edit,
|
||||
reload,
|
||||
reloadPage,
|
||||
can,
|
||||
requestCallback,
|
||||
router,
|
||||
submit,
|
||||
remove,
|
|
@ -8,10 +8,10 @@
|
|||
</page-header>
|
||||
|
||||
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
|
||||
<f-text id="completed_at" v-model="single.completed_at" type="date" label="Datum" required></f-text>
|
||||
<f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required></f-select>
|
||||
<f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required></f-text>
|
||||
<f-text id="organizer" v-model="single.organizer" label="Veranstalter" required></f-text>
|
||||
<f-text id="completed_at" v-model="single.completed_at" type="date" label="Datum" required />
|
||||
<f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required />
|
||||
<f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required />
|
||||
<f-text id="organizer" v-model="single.organizer" label="Veranstalter" required />
|
||||
<button type="submit" class="btn btn-primary">Absenden</button>
|
||||
</form>
|
||||
|
||||
|
@ -23,18 +23,18 @@
|
|||
<th>Veranstaltung</th>
|
||||
<th>Veranstalter</th>
|
||||
<th>Datum</th>
|
||||
<th></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr v-for="(course, index) in data" :key="index">
|
||||
<td v-text="course.course_name"></td>
|
||||
<td v-text="course.event_name"></td>
|
||||
<td v-text="course.organizer"></td>
|
||||
<td v-text="course.completed_at_human"></td>
|
||||
<td v-text="course.course_name" />
|
||||
<td v-text="course.event_name" />
|
||||
<td v-text="course.organizer" />
|
||||
<td v-text="course.completed_at_human" />
|
||||
<td class="flex">
|
||||
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(course)"><ui-sprite src="pencil"></ui-sprite></a>
|
||||
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash"></ui-sprite></a>
|
||||
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(course)"><ui-sprite src="pencil" /></a>
|
||||
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -44,7 +44,7 @@
|
|||
|
||||
<script lang="js" setup>
|
||||
defineEmits(['close']);
|
||||
import { useApiIndex } from '../../composables/useApiIndex.js';
|
||||
import { useApiIndex } from '../../composables/useApiIndex.ts';
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<page-header title="Zahlungen" @close="$emit('close')"> </page-header>
|
||||
<page-header title="Zahlungen" @close="$emit('close')" />
|
||||
|
||||
<div class="grow">
|
||||
<table class="custom-table custom-table-light custom-table-sm text-sm">
|
||||
|
@ -10,15 +10,15 @@
|
|||
</thead>
|
||||
|
||||
<tr v-for="(position, index) in data" :key="index">
|
||||
<td v-text="position.description"></td>
|
||||
<td v-text="position.invoice.status"></td>
|
||||
<td v-text="position.price_human"></td>
|
||||
<td v-text="position.description" />
|
||||
<td v-text="position.invoice.status" />
|
||||
<td v-text="position.price_human" />
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js" setup>
|
||||
<script lang="ts" setup>
|
||||
defineEmits(['close']);
|
||||
import { useApiIndex } from '../../composables/useApiIndex.js';
|
||||
|
||||
|
|
|
@ -6,60 +6,62 @@
|
|||
</template>
|
||||
</page-header>
|
||||
|
||||
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
|
||||
<f-select id="group_id" v-model="single.group_id" name="group_id" :options="meta.groups" label="Gruppierung" required></f-select>
|
||||
<f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="meta.activities" label="Tätigkeit" required></f-select>
|
||||
<f-select
|
||||
v-if="single.activity_id"
|
||||
id="subactivity_id"
|
||||
:model-value="single.subactivity_id"
|
||||
name="subactivity_id"
|
||||
:options="meta.subactivities[single.activity_id]"
|
||||
label="Untertätigkeit"
|
||||
@update:modelValue="setSubactivityId(single, $event)"
|
||||
></f-select>
|
||||
<f-switch
|
||||
v-if="displayPromisedAt"
|
||||
id="has_promise"
|
||||
name="has_promise"
|
||||
:model-value="single.promised_at !== null"
|
||||
label="Hat Versprechen"
|
||||
@update:modelValue="setPromisedAtSwitch(single, $event)"
|
||||
></f-switch>
|
||||
<f-text v-show="displayPromisedAt && single.promised_at !== null" id="promised_at" v-model="single.promised_at" type="date" label="Versprechensdatum" size="sm"></f-text>
|
||||
<form v-if="single && meta !== null" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
|
||||
<f-select id="group_id" v-model="single.group" name="group_id" :options="meta.groups" label="Gruppierung" required />
|
||||
<f-select id="activity_id" v-model="single.activity" name="activity_id" :options="meta.activities" label="Tätigkeit" required />
|
||||
<f-select v-if="single.activity"
|
||||
id="subactivity_id"
|
||||
:model-value="single.subactivity"
|
||||
name="subactivity_id"
|
||||
:options="meta.subactivities[single.activity.id]"
|
||||
label="Untertätigkeit"
|
||||
@update:model-value="setSubactivityId(single, $event)"
|
||||
/>
|
||||
<f-switch v-if="displayPromisedAt"
|
||||
id="has_promise"
|
||||
name="has_promise"
|
||||
:model-value="single.promisedAt !== null"
|
||||
label="Hat Versprechen"
|
||||
@update:model-value="setPromisedAtSwitch(single, $event)"
|
||||
/>
|
||||
<f-text v-show="displayPromisedAt && single.promisedAt !== null" id="promised_at" v-model="single.promisedAt" type="date" label="Versprechensdatum" size="sm" />
|
||||
<button type="submit" class="btn btn-primary">Absenden</button>
|
||||
</form>
|
||||
|
||||
<div v-else class="grow">
|
||||
<table class="custom-table custom-table-light custom-table-sm text-sm">
|
||||
<thead>
|
||||
<th>Tätigkeit</th>
|
||||
<th>Untertätigkeit</th>
|
||||
<th>Datum</th>
|
||||
<th>Aktiv</th>
|
||||
<th></th>
|
||||
<tr>
|
||||
<th>Tätigkeit</th>
|
||||
<th>Untertätigkeit</th>
|
||||
<th>Datum</th>
|
||||
<th>Aktiv</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr v-for="(membership, index) in data" :key="index">
|
||||
<td v-text="membership.activity_name"></td>
|
||||
<td v-text="membership.subactivity_name"></td>
|
||||
<td v-text="membership.human_date"></td>
|
||||
<td><ui-boolean-display :value="membership.is_active" dark></ui-boolean-display></td>
|
||||
<td class="flex space-x-1">
|
||||
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil"></ui-sprite></a>
|
||||
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash"></ui-sprite></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr v-for="(membership, index) in data" :key="index">
|
||||
<td v-text="membership.activity?.name" />
|
||||
<td v-text="membership.subactivity?.name" />
|
||||
<td v-text="membership.from?.human" />
|
||||
<td><ui-boolean-display :value="membership.isActive" dark /></td>
|
||||
<td class="flex space-x-1">
|
||||
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil" /></a>
|
||||
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js" setup>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
defineEmits(['close']);
|
||||
import { useApiIndex } from '../../composables/useApiIndex.js';
|
||||
import { useApiIndex } from '../../composables/useApiIndex.ts';
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
|
@ -67,14 +69,14 @@ const props = defineProps({
|
|||
required: true,
|
||||
},
|
||||
});
|
||||
const { data, meta, reload, single, create, edit, submit, remove } = useApiIndex(props.url, 'membership');
|
||||
const { data, meta, reload, single, create, edit, submit, remove } = useApiIndex<App.Member.Data.MembershipData, App.Member.Data.MembershipMeta>(props.url, 'membership');
|
||||
|
||||
function setPromisedAtSwitch(single, value) {
|
||||
single.promised_at = value ? dayjs().format('YYYY-MM-DD') : null;
|
||||
}
|
||||
|
||||
const displayPromisedAt = computed(function () {
|
||||
if (!single.value || !single.value.activity_id || !single.value.subactivity_id) {
|
||||
if (!single.value || !single.value.activity || !single.value.subactivity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
<member-filter-fields :model-value="filter" @update:model-value="setFilterObject($event)" />
|
||||
</template>
|
||||
<template #buttons>
|
||||
<f-text id="search" :model-value="filter.search" label="Suchen …" size="sm" @update:model-value="setFilterObject({...filter, search: $event})"></f-text>
|
||||
<f-text id="search" :model-value="filter.search" label="Suchen …" size="sm" @update:model-value="setFilterObject({...filter, search: $event})" />
|
||||
<button class="btn btn-primary label mr-2" @click.prevent="exportMembers">
|
||||
<ui-sprite class="w-3 h-3 xl:mr-2" src="save"></ui-sprite>
|
||||
<ui-sprite class="w-3 h-3 xl:mr-2" src="save" />
|
||||
<span class="hidden xl:inline">Exportieren</span>
|
||||
</button>
|
||||
</template>
|
||||
|
@ -32,7 +32,7 @@
|
|||
|
||||
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm hidden md:table">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th />
|
||||
<th>Nachname</th>
|
||||
<th>Vorname</th>
|
||||
<th class="!hidden 2xl:!table-cell">Ort</th>
|
||||
|
@ -40,26 +40,26 @@
|
|||
<th class="!hidden xl:!table-cell">Alter</th>
|
||||
<th v-if="hasModule('bill')" class="!hidden xl:!table-cell">Rechnung</th>
|
||||
<th v-if="hasModule('bill')">Ausstand</th>
|
||||
<th></th>
|
||||
<th />
|
||||
</thead>
|
||||
|
||||
<tr v-for="(member, index) in data" :key="index">
|
||||
<td><ui-age-groups :member="member"></ui-age-groups></td>
|
||||
<td v-text="member.lastname"></td>
|
||||
<td v-text="member.firstname"></td>
|
||||
<td class="!hidden 2xl:!table-cell" v-text="member.full_address"></td>
|
||||
<td><ui-age-groups :member="member" /></td>
|
||||
<td v-text="member.lastname" />
|
||||
<td v-text="member.firstname" />
|
||||
<td class="!hidden 2xl:!table-cell" v-text="member.full_address" />
|
||||
<td>
|
||||
<tags :member="member"></tags>
|
||||
<tags :member="member" />
|
||||
</td>
|
||||
<td class="!hidden xl:!table-cell" v-text="member.age"></td>
|
||||
<td class="!hidden xl:!table-cell" v-text="member.age" />
|
||||
<td v-if="hasModule('bill')" class="!hidden xl:!table-cell">
|
||||
<ui-label :value="member.bill_kind_name" fallback="kein"></ui-label>
|
||||
<ui-label :value="member.bill_kind_name" fallback="kein" />
|
||||
</td>
|
||||
<td v-if="hasModule('bill')">
|
||||
<ui-label :value="member.pending_payment" fallback="---"></ui-label>
|
||||
<ui-label :value="member.pending_payment" fallback="---" />
|
||||
</td>
|
||||
<td>
|
||||
<actions :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)"></actions>
|
||||
<actions :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -67,28 +67,28 @@
|
|||
<div class="md:hidden p-3 grid gap-3">
|
||||
<ui-box v-for="(member, index) in data" :key="index" class="relative" :heading="member.fullname">
|
||||
<template #in-title>
|
||||
<ui-age-groups class="ml-2" :member="member" icon-class="w-4 h-4"></ui-age-groups>
|
||||
<ui-age-groups class="ml-2" :member="member" icon-class="w-4 h-4" />
|
||||
</template>
|
||||
<div class="text-xs text-gray-200" v-text="member.full_address"></div>
|
||||
<div class="text-xs text-gray-200" v-text="member.full_address" />
|
||||
<div class="flex items-center mt-1 space-x-4">
|
||||
<tags :member="member"></tags>
|
||||
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment" fallback=""></ui-label>
|
||||
<tags :member="member" />
|
||||
<ui-label v-show="hasModule('bill')" class="text-gray-100 block" :value="member.pending_payment" fallback="" />
|
||||
</div>
|
||||
<actions class="mt-2" :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)"> </actions>
|
||||
<actions class="mt-2" :member="member" @sidebar="openSidebar($event, member)" @remove="remove(member)" />
|
||||
<div class="absolute right-0 top-0 h-full flex items-center mr-2">
|
||||
<i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron" class="w-6 h-6 text-teal-100 -rotate-90"></ui-sprite></i-link>
|
||||
<i-link v-tooltip="`Details`" :href="member.links.show"><ui-sprite src="chevron" class="w-6 h-6 text-teal-100 -rotate-90" /></i-link>
|
||||
</div>
|
||||
</ui-box>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<ui-sidebar v-if="single !== null" @close="closeSidebar">
|
||||
<member-invoice-positions v-if="single.type === 'invoicePosition'" :url="single.model.links.invoiceposition_index" @close="closeSidebar"></member-invoice-positions>
|
||||
<member-memberships v-if="single.type === 'membership'" :url="single.model.links.membership_index" @close="closeSidebar"></member-memberships>
|
||||
<member-courses v-if="single.type === 'courses'" :url="single.model.links.course_index" @close="closeSidebar"></member-courses>
|
||||
<member-invoice-positions v-if="single.type === 'invoicePosition'" :url="single.model.links.invoiceposition_index" @close="closeSidebar" />
|
||||
<member-memberships v-if="single.type === 'membership'" :url="single.model.links.membership_index" @close="closeSidebar" />
|
||||
<member-courses v-if="single.type === 'courses'" :url="single.model.links.course_index" @close="closeSidebar" />
|
||||
</ui-sidebar>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
@ -107,7 +107,7 @@ const single = ref(null);
|
|||
const deleting = ref(null);
|
||||
|
||||
const props = defineProps(indexProps);
|
||||
var {router, data, meta, filter, setFilterObject, filterString, reloadPage} = useIndex(props.data, 'member');
|
||||
const {router, data, meta, filter, setFilterObject, filterString, reloadPage} = useIndex(props.data, 'member');
|
||||
|
||||
function exportMembers() {
|
||||
window.open(`/member-export?filter=${filterString.value}`);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
declare namespace Custom {
|
||||
export type PageMetadata = {
|
||||
current_page: number;
|
||||
default: object;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,487 @@
|
|||
declare namespace App.Contribution.Data {
|
||||
export type MemberData = {
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
address: string;
|
||||
zip: string;
|
||||
location: string;
|
||||
birthday: string;
|
||||
gender: any | null;
|
||||
isLeader: boolean;
|
||||
};
|
||||
}
|
||||
declare namespace App.Contribution.Documents {
|
||||
export type BdkjHesse = {
|
||||
dateFrom: string;
|
||||
dateUntil: string;
|
||||
zipLocation: string;
|
||||
country: any | null;
|
||||
members: any;
|
||||
eventName: string;
|
||||
filename: string | null;
|
||||
type: string;
|
||||
};
|
||||
export type CityFrankfurtMainDocument = {
|
||||
fromName: string;
|
||||
dateFrom: string;
|
||||
dateUntil: string;
|
||||
zipLocation: string;
|
||||
country: any | null;
|
||||
members: any;
|
||||
eventName: string;
|
||||
filename: string | null;
|
||||
type: string;
|
||||
};
|
||||
export type CityRemscheidDocument = {
|
||||
dateFrom: string;
|
||||
dateUntil: string;
|
||||
zipLocation: string;
|
||||
country: any | null;
|
||||
leaders: any;
|
||||
children: any;
|
||||
filename: string | null;
|
||||
type: string;
|
||||
eventName: string;
|
||||
};
|
||||
export type CitySolingenDocument = {
|
||||
fromName: string;
|
||||
dateFrom: string;
|
||||
dateUntil: string;
|
||||
zipLocation: string;
|
||||
members: Array<App.Contribution.Data.MemberData>;
|
||||
eventName: string;
|
||||
type: string;
|
||||
};
|
||||
export type ContributionDocument = {
|
||||
};
|
||||
export type RdpNrwDocument = {
|
||||
dateFrom: string;
|
||||
dateUntil: string;
|
||||
zipLocation: string;
|
||||
country: any | null;
|
||||
members: any;
|
||||
filename: string | null;
|
||||
type: string;
|
||||
eventName: string;
|
||||
};
|
||||
export type WuppertalDocument = {
|
||||
dateFrom: string;
|
||||
dateUntil: string;
|
||||
zipLocation: string;
|
||||
country: any | null;
|
||||
members: any;
|
||||
filename: string | null;
|
||||
type: string;
|
||||
eventName: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Efz {
|
||||
export type EfzDocument = {
|
||||
name: string;
|
||||
slug: string;
|
||||
secondLine: string;
|
||||
now: string;
|
||||
sender: App.Pdf.Sender;
|
||||
member: any;
|
||||
};
|
||||
}
|
||||
declare namespace App.Fileshare.ConnectionTypes {
|
||||
export type ConnectionType = {
|
||||
};
|
||||
export type NextcloudConnection = {
|
||||
user: string;
|
||||
password: string;
|
||||
base_url: string;
|
||||
};
|
||||
export type OwncloudConnection = {
|
||||
user: string;
|
||||
password: string;
|
||||
base_url: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Fileshare.Data {
|
||||
export type FileshareResourceData = {
|
||||
connection_id: number;
|
||||
resource: string;
|
||||
};
|
||||
export type ResourceData = {
|
||||
name: string;
|
||||
path: string;
|
||||
parent: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Form.Data {
|
||||
export type ColumnData = {
|
||||
mobile: number;
|
||||
tablet: number;
|
||||
desktop: number;
|
||||
};
|
||||
export type ExportData = {
|
||||
root: App.Fileshare.Data.FileshareResourceData | null;
|
||||
group_by: string | null;
|
||||
to_group_field: string | null;
|
||||
};
|
||||
export type FormConfigData = {
|
||||
sections: Array<App.Form.Data.SectionData>;
|
||||
};
|
||||
export type SectionData = {
|
||||
name: string;
|
||||
fields: Array<App.Form.Fields.Field>;
|
||||
intro: string | null;
|
||||
};
|
||||
}
|
||||
declare namespace App.Form.Enums {
|
||||
export type NamiType = 'Vorname' | 'Nachname' | 'Spitzname' | 'Geburtstag' | 'Bezirk' | 'Stamm' | 'E-Mail-Adresse' | 'Adresse' | 'PLZ' | 'Ort' | 'Geschlecht' | 'Handynummer' | 'Alter (zum Zeitpunkt der Anmeldung)' | 'Alter (zum Zeitpunkt der Veranstaltung)';
|
||||
export type SpecialType = 'Vorname' | 'Nachname' | 'E-Mail-Adresse';
|
||||
}
|
||||
declare namespace App.Form.Fields {
|
||||
export type CheckboxField = {
|
||||
required: boolean;
|
||||
description: string;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type CheckboxesField = {
|
||||
options: { [key: number]: string };
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type DateField = {
|
||||
required: boolean;
|
||||
max_today: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type DropdownField = {
|
||||
required: boolean;
|
||||
options: { [key: number]: string };
|
||||
allowcustom: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type EmailField = {
|
||||
required: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type Field = {
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type GroupField = {
|
||||
required: boolean;
|
||||
parent_field: string | null;
|
||||
parent_group: number | null;
|
||||
has_empty_option: boolean;
|
||||
empty_option_value: string | null;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type NamiField = {
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type NumberField = {
|
||||
required: boolean;
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type RadioField = {
|
||||
required: boolean;
|
||||
options: { [key: number]: string };
|
||||
allowcustom: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type TextField = {
|
||||
required: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
export type TextareaField = {
|
||||
required: boolean;
|
||||
rows: number;
|
||||
key: string;
|
||||
name: string;
|
||||
nami_type: App.Form.Enums.NamiType | null;
|
||||
columns: App.Form.Data.ColumnData;
|
||||
for_members: boolean;
|
||||
special_type: App.Form.Enums.SpecialType | null;
|
||||
hint: string | null;
|
||||
intro: string | null;
|
||||
value: any;
|
||||
};
|
||||
}
|
||||
declare namespace App.Form.Scopes {
|
||||
export type FormFilterScope = {
|
||||
search: string | null;
|
||||
past: boolean;
|
||||
inactive: boolean;
|
||||
};
|
||||
export type ParticipantFilterScope = {
|
||||
data: Array<any>;
|
||||
search: string;
|
||||
options: Array<any>;
|
||||
parent: number | null;
|
||||
sort: App.Lib.Sorting | null;
|
||||
};
|
||||
}
|
||||
declare namespace App.Group.Enums {
|
||||
export type Level = 'Diözese' | 'Bezirk' | 'Stamm';
|
||||
}
|
||||
declare namespace App.Invoice {
|
||||
export type BillKind = 'E-Mail' | 'Post';
|
||||
export type BillDocument = {
|
||||
until: string;
|
||||
filename: string;
|
||||
toName: string;
|
||||
toAddress: string;
|
||||
toZip: string;
|
||||
toLocation: string;
|
||||
greeting: string;
|
||||
positions: Array<any>;
|
||||
usage: string;
|
||||
};
|
||||
export type InvoiceDocument = {
|
||||
until: string;
|
||||
filename: string;
|
||||
toName: string;
|
||||
toAddress: string;
|
||||
toZip: string;
|
||||
toLocation: string;
|
||||
greeting: string;
|
||||
positions: Array<any>;
|
||||
usage: string;
|
||||
};
|
||||
export type RememberDocument = {
|
||||
until: string;
|
||||
filename: string;
|
||||
toName: string;
|
||||
toAddress: string;
|
||||
toZip: string;
|
||||
toLocation: string;
|
||||
greeting: string;
|
||||
positions: Array<any>;
|
||||
usage: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Invoice.Enums {
|
||||
export type InvoiceStatus = 'Neu' | 'Rechnung gestellt' | 'Rechnung beglichen';
|
||||
}
|
||||
declare namespace App.Invoice.Scopes {
|
||||
export type InvoiceFilterScope = {
|
||||
statuses: Array<any> | null;
|
||||
search: string | null;
|
||||
};
|
||||
}
|
||||
declare namespace App.Lib {
|
||||
export type Filter = {
|
||||
};
|
||||
export type ScoutFilter = {
|
||||
};
|
||||
export type Sorting = {
|
||||
by: string;
|
||||
direction: boolean;
|
||||
};
|
||||
}
|
||||
declare namespace App.Lib.Data {
|
||||
export type DateData = {
|
||||
raw: string;
|
||||
human: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Lib.Editor {
|
||||
export type Comparator = 'isEqual' | 'isNotEqual' | 'isIn' | 'isNotIn';
|
||||
export type ConditionMode = 'all' | 'any';
|
||||
export type Condition = {
|
||||
mode: App.Lib.Editor.ConditionMode;
|
||||
ifs: Array<App.Lib.Editor.Statement>;
|
||||
};
|
||||
export type EditorData = {
|
||||
version: string;
|
||||
blocks: Array<any>;
|
||||
time: number;
|
||||
};
|
||||
export type Statement = {
|
||||
field: string;
|
||||
value: any;
|
||||
comparator: App.Lib.Editor.Comparator;
|
||||
};
|
||||
}
|
||||
declare namespace App.Maildispatcher.Data {
|
||||
export type MailEntry = {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Mailman.Data {
|
||||
export type MailingList = {
|
||||
description: string;
|
||||
display_name: string;
|
||||
name: string;
|
||||
id: string;
|
||||
list_name: string;
|
||||
mail_host: string;
|
||||
member_count: number;
|
||||
self_link: string;
|
||||
volume: number;
|
||||
};
|
||||
export type Member = {
|
||||
email: string;
|
||||
member_id: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Member {
|
||||
export type FilterScope = {
|
||||
options: { [key: string]: any };
|
||||
ausstand: boolean;
|
||||
bill_kind: string | null;
|
||||
memberships: Array<any>;
|
||||
activity_ids: Array<any>;
|
||||
subactivity_ids: Array<any>;
|
||||
search: string | null;
|
||||
group_ids: Array<any>;
|
||||
include: Array<any>;
|
||||
exclude: Array<any>;
|
||||
has_full_address: boolean | null;
|
||||
has_birthday: boolean | null;
|
||||
has_svk: boolean | null;
|
||||
has_vk: boolean | null;
|
||||
};
|
||||
}
|
||||
declare namespace App.Member.Data {
|
||||
export type ActivityData = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
export type FullMember = {
|
||||
member: any;
|
||||
courses: Array<any>;
|
||||
memberships: Array<any>;
|
||||
};
|
||||
export type GroupData = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
export type MembershipData = {
|
||||
activity: App.Member.Data.ActivityData | null;
|
||||
subactivity: App.Member.Data.SubactivityData | null;
|
||||
group: App.Member.Data.GroupData | null;
|
||||
promisedAt: App.Lib.Data.DateData | null;
|
||||
from: App.Lib.Data.DateData | null;
|
||||
isActive: boolean;
|
||||
links: Array<any>;
|
||||
};
|
||||
export type MembershipMeta = {
|
||||
links: Array<any>;
|
||||
groups: any;
|
||||
activities: any;
|
||||
subactivities: any;
|
||||
default: App.Member.Data.MembershipData;
|
||||
};
|
||||
export type NestedGroup = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
export type SubactivityData = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Module {
|
||||
export type Module = 'bill' | 'course' | 'event';
|
||||
}
|
||||
declare namespace App.Pdf {
|
||||
export type Sender = {
|
||||
name: string;
|
||||
address: string;
|
||||
zipLocation: string;
|
||||
mglnr?: string;
|
||||
};
|
||||
}
|
||||
declare namespace App.Prevention.Data {
|
||||
export type PreventionData = {
|
||||
type: any;
|
||||
expires: string;
|
||||
};
|
||||
}
|
|
@ -130,20 +130,6 @@ class IndexTest extends EndToEndTestCase
|
|||
$this->assertInertiaHas(null, $response, 'data.data.0.birthday');
|
||||
}
|
||||
|
||||
public function testItShowsActivitiesAndSubactivities(): void
|
||||
{
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$activity = Activity::factory()->hasAttached(Subactivity::factory()->name('Biber'))->name('€ Mitglied')->create();
|
||||
$subactivity = $activity->subactivities->first();
|
||||
|
||||
sleep(1);
|
||||
$this->get('/member')
|
||||
->assertInertiaPath("data.meta.formSubactivities.{$activity->id}.{$subactivity->id}", 'Biber')
|
||||
->assertInertiaPath("data.meta.filterSubactivities.0.name", 'Biber')
|
||||
->assertInertiaPath("data.meta.filterSubactivities.0.id", $activity->id)
|
||||
->assertInertiaPath("data.meta.formActivities.{$activity->id}", '€ Mitglied');
|
||||
}
|
||||
|
||||
public function testItCanFilterForBillKinds(): void
|
||||
{
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
|
|
|
@ -23,23 +23,8 @@ class CreateTest extends TestCase
|
|||
|
||||
public function testItDisplaysCreatePage(): void
|
||||
{
|
||||
$activity = Activity::factory()->inNami(5)->hasAttached(Subactivity::factory()->inNami(23)->name('Biber'))->name('€ Mitglied')->create();
|
||||
$subactivity = $activity->subactivities->first();
|
||||
|
||||
$response = $this->get(route('member.create'));
|
||||
|
||||
$this->assertInertiaHas('Biber', $response, "meta.formSubactivities.{$activity->id}.{$subactivity->id}");
|
||||
$this->assertInertiaHas('€ Mitglied', $response, "meta.formActivities.{$activity->id}");
|
||||
$this->assertInertiaHas(['name' => 'E-Mail', 'id' => 'E-Mail'], $response, 'meta.billKinds.0');
|
||||
|
||||
$this->assertInertiaHas([
|
||||
'efz' => null,
|
||||
'ps_at' => null,
|
||||
'more_ps_at' => null,
|
||||
'without_education_at' => null,
|
||||
'without_efz_at' => null,
|
||||
'address' => '',
|
||||
], $response, 'meta.default');
|
||||
$this->get(route('member.create'))
|
||||
->assertInertiaPath('meta.default.address', '');
|
||||
}
|
||||
|
||||
public function testItDoesntDisplayActivitiesAndSubactivitiesNotInNami(): void
|
||||
|
|
|
@ -30,8 +30,6 @@ class EditTest extends TestCase
|
|||
|
||||
$response = $this->get(route('member.edit', ['member' => $member]));
|
||||
|
||||
$this->assertInertiaHas('Biber', $response, "meta.formSubactivities.{$activity->id}.{$subactivity->id}");
|
||||
$this->assertInertiaHas('€ Mitglied', $response, "meta.formActivities.{$activity->id}");
|
||||
$this->assertInertiaHas('Max', $response, 'data.firstname');
|
||||
$this->assertInertiaHas(false, $response, 'data.keepdata');
|
||||
$this->assertInertiaHas('Doktor', $response, 'data.salutation');
|
||||
|
|
|
@ -2,193 +2,266 @@
|
|||
|
||||
namespace Tests\Feature\Member;
|
||||
|
||||
use App\Activity;
|
||||
use App\Confession;
|
||||
use App\Country;
|
||||
use App\Course\Models\Course;
|
||||
use App\Course\Models\CourseMember;
|
||||
use App\Gender;
|
||||
use App\Group;
|
||||
use App\Invoice\BillKind;
|
||||
use App\Invoice\Models\Invoice;
|
||||
use App\Invoice\Models\InvoicePosition;
|
||||
use App\Member\Member;
|
||||
use App\Member\MemberResource;
|
||||
use App\Member\Membership;
|
||||
use App\Nationality;
|
||||
use App\Payment\Subscription;
|
||||
use App\Region;
|
||||
use App\Subactivity;
|
||||
use Carbon\Carbon;
|
||||
use Generator;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ShowTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Country::factory()->create(['name' => 'Deutschland']);
|
||||
}
|
||||
beforeEach(function () {
|
||||
Country::factory()->create(['name' => 'Deutschland']);
|
||||
});
|
||||
|
||||
public function testItShowsSingleMember(): void
|
||||
{
|
||||
Carbon::setTestNow(Carbon::parse('2006-01-01 15:00:00'));
|
||||
mutates(MemberResource::class);
|
||||
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->for(Group::factory()->name('Stamm Beispiel'))
|
||||
->has(Membership::factory()->promise(now())->in('€ LeiterIn', 5, 'Jungpfadfinder', 88)->from('2022-11-19'))
|
||||
->has(InvoicePosition::factory()->for(Invoice::factory())->price(1050)->description('uu'))
|
||||
->for(Gender::factory()->male())
|
||||
->for(Region::factory()->name('NRW'))
|
||||
->postBillKind()
|
||||
->inNami(123)
|
||||
->for(Subscription::factory()->name('Sub')->forFee())
|
||||
->has(CourseMember::factory()->for(Course::factory()->name(' Baustein 2e - Gewalt gegen Kinder und Jugendliche: Vertiefung, Prävention '))->state(['organizer' => 'DPSG', 'event_name' => 'Wochenende', 'completed_at' => '2022-03-03']), 'courses')
|
||||
->create([
|
||||
'birthday' => '1991-04-20',
|
||||
'address' => 'Itterstr 3',
|
||||
'zip' => '42719',
|
||||
'location' => 'Solingen',
|
||||
'firstname' => 'Max',
|
||||
'lastname' => 'Muster',
|
||||
'other_country' => 'other',
|
||||
'main_phone' => '+49 212 1266775',
|
||||
'mobile_phone' => '+49 212 1266776',
|
||||
'work_phone' => '+49 212 1266777',
|
||||
'children_phone' => '+49 212 1266778',
|
||||
'email' => 'a@b.de',
|
||||
'email_parents' => 'b@c.de',
|
||||
'fax' => '+49 212 1255674',
|
||||
'efz' => '2022-09-20',
|
||||
'ps_at' => '2022-04-20',
|
||||
'more_ps_at' => '2022-06-02',
|
||||
'recertified_at' => '2022-06-13',
|
||||
'without_education_at' => '2022-06-03',
|
||||
'without_efz_at' => '2022-06-04',
|
||||
'has_vk' => true,
|
||||
'has_svk' => true,
|
||||
'multiply_pv' => true,
|
||||
'multiply_more_pv' => true,
|
||||
'send_newspaper' => true,
|
||||
'joined_at' => '2022-06-11',
|
||||
'mitgliedsnr' => 998,
|
||||
'lon' => 19.05,
|
||||
'lat' => 14.053,
|
||||
]);
|
||||
it('shows courses', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(
|
||||
CourseMember::factory()
|
||||
->for(Course::factory()->name(' Baustein 2e - Gewalt gegen Kinder und Jugendliche: Vertiefung, Prävention '))
|
||||
->state(['organizer' => 'DPSG', 'event_name' => 'Wochenende', 'completed_at' => '2022-03-03']),
|
||||
'courses'
|
||||
)
|
||||
->create();
|
||||
|
||||
$response = $this->get("/member/{$member->id}");
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.courses.0.organizer', 'DPSG')
|
||||
->assertInertiaPath('data.courses.0.event_name', 'Wochenende')
|
||||
->assertInertiaPath('data.courses.0.completed_at_human', '03.03.2022')
|
||||
->assertInertiaPath('data.courses.0.course.name', ' Baustein 2e - Gewalt gegen Kinder und Jugendliche: Vertiefung, Prävention ')
|
||||
->assertInertiaPath('data.courses.0.course.short_name', '2e');
|
||||
});
|
||||
|
||||
$this->assertInertiaHas([
|
||||
'birthday_human' => '20.04.1991',
|
||||
'age' => 14,
|
||||
'group_name' => 'Stamm Beispiel',
|
||||
'full_address' => 'Itterstr 3, 42719 Solingen',
|
||||
'region' => ['name' => 'NRW'],
|
||||
'other_country' => 'other',
|
||||
'main_phone' => '+49 212 1266775',
|
||||
'mobile_phone' => '+49 212 1266776',
|
||||
'work_phone' => '+49 212 1266777',
|
||||
'children_phone' => '+49 212 1266778',
|
||||
'email' => 'a@b.de',
|
||||
'email_parents' => 'b@c.de',
|
||||
'fax' => '+49 212 1255674',
|
||||
'fullname' => 'Herr Max Muster',
|
||||
'efz_human' => '20.09.2022',
|
||||
'ps_at_human' => '20.04.2022',
|
||||
'more_ps_at_human' => '02.06.2022',
|
||||
'without_education_at_human' => '03.06.2022',
|
||||
'without_efz_at_human' => '04.06.2022',
|
||||
'recertified_at_human' => '13.06.2022',
|
||||
'has_vk' => true,
|
||||
'has_svk' => true,
|
||||
'multiply_pv' => true,
|
||||
'multiply_more_pv' => true,
|
||||
'has_nami' => true,
|
||||
'nami_id' => 123,
|
||||
'send_newspaper' => true,
|
||||
'joined_at_human' => '11.06.2022',
|
||||
'bill_kind_name' => 'Post',
|
||||
'mitgliedsnr' => 998,
|
||||
'lon' => 19.05,
|
||||
'lat' => 14.053,
|
||||
'subscription' => [
|
||||
'name' => 'Sub',
|
||||
],
|
||||
], $response, 'data');
|
||||
$this->assertInertiaHas([
|
||||
'activity_name' => '€ LeiterIn',
|
||||
'subactivity_name' => 'Jungpfadfinder',
|
||||
'id' => $member->memberships->first()->id,
|
||||
'human_date' => '19.11.2022',
|
||||
'promised_at' => now()->format('Y-m-d'),
|
||||
], $response, 'data.memberships.0');
|
||||
$this->assertInertiaHas([
|
||||
'organizer' => 'DPSG',
|
||||
'event_name' => 'Wochenende',
|
||||
'completed_at_human' => '03.03.2022',
|
||||
'course' => [
|
||||
'name' => ' Baustein 2e - Gewalt gegen Kinder und Jugendliche: Vertiefung, Prävention ',
|
||||
'short_name' => '2e',
|
||||
],
|
||||
], $response, 'data.courses.0');
|
||||
$this->assertInertiaHas([
|
||||
'description' => 'uu',
|
||||
'price_human' => '10,50 €',
|
||||
'invoice' => [
|
||||
'status' => 'Neu',
|
||||
]
|
||||
], $response, 'data.invoicePositions.0');
|
||||
}
|
||||
it('shows default', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$activity = Activity::factory()->name('€ Mitglied')->create();
|
||||
$subactivity = Subactivity::factory()->name('Jungpfadfinder')->create();
|
||||
$activity->subactivities()->attach($subactivity);
|
||||
$course = Course::factory()->name('LL')->create();
|
||||
$region = Region::factory()->name('LLa')->create();
|
||||
$member = Member::factory()->male()->defaults()->create();
|
||||
|
||||
public function testItShowsMinimalSingleMember(): void
|
||||
{
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->for(Group::factory())
|
||||
->for(Nationality::factory()->name('deutsch'))
|
||||
->for(Subscription::factory()->forFee())
|
||||
->create(['firstname' => 'Max', 'lastname' => 'Muster', 'has_vk' => false, 'has_svk' => false]);
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('meta.default.address', '')
|
||||
->assertInertiaPath('meta.filterActivities.0.name', '€ Mitglied')
|
||||
->assertInertiaPath('meta.filterActivities.0.id', $activity->id)
|
||||
->assertInertiaPath('meta.filterSubactivities.0.name', 'Jungpfadfinder')
|
||||
->assertInertiaPath('meta.filterSubactivities.0.id', $subactivity->id)
|
||||
->assertInertiaPath('meta.formActivities.0.name', '€ Mitglied')
|
||||
->assertInertiaPath('meta.formActivities.0.id', $activity->id)
|
||||
->assertInertiaPath("meta.formSubactivities.{$activity->id}.{$subactivity->id}", "Jungpfadfinder")
|
||||
->assertInertiaPath('meta.default.has_nami', false)
|
||||
->assertInertiaPath('meta.default.send_newspaper', false)
|
||||
->assertInertiaPath('meta.groups.0.id', $member->group->id)
|
||||
->assertInertiaPath('meta.default.fax', '')
|
||||
->assertInertiaPath('meta.filter.search', '')
|
||||
->assertInertiaPath('meta.billKinds.0.id', 'E-Mail')
|
||||
->assertInertiaPath('meta.courses.0.id', $course->id)
|
||||
->assertInertiaPath('meta.courses.0.name', 'LL')
|
||||
->assertInertiaPath('meta.regions.0.name', 'LLa')
|
||||
->assertInertiaPath('meta.regions.0.id', $region->id)
|
||||
->assertInertiaPath('meta.subscriptions.0.id', $member->subscription->id)
|
||||
->assertInertiaPath('meta.subscriptions.0.name', $member->subscription->name)
|
||||
->assertInertiaPath('meta.links.create', route('member.create'))
|
||||
->assertInertiaPath('meta.links.index', route('member.index'))
|
||||
->assertInertiaPath('meta.countries.0.id', $member->country->id)
|
||||
->assertInertiaPath('meta.countries.0.name', $member->country->name)
|
||||
->assertInertiaPath('meta.genders.0.id', $member->gender->id)
|
||||
->assertInertiaPath('meta.genders.0.name', $member->gender->name)
|
||||
->assertInertiaPath('meta.default.bank_account.iban', '')
|
||||
->assertInertiaPath('meta.default_membership_filter.group_ids', [])
|
||||
->assertInertiaPath('meta.default_membership_filter.activity_ids', [])
|
||||
->assertInertiaPath('meta.default_membership_filter.subactivity_ids', [])
|
||||
->assertInertiaPath('meta.default.bank_account.bic', '')
|
||||
->assertInertiaPath('meta.default.bank_account.blz', '')
|
||||
->assertInertiaPath('meta.default.bank_account.bank_name', '')
|
||||
->assertInertiaPath('meta.default.bank_account.person', '')
|
||||
->assertInertiaPath('meta.default.bank_account.account_number', '')
|
||||
->assertInertiaPath('meta.default.letter_address', '')
|
||||
->assertInertiaPath('meta.default.email', '')
|
||||
->assertInertiaPath('meta.default.has_vk', false)
|
||||
->assertInertiaPath('meta.default.has_svk', false)
|
||||
->assertInertiaPath('meta.default.multiply_pv', false)
|
||||
->assertInertiaPath('meta.default.multiply_more_pv', false)
|
||||
->assertInertiaPath('meta.default.email_parents', '')
|
||||
->assertInertiaPath('meta.default.children_phone', '')
|
||||
->assertInertiaPath('meta.default.work_phone', '')
|
||||
->assertInertiaPath('meta.default.mobile_phone', '')
|
||||
->assertInertiaPath('meta.default.main_phone', '')
|
||||
->assertInertiaPath('meta.default.other_country', '')
|
||||
->assertInertiaPath('meta.default.birthday', '')
|
||||
->assertInertiaPath('meta.default.location', '')
|
||||
->assertInertiaPath('meta.default.zip', '')
|
||||
->assertInertiaPath('meta.default.address', '')
|
||||
->assertInertiaPath('meta.default.further_address', '')
|
||||
->assertInertiaPath('meta.default.firstname', '')
|
||||
->assertInertiaPath('meta.default.comment', '')
|
||||
->assertInertiaPath('meta.default.joined_at', now()->format('Y-m-d'))
|
||||
->assertInertiaPath('meta.default.lastname', '')
|
||||
->assertInertiaPath('meta.default.ps_at', null)
|
||||
->assertInertiaPath('meta.default.more_ps_at', null)
|
||||
->assertInertiaPath('meta.default.without_education_at', null)
|
||||
->assertInertiaPath('meta.default.without_efz_at', null)
|
||||
->assertInertiaPath('meta.default.efz', null);
|
||||
});
|
||||
|
||||
$response = $this->get("/member/{$member->id}");
|
||||
it('shows efz link', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()->defaults()->create();
|
||||
|
||||
$this->assertInertiaHas([
|
||||
'region' => ['name' => '-- kein --'],
|
||||
'fullname' => 'Max Muster',
|
||||
'nationality' => [
|
||||
'name' => 'deutsch',
|
||||
],
|
||||
'efz_human' => null,
|
||||
'ps_at_human' => null,
|
||||
'more_ps_at_human' => null,
|
||||
'without_education_at_human' => null,
|
||||
'without_efz_at_human' => null,
|
||||
'has_vk' => false,
|
||||
'has_svk' => false,
|
||||
'multiply_pv' => false,
|
||||
'multiply_more_pv' => false,
|
||||
], $response, 'data');
|
||||
}
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.efz_link', route('efz', $member));
|
||||
});
|
||||
|
||||
public static function membershipDataProvider(): Generator
|
||||
{
|
||||
yield [now()->subMonths(2), null, true];
|
||||
yield [now()->subMonths(2), now()->subDay(), false];
|
||||
yield [now()->addDays(2), null, false];
|
||||
}
|
||||
it('shows memberships', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2006-01-01 15:00:00'));
|
||||
|
||||
#[DataProvider('membershipDataProvider')]
|
||||
public function testItShowsIfMembershipIsActive(Carbon $from, ?Carbon $to, bool $isActive): void
|
||||
{
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)->state(['from' => $from, 'to' => $to]))
|
||||
->create();
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->for(Group::factory()->name('Stamm Beispiel'))
|
||||
->has(Membership::factory()->promise(now())->in('€ LeiterIn', 5, 'Jungpfadfinder', 88)->from('2005-11-19'))
|
||||
->create();
|
||||
|
||||
$response = $this->get("/member/{$member->id}");
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.age_group_icon', 'jungpfadfinder')
|
||||
->assertInertiaPath('data.is_leader', true)
|
||||
->assertInertiaPath('data.memberships.0.id', $member->memberships->first()->id)
|
||||
->assertInertiaPath('data.memberships.0.from.human', '19.11.2005')
|
||||
->assertInertiaPath('data.memberships.0.from.raw', '2005-11-19')
|
||||
->assertInertiaPath('data.memberships.0.promised_at.human', now()->format('d.m.Y'))
|
||||
->assertInertiaPath('data.memberships.0.promised_at.raw', now()->format('Y-m-d'))
|
||||
->assertInertiaPath('data.memberships.0.activity.name', '€ LeiterIn')
|
||||
->assertInertiaPath('data.memberships.0.activity.id', $member->memberships->first()->activity->id)
|
||||
->assertInertiaPath('data.memberships.0.subactivity.name', 'Jungpfadfinder')
|
||||
->assertInertiaPath('data.memberships.0.subactivity.id', $member->memberships->first()->subactivity->id);
|
||||
});
|
||||
|
||||
$this->assertInertiaHas($isActive, $response, 'data.memberships.0.is_active');
|
||||
}
|
||||
}
|
||||
it('shows that member is not a leader', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()->defaults()->create();
|
||||
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.is_leader', false);
|
||||
});
|
||||
|
||||
it('shows links', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()->defaults()->create();
|
||||
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.links.edit', route('member.edit', $member))
|
||||
->assertInertiaPath('data.links.show', route('member.show', $member))
|
||||
->assertInertiaPath('data.links.invoiceposition_index', route('member.invoice-position.index', $member))
|
||||
->assertInertiaPath('data.links.membership_index', route('member.membership.index', $member))
|
||||
->assertInertiaPath('data.links.course_index', route('member.course.index', $member));
|
||||
});
|
||||
|
||||
it('shows invoice positions', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2006-01-01 15:00:00'));
|
||||
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(InvoicePosition::factory()->for(Invoice::factory())->price(1050)->description('uu'))
|
||||
->create();
|
||||
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.pending_payment', '10,50 €')
|
||||
->assertInertiaPath('data.invoicePositions.0.description', 'uu')
|
||||
->assertInertiaPath('data.invoicePositions.0.price_human', '10,50 €')
|
||||
->assertInertiaPath('data.invoicePositions.0.invoice.status', 'Neu');
|
||||
});
|
||||
|
||||
it('shows member single', function (array $attributes, array $expect) {
|
||||
Carbon::setTestNow(Carbon::parse('2006-01-01 15:00:00'));
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->for(Group::factory())
|
||||
->for(Nationality::factory()->name('deutsch'))
|
||||
->for(Subscription::factory()->forFee())
|
||||
->create($attributes);
|
||||
|
||||
$this->get("/member/{$member->id}")
|
||||
->assertInertiaPath('data.id', $member->id)
|
||||
->assertInertiaPathArray($expect);
|
||||
})->with([
|
||||
fn() => [['region_id' => Region::factory()->name('UUU')->create()->id], ['data.region.name' => 'UUU', 'data.region.id' => Region::first()->id, 'data.region_id' => Region::first()->id]],
|
||||
fn() => [['confession_id' => Confession::factory()->create(['name' => 'UUU'])->id], ['data.confession_id' => Confession::firstWhere('name', 'UUU')->id]],
|
||||
fn() => [['nationality_id' => Nationality::factory()->name('UUU')->create()->id], ['data.nationality.name' => 'UUU', 'data.nationality_id' => Nationality::first()->id, 'data.nationality.id' => Nationality::first()->id]],
|
||||
fn() => [['group_id' => Group::factory()->name('UUU')->create()->id], ['data.group_name' => 'UUU', 'data.group_id' => Group::firstWhere('name', 'UUU')->id]],
|
||||
fn() => [['bill_kind' => BillKind::EMAIL->value], ['data.bill_kind_name' => 'E-Mail', 'data.bill_kind' => 'E-Mail']],
|
||||
fn() => [['subscription_id' => Subscription::factory()->name('Sub')->forFee()->create()], ['data.subscription.name' => 'Sub', 'data.subscription_id' => Subscription::first()->id]],
|
||||
fn() => [['country_id' => Country::factory()->create(['name' => 'Sub'])->id], ['data.country_id' => Country::firstWhere('name', 'Sub')->id]],
|
||||
fn() => [['firstname' => 'Max', 'lastname' => 'Muster', 'gender_id' => Gender::factory()->male()->create()->id], ['data.firstname' => 'Max', 'data.lastname' => 'Muster', 'data.fullname' => 'Herr Max Muster', 'data.gender_id' => Gender::first()->id]],
|
||||
[['firstname' => 'Max', 'lastname' => 'Muster', 'gender_id' => null], ['data.fullname' => 'Max Muster']],
|
||||
[['other_country' => 'other', 'further_address' => 'other', 'letter_address' => 'A'], ['data.other_country' => 'other', 'data.further_address' => 'other', 'data.letter_address' => 'A']],
|
||||
[['gender_id' => null], ['data.gender_name' => 'keine Angabe']],
|
||||
[['salutation' => 'Dr'], ['data.salutation' => 'Dr']],
|
||||
[['comment' => 'Com'], ['data.comment' => 'Com']],
|
||||
[['birthday' => null], ['data.birthday' => null, 'data.birthday_human' => null]],
|
||||
[[], ['data.bank_account.iban' => null]],
|
||||
[
|
||||
['efz' => null, 'ps_at' => null, 'ps_at' => null, 'more_ps_at' => null, 'has_svk' => false, 'has_vk' => false, 'has_svk' => true],
|
||||
['data.efz_human' => null, 'data.ps_at_human' => null, 'data.ps_at_human' => null, 'data.more_ps_at_human' => null, 'data.has_svk' => false, 'data.has_vk' => false, 'data.has_svk' => true]
|
||||
],
|
||||
[
|
||||
['has_vk' => true, 'multiply_more_pv' => false, 'without_efz_at' => null, 'without_education_at' => null],
|
||||
['data.has_vk' => true, 'data.multiply_more_pv' => false, 'data.without_efz_at_human' => null, 'data.without_education_at_human' => null]
|
||||
],
|
||||
[
|
||||
['main_phone' => '+49 212 1266775', 'mobile_phone' => '+49 212 1266776', 'work_phone' => '+49 212 1266777', 'children_phone' => '+49 212 1266778'],
|
||||
['data.main_phone' => '+49 212 1266775', 'data.mobile_phone' => '+49 212 1266776', 'data.work_phone' => '+49 212 1266777', 'data.children_phone' => '+49 212 1266778']
|
||||
],
|
||||
[
|
||||
['efz' => '2022-09-20', 'ps_at' => '2022-04-20', 'more_ps_at' => '2022-06-02', 'without_education_at' => '2022-06-03', 'without_efz_at' => '2022-06-04'],
|
||||
['data.efz' => '2022-09-20', 'data.efz_human' => '20.09.2022', 'data.ps_at' => '2022-04-20', 'data.more_ps_at' => '2022-06-02', 'data.without_education_at' => '2022-06-03', 'data.without_efz_at' => '2022-06-04', 'data.ps_at_human' => '20.04.2022', 'data.more_ps_at_human' => '02.06.2022', 'data.without_education_at_human' => '03.06.2022', 'data.without_efz_at_human' => '04.06.2022']
|
||||
],
|
||||
[['recertified_at' => '2022-06-13'], ['data.recertified_at_human' => '13.06.2022', 'data.recertified_at' => '2022-06-13']],
|
||||
[['multiply_pv' => true, 'multiply_more_pv' => true], ['data.multiply_pv' => true, 'data.multiply_more_pv' => true]],
|
||||
[
|
||||
['email' => 'a@b.de', 'email_parents' => 'b@c.de', 'fax' => '+49 212 1255674'],
|
||||
['data.email' => 'a@b.de', 'data.email_parents' => 'b@c.de', 'data.fax' => '+49 212 1255674']
|
||||
],
|
||||
[['nami_id' => 123], ['data.nami_id' => 123, 'data.has_nami' => true]],
|
||||
[['send_newspaper' => true], ['data.send_newspaper' => true]],
|
||||
[['address' => 'Itterstr 3', 'location' => 'Solingen', 'zip' => '42719'], ['data.location' => 'Solingen', 'data.address' => 'Itterstr 3', 'data.zip' => '42719', 'data.full_address' => 'Itterstr 3, 42719 Solingen']],
|
||||
[['lon' => 19.05, 'lat' => 14.053], ['data.lon' => 19.05, 'data.lat' => 14.053]],
|
||||
[['birthday' => '1991-04-20'], ['data.birthday' => '1991-04-20', 'data.birthday_human' => '20.04.1991', 'data.age' => 14]],
|
||||
[['joined_at' => '2022-06-11'], ['data.joined_at' => '2022-06-11', 'data.joined_at_human' => '11.06.2022']],
|
||||
[['mitgliedsnr' => 998, 'keepdata' => true], ['data.mitgliedsnr' => 998, 'data.keepdata' => true]],
|
||||
]);
|
||||
|
||||
it('testItShowsIfMembershipIsActive', function (Carbon $from, ?Carbon $to, bool $isActive) {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)->state(['from' => $from, 'to' => $to]))
|
||||
->create();
|
||||
|
||||
$response = $this->get("/member/{$member->id}");
|
||||
|
||||
$this->assertInertiaHas($isActive, $response, 'data.memberships.0.is_active');
|
||||
})->with([
|
||||
[now()->subMonths(2), null, true],
|
||||
[now()->subMonths(2), now()->subDay(), false],
|
||||
[now()->addDays(2), null, false],
|
||||
]);
|
||||
|
|
|
@ -3,66 +3,106 @@
|
|||
namespace Tests\Feature\Membership;
|
||||
|
||||
use App\Group;
|
||||
use App\Lib\Data\DateData;
|
||||
use App\Member\Data\ActivityData;
|
||||
use App\Member\Data\GroupData;
|
||||
use App\Member\Data\MembershipData;
|
||||
use App\Member\Data\SubactivityData;
|
||||
use App\Member\Member;
|
||||
use App\Member\Membership;
|
||||
use Generator;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
class IndexTest extends TestCase
|
||||
{
|
||||
covers(MembershipData::class);
|
||||
covers(DateData::class);
|
||||
covers(GroupData::class);
|
||||
covers(ActivityData::class);
|
||||
covers(SubactivityData::class);
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
use DatabaseTransactions;
|
||||
it('testItShowsActivityAndSubactivityNamesOfMember', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$group = Group::factory()->create(['name' => 'aaaaaaaa']);
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->for($group)
|
||||
->has(Membership::factory()->for($group)->in('€ Mitglied', 122, 'Wölfling', 234)->from('2022-11-02')->promise(now()))
|
||||
->create();
|
||||
$membership = $member->memberships->first();
|
||||
|
||||
public function testItShowsActivityAndSubactivityNamesOfMember(): void
|
||||
{
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$group = Group::factory()->create(['name' => 'aaaaaaaa']);
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->for($group)
|
||||
->has(Membership::factory()->for($group)->in('€ Mitglied', 122, 'Wölfling', 234)->from('2022-11-02'))
|
||||
->create();
|
||||
$membership = $member->memberships->first();
|
||||
$this->get("/member/{$member->id}/membership")
|
||||
->assertJsonPath('data.0.activity.id', $membership->activity_id)
|
||||
->assertJsonPath('data.0.subactivity.id', $membership->subactivity_id)
|
||||
->assertJsonPath('data.0.activity.name', '€ Mitglied')
|
||||
->assertJsonPath('data.0.subactivity.name', 'Wölfling')
|
||||
->assertJsonPath('data.0.from.human', '02.11.2022')
|
||||
->assertJsonPath('data.0.from.raw', '2022-11-02')
|
||||
->assertJsonPath('data.0.promisedAt.raw', now()->format('Y-m-d'))
|
||||
->assertJsonPath('data.0.promisedAt.human', now()->format('d.m.Y'))
|
||||
->assertJsonPath('data.0.group.id', $group->id)
|
||||
->assertJsonPath('data.0.links.update', route('membership.update', ['membership' => $membership]))
|
||||
->assertJsonPath('data.0.links.destroy', route('membership.destroy', ['membership' => $membership]));
|
||||
});
|
||||
|
||||
$this->get("/member/{$member->id}/membership")
|
||||
->assertJsonPath('data.0.activity_id', $membership->activity_id)
|
||||
->assertJsonPath('data.0.subactivity_id', $membership->subactivity_id)
|
||||
->assertJsonPath('data.0.activity_name', '€ Mitglied')
|
||||
->assertJsonPath('data.0.subactivity_name', 'Wölfling')
|
||||
->assertJsonPath('data.0.human_date', '02.11.2022')
|
||||
->assertJsonPath('data.0.group_id', $group->id)
|
||||
->assertJsonPath('data.0.id', $membership->id)
|
||||
->assertJsonPath('data.0.links.update', route('membership.update', ['membership' => $membership]))
|
||||
->assertJsonPath('data.0.links.destroy', route('membership.destroy', ['membership' => $membership]))
|
||||
->assertJsonPath('meta.default.activity_id', null)
|
||||
->assertJsonPath('meta.default.group_id', $group->id)
|
||||
->assertJsonPath('meta.groups.0.id', $group->id)
|
||||
->assertJsonPath('meta.activities.0.id', $membership->activity_id)
|
||||
->assertJsonPath("meta.subactivities.{$membership->activity_id}.0.id", $membership->subactivity_id)
|
||||
->assertJsonPath("meta.subactivities.{$membership->activity_id}.0.is_age_group", true)
|
||||
->assertJsonPath('meta.links.store', route('member.membership.store', ['member' => $member]));
|
||||
}
|
||||
it('activity and subactivity can be null', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$group = Group::factory()->create(['name' => 'aaaaaaaa']);
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->for($group)
|
||||
->has(Membership::factory()->for($group)->in('€ Mitglied', 122)->from('2022-11-02')->promise(now()))
|
||||
->create();
|
||||
|
||||
public static function membershipDataProvider(): Generator
|
||||
{
|
||||
yield [now()->subMonths(2), null, true];
|
||||
yield [now()->subMonths(2), now()->subDay(), false];
|
||||
yield [now()->addDays(2), null, false];
|
||||
}
|
||||
$this->get("/member/{$member->id}/membership")->assertNull('data.0.subactivity');
|
||||
});
|
||||
|
||||
#[DataProvider('membershipDataProvider')]
|
||||
public function testItShowsIfMembershipIsActive(Carbon $from, ?Carbon $to, bool $isActive): void
|
||||
{
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)->state(['from' => $from, 'to' => $to]))
|
||||
->create();
|
||||
it('returns meta', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$group = Group::factory()->create(['name' => 'aaaaaaaa']);
|
||||
$membership = Membership::factory()
|
||||
->for(Member::factory()->defaults()->for($group))
|
||||
->for($group)
|
||||
->in('€ Mitglied', 122, 'Wölfling', 234)->from('2022-11-02')
|
||||
->create();
|
||||
|
||||
$this->get("/member/{$member->id}/membership")
|
||||
->assertJsonPath('data.0.is_active', $isActive);
|
||||
}
|
||||
}
|
||||
$this->get("/member/{$membership->member->id}/membership")
|
||||
->assertNull('meta.default.activity')
|
||||
->assertNull('meta.default.subactivity')
|
||||
->assertNull('meta.default.promisedAt')
|
||||
->assertJsonPath('meta.default.group.id', $group->id)
|
||||
->assertJsonPath('meta.default.group.name', $group->name)
|
||||
->assertJsonPath('meta.groups.0.id', $group->id)
|
||||
->assertJsonPath('meta.activities.0.id', $membership->activity->id)
|
||||
->assertJsonPath('meta.activities.0.name', $membership->activity->name)
|
||||
->assertJsonPath("meta.subactivities.{$membership->activity_id}.0.id", $membership->subactivity_id)
|
||||
->assertJsonPath("meta.subactivities.{$membership->activity_id}.0.name", $membership->subactivity->name)
|
||||
->assertJsonPath("meta.subactivities.{$membership->activity_id}.0.is_age_group", true)
|
||||
->assertJsonPath('meta.links.store', route('member.membership.store', ['member' => $membership->member]));
|
||||
});
|
||||
|
||||
it('promised at can be null', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(Membership::factory()->in('€ Mitglied', 122, 'Wölfling', 234))
|
||||
->create();
|
||||
|
||||
$this->get("/member/{$member->id}/membership")
|
||||
->assertJsonPath('data.0.promisedAt', null);
|
||||
});
|
||||
|
||||
|
||||
it('testItShowsIfMembershipIsActive', function (Carbon $from, ?Carbon $to, bool $isActive) {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()
|
||||
->defaults()
|
||||
->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)->state(['from' => $from, 'to' => $to]))
|
||||
->create();
|
||||
|
||||
$this->get("/member/{$member->id}/membership")
|
||||
->assertJsonPath('data.0.isActive', $isActive);
|
||||
})->with([
|
||||
[now()->subMonths(2), null, true],
|
||||
[now()->subMonths(2), now()->subDay(), false],
|
||||
[now()->addDays(2), null, false],
|
||||
]);
|
||||
|
|
|
@ -119,12 +119,24 @@ class TestCase extends BaseTestCase
|
|||
/** @var TestResponse */
|
||||
$response = $this;
|
||||
$props = data_get($response->viewData('page'), 'props');
|
||||
Assert::assertTrue(Arr::has($props, $path), 'Failed that key ' . $path . ' is in Response.');
|
||||
Assert::assertNotNull($props);
|
||||
$json = new AssertableJsonString($props);
|
||||
$json->assertPath($path, $value);
|
||||
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) {
|
||||
/** @var TestResponse */
|
||||
$response = $this;
|
||||
|
@ -171,5 +183,13 @@ class TestCase extends BaseTestCase
|
|||
|
||||
return $this;
|
||||
});
|
||||
|
||||
TestResponse::macro('assertNull', function (string $path) {
|
||||
/** @var TestResponse */
|
||||
$response = $this;
|
||||
$response->assertHasJsonPath($path)->assertJsonPath($path, null);
|
||||
|
||||
return $this;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,5 +59,5 @@
|
|||
"@/*": ["./resources/js/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*", "resources/js/components/components.d.ts"]
|
||||
"include": ["**/*", "resources/types/generated.d.ts", "resources/types/custom.d.ts"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue