Compare commits
	
		
			103 Commits
		
	
	
		
			febb90e40e
			...
			4644f3832b
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 4644f3832b | |
|  | d68b166e15 | |
|  | 5d103b1908 | |
|  | 195fcc97b2 | |
|  | ef4f4e68fb | |
|  | 7c73b489ce | |
|  | 74db2f7c6b | |
|  | 458d68c271 | |
|  | 471f766638 | |
|  | dc3eb68861 | |
|  | 003e30d423 | |
|  | 5d40c0b2bb | |
|  | e60105e5af | |
|  | 2fadba3193 | |
|  | 357a696a53 | |
|  | 79fed8c002 | |
|  | 9b02bddea9 | |
|  | 1b6057673a | |
|  | 5c9c59718c | |
|  | c1e0842457 | |
|  | a61fd55b29 | |
|  | 4156a8744a | |
|  | 84b17b312f | |
|  | 6906b7c097 | |
|  | 30c84f0cfd | |
|  | d765bad0ed | |
|  | 89ce931f9d | |
|  | 2fac4d5a73 | |
|  | 56090b0104 | |
|  | 131061a3ee | |
|  | 9733c260b2 | |
|  | e2e9ddb9fd | |
|  | 511a58ded3 | |
|  | b937a8f4fa | |
|  | cb01496028 | |
|  | 92284b053c | |
|  | 39f0d476fb | |
|  | e6aa5e2c61 | |
|  | d225be6137 | |
|  | 6094a16c36 | |
|  | 34355d9b32 | |
|  | 7a39798e8b | |
|  | b07ad8d739 | |
|  | 5cd164f0d8 | |
|  | 652b83a140 | |
|  | 4d0923a01f | |
|  | 1de148eecc | |
|  | 12f053428c | |
|  | 0ce797c874 | |
|  | 8e2487c719 | |
|  | 2b1fe6ddcd | |
|  | fe8a2d69e6 | |
|  | 76d6c4ef90 | |
|  | 8bd66010dd | |
|  | bb9c219d81 | |
|  | b52eedc254 | |
|  | a06fd01194 | |
|  | 4a731f1104 | |
|  | 278a598078 | |
|  | 3c72b890fb | |
|  | 7de12a92dd | |
|  | 643959ff04 | |
|  | 5d16d65f8a | |
|  | 985b7942c4 | |
|  | 6582f49321 | |
|  | 5998230569 | |
|  | 0dbac1da4a | |
|  | 4b507aa297 | |
|  | af45560fe1 | |
|  | cecc90f07b | |
|  | 5b116a54c4 | |
|  | 0070976e34 | |
|  | dd07f6dbd7 | |
|  | ab74abdfdf | |
|  | 8f5448bbbc | |
|  | 2fd6598ef3 | |
|  | 0c90f66a66 | |
|  | b541d246e0 | |
|  | 858ae7f8d1 | |
|  | abf4158256 | |
|  | f2d59e4535 | |
|  | a75b4a7808 | |
|  | ecf3cc998c | |
|  | 7202cd60f5 | |
|  | 9d5c71adcd | |
|  | eb00d928fc | |
|  | 6683990755 | |
|  | 2a5f4832e1 | |
|  | 719b1920c9 | |
|  | 3260202465 | |
|  | 9b995641b6 | |
|  | 2d5921fe1d | |
|  | efa9eeccbe | |
|  | b33072ad20 | |
|  | 2e77823c3a | |
|  | 9a5d9ae30e | |
|  | d607c8beac | |
|  | 1a86ea7ea5 | |
|  | a157f2b656 | |
|  | e3c7dad51e | |
|  | 749e3dcd7c | |
|  | 4417008d22 | |
|  | b38591b87c | 
|  | @ -1,5 +1,9 @@ | ||||||
| # Letzte Änderungen | # Letzte Änderungen | ||||||
| 
 | 
 | ||||||
|  | ### 1.12.7 | ||||||
|  | 
 | ||||||
|  | -   Fix: Synchronisation von allen Mitgliedern bei Mail-Verteilern - nicht nur den ersten 20 | ||||||
|  | 
 | ||||||
| ### 1.12.6 | ### 1.12.6 | ||||||
| 
 | 
 | ||||||
| -   Fix: Beiträge von Familienmitgliedern splitten | -   Fix: Beiträge von Familienmitgliedern splitten | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| @servers(['docker' => ['stammsilva@zoomyboy.de', 'stammgallier@stamm-gallier.de', 'dpsg-lennep@zoomyboy.de', 'dpsgbergischland@zoomyboy.de', 'dpsg-koeln@dpsg-koeln.de']]) | @servers(['docker' => ['stamm-silva@zoomyboy.de', 'stammgallier@stamm-gallier.de', 'dpsg-lennep@zoomyboy.de', 'dpsgbergischland@zoomyboy.de', 'dpsg-koeln@dpsg-koeln.de']]) | ||||||
| 
 | 
 | ||||||
