Compare commits
18 Commits
dbcebbb2c4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| afc91545a9 | |||
| 12d98b4d7e | |||
| 6c7fe56579 | |||
| e09987f5a8 | |||
| 5c514e9ff5 | |||
| bc60461dac | |||
| 6848fbd95f | |||
| 012ebb6538 | |||
| c1ef1d71ad | |||
| aebb2f9aaa | |||
| cfc7c7eee2 | |||
| 1b9384dad1 | |||
| fed54514c8 | |||
| 12f05ceb09 | |||
| 5f56ef94a6 | |||
| 79bb186234 | |||
| a012c16425 | |||
| a8205a4f96 |
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class CreateTenantAction
|
||||||
|
{
|
||||||
|
public function __construct(private CreateTenantRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): CreateTenantResponse
|
||||||
|
{
|
||||||
|
$response = new CreateTenantResponse();
|
||||||
|
|
||||||
|
$tenant = Tenant::create([
|
||||||
|
'name' => $this->request->name,
|
||||||
|
'slug' => $this->request->slug,
|
||||||
|
'url' => $this->request->url,
|
||||||
|
'email' => '',
|
||||||
|
'email_finance' => '',
|
||||||
|
'account_name' => '',
|
||||||
|
'account_iban' => '',
|
||||||
|
'account_bic' => '',
|
||||||
|
'city' => '',
|
||||||
|
'postcode' => '',
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Stamm wurde angelegt.';
|
||||||
|
$response->slug = $tenant->slug;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||||
|
|
||||||
|
class CreateTenantRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $name,
|
||||||
|
public string $slug,
|
||||||
|
public string $url,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\CreateTenant;
|
||||||
|
|
||||||
|
class CreateTenantResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
public ?string $slug = null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\ToggleUserActive;
|
||||||
|
|
||||||
|
class ToggleUserActiveAction
|
||||||
|
{
|
||||||
|
public function __construct(private ToggleUserActiveRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): ToggleUserActiveResponse
|
||||||
|
{
|
||||||
|
$response = new ToggleUserActiveResponse();
|
||||||
|
|
||||||
|
if ($this->request->currentUserId === $this->request->user->id) {
|
||||||
|
$response->message = 'Du kannst dich nicht selbst deaktivieren.';
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->user->update(['active' => !$this->request->user->active]);
|
||||||
|
|
||||||
|
$status = $this->request->user->active ? 'aktiviert' : 'deaktiviert';
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Benutzer*in wurde ' . $status . '.';
|
||||||
|
$response->active = $this->request->user->active;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\ToggleUserActive;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class ToggleUserActiveRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public User $user,
|
||||||
|
public int $currentUserId,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\ToggleUserActive;
|
||||||
|
|
||||||
|
class ToggleUserActiveResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
public ?bool $active = null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantContact;
|
||||||
|
|
||||||
|
class UpdateTenantContactAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateTenantContactRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateTenantContactResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateTenantContactResponse();
|
||||||
|
|
||||||
|
$this->request->tenant->update([
|
||||||
|
'email' => $this->request->email,
|
||||||
|
'email_finance' => $this->request->emailFinance,
|
||||||
|
'postcode' => $this->request->postcode,
|
||||||
|
'city' => $this->request->city,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Kontaktdaten wurden gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantContact;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class UpdateTenantContactRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public Tenant $tenant,
|
||||||
|
public string $email,
|
||||||
|
public string $emailFinance,
|
||||||
|
public string $postcode,
|
||||||
|
public string $city,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantContact;
|
||||||
|
|
||||||
|
class UpdateTenantContactResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantGdpr;
|
||||||
|
|
||||||
|
class UpdateTenantGdprAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateTenantGdprRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateTenantGdprResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateTenantGdprResponse();
|
||||||
|
|
||||||
|
$this->request->tenant->update([
|
||||||
|
'gdpr_text' => $this->request->gdprText,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Datenschutzerklärung wurde gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantGdpr;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class UpdateTenantGdprRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public Tenant $tenant,
|
||||||
|
public string $gdprText,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantGdpr;
|
||||||
|
|
||||||
|
class UpdateTenantGdprResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||||
|
|
||||||
|
class UpdateTenantGeneralAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateTenantGeneralRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateTenantGeneralResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateTenantGeneralResponse();
|
||||||
|
|
||||||
|
$this->request->tenant->update([
|
||||||
|
'name' => $this->request->name,
|
||||||
|
'slug' => $this->request->slug,
|
||||||
|
'url' => $this->request->url,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Allgemeine Daten wurden gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class UpdateTenantGeneralRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public Tenant $tenant,
|
||||||
|
public string $name,
|
||||||
|
public string $slug,
|
||||||
|
public string $url,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantGeneral;
|
||||||
|
|
||||||
|
class UpdateTenantGeneralResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantImpress;
|
||||||
|
|
||||||
|
class UpdateTenantImpressAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateTenantImpressRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateTenantImpressResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateTenantImpressResponse();
|
||||||
|
|
||||||
|
$this->request->tenant->update([
|
||||||
|
'impress_text' => $this->request->impressText,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Impressum wurde gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantImpress;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class UpdateTenantImpressRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public Tenant $tenant,
|
||||||
|
public string $impressText,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantImpress;
|
||||||
|
|
||||||
|
class UpdateTenantImpressResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||||
|
|
||||||
|
class UpdateTenantPaymentAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateTenantPaymentRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateTenantPaymentResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateTenantPaymentResponse();
|
||||||
|
|
||||||
|
$this->request->tenant->update([
|
||||||
|
'account_iban' => $this->request->accountIban,
|
||||||
|
'account_bic' => $this->request->accountBic,
|
||||||
|
'account_name' => $this->request->accountName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Bezahldaten wurden gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class UpdateTenantPaymentRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public Tenant $tenant,
|
||||||
|
public string $accountIban,
|
||||||
|
public string $accountBic,
|
||||||
|
public string $accountName,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
|
||||||
|
|
||||||
|
class UpdateTenantPaymentResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateUser;
|
||||||
|
|
||||||
|
class UpdateUserAction
|
||||||
|
{
|
||||||
|
public function __construct(private UpdateUserRequest $request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): UpdateUserResponse
|
||||||
|
{
|
||||||
|
$response = new UpdateUserResponse();
|
||||||
|
|
||||||
|
$allowedFields = [
|
||||||
|
'firstname', 'lastname', 'nickname', 'email', 'phone', 'birthday',
|
||||||
|
'membership_id', 'address_1', 'address_2', 'postcode', 'city',
|
||||||
|
'eating_habits', 'swimming_permission', 'first_aid_permission',
|
||||||
|
'bank_account_owner', 'bank_account_iban',
|
||||||
|
'medications', 'allergies', 'intolerances',
|
||||||
|
'user_role_local_group',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->request->isLvTenant) {
|
||||||
|
$allowedFields[] = 'local_group';
|
||||||
|
if (!$this->request->isOwnUser) {
|
||||||
|
$allowedFields[] = 'user_role_main';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array_intersect_key($this->request->data, array_flip($allowedFields));
|
||||||
|
$this->request->user->update($data);
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
$response->message = 'Benutzerdaten wurden gespeichert.';
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateUser;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class UpdateUserRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public User $user,
|
||||||
|
public array $data,
|
||||||
|
public bool $isOwnUser,
|
||||||
|
public bool $isLvTenant,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Actions\UpdateUser;
|
||||||
|
|
||||||
|
class UpdateUserResponse
|
||||||
|
{
|
||||||
|
public bool $success = false;
|
||||||
|
public string $message = '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class AdminDashboardController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Admin/Dashboard', []);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantContactGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'email' => $tenant->email,
|
||||||
|
'email_finance' => $tenant->email_finance,
|
||||||
|
'postcode' => $tenant->postcode,
|
||||||
|
'city' => $tenant->city,
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/contact',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantContactUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
$action = new UpdateTenantContactAction(new UpdateTenantContactRequest(
|
||||||
|
tenant: $tenant,
|
||||||
|
email: $request->input('email'),
|
||||||
|
emailFinance: $request->input('email_finance'),
|
||||||
|
postcode: $request->input('postcode'),
|
||||||
|
city: $request->input('city'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantGdprGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'gdpr_text' => $tenant->gdpr_text ?? '',
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/gdpr',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantGdprUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
$action = new UpdateTenantGdprAction(new UpdateTenantGdprRequest(
|
||||||
|
tenant: $tenant,
|
||||||
|
gdprText: $request->input('gdpr_text'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantImpressGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'impress_text' => $tenant->impress_text ?? '',
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/impress',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantImpressUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
$action = new UpdateTenantImpressAction(new UpdateTenantImpressRequest(
|
||||||
|
tenant: $tenant,
|
||||||
|
impressText: $request->input('impress_text'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantPaymentGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'account_iban' => $tenant->account_iban,
|
||||||
|
'account_bic' => $tenant->account_bic,
|
||||||
|
'account_name' => $tenant->account_name,
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/payment',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ManagedTenantPaymentUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
$action = new UpdateTenantPaymentAction(new UpdateTenantPaymentRequest(
|
||||||
|
tenant: $tenant,
|
||||||
|
accountIban: $request->input('account_iban'),
|
||||||
|
accountBic: $request->input('account_bic'),
|
||||||
|
accountName: $request->input('account_name'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantContactGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'email' => $this->tenant->email,
|
||||||
|
'email_finance' => $this->tenant->email_finance,
|
||||||
|
'postcode' => $this->tenant->postcode,
|
||||||
|
'city' => $this->tenant->city,
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenant/contact',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantContact\UpdateTenantContactRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantContactUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$action = new UpdateTenantContactAction(new UpdateTenantContactRequest(
|
||||||
|
tenant: $this->tenant,
|
||||||
|
email: $request->input('email'),
|
||||||
|
emailFinance: $request->input('email_finance'),
|
||||||
|
postcode: $request->input('postcode'),
|
||||||
|
city: $request->input('city'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\CreateTenant\CreateTenantAction;
|
||||||
|
use App\Domains\Admin\Actions\CreateTenant\CreateTenantRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantCreateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$action = new CreateTenantAction(new CreateTenantRequest(
|
||||||
|
name: $request->input('name'),
|
||||||
|
slug: $request->input('slug'),
|
||||||
|
url: $request->input('url'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
'slug' => $response->slug,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class TenantEditPageController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): Response
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
$inertiaProvider = new InertiaProvider('Admin/TenantEdit', [
|
||||||
|
'tenant' => [
|
||||||
|
'name' => $tenant->name,
|
||||||
|
'slug' => $tenant->slug,
|
||||||
|
'url' => $tenant->url,
|
||||||
|
'is_active_local_group' => $tenant->is_active_local_group,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantGdprGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'gdpr_text' => $this->tenant->gdpr_text ?? '',
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenant/gdpr',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantGdpr\UpdateTenantGdprRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantGdprUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$action = new UpdateTenantGdprAction(new UpdateTenantGdprRequest(
|
||||||
|
tenant: $this->tenant,
|
||||||
|
gdprText: $request->input('gdpr_text'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantGeneralGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'name' => $tenant->name,
|
||||||
|
'slug' => $tenant->slug,
|
||||||
|
'url' => $tenant->url,
|
||||||
|
'is_active_local_group' => $tenant->is_active_local_group,
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenants/' . $slug . '/general',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantGeneral\UpdateTenantGeneralAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantGeneral\UpdateTenantGeneralRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantGeneralUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $slug, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenant = $this->adminTenants->findBySlug($slug);
|
||||||
|
|
||||||
|
$action = new UpdateTenantGeneralAction(new UpdateTenantGeneralRequest(
|
||||||
|
tenant: $tenant,
|
||||||
|
name: $request->input('name'),
|
||||||
|
slug: $request->input('slug'),
|
||||||
|
url: $request->input('url'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantImpressGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'impress_text' => $this->tenant->impress_text ?? '',
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenant/impress',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantImpress\UpdateTenantImpressRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantImpressUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$action = new UpdateTenantImpressAction(new UpdateTenantImpressRequest(
|
||||||
|
tenant: $this->tenant,
|
||||||
|
impressText: $request->input('impress_text'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantListApiController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenants = $this->adminTenants->getActiveTenants();
|
||||||
|
|
||||||
|
$mapped = $tenants->map(function ($tenant) {
|
||||||
|
return [
|
||||||
|
'name' => $tenant->name,
|
||||||
|
'slug' => $tenant->slug,
|
||||||
|
'url' => $tenant->url,
|
||||||
|
'is_active_local_group' => $tenant->is_active_local_group,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(['tenants' => $mapped]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class TenantListPageController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Admin/TenantList', []);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class TenantPageController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Admin/TenantData', [
|
||||||
|
'tenant' => [
|
||||||
|
'name' => $this->tenant->name,
|
||||||
|
'slug' => $this->tenant->slug,
|
||||||
|
'url' => $this->tenant->url,
|
||||||
|
'is_active_local_group' => $this->tenant->is_active_local_group,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantPaymentGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'account_iban' => $this->tenant->account_iban,
|
||||||
|
'account_bic' => $this->tenant->account_bic,
|
||||||
|
'account_name' => $this->tenant->account_name,
|
||||||
|
'saveEndpoint' => '/api/v1/admin/tenant/payment',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TenantPaymentUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$action = new UpdateTenantPaymentAction(new UpdateTenantPaymentRequest(
|
||||||
|
tenant: $this->tenant,
|
||||||
|
accountIban: $request->input('account_iban'),
|
||||||
|
accountBic: $request->input('account_bic'),
|
||||||
|
accountName: $request->input('account_name'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserDetailGetController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->adminUsers->findById($id);
|
||||||
|
|
||||||
|
$userData = $user->toArray();
|
||||||
|
unset($userData['password'], $userData['remember_token'], $userData['activation_token'], $userData['activation_token_expires_at']);
|
||||||
|
|
||||||
|
$tenantNames = $this->adminTenants->getTenantNames();
|
||||||
|
$userData['nicename'] = $user->getNicename();
|
||||||
|
$userData['fullname'] = $user->getFullName();
|
||||||
|
$userData['local_group_name'] = $tenantNames[$user->local_group] ?? $user->local_group;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'user' => $userData,
|
||||||
|
'isOwnUser' => auth()->id() === $user->id,
|
||||||
|
'isLvTenant' => $this->tenant->slug === 'lv',
|
||||||
|
'userRoles' => UserRole::all()->map(fn($role) => ['slug' => $role->slug, 'name' => $role->name]),
|
||||||
|
'localGroups' => $this->adminTenants->getActiveLocalGroups()->map(fn($t) => ['slug' => $t->slug, 'name' => $t->name]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserListApiController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tenantNames = $this->adminTenants->getTenantNames();
|
||||||
|
$users = $this->adminUsers->getListForTenant($this->tenant->slug);
|
||||||
|
|
||||||
|
$mapped = $users->map(function ($user) use ($tenantNames) {
|
||||||
|
return [
|
||||||
|
'id' => $user->id,
|
||||||
|
'firstname' => $user->firstname,
|
||||||
|
'lastname' => $user->lastname,
|
||||||
|
'nickname' => $user->nickname,
|
||||||
|
'local_group' => $user->local_group,
|
||||||
|
'local_group_name' => $tenantNames[$user->local_group] ?? $user->local_group,
|
||||||
|
'active' => $user->active,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(['users' => $mapped]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class UserListPageController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Admin/UserList', [
|
||||||
|
'isLvTenant' => $this->tenant->slug === 'lv',
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserResetPasswordController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->adminUsers->findById($id);
|
||||||
|
|
||||||
|
if (!$user->email) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Benutzer*in hat keine E-Mail-Adresse hinterlegt.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = new GenerateActivationTokenCommand($user);
|
||||||
|
$command->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Passwort-Reset-Mail wurde gesendet.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\ToggleUserActive\ToggleUserActiveAction;
|
||||||
|
use App\Domains\Admin\Actions\ToggleUserActive\ToggleUserActiveRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserToggleActiveController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->adminUsers->findById($id);
|
||||||
|
|
||||||
|
$action = new ToggleUserActiveAction(new ToggleUserActiveRequest(
|
||||||
|
user: $user,
|
||||||
|
currentUserId: auth()->id(),
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
'active' => $response->active,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Actions\UpdateUser\UpdateUserAction;
|
||||||
|
use App\Domains\Admin\Actions\UpdateUser\UpdateUserRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserUpdateController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(int $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->adminUsers->findById($id);
|
||||||
|
|
||||||
|
$action = new UpdateUserAction(new UpdateUserRequest(
|
||||||
|
user: $user,
|
||||||
|
data: $request->all(),
|
||||||
|
isOwnUser: auth()->id() === $user->id,
|
||||||
|
isLvTenant: $this->tenant->slug === 'lv',
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = $action->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => $response->success ? 'success' : 'error',
|
||||||
|
'message' => $response->message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Support\Collection as SupportCollection;
|
||||||
|
|
||||||
|
class AdminTenantRepository
|
||||||
|
{
|
||||||
|
public function findBySlug(string $slug): Tenant
|
||||||
|
{
|
||||||
|
return Tenant::where('slug', $slug)->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveTenants(): Collection
|
||||||
|
{
|
||||||
|
return Tenant::where('has_active_instance', true)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTenantNames(): SupportCollection
|
||||||
|
{
|
||||||
|
return Tenant::pluck('name', 'slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveLocalGroups(): Collection
|
||||||
|
{
|
||||||
|
return Tenant::where('is_active_local_group', true)->get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Admin\Repositories;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
|
class AdminUserRepository
|
||||||
|
{
|
||||||
|
public function findById(int $id): User
|
||||||
|
{
|
||||||
|
return User::findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getListForTenant(string $tenantSlug): Collection
|
||||||
|
{
|
||||||
|
$query = User::query();
|
||||||
|
|
||||||
|
if ($tenantSlug !== 'lv') {
|
||||||
|
$query->where('local_group', $tenantSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->orderBy('lastname')->orderBy('firstname')->get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Admin\Controllers\UserDetailGetController;
|
||||||
|
use App\Domains\Admin\Controllers\UserListApiController;
|
||||||
|
use App\Domains\Admin\Controllers\UserResetPasswordController;
|
||||||
|
use App\Domains\Admin\Controllers\UserToggleActiveController;
|
||||||
|
use App\Domains\Admin\Controllers\UserUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantContactGetController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantContactUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantGdprGetController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantGdprUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantImpressGetController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantImpressUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantPaymentGetController;
|
||||||
|
use App\Domains\Admin\Controllers\ManagedTenantPaymentUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantContactGetController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantContactUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantCreateController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantGdprGetController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantGdprUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantGeneralGetController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantGeneralUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantImpressGetController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantImpressUpdateController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantListApiController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantPaymentGetController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantPaymentUpdateController;
|
||||||
|
use App\Middleware\AdminRoleMiddleware;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use App\Middleware\LvOnlyMiddleware;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->group(function () {
|
||||||
|
Route::prefix('api/v1/admin/tenant')->group(function () {
|
||||||
|
Route::get('/contact', TenantContactGetController::class);
|
||||||
|
Route::post('/contact', TenantContactUpdateController::class);
|
||||||
|
Route::get('/payment', TenantPaymentGetController::class);
|
||||||
|
Route::post('/payment', TenantPaymentUpdateController::class);
|
||||||
|
Route::get('/impress', TenantImpressGetController::class);
|
||||||
|
Route::post('/impress', TenantImpressUpdateController::class);
|
||||||
|
Route::get('/gdpr', TenantGdprGetController::class);
|
||||||
|
Route::post('/gdpr', TenantGdprUpdateController::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::prefix('api/v1/admin/users')->group(function () {
|
||||||
|
Route::get('/list', UserListApiController::class);
|
||||||
|
Route::get('/{id}', UserDetailGetController::class);
|
||||||
|
Route::post('/{id}', UserUpdateController::class);
|
||||||
|
Route::post('/{id}/toggle-active', UserToggleActiveController::class);
|
||||||
|
Route::post('/{id}/reset-password', UserResetPasswordController::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||||
|
Route::prefix('api/v1/admin/tenants')->group(function () {
|
||||||
|
Route::get('/list', TenantListApiController::class);
|
||||||
|
Route::post('/create', TenantCreateController::class);
|
||||||
|
Route::prefix('/{slug}')->group(function () {
|
||||||
|
Route::get('/general', TenantGeneralGetController::class);
|
||||||
|
Route::post('/general', TenantGeneralUpdateController::class);
|
||||||
|
Route::get('/contact', ManagedTenantContactGetController::class);
|
||||||
|
Route::post('/contact', ManagedTenantContactUpdateController::class);
|
||||||
|
Route::get('/payment', ManagedTenantPaymentGetController::class);
|
||||||
|
Route::post('/payment', ManagedTenantPaymentUpdateController::class);
|
||||||
|
Route::get('/impress', ManagedTenantImpressGetController::class);
|
||||||
|
Route::post('/impress', ManagedTenantImpressUpdateController::class);
|
||||||
|
Route::get('/gdpr', ManagedTenantGdprGetController::class);
|
||||||
|
Route::post('/gdpr', ManagedTenantGdprUpdateController::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Admin\Controllers\AdminDashboardController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantEditPageController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantListPageController;
|
||||||
|
use App\Domains\Admin\Controllers\TenantPageController;
|
||||||
|
use App\Domains\Admin\Controllers\UserListPageController;
|
||||||
|
use App\Middleware\AdminRoleMiddleware;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use App\Middleware\LvOnlyMiddleware;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware([IdentifyTenant::class, 'auth', AdminRoleMiddleware::class])->group(function () {
|
||||||
|
Route::prefix('admin')->group(function () {
|
||||||
|
Route::get('/', AdminDashboardController::class);
|
||||||
|
Route::get('/tenant', TenantPageController::class);
|
||||||
|
Route::get('/users', UserListPageController::class);
|
||||||
|
|
||||||
|
Route::middleware(LvOnlyMiddleware::class)->group(function () {
|
||||||
|
Route::get('/tenants', TenantListPageController::class);
|
||||||
|
Route::get('/tenants/{slug}', TenantEditPageController::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup>
|
||||||
|
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminAppLayout title="Administration">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<h2>Administration</h2>
|
||||||
|
</shadowed-box>
|
||||||
|
</AdminAppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const editing = ref(false)
|
||||||
|
const form = ref({
|
||||||
|
email: props.data.email ?? '',
|
||||||
|
email_finance: props.data.email_finance ?? '',
|
||||||
|
postcode: props.data.postcode ?? '',
|
||||||
|
city: props.data.city ?? '',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/contact'
|
||||||
|
const response = await request(saveUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
editing.value = false
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="!editing">
|
||||||
|
<table class="data-table">
|
||||||
|
<tr><th>Email:</th><td>{{ form.email }}</td></tr>
|
||||||
|
<tr><th>Email Schatzmeister*in:</th><td>{{ form.email_finance }}</td></tr>
|
||||||
|
<tr><th>Postleitzahl:</th><td>{{ form.postcode }}</td></tr>
|
||||||
|
<tr><th>Ort:</th><td>{{ form.city }}</td></tr>
|
||||||
|
</table>
|
||||||
|
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<table class="data-table">
|
||||||
|
<tr>
|
||||||
|
<th>Email:</th>
|
||||||
|
<td><input type="email" v-model="form.email" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Email Schatzmeister*in:</th>
|
||||||
|
<td><input type="email" v-model="form.email_finance" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Postleitzahl:</th>
|
||||||
|
<td><input type="text" v-model="form.postcode" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Ort:</th>
|
||||||
|
<td><input type="text" v-model="form.city" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 200px;
|
||||||
|
color: #374151;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit, .btn-save, .btn-cancel {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||||
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
import gdprTemplate from "../../../../../resources/templates/gdpr-template.html?raw";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const content = ref(props.data.gdpr_text ?? '')
|
||||||
|
|
||||||
|
function autoGenerate() {
|
||||||
|
if (content.value && content.value.trim() !== '') {
|
||||||
|
toast.error('Der Editor ist nicht leer. Bitte leere den Inhalt zuerst, um die Vorlage zu verwenden.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||||
|
content.value = gdprTemplate.replace('[Datum]', today)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/gdpr'
|
||||||
|
const response = await request(saveUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: { gdpr_text: content.value },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<TextEditor v-model="content" />
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
<button class="btn-generate" @click="autoGenerate">Auto-generieren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save, .btn-generate {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-generate {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-generate:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const editing = ref(false)
|
||||||
|
const form = ref({
|
||||||
|
name: props.data.name ?? '',
|
||||||
|
slug: props.data.slug ?? '',
|
||||||
|
url: props.data.url ?? '',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenants/' + props.data.slug + '/general'
|
||||||
|
const response = await request(saveUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
editing.value = false
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="!editing">
|
||||||
|
<table class="data-table">
|
||||||
|
<tr><th>Name:</th><td>{{ form.name }}</td></tr>
|
||||||
|
<tr><th>Slug:</th><td>{{ form.slug }}</td></tr>
|
||||||
|
<tr><th>URL:</th><td>{{ form.url }}</td></tr>
|
||||||
|
<tr><th>Status:</th><td>
|
||||||
|
<span class="badge-active">Aktiv</span>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<table class="data-table">
|
||||||
|
<tr>
|
||||||
|
<th>Name:</th>
|
||||||
|
<td><input type="text" v-model="form.name" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Slug:</th>
|
||||||
|
<td><input type="text" v-model="form.slug" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>URL:</th>
|
||||||
|
<td><input type="text" v-model="form.url" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr><th>Status:</th><td>
|
||||||
|
<span class="badge-active">Aktiv</span>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 200px;
|
||||||
|
color: #374151;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #166534;
|
||||||
|
background-color: #dcfce7;
|
||||||
|
border: 1px solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit, .btn-save, .btn-cancel {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||||
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const content = ref(props.data.impress_text ?? '')
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/impress'
|
||||||
|
const response = await request(saveUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: { impress_text: content.value },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<TextEditor v-model="content" />
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.btn-save {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const editing = ref(false)
|
||||||
|
const form = ref({
|
||||||
|
account_iban: props.data.account_iban ?? '',
|
||||||
|
account_bic: props.data.account_bic ?? '',
|
||||||
|
account_name: props.data.account_name ?? '',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const saveUrl = props.data.saveEndpoint ?? '/api/v1/admin/tenant/payment'
|
||||||
|
const response = await request(saveUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
editing.value = false
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="!editing">
|
||||||
|
<table class="data-table">
|
||||||
|
<tr><th>IBAN:</th><td>{{ form.account_iban }}</td></tr>
|
||||||
|
<tr><th>BIC:</th><td>{{ form.account_bic }}</td></tr>
|
||||||
|
<tr><th>Name Kontoinhaber:</th><td>{{ form.account_name }}</td></tr>
|
||||||
|
</table>
|
||||||
|
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<table class="data-table">
|
||||||
|
<tr>
|
||||||
|
<th>IBAN:</th>
|
||||||
|
<td><input type="text" v-model="form.account_iban" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>BIC:</th>
|
||||||
|
<td><input type="text" v-model="form.account_bic" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name Kontoinhaber:</th>
|
||||||
|
<td><input type="text" v-model="form.account_name" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 200px;
|
||||||
|
color: #374151;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit, .btn-save, .btn-cancel {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['updated', 'closed'])
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const editing = ref(false)
|
||||||
|
const user = props.data.user
|
||||||
|
const isOwnUser = props.data.isOwnUser
|
||||||
|
const isLvTenant = props.data.isLvTenant
|
||||||
|
const userRoles = props.data.userRoles
|
||||||
|
const localGroups = props.data.localGroups
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
firstname: user.firstname ?? '',
|
||||||
|
lastname: user.lastname ?? '',
|
||||||
|
nickname: user.nickname ?? '',
|
||||||
|
email: user.email ?? '',
|
||||||
|
phone: user.phone ?? '',
|
||||||
|
birthday: user.birthday ?? '',
|
||||||
|
membership_id: user.membership_id ?? '',
|
||||||
|
address_1: user.address_1 ?? '',
|
||||||
|
address_2: user.address_2 ?? '',
|
||||||
|
postcode: user.postcode ?? '',
|
||||||
|
city: user.city ?? '',
|
||||||
|
medications: user.medications ?? '',
|
||||||
|
allergies: user.allergies ?? '',
|
||||||
|
intolerances: user.intolerances ?? '',
|
||||||
|
bank_account_owner: user.bank_account_owner ?? '',
|
||||||
|
bank_account_iban: user.bank_account_iban ?? '',
|
||||||
|
user_role_local_group: user.user_role_local_group ?? '',
|
||||||
|
user_role_main: user.user_role_main ?? '',
|
||||||
|
local_group: user.local_group ?? '',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const response = await request('/api/v1/admin/users/' + user.id, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
editing.value = false
|
||||||
|
emit('updated')
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleActive() {
|
||||||
|
const response = await request('/api/v1/admin/users/' + user.id + '/toggle-active', {
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
emit('updated')
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetPassword() {
|
||||||
|
const response = await request('/api/v1/admin/users/' + user.id + '/reset-password', {
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoleName(slug) {
|
||||||
|
const role = userRoles.find(r => r.slug === slug)
|
||||||
|
return role ? role.name : slug
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupName(slug) {
|
||||||
|
const group = localGroups.find(g => g.slug === slug)
|
||||||
|
return group ? group.name : slug
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="detail-header">
|
||||||
|
<h3>{{ user.firstname }} {{ user.lastname }}</h3>
|
||||||
|
<div class="detail-actions">
|
||||||
|
<button v-if="!editing" class="btn-edit" @click="editing = true">Bearbeiten</button>
|
||||||
|
<button class="btn-reset" @click="resetPassword">Passwort zurücksetzen</button>
|
||||||
|
<button v-if="!isOwnUser" :class="user.active ? 'btn-deactivate' : 'btn-activate'" @click="toggleActive">
|
||||||
|
{{ user.active ? 'Deaktivieren' : 'Aktivieren' }}
|
||||||
|
</button>
|
||||||
|
<button class="btn-close" @click="emit('closed')">Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!editing">
|
||||||
|
<table class="data-table">
|
||||||
|
<tr><th>Anmeldename:</th><td>{{ user.username }}</td></tr>
|
||||||
|
<tr><th>Vorname:</th><td>{{ form.firstname }}</td></tr>
|
||||||
|
<tr><th>Nachname:</th><td>{{ form.lastname }}</td></tr>
|
||||||
|
<tr><th>Nickname:</th><td>{{ form.nickname }}</td></tr>
|
||||||
|
<tr><th>E-Mail:</th><td>{{ form.email }}</td></tr>
|
||||||
|
<tr><th>Telefon:</th><td>{{ form.phone }}</td></tr>
|
||||||
|
<tr><th>Geburtstag:</th><td>{{ form.birthday }}</td></tr>
|
||||||
|
<tr><th>Mitgliedsnummer:</th><td>{{ form.membership_id }}</td></tr>
|
||||||
|
<tr><th>Adresse 1:</th><td>{{ form.address_1 }}</td></tr>
|
||||||
|
<tr><th>Adresse 2:</th><td>{{ form.address_2 }}</td></tr>
|
||||||
|
<tr><th>PLZ:</th><td>{{ form.postcode }}</td></tr>
|
||||||
|
<tr><th>Ort:</th><td>{{ form.city }}</td></tr>
|
||||||
|
<tr><th>Medikamente:</th><td>{{ form.medications }}</td></tr>
|
||||||
|
<tr><th>Allergien:</th><td>{{ form.allergies }}</td></tr>
|
||||||
|
<tr><th>Unverträglichkeiten:</th><td>{{ form.intolerances }}</td></tr>
|
||||||
|
<tr><th>Kontoinhaber:</th><td>{{ form.bank_account_owner }}</td></tr>
|
||||||
|
<tr><th>IBAN:</th><td>{{ form.bank_account_iban }}</td></tr>
|
||||||
|
<tr><th>Stamm:</th><td>{{ getGroupName(form.local_group) }}</td></tr>
|
||||||
|
<tr><th>Rolle (Stamm):</th><td>{{ getRoleName(form.user_role_local_group) }}</td></tr>
|
||||||
|
<tr><th>Rolle (LV):</th><td>{{ getRoleName(form.user_role_main) }}</td></tr>
|
||||||
|
<tr><th>Status:</th><td>
|
||||||
|
<span :class="user.active ? 'badge-active' : 'badge-inactive'">
|
||||||
|
{{ user.active ? 'Aktiv' : 'Inaktiv' }}
|
||||||
|
</span>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<table class="data-table">
|
||||||
|
<tr><th>Anmeldename:</th><td>{{ user.username }}</td></tr>
|
||||||
|
<tr><th>Vorname:</th><td><input type="text" v-model="form.firstname" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Nachname:</th><td><input type="text" v-model="form.lastname" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Nickname:</th><td><input type="text" v-model="form.nickname" class="form-input" /></td></tr>
|
||||||
|
<tr><th>E-Mail:</th><td><input type="email" v-model="form.email" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Telefon:</th><td><input type="text" v-model="form.phone" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Geburtstag:</th><td><input type="date" v-model="form.birthday" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Mitgliedsnummer:</th><td><input type="text" v-model="form.membership_id" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Adresse 1:</th><td><input type="text" v-model="form.address_1" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Adresse 2:</th><td><input type="text" v-model="form.address_2" class="form-input" /></td></tr>
|
||||||
|
<tr><th>PLZ:</th><td><input type="text" v-model="form.postcode" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Ort:</th><td><input type="text" v-model="form.city" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Medikamente:</th><td><input type="text" v-model="form.medications" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Allergien:</th><td><input type="text" v-model="form.allergies" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Unverträglichkeiten:</th><td><input type="text" v-model="form.intolerances" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Kontoinhaber:</th><td><input type="text" v-model="form.bank_account_owner" class="form-input" /></td></tr>
|
||||||
|
<tr><th>IBAN:</th><td><input type="text" v-model="form.bank_account_iban" class="form-input" /></td></tr>
|
||||||
|
<tr><th>Stamm:</th><td>
|
||||||
|
<select v-if="isLvTenant" v-model="form.local_group" class="form-input">
|
||||||
|
<option v-for="group in localGroups" :key="group.slug" :value="group.slug">{{ group.name }}</option>
|
||||||
|
</select>
|
||||||
|
<span v-else>{{ getGroupName(form.local_group) }}</span>
|
||||||
|
</td></tr>
|
||||||
|
<tr><th>Rolle (Stamm):</th><td>
|
||||||
|
<select v-model="form.user_role_local_group" class="form-input">
|
||||||
|
<option v-for="role in userRoles" :key="role.slug" :value="role.slug">{{ role.name }}</option>
|
||||||
|
</select>
|
||||||
|
</td></tr>
|
||||||
|
<tr><th>Rolle (LV):</th><td>
|
||||||
|
<select v-if="isLvTenant && !isOwnUser" v-model="form.user_role_main" class="form-input">
|
||||||
|
<option v-for="role in userRoles" :key="role.slug" :value="role.slug">{{ role.name }}</option>
|
||||||
|
</select>
|
||||||
|
<span v-else>{{ getRoleName(form.user_role_main) }}</span>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="save">Speichern</button>
|
||||||
|
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 6px 12px;
|
||||||
|
width: 180px;
|
||||||
|
color: #374151;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #166534;
|
||||||
|
background-color: #dcfce7;
|
||||||
|
border: 1px solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-inactive {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #991b1b;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit, .btn-save, .btn-cancel, .btn-reset, .btn-deactivate, .btn-activate, .btn-close {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-reset {
|
||||||
|
background-color: #f59e0b;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-reset:hover {
|
||||||
|
background-color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-deactivate {
|
||||||
|
background-color: #ef4444;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-deactivate:hover {
|
||||||
|
background-color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-activate {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-activate:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
background-color: #6b7280;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close:hover {
|
||||||
|
background-color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<script setup>
|
||||||
|
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||||
|
import TenantContact from "./Partials/TenantContact.vue";
|
||||||
|
import TenantPayment from "./Partials/TenantPayment.vue";
|
||||||
|
import TenantImpress from "./Partials/TenantImpress.vue";
|
||||||
|
import TenantGdpr from "./Partials/TenantGdpr.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tenant: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: 'Kontaktdaten',
|
||||||
|
component: TenantContact,
|
||||||
|
endpoint: '/api/v1/admin/tenant/contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Bezahldaten',
|
||||||
|
component: TenantPayment,
|
||||||
|
endpoint: '/api/v1/admin/tenant/payment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Impressum',
|
||||||
|
component: TenantImpress,
|
||||||
|
endpoint: '/api/v1/admin/tenant/impress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Datenschutzerklärung',
|
||||||
|
component: TenantGdpr,
|
||||||
|
endpoint: '/api/v1/admin/tenant/gdpr',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminAppLayout :title="props.tenant.slug === 'lv' ? 'LV-Daten' : 'Stammesdaten'">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<table class="tenant-header">
|
||||||
|
<tr><th>Name</th><td>{{ props.tenant.name }}</td></tr>
|
||||||
|
<tr><th>Slug</th><td>{{ props.tenant.slug }}</td></tr>
|
||||||
|
<tr><th>mareike-URL:</th><td>{{ props.tenant.url }}</td></tr>
|
||||||
|
<tr><th>Status</th><td>
|
||||||
|
<span :class="props.tenant.is_active_local_group ? 'badge-active' : 'badge-inactive'">
|
||||||
|
{{ props.tenant.is_active_local_group ? 'Aktiv' : 'Inaktiv' }}
|
||||||
|
</span>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px;">
|
||||||
|
<tabbed-page :tabs="tabs" />
|
||||||
|
</div>
|
||||||
|
</shadowed-box>
|
||||||
|
</AdminAppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tenant-header {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-header th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 16px;
|
||||||
|
width: 200px;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border-bottom: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-header td {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-bottom: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-header tr:last-child th,
|
||||||
|
.tenant-header tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #166534;
|
||||||
|
background-color: #dcfce7;
|
||||||
|
border: 1px solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-inactive {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #991b1b;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup>
|
||||||
|
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||||
|
import TenantGeneral from "./Partials/TenantGeneral.vue";
|
||||||
|
import TenantContact from "./Partials/TenantContact.vue";
|
||||||
|
import TenantPayment from "./Partials/TenantPayment.vue";
|
||||||
|
import TenantImpress from "./Partials/TenantImpress.vue";
|
||||||
|
import TenantGdpr from "./Partials/TenantGdpr.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tenant: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const slug = props.tenant.slug
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: 'Allgemeines',
|
||||||
|
component: TenantGeneral,
|
||||||
|
endpoint: '/api/v1/admin/tenants/' + slug + '/general',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Kontaktdaten',
|
||||||
|
component: TenantContact,
|
||||||
|
endpoint: '/api/v1/admin/tenants/' + slug + '/contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Bezahldaten',
|
||||||
|
component: TenantPayment,
|
||||||
|
endpoint: '/api/v1/admin/tenants/' + slug + '/payment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Impressum',
|
||||||
|
component: TenantImpress,
|
||||||
|
endpoint: '/api/v1/admin/tenants/' + slug + '/impress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Datenschutzerklärung',
|
||||||
|
component: TenantGdpr,
|
||||||
|
endpoint: '/api/v1/admin/tenants/' + slug + '/gdpr',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminAppLayout :title="props.tenant.name">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<tabbed-page :tabs="tabs" />
|
||||||
|
</shadowed-box>
|
||||||
|
</AdminAppLayout>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import { toast } from "vue3-toastify";
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
function openTenant(slug) {
|
||||||
|
window.location.href = '/admin/tenants/' + slug
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenants = ref([])
|
||||||
|
const showCreate = ref(false)
|
||||||
|
const createForm = ref({
|
||||||
|
name: '',
|
||||||
|
slug: '',
|
||||||
|
url: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadTenants()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadTenants() {
|
||||||
|
const response = await fetch('/api/v1/admin/tenants/list', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
})
|
||||||
|
const data = await response.json()
|
||||||
|
tenants.value = data.tenants
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTenant() {
|
||||||
|
const response = await request('/api/v1/admin/tenants/create', {
|
||||||
|
method: 'POST',
|
||||||
|
body: createForm.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response && response.status === 'success') {
|
||||||
|
toast.success(response.message)
|
||||||
|
showCreate.value = false
|
||||||
|
createForm.value = { name: '', slug: '', url: '' }
|
||||||
|
await loadTenants()
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message ?? 'Fehler beim Anlegen')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminAppLayout title="Stämme">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<table class="tenant-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Slug</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="tenant in tenants" :key="tenant.slug" class="tenant-row" @click="openTenant(tenant.slug)">
|
||||||
|
<td>{{ tenant.name }}</td>
|
||||||
|
<td>{{ tenant.slug }}</td>
|
||||||
|
<td>{{ tenant.url }}</td>
|
||||||
|
<td>
|
||||||
|
<span :class="tenant.is_active_local_group ? 'badge-active' : 'badge-inactive'">
|
||||||
|
{{ tenant.is_active_local_group ? 'Aktiv' : 'Inaktiv' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px;">
|
||||||
|
<button v-if="!showCreate" class="btn-create" @click="showCreate = true">Neuen Stamm anlegen</button>
|
||||||
|
|
||||||
|
<div v-if="showCreate" class="create-form">
|
||||||
|
<h3>Neuen Stamm anlegen</h3>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<td><input type="text" v-model="createForm.name" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Slug</th>
|
||||||
|
<td><input type="text" v-model="createForm.slug" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>URL</th>
|
||||||
|
<td><input type="text" v-model="createForm.url" class="form-input" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-save" @click="createTenant">Anlegen</button>
|
||||||
|
<button class="btn-cancel" @click="showCreate = false">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</shadowed-box>
|
||||||
|
</AdminAppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tenant-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-table thead th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-table tbody td {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-row:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #166534;
|
||||||
|
background-color: #dcfce7;
|
||||||
|
border: 1px solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-inactive {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #991b1b;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 100px;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create:hover {
|
||||||
|
background-color: #163a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save, .btn-cancel {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: #d1d5db;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import FullScreenModal from "../../../Views/Components/FullScreenModal.vue";
|
||||||
|
import UserDetail from "./Partials/UserDetail.vue";
|
||||||
|
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isLvTenant: Boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
const users = ref([])
|
||||||
|
const selectedUserId = ref(null)
|
||||||
|
const userDetail = ref(null)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadUsers()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadUsers() {
|
||||||
|
const response = await fetch('/api/v1/admin/users/list', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
})
|
||||||
|
const data = await response.json()
|
||||||
|
users.value = data.users
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectUser(userId) {
|
||||||
|
if (selectedUserId.value === userId) {
|
||||||
|
selectedUserId.value = null
|
||||||
|
userDetail.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedUserId.value = userId
|
||||||
|
const response = await fetch('/api/v1/admin/users/' + userId, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
})
|
||||||
|
userDetail.value = await response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUserUpdated() {
|
||||||
|
loadUsers()
|
||||||
|
if (selectedUserId.value) {
|
||||||
|
selectUser(selectedUserId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDetailClosed() {
|
||||||
|
selectedUserId.value = null
|
||||||
|
userDetail.value = null
|
||||||
|
loadUsers()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminAppLayout title="Benutzer*innen">
|
||||||
|
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||||
|
<table class="user-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nachname</th>
|
||||||
|
<th>Vorname</th>
|
||||||
|
<th>Pfadiname</th>
|
||||||
|
<th v-if="props.isLvTenant">Stamm</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="user in users"
|
||||||
|
:key="user.id"
|
||||||
|
:class="['user-row', { 'user-row-selected': selectedUserId === user.id }]"
|
||||||
|
@click="selectUser(user.id)">
|
||||||
|
<td>{{ user.lastname }}</td>
|
||||||
|
<td>{{ user.firstname }}</td>
|
||||||
|
<td>{{ user.nickname }}</td>
|
||||||
|
<td v-if="props.isLvTenant">{{ user.local_group_name }}</td>
|
||||||
|
<td>
|
||||||
|
<span :class="user.active ? 'badge-active' : 'badge-inactive'">
|
||||||
|
{{ user.active ? 'Aktiv' : 'Inaktiv' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</shadowed-box>
|
||||||
|
|
||||||
|
<FullScreenModal :show="userDetail !== null" @close="onDetailClosed">
|
||||||
|
<UserDetail
|
||||||
|
v-if="userDetail"
|
||||||
|
:data="userDetail"
|
||||||
|
@updated="onUserUpdated"
|
||||||
|
@closed="onDetailClosed"
|
||||||
|
/>
|
||||||
|
</FullScreenModal>
|
||||||
|
</AdminAppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-table thead th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-table tbody td {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-row:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-row-selected {
|
||||||
|
background-color: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #166534;
|
||||||
|
background-color: #dcfce7;
|
||||||
|
border: 1px solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-inactive {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #991b1b;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -68,6 +68,11 @@ class SignupController extends CommonController {
|
|||||||
|
|
||||||
public function signUp(int $eventId, Request $request) {
|
public function signUp(int $eventId, Request $request) {
|
||||||
$event = $this->events->getById($eventId, false);
|
$event = $this->events->getById($eventId, false);
|
||||||
|
|
||||||
|
if (!$event->registration_allowed || new \DateTime() > $event->start_date) {
|
||||||
|
return response()->json(['status' => 'closed'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
$eventResource = $event->toResource();
|
$eventResource = $event->toResource();
|
||||||
$registrationData = $request->input('registration_data');
|
$registrationData = $request->input('registration_data');
|
||||||
$siblingReduction = $registrationData['sibling'] === 'true';
|
$siblingReduction = $registrationData['sibling'] === 'true';
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function eatingHabit() {
|
|||||||
<table class="form-table" style="margin-bottom: 20px;">
|
<table class="form-table" style="margin-bottom: 20px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Dein Name:</td>
|
<td>Dein Name:</td>
|
||||||
<td>{{props.formData.vorname}} {{props.formData.vorname}}</td>
|
<td>{{props.formData.vorname}} {{props.formData.nachname}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -99,10 +99,14 @@ function close() {
|
|||||||
|
|
||||||
<div class="signup-body">
|
<div class="signup-body">
|
||||||
<SignupForm
|
<SignupForm
|
||||||
|
v-if="props.event.registrationAllowed"
|
||||||
:event="props.event"
|
:event="props.event"
|
||||||
:participantData="props.participantData ?? {}"
|
:participantData="props.participantData ?? {}"
|
||||||
:localGroups="props.localGroups ?? []"
|
:localGroups="props.localGroups ?? []"
|
||||||
/>
|
/>
|
||||||
|
<p v-else class="signup-closed-notice">
|
||||||
|
Die Anmeldung für diese Veranstaltung ist geschlossen.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</shadowed-box>
|
</shadowed-box>
|
||||||
@@ -220,6 +224,14 @@ function close() {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signup-closed-notice {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px;
|
||||||
|
color: #991b1b;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Tablet (640–1023px) ─── */
|
/* ─── Tablet (640–1023px) ─── */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
.signup-box {
|
.signup-box {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Invoice\Actions\CreateInvoice;
|
|||||||
|
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
use App\Mail\InvoiceMails\InvoiceMailsNewInvoiceMail;
|
use App\Mail\InvoiceMails\InvoiceMailsNewInvoiceMail;
|
||||||
|
use App\Mail\InvoiceMails\InvoiceMailsSubmittedConfirmationMail;
|
||||||
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
@@ -50,6 +51,13 @@ class CreateInvoiceCommand {
|
|||||||
if ($invoice !== null) {
|
if ($invoice !== null) {
|
||||||
$response->success = true;
|
$response->success = true;
|
||||||
$response->invoice = $invoice;
|
$response->invoice = $invoice;
|
||||||
|
|
||||||
|
if ($invoice->contact_email !== null) {
|
||||||
|
Mail::to($invoice->contact_email)->send(new InvoiceMailsSubmittedConfirmationMail(
|
||||||
|
invoice: $invoice,
|
||||||
|
costUnit: $this->request->costUnit,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->costUnit->mail_on_new) {
|
if ($this->request->costUnit->mail_on_new) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class EditController extends CommonController{
|
|||||||
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'invoice' => new InvoiceResource($invoice)->toArray(),
|
'invoice' => new InvoiceResource($newInvoice)->toArray(),
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'costUnits' => array_merge($runningJobs, $currentEvents),
|
'costUnits' => array_merge($runningJobs, $currentEvents),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Legal\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class GdprController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Legal/LegalPage', [
|
||||||
|
'title' => 'Datenschutzerklärung',
|
||||||
|
'content' => $this->tenant->gdpr_text ?? '',
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Legal\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class ImpressController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$inertiaProvider = new InertiaProvider('Legal/LegalPage', [
|
||||||
|
'title' => 'Impressum',
|
||||||
|
'content' => $this->tenant->impress_text ?? '',
|
||||||
|
]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Legal\Controllers\ImpressController;
|
||||||
|
use App\Domains\Legal\Controllers\GdprController;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::get('/impress', ImpressController::class);
|
||||||
|
Route::get('/gdpr', GdprController::class);
|
||||||
|
});
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from "../../../../resources/js/layouts/AppLayout.vue";
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout :title="props.title">
|
||||||
|
<ShadowedBox class="legal-page-box">
|
||||||
|
<h2 class="legal-page-title">{{ props.title }}</h2>
|
||||||
|
<div class="legal-page-content" v-html="props.content"></div>
|
||||||
|
</ShadowedBox>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.legal-page-box {
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #e5e7eb;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-content {
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #374151;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-content :deep(img) {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-content :deep(table) {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-content :deep(a) {
|
||||||
|
color: #2563eb;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-content :deep(h1),
|
||||||
|
.legal-page-content :deep(h2),
|
||||||
|
.legal-page-content :deep(h3) {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-content :deep(p) {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.legal-page-box {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 12px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-page-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail\InvoiceMails;
|
||||||
|
|
||||||
|
use App\Models\CostUnit;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\ValueObjects\Amount;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Attachment;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
|
||||||
|
class InvoiceMailsSubmittedConfirmationMail extends Mailable {
|
||||||
|
public function __construct(
|
||||||
|
private Invoice $invoice,
|
||||||
|
private CostUnit $costUnit,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'Deine Abrechnung wurde erfolgreich eingereicht',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'emails.invoices.submitted_confirmation',
|
||||||
|
with: [
|
||||||
|
'contactName' => $this->invoice->contact_name,
|
||||||
|
'costUnitName' => $this->costUnit->name,
|
||||||
|
'invoiceAmount' => Amount::fromString($this->invoice->amount)->toString(),
|
||||||
|
'invoiceType' => $this->invoice->invoiceType()->name,
|
||||||
|
'invoiceNumber' => $this->invoice->invoice_number,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Middleware;
|
||||||
|
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Providers\AuthCheckProvider;
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
class AdminRoleMiddleware
|
||||||
|
{
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
if (!auth()->check()) {
|
||||||
|
return redirect('/login')->with('message', 'Du musst eingeloggt sein.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$authCheck = new AuthCheckProvider();
|
||||||
|
$role = $authCheck->getUserRole();
|
||||||
|
|
||||||
|
if (!in_array($role, [UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER], true)) {
|
||||||
|
return redirect('/')->with('message', 'Du bist dazu nicht berechtigt.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
class LvOnlyMiddleware
|
||||||
|
{
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
if (app('tenant')->slug !== 'lv') {
|
||||||
|
return redirect('/admin')->with('message', 'Diese Funktion ist nur auf LV-Ebene verfügbar.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,13 @@ class GlobalDataProvider {
|
|||||||
public function __invoke() {
|
public function __invoke() {
|
||||||
$this->user = auth()->user();
|
$this->user = auth()->user();
|
||||||
|
|
||||||
|
$canAccessAdmin = false;
|
||||||
|
if (null !== $this->user) {
|
||||||
|
$authCheck = new AuthCheckProvider();
|
||||||
|
$effectiveRole = $authCheck->getUserRole();
|
||||||
|
$canAccessAdmin = in_array($effectiveRole, [UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER], true);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'user' => null !== $this->user ? new UserResource($this->user)->toArray(request()) : null,
|
'user' => null !== $this->user ? new UserResource($this->user)->toArray(request()) : null,
|
||||||
'navbar' => $this->generateNavbar(),
|
'navbar' => $this->generateNavbar(),
|
||||||
@@ -27,6 +34,7 @@ class GlobalDataProvider {
|
|||||||
'activeUsers' => $this->getActiveUsers(),
|
'activeUsers' => $this->getActiveUsers(),
|
||||||
'version' => config('app.version'),
|
'version' => config('app.version'),
|
||||||
'currentEvent' => $this->getCurrentEventData(),
|
'currentEvent' => $this->getCurrentEventData(),
|
||||||
|
'canAccessAdmin' => $canAccessAdmin,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,34 +66,20 @@ class CostUnitRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getCostUnitsByCriteria(array $criteria, bool $forDisplay = true, $disableAccessCheck = false) : array {
|
public function getCostUnitsByCriteria(array $criteria, bool $forDisplay = true, $disableAccessCheck = false) : array {
|
||||||
$tenant = app('tenant');
|
|
||||||
|
|
||||||
$canSeeAll = false;
|
|
||||||
$user = Auth()->user();
|
$user = Auth()->user();
|
||||||
|
|
||||||
if ($disableAccessCheck) {
|
if ($disableAccessCheck) {
|
||||||
$canSeeAll = true;
|
$canSeeAll = true;
|
||||||
} else {
|
} else {
|
||||||
if ($tenant->slug !== 'lv') {
|
$canSeeAll = in_array(new AuthCheckProvider()->getUserRole(), [
|
||||||
if (
|
UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER
|
||||||
new AuthCheckProvider()->isAdministrator() ||
|
]);
|
||||||
$user->user_role_local_group === UserRole::USER_ROLE_ADMIN
|
|
||||||
) {
|
|
||||||
$canSeeAll = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
in_array($user->user_role_main, [UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_ADMIN])
|
|
||||||
) {
|
|
||||||
$canSeeAll = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$visibleCostUnits = [];
|
$visibleCostUnits = [];
|
||||||
/** @var CostUnit $costUnit */
|
/** @var CostUnit $costUnit */
|
||||||
foreach (Costunit::where($criteria)->get() as $costUnit) {
|
foreach (Costunit::where($criteria)->get() as $costUnit) {
|
||||||
if ($canSeeAll || $disableAccessCheck || $costUnit->treasurers()->where('user_id', $user->id)->exists() ) {
|
if ($canSeeAll || $costUnit->treasurers()->where('user_id', $user->id)->exists() ) {
|
||||||
if ($forDisplay) {
|
if ($forDisplay) {
|
||||||
$visibleCostUnits[] = new CostUnitResource($costUnit)->toArray(request());
|
$visibleCostUnits[] = new CostUnitResource($costUnit)->toArray(request());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -77,38 +77,20 @@ class EventRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getEventsByCriteria(array $criteria, $accessCheck = true) : array {
|
public function getEventsByCriteria(array $criteria, $accessCheck = true) : array {
|
||||||
$tenant = app('tenant');
|
|
||||||
|
|
||||||
$canSeeAll = false;
|
|
||||||
$user = Auth()->user();
|
$user = Auth()->user();
|
||||||
|
|
||||||
if (!$accessCheck) {
|
if (!$accessCheck) {
|
||||||
$canSeeAll = true;
|
$canSeeAll = true;
|
||||||
} else {
|
} else {
|
||||||
if (
|
$canSeeAll = in_array(new AuthCheckProvider()->getUserRole(), [
|
||||||
new AuthCheckProvider()->isAdministrator() ||
|
UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER
|
||||||
$user->user_role_local_group === UserRole::USER_ROLE_ADMIN
|
]);
|
||||||
) {
|
|
||||||
if (
|
|
||||||
$user->user_role_main === UserRole::USER_ROLE_ADMIN ||
|
|
||||||
in_array($user->user_role_local_group, [UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_ADMIN])
|
|
||||||
) {
|
|
||||||
$canSeeAll = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
in_array($user->user_role_main, [UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_ADMIN])
|
|
||||||
) {
|
|
||||||
$canSeeAll = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$visibleEvents = [];
|
$visibleEvents = [];
|
||||||
/** @var Event $event */
|
/** @var Event $event */
|
||||||
foreach (Event::where($criteria)->orderBy('start_date')->get() as $event) {
|
foreach (Event::where($criteria)->orderBy('start_date')->get() as $event) {
|
||||||
|
if ($canSeeAll || $event->eventManagers()->where('user_id', $user->id)->exists()) {
|
||||||
if ($canSeeAll || !$accessCheck || $event->eventManagers()->where('user_id', $user->id)->exists()) {
|
|
||||||
$visibleEvents[] = $event;
|
$visibleEvents[] = $event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Enumerations\InvoiceStatus;
|
|||||||
use App\Enumerations\UserRole;
|
use App\Enumerations\UserRole;
|
||||||
use App\Models\CostUnit;
|
use App\Models\CostUnit;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
|
use App\Providers\AuthCheckProvider;
|
||||||
use App\Resources\InvoiceResource;
|
use App\Resources\InvoiceResource;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
@@ -83,19 +84,9 @@ class InvoiceRepository {
|
|||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = auth()->user();
|
return in_array(new AuthCheckProvider()->getUserRole(), [
|
||||||
if ($user->user_role_main === UserRole::USER_ROLE_ADMIN) {
|
UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER
|
||||||
return $invoice;
|
]) ? $invoice : null;
|
||||||
}
|
|
||||||
|
|
||||||
if (app('tenant')->slug === 'lv' && $user->user_role_main === UserRole::USER_ROLE_GROUP_LEADER) {
|
|
||||||
return $invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app('tenant')->slug !== 'lv' && $user->local_group === app('tenant')->slug && $user->user_role_local_group === UserRole::USER_ROLE_GROUP_LEADER) {
|
|
||||||
return $invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class EventResource extends JsonResource{
|
|||||||
'accountIban' => $this->event->account_iban,
|
'accountIban' => $this->event->account_iban,
|
||||||
'alcoholicsAge' => $this->event->alcoholics_age,
|
'alcoholicsAge' => $this->event->alcoholics_age,
|
||||||
'sendWeeklyReports' => $this->event->send_weekly_report,
|
'sendWeeklyReports' => $this->event->send_weekly_report,
|
||||||
'registrationAllowed' => $this->event->registration_allowed,
|
'registrationAllowed' => $this->event->registration_allowed && new \DateTime() <= $this->event->start_date,
|
||||||
'archived' => $this->event->archived,
|
'archived' => $this->event->archived,
|
||||||
'earlyBirdEnd' => ['internal' => $this->event->early_bird_end->format('Y-m-d'), 'formatted' => $this->event->early_bird_end->format('d.m.Y')],
|
'earlyBirdEnd' => ['internal' => $this->event->early_bird_end->format('Y-m-d'), 'formatted' => $this->event->early_bird_end->format('d.m.Y')],
|
||||||
'registrationFinalEnd' => ['internal' => $this->event->registration_final_end->format('Y-m-d'), 'formatted' => $this->event->registration_final_end->format('d.m.Y')],
|
'registrationFinalEnd' => ['internal' => $this->event->registration_final_end->format('Y-m-d'), 'formatted' => $this->event->registration_final_end->format('d.m.Y')],
|
||||||
@@ -331,7 +331,7 @@ class EventResource extends JsonResource{
|
|||||||
$basicFee = $basicFee->multiply($this->getMultiplier());
|
$basicFee = $basicFee->multiply($this->getMultiplier());
|
||||||
|
|
||||||
if ($this->event->pay_per_day) {
|
if ($this->event->pay_per_day) {
|
||||||
$days = $arrival->diff($departure)->days;
|
$days = $arrival->diff($departure)->days + 1;
|
||||||
$basicFee = $basicFee->multiply($days);
|
$basicFee = $basicFee->multiply($days);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Scopes;
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use App\Domains\Admin\Repositories\AdminTenantRepository;
|
||||||
|
use App\Domains\Admin\Repositories\AdminUserRepository;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Providers\AuthCheckProvider;
|
use App\Providers\AuthCheckProvider;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
@@ -23,6 +25,8 @@ abstract class CommonController {
|
|||||||
protected EventRepository $events;
|
protected EventRepository $events;
|
||||||
protected EventParticipantRepository $eventParticipants;
|
protected EventParticipantRepository $eventParticipants;
|
||||||
protected EstimatesRepository $estimates;
|
protected EstimatesRepository $estimates;
|
||||||
|
protected AdminUserRepository $adminUsers;
|
||||||
|
protected AdminTenantRepository $adminTenants;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->tenant = app('tenant');
|
$this->tenant = app('tenant');
|
||||||
@@ -33,6 +37,8 @@ abstract class CommonController {
|
|||||||
$this->events = new EventRepository();
|
$this->events = new EventRepository();
|
||||||
$this->eventParticipants = new EventParticipantRepository();
|
$this->eventParticipants = new EventParticipantRepository();
|
||||||
$this->estimates = new EstimatesRepository();
|
$this->estimates = new EstimatesRepository();
|
||||||
|
$this->adminUsers = new AdminUserRepository();
|
||||||
|
$this->adminTenants = new AdminTenantRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function checkAuth() {
|
protected function checkAuth() {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('tenants', function (Blueprint $table) {
|
||||||
|
$table->longText('gdpr_text')->nullable()->change();
|
||||||
|
$table->longText('impress_text')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('tenants', function (Blueprint $table) {
|
||||||
|
$table->string('gdpr_text')->nullable()->change();
|
||||||
|
$table->string('impress_text')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
+4
-4
@@ -57,8 +57,8 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
|
|
||||||
.header .user-info {
|
.header .user-info {
|
||||||
position: relative;
|
position: relative;
|
||||||
right: calc(-100% + 190px);
|
right: calc(-100% + 250px);
|
||||||
width: 195px;
|
width: 240px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 50px 0 0 50px;
|
border-radius: 50px 0 0 50px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -108,11 +108,11 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
height: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo img {
|
.logo img {
|
||||||
width: 135px !important;
|
width: 100% !important;
|
||||||
height: 70px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 321 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 321 KiB |
@@ -0,0 +1,459 @@
|
|||||||
|
<script setup>
|
||||||
|
import {reactive, onMounted, ref, computed} from 'vue';
|
||||||
|
import Icon from "../../../app/Views/Components/Icon.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
import {useAjax} from "../components/ajaxHandler.js";
|
||||||
|
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const globalProps = reactive({
|
||||||
|
tenant: '',
|
||||||
|
user: null,
|
||||||
|
currentPath: '/',
|
||||||
|
errors: {},
|
||||||
|
availableLocalGroups: [],
|
||||||
|
message: '',
|
||||||
|
currentEvent: null,
|
||||||
|
version: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const sidebarOpen = ref(false);
|
||||||
|
|
||||||
|
function toggleSidebar() {
|
||||||
|
sidebarOpen.value = !sidebarOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSidebar() {
|
||||||
|
sidebarOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||||
|
const data = await response.json();
|
||||||
|
Object.assign(globalProps, data);
|
||||||
|
|
||||||
|
const messageResponse = await request('/api/v1/core/retrieve-messages', {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (messageResponse.message !== '') {
|
||||||
|
if (messageResponse.messageType === 'success') {
|
||||||
|
toast.success(messageResponse.message)
|
||||||
|
} else {
|
||||||
|
toast.error(messageResponse.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: { type: String, default: 'Administration' },
|
||||||
|
flash: { type: Object, default: () => ({}) }
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-layout">
|
||||||
|
<!-- Mobile Overlay -->
|
||||||
|
<div class="sidebar-overlay" :class="{ active: sidebarOpen }" @click="closeSidebar"></div>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<button class="hamburger-btn" @click="toggleSidebar" aria-label="Menü öffnen">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="left-side">
|
||||||
|
<h1>{{ props.title }}</h1>
|
||||||
|
<label id="show_username" v-if="globalProps.user !== null">Willkommen, {{ globalProps.user.nicename }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-actions" v-if="globalProps.user !== null">
|
||||||
|
<div class="user-info">
|
||||||
|
<a href="/" class="header-link-anonymous" title="Zurück zur Anwendung">
|
||||||
|
<Icon name="desktop" />
|
||||||
|
</a>
|
||||||
|
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
|
||||||
|
<Icon name="envelope" />
|
||||||
|
</a>
|
||||||
|
<a href="/profile" class="header-link-anonymous" title="Mein Profil">
|
||||||
|
<Icon name="user" />
|
||||||
|
</a>
|
||||||
|
<a href="/logout" class="header-link-anonymous-logout" title="Abmelden">
|
||||||
|
<Icon name="lock" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Flexbox: Sidebar + Content -->
|
||||||
|
<div class="flexbox">
|
||||||
|
<div class="sidebar" :class="{ 'sidebar-open': sidebarOpen }">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="../../../public/images/mareike.png" alt="Logo" />
|
||||||
|
</div>
|
||||||
|
<nav class="nav">
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li>
|
||||||
|
<a href="/admin/tenant" @click="closeSidebar">
|
||||||
|
{{ globalProps.tenant.slug === 'lv' ? 'LV-Daten' : 'Stammesdaten' }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li v-if="globalProps.tenant.slug === 'lv'">
|
||||||
|
<a href="/admin/tenants" @click="closeSidebar">Stämme</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/users" @click="closeSidebar">Benutzer*innen</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
<div class="content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="footer-inner">
|
||||||
|
<span>Version {{ globalProps.version }}</span>
|
||||||
|
<span class="footer-hide-mobile">mareike – Modernes Anmeldesystem und richtig einfache Kostenerfassung</span>
|
||||||
|
<a href="/impress" class="footer-link">Impressum</a>
|
||||||
|
<a href="/gdpr" class="footer-link">Datenschutzerklärung</a>
|
||||||
|
<span>© 2022 – 2026</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="flash.message" class="toaster">
|
||||||
|
{{ flash.message }}
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* ─── Header ─── */
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 80px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 50;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-side {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-side h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
#show_username {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous,
|
||||||
|
.header-link-anonymous-logout {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous:hover {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous-logout:hover {
|
||||||
|
background-color: #ff0000;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Hamburger ─── */
|
||||||
|
.hamburger-btn {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
width: 50px;
|
||||||
|
height: 80px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-btn span {
|
||||||
|
display: block;
|
||||||
|
width: 24px;
|
||||||
|
height: 3px;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Layout ─── */
|
||||||
|
.app-layout {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 20px;
|
||||||
|
box-shadow: 20px 20px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 0 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexbox {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
background-color: #FAFAFB;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Sidebar ─── */
|
||||||
|
.sidebar {
|
||||||
|
flex-basis: 275px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #ffffff;
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
height: 180px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Nav ─── */
|
||||||
|
.nav {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li a {
|
||||||
|
color: #b6b6b6;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 16px 25px;
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a:hover {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Content ─── */
|
||||||
|
.content-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Footer ─── */
|
||||||
|
.footer {
|
||||||
|
background: #666666;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 11pt;
|
||||||
|
font-weight: bold;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 20px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════
|
||||||
|
TABLET (640px – 1023px)
|
||||||
|
═══════════════════════════════════════════ */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.app-layout {
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-btn {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 100;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
width: 260px;
|
||||||
|
flex-basis: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.sidebar-open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-overlay.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous,
|
||||||
|
.header-link-anonymous-logout {
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-side h1 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════
|
||||||
|
SMARTPHONE (< 640px)
|
||||||
|
═══════════════════════════════════════════ */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
.header {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-btn {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-side h1 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#show_username {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous,
|
||||||
|
.header-link-anonymous-logout {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-hide-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-inner {
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 9pt;
|
||||||
|
gap: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 15px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 240px;
|
||||||
|
flex-basis: 240px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -61,6 +61,8 @@ const showCurrentEventLink = computed(() => {
|
|||||||
return currentPath !== '/event/details/' + globalProps.currentEvent.identifier;
|
return currentPath !== '/event/details/' + globalProps.currentEvent.identifier;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canAccessAdmin = computed(() => globalProps.canAccessAdmin ?? false);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: { type: String, default: 'App' },
|
title: { type: String, default: 'App' },
|
||||||
flash: { type: Object, default: () => ({}) }
|
flash: { type: Object, default: () => ({}) }
|
||||||
@@ -99,6 +101,9 @@ const props = defineProps({
|
|||||||
|
|
||||||
<div class="header-actions" v-if="globalProps.user !== null">
|
<div class="header-actions" v-if="globalProps.user !== null">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
|
<a v-if="canAccessAdmin" href="/admin" class="header-link-anonymous" title="Administration">
|
||||||
|
<Icon name="gear" />
|
||||||
|
</a>
|
||||||
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
|
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
|
||||||
<Icon name="envelope" />
|
<Icon name="envelope" />
|
||||||
</a>
|
</a>
|
||||||
@@ -123,7 +128,7 @@ const props = defineProps({
|
|||||||
<div class="flexbox">
|
<div class="flexbox">
|
||||||
<div class="sidebar" :class="{ 'sidebar-open': sidebarOpen }">
|
<div class="sidebar" :class="{ 'sidebar-open': sidebarOpen }">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="../../../public/images/logo.png" alt="Logo" />
|
<img src="../../../public/images/mareike.png" alt="Logo" />
|
||||||
</div>
|
</div>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<ul class="nav-links" v-if="globalProps.navbar.personal.length > 0">
|
<ul class="nav-links" v-if="globalProps.navbar.personal.length > 0">
|
||||||
@@ -172,8 +177,8 @@ const props = defineProps({
|
|||||||
<div class="footer-inner">
|
<div class="footer-inner">
|
||||||
<span>Version {{ globalProps.version }}</span>
|
<span>Version {{ globalProps.version }}</span>
|
||||||
<span class="footer-hide-mobile">mareike – Modernes Anmeldesystem und richtig einfache Kostenerfassung</span>
|
<span class="footer-hide-mobile">mareike – Modernes Anmeldesystem und richtig einfache Kostenerfassung</span>
|
||||||
<span>Impressum</span>
|
<a href="/impress" class="footer-link">Impressum</a>
|
||||||
<span>Datenschutzerklärung</span>
|
<a href="/gdpr" class="footer-link">Datenschutzerklärung</a>
|
||||||
<span>© 2022 – 2026</span>
|
<span>© 2022 – 2026</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -235,7 +240,7 @@ const props = defineProps({
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 200px;
|
width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-link-anonymous,
|
.header-link-anonymous,
|
||||||
@@ -335,11 +340,13 @@ const props = defineProps({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
height: 180px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo img {
|
.logo img {
|
||||||
width: 135px;
|
width: 100%;
|
||||||
height: 70px;
|
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +414,15 @@ const props = defineProps({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Direktlink zum aktuellen Event ─── */
|
/* ─── Direktlink zum aktuellen Event ─── */
|
||||||
.current-event-link {
|
.current-event-link {
|
||||||
display: none; /* per Default ausgeblendet – nur auf Mobile sichtbar */
|
display: none; /* per Default ausgeblendet – nur auf Mobile sichtbar */
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<h3>1. Verantwortlicher</h3>
|
||||||
|
<p>Verantwortlich für die Datenverarbeitung auf dieser Website ist:<br>
|
||||||
|
<strong>[Name des Vereins]</strong><br>
|
||||||
|
[Straße und Hausnummer]<br>
|
||||||
|
[PLZ Ort]<br>
|
||||||
|
E-Mail: [E-Mail-Adresse]</p>
|
||||||
|
|
||||||
|
<p>Der Verein hat keinen Datenschutzbeauftragten bestellt, da die Voraussetzungen nach Art. 37 DSGVO nicht vorliegen.</p>
|
||||||
|
|
||||||
|
<h3>2. Erhebung und Speicherung personenbezogener Daten</h3>
|
||||||
|
<p>Beim Besuch dieser Website werden durch den Webserver automatisch folgende Daten in Server-Logfiles gespeichert:</p>
|
||||||
|
<ul>
|
||||||
|
<li>IP-Adresse des anfragenden Rechners</li>
|
||||||
|
<li>Datum und Uhrzeit des Zugriffs</li>
|
||||||
|
<li>Aufgerufene Seite bzw. Name der abgerufenen Datei</li>
|
||||||
|
<li>Verwendeter Browser und Betriebssystem</li>
|
||||||
|
</ul>
|
||||||
|
<p>Diese Daten sind nicht bestimmten Personen zuordenbar. Eine Zusammenführung mit anderen Datenquellen findet nicht statt. Die Daten werden nach einer statistischen Auswertung gelöscht.</p>
|
||||||
|
|
||||||
|
<h3>3. Registrierung und Nutzerkonto</h3>
|
||||||
|
<p>Zur Nutzung bestimmter Funktionen ist eine Registrierung erforderlich. Dabei werden folgende Daten erhoben:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Vor- und Nachname</li>
|
||||||
|
<li>E-Mail-Adresse</li>
|
||||||
|
<li>Weitere freiwillige Angaben je nach Funktion (z. B. Geburtsdatum, Telefonnummer, Adresse)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Die Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung bzw. vorvertragliche Maßnahmen) sowie Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an der Vereinsverwaltung).</p>
|
||||||
|
|
||||||
|
<h3>4. Cookies</h3>
|
||||||
|
<p>Diese Website verwendet ausschließlich technisch notwendige Cookies, die für den Betrieb der Seite erforderlich sind (z. B. Session-Cookies zur Authentifizierung). Diese Cookies werden nach Ende der Browser-Sitzung automatisch gelöscht.</p>
|
||||||
|
<p>Es werden keine Tracking-Cookies, Analyse-Cookies oder Cookies von Drittanbietern eingesetzt. Eine Einwilligung ist für technisch notwendige Cookies nicht erforderlich (§ 25 Abs. 2 TDDDG).</p>
|
||||||
|
|
||||||
|
<h3>5. Keine Weitergabe an Dritte und keine externen Dienste</h3>
|
||||||
|
<p>Es werden keine externen Tools, Analysedienste, Social-Media-Plugins oder Content-Delivery-Networks eingebunden. Sämtliche Daten werden ausschließlich auf unserem eigenen Server verarbeitet.</p>
|
||||||
|
<p>Eine Weitergabe personenbezogener Daten an Dritte erfolgt nur, wenn dies zur Vertragserfüllung erforderlich ist (z. B. Weitergabe von Teilnehmerdaten an Veranstaltungsorte) oder eine gesetzliche Verpflichtung besteht.</p>
|
||||||
|
|
||||||
|
<h3>6. Deine Rechte</h3>
|
||||||
|
<p>Du hast jederzeit das Recht auf:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Auskunft</strong> über deine bei uns gespeicherten Daten (Art. 15 DSGVO)</li>
|
||||||
|
<li><strong>Berichtigung</strong> unrichtiger Daten (Art. 16 DSGVO)</li>
|
||||||
|
<li><strong>Löschung</strong> deiner Daten (Art. 17 DSGVO)</li>
|
||||||
|
<li><strong>Einschränkung der Verarbeitung</strong> (Art. 18 DSGVO)</li>
|
||||||
|
<li><strong>Datenübertragbarkeit</strong> (Art. 20 DSGVO)</li>
|
||||||
|
<li><strong>Widerspruch</strong> gegen die Verarbeitung (Art. 21 DSGVO)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Zur Ausübung deiner Rechte genügt eine formlose Mitteilung an die oben genannte E-Mail-Adresse.</p>
|
||||||
|
|
||||||
|
<h3>7. Beschwerderecht</h3>
|
||||||
|
<p>Du hast das Recht, dich bei einer Datenschutz-Aufsichtsbehörde über die Verarbeitung deiner personenbezogenen Daten zu beschweren.</p>
|
||||||
|
|
||||||
|
<h3>8. Aktualität dieser Datenschutzerklärung</h3>
|
||||||
|
<p>Diese Datenschutzerklärung ist aktuell gültig.<br>
|
||||||
|
Stand: [Datum]</p>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<link rel="stylesheet" href="/css/modalBox.css" />
|
<link rel="stylesheet" href="/css/modalBox.css" />
|
||||||
<link rel="stylesheet" href="/css/costunits.css" />
|
<link rel="stylesheet" href="/css/costunits.css" />
|
||||||
<link rel="stylesheet" href="/css/invoices.css" />
|
<link rel="stylesheet" href="/css/invoices.css" />
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Hallo {{$contactName}},</h1>
|
||||||
|
<p>
|
||||||
|
deine Abrechnung wurde erfolgreich eingereicht.<br />
|
||||||
|
In der nachfolgenden Übersicht findest du die Details zu deiner Abrechnung:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
Abrechnungsnummer:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
{{$invoiceNumber}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
Veranstaltung / Kostenstelle:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
{{$costUnitName}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
Betrag:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
{{$invoiceAmount}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
Grund der Abrechnung:
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
|
||||||
|
{{$invoiceType}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Deine Abrechnung wird nun bearbeitet. Du wirst per E-Mail benachrichtigt, sobald sich der Status deiner Abrechnung ändert.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -23,6 +23,9 @@ require_once __DIR__ . '/../app/Domains/Event/Routes/web.php';
|
|||||||
require_once __DIR__ . '/../app/Domains/Event/Routes/api.php';
|
require_once __DIR__ . '/../app/Domains/Event/Routes/api.php';
|
||||||
require_once __DIR__ . '/../app/Domains/Budget/Routes/web.php';
|
require_once __DIR__ . '/../app/Domains/Budget/Routes/web.php';
|
||||||
require_once __DIR__ . '/../app/Domains/Budget/Routes/api.php';
|
require_once __DIR__ . '/../app/Domains/Budget/Routes/api.php';
|
||||||
|
require_once __DIR__ . '/../app/Domains/Legal/Routes/web.php';
|
||||||
|
require_once __DIR__ . '/../app/Domains/Admin/Routes/web.php';
|
||||||
|
require_once __DIR__ . '/../app/Domains/Admin/Routes/api.php';
|
||||||
|
|
||||||
|
|
||||||
Route::get('/LKvDUqWl', function () {
|
Route::get('/LKvDUqWl', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user