Compare commits

...

5 Commits

Author SHA1 Message Date
Philipp Lang 3cc35348ff Add search params for input
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is failing Details
2023-05-08 15:11:16 +02:00
Philipp Lang 48b1808497 Add test for login check 2023-05-07 21:17:28 +02:00
Philipp Lang ab53a80c4d Add search api 2023-05-07 21:15:17 +02:00
Philipp Lang f690246a7e Add action for login check 2023-05-05 13:22:38 +02:00
Philipp Lang 39a269d10a fixed tests 2023-05-05 12:28:22 +02:00
23 changed files with 680 additions and 270 deletions

View File

@ -11,7 +11,7 @@ class RedirectIfNotInitializedMiddleware
/**
* @var array<int, string>
*/
public array $dontRedirect = ['initialize.form', 'initialize.store'];
public array $dontRedirect = ['initialize.form', 'initialize.store', 'nami.login-check', 'nami.search'];
/**
* Handle an incoming request.

View File

@ -13,9 +13,7 @@ use App\Initialize\InitializeMembers;
use App\Initialize\InitializeNationalities;
use App\Initialize\InitializeRegions;
use App\Setting\NamiSettings;
use Illuminate\Console\Command;
use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\ValidationException;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
use Zoomyboy\LaravelNami\Nami;
@ -60,6 +58,7 @@ class InitializeAction
'mglnr' => 'required|numeric',
'password' => 'required|string',
'group_id' => 'required|numeric',
'params' => 'required|array',
];
}
@ -73,32 +72,17 @@ class InitializeAction
];
}
public function asController(ActionRequest $request): RedirectResponse
public function asController(ActionRequest $request, NamiSettings $settings): RedirectResponse
{
$api = Nami::freshLogin($request->input('mglnr'), $request->input('password'));
if (!$api->hasGroup($request->input('group_id'))) {
throw ValidationException::withMessages(['nami' => 'Gruppierung nicht gefunden.']);
}
$this->setApi((int) $request->input('mglnr'), $request->input('password'), (int) $request->input('group_id'));
self::dispatch();
return redirect()->route('home')->success('Initialisierung beauftragt. Wir benachrichtigen dich per Mail wenn alles fertig ist.');
}
public function asCommand(Command $command): void
{
$this->setApi((int) $command->option('mglnr'), $command->option('password'), (int) $command->option('group'));
self::dispatch();
}
private function setApi(int $mglnr, string $password, int $groupId): void
{
$settings = app(NamiSettings::class);
$settings->mglnr = $mglnr;
$settings->password = $password;
$settings->default_group_id = $groupId;
$settings->mglnr = (int) $request->input('mglnr');
$settings->password = $request->input('password');
$settings->default_group_id = (int) $request->input('group_id');
$settings->search_params = $request->input('params');
$settings->save();
self::dispatch();
return redirect()->route('home');
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Initialize\Actions;
use Illuminate\Http\Response;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
use Zoomyboy\LaravelNami\Nami;
class NamiLoginCheckAction
{
use AsAction;
/**
* @param array{mglnr: string, password: string} $input
*/
public function handle(array $input): void
{
Nami::freshLogin((int) $input['mglnr'], $input['password']);
}
/**
* @return array<string, string>
*/
public function rules(): array
{
return [
'mglnr' => 'required|numeric|min:0',
'password' => 'required|string',
];
}
public function asController(ActionRequest $request): Response
{
$this->handle($request->validated());
return response()->noContent();
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Initialize\Actions;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
use Zoomyboy\LaravelNami\Api;
use Zoomyboy\LaravelNami\Data\MemberEntry;
use Zoomyboy\LaravelNami\Nami;
class NamiSearchAction
{
use AsAction;
/**
* @param array<string, mixed> $params
*
* @return LengthAwarePaginator<MemberEntry>
*/
public function handle(Api $api, int $page, array $params): LengthAwarePaginator
{
return $api->pageSearch($params, $page, 10);
}
/**
* @return array<string, string>
*/
public function rules(): array
{
return [
'mglnr' => 'required|numeric|min:0',
'password' => 'required|string',
'params' => 'array',
];
}
/**
* @return LengthAwarePaginator<MemberEntry>
*/
public function asController(ActionRequest $request): LengthAwarePaginator
{
$api = Nami::login($request->input('mglnr'), $request->input('password'));
return $this->handle($api, $request->input('page', 1), $request->input('params'));
}
}

View File

@ -21,9 +21,10 @@ class InitializeMembers
public function handle(Api $api): void
{
$settings = app(NamiSettings::class);
Redis::delete('members');
$jobs = $api->search([])->map(function (NamiMemberEntry $member) use ($api) {
$jobs = $api->search($settings->search_params)->map(function (NamiMemberEntry $member) use ($api) {
return FullMemberAction::makeJob($api, $member->groupId, $member->id, 'members');
})->toArray();

View File

@ -15,6 +15,9 @@ class NamiSettings extends Settings
public int $default_group_id;
/** @var array<string, string> */
public array $search_params;
public static function group(): string
{
return 'nami';

View File

@ -0,0 +1,11 @@
<?php
use Spatie\LaravelSettings\Migrations\SettingsMigration;
class CreateNamiSearchSetting extends SettingsMigration
{
public function up(): void
{
$this->migrator->add('nami.search_params', []);
}
}

91
package-lock.json generated
View File

@ -7,6 +7,7 @@
"dependencies": {
"@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia-vue": "^0.8.0",
"@tailwindcss/typography": "^0.5.9",
"lodash": "^4.17.21",
"merge": "^2.1.1",
"pinia": "^2.0.35",
@ -1994,7 +1995,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@ -2007,7 +2007,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"engines": {
"node": ">= 8"
}
@ -2016,7 +2015,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@ -2227,6 +2225,32 @@
"node": ">= 10"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -2856,7 +2880,6 @@
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
"integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
"dev": true,
"dependencies": {
"acorn": "^7.0.0",
"acorn-walk": "^7.0.0",
@ -2867,7 +2890,6 @@
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
@ -2879,7 +2901,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@ -2986,7 +3007,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -2998,8 +3018,7 @@
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
},
"node_modules/argparse": {
"version": "2.0.1",
@ -3230,7 +3249,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -3325,7 +3343,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@ -3527,7 +3544,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"engines": {
"node": ">= 6"
}
@ -3589,7 +3605,6 @@
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
@ -3616,7 +3631,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@ -4208,7 +4222,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": {
"cssesc": "bin/cssesc"
},
@ -4375,7 +4388,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz",
"integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -4428,7 +4440,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
"integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
"dev": true,
"dependencies": {
"acorn-node": "^1.8.2",
"defined": "^1.0.0",
@ -4444,8 +4455,7 @@
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
"node_modules/diffie-hellman": {
"version": "5.0.3",
@ -4479,8 +4489,7 @@
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"node_modules/dns-equal": {
"version": "1.0.0",
@ -5077,7 +5086,6 @@
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -5093,7 +5101,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@ -5126,7 +5133,6 @@
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
}
@ -5211,7 +5217,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -5402,7 +5407,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@ -5482,7 +5486,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.3"
},
@ -6063,7 +6066,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@ -6107,7 +6109,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -6124,7 +6125,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@ -6136,7 +6136,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
@ -6497,7 +6496,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"dev": true,
"engines": {
"node": ">=10"
}
@ -6551,6 +6549,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@ -6572,6 +6575,11 @@
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -6732,7 +6740,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"engines": {
"node": ">= 8"
}
@ -6750,7 +6757,6 @@
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
@ -6886,7 +6892,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -7023,7 +7028,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -7085,7 +7089,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"engines": {
"node": ">= 6"
}
@ -7415,7 +7418,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
},
@ -7676,7 +7678,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"dependencies": {
"camelcase-css": "^2.0.1"
},
@ -7695,7 +7696,6 @@
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"dev": true,
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
@ -7903,7 +7903,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
"integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.10"
},
@ -8103,7 +8102,6 @@
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
"integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -8298,7 +8296,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
@ -8318,7 +8315,6 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true,
"engines": {
"node": ">=10"
},
@ -8417,7 +8413,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@ -8623,7 +8618,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true,
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@ -8658,7 +8652,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
@ -9356,7 +9349,6 @@
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz",
"integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==",
"dev": true,
"dependencies": {
"arg": "^5.0.2",
"chokidar": "^3.5.3",
@ -9526,7 +9518,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@ -10568,7 +10559,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true,
"engines": {
"node": ">=0.4"
}
@ -10591,7 +10581,6 @@
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}

View File

@ -27,6 +27,7 @@
"dependencies": {
"@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia-vue": "^0.8.0",
"@tailwindcss/typography": "^0.5.9",
"lodash": "^4.17.21",
"merge": "^2.1.1",
"pinia": "^2.0.35",

@ -1 +1 @@
Subproject commit 75983ebb4bad205629b57e18915b2f15fa38e835
Subproject commit 33875d36fa5bd6fab4147e95f4aa705092f42d93

View File

@ -1,110 +1,111 @@
.tooltip {
display: block !important;
z-index: 10000;
display: block !important;
max-width: 50vw;
z-index: 10000;
.tooltip-inner {
@apply bg-primary-900;
color: white;
border-radius: 8px;
padding: 5px 10px 4px;
}
.tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: black;
@apply border-primary-900;
z-index: 1;
}
&[x-placement^="top"] {
margin-bottom: 5px;
.tooltip-inner {
@apply bg-primary-900;
color: white;
border-radius: 8px;
padding: 5px 10px 4px;
}
.tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="bottom"] {
margin-top: 5px;
.tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="right"] {
margin-left: 5px;
.tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&[x-placement^="left"] {
margin-right: 5px;
.tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&.popover {
$color: #f9f9f9;
.popover-inner {
background: $color;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, .1);
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: black;
@apply border-primary-900;
z-index: 1;
}
.popover-arrow {
border-color: $color;
&[x-placement^='top'] {
margin-bottom: 5px;
.tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
}
&[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity .15s, visibility .15s;
}
&[x-placement^='bottom'] {
margin-top: 5px;
&[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity .15s;
}
.tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^='right'] {
margin-left: 5px;
.tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&[x-placement^='left'] {
margin-right: 5px;
.tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&.popover {
$color: #f9f9f9;
.popover-inner {
background: $color;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, 0.1);
}
.popover-arrow {
border-color: $color;
}
}
&[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity 0.15s, visibility 0.15s;
}
&[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity 0.15s;
}
}

View File

@ -1,9 +1,13 @@
<template>
<label class="flex relative field-switch cursor-pointer" :for="id" :class="{
'items-center flex-row-reverse space-x-3 space-x-reverse justify-end': inline,
'flex-col': !inline,
[sizes[size].wrap]: true,
}">
<label
class="flex relative field-switch cursor-pointer"
:for="id"
:class="{
'items-center flex-row-reverse space-x-3 space-x-reverse justify-end': inline,
'flex-col': !inline,
[sizes[size].wrap]: true,
}"
>
<span
v-if="label"
class="font-semibold leading-none text-gray-400"
@ -13,37 +17,20 @@
}"
>{{ label }}</span
>
<div class="relative inner-field mt-1" :class="`h-field-${fieldSize}`">
<input
:id="id"
type="checkbox"
:name="name"
:value="value"
v-model="v"
:disabled="disabled"
class="absolute peer"
@keypress="$emit('keypress', $event)"
/>
<span
class="relative cursor-pointer peer-focus:bg-red-500 flex grow display"
:class="{'bg-switch': v === true, 'bg-gray-700': v === false}"
>
<span
><svg-sprite
class="relative text-gray-400 flex-none"
:class="{'w-2 h-2': size === 'sm' || size == 'xs', 'w-4 h-4': size === 'base'}"
src="check"
></svg-sprite
></span>
<span
><svg-sprite
class="relative text-gray-400 flex-none"
:class="{'w-2 h-2': size === 'sm' || size == 'xs', 'w-4 h-4': size === 'base'}"
src="close"
></svg-sprite
></span>
<var class="absolute overlay bg-gray-400 rounded top-0"></var>
<div class="relative flex items-center inner-field mt-1" :class="`h-field-${fieldSize}`">
<span>
<input :id="id" type="checkbox" :name="name" :value="value" v-model="v" :disabled="disabled" class="absolute peer" @keypress="$emit('keypress', $event)" />
<span class="relative cursor-pointer peer-focus:bg-red-500 flex grow display" :class="{'bg-switch': v === true, 'bg-gray-700': v === false}">
<span><svg-sprite class="relative text-gray-400 flex-none" :class="{'w-2 h-2': size === 'sm' || size == 'xs', 'w-4 h-4': size === 'base'}" src="check"></svg-sprite></span>
<span><svg-sprite class="relative text-gray-400 flex-none" :class="{'w-2 h-2': size === 'sm' || size == 'xs', 'w-4 h-4': size === 'base'}" src="close"></svg-sprite></span>
<var class="absolute overlay bg-gray-400 rounded top-0"></var>
</span>
</span>
<div v-if="hint" class="ml-2 info-wrap">
<div v-tooltip="hint">
<svg-sprite src="info-button" class="info-button w-4 h-4 text-primary-700"></svg-sprite>
</div>
</div>
</div>
</label>
</template>
@ -73,6 +60,9 @@ export default {
event: 'input',
},
props: {
hint: {
default: null,
},
inline: {
default: false,
type: Boolean,

View File

@ -362,12 +362,6 @@ export default {
<style scope>
.bg-inset {
background: linear-gradient(
to bottom,
hsl(247.5, 66.7%, 97.6%) 0%,
hsl(247.5, 66.7%, 97.6%) 41%,
hsl(0deg 0% 100%) 41%,
hsl(180deg 0% 100%) 100%
);
background: linear-gradient(to bottom, hsl(247.5, 66.7%, 97.6%) 0%, hsl(247.5, 66.7%, 97.6%) 41%, hsl(0deg 0% 100%) 41%, hsl(180deg 0% 100%) 100%);
}
</style>

View File

@ -21,7 +21,8 @@
export default {
props: {
only: {
required: true,
default: null,
required: false,
},
value: {
required: true,
@ -33,6 +34,11 @@ export default {
},
methods: {
goto(page) {
if (this.$listeners.reload) {
this.$emit('reload', page.page);
return;
}
var params = new URLSearchParams(window.location.search);
params.set('page', page.page);

View File

@ -0,0 +1,16 @@
<template>
<div id="app" class="bg-gray-900 font-sans flex flex-col grow items-center justify-center">
<v-notification class="fixed z-40 right-0 bottom-0 mb-3 mr-3"></v-notification>
<div class="bg-gray-800 rounded-xl overflow-hidden shadow-lg p-6">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
components: {
VNotification: () => import('../components/VNotification.vue'),
},
};
</script>

View File

@ -1,37 +1,211 @@
<template>
<page-layout>
<div class="p-6 grid h-full items-center justify-center">
<div class="rounded border-primary-700 border border-solid p-6">
<div class="text-lg text-gray-200 text-sm">
Willkommen bei ScoutRobot.<br />
Bitte gib deine Zugangsdaten ein,<br />
<div>
<div v-if="step === 0">
<div class="prose prose-invert">
<p>Willkommen im Adrema-Setup.<br /></p>
<p>
Bitte gib deine NaMi-Zugangsdaten ein,<br />
um eine erste Synchronisation durchzuführen.
</div>
<form @submit.prevent="submit" class="grid gap-3 mt-5">
<f-text v-model="values.mglnr" label="Mitgliedsnummer" name="mglnr" id="mglnr"></f-text>
<f-text v-model="values.password" type="password" label="Passwort" name="password" id="password"></f-text>
<f-text v-model="values.group_id" label="Gruppierungsnummer" name="group_id" id="group_id"></f-text>
<button type="submit" class="btn w-full btn-primary mt-6 inline-block">Jetzt initialisieren</button>
</form>
</p>
</div>
<form @submit.prevent="check" class="grid gap-3 mt-5">
<f-text v-model="values.mglnr" label="Mitgliedsnummer" name="mglnr" id="mglnr" type="tel" required></f-text>
<f-text v-model="values.password" type="password" label="Passwort" name="password" id="password" required></f-text>
<button type="submit" class="btn w-full btn-primary mt-6 inline-block">Weiter</button>
</form>
</div>
</page-layout>
<div v-if="step === 1" class="flex flex-col" style="width: 90vw; height: 90vh">
<form @submit.prevent="storeSearch" class="border-2 border-primary-700 border-solid p-3 rounded-lg grid grid-cols-3 gap-3 flex-none">
<div class="prose prose-invert max-w-none col-span-full">
<p>
Lege hier die Suchkriterien für den Abruf der Mitglieder-Daten fest. Mit diesen Suchkriterien wird im Anschluss eine Mitgliedersuche in NaMi durchgeführt. Alle Mitglieder, die
dann dort auftauchen werden in die Adrema übernommen. Dir wird hier eine Vorschau eingeblendet, damit du sicherstellen kannst, dass die Suchkriterien die richtigen sind.
</p>
<p>
Außerdem werden diese Suchkriterien bei jedem neuen Abgleich (der automatisch täglich erfolgt) angewendet. Du kannst die Suchkriterien in den globalen Einstellungen jederzeit
ändern.
</p>
</div>
<f-text
v-model="values.params.gruppierung1Id"
label="Diözesan-Gruppierung"
name="gruppierung1Id"
id="gruppierung1Id"
type="tel"
size="sm"
@input="search"
hint="Gruppierungs-Nummer einer Diözese, auf die die Mitglieder passen sollen. I.d.R. ist das die Gruppierungsnummer deiner Diözese. Entspricht dem Feld '1. Ebene' in der NaMi Suche."
></f-text>
<f-text
v-model="values.params.gruppierung2Id"
label="Bezirks-Gruppierung"
name="gruppierung2Id"
id="gruppierung2Id"
type="tel"
hint="Gruppierungs-Nummer eines Bezirks, auf die die Mitglieder passen sollen. I.d.R. ist das die Gruppierungsnummer deines Bezirks. Entspricht dem Feld '2. Ebene' in der NaMi Suche. Fülle dieses Feld aus, um Mitglieder auf einen bestimmten Bezirk zu begrenzen."
:disabled="!values.params.gruppierung1Id"
@input="search"
size="sm"
></f-text>
<f-text
v-model="values.params.gruppierung3Id"
label="Stammes-Gruppierung"
name="gruppierung3Id"
id="gruppierung3Id"
type="tel"
size="sm"
@input="search"
hint="Gruppierungs-Nummer deines Stammes, auf die die Mitglieder passen sollen. I.d.R. ist das die Gruppierungsnummer deines Stammes. Entspricht dem Feld '3. Ebene' in der NaMi Suche. Fülle dieses Feld aus, um Mitglieder auf einen bestimmten Stamm zu beschränken."
:disabled="!values.params.gruppierung1Id || !values.params.gruppierung2Id"
></f-text>
<f-select
v-model="values.params.mglStatusId"
label="Mitglieds-Status"
name="mglStatusId"
id="mglStatusId"
size="sm"
@input="search"
:options="states"
hint="Wähle hier etwas aus, um nur aktive oder nur inaktive Mitglieder zu synchronisieren. Wir empfehlen dir, dies so zu belassen und Mitglieder ohne 'Datenweiterverwendung' gänzlich zu löschen, um Karteileichen zu entfernen."
></f-select>
<f-switch
v-model="values.params.inGrp"
label="In Gruppierung suchen"
name="inGrp"
id="inGrp"
@input="search"
hint="Mitglieder finden, die direktes Mitglied in der kleinsten befüllten Gruppierung sind."
size="sm"
></f-switch>
<f-switch
v-model="values.params.unterhalbGrp"
label="Unterhalb Gruppierung suchen"
name="unterhalbGrp"
id="unterhalbGrp"
@input="search"
hint="Mitglieder finden, die direktes Mitglied in einer Untergruppe der kleinsten befüllten Gruppierung sind."
size="sm"
></f-switch>
<div class="col-span-full">
<button type="submit" class="btn btn-primary btn-sm">Weiter</button>
</div>
</form>
<section class="grow border-2 border-primary-700 border-solid p-3 rounded-lg mt-4" v-if="preview !== null && preview.data.length">
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm hidden md:table">
<thead>
<th>GruppierungsNr</th>
<th>MitgliedsNr</th>
<th>Nachname</th>
<th>Vorname</th>
<th>Geburtsdatum</th>
</thead>
<tr v-for="(member, index) in preview.data" :key="index">
<td v-text="member.groupId"></td>
<td v-text="member.memberId"></td>
<td v-text="member.lastname"></td>
<td v-text="member.firstname"></td>
<td v-text="member.birthday_human"></td>
</tr>
</table>
<div v-if="preview !== null" class="px-6">
<v-pages class="mt-4" :value="preview" @reload="reloadPage"></v-pages>
</div>
</section>
<section class="grow items-center justify-center flex text-xl text-gray-200 border-2 border-primary-700 border-solid p-3 rounded-lg mt-4" v-else>Keine Mitglieder gefunden</section>
</div>
<div v-if="step === 2">
<div class="prose prose-invert">
<p>Bitte gib hier deine Standard-Gruppierungsnummer ein.</p>
<p>Dieser Gruppierung werden Mitglieder automatisch zugeordnet,<br />falls nichts anderes angegeben wurde.</p>
<p>I.d.R. ist das z.B. die Nummer deines Stammes, wenn du als StaVo mit Adrema Daten verwaltest.</p>
</div>
<form @submit.prevent="submit" class="grid gap-3 mt-5">
<f-text v-model="values.group_id" label="Gruppierungs-Nummer" name="groupId" id="groupId" type="tel" required></f-text>
<button type="submit" class="btn w-full btn-primary mt-6 inline-block">Weiter</button>
</form>
</div>
<div v-if="step === 3">
<div class="prose prose-invert">
<p>Wir werden nun die Mitgliederdaten anhand deiner festgelegten Kriterien abrufen.</p>
<p>Per Klick auf "Abschließen" gelangst du zum Dashboard</p>
<p>Viel Spaß mit Adrema</p>
</div>
<a href="/" class="mt-5 inline-block btn btn-primary">Abschließen</a>
</div>
</div>
</template>
<script>
import InstallLayout from '../../layouts/InstallLayout.vue';
import hasFlash from '../../mixins/hasFlash.js';
import debounce from 'lodash/debounce';
export default {
layout: InstallLayout,
mixins: [hasFlash],
data: function () {
return {
preview: null,
states: [
{id: 'INAKTIV', name: 'Inaktiv'},
{id: 'AKTIV', name: 'Aktiv'},
],
step: 0,
values: {
mglnr: '',
password: '',
group_id: '',
params: {
mglStatusId: 'AKTIV',
gruppierung1Id: '',
gruppierung2Id: '',
gruppierung3Id: '',
inGrp: true,
unterhalbGrp: true,
},
},
};
},
methods: {
submit() {
this.$inertia.post('/initialize', this.values);
async submit() {
try {
await this.axios.post('/initialize', this.values);
this.step = 3;
} catch (e) {
this.errorsFromException(e);
}
},
async storeSearch() {
this.values.group_id = this.values.params.gruppierung3Id ? this.values.params.gruppierung3Id : this.values.params.gruppierung2Id;
this.step = 2;
},
async reloadPage(page) {
await this.loadSearchResult(page);
},
async check() {
try {
await this.axios.post('/nami/login-check', this.values);
this.step = 1;
await this.loadSearchResult(1);
} catch (e) {
this.errorsFromException(e);
}
},
search: debounce(async function () {
await this.loadSearchResult(1);
}, 500),
async loadSearchResult(page) {
try {
var result = await this.axios.post('/nami/search', {...this.values, page: page});
this.preview = result.data;
} catch (e) {
this.errorsFromException(e);
}
},
},
};

View File

@ -17,6 +17,8 @@ use App\Dashboard\Actions\IndexAction as DashboardIndexAction;
use App\Efz\ShowEfzDocumentAction;
use App\Initialize\Actions\InitializeAction;
use App\Initialize\Actions\InitializeFormAction;
use App\Initialize\Actions\NamiLoginCheckAction;
use App\Initialize\Actions\NamiSearchAction;
use App\Member\Actions\ExportAction;
use App\Member\Actions\MemberResyncAction;
use App\Member\Actions\MemberShowAction;
@ -38,6 +40,8 @@ Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void {
Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/', DashboardIndexAction::class)->name('home');
Route::post('/nami/login-check', NamiLoginCheckAction::class)->name('nami.login-check');
Route::post('/nami/search', NamiSearchAction::class)->name('nami.search');
Route::post('/api/member/search', SearchAction::class)->name('member.search');
Route::get('/initialize', InitializeFormAction::class)->name('initialize.form');
Route::post('/initialize', InitializeAction::class)->name('initialize.store');

9
tailwind.config.js vendored
View File

@ -1,12 +1,7 @@
const {colors} = require('tailwindcss/defaultTheme');
module.exports = {
content: [
'resources/js/views/**/*.vue',
'resources/js/components/**/*.vue',
'resources/js/layouts/**/*.vue',
'resources/views/**/*.blade.php',
],
content: ['resources/js/views/**/*.vue', 'resources/js/components/**/*.vue', 'resources/js/layouts/**/*.vue', 'resources/views/**/*.blade.php'],
theme: {
extend: {
colors: {
@ -30,4 +25,6 @@ module.exports = {
},
},
},
plugins: [require('@tailwindcss/typography')],
};

View File

@ -5,10 +5,10 @@ namespace Tests\Feature\Initialize;
use App\Initialize\Actions\InitializeAction;
use App\Setting\NamiSettings;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Queue;
use Tests\RequestFactories\InitializeRequestFactory;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Authentication\Auth;
use Zoomyboy\LaravelNami\Fakes\GroupFake;
class InitializeActionTest extends TestCase
{
@ -23,22 +23,6 @@ class InitializeActionTest extends TestCase
$response->assertRedirect('/login');
}
public function testItSetsSettingsBeforeRunningInitializer(): void
{
$this->withoutExceptionHandling()->login();
InitializeAction::partialMock()->shouldReceive('handle')->andReturn(true);
Auth::success(12345, 'secret');
app(GroupFake::class)->fetches(null, [185 => ['name' => 'testgroup']]);
$response = $this->post('/initialize', $this->factory()->withCredentials(12345, 'secret')->withGroup(185)->create());
$response->assertRedirect('/');
$settings = app(NamiSettings::class);
$this->assertEquals(12345, $settings->mglnr);
$this->assertEquals('secret', $settings->password);
$this->assertEquals(185, $settings->default_group_id);
}
public function testItValidatesSetupInfo(): void
{
$this->login();
@ -59,15 +43,20 @@ class InitializeActionTest extends TestCase
$this->assertErrors(['nami' => 'NaMi Login fehlgeschlagen.'], $response);
}
public function testItValidatesGroupExistance(): void
public function testItSetsSettings(): void
{
Queue::fake();
Auth::success(100222, 'secret');
$this->login();
Auth::success(12345, 'secret');
app(GroupFake::class)->fetches(null, []);
$response = $this->post('/initialize', $this->factory()->withCredentials(12345, 'secret')->withGroup(185)->create());
$response = $this->post('/initialize', $this->factory()->withCredentials(100222, 'secret')->withGroup(77)->withParams(['gruppierung1Id' => '66777'])->create());
$this->assertErrors(['nami' => 'Gruppierung nicht gefunden.'], $response);
$response->assertRedirect('/');
$settings = app(NamiSettings::class);
$this->assertEquals(100222, $settings->mglnr);
$this->assertEquals('secret', $settings->password);
$this->assertEquals(77, $settings->default_group_id);
$this->assertEquals('66777', $settings->search_params['gruppierung1Id']);
}
private function factory(): InitializeRequestFactory

View File

@ -19,7 +19,7 @@ class InitializeMembersTest extends TestCase
{
$this->loginNami();
$api = app(NamiSettings::class)->login();
app(SearchFake::class)->fetches(1, 0, [
app(SearchFake::class)->fetches(1, 0, 100, [
MemberEntry::factory()->toMember(['groupId' => 100, 'id' => 20]),
]);
FullMemberAction::shouldRun()->once()->shouldReceive('configureJob');
@ -30,7 +30,7 @@ class InitializeMembersTest extends TestCase
public function testFetchesMembersViaCommandLine(): void
{
$this->loginNami();
app(SearchFake::class)->fetches(1, 0, [
app(SearchFake::class)->fetches(1, 0, 100, [
MemberEntry::factory()->toMember(['groupId' => 100, 'id' => 20]),
]);
FullMemberAction::shouldRun()->once()->shouldReceive('configureJob');

View File

@ -0,0 +1,94 @@
<?php
namespace Tests\Feature\Initializer;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Authentication\Auth;
use Zoomyboy\LaravelNami\Data\MemberEntry;
use Zoomyboy\LaravelNami\Fakes\SearchFake;
class SearchTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
$this->login();
}
public function testItSearchesForMembers(): void
{
$this->withoutExceptionHandling();
app(SearchFake::class)->fetches(1, 0, 10, [
MemberEntry::factory()->state(['id' => 2, 'groupId' => 100, 'firstname' => 'Max', 'lastname' => 'Muster', 'birthday' => '2013-07-04 00:00:00'])->toMember(),
MemberEntry::factory()->state(['id' => 2, 'groupId' => 150, 'firstname' => 'Jane', 'lastname' => 'Muster', 'birthday' => '2013-07-04 00:00:00'])->toMember(),
]);
Auth::success(333, 'secret');
$repsonse = $this->postJson('/nami/search', [
'params' => [
'gruppierung1Id' => 100,
'gruppierung2Id' => 101,
],
'mglnr' => 333,
'password' => 'secret',
]);
$repsonse->assertOk();
$repsonse->assertJsonPath('data.0.birthday_human', '04.07.2013');
$repsonse->assertJsonPath('data.0.firstname', 'Max');
$repsonse->assertJsonPath('data.0.lastname', 'Muster');
$repsonse->assertJsonPath('data.0.id', 2);
$repsonse->assertJsonPath('data.0.groupId', 100);
app(SearchFake::class)->assertFetched(1, 0, 10, [
'gruppierung1Id' => 100,
'gruppierung2Id' => 101,
]);
}
public function testItDoesntNeedFirstname(): void
{
$this->withoutExceptionHandling();
app(SearchFake::class)->fetches(1, 0, 10, [
MemberEntry::factory()->noFirstname()->toMember(),
]);
Auth::success(333, 'secret');
$this->postJson('/nami/search', [
'params' => [],
'mglnr' => 333,
'password' => 'secret',
'birthday_human' => null,
'agegroup' => null,
])->assertJsonPath('data.0.firstname', null);
}
public function testItGetsPageInformation(): void
{
$this->withoutExceptionHandling();
app(SearchFake::class)->fetches(2, 10, 10, [
MemberEntry::factory()->toMember(),
MemberEntry::factory()->toMember(),
]);
Auth::success(333, 'secret');
$response = $this->postJson('/nami/search', [
'params' => [
'gruppierung1Id' => 100,
'gruppierung2Id' => 101,
],
'page' => 2,
'mglnr' => 333,
'password' => 'secret',
]);
$response->assertOk();
app(SearchFake::class)->assertFetched(2, 10, 10, [
'gruppierung1Id' => 100,
'gruppierung2Id' => 101,
]);
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Tests\Feature\Initializer;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Authentication\Auth;
class ValidateLoginTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
$this->login();
}
public function testItValidatesLogin(): void
{
Auth::success(333, 'secret');
$this->postJson('/nami/login-check', [
'mglnr' => 333,
'password' => 'secret',
])->assertStatus(204);
}
public function testItNeedsPasswordAndMglnr(): void
{
$this->postJson('/nami/login-check', [
'mglnr' => '',
'password' => '',
])->assertJsonValidationErrors(['mglnr', 'password']);
}
public function testMglnrShouldBeNumeric(): void
{
$this->postJson('/nami/login-check', [
'mglnr' => 'aaa',
'password' => 'secret',
])->assertJsonValidationErrors(['mglnr']);
}
public function testLoginCanFail(): void
{
$this->postJson('/nami/login-check', [
'mglnr' => '111',
'password' => 'secret',
])->assertJsonValidationErrors(['nami' => 'NaMi Login fehlgeschlagen.']);
}
}

View File

@ -9,9 +9,16 @@ class InitializeRequestFactory extends RequestFactory
public function definition(): array
{
return [
'group_id' => $this->faker->numberBetween(100, 200),
'group_id' => (string) $this->faker->numberBetween(100, 200),
'password' => $this->faker->word(),
'mglnr' => $this->faker->numberBetween(100, 200),
'mglnr' => (string) $this->faker->numberBetween(100, 200),
'params' => [
'gruppierung1Id' => $this->faker->numberBetween(100000, 200000),
'gruppierung2Id' => $this->faker->numberBetween(100000, 200000),
'gruppierung3Id' => $this->faker->numberBetween(100000, 200000),
'inGrp' => $this->faker->boolean(),
'unterhalbGrp' => $this->faker->boolean(),
],
];
}
@ -32,6 +39,16 @@ class InitializeRequestFactory extends RequestFactory
]);
}
/**
* @param array<string, string|int|bool> $params
*/
public function withParams(array $params): self
{
return $this->state([
'params' => $params,
]);
}
public function withGroup(int $group): self
{
return $this->state([