| @task('deploy', ['on' => 'docker']) | @task('deploy', ['on' => 'docker']) | ||||||
| cd $ADREMA_PATH | cd $ADREMA_PATH | ||||||
|  |  | ||||||
|  | @ -22,7 +22,6 @@ class InsertMemberAction | ||||||
|     { |     { | ||||||
|         $region = Region::firstWhere('nami_id', $member->regionId ?: -1); |         $region = Region::firstWhere('nami_id', $member->regionId ?: -1); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         $payload = [ |         $payload = [ | ||||||
|             'firstname' => $member->firstname, |             'firstname' => $member->firstname, | ||||||
|             'lastname' => $member->lastname, |             'lastname' => $member->lastname, | ||||||
|  | @ -61,7 +60,17 @@ class InsertMemberAction | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return Member::updateOrCreate(['nami_id' => $member->id], $payload); |         return tap(Member::updateOrCreate(['nami_id' => $member->id], $payload), function ($insertedMember) use ($member) { | ||||||
|  |             $insertedMember->bankAccount->update([ | ||||||
|  |                 'iban' => $member->bankAccount->iban, | ||||||
|  |                 'bic' => $member->bankAccount->bic, | ||||||
|  |                 'blz' => $member->bankAccount->blz, | ||||||
|  |                 'account_number' => $member->bankAccount->accountNumber, | ||||||
|  |                 'person' => $member->bankAccount->person, | ||||||
|  |                 'bank_name' => $member->bankAccount->bankName, | ||||||
|  |                 'nami_id' => $member->bankAccount->id, | ||||||
|  |             ]); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getSubscription(NamiMember $member): ?Subscription |     public function getSubscription(NamiMember $member): ?Subscription | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ class Activity extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsToMany<Subactivity> |      * @return BelongsToMany<Subactivity, $this> | ||||||
|      */ |      */ | ||||||
|     public function subactivities(): BelongsToMany |     public function subactivities(): BelongsToMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ use App\Contribution\Data\MemberData; | ||||||
| use App\Contribution\Traits\FormatsDates; | use App\Contribution\Traits\FormatsDates; | ||||||
| use App\Contribution\Traits\HasPdfBackground; | use App\Contribution\Traits\HasPdfBackground; | ||||||
| use App\Country; | use App\Country; | ||||||
| use App\Invoice\InvoiceSettings; | use Modules\Invoice\InvoiceSettings; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| 
 | 
 | ||||||
| class CityFrankfurtMainDocument extends ContributionDocument | class CityFrankfurtMainDocument extends ContributionDocument | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| namespace App\Contribution\Documents; | namespace App\Contribution\Documents; | ||||||
| 
 | 
 | ||||||
| use App\Contribution\Data\MemberData; | use App\Contribution\Data\MemberData; | ||||||
| use App\Invoice\InvoiceSettings; | use Modules\Invoice\InvoiceSettings; | ||||||
| use Carbon\Carbon; | use Carbon\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Zoomyboy\Tex\Engine; | use Zoomyboy\Tex\Engine; | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ class CourseMember extends Model | ||||||
|     public $guarded = []; |     public $guarded = []; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Course, self> |      * @return BelongsTo<Course, $this> | ||||||
|      */ |      */ | ||||||
|     public function course(): BelongsTo |     public function course(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -25,7 +25,7 @@ class CourseMember extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Member, self> |      * @return BelongsTo<Member, $this> | ||||||
|      */ |      */ | ||||||
|     public function member(): BelongsTo |     public function member(): BelongsTo | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -129,6 +129,7 @@ class Principal implements PrincipalBackendInterface | ||||||
|      */ |      */ | ||||||
|     public function findByUri($uri, $principalPrefix) |     public function findByUri($uri, $principalPrefix) | ||||||
|     { |     { | ||||||
|  |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -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'; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -15,14 +15,13 @@ class Fee extends Model | ||||||
|     use HasFactory; |     use HasFactory; | ||||||
|     use HasNamiField; |     use HasNamiField; | ||||||
| 
 | 
 | ||||||
|     /** @var array<int, string> */ |  | ||||||
|     public $fillable = ['name', 'nami_id']; |     public $fillable = ['name', 'nami_id']; | ||||||
| 
 | 
 | ||||||
|     /** @var bool */ |     /** @var bool */ | ||||||
|     public $timestamps = false; |     public $timestamps = false; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<Subscription> |      * @return HasMany<Subscription, $this> | ||||||
|      */ |      */ | ||||||
|     public function subscriptions(): HasMany |     public function subscriptions(): HasMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ | ||||||
| namespace App\Form\Data; | namespace App\Form\Data; | ||||||
| 
 | 
 | ||||||
| use App\Fileshare\Data\FileshareResourceData; | use App\Fileshare\Data\FileshareResourceData; | ||||||
| use App\Form\Fields\Field; |  | ||||||
| use Spatie\LaravelData\Attributes\MapInputName; | use Spatie\LaravelData\Attributes\MapInputName; | ||||||
| use Spatie\LaravelData\Attributes\MapOutputName; | use Spatie\LaravelData\Attributes\MapOutputName; | ||||||
| use Spatie\LaravelData\Data; | use Spatie\LaravelData\Data; | ||||||
| use Spatie\LaravelData\Mappers\SnakeCaseMapper; | use Spatie\LaravelData\Mappers\SnakeCaseMapper; | ||||||
|  | use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast; | ||||||
| 
 | 
 | ||||||
| #[MapInputName(SnakeCaseMapper::class)]
 | #[MapInputName(SnakeCaseMapper::class)]
 | ||||||
| #[MapOutputName(SnakeCaseMapper::class)]
 | #[MapOutputName(SnakeCaseMapper::class)]
 | ||||||
|  | @ -16,4 +16,13 @@ class ExportData extends Data | ||||||
|     public function __construct(public ?FileshareResourceData $root = null, public ?string $groupBy = null, public ?string $toGroupField = null) |     public function __construct(public ?FileshareResourceData $root = null, public ?string $groupBy = null, public ?string $toGroupField = null) | ||||||
|     { |     { | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @param array<int, mixed> $arguments | ||||||
|  |      * @return DataEloquentCast<self> | ||||||
|  |      */ | ||||||
|  |     public static function castUsing(array $arguments): DataEloquentCast | ||||||
|  |     { | ||||||
|  |         return new DataEloquentCast(static::class, $arguments); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ use Illuminate\Support\Collection; | ||||||
| use Spatie\LaravelData\Data; | use Spatie\LaravelData\Data; | ||||||
| use Spatie\LaravelData\Attributes\WithCast; | use Spatie\LaravelData\Attributes\WithCast; | ||||||
| use Spatie\LaravelData\Attributes\WithTransformer; | use Spatie\LaravelData\Attributes\WithTransformer; | ||||||
|  | use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast; | ||||||
| 
 | 
 | ||||||
| class FormConfigData extends Data | class FormConfigData extends Data | ||||||
| { | { | ||||||
|  | @ -29,4 +30,13 @@ class FormConfigData extends Data | ||||||
|             new FieldCollection([]) |             new FieldCollection([]) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @param array<int, mixed> $arguments | ||||||
|  |      * @return DataEloquentCast<self> | ||||||
|  |      */ | ||||||
|  |     public static function castUsing(array $arguments): DataEloquentCast | ||||||
|  |     { | ||||||
|  |         return new DataEloquentCast(static::class, $arguments); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ class Form extends Model implements HasMedia | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<Participant> |      * @return HasMany<Participant, $this> | ||||||
|      */ |      */ | ||||||
|     public function participants(): HasMany |     public function participants(): HasMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ class Participant extends Model implements Preventable | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Form, self> |      * @return BelongsTo<Form, $this> | ||||||
|      */ |      */ | ||||||
|     public function form(): BelongsTo |     public function form(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -41,7 +41,7 @@ class Participant extends Model implements Preventable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<self> |      * @return HasMany<Participant, $this> | ||||||
|      */ |      */ | ||||||
|     public function children(): HasMany |     public function children(): HasMany | ||||||
|     { |     { | ||||||
|  | @ -49,7 +49,7 @@ class Participant extends Model implements Preventable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Member, self> |      * @return BelongsTo<Member, $this> | ||||||
|      */ |      */ | ||||||
|     public function member(): BelongsTo |     public function member(): BelongsTo | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ class Group extends Model | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<self, self> |      * @return BelongsTo<Group, $this> | ||||||
|      */ |      */ | ||||||
|     public function parent(): BelongsTo |     public function parent(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -34,7 +34,7 @@ class Group extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<self> |      * @return HasMany<Group, $this> | ||||||
|      */ |      */ | ||||||
|     public function children(): HasMany |     public function children(): HasMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -1,60 +0,0 @@ | ||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Http\Controllers\Auth; |  | ||||||
| 
 |  | ||||||
| use App\Http\Controllers\Controller; |  | ||||||
| use App\Providers\RouteServiceProvider; |  | ||||||
| use Illuminate\Foundation\Auth\AuthenticatesUsers; |  | ||||||
| use Illuminate\Http\Request; |  | ||||||
| use Inertia\Response; |  | ||||||
| 
 |  | ||||||
| class LoginController extends Controller |  | ||||||
| { |  | ||||||
|     /* |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | Login Controller |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | |  | ||||||
|     | This controller handles authenticating users for the application and |  | ||||||
|     | redirecting them to your home screen. The controller uses a trait |  | ||||||
|     | to conveniently provide its functionality to your applications. |  | ||||||
|     | |  | ||||||
|     */ |  | ||||||
| 
 |  | ||||||
|     use AuthenticatesUsers; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Where to redirect users after login. |  | ||||||
|      * |  | ||||||
|      * @var string |  | ||||||
|      */ |  | ||||||
|     protected $redirectTo = RouteServiceProvider::HOME; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Create a new controller instance. |  | ||||||
|      * |  | ||||||
|      * @return void |  | ||||||
|      */ |  | ||||||
|     public function __construct() |  | ||||||
|     { |  | ||||||
|         $this->middleware('guest')->except('logout'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function showLoginForm(): Response |  | ||||||
|     { |  | ||||||
|         session()->put('title', 'Anmelden'); |  | ||||||
| 
 |  | ||||||
|         return \Inertia::render('authentication/VLogin'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Validate the user login request. |  | ||||||
|      */ |  | ||||||
|     protected function validateLogin(Request $request): void |  | ||||||
|     { |  | ||||||
|         $request->validate([ |  | ||||||
|             $this->username() => 'required|max:255|string|email', |  | ||||||
|             'password' => 'required|string', |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,71 +0,0 @@ | ||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Http\Controllers\Auth; |  | ||||||
| 
 |  | ||||||
| use App\Http\Controllers\Controller; |  | ||||||
| use App\Providers\RouteServiceProvider; |  | ||||||
| use App\User; |  | ||||||
| use Illuminate\Foundation\Auth\RegistersUsers; |  | ||||||
| use Illuminate\Support\Facades\Hash; |  | ||||||
| use Illuminate\Support\Facades\Validator; |  | ||||||
| 
 |  | ||||||
| class RegisterController extends Controller |  | ||||||
| { |  | ||||||
|     /* |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | Register Controller |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | |  | ||||||
|     | This controller handles the registration of new users as well as their |  | ||||||
|     | validation and creation. By default this controller uses a trait to |  | ||||||
|     | provide this functionality without requiring any additional code. |  | ||||||
|     | |  | ||||||
|     */ |  | ||||||
| 
 |  | ||||||
|     use RegistersUsers; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Where to redirect users after registration. |  | ||||||
|      * |  | ||||||
|      * @var string |  | ||||||
|      */ |  | ||||||
|     protected $redirectTo = RouteServiceProvider::HOME; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Create a new controller instance. |  | ||||||
|      * |  | ||||||
|      * @return void |  | ||||||
|      */ |  | ||||||
|     public function __construct() |  | ||||||
|     { |  | ||||||
|         $this->middleware('guest'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get a validator for an incoming registration request. |  | ||||||
|      * |  | ||||||
|      * @return \Illuminate\Contracts\Validation\Validator |  | ||||||
|      */ |  | ||||||
|     protected function validator(array $data) |  | ||||||
|     { |  | ||||||
|         return Validator::make($data, [ |  | ||||||
|             'name' => ['required', 'string', 'max:255'], |  | ||||||
|             'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], |  | ||||||
|             'password' => ['required', 'string', 'min:8', 'confirmed'], |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Create a new user instance after a valid registration. |  | ||||||
|      * |  | ||||||
|      * @return \App\User |  | ||||||
|      */ |  | ||||||
|     protected function create(array $data) |  | ||||||
|     { |  | ||||||
|         return User::create([ |  | ||||||
|             'name' => $data['name'], |  | ||||||
|             'email' => $data['email'], |  | ||||||
|             'password' => Hash::make($data['password']), |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,50 +0,0 @@ | ||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Http\Controllers\Auth; |  | ||||||
| 
 |  | ||||||
| use App\Http\Controllers\Controller; |  | ||||||
| use App\Providers\RouteServiceProvider; |  | ||||||
| use Illuminate\Foundation\Auth\ResetsPasswords; |  | ||||||
| use Illuminate\Http\Request; |  | ||||||
| use Inertia\Inertia; |  | ||||||
| use Inertia\Response; |  | ||||||
| 
 |  | ||||||
| class ResetPasswordController extends Controller |  | ||||||
| { |  | ||||||
|     /* |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | Password Reset Controller |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | |  | ||||||
|     | This controller is responsible for handling password reset requests |  | ||||||
|     | and uses a simple trait to include this behavior. You're free to |  | ||||||
|     | explore this trait and override any methods you wish to tweak. |  | ||||||
|     | |  | ||||||
|     */ |  | ||||||
| 
 |  | ||||||
|     use ResetsPasswords; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Where to redirect users after resetting their password. |  | ||||||
|      * |  | ||||||
|      * @var string |  | ||||||
|      */ |  | ||||||
|     protected $redirectTo = RouteServiceProvider::HOME; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Display the password reset view for the given token. |  | ||||||
|      * |  | ||||||
|      * If no token is present, display the link request form. |  | ||||||
|      * |  | ||||||
|      * @param  \Illuminate\Http\Request  $request |  | ||||||
|      */ |  | ||||||
|     public function showResetForm(Request $request): Response |  | ||||||
|     { |  | ||||||
|         $token = $request->route()->parameter('token'); |  | ||||||
| 
 |  | ||||||
|         return Inertia::render('authentication/PasswordResetConfirm', [ |  | ||||||
|             'token' => $token, |  | ||||||
|             'email' => $request->email, |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -18,5 +18,7 @@ class Authenticate extends Middleware | ||||||
|         if (!$request->expectsJson()) { |         if (!$request->expectsJson()) { | ||||||
|             return route('login'); |             return route('login'); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| namespace App\Http\Middleware; | namespace App\Http\Middleware; | ||||||
| 
 | 
 | ||||||
| use App\Http\Resources\UserResource; | use App\Http\Resources\UserResource; | ||||||
| use App\Module\ModuleSettings; | use Modules\Module\ModuleSettings; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Illuminate\Support\Facades\Session; | use Illuminate\Support\Facades\Session; | ||||||
| use Inertia\Middleware; | use Inertia\Middleware; | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ class MassStoreAction | ||||||
|         foreach ($memberGroup as $members) { |         foreach ($memberGroup as $members) { | ||||||
|             $invoice = Invoice::createForMember($members->first(), $members, $year); |             $invoice = Invoice::createForMember($members->first(), $members, $year); | ||||||
|             $invoice->save(); |             $invoice->save(); | ||||||
|             $invoice->positions()->createMany($invoice->positions); |             $invoice->positions()->createMany($invoice->positions->toArray()); | ||||||
|             $invoices->push($invoice->fresh('positions')); |             $invoices->push($invoice->fresh('positions')); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ use App\Invoice\Models\Invoice; | ||||||
| use App\Payment\Payment; | use App\Payment\Payment; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
|  | use Modules\Invoice\InvoiceSettings; | ||||||
| use Zoomyboy\Tex\Document; | use Zoomyboy\Tex\Document; | ||||||
| use Zoomyboy\Tex\Engine; | use Zoomyboy\Tex\Engine; | ||||||
| use Zoomyboy\Tex\Template; | 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,7 @@ use App\Invoice\BillDocument; | ||||||
| use App\Invoice\BillKind; | use App\Invoice\BillKind; | ||||||
| use App\Invoice\Enums\InvoiceStatus; | use App\Invoice\Enums\InvoiceStatus; | ||||||
| use App\Invoice\InvoiceDocument; | use App\Invoice\InvoiceDocument; | ||||||
| use App\Invoice\InvoiceSettings; | use Modules\Invoice\InvoiceSettings; | ||||||
| use App\Invoice\RememberDocument; | use App\Invoice\RememberDocument; | ||||||
| use App\Invoice\Scopes\InvoiceFilterScope; | use App\Invoice\Scopes\InvoiceFilterScope; | ||||||
| use App\Member\Member; | use App\Member\Member; | ||||||
|  | @ -37,7 +37,7 @@ class Invoice extends Model | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<InvoicePosition> |      * @return HasMany<InvoicePosition, $this> | ||||||
|      */ |      */ | ||||||
|     public function positions(): HasMany |     public function positions(): HasMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ class InvoicePosition extends Model | ||||||
|     public $guarded = []; |     public $guarded = []; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Member, self> |      * @return BelongsTo<Member, $this> | ||||||
|      */ |      */ | ||||||
|     public function member(): BelongsTo |     public function member(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -24,7 +24,7 @@ class InvoicePosition extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Invoice, self> |      * @return BelongsTo<Invoice, $this> | ||||||
|      */ |      */ | ||||||
|     public function invoice(): BelongsTo |     public function invoice(): BelongsTo | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ use Spatie\LaravelData\Data; | ||||||
| use Spatie\LaravelData\DataCollection; | use Spatie\LaravelData\DataCollection; | ||||||
| use Spatie\MediaLibrary\MediaCollections\Models\Media; | use Spatie\MediaLibrary\MediaCollections\Models\Media; | ||||||
| use Spatie\LaravelData\Attributes\DataCollectionOf; | use Spatie\LaravelData\Attributes\DataCollectionOf; | ||||||
|  | use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast; | ||||||
| 
 | 
 | ||||||
| class Condition extends Data | class Condition extends Data | ||||||
| { | { | ||||||
|  | @ -42,4 +43,13 @@ class Condition extends Data | ||||||
|     { |     { | ||||||
|         return $this->mode === ConditionMode::ALL; |         return $this->mode === ConditionMode::ALL; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @param array<int, mixed> $arguments | ||||||
|  |      * @return DataEloquentCast<self> | ||||||
|  |      */ | ||||||
|  |     public static function castUsing(array $arguments): DataEloquentCast | ||||||
|  |     { | ||||||
|  |         return new DataEloquentCast(static::class, $arguments); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,12 +2,17 @@ | ||||||
| 
 | 
 | ||||||
| namespace App\Lib\Editor; | namespace App\Lib\Editor; | ||||||
| 
 | 
 | ||||||
|  | use Livewire\Wireable; | ||||||
|  | use Spatie\LaravelData\Concerns\WireableData; | ||||||
| use Spatie\LaravelData\Data; | use Spatie\LaravelData\Data; | ||||||
|  | use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast; | ||||||
| 
 | 
 | ||||||
| /** @todo replace blocks with actual block data classes */ | /** @todo replace blocks with actual block data classes */ | ||||||
| class EditorData extends Data implements Editorable | class EditorData extends Data implements Editorable, Wireable | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|  |     use WireableData; | ||||||
|  | 
 | ||||||
|     /** @param array<int, mixed> $blocks */ |     /** @param array<int, mixed> $blocks */ | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         public string $version, |         public string $version, | ||||||
|  | @ -83,4 +88,13 @@ class EditorData extends Data implements Editorable | ||||||
|     { |     { | ||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @param array<int, mixed> $arguments | ||||||
|  |      * @return DataEloquentCast<self> | ||||||
|  |      */ | ||||||
|  |     public static function castUsing(array $arguments): DataEloquentCast | ||||||
|  |     { | ||||||
|  |         return new DataEloquentCast(static::class, $arguments); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ use Spatie\LaravelData\Data; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @template T of Model |  * @template T of Model | ||||||
|  * @property Builder $query |  * @property Builder<T> $query | ||||||
|  */ |  */ | ||||||
| abstract class Filter extends Data | abstract class Filter extends Data | ||||||
| { | { | ||||||
|  | @ -40,7 +40,7 @@ abstract class Filter extends Data | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return static(self<T>) |      * @return static | ||||||
|      */ |      */ | ||||||
|     public function toDefault(): self |     public function toDefault(): self | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ use Spatie\LaravelData\Data; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @template T of Model |  * @template T of Model | ||||||
|  * @property Builder $query |  * @property Builder<T> $query | ||||||
|  */ |  */ | ||||||
| abstract class ScoutFilter extends Data | abstract class ScoutFilter extends Data | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ class ResyncAction | ||||||
|      */ |      */ | ||||||
|     public function getResults(Maildispatcher $dispatcher): Collection |     public function getResults(Maildispatcher $dispatcher): Collection | ||||||
|     { |     { | ||||||
|         return FilterScope::fromPost($dispatcher->filter)->getQuery()->get() |         return FilterScope::fromPost($dispatcher->filter)->noPageLimit()->getQuery()->get() | ||||||
|             ->filter(fn ($member) => $member->email || $member->email_parents) |             ->filter(fn ($member) => $member->email || $member->email_parents) | ||||||
|             ->map(fn ($member) => MailEntry::from(['email' => $member->email ?: $member->email_parents])) |             ->map(fn ($member) => MailEntry::from(['email' => $member->email ?: $member->email_parents])) | ||||||
|             ->unique(fn ($member) => $member->email); |             ->unique(fn ($member) => $member->email); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| namespace App\Maildispatcher\Models; | namespace App\Maildispatcher\Models; | ||||||
| 
 | 
 | ||||||
| use App\Mailgateway\Models\Mailgateway; | use Modules\Mailgateway\Models\Mailgateway; | ||||||
| use Database\Factories\Maildispatcher\Models\MaildispatcherFactory; | use Database\Factories\Maildispatcher\Models\MaildispatcherFactory; | ||||||
| use Illuminate\Database\Eloquent\Concerns\HasUuids; | use Illuminate\Database\Eloquent\Concerns\HasUuids; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | @ -23,7 +23,7 @@ class Maildispatcher extends Model | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Mailgateway, self> |      * @return BelongsTo<Mailgateway, $this> | ||||||
|      */ |      */ | ||||||
|     public function gateway(): BelongsTo |     public function gateway(): BelongsTo | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -4,8 +4,7 @@ namespace App\Maildispatcher\Resources; | ||||||
| 
 | 
 | ||||||
| use App\Lib\HasMeta; | use App\Lib\HasMeta; | ||||||
| use App\Maildispatcher\Models\Maildispatcher; | use App\Maildispatcher\Models\Maildispatcher; | ||||||
| use App\Mailgateway\Models\Mailgateway; | use Modules\Mailgateway\Models\Mailgateway; | ||||||
| use App\Mailgateway\Resources\MailgatewayResource; |  | ||||||
| use App\Member\FilterScope; | use App\Member\FilterScope; | ||||||
| use App\Member\Member; | use App\Member\Member; | ||||||
| use Illuminate\Http\Resources\Json\JsonResource; | 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, |  | ||||||
|                 ], |  | ||||||
|             ], |  | ||||||
|         ]; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -85,7 +85,7 @@ class MailmanService | ||||||
|         return app(Paginator::class)->result( |         return app(Paginator::class)->result( | ||||||
|             fn ($page) => $this->http()->get("/lists/{$list->listId}/roster/member?page={$page}&count=10"), |             fn ($page) => $this->http()->get("/lists/{$list->listId}/roster/member?page={$page}&count=10"), | ||||||
|             function ($response) use ($list) { |             function ($response) use ($list) { | ||||||
|                 throw_unless($response->ok(), MailmanServiceException::class, 'Fetching members for listId '.$list->listId.' failed.'); |                 throw_unless($response->ok(), MailmanServiceException::class, 'Fetching members for listId ' . $list->listId . ' failed.'); | ||||||
|                 /** @var array<int, array{email: string, self_link: string}>|null */ |                 /** @var array<int, array{email: string, self_link: string}>|null */ | ||||||
|                 $entries = data_get($response->json(), 'entries', []); |                 $entries = data_get($response->json(), 'entries', []); | ||||||
|                 throw_if(is_null($entries), MailmanServiceException::class, 'Failed getting member list from response'); |                 throw_if(is_null($entries), MailmanServiceException::class, 'Failed getting member list from response'); | ||||||
|  | @ -112,7 +112,7 @@ class MailmanService | ||||||
|             'pre_confirmed' => 'true', |             'pre_confirmed' => 'true', | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         throw_unless(201 === $response->status(), MailmanServiceException::class, 'Adding member '.$email.' to '.$list->listId.' failed'); |         throw_unless(201 === $response->status(), MailmanServiceException::class, 'Adding member ' . $email . ' to ' . $list->listId . ' failed'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function removeMember(Member $member): void |     public function removeMember(Member $member): void | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ use App\Member\Member; | ||||||
| use App\Setting\NamiSettings; | use App\Setting\NamiSettings; | ||||||
| use App\Subactivity; | use App\Subactivity; | ||||||
| use Lorisleiva\Actions\Concerns\AsAction; | use Lorisleiva\Actions\Concerns\AsAction; | ||||||
|  | use Zoomyboy\LaravelNami\Data\BankAccount; | ||||||
| use Zoomyboy\LaravelNami\Data\Member as NamiMember; | use Zoomyboy\LaravelNami\Data\Member as NamiMember; | ||||||
| 
 | 
 | ||||||
| class NamiPutMemberAction | class NamiPutMemberAction | ||||||
|  | @ -47,6 +48,16 @@ class NamiPutMemberAction | ||||||
|             'id' => $member->nami_id, |             'id' => $member->nami_id, | ||||||
|             'version' => $member->version, |             'version' => $member->version, | ||||||
|             'keepdata' => $member->keepdata, |             'keepdata' => $member->keepdata, | ||||||
|  |             'bankAccount' => BankAccount::from([ | ||||||
|  |                 'bankName' => $member->bankAccount->bank_name, | ||||||
|  |                 'id' => $member->bankAccount->nami_id, | ||||||
|  |                 'memberId' => $member->mitgliedsnr, | ||||||
|  |                 'iban' => $member->bankAccount->iban, | ||||||
|  |                 'bic' => $member->bankAccount->bic, | ||||||
|  |                 'blz' => $member->bankAccount->blz, | ||||||
|  |                 'person' => $member->bankAccount->person, | ||||||
|  |                 'accountNumber' => $member->bankAccount->account_number, | ||||||
|  |             ]), | ||||||
|         ]); |         ]); | ||||||
|         $response = $api->putMember($namiMember, $activity ? $activity->nami_id : null, $subactivity ? $subactivity->nami_id : null); |         $response = $api->putMember($namiMember, $activity ? $activity->nami_id : null, $subactivity ? $subactivity->nami_id : null); | ||||||
|         Member::withoutEvents(function () use ($response, $member) { |         Member::withoutEvents(function () use ($response, $member) { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Member; | ||||||
|  | 
 | ||||||
|  | use Database\Factories\Member\BankAccountFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | 
 | ||||||
|  | class BankAccount extends Model | ||||||
|  | { | ||||||
|  |     /** @use HasFactory<BankAccountFactory> */ | ||||||
|  |     use HasFactory; | ||||||
|  | 
 | ||||||
|  |     public $guarded = []; | ||||||
|  | 
 | ||||||
|  |     public $primaryKey = 'member_id'; | ||||||
|  | } | ||||||
|  | @ -9,7 +9,8 @@ use Zoomyboy\LaravelNami\Data\Member as NamiMember; | ||||||
| use Zoomyboy\LaravelNami\Data\MembershipEntry as NamiMembershipEntry; | use Zoomyboy\LaravelNami\Data\MembershipEntry as NamiMembershipEntry; | ||||||
| use Spatie\LaravelData\Attributes\DataCollectionOf; | use Spatie\LaravelData\Attributes\DataCollectionOf; | ||||||
| 
 | 
 | ||||||
| class FullMember extends Data { | class FullMember extends Data | ||||||
|  | { | ||||||
|     /** |     /** | ||||||
|      * @param DataCollection<int, NamiCourse> $courses |      * @param DataCollection<int, NamiCourse> $courses | ||||||
|      * @param DataCollection<int, NamiMembershipEntry> $memberships |      * @param DataCollection<int, NamiMembershipEntry> $memberships | ||||||
|  | @ -20,6 +21,6 @@ class FullMember extends Data { | ||||||
|         public DataCollection $courses, |         public DataCollection $courses, | ||||||
|         #[DataCollectionOf(NamiMembershipEntry::class)]
 |         #[DataCollectionOf(NamiMembershipEntry::class)]
 | ||||||
|         public DataCollection $memberships, |         public DataCollection $memberships, | ||||||
|     ) {} |     ) { | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -34,6 +34,7 @@ use Zoomyboy\Osm\HasGeolocation; | ||||||
| use Zoomyboy\Phone\HasPhoneNumbers; | use Zoomyboy\Phone\HasPhoneNumbers; | ||||||
| use App\Prevention\Enums\Prevention; | use App\Prevention\Enums\Prevention; | ||||||
| use Database\Factories\Member\MemberFactory; | use Database\Factories\Member\MemberFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\HasOne; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @property string $subscription_name |  * @property string $subscription_name | ||||||
|  | @ -195,7 +196,7 @@ class Member extends Model implements Geolocatable | ||||||
| 
 | 
 | ||||||
|     // ---------------------------------- Relations ----------------------------------
 |     // ---------------------------------- Relations ----------------------------------
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Country, self> |      * @return BelongsTo<Country, $this> | ||||||
|      */ |      */ | ||||||
|     public function country(): BelongsTo |     public function country(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -203,7 +204,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Gender, self> |      * @return BelongsTo<Gender, $this> | ||||||
|      */ |      */ | ||||||
|     public function gender(): BelongsTo |     public function gender(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -211,7 +212,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Region, self> |      * @return BelongsTo<Region, $this> | ||||||
|      */ |      */ | ||||||
|     public function region(): BelongsTo |     public function region(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -222,7 +223,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<InvoicePosition> |      * @return HasMany<InvoicePosition, $this> | ||||||
|      */ |      */ | ||||||
|     public function invoicePositions(): HasMany |     public function invoicePositions(): HasMany | ||||||
|     { |     { | ||||||
|  | @ -230,7 +231,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Confession, self> |      * @return BelongsTo<Confession, $this> | ||||||
|      */ |      */ | ||||||
|     public function confession(): BelongsTo |     public function confession(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -238,7 +239,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Nationality, self> |      * @return BelongsTo<Nationality, $this> | ||||||
|      */ |      */ | ||||||
|     public function nationality(): BelongsTo |     public function nationality(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -246,7 +247,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Subscription, self> |      * @return BelongsTo<Subscription, $this> | ||||||
|      */ |      */ | ||||||
|     public function subscription(): BelongsTo |     public function subscription(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -254,7 +255,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Group, self> |      * @return BelongsTo<Group, $this> | ||||||
|      */ |      */ | ||||||
|     public function group(): BelongsTo |     public function group(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -262,7 +263,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<CourseMember> |      * @return HasMany<CourseMember, $this> | ||||||
|      */ |      */ | ||||||
|     public function courses(): HasMany |     public function courses(): HasMany | ||||||
|     { |     { | ||||||
|  | @ -270,7 +271,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<Membership> |      * @return HasMany<Membership, $this> | ||||||
|      */ |      */ | ||||||
|     public function memberships(): HasMany |     public function memberships(): HasMany | ||||||
|     { |     { | ||||||
|  | @ -278,7 +279,7 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<Membership> |      * @return HasMany<Membership, $this> | ||||||
|      */ |      */ | ||||||
|     public function leaderMemberships(): HasMany |     public function leaderMemberships(): HasMany | ||||||
|     { |     { | ||||||
|  | @ -286,21 +287,34 @@ class Member extends Model implements Geolocatable | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<Membership> |      * @return HasMany<Membership, $this> | ||||||
|      */ |      */ | ||||||
|     public function ageGroupMemberships(): HasMany |     public function ageGroupMemberships(): HasMany | ||||||
|     { |     { | ||||||
|         return $this->memberships()->isAgeGroup()->active(); |         return $this->memberships()->isAgeGroup()->active(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @return HasOne<BankAccount, $this> | ||||||
|  |      */ | ||||||
|  |     public function bankAccount(): HasOne | ||||||
|  |     { | ||||||
|  |         return $this->hasOne(BankAccount::class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static function booted() |     public static function booted() | ||||||
|     { |     { | ||||||
|  |         static::created(function (self $model): void { | ||||||
|  |             $model->bankAccount()->create([]); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         static::deleting(function (self $model): void { |         static::deleting(function (self $model): void { | ||||||
|             $model->memberships->each->delete(); |             $model->memberships->each->delete(); | ||||||
|             $model->courses->each->delete(); |             $model->courses->each->delete(); | ||||||
|             $model->invoicePositions->each(function ($position) { |             $model->invoicePositions->each(function ($position) { | ||||||
|                 $position->delete(); |                 $position->delete(); | ||||||
|             }); |             }); | ||||||
|  |             $model->bankAccount()->delete(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ class Membership extends Model | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Activity, self> |      * @return BelongsTo<Activity, $this> | ||||||
|      */ |      */ | ||||||
|     public function activity(): BelongsTo |     public function activity(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -40,7 +40,7 @@ class Membership extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Group, self> |      * @return BelongsTo<Group, $this> | ||||||
|      */ |      */ | ||||||
|     public function group(): BelongsTo |     public function group(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -48,7 +48,7 @@ class Membership extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Subactivity, self> |      * @return BelongsTo<Subactivity, $this> | ||||||
|      */ |      */ | ||||||
|     public function subactivity(): BelongsTo |     public function subactivity(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -56,7 +56,7 @@ class Membership extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Member, self> |      * @return BelongsTo<Member, $this> | ||||||
|      */ |      */ | ||||||
|     public function member(): BelongsTo |     public function member(): BelongsTo | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -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'; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -14,9 +14,6 @@ class Subscription extends Model | ||||||
|     /** @use HasFactory<SubscriptionFactory> */ |     /** @use HasFactory<SubscriptionFactory> */ | ||||||
|     use HasFactory; |     use HasFactory; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @var array<int, string> |  | ||||||
|      */ |  | ||||||
|     public $fillable = ['name', 'fee_id']; |     public $fillable = ['name', 'fee_id']; | ||||||
| 
 | 
 | ||||||
|     public function getAmount(): int |     public function getAmount(): int | ||||||
|  | @ -25,7 +22,7 @@ class Subscription extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsTo<Fee, self> |      * @return BelongsTo<Fee, $this> | ||||||
|      */ |      */ | ||||||
|     public function fee(): BelongsTo |     public function fee(): BelongsTo | ||||||
|     { |     { | ||||||
|  | @ -33,7 +30,7 @@ class Subscription extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return HasMany<SubscriptionChild> |      * @return HasMany<SubscriptionChild, $this> | ||||||
|      */ |      */ | ||||||
|     public function children(): HasMany |     public function children(): HasMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Prevention\Actions; |  | ||||||
| 
 |  | ||||||
| use App\Prevention\PreventionSettings; |  | ||||||
| use Illuminate\Http\JsonResponse; |  | ||||||
| use Lorisleiva\Actions\Concerns\AsAction; |  | ||||||
| 
 |  | ||||||
| class SettingApiAction |  | ||||||
| { |  | ||||||
|     use AsAction; |  | ||||||
| 
 |  | ||||||
|     public function handle(): JsonResponse |  | ||||||
|     { |  | ||||||
|         return response()->json([ |  | ||||||
|             'data' => app(PreventionSettings::class)->toArray(), |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Prevention\Actions; |  | ||||||
| 
 |  | ||||||
| use App\Lib\Editor\EditorData; |  | ||||||
| use App\Lib\Events\Succeeded; |  | ||||||
| use App\Prevention\PreventionSettings; |  | ||||||
| use Lorisleiva\Actions\ActionRequest; |  | ||||||
| use Lorisleiva\Actions\Concerns\AsAction; |  | ||||||
| 
 |  | ||||||
| class SettingStoreAction |  | ||||||
| { |  | ||||||
|     use AsAction; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @return array<string, string> |  | ||||||
|      */ |  | ||||||
|     public function rules(): array |  | ||||||
|     { |  | ||||||
|         return [ |  | ||||||
|             'formmail' => 'array', |  | ||||||
|         ]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function handle(ActionRequest $request): void |  | ||||||
|     { |  | ||||||
|         $settings = app(PreventionSettings::class); |  | ||||||
|         $settings->formmail = EditorData::from($request->formmail); |  | ||||||
|         $settings->save(); |  | ||||||
| 
 |  | ||||||
|         Succeeded::message('Einstellungen gespeichert.')->dispatch(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -2,7 +2,6 @@ | ||||||
| 
 | 
 | ||||||
| namespace App\Prevention\Mails; | namespace App\Prevention\Mails; | ||||||
| 
 | 
 | ||||||
| use App\Invoice\InvoiceSettings; |  | ||||||
| use App\Lib\Editor\EditorData; | use App\Lib\Editor\EditorData; | ||||||
| use App\Prevention\Contracts\Preventable; | use App\Prevention\Contracts\Preventable; | ||||||
| use Illuminate\Bus\Queueable; | use Illuminate\Bus\Queueable; | ||||||
|  | @ -11,6 +10,7 @@ use Illuminate\Mail\Mailable; | ||||||
| use Illuminate\Mail\Mailables\Content; | use Illuminate\Mail\Mailables\Content; | ||||||
| use Illuminate\Mail\Mailables\Envelope; | use Illuminate\Mail\Mailables\Envelope; | ||||||
| use Illuminate\Queue\SerializesModels; | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Modules\Invoice\InvoiceSettings; | ||||||
| 
 | 
 | ||||||
| class PreventionRememberMail extends Mailable | class PreventionRememberMail extends Mailable | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -3,13 +3,10 @@ | ||||||
| namespace App\Providers; | namespace App\Providers; | ||||||
| 
 | 
 | ||||||
| use App\Form\Models\Form; | use App\Form\Models\Form; | ||||||
| use App\Mailgateway\Types\LocalType; |  | ||||||
| use App\Mailgateway\Types\MailmanType; |  | ||||||
| use Illuminate\Http\RedirectResponse; | use Illuminate\Http\RedirectResponse; | ||||||
| use Illuminate\Http\Resources\Json\JsonResource; | use Illuminate\Http\Resources\Json\JsonResource; | ||||||
| use Illuminate\Support\Facades\Blade; | use Illuminate\Support\Facades\Blade; | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
| use Laravel\Telescope\Telescope; |  | ||||||
| 
 | 
 | ||||||
| class AppServiceProvider extends ServiceProvider | class AppServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|  | @ -30,11 +27,6 @@ class AppServiceProvider extends ServiceProvider | ||||||
|             return $this; |             return $this; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         app()->bind('mail-gateways', fn () => collect([ |  | ||||||
|             LocalType::class, |  | ||||||
|             MailmanType::class, |  | ||||||
|         ])); |  | ||||||
| 
 |  | ||||||
|         app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class)); |         app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class)); | ||||||
| 
 | 
 | ||||||
|         Blade::componentNamespace('App\\View\\Mail', 'mail-view'); |         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\ActionRequest; | ||||||
| use Lorisleiva\Actions\Concerns\AsAction; | use Lorisleiva\Actions\Concerns\AsAction; | ||||||
| 
 | 
 | ||||||
|  | /** @deprecated */ | ||||||
| class StoreAction | class StoreAction | ||||||
| { | { | ||||||
|     use AsAction; |     use AsAction; | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ use Inertia\Inertia; | ||||||
| use Inertia\Response; | use Inertia\Response; | ||||||
| use Lorisleiva\Actions\Concerns\AsAction; | use Lorisleiva\Actions\Concerns\AsAction; | ||||||
| 
 | 
 | ||||||
|  | /** @deprecated */ | ||||||
| class ViewAction | class ViewAction | ||||||
| { | { | ||||||
|     use AsAction; |     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 |     public function url(): string | ||||||
|     { |     { | ||||||
|         return route('setting.view', ['settingGroup' => $this->group()]); |         return url('setting/' . $this->group()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -3,14 +3,10 @@ | ||||||
| namespace App\Setting; | namespace App\Setting; | ||||||
| 
 | 
 | ||||||
| use App\Group; | 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\Api; | ||||||
| use Zoomyboy\LaravelNami\Nami; | use Zoomyboy\LaravelNami\Nami; | ||||||
| 
 | 
 | ||||||
| class NamiSettings extends LocalSettings implements Storeable | class NamiSettings extends LocalSettings | ||||||
| { | { | ||||||
|     public int $mglnr; |     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 |     public function localGroup(): ?Group | ||||||
|     { |     { | ||||||
|         return Group::firstWhere('nami_id', $this->default_group_id); |         return Group::firstWhere('nami_id', $this->default_group_id); | ||||||
|  |  | ||||||
|  | @ -2,9 +2,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace App\Setting; | namespace App\Setting; | ||||||
| 
 | 
 | ||||||
| use App\Invoice\InvoiceSettings; |  | ||||||
| use App\Setting\Contracts\Storeable; |  | ||||||
| use Illuminate\Routing\Router; | use Illuminate\Routing\Router; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| 
 | 
 | ||||||
| class SettingFactory | 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) => [ |         return collect($this->settings)->map(fn ($setting) => new $setting); | ||||||
|             'url' => (new $setting)->url(), |  | ||||||
|             'is_active' => url(request()->path()) === (new $setting)->url(), |  | ||||||
|             'title' => $setting::title(), |  | ||||||
|         ]) |  | ||||||
|             ->toArray(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function resolveGroupName(string $name): LocalSettings |     public function resolveGroupName(string $name): LocalSettings | ||||||
|  |  | ||||||
|  | @ -2,16 +2,12 @@ | ||||||
| 
 | 
 | ||||||
| namespace App\Setting; | namespace App\Setting; | ||||||
| 
 | 
 | ||||||
| use App\Fileshare\FileshareSettings; | use Modules\Module\ModuleSettings; | ||||||
| use App\Form\FormSettings; | use App\Setting\Data\SettingSynthesizer; | ||||||
| use App\Invoice\InvoiceSettings; |  | ||||||
| use App\Mailgateway\MailgatewaySettings; |  | ||||||
| use App\Module\ModuleSettings; |  | ||||||
| use App\Prevention\PreventionSettings; |  | ||||||
| use App\Setting\Actions\StoreAction; |  | ||||||
| use App\Setting\Actions\ViewAction; |  | ||||||
| use Illuminate\Routing\Router; | use Illuminate\Routing\Router; | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
|  | use Livewire\Livewire; | ||||||
|  | use Modules\Invoice\InvoiceSettings; | ||||||
| 
 | 
 | ||||||
| class SettingServiceProvider extends ServiceProvider | class SettingServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|  | @ -24,9 +20,6 @@ class SettingServiceProvider extends ServiceProvider | ||||||
|     { |     { | ||||||
|         app()->singleton(SettingFactory::class, fn () => new SettingFactory()); |         app()->singleton(SettingFactory::class, fn () => new SettingFactory()); | ||||||
|         app(Router::class)->bind('settingGroup', fn ($param) => app(SettingFactory::class)->resolveGroupName($param)); |         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 +31,8 @@ class SettingServiceProvider extends ServiceProvider | ||||||
|     { |     { | ||||||
|         app(SettingFactory::class)->register(ModuleSettings::class); |         app(SettingFactory::class)->register(ModuleSettings::class); | ||||||
|         app(SettingFactory::class)->register(InvoiceSettings::class); |         app(SettingFactory::class)->register(InvoiceSettings::class); | ||||||
|         app(SettingFactory::class)->register(MailgatewaySettings::class); |  | ||||||
|         app(SettingFactory::class)->register(NamiSettings::class); |         app(SettingFactory::class)->register(NamiSettings::class); | ||||||
|         app(SettingFactory::class)->register(FormSettings::class); | 
 | ||||||
|         app(SettingFactory::class)->register(FileshareSettings::class); |         Livewire::propertySynthesizer(SettingSynthesizer::class); | ||||||
|         app(SettingFactory::class)->register(PreventionSettings::class); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -16,9 +16,6 @@ class Subactivity extends Model | ||||||
|     use HasNamiField; |     use HasNamiField; | ||||||
|     use Sluggable; |     use Sluggable; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @var array<int, string> |  | ||||||
|      */ |  | ||||||
|     public $fillable = ['is_age_group', 'is_filterable', 'slug', 'name', 'nami_id']; |     public $fillable = ['is_age_group', 'is_filterable', 'slug', 'name', 'nami_id']; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -26,9 +23,6 @@ class Subactivity extends Model | ||||||
|      */ |      */ | ||||||
|     public $timestamps = false; |     public $timestamps = false; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @var array<string, string> |  | ||||||
|      */ |  | ||||||
|     public $casts = [ |     public $casts = [ | ||||||
|         'is_age_group' => 'boolean', |         'is_age_group' => 'boolean', | ||||||
|         'is_filterable' => 'boolean', |         'is_filterable' => 'boolean', | ||||||
|  | @ -47,7 +41,7 @@ class Subactivity extends Model | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return BelongsToMany<Activity> |      * @return BelongsToMany<Activity, $this> | ||||||
|      */ |      */ | ||||||
|     public function activities(): BelongsToMany |     public function activities(): BelongsToMany | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -2,11 +2,11 @@ | ||||||
| 
 | 
 | ||||||
| namespace App; | namespace App; | ||||||
| 
 | 
 | ||||||
| use App\Auth\ResetPassword; |  | ||||||
| use Database\Factories\UserFactory; | use Database\Factories\UserFactory; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Foundation\Auth\User as Authenticatable; | use Illuminate\Foundation\Auth\User as Authenticatable; | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
|  | use Modules\Auth\Mails\ResetPassword; | ||||||
| 
 | 
 | ||||||
| class User extends Authenticatable | class User extends Authenticatable | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -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,50 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\View\Form; | ||||||
|  | 
 | ||||||
|  | use App\View\Traits\HasFormDimensions; | ||||||
|  | use Illuminate\View\Component; | ||||||
|  | 
 | ||||||
|  | class Editor 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 = '', | ||||||
|  |     ) { | ||||||
|  |         $this->id = str()->uuid()->toString(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return <<<'HTML' | ||||||
|  |             <div class="flex flex-col group {{$heightClass}}"> | ||||||
|  |                 @if ($label) | ||||||
|  |                 <x-form::label :required="$required">{{$label}}</x-form::label> | ||||||
|  |                 @endif | ||||||
|  | 
 | ||||||
|  |                 <div class="relative w-full h-full"> | ||||||
|  |                     <div class=" | ||||||
|  |                         w-full 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 | ||||||
|  |                         " @updated="$wire.{{$attributes->wire('model')->value}} = $event.detail" x-bind="editor"x-data="{ | ||||||
|  |                             value: $wire.{{$attributes->wire('model')->value}}, | ||||||
|  |                         }" id="{{$id}}" {{$attributes}}></div>
 | ||||||
|  |                     <x-ui::errors :for="$name" /> | ||||||
|  |                     @if($hint) | ||||||
|  |                     <x-form::hint>{{$hint}}</x-form::hint> | ||||||
|  |                     @endif | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         HTML; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -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,32 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\View\Page; | ||||||
|  | 
 | ||||||
|  | use Illuminate\View\Component; | ||||||
|  | 
 | ||||||
|  | class Full extends Component | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     public function __construct(public string $title = '', public ?string $heading = null) | ||||||
|  |     { | ||||||
|  |         session()->put('title', $title); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return <<<'HTML' | ||||||
|  |             <div class="min-w-[16rem] sm:min-w-[18rem] md:min-w-[24rem] bg-gray-800 rounded-xl overflow-hidden shadow-lg @if($heading === null) p-6 md:p-10 @endif"> | ||||||
|  |                 @if ($heading) | ||||||
|  |                 <div class="h-24 p-6 md:px-10 bg-primary-800 flex justify-between items-center w-full"> | ||||||
|  |                     <span class="text-primary-500 text-xl">{{$heading}}</span> | ||||||
|  |                     <img src="{{asset('img/dpsg.gif')}}" class="w-24" /> | ||||||
|  |                 </div> | ||||||
|  |                 @endif | ||||||
|  | 
 | ||||||
|  |                 <div @if($heading !== null) class="p-6 md:p-10" @endif> | ||||||
|  |                     {{ $slot }} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         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,59 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\View\Page; | ||||||
|  | 
 | ||||||
|  | use Illuminate\View\Component; | ||||||
|  | 
 | ||||||
|  | class Layout extends Component | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     public function __construct(public string $pageClass = '', public string $title = '', public string $menu = '') | ||||||
|  |     { | ||||||
|  |         session()->put('title', $title); | ||||||
|  |         session()->put('menu', $menu); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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,42 @@ | ||||||
|  | <?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(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return <<<'HTML' | ||||||
|  |             <x-page::layout :title="$active::title()" menu="setting"> | ||||||
|  |                 <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,22 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\View\Ui; | ||||||
|  | 
 | ||||||
|  | use Illuminate\View\Component; | ||||||
|  | 
 | ||||||
|  | class Button extends Component | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     public function __construct(public string $type = 'button') | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return <<<'HTML' | ||||||
|  |             <button type="{{$type}}" class="px-3 py-2 uppercase no-underline text-sm items-center justify-center bg-primary-700 rounded text-primary-300"> | ||||||
|  |                 {{$slot}} | ||||||
|  |             </button> | ||||||
|  |         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; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -3,8 +3,10 @@ | ||||||
| echo "drop database scoutrobot;" | sudo mysql | echo "drop database scoutrobot;" | sudo mysql | ||||||
| echo "create database scoutrobot;" | sudo mysql | echo "create database scoutrobot;" | sudo mysql | ||||||
| 
 | 
 | ||||||
| ssh -l stammsilva zoomyboy.de "cd /usr/share/webapps/nami_silva && docker compose exec db mysqldump -udb -p$SCOUTROBOT_DB_PASSWORD db" > db.tmp | ssh -l stamm-silva zoomyboy.de "cd /usr/share/webapps/adrema_silva && docker compose exec db mysqldump -udb -p$SCOUTROBOT_DB_PASSWORD db" > db.tmp | ||||||
| sudo mysql scoutrobot < db.tmp | sudo mysql adrema < db.tmp | ||||||
| rm db.tmp | rm db.tmp | ||||||
| 
 | 
 | ||||||
| echo 'app(\App\Form\FormSettings::class)->fill(["registerUrl" => "http://stammsilva.test/anmeldung/{slug}/register", "clearCacheUrl" => "http://stammsilva.test/adrema/clear-cache"])->save();' | php artisan tinker | echo 'app(\App\Form\FormSettings::class)->fill(["registerUrl" => "http://stammsilva.test/anmeldung/{slug}/register", "clearCacheUrl" => "http://stammsilva.test/adrema/clear-cache"])->save();' | php artisan tinker | ||||||
|  | 
 | ||||||
|  | exit 0 | ||||||
|  |  | ||||||
|  | @ -61,6 +61,7 @@ | ||||||
|         "laravel/ui": "^4.0", |         "laravel/ui": "^4.0", | ||||||
|         "league/csv": "^9.9", |         "league/csv": "^9.9", | ||||||
|         "league/flysystem-webdav": "dev-master as 3.28.0", |         "league/flysystem-webdav": "dev-master as 3.28.0", | ||||||
|  |         "livewire/livewire": "^3.5", | ||||||
|         "lorisleiva/laravel-actions": "^2.4", |         "lorisleiva/laravel-actions": "^2.4", | ||||||
|         "meilisearch/meilisearch-php": "^1.6", |         "meilisearch/meilisearch-php": "^1.6", | ||||||
|         "monicahq/laravel-sabre": "^1.6", |         "monicahq/laravel-sabre": "^1.6", | ||||||
|  | @ -80,12 +81,13 @@ | ||||||
|     }, |     }, | ||||||
|     "require-dev": { |     "require-dev": { | ||||||
|         "fakerphp/faker": "^1.9.1", |         "fakerphp/faker": "^1.9.1", | ||||||
|         "larastan/larastan": "^2.0", |         "larastan/larastan": "^3.0", | ||||||
|         "laravel/envoy": "^2.8", |         "laravel/envoy": "^2.8", | ||||||
|         "mockery/mockery": "^1.4.4", |         "mockery/mockery": "^1.4.4", | ||||||
|         "orchestra/testbench": "^9.0", |         "orchestra/testbench": "^9.0", | ||||||
|         "pestphp/pest": "^3.0", |         "pestphp/pest": "^3.0", | ||||||
|         "phpstan/phpstan-mockery": "^1.1" |         "phpstan/phpstan-mockery": "^2.0", | ||||||
|  |         "qossmic/deptrac": "^2.0" | ||||||
|     }, |     }, | ||||||
|     "config": { |     "config": { | ||||||
|         "optimize-autoloader": true, |         "optimize-autoloader": true, | ||||||
|  | @ -104,6 +106,7 @@ | ||||||
|     "autoload": { |     "autoload": { | ||||||
|         "psr-4": { |         "psr-4": { | ||||||
|             "App\\": "app/", |             "App\\": "app/", | ||||||
|  |             "Modules\\": "modules/", | ||||||
|             "Plugins\\": "plugins/", |             "Plugins\\": "plugins/", | ||||||
|             "Database\\Factories\\": "database/factories/", |             "Database\\Factories\\": "database/factories/", | ||||||
|             "Database\\Seeders\\": "database/seeders/" |             "Database\\Seeders\\": "database/seeders/" | ||||||
|  | @ -111,6 +114,7 @@ | ||||||
|     }, |     }, | ||||||
|     "autoload-dev": { |     "autoload-dev": { | ||||||
|         "psr-4": { |         "psr-4": { | ||||||
|  |             "Modules\\Dashboard\\Tests\\": "modules/dashboard/tests/", | ||||||
|             "Tests\\": "tests/", |             "Tests\\": "tests/", | ||||||
|             "Zoomyboy\\LaravelNami\\Tests\\": "packages/laravel-nami/tests/" |             "Zoomyboy\\LaravelNami\\Tests\\": "packages/laravel-nami/tests/" | ||||||
|         } |         } | ||||||
|  | @ -118,6 +122,10 @@ | ||||||
|     "minimum-stability": "dev", |     "minimum-stability": "dev", | ||||||
|     "prefer-stable": true, |     "prefer-stable": true, | ||||||
|     "scripts": { |     "scripts": { | ||||||
|  |         "archtest": [ | ||||||
|  |             "./vendor/bin/deptrac analyze", | ||||||
|  |             "./vendor/bin/pest tests/Arch.php" | ||||||
|  |         ], | ||||||
|         "post-autoload-dump": [ |         "post-autoload-dump": [ | ||||||
|             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", |             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", | ||||||
|             "@php artisan package:discover --ansi" |             "@php artisan package:discover --ansi" | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -168,7 +168,6 @@ return [ | ||||||
|          */ |          */ | ||||||
|         App\Providers\AppServiceProvider::class, |         App\Providers\AppServiceProvider::class, | ||||||
|         App\Providers\AuthServiceProvider::class, |         App\Providers\AuthServiceProvider::class, | ||||||
|         // App\Providers\BroadcastServiceProvider::class,
 |  | ||||||
|         App\Providers\EventServiceProvider::class, |         App\Providers\EventServiceProvider::class, | ||||||
|         App\Providers\HorizonServiceProvider::class, |         App\Providers\HorizonServiceProvider::class, | ||||||
|         App\Providers\RouteServiceProvider::class, |         App\Providers\RouteServiceProvider::class, | ||||||
|  | @ -176,8 +175,17 @@ return [ | ||||||
|         App\Tex\TexServiceProvider::class, |         App\Tex\TexServiceProvider::class, | ||||||
|         App\Dav\ServiceProvider::class, |         App\Dav\ServiceProvider::class, | ||||||
|         App\Setting\SettingServiceProvider::class, |         App\Setting\SettingServiceProvider::class, | ||||||
|         App\Dashboard\DashboardServiceProvider::class, |         // App\Dashboard\DashboardServiceProvider::class,
 | ||||||
|         App\Providers\PluginServiceProvider::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, | ||||||
|  |         Modules\Auth\AuthServiceProvider::class, | ||||||
|  |         Modules\Form\FormServiceProvider::class, | ||||||
|  |         Modules\Fileshare\FileshareServiceProvider::class, | ||||||
|     ], |     ], | ||||||
| 
 | 
 | ||||||
|     /* |     /* | ||||||
|  | @ -228,6 +236,7 @@ return [ | ||||||
|         'URL' => Illuminate\Support\Facades\URL::class, |         'URL' => Illuminate\Support\Facades\URL::class, | ||||||
|         'Validator' => Illuminate\Support\Facades\Validator::class, |         'Validator' => Illuminate\Support\Facades\Validator::class, | ||||||
|         'View' => Illuminate\Support\Facades\View::class, |         'View' => Illuminate\Support\Facades\View::class, | ||||||
|  |         'Vite' => Illuminate\Support\Facades\Vite::class, | ||||||
|         'Inertia' => \Inertia\Inertia::class, |         'Inertia' => \Inertia\Inertia::class, | ||||||
|     ], |     ], | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | return [ | ||||||
|  |     'email' => env('USER_EMAIL', 'admin@example.com'), | ||||||
|  |     'password' => env('USER_PASSWORD', 'admin'), | ||||||
|  | ]; | ||||||
|  | @ -0,0 +1,160 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | return [ | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Class Namespace | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | This value sets the root class namespace for Livewire component classes in | ||||||
|  |     | your application. This value will change where component auto-discovery | ||||||
|  |     | finds components. It's also referenced by the file creation commands. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'class_namespace' => 'App\\View', | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | View Path | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | This value is used to specify where Livewire component Blade templates are | ||||||
|  |     | stored when running file creation commands like `artisan make:livewire`. | ||||||
|  |     | It is also used if you choose to omit a component's render() method. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'view_path' => resource_path('views/livewire'), | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Layout | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | The view that will be used as the layout when rendering a single component | ||||||
|  |     | as an entire page via `Route::get('/post/create', CreatePost::class);`. | ||||||
|  |     | In this case, the view returned by CreatePost will render into $slot. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'layout' => 'components.layouts.app', | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Lazy Loading Placeholder | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Livewire allows you to lazy load components that would otherwise slow down | ||||||
|  |     | the initial page load. Every component can have a custom placeholder or | ||||||
|  |     | you can define the default placeholder view for all components below. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'lazy_placeholder' => null, | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Temporary File Uploads | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | Livewire handles file uploads by storing uploads in a temporary directory | ||||||
|  |     | before the file is stored permanently. All file uploads are directed to | ||||||
|  |     | a global endpoint for temporary storage. You may configure this below: | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'temporary_file_upload' => [ | ||||||
|  |         'disk' => null,        // Example: 'local', 's3'              | Default: 'default'
 | ||||||
|  |         'rules' => null,       // Example: ['file', 'mimes:png,jpg']  | Default: ['required', 'file', 'max:12288'] (12MB)
 | ||||||
|  |         'directory' => null,   // Example: 'tmp'                      | Default: 'livewire-tmp'
 | ||||||
|  |         'middleware' => null,  // Example: 'throttle:5,1'             | Default: 'throttle:60,1'
 | ||||||
|  |         'preview_mimes' => [   // Supported file types for temporary pre-signed file URLs...
 | ||||||
|  |             'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', | ||||||
|  |             'mov', 'avi', 'wmv', 'mp3', 'm4a', | ||||||
|  |             'jpg', 'jpeg', 'mpga', 'webp', 'wma', | ||||||
|  |         ], | ||||||
|  |         'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
 | ||||||
|  |         'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
 | ||||||
|  |     ], | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Render On Redirect | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | This value determines if Livewire will run a component's `render()` method | ||||||
|  |     | after a redirect has been triggered using something like `redirect(...)` | ||||||
|  |     | Setting this to true will render the view once more before redirecting | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'render_on_redirect' => false, | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Eloquent Model Binding | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | Previous versions of Livewire supported binding directly to eloquent model | ||||||
|  |     | properties using wire:model by default. However, this behavior has been | ||||||
|  |     | deemed too "magical" and has therefore been put under a feature flag. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'legacy_model_binding' => false, | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Auto-inject Frontend Assets | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | By default, Livewire automatically injects its JavaScript and CSS into the | ||||||
|  |     | <head> and <body> of pages containing Livewire components. By disabling | ||||||
|  |     | this behavior, you need to use @livewireStyles and @livewireScripts. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'inject_assets' => true, | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Navigate (SPA mode) | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | By adding `wire:navigate` to links in your Livewire application, Livewire | ||||||
|  |     | will prevent the default link handling and instead request those pages | ||||||
|  |     | via AJAX, creating an SPA-like effect. Configure this behavior here. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'navigate' => [ | ||||||
|  |         'show_progress_bar' => true, | ||||||
|  |         'progress_bar_color' => '#2299dd', | ||||||
|  |     ], | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | HTML Morph Markers | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | Livewire intelligently "morphs" existing HTML into the newly rendered HTML | ||||||
|  |     | after each update. To make this process more reliable, Livewire injects | ||||||
|  |     | "markers" into the rendered Blade surrounding @if, @class & @foreach. | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'inject_morph_markers' => true, | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | Pagination Theme | ||||||
|  |     |--------------------------------------------------------------------------- | ||||||
|  |     | | ||||||
|  |     | When enabling Livewire's pagination feature by using the `WithPagination` | ||||||
|  |     | trait, Livewire will use Tailwind templates to render pagination views | ||||||
|  |     | on the page. If you want Bootstrap CSS, you can specify: "bootstrap" | ||||||
|  |     | | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     'pagination_theme' => 'tailwind', | ||||||
|  | ]; | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Database\Factories\Member; | ||||||
|  | 
 | ||||||
|  | use App\Member\BankAccount; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\Factory; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @extends Factory<BankAccount> | ||||||
|  |  */ | ||||||
|  | class BankAccountFactory extends Factory | ||||||
|  | { | ||||||
|  |     protected $model = BankAccount::class; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Define the model's default state. | ||||||
|  |      * | ||||||
|  |      * @return array<string, mixed> | ||||||
|  |      */ | ||||||
|  |     public function definition(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'bank_name' => $this->faker->name(), | ||||||
|  |             'bic' => $this->faker->swiftBicNumber(), | ||||||
|  |             'iban' => $this->faker->iban('DE'), | ||||||
|  |             'blz' => $this->faker->name(), | ||||||
|  |             'person' => $this->faker->name(), | ||||||
|  |             'account_number' => $this->faker->name(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function inNami(int $namiId): self | ||||||
|  |     { | ||||||
|  |         return $this->state(['nami_id' => $namiId]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -93,6 +93,13 @@ class MemberFactory extends Factory | ||||||
|         return $this->state(['nami_id' => $namiId]); |         return $this->state(['nami_id' => $namiId]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function withBankAccount(BankAccountFactory $factory): self | ||||||
|  |     { | ||||||
|  |         return $this->afterCreating(function ($member) use ($factory) { | ||||||
|  |             $member->bankAccount->update($factory->make()->toArray()); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function sameFamilyAs(Member $member): self |     public function sameFamilyAs(Member $member): self | ||||||
|     { |     { | ||||||
|         return $this->state([ |         return $this->state([ | ||||||
|  |  | ||||||
|  | @ -27,4 +27,9 @@ class UserFactory extends Factory | ||||||
|             'lastname' => $this->faker->lastName, |             'lastname' => $this->faker->lastName, | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function loginData(string $email, string $password): self | ||||||
|  |     { | ||||||
|  |         return $this->state(['email' => $email, 'password' => Hash::make($password)]); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | <?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::create('bank_accounts', function (Blueprint $table) { | ||||||
|  |             $table->unsignedBigInteger('member_id')->primary(); | ||||||
|  |             $table->unsignedBigInteger('nami_id')->nullable(); | ||||||
|  |             $table->string('iban')->nullable(); | ||||||
|  |             $table->string('bic')->nullable(); | ||||||
|  |             $table->string('blz')->nullable(); | ||||||
|  |             $table->string('bank_name')->nullable(); | ||||||
|  |             $table->string('person')->nullable(); | ||||||
|  |             $table->string('account_number')->nullable(); | ||||||
|  |             $table->timestamps(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::dropIfExists('bank_accounts'); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | @ -16,9 +16,9 @@ class UserSeeder extends Seeder | ||||||
|     public function run() |     public function run() | ||||||
|     { |     { | ||||||
|         User::create([ |         User::create([ | ||||||
|             'email' => env('USER_EMAIL', 'admin@example.com'), |             'email' => config('init.email'), | ||||||
|             'email_verified_at' => now(), |             'email_verified_at' => now(), | ||||||
|             'password' => Hash::make(env('USER_PASSWORD', 'admin')), |             'password' => Hash::make(config('init.password')), | ||||||
|             'name' => 'Adrema Benutzer', |             'name' => 'Adrema Benutzer', | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -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,35 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Modules\Auth; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Routing\Router; | ||||||
|  | use Illuminate\Support\ServiceProvider; | ||||||
|  | use Modules\Auth\Components\LoginForm; | ||||||
|  | use Modules\Auth\Components\PasswordReset; | ||||||
|  | use Modules\Auth\Components\PasswordResetConfirm; | ||||||
|  | 
 | ||||||
|  | class AuthServiceProvider extends ServiceProvider | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Register services. | ||||||
|  |      * | ||||||
|  |      * @return void | ||||||
|  |      */ | ||||||
|  |     public function register() | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Bootstrap services. | ||||||
|  |      * | ||||||
|  |      * @return void | ||||||
|  |      */ | ||||||
|  |     public function boot() | ||||||
|  |     { | ||||||
|  |         app(Router::class)->middleware(['web', 'guest'])->group(function ($router) { | ||||||
|  |             $router->get('/login', LoginForm::class)->name('login'); | ||||||
|  |             $router->get('/password/reset', PasswordReset::class)->name('password.request'); | ||||||
|  |             $router->get('/password/reset/{token}', PasswordResetConfirm::class)->name('password.reset'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Modules\Auth\Components; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Foundation\Auth\AuthenticatesUsers; | ||||||
|  | use Illuminate\Foundation\Auth\ThrottlesLogins; | ||||||
|  | use Illuminate\Http\Request; | ||||||
|  | use Illuminate\Validation\ValidationException; | ||||||
|  | use Livewire\Attributes\Layout; | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class LoginForm extends Component | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     use AuthenticatesUsers; | ||||||
|  |     use ThrottlesLogins; | ||||||
|  | 
 | ||||||
|  |     public string $email = ''; | ||||||
|  |     public string $password = ''; | ||||||
|  | 
 | ||||||
|  |     public function validateLogin(Request $request) | ||||||
|  |     { | ||||||
|  |         $this->validate([ | ||||||
|  |             'email' => 'required|max:255|string|email', | ||||||
|  |             'password' => 'required|string', | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function credentials(Request $request) | ||||||
|  |     { | ||||||
|  |         return ['email' => $this->email, 'password' => $this->password]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function sendLoginResponse(Request $request) | ||||||
|  |     { | ||||||
|  |         return redirect()->intended('/'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function submit() | ||||||
|  |     { | ||||||
|  |         return $this->login(request()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[Layout('components.layouts.full')]
 | ||||||
|  |     public function render(): string | ||||||
|  |     { | ||||||
|  |         return <<<'HTML' | ||||||
|  |             <x-page::full heading="Login" title="Login"> | ||||||
|  |                 <form wire:submit="submit"> | ||||||
|  |                     <div class="grid gap-5"> | ||||||
|  |                         <x-form::text name="email" wire:model="email" label="E-Mail-Adresse"></x-form::text> | ||||||
|  |                         <x-form::text name="password" wire:model="password" type="password" label="Passwort"></x-form::text> | ||||||
|  |                         <x-ui::button type="submit">Login</x-ui::button> | ||||||
|  |                         <div class="flex justify-center"> | ||||||
|  |                             <a href="{{route('password.request')}}" class="text-gray-500 text-sm hover:text-gray-300">Passwort vergessen?</a> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </form> | ||||||
|  |             </x-page::full> | ||||||
|  |         HTML; | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue