Compare commits
1 Commits
14925f6cb5
...
db4a058b5e
Author | SHA1 | Date |
---|---|---|
philipp lang | db4a058b5e |
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
- Rechnungen und Erinnerungen werden nun automatisch täglich um 10 Uhr verschickt
|
- Rechnungen und Erinnerungen werden nun automatisch täglich um 10 Uhr verschickt
|
||||||
- Es kann eingestellt werden, nach wie vielen Wochen an Rechnungen erinnert werden soll (Standard: 12)
|
- Es kann eingestellt werden, nach wie vielen Wochen an Rechnungen erinnert werden soll (Standard: 12)
|
||||||
- Name und Profilbild des angemeldeten Benutzers wird nun oben rechts angezeigt
|
|
||||||
|
|
||||||
### 1.10.15
|
### 1.10.15
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Fileshare\Actions;
|
||||||
|
|
||||||
|
use App\Fileshare\Models\Fileshare;
|
||||||
|
use App\Fileshare\Resources\FileshareResource;
|
||||||
|
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class FileshareApiIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(): AnonymousResourceCollection
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Datei-Verbindungen');
|
||||||
|
|
||||||
|
return FileshareResource::collection(Fileshare::paginate(15));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Fileshare\Actions;
|
||||||
|
|
||||||
|
use App\Fileshare\Models\Fileshare;
|
||||||
|
use App\Fileshare\Resources\FileshareResource;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class FileshareIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Datei-Verbindungen');
|
||||||
|
|
||||||
|
return Inertia::render('fileshare/Index', [
|
||||||
|
'data' => FileshareResource::collection(Fileshare::paginate(15)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,27 +2,29 @@
|
||||||
|
|
||||||
namespace App\Fileshare;
|
namespace App\Fileshare;
|
||||||
|
|
||||||
use App\Fileshare\Models\Fileshare;
|
use App\Fileshare\Actions\FileshareIndexAction;
|
||||||
use App\Fileshare\Resources\FileshareResource;
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
|
|
||||||
class FileshareSettings extends LocalSettings
|
class FileshareSettings extends LocalSettings implements Indexable
|
||||||
{
|
{
|
||||||
public static function group(): string
|
public static function group(): string
|
||||||
{
|
{
|
||||||
return 'fileshare';
|
return 'fileshare';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'fileshare';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function indexAction(): string
|
||||||
|
{
|
||||||
|
return FileshareIndexAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
{
|
{
|
||||||
return 'Datei-Verbindungen';
|
return 'Datei-Verbindungen';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function data()
|
|
||||||
{
|
|
||||||
return FileshareResource::collection(Fileshare::paginate(15));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form\Actions;
|
||||||
|
|
||||||
|
use App\Form\FormSettings;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function handle(FormSettings $settings): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => [
|
||||||
|
'register_url' => $settings->registerUrl,
|
||||||
|
'clear_cache_url' => $settings->clearCacheUrl,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(FormSettings $settings): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Module');
|
||||||
|
|
||||||
|
return Inertia::render('setting/Form', [
|
||||||
|
'data' => $this->handle($settings),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form\Actions;
|
||||||
|
|
||||||
|
use App\Form\FormSettings;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingStoreAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
*/
|
||||||
|
public function handle(array $input): void
|
||||||
|
{
|
||||||
|
$settings = app(FormSettings::class);
|
||||||
|
|
||||||
|
$settings->fill([
|
||||||
|
'registerUrl' => $input['register_url'],
|
||||||
|
'clearCacheUrl' => $input['clear_cache_url'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'register_url' => 'present|string',
|
||||||
|
'clear_cache_url' => 'present|string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(ActionRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->handle($request->validated());
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Form\Actions\SettingIndexAction;
|
||||||
|
use App\Form\Actions\SettingStoreAction;
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\Contracts\Storeable;
|
use App\Setting\Contracts\Storeable;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
|
|
||||||
class FormSettings extends LocalSettings implements Storeable
|
class FormSettings extends LocalSettings implements Indexable, Storeable
|
||||||
{
|
{
|
||||||
public string $registerUrl;
|
public string $registerUrl;
|
||||||
public string $clearCacheUrl;
|
public string $clearCacheUrl;
|
||||||
|
@ -15,19 +18,23 @@ class FormSettings extends LocalSettings implements Storeable
|
||||||
return 'form';
|
return 'form';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'form';
|
||||||
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
{
|
{
|
||||||
return 'Formulare';
|
return 'Formulare';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function indexAction(): string
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
{
|
||||||
return [
|
return SettingIndexAction::class;
|
||||||
'registerUrl' => 'present|string',
|
}
|
||||||
'clearCacheUrl' => 'present|string',
|
|
||||||
];
|
public static function storeAction(): string
|
||||||
|
{
|
||||||
|
return SettingStoreAction::class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,6 @@ class UserResource extends JsonResource
|
||||||
public static function meta(): array
|
public static function meta(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'default' => [
|
|
||||||
'firstname' => '',
|
|
||||||
'lastname' => '',
|
|
||||||
],
|
|
||||||
'links' => []
|
'links' => []
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
namespace App\Invoice;
|
namespace App\Invoice;
|
||||||
|
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\Contracts\Storeable;
|
use App\Setting\Contracts\Storeable;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
|
|
||||||
class InvoiceSettings extends LocalSettings implements Storeable
|
class InvoiceSettings extends LocalSettings implements Indexable, Storeable
|
||||||
{
|
{
|
||||||
public string $from_long;
|
public string $from_long;
|
||||||
|
|
||||||
|
@ -34,24 +35,19 @@ class InvoiceSettings extends LocalSettings implements Storeable
|
||||||
return 'bill';
|
return 'bill';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function slug(): string
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
{
|
||||||
return [
|
return 'bill';
|
||||||
'from_long' => '',
|
}
|
||||||
'from' => '',
|
|
||||||
'mobile' => '',
|
public static function indexAction(): string
|
||||||
'email' => '',
|
{
|
||||||
'website' => '',
|
return SettingIndexAction::class;
|
||||||
'address' => '',
|
}
|
||||||
'place' => '',
|
|
||||||
'zip' => '',
|
public static function storeAction(): string
|
||||||
'iban' => '',
|
{
|
||||||
'bic' => '',
|
return SettingSaveAction::class;
|
||||||
'rememberWeeks' => '',
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Invoice;
|
||||||
|
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function handle(InvoiceSettings $settings): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'from_long' => $settings->from_long,
|
||||||
|
'from' => $settings->from,
|
||||||
|
'mobile' => $settings->mobile,
|
||||||
|
'email' => $settings->email,
|
||||||
|
'website' => $settings->website,
|
||||||
|
'address' => $settings->address,
|
||||||
|
'place' => $settings->place,
|
||||||
|
'zip' => $settings->zip,
|
||||||
|
'iban' => $settings->iban,
|
||||||
|
'bic' => $settings->bic,
|
||||||
|
'remember_weeks' => $settings->rememberWeeks,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(InvoiceSettings $settings): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Rechnungs-Einstellungen');
|
||||||
|
|
||||||
|
return Inertia::render('setting/Bill', [
|
||||||
|
'data' => $this->handle($settings),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Invoice;
|
||||||
|
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingSaveAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $input
|
||||||
|
*/
|
||||||
|
public function handle(array $input): void
|
||||||
|
{
|
||||||
|
$settings = app(InvoiceSettings::class);
|
||||||
|
|
||||||
|
$settings->fill([
|
||||||
|
'from_long' => $input['from_long'] ?? '',
|
||||||
|
'from' => $input['from'] ?? '',
|
||||||
|
'mobile' => $input['mobile'] ?? '',
|
||||||
|
'email' => $input['email'] ?? '',
|
||||||
|
'website' => $input['website'] ?? '',
|
||||||
|
'address' => $input['address'] ?? '',
|
||||||
|
'place' => $input['place'] ?? '',
|
||||||
|
'zip' => $input['zip'] ?? '',
|
||||||
|
'iban' => $input['iban'] ?? '',
|
||||||
|
'bic' => $input['bic'] ?? '',
|
||||||
|
'rememberWeeks' => $input['remember_weeks'] ?? 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(ActionRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->handle($request->all());
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mailgateway\Actions;
|
||||||
|
|
||||||
|
use App\Mailgateway\Models\Mailgateway;
|
||||||
|
use App\Mailgateway\Resources\MailgatewayResource;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class IndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Builder<Mailgateway>
|
||||||
|
*/
|
||||||
|
public function handle(): Builder
|
||||||
|
{
|
||||||
|
return (new Mailgateway())->newQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'E-Mail-Verbindungen');
|
||||||
|
|
||||||
|
return Inertia::render('mailgateway/Index', [
|
||||||
|
'data' => MailgatewayResource::collection($this->handle()->paginate(10)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,27 +2,29 @@
|
||||||
|
|
||||||
namespace App\Mailgateway;
|
namespace App\Mailgateway;
|
||||||
|
|
||||||
use App\Mailgateway\Models\Mailgateway;
|
use App\Mailgateway\Actions\IndexAction;
|
||||||
use App\Mailgateway\Resources\MailgatewayResource;
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
|
|
||||||
class MailgatewaySettings extends LocalSettings
|
class MailgatewaySettings extends LocalSettings implements Indexable
|
||||||
{
|
{
|
||||||
public static function group(): string
|
public static function group(): string
|
||||||
{
|
{
|
||||||
return 'mailgateway';
|
return 'mailgateway';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'mailgateway';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function indexAction(): string
|
||||||
|
{
|
||||||
|
return IndexAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
{
|
{
|
||||||
return 'E-Mail-Verbindungen';
|
return 'E-Mail-Verbindungen';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function data()
|
|
||||||
{
|
|
||||||
return MailgatewayResource::collection(Mailgateway::paginate(10));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Module;
|
||||||
|
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ModuleIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function handle(ModuleSettings $settings): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => [
|
||||||
|
'modules' => $settings->modules,
|
||||||
|
],
|
||||||
|
'meta' => ['modules' => Module::forSelect()],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(ModuleSettings $settings): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Module');
|
||||||
|
|
||||||
|
return Inertia::render('setting/Module', [
|
||||||
|
'data' => $this->handle($settings),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,11 @@
|
||||||
|
|
||||||
namespace App\Module;
|
namespace App\Module;
|
||||||
|
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\Contracts\Storeable;
|
use App\Setting\Contracts\Storeable;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
|
|
||||||
class ModuleSettings extends LocalSettings implements Storeable
|
class ModuleSettings extends LocalSettings implements Indexable, Storeable
|
||||||
{
|
{
|
||||||
/** @var array<int, string> */
|
/** @var array<int, string> */
|
||||||
public array $modules;
|
public array $modules;
|
||||||
|
@ -17,38 +16,28 @@ class ModuleSettings extends LocalSettings implements Storeable
|
||||||
return 'module';
|
return 'module';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'module';
|
||||||
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
{
|
{
|
||||||
return 'Module';
|
return 'Module';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function indexAction(): string
|
||||||
|
{
|
||||||
|
return ModuleIndexAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function storeAction(): string
|
||||||
|
{
|
||||||
|
return ModuleStoreAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasModule(string $module): bool
|
public function hasModule(string $module): bool
|
||||||
{
|
{
|
||||||
return in_array($module, $this->modules);
|
return in_array($module, $this->modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'modules' => 'present|array',
|
|
||||||
'modules.*' => ['string', Rule::in(Module::values())],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function data(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
...parent::data(),
|
|
||||||
'meta' => [
|
|
||||||
...parent::data()['meta'],
|
|
||||||
'modules' => Module::forSelect(),
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Module;
|
||||||
|
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ModuleStoreAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
*/
|
||||||
|
public function handle(array $input): void
|
||||||
|
{
|
||||||
|
$settings = app(ModuleSettings::class);
|
||||||
|
|
||||||
|
$settings->fill([
|
||||||
|
'modules' => $input['modules'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'modules' => 'present|array',
|
||||||
|
'modules.*' => ['string', Rule::in(Module::values())],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(ActionRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->handle($request->validated());
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Nami\Actions;
|
||||||
|
|
||||||
|
use App\Setting\NamiSettings;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function handle(NamiSettings $settings): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'mglnr' => $settings->mglnr,
|
||||||
|
'password' => '',
|
||||||
|
'default_group_id' => $settings->default_group_id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(NamiSettings $settings): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'NaMi-Settings');
|
||||||
|
|
||||||
|
return Inertia::render('setting/Nami', [
|
||||||
|
'data' => $this->handle($settings),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Nami\Actions;
|
||||||
|
|
||||||
|
use App\Initialize\Actions\NamiLoginCheckAction;
|
||||||
|
use App\Setting\NamiSettings;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingSaveAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $input
|
||||||
|
*/
|
||||||
|
public function handle(array $input): void
|
||||||
|
{
|
||||||
|
$settings = app(NamiSettings::class);
|
||||||
|
|
||||||
|
$settings->fill([
|
||||||
|
'mglnr' => $input['mglnr'] ?? '',
|
||||||
|
'password' => $input['password'] ?? '',
|
||||||
|
'default_group_id' => $input['default_group_id'] ?? '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(ActionRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
NamiLoginCheckAction::run([
|
||||||
|
'mglnr' => $request->mglnr,
|
||||||
|
'password' => $request->password,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->handle($request->all());
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Actions;
|
||||||
|
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class PreventionIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Prävention');
|
||||||
|
|
||||||
|
return Inertia::render('setting/Prevention');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Actions;
|
||||||
|
|
||||||
|
use App\Prevention\PreventionSettings;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingApiAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'data' => app(PreventionSettings::class)->toArray(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Actions;
|
||||||
|
|
||||||
|
use App\Lib\Editor\EditorData;
|
||||||
|
use App\Lib\Events\Succeeded;
|
||||||
|
use App\Prevention\PreventionSettings;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingStoreAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'formmail' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ActionRequest $request): void
|
||||||
|
{
|
||||||
|
$settings = app(PreventionSettings::class);
|
||||||
|
$settings->formmail = EditorData::from($request->formmail);
|
||||||
|
$settings->save();
|
||||||
|
|
||||||
|
Succeeded::message('Einstellungen gespeichert.')->dispatch();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,11 @@
|
||||||
namespace App\Prevention;
|
namespace App\Prevention;
|
||||||
|
|
||||||
use App\Lib\Editor\EditorData;
|
use App\Lib\Editor\EditorData;
|
||||||
use App\Setting\Contracts\Storeable;
|
use App\Prevention\Actions\PreventionIndexAction;
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
|
|
||||||
class PreventionSettings extends LocalSettings implements Storeable
|
class PreventionSettings extends LocalSettings implements Indexable
|
||||||
{
|
{
|
||||||
|
|
||||||
public EditorData $formmail;
|
public EditorData $formmail;
|
||||||
|
@ -17,25 +17,18 @@ class PreventionSettings extends LocalSettings implements Storeable
|
||||||
return 'prevention';
|
return 'prevention';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'prevention';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function indexAction(): string
|
||||||
|
{
|
||||||
|
return PreventionIndexAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
{
|
{
|
||||||
return 'Prävention';
|
return 'Prävention';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'formmail' => 'required',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveAttributes(ActionRequest $request): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'formmail' => EditorData::from($request->formmail),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Setting\Actions;
|
|
||||||
|
|
||||||
use App\Lib\Events\Succeeded;
|
|
||||||
use App\Setting\Contracts\Storeable;
|
|
||||||
use GrahamCampbell\ResultType\Success;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
|
|
||||||
class StoreAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $input
|
|
||||||
*/
|
|
||||||
public function handle(Storeable $settings, array $input): void
|
|
||||||
{
|
|
||||||
$settings->fill($input)->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
/** @var Storeable */
|
|
||||||
$group = request()->route('settingGroup');
|
|
||||||
|
|
||||||
return $group->rules();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function asController(ActionRequest $request, Storeable $settingGroup): RedirectResponse
|
|
||||||
{
|
|
||||||
$settingGroup->beforeSave($request);
|
|
||||||
$this->handle($settingGroup, $settingGroup->saveAttributes($request));
|
|
||||||
|
|
||||||
Succeeded::message('Einstellungen gespeichert')->dispatch();
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Setting\Actions;
|
|
||||||
|
|
||||||
use App\Setting\LocalSettings;
|
|
||||||
use App\Setting\SettingFactory;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
|
|
||||||
class ViewAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
public function handle(LocalSettings $settingGroup): Response
|
|
||||||
{
|
|
||||||
session()->put('menu', 'setting');
|
|
||||||
session()->put('title', $settingGroup::title());
|
|
||||||
|
|
||||||
return Inertia::render('setting/' . ucfirst($settingGroup::group()), [
|
|
||||||
'data' => $settingGroup->data(),
|
|
||||||
'settingMenu' => app(SettingFactory::class)->getShare(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Setting\Contracts;
|
||||||
|
|
||||||
|
interface Indexable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return class-string
|
||||||
|
*/
|
||||||
|
public static function indexAction(): string;
|
||||||
|
}
|
|
@ -2,28 +2,10 @@
|
||||||
|
|
||||||
namespace App\Setting\Contracts;
|
namespace App\Setting\Contracts;
|
||||||
|
|
||||||
use App\Setting\LocalSettings;
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Spatie\LaravelSettings\Settings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @mixin LocalSettings
|
|
||||||
*/
|
|
||||||
interface Storeable
|
interface Storeable
|
||||||
{
|
{
|
||||||
public function url(): string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $input
|
* @return class-string
|
||||||
*/
|
*/
|
||||||
public function fill(array $input): Settings;
|
public static function storeAction(): string;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function rules(): array;
|
|
||||||
|
|
||||||
public function beforeSave(ActionRequest $request): void;
|
|
||||||
|
|
||||||
public function saveAttributes(ActionRequest $request): array;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,48 +2,16 @@
|
||||||
|
|
||||||
namespace App\Setting;
|
namespace App\Setting;
|
||||||
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Spatie\LaravelSettings\Settings;
|
use Spatie\LaravelSettings\Settings;
|
||||||
|
|
||||||
abstract class LocalSettings extends Settings
|
abstract class LocalSettings extends Settings
|
||||||
{
|
{
|
||||||
|
abstract public static function slug(): string;
|
||||||
|
|
||||||
abstract public static function title(): string;
|
abstract public static function title(): string;
|
||||||
|
|
||||||
public function url(): string
|
public static function url(): string
|
||||||
{
|
{
|
||||||
return route('setting.view', ['settingGroup' => $this->group()]);
|
return '/setting/'.static::slug();
|
||||||
}
|
|
||||||
|
|
||||||
public function storeUrl(): string
|
|
||||||
{
|
|
||||||
return $this->url();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function data()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'data' => $this->toArray(),
|
|
||||||
'meta' => [
|
|
||||||
'links' => [
|
|
||||||
'store' => $this->storeUrl(),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function beforeSave(ActionRequest $request): void
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function saveAttributes(ActionRequest $request): array
|
|
||||||
{
|
|
||||||
return $request->validated();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
namespace App\Setting;
|
namespace App\Setting;
|
||||||
|
|
||||||
use App\Group;
|
use App\Group;
|
||||||
use App\Initialize\Actions\NamiLoginCheckAction;
|
use App\Nami\Actions\SettingIndexAction;
|
||||||
|
use App\Nami\Actions\SettingSaveAction;
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
use App\Setting\Contracts\Storeable;
|
use App\Setting\Contracts\Storeable;
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Zoomyboy\LaravelNami\Api;
|
use Zoomyboy\LaravelNami\Api;
|
||||||
use Zoomyboy\LaravelNami\Nami;
|
use Zoomyboy\LaravelNami\Nami;
|
||||||
|
|
||||||
class NamiSettings extends LocalSettings implements Storeable
|
class NamiSettings extends LocalSettings implements Indexable, Storeable
|
||||||
{
|
{
|
||||||
public int $mglnr;
|
public int $mglnr;
|
||||||
|
|
||||||
|
@ -30,48 +31,28 @@ class NamiSettings extends LocalSettings implements Storeable
|
||||||
return Nami::login($this->mglnr, $this->password);
|
return Nami::login($this->mglnr, $this->password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'mglnr' => 'required',
|
|
||||||
'password' => 'required',
|
|
||||||
'default_group_id' => 'required',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function beforeSave(ActionRequest $request): void
|
|
||||||
{
|
|
||||||
NamiLoginCheckAction::run([
|
|
||||||
'mglnr' => $request->mglnr,
|
|
||||||
'password' => $request->password,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function localGroup(): ?Group
|
public function localGroup(): ?Group
|
||||||
{
|
{
|
||||||
return Group::firstWhere('nami_id', $this->default_group_id);
|
return Group::firstWhere('nami_id', $this->default_group_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'nami';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function indexAction(): string
|
||||||
|
{
|
||||||
|
return SettingIndexAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function storeAction(): string
|
||||||
|
{
|
||||||
|
return SettingSaveAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
public static function title(): string
|
public static function title(): string
|
||||||
{
|
{
|
||||||
return 'NaMi-Login';
|
return 'NaMi-Login';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function data(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
...parent::data(),
|
|
||||||
'data' => [
|
|
||||||
'mglnr' => $this->mglnr,
|
|
||||||
'password' => '',
|
|
||||||
'default_group_id' => $this->default_group_id,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace App\Setting;
|
namespace App\Setting;
|
||||||
|
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
|
use App\Setting\Contracts\Storeable;
|
||||||
use Illuminate\Routing\Router;
|
use Illuminate\Routing\Router;
|
||||||
|
|
||||||
class SettingFactory
|
class SettingFactory
|
||||||
|
@ -12,14 +14,22 @@ class SettingFactory
|
||||||
private array $settings = [];
|
private array $settings = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param class-string<LocalSettings> $setting
|
* @param class-string $setting
|
||||||
*/
|
*/
|
||||||
public function register(string $setting): void
|
public function register(string $setting): void
|
||||||
{
|
{
|
||||||
$this->settings[] = $setting;
|
$this->settings[] = $setting;
|
||||||
|
|
||||||
|
if (new $setting() instanceof Indexable) {
|
||||||
|
app(Router::class)->middleware(['web', 'auth:web', SettingMiddleware::class])->get($setting::url(), $setting::indexAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new $setting() instanceof Storeable) {
|
||||||
|
app(Router::class)->middleware(['web', 'auth:web', SettingMiddleware::class])->post($setting::url(), $setting::storeAction());
|
||||||
|
}
|
||||||
|
|
||||||
if (1 === count($this->settings)) {
|
if (1 === count($this->settings)) {
|
||||||
app(Router::class)->redirect('/setting', '/setting/' . $setting::group());
|
app(Router::class)->redirect('/setting', '/setting/'.$setting::slug());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,17 +39,10 @@ class SettingFactory
|
||||||
public function getShare(): array
|
public function getShare(): array
|
||||||
{
|
{
|
||||||
return collect($this->settings)->map(fn ($setting) => [
|
return collect($this->settings)->map(fn ($setting) => [
|
||||||
'url' => (new $setting)->url(),
|
'url' => $setting::url(),
|
||||||
'is_active' => url(request()->path()) === (new $setting)->url(),
|
'is_active' => '/'.request()->path() === $setting::url(),
|
||||||
'title' => $setting::title(),
|
'title' => $setting::title(),
|
||||||
])
|
])
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolveGroupName(string $name): LocalSettings
|
|
||||||
{
|
|
||||||
$settingClass = collect($this->settings)->first(fn ($setting) => $setting::group() === $name);
|
|
||||||
|
|
||||||
return app($settingClass);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Setting;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia;
|
||||||
|
|
||||||
|
class SettingMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
Inertia::share([
|
||||||
|
'setting_menu' => app(SettingFactory::class)->getShare(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,6 @@ use App\Invoice\InvoiceSettings;
|
||||||
use App\Mailgateway\MailgatewaySettings;
|
use App\Mailgateway\MailgatewaySettings;
|
||||||
use App\Module\ModuleSettings;
|
use App\Module\ModuleSettings;
|
||||||
use App\Prevention\PreventionSettings;
|
use App\Prevention\PreventionSettings;
|
||||||
use App\Setting\Actions\StoreAction;
|
|
||||||
use App\Setting\Actions\ViewAction;
|
|
||||||
use App\User\UserSettings;
|
|
||||||
use Illuminate\Routing\Router;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class SettingServiceProvider extends ServiceProvider
|
class SettingServiceProvider extends ServiceProvider
|
||||||
|
@ -24,10 +20,6 @@ class SettingServiceProvider extends ServiceProvider
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
app()->singleton(SettingFactory::class, fn () => new SettingFactory());
|
app()->singleton(SettingFactory::class, fn () => new SettingFactory());
|
||||||
app(Router::class)->bind('settingGroup', fn ($param) => app(SettingFactory::class)->resolveGroupName($param));
|
|
||||||
app(Router::class)->middleware(['web', 'auth:web'])->name('setting.view')->get('/setting/{settingGroup}', ViewAction::class);
|
|
||||||
app(Router::class)->middleware(['web', 'auth:web'])->name('setting.data')->get('/setting/{settingGroup}/data', ViewAction::class);
|
|
||||||
app(Router::class)->middleware(['web', 'auth:web'])->name('setting.store')->post('/setting/{settingGroup}', StoreAction::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +36,5 @@ class SettingServiceProvider extends ServiceProvider
|
||||||
app(SettingFactory::class)->register(FormSettings::class);
|
app(SettingFactory::class)->register(FormSettings::class);
|
||||||
app(SettingFactory::class)->register(FileshareSettings::class);
|
app(SettingFactory::class)->register(FileshareSettings::class);
|
||||||
app(SettingFactory::class)->register(PreventionSettings::class);
|
app(SettingFactory::class)->register(PreventionSettings::class);
|
||||||
app(SettingFactory::class)->register(UserSettings::class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\User\Actions;
|
||||||
|
|
||||||
|
use App\Http\Resources\UserResource;
|
||||||
|
use App\User;
|
||||||
|
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class IndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(): AnonymousResourceCollection
|
||||||
|
{
|
||||||
|
return UserResource::collection(User::orderByRaw('lastname, firstname')->get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\User;
|
|
||||||
|
|
||||||
use App\Http\Resources\UserResource;
|
|
||||||
use App\Setting\LocalSettings;
|
|
||||||
use App\User;
|
|
||||||
|
|
||||||
class UserSettings extends LocalSettings
|
|
||||||
{
|
|
||||||
public static function group(): string
|
|
||||||
{
|
|
||||||
return 'user';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function title(): string
|
|
||||||
{
|
|
||||||
return 'Benutzer';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function data()
|
|
||||||
{
|
|
||||||
return UserResource::collection(User::orderByRaw('lastname, firstname')->get());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,9 @@ import {ref, inject, computed, onBeforeUnmount} from 'vue';
|
||||||
import {router} from '@inertiajs/vue3';
|
import {router} from '@inertiajs/vue3';
|
||||||
import useQueueEvents from './useQueueEvents.js';
|
import useQueueEvents from './useQueueEvents.js';
|
||||||
|
|
||||||
export function useIndex(props, siteName = null) {
|
export function useIndex(props, siteName) {
|
||||||
const axios = inject('axios');
|
const axios = inject('axios');
|
||||||
if (siteName !== null) {
|
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
||||||
var {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
|
||||||
}
|
|
||||||
const rawProps = JSON.parse(JSON.stringify(props));
|
const rawProps = JSON.parse(JSON.stringify(props));
|
||||||
const inner = {
|
const inner = {
|
||||||
data: ref(rawProps.data),
|
data: ref(rawProps.data),
|
||||||
|
@ -58,10 +56,8 @@ export function useIndex(props, siteName = null) {
|
||||||
reload(true);
|
reload(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (siteName !== null) {
|
|
||||||
startListener();
|
startListener();
|
||||||
onBeforeUnmount(() => stopListener());
|
onBeforeUnmount(() => stopListener());
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: inner.data,
|
data: inner.data,
|
||||||
|
|
|
@ -2,11 +2,9 @@ import {computed, ref, inject, onBeforeUnmount} from 'vue';
|
||||||
import {router} from '@inertiajs/vue3';
|
import {router} from '@inertiajs/vue3';
|
||||||
import useQueueEvents from './useQueueEvents.js';
|
import useQueueEvents from './useQueueEvents.js';
|
||||||
|
|
||||||
export function useIndex(props, siteName = null) {
|
export function useIndex(props, siteName) {
|
||||||
const axios = inject('axios');
|
const axios = inject('axios');
|
||||||
if (siteName !== null) {
|
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
||||||
var {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
|
||||||
}
|
|
||||||
const single = ref(null);
|
const single = ref(null);
|
||||||
const rawProps = JSON.parse(JSON.stringify(props));
|
const rawProps = JSON.parse(JSON.stringify(props));
|
||||||
const inner = {
|
const inner = {
|
||||||
|
@ -88,10 +86,8 @@ export function useIndex(props, siteName = null) {
|
||||||
reload(true);
|
reload(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (siteName !== null) {
|
|
||||||
startListener();
|
startListener();
|
||||||
onBeforeUnmount(() => stopListener());
|
onBeforeUnmount(() => stopListener());
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: inner.data,
|
data: inner.data,
|
||||||
|
|
|
@ -76,12 +76,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js" setup>
|
<script lang="js" setup>
|
||||||
import { useIndex, indexProps } from '../../composables/useInertiaApiIndex.js';
|
import { useApiIndex } from '../../composables/useApiIndex.js';
|
||||||
import SettingLayout from '../setting/Layout.vue';
|
import SettingLayout from '../setting/Layout.vue';
|
||||||
|
|
||||||
const props = defineProps(indexProps);
|
const { meta, data, reload, create, edit, cancel, single, submit } = useApiIndex('/api/fileshare', 'fileshare');
|
||||||
|
|
||||||
const { meta, data, reload, create, edit, cancel, single, submit } = useIndex(props.data, 'fileshare');
|
|
||||||
|
|
||||||
function getType(type) {
|
function getType(type) {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
|
@ -91,4 +89,6 @@ function getType(type) {
|
||||||
}
|
}
|
||||||
return meta.value.types.find((t) => t.id === type);
|
return meta.value.types.find((t) => t.id === type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload();
|
||||||
</script>
|
</script>
|
|
@ -5,26 +5,45 @@
|
||||||
</template>
|
</template>
|
||||||
<setting-layout>
|
<setting-layout>
|
||||||
<form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
|
<form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
|
||||||
<f-text id="from" v-model="data.from" label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname"></f-text>
|
<f-text id="from" v-model="inner.from" label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname"></f-text>
|
||||||
<f-text id="from_long" v-model="data.from_long" label="Absender (lang)" hint="Absender-Name in Langform, i.d.R. der Stammesname"></f-text>
|
<f-text id="from_long" v-model="inner.from_long" label="Absender (lang)" hint="Absender-Name in Langform, i.d.R. der Stammesname"></f-text>
|
||||||
<h2 class="text-lg font-semibold text-gray-300 col-span-2 mt-5">Kontaktdaten</h2>
|
<h2 class="text-lg font-semibold text-gray-300 col-span-2 mt-5">Kontaktdaten</h2>
|
||||||
<div class="col-span-2 text-gray-300 text-sm">Diese Kontaktdaten stehen im Absender-Bereich auf der Rechnung.</div>
|
<div class="col-span-2 text-gray-300 text-sm">Diese Kontaktdaten stehen im Absender-Bereich auf der Rechnung.</div>
|
||||||
<f-text id="address" v-model="data.address" label="Straße"></f-text>
|
<f-text id="address" v-model="inner.address" label="Straße"></f-text>
|
||||||
<f-text id="zip" v-model="data.zip" label="PLZ"></f-text>
|
<f-text id="zip" v-model="inner.zip" label="PLZ"></f-text>
|
||||||
<f-text id="place" v-model="data.place" label="Ort"></f-text>
|
<f-text id="place" v-model="inner.place" label="Ort"></f-text>
|
||||||
<f-text id="email" v-model="data.email" label="E-Mail-Adresse"></f-text>
|
<f-text id="email" v-model="inner.email" label="E-Mail-Adresse"></f-text>
|
||||||
<f-text id="mobile" v-model="data.mobile" label="Telefonnummer"></f-text>
|
<f-text id="mobile" v-model="inner.mobile" label="Telefonnummer"></f-text>
|
||||||
<f-text id="website" v-model="data.website" label="Webseite"></f-text>
|
<f-text id="website" v-model="inner.website" label="Webseite"></f-text>
|
||||||
<f-text id="iban" v-model="data.iban" label="IBAN"></f-text>
|
<f-text id="iban" v-model="inner.iban" label="IBAN"></f-text>
|
||||||
<f-text id="bic" v-model="data.bic" label="BIC"></f-text>
|
<f-text id="bic" v-model="inner.bic" label="BIC"></f-text>
|
||||||
<f-text id="remember_weeks" v-model="data.rememberWeeks" type="number" label="Erinnerung alle X Wochen versenden"></f-text>
|
<f-text id="remember_weeks" v-model="inner.remember_weeks" type="number" label="Erinnerung alle X Wochen versenden"></f-text>
|
||||||
</form>
|
</form>
|
||||||
</setting-layout>
|
</setting-layout>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script>
|
||||||
import {props, useSettings} from './useSettings.js';
|
import SettingLayout from './Layout.vue';
|
||||||
const innerProps = defineProps(props);
|
|
||||||
const {submit, data, meta, SettingLayout} = useSettings(innerProps);
|
export default {
|
||||||
|
components: {
|
||||||
|
SettingLayout,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
inner: {...this.data},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$inertia.post('/setting/bill', this.inner, {
|
||||||
|
onSuccess: () => this.$success('Einstellungen gespeichert.'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,16 +9,40 @@
|
||||||
<p class="text-sm">Hier kannst du Einstellungen für Anmeldeformulare setzen.</p>
|
<p class="text-sm">Hier kannst du Einstellungen für Anmeldeformulare setzen.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<f-text id="register_url" v-model="data.registerUrl" label="Formular-Link"></f-text>
|
<f-text id="register_url" v-model="inner.register_url" label="Formular-Link"></f-text>
|
||||||
<f-text id="clear_cache_url" v-model="data.clearCacheUrl" label="Frontend-Cache-Url"></f-text>
|
<f-text id="clear_cache_url" v-model="inner.clear_cache_url" label="Frontend-Cache-Url"></f-text>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</setting-layout>
|
</setting-layout>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script>
|
||||||
import {props, useSettings} from './useSettings.js';
|
import SettingLayout from './Layout.vue';
|
||||||
const innerProps = defineProps(props);
|
|
||||||
const {submit, data, meta, SettingLayout} = useSettings(innerProps);
|
export default {
|
||||||
|
components: {
|
||||||
|
SettingLayout,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
inner: {...this.data.data},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$inertia.post('/setting/form', this.inner, {
|
||||||
|
onSuccess: () => this.$success('Einstellungen gespeichert.'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex grow relative">
|
<div class="flex grow relative">
|
||||||
<ui-menulist v-model="active" :entries="$page.props.settingMenu"></ui-menulist>
|
<ui-menulist v-model="active" :entries="$page.props.setting_menu"></ui-menulist>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
export default {
|
export default {
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
innerActive: this.$page.props.settingMenu.findIndex((menu) => menu.is_active),
|
innerActive: this.$page.props.setting_menu.findIndex((menu) => menu.is_active),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -19,7 +19,7 @@ export default {
|
||||||
},
|
},
|
||||||
set(v) {
|
set(v) {
|
||||||
var _self = this;
|
var _self = this;
|
||||||
this.$inertia.visit(this.$page.props.settingMenu[v].url, {
|
this.$inertia.visit(this.$page.props.setting_menu[v].url, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
_self.innerActive = v;
|
_self.innerActive = v;
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<page-layout>
|
||||||
|
<template #right>
|
||||||
|
<f-save-button form="mailmansettingform"></f-save-button>
|
||||||
|
</template>
|
||||||
|
<setting-layout>
|
||||||
|
<form id="mailmansettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
|
||||||
|
<div class="col-span-full text-gray-100 mb-3">
|
||||||
|
<p class="text-sm">
|
||||||
|
Scoutrobot kann automatisch Mailinglisten erstellen, wenn es mit einem existierenden
|
||||||
|
<a href="https://docs.mailman3.org/en/latest/">Mailman Server</a> verbunden wird. Mailman ist ein OpenSource-Mailinglisten-System, um E-Mails an mehrere Leute zu senden.
|
||||||
|
</p>
|
||||||
|
<p class="text-sm mt-1">Scoutrobot wird nach der Ersteinrichtung deine Mitglieder zu bestehenden E-Mail-Verteilern hinzufügen.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<f-switch id="is_active" v-model="inner.is_active" label="Mailman-Synchronisation aktiv"></f-switch>
|
||||||
|
</div>
|
||||||
|
<div class="flex h-full items-center">
|
||||||
|
<ui-sprite :src="stateDisplay.icon" :class="stateDisplay.text" class="w-5 h-5"></ui-sprite>
|
||||||
|
<span class="ml-3" :class="stateDisplay.text" v-text="stateDisplay.label"></span>
|
||||||
|
</div>
|
||||||
|
<f-text id="base_url" v-model="inner.base_url" label="URL" hint="URL der Mailman Api"></f-text>
|
||||||
|
<f-text id="username" v-model="inner.username" label="Benutzername"></f-text>
|
||||||
|
<f-text id="password" v-model="inner.password" type="password" label="Passwort"></f-text>
|
||||||
|
<f-select id="all_list" v-model="inner.all_list" label="Liste für alle Mitglieder" name="all_list" :options="lists"></f-select>
|
||||||
|
<f-select id="all_parents_list" v-model="inner.all_parents_list" label="Liste für Eltern" name="all_parents_list" :options="lists"></f-select>
|
||||||
|
<f-select id="active_leaders_list" v-model="inner.active_leaders_list" label="Liste für aktive Leiter" name="active_leaders_list" :options="lists"></f-select>
|
||||||
|
<f-select id="passive_leaders_list" v-model="inner.passive_leaders_list" label="Liste für passive Leiter" name="passive_leaders_list" :options="lists"></f-select>
|
||||||
|
<div></div>
|
||||||
|
</form>
|
||||||
|
</setting-layout>
|
||||||
|
</page-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SettingLayout from './Layout.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SettingLayout,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {},
|
||||||
|
state: {},
|
||||||
|
lists: {},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
inner: {...this.data},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
stateDisplay() {
|
||||||
|
if (this.state === null) {
|
||||||
|
return {
|
||||||
|
text: 'text-gray-500',
|
||||||
|
icon: 'disabled',
|
||||||
|
label: 'Deaktiviert',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state
|
||||||
|
? {
|
||||||
|
text: 'text-green-500',
|
||||||
|
icon: 'check',
|
||||||
|
label: 'Verbindung erfolgreich.',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
text: 'text-red-500',
|
||||||
|
icon: 'close',
|
||||||
|
label: 'Verbindung fehlgeschlagen.',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$inertia.post('/setting/mailman', this.inner, {
|
||||||
|
onSuccess: (page) => {
|
||||||
|
this.$success('Einstellungen gespeichert.');
|
||||||
|
this.inner = page.props.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -4,20 +4,48 @@
|
||||||
<f-save-button form="modulesettingform"></f-save-button>
|
<f-save-button form="modulesettingform"></f-save-button>
|
||||||
</template>
|
</template>
|
||||||
<setting-layout>
|
<setting-layout>
|
||||||
<form id="modulesettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
|
<form id="modulesettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start"
|
||||||
|
@submit.prevent="submit">
|
||||||
<div class="col-span-full text-gray-100 mb-3">
|
<div class="col-span-full text-gray-100 mb-3">
|
||||||
<p class="text-sm">Hier kannst du Funktionen innerhalb von Adrema (Module) aktivieren oder deaktivieren und so den Funktionsumfang auf deine Bedürfnisse anpassen.</p>
|
<p class="text-sm">Hier kannst du Funktionen innerhalb von Adrema (Module) aktivieren oder deaktivieren
|
||||||
|
und so den Funktionsumfang auf deine Bedürfnisse anpassen.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<f-switch v-for="module in meta.modules" :id="module.id" v-model="data.modules" :value="module.id" size="sm" name="modules" :label="module.name"></f-switch>
|
<f-switch v-for="module in meta.modules" :id="module.id" v-model="inner.modules" :value="module.id"
|
||||||
|
size="sm" name="modules" :label="module.name"></f-switch>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</setting-layout>
|
</setting-layout>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script>
|
||||||
import {props, useSettings} from './useSettings.js';
|
import SettingLayout from './Layout.vue';
|
||||||
const innerProps = defineProps(props);
|
|
||||||
const {submit, data, meta, SettingLayout} = useSettings(innerProps);
|
export default {
|
||||||
|
components: {
|
||||||
|
SettingLayout,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
inner: { ...this.data.data },
|
||||||
|
meta: { ...this.data.meta },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$inertia.post('/setting/module', this.inner, {
|
||||||
|
onSuccess: () => this.$success('Einstellungen gespeichert.'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,16 +8,40 @@
|
||||||
<div class="col-span-full text-gray-100 mb-3">
|
<div class="col-span-full text-gray-100 mb-3">
|
||||||
<p class="text-sm">Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.</p>
|
<p class="text-sm">Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.</p>
|
||||||
</div>
|
</div>
|
||||||
<f-text id="mglnr" v-model="data.mglnr" label="Mitgliedsnummer"></f-text>
|
<f-text id="mglnr" v-model="inner.mglnr" label="Mitgliedsnummer"></f-text>
|
||||||
<f-text id="default_group_id" v-model="data.default_group_id" label="Standard-Gruppierung"></f-text>
|
<f-text id="default_group_id" v-model="inner.default_group_id" label="Standard-Gruppierung"></f-text>
|
||||||
<f-text id="password" v-model="data.password" label="Passwort" name="password" type="password"></f-text>
|
<f-text id="password" v-model="inner.password" label="Passwort" name="password" type="password"></f-text>
|
||||||
</form>
|
</form>
|
||||||
</setting-layout>
|
</setting-layout>
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script>
|
||||||
import {props, useSettings} from './useSettings.js';
|
import SettingLayout from './Layout.vue';
|
||||||
const innerProps = defineProps(props);
|
|
||||||
const {submit, data, meta, SettingLayout} = useSettings(innerProps);
|
export default {
|
||||||
|
components: {
|
||||||
|
SettingLayout,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
inner: {...this.data},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$inertia.post('/setting/nami', this.inner, {
|
||||||
|
onSuccess: () => this.$success('Einstellungen gespeichert.'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<template #right>
|
<template #right>
|
||||||
<f-save-button form="preventionform"></f-save-button>
|
<f-save-button form="preventionform"></f-save-button>
|
||||||
</template>
|
</template>
|
||||||
<setting-layout>
|
<setting-layout v-if="loaded">
|
||||||
<form id="preventionform" class="grow p-6" @submit.prevent="submit">
|
<form id="preventionform" class="grow p-6" @submit.prevent="submit">
|
||||||
<div class="col-span-full text-gray-100 mb-3">
|
<div class="col-span-full text-gray-100 mb-3">
|
||||||
<p class="text-sm">Hier kannst du Einstellungen zu Prävention setzen.</p>
|
<p class="text-sm">Hier kannst du Einstellungen zu Prävention setzen.</p>
|
||||||
|
@ -16,8 +16,22 @@
|
||||||
</page-layout>
|
</page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script lang="js" setup>
|
||||||
import {props, useSettings} from './useSettings.js';
|
import { ref } from 'vue';
|
||||||
const innerProps = defineProps(props);
|
import { useApiIndex } from '../../composables/useApiIndex.js';
|
||||||
const {submit, data, meta, SettingLayout} = useSettings(innerProps);
|
import SettingLayout from '../setting/Layout.vue';
|
||||||
|
|
||||||
|
const { axios, data, reload } = useApiIndex('/api/prevention', 'prevention');
|
||||||
|
const loaded = ref(false);
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
await reload();
|
||||||
|
loaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
await axios.post('/api/prevention', { ...data.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
<template>
|
|
||||||
<page-layout>
|
|
||||||
<template #toolbar>
|
|
||||||
<page-toolbar-button color="primary" icon="plus" @click.prevent="create">Neuer Benutzer</page-toolbar-button>
|
|
||||||
</template>
|
|
||||||
<ui-popup v-if="single !== null" :heading="single.id ? 'Benutzer bearbeiten' : 'Neuer Benutzer'" @close="cancel">
|
|
||||||
<form @submit.prevent="submit">
|
|
||||||
<section class="grid grid-cols-2 gap-3 mt-6">
|
|
||||||
<f-text id="firstname" v-model="single.firstname" name="firstname" label="Vorname" required></f-text>
|
|
||||||
<f-text id="lastname" v-model="single.lastname" name="lastname" label="Nachname" required></f-text>
|
|
||||||
</section>
|
|
||||||
<section class="flex mt-4 space-x-2">
|
|
||||||
<ui-button type="submit" class="btn-danger">Speichern</ui-button>
|
|
||||||
<ui-button class="btn-primary" @click.prevent="single = null">Abbrechen</ui-button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
</ui-popup>
|
|
||||||
<setting-layout>
|
|
||||||
<div class="w-full h-full pb-6">
|
|
||||||
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm hidden md:table">
|
|
||||||
<thead>
|
|
||||||
<th>Nachname</th>
|
|
||||||
<th>Vorname</th>
|
|
||||||
<th>Aktion</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tr v-for="(user, index) in data" :key="index">
|
|
||||||
<td v-text="user.lastname"></td>
|
|
||||||
<td v-text="user.firstname"></td>
|
|
||||||
<td>
|
|
||||||
<a v-tooltip="`Bearbeiten`" href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(user)"><ui-sprite src="pencil"></ui-sprite></a>
|
|
||||||
<a v-tooltip="`Löschen`" href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(user)"><ui-sprite src="pencil"></ui-sprite></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="px-6">
|
|
||||||
<ui-pagination class="mt-4" :value="meta" :only="['data']"></ui-pagination>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</setting-layout>
|
|
||||||
</page-layout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="js" setup>
|
|
||||||
import SettingLayout from '../setting/Layout.vue';
|
|
||||||
|
|
||||||
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
|
|
||||||
|
|
||||||
const props = defineProps(indexProps);
|
|
||||||
|
|
||||||
const {data, cancel, meta, single, create, edit} = useIndex(props.data);
|
|
||||||
</script>
|
|
|
@ -1,29 +0,0 @@
|
||||||
import {useIndex} from '../../composables/useInertiaApiIndex.js';
|
|
||||||
import SettingLayout from './Layout.vue';
|
|
||||||
|
|
||||||
export function useSettings(props) {
|
|
||||||
const {data, meta, router} = useIndex(props.data);
|
|
||||||
|
|
||||||
function submit() {
|
|
||||||
router.post(meta.value.links.store, {...data.value});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
submit,
|
|
||||||
data,
|
|
||||||
meta,
|
|
||||||
props,
|
|
||||||
SettingLayout,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
data: {
|
|
||||||
default: () => {
|
|
||||||
return {data: [], meta: {}};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export {props};
|
|
|
@ -3,9 +3,15 @@
|
||||||
use App\Contribution\Actions\GenerateApiAction as ContributionGenerateApiAction;
|
use App\Contribution\Actions\GenerateApiAction as ContributionGenerateApiAction;
|
||||||
use App\Form\Actions\FormApiListAction;
|
use App\Form\Actions\FormApiListAction;
|
||||||
use App\Form\Actions\RegisterAction;
|
use App\Form\Actions\RegisterAction;
|
||||||
|
use App\Prevention\Actions\SettingStoreAction as PreventionStoreAction;
|
||||||
use App\Group\Actions\GroupApiIndexAction;
|
use App\Group\Actions\GroupApiIndexAction;
|
||||||
|
use App\Prevention\Actions\SettingApiAction;
|
||||||
|
use App\User\Actions\IndexAction as UserIndexAction;
|
||||||
|
|
||||||
Route::post('/contribution-generate', ContributionGenerateApiAction::class)->name('api.contribution.generate')->middleware('client:contribution-generate');
|
Route::post('/contribution-generate', ContributionGenerateApiAction::class)->name('api.contribution.generate')->middleware('client:contribution-generate');
|
||||||
Route::post('/form/{form}/register', RegisterAction::class)->name('form.register');
|
Route::post('/form/{form}/register', RegisterAction::class)->name('form.register');
|
||||||
Route::get('/group/{group?}', GroupApiIndexAction::class)->name('api.group');
|
Route::get('/group/{group?}', GroupApiIndexAction::class)->name('api.group');
|
||||||
Route::get('/form', FormApiListAction::class)->name('api.form.index');
|
Route::get('/form', FormApiListAction::class)->name('api.form.index');
|
||||||
|
Route::get('/prevention', SettingApiAction::class)->name('api.prevention.index');
|
||||||
|
Route::post('/prevention', PreventionStoreAction::class)->name('api.prevention.store');
|
||||||
|
Route::get('/user', UserIndexAction::class)->name('api.user.index');
|
||||||
|
|
|
@ -19,6 +19,7 @@ use App\Invoice\Actions\InvoiceStoreAction;
|
||||||
use App\Course\Actions\CourseUpdateAction;
|
use App\Course\Actions\CourseUpdateAction;
|
||||||
use App\Dashboard\Actions\IndexAction as DashboardIndexAction;
|
use App\Dashboard\Actions\IndexAction as DashboardIndexAction;
|
||||||
use App\Efz\ShowEfzDocumentAction;
|
use App\Efz\ShowEfzDocumentAction;
|
||||||
|
use App\Fileshare\Actions\FileshareApiIndexAction;
|
||||||
use App\Fileshare\Actions\FileshareStoreAction;
|
use App\Fileshare\Actions\FileshareStoreAction;
|
||||||
use App\Fileshare\Actions\FileshareUpdateAction;
|
use App\Fileshare\Actions\FileshareUpdateAction;
|
||||||
use App\Fileshare\Actions\ListFilesAction;
|
use App\Fileshare\Actions\ListFilesAction;
|
||||||
|
@ -181,5 +182,6 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
||||||
// ------------------------------------ fileshare -----------------------------------
|
// ------------------------------------ fileshare -----------------------------------
|
||||||
Route::post('/fileshare', FileshareStoreAction::class)->name('fileshare.store');
|
Route::post('/fileshare', FileshareStoreAction::class)->name('fileshare.store');
|
||||||
Route::patch('/fileshare/{fileshare}', FileshareUpdateAction::class)->name('fileshare.update');
|
Route::patch('/fileshare/{fileshare}', FileshareUpdateAction::class)->name('fileshare.update');
|
||||||
|
Route::get('/api/fileshare', FileshareApiIndexAction::class)->name('api.fileshare.index');
|
||||||
Route::post('/api/fileshare/{fileshare}/files', ListFilesAction::class)->name('api.fileshare.files');
|
Route::post('/api/fileshare/{fileshare}/files', ListFilesAction::class)->name('api.fileshare.files');
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,8 +11,10 @@ use App\Membership\Actions\MassStoreAction;
|
||||||
use App\Membership\Actions\MembershipDestroyAction;
|
use App\Membership\Actions\MembershipDestroyAction;
|
||||||
use App\Membership\Actions\MembershipStoreAction;
|
use App\Membership\Actions\MembershipStoreAction;
|
||||||
use App\Subactivity;
|
use App\Subactivity;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Illuminate\Support\Facades\Queue;
|
use Illuminate\Support\Facades\Queue;
|
||||||
use Tests\EndToEndTestCase;
|
use Tests\EndToEndTestCase;
|
||||||
|
use Tests\TestCase;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Zoomyboy\LaravelNami\Fakes\MembershipFake;
|
use Zoomyboy\LaravelNami\Fakes\MembershipFake;
|
||||||
|
|
||||||
|
|
|
@ -10,19 +10,10 @@ class SettingTest extends TestCase
|
||||||
{
|
{
|
||||||
use DatabaseTransactions;
|
use DatabaseTransactions;
|
||||||
|
|
||||||
public function testItDisplaysView(): void
|
public function testSettingIndex(): void
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
|
InvoiceSettings::fake([
|
||||||
$this->get(route('setting.view', ['settingGroup' => 'bill']))
|
|
||||||
->assertOk()
|
|
||||||
->assertComponent('setting/Bill');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDisplaySettings(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
|
||||||
app(InvoiceSettings::class)->fill([
|
|
||||||
'from_long' => 'DPSG Stamm Muster',
|
'from_long' => 'DPSG Stamm Muster',
|
||||||
'from' => 'Stamm Muster',
|
'from' => 'Stamm Muster',
|
||||||
'mobile' => '+49 176 55555',
|
'mobile' => '+49 176 55555',
|
||||||
|
@ -34,33 +25,43 @@ class SettingTest extends TestCase
|
||||||
'iban' => 'DE05',
|
'iban' => 'DE05',
|
||||||
'bic' => 'SOLSDE',
|
'bic' => 'SOLSDE',
|
||||||
'rememberWeeks' => 6
|
'rememberWeeks' => 6
|
||||||
])->save();
|
]);
|
||||||
|
|
||||||
$this->get(route('setting.data', ['settingGroup' => 'bill']))
|
$response = $this->get('/setting/bill');
|
||||||
->assertOk()
|
|
||||||
->assertComponent('setting/Bill')
|
$response->assertOk();
|
||||||
->assertInertiaPath('data.from_long', 'DPSG Stamm Muster')
|
$this->assertInertiaHas([
|
||||||
->assertInertiaPath('data.from', 'Stamm Muster')
|
'from_long' => 'DPSG Stamm Muster',
|
||||||
->assertInertiaPath('data.mobile', '+49 176 55555')
|
'from' => 'Stamm Muster',
|
||||||
->assertInertiaPath('data.email', 'max@muster.de')
|
'mobile' => '+49 176 55555',
|
||||||
->assertInertiaPath('data.website', 'https://example.com')
|
'email' => 'max@muster.de',
|
||||||
->assertInertiaPath('data.address', 'Musterstr 4')
|
'website' => 'https://example.com',
|
||||||
->assertInertiaPath('data.place', 'Solingen')
|
'address' => 'Musterstr 4',
|
||||||
->assertInertiaPath('data.zip', '12345')
|
'place' => 'Solingen',
|
||||||
->assertInertiaPath('data.iban', 'DE05')
|
'zip' => '12345',
|
||||||
->assertInertiaPath('data.bic', 'SOLSDE')
|
'iban' => 'DE05',
|
||||||
->assertInertiaPath('data.rememberWeeks', 6);
|
'bic' => 'SOLSDE',
|
||||||
|
'remember_weeks' => 6
|
||||||
|
], $response, 'data');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItReturnsTabs(): void
|
public function testItReturnsTabs(): void
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
|
|
||||||
$this->get(route('setting.view', ['settingGroup' => 'bill']))
|
$response = $this->get('/setting/bill');
|
||||||
->assertInertiaPath('setting_menu.1.title', 'Rechnung')
|
|
||||||
->assertInertiaPath('setting_menu.1.url', url('/setting/bill'))
|
/** @var array<int, array{url: string, title: string, is_active: bool}> */
|
||||||
->assertInertiaPath('setting_menu.1.is_active', true)
|
$menus = $this->inertia($response, 'setting_menu');
|
||||||
->assertInertiaPath('setting_menu.0.is_active', false);
|
$this->assertTrue(
|
||||||
|
collect($menus)
|
||||||
|
->pluck('url')
|
||||||
|
->contains('/setting/bill')
|
||||||
|
);
|
||||||
|
|
||||||
|
$settingMenu = collect($menus)->first(fn ($menu) => '/setting/bill' === $menu['url']);
|
||||||
|
$this->assertTrue($settingMenu['is_active']);
|
||||||
|
$this->assertEquals('Rechnung', $settingMenu['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItCanChangeSettings(): void
|
public function testItCanChangeSettings(): void
|
||||||
|
@ -78,7 +79,7 @@ class SettingTest extends TestCase
|
||||||
'zip' => '12345',
|
'zip' => '12345',
|
||||||
'iban' => 'DE05',
|
'iban' => 'DE05',
|
||||||
'bic' => 'SOLSDE',
|
'bic' => 'SOLSDE',
|
||||||
'rememberWeeks' => 10
|
'remember_weeks' => 10
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response->assertRedirect('/setting/bill');
|
$response->assertRedirect('/setting/bill');
|
||||||
|
|
|
@ -11,20 +11,17 @@ class UserIndexTest extends TestCase
|
||||||
|
|
||||||
use DatabaseTransactions;
|
use DatabaseTransactions;
|
||||||
|
|
||||||
public function testItOpensSettingsPage(): void
|
public function testItListsUsers(): void
|
||||||
{
|
{
|
||||||
$this->login()->loginNami();
|
$this->login()->loginNami();
|
||||||
auth()->user()->update(['firstname' => 'Jane', 'lastname' => 'Doe']);
|
auth()->user()->update(['firstname' => 'Jane', 'lastname' => 'Doe']);
|
||||||
User::factory()->create(['firstname' => 'John', 'lastname' => 'Doe']);
|
User::factory()->create(['firstname' => 'John', 'lastname' => 'Doe']);
|
||||||
$anna = User::factory()->create(['firstname' => 'Anna', 'lastname' => 'Doe']);
|
$anna = User::factory()->create(['firstname' => 'Anna', 'lastname' => 'Doe']);
|
||||||
$this->get(route('setting.view', ['settingGroup' => 'user']))
|
$this->get(route('api.user.index'))
|
||||||
->assertOk()
|
->assertJsonPath('data.0.firstname', 'Anna')
|
||||||
->assertComponent('setting/User')
|
->assertJsonPath('data.0.lastname', 'Doe')
|
||||||
->assertInertiaPath('data.data.0.firstname', 'Anna')
|
->assertJsonPath('data.0.id', $anna->id)
|
||||||
->assertInertiaPath('data.data.0.lastname', 'Doe')
|
->assertJsonPath('data.1.firstname', 'Jane')
|
||||||
->assertInertiaPath('data.data.0.id', $anna->id)
|
->assertJsonPath('data.2.firstname', 'John');
|
||||||
->assertInertiaPath('data.data.1.firstname', 'Jane')
|
|
||||||
->assertInertiaPath('data.data.2.firstname', 'John')
|
|
||||||
->assertInertiaPath('data.meta.default.firstname', '');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,25 +16,25 @@ class SettingTest extends TestCase
|
||||||
{
|
{
|
||||||
$this->login()->loginNami();
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
$this->get('/setting/prevention')->assertComponent('setting/Prevention')->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReceivesSettings(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
$text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->toData();
|
$text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->toData();
|
||||||
app(PreventionSettings::class)->fill(['formmail' => $text])->save();
|
app(PreventionSettings::class)->fill(['formmail' => $text])->save();
|
||||||
|
|
||||||
$this->get(route('setting.view', ['settingGroup' => 'prevention']))
|
$this->get('/api/prevention')
|
||||||
->assertOk()
|
->assertJsonPath('data.formmail.blocks.0.data.text', 'lorem ipsum');
|
||||||
->assertComponent('setting/Prevention')
|
|
||||||
->assertInertiaPath('data.formmail.blocks.0.data.text', 'lorem ipsum')
|
|
||||||
->assertInertiaPath('store_url', route('setting.store', ['settingGroup' => 'prevention']));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItStoresSettings(): void
|
public function testItStoresSettings(): void
|
||||||
{
|
{
|
||||||
$this->login()->loginNami();
|
$this->login()->loginNami();
|
||||||
|
|
||||||
$route = route('setting.store', ['settingGroup' => 'prevention']);
|
$this->post('/api/prevention', ['formmail' => EditorRequestFactory::new()->text(50, 'new lorem')->create()])->assertOk();
|
||||||
$this
|
|
||||||
->from($route)
|
|
||||||
->post($route, ['formmail' => EditorRequestFactory::new()->text(50, 'new lorem')->create()])
|
|
||||||
->assertRedirect($route);
|
|
||||||
$this->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem']));
|
$this->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem']));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,25 +17,31 @@ class FileshareIndexActionTest extends FileshareTestCase
|
||||||
->name('lokaler Server')
|
->name('lokaler Server')
|
||||||
->create();
|
->create();
|
||||||
|
|
||||||
$this->get(route('setting.view', ['settingGroup' => 'fileshare']))
|
$this->get('/api/fileshare')
|
||||||
->assertComponent('setting/Fileshare')
|
->assertJsonPath('data.0.name', 'lokaler Server')
|
||||||
->assertInertiaPath('data.data.0.name', 'lokaler Server')
|
->assertJsonPath('data.0.type', OwncloudConnection::class)
|
||||||
->assertInertiaPath('data.data.0.type', OwncloudConnection::class)
|
->assertJsonPath('data.0.config.user', 'badenpowell')
|
||||||
->assertInertiaPath('data.data.0.config.user', 'badenpowell')
|
->assertJsonPath('data.0.config.password', 'secret')
|
||||||
->assertInertiaPath('data.data.0.config.password', 'secret')
|
->assertJsonPath('data.0.config.base_url', env('TEST_OWNCLOUD_DOMAIN'))
|
||||||
->assertInertiaPath('data.data.0.config.base_url', env('TEST_OWNCLOUD_DOMAIN'))
|
->assertJsonPath('data.0.id', $connection->id)
|
||||||
->assertInertiaPath('data.data.0.id', $connection->id)
|
->assertJsonPath('data.0.is_active', true)
|
||||||
->assertInertiaPath('data.data.0.is_active', true)
|
->assertJsonPath('data.0.type_human', 'Owncloud')
|
||||||
->assertInertiaPath('data.data.0.type_human', 'Owncloud')
|
->assertJsonPath('data.0.links.update', route('fileshare.update', ['fileshare' => $connection]))
|
||||||
->assertInertiaPath('data.data.0.links.update', route('fileshare.update', ['fileshare' => $connection]))
|
->assertJsonPath('meta.default.name', '')
|
||||||
->assertInertiaPath('data.meta.default.name', '')
|
->assertJsonPath('meta.links.store', route('fileshare.store'))
|
||||||
->assertInertiaPath('data.meta.links.store', route('fileshare.store'))
|
->assertJsonPath('meta.types.0.id', NextcloudConnection::class)
|
||||||
->assertInertiaPath('data.meta.types.0.id', NextcloudConnection::class)
|
->assertJsonPath('meta.types.0.name', 'Nextcloud')
|
||||||
->assertInertiaPath('data.meta.types.0.name', 'Nextcloud')
|
->assertJsonPath('meta.types.0.defaults.base_url', '')
|
||||||
->assertInertiaPath('data.meta.types.0.defaults.base_url', '')
|
->assertJsonPath('meta.types.1.id', OwncloudConnection::class)
|
||||||
->assertInertiaPath('data.meta.types.1.id', OwncloudConnection::class)
|
->assertJsonPath('meta.types.1.name', 'Owncloud')
|
||||||
->assertInertiaPath('data.meta.types.1.name', 'Owncloud')
|
->assertJsonPath('meta.types.1.defaults.base_url', '')
|
||||||
->assertInertiaPath('data.meta.types.1.defaults.base_url', '')
|
->assertJsonPath('meta.types.0.fields.1', ['label' => 'Benutzer', 'key' => 'user', 'type' => 'text']);
|
||||||
->assertInertiaPath('data.meta.types.0.fields.1', ['label' => 'Benutzer', 'key' => 'user', 'type' => 'text']);
|
}
|
||||||
|
|
||||||
|
public function testItRendersComponent(): void
|
||||||
|
{
|
||||||
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
|
|
||||||
|
$this->get('/setting/fileshare')->assertComponent('fileshare/Index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue