Compare commits
67 Commits
cc934b1b3c
...
2ded545a94
Author | SHA1 | Date |
---|---|---|
philipp lang | 2ded545a94 | |
philipp lang | 4f2f2b43ce | |
philipp lang | 744782ec61 | |
philipp lang | 93c132210a | |
philipp lang | c838884cce | |
philipp lang | 6ad2c24833 | |
philipp lang | 3a1c327cfe | |
philipp lang | 45de2da93e | |
philipp lang | 340d6dab45 | |
philipp lang | 66cc226a67 | |
philipp lang | d5ef4e6651 | |
philipp lang | 336b9c9c1b | |
philipp lang | 4dae977bd9 | |
philipp lang | 806bf73abd | |
philipp lang | b8be5e4623 | |
philipp lang | 4d2272e067 | |
philipp lang | 9330398c9b | |
philipp lang | a9fed5503f | |
philipp lang | 4672344bd9 | |
philipp lang | bd90f6f77b | |
philipp lang | 7e09d78265 | |
philipp lang | 523e77c76a | |
philipp lang | 3cffbc9230 | |
philipp lang | 0915de8f8b | |
philipp lang | e67af9e33a | |
philipp lang | 06a9a584e9 | |
philipp lang | cc481b6536 | |
philipp lang | d60e24ff64 | |
philipp lang | 09fbaabcb8 | |
philipp lang | 0b9446e010 | |
philipp lang | 6dbf091a74 | |
philipp lang | e67325e643 | |
philipp lang | b190a4aecf | |
philipp lang | 1c9c9b343b | |
philipp lang | b51419c87a | |
philipp lang | 1081cf2b70 | |
philipp lang | c192367bd1 | |
philipp lang | c129d4927a | |
philipp lang | 557a529813 | |
philipp lang | 4c77654195 | |
philipp lang | 08821b3731 | |
philipp lang | a8dbfcbb9d | |
philipp lang | c5d76ffd95 | |
philipp lang | abb298cdfa | |
philipp lang | e46e0fcb73 | |
philipp lang | c6dedff312 | |
philipp lang | 7f96a0122d | |
philipp lang | 79c6402073 | |
philipp lang | 217e82bd44 | |
philipp lang | 0934297698 | |
philipp lang | 7bf0f76ca8 | |
philipp lang | 87496d4dc3 | |
philipp lang | 5649310b13 | |
philipp lang | ba91cf0f92 | |
philipp lang | 845385f687 | |
philipp lang | d051929a49 | |
philipp lang | 12cb71bb95 | |
philipp lang | 9f29e976c0 | |
philipp lang | 35be12dd01 | |
philipp lang | 990aec57be | |
philipp lang | e134ee8c12 | |
philipp lang | 75642f336b | |
philipp lang | ed1a8f30bc | |
philipp lang | e5066af192 | |
philipp lang | 31afa2e418 | |
philipp lang | a897690e7a | |
philipp lang | 24ed5d4ab9 |
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
docker buildx build -f .docker/base.Dockerfile .
|
||||
docker image tag sha256:... zoomyboy/adrema-base
|
||||
docker push zoomyboy/adrema-base
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM composer:2.2.7 as composer
|
||||
FROM composer:2.7.9 as composer
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN composer install --ignore-platform-reqs --no-dev
|
||||
|
|
|
@ -40,3 +40,4 @@ Homestead.json
|
|||
/public/sprite.svg
|
||||
/.php-cs-fixer.cache
|
||||
/groups.sql
|
||||
/.phpunit.cache/
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Letzte Änderungen
|
||||
|
||||
### 1.11.1
|
||||
|
||||
- Es kann nun auch das Feld "Datenweiterverwendung" über Adrema gepflegt werden.
|
||||
|
||||
### 1.10.20
|
||||
|
||||
- Fixed: Bei Textfeldern wird nun die Einleitung dargestellt
|
||||
|
|
|
@ -50,6 +50,7 @@ class InsertMemberAction
|
|||
'nationality_id' => Nationality::where('nami_id', $member->nationalityId)->firstOrFail()->id,
|
||||
'mitgliedsnr' => $member->memberId,
|
||||
'version' => $member->version,
|
||||
'keepdata' => $member->keepdata,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ namespace App\Contribution\Documents;
|
|||
|
||||
use App\Contribution\Data\MemberData;
|
||||
use App\Country;
|
||||
use App\Invoice\InvoiceSettings;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
use Zoomyboy\Tex\Engine;
|
||||
use Zoomyboy\Tex\Template;
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
namespace App\Contribution\Documents;
|
||||
|
||||
use App\Contribution\Data\MemberData;
|
||||
use App\Invoice\InvoiceSettings;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
use Zoomyboy\Tex\Engine;
|
||||
use Zoomyboy\Tex\Template;
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Efz;
|
||||
|
||||
use App\Dashboard\Blocks\Block;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class EfzPendingBlock extends Block
|
||||
{
|
||||
/**
|
||||
* @return Builder<Member>
|
||||
*/
|
||||
public function query(): Builder
|
||||
{
|
||||
return Member::where(function ($query) {
|
||||
return $query->where('efz', '<=', now()->subYears(5)->endOfYear())
|
||||
->orWhereNull('efz');
|
||||
})
|
||||
->whereCurrentGroup()
|
||||
->orderByRaw('lastname, firstname')
|
||||
->whereHas('memberships', fn ($builder) => $builder->isLeader()->active());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{members: array<int, string>}
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'members' => $this->query()->get()->map(fn ($member) => $member->fullname)->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
public function component(): string
|
||||
{
|
||||
return 'efz-pending';
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Führungszeugnisse';
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Module\ModuleSettings;
|
||||
use Modules\Module\ModuleSettings;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Inertia\Middleware;
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Invoice\Models\Invoice;
|
|||
use App\Payment\Payment;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
use Zoomyboy\Tex\Document;
|
||||
use Zoomyboy\Tex\Engine;
|
||||
use Zoomyboy\Tex\Template;
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Invoice;
|
||||
|
||||
use App\Dashboard\Blocks\Block;
|
||||
use App\Invoice\Models\InvoicePosition;
|
||||
use App\Member\Member;
|
||||
|
||||
class MemberPaymentBlock extends Block
|
||||
{
|
||||
/**
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
$amount = InvoicePosition::whereHas('invoice', fn ($query) => $query->whereNeedsPayment())
|
||||
->selectRaw('sum(price) AS price')
|
||||
->first();
|
||||
$members = Member::whereHasPendingPayment()->count();
|
||||
|
||||
return [
|
||||
'members' => $members,
|
||||
'total_members' => Member::count(),
|
||||
'amount' => number_format((int) $amount->price / 100, 2, ',', '.') . ' €',
|
||||
];
|
||||
}
|
||||
|
||||
public function component(): string
|
||||
{
|
||||
return 'member-payment';
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Mitgliedsbeiträge';
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ use App\Invoice\BillDocument;
|
|||
use App\Invoice\BillKind;
|
||||
use App\Invoice\Enums\InvoiceStatus;
|
||||
use App\Invoice\InvoiceDocument;
|
||||
use App\Invoice\InvoiceSettings;
|
||||
use App\Invoice\RememberDocument;
|
||||
use App\Invoice\Scopes\InvoiceFilterScope;
|
||||
use App\Member\Member;
|
||||
|
@ -17,6 +16,7 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
use stdClass;
|
||||
|
||||
class Invoice extends Model
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Maildispatcher\Models;
|
||||
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use Modules\Mailgateway\Models\Mailgateway;
|
||||
use Database\Factories\Maildispatcher\Models\MaildispatcherFactory;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
|
@ -4,8 +4,7 @@ namespace App\Maildispatcher\Resources;
|
|||
|
||||
use App\Lib\HasMeta;
|
||||
use App\Maildispatcher\Models\Maildispatcher;
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use App\Mailgateway\Resources\MailgatewayResource;
|
||||
use Modules\Mailgateway\Models\Mailgateway;
|
||||
use App\Member\FilterScope;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Actions;
|
||||
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StoreAction
|
||||
{
|
||||
use AsAction;
|
||||
use ValidatesRequests;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function handle(array $input): void
|
||||
{
|
||||
$this->checkIfWorks($input);
|
||||
Mailgateway::create($input);
|
||||
}
|
||||
|
||||
public function asController(ActionRequest $request): void
|
||||
{
|
||||
$this->handle($request->validated());
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Actions;
|
||||
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class UpdateAction
|
||||
{
|
||||
use AsAction;
|
||||
use ValidatesRequests;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function handle(Mailgateway $mailgateway, array $input): void
|
||||
{
|
||||
$this->checkIfWorks($input);
|
||||
|
||||
$mailgateway->update($input);
|
||||
}
|
||||
|
||||
public function asController(Mailgateway $mailgateway, ActionRequest $request): void
|
||||
{
|
||||
$this->handle($mailgateway, $request->validated());
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Actions;
|
||||
|
||||
use App\Mailgateway\Types\Type;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
|
||||
trait ValidatesRequests
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function checkIfWorks(array $input): void
|
||||
{
|
||||
if (!app(data_get($input, 'type.cls'))->setParams($input['type']['params'])->works()) {
|
||||
throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'domain' => 'required|string|max:255',
|
||||
...$this->typeValidation(),
|
||||
'type.params' => 'present|array',
|
||||
...collect(request()->input('type.cls')::rules('storeValidator'))->mapWithKeys(fn ($rules, $key) => ["type.params.{$key}" => $rules]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getValidationAttributes(): array
|
||||
{
|
||||
return [
|
||||
'type.cls' => 'Typ',
|
||||
'name' => 'Beschreibung',
|
||||
'domain' => 'Domain',
|
||||
...collect(request()->input('type.cls')::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["type.params.{$key}" => $attribute]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function typeValidation(): array
|
||||
{
|
||||
return [
|
||||
'type.cls' => ['required', 'string', 'max:255', Rule::in(app('mail-gateways'))],
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation(ActionRequest $request): void
|
||||
{
|
||||
if (!is_subclass_of(request()->input('type.cls'), Type::class)) {
|
||||
throw ValidationException::withMessages(['type.cls' => 'Typ ist nicht valide.']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Casts;
|
||||
|
||||
use App\Mailgateway\Types\Type;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
/**
|
||||
* @implements CastsAttributes<Type, Type>
|
||||
*/
|
||||
class TypeCast implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* Cast the given value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $attributes
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($model, string $key, $value, array $attributes)
|
||||
{
|
||||
$value = json_decode($value, true);
|
||||
|
||||
return app($value['cls'])->setParams($value['params']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $attributes
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return json_encode($value);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Resources;
|
||||
|
||||
use App\Lib\HasMeta;
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin Mailgateway
|
||||
*/
|
||||
class MailgatewayResource extends JsonResource
|
||||
{
|
||||
use HasMeta;
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'domain' => $this->domain,
|
||||
'type_human' => $this->type::name(),
|
||||
'works' => $this->type->works(),
|
||||
'type' => $this->type->toResource(),
|
||||
'id' => $this->id,
|
||||
'links' => [
|
||||
'update' => route('mailgateway.update', ['mailgateway' => $this->getModel()]),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function meta(): array
|
||||
{
|
||||
return [
|
||||
'links' => [
|
||||
'store' => route('mailgateway.store'),
|
||||
],
|
||||
'types' => app('mail-gateways')->map(fn ($gateway) => [
|
||||
'id' => $gateway,
|
||||
'name' => $gateway::name(),
|
||||
'fields' => $gateway::presentFields('storeValidator'),
|
||||
'defaults' => (object) $gateway::defaults(),
|
||||
])->prepend([
|
||||
'id' => null,
|
||||
'name' => '-- kein --',
|
||||
'fields' => [],
|
||||
'defaults' => (object) [],
|
||||
]),
|
||||
'default' => [
|
||||
'domain' => '',
|
||||
'name' => '',
|
||||
'type' => [
|
||||
'params' => [],
|
||||
'cls' => null,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ class NamiPutMemberAction
|
|||
'groupId' => $member->group->nami_id,
|
||||
'id' => $member->nami_id,
|
||||
'version' => $member->version,
|
||||
'keepdata' => false,
|
||||
'keepdata' => $member->keepdata,
|
||||
]);
|
||||
$response = $api->putMember($namiMember, $activity ? $activity->nami_id : null, $subactivity ? $subactivity->nami_id : null);
|
||||
Member::withoutEvents(function () use ($response, $member) {
|
||||
|
|
|
@ -58,7 +58,7 @@ class Member extends Model implements Geolocatable
|
|||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'subscription_id'];
|
||||
public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'subscription_id', 'keepdata'];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
|
@ -77,6 +77,7 @@ class Member extends Model implements Geolocatable
|
|||
'multiply_pv' => 'boolean',
|
||||
'multiply_more_pv' => 'boolean',
|
||||
'is_leader' => 'boolean',
|
||||
'keepdata' => 'boolean',
|
||||
'bill_kind' => BillKind::class,
|
||||
'mitgliedsnr' => 'integer',
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ class MemberRequest extends FormRequest
|
|||
'other_country' => '',
|
||||
'salutation' => '',
|
||||
'comment' => '',
|
||||
'keepdata' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ class MemberResource extends JsonResource
|
|||
'lat' => $this->lat,
|
||||
'lon' => $this->lon,
|
||||
'group_name' => $this->group->name,
|
||||
'keepdata' => $this->keepdata,
|
||||
'links' => [
|
||||
'membership_index' => route('member.membership.index', ['member' => $this->getModel()]),
|
||||
'invoiceposition_index' => route('member.invoice-position.index', ['member' => $this->getModel()]),
|
||||
|
@ -202,6 +203,7 @@ class MemberResource extends JsonResource
|
|||
'has_svk' => false,
|
||||
'multiply_pv' => false,
|
||||
'multiply_more_pv' => false,
|
||||
'keepdata' => false,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Membership;
|
||||
|
||||
use App\Dashboard\Blocks\Block;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class TestersBlock extends Block
|
||||
{
|
||||
/**
|
||||
* @return Builder<Member>
|
||||
*/
|
||||
public function query(): Builder
|
||||
{
|
||||
return Member::whereHas('memberships', fn ($q) => $q->isTrying())
|
||||
->with('memberships', fn ($q) => $q->isTrying());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{members: array<int, array{name: string, try_ends_at: string, try_ends_at_human: string}>}
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'members' => $this->query()->get()->map(fn ($member) => [
|
||||
'name' => $member->fullname,
|
||||
'try_ends_at' => $member->memberships->first()->from->addWeeks(8)->format('d.m.Y'),
|
||||
'try_ends_at_human' => $member->memberships->first()->from->addWeeks(8)->diffForHumans(),
|
||||
])->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
public function component(): string
|
||||
{
|
||||
return 'testers';
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Endende Schhnupperzeiten';
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Prevention\Mails;
|
||||
|
||||
use App\Invoice\InvoiceSettings;
|
||||
use App\Lib\Editor\EditorData;
|
||||
use App\Prevention\Contracts\Preventable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
|
@ -11,6 +10,7 @@ use Illuminate\Mail\Mailable;
|
|||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
|
||||
class PreventionRememberMail extends Mailable
|
||||
{
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Form\Models\Form;
|
||||
use App\Mailgateway\Types\LocalType;
|
||||
use App\Mailgateway\Types\MailmanType;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Telescope\Telescope;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -30,11 +27,6 @@ class AppServiceProvider extends ServiceProvider
|
|||
return $this;
|
||||
});
|
||||
|
||||
app()->bind('mail-gateways', fn () => collect([
|
||||
LocalType::class,
|
||||
MailmanType::class,
|
||||
]));
|
||||
|
||||
app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class));
|
||||
|
||||
Blade::componentNamespace('App\\View\\Mail', 'mail-view');
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class BaseServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Blade::componentNamespace('App\\View\\Ui', 'ui');
|
||||
Blade::componentNamespace('App\\View\\Page', 'page');
|
||||
Blade::componentNamespace('App\\View\\Form', 'form');
|
||||
|
||||
ComponentAttributeBag::macro('mergeWhen', function ($condition, $key, $attributes) {
|
||||
/** @var ComponentAttributeBag */
|
||||
$self = $this;
|
||||
return $condition ? $self->merge([$key => $attributes]) : $self;
|
||||
});
|
||||
|
||||
Livewire::resolveMissingComponent(function ($name) {
|
||||
'modules.dashboard.components.dashboard-component';
|
||||
if (str($name)->startsWith('modules.')) {
|
||||
return str($name)->explode('.')->map(fn ($name) => str($name)->studly()->toString())->implode('\\');
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
/** @deprecated */
|
||||
class StoreAction
|
||||
{
|
||||
use AsAction;
|
||||
|
|
|
@ -8,6 +8,7 @@ use Inertia\Inertia;
|
|||
use Inertia\Response;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
/** @deprecated */
|
||||
class ViewAction
|
||||
{
|
||||
use AsAction;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Setting\Data;
|
||||
|
||||
use Livewire\Mechanisms\HandleComponents\Synthesizers\Synth;
|
||||
use Spatie\LaravelSettings\Settings;
|
||||
|
||||
class SettingSynthesizer extends Synth
|
||||
{
|
||||
public static $key = 'setting-class';
|
||||
|
||||
public static function match($target)
|
||||
{
|
||||
return $target instanceof Settings;
|
||||
}
|
||||
|
||||
public function dehydrate($target)
|
||||
{
|
||||
return [$target->toArray(), ['setting_class' => get_class($target)]];
|
||||
}
|
||||
|
||||
public function hydrate($value, $meta)
|
||||
{
|
||||
return app($meta['setting_class'])->fill($value);
|
||||
}
|
||||
|
||||
public function get(&$target, $key)
|
||||
{
|
||||
return $target->{$key};
|
||||
}
|
||||
|
||||
public function set(&$target, $key, $value)
|
||||
{
|
||||
$target->{$key} = $value;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ abstract class LocalSettings extends Settings
|
|||
|
||||
public function url(): string
|
||||
{
|
||||
return route('setting.view', ['settingGroup' => $this->group()]);
|
||||
return url('setting/' . $this->group());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,14 +3,10 @@
|
|||
namespace App\Setting;
|
||||
|
||||
use App\Group;
|
||||
use App\Initialize\Actions\NamiLoginCheckAction;
|
||||
use App\Nami\Actions\SettingSaveAction;
|
||||
use App\Setting\Contracts\Storeable;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Zoomyboy\LaravelNami\Api;
|
||||
use Zoomyboy\LaravelNami\Nami;
|
||||
|
||||
class NamiSettings extends LocalSettings implements Storeable
|
||||
class NamiSettings extends LocalSettings
|
||||
{
|
||||
public int $mglnr;
|
||||
|
||||
|
@ -43,14 +39,6 @@ class NamiSettings extends LocalSettings implements Storeable
|
|||
];
|
||||
}
|
||||
|
||||
public function beforeSave(ActionRequest $request): void
|
||||
{
|
||||
NamiLoginCheckAction::run([
|
||||
'mglnr' => $request->mglnr,
|
||||
'password' => $request->password,
|
||||
]);
|
||||
}
|
||||
|
||||
public function localGroup(): ?Group
|
||||
{
|
||||
return Group::firstWhere('nami_id', $this->default_group_id);
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
namespace App\Setting;
|
||||
|
||||
use App\Invoice\InvoiceSettings;
|
||||
use App\Setting\Contracts\Storeable;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SettingFactory
|
||||
{
|
||||
|
@ -26,16 +25,11 @@ class SettingFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{url: string, is_active: bool}>
|
||||
* @return Collection<int, LocalSettings>
|
||||
*/
|
||||
public function getShare(): array
|
||||
public function all(): Collection
|
||||
{
|
||||
return collect($this->settings)->map(fn ($setting) => [
|
||||
'url' => (new $setting)->url(),
|
||||
'is_active' => url(request()->path()) === (new $setting)->url(),
|
||||
'title' => $setting::title(),
|
||||
])
|
||||
->toArray();
|
||||
return collect($this->settings)->map(fn ($setting) => new $setting);
|
||||
}
|
||||
|
||||
public function resolveGroupName(string $name): LocalSettings
|
||||
|
|
|
@ -4,14 +4,13 @@ namespace App\Setting;
|
|||
|
||||
use App\Fileshare\FileshareSettings;
|
||||
use App\Form\FormSettings;
|
||||
use App\Invoice\InvoiceSettings;
|
||||
use App\Mailgateway\MailgatewaySettings;
|
||||
use App\Module\ModuleSettings;
|
||||
use Modules\Module\ModuleSettings;
|
||||
use App\Prevention\PreventionSettings;
|
||||
use App\Setting\Actions\StoreAction;
|
||||
use App\Setting\Actions\ViewAction;
|
||||
use App\Setting\Data\SettingSynthesizer;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
|
||||
class SettingServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -24,9 +23,6 @@ class SettingServiceProvider extends ServiceProvider
|
|||
{
|
||||
app()->singleton(SettingFactory::class, fn () => new SettingFactory());
|
||||
app(Router::class)->bind('settingGroup', fn ($param) => app(SettingFactory::class)->resolveGroupName($param));
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->name('setting.view')->get('/setting/{settingGroup}', ViewAction::class);
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->name('setting.data')->get('/setting/{settingGroup}/data', ViewAction::class);
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->name('setting.store')->post('/setting/{settingGroup}', StoreAction::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,10 +34,11 @@ class SettingServiceProvider extends ServiceProvider
|
|||
{
|
||||
app(SettingFactory::class)->register(ModuleSettings::class);
|
||||
app(SettingFactory::class)->register(InvoiceSettings::class);
|
||||
app(SettingFactory::class)->register(MailgatewaySettings::class);
|
||||
app(SettingFactory::class)->register(NamiSettings::class);
|
||||
app(SettingFactory::class)->register(FormSettings::class);
|
||||
app(SettingFactory::class)->register(FileshareSettings::class);
|
||||
app(SettingFactory::class)->register(PreventionSettings::class);
|
||||
|
||||
Livewire::propertySynthesizer(SettingSynthesizer::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Enums;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
|
||||
enum Variant: string
|
||||
{
|
||||
|
||||
case PRIMARY = 'primary';
|
||||
case SECONDARY = 'secondary';
|
||||
case PRIMARYLIGHT = 'primary-light';
|
||||
case WARNING = 'warning';
|
||||
case INFO = 'info';
|
||||
case DANGER = 'danger';
|
||||
|
||||
public function foreground(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PRIMARY => 'text-primary-300',
|
||||
self::SECONDARY => 'text-primary-400',
|
||||
self::PRIMARYLIGHT => 'text-primary-200',
|
||||
self::WARNING => 'text-yellow-300',
|
||||
self::INFO => 'text-blue-300',
|
||||
self::DANGER => 'text-red-100',
|
||||
};
|
||||
}
|
||||
|
||||
public function background(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PRIMARY => 'bg-primary-700',
|
||||
self::SECONDARY => 'bg-primary-800',
|
||||
self::PRIMARYLIGHT => 'bg-primary-600',
|
||||
self::WARNING => 'bg-yellow-700',
|
||||
self::INFO => 'bg-blue-700',
|
||||
self::DANGER => 'bg-red-400',
|
||||
};
|
||||
}
|
||||
|
||||
public function hoverForeground(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PRIMARY => 'hover:text-primary-100',
|
||||
self::SECONDARY => 'hover:text-primary-200',
|
||||
self::PRIMARYLIGHT => 'hover:text-primary-100',
|
||||
self::WARNING => 'hover:text-yellow-100',
|
||||
self::INFO => 'hover:text-blue-100',
|
||||
self::DANGER => 'hover:text-red-100',
|
||||
};
|
||||
}
|
||||
|
||||
public function hoverBackground(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PRIMARY => 'hover:bg-primary-500',
|
||||
self::SECONDARY => 'hover:bg-primary-600',
|
||||
self::PRIMARYLIGHT => 'hover:bg-primary-500',
|
||||
self::WARNING => 'hover:bg-yellow-500',
|
||||
self::INFO => 'hover:bg-blue-500',
|
||||
self::DANGER => 'hover:bg-red-500',
|
||||
};
|
||||
}
|
||||
|
||||
public static function fromString(string $input): self
|
||||
{
|
||||
return collect(static::cases())
|
||||
->first(fn ($variant) => $variant->value === $input)
|
||||
?: throw new InvalidArgumentException("Unknown variant: {$input} - Available Variants: " . self::values()->implode(', '));
|
||||
}
|
||||
|
||||
public static function values(): Collection
|
||||
{
|
||||
return collect(static::cases())->map(fn ($variant) => $variant->value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use App\View\Traits\HasFormDimensions;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Hint extends Component
|
||||
{
|
||||
|
||||
use HasFormDimensions;
|
||||
|
||||
public function __construct(
|
||||
public bool $required = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div {{ $attributes->merge(['class' => 'h-full items-center flex absolute top-0 right-0']) }}>
|
||||
<div x-tooltip.raw="{{$slot}}" class="mr-2">
|
||||
<x-ui::sprite src="info-button" class="w-5 h-5 text-primary-700"></x-ui::sprite>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use App\View\Traits\HasFormDimensions;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Label extends Component
|
||||
{
|
||||
|
||||
use HasFormDimensions;
|
||||
|
||||
public function __construct(
|
||||
public bool $required = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<span class="font-semibold leading-none text-gray-400 group-[.size-default]:text-sm group-[.size-sm]:text-xs">
|
||||
{{ $slot }}
|
||||
@if ($required)
|
||||
<span class="text-red-800"> *</span>
|
||||
@endif
|
||||
</span>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use App\View\Traits\HasFormDimensions;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Lever extends Component
|
||||
{
|
||||
|
||||
use HasFormDimensions;
|
||||
|
||||
public string $id;
|
||||
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $size = 'default',
|
||||
public $value = null,
|
||||
public ?string $hint = null,
|
||||
public bool $disabled = false,
|
||||
public bool $required = false,
|
||||
public string $label = '',
|
||||
) {
|
||||
$this->id = str()->uuid()->toString();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<label class="flex flex-col items-start group {{$heightClass}} " for="{{$id}}" style="{{$heightVars}}">
|
||||
@if ($label)
|
||||
<x-form::label :required="$required">{{$label}}</x-form::label>
|
||||
@endif
|
||||
<span class="relative flex-none flex h-[var(--height)] @if($hint) pr-8 @endif">
|
||||
<input id="{{$id}}" type="checkbox" name="{{$name}}" value="{{$value}}" @if($disabled) disabled="disabled" @endif class="absolute peer opacity-0" {{ $attributes }} />
|
||||
<span class="relative cursor-pointer h-full w-[calc(var(--height)*2)] rounded peer-focus:bg-red-500 duration-300 bg-gray-700 peer-checked:bg-primary-700"></span>
|
||||
<span class="absolute h-full top-0 left-0 flex-none flex justify-center items-center aspect-square">
|
||||
<x-ui::sprite
|
||||
class="relative text-gray-400 flex-none text-white duration-300 group-[.size-default]:size-3 group-[.size-sm]:size-2"
|
||||
src="check"
|
||||
></x-ui::sprite>
|
||||
</span>
|
||||
<span class="absolute h-full top-0 left-[var(--height)] flex-none flex justify-center items-center aspect-square">
|
||||
<x-ui::sprite
|
||||
class="relative text-gray-400 flex-none text-white duration-300 group-[.size-default]:size-3 group-[.size-sm]:size-2"
|
||||
src="close"
|
||||
></x-ui::sprite>
|
||||
</span>
|
||||
<var class="absolute duration-300 bg-gray-400 rounded
|
||||
top-[var(--padding)] left-[var(--padding)]
|
||||
size-[calc(var(--height)-var(--padding)*2)] peer-checked:left-[calc(var(--height)+var(--padding))]"
|
||||
></var>
|
||||
@if($hint)
|
||||
<x-form::hint>{{$hint}}</x-form::hint>
|
||||
@endif
|
||||
</span>
|
||||
</label>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class SaveButton extends Component
|
||||
{
|
||||
|
||||
public function __construct(public string $form = '')
|
||||
{
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<button @if($form) form="{{$form}}" @endif type="submit" class="flex items-center transition-all justify-center w-8 h-8 bg-primary-700 hover:bg-primary-600 rounded" x-tooltip="`speichern`">
|
||||
<x-ui::sprite class="w-4 h-4 text-white" src="save"></x-ui::sprite>
|
||||
</button>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use App\View\Traits\HasFormDimensions;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Select extends Component
|
||||
{
|
||||
|
||||
use HasFormDimensions;
|
||||
|
||||
public string $id;
|
||||
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $size = 'default',
|
||||
public ?string $hint = null,
|
||||
public bool $required = false,
|
||||
public string $label = '',
|
||||
public $options = [],
|
||||
public bool $disabled = false,
|
||||
) {
|
||||
$this->id = str()->uuid()->toString();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<label class="flex flex-col group {{$heightClass}}" for="{{$id}}" style="{{$heightVars}}">
|
||||
@if ($label)
|
||||
<x-form::label :required="$required">{{$label}}</x-form::label>
|
||||
@endif
|
||||
|
||||
<div class="relative flex-none flex">
|
||||
<select {{$attributes}} @if($disabled) disabled @endif name="{{$name}}" id="{{$id}}"
|
||||
class="
|
||||
w-full h-[var(--height)] border-gray-600 border-solid text-gray-300 bg-gray-700 leading-none rounded-lg
|
||||
group-[.size-default]:border-2 group-[.size-sm]:border
|
||||
group-[.size-default]:text-sm group-[.size-sm]:text-xs
|
||||
group-[.size-default]:px-2 group-[.size-sm]:px-1
|
||||
py-0
|
||||
"
|
||||
>
|
||||
<option value="">-- kein --</option>
|
||||
@foreach ($options as $option)
|
||||
<option value="{{$option['id']}}">{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-ui::errors :for="$name" />
|
||||
@if($hint)
|
||||
<x-form::hint class="right-6">{{$hint}}</x-form::hint>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</label>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use App\View\Traits\HasFormDimensions;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Text extends Component
|
||||
{
|
||||
|
||||
use HasFormDimensions;
|
||||
|
||||
public string $id;
|
||||
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $size = 'default',
|
||||
public ?string $hint = null,
|
||||
public bool $required = false,
|
||||
public string $label = '',
|
||||
public string $type = 'text'
|
||||
) {
|
||||
$this->id = str()->uuid()->toString();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<label class="flex flex-col group {{$heightClass}}" for="{{$id}}" style="{{$heightVars}}">
|
||||
@if ($label)
|
||||
<x-form::label :required="$required">{{$label}}</x-form::label>
|
||||
@endif
|
||||
<div class="relative flex-none flex">
|
||||
<input
|
||||
id="{{$id}}"
|
||||
type="{{$type}}"
|
||||
@if ($type === 'password') autocomplete="off" @endif
|
||||
placeholder=""
|
||||
class="
|
||||
w-full h-[var(--height)] border-gray-600 border-solid text-gray-300 bg-gray-700 leading-none rounded-lg
|
||||
group-[.size-default]:border-2 group-[.size-sm]:border
|
||||
group-[.size-default]:text-sm group-[.size-sm]:text-xs
|
||||
group-[.size-default]:p-2 group-[.size-sm]:p-1
|
||||
"
|
||||
{{ $attributes }}
|
||||
/>
|
||||
<x-ui::errors :for="$name" />
|
||||
@if($hint)
|
||||
<x-form::hint>{{$hint}}</x-form::hint>
|
||||
@endif
|
||||
</div>
|
||||
</label>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Header extends Component
|
||||
{
|
||||
|
||||
public function __construct(public string $title)
|
||||
{
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div class="h-16 px-6 flex items-center justify-between border-b border-solid border-gray-600 group-[.is-bright]:border-gray-500">
|
||||
<div class="flex items-center space-x-4">
|
||||
{{ $beforeTitle ?? ''}}
|
||||
<span class="text-sm md:text-xl font-semibold leading-none text-white">{{ $title }}</span>
|
||||
{{ $toolbar ?? '' }}
|
||||
</div>
|
||||
<div class="flex items-center space-x-4 ml-2">
|
||||
{{ $right ?? '' }}
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Layout extends Component
|
||||
{
|
||||
|
||||
public function __construct(public string $pageClass = '', public string $title = '')
|
||||
{
|
||||
session()->put('title', $title);
|
||||
}
|
||||
|
||||
public function userName(): string
|
||||
{
|
||||
return auth()->user()->firstname . ' ' . auth()->user()->lastname;
|
||||
}
|
||||
|
||||
public function userAvatar(): string
|
||||
{
|
||||
return auth()->user()->getGravatarUrl();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div class="grow flex flex-col" @refresh-page.window="$wire.$refresh">
|
||||
<div class="grow bg-gray-900 flex flex-col duration-300 navbar:ml-60">
|
||||
<x-page::header :title="$title">
|
||||
<x-slot:beforeTitle>
|
||||
<a href="#" class="lg:hidden" wire:click.prevent="dispatch('toggle-sidebar')">
|
||||
<x-ui::sprite src="menu" class="text-gray-100 w-5 h-5"></x-ui::sprite>
|
||||
</a>
|
||||
</x-slot:beforeTitle>
|
||||
<x-slot:toolbar>
|
||||
{{ $toolbar ?? ''}}
|
||||
</x-slot:toolbar>
|
||||
<x-slot:right>
|
||||
{{ $right ?? '' }}
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="rounded-full overflow-hidden border-2 border-solid border-gray-300">
|
||||
<img src="{{ $userAvatar() }}" class="w-8 h-8 object-cover" />
|
||||
</div>
|
||||
<div class="text-gray-300"">{{ $userName() }}</div>
|
||||
</div>
|
||||
</x-slot:right>
|
||||
</x-page::header>
|
||||
|
||||
<div class="grow flex flex-col {{$pageClass}}">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
<livewire:page.sidebar :mobile="true" />
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class MenuEntry extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $href,
|
||||
public string $menu,
|
||||
public string $icon,
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<a class="flex text-white py-2 px-3 rounded-lg hover:bg-gray-600 {{ $menu === session('menu') ? 'bg-gray-700' : '' }}" href="{{$href}}">
|
||||
<x-ui::sprite class="text-white w-6 h-6 mr-4" src="{{$icon}}"></x-ui::sprite>
|
||||
<span class="font-semibold">
|
||||
{{ $slot }}
|
||||
</span>
|
||||
</a>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class Modal extends Component
|
||||
{
|
||||
|
||||
public ?string $component = null;
|
||||
public array $props = [];
|
||||
public string $key = '';
|
||||
public array $actions = [];
|
||||
public ?string $size = null;
|
||||
public string $title = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
#[On('openModal')]
|
||||
public function onOpenModal(string $component, string $title, array $props = [], array $actions = ['storeable', 'closeable'], string $size = "xl"): void
|
||||
{
|
||||
$this->component = $component;
|
||||
$this->props = $props;
|
||||
$this->size = $size;
|
||||
$this->title = $title;
|
||||
$this->key = md5(json_encode(['component' => $component, 'props' => $props]));
|
||||
$this->actions = $this->parseActions($actions);
|
||||
}
|
||||
|
||||
public function parseActions(array $actions): array
|
||||
{
|
||||
return collect($actions)->map(function ($action) {
|
||||
if ($action === 'closeable') {
|
||||
return ['event' => 'closeModal', 'icon' => 'close', 'label' => 'Schließen'];
|
||||
}
|
||||
|
||||
if ($action === 'storeable') {
|
||||
return ['event' => 'onStoreFromModal', 'icon' => 'save', 'label' => 'Speichern'];
|
||||
}
|
||||
|
||||
return $action;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
#[On('closeModal')]
|
||||
public function onCloseModal(): void
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
public function sizeClass(): string
|
||||
{
|
||||
if ($this->size === 'lg') {
|
||||
return 'max-w-lg';
|
||||
}
|
||||
|
||||
if ($this->size === 'xl') {
|
||||
return 'max-w-xl';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
@if($component)
|
||||
<div class="fixed z-40 top-0 left-0 w-full h-full flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm" @click.self="$dispatch('closeModal')">
|
||||
<div
|
||||
class="relative rounded-lg p-8 bg-zinc-800 shadow-2xl shadow-black border border-zinc-700 border-solid w-full max-h-full flex flex-col overflow-auto {{$this->sizeClass()}}"
|
||||
>
|
||||
<div class="flex">
|
||||
<h3 class="font-semibold text-primary-200 text-xl grow">{{$title}}</h3>
|
||||
<div class="flex space-x-6">
|
||||
@foreach ($this->actions as $action)
|
||||
<a x-tooltip.raw="{{$action['label']}}" href="#" @click.prevent="$dispatch('{{$action['event']}}')">
|
||||
<x-ui::sprite :src="$action['icon']" class="text-zinc-400 w-6 h-6"></x-ui::sprite>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-primary-100 group is-popup grow flex flex-col mt-3">
|
||||
<div>
|
||||
@if ($component)
|
||||
@livewire($component, $props, key($key))
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div></div>
|
||||
@endif
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use App\Setting\SettingFactory;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class SettingLayout extends Component
|
||||
{
|
||||
|
||||
public array $entries;
|
||||
|
||||
public function __construct(public string $active)
|
||||
{
|
||||
$this->entries = app(SettingFactory::class)->all()
|
||||
->map(fn ($setting) => [
|
||||
'url' => $setting->url(),
|
||||
'is_active' => get_class($setting) === $active,
|
||||
'title' => $setting->title(),
|
||||
])->toArray();
|
||||
session()->put('menu', 'setting');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::layout :title="$active::title()">
|
||||
<x-slot:right>
|
||||
{{ $right ?? '' }}
|
||||
</x-slot:right>
|
||||
<x-slot:toolbar>
|
||||
{{ $toolbar ?? '' }}
|
||||
</x-slot:toolbar>
|
||||
<div class="flex grow relative">
|
||||
<x-ui::menulist :entries="$entries"></x-ui::menulist>
|
||||
<div class="grow">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</x-page::layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Page;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Sidebar extends Component
|
||||
{
|
||||
|
||||
public $mobile = false;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div
|
||||
class="fixed z-40 bg-gray-800 p-6 w-60 top-0 h-screen border-r border-gray-600 border-solid flex flex-col justify-between duration-300
|
||||
@if (!$mobile) left-[-16rem] navbar:left-0 @endif"
|
||||
@if($mobile)
|
||||
x-data="{ visible: false }"
|
||||
x-on:toggle-sidebar.window="visible = true"
|
||||
:class="{'left-[-16rem]' : !visible, 'left-0': visible}"
|
||||
@endif
|
||||
>
|
||||
<div class="grid gap-2">
|
||||
<x-page::menu-entry href="/" menu="dashboard" icon="loss">Dashboard</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/member" menu="member" icon="user">Mitglieder</x-page::menu-entry>
|
||||
<x-page::menu-entry v-show="hasModule('bill')" href="/subscription" menu="subscription" icon="money">Beiträge</x-page::menu-entry>
|
||||
<x-page::menu-entry v-show="hasModule('bill')" href="/invoice" menu="invoice" icon="moneypaper">Rechnungen</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/contribution" menu="contribution" icon="contribution">Zuschüsse</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/activity" menu="activity" icon="activity">Tätigkeiten</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/group" menu="group" icon="group">Gruppierungen</x-page::menu-entry>
|
||||
<x-page::menu-entry v-if="hasModule('event')" href="/form" menu="form" icon="event">Veranstaltungen</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/maildispatcher" menu="maildispatcher" icon="at">Mail-Verteiler</x-page::menu-entry>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<a href="#" class="flex w-full px-3 py-2 rounded-xl text-gray-300 bg-gray-700" @click.prevent="dispatch('show-search')">
|
||||
<x-ui::sprite class="text-white w-6 h-6 mr-4" src="search"></x-ui::sprite>
|
||||
<div class="">Suchen</div>
|
||||
</a>
|
||||
<x-page::menu-entry href="/setting" menu="setting" icon="setting">Einstellungen</x-page::menu-entry>
|
||||
<x-page::menu-entry href="/logout" menu="" icon="logout">Abmelden</x-page::menu-entry>
|
||||
</div>
|
||||
@if($mobile)
|
||||
<a href="#" class="absolute right-0 top-0 mr-2 mt-2" @click.prevent="visible = false">
|
||||
<x-ui::sprite src="close" class="w-5 h-5 text-gray-300"></x-ui::sprite>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Traits;
|
||||
|
||||
use App\View\Enums\Variant;
|
||||
|
||||
trait HasColors
|
||||
{
|
||||
public function bgColor(string $variant): string
|
||||
{
|
||||
$variant = Variant::fromString($variant);
|
||||
|
||||
return implode(' ', [
|
||||
$variant->background(),
|
||||
$variant->hoverBackground(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function fgColor(string $variant): string
|
||||
{
|
||||
$variant = Variant::fromString($variant);
|
||||
|
||||
return implode(' ', [
|
||||
$variant->foreground(),
|
||||
$variant->hoverForeground(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function allColors(string $variant): string
|
||||
{
|
||||
return "{$this->bgColor($variant)} {$this->fgColor($variant)}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Traits;
|
||||
|
||||
trait HasFormDimensions
|
||||
{
|
||||
|
||||
public function heightVars(): string
|
||||
{
|
||||
return data_get([
|
||||
'default' => '--height: 35px; --padding: 3px;',
|
||||
'sm' => '--height: 23px; --padding: 2px;',
|
||||
], $this->size);
|
||||
}
|
||||
|
||||
public function heightClass(): string
|
||||
{
|
||||
return data_get([
|
||||
'default' => 'size-default',
|
||||
'sm' => 'size-sm',
|
||||
], $this->size);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use App\View\Traits\HasColors;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Action extends Component
|
||||
{
|
||||
|
||||
use HasColors;
|
||||
|
||||
public function __construct(
|
||||
public string $icon,
|
||||
public string $variant = 'primary'
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<a x-tooltip.raw="{{$slot}}" href="#" {{ $attributes }} class="inline-flex
|
||||
w-6 h-5 flex items-center justify-center rounded {{ $allColors($variant) }}
|
||||
">
|
||||
<x-ui::sprite class="w-3 h-3 flex-none" :src="$icon"></x-ui::sprite>
|
||||
</a>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use App\View\Traits\HasColors;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Badge extends Component
|
||||
{
|
||||
|
||||
use HasColors;
|
||||
|
||||
public function __construct(
|
||||
public string $icon,
|
||||
public string $variant = 'primary'
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<button type="button" href="#" {{ $attributes }} class="h-6 px-3 space-x-2 items-center rounded-full {{ $allColors($variant) }} hidden lg:flex">
|
||||
<x-ui::sprite class="w-3 h-3 flex-none" :src="$icon"></x-ui::sprite>
|
||||
<span class="text-sm">
|
||||
{{$slot}}
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" x-tooltip.raw="{{$slot}}" href="#" {{ $attributes }} class="h-6 px-3 space-x-2 flex items-center rounded-full {{ $allColors($variant) }} lg:hidden">
|
||||
<x-ui::sprite class="w-3 h-3 flex-none" :src="$icon"></x-ui::sprite>
|
||||
</button>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class BooleanDisplay extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public bool $value,
|
||||
public string $hint,
|
||||
public string $right,
|
||||
public string $wrong,
|
||||
public bool $dark = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function spriteClass(): string
|
||||
{
|
||||
return $this->value ? 'text-green-800 group-[.dark]:text-green-600' : 'text-red-800 group-[.dark]:text-red-600';
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div x-tooltip.raw="{{$hint}}" class="flex space-x-2 items-center group @if($dark) dark @endif">
|
||||
<div class="border-2 rounded-full w-5 h-5 flex items-center justify-center
|
||||
@if ($value) border-green-700 group-[.dark]:border-green-500
|
||||
@else border-red-700 group-[.dark]:border-red-500
|
||||
@endif
|
||||
"
|
||||
>
|
||||
<x-ui::sprite :src="$value ? 'check ' :'close'" class="w-3 h-3 flex-none {{$spriteClass}}"></x-ui::sprite>
|
||||
</div>
|
||||
<div class="text-gray-400 text-xs">{{ $value ? $right : $wrong }}</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Box extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $containerClass = '',
|
||||
public bool $second = false,
|
||||
public string $title = '',
|
||||
public string $inTitle = '',
|
||||
) {
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<section {!! $attributes
|
||||
->mergeWhen($second, 'class', 'bg-gray-700 group-[.is-popup]:bg-zinc-600')
|
||||
->mergeWhen(!$second, 'class', 'bg-gray-800 group-[.is-popup]:bg-zinc-700')
|
||||
->mergeWhen(true, 'class', 'p-3 rounded-lg flex flex-col')
|
||||
!!}>
|
||||
<div class="flex items-center">
|
||||
@if($title)
|
||||
<div class="col-span-full font-semibold text-gray-300 group-[.is-popup]:text-zinc-300">{{$title}}</div>
|
||||
@endif
|
||||
{{$inTitle}}
|
||||
</div>
|
||||
<main class="{{ $title ? 'mt-2' : '' }} {{ $containerClass }}">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</section>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Errors extends Component
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $for,
|
||||
) {
|
||||
}
|
||||
|
||||
public function id(): string
|
||||
{
|
||||
return 'errors-' . str_replace('.', '--', $this->for);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
@error($for)
|
||||
<div x-data="{error: ''}" x-init="window.setTimeout(() => document.querySelector('#{{$id()}}') ? document.querySelector('#{{$id()}}').remove() : null, 2000)" class="absolute bottom-[calc(100%+0.5rem)] right-0" id="{{$id}}">
|
||||
<div class="tippy-box" tabindex="-1" data-theme="danger" data-placement="top">
|
||||
<div class="tippy-content">{{$message}}</div>
|
||||
<div class="tippy-arrow absolute right-0 mr-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
@enderror
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Menulist extends Component
|
||||
{
|
||||
|
||||
public function __construct(public array $entries)
|
||||
{
|
||||
}
|
||||
|
||||
public function activeClass($entry): string
|
||||
{
|
||||
return $entry['is_active'] ? 'bg-gray-600' : '';
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div class="p-6 bg-gray-700 border-r border-gray-600 flex-none w-maxc flex flex-col justify-between">
|
||||
<div class="grid gap-1">
|
||||
@foreach($entries as $entry)
|
||||
<a href="{{$entry['url']}}" class="rounded py-1 px-3 text-gray-400 duration-200 hover:bg-gray-600 {{$activeClass($entry)}}" @if($entry['is_active']) data-active @endif>
|
||||
{{$entry['title']}}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
<slot name="bottom"></slot>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class SettingIntro extends Component
|
||||
{
|
||||
|
||||
public function __construct(public ?string $title = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div {{$attributes}}>
|
||||
@if ($title) <h2 class="text-lg font-semibold text-gray-300">{{$title}}</h2> @endif
|
||||
<div class="text-gray-100 text-sm">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Sprite extends Component
|
||||
{
|
||||
|
||||
public string $spritemapFile;
|
||||
|
||||
public function __construct(
|
||||
public string $src = '',
|
||||
) {
|
||||
$this->spritemapFile = Cache::rememberForever('spritemap_file', function () {
|
||||
$manifest = json_decode(file_get_contents(public_path('build/manifest.json')), true);
|
||||
return asset('build/' . $manifest['spritemap.svg']['file']);
|
||||
});
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<svg {{ $attributes->merge(['class' => 'fill-current']) }}><use xlink:href="{{$spritemapFile}}#sprite-{{$src}}" /></svg>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Ui;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Table extends Component
|
||||
{
|
||||
|
||||
public function __construct(public string $mode = 'dark')
|
||||
{
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div class="@container/table">
|
||||
<table cellpadding="0" cellspacing="0" border="0" class="w-full @if ($mode === 'dark') table-dark @else table-light @endif
|
||||
[&_th]:text-left [&_th]:px-2 [&_th]:@4xl/table:px-6 [&_th]:text-gray-200 [&_th]:font-semibold [&_th]:py-3 [&_th]:border-gray-600 [&_th]:border-b
|
||||
[&_tbody_tr]:text-gray-200 [&_tbody_tr]:duration-300 [&_tbody_tr]:rounded [&_tbody_tr:hover]:bg-gray-800
|
||||
[&_tr_td]:py-1 [&_tr_td]:px-2 [&_tr_td]:@4xl/table:px-6
|
||||
[&.table-light_th]:border-gray-500 [&.table-light_tbody_tr:hover]:bg-gray-700
|
||||
|
||||
|
||||
">
|
||||
{{ $slot }}
|
||||
</table>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -61,6 +61,7 @@
|
|||
"laravel/ui": "^4.0",
|
||||
"league/csv": "^9.9",
|
||||
"league/flysystem-webdav": "dev-master as 3.28.0",
|
||||
"livewire/livewire": "^3.5",
|
||||
"lorisleiva/laravel-actions": "^2.4",
|
||||
"meilisearch/meilisearch-php": "^1.6",
|
||||
"monicahq/laravel-sabre": "^1.6",
|
||||
|
@ -85,7 +86,8 @@
|
|||
"mockery/mockery": "^1.4.4",
|
||||
"orchestra/testbench": "^9.0",
|
||||
"pestphp/pest": "^3.0",
|
||||
"phpstan/phpstan-mockery": "^1.1"
|
||||
"phpstan/phpstan-mockery": "^1.1",
|
||||
"qossmic/deptrac": "^2.0"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
|
@ -104,6 +106,7 @@
|
|||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Modules\\": "modules/",
|
||||
"Plugins\\": "plugins/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
|
@ -111,6 +114,7 @@
|
|||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Modules\\Dashboard\\Tests\\": "modules/dashboard/tests/",
|
||||
"Tests\\": "tests/",
|
||||
"Zoomyboy\\LaravelNami\\Tests\\": "packages/laravel-nami/tests/"
|
||||
}
|
||||
|
@ -118,6 +122,10 @@
|
|||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"scripts": {
|
||||
"archtest": [
|
||||
"./vendor/bin/deptrac analyze",
|
||||
"./vendor/bin/pest tests/Arch.php"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
|
|
|
@ -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": "10f0b0d2a8dee4a8dad4821de9935f85",
|
||||
"content-hash": "3ffa3d4a189ba5d296ca71166298a74f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amphp/amp",
|
||||
|
@ -5153,6 +5153,82 @@
|
|||
],
|
||||
"time": "2024-03-23T07:42:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "livewire/livewire",
|
||||
"version": "v3.5.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/livewire/livewire.git",
|
||||
"reference": "ce1ce71b39a3492b98f7d2f2a4583f1b163fe6ae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/ce1ce71b39a3492b98f7d2f2a4583f1b163fe6ae",
|
||||
"reference": "ce1ce71b39a3492b98f7d2f2a4583f1b163fe6ae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^10.0|^11.0",
|
||||
"illuminate/routing": "^10.0|^11.0",
|
||||
"illuminate/support": "^10.0|^11.0",
|
||||
"illuminate/validation": "^10.0|^11.0",
|
||||
"laravel/prompts": "^0.1.24",
|
||||
"league/mime-type-detection": "^1.9",
|
||||
"php": "^8.1",
|
||||
"symfony/console": "^6.0|^7.0",
|
||||
"symfony/http-kernel": "^6.2|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"calebporzio/sushi": "^2.1",
|
||||
"laravel/framework": "^10.15.0|^11.0",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"orchestra/testbench": "^8.21.0|^9.0",
|
||||
"orchestra/testbench-dusk": "^8.24|^9.1",
|
||||
"phpunit/phpunit": "^10.4",
|
||||
"psy/psysh": "^0.11.22|^0.12"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Livewire\\LivewireServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Livewire": "Livewire\\Livewire"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Livewire\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Caleb Porzio",
|
||||
"email": "calebporzio@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A front-end framework for Laravel.",
|
||||
"support": {
|
||||
"issues": "https://github.com/livewire/livewire/issues",
|
||||
"source": "https://github.com/livewire/livewire/tree/v3.5.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/livewire",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-20T19:41:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lorisleiva/laravel-actions",
|
||||
"version": "v2.8.4",
|
||||
|
@ -15667,6 +15743,51 @@
|
|||
},
|
||||
"time": "2024-09-11T15:47:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "qossmic/deptrac",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/qossmic/deptrac.git",
|
||||
"reference": "f646695c1468051138e1347d89590f9084fa6256"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/qossmic/deptrac/zipball/f646695c1468051138e1347d89590f9084fa6256",
|
||||
"reference": "f646695c1468051138e1347d89590f9084fa6256",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "For using the JUnit output formatter"
|
||||
},
|
||||
"bin": [
|
||||
"bin/deptrac",
|
||||
"deptrac.phar"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Qossmic\\Deptrac\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Deptrac is a static code analysis tool that helps to enforce rules for dependencies between software layers.",
|
||||
"support": {
|
||||
"issues": "https://github.com/qossmic/deptrac/issues",
|
||||
"source": "https://github.com/qossmic/deptrac/tree/2.0.1"
|
||||
},
|
||||
"time": "2024-06-17T10:43:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
"version": "1.2.5",
|
||||
|
|
|
@ -168,7 +168,6 @@ return [
|
|||
*/
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
|
@ -176,8 +175,14 @@ return [
|
|||
App\Tex\TexServiceProvider::class,
|
||||
App\Dav\ServiceProvider::class,
|
||||
App\Setting\SettingServiceProvider::class,
|
||||
App\Dashboard\DashboardServiceProvider::class,
|
||||
// App\Dashboard\DashboardServiceProvider::class,
|
||||
App\Providers\PluginServiceProvider::class,
|
||||
App\Providers\BaseServiceProvider::class,
|
||||
Modules\Dashboard\DashboardServiceProvider::class,
|
||||
Modules\Module\ModuleServiceProvider::class,
|
||||
Modules\Invoice\InvoiceServiceProvider::class,
|
||||
Modules\Mailgateway\MailgatewayServiceProvider::class,
|
||||
Modules\Nami\NamiServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -228,6 +233,7 @@ return [
|
|||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
'Vite' => Illuminate\Support\Facades\Vite::class,
|
||||
'Inertia' => \Inertia\Inertia::class,
|
||||
],
|
||||
];
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Class Namespace
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the root class namespace for Livewire component classes in
|
||||
| your application. This value will change where component auto-discovery
|
||||
| finds components. It's also referenced by the file creation commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'class_namespace' => 'App\\View',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| View Path
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value is used to specify where Livewire component Blade templates are
|
||||
| stored when running file creation commands like `artisan make:livewire`.
|
||||
| It is also used if you choose to omit a component's render() method.
|
||||
|
|
||||
*/
|
||||
|
||||
'view_path' => resource_path('views/livewire'),
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Layout
|
||||
|---------------------------------------------------------------------------
|
||||
| The view that will be used as the layout when rendering a single component
|
||||
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
|
||||
| In this case, the view returned by CreatePost will render into $slot.
|
||||
|
|
||||
*/
|
||||
|
||||
'layout' => 'components.layouts.app',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Lazy Loading Placeholder
|
||||
|---------------------------------------------------------------------------
|
||||
| Livewire allows you to lazy load components that would otherwise slow down
|
||||
| the initial page load. Every component can have a custom placeholder or
|
||||
| you can define the default placeholder view for all components below.
|
||||
|
|
||||
*/
|
||||
|
||||
'lazy_placeholder' => null,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Temporary File Uploads
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire handles file uploads by storing uploads in a temporary directory
|
||||
| before the file is stored permanently. All file uploads are directed to
|
||||
| a global endpoint for temporary storage. You may configure this below:
|
||||
|
|
||||
*/
|
||||
|
||||
'temporary_file_upload' => [
|
||||
'disk' => null, // Example: 'local', 's3' | Default: 'default'
|
||||
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
|
||||
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
||||
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
|
||||
'mov', 'avi', 'wmv', 'mp3', 'm4a',
|
||||
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
|
||||
],
|
||||
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
|
||||
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
|
||||
],
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Render On Redirect
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines if Livewire will run a component's `render()` method
|
||||
| after a redirect has been triggered using something like `redirect(...)`
|
||||
| Setting this to true will render the view once more before redirecting
|
||||
|
|
||||
*/
|
||||
|
||||
'render_on_redirect' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Eloquent Model Binding
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Previous versions of Livewire supported binding directly to eloquent model
|
||||
| properties using wire:model by default. However, this behavior has been
|
||||
| deemed too "magical" and has therefore been put under a feature flag.
|
||||
|
|
||||
*/
|
||||
|
||||
'legacy_model_binding' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Auto-inject Frontend Assets
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| By default, Livewire automatically injects its JavaScript and CSS into the
|
||||
| <head> and <body> of pages containing Livewire components. By disabling
|
||||
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
||||
|
|
||||
*/
|
||||
|
||||
'inject_assets' => true,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Navigate (SPA mode)
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| By adding `wire:navigate` to links in your Livewire application, Livewire
|
||||
| will prevent the default link handling and instead request those pages
|
||||
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
||||
|
|
||||
*/
|
||||
|
||||
'navigate' => [
|
||||
'show_progress_bar' => true,
|
||||
'progress_bar_color' => '#2299dd',
|
||||
],
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| HTML Morph Markers
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
|
||||
| after each update. To make this process more reliable, Livewire injects
|
||||
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
||||
|
|
||||
*/
|
||||
|
||||
'inject_morph_markers' => true,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Pagination Theme
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| When enabling Livewire's pagination feature by using the `WithPagination`
|
||||
| trait, Livewire will use Tailwind templates to render pagination views
|
||||
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
||||
|
|
||||
*/
|
||||
|
||||
'pagination_theme' => 'tailwind',
|
||||
];
|
|
@ -36,6 +36,7 @@ class MemberFactory extends Factory
|
|||
'location' => $this->faker->city,
|
||||
'email' => $this->faker->safeEmail(),
|
||||
'recertified_at' => null,
|
||||
'keepdata' => false,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->boolean('keepdata')->after('email_parents')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropColumn('keepdata');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
deptrac:
|
||||
paths:
|
||||
- ./app
|
||||
- ./modules
|
||||
exclude_files:
|
||||
- '#.*Test.php$#'
|
||||
layers:
|
||||
- name: AppFiles
|
||||
collectors:
|
||||
- type: bool
|
||||
must:
|
||||
- type: classLike
|
||||
value: .*
|
||||
- type: directory
|
||||
value: app/.*
|
||||
|
||||
- name: ModuleFiles
|
||||
collectors:
|
||||
- type: bool
|
||||
must:
|
||||
- type: classLike
|
||||
value: .*
|
||||
- type: directory
|
||||
value: modules/.*
|
||||
|
||||
ruleset:
|
||||
ModuleFiles:
|
||||
- AppFiles
|
||||
AppFiles:
|
||||
- ModuleFiles
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
abstract class Block extends Component
|
||||
{
|
||||
abstract protected function title(): string;
|
||||
|
||||
abstract public function render(): string;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard\Components;
|
||||
|
||||
use Livewire\Component;
|
||||
use Modules\Dashboard\DashboardFactory;
|
||||
|
||||
class DashboardComponent extends Component
|
||||
{
|
||||
|
||||
private array $blocks = [];
|
||||
|
||||
public function mount(DashboardFactory $factory): void
|
||||
{
|
||||
session()->put('menu', 'dashboard');
|
||||
session()->put('title', 'Dashboard');
|
||||
|
||||
$this->blocks = $factory->load();
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::layout>
|
||||
<div class="gap-6 md:grid-cols-2 xl:grid-cols-4 grid p-6">
|
||||
@foreach($this->blocks as $block)
|
||||
<x-ui::box title="{{$block->title()}}" :second="true">
|
||||
<livewire:dynamic-component is="{{ get_class($block) }}" lazy></livewire:dynamic-component>
|
||||
</x-ui::box>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-page::layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard;
|
||||
|
||||
use Livewire\Livewire;
|
||||
|
||||
class DashboardFactory
|
||||
{
|
||||
/**
|
||||
* @var array<int, class-string<Block>>
|
||||
*/
|
||||
private array $blocks = [];
|
||||
|
||||
/**
|
||||
* @return array<int, Block>
|
||||
*/
|
||||
public function load(): array
|
||||
{
|
||||
return collect($this->blocks)->map(fn ($block) => app($block))->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Block> $block
|
||||
*/
|
||||
public function register(string $block): self
|
||||
{
|
||||
$this->blocks[] = $block;
|
||||
|
||||
$componentName = str($block)->replace('\\', '.')->explode('.')->map(fn ($part) => str($part)->lcfirst()->kebab())->implode('.');
|
||||
Livewire::component($componentName, $block);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function purge(): self
|
||||
{
|
||||
$this->blocks = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Dashboard\Components\DashboardComponent;
|
||||
use Modules\Invoice\MemberPaymentBlock;
|
||||
use Modules\Member\AgeGroupCountBlock;
|
||||
use Modules\Member\TestersBlock;
|
||||
use Modules\Prevention\EfzPendingBlock;
|
||||
use Modules\Prevention\PsPendingBlock;
|
||||
|
||||
class DashboardServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
app()->singleton(DashboardFactory::class, fn () => new DashboardFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/', DashboardComponent::class)->name('home');
|
||||
});
|
||||
|
||||
app(DashboardFactory::class)->register(AgeGroupCountBlock::class);
|
||||
app(DashboardFactory::class)->register(MemberPaymentBlock::class);
|
||||
app(DashboardFactory::class)->register(TestersBlock::class);
|
||||
app(DashboardFactory::class)->register(EfzPendingBlock::class);
|
||||
app(DashboardFactory::class)->register(PsPendingBlock::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Dashboard\Tests;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use Modules\Dashboard\Components\DashboardComponent;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Dashboard\DashboardFactory;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
uses(TestCase::class);
|
||||
|
||||
it('renders successfully', function () {
|
||||
$this->login()->loginNami();
|
||||
|
||||
app(DashboardFactory::class)->purge();
|
||||
app(DashboardFactory::class)->register(ExampleBlock::class);
|
||||
|
||||
Livewire::test(DashboardComponent::class)
|
||||
->assertSee('ExampleTitle');
|
||||
});
|
||||
|
||||
it('renders page successfully', function () {
|
||||
$this->login()->loginNami();
|
||||
|
||||
$this->get('/')->assertSeeLivewire(DashboardComponent::class)->assertSee('Dashboard');
|
||||
});
|
||||
|
||||
it('shows member component', function () {
|
||||
$this->login()->loginNami();
|
||||
|
||||
Livewire::test(DashboardComponent::class)
|
||||
->assertSee('Gruppierungs-Verteilung');
|
||||
});
|
||||
|
||||
class ExampleBlock extends Block
|
||||
{
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'ExampleTitle';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
Example Content
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Invoice\Components;
|
||||
|
||||
use App\Module\Module;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Component;
|
||||
use Modules\Invoice\InvoiceSettings;
|
||||
use Modules\Module\ModuleSettings;
|
||||
|
||||
class SettingView extends Component
|
||||
{
|
||||
|
||||
public $settingClass = InvoiceSettings::class;
|
||||
public InvoiceSettings $settings;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->settings = app(InvoiceSettings::class);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Einstellungen gespeichert.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::setting-layout :active="$settingClass">
|
||||
<x-slot:right>
|
||||
<x-form::save-button form="billsettingform"></x-form::save-button>
|
||||
</x-slot:right>
|
||||
<form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" wire:submit.prevent="save">
|
||||
<x-form::text name="from" wire:model="settings.from" label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname"></x-form::text>
|
||||
<x-form::text name="from_long" wire:model="settings.from_long" label="Absender (lang)" hint="Absender-Name in Langform, i.d.R. der Stammesname"></x-form::text>
|
||||
<x-ui::setting-intro class="col-span-full mt-5" title="Kontaktdaten">
|
||||
Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.
|
||||
</x-ui::setting-intro>
|
||||
<x-form::text name="address" wire:model="settings.address" label="Straße"></x-form::text>
|
||||
<x-form::text name="zip" wire:model="settings.zip" label="PLZ"></x-form::text>
|
||||
<x-form::text name="place" wire:model="settings.place" label="Ort"></x-form::text>
|
||||
<x-form::text name="email" wire:model="settings.email" label="E-Mail-Adresse"></x-form::text>
|
||||
<x-form::text name="mobile" wire:model="settings.mobile" label="Telefonnummer"></x-form::text>
|
||||
<x-form::text name="website" wire:model="settings.website" label="Webseite"></x-form::text>
|
||||
<x-form::text name="iban" wire:model="settings.iban" label="IBAN"></x-form::text>
|
||||
<x-form::text name="bic" wire:model="settings.bic" label="BIC"></x-form::text>
|
||||
<x-form::text name="remember_weeks" wire:model="settings.rememberWeeks" type="number" label="Erinnerung alle X Wochen versenden"></x-form::text>
|
||||
</form>
|
||||
</x-page::setting-layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Invoice;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Invoice\Components\SettingView as ComponentsSettingView;
|
||||
|
||||
class InvoiceServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/setting/bill', ComponentsSettingView::class)->name('setting.bill');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Invoice;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Invoice\Components\SettingView;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
it('it renders page', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
test()->get('/setting/bill')->assertSeeLivewire(SettingView::class);
|
||||
});
|
||||
|
||||
it('it displays settings', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
app(InvoiceSettings::class)->fill([
|
||||
'from_long' => 'DPSG Stamm Muster',
|
||||
'from' => 'Stamm Muster',
|
||||
'mobile' => '+49 176 55555',
|
||||
'email' => 'max@muster.de',
|
||||
'website' => 'https://example.com',
|
||||
'address' => 'Musterstr 4',
|
||||
'place' => 'Solingen',
|
||||
'zip' => '12345',
|
||||
'iban' => 'DE05',
|
||||
'bic' => 'SOLSDE',
|
||||
'rememberWeeks' => 6
|
||||
])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->assertSet('settings.from_long', 'DPSG Stamm Muster')
|
||||
->assertSet('settings.from', 'Stamm Muster')
|
||||
->assertSet('settings.mobile', '+49 176 55555')
|
||||
->assertSet('settings.email', 'max@muster.de')
|
||||
->assertSet('settings.website', 'https://example.com')
|
||||
->assertSet('settings.address', 'Musterstr 4')
|
||||
->assertSet('settings.place', 'Solingen')
|
||||
->assertSet('settings.zip', '12345')
|
||||
->assertSet('settings.iban', 'DE05')
|
||||
->assertSet('settings.bic', 'SOLSDE')
|
||||
->assertSet('settings.rememberWeeks', 6);
|
||||
});
|
||||
|
||||
it('testItCanChangeSettings', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->set('settings.from_long', 'DPSG Stamm Muster')
|
||||
->set('settings.from', 'Stamm Muster')
|
||||
->set('settings.mobile', '+49 176 55555')
|
||||
->set('settings.email', 'max@muster.de')
|
||||
->set('settings.website', 'https://example.com')
|
||||
->set('settings.address', 'Musterstr 4')
|
||||
->set('settings.place', 'Solingen')
|
||||
->set('settings.zip', '12345')
|
||||
->set('settings.iban', 'DE05')
|
||||
->set('settings.bic', 'SOLSDE')
|
||||
->set('settings.rememberWeeks', 10)
|
||||
->call('save')
|
||||
->assertDispatched('success', 'Einstellungen gespeichert.');
|
||||
|
||||
$settings = app(InvoiceSettings::class);
|
||||
$this->assertEquals('DPSG Stamm Muster', $settings->from_long);
|
||||
$this->assertEquals('DE05', $settings->iban);
|
||||
$this->assertEquals('SOLSDE', $settings->bic);
|
||||
$this->assertEquals(10, $settings->rememberWeeks);
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Invoice;
|
||||
namespace Modules\Invoice;
|
||||
|
||||
use App\Setting\Contracts\Storeable;
|
||||
use App\Setting\LocalSettings;
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Invoice;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use App\Invoice\Models\InvoicePosition;
|
||||
use App\Member\Member;
|
||||
|
||||
class MemberPaymentBlock extends Block
|
||||
{
|
||||
|
||||
public string $amount = '';
|
||||
public int $members = 0;
|
||||
public int $totalMembers = 0;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$amount = InvoicePosition::whereHas('invoice', fn ($query) => $query->whereNeedsPayment())
|
||||
->selectRaw('sum(price) AS price')
|
||||
->first();
|
||||
|
||||
$this->amount = number_format((int) $amount->price / 100, 2, ',', '.') . ' €';
|
||||
$this->members = Member::whereHasPendingPayment()->count();
|
||||
$this->totalMembers = Member::count();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Mitgliedsbeiträge';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
<div class="text-gray-100">
|
||||
<span class="text-xl mr-1 font-semibold">{{$amount}}</span>
|
||||
<span class="text-sm">von {{$members}} / {{$totalMembers}} Mitgliedern</span>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Mailgateway\Components;
|
||||
|
||||
use Modules\Mailgateway\Models\Mailgateway;
|
||||
use Modules\Mailgateway\Types\Type;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
|
||||
public string $name = '';
|
||||
public string $domain = '';
|
||||
public ?Type $type = null;
|
||||
#[Validate('required|string')]
|
||||
public ?string $typeClass = null;
|
||||
public Collection $types;
|
||||
public ?Mailgateway $model = null;
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'domain' => 'required|string|max:255',
|
||||
...$this->type ? collect($this->type::rules())->mapWithKeys(fn ($rules, $key) => ["type.{$key}" => $rules]) : [],
|
||||
];
|
||||
}
|
||||
|
||||
public function validationAttributes(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'Typ',
|
||||
'name' => 'Beschreibung',
|
||||
'domain' => 'Domain',
|
||||
...$this->type ? collect($this->type::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["type.{$key}" => $attribute]) : [],
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(?string $id = null): void
|
||||
{
|
||||
$this->types = app('mail-gateways')->map(fn ($gateway) => [
|
||||
'name' => $gateway::name(),
|
||||
'id' => $gateway,
|
||||
]);
|
||||
|
||||
if ($id) {
|
||||
$this->model = Mailgateway::find($id);
|
||||
$this->name = $this->model->name;
|
||||
$this->domain = $this->model->domain;
|
||||
$this->type = $this->model->type;
|
||||
$this->typeClass = get_class($this->model->type);
|
||||
}
|
||||
}
|
||||
|
||||
public function fields(): array
|
||||
{
|
||||
return $this->type ? $this->type::fields() : [];
|
||||
}
|
||||
|
||||
public function updatedTypeClass(?string $type): void
|
||||
{
|
||||
if (!$type) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->type = $type::from([]);
|
||||
}
|
||||
|
||||
#[On('onStoreFromModal')]
|
||||
public function onSave(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
if (!$this->type->works()) {
|
||||
throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'name' => $this->name,
|
||||
'domain' => $this->domain,
|
||||
'type' => $this->type,
|
||||
];
|
||||
if ($this->model) {
|
||||
$this->model->update($payload);
|
||||
} else {
|
||||
Mailgateway::create($payload);
|
||||
}
|
||||
$this->dispatch('closeModal');
|
||||
$this->dispatch('refresh-page');
|
||||
$this->dispatch('success', 'Erfolgreich gespeichert.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
<form class="grid grid-cols-2 gap-3">
|
||||
<x-form::text name="name" wire:model="name" label="Beschreibung" required />
|
||||
<x-form::text name="domain" wire:model="domain" label="Domain" required />
|
||||
<x-form::select name="typeClass" wire:model.live="typeClass" label="Typ" :options="$types" required />
|
||||
@foreach($this->fields() as $index => $field)
|
||||
<x-form::text
|
||||
wire:key="$index"
|
||||
wire:model="type.{{$field['name']}}"
|
||||
:label="$field['label']"
|
||||
:type="$field['type']"
|
||||
:name="'type.'.$field['name']"
|
||||
:required="str_contains('required', $field['validator'])"
|
||||
></x-form::text>
|
||||
@endforeach
|
||||
</form>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Mailgateway\Components;
|
||||
|
||||
use Modules\Mailgateway\Models\Mailgateway;
|
||||
use Livewire\Component;
|
||||
use Modules\Mailgateway\MailgatewaySettings;
|
||||
|
||||
class SettingView extends Component
|
||||
{
|
||||
public string $settingClass = MailgatewaySettings::class;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('mailgateway::setting-view', [
|
||||
'data' => Mailgateway::get(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<x-page::setting-layout :active="$settingClass">
|
||||
<x-slot:toolbar>
|
||||
<x-ui::badge wire:click.prevent="$dispatch('openModal', {component: 'modules.mailgateway.components.form', props: {}, title: 'Verbindung erstellen'})" icon="plus">Neue Verbindung</x-ui::badge>
|
||||
</x-slot:toolbar>
|
||||
<div>
|
||||
<x-ui::table>
|
||||
<thead>
|
||||
<th>Bezeichnung</th>
|
||||
<th>Domain</th>
|
||||
<th>Typ</th>
|
||||
<th>Prüfung</th>
|
||||
<th>Aktion</th>
|
||||
</thead>
|
||||
|
||||
|
||||
@foreach ($data as $index => $gateway)
|
||||
<tr wire:key="$index">
|
||||
<td>{{ $gateway->name }}</td>
|
||||
<td>{{ $gateway->domain }}</td>
|
||||
<td>{{ $gateway->type::name() }}</td>
|
||||
<td>
|
||||
<x-ui::boolean-display :value="$gateway->type->works()"
|
||||
hint="Verbindungsstatus"
|
||||
right="Verbindung erfolgreich"
|
||||
wrong="Verbindung fehlgeschlagen"
|
||||
></x-ui::boolean-display>
|
||||
</td>
|
||||
<td>
|
||||
<x-ui::action wire:click="$dispatch('openModal', {
|
||||
component: 'modules.mailgateway.components.form',
|
||||
props: {id: '{{$gateway->id}}'},
|
||||
title: 'Verbindung {{$gateway->name}} bearbeiten'}
|
||||
)" icon="pencil" variant="warning">Bearbeiten</x-ui::action>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</x-ui::table>
|
||||
</div>
|
||||
</x-page::setting-layout>
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Mailgateway;
|
||||
|
||||
use App\Setting\SettingFactory;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Mailgateway\Components\SettingView;
|
||||
use Modules\Mailgateway\Types\LocalType;
|
||||
use Modules\Mailgateway\Types\MailmanType;
|
||||
|
||||
class MailgatewayServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
app(SettingFactory::class)->register(MailgatewaySettings::class);
|
||||
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/setting/mailgateway', SettingView::class)->name('setting.mailgateway');
|
||||
});
|
||||
|
||||
app()->bind('mail-gateways', fn () => collect([
|
||||
LocalType::class,
|
||||
MailmanType::class,
|
||||
]));
|
||||
|
||||
View::addNamespace('mailgateway', __DIR__ . '/Components');
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway;
|
||||
namespace Modules\Mailgateway;
|
||||
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use App\Mailgateway\Resources\MailgatewayResource;
|
||||
use App\Setting\LocalSettings;
|
||||
|
||||
class MailgatewaySettings extends LocalSettings
|
||||
|
@ -23,8 +21,6 @@ class MailgatewaySettings extends LocalSettings
|
|||
*/
|
||||
public function viewData(): array
|
||||
{
|
||||
return [
|
||||
'data' => MailgatewayResource::collection(Mailgateway::paginate(10)),
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -1,19 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Models;
|
||||
namespace Modules\Mailgateway\Models;
|
||||
|
||||
use App\Mailgateway\Casts\TypeCast;
|
||||
use Database\Factories\Mailgateway\Models\MailgatewayFactory;
|
||||
use Modules\Mailgateway\Types\Type;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Mailgateway extends Model
|
||||
{
|
||||
|
||||
public static $factory = MailgatewayFactory::class;
|
||||
|
||||
/** @use HasFactory<MailgatewayFactory> */
|
||||
use HasFactory;
|
||||
use HasUuids;
|
||||
|
||||
public $casts = ['type' => TypeCast::class];
|
||||
public $casts = ['type' => Type::class];
|
||||
public $guarded = [];
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories\Mailgateway\Models;
|
||||
namespace Modules\Mailgateway\Models;
|
||||
|
||||
use App\Mailgateway\Models\Mailgateway;
|
||||
use App\Mailgateway\Types\Type;
|
||||
use Modules\Mailgateway\Types\Type;
|
||||
use Modules\Mailgateway\Types\LocalType;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Mailgateway\Models\Mailgateway>
|
||||
* @extends Factory<Mailgateway>
|
||||
*/
|
||||
class MailgatewayFactory extends Factory
|
||||
{
|
||||
|
@ -22,24 +22,14 @@ class MailgatewayFactory extends Factory
|
|||
{
|
||||
return [
|
||||
'name' => $this->faker->words(5, true),
|
||||
'type' => [
|
||||
'cls' => app('mail-gateways')->random(),
|
||||
'params' => [],
|
||||
],
|
||||
'type' => new LocalType(),
|
||||
'domain' => $this->faker->safeEmailDomain(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Type> $type
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function type(string $type, array $params): self
|
||||
public function type(Type $type): self
|
||||
{
|
||||
return $this->state(['type' => [
|
||||
'cls' => $type,
|
||||
'params' => $params,
|
||||
]]);
|
||||
return $this->state(['type' => $type]);
|
||||
}
|
||||
|
||||
public function name(string $name): self
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Mailgateway\Tests;
|
||||
|
||||
use Modules\Mailgateway\Models\Mailgateway;
|
||||
use Modules\Mailgateway\Types\LocalType;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Mailgateway\Components\SettingView;
|
||||
use Tests\RequestFactories\MailmanTypeRequest;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
uses(TestCase::class);
|
||||
|
||||
it('test it can view index page', function () {
|
||||
test()->login()->loginNami();
|
||||
test()->get('/setting/mailgateway')->assertSeeLivewire(SettingView::class);
|
||||
});
|
||||
|
||||
it('test it displays local gateways', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
Mailgateway::factory()->type(LocalType::from([]))->name('Lore')->domain('example.com')->create();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->assertSeeHtml('example.com')
|
||||
->assertSeeHtml('Lore')
|
||||
->assertSeeHtml('Lokal')
|
||||
->assertSeeHtml('Verbindung erfolgreich');
|
||||
});
|
||||
|
||||
it('displays mailman gateways', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
$typeParams = MailmanTypeRequest::new()->succeeds()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']);
|
||||
Mailgateway::factory()->type($typeParams->toData())->create();
|
||||
|
||||
Livewire::test(SettingView::class)->assertSeeHtml('Verbindung erfolgreich');
|
||||
});
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Mailgateway\Tests;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Mailgateway\Components\Form;
|
||||
use Modules\Mailgateway\Types\LocalType;
|
||||
use Modules\Mailgateway\Types\MailmanType;
|
||||
use Tests\RequestFactories\MailmanTypeRequest;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
uses(TestCase::class);
|
||||
|
||||
it('test it saves a mail gateway', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
Livewire::test(Form::class)
|
||||
->set('typeClass', LocalType::class)
|
||||
->set('name', 'lala')
|
||||
->set('domain', 'example.com')
|
||||
->call('onSave')
|
||||
->assertHasNoErrors()
|
||||
->assertDispatched('closeModal')
|
||||
->assertDispatched('refresh-page')
|
||||
->assertDispatched('success');
|
||||
|
||||
test()->assertDatabaseHas('mailgateways', [
|
||||
'domain' => 'example.com',
|
||||
'name' => 'lala',
|
||||
'type' => json_encode([
|
||||
'type' => LocalType::class,
|
||||
'data' => [],
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('test it validates mail gateway', function (array $attributes, array $errors) {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
Livewire::test(Form::class)
|
||||
->set('name', 'lala')
|
||||
->set('domain', 'example.com')
|
||||
->set('typeClass', LocalType::class)
|
||||
->setArray($attributes)
|
||||
->call('onSave')
|
||||
->assertHasErrors($errors)
|
||||
->assertNotDispatched('closeModal')
|
||||
->assertNotDispatched('refresh-page')
|
||||
->assertNotDispatched('success');
|
||||
})->with([
|
||||
[['name' => ''], ['name' => 'required']],
|
||||
[['domain' => ''], ['domain' => 'required']],
|
||||
[['typeClass' => ''], ['typeClass' => 'required']],
|
||||
]);
|
||||
|
||||
it('test it validates mailman type', function (array $attributes, array $errors) {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
Livewire::test(Form::class)
|
||||
->set('name', 'lala')
|
||||
->set('domain', 'example.com')
|
||||
->set('typeClass', MailmanType::class)
|
||||
->set('type.url', 'exampl.com')
|
||||
->set('type.user', '::user::')
|
||||
->set('type.password', 'password')
|
||||
->setArray($attributes)
|
||||
->call('onSave')
|
||||
->assertHasErrors($errors)
|
||||
->assertNotDispatched('closeModal');
|
||||
})->with([
|
||||
[['type.url' => ''], ['type.url' => 'required']],
|
||||
[['type.user' => ''], ['type.user' => 'required']],
|
||||
[['type.password' => ''], ['type.password' => 'required']],
|
||||
[['type.owner' => ''], ['type.owner' => 'required']],
|
||||
[['type.owner' => 'aaa'], ['type.owner' => 'email']],
|
||||
]);
|
||||
|
||||
it('test it stores mailman gateway', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$typeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://example.com', 'user' => 'user', 'password' => 'secret', 'owner' => 'owner@example.com']);
|
||||
|
||||
Livewire::test(Form::class)
|
||||
->setArray([
|
||||
'name' => 'lala',
|
||||
'domain' => 'https://example.com',
|
||||
'typeClass' => MailmanType::class,
|
||||
])
|
||||
->setArray('type', $typeParams)
|
||||
->call('onSave')
|
||||
->assertDispatched('closeModal');
|
||||
|
||||
test()->assertDatabaseHas('mailgateways', [
|
||||
'type' => json_encode([
|
||||
'type' => MailmanType::class,
|
||||
'data' => $typeParams,
|
||||
]),
|
||||
'name' => 'lala',
|
||||
'domain' => 'https://example.com',
|
||||
]);
|
||||
});
|
||||
|
||||
it('test it checks mailman connection', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$typeParams = MailmanTypeRequest::new()->fails()->create(['url' => 'https://example.com', 'user' => 'user', 'password' => 'secret', 'owner' => 'owner@example.com']);
|
||||
|
||||
Livewire::test(Form::class)
|
||||
->setArray([
|
||||
'name' => 'lala',
|
||||
'domain' => 'https://example.com',
|
||||
'typeClass' => MailmanType::class,
|
||||
])
|
||||
->setArray('type', $typeParams)
|
||||
->call('onSave')
|
||||
->assertHasErrors('connection')
|
||||
->assertNotDispatched('closeModal');
|
||||
|
||||
test()->assertDatabaseCount('mailgateways', 0);
|
||||
});
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Mailgateway\Tests;
|
||||
|
||||
use Modules\Mailgateway\Models\Mailgateway;
|
||||
use Modules\Mailgateway\Types\LocalType;
|
||||
use Modules\Mailgateway\Types\MailmanType;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Mailgateway\Components\Form;
|
||||
use Tests\RequestFactories\MailmanTypeRequest;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
uses(TestCase::class);
|
||||
|
||||
it('test it sets attributes for mailman', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$typeParams = MailmanTypeRequest::new()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']);
|
||||
$mailgateway = Mailgateway::factory()->type($typeParams->toData())->create(['name' => '::name::', 'domain' => 'example.com']);
|
||||
|
||||
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||
->assertSet('model', fn ($m) => $m->is($mailgateway))
|
||||
->assertSet('name', '::name::')
|
||||
->assertSet('domain', 'example.com')
|
||||
->assertSet(
|
||||
'type',
|
||||
fn ($type) => $type instanceof MailmanType
|
||||
&& $type->url === 'https://mailman.example.com'
|
||||
&& $type->user === 'user' &&
|
||||
$type->password === 'password'
|
||||
&& $type->owner === 'owner'
|
||||
);
|
||||
});
|
||||
|
||||
it('test it sets attributes for local', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$mailgateway = Mailgateway::factory()->create(['name' => '::name::', 'domain' => 'example.com']);
|
||||
|
||||
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||
->assertSet('name', '::name::')
|
||||
->assertSet('domain', 'example.com')
|
||||
->assertSet('type', fn ($type) => $type instanceof LocalType);
|
||||
});
|
||||
|
||||
it('test it validates type', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$mailgateway = Mailgateway::factory()->create(['name' => '::name::', 'domain' => 'example.com']);
|
||||
|
||||
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||
->set('typeClass', '')
|
||||
->assertHasErrors(['typeClass' => 'required']);
|
||||
});
|
||||
|
||||
it('test it updates a mailman gateway without updating password', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$typeParams = MailmanTypeRequest::new()
|
||||
->succeeds()
|
||||
->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com'])
|
||||
->toData();
|
||||
$mailgateway = Mailgateway::factory()->type($typeParams)->create(['name' => '::name::', 'domain' => 'example.com']);
|
||||
|
||||
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||
->set('name', '::newname::')
|
||||
->call('onSave')
|
||||
->assertHasNoErrors()
|
||||
->assertDispatched('closeModal')
|
||||
->assertDispatched('refresh-page')
|
||||
->assertDispatched('success');
|
||||
|
||||
$this->assertDatabaseCount('mailgateways', 1);
|
||||
$this->assertDatabaseHas('mailgateways', [
|
||||
'name' => '::newname::',
|
||||
'type' => json_encode(['type' => MailmanType::class, 'data' => $typeParams]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('test it updates a mailman gateway with password', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$typeParams = MailmanTypeRequest::new()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']);
|
||||
$newTypeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'newuser', 'password' => 'password', 'owner' => 'owner@example.com']);
|
||||
$mailgateway = Mailgateway::factory()->type($typeParams->toData())->create();
|
||||
|
||||
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||
->set('type.user', 'newuser')
|
||||
->call('onSave')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$this->assertDatabaseCount('mailgateways', 1);
|
||||
$this->assertDatabaseHas('mailgateways', [
|
||||
'type' => json_encode(['type' => MailmanType::class, 'data' => $newTypeParams]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('test it checks mailgateway connection when updating', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$typeParams = MailmanTypeRequest::new()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']);
|
||||
MailmanTypeRequest::new()->fails()->create(['url' => 'https://mailman.example.com', 'user' => 'newuser', 'password' => 'password', 'owner' => 'owner@example.com']);
|
||||
$mailgateway = Mailgateway::factory()->type($typeParams->toData())->create();
|
||||
|
||||
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||
->set('type.user', 'newuser')
|
||||
->call('onSave')
|
||||
->assertHasErrors('connection');
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Types;
|
||||
namespace Modules\Mailgateway\Types;
|
||||
|
||||
use App\Maildispatcher\Data\MailEntry;
|
||||
use App\Maildispatcher\Models\Localmaildispatcher;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Types;
|
||||
namespace Modules\Mailgateway\Types;
|
||||
|
||||
use App\Maildispatcher\Data\MailEntry;
|
||||
use App\Mailman\Data\MailingList;
|
||||
|
@ -45,32 +45,28 @@ class MailmanType extends Type
|
|||
'name' => 'url',
|
||||
'label' => 'URL',
|
||||
'type' => 'text',
|
||||
'storeValidator' => 'required|max:255',
|
||||
'updateValidator' => 'required|max:255',
|
||||
'validator' => 'required|max:255',
|
||||
'default' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'user',
|
||||
'label' => 'Benutzer',
|
||||
'type' => 'text',
|
||||
'storeValidator' => 'required|max:255',
|
||||
'updateValidator' => 'required|max:255',
|
||||
'validator' => 'required|max:255',
|
||||
'default' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'password',
|
||||
'label' => 'Passwort',
|
||||
'type' => 'password',
|
||||
'storeValidator' => 'required|max:255',
|
||||
'updateValidator' => 'nullable|max:255',
|
||||
'validator' => 'required|max:255',
|
||||
'default' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'owner',
|
||||
'label' => 'E-Mail-Adresse des Eigentümers',
|
||||
'type' => 'email',
|
||||
'storeValidator' => 'required|email|max:255',
|
||||
'updateValidator' => 'required|email|max:255',
|
||||
'validator' => 'required|email|max:255',
|
||||
'default' => '',
|
||||
],
|
||||
];
|
|
@ -1,12 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mailgateway\Types;
|
||||
namespace Modules\Mailgateway\Types;
|
||||
|
||||
use App\Maildispatcher\Data\MailEntry;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Wireable;
|
||||
use Spatie\LaravelData\Concerns\WireableData;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
abstract class Type
|
||||
abstract class Type extends Data implements Wireable
|
||||
{
|
||||
|
||||
use WireableData;
|
||||
|
||||
abstract public static function name(): string;
|
||||
|
||||
/**
|
||||
|
@ -60,24 +66,13 @@ abstract class Type
|
|||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function rules(string $validator): array
|
||||
public static function rules(): array
|
||||
{
|
||||
return collect(static::fields())->mapWithKeys(fn ($field) => [
|
||||
$field['name'] => $field[$validator],
|
||||
$field['name'] => $field['validator'],
|
||||
])->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public function toResource(): array
|
||||
{
|
||||
return [
|
||||
'cls' => get_class($this),
|
||||
'params' => get_object_vars($this),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, MailEntry> $results
|
||||
*/
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Member;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use App\Member\Membership;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class AgeGroupCountBlock extends Block
|
||||
{
|
||||
|
||||
public $groups;
|
||||
|
||||
/**
|
||||
* @return Builder<Membership>
|
||||
*/
|
||||
protected function memberQuery(): Builder
|
||||
{
|
||||
return Membership::select('subactivities.slug', 'subactivities.name')
|
||||
->selectRaw('COUNT(member_id) AS count')
|
||||
->join('activities', 'memberships.activity_id', 'activities.id')
|
||||
->join('subactivities', 'memberships.subactivity_id', 'subactivities.id')
|
||||
->isAgeGroup()
|
||||
->isMember()
|
||||
->active()
|
||||
->groupBy('subactivities.slug', 'subactivities.name')
|
||||
->orderBy('subactivity_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<Membership>
|
||||
*/
|
||||
protected function leaderQuery(): Builder
|
||||
{
|
||||
return Membership::selectRaw('"leiter" AS slug, "Leiter" AS name, COUNT(member_id) AS count')
|
||||
->join('activities', 'memberships.activity_id', 'activities.id')
|
||||
->join('subactivities', 'memberships.subactivity_id', 'subactivities.id')
|
||||
->active()
|
||||
->isLeader();
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->groups = [
|
||||
...$this->memberQuery()->get(),
|
||||
...$this->leaderQuery()->get(),
|
||||
];
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Gruppierungs-Verteilung';
|
||||
}
|
||||
|
||||
public function groupColor(string $slug): string
|
||||
{
|
||||
return data_get([
|
||||
'biber' => 'text-biber',
|
||||
'woelfling' => 'text-woelfling',
|
||||
'jungpfadfinder' => 'text-jungpfadfinder',
|
||||
'pfadfinder' => 'text-pfadfinder',
|
||||
'rover' => 'text-rover',
|
||||
'leiter' => 'text-leiter',
|
||||
], $slug);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
@foreach($groups as $group)
|
||||
<div class="flex mt-2 items-center leading-none text-gray-100">
|
||||
<x-ui::sprite class="w-4 h-4 mr-2 {{ $this->groupColor($group->slug) }}" src="lilie"></x-ui::sprite>
|
||||
<span class="grow">{{$group->name}}</span>
|
||||
<span>{{$group->count}}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Member;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class TestersBlock extends Block
|
||||
{
|
||||
|
||||
private $months = 8;
|
||||
|
||||
/**
|
||||
* @var Collection<Member>
|
||||
*/
|
||||
public Collection $members;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->members = Member::whereHas('memberships', fn ($q) => $q->isTrying())
|
||||
->with('memberships', fn ($q) => $q->isTrying())
|
||||
->get();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Endende Schhnupperzeiten';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
@foreach($members as $member)
|
||||
<div class="flex mt-2 items-center leading-none text-gray-100">
|
||||
<span class="grow">{{ $member->fullname }}</span>
|
||||
<span class="mr-2 text-sm tex-gray-600">{{ $member->memberships->first()->from->addWeeks($this->months)->format('d.m.Y') }}</span>
|
||||
<span class="text-xs tex-gray-600">{{ $member->memberships->first()->from->addWeeks($this->months)->diffForHumans() }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Module\Components;
|
||||
|
||||
use App\Module\Module;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Component;
|
||||
use Modules\Module\ModuleSettings;
|
||||
|
||||
class SettingView extends Component
|
||||
{
|
||||
|
||||
public array $modules;
|
||||
public array $all;
|
||||
public $settingClass = ModuleSettings::class;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'modules' => 'present|array',
|
||||
'modules.*' => ['string', Rule::in(Module::values())],
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->modules = app(ModuleSettings::class)->modules;
|
||||
$this->all = Module::forSelect();
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
app(ModuleSettings::class)->fill($this->validate())->save();
|
||||
$this->dispatch('success', 'Einstellungen gespeichert.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::setting-layout :active="$settingClass">
|
||||
<x-slot:right>
|
||||
<x-form::save-button form="modulesettingform"></x-form::save-button>
|
||||
</x-slot:right>
|
||||
<form id="modulesettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start"
|
||||
wire:submit.prevent="save">
|
||||
<x-ui::setting-intro class="col-span-full">
|
||||
Hier kannst du Funktionen innerhalb von Adrema (Module) aktivieren oder deaktivieren und so den Funktionsumfang auf deine Bedürfnisse anpassen.
|
||||
</x-ui::setting-intro>
|
||||
@foreach ($all as $module)
|
||||
<x-form::lever wire:model="modules" hint="lala" :value="$module['id']" name="modules" size="sm" :label="$module['name']"></x-form::lever>
|
||||
@endforeach
|
||||
</form>
|
||||
</x-page::setting-layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Module;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Module\Components\SettingView;
|
||||
|
||||
class ModuleServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/setting/module', SettingView::class)->name('setting.module');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Module;
|
||||
|
||||
use App\View\Setting;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Module\Components\SettingView;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
it('it renders page', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
test()->get('/setting/module')->assertSeeLivewire(SettingView::class);
|
||||
});
|
||||
|
||||
it('it displays acive modules', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
app(ModuleSettings::class)->fill(['modules' => ['bill']])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->assertSee('Module')
|
||||
->assertSee('Ausbildung')
|
||||
->assertSet('modules', fn ($modules) => $modules === ['bill'])
|
||||
->assertSeeHtml('data-active');
|
||||
});
|
||||
|
||||
it('it saves modules', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
app(ModuleSettings::class)->fill(['modules' => ['bill']])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->set('modules', ['bill', 'course'])
|
||||
->call('save')
|
||||
->assertDispatched('success', 'Einstellungen gespeichert.');
|
||||
|
||||
test()->assertEquals(['bill', 'course'], app(ModuleSettings::class)->modules);
|
||||
});
|
||||
|
||||
it('test module must exist', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
app(ModuleSettings::class)->fill(['modules' => ['bill']])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->set('modules', ['bill', 'lala'])
|
||||
->call('save')
|
||||
->assertHasErrors('modules.1');
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
namespace Modules\Module;
|
||||
|
||||
use App\Setting\Contracts\Storeable;
|
||||
use App\Setting\LocalSettings;
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Nami\Components;
|
||||
|
||||
use App\Initialize\Actions\NamiLoginCheckAction;
|
||||
use App\Setting\NamiSettings;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
use Zoomyboy\LaravelNami\LoginException;
|
||||
|
||||
class SettingView extends Component
|
||||
{
|
||||
|
||||
public $settingClass = NamiSettings::class;
|
||||
|
||||
#[Validate('required|string')]
|
||||
public string $password = '';
|
||||
#[Validate('required|string')]
|
||||
public string $mglnr = '';
|
||||
#[Validate('required|string')]
|
||||
public string $default_group_id = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->mglnr = app(NamiSettings::class)->mglnr;
|
||||
$this->default_group_id = app(NamiSettings::class)->default_group_id;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$validated = $this->validate();
|
||||
try {
|
||||
NamiLoginCheckAction::run($this->only(['mglnr', 'password']));
|
||||
app(NamiSettings::class)->fill($validated)->save();
|
||||
$this->dispatch('success', 'Einstellungen gespeichert.');
|
||||
} catch (LoginException $e) {
|
||||
throw ValidationException::withMessages([
|
||||
'mglnr' => 'Login fehlgeschlagen.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::setting-layout :active="$settingClass">
|
||||
<x-slot:right>
|
||||
<x-form::save-button form="namisettingform"></x-form::save-button>
|
||||
</x-slot:right>
|
||||
<form id="namisettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" wire:submit.prevent="save">
|
||||
<x-ui::setting-intro class="col-span-full">
|
||||
Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.
|
||||
</x-ui::setting-intro>
|
||||
<x-form::text name="mglnr" wire:model="mglnr" label="Mitgliedsnummer"></x-form::text>
|
||||
<x-form::text name="default_group_id" wire:model="default_group_id" label="Standard-Gruppierung"></x-form::text>
|
||||
<x-form::text name="password" wire:model="password" label="Passwort" name="password" type="password"></x-form::text>
|
||||
</form>
|
||||
</x-page::setting-layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Nami;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Nami\Components\SettingView;
|
||||
|
||||
class NamiServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/setting/nami', SettingView::class)->name('setting.nami');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Module;
|
||||
|
||||
use App\Setting\NamiSettings;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Nami\Components\SettingView;
|
||||
use Tests\TestCase;
|
||||
use Zoomyboy\LaravelNami\Authentication\Auth;
|
||||
use Zoomyboy\LaravelNami\Fakes\LoginFake;
|
||||
|
||||
uses(TestCase::class);
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
it('it renders page', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
test()->get(route('setting.nami'))->assertSeeLivewire(SettingView::class);
|
||||
});
|
||||
|
||||
it('it displays active credentials', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->assertSet('mglnr', 903)
|
||||
->assertSet('password', '')
|
||||
->assertSet('default_group_id', 55)
|
||||
->assertSee('Mitgliedsnummer')
|
||||
->assertSee('Standard-Gruppierung')
|
||||
->assertSee('Passwort')
|
||||
->assertDontSee('secret');
|
||||
});
|
||||
|
||||
it('it saves credentials', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
Auth::success(100, 'secretneu');
|
||||
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->set('mglnr', 100)
|
||||
->set('password', 'secretneu')
|
||||
->set('default_group_id', 80)
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertDispatched('success', 'Einstellungen gespeichert.');
|
||||
|
||||
$this->assertEquals(100, app(NamiSettings::class)->mglnr);
|
||||
$this->assertEquals('secretneu', app(NamiSettings::class)->password);
|
||||
$this->assertEquals('80', app(NamiSettings::class)->default_group_id);
|
||||
});
|
||||
|
||||
it('validates fields', function ($name, $value, $errors) {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
Auth::success(100, 'secretneu');
|
||||
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->set('mglnr', 100)
|
||||
->set('password', 'secretneu')
|
||||
->set('default_group_id', 80)
|
||||
->set($name, $value)
|
||||
->call('save')
|
||||
->assertHasErrors($errors)
|
||||
->assertNotDispatched('success');
|
||||
})->with([
|
||||
['password', '', ['password' => 'required']],
|
||||
['mglnr', '', ['mglnr' => 'required']],
|
||||
['default_group_id', '', ['default_group_id' => 'required']],
|
||||
['default_group_id', null, ['default_group_id' => 'required']],
|
||||
]);
|
||||
|
||||
it('it throws error when saving failed', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
|
||||
|
||||
Livewire::test(SettingView::class)
|
||||
->set('mglnr', 100)
|
||||
->set('password', 'secretneu')
|
||||
->set('default_group_id', 80)
|
||||
->call('save')
|
||||
->assertHasErrors(['mglnr' => 'Login fehlgeschlagen.'])
|
||||
->assertNotDispatched('success');
|
||||
|
||||
$settings = app(NamiSettings::class)->refresh();
|
||||
$this->assertEquals(903, $settings->mglnr);
|
||||
$this->assertEquals('secret', $settings->password);
|
||||
$this->assertEquals('55', $settings->default_group_id);
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Prevention;
|
||||
|
||||
use Modules\Dashboard\Block;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class EfzPendingBlock extends Block
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Collection<Member>
|
||||
*/
|
||||
public Collection $members;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->members = Member::where(function ($query) {
|
||||
return $query->where('efz', '<=', now()->subYears(5)->endOfYear())
|
||||
->orWhereNull('efz');
|
||||
})
|
||||
->whereCurrentGroup()
|
||||
->orderByRaw('lastname, firstname')
|
||||
->whereHas('memberships', fn ($builder) => $builder->isLeader()->active())
|
||||
->get();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Führungszeugnisse';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
@foreach($members as $member)
|
||||
<div class="flex mt-2 items-center leading-none text-gray-100">
|
||||
<span class="grow">{{$member->fullname}}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Member;
|
||||
namespace Modules\Prevention;
|
||||
|
||||
use App\Dashboard\Blocks\Block;
|
||||
use App\Member\Member;
|
||||
use Modules\Dashboard\Block;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PsPendingBlock extends Block
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Builder<Member>
|
||||
* @var Collection<Member>
|
||||
*/
|
||||
public function query(): Builder
|
||||
public Collection $members;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
return Member::where(function ($query) {
|
||||
$this->members = Member::where(function ($query) {
|
||||
$time = now()->subYears(5)->endOfYear();
|
||||
|
||||
return $query
|
||||
|
@ -23,28 +28,25 @@ class PsPendingBlock extends Block
|
|||
})
|
||||
->whereCurrentGroup()
|
||||
->orderByRaw('lastname, firstname')
|
||||
->whereHas('memberships', fn ($builder) => $builder->isLeader()->active());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{members: array{fullname: string}}
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'members' => $this->query()->get()->map(fn ($member) => [
|
||||
'fullname' => $member->fullname,
|
||||
])->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
public function component(): string
|
||||
{
|
||||
return 'ps-pending';
|
||||
->whereHas('memberships', fn ($builder) => $builder->isLeader()->active())
|
||||
->get();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return 'Ausstehende Präventionsschulungen';
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div>
|
||||
@foreach ($members as $member)
|
||||
<div class="flex mt-2 items-center leading-none text-gray-100">
|
||||
<span class="grow">{{ $member->fullname }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